diff options
author | maride <maride@darknebu.la> | 2018-08-21 18:49:59 +0200 |
---|---|---|
committer | maride <maride@darknebu.la> | 2018-08-21 18:49:59 +0200 |
commit | 07294af77f4b088f67efafbeee79e7ddd0eeac84 (patch) | |
tree | 161c510ef65f1051deda57679ba9d3ca404d046e | |
parent | f35350d2691007a1b52c3b1e16794ec74702cd0d (diff) |
Feature: Start and stop challenge containers
-rw-r--r-- | hosted/challenges.html | 27 | ||||
-rw-r--r-- | src/challenge.go | 5 | ||||
-rw-r--r-- | src/container.go | 70 | ||||
-rw-r--r-- | src/containerManager.go | 74 |
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 "" +} |