about summary refs log tree commit diff
diff options
context:
space:
mode:
-rw-r--r--Dockerfile19
-rw-r--r--README.md59
-rw-r--r--hosted/index.html39
-rw-r--r--imgs/Procedure.pngbin0 -> 45470 bytes
-rw-r--r--src/access.go26
-rw-r--r--src/docker.go201
-rw-r--r--src/http.go68
-rw-r--r--src/main.go18
-rw-r--r--src/structs.go5
9 files changed, 433 insertions, 2 deletions
diff --git a/Dockerfile b/Dockerfile
new file mode 100644
index 0000000..e0e783f
--- /dev/null
+++ b/Dockerfile
@@ -0,0 +1,19 @@
+FROM golang
+
+# Install libraries
+RUN go get \
+	github.com/gorilla/mux \
+    golang.org/x/net
+
+
+# Create workdir
+RUN mkdir /workdir
+WORKDIR /workdir
+
+# Copy our sources
+COPY src /workdir/src
+COPY hosted /workdir/hosted
+# and build them
+RUN go build -o /workdir/landingpage src/*.go
+
+ENTRYPOINT [ "/workdir/landingpage" ]
diff --git a/README.md b/README.md
index f85c49e..e5d7d28 100644
--- a/README.md
+++ b/README.md
@@ -1,3 +1,58 @@
-# landingpage
+# circus-register
 
-circus landingpage routing the user to the registration page, the scoreboard and to possible communication channels with the organizators
\ No newline at end of file
+Register a new companion
+
+## Setup
+
+First, build an image:
+
+```
+$ docker build . -t circus-register
+```
+
+Then run the container:
+
+```
+$ docker run --rm -v /var/run/docker.sock:/var/run/docker.sock --net circus -p 8081:8081 circus-register:latest
+```
+
+## Function
+
+This container should only show a small registration form for the user.  The user
+inputs a name and recieves an access token and a link to a companion container.
+
+All this contains does is run this command with a few custom values:
+
+```
+$ docker run \
+    --net circus \
+    -v /var/run/docker.sock:/var/run/docker.sock \
+    -v $(pwd)/companion.json:/etc/companion.json \
+    -p 8086:8080 \
+    -d circus-companion:latest \
+    -username "Player 1" \
+    -accessCode theoneandonlyplayeroneisready \
+    -sessionSalt salty \
+    -vpnRemoteAddress 127.0.0.1 \
+    -vpnRemotePort 1193
+```
+
+What this command does:
+
+- starts a circus companion
+- inserts the companion into the circus network
+- mounts the docker socket
+- mounts the companion.json seeding the challenges
+- exposes port 8080 to the outside world 
+- defines the name of the player
+- defines the access-token the player can use to login
+- defines a salt for the session
+- defines where the remote vpn is running
+- defines behind which port the vpn can be found
+
+That's a lot, but all we need to do is to get a name for the user, generate a
+random access code, start the container and return the access code to the user.
+
+## Overall procedure:
+
+![](https://git.darknebu.la/circus/circus-register/raw/branch/master/imgs/Procedure.png)
diff --git a/hosted/index.html b/hosted/index.html
new file mode 100644
index 0000000..ecc3989
--- /dev/null
+++ b/hosted/index.html
@@ -0,0 +1,39 @@
+{{define "index"}}
+<!DOCTYPE html>
+<html>
+    <head>
+        <meta charset="utf-8">
+        <link rel="stylesheet" href="https://stackpath.bootstrapcdn.com/bootstrap/4.1.3/css/bootstrap.min.css" integrity="sha384-MCw98/SFnGE8fJT3GXwEOngsV7Zt27NXFoaoApmYm81iuXoPkFOJwJ8ERdknLPMO" crossorigin="anonymous">
+        <script type="text/javascript" src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.3.1/jquery.min.js"></script>
+    </head>
+    <body>
+        <nav class="navbar navbar-expand-md navbar-dark bg-dark mb-4">
+            <a class="navbar-brand" href="/">dorfCTF</a>
+            <button class="navbar-toggler" type="button" data-toggle="collapse" data-target="#navbarCollapse" aria-controls="navbarCollapse" aria-expanded="false" aria-label="Toggle navigation">
+                <span class="navbar-toggler-icon"></span>
+            </button>
+            <div class="collapse navbar-collapse" id="navbarCollapse">
+                <ul class="navbar-nav mr-auto">
+                </ul>
+            </div>
+        </nav>
+        <main class="container" role="main">
+            <div class="jumbotron">
+                <h1 class="display-4">Hello.</h1>
+                <h3>Welcome to the dorfCTF landing page</h3>
+                <p>This CTF is a bit different than all the other CTFs. First of
+                all, you need to register a companion. Using the companion you
+                can spawn your challenges in which you must find flags. 
+                </p>
+                <hr>
+                <a class="btn btn-primary btn-lg"
+                   href="http://register.{{.Hostname}}"
+                    role="button">Register!</a>
+                <a class="btn btn-primary btn-lg"
+                   href="http://grafana.{{.Hostname}}"
+                    role="button">Scoreboard</a>
+            </div>
+        </main>
+    </body>
+</html>
+{{end}}
diff --git a/imgs/Procedure.png b/imgs/Procedure.png
new file mode 100644
index 0000000..b5bc925
--- /dev/null
+++ b/imgs/Procedure.png
Binary files differdiff --git a/src/access.go b/src/access.go
new file mode 100644
index 0000000..6de2c05
--- /dev/null
+++ b/src/access.go
@@ -0,0 +1,26 @@
+package main
+
+import (
+	"math/rand"
+	"strings"
+	"time"
+)
+
+func newAccessCode(length int) string {
+	// seed the random numbergenerator
+	rand.Seed(time.Now().UnixNano())
+
+	// define the alphabet
+	chars := []rune("ABCDEFGHIJKLMNOPQRSTUVWXYZ" +
+		"abcdefghijklmnopqrstuvwxyz" +
+		"0123456789")
+
+	// build the accesscode
+	var b strings.Builder
+	for i := 0; i < length; i++ {
+		b.WriteRune(chars[rand.Intn(len(chars))])
+	}
+	str := b.String()
+
+	return str
+}
diff --git a/src/docker.go b/src/docker.go
new file mode 100644
index 0000000..e3ff19c
--- /dev/null
+++ b/src/docker.go
@@ -0,0 +1,201 @@
+package main
+
+import (
+	"context"
+	"fmt"
+	"log"
+	"os"
+	"time"
+
+	"github.com/docker/docker/api/types"
+	"github.com/docker/docker/api/types/container"
+	"github.com/docker/docker/api/types/mount"
+	"github.com/docker/docker/api/types/network"
+	"github.com/docker/docker/client"
+	"github.com/docker/go-connections/nat"
+)
+
+const (
+	fixedDockerVersion = "1.38"
+)
+
+var (
+	dockerCtx            context.Context
+	dockerCLI            *client.Client
+	currentVpnRemptePort = 1194
+)
+
+func setupContext() {
+	if dockerCtx == nil {
+		dockerCtx = context.Background()
+	}
+}
+
+func setupDockerCLI() (err error) {
+	os.Setenv("DOCKER_API_VERSION", "1.27")
+	if dockerCLI == nil {
+		dockerCLI, err = client.NewEnvClient()
+	}
+	return err
+}
+
+func spawnCompanion(username string, accesscode string) {
+	log.Println("Spwaning a new companion container")
+	// setup the context and the docker cli connection
+	setupContext()
+	setupDockerCLI()
+
+	sessionSalt := generateSessionSalt()
+	vpnRemoteAddress := os.Getenv("HOSTNAME")
+
+	// get the vpnRemoteport and increment it
+	vpnRemotePort := currentVpnRemptePort
+	log.Println("vpnRemptePort ", currentVpnRemptePort)
+	currentVpnRemptePort++
+
+	log.Println("Generating the container config")
+
+	Config := &container.Config{
+		Image: "circus-companion:latest",
+		Cmd:   []string{"-username", username, "-accessCode", accesscode, "-sessionSalt", sessionSalt, "-vpnRemoteAddress", vpnRemoteAddress, "-vpnRemotePort", fmt.Sprintf("%d", vpnRemotePort)},
+		ExposedPorts: nat.PortSet{
+			"8080/tcp": struct{}{},
+		},
+		// Container labels used by traefik
+		Labels: map[string]string{
+			"traefik.enable": "true",
+			fmt.Sprintf("traefik.http.routers.%s.entrypoints", username):                              "web",
+			fmt.Sprintf("traefik.http.routers.%s.rule", username):                                     fmt.Sprintf("Host(`%s.%s`)", username, os.Getenv("HOSTNAME")),
+			fmt.Sprintf("traefik.http.middlewares.%s-https-redirect.redirectscheme.scheme", username): "https",
+			fmt.Sprintf("traefik.http.routers.%s.middlewares", username):                              fmt.Sprintf("%s-https-redirect", username),
+			fmt.Sprintf("traefik.http.routers.%s-secure.entrypoints", username):                       "websecure",
+			fmt.Sprintf("traefik.http.routers.%s-secure.rule", username):                              fmt.Sprintf("Host(`%s.%s`)", username, os.Getenv("HOSTNAME")),
+			fmt.Sprintf("traefik.http.routers.%s-secure.tls", username):                               "true",
+			fmt.Sprintf("traefik.http.routers.%s-secure.tls.certresolver", username):                  "mytlschallenge",
+			fmt.Sprintf("traefik.http.routers.%s-secure.service", username):                           fmt.Sprintf("%s", username),
+			fmt.Sprintf("traefik.http.services.%s.loadbalancer.server.port", username):                "8080",
+			"traefik.docker.network": "circus",
+		},
+	}
+
+	// configure the host
+	// this mounts the docker socket and the companion seed file into the
+	// container
+	HostConfig := &container.HostConfig{
+		Mounts: []mount.Mount{
+			{
+				Type:   mount.TypeBind,
+				Source: "/var/run/docker.sock",
+				Target: "/var/run/docker.sock",
+			},
+			{
+				Type:   mount.TypeBind,
+				Source: "/etc/companion.json",
+				Target: "/etc/companion.json",
+			},
+		},
+	}
+
+	circusNetworkID, err := getCircusNetworkID(dockerCtx, dockerCLI)
+	NetworkingConfig := &network.NetworkingConfig{
+		EndpointsConfig: map[string]*network.EndpointSettings{
+			"circus": {
+				NetworkID: circusNetworkID,
+			},
+		},
+	}
+
+	log.Println("Creating the container")
+
+	// create the docker container
+	resp, err := dockerCLI.ContainerCreate(dockerCtx, Config, HostConfig, NetworkingConfig, "")
+	if err != nil {
+		panic(err)
+	}
+
+	log.Println("Container created!")
+
+	// id of the created container
+	containerID := resp.ID
+
+	// start the docker container
+	err = dockerCLI.ContainerStart(dockerCtx, containerID, types.ContainerStartOptions{})
+	if err != nil {
+		fmt.Println(err)
+	}
+
+	log.Printf("Adding the container (%s) to the circus network", containerID)
+
+	// get the circus network id
+	circusNetworkID, err = getCircusNetworkID(dockerCtx, dockerCLI)
+	if err != nil {
+		log.Println("error: could not get CircusNetworkID", err)
+	}
+	log.Printf("circus network ID: %s", circusNetworkID)
+
+	// connect the companion container to the circus network
+	connectContainerToNetwork(containerID, circusNetworkID)
+
+	log.Println("Container added to the circus network")
+
+	// Get IP Address of that container
+	inspectJSON, err := dockerCLI.ContainerInspect(dockerCtx, containerID)
+	if err != nil {
+		log.Println("inspectJsonErr: ", err)
+	}
+
+	fmt.Println("---")
+	fmt.Printf("%#v", inspectJSON.NetworkSettings.Networks)
+	fmt.Printf("companion IP: %#v", inspectJSON.NetworkSettings.Networks["circus"].IPAddress)
+}
+
+func generateSessionSalt() string {
+	return fmt.Sprintf("%d", time.Now().UnixNano())
+}
+
+// listDockerNetworks lists the docker networks
+func listDockerNetworks(dockerCtx context.Context, dockerCLI *client.Client) ([]types.NetworkResource, error) {
+
+	// list the docker networks
+	networks, err := dockerCLI.NetworkList(dockerCtx, types.NetworkListOptions{})
+	if err != nil {
+		return nil, fmt.Errorf("network list err: %s", err)
+	}
+
+	return networks, nil
+}
+
+// searchDockerNetworkIDFromName returns the docker network ID using the given name
+// to find it.
+func searchDockerNetworkIDFromName(networks []types.NetworkResource, NetworkNameToFind string) (string, error) {
+
+	for _, network := range networks {
+		if network.Name == NetworkNameToFind {
+			return network.ID, nil
+		}
+	}
+	return "", fmt.Errorf("could not find the network id for the network %s: %s", NetworkNameToFind)
+}
+
+// connectContainerToNetwork inserts the container with the given ID into the
+// network with the given ID
+func connectContainerToNetwork(containerID string, networkID string) {
+	err := dockerCLI.NetworkConnect(dockerCtx, networkID, containerID, nil)
+	if err != nil {
+		log.Printf("Could not connect container %s to network %s: %s", containerID[:10], networkID[:10], err)
+	}
+	log.Println("connectContainerToNetwork err: ", err)
+}
+
+func getCircusNetworkID(dockerCtx context.Context, dockerCLI *client.Client) (string, error) {
+	networks, err := listDockerNetworks(dockerCtx, dockerCLI)
+	if err != nil {
+		return "", err
+	}
+
+	circusNetworkID, err := searchDockerNetworkIDFromName(networks, "circus")
+	if err != nil {
+		return "", err
+	}
+	return circusNetworkID, nil
+}
diff --git a/src/http.go b/src/http.go
new file mode 100644
index 0000000..248eea4
--- /dev/null
+++ b/src/http.go
@@ -0,0 +1,68 @@
+package main
+
+import (
+	"flag"
+	"fmt"
+	"io/ioutil"
+	"log"
+	"net/http"
+	"os"
+	"strings"
+	"text/template"
+
+	"github.com/gorilla/mux"
+)
+
+var (
+	port      *int     // port the http server listens on
+	usernames []string // list of usernames
+)
+
+// Credentials stores user credentials
+type Credentials struct {
+	Username   string
+	Accesscode string
+	Hostname   string
+}
+
+func registerHTTPFlags() {
+	port = flag.Int("port", 8081, "The port the http server should listen on")
+}
+
+func setupHTTPServer() http.Server {
+	r := mux.NewRouter()
+
+	r.HandleFunc("/", indexHandler)
+	return http.Server{
+		Addr:    fmt.Sprintf("0.0.0.0:%d", *port),
+		Handler: r,
+	}
+}
+
+// Host of the index file
+func indexHandler(w http.ResponseWriter, r *http.Request) {
+	t := template.New("")
+	t, err := t.ParseFiles("./hosted/index.html")
+	if err != nil {
+		log.Println(err)
+		return
+	}
+
+	content := Content{
+		Hostname: os.Getenv("HOSTNAME"),
+	}
+
+	t.ExecuteTemplate(w, "index", content)
+}
+
+func readFileToReponse(w http.ResponseWriter, path string) {
+	requestedFile := strings.Replace(path, "..", "", -1)
+
+	contents, readError := ioutil.ReadFile(fmt.Sprintf("hosted/%s", requestedFile))
+
+	if readError != nil {
+		w.Write([]byte(fmt.Sprintf("unable to read %s", requestedFile)))
+	} else {
+		w.Write([]byte(contents))
+	}
+}
diff --git a/src/main.go b/src/main.go
new file mode 100644
index 0000000..57007e8
--- /dev/null
+++ b/src/main.go
@@ -0,0 +1,18 @@
+package main
+
+import (
+	"flag"
+	"log"
+)
+
+func main() {
+	// Set up flags
+	registerHTTPFlags()
+	flag.Parse()
+
+	// Set up the http server
+	log.Printf("Running the HTTP server on port %d", *port)
+	httpServer := setupHTTPServer()
+
+	log.Fatalln(httpServer.ListenAndServe())
+}
diff --git a/src/structs.go b/src/structs.go
new file mode 100644
index 0000000..cdc682a
--- /dev/null
+++ b/src/structs.go
@@ -0,0 +1,5 @@
+package main
+
+type Content struct {
+	Hostname string
+}