about summary refs log tree commit diff
diff options
context:
space:
mode:
authormaride <maride@darknebu.la>2018-08-21 18:49:59 +0200
committermaride <maride@darknebu.la>2018-08-21 18:49:59 +0200
commit07294af77f4b088f67efafbeee79e7ddd0eeac84 (patch)
tree161c510ef65f1051deda57679ba9d3ca404d046e
parentf35350d2691007a1b52c3b1e16794ec74702cd0d (diff)
Feature: Start and stop challenge containers
-rw-r--r--hosted/challenges.html27
-rw-r--r--src/challenge.go5
-rw-r--r--src/container.go70
-rw-r--r--src/containerManager.go74
4 files changed, 176 insertions, 0 deletions
diff --git a/hosted/challenges.html b/hosted/challenges.html
index 3c18f2d..34c1ab5 100644
--- a/hosted/challenges.html
+++ b/hosted/challenges.html
@@ -23,6 +23,17 @@
                         <p>
                             {{{ description }}}
                         </p>
+                        {{#if ContainsLaunchable}}
+                            {{#if IPAddress}}
+                                <p class="text-primary">
+                                    Container running IPv4: <kbd>{{IPAddress}}</kbd>
+                                </p>
+                                <button class="btn btn-primary" onclick="stopContainer('{{ name }}');$(this).prop('disabled', true)">Stop Container</button>
+                            {{else}}
+                                <button class="btn btn-primary" onclick="startContainer('{{ name }}');$(this).prop('disabled', true)">Launch Container</button>
+                            {{/if}}
+                        {{/if}}
+                        <hr>
                         {{#if foundFlag}}
                             <p class="text-success">Flag found!</p>
                         {{else}}
@@ -99,6 +110,22 @@
             });
         }
 
+        function startContainer(name) {
+            $.post("/api/startContainer", {
+                "challengeName": name
+            }).done(function() {
+                loadChallengesAndRender();
+            });
+        }
+
+        function stopContainer(name) {
+            $.post("/api/stopContainer", {
+                "challengeName": name
+            }).done(function() {
+                loadChallengesAndRender();
+            });
+        }
+
         $(document).ready(function() {
             loadChallengesAndRender();
         });
diff --git a/src/challenge.go b/src/challenge.go
index 38cffee..991e4e5 100644
--- a/src/challenge.go
+++ b/src/challenge.go
@@ -10,11 +10,14 @@ type Challenge struct {
 	Category string
 }
 
+// TODO: The name "Stripped" is a bit inaccurate now. Rename.
 type StrippedChallenge struct {
 	Name string `json:"name"`
 	Description string `json:"description"`
 	Category string `json:"category"`
 	FoundFlag bool `json:"foundFlag"`
+	ContainsLaunchable bool `json:"ContainsLaunchable"`
+	IPAddress string `json:"IPAddress"`
 }
 
 func stripChallenge(c Challenge) (StrippedChallenge) {
@@ -23,5 +26,7 @@ func stripChallenge(c Challenge) (StrippedChallenge) {
 		Description: c.Description,
 		Category: c.Category,
 		FoundFlag: c.FoundFlag,
+		ContainsLaunchable: c.Container != "",
+		IPAddress: getAddressForChallengeContainer(c.Container),
 	}
 }
diff --git a/src/container.go b/src/container.go
new file mode 100644
index 0000000..73912bf
--- /dev/null
+++ b/src/container.go
@@ -0,0 +1,70 @@
+package main
+
+import (
+	"context"
+	"github.com/docker/docker/client"
+	"github.com/docker/docker/api/types/container"
+	"github.com/docker/docker/api/types"
+	"fmt"
+	"time"
+)
+
+type ChallengeContainer struct {
+	Challenge *Challenge
+	ContainerID string
+	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
+		}
+	}
+
+	// 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, "")
+
+	if err != nil {
+		return "", "", err
+	}
+
+	// Start container
+	err = dockerCli.ContainerStart(dockerCtx, resp.ID, types.ContainerStartOptions{})
+	if err != nil {
+		return "", "", err
+	}
+
+	// Get IP Address of that container
+	inspectJSON, err := dockerCli.ContainerInspect(dockerCtx, resp.ID)
+	if err != nil {
+		return "", "", err
+	}
+
+	// Return IP, Container ID and error
+	return inspectJSON.NetworkSettings.IPAddress, resp.ID,nil
+}
+
+// Stops the container with a timeout of one second
+func (cc ChallengeContainer) stopContainer() {
+	timeout := time.Second
+	dockerCli.ContainerStop(dockerCtx, cc.ContainerID, &timeout)
+}
diff --git a/src/containerManager.go b/src/containerManager.go
new file mode 100644
index 0000000..0c25ae9
--- /dev/null
+++ b/src/containerManager.go
@@ -0,0 +1,74 @@
+package main
+
+import (
+	"errors"
+	"sync"
+)
+
+var(
+	containers = []*ChallengeContainer{}
+	containerStartLock sync.Mutex
+	containerStopLock sync.Mutex
+)
+
+// Checks if the given container name is already running
+func isChallengeContainerRunning(name string) (bool) {
+	for _, c := range containers {
+		if c.Challenge.Name == name {
+			return true
+		}
+	}
+
+	return false
+}
+
+// Starts the given container. Thread-safe!
+func startChallengeContainer(c Challenge) (*ChallengeContainer, error) {
+	// Lock procedure to avoid race conditions
+	containerStartLock.Lock()
+	defer containerStartLock.Unlock()
+
+	if(isChallengeContainerRunning(c.Name)) {
+		return nil, errors.New("Container already running")
+	}
+
+	cc := ChallengeContainer{Challenge: &c}
+	address, containerID, err := cc.startContainer()
+
+	if err != nil {
+		return nil, err
+	}
+
+	cc.IP = address
+	cc.ContainerID = containerID
+
+	// Add container to list
+	containers = append(containers, &cc)
+
+	return &cc, nil
+}
+
+// Stops the given container. Thread-safe!
+func stopChallengeContainer(name string) {
+	containerStopLock.Lock()
+	defer containerStopLock.Unlock()
+
+	for index, c := range containers {
+		if c.Challenge.Name == name {
+			c.stopContainer()
+			containers = append(containers[:index], containers[index+1:]...)
+			return
+		}
+	}
+}
+
+// Returns the address for the given container, if there is a container with this name running
+func getAddressForChallengeContainer(container string) (address string) {
+	for _, c := range containers {
+		if c.Challenge.Container == container {
+			return c.IP
+		}
+	}
+
+	return ""
+}