From 8b65f91699cd474563c0abacc726a3d47961a78f Mon Sep 17 00:00:00 2001 From: maride Date: Thu, 23 Aug 2018 11:46:23 +0200 Subject: Add VPN container and access --- src/access.go | 111 +++++++++++++++++++++++++++++++++++++++++++++++++++++++ src/container.go | 40 +++++++++----------- src/docker.go | 25 +++++++++++++ src/http.go | 39 +++++++++++++++++++ src/main.go | 4 ++ 5 files changed, 197 insertions(+), 22 deletions(-) create mode 100644 src/access.go create mode 100644 src/docker.go (limited to 'src') diff --git a/src/access.go b/src/access.go new file mode 100644 index 0000000..6072025 --- /dev/null +++ b/src/access.go @@ -0,0 +1,111 @@ +package main + +import ( + "github.com/docker/docker/api/types/container" + "github.com/docker/docker/api/types" + "time" + "errors" + "net/http" + "fmt" + "github.com/docker/docker/api/types/network" +) + +var vpnContainerID string +var vpnNetworkID string + +func startVPN() (err error) { + // Set up our context and Docker CLI connection + setupContext() + setupDockerCLI() + // Set up network + setupNetwork() + + // Create container + resp, err := dockerCli.ContainerCreate(dockerCtx, &container.Config{ + Image: "circus-vpn", + }, &container.HostConfig{ + Privileged: true, + }, &network.NetworkingConfig{ + EndpointsConfig: map[string]*network.EndpointSettings{ + "endpoint": { + NetworkID: vpnNetworkID, + }, + }, + }, "") + + if err != nil { + return err + } + + // Start container + err = dockerCli.ContainerStart(dockerCtx, resp.ID, types.ContainerStartOptions{}) + 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 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(buffer), nil +} diff --git a/src/container.go b/src/container.go index 73912bf..5b2075b 100644 --- a/src/container.go +++ b/src/container.go @@ -1,12 +1,15 @@ package main import ( - "context" - "github.com/docker/docker/client" "github.com/docker/docker/api/types/container" "github.com/docker/docker/api/types" "fmt" "time" + "github.com/docker/docker/api/types/network" +) + +const ( + VPNNetworkName = "vpn-network" ) type ChallengeContainer struct { @@ -15,33 +18,26 @@ type ChallengeContainer struct { IP string } -var ( - dockerCtx context.Context - dockerCli *client.Client -) - // Starts the container and returns its address and containerID if successful func (cc ChallengeContainer) startContainer() (address string, containerID string, err error) { - // Set up our context if there is none already set up - if dockerCtx == nil { - dockerCtx = context.Background() - } - - // Set up our Docker CLI connection if there is not already one - if dockerCli == nil { - dockerCli, err = client.NewEnvClient() - - if err != nil { - return "", "", err - } - } + // Set up our context and Docker CLI connection + setupContext() + setupDockerCLI() + // Set up network + setupNetwork() // Create container resp, err := dockerCli.ContainerCreate(dockerCtx, &container.Config{ Image: cc.Challenge.Container, Env: []string{fmt.Sprintf("FLAG=%s", cc.Challenge.Flag)}, Tty: false, - }, nil, nil, "") + }, nil, &network.NetworkingConfig{ + EndpointsConfig: map[string]*network.EndpointSettings{ + VPNNetworkName: { + NetworkID: vpnNetworkID, + }, + }, + }, "") if err != nil { return "", "", err @@ -60,7 +56,7 @@ func (cc ChallengeContainer) startContainer() (address string, containerID strin } // Return IP, Container ID and error - return inspectJSON.NetworkSettings.IPAddress, resp.ID,nil + return inspectJSON.NetworkSettings.Networks[VPNNetworkName].IPAddress, resp.ID,nil } // Stops the container with a timeout of one second diff --git a/src/docker.go b/src/docker.go new file mode 100644 index 0000000..9bc667b --- /dev/null +++ b/src/docker.go @@ -0,0 +1,25 @@ +package main + +import ( + "github.com/docker/docker/client" + "context" +) + +var ( + dockerCtx context.Context + dockerCli *client.Client +) + +func setupContext() { + if dockerCtx == nil { + dockerCtx = context.Background() + } +} + +func setupDockerCLI() (err error) { + if dockerCli == nil { + dockerCli, err = client.NewEnvClient() + } + + return err +} \ No newline at end of file diff --git a/src/http.go b/src/http.go index 287004f..c7ad213 100644 --- a/src/http.go +++ b/src/http.go @@ -30,10 +30,12 @@ func runHTTPServer() (error) { r.HandleFunc("/login", loginPostHandler).Methods("POST") r.HandleFunc("/logout", logoutHandler).Methods("POST") r.HandleFunc("/challenges", challengesHandler).Methods("GET") + r.HandleFunc("/access", accessHandler).Methods("GET") r.HandleFunc("/api/getChallenges", getChallengesHandler).Methods("GET") r.HandleFunc("/api/submitFlag", submitFlagHandler).Methods("POST") r.HandleFunc("/api/startContainer", startContainerHandler).Methods("POST") r.HandleFunc("/api/stopContainer", stopContainerHandler).Methods("POST") + r.HandleFunc("/api/getAccess", getAccessHandler).Methods("GET") address := fmt.Sprintf(":%d", *port) return http.ListenAndServe(address, r) @@ -163,6 +165,19 @@ func challengesHandler(w http.ResponseWriter, r *http.Request) { } } +// Host the access file +func accessHandler(w http.ResponseWriter, r *http.Request) { + session, cookieNotFoundError := r.Cookie("session") + + if cookieNotFoundError != nil || !isValidSession(session.Value) { + // either no session cookie found, or it contains an invalid session token. Redirect. + http.Redirect(w, r, "/login", http.StatusTemporaryRedirect) + } else { + // valid session token found, redirect to frontpage + readFileToResponse(w, "/access.html") + } +} + func getChallengesHandler(w http.ResponseWriter, r *http.Request) { session, cookieNotFoundError := r.Cookie("session") @@ -282,3 +297,27 @@ func stopContainerHandler(w http.ResponseWriter, r *http.Request) { stopChallengeContainer(challengeName) } } + +// Returns the configuration for the VPN +func getAccessHandler(w http.ResponseWriter, r *http.Request) { + session, cookieNotFoundError := r.Cookie("session") + + if cookieNotFoundError != nil || !isValidSession(session.Value) { + // either no session cookie found, or it contains an invalid session token. Redirect. + http.Redirect(w, r, "/login", http.StatusTemporaryRedirect) + } else { + // valid session token found, get credentials + credentials, err := getCertificate() + errorString := "" + + if err != nil { + errorString = err.Error() + } + + jsonAnswer, _ := json.Marshal(map[string]string{ + "error": errorString, + "credentials": credentials, + }) + w.Write([]byte(jsonAnswer)) + } +} diff --git a/src/main.go b/src/main.go index 9885957..a06ee66 100644 --- a/src/main.go +++ b/src/main.go @@ -16,6 +16,10 @@ func main() { // Read challenges from file getChallengesFromSeedFile() + // Start our VPN container and network + startVPN() + defer stopVPN() + // Run HTTP server log.Fatalln(runHTTPServer()) } \ No newline at end of file -- cgit 1.4.1 From 65d1f311e1679d0d61b4be9162fdab23359c2403 Mon Sep 17 00:00:00 2001 From: maride Date: Thu, 23 Aug 2018 12:10:35 +0200 Subject: Add port forwarding for VPN, print remote address/port into env vars of VPN container --- README.md | 2 ++ src/access.go | 15 +++++++++++++++ src/main.go | 1 + 3 files changed, 18 insertions(+) (limited to 'src') diff --git a/README.md b/README.md index 89b2017..bef50bc 100644 --- a/README.md +++ b/README.md @@ -19,6 +19,8 @@ This executable needs some parameters to work properly: | `-accessCode` | Yes | Access code for the user. *Default: AllYourCodesAreBelongToUs* | | `-sessionSalt` | Yes | Variable to salt the session token generator with. | | `-seedFile` | Yes | JSON file to read challenge information from. | +| `-vpnRemoteAddress` | Yes | Address the VPN will run on, as rendered into the client VPN configuration file. | +| `-vpnRemotePort` | No | Port the VPN will run on | ## Seed file diff --git a/src/access.go b/src/access.go index 6072025..ddb032d 100644 --- a/src/access.go +++ b/src/access.go @@ -8,10 +8,18 @@ import ( "net/http" "fmt" "github.com/docker/docker/api/types/network" + "flag" ) var vpnContainerID string var vpnNetworkID 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 @@ -23,12 +31,19 @@ func startVPN() (err error) { // Create container resp, err := dockerCli.ContainerCreate(dockerCtx, &container.Config{ Image: "circus-vpn", + Env: []string{ + fmt.Sprintf("remoteAddress=%s", *remoteAddress), + fmt.Sprintf("remotePort=%d", *remotePort), + }, }, &container.HostConfig{ Privileged: true, }, &network.NetworkingConfig{ EndpointsConfig: map[string]*network.EndpointSettings{ "endpoint": { NetworkID: vpnNetworkID, + Links: []string{ + fmt.Sprintf("%d:1194/tcp", *remotePort), + }, }, }, }, "") diff --git a/src/main.go b/src/main.go index a06ee66..ae50632 100644 --- a/src/main.go +++ b/src/main.go @@ -11,6 +11,7 @@ func main() { registerSessionFlags() registerCredentialsFlags() registerSeedFlags() + registerAccessFlags() flag.Parse() // Read challenges from file -- cgit 1.4.1 From a2540513db864492ff89f9ef8622a7d4bbec1880 Mon Sep 17 00:00:00 2001 From: maride Date: Sun, 2 Sep 2018 01:49:00 +0200 Subject: Correctly link port to container, specify IP range --- src/access.go | 78 ++++++++++++++++++++++++++++++++++++++++++++++++++--------- 1 file changed, 66 insertions(+), 12 deletions(-) (limited to 'src') diff --git a/src/access.go b/src/access.go index ddb032d..34b2470 100644 --- a/src/access.go +++ b/src/access.go @@ -1,18 +1,21 @@ package main import ( - "github.com/docker/docker/api/types/container" - "github.com/docker/docker/api/types" - "time" + "bytes" "errors" - "net/http" + "flag" "fmt" + "github.com/docker/docker/api/types" + "github.com/docker/docker/api/types/container" "github.com/docker/docker/api/types/network" - "flag" + "github.com/docker/go-connections/nat" + "net/http" + "time" ) var vpnContainerID string var vpnNetworkID string +var vpnHostNetworkID string var remoteAddress* string var remotePort* int @@ -26,7 +29,17 @@ func startVPN() (err error) { setupContext() setupDockerCLI() // Set up network - setupNetwork() + err = setupNetwork() + + if(err != nil) { + return err + } + + err = setupVPNHostNetwork() + + if err != nil { + return err + } // Create container resp, err := dockerCli.ContainerCreate(dockerCtx, &container.Config{ @@ -35,15 +48,23 @@ func startVPN() (err error) { fmt.Sprintf("remoteAddress=%s", *remoteAddress), fmt.Sprintf("remotePort=%d", *remotePort), }, + 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{ - "endpoint": { - NetworkID: vpnNetworkID, - Links: []string{ - fmt.Sprintf("%d:1194/tcp", *remotePort), - }, + "startpoint": { + NetworkID: vpnHostNetworkID, }, }, }, "") @@ -52,6 +73,12 @@ func startVPN() (err error) { 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 { @@ -80,6 +107,14 @@ func setupNetwork() (error) { if vpnNetworkID == "" { response, err := dockerCli.NetworkCreate(dockerCtx, VPNNetworkName, types.NetworkCreate{ Internal: true, + IPAM: &network.IPAM{ + Config: []network.IPAMConfig{ + { + Subnet: "10.13.37.0/24", + Gateway: "10.13.37.254", + }, + }, + }, }) if err != nil { @@ -92,6 +127,25 @@ func setupNetwork() (error) { return nil } +func setupVPNHostNetwork() (error) { + setupContext() + setupDockerCLI() + + if vpnHostNetworkID == "" { + response, err := dockerCli.NetworkCreate(dockerCtx, "vpnhostnet", 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") @@ -122,5 +176,5 @@ func getCertificate() (string, error) { buffer := make([]byte, 1024) certResponse.Body.Read(buffer) - return string(buffer), nil + return string(bytes.Trim(buffer, "\x00")), nil } -- cgit 1.4.1 From cbb1ab24a40b5e84adcd44a4d0d4d7fdd9f49f5f Mon Sep 17 00:00:00 2001 From: maride Date: Sun, 2 Sep 2018 01:49:26 +0200 Subject: Abort on errors --- src/container.go | 14 +++++++++----- 1 file changed, 9 insertions(+), 5 deletions(-) (limited to 'src') diff --git a/src/container.go b/src/container.go index 5b2075b..c9a918f 100644 --- a/src/container.go +++ b/src/container.go @@ -1,15 +1,15 @@ package main import ( - "github.com/docker/docker/api/types/container" - "github.com/docker/docker/api/types" "fmt" - "time" + "github.com/docker/docker/api/types" + "github.com/docker/docker/api/types/container" "github.com/docker/docker/api/types/network" + "time" ) const ( - VPNNetworkName = "vpn-network" + VPNNetworkName = "circus-vpnnet" ) type ChallengeContainer struct { @@ -24,7 +24,11 @@ func (cc ChallengeContainer) startContainer() (address string, containerID strin setupContext() setupDockerCLI() // Set up network - setupNetwork() + err = setupNetwork() + + if err != nil { + return "", "", err + } // Create container resp, err := dockerCli.ContainerCreate(dockerCtx, &container.Config{ -- cgit 1.4.1 From 939ef7db9b3f209c4fa521a8d6ef4231ee397760 Mon Sep 17 00:00:00 2001 From: maride Date: Tue, 4 Sep 2018 11:07:18 +0200 Subject: Un-hardcode network name, remove fixed IP block to avoid collision on the host system --- src/access.go | 14 +++++--------- 1 file changed, 5 insertions(+), 9 deletions(-) (limited to 'src') diff --git a/src/access.go b/src/access.go index 34b2470..6f9cd73 100644 --- a/src/access.go +++ b/src/access.go @@ -13,6 +13,10 @@ import ( "time" ) +const( + vpnHostNetworkName = "vpnhostnet" +) + var vpnContainerID string var vpnNetworkID string var vpnHostNetworkID string @@ -107,14 +111,6 @@ func setupNetwork() (error) { if vpnNetworkID == "" { response, err := dockerCli.NetworkCreate(dockerCtx, VPNNetworkName, types.NetworkCreate{ Internal: true, - IPAM: &network.IPAM{ - Config: []network.IPAMConfig{ - { - Subnet: "10.13.37.0/24", - Gateway: "10.13.37.254", - }, - }, - }, }) if err != nil { @@ -132,7 +128,7 @@ func setupVPNHostNetwork() (error) { setupDockerCLI() if vpnHostNetworkID == "" { - response, err := dockerCli.NetworkCreate(dockerCtx, "vpnhostnet", types.NetworkCreate{ + response, err := dockerCli.NetworkCreate(dockerCtx, vpnHostNetworkName, types.NetworkCreate{ Internal: false, }) -- cgit 1.4.1