diff --git a/.vscode/extensions.json b/.vscode/extensions.json new file mode 100644 index 00000000..f2d9b112 --- /dev/null +++ b/.vscode/extensions.json @@ -0,0 +1,5 @@ +{ + "recommendations": [ + "foxundermoon.shell-format" + ] +} \ No newline at end of file diff --git a/challenges-sample/the-varsity/challenge-checker/app.py b/challenges-sample/the-varsity/challenge-checker/app.py index d778f970..5eab2b68 100644 --- a/challenges-sample/the-varsity/challenge-checker/app.py +++ b/challenges-sample/the-varsity/challenge-checker/app.py @@ -158,7 +158,8 @@ def check_route(): result["status"] = status except Exception as e: logging.error(f"Error checking challenge: {str(e)}") - result["error"] = str(e) + # result["error"] = str(e) + result["status"] = 0 results["data"].append(result) return jsonify(results) diff --git a/challenges-sample/the-varsity/challenge-checker/chall_checker.py b/challenges-sample/the-varsity/challenge-checker/chall_checker.py index dfb402ba..8cebe29a 100644 --- a/challenges-sample/the-varsity/challenge-checker/chall_checker.py +++ b/challenges-sample/the-varsity/challenge-checker/chall_checker.py @@ -6,7 +6,7 @@ def check_challenge(url): result = {} register_response = requests.post( - url + "register", json={"username": "something", "voucher": ""} + url + "register", json={"username": "something", "voucher": ""}, timeout=2 ) # Check if the request was successful (status code 200) @@ -31,7 +31,7 @@ def check_challenge(url): article_data = {"issue": "5"} article_response = requests.post( - url + "article", json=article_data, headers=article_headers + url + "article", json=article_data, headers=article_headers, timeout=2 ) if article_response.status_code == 200: diff --git a/challenges/.gitkeep b/challenges/.gitkeep deleted file mode 100644 index e69de29b..00000000 diff --git a/config.sample.toml b/config.sample.toml index da211480..c436a713 100644 --- a/config.sample.toml +++ b/config.sample.toml @@ -1,6 +1,6 @@ -kubeconfig = "" +kubeconfig = "/home/darkhood148/.kube/config" kubehost = "0.0.0.0" -backendurl = "http://10.25.1.15:15528" +backendurl = "http://10.25.1.15:3000" kubenamespace = "default" timeout = 20 # in seconds @@ -19,7 +19,7 @@ templated_manifests = [ [services.api] host = "0.0.0.0" -port = 15528 +port = 3000 [teamvm] teampodname = "katana-team-master-pod" diff --git a/docs/themes/hugo-geekdoc b/docs/themes/hugo-geekdoc new file mode 160000 index 00000000..c420c637 --- /dev/null +++ b/docs/themes/hugo-geekdoc @@ -0,0 +1 @@ +Subproject commit c420c637eb43b091f972b61340dcb45422e650d3 diff --git a/go.mod b/go.mod index 13ded235..d6095dd7 100644 --- a/go.mod +++ b/go.mod @@ -9,6 +9,7 @@ require ( github.com/go-sql-driver/mysql v1.7.1 github.com/gofiber/fiber/v2 v2.48.0 github.com/golang-jwt/jwt v3.2.2+incompatible + github.com/golang-jwt/jwt/v4 v4.5.0 github.com/mholt/archiver/v3 v3.5.1 github.com/op/go-logging v0.0.0-20160315200505-970db520ece7 github.com/sirupsen/logrus v1.9.3 diff --git a/go.sum b/go.sum index 96b5b9ca..6cfb999f 100644 --- a/go.sum +++ b/go.sum @@ -89,6 +89,8 @@ github.com/gogo/protobuf v1.3.2/go.mod h1:P1XiOD3dCwIKUDQYPy72D8LYyHL2YPYrpS2s69 github.com/golang-jwt/jwt v3.2.2+incompatible h1:IfV12K8xAKAnZqdXVzCZ+TOjboZ2keLg81eXfW3O+oY= github.com/golang-jwt/jwt v3.2.2+incompatible/go.mod h1:8pz2t5EyA70fFQQSrl6XZXzqecmYZeUEB8OUGHkxJ+I= github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q= +github.com/golang-jwt/jwt/v4 v4.5.0 h1:7cYmW1XlMY7h7ii7UhUyChSgS5wUJEnm9uZVTGqOWzg= +github.com/golang-jwt/jwt/v4 v4.5.0/go.mod h1:m21LjoU+eqJr34lmDMbreY2eSTRJ1cv77w39/MY0Ch0= github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da h1:oI5xCqsCo564l8iNU+DwB5epxmsaqB+rhGL0m5jtYqE= github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= github.com/golang/mock v1.1.1/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A= diff --git a/lib/deployment/deployment.go b/lib/deployment/deployment.go index c4f8aaee..2df81441 100644 --- a/lib/deployment/deployment.go +++ b/lib/deployment/deployment.go @@ -115,8 +115,10 @@ func DeployCluster(kubeconfig *rest.Config, kubeclientset *kubernetes.Clientset) clientset, _ := utils.GetKubeClient() - nodes, _ := utils.GetNodes(clientset) - + nodes, err := utils.GetNodes(clientset) + if err != nil { + log.Println(err) + } deploymentConfig.NodeAffinityValue = nodes[0].Name for _, m := range clusterConfig.TemplatedManifests { diff --git a/lib/mongo/connection.go b/lib/mongo/connection.go index c58ca177..6af3754a 100644 --- a/lib/mongo/connection.go +++ b/lib/mongo/connection.go @@ -20,17 +20,13 @@ var link *mongo.Database func setupAdmin() error { adminUser := configs.AdminConfig - pwd, err := utils.HashPassword(adminUser.Password) - if err != nil { - return fmt.Errorf("cannot hash password: %w", err) - } - + pwd := utils.SHA256(adminUser.Password) admin := types.AdminUser{ Username: adminUser.Username, Password: pwd, } - if _, err = AddAdmin(context.Background(), admin); err != nil { + if _, err := AddAdmin(context.Background(), admin); err != nil { return fmt.Errorf("cannot add admin: %w", err) } else { log.Printf("admin privileges have been given to username: %s", admin.Username) diff --git a/lib/mongo/create.go b/lib/mongo/create.go index f8e18926..34703578 100644 --- a/lib/mongo/create.go +++ b/lib/mongo/create.go @@ -4,6 +4,7 @@ import ( "context" "github.com/sdslabs/katana/types" + "go.mongodb.org/mongo-driver/bson" ) func InsertOne(ctx context.Context, collectionName string, data interface{}) (interface{}, error) { @@ -31,3 +32,13 @@ func CreateTeams(teams []interface{}) (interface{}, error) { func AddAdmin(ctx context.Context, admin types.AdminUser) (interface{}, error) { return InsertOne(ctx, AdminCollection, admin) } + +func AddChallenge(challenge types.Challenge, teamName string) error { + teamFilter := bson.M{"username": teamName} + update := bson.M{"$push": bson.M{"challenges": challenge}} + _, err := link.Collection(TeamsCollection).UpdateOne(context.TODO(), teamFilter, update) + if err != nil { + return err + } + return nil +} diff --git a/lib/mongo/update.go b/lib/mongo/update.go index 53dbe472..5fd6082c 100644 --- a/lib/mongo/update.go +++ b/lib/mongo/update.go @@ -11,7 +11,3 @@ func UpdateOne(ctx context.Context, collectionName string, filter bson.M, data i collection := link.Collection(collectionName) return collection.FindOneAndUpdate(ctx, filter, bson.M{"$set": data}, option).Err() } - -func UpsertOne(ctx context.Context, collectionName string, filter bson.M, data interface{}) error { - return UpdateOne(ctx, collectionName, filter, data, options.FindOneAndUpdate().SetUpsert(true)) -} diff --git a/lib/mysql/connection.go b/lib/mysql/connection.go index 953206d5..6b8e6be4 100644 --- a/lib/mysql/connection.go +++ b/lib/mysql/connection.go @@ -41,7 +41,7 @@ func setup() error { func setupGogs() error { if err := CreateDatabase(gogsDatabase); err != nil { - fmt.Errorf("cannot create database: %w", err) + log.Println("cannot create database: %w", err) } if err := CreateGogsAdmin(configs.AdminConfig.Username, configs.AdminConfig.Password); err != nil { fmt.Errorf("cannot create gogs admin: %w", err) diff --git a/lib/retrieve_data/jsonSaver.go b/lib/retrieve_data/jsonSaver.go new file mode 100644 index 00000000..cafc358b --- /dev/null +++ b/lib/retrieve_data/jsonSaver.go @@ -0,0 +1,71 @@ +package retrieve_data + +import ( + "context" + "encoding/json" + "fmt" + "os" + "time" + + "github.com/sdslabs/katana/lib/mongo" + "github.com/sdslabs/katana/lib/utils" + "go.mongodb.org/mongo-driver/bson" +) + +var ticker *time.Ticker +var tick int + +func saveJSON() { + for range ticker.C { + data := mongo.FetchDocs(context.Background(), "teams", bson.M{}) + path := fmt.Sprintf("./json_data/data-tick-%d.json", tick) + jsonData, err := convertBSONArrayToJSONArray(data) + if err != nil { + fmt.Println(err) + return + } + storeJSONToFile(jsonData, path) + tick++ + } +} + +func StartSaving() { + ticker = utils.GetTicker() + utils.CreateDirectoryIfNotExists("json_data") + go saveJSON() +} + +func convertBSONArrayToJSONArray(bsonArray []bson.M) ([]byte, error) { + var jsonArray []map[string]interface{} + + for _, bsonDoc := range bsonArray { + delete(bsonDoc, "publicKey") + delete(bsonDoc, "password") + + jsonArray = append(jsonArray, bsonDoc) + } + + jsonData, err := json.Marshal(jsonArray) + if err != nil { + return nil, fmt.Errorf("failed to marshal JSON array: %w", err) + } + + return jsonData, nil +} + +func storeJSONToFile(jsonData []byte, filePath string) error { + jsonMap := map[string]interface{}{"data": json.RawMessage(jsonData)} + + finalJSONData, err := json.MarshalIndent(jsonMap, "", " ") + if err != nil { + return fmt.Errorf("failed to marshal final JSON: %w", err) + } + + err = os.WriteFile(filePath, finalJSONData, 0644) + if err != nil { + return fmt.Errorf("failed to write JSON to file: %w", err) + } + + fmt.Printf("JSON data stored in file: %s\n", filePath) + return nil +} diff --git a/lib/utils/crypto.go b/lib/utils/crypto.go index 12e3b770..eaf6cf2f 100644 --- a/lib/utils/crypto.go +++ b/lib/utils/crypto.go @@ -108,10 +108,13 @@ func HashPassword(password string) (string, error) { } func CompareHashWithPassword(hashedPassword, password string) bool { - hash := []byte(hashedPassword) - pass := []byte(password) - err := bcrypt.CompareHashAndPassword(hash, pass) - return err == nil + + pass := SHA256(password) + if pass == hashedPassword { + return true; + }else{ + return false + } } // EncodePassword encodes password using PBKDF2 SHA256 with given salt. diff --git a/lib/utils/crypto_test.go b/lib/utils/crypto_test.go index 98df29ee..7ebf8805 100644 --- a/lib/utils/crypto_test.go +++ b/lib/utils/crypto_test.go @@ -104,23 +104,20 @@ func TestHashPassword(t *testing.T) { func TestCompareHashWithPassword(t *testing.T) { // Generate a hash from a password password := "myStrongPassword123" - hashedPassword, err := bcrypt.GenerateFromPassword([]byte(password), bcrypt.MinCost) - if err != nil { - t.Fatalf("Error generating hash: %v", err) - } + hashedPassword := SHA256(password) // Test case 1: Hash matches the original password - if !CompareHashWithPassword(string(hashedPassword), password) { + if !CompareHashWithPassword(hashedPassword, password) { t.Error("Hash and password should match") } // Test case 2: Hash does not match the original password - if CompareHashWithPassword(string(hashedPassword), "wrongPassword") { + if CompareHashWithPassword(hashedPassword, "wrongPassword") { t.Error("Hash and wrong password should not match") } // Test case 3: Empty password - if CompareHashWithPassword(string(hashedPassword), "") { + if CompareHashWithPassword(hashedPassword, "") { t.Error("Hash and empty password should not match") } } diff --git a/lib/utils/kube.go b/lib/utils/kube.go index d5b1ebec..96e425c6 100644 --- a/lib/utils/kube.go +++ b/lib/utils/kube.go @@ -7,6 +7,7 @@ import ( "io" "io/ioutil" "log" + "net/http" "os" "path/filepath" "strings" @@ -150,7 +151,7 @@ func CopyFromPod(podName string, containerName string, pathInPod string, localFi TTY: false, }, scheme.ParameterCodec) - exec, err := remotecommand.NewSPDYExecutor(config, "POST", req.URL()) + exec, err := remotecommand.NewSPDYExecutor(config, http.MethodPost, req.URL()) if err != nil { log.Printf("Error creating executor: %s\n", err) return err diff --git a/lib/utils/os.go b/lib/utils/os.go index 5d8b4c18..f6333168 100644 --- a/lib/utils/os.go +++ b/lib/utils/os.go @@ -70,6 +70,21 @@ func RunCommand(cmd string) error { return nil } +func CreateDirectoryIfNotExists(dirPath string) error { + if _, err := os.Stat(dirPath); !os.IsNotExist(err) { + if err := os.RemoveAll(dirPath); err != nil { + return fmt.Errorf("failed to delete existing directory: %w", err) + } + fmt.Printf("Directory '%s' deleted.\n", dirPath) + } + + if err := os.MkdirAll(dirPath, 0755); err != nil { + return fmt.Errorf("failed to create directory: %w", err) + } + fmt.Printf("Directory '%s' created.\n", dirPath) + return nil +} + func GetKatanaRootPath() (string, error) { katanaDir, err := os.Getwd() if err != nil { diff --git a/lib/utils/ticker.go b/lib/utils/ticker.go new file mode 100644 index 00000000..cd4ac50c --- /dev/null +++ b/lib/utils/ticker.go @@ -0,0 +1,31 @@ +package utils + +import ( + "time" +) + +var Ticker *time.Ticker + +func InitTicker(duration time.Duration) { + Ticker = time.NewTicker(duration) +} + +func GetTicker() *time.Ticker { + return Ticker +} + +func SetTicker(ticker *time.Ticker) { + Ticker = ticker +} + +func StopTicker() { + Ticker.Stop() +} + +func ResetTicker(duration time.Duration) { + Ticker.Reset(duration) +} + +func GetRemainingTimeBeforeNextTick() time.Duration { + return time.Until(<-Ticker.C) +} diff --git a/services/challengedeployerservice/controller.go b/services/challengedeployerservice/controller.go index add27bce..462c8db3 100644 --- a/services/challengedeployerservice/controller.go +++ b/services/challengedeployerservice/controller.go @@ -15,12 +15,50 @@ import ( archiver "github.com/mholt/archiver/v3" g "github.com/sdslabs/katana/configs" "github.com/sdslabs/katana/lib/deployment" + "github.com/sdslabs/katana/lib/mongo" "github.com/sdslabs/katana/lib/utils" "github.com/sdslabs/katana/services/infrasetservice" "github.com/sdslabs/katana/types" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "github.com/BurntSushi/toml" ) +type ChallengeToml struct { + Challenge Challenge `toml:"challenge"` +} + +type Challenge struct { + Author Author `toml:"author"` + Metadata Metadata `toml:"metadata"` + Env Env `toml:"env"` +} + +type Author struct { + Name string `toml:"name"` + Email string `toml:"email"` +} + +type Metadata struct { + Name string `toml:"name"` + Flag string `toml:"flag"` + Description string `toml:"description"` + Type string `toml:"type"` + Points int `toml:"points"` +} + +type Env struct { + PortMappings []string `toml:"port_mappings"` +} + +func LoadConfiguration(configFile string) ChallengeToml { + var config ChallengeToml + if _, err := toml.DecodeFile(configFile, &config); err != nil { + log.Fatal(err) + return ChallengeToml{} + } + return config +} + func Deploy(c *fiber.Ctx) error { patch := false replicas := int32(1) @@ -278,6 +316,9 @@ func DeployChallenge(c *fiber.Ctx) error { return c.SendString("Error in unarchiving") } + data := LoadConfiguration("./challenges/"+folderName+"/config.toml") + challengeType = data.Challenge.Metadata.Type + //Update challenge path to get dockerfile utils.BuildDockerImage(folderName, challengePath+"/"+folderName) @@ -288,8 +329,25 @@ func DeployChallenge(c *fiber.Ctx) error { for i := 0; i < int(numberOfTeams); i++ { log.Println("-----------Deploying challenge for team: " + strconv.Itoa(i) + " --------") teamName := "katana-team-" + strconv.Itoa(i) + challenge := types.Challenge{ + ChallengeName: folderName, + Uptime: 0, + Attacks: 0, + Defences: 0, + Points: data.Challenge.Metadata.Points, + Flag: data.Challenge.Metadata.Flag, + } + err := mongo.AddChallenge(challenge, teamName) + if err != nil { + fmt.Println("Error in adding challenge to mongo") + log.Println(err) + } deployment.DeployChallengeToCluster(folderName, teamName, patch, replicas) - url, err := createServiceForChallenge(folderName, teamName, 3000, i) + port, err := strconv.ParseInt(strings.Split(data.Challenge.Env.PortMappings[0], ":")[1], 10, 32) + if err != nil { + log.Println("Error occured") + } + url, err := createServiceForChallenge(folderName, teamName, int32(port), i) if err != nil { res = append(res, []string{teamName, err.Error()}) } else { @@ -319,59 +377,64 @@ func ChallengeUpdate(c *fiber.Ctx) error { return err } - dir := p.Repository.FullName - s := strings.Split(dir, "/") - challengeName := s[1] - teamName := s[0] - namespace := teamName + "-ns" - log.Println("Challenge update request received for", challengeName, "by", teamName) - repo, err := git.PlainOpen("teams/" + dir) - if err != nil { - log.Println(err) - } - - auth := &http.BasicAuth{ - Username: g.AdminConfig.Username, - Password: g.AdminConfig.Password, - } - - worktree, err := repo.Worktree() - worktree.Pull(&git.PullOptions{ - RemoteName: "origin", - Auth: auth, - }) - - if err != nil { - log.Println("Error pulling changes:", err) - } - katanaDir, err := utils.GetKatanaRootPath() - imageName := strings.Replace(dir, "/", "-", -1) + if p.Ref != "refs/heads/master" { + return c.SendString("Push not on master branch. Ignoring") + } else { - log.Println("Pull successful for", teamName, ". Building image...") - firstPatch := !utils.DockerImageExists(imageName) - utils.BuildDockerImage(imageName, katanaDir+"/teams/"+dir) + dir := p.Repository.FullName + s := strings.Split(dir, "/") + challengeName := s[1] + teamName := s[0] + namespace := teamName + "-ns" + log.Println("Challenge update request received for", challengeName, "by", teamName) + repo, err := git.PlainOpen("teams/" + dir) + if err != nil { + log.Println(err) + } - if firstPatch { - log.Println("First Patch for", teamName) - deployment.DeployChallengeToCluster(challengeName, teamName, patch, replicas) - } else { - log.Println("Not the first patch for", teamName, ". Simply deploying the image...") - labelSelector := metav1.LabelSelector{ - MatchLabels: map[string]string{ - "app": challengeName, - }, + auth := &http.BasicAuth{ + Username: g.AdminConfig.Username, + Password: g.AdminConfig.Password, } - // Delete the challenge pod - err = client.CoreV1().Pods(namespace).DeleteCollection(context.Background(), metav1.DeleteOptions{}, metav1.ListOptions{ - LabelSelector: metav1.FormatLabelSelector(&labelSelector), + + worktree, err := repo.Worktree() + worktree.Pull(&git.PullOptions{ + RemoteName: "origin", + Auth: auth, }) + if err != nil { - log.Println("Error") - log.Println(err) + log.Println("Error pulling changes:", err) + } + + imageName := strings.Replace(dir, "/", "-", -1) + + log.Println("Pull successful for", teamName, ". Building image...") + firstPatch := !utils.DockerImageExists(imageName) + utils.BuildDockerImage(imageName, "./teams/"+dir) + + if firstPatch { + log.Println("First Patch for", teamName) + deployment.DeployChallengeToCluster(challengeName, teamName, patch, replicas) + } else { + log.Println("Not the first patch for", teamName, ". Simply deploying the image...") + labelSelector := metav1.LabelSelector{ + MatchLabels: map[string]string{ + "app": challengeName, + }, + } + // Delete the challenge pod + err = client.CoreV1().Pods(namespace).DeleteCollection(context.Background(), metav1.DeleteOptions{}, metav1.ListOptions{ + LabelSelector: metav1.FormatLabelSelector(&labelSelector), + }) + if err != nil { + log.Println("Error") + log.Println(err) + } } + log.Println("Image built for", teamName) + return c.SendString("Challenge updated") } - log.Println("Image built for", teamName) - return c.SendString("Challenge updated") } func DeleteChallenge(c *fiber.Ctx) error { diff --git a/services/flaghandlerservice/controller.go b/services/flaghandlerservice/controller.go deleted file mode 100644 index 11aad807..00000000 --- a/services/flaghandlerservice/controller.go +++ /dev/null @@ -1 +0,0 @@ -package flaghandlerservice diff --git a/services/flaghandlerservice/server.go b/services/flaghandlerservice/server.go new file mode 100644 index 00000000..8352f089 --- /dev/null +++ b/services/flaghandlerservice/server.go @@ -0,0 +1,45 @@ +package flaghandlerservice + +import ( + "context" + "fmt" + + utils "github.com/sdslabs/katana/lib/utils" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" +) + +var namespace string = "katana" +var podName string = "kashira" +var containerName string = "kashira" + +func SendFlagCheckerAndUpdaterToKashira(localFilePath string) { + pathInPod := "/opt/kashira/tmp" + utils.CopyIntoPod(podName, containerName, pathInPod, localFilePath, namespace) +} + +func Server() { + // Get the ticker + ticker := utils.GetTicker() + + // Get Kubernetes client + client, err := utils.GetKubeClient() + if err != nil { + fmt.Println(err) + } + + go func() { + for range ticker.C { + pod, err := client.AppsV1().StatefulSets(namespace).Get(context.TODO(), podName, metav1.GetOptions{}) + if err != nil { + fmt.Println(err) + return + } + pod.Annotations["tick"] = "true" + _, err = client.AppsV1().StatefulSets(namespace).Update(context.TODO(), pod, metav1.UpdateOptions{}) + if err != nil { + fmt.Println(err) + return + } + } + }() +} diff --git a/services/infrasetservice/helper.go b/services/infrasetservice/helper.go index 71b344ce..8c8abdee 100644 --- a/services/infrasetservice/helper.go +++ b/services/infrasetservice/helper.go @@ -47,10 +47,7 @@ func createTeamCredentials(teamNumber int) (string, types.CTFTeam) { podName := teamlabels + "-team-master-pod-0" gogs := utils.GetKatanaLoadbalancer() + ":3000" pwd := utils.RandomString(configs.SSHProviderConfig.PasswordLen) - hashed, err := utils.HashPassword(pwd) - if err != nil { - log.Fatal(err) - } + hashed := utils.SHA256(pwd) podNamespace := "katana-team-" + fmt.Sprint(teamNumber) // start watching for container events go envVariables(gogs, pwd, podNamespace) @@ -59,6 +56,8 @@ func createTeamCredentials(teamNumber int) (string, types.CTFTeam) { Name: podNamespace, PodName: podName, Password: hashed, + Score: 0, + Challenges: []types.Challenge{}, } mysql.CreateGogsUser(team.Name, pwd) mysql.CreateAccessToken(team.Name, pwd) diff --git a/services/leaderboardservice/controller.go b/services/leaderboardservice/controller.go new file mode 100644 index 00000000..63a2118d --- /dev/null +++ b/services/leaderboardservice/controller.go @@ -0,0 +1,107 @@ +package leaderboardservice + +import ( + "bytes" + "context" + "encoding/json" + "fmt" + "os" + "sort" + "strconv" + "text/tabwriter" + + "github.com/gofiber/fiber/v2" + "github.com/sdslabs/katana/lib/mongo" + "github.com/sdslabs/katana/types" + "go.mongodb.org/mongo-driver/bson" +) + +func Leaderboard(c *fiber.Ctx, str string) error { + var sortedData []types.CTFTeam + if str == "" { + data := mongo.FetchDocs(context.Background(), "teams", bson.M{}) + sort.Slice(data, func(i, j int) bool { + return data[i]["score"].(int32) > data[j]["score"].(int32) + }) + for _, team := range data { + var temp types.CTFTeam + bsonBytes, _ := bson.Marshal(team) + bson.Unmarshal(bsonBytes, &temp) + sortedData = append(sortedData, temp) + } + } else { + tick, err := strconv.ParseInt(str, 10, 32) + if err != nil { + return c.SendString(fmt.Sprintf("%s is not an integer", str)) + } + filename := fmt.Sprintf("./json_data/data-tick-%d.json", tick) + if _, err := os.Stat(filename); err != nil { + return c.SendString("Tick hasn't occured yet. Please ask for a tick that has occured") + } else { + content, err := os.ReadFile(filename) + if err != nil { + return c.SendString("Error while reading file") + } + type temp struct { + Data []types.CTFTeam `json:"data"` + } + var tmp temp + if err := json.Unmarshal(content, &tmp); err != nil { + return c.SendString("Error in unmarshaling json") + } + sortedData = append(sortedData, tmp.Data...) + sort.Slice(sortedData, func(i, j int) bool { + return sortedData[i].Score > sortedData[j].Score + }) + } + } + var challenges []string + var teams []string + for _, challenge := range sortedData[0].Challenges { + challenges = append(challenges, challenge.ChallengeName) + } + for _, team := range sortedData { + teams = append(teams, team.Name) + } + columns := len(challenges) + 2 + rows := len(teams) + 1 + tempDisplayData := make([][]string, rows) + for i := range tempDisplayData { + tempDisplayData[i] = make([]string, columns) + } + for i := 0; i < columns-2; i++ { + tempDisplayData[0][i+1] = challenges[i] + } + for i := 0; i < rows-1; i++ { + tempDisplayData[i+1][0] = teams[i] + } + tempDisplayData[0][columns-1] = "Score" + + for i := 0; i < rows-1; i++ { + for j := 0; j < columns-2; j++ { + tempDisplayData[i+1][j+1] = fmt.Sprintf("Attacks: %d, Defences: %d, Uptime %f", sortedData[i].Challenges[j].Attacks, sortedData[i].Challenges[j].Defences, sortedData[i].Challenges[j].Uptime) + } + tempDisplayData[i+1][columns-1] = strconv.Itoa(sortedData[i].Score) + } + return c.SendString(createMatrixString(tempDisplayData)) +} + +func createMatrixString(matrix [][]string) string { + var result string + buf := new(bytes.Buffer) + defer buf.Reset() + w := tabwriter.NewWriter(buf, 0, 0, 2, ' ', 0) + + for _, row := range matrix { + for _, value := range row { + fmt.Fprintf(w, "%s\t", value) + } + fmt.Fprintln(w, "") + } + + w.Flush() + + result = buf.String() + + return result +} diff --git a/services/master/controllers/ticker.go b/services/master/controllers/ticker.go new file mode 100644 index 00000000..6693372b --- /dev/null +++ b/services/master/controllers/ticker.go @@ -0,0 +1,13 @@ +package controllers + +import ( + "github.com/gofiber/fiber/v2" + "github.com/sdslabs/katana/lib/retrieve_data" + t "github.com/sdslabs/katana/services/flaghandlerservice" +) + +func StartTicker(c *fiber.Ctx) error { + t.Server() + retrieve_data.StartSaving() + return c.SendString("Ticker Started") +} diff --git a/services/master/server.go b/services/master/server.go index bb9fad36..f2dd09c3 100644 --- a/services/master/server.go +++ b/services/master/server.go @@ -8,12 +8,17 @@ import ( "github.com/gofiber/fiber/v2" "github.com/gofiber/fiber/v2/middleware/cors" cfg "github.com/sdslabs/katana/configs" + "github.com/sdslabs/katana/lib/utils" challengeDeployerService "github.com/sdslabs/katana/services/challengedeployerservice" infraSetService "github.com/sdslabs/katana/services/infrasetservice" + "github.com/sdslabs/katana/services/leaderboardservice" c "github.com/sdslabs/katana/services/master/controllers" ) func Server() error { + + utils.InitTicker(10 * time.Second) + fiberConfig := fiber.Config{ ReadTimeout: 5 * time.Second, WriteTimeout: 30 * time.Second, @@ -55,5 +60,11 @@ func Server() error { admin.Get("/deleteChallenge/:chall_name", challengeDeployerService.DeleteChallenge) log.Printf("Listening on %s:%d\n", cfg.APIConfig.Host, cfg.APIConfig.Port) + admin.Get("/startTicker", c.StartTicker) + admin.Get("/leaderboard/:x?", func(c *fiber.Ctx) error { + x := c.Params("x") + leaderboardservice.Leaderboard(c, x) + return nil + }) return app.Listen(fmt.Sprintf("%s:%d", cfg.APIConfig.Host, cfg.APIConfig.Port)) } diff --git a/types/mongo.go b/types/mongo.go index 0925ac88..25fd9474 100644 --- a/types/mongo.go +++ b/types/mongo.go @@ -6,9 +6,20 @@ type AdminUser struct { } type CTFTeam struct { - Index int `json:"id" bson:"id" binding:"required"` - Name string `json:"name" bson:"username" binding:"required"` - PodName string `json:"podname" bson:"podname" binding:"required"` - Password string `json:"password" bson:"password" binding:"required"` - PublicKey string `json:"publicKey" bson:"publicKey" binding:"required"` // TODO : initialize + Index int `json:"id" bson:"id" binding:"required"` + Name string `json:"username" bson:"username" binding:"required"` + PodName string `json:"podname" bson:"podname" binding:"required"` + Password string `json:"password" bson:"password" binding:"required"` + Challenges []Challenge `json:"challenges"` + Score int `json:"score"` + PublicKey string `json:"publicKey" bson:"publicKey" binding:"required"` +} + +type Challenge struct { + ChallengeName string `json:"challengename"` + Uptime float64 `json:"uptime"` + Attacks int `json:"attacks"` + Defences int `json:"defences"` + Flag string `json:"flag"` + Points int `json:"points"` }