package main import ( "bytes" "errors" "flag" "fmt" "github.com/docker/docker/api/types" "github.com/docker/docker/api/types/container" "github.com/docker/docker/api/types/network" "github.com/docker/go-connections/nat" "net" "net/http" "os" "time" ) const( vpnHostNetworkName = "vpnhostnet" vpnContainerName = "circus-vpn" ) var vpnContainerID string var vpnNetworkID string var vpnHostNetworkID string var remoteAddress* string var remotePort* int func registerAccessFlags() { remoteAddress = flag.String("vpnRemoteAddress", "", "The remote domain name or IP the VPN will run on") remotePort = flag.Int("vpnRemotePort", 1194, "The port the VPN should listen on") } func startVPN() (err error) { // Set up our context and Docker CLI connection setupContext() setupDockerCLI() // Set up network err = setupNetwork() if(err != nil) { return err } err = setupVPNHostNetwork() if err != nil { return err } // Get subnet of challenge container network, to hand it over to our VPN container for routes inspectResp, err := dockerCli.NetworkInspect(dockerCtx, vpnNetworkID, types.NetworkInspectOptions{}) if err != nil { return err } // Parse subnet (in CIDR notation) _, ipnet, err := net.ParseCIDR(inspectResp.IPAM.Config[0].Subnet) if err != nil { return err } // Create VPN container resp, err := dockerCli.ContainerCreate(dockerCtx, &container.Config{ Image: vpnContainerName, Env: []string{ fmt.Sprintf("remoteAddress=%s", *remoteAddress), fmt.Sprintf("remotePort=%d", *remotePort), fmt.Sprintf("subnet=%s", ipnet.IP.String()), fmt.Sprintf("subnetMask=%d.%d.%d.%d", ipnet.Mask[0], ipnet.Mask[1], ipnet.Mask[2], ipnet.Mask[3]), }, ExposedPorts: map[nat.Port]struct{}{ "1194/udp": {}, }, }, &container.HostConfig{ Privileged: true, PortBindings: nat.PortMap{ "1194/udp": []nat.PortBinding{ { HostIP: "0.0.0.0", HostPort: "1194", }, }, }, }, &network.NetworkingConfig{ EndpointsConfig: map[string]*network.EndpointSettings{ "startpoint": { NetworkID: vpnHostNetworkID, }, }, }, "") if err != nil { return err } // Attach container network to VPN container err = dockerCli.NetworkConnect(dockerCtx, vpnNetworkID, resp.ID, &network.EndpointSettings{}) if err != nil { return err } // Start container err = dockerCli.ContainerStart(dockerCtx, resp.ID, types.ContainerStartOptions{}) if err != nil { return err } // We now need to do a little stunt. If the companion is started inside a container, it's not possible to dial to port 9999 of the VPN container. // However, getCertificate() requires that port 9999 of the VPN container hosts the configuration files for our client. // That means we need to attach our own container - thanks to --privileged mode - into the VPN container network. // We get the ID of our container from the "hostname" environment variable. That's a bit dirty, but works for the moment. TODO: solve this better. err = dockerCli.NetworkConnect(dockerCtx, vpnNetworkID, os.Getenv("HOSTNAME"), &network.EndpointSettings{}) if err != nil { return err } vpnContainerID = resp.ID return nil } func stopVPN() { setupContext() setupDockerCLI() timeout := time.Second * 5 dockerCli.ContainerStop(dockerCtx, vpnContainerID, &timeout) vpnContainerID = "" } func setupNetwork() (error) { setupContext() setupDockerCLI() if vpnNetworkID == "" { response, err := dockerCli.NetworkCreate(dockerCtx, VPNNetworkName, types.NetworkCreate{ Internal: true, }) if err != nil { return err } vpnNetworkID = response.ID } return nil } func setupVPNHostNetwork() (error) { setupContext() setupDockerCLI() if vpnHostNetworkID == "" { response, err := dockerCli.NetworkCreate(dockerCtx, vpnHostNetworkName, types.NetworkCreate{ Internal: false, }) if err != nil { return err } vpnHostNetworkID = response.ID } return nil } func getCertificate() (string, error) { if vpnContainerID == "" { return "", errors.New("VPN container not up") } // Get IP of VPN container inspectJSON, err := dockerCli.ContainerInspect(dockerCtx, vpnContainerID) if err != nil { return "", err } // get certificate var certResponse *http.Response for i := 0; i < 10; i++ { certResponse, err = http.Get(fmt.Sprintf("http://%s:9999/", inspectJSON.NetworkSettings.Networks[VPNNetworkName].IPAddress)) if err == nil { break } time.Sleep(time.Second) } if err != nil { return "", err } buffer := make([]byte, 1024) certResponse.Body.Read(buffer) return string(bytes.Trim(buffer, "\x00")), nil }