Skip to content
Open
Show file tree
Hide file tree
Changes from 6 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions _examples/service/beast.toml
Original file line number Diff line number Diff line change
Expand Up @@ -24,3 +24,6 @@ apt_deps = ["gcc", "socat"]
setup_scripts = ["setup.sh"]
service_path = "pwn"
ports = [10004]

[resource]
memory_limit = 6710886
3 changes: 3 additions & 0 deletions _examples/simple/beast.toml
Original file line number Diff line number Diff line change
Expand Up @@ -22,3 +22,6 @@ apt_deps = ["gcc", "socat"]
setup_scripts = ["setup.sh"]
run_cmd = "socat tcp-l:10005,fork,reuseaddr exec:./pwn"
ports = [10005]

[resource]
memory_limit = 67108864
3 changes: 3 additions & 0 deletions _examples/web-php/beast.toml
Original file line number Diff line number Diff line change
Expand Up @@ -23,3 +23,6 @@ points = 20
ports = [10002]
web_root = "challenge"
default_port = 10002

[resource]
memory_limit = 6710886
5 changes: 4 additions & 1 deletion api/router.go
Original file line number Diff line number Diff line change
Expand Up @@ -50,7 +50,8 @@ func initGinRouter() *gin.Engine {
router.GET("/api/info/download", serveAssets)

// API routes group
apiGroup := router.Group("/api", authorize)
apiGroup := router.Group("/api",

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The "authorize" middleware has been removed from this entire group. Please fix it. Also, we only want admins to be able to access the healthcheck route, so move this endpoint to an appropriate group.

)
{
// Deploy route group
manageGroup := apiGroup.Group("/manage", managerAuthorize)
Expand All @@ -74,6 +75,7 @@ func initGinRouter() *gin.Engine {
statusGroup.GET("/challenge/:name", challengeStatusHandler)
statusGroup.GET("/all", statusHandler)
statusGroup.GET("/all/:filter", statusHandler)
statusGroup.GET("/health/:name", ChallengeHealthHandler)

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why is this route unauthorized?

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

the authorise middleware is there in the whole api group on line 53. Do i need to create a separate auth for status group?

}

// Info route group
Expand All @@ -92,6 +94,7 @@ func initGinRouter() *gin.Engine {
infoGroup.GET("/tags", tagHandler)
infoGroup.GET("/hint/:hintID", hintHandler)
infoGroup.POST("/hint/:hintID", hintHandler)

}

// Notification route group
Expand Down
45 changes: 44 additions & 1 deletion api/status.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ import (
"fmt"
"net/http"
"time"

"github.com/sdslabs/beastv4/pkg/cr"
"github.com/gin-gonic/gin"
"github.com/sdslabs/beastv4/core"
"github.com/sdslabs/beastv4/core/database"
Expand Down Expand Up @@ -109,3 +109,46 @@ func statusHandler(c *gin.Context) {
c.JSON(http.StatusOK, resp)
}
}

func ChallengeHealthHandler(c *gin.Context) {
name := c.Param("name")
if name == "" {
c.JSON(http.StatusBadRequest, HTTPPlainResp{
Message: "Name of the challenge is a required parameter to process request.",
})
return
}

challenge, err := database.QueryChallengeEntries("name", name)
if err != nil {
c.JSON(http.StatusInternalServerError, HTTPPlainResp{
Message: "DATABASE ERROR while processing the request.",
})
return
}
chall := challenge[0]

memory_usage, cpu_perc, err := cr.GetContainerStats(chall.ContainerId)
if err != nil {
c.JSON(http.StatusInternalServerError, HTTPPlainResp{
Message: "error while fetching container stats",
})
return
}

memory_limit, _, err := cr.GetContainerLimits(chall.ContainerId)
if err != nil {
c.JSON(http.StatusInternalServerError, HTTPPlainResp{
Message: "error while fetching container limits",
})
return
}
memory_perc := (float64(memory_usage) / float64(memory_limit)) * 100

c.JSON(http.StatusOK, gin.H{
"status": "healthy",
"memory(bytes)": memory_usage,
"memory_perc": memory_perc,
"cpu_perc": cpu_perc,
})
}
4 changes: 3 additions & 1 deletion cmd/beast/healthprobe.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import (
"github.com/sdslabs/beastv4/core/config"
"github.com/sdslabs/beastv4/core/manager"
"github.com/spf13/cobra"
"github.com/sdslabs/beastv4/core/database"
)

var healthProbeCmd = &cobra.Command{
Expand All @@ -13,7 +14,8 @@ var healthProbeCmd = &cobra.Command{

Run: func(cmd *cobra.Command, args []string) {
config.InitConfig()
database.Init()

go manager.BeastHeathCheckProber(config.Cfg.TickerFrequency)
manager.BeastHeathCheckProber(config.Cfg.TickerFrequency)
},
}
31 changes: 30 additions & 1 deletion core/manager/health_check.go
Original file line number Diff line number Diff line change
Expand Up @@ -57,6 +57,35 @@ func containerProber(chall database.Challenge) error {
return err
}
}

memory_usage, _, err := cr.GetContainerStats(chall.ContainerId)
if err != nil {
err = fmt.Errorf("error while fetching container stats with id %s ", chall.ContainerId)
return err
}

memory_limit, _, err := cr.GetContainerLimits(chall.ContainerId)
if err != nil {
err = fmt.Errorf("error while fetching container limits with id %s ", chall.ContainerId)
return err
}
memory_perc := (float64(memory_usage) / float64(memory_limit)) * 100

log.WithFields(log.Fields{
"ChallName": chall.Name,
}).Infof("Container ID: %s", chall.ContainerId)
log.WithFields(log.Fields{
"ChallName": chall.Name,
}).Infof("Memory Usage: %.2f MB", float64(memory_usage)/1024/1024)
log.WithFields(log.Fields{
"ChallName": chall.Name,
}).Infof("Memory Usage Percentage: %.2f%%", memory_perc)

if memory_perc > 100 {
err := fmt.Errorf("Memory limit exceeded for container with id %s ", chall.ContainerId)
return err
}

return nil
}

Expand Down Expand Up @@ -101,7 +130,7 @@ func ChallengesHealthProber(waitTime int) {
}
err = containerProber(chall)
if err != nil {
msg := fmt.Sprintf("CONTAINER HEALTH CHECK %s: %s : %s", result, chall.Name, err)
msg := fmt.Sprintf("CONTAINER HEALTH CHECK %s: %s : %s", result, chall.Name, err)
log.WithFields(log.Fields{
"ChallName": chall.Name,
}).Error(msg)
Expand Down
74 changes: 72 additions & 2 deletions pkg/cr/containers.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,15 +4,15 @@ import (
"fmt"
"io/ioutil"
"strconv"

"encoding/json"
"github.com/docker/docker/api/types"
"github.com/docker/docker/api/types/container"
"github.com/docker/docker/api/types/filters"
"github.com/docker/docker/api/types/mount"
"github.com/docker/docker/client"
"github.com/docker/go-connections/nat"
"github.com/sdslabs/beastv4/pkg/defaults"

"time"
log "github.com/sirupsen/logrus"
"golang.org/x/net/context"
)
Expand Down Expand Up @@ -279,3 +279,73 @@ func CommitContainer(containerId string) (string, error) {

return commitResp.ID, nil
}

func GetContainerStats(containerId string) (int64, float64, error) {
ctx := context.Background()
var data1 types.StatsJSON
var data2 types.StatsJSON
cli, err := client.NewClientWithOpts(client.FromEnv)
if err != nil {
log.Error("Failed to connect to docker sdk")
return 0, 0, err
}
defer cli.Close()

stats, err := cli.ContainerStats(ctx, containerId, false)
if err != nil {
log.Error("Failed to fetch container stats : ", containerId)
return 0, 0, err
}
time.Sleep(1 * time.Second)
stats2, err := cli.ContainerStats(ctx, containerId, false)
if err != nil {
log.Error("Failed to fetch container stats : ", containerId)
return 0, 0, err
}
defer stats.Body.Close()

if err := json.NewDecoder(stats.Body).Decode(&data1); err != nil {
return 0, 0, err
}
if err := json.NewDecoder(stats2.Body).Decode(&data2); err != nil {
return 0, 0, err
}

memoryUsage := data2.MemoryStats.Usage

cpuDelta := data2.CPUStats.CPUUsage.TotalUsage - data1.CPUStats.CPUUsage.TotalUsage
systemDelta := data2.CPUStats.SystemUsage - data1.CPUStats.SystemUsage
numCPUs := float64(len(data2.CPUStats.CPUUsage.PercpuUsage))
cpuPercent := 0.0
if systemDelta > 0 && cpuDelta > 0 {
cpuPercent = (float64(cpuDelta) / float64(systemDelta)) * numCPUs * 100
}

return int64(memoryUsage), cpuPercent, nil

}

func GetContainerLimits(containerId string) (int64, int64, error) {
ctx := context.Background()
cli, err := client.NewClientWithOpts(client.FromEnv)
if err != nil {
log.Error("Failed to connect to docker sdk")
return 0, 0, err
}
defer cli.Close()

stats, err := cli.ContainerInspect(ctx, containerId)
if err != nil {
log.Error("Failed to fetch container stats : ", containerId)
return 0, 0, err
}

memory_limit := stats.HostConfig.Resources.Memory
cpu_shares := stats.HostConfig.Resources.CPUShares

return memory_limit, cpu_shares, nil
}