From a4d49761033969cf2288b831bdcc5bbdefc2f420 Mon Sep 17 00:00:00 2001 From: Jonny Booker <1131478+JonnyBooker@users.noreply.github.com> Date: Mon, 9 Jun 2025 22:54:10 +0100 Subject: [PATCH] Update to use docker network checking against newt networking --- docker/client.go | 99 +++++++++++++++++++++++++++++++++++++++++++----- main.go | 57 ++++++++++++++++++---------- 2 files changed, 128 insertions(+), 28 deletions(-) diff --git a/docker/client.go b/docker/client.go index 98936fe..762ce28 100644 --- a/docker/client.go +++ b/docker/client.go @@ -4,6 +4,8 @@ import ( "context" "fmt" "net" + "os" + "strconv" "strings" "time" @@ -67,8 +69,44 @@ func CheckSocket(socketPath string) bool { return true } +// IsWithinNewtNetwork checks if a provided target is within the newt network +func IsWithinNewtNetwork(socketPath string, containerNameAsHostname bool, targetAddress string, targetPort int) (bool, error) { + containers, err := ListContainers(socketPath, containerNameAsHostname) + if err != nil { + return false, fmt.Errorf("failed to list Docker containers: %s", err) + } + + // If we can find the passed hostname/ip in the networks or as the container name, it is valid and can add it + for _, c := range containers { + for _, network := range c.Networks { + //If the container name matches, check the ports being mapped too + if containerNameAsHostname { + if c.Name == targetAddress { + for _, port := range c.Ports { + if port.PublicPort == targetPort || port.PrivatePort == targetPort { + return true, nil + } + } + } + } else { + //If the ip address matches, check the ports being mapped too + if network.IPAddress == targetAddress { + for _, port := range c.Ports { + if port.PublicPort == targetPort || port.PrivatePort == targetPort { + return true, nil + } + } + } + } + } + } + + combinedTargetAddress := targetAddress + ":" + strconv.Itoa(targetPort) + return false, fmt.Errorf("target address not within newt network: %s", combinedTargetAddress) +} + // ListContainers lists all Docker containers with their network information -func ListContainers(socketPath string) ([]Container, error) { +func ListContainers(socketPath string, containerNameAsHostname bool) ([]Container, error) { // Use the provided socket path or default to standard location if socketPath == "" { socketPath = "/var/run/docker.sock" @@ -88,6 +126,12 @@ func ListContainers(socketPath string) ([]Container, error) { } defer cli.Close() + // Get the newt container + newtContainer, err := getNewtContainer(ctx, cli) + if err != nil { + return nil, fmt.Errorf("failed to list containers: %v", err) + } + // List containers containers, err := cli.ContainerList(ctx, container.ListOptions{All: true}) if err != nil { @@ -96,6 +140,12 @@ func ListContainers(socketPath string) ([]Container, error) { var dockerContainers []Container for _, c := range containers { + // Get container name (remove leading slash) + name := "" + if len(c.Names) > 0 { + name = strings.TrimPrefix(c.Names[0], "/") + } + // Convert ports var ports []Port for _, port := range c.Ports { @@ -112,29 +162,32 @@ func ListContainers(socketPath string) ([]Container, error) { 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 + // Inspect the 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 { + // Only containers within the newt network will be returned + isInNewtNetwork := false + // Extract network information from inspection if containerInfo.NetworkSettings != nil && containerInfo.NetworkSettings.Networks != nil { for networkName, endpoint := range containerInfo.NetworkSettings.Networks { + // Determine if the current container is in the newt network + for _, newtNetwork := range newtContainer.NetworkSettings.Networks { + if !isInNewtNetwork { + isInNewtNetwork = endpoint.NetworkID == newtNetwork.NetworkID + } + } + dockerNetwork := Network{ NetworkID: endpoint.NetworkID, EndpointID: endpoint.EndpointID, Gateway: endpoint.Gateway, - IPAddress: endpoint.IPAddress, IPPrefixLen: endpoint.IPPrefixLen, IPv6Gateway: endpoint.IPv6Gateway, GlobalIPv6Address: endpoint.GlobalIPv6Address, @@ -143,9 +196,21 @@ func ListContainers(socketPath string) ([]Container, error) { Aliases: endpoint.Aliases, DNSNames: endpoint.DNSNames, } + + // Don't set the IP address if container name is used as hostname + if !containerNameAsHostname { + dockerNetwork.IPAddress = endpoint.IPAddress + } + networks[networkName] = dockerNetwork } } + + // Don't continue returning this container if not in the newt network(s) + if !isInNewtNetwork { + logger.Debug("container not found within the newt network, skipping: %s", name) + continue + } } dockerContainer := Container{ @@ -164,3 +229,19 @@ func ListContainers(socketPath string) ([]Container, error) { return dockerContainers, nil } + +func getNewtContainer(dockerContext context.Context, dockerClient *client.Client) (*container.InspectResponse, error) { + // Get newt hostname from the os + newtContainerName, err := os.Hostname() + if err != nil { + return nil, fmt.Errorf("failed to find newt hostname: %v", err) + } + + // Get newt container from the docker socket + newtContainer, err := dockerClient.ContainerInspect(dockerContext, newtContainerName) + if err != nil { + return nil, fmt.Errorf("failed to find newt container: %v", err) + } + + return &newtContainer, nil +} \ No newline at end of file diff --git a/main.go b/main.go index fdece97..7cfb002 100644 --- a/main.go +++ b/main.go @@ -341,18 +341,20 @@ func resolveDomain(domain string) (string, error) { } var ( - endpoint string - id string - secret string - mtu string - mtuInt int - dns string - privateKey wgtypes.Key - err error - logLevel string - updownScript string - tlsPrivateKey string - dockerSocket string + endpoint string + id string + secret string + mtu string + mtuInt int + dns string + privateKey wgtypes.Key + err error + logLevel string + updownScript string + tlsPrivateKey string + dockerSocket string + dockerContainerAsHostname string + dockerContainerAsHostnameBool bool ) func main() { @@ -366,6 +368,7 @@ func main() { updownScript = os.Getenv("UPDOWN_SCRIPT") tlsPrivateKey = os.Getenv("TLS_CLIENT_CERT") dockerSocket = os.Getenv("DOCKER_SOCKET") + dockerContainerAsHostname = os.Getenv("DOCKER_CONTAINER_NAME_AS_HOSTNAME") if endpoint == "" { flag.StringVar(&endpoint, "endpoint", "", "Endpoint of your pangolin server") @@ -394,6 +397,9 @@ func main() { if dockerSocket == "" { flag.StringVar(&dockerSocket, "docker-socket", "", "Path to Docker socket (typically /var/run/docker.sock)") } + if dockerContainerAsHostname == "" { + flag.StringVar(&dockerContainerAsHostname, "docker-container-name-as-hostname", "false", "Use container name when hostname for networking (true or false)") + } // do a --version check version := flag.Bool("version", false, "Print the version") @@ -418,6 +424,13 @@ func main() { logger.Fatal("Failed to parse MTU: %v", err) } + // pase if to use hostname over ip address for network sent to pangolin + dockerContainerAsHostnameBool, err = strconv.ParseBool(dockerContainerAsHostname) + if err != nil { + logger.Info("Docker use container name cannot be parsed. Defaulting to 'false'") + dockerContainerAsHostnameBool = false + } + privateKey, err = wgtypes.GeneratePrivateKey() if err != nil { logger.Fatal("Failed to generate private key: %v", err) @@ -676,7 +689,7 @@ persistent_keepalive_interval=5`, fixKey(privateKey.String()), fixKey(wgData.Pub } // List Docker containers - containers, err := docker.ListContainers(dockerSocket) + containers, err := docker.ListContainers(dockerSocket, dockerContainerAsHostnameBool) if err != nil { logger.Error("Failed to list Docker containers: %v", err) return @@ -760,12 +773,14 @@ func updateTargets(pm *proxy.ProxyManager, action string, tunnelIP string, proto } if action == "add" { - target := parts[1] + ":" + parts[2] + targetAddress := parts[1] + targetPort, _ := strconv.Atoi(parts[2]) + combinedAddress := targetAddress + ":" + parts[2] // Call updown script if provided - processedTarget := target + processedTarget := combinedAddress if updownScript != "" { - newTarget, err := executeUpdownScript(action, proto, target) + newTarget, err := executeUpdownScript(action, proto, combinedAddress) if err != nil { logger.Warn("Updown script error: %v", err) } else if newTarget != "" { @@ -783,8 +798,12 @@ func updateTargets(pm *proxy.ProxyManager, action string, tunnelIP string, proto } // Add the new target - pm.AddTarget(proto, tunnelIP, port, processedTarget) - + isWithinNewtNetwork, err := docker.IsWithinNewtNetwork(dockerSocket, dockerContainerAsHostnameBool, targetAddress, targetPort) + if !isWithinNewtNetwork { + logger.Error("Not adding target: %v", err) + } else { + pm.AddTarget(proto, tunnelIP, port, processedTarget) + } } else if action == "remove" { logger.Info("Removing target with port %d", port) @@ -849,4 +868,4 @@ func executeUpdownScript(action, proto, target string) (string, error) { } return target, nil -} +} \ No newline at end of file