From c3484ee931e6f85ff77b1855e5262c01dd2ec1e7 Mon Sep 17 00:00:00 2001 From: Maarten de Kruijf Date: Thu, 20 Jun 2024 10:21:30 +0200 Subject: [PATCH] Feature/174 status api (#175) --- .../en/docs/core-components/api-design.md | 111 ++++++------------ main.go | 7 +- models/api/reporter.go | 2 - models/api/status.go | 16 +++ routes/status/status_api.go | 38 ++++++ routes/status/status_endpoints.go | 38 +++--- test/integration/api/api_test.go | 73 ++++++++++++ 7 files changed, 184 insertions(+), 101 deletions(-) create mode 100644 models/api/status.go create mode 100644 routes/status/status_api.go diff --git a/docs/content/en/docs/core-components/api-design.md b/docs/content/en/docs/core-components/api-design.md index 6120c384..5903be6a 100644 --- a/docs/content/en/docs/core-components/api-design.md +++ b/docs/content/en/docs/core-components/api-design.md @@ -417,14 +417,13 @@ None @startjson { "version": "1.0.0", - "components": [ - { - "name": "Component name", - "status": "ready/running/failed/stopped/...", - "message": "Some message", - "version": "semver verison: 1.0.0" - } - ] + "runtime": "docker/windows/linux/macos/other", + "mode" : "development/production", + "time" : "2020-03-04T15:56:00.123456Z", + "uptime": { + "since": "2020-03-04T15:56:00.123456Z", + "milis": "uptime in miliseconds" + } } @endjson ``` @@ -432,11 +431,13 @@ None ##### Error 5XX/Internal error, 500/503/504 message. ---- +---- + +#### GET `/status/fins` | not implemented +Call this endpoint to see if SOARCA Fins are up and ready. This call has no payload body. -#### GET /status/playbook -Get all running playbooks ##### Call payload +None ##### Response 200/OK @@ -444,53 +445,26 @@ Get all running playbooks ```plantuml @startjson { - "playbooks": [ - {"type": "playbook", - "spec_version": "cacao-2.0", - "id": "playbook--91220064-3c6f-4b58-99e9-196e64f9bde7", - "name": "SOARCA Main Flow", - "description": "This playbook will run for each trigger event in SOARCA", - "playbook_types": ["notification"], - "created_by": "identity--06d8f218-f4e9-4f9f-9108-501de03d419f", - "created": "2020-03-04T15:56:00.123456Z", - "modified": "2020-03-04T15:56:00.123456Z", - "revoked": false, - "valid_from": "2020-03-04T15:56:00.123456Z", - "valid_until": "2020-07-31T23:59:59.999999Z", - "derived_from": [], - "priority": 1, - "severity": 1, - "impact": 1} - ] - + "fins": [ + { + "name": "Fin name", + "status": "ready/running/failed/stopped/...", + "id": "The fin UUID", + "version": "semver verison: 1.0.0" + } + ] } @endjson ``` ##### Error -400/BAD REQUEST general error on error. - ----- - -#### GET `/status/playbook/xxxxxxxx-xxxx-Mxxx-Nxxx-xxxxxxxxxxxx (playbook-id)` -Get playbook details of running a playbook. It will return CACAO playbook JSON - -##### Call payload -None - -##### Response -200/OK - -See [cacao playbook JSON](#cacao-playbook-json) -Empty payload if no playbooks are running +5XX/Internal error, 500/503/504 message. -##### Error -400/BAD REQUEST general error on error. ---- -#### GET `/status/playbook/{playbook-id}` -Get course of action list for coa awaiting action. +#### GET `/status/reporters` | not implemented +Call this endpoint to see which SOARCA reportes are used. This call has no payload body. ##### Call payload None @@ -501,24 +475,23 @@ None ```plantuml @startjson { - "actions": [ - { - "playbook_id": "playbook--91220064-3c6f-4b58-99e9-196e64f9bde7", - "status": "running/finished/failed/stopped/paused" - } - ] - + "reporters": [ + { + "name": "Reporter name" + } + ] } @endjson ``` ##### Error -400/BAD REQUEST general error on error. +5XX/Internal error, 500/503/504 message. + +---- ---- -#### GET /status/history -Get all playbook ids and statuses that have been run excluded those that are running or paused. +#### GET `/status/ping` +See if SOARCA is up this will only return if all SOARCA services are ready ##### Call payload None @@ -526,23 +499,7 @@ None ##### Response 200/OK -```plantuml -@startjson -{ - "actions": [ - { - "playbook_id": "playbook--91220064-3c6f-4b58-99e9-196e64f9bde7", - "status": "running/finished/failed/stopped/paused" - } - ] - -} -@endjson -``` - -##### Error -400/BAD REQUEST general error on error. - +`pong` ## Usage example flow diff --git a/main.go b/main.go index cd5ac380..21903c21 100644 --- a/main.go +++ b/main.go @@ -5,6 +5,7 @@ import ( "soarca/internal/controller" "soarca/logger" + "soarca/routes/status" "soarca/swaggerdocs" "soarca/utils" @@ -35,8 +36,8 @@ const banner = ` ` -// @title SOARCA API -// @version 1.0.0 +// @title SOARCA API +// @version 1.0.0 func main() { fmt.Print(banner) log.Info("Version: ", Version) @@ -49,6 +50,8 @@ func main() { Host = "localhost:" + utils.GetEnv("PORT", "8080") swaggerdocs.SwaggerInfo.Host = Host + // Version is only available here + status.SetVersion(Version) errinit := controller.Initialize() if errinit != nil { log.Fatal("Something Went wrong with setting-up the app, msg: ", errinit) diff --git a/models/api/reporter.go b/models/api/reporter.go index 0d6d73fc..76806371 100644 --- a/models/api/reporter.go +++ b/models/api/reporter.go @@ -8,8 +8,6 @@ import ( "time" ) -type Status uint8 - // Reporter model adapted from https://github.com/cyentific-rni/workflow-status/blob/main/README.md const ( diff --git a/models/api/status.go b/models/api/status.go new file mode 100644 index 00000000..561e9e65 --- /dev/null +++ b/models/api/status.go @@ -0,0 +1,16 @@ +package api + +import "time" + +type Uptime struct { + Since time.Time `json:"since"` + Milliseconds uint64 `json:"milliseconds"` +} + +type Status struct { + Version string `json:"version"` + Runtime string `json:"runtime"` + Mode string `json:"mode"` + Time time.Time `json:"time"` + Uptime Uptime `json:"uptime"` +} diff --git a/routes/status/status_api.go b/routes/status/status_api.go new file mode 100644 index 00000000..347aecd9 --- /dev/null +++ b/routes/status/status_api.go @@ -0,0 +1,38 @@ +package status + +import ( + "net/http" + "runtime" + "soarca/models/api" + "soarca/utils" + "time" + + "github.com/gin-gonic/gin" +) + +var status = api.Status{Uptime: api.Uptime{Since: time.Now(), Milliseconds: 0}, + Mode: utils.GetEnv("LOG_MODE", "production"), + Runtime: runtime.GOOS} + +func SetVersion(version string) { + status.Version = version +} + +// /Status GET handler for handling status api calls +// Returns the status model object for SOARCA +// +// @Summary gets the SOARCA status +// @Schemes +// @Description return SOARCA status +// @Tags status +// @Produce json +// @success 200 {object} api.Status +// @failure 400 {object} api.Error +// @Router /status [GET] +func Api(g *gin.Context) { + + status.Uptime.Milliseconds = uint64(time.Since(status.Uptime.Since).Milliseconds()) + status.Time = time.Now() + + g.JSON(http.StatusOK, status) +} diff --git a/routes/status/status_endpoints.go b/routes/status/status_endpoints.go index 294afe0c..737af347 100644 --- a/routes/status/status_endpoints.go +++ b/routes/status/status_endpoints.go @@ -1,34 +1,32 @@ -package coa +package status import ( - "fmt" "net/http" "github.com/gin-gonic/gin" ) -func Helloworld(g *gin.Context) { - g.JSON(http.StatusOK, "helloworld from /status") -} - -func id_tester(g *gin.Context) { - // Get the value of the 'id' parameter from the URL - id := g.Param("id") - fmt.Println(id) +// /Status/ping GET handler for handling status api calls +// Returns the status model object for SOARCA +// +// @Summary ping to see if SOARCA is up returns pong +// @Schemes +// @Description return SOARCA status +// @Tags ping pong +// @Produce plain +// @success 200 string pong +// @Router /status/ping [GET] +func Pong(g *gin.Context) { + g.Data(http.StatusOK, "text/plain", []byte("pong")) } // GET /status -// GET /status/playbook -// GET /status/playbook/id -// GET /status/coa/id -// GET /status/history +// GET /status/ping func Routes(route *gin.Engine) { - coa := route.Group("/status") + router := route.Group("/status") { - coa.GET("/", Helloworld) - coa.GET("/playbook/:id", id_tester) - coa.GET("/coa/:id", id_tester) - coa.GET("/history", Helloworld) - // workflow.POST() + router.GET("/", Api) + router.GET("/ping", Pong) + } } diff --git a/test/integration/api/api_test.go b/test/integration/api/api_test.go index 201efdb2..e90b0233 100644 --- a/test/integration/api/api_test.go +++ b/test/integration/api/api_test.go @@ -2,8 +2,11 @@ package api_test import ( "bytes" + "encoding/json" + "io" "net/http" "soarca/internal/controller" + "soarca/models/api" "testing" "time" @@ -89,3 +92,73 @@ func TestCorsHeaderFromNonAllowedOrigin(t *testing.T) { assert.Equal(t, http.StatusForbidden, response2.StatusCode) } + +func TestPingPong(t *testing.T) { + // Start SOARCA in separate threat + t.Setenv("PORT", "8082") + go initializeSoarca(t) + + // Wait for the server to be online + time.Sleep(400 * time.Millisecond) + + client := http.Client{} + buffer := bytes.NewBufferString("") + request, err := http.NewRequest("GET", "http://localhost:8082/status/ping", buffer) + if err != nil { + t.Fail() + } + + // request.Header.Add("Origin", "http://example.com") + response, err := client.Do(request) + t.Log(response) + if err != nil { + t.Log(err) + t.Fail() + } + + assert.Equal(t, http.StatusOK, response.StatusCode) + defer response.Body.Close() + body, err := io.ReadAll(response.Body) + assert.Equal(t, nil, err) + + assert.Equal(t, "pong", string(body)) + +} + +func TestStatus(t *testing.T) { + // Start SOARCA in separate threat + t.Setenv("PORT", "8083") + go initializeSoarca(t) + + // Wait for the server to be online + time.Sleep(400 * time.Millisecond) + + client := http.Client{} + buffer := bytes.NewBufferString("") + request, err := http.NewRequest("GET", "http://localhost:8083/status", buffer) + if err != nil { + t.Fail() + } + + response, err := client.Do(request) + if err != nil { + t.Log(err) + t.Fail() + } + + assert.Equal(t, http.StatusOK, response.StatusCode) + defer response.Body.Close() + body, err := io.ReadAll(response.Body) + assert.Equal(t, nil, err) + + status := api.Status{} + err = json.Unmarshal(body, &status) + assert.Equal(t, nil, err) + + assert.Equal(t, "production", status.Mode) + assert.NotEmpty(t, status.Mode) + assert.NotEmpty(t, status.Runtime) + assert.NotEmpty(t, status.Time) + t.Log(status) + +}