From fc34c6586bc127cc39c28ddb65d9c09bf98f3775 Mon Sep 17 00:00:00 2001 From: sukhman Date: Tue, 29 Oct 2024 06:30:08 +0530 Subject: [PATCH 01/11] Add dockerfile for serving beast_docs --- Dockerfile | 14 ++++ _examples/example.config.toml | 125 +++++++++++++++++++++++++--------- requirements.txt | 2 +- 3 files changed, 106 insertions(+), 35 deletions(-) create mode 100644 Dockerfile diff --git a/Dockerfile b/Dockerfile new file mode 100644 index 00000000..0aa3db65 --- /dev/null +++ b/Dockerfile @@ -0,0 +1,14 @@ +# Use Python 2.7 as the base image +FROM python:2.7 + +WORKDIR /app + +COPY requirements.txt /app/ + +RUN pip install --no-cache-dir -r requirements.txt + +COPY ./docs/ /app/docs/ +COPY mkdocs.yml /app/ +EXPOSE 8000 + +CMD ["mkdocs", "serve", "-a", "0.0.0.0:8000"] diff --git a/_examples/example.config.toml b/_examples/example.config.toml index b229dd3d..36855228 100644 --- a/_examples/example.config.toml +++ b/_examples/example.config.toml @@ -1,41 +1,98 @@ -# $ ~/.beast -# ❮❮ tree -# . -# ├── auth_file -# ├── beast.db -# ├── config.toml -# └── .static.beast.htpasswd -# ├── remote/ -# │   └── hack-test -# ├── secrets/ -# │   ├── key.priv -# │   └── key.pub -# └── staging/ -# -# Sample snapshot of .beast directory. -# Make sure the hack-test is a valid git directory -# Make sure you provide a valid existing authorized_keys_file -# Make sure key.pub is added to the deployed keys of the beast remote repo - -# scripts_dir = "/home/vsts/.beast/scripts" -available_sidecars = ["mysql"] -jwt_secret = "beast" -allowed_base_images = ["ubuntu:16.04", "php:7.1-cli"] -authorized_keys_file = "/home/vsts/.beast/authorized_keys" -remote_sync_period = "0h2m0s" +# Authorized key file used by ssh daemon running on the host +# This is used for forwarding ssh connection to docker containers, the +# access to a container is only given to the author of the challenge. +authorized_keys_file = "$HOME/.beast/beast_authorized_keys" -[[remote]] -ssh_key = "/home/vsts/.beast/secret.key" -url = "git@github.com:sdslabs/nonexistent.git" -name = "nonexistent" -branch = "nonexistent" +# Directory which will contain all the autogenerated scripts by beast +# These scripts are the heart to above authorized keys file. Each entry in authorized +# keys file as a corresponding script which is executed during an SSH attempt. +scripts_dir = "$HOME/.beast/scripts" + +# Base OS image that beast allows the challenges to use. +allowed_base_images = ["ubuntu:18.04", "ubuntu:16.04", "debian:jessie"] + + +# For authentication purposes beast uses JWT based authentication, this is the +# key used for encrypting the claims of a user. Keep this strong. +jwt_secret = "beast_jwt_secret_SUPER_STRONG_0x100010000100" + +# To allow beast to send notification to a notification channel povide this webhook URL +# We are also working on implmeneting notification using IRC. [[notification_webhooks]] + +# The webhook URL of notification channel where notification should be sent url = "" + +# The service name to be used. It can be `discord` and `slack` service_name = "discord" -active = true -[[notification_webhooks]] -url = "" -service_name = "slack" +# Status of webhook URL to be used. +# If it is false then notification will not be sent on this URL active = false + +# The sidecar that we support with beast, currently we only support two MySQL and +# MongoDB. +available_sidecars = ["mysql", "mongodb"] + + +# The frequency for any periodic event in beast, the value is provided in seconds. +# This is currently only used for health check periodic duration.s +ticker_frequency = 3000 + + +# Container default resource limits for each challenge, this can be +# Overridden by challenge configuration beast.toml file. +default_cpu_shares = 1024 +default_memory_limit = 1024 +default_pids_limit = 100 + + +# Configuration corresponding to the remote repository used by beast +# We use ssh authentication mechanism for interacting with git repository. +[[remote]] + +# URL of the remote git repository, this should be user@host: format +url = "git@github.com:sdslabs/hack-test.git" + +# Name of the remote +name = "hack-test" + +# Branch we are tracking the remote in beast. +branch = "master" + +# Path to private SSH key for interacting with the git repository. +ssh_key = "$HOME/.beast/secrets/key.priv" + +# Status of remote git repository URL to be used +# If it is set to false then that remote git repository will not be used +active = false + +# The following fields are required only while hosting a competition on beast +# This section contains information about the competition to be hosted +# Structure of the sections with the acceptable fields are: +[[competition_info]] +# Required Fields + +# Name of the competition +name = "" + +# About the competition +about = "" + +# Starting time of competition wrt time zone in `16:31:23 UTC: +05:30, 17th February 2021, Wednesday` format +starting_time = "" + +# Ending time of competition wrt time zone in `16:31:23 UTC: +05:30, 17th February 2021, Wednesday` format +ending_time = "" + +# Time zone for reference in `Asia/Calcutta: UTC +05:30` format +timezone = "" + +# Optional fields + +# Prizes for the competition winners +prizes = "" + +# Absolute path of logo file. Default logo dir is in the "BEAST_GLOBAL_DIR/assets/" +logo_url = "" diff --git a/requirements.txt b/requirements.txt index 9894e3b1..085dd2ea 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,3 +1,3 @@ -mkdocs==1.0.4 +mkdocs mkdocs-bootswatch PyYAML From 03eca16e37032b00726a0713638ca085a2974cf7 Mon Sep 17 00:00:00 2001 From: vibhatsu Date: Wed, 25 Dec 2024 16:13:14 +0530 Subject: [PATCH 02/11] mod: minor bug fixes --- core/config/challenge.go | 30 ++++++++++++------------------ core/config/config.go | 17 ++++------------- pkg/cr/containers.go | 2 +- 3 files changed, 17 insertions(+), 32 deletions(-) diff --git a/core/config/challenge.go b/core/config/challenge.go index 46520bac..a8298db8 100644 --- a/core/config/challenge.go +++ b/core/config/challenge.go @@ -146,10 +146,11 @@ type ChallengeMetadata struct { // In this validation returned boolean value represents if the challenge type is // static or not. func (config *ChallengeMetadata) ValidateRequiredFields() (error, bool) { + log.Debugf("Validating Challenge: %s", config.Name) if config.Name == "" || config.Flag == "" { return fmt.Errorf("Name and Flag required for the challenge"), false } - + log.Debugf("Available sidecars: %v", Cfg.AvailableSidecars) if !(utils.StringInSlice(config.Sidecar, Cfg.AvailableSidecars) || config.Sidecar == "") { return fmt.Errorf("Sidecar provided is not an available sidecar."), false } @@ -188,59 +189,52 @@ func (config *ChallengeMetadata) ValidateRequiredFields() (error, bool) { // # Dependencies required by challenge, installed using default package manager of base image apt for most cases. // apt_deps = ["", ""] // -// // # A list of setup scripts to run for building challenge enviroment. // # Keep in mind that these are only for building the challenge environment and are executed // # in the iamge building step of the deployment pipeline. // setup_scripts = ["", ""] // -// // # A directory containing any of the static assets for the challenge, exposed by beast static endpoint. // static_dir = "" // -// // # Command to execute inside the container, if a predefined type is being used try to // # use an existing field to let beast automatically calculate what command to run. // # If you want to host a binary using xinetd use type service and specify absolute path // # of the service using service_path field. // run_cmd = "" // -// // # Similar to run_cmd but in this case you have the entire container to yourself // # and everything you are doing is done using root permissions inside the container // # When using this keep in mind you are root inside the container. // entrypoint = "" // -// // # Relative path to binary which needs to be executed when the specified // # Type for the challenge is service. // # This can be anything which can be exeucted, a python file, a binary etc. // service_path = "" // -// // # Relative directory corresponding to root of the challenge where the root // # of the web application lies. // web_root = "" // -// // # Any custom base image you might want to use for your particular challenge. // # Exists for flexibility reasons try to use existing base iamges wherever possible. // base_image = "" // -// // # Docker file name for specific type challenge - `docker`. // # Helps to build flexible images for specific user-custom challenges // docket_context = "" // -// // # Environment variables that can be used in the application code. // [[var]] -// key = "" -// value = "" +// +// key = "" +// value = "" // // [[var]] -// key = "" -// value = "" +// +// key = "" +// value = "" // // Type of traffic to expose through the port mapping provided. // traffic = "udp" / "tcp" @@ -501,10 +495,10 @@ func (config *ChallengeEnv) ValidateRequiredFields(challType string, challdir st // Metadata related to author of the challenge, this structure includes // -// * Name - Name of the author of the challenge -// * Email - Email of the author -// * SSHKey - Public SSH key for the challenge author, to give the access -// to the challenge container. +// - Name - Name of the author of the challenge +// - Email - Email of the author +// - SSHKey - Public SSH key for the challenge author, to give the access +// to the challenge container. // // ```toml // # Optional fields diff --git a/core/config/config.go b/core/config/config.go index 79d8008e..11b79ddc 100644 --- a/core/config/config.go +++ b/core/config/config.go @@ -18,7 +18,7 @@ import ( // This is the global beast configuration structure // -// An example of a config file +// # An example of a config file // // ```toml // # Authorized key file used by ssh daemon running on the host @@ -26,50 +26,41 @@ import ( // # access to a container is only given to the author, maintainers of challenge and admin. // authorized_keys_file = "/home/fristonio/.beast/beast_authorized_keys" // -// // # Directory which will contain all the autogenerated scripts by beast // # These scripts are the heart to above authorized keys file. Each entry in authorized // # keys file as a corresponding script which is executed during an SSH attempt. // scripts_dir = "/home/fristonio/.beast/scripts" // -// // # Base OS image that beast allows the challenges to use. // allowed_base_images = ["ubuntu:18.04", "ubuntu:16.04", "debian:jessie"] // -// // # Beast static URL refers to the host used by beast for serving static content // # of the challenges, whenever required. It follows the URL pattern like // # [beast_static_url]/static/[chall_dir]/[file_name] // beast_static_url = "http://hack.sdslabs.co:8034" // -// // # For authentication purposes beast uses JWT based authentication, this is the // # key used for encrypting the claims of a user. Keep this strong. // jwt_secret = "beast_jwt_secret_SUPER_STRONG_0x100010000100" // -// // # To allow beast to send notification to a notification channel povide this webhook URL // # We are also working on implmeneting notification using Discord and IRC. // slack_webhook = "" // -// // # The sidecar that we support with beast, currently we only support two MySQL and // # MongoDB. // available_sidecars = ["mysql", "mongodb"] // -// // # The frequency for any periodic event in beast, the value is provided in seconds. // # This is currently only used for health check periodic duration.s // ticker_frequency = 3000 // -// // # Container default resource limits for each challenge, this can be // # Overridden by challenge configuration beast.toml file. // default_cpu_shares = 1024 // default_memory_limit = 1024 // default_pids_limit = 100 // -// // # Configuration corresponding to the remote repository used by beast // # We use ssh authentication mechanism for interacting with git repository. // [remote] @@ -153,7 +144,7 @@ func (config *BeastConfig) ValidateConfig() error { } for _, gitRemote := range config.GitRemotes { - if gitRemote.Active == true { + if gitRemote.Active { err := gitRemote.ValidateGitConfig() if err != nil { return fmt.Errorf("Error while validating config : %s", gitRemote.RemoteName) @@ -318,7 +309,7 @@ func LoadBeastConfig(configPath string) (BeastConfig, error) { return config, err } - log.Debugf("Parsed beast global config file is : %s", config) + log.Debugf("Parsed beast global config file is : %v", config) err = config.ValidateConfig() if err != nil { return config, err @@ -338,7 +329,7 @@ func UpdateUsedPortList() { beastRemoteDir := filepath.Join(core.BEAST_GLOBAL_DIR, core.BEAST_REMOTES_DIR) for _, gitRemote := range Cfg.GitRemotes { - if gitRemote.Active != true { + if !gitRemote.Active { continue } diff --git a/pkg/cr/containers.go b/pkg/cr/containers.go index f8a94a66..5195213a 100644 --- a/pkg/cr/containers.go +++ b/pkg/cr/containers.go @@ -177,7 +177,7 @@ func CreateContainerFromImage(containerConfig *CreateContainerConfig) (string, e createResp, err := cli.ContainerCreate(ctx, config, hostConfig, nil, containerName) if err != nil { - log.Error("Error while creating the container with name %s", containerName) + log.Errorf("Error while creating the container with name %s", containerName) return "", err } From 6dfd5f8126e21d66201e519b7ea0ab7b36317cad Mon Sep 17 00:00:00 2001 From: vibhatsu Date: Thu, 26 Dec 2024 17:24:32 +0530 Subject: [PATCH 03/11] mod: minor bug fixes --- core/manager/pipeline.go | 9 +++++---- docs/ChallTypes.md | 4 ++-- 2 files changed, 7 insertions(+), 6 deletions(-) diff --git a/core/manager/pipeline.go b/core/manager/pipeline.go index e727cb18..f7b8d7b0 100644 --- a/core/manager/pipeline.go +++ b/core/manager/pipeline.go @@ -212,6 +212,7 @@ func deployChallenge(challenge *database.Challenge, config cfg.BeastChallengeCon log.Debugf("Container config for challenge %s are: CPU(%d), Memory(%d), PidsLimit(%d)", config.Challenge.Metadata.Name, config.Resources.CPUShares, + config.Resources.Memory, config.Resources.PidsLimit) // Since till this point we have already valiadated the challenge config this is highly @@ -264,10 +265,10 @@ func deployChallenge(challenge *database.Challenge, config cfg.BeastChallengeCon // // The pipeline goes through the following stages: // -// * stageChallenge - Add the challenge to the staging area for beast creating -// a tar for the challenge with Dockerfile embedded into the context. -// This challenge is then present in the staging area($BEAST_HOME/staging/challengeId/) -// for further steps in the pipeline. +// - stageChallenge - Add the challenge to the staging area for beast creating +// a tar for the challenge with Dockerfile embedded into the context. +// This challenge is then present in the staging area($BEAST_HOME/staging/challengeId/) +// for further steps in the pipeline. // // The skipStage flag is a boolean value to skip the staging step for the challenge // if this flag is true then the deployment to succeed the challenge should already diff --git a/docs/ChallTypes.md b/docs/ChallTypes.md index 6816ee7a..cc0a7b6d 100644 --- a/docs/ChallTypes.md +++ b/docs/ChallTypes.md @@ -4,7 +4,7 @@ Any service whether it is a binary file, or a shell script, which needs to be instantiated on every connection can be easily hosted using `service` type challenge. **Xinetd** is for hosting these type of challenges inside a docker container. -###Primary Requirements +### Primary Requirements ```toml # Relative path to binary or script which needs to be executed when the specified @@ -21,7 +21,7 @@ Web challenges are hosted using the corresponding images from Dockerhub. Current * Python : Django and Flask * Php -###Primary Requirements +### Primary Requirements ```toml # Relative directory corresponding to root of the challenge where the root From 241ac1a6da01a39f5fe585378467821ce68a2e50 Mon Sep 17 00:00:00 2001 From: vibhatsu Date: Thu, 26 Dec 2024 17:25:46 +0530 Subject: [PATCH 04/11] feat: global error handling init --- utils/errorrendering.go | 46 +++++++++++++++++++++++++++++++++++++++++ 1 file changed, 46 insertions(+) create mode 100644 utils/errorrendering.go diff --git a/utils/errorrendering.go b/utils/errorrendering.go new file mode 100644 index 00000000..58ea715e --- /dev/null +++ b/utils/errorrendering.go @@ -0,0 +1,46 @@ +package utils + +import ( + "errors" + "fmt" + "net/http" + + "github.com/gin-gonic/gin" +) + +// This can be used for global error handling in the application +// Send error to the client + +func SendError(c *gin.Context, err error) { + c.JSON(http.StatusBadRequest, gin.H{ + "error": err.Error(), + }) +} + +func ValidatePoints(points int, err error) error { + if err != nil { + return err + } + if points < 0 { + return errors.New("Points cannot be negative") + } + return nil +} + +func FileRecieveError(c *gin.Context, err error) error { + if err != nil { + err = fmt.Errorf("Error while receiving file: %s", err) + SendError(c, err) + return err + } + return nil +} + +func FileSaveError(c *gin.Context, err error) error { + if err != nil { + err = fmt.Errorf("Error while saving file: %s", err) + SendError(c, err) + return err + } + return nil +} From ac05e530fecc6c3c6ecb57da94e80fd529085853 Mon Sep 17 00:00:00 2001 From: vibhatsu Date: Thu, 26 Dec 2024 17:27:44 +0530 Subject: [PATCH 05/11] add: file transfer utils --- utils/filetransfer.go | 17 +++++++++++++++++ 1 file changed, 17 insertions(+) create mode 100644 utils/filetransfer.go diff --git a/utils/filetransfer.go b/utils/filetransfer.go new file mode 100644 index 00000000..262094ab --- /dev/null +++ b/utils/filetransfer.go @@ -0,0 +1,17 @@ +package utils + +import ( + "github.com/gin-gonic/gin" +) + +func FileDownload(c *gin.Context, filename string, path string) string { + file, err := c.FormFile(filename) + if FileRecieveError(c, err) != nil { + return "" + } + err = c.SaveUploadedFile(file, path) + if FileSaveError(c, err) != nil { + return "" + } + return file.Filename +} From fc685d381c512c9f815cd92cb268d6b45f1fdbee Mon Sep 17 00:00:00 2001 From: vibhatsu Date: Thu, 26 Dec 2024 17:29:03 +0530 Subject: [PATCH 06/11] mod: improved template and constant added for chall dir --- core/constants.go | 1 + templates/templates.go | 7 +++++-- 2 files changed, 6 insertions(+), 2 deletions(-) diff --git a/core/constants.go b/core/constants.go index 8938d2c6..641d2eb0 100644 --- a/core/constants.go +++ b/core/constants.go @@ -26,6 +26,7 @@ const ( //names BEAST_REMOTE_CHALLENGE_DIR string = "challenges" BEAST_STATIC_CONTAINER_NAME string = "beast-static" BEAST_STATIC_FOLDER string = "static" + BEAST_CHALLENGE_DIR string = "chall" PUBLIC string = "public" HIDDEN string = ".hidden" ISSUER string = "beast-sds" diff --git a/templates/templates.go b/templates/templates.go index bd55725e..9645c51a 100644 --- a/templates/templates.go +++ b/templates/templates.go @@ -36,7 +36,9 @@ RUN apt-get -y install {{.AptDeps}} {{if .Ports}}EXPOSE {{.Ports}} {{end}} VOLUME ["{{.MountVolume}}"] -COPY . /challenge +COPY ./challenge /challenge + +COPY {{ range $index, $elem := .SetupScripts}}{{$elem}} /challenge/{{end}} WORKDIR /challenge @@ -47,7 +49,8 @@ ENV {{$key}} "{{$elem}}" RUN cd /challenge {{ range $index, $elem := .SetupScripts}} && \ chmod u+x {{$elem}} {{end}} {{ range $index, $elem := .SetupScripts}} && \ ./{{$elem}} {{end}} - +{{ range $index, $elem := .SetupScripts}}RUN rm /challenge/{{$elem}} {{end}} + {{if not .Entrypoint}} RUN touch /entrypoint.sh && \ echo "#!/bin/bash" > /entrypoint.sh && \ From 9c38a136cfa8a71648ad765e0301f834f5d7921b Mon Sep 17 00:00:00 2001 From: vibhatsu Date: Thu, 26 Dec 2024 17:32:12 +0530 Subject: [PATCH 07/11] mod: simplified unnecessary complications --- core/config/challenge.go | 7 +++++-- core/manager/challenge.go | 15 ++++++++------- core/manager/utils.go | 6 +++--- 3 files changed, 16 insertions(+), 12 deletions(-) diff --git a/core/config/challenge.go b/core/config/challenge.go index a8298db8..8c163118 100644 --- a/core/config/challenge.go +++ b/core/config/challenge.go @@ -154,7 +154,10 @@ func (config *ChallengeMetadata) ValidateRequiredFields() (error, bool) { if !(utils.StringInSlice(config.Sidecar, Cfg.AvailableSidecars) || config.Sidecar == "") { return fmt.Errorf("Sidecar provided is not an available sidecar."), false } - + //Check if challenge points are within permissible range. + if config.MaxPoints < config.MinPoints { + return fmt.Errorf("Max points should be greater than min points"), false + } // Check if the config type is static here and if it is // then return an indication for that, so that caller knows if it need // to check a valid environment or not. @@ -396,7 +399,7 @@ func (config *ChallengeEnv) ValidateRequiredFields(challType string, challdir st for _, portMap := range portMappings { if portMap.HostPort < core.ALLOWED_MIN_PORT_VALUE || portMap.HostPort > core.ALLOWED_MAX_PORT_VALUE { - return fmt.Errorf("Port value must be between %s and %s", core.ALLOWED_MIN_PORT_VALUE, core.ALLOWED_MAX_PORT_VALUE) + return fmt.Errorf("Port value must be between %d and %d", core.ALLOWED_MIN_PORT_VALUE, core.ALLOWED_MAX_PORT_VALUE) } } diff --git a/core/manager/challenge.go b/core/manager/challenge.go index d296c13b..d0bb2b5e 100644 --- a/core/manager/challenge.go +++ b/core/manager/challenge.go @@ -298,7 +298,6 @@ func GetDeployWork(challengeName string) (*wpool.Task, error) { Info: info, }, nil } - return nil, nil } // Handle multiple challenges simultaneously. @@ -349,12 +348,12 @@ func HandleTagRelatedChallenges(action string, tag string, user string) []string // exist the provided tag, simply skip doing anything. err := database.QueryOrCreateTagEntry(tagEntry) if err != nil { - return []string{fmt.Sprintf("DATABASE_ERROR")} + return []string{"DATABASE_ERROR"} } challs, err := database.QueryRelatedChallenges(tagEntry) if err != nil { - return []string{fmt.Sprintf("DATABASE_ERROR")} + return []string{"DATABASE_ERROR"} } var challsNameList []string @@ -400,7 +399,7 @@ func HandleAll(action string, user string) []string { // is up to date. This ignores this error. if !strings.Contains(err.Error(), "already up-to-date") { log.Warnf("Error while syncing beast for DEPLOY_ALL : %s ...", err) - return []string{fmt.Sprintf("GIT_REMOTE_SYNC_ERROR")} + return []string{"GIT_REMOTE_SYNC_ERROR"} } } log.Debugf("Sync for beast remote done for DEPLOY_ALL") @@ -480,10 +479,12 @@ func InitialAutoDeploy() { // - If a new challenge is added to the remote repo then it is deployed // - If an existing challenge is modified in the remote repo then it is redeployed // - If an existing challenge is deleted in the remote repo then it is purged +// // Note: -// If an existing challenge was undeployed manually then it will -// remain undeployed even if it is modified in the remote remo, but -// it will be purged if it is deleted in the remote repo +// +// If an existing challenge was undeployed manually then it will +// remain undeployed even if it is modified in the remote remo, but +// it will be purged if it is deleted in the remote repo func AutoUpdate() { log.Infof("Checking for updates in remote repository") diff --git a/core/manager/utils.go b/core/manager/utils.go index 4a5e5fc7..72d59fb0 100644 --- a/core/manager/utils.go +++ b/core/manager/utils.go @@ -440,9 +440,9 @@ func UpdateOrCreateChallengeDbEntry(challEntry *database.Challenge, config cfg.B } if userEntry.Email == "" { - if defaultauthorpassword == "" { - return fmt.Errorf("User with the given email does not exist : %v. You can pass q flag with password to autogenerate authors in this case.", config.Author.Email) - } + // if defaultauthorpassword == "" { + // return fmt.Errorf("User with the given email does not exist : %v. You can pass q flag with password to autogenerate authors in this case.", config.Author.Email) + // } log.Infof("User with the given email does not exist : %v, creating this user", config.Author.Email) newUser := database.User{ Name: config.Author.Name, From b4cba2f1c76ea4fb47044568e66657624756f999 Mon Sep 17 00:00:00 2001 From: vibhatsu Date: Thu, 26 Dec 2024 17:33:19 +0530 Subject: [PATCH 08/11] feat: /challenge/configure api sketch implemented --- api/manage.go | 191 +++++++++++++++++++++++++++++++++++++++++++++++++- api/router.go | 1 + 2 files changed, 190 insertions(+), 2 deletions(-) diff --git a/api/manage.go b/api/manage.go index ac29e6be..f11cfb01 100644 --- a/api/manage.go +++ b/api/manage.go @@ -5,6 +5,7 @@ import ( "net/http" "os" "path/filepath" + "strconv" "strings" "time" @@ -458,9 +459,9 @@ func manageUploadHandler(c *gin.Context) { // Extract and show from zip and return response tempStageDir, err := manager.UnzipChallengeFolder(zipContextPath, core.BEAST_TEMP_DIR) - + // log.Debug("The dir is ",tempStageDir) - + // The file cannot be successfully un-zipped or the resultant was a malformed directory if err != nil { c.JSON(http.StatusBadRequest, HTTPErrorResp{ @@ -514,3 +515,189 @@ func manageUploadHandler(c *gin.Context) { Points: config.Challenge.Metadata.Points, }) } + +func manageConifgureHandler(c *gin.Context) { + var config cfg.BeastChallengeConfig + + // Populating Author + config.Author.Name = c.PostForm("author_name") + config.Author.Email = c.PostForm("author_email") + config.Author.SSHKey = c.PostForm("author_ssh_key") + err := config.Author.ValidateRequiredFields() + if err != nil { + utils.SendError(c, err) + return + } + + // Populating Challenge Metadata + config.Challenge.Metadata.Name = c.PostForm("challenge_name") + config.Challenge.Metadata.Type = c.PostForm("challenge_type") + config.Challenge.Metadata.Flag = c.PostForm("challenge_flag") + config.Challenge.Metadata.Sidecar = c.PostForm("challenge_sidecar") + config.Challenge.Metadata.Tags = strings.Split(c.PostForm("challenge_tags"), ",") + config.Challenge.Metadata.Assets = strings.Split(c.PostForm("challenge_assets"), ",") + config.Challenge.Metadata.AdditionalLinks = strings.Split(c.PostForm("challenge_additional_links"), ",") + config.Challenge.Metadata.Hints = strings.Split(c.PostForm("challenge_hints"), ",") + config.Challenge.Metadata.Description = c.PostForm("challenge_desc") + maxpointsStr := c.PostForm("challenge_maxpoints") + maxpoints, err := strconv.ParseInt(maxpointsStr, 10, 64) + err = utils.ValidatePoints(int(maxpoints), err) + if err != nil { + utils.SendError(c, err) + return + } + config.Challenge.Metadata.MaxPoints = uint(maxpoints) + minpointsStr := c.PostForm("challenge_minpoints") + minpoints, err := strconv.ParseInt(minpointsStr, 10, 64) + err = utils.ValidatePoints(int(minpoints), err) + if err != nil { + utils.SendError(c, err) + return + } + config.Challenge.Metadata.MinPoints = uint(minpoints) + + err, _ = config.Challenge.Metadata.ValidateRequiredFields() + if err != nil { + utils.SendError(c, err) + return + } + + // Populating Challenge Env + config.Challenge.Env.AptDeps = strings.Split(c.PostForm("challenge_apt_deps"), ",") + portsStr := c.PostForm("challenge_ports") + ports := strings.Split(portsStr, ",") + config.Challenge.Env.Ports = make([]uint32, len(ports)) + for i, port := range ports { + portInt, err := strconv.ParseUint(port, 10, 32) + if err != nil { + err = fmt.Errorf("Port is not a valid port in %s: %s", portsStr, err) + utils.SendError(c, err) + return + } + config.Challenge.Env.Ports[i] = uint32(portInt) + } + if err = utils.CreateIfNotExistDir(core.BEAST_TEMP_DIR); err != nil { + if err := os.MkdirAll(core.BEAST_TEMP_DIR, 0755); err != nil { + err = fmt.Errorf("Could not create dir %s: %s", core.BEAST_TEMP_DIR, err) + utils.SendError(c, err) + return + } + } + + // Preparing challenge directory + challroot := filepath.Join(core.BEAST_TEMP_DIR, config.Challenge.Metadata.Name) + err = utils.CreateIfNotExistDir(challroot) + if err != nil { + err = fmt.Errorf("Could not create dir %s: %s", config.Challenge.Metadata.Name, err) + utils.SendError(c, err) + } + + files := c.Request.MultipartForm.File["challenge_setup_scripts"] + for _, fileHeader := range files { + config.Challenge.Env.SetupScripts = append(config.Challenge.Env.SetupScripts, fileHeader.Filename) + if err != nil { + utils.SendError(c, err) + return + } + err = c.SaveUploadedFile(fileHeader, challroot) + if utils.FileSaveError(c, err) != nil { + return + } + } + defaultport, err := strconv.ParseUint(c.PostForm("challenge_default_port"), 10, 32) + if err != nil { + err = fmt.Errorf("Port is not a valid port in %s: %s", portsStr, err) + utils.SendError(c, err) + return + } + config.Challenge.Env.DefaultPort = uint32(defaultport) + config.Challenge.Env.PortMappings = c.PostFormArray("challenge_port_mappings") + + // Preparing public directory + publicdir := filepath.Join(challroot, core.PUBLIC) + err = utils.CreateIfNotExistDir(publicdir) + if err != nil { + err = fmt.Errorf("Could not create dir %s: %s", core.PUBLIC, err) + utils.SendError(c, err) + return + } + // if yes, we unzip the public.zip file given else we simply store it in the public directory + publicopt := c.PostForm("public_zip_btn") + publiczip := utils.FileDownload(c, "public_zip", publicdir) + zipContextPath := filepath.Join(publicdir, publiczip) + if publicopt == "yes" { + _, err = manager.UnzipChallengeFolder(zipContextPath, publicdir) + if err != nil { + err = fmt.Errorf("The unzip process failed or the ZIP was unacceptable: %s", err) + utils.SendError(c, err) + return + } + } + config.Challenge.Env.StaticContentDir = core.PUBLIC + + // Preparing chall directory + challdir := filepath.Join(challroot, core.BEAST_CHALLENGE_DIR) + challzip := utils.FileDownload(c, "challenge_zip", challdir) + zipContextPath = filepath.Join(challdir, challzip) + _, err = manager.UnzipChallengeFolder(zipContextPath, challdir) + if err != nil { + err = fmt.Errorf("The unzip process failed or the ZIP was unacceptable: %s", err) + utils.SendError(c, err) + return + } + config.Challenge.Env.RunCmd = c.PostForm("challenge_run_cmd") + config.Challenge.Env.BaseImage = c.PostForm("challenge_base_image") + config.Challenge.Env.Entrypoint = c.PostForm("challenge_entrypoint") + config.Challenge.Env.WebRoot = c.PostForm("challenge_web_root") + config.Challenge.Env.ServicePath = c.PostForm("challenge_service_path") + config.Challenge.Env.DockerCtx = utils.FileDownload(c, "challenge_dockerfile", challroot) + config.Challenge.Env.Traffic = c.PostForm("challenge_traffic") + envVars := c.PostFormMap("challenge_env_vars") + for key, value := range envVars { + var EnvVar = cfg.EnvironmentVar{Key: key, Value: value} + config.Challenge.Env.EnvironmentVars = append(config.Challenge.Env.EnvironmentVars, EnvVar) + } + err = config.Challenge.Env.ValidateRequiredFields(config.Challenge.Metadata.Type, challroot) + if err != nil { + utils.SendError(c, err) + return + } + + //Populating Resources + nCPU, err := strconv.ParseInt(c.PostForm("resources_cpu"), 10, 64) + if err != nil || nCPU < 0 { + err = fmt.Errorf("CPU is not a valid number: %s", err) + utils.SendError(c, err) + return + } + config.Resources.CPUShares = nCPU + mem, err := strconv.ParseInt(c.PostForm("resources_memory"), 10, 64) + if err != nil || mem < 0 { + err = fmt.Errorf("Memory is not a valid number: %s", err) + utils.SendError(c, err) + return + } + config.Resources.Memory = mem + pidslimit, err := strconv.ParseInt(c.PostForm("resources_pidslimit"), 10, 64) + if err != nil || pidslimit < 0 { + err = fmt.Errorf("Pids limit is not a valid number: %s", err) + utils.SendError(c, err) + return + } + config.Resources.PidsLimit = pidslimit + config.Resources.ValidateRequiredFields() + nMaintainers, err := strconv.ParseInt(c.PostForm("maintainers_count"), 10, 64) + if err != nil || nMaintainers < 0 { + err = fmt.Errorf("Maintainers count is not a valid number: %s", err) + utils.SendError(c, err) + return + } + for i := 0; i < int(nMaintainers); i++ { + config.Maintainers = append(config.Maintainers, cfg.Author{ + Name: c.PostForm(fmt.Sprintf("maintainer_name_%d", i)), + Email: c.PostForm(fmt.Sprintf("maintainer_email_%d", i)), + SSHKey: c.PostForm(fmt.Sprintf("maintainer_ssh_key_%d", i)), + }) + } + +} diff --git a/api/router.go b/api/router.go index 13fb5732..1a765273 100644 --- a/api/router.go +++ b/api/router.go @@ -59,6 +59,7 @@ func initGinRouter() *gin.Engine { manageGroup.POST("/challenge/verify", verifyHandler) manageGroup.POST("/schedule/:action", manageScheduledAction) manageGroup.POST("/challenge/upload", manageUploadHandler) + manageGroup.POST("/challenge/configure", manageConifgureHandler) } // Status route group From f25badfdd32ef4782dbeca4b2f4498578f5f69ca Mon Sep 17 00:00:00 2001 From: vibhatsu Date: Thu, 26 Dec 2024 18:13:37 +0530 Subject: [PATCH 09/11] mod: toml generator implemented --- api/manage.go | 32 ++++++++++++++++++++++++++++---- utils/file.go | 20 ++++++++++++++++++++ 2 files changed, 48 insertions(+), 4 deletions(-) diff --git a/api/manage.go b/api/manage.go index f11cfb01..6d257542 100644 --- a/api/manage.go +++ b/api/manage.go @@ -516,20 +516,31 @@ func manageUploadHandler(c *gin.Context) { }) } +// Create challenge configuration file +// @Summary Create a challenge configuration file from the fields provided +// @Description Handles the creation of a challenge configuration file from the fields provided in the form along with the files uploaded. +// @Tags manage +// @Accept json +// @Produce json +// @Param ChallengeConfig +// @Success 200 {object} api.HTTPPlainResp +// @Failure 400 {object} json +// @Router /api/manage/challenge/configure [post] func manageConifgureHandler(c *gin.Context) { var config cfg.BeastChallengeConfig - // Populating Author + // Populating Author struct config.Author.Name = c.PostForm("author_name") config.Author.Email = c.PostForm("author_email") config.Author.SSHKey = c.PostForm("author_ssh_key") + // Validate Author struct err := config.Author.ValidateRequiredFields() if err != nil { utils.SendError(c, err) return } - // Populating Challenge Metadata + // Populating Challenge Metadata struct config.Challenge.Metadata.Name = c.PostForm("challenge_name") config.Challenge.Metadata.Type = c.PostForm("challenge_type") config.Challenge.Metadata.Flag = c.PostForm("challenge_flag") @@ -555,7 +566,7 @@ func manageConifgureHandler(c *gin.Context) { return } config.Challenge.Metadata.MinPoints = uint(minpoints) - + // Validate Challenge Metadata struct err, _ = config.Challenge.Metadata.ValidateRequiredFields() if err != nil { utils.SendError(c, err) @@ -699,5 +710,18 @@ func manageConifgureHandler(c *gin.Context) { SSHKey: c.PostForm(fmt.Sprintf("maintainer_ssh_key_%d", i)), }) } - + tomlfile := filepath.Join(challroot, core.CHALLENGE_CONFIG_FILE_NAME) + err = utils.WriteChallConfigFile(tomlfile, config) + if err != nil { + utils.SendError(c, err) + return + } + err = manager.CopyDir(challroot, filepath.Join(core.BEAST_GLOBAL_DIR, core.BEAST_UPLOADS_DIR, config.Challenge.Metadata.Name)) + if err != nil { + utils.SendError(c, err) + return + } + c.JSON(http.StatusOK, HTTPPlainResp{ + Message: "Challenge configuration file created", + }) } diff --git a/utils/file.go b/utils/file.go index 90457748..5b87559b 100644 --- a/utils/file.go +++ b/utils/file.go @@ -10,6 +10,7 @@ import ( "path/filepath" "strings" + "github.com/BurntSushi/toml" log "github.com/sirupsen/logrus" ) @@ -247,3 +248,22 @@ func GetCurrentDirectoryName(path string) string { directories := strings.Split(path, string(os.PathSeparator)) return directories[len(directories)-1] } + +// Generates a Toml +func WriteChallConfigFile(filePath string, data interface{}) error { + err := ValidateDirExists(filepath.Dir(filePath)) + if err != nil { + return err + } + file, err := os.Create(filePath) + if err != nil { + return fmt.Errorf("Error while creating file : %s", err) + } + defer file.Close() + + err = toml.NewEncoder(file).Encode(data) + if err != nil { + return fmt.Errorf("Error while writing to file : %s", err) + } + return nil +} From b1dd66b75a4acd7f49eb7e48a3ce482ce7cd9b55 Mon Sep 17 00:00:00 2001 From: vibhatsu Date: Thu, 26 Dec 2024 18:21:12 +0530 Subject: [PATCH 10/11] fix: minor bug fixes --- _examples/.static.beast.htpasswd | 3 --- core/config/challenge.go | 6 +++--- core/config/config.go | 6 +++--- core/manager/challenge.go | 2 +- 4 files changed, 7 insertions(+), 10 deletions(-) delete mode 100644 _examples/.static.beast.htpasswd diff --git a/_examples/.static.beast.htpasswd b/_examples/.static.beast.htpasswd deleted file mode 100644 index 76ddfc7d..00000000 --- a/_examples/.static.beast.htpasswd +++ /dev/null @@ -1,3 +0,0 @@ -# Password is fristonio_beast_static_pass -# Username fristonio -fristonio:$apr1$SmANaQLf$LSOCPJhgYsqs6ayf.QS3K. diff --git a/core/config/challenge.go b/core/config/challenge.go index 8c163118..b9560570 100644 --- a/core/config/challenge.go +++ b/core/config/challenge.go @@ -152,7 +152,7 @@ func (config *ChallengeMetadata) ValidateRequiredFields() (error, bool) { } log.Debugf("Available sidecars: %v", Cfg.AvailableSidecars) if !(utils.StringInSlice(config.Sidecar, Cfg.AvailableSidecars) || config.Sidecar == "") { - return fmt.Errorf("Sidecar provided is not an available sidecar."), false + return fmt.Errorf("Sidecar provided is not an available sidecar"), false } //Check if challenge points are within permissible range. if config.MaxPoints < config.MinPoints { @@ -434,7 +434,7 @@ func (config *ChallengeEnv) ValidateRequiredFields(challType string, challdir st // ServicePath must be relative. if config.ServicePath != "" { if filepath.IsAbs(config.ServicePath) { - return fmt.Errorf("For challenge type `services` service_path is a required variable, which should be relative path to executable.") + return fmt.Errorf("For challenge type `services` service_path is a required variable, which should be relative path to executable") } else if err := utils.ValidateFileExists(filepath.Join(challdir, config.ServicePath)); err != nil { // Skip this, we might create service later too. log.Warnf("Service path file %s does not exist", config.ServicePath) @@ -481,7 +481,7 @@ func (config *ChallengeEnv) ValidateRequiredFields(challType string, challdir st if config.DockerCtx == "" { return errors.New("Docker Context file not provided in docker-type challenge") } else if filepath.IsAbs(config.DockerCtx) { - return fmt.Errorf("For challenge type `docker-type` docker_context is a required variable, which should be relative path to docker context file.") + return fmt.Errorf("For challenge type `docker-type` docker_context is a required variable, which should be relative path to docker context file") } else if err := utils.ValidateFileExists(filepath.Join(challdir, config.DockerCtx)); err != nil { return fmt.Errorf("File : %s does not exist", config.DockerCtx) } diff --git a/core/config/config.go b/core/config/config.go index 11b79ddc..3a0da27d 100644 --- a/core/config/config.go +++ b/core/config/config.go @@ -118,7 +118,7 @@ func (config *BeastConfig) ValidateConfig() error { if config.BeastScriptsDir == "" { defaultBeastScriptDir := filepath.Join(core.BEAST_GLOBAL_DIR, core.BEAST_SCRIPTS_DIR) - log.Warn("No scripts directory provided for beast, using default : %s", defaultBeastScriptDir) + log.Warnf("No scripts directory provided for beast, using default : %s", defaultBeastScriptDir) config.BeastScriptsDir = defaultBeastScriptDir } else { err := utils.CreateIfNotExistDir(config.BeastScriptsDir) @@ -209,11 +209,11 @@ func (config *GitRemote) ValidateGitConfig() error { } if !gitUrlRegexp.MatchString(config.Url) { - return errors.New("The provided git url is not valid.") + return errors.New("The provided git url is not valid") } if config.Branch == "" { - log.Warn("Branch for git remote not provided, using %s", core.GIT_REMOTE_DEFAULT_BRANCH) + log.Warnf("Branch for git remote not provided, using %s", core.GIT_REMOTE_DEFAULT_BRANCH) config.Branch = core.GIT_REMOTE_DEFAULT_BRANCH } diff --git a/core/manager/challenge.go b/core/manager/challenge.go index d0bb2b5e..8a736787 100644 --- a/core/manager/challenge.go +++ b/core/manager/challenge.go @@ -171,7 +171,7 @@ func GetDeployWork(challengeName string) (*wpool.Task, error) { if coreUtils.IsContainerIdValid(challenge.ContainerId) { containers, err := cr.SearchContainerByFilter(map[string]string{"id": challenge.ContainerId}) if err != nil { - log.Error("Error while searching for container with id %s", challenge.ContainerId) + log.Errorf("Error while searching for container with id %s", challenge.ContainerId) return nil, errors.New("CONTAINER RUNTIME ERROR") } From d05f1d06b0782b1a5824c78edd3b7ed68cf653c8 Mon Sep 17 00:00:00 2001 From: vibhatsu Date: Thu, 23 Jan 2025 11:31:20 +0530 Subject: [PATCH 11/11] add: form for testing --- config-gen.html | 292 ++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 292 insertions(+) create mode 100644 config-gen.html diff --git a/config-gen.html b/config-gen.html new file mode 100644 index 00000000..9f50ffd2 --- /dev/null +++ b/config-gen.html @@ -0,0 +1,292 @@ + + + + + + Create New Challenge + + + + + + +

Create New Challenge

+ +
+ + +
+

Author Information

+
+ + +
+
+ + +
+
+ + +
+
+ + +
+

Challenge Metadata

+
+ + +
+
+ + +
+
+ + +
+
+ + +
+
+ + +
+
+ + +
+
+ + +
+
+ + +
+
+ + +
+
+ + +
+
+ + +
+
+ + +
+

Challenge Environment

+
+ + +
+
+ + +
+
+ + +
+
+ + +
+
+ + +
+
+ + + + + +
+
+ + +
+
+ + +
+
+ + +
+
+ + +
+
+ + +
+
+ + +
+
+ + +
+
+ + +
+
+ + +
+
+ +
+ + +
+ +
+
+ + +
+

Resources

+
+ + +
+
+ + +
+
+ + +
+
+ + +
+

Maintainers

+
+ + +
+
+ +
+ + +
+ +
+ +
+ + + + + + \ No newline at end of file