diff --git a/docker/client.go b/docker/client.go new file mode 100644 index 0000000..98936fe --- /dev/null +++ b/docker/client.go @@ -0,0 +1,166 @@ +package docker + +import ( + "context" + "fmt" + "net" + "strings" + "time" + + "github.com/docker/docker/api/types/container" + "github.com/docker/docker/client" + "github.com/fosrl/newt/logger" +) + +// Container represents a Docker container +type Container struct { + ID string `json:"id"` + Name string `json:"name"` + Image string `json:"image"` + State string `json:"state"` + Status string `json:"status"` + Ports []Port `json:"ports"` + Labels map[string]string `json:"labels"` + Created int64 `json:"created"` + Networks map[string]Network `json:"networks"` +} + +// Port represents a port mapping for a Docker container +type Port struct { + PrivatePort int `json:"privatePort"` + PublicPort int `json:"publicPort,omitempty"` + Type string `json:"type"` + IP string `json:"ip,omitempty"` +} + +// Network represents network information for a Docker container +type Network struct { + NetworkID string `json:"networkId"` + EndpointID string `json:"endpointId"` + Gateway string `json:"gateway,omitempty"` + IPAddress string `json:"ipAddress,omitempty"` + IPPrefixLen int `json:"ipPrefixLen,omitempty"` + IPv6Gateway string `json:"ipv6Gateway,omitempty"` + GlobalIPv6Address string `json:"globalIPv6Address,omitempty"` + GlobalIPv6PrefixLen int `json:"globalIPv6PrefixLen,omitempty"` + MacAddress string `json:"macAddress,omitempty"` + Aliases []string `json:"aliases,omitempty"` + DNSNames []string `json:"dnsNames,omitempty"` +} + +// CheckSocket checks if Docker socket is available +func CheckSocket(socketPath string) bool { + // Use the provided socket path or default to standard location + if socketPath == "" { + socketPath = "/var/run/docker.sock" + } + + // Try to create a connection to the Docker socket + conn, err := net.Dial("unix", socketPath) + if err != nil { + logger.Debug("Docker socket not available at %s: %v", socketPath, err) + return false + } + defer conn.Close() + + logger.Debug("Docker socket is available at %s", socketPath) + return true +} + +// ListContainers lists all Docker containers with their network information +func ListContainers(socketPath string) ([]Container, error) { + // Use the provided socket path or default to standard location + if socketPath == "" { + socketPath = "/var/run/docker.sock" + } + + // Create a new Docker client + ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second) + defer cancel() + + // Create client with custom socket path + cli, err := client.NewClientWithOpts( + client.WithHost("unix://"+socketPath), + client.WithAPIVersionNegotiation(), + ) + if err != nil { + return nil, fmt.Errorf("failed to create Docker client: %v", err) + } + defer cli.Close() + + // List containers + containers, err := cli.ContainerList(ctx, container.ListOptions{All: true}) + if err != nil { + return nil, fmt.Errorf("failed to list containers: %v", err) + } + + var dockerContainers []Container + for _, c := range containers { + // Convert ports + var ports []Port + for _, port := range c.Ports { + dockerPort := Port{ + PrivatePort: int(port.PrivatePort), + Type: port.Type, + } + if port.PublicPort != 0 { + dockerPort.PublicPort = int(port.PublicPort) + } + if port.IP != "" { + dockerPort.IP = port.IP + } + ports = append(ports, dockerPort) + } + + // Get container name (remove leading slash) + name := "" + if len(c.Names) > 0 { + name = strings.TrimPrefix(c.Names[0], "/") + } + + // Get network information by inspecting the container + networks := make(map[string]Network) + + // Inspect container to get detailed network information + containerInfo, err := cli.ContainerInspect(ctx, c.ID) + if err != nil { + logger.Debug("Failed to inspect container %s for network info: %v", c.ID[:12], err) + // Continue without network info if inspection fails + } else { + // Extract network information from inspection + if containerInfo.NetworkSettings != nil && containerInfo.NetworkSettings.Networks != nil { + for networkName, endpoint := range containerInfo.NetworkSettings.Networks { + dockerNetwork := Network{ + NetworkID: endpoint.NetworkID, + EndpointID: endpoint.EndpointID, + Gateway: endpoint.Gateway, + IPAddress: endpoint.IPAddress, + IPPrefixLen: endpoint.IPPrefixLen, + IPv6Gateway: endpoint.IPv6Gateway, + GlobalIPv6Address: endpoint.GlobalIPv6Address, + GlobalIPv6PrefixLen: endpoint.GlobalIPv6PrefixLen, + MacAddress: endpoint.MacAddress, + Aliases: endpoint.Aliases, + DNSNames: endpoint.DNSNames, + } + networks[networkName] = dockerNetwork + } + } + } + + dockerContainer := Container{ + ID: c.ID[:12], // Show short ID like docker ps + Name: name, + Image: c.Image, + State: c.State, + Status: c.Status, + Ports: ports, + Labels: c.Labels, + Created: c.Created, + Networks: networks, + } + dockerContainers = append(dockerContainers, dockerContainer) + } + + return dockerContainers, nil +} diff --git a/main.go b/main.go index 24f17e3..c5dd080 100644 --- a/main.go +++ b/main.go @@ -18,6 +18,7 @@ import ( "syscall" "time" + "github.com/fosrl/newt/docker" "github.com/fosrl/newt/logger" "github.com/fosrl/newt/proxy" "github.com/fosrl/newt/websocket" @@ -55,7 +56,7 @@ func fixKey(key string) string { // Decode from base64 decoded, err := base64.StdEncoding.DecodeString(key) if err != nil { - logger.Fatal("Error decoding base64:", err) + logger.Fatal("Error decoding base64: %v", err) } // Convert to hex @@ -194,7 +195,7 @@ func monitorConnectionStatus(tnet *netstack.Net, serverIP string, client *websoc // Tell the server we're back err := client.SendMessage("newt/wg/register", map[string]interface{}{ - "publicKey": fmt.Sprintf("%s", privateKey.PublicKey()), + "publicKey": privateKey.PublicKey().String(), }) if err != nil { @@ -351,6 +352,7 @@ var ( logLevel string updownScript string tlsPrivateKey string + dockerSocket string ) func main() { @@ -363,6 +365,7 @@ func main() { logLevel = os.Getenv("LOG_LEVEL") updownScript = os.Getenv("UPDOWN_SCRIPT") tlsPrivateKey = os.Getenv("TLS_CLIENT_CERT") + dockerSocket = os.Getenv("DOCKER_SOCKET") if endpoint == "" { flag.StringVar(&endpoint, "endpoint", "", "Endpoint of your pangolin server") @@ -388,6 +391,9 @@ func main() { if tlsPrivateKey == "" { flag.StringVar(&tlsPrivateKey, "tls-client-cert", "", "Path to client certificate used for mTLS") } + if dockerSocket == "" { + flag.StringVar(&dockerSocket, "docker-socket", "/var/run/docker.sock", "Path to Docker socket") + } // do a --version check version := flag.Bool("version", false, "Print the version") @@ -498,7 +504,7 @@ func main() { public_key=%s allowed_ip=%s/32 endpoint=%s -persistent_keepalive_interval=5`, fixKey(fmt.Sprintf("%s", privateKey)), fixKey(wgData.PublicKey), wgData.ServerIP, endpoint) +persistent_keepalive_interval=5`, fixKey(privateKey.String()), fixKey(wgData.PublicKey), wgData.ServerIP, endpoint) err = dev.IpcSet(config) if err != nil { @@ -626,12 +632,53 @@ persistent_keepalive_interval=5`, fixKey(fmt.Sprintf("%s", privateKey)), fixKey( } }) + // Register handler for Docker socket check + client.RegisterHandler("newt/socket/check", func(msg websocket.WSMessage) { + logger.Info("Received Docker socket check request") + + // Check if Docker socket is available + isAvailable := docker.CheckSocket(dockerSocket) + + // Send response back to server + err := client.SendMessage("newt/socket/status", map[string]interface{}{ + "available": isAvailable, + "socketPath": dockerSocket, + }) + if err != nil { + logger.Error("Failed to send Docker socket check response: %v", err) + } else { + logger.Info("Docker socket check response sent: available=%t", isAvailable) + } + }) + + // Register handler for Docker container listing + client.RegisterHandler("newt/socket/fetch", func(msg websocket.WSMessage) { + logger.Info("Received Docker container fetch request") + + // List Docker containers + containers, err := docker.ListContainers(dockerSocket) + if err != nil { + logger.Error("Failed to list Docker containers: %v", err) + return + } + + // Send container list back to server + err = client.SendMessage("newt/socket/containers", map[string]interface{}{ + "containers": containers, + }) + if err != nil { + logger.Error("Failed to send Docker container list: %v", err) + } else { + logger.Info("Docker container list sent, count: %d", len(containers)) + } + }) + client.OnConnect(func() error { publicKey := privateKey.PublicKey() logger.Debug("Public key: %s", publicKey) err := client.SendMessage("newt/wg/register", map[string]interface{}{ - "publicKey": fmt.Sprintf("%s", publicKey), + "publicKey": publicKey.String(), }) if err != nil { logger.Error("Failed to send registration message: %v", err)