Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

reporter api #137

Merged
merged 40 commits into from
May 15, 2024
Merged
Show file tree
Hide file tree
Changes from 4 commits
Commits
Show all changes
40 commits
Select commit Hold shift + click to select a range
233191e
add first reporter api documentation
lucamrgs May 1, 2024
0baa488
create api reporter instantiation and links. must test
lucamrgs May 6, 2024
0c8f3b3
add model for api response
lucamrgs May 7, 2024
b2c9373
add parsing from cache entry to reporter api results
lucamrgs May 7, 2024
607dcb8
enroll soarca-level cache object in both api and reporting
lucamrgs May 7, 2024
de79172
add template report api testing
lucamrgs May 7, 2024
73467f8
rename report model to cache model
lucamrgs May 7, 2024
4b0a21a
add get executions base test
lucamrgs May 7, 2024
35ad402
add reporter api testing
lucamrgs May 7, 2024
1610440
split unittest and integration test on reporter api
lucamrgs May 7, 2024
efe5afd
trying to solve lint error cannot parse response comment
lucamrgs May 7, 2024
df5e1e2
solving lint error due to scheme specification
lucamrgs May 7, 2024
6d25758
remove schemes specification
lucamrgs May 7, 2024
9ef4e67
remove scheme from success specification
lucamrgs May 7, 2024
b650e83
add right scheme to response
lucamrgs May 7, 2024
de82654
fix lint error check in controller and recorder body bytes in tests
lucamrgs May 7, 2024
824b88c
change reporter documentation according to review
lucamrgs May 14, 2024
7f58da3
change get executions to return full executions information
lucamrgs May 14, 2024
9e3bbc0
fix commented test check
lucamrgs May 14, 2024
b27bd8b
remove unused function for cache
lucamrgs May 14, 2024
e8266ed
inject cache size from controller
lucamrgs May 14, 2024
b14a27f
add comment ref to cyentific workflow status repo in reporter api model
lucamrgs May 14, 2024
3d38c23
move reporter api init setup to reporter api file
lucamrgs May 14, 2024
bda81e2
use error soarca package in reporter api
lucamrgs May 14, 2024
b92772a
change mustparse to parse in reporter api
lucamrgs May 14, 2024
6af48b8
change default request interval in reporter api to const var
lucamrgs May 14, 2024
b89e7c3
fix playbook error var use in reporter api
lucamrgs May 14, 2024
c2da441
change uuid parse to mustparse in cache tests
lucamrgs May 14, 2024
bc7aa3a
separate mock cache from mock downstram reporter
lucamrgs May 14, 2024
bb688c1
add file mock cache
lucamrgs May 14, 2024
0d16403
remove unused fcns from cache mock
lucamrgs May 14, 2024
fee2f3a
move report api dynamic tests to unittest
lucamrgs May 14, 2024
e0e5f33
align reporter api model with cyentific workflow status model
lucamrgs May 14, 2024
c3cfbcf
implement status text
lucamrgs May 14, 2024
8d6197e
fix lint error check
lucamrgs May 14, 2024
867f444
add command types to cacao model
lucamrgs May 15, 2024
841c51a
change reporter api resutls model
lucamrgs May 15, 2024
9990771
remove deep copy of cache entries now passed by reference to api
lucamrgs May 15, 2024
4c0c736
fix lint bool comparison complaint
lucamrgs May 15, 2024
98b95c1
change reporting status int to status type in tests
lucamrgs May 15, 2024
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
1 change: 0 additions & 1 deletion docs/content/en/docs/core-components/api-reporter.md
Original file line number Diff line number Diff line change
Expand Up @@ -73,7 +73,6 @@ Response data model:
|ended |timestamp |string |The time at which the execution of the playbook ended (if so)
|status |execution-status-enum |string |The current [status](#execution-stataus) of the execution
|status_text |explanation |string |A natural language explanation of the current status or related info
|error |error |string |Error raised along the execution of the playbook at execution level
|step_results |step_results |dictionary |Map of step-id to related [step execution data](#step-execution-data)
|request_interval |seconds |integer |Suggests the polling interval for the next request (default suggested is 5 seconds).

Expand Down
24 changes: 5 additions & 19 deletions internal/reporter/downstream_reporter/cache/cache.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,6 @@ package cache

import (
b64 "encoding/base64"
"encoding/json"
"errors"
"reflect"
"soarca/logger"
Expand Down Expand Up @@ -110,7 +109,7 @@ func (cacheReporter *Cache) ReportWorkflowEnd(executionId uuid.UUID, playbook ca
}

if workflowError != nil {
executionEntry.PlaybookResult = workflowError
executionEntry.Error = workflowError
executionEntry.Status = cache_report.Failed
} else {
executionEntry.Status = cache_report.SuccessfullyExecuted
Expand Down Expand Up @@ -203,9 +202,10 @@ func (cacheReporter *Cache) GetExecutions() ([]cache_report.ExecutionEntry, erro
// NOTE: fetched via fifo register key reference as is ordered array,
// needed to test and report back ordered executions stored
for _, executionEntryKey := range cacheReporter.fifoRegister {
entry, err := cacheReporter.copyExecutionEntry(executionEntryKey)
if err != nil {
return []cache_report.ExecutionEntry{}, err
// NOTE: cached executions are passed by reference, so they must not be modified
entry, present := cacheReporter.Cache[executionEntryKey]
if !present {
return []cache_report.ExecutionEntry{}, errors.New("internal error. cache fifo register and cache executions mismatch.")
}
executions = append(executions, entry)
}
Expand All @@ -221,17 +221,3 @@ func (cacheReporter *Cache) GetExecutionReport(executionKey uuid.UUID) (cache_re

return report, nil
}

func (cacheReporter *Cache) copyExecutionEntry(executionKeyStr string) (cache_report.ExecutionEntry, error) {
// NOTE: Deep copy via JSON serialization and de-serialization, longer computation time than custom function
// might want to implement custom deep copy in future
origJSON, err := json.Marshal(cacheReporter.Cache[executionKeyStr])
if err != nil {
return cache_report.ExecutionEntry{}, err
}
clone := cache_report.ExecutionEntry{}
if err = json.Unmarshal(origJSON, &clone); err != nil {
return cache_report.ExecutionEntry{}, err
}
return clone, nil
}
41 changes: 20 additions & 21 deletions models/api/reporter.go
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import (
"fmt"
"soarca/models/cacao"
cache_model "soarca/models/cache"
"time"
)

type Status uint8
Expand Down Expand Up @@ -35,30 +36,28 @@ const (
)

type PlaybookExecutionReport struct {
Type string
ExecutionId string
PlaybookId string
Started string
Ended string
Status string
StatusText string
StepResults map[string]StepExecutionReport
Error string
RequestInterval int
Type string `bson:"type" json:"type"`
ExecutionId string `bson:"execution_id" json:"execution_id"`
PlaybookId string `bson:"playbook_id" json:"playbook_id"`
Started time.Time `bson:"started" json:"started"`
Ended time.Time `bson:"ended" json:"ended"`
Status string `bson:"status" json:"status"`
StatusText string `bson:"status_text" json:"status_text"`
StepResults map[string]StepExecutionReport `bson:"step_results" json:"step_results"`
RequestInterval int `bson:"request_interval" json:"request_interval"`
}

type StepExecutionReport struct {
ExecutionId string
StepId string
Started string
Ended string
Status string
StatusText string
ExecutedBy string
CommandsB64 []string
Error string
Variables map[string]cacao.Variable
AutomatedExecution string
ExecutionId string `bson:"execution_id" json:"execution_id"`
StepId string `bson:"step_id" json:"step_id"`
Started time.Time `bson:"started" json:"started"`
Ended time.Time `bson:"ended" json:"ended"`
Status string `bson:"status" json:"status"`
StatusText string `bson:"status_text" json:"status_text"`
ExecutedBy string `bson:"executed_by" json:"executed_by"`
CommandsB64 []string `bson:"commands_b64" json:"commands_b64"`
Variables map[string]cacao.Variable `bson:"variables" json:"variables"`
AutomatedExecution bool `bson:"automated_execution" json:"automated_execution"`
// Make sure we can have a playbookID for playbook actions, and also
// the execution ID for the invoked playbook
}
Expand Down
13 changes: 12 additions & 1 deletion models/cacao/cacao.go
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,18 @@ const (
StepTypeWhileCondition = "while-condition"
StepTypeSwitchCondition = "switch-condition"

CommandTypeManual = "manual"
CommandTypeManual = "manual"
CommandTypeBash = "bash"
CommandTypeCalderaCmd = "caldera-cmd"
CommandTypeElastic = "elastic"
CommandTypeHttpApi = "http-api"
CommandTypeJupyter = "jupyter"
CommandTypeKestrel = "kestrel"
CommandTypeOpenC2Http = "openc2-http"
CommandTypePowershell = "powershell"
CommandTypeSigma = "sigma"
CommandTypeSsh = "ssh"
CommandTypeYara = "yara"

AuthInfoOAuth2Type = "oauth2"
AuthInfoHTTPBasicType = "http-basic"
Expand Down
14 changes: 7 additions & 7 deletions models/cache/cache.go
Original file line number Diff line number Diff line change
Expand Up @@ -21,13 +21,13 @@ const (
)

type ExecutionEntry struct {
ExecutionId uuid.UUID
PlaybookId string
Started time.Time
Ended time.Time
StepResults map[string]StepResult
PlaybookResult error
Status Status
ExecutionId uuid.UUID
PlaybookId string
Started time.Time
Ended time.Time
StepResults map[string]StepResult
Error error
Status Status
}

type StepResult struct {
Expand Down
29 changes: 10 additions & 19 deletions routes/reporter/reporter_parser.go
Original file line number Diff line number Diff line change
Expand Up @@ -12,13 +12,13 @@ func parseCachePlaybookEntry(cacheEntry cache_model.ExecutionEntry) (api_model.P
if err != nil {
return api_model.PlaybookExecutionReport{}, err
}

playbookStatusText, err := api_model.GetCacheStatusText(playbookStatus, api_model.ReportLevelPlaybook)
if err != nil {
return api_model.PlaybookExecutionReport{}, err
}
playbookErrorStr := ""
if cacheEntry.PlaybookResult != nil {
playbookErrorStr = cacheEntry.PlaybookResult.Error()
if cacheEntry.Error != nil {
playbookStatusText = playbookStatusText + " - error: " + cacheEntry.Error.Error()
}

stepResults, err := parseCacheStepEntries(cacheEntry.StepResults)
Expand All @@ -30,11 +30,10 @@ func parseCachePlaybookEntry(cacheEntry cache_model.ExecutionEntry) (api_model.P
Type: "execution_status",
ExecutionId: cacheEntry.ExecutionId.String(),
PlaybookId: cacheEntry.PlaybookId,
Started: cacheEntry.Started.String(),
Ended: cacheEntry.Ended.String(),
Started: cacheEntry.Started,
Ended: cacheEntry.Ended,
Status: playbookStatus,
StatusText: playbookStatusText,
Error: playbookErrorStr,
StepResults: stepResults,
RequestInterval: defaultRequestInterval,
}
Expand All @@ -54,29 +53,21 @@ func parseCacheStepEntries(cacheStepEntries map[string]cache_model.StepResult) (
return map[string]api_model.StepExecutionReport{}, err
}

stepError := stepEntry.Error
stepErrorStr := ""
if stepError != nil {
stepErrorStr = stepError.Error()
}

automatedExecution := "true"
if !stepEntry.IsAutomated {
automatedExecution = "false"
if stepEntry.Error != nil {
stepStatusText = stepStatusText + " - error: " + stepEntry.Error.Error()
}

parsedEntries[stepId] = api_model.StepExecutionReport{
Copy link
Collaborator

Choose a reason for hiding this comment

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

Creating an empty object and then setting according to the its status might make more sense you won't need so many temp variables

ExecutionId: stepEntry.ExecutionId.String(),
StepId: stepEntry.StepId,
Started: stepEntry.Started.String(),
Ended: stepEntry.Ended.String(),
Started: stepEntry.Started,
Ended: stepEntry.Ended,
Status: stepStatus,
StatusText: stepStatusText,
ExecutedBy: "soarca",
CommandsB64: stepEntry.CommandsB64,
Error: stepErrorStr,
Variables: stepEntry.Variables,
AutomatedExecution: automatedExecution,
AutomatedExecution: stepEntry.IsAutomated,
}
}
return parsedEntries, nil
Expand Down
42 changes: 21 additions & 21 deletions test/unittest/reporters/downstream_reporter/cache_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -100,13 +100,13 @@ func TestReportWorkflowStartFirst(t *testing.T) {
expectedEnded, _ := time.Parse(layout, "0001-01-01T00:00:00Z")
expectedExecutions := []cache_model.ExecutionEntry{
{
ExecutionId: executionId0,
PlaybookId: "test",
Started: expectedStarted,
Ended: expectedEnded,
StepResults: map[string]cache_model.StepResult{},
PlaybookResult: nil,
Status: 2,
ExecutionId: executionId0,
PlaybookId: "test",
Started: expectedStarted,
Ended: expectedEnded,
StepResults: map[string]cache_model.StepResult{},
Error: nil,
Status: 2,
},
}

Expand Down Expand Up @@ -207,13 +207,13 @@ func TestReportWorkflowStartFifo(t *testing.T) {
for _, executionId := range executionIds[:len(executionIds)-1] {
t.Log(executionId)
entry := cache_model.ExecutionEntry{
ExecutionId: executionId,
PlaybookId: "test",
Started: expectedStarted,
Ended: expectedEnded,
StepResults: map[string]cache_model.StepResult{},
PlaybookResult: nil,
Status: 2,
ExecutionId: executionId,
PlaybookId: "test",
Started: expectedStarted,
Ended: expectedEnded,
StepResults: map[string]cache_model.StepResult{},
Error: nil,
Status: 2,
}
expectedExecutionsFull = append(expectedExecutionsFull, entry)
}
Expand All @@ -222,13 +222,13 @@ func TestReportWorkflowStartFifo(t *testing.T) {
for _, executionId := range executionIds[1:] {
t.Log(executionId)
entry := cache_model.ExecutionEntry{
ExecutionId: executionId,
PlaybookId: "test",
Started: expectedStarted,
Ended: expectedEnded,
StepResults: map[string]cache_model.StepResult{},
PlaybookResult: nil,
Status: 2,
ExecutionId: executionId,
PlaybookId: "test",
Started: expectedStarted,
Ended: expectedEnded,
StepResults: map[string]cache_model.StepResult{},
Error: nil,
Status: 2,
}
expectedExecutionsFifo = append(expectedExecutionsFifo, entry)
}
Expand Down
38 changes: 18 additions & 20 deletions test/unittest/routes/reporter_api/reporter_api_invocation_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -95,36 +95,34 @@ func TestGetExecutionReportInvocation(t *testing.T) {
app.ServeHTTP(recorder, request)

expectedResponse := `{
"Type":"execution_status",
"ExecutionId":"6ba7b810-9dad-11d1-80b4-00c04fd430c0",
"PlaybookId":"test",
"Started":"2014-11-12 11:45:26.371 +0000 UTC",
"Ended":"0001-01-01 00:00:00 +0000 UTC",
"Status":"ongoing",
"StatusText":"this playbook is currently being executed",
"StepResults":{
"type":"execution_status",
"execution_id":"6ba7b810-9dad-11d1-80b4-00c04fd430c0",
"playbook_id":"test",
"started":"2014-11-12T11:45:26.371Z",
"ended":"0001-01-01T00:00:00Z",
"status":"ongoing",
"status_text":"this playbook is currently being executed",
"step_results":{
"action--test":{
"ExecutionId":"6ba7b810-9dad-11d1-80b4-00c04fd430c0",
"StepId": "action--test",
"Started": "2014-11-12 11:45:26.371 +0000 UTC",
"Ended": "2014-11-12 11:45:26.371 +0000 UTC",
"Status": "successfully_executed",
"StatusText": "step execution completed successfully",
"Error":"",
"execution_id":"6ba7b810-9dad-11d1-80b4-00c04fd430c0",
"step_id": "action--test",
"started": "2014-11-12T11:45:26.371Z",
"ended": "2014-11-12T11:45:26.371Z",
"status": "successfully_executed",
"status_text": "step execution completed successfully",
"Variables":{
"var1":{
"type":"string",
"name":"var1",
"value":"testing"
}
},
"CommandsB64" : [],
"AutomatedExecution" : "true",
"ExecutedBy" : "soarca"
"commands_b64" : [],
"automated_execution" : true,
"executed_by" : "soarca"
}
},
"Error":"",
"RequestInterval":5
"request_interval":5
}`
expectedResponseData := api_model.PlaybookExecutionReport{}
err = json.Unmarshal([]byte(expectedResponse), &expectedResponseData)
Expand Down
Loading