diff options
author | Emile <hanemile@protonmail.com> | 2019-10-20 16:53:28 +0200 |
---|---|---|
committer | Emile <hanemile@protonmail.com> | 2019-10-20 16:53:28 +0200 |
commit | c856c7e66e3ad29ea0491f1f4951b31deb8279ea (patch) | |
tree | e32daefbeec30e667f261eca2c5a3409e783453d | |
parent | 22e3029cbf5fa7fd64bfaea2e4dcd011c40a4236 (diff) |
functional
-rw-r--r-- | Dockerfile | 19 | ||||
-rw-r--r-- | README.md | 59 | ||||
-rw-r--r-- | hosted/index.html | 39 | ||||
-rw-r--r-- | imgs/Procedure.png | bin | 0 -> 45470 bytes | |||
-rw-r--r-- | src/access.go | 26 | ||||
-rw-r--r-- | src/docker.go | 201 | ||||
-rw-r--r-- | src/http.go | 68 | ||||
-rw-r--r-- | src/main.go | 18 | ||||
-rw-r--r-- | src/structs.go | 5 |
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 +} |