From 9cf844301541b60271a73d09640661e8ab241490 Mon Sep 17 00:00:00 2001 From: Maarten de Kruijf Date: Wed, 1 May 2024 18:17:14 +0200 Subject: [PATCH 01/17] Added stix compare for int and string --- test/unittest/utils/stix/stix_test.go | 193 ++++++++++++++++++++++++++ utils/stix/strix.go | 165 ++++++++++++++++++++++ 2 files changed, 358 insertions(+) create mode 100644 test/unittest/utils/stix/stix_test.go create mode 100644 utils/stix/strix.go diff --git a/test/unittest/utils/stix/stix_test.go b/test/unittest/utils/stix/stix_test.go new file mode 100644 index 00000000..49392671 --- /dev/null +++ b/test/unittest/utils/stix/stix_test.go @@ -0,0 +1,193 @@ +package stix_test + +import ( + "errors" + "soarca/models/cacao" + "soarca/utils/stix" + "testing" + "time" + + "github.com/go-playground/assert/v2" +) + +func TestStringEquals(t *testing.T) { + + var1 := cacao.Variable{Type: cacao.VariableTypeString} + var1.Value = "a" + var1.Name = "__var1__" + vars := cacao.NewVariables(var1) + + result, err := stix.Evaluate("__var1__:value = a", vars) + assert.Equal(t, err, nil) + assert.Equal(t, result, true) + result, err = stix.Evaluate("__var1__:value = b", vars) + assert.Equal(t, result, false) + assert.Equal(t, err, nil) + + result, err = stix.Evaluate("__var1__:value = 1", vars) + assert.Equal(t, result, false) + assert.Equal(t, err, nil) + + result, err = stix.Evaluate("__var1__:value > b", vars) + assert.Equal(t, result, false) + assert.Equal(t, err, nil) + + result, err = stix.Evaluate("__var1__:value < b", vars) + assert.Equal(t, result, true) + assert.Equal(t, err, nil) + + result, err = stix.Evaluate("__var1__:value <= b", vars) + assert.Equal(t, result, true) + assert.Equal(t, err, nil) + + result, err = stix.Evaluate("__var1__:value >= b", vars) + assert.Equal(t, result, false) + assert.Equal(t, err, nil) + + result, err = stix.Evaluate("a = b", vars) + assert.Equal(t, result, false) + assert.Equal(t, err, errors.New("comparisons can only contain 3 parts as per STIX specification")) + result, err = stix.Evaluate("a = b c", vars) + assert.Equal(t, result, false) + assert.Equal(t, err, errors.New("comparisons can only contain 3 parts as per STIX specification")) + time.Now() +} + +func TestIntEquals(t *testing.T) { + + var1 := cacao.Variable{Type: cacao.VariableTypeLong} + var1.Value = "1000" + var1.Name = "__var1__" + vars := cacao.NewVariables(var1) + + result, err := stix.Evaluate("__var1__:value = 1000", vars) + assert.Equal(t, err, nil) + assert.Equal(t, result, true) + result, err = stix.Evaluate("__var1__:value = 9999", vars) + assert.Equal(t, result, false) + assert.Equal(t, err, nil) + + result, err = stix.Evaluate("__var1__:value = 10000", vars) + assert.Equal(t, result, false) + assert.Equal(t, err, nil) + + result, err = stix.Evaluate("__var1__:value > 999", vars) + assert.Equal(t, result, true) + assert.Equal(t, err, nil) + + result, err = stix.Evaluate("__var1__:value < 1001", vars) + assert.Equal(t, result, true) + assert.Equal(t, err, nil) + + result, err = stix.Evaluate("__var1__:value <= 1000", vars) + assert.Equal(t, result, true) + assert.Equal(t, err, nil) + + result, err = stix.Evaluate("__var1__:value >= 1000", vars) + assert.Equal(t, result, true) + assert.Equal(t, err, nil) + + result, err = stix.Evaluate("__var1__:value >= a", vars) + assert.Equal(t, result, false) + assert.NotEqual(t, err, nil) + + result, err = stix.Evaluate("a = b", vars) + assert.Equal(t, result, false) + assert.Equal(t, err, errors.New("comparisons can only contain 3 parts as per STIX specification")) + result, err = stix.Evaluate("a = b c", vars) + assert.Equal(t, result, false) + assert.Equal(t, err, errors.New("comparisons can only contain 3 parts as per STIX specification")) +} + +// func TestNotEquals(t *testing.T) { +// result, err := stix.Evaluate("a != a") +// assert.Equal(t, result, false) +// assert.Equal(t, err, nil) +// result, err = stix.Evaluate("a != b") +// assert.Equal(t, result, true) +// assert.Equal(t, err, nil) +// } + +// func TestGreater(t *testing.T) { +// result, err := stix.Evaluate("2 > 1") +// assert.Equal(t, result, true) +// assert.Equal(t, err, nil) +// result, err = stix.Evaluate("200000000 > 10000000") +// assert.Equal(t, result, true) +// assert.Equal(t, err, nil) +// result, err = stix.Evaluate("200 > 199") +// assert.Equal(t, result, true) +// assert.Equal(t, err, nil) +// result, err = stix.Evaluate("199 > 200") +// assert.Equal(t, result, false) +// assert.Equal(t, err, nil) +// } + +// func TestLess(t *testing.T) { +// result, err := stix.Evaluate("2 < 1") +// assert.Equal(t, result, false) +// assert.Equal(t, err, nil) +// result, err = stix.Evaluate("200000000 < 10000000") +// assert.Equal(t, result, false) +// assert.Equal(t, err, nil) +// result, err = stix.Evaluate("200 < 199") +// assert.Equal(t, result, false) +// assert.Equal(t, err, nil) +// result, err = stix.Evaluate("199 < 200") +// assert.Equal(t, result, true) +// assert.Equal(t, err, nil) +// } + +// func TestLessEqual(t *testing.T) { +// result, err := stix.Evaluate("2 <= 1") +// assert.Equal(t, result, false) +// assert.Equal(t, err, nil) +// result, err = stix.Evaluate("200000000 <= 10000000") +// assert.Equal(t, result, false) +// assert.Equal(t, err, nil) +// result, err = stix.Evaluate("200 <= 199") +// assert.Equal(t, result, false) +// assert.Equal(t, err, nil) +// result, err = stix.Evaluate("199 <= 200") +// assert.Equal(t, result, true) +// assert.Equal(t, err, nil) +// result, err = stix.Evaluate("200 <= 200") +// assert.Equal(t, result, true) +// assert.Equal(t, err, nil) +// } + +// func TestGreaterEqual(t *testing.T) { +// result, err := stix.Evaluate("2 >= 1") +// assert.Equal(t, result, true) +// assert.Equal(t, err, nil) +// result, err = stix.Evaluate("10000000 >= 200000000") +// assert.Equal(t, result, false) +// assert.Equal(t, err, nil) +// result, err = stix.Evaluate("200 >= 199") +// assert.Equal(t, result, true) +// assert.Equal(t, err, nil) +// result, err = stix.Evaluate("199 >= 200") +// assert.Equal(t, result, false) +// assert.Equal(t, err, nil) +// result, err = stix.Evaluate("200 >= 200") +// assert.Equal(t, result, true) +// assert.Equal(t, err, nil) +// } + +// func TestIn(t *testing.T) { +// result, err := stix.Evaluate("2 = (1,2,3,4)") +// assert.Equal(t, result, true) +// assert.Equal(t, err, nil) +// result, err = stix.Evaluate("10000000 >= 200000000") +// assert.Equal(t, result, false) +// assert.Equal(t, err, nil) +// result, err = stix.Evaluate("200 >= 199") +// assert.Equal(t, result, true) +// assert.Equal(t, err, nil) +// result, err = stix.Evaluate("199 >= 200") +// assert.Equal(t, result, false) +// assert.Equal(t, err, nil) +// result, err = stix.Evaluate("200 >= 200") +// assert.Equal(t, result, true) +// assert.Equal(t, err, nil) +// } diff --git a/utils/stix/strix.go b/utils/stix/strix.go new file mode 100644 index 00000000..8ff30b2b --- /dev/null +++ b/utils/stix/strix.go @@ -0,0 +1,165 @@ +package stix + +import ( + "errors" + "fmt" + "soarca/models/cacao" + "strconv" + "strings" +) + +const ( + Equal = "=" + NotEqual = "!=" + Greater = ">" + Less = "<" + LessOrEqual = "<=" + GreaterOrEqual = ">=" + In = "IN" + Like = "LIKE" + Matches = "MATCHES" + IsSubset = "ISSUBSET" + IsSuperSet = "ISSUPERSET" +) + +func Evaluate(expression string, vars cacao.Variables) (bool, error) { + + //"condition": "__variable__:value == '10.0.0.0/8'" + //"condition": "__ip__:value/__subnet__:value == '10.0.0.0/8'" + //"condition": "__ip__:value = __another_ip__:value" + //"expresion_type": "ipv4" + parts := strings.Split(expression, " ") + if len(parts) != 3 { + err := errors.New("comparisons can only contain 3 parts as per STIX specification") + return false, err + } + + usedVariable, err := findVariable(parts[0], vars) + if err != nil { + return false, err + } + + parts[0] = vars.Interpolate(parts[0]) + + switch usedVariable.Type { + case cacao.VariableTypeString: + return stringCompare(parts) + case cacao.VariableTypeInt: + return numberCompare(parts) + case cacao.VariableTypeLong: + return numberCompare(parts) + case cacao.VariableTypeFloat: + return floatCompare(parts) + + default: + err := errors.New("variable type is not a cacao variable type") + return false, err + } + +} + +func findVariable(variable string, vars cacao.Variables) (cacao.Variable, error) { + for key, value := range vars { + replacementKey := fmt.Sprint(key, ":value") + if strings.Contains(variable, replacementKey) { + return value, nil + } + + } + return cacao.Variable{}, nil +} + +func checkIpInRange(ipString string, ipRangeString string) { + // ip, ipRange, err := net.ParseCIDR(ipString) + // ip2, ipRange2, err2 := net.ParseCIDR(ipRangeString) + // ipRange.Mask.Size() +} + +func stringCompare(parts []string) (bool, error) { + + lhs := parts[0] + comparator := parts[1] + rhs := parts[2] + + switch comparator { + case Equal: + return strings.Compare(lhs, rhs) == 0, nil + case NotEqual: + return strings.Compare(lhs, rhs) != 0, nil + case Greater: + return strings.Compare(lhs, rhs) == 1, nil + case Less: + return strings.Compare(lhs, rhs) == -1, nil + case LessOrEqual: + return strings.Compare(lhs, rhs) <= 0, nil + case GreaterOrEqual: + return strings.Compare(lhs, rhs) >= 0, nil + case In: + return strings.Contains(lhs, rhs), nil + default: + err := errors.New("operator not valid") + return false, err + } +} + +func numberCompare(parts []string) (bool, error) { + lhs, err := strconv.Atoi(parts[0]) + + if err != nil { + return false, err + } + comparator := parts[1] + rhs, err := strconv.Atoi(parts[2]) + if err != nil { + return false, err + } + + switch comparator { + case Equal: + return lhs == rhs, nil + case NotEqual: + return lhs != rhs, nil + case Greater: + return lhs > rhs, nil + case Less: + return lhs < rhs, nil + case LessOrEqual: + return lhs <= rhs, nil + case GreaterOrEqual: + return lhs >= rhs, nil + default: + err := errors.New("operator not valid") + return false, err + } +} + +func floatCompare(parts []string) (bool, error) { + lhs, err := strconv.ParseFloat(parts[0], 0) + + if err != nil { + return false, err + } + comparator := parts[1] + rhs, err := strconv.ParseFloat(parts[2], 0) + if err != nil { + return false, err + } + + switch comparator { + case Equal: + return lhs == rhs, nil + case NotEqual: + return lhs != rhs, nil + case Greater: + return lhs > rhs, nil + case Less: + return lhs < rhs, nil + case LessOrEqual: + return lhs <= rhs, nil + case GreaterOrEqual: + return lhs >= rhs, nil + default: + err := errors.New("operator not valid") + return false, err + } +} From cc0048da5b72175bfbb32e300394f6e8b57d3990 Mon Sep 17 00:00:00 2001 From: Maarten de Kruijf Date: Wed, 1 May 2024 18:44:26 +0200 Subject: [PATCH 02/17] Base if-condition --- .../executors/if_condition/if_condition.go | 42 +++++++++++++++++++ 1 file changed, 42 insertions(+) create mode 100644 internal/executors/if_condition/if_condition.go diff --git a/internal/executors/if_condition/if_condition.go b/internal/executors/if_condition/if_condition.go new file mode 100644 index 00000000..ab4c64a4 --- /dev/null +++ b/internal/executors/if_condition/if_condition.go @@ -0,0 +1,42 @@ +package ifcondition + +import ( + "errors" + "reflect" + "soarca/internal/capability" + "soarca/logger" + "soarca/models/cacao" + "soarca/models/execution" +) + +var component = reflect.TypeOf(Executor{}).PkgPath() +var log *logger.Log + +func init() { + log = logger.Logger(component, logger.Info, "", logger.Json) +} + +func New(capabilities map[string]capability.ICapability) *Executor { + var instance = Executor{} + instance.capabilities = capabilities + return &instance +} + +type IExecuter interface { + Execute(metadata execution.Metadata, + step cacao.Step) (string, error) +} + +type Executor struct { + capabilities map[string]capability.ICapability +} + +func (executor *Executor) Execute(meta execution.Metadata, step cacao.Step) (string, error) { + + if step.Type != cacao.StepTypeIfCondition { + err := errors.New("the provided step type is not compatible with this executor") + log.Error(err) + return step.OnFailure, err + } + return "", nil +} From 89cb1fed6b02b76e21ecdbd83f0f821c7c9af58b Mon Sep 17 00:00:00 2001 From: Maarten de Kruijf Date: Fri, 3 May 2024 09:26:54 +0200 Subject: [PATCH 03/17] Moved to condition --- .../condition.go} | 29 +++++++++++++++---- 1 file changed, 24 insertions(+), 5 deletions(-) rename internal/executors/{if_condition/if_condition.go => condition/condition.go} (61%) diff --git a/internal/executors/if_condition/if_condition.go b/internal/executors/condition/condition.go similarity index 61% rename from internal/executors/if_condition/if_condition.go rename to internal/executors/condition/condition.go index ab4c64a4..da9f332f 100644 --- a/internal/executors/if_condition/if_condition.go +++ b/internal/executors/condition/condition.go @@ -1,4 +1,4 @@ -package ifcondition +package condition import ( "errors" @@ -7,6 +7,7 @@ import ( "soarca/logger" "soarca/models/cacao" "soarca/models/execution" + "soarca/utils/stix" ) var component = reflect.TypeOf(Executor{}).PkgPath() @@ -24,19 +25,37 @@ func New(capabilities map[string]capability.ICapability) *Executor { type IExecuter interface { Execute(metadata execution.Metadata, - step cacao.Step) (string, error) + step cacao.Step) (string, bool, error) } type Executor struct { capabilities map[string]capability.ICapability } -func (executor *Executor) Execute(meta execution.Metadata, step cacao.Step) (string, error) { +func (executor *Executor) Execute(meta execution.Metadata, step cacao.Step) (string, bool, error) { if step.Type != cacao.StepTypeIfCondition { err := errors.New("the provided step type is not compatible with this executor") log.Error(err) - return step.OnFailure, err + return step.OnFailure, false, err } - return "", nil + + result, err := stix.Evaluate(step.Condition, step.StepVariables) + if err != nil { + log.Error(err) + return "", false, err + } + + if result { + if step.OnTrue != "" { + log.Trace("") + return step.OnTrue, true, nil + } + } else { + if step.OnFalse != "" { + return step.OnFalse, true, nil + } + } + + return step.OnCompletion, false, nil } From 99909e4244987fc8e8ba2136fc201115066f765b Mon Sep 17 00:00:00 2001 From: Maarten de Kruijf Date: Fri, 3 May 2024 09:27:10 +0200 Subject: [PATCH 04/17] Updated decomposer with condition logic --- internal/decomposer/decomposer.go | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/internal/decomposer/decomposer.go b/internal/decomposer/decomposer.go index c33dc3c0..69797646 100644 --- a/internal/decomposer/decomposer.go +++ b/internal/decomposer/decomposer.go @@ -7,6 +7,7 @@ import ( "soarca/internal/executors" "soarca/internal/executors/action" + "soarca/internal/executors/condition" "soarca/internal/guid" "soarca/internal/reporter" "soarca/logger" @@ -52,6 +53,7 @@ type Decomposer struct { details ExecutionDetails actionExecutor action.IExecuter playbookActionExecutor executors.IPlaybookExecuter + conditionExecutor condition.IExecuter guid guid.IGuid reporter reporter.IWorkflowReporter } @@ -157,6 +159,15 @@ func (decomposer *Decomposer) ExecuteStep(step cacao.Step, scopeVariables cacao. return decomposer.actionExecutor.Execute(metadata, actionMetadata) case cacao.StepTypePlaybookAction: return decomposer.playbookActionExecutor.Execute(metadata, step, variables) + case cacao.StepTypeIfCondition: + stepId, branch, err := decomposer.conditionExecutor.Execute(metadata, step) + if err != nil { + return cacao.NewVariables(), err + } + if branch { + return decomposer.ExecuteBranch(stepId, variables) + } + return variables, nil default: // NOTE: This currently silently handles unknown step types. Should we return an error instead? return cacao.NewVariables(), nil From f8c15b37f29b841304a2fe325c93e81d5de10021 Mon Sep 17 00:00:00 2001 From: Maarten de Kruijf Date: Fri, 3 May 2024 17:00:33 +0200 Subject: [PATCH 05/17] Added decomposer tests for condition step --- internal/decomposer/decomposer.go | 10 +- internal/executors/condition/condition.go | 4 +- test/unittest/decomposer/decomposer_test.go | 200 +++++++++++++++++- .../condition/condition_executor.go | 19 ++ 4 files changed, 223 insertions(+), 10 deletions(-) create mode 100644 test/unittest/mocks/mock_executor/condition/condition_executor.go diff --git a/internal/decomposer/decomposer.go b/internal/decomposer/decomposer.go index 69797646..a1ee6545 100644 --- a/internal/decomposer/decomposer.go +++ b/internal/decomposer/decomposer.go @@ -40,12 +40,16 @@ func init() { func New(actionExecutor action.IExecuter, playbookActionExecutor executors.IPlaybookExecuter, - guid guid.IGuid, reporter reporter.IWorkflowReporter) *Decomposer { + condition condition.IExecuter, + guid guid.IGuid, + reporter reporter.IWorkflowReporter) *Decomposer { return &Decomposer{actionExecutor: actionExecutor, playbookActionExecutor: playbookActionExecutor, + conditionExecutor: condition, guid: guid, - reporter: reporter} + reporter: reporter, + } } type Decomposer struct { @@ -160,7 +164,7 @@ func (decomposer *Decomposer) ExecuteStep(step cacao.Step, scopeVariables cacao. case cacao.StepTypePlaybookAction: return decomposer.playbookActionExecutor.Execute(metadata, step, variables) case cacao.StepTypeIfCondition: - stepId, branch, err := decomposer.conditionExecutor.Execute(metadata, step) + stepId, branch, err := decomposer.conditionExecutor.Execute(metadata, step, variables) if err != nil { return cacao.NewVariables(), err } diff --git a/internal/executors/condition/condition.go b/internal/executors/condition/condition.go index da9f332f..d25e8982 100644 --- a/internal/executors/condition/condition.go +++ b/internal/executors/condition/condition.go @@ -25,14 +25,14 @@ func New(capabilities map[string]capability.ICapability) *Executor { type IExecuter interface { Execute(metadata execution.Metadata, - step cacao.Step) (string, bool, error) + step cacao.Step, variables cacao.Variables) (string, bool, error) } type Executor struct { capabilities map[string]capability.ICapability } -func (executor *Executor) Execute(meta execution.Metadata, step cacao.Step) (string, bool, error) { +func (executor *Executor) Execute(meta execution.Metadata, step cacao.Step, variables cacao.Variables) (string, bool, error) { if step.Type != cacao.StepTypeIfCondition { err := errors.New("the provided step type is not compatible with this executor") diff --git a/test/unittest/decomposer/decomposer_test.go b/test/unittest/decomposer/decomposer_test.go index a2fe38db..1db983cc 100644 --- a/test/unittest/decomposer/decomposer_test.go +++ b/test/unittest/decomposer/decomposer_test.go @@ -10,6 +10,7 @@ import ( "soarca/models/cacao" "soarca/models/execution" "soarca/test/unittest/mocks/mock_executor" + mock_condition_executor "soarca/test/unittest/mocks/mock_executor/condition" mock_playbook_action_executor "soarca/test/unittest/mocks/mock_executor/playbook_action" "soarca/test/unittest/mocks/mock_guid" "soarca/test/unittest/mocks/mock_reporter" @@ -21,6 +22,7 @@ import ( func TestExecutePlaybook(t *testing.T) { mock_action_executor := new(mock_executor.Mock_Action_Executor) mock_playbook_action_executor := new(mock_playbook_action_executor.Mock_PlaybookActionExecutor) + mock_condition_executor := new(mock_condition_executor.Mock_Condition) uuid_mock := new(mock_guid.Mock_Guid) mock_reporter := new(mock_reporter.Mock_Reporter) @@ -37,7 +39,9 @@ func TestExecutePlaybook(t *testing.T) { decomposer := decomposer.New(mock_action_executor, mock_playbook_action_executor, - uuid_mock, mock_reporter) + mock_condition_executor, + uuid_mock, + mock_reporter) step1 := cacao.Step{ Type: "action", @@ -117,6 +121,7 @@ func TestExecutePlaybook(t *testing.T) { func TestExecutePlaybookMultiStep(t *testing.T) { mock_action_executor := new(mock_executor.Mock_Action_Executor) mock_playbook_action_executor := new(mock_playbook_action_executor.Mock_PlaybookActionExecutor) + mock_condition_executor := new(mock_condition_executor.Mock_Condition) uuid_mock := new(mock_guid.Mock_Guid) mock_reporter := new(mock_reporter.Mock_Reporter) @@ -144,7 +149,9 @@ func TestExecutePlaybookMultiStep(t *testing.T) { decomposer := decomposer.New(mock_action_executor, mock_playbook_action_executor, - uuid_mock, mock_reporter) + mock_condition_executor, + uuid_mock, + mock_reporter) step1 := cacao.Step{ Type: "action", @@ -262,6 +269,7 @@ Test with an Empty OnCompletion will result in not executing the step. func TestExecuteEmptyMultiStep(t *testing.T) { mock_action_executor2 := new(mock_executor.Mock_Action_Executor) mock_playbook_action_executor2 := new(mock_playbook_action_executor.Mock_PlaybookActionExecutor) + mock_condition_executor := new(mock_condition_executor.Mock_Condition) uuid_mock2 := new(mock_guid.Mock_Guid) mock_reporter := new(mock_reporter.Mock_Reporter) @@ -288,7 +296,9 @@ func TestExecuteEmptyMultiStep(t *testing.T) { decomposer2 := decomposer.New(mock_action_executor2, mock_playbook_action_executor2, - uuid_mock2, mock_reporter) + mock_condition_executor, + uuid_mock2, + mock_reporter) step1 := cacao.Step{ Type: "ssh", @@ -332,6 +342,7 @@ Test with an not occuring on completion id will result in not executing the step func TestExecuteIllegalMultiStep(t *testing.T) { mock_action_executor2 := new(mock_executor.Mock_Action_Executor) mock_playbook_action_executor2 := new(mock_playbook_action_executor.Mock_PlaybookActionExecutor) + mock_condition_executor := new(mock_condition_executor.Mock_Condition) uuid_mock2 := new(mock_guid.Mock_Guid) mock_reporter := new(mock_reporter.Mock_Reporter) @@ -348,7 +359,9 @@ func TestExecuteIllegalMultiStep(t *testing.T) { decomposer2 := decomposer.New(mock_action_executor2, mock_playbook_action_executor2, - uuid_mock2, mock_reporter) + mock_condition_executor, + uuid_mock2, + mock_reporter) step1 := cacao.Step{ Type: "action", @@ -386,6 +399,7 @@ func TestExecuteIllegalMultiStep(t *testing.T) { func TestExecutePlaybookAction(t *testing.T) { mock_action_executor := new(mock_executor.Mock_Action_Executor) mock_playbook_action_executor := new(mock_playbook_action_executor.Mock_PlaybookActionExecutor) + mock_condition_executor := new(mock_condition_executor.Mock_Condition) uuid_mock := new(mock_guid.Mock_Guid) mock_reporter := new(mock_reporter.Mock_Reporter) expectedVariables := cacao.Variable{ @@ -396,7 +410,9 @@ func TestExecutePlaybookAction(t *testing.T) { decomposer := decomposer.New(mock_action_executor, mock_playbook_action_executor, - uuid_mock, mock_reporter) + mock_condition_executor, + uuid_mock, + mock_reporter) step1 := cacao.Step{ Type: "playbook-action", @@ -444,3 +460,177 @@ func TestExecutePlaybookAction(t *testing.T) { assert.Equal(t, found, true) assert.Equal(t, value.Value, "value") } + +func TestExecuteIfCondition(t *testing.T) { + + mock_action_executor := new(mock_executor.Mock_Action_Executor) + mock_playbook_action_executor := new(mock_playbook_action_executor.Mock_PlaybookActionExecutor) + mock_condition_executor := new(mock_condition_executor.Mock_Condition) + uuid_mock := new(mock_guid.Mock_Guid) + mock_reporter := new(mock_reporter.Mock_Reporter) + expectedVariables := cacao.Variable{ + Type: "string", + Name: "__var1__", + Value: "testing", + } + + // returned from step + expectedVariables2 := cacao.Variable{ + Type: "string", + Name: "__var2__", + Value: "testing2", + } + + expectedCommand := cacao.Command{ + Type: "ssh", + Command: "ssh ls -la", + } + + expectedTarget := cacao.AgentTarget{ + Name: "sometarget", + AuthInfoIdentifier: "id", + } + + expectedAgent := cacao.AgentTarget{ + Type: "soarca", + Name: "soarca-ssh", + } + + decomposer := decomposer.New(mock_action_executor, + mock_playbook_action_executor, + mock_condition_executor, + uuid_mock, + mock_reporter) + + end := cacao.Step{ + Type: cacao.StepTypeEnd, + ID: "end--test", + Name: "end step", + } + + endTrue := cacao.Step{ + Type: cacao.StepTypeEnd, + ID: "end--true", + Name: "end branch true step", + } + + stepTrue := cacao.Step{ + Type: cacao.StepTypeAction, + ID: "action--step-true", + Name: "ssh-tests", + Commands: []cacao.Command{expectedCommand}, + Targets: []string{expectedTarget.ID}, + StepVariables: cacao.NewVariables(expectedVariables), + OnCompletion: endTrue.ID, + } + + endFalse := cacao.Step{ + Type: cacao.StepTypeEnd, + ID: "end--false", + Name: "end branch false step", + } + + stepFalse := cacao.Step{ + Type: cacao.StepTypeAction, + ID: "action--step-false", + Name: "ssh-tests", + Commands: []cacao.Command{expectedCommand}, + Targets: []string{expectedTarget.ID}, + StepVariables: cacao.NewVariables(expectedVariables), + OnCompletion: endFalse.ID, + } + + stepCompletion := cacao.Step{ + Type: cacao.StepTypeAction, + ID: "action--step-completion", + Name: "ssh-tests", + Commands: []cacao.Command{expectedCommand}, + Targets: []string{expectedTarget.ID}, + StepVariables: cacao.NewVariables(expectedVariables), + OnCompletion: end.ID, + } + + stepIf := cacao.Step{ + Type: cacao.StepTypeIfCondition, + ID: "if-condition--test", + Name: "if condition", + StepVariables: cacao.NewVariables(expectedVariables), + Condition: "__var1__:value = testing", + OnTrue: stepTrue.ID, + OnFalse: stepFalse.ID, + OnCompletion: stepCompletion.ID, + } + + start := cacao.Step{ + Type: cacao.StepTypeStart, + ID: "start--test", + Name: "start step", + OnCompletion: stepIf.ID, + } + + playbook := cacao.Playbook{ + ID: "test", + Type: "test", + Name: "playbook-test", + WorkflowStart: start.ID, + Workflow: map[string]cacao.Step{start.ID: start, + stepIf.ID: stepIf, + stepTrue.ID: stepTrue, + stepFalse.ID: stepFalse, + stepCompletion.ID: stepCompletion, + end.ID: end, + endTrue.ID: endTrue, + endFalse.ID: endFalse}, + AgentDefinitions: map[string]cacao.AgentTarget{expectedAgent.ID: expectedAgent}, + TargetDefinitions: map[string]cacao.AgentTarget{expectedTarget.ID: expectedTarget}, + } + + executionId, _ := uuid.Parse("6ba7b810-9dad-11d1-80b4-00c04fd430c8") + metaStepIf := execution.Metadata{ExecutionId: executionId, PlaybookId: "test", StepId: stepIf.ID} + + uuid_mock.On("New").Return(executionId) + mock_reporter.On("ReportWorkflow", executionId, playbook).Return() + + mock_condition_executor.On("Execute", + metaStepIf, + stepIf, + cacao.NewVariables(expectedVariables)).Return(stepTrue.ID, true, nil) + + stepTrueDetails := action.PlaybookStepMetadata{ + Step: stepTrue, + Targets: playbook.TargetDefinitions, + Auth: playbook.AuthenticationInfoDefinitions, + Agent: expectedAgent, + Variables: cacao.NewVariables(expectedVariables), + } + + metaStepTrue := execution.Metadata{ExecutionId: executionId, PlaybookId: "test", StepId: stepTrue.ID} + + mock_action_executor.On("Execute", + metaStepTrue, + stepTrueDetails).Return(cacao.NewVariables(expectedVariables2), nil) + + stepCompletionDetails := action.PlaybookStepMetadata{ + Step: stepCompletion, + Targets: playbook.TargetDefinitions, + Auth: playbook.AuthenticationInfoDefinitions, + Agent: expectedAgent, + Variables: cacao.NewVariables(expectedVariables, expectedVariables2), + } + + metaStepCompletion := execution.Metadata{ExecutionId: executionId, PlaybookId: "test", StepId: stepCompletion.ID} + + mock_action_executor.On("Execute", + metaStepCompletion, + stepCompletionDetails).Return(cacao.NewVariables(), nil) + + details, err := decomposer.Execute(playbook) + uuid_mock.AssertExpectations(t) + fmt.Println(err) + assert.Equal(t, err, nil) + assert.Equal(t, details.ExecutionId, executionId) + mock_reporter.AssertExpectations(t) + mock_condition_executor.AssertExpectations(t) + mock_action_executor.AssertExpectations(t) + +} diff --git a/test/unittest/mocks/mock_executor/condition/condition_executor.go b/test/unittest/mocks/mock_executor/condition/condition_executor.go new file mode 100644 index 00000000..3774c2c6 --- /dev/null +++ b/test/unittest/mocks/mock_executor/condition/condition_executor.go @@ -0,0 +1,19 @@ +package mock_condition_executor + +import ( + "soarca/models/cacao" + "soarca/models/execution" + + "github.com/stretchr/testify/mock" +) + +type Mock_Condition struct { + mock.Mock +} + +func (executer *Mock_Condition) Execute(metadata execution.Metadata, + step cacao.Step, + variables cacao.Variables) (string, bool, error) { + args := executer.Called(metadata, step, variables) + return args.String(0), args.Bool(1), args.Error(2) +} From 8f1d877143b71471740abb5c671015e3876bbdf5 Mon Sep 17 00:00:00 2001 From: Maarten de Kruijf Date: Sun, 5 May 2024 14:41:08 +0200 Subject: [PATCH 06/17] Added condition executor to controller --- internal/controller/controller.go | 8 +++++++- internal/executors/condition/condition.go | 5 +---- 2 files changed, 8 insertions(+), 5 deletions(-) diff --git a/internal/controller/controller.go b/internal/controller/controller.go index 757ac379..bbfd4b6a 100644 --- a/internal/controller/controller.go +++ b/internal/controller/controller.go @@ -15,6 +15,7 @@ import ( "soarca/internal/capability/ssh" "soarca/internal/decomposer" "soarca/internal/executors/action" + "soarca/internal/executors/condition" "soarca/internal/executors/playbook_action" "soarca/internal/fin/protocol" "soarca/internal/guid" @@ -78,8 +79,13 @@ func (controller *Controller) NewDecomposer() decomposer.IDecomposer { actionExecutor := action.New(capabilities, reporter) playbookActionExecutor := playbook_action.New(controller, controller, reporter) + conditionExecutor := condition.New() guid := new(guid.Guid) - decompose := decomposer.New(actionExecutor, playbookActionExecutor, guid, reporter) + decompose := decomposer.New(actionExecutor, + playbookActionExecutor, + conditionExecutor, + guid, + reporter) return decompose } diff --git a/internal/executors/condition/condition.go b/internal/executors/condition/condition.go index d25e8982..eb19b01b 100644 --- a/internal/executors/condition/condition.go +++ b/internal/executors/condition/condition.go @@ -3,7 +3,6 @@ package condition import ( "errors" "reflect" - "soarca/internal/capability" "soarca/logger" "soarca/models/cacao" "soarca/models/execution" @@ -17,9 +16,8 @@ func init() { log = logger.Logger(component, logger.Info, "", logger.Json) } -func New(capabilities map[string]capability.ICapability) *Executor { +func New() *Executor { var instance = Executor{} - instance.capabilities = capabilities return &instance } @@ -29,7 +27,6 @@ type IExecuter interface { } type Executor struct { - capabilities map[string]capability.ICapability } func (executor *Executor) Execute(meta execution.Metadata, step cacao.Step, variables cacao.Variables) (string, bool, error) { From a10feeaa323cb6e33f8260c5bda4531a78844047 Mon Sep 17 00:00:00 2001 From: Maarten de Kruijf Date: Sun, 5 May 2024 16:38:40 +0200 Subject: [PATCH 07/17] Added all variable types and tests --- test/unittest/utils/stix/stix_test.go | 327 ++++++++++++++++++-------- utils/stix/strix.go | 188 ++++++++++++++- 2 files changed, 409 insertions(+), 106 deletions(-) diff --git a/test/unittest/utils/stix/stix_test.go b/test/unittest/utils/stix/stix_test.go index 49392671..5ad9dfa8 100644 --- a/test/unittest/utils/stix/stix_test.go +++ b/test/unittest/utils/stix/stix_test.go @@ -5,7 +5,6 @@ import ( "soarca/models/cacao" "soarca/utils/stix" "testing" - "time" "github.com/go-playground/assert/v2" ) @@ -50,7 +49,7 @@ func TestStringEquals(t *testing.T) { result, err = stix.Evaluate("a = b c", vars) assert.Equal(t, result, false) assert.Equal(t, err, errors.New("comparisons can only contain 3 parts as per STIX specification")) - time.Now() + } func TestIntEquals(t *testing.T) { @@ -99,95 +98,235 @@ func TestIntEquals(t *testing.T) { assert.Equal(t, err, errors.New("comparisons can only contain 3 parts as per STIX specification")) } -// func TestNotEquals(t *testing.T) { -// result, err := stix.Evaluate("a != a") -// assert.Equal(t, result, false) -// assert.Equal(t, err, nil) -// result, err = stix.Evaluate("a != b") -// assert.Equal(t, result, true) -// assert.Equal(t, err, nil) -// } - -// func TestGreater(t *testing.T) { -// result, err := stix.Evaluate("2 > 1") -// assert.Equal(t, result, true) -// assert.Equal(t, err, nil) -// result, err = stix.Evaluate("200000000 > 10000000") -// assert.Equal(t, result, true) -// assert.Equal(t, err, nil) -// result, err = stix.Evaluate("200 > 199") -// assert.Equal(t, result, true) -// assert.Equal(t, err, nil) -// result, err = stix.Evaluate("199 > 200") -// assert.Equal(t, result, false) -// assert.Equal(t, err, nil) -// } - -// func TestLess(t *testing.T) { -// result, err := stix.Evaluate("2 < 1") -// assert.Equal(t, result, false) -// assert.Equal(t, err, nil) -// result, err = stix.Evaluate("200000000 < 10000000") -// assert.Equal(t, result, false) -// assert.Equal(t, err, nil) -// result, err = stix.Evaluate("200 < 199") -// assert.Equal(t, result, false) -// assert.Equal(t, err, nil) -// result, err = stix.Evaluate("199 < 200") -// assert.Equal(t, result, true) -// assert.Equal(t, err, nil) -// } - -// func TestLessEqual(t *testing.T) { -// result, err := stix.Evaluate("2 <= 1") -// assert.Equal(t, result, false) -// assert.Equal(t, err, nil) -// result, err = stix.Evaluate("200000000 <= 10000000") -// assert.Equal(t, result, false) -// assert.Equal(t, err, nil) -// result, err = stix.Evaluate("200 <= 199") -// assert.Equal(t, result, false) -// assert.Equal(t, err, nil) -// result, err = stix.Evaluate("199 <= 200") -// assert.Equal(t, result, true) -// assert.Equal(t, err, nil) -// result, err = stix.Evaluate("200 <= 200") -// assert.Equal(t, result, true) -// assert.Equal(t, err, nil) -// } - -// func TestGreaterEqual(t *testing.T) { -// result, err := stix.Evaluate("2 >= 1") -// assert.Equal(t, result, true) -// assert.Equal(t, err, nil) -// result, err = stix.Evaluate("10000000 >= 200000000") -// assert.Equal(t, result, false) -// assert.Equal(t, err, nil) -// result, err = stix.Evaluate("200 >= 199") -// assert.Equal(t, result, true) -// assert.Equal(t, err, nil) -// result, err = stix.Evaluate("199 >= 200") -// assert.Equal(t, result, false) -// assert.Equal(t, err, nil) -// result, err = stix.Evaluate("200 >= 200") -// assert.Equal(t, result, true) -// assert.Equal(t, err, nil) -// } - -// func TestIn(t *testing.T) { -// result, err := stix.Evaluate("2 = (1,2,3,4)") -// assert.Equal(t, result, true) -// assert.Equal(t, err, nil) -// result, err = stix.Evaluate("10000000 >= 200000000") -// assert.Equal(t, result, false) -// assert.Equal(t, err, nil) -// result, err = stix.Evaluate("200 >= 199") -// assert.Equal(t, result, true) -// assert.Equal(t, err, nil) -// result, err = stix.Evaluate("199 >= 200") -// assert.Equal(t, result, false) -// assert.Equal(t, err, nil) -// result, err = stix.Evaluate("200 >= 200") -// assert.Equal(t, result, true) -// assert.Equal(t, err, nil) -// } +func TestFloatEquals(t *testing.T) { + + var1 := cacao.Variable{Type: cacao.VariableTypeFloat} + var1.Value = "1000.0" + var1.Name = "__var1__" + vars := cacao.NewVariables(var1) + + result, err := stix.Evaluate("__var1__:value = 1000.0", vars) + assert.Equal(t, err, nil) + assert.Equal(t, result, true) + + result, err = stix.Evaluate("__var1__:value = 1000.000000000000000000001", vars) + assert.Equal(t, err, nil) + assert.Equal(t, result, true) + + result, err = stix.Evaluate("__var1__:value = 1000.000001", vars) + assert.Equal(t, err, nil) + assert.Equal(t, result, false) + + result, err = stix.Evaluate("__var1__:value = 9999", vars) + assert.Equal(t, result, false) + assert.Equal(t, err, nil) + + result, err = stix.Evaluate("__var1__:value = 10000", vars) + assert.Equal(t, result, false) + assert.Equal(t, err, nil) + + result, err = stix.Evaluate("__var1__:value > 999", vars) + assert.Equal(t, result, true) + assert.Equal(t, err, nil) + + result, err = stix.Evaluate("__var1__:value < 1001", vars) + assert.Equal(t, result, true) + assert.Equal(t, err, nil) + + result, err = stix.Evaluate("__var1__:value <= 1000", vars) + assert.Equal(t, result, true) + assert.Equal(t, err, nil) + + result, err = stix.Evaluate("__var1__:value >= 1000", vars) + assert.Equal(t, result, true) + assert.Equal(t, err, nil) + + result, err = stix.Evaluate("__var1__:value >= a", vars) + assert.Equal(t, result, false) + assert.NotEqual(t, err, nil) + + result, err = stix.Evaluate("a = b", vars) + assert.Equal(t, result, false) + assert.Equal(t, err, errors.New("comparisons can only contain 3 parts as per STIX specification")) + result, err = stix.Evaluate("a = b c", vars) + assert.Equal(t, result, false) + assert.Equal(t, err, errors.New("comparisons can only contain 3 parts as per STIX specification")) +} + +func TestIp4AddressEquals(t *testing.T) { + var1 := cacao.Variable{Type: cacao.VariableTypeIpv4Address} + var1.Value = "10.0.0.30" + var1.Name = "__var1__" + vars := cacao.NewVariables(var1) + + result, err := stix.Evaluate("__var1__:value = 10.0.0.30", vars) + assert.Equal(t, err, nil) + assert.Equal(t, result, true) + + result, err = stix.Evaluate("__var1__:value IN 10.0.0.0/8", vars) + assert.Equal(t, err, nil) + assert.Equal(t, result, true) + + result, err = stix.Evaluate("__var1__:value IN 10.30.0.0/16", vars) + assert.Equal(t, err, nil) + assert.Equal(t, result, false) + + result, err = stix.Evaluate("__var1__:value != 10.0.0.31", vars) + assert.Equal(t, err, nil) + assert.Equal(t, result, true) +} + +func TestIp6AddressEquals(t *testing.T) { + var1 := cacao.Variable{Type: cacao.VariableTypeIpv6Address} + var1.Value = "2001:db8::1" + var1.Name = "__var1__" + vars := cacao.NewVariables(var1) + + result, err := stix.Evaluate("__var1__:value = 2001:db8::1", vars) + assert.Equal(t, err, nil) + assert.Equal(t, result, true) + + result, err = stix.Evaluate("__var1__:value IN 2001:db8::1/64", vars) + assert.Equal(t, err, nil) + assert.Equal(t, result, true) + + result, err = stix.Evaluate("__var1__:value IN 2001:db81::1/64", vars) + assert.Equal(t, err, nil) + assert.Equal(t, result, false) + + result, err = stix.Evaluate("__var1__:value != 2001:db8::2", vars) + assert.Equal(t, err, nil) + assert.Equal(t, result, true) +} + +func TestMacAddressEquals(t *testing.T) { + var1 := cacao.Variable{Type: cacao.VariableTypeMacAddress} + var1.Value = "BC-24-11-00-00-01" + var1.Name = "__var1__" + vars := cacao.NewVariables(var1) + + result, err := stix.Evaluate("__var1__:value = BC-24-11-00-00-01", vars) + assert.Equal(t, err, nil) + assert.Equal(t, result, true) + + var2 := cacao.Variable{Type: cacao.VariableTypeMacAddress} + var2.Value = "BC:24:11:00:00:01" + var2.Name = "__var2__" + vars2 := cacao.NewVariables(var2) + + result, err = stix.Evaluate("__var2__:value = BC:24:11:00:00:01", vars2) + assert.Equal(t, err, nil) + assert.Equal(t, result, true) + + // Mixed notations + result, err = stix.Evaluate("__var1__:value = BC:24:11:00:00:01", vars) + assert.Equal(t, err, nil) + assert.Equal(t, result, true) + + result, err = stix.Evaluate("__var1__:value > BC:24:11:00:00:00", vars) + assert.Equal(t, err, nil) + assert.Equal(t, result, true) + + result, err = stix.Evaluate("__var1__:value < BC:24:11:00:00:02", vars) + assert.Equal(t, err, nil) + assert.Equal(t, result, true) +} + +func TestHashEquals(t *testing.T) { + md5 := cacao.Variable{Type: cacao.VariableTypeMd5Has} + md5.Value = "d41d8cd98f00b204e9800998ecf8427e" + md5.Name = "__md5__" + + sha1 := cacao.Variable{Type: cacao.VariableTypeMd5Has} + sha1.Value = "da39a3ee5e6b4b0d3255bfef95601890afd80709" + sha1.Name = "__sha1__" + + sha224 := cacao.Variable{Type: cacao.VariableTypeHash} + sha224.Value = "d14a028c2a3a2bc9476102bb288234c415a2b01f828ea62ac5b3e42f" + sha224.Name = "__sha224__" + + sha256 := cacao.Variable{Type: cacao.VariableTypeSha256} + sha256.Value = "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855" + sha256.Name = "__sha256__" + + sha384 := cacao.Variable{Type: cacao.VariableTypeHash} + sha384.Value = "38b060a751ac96384cd9327eb1b1e36a21fdb71114be07434c0cc7bf63f6e1da274edebfe76f65fbd51ad2f14898b95b" + sha384.Name = "__sha384__" + + sha512 := cacao.Variable{Type: cacao.VariableTypeHash} + sha512.Value = "cf83e1357eefb8bdf1542850d66d8007d620e4050b5715dc83f4a921d36ce9ce47d0d13c5d85f2b0ff8318d2877eec2f63b931bd47417a81a538327af927da3e" + sha512.Name = "__sha512__" + + vars := cacao.NewVariables(md5, sha1, sha224, sha256, sha384, sha512) + + result, err := stix.Evaluate("__md5__:value = d41d8cd98f00b204e9800998ecf8427e", vars) + assert.Equal(t, err, nil) + assert.Equal(t, result, true) + + result, err = stix.Evaluate("__sha1__:value = da39a3ee5e6b4b0d3255bfef95601890afd80709", vars) + assert.Equal(t, err, nil) + assert.Equal(t, result, true) + + result, err = stix.Evaluate("__sha224__:value = d14a028c2a3a2bc9476102bb288234c415a2b01f828ea62ac5b3e42f", vars) + assert.Equal(t, err, nil) + assert.Equal(t, result, true) + + result, err = stix.Evaluate("__sha256__:value = e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855", vars) + assert.Equal(t, err, nil) + assert.Equal(t, result, true) + + result, err = stix.Evaluate("__sha384__:value = 38b060a751ac96384cd9327eb1b1e36a21fdb71114be07434c0cc7bf63f6e1da274edebfe76f65fbd51ad2f14898b95b", vars) + assert.Equal(t, err, nil) + assert.Equal(t, result, true) + + result, err = stix.Evaluate("__sha512__:value = cf83e1357eefb8bdf1542850d66d8007d620e4050b5715dc83f4a921d36ce9ce47d0d13c5d85f2b0ff8318d2877eec2f63b931bd47417a81a538327af927da3e", vars) + assert.Equal(t, err, nil) + assert.Equal(t, result, true) + +} + +func TestUriEquals(t *testing.T) { + + var1 := cacao.Variable{Type: cacao.VariableTypeUri} + var1.Value = "https://google.com" + var1.Name = "__var1__" + vars := cacao.NewVariables(var1) + + result, err := stix.Evaluate("__var1__:value = https://google.com", vars) + assert.Equal(t, err, nil) + assert.Equal(t, result, true) + + result, err = stix.Evaluate("__var1__:value = https://example.com", vars) + assert.Equal(t, err, nil) + assert.Equal(t, result, false) + + result, err = stix.Evaluate("__var1__:value > https://example.com", vars) + assert.Equal(t, err, errors.New("operator: "+">"+" not valid or implemented")) + assert.Equal(t, result, false) + +} + +func TestUuidEquals(t *testing.T) { + + var1 := cacao.Variable{Type: cacao.VariableTypeUuid} + var1.Value = "ec887691-9a21-4ccf-8fae-360c13a819d1" + var1.Name = "__var1__" + vars := cacao.NewVariables(var1) + + result, err := stix.Evaluate("__var1__:value = ec887691-9a21-4ccf-8fae-360c13a819d1", vars) + assert.Equal(t, err, nil) + assert.Equal(t, result, true) + + result, err = stix.Evaluate("__var1__:value = ec887691-9a21-4ccf-8fae-360c13a819d2", vars) + assert.Equal(t, err, nil) + assert.Equal(t, result, false) + + result, err = stix.Evaluate("__var1__:value != ec887691-9a21-4ccf-8fae-360c13a819d2", vars) + assert.Equal(t, err, nil) + assert.Equal(t, result, true) + + result, err = stix.Evaluate("__var1__:value > ec887691-9a21-4ccf-8fae-360c13a819d2", vars) + assert.Equal(t, err, errors.New("operator: "+">"+" not valid or implemented")) + assert.Equal(t, result, false) + +} diff --git a/utils/stix/strix.go b/utils/stix/strix.go index 8ff30b2b..5d3f32c4 100644 --- a/utils/stix/strix.go +++ b/utils/stix/strix.go @@ -3,9 +3,15 @@ package stix import ( "errors" "fmt" + "net" + "net/url" + "reflect" + "soarca/logger" "soarca/models/cacao" "strconv" "strings" + + "github.com/google/uuid" ) const ( @@ -22,6 +28,17 @@ const ( IsSuperSet = "ISSUPERSET" ) +type Empty struct{} + +var ( + component = reflect.TypeOf(Empty{}).PkgPath() + log *logger.Log +) + +func init() { + log = logger.Logger(component, logger.Info, "", logger.Json) +} + func Evaluate(expression string, vars cacao.Variables) (bool, error) { //"condition": "__variable__:value == '10.0.0.0/8'" @@ -42,6 +59,8 @@ func Evaluate(expression string, vars cacao.Variables) (bool, error) { parts[0] = vars.Interpolate(parts[0]) switch usedVariable.Type { + case cacao.VariableTypeBool: + return boolCompare(parts) case cacao.VariableTypeString: return stringCompare(parts) case cacao.VariableTypeInt: @@ -50,7 +69,27 @@ func Evaluate(expression string, vars cacao.Variables) (bool, error) { return numberCompare(parts) case cacao.VariableTypeFloat: return floatCompare(parts) - + case cacao.VariableTypeIpv4Address: + return compareIp(parts) + case cacao.VariableTypeIpv6Address: + return compareIp(parts) + case cacao.VariableTypeMacAddress: + return compareMac(parts) + case cacao.VariableTypeHash: + // Just compare the hash strings + return compareHash(parts) + case cacao.VariableTypeMd5Has: + // Just compare the hash strings + return compareHash(parts) + case cacao.VariableTypeSha256: + // Just compare the hash strings + return compareHash(parts) + case cacao.VariableTypeHexString: + return stringCompare(parts) + case cacao.VariableTypeUri: + return compareUri(parts) + case cacao.VariableTypeUuid: + return compareUuid(parts) default: err := errors.New("variable type is not a cacao variable type") return false, err @@ -69,12 +108,6 @@ func findVariable(variable string, vars cacao.Variables) (cacao.Variable, error) return cacao.Variable{}, nil } -func checkIpInRange(ipString string, ipRangeString string) { - // ip, ipRange, err := net.ParseCIDR(ipString) - // ip2, ipRange2, err2 := net.ParseCIDR(ipRangeString) - // ipRange.Mask.Size() -} - func stringCompare(parts []string) (bool, error) { lhs := parts[0] @@ -97,7 +130,7 @@ func stringCompare(parts []string) (bool, error) { case In: return strings.Contains(lhs, rhs), nil default: - err := errors.New("operator not valid") + err := errors.New("operator: " + comparator + " not valid or implemented") return false, err } } @@ -128,19 +161,19 @@ func numberCompare(parts []string) (bool, error) { case GreaterOrEqual: return lhs >= rhs, nil default: - err := errors.New("operator not valid") + err := errors.New("operator: " + comparator + " not valid or implemented") return false, err } } func floatCompare(parts []string) (bool, error) { - lhs, err := strconv.ParseFloat(parts[0], 0) + lhs, err := strconv.ParseFloat(parts[0], 64) if err != nil { return false, err } comparator := parts[1] - rhs, err := strconv.ParseFloat(parts[2], 0) + rhs, err := strconv.ParseFloat(parts[2], 64) if err != nil { return false, err } @@ -159,7 +192,138 @@ func floatCompare(parts []string) (bool, error) { case GreaterOrEqual: return lhs >= rhs, nil default: - err := errors.New("operator not valid") + err := errors.New("operator: " + comparator + " not valid or implemented") + return false, err + } +} + +func boolCompare(parts []string) (bool, error) { + lhs, err := strconv.ParseBool(parts[0]) + if err != nil { + return false, err + } + comparator := parts[1] + rhs, err := strconv.ParseBool(parts[2]) + if err != nil { + return false, err + } + switch comparator { + case Equal: + return lhs == rhs, nil + case NotEqual: + return lhs != rhs, nil + default: + err := errors.New("operator: " + comparator + " not valid or implemented") + return false, err + } +} + +func compareIp(parts []string) (bool, error) { + lhsIp := net.ParseIP(parts[0]) + + comparator := parts[1] + rhsIp := net.ParseIP(parts[2]) + switch comparator { + case Equal: + return lhsIp.Equal(rhsIp), nil + case NotEqual: + return !lhsIp.Equal(rhsIp), nil + case In: + _, rhsNet, err := net.ParseCIDR(parts[2]) + if err != nil { + return false, err + } + return rhsNet.Contains(lhsIp), err + default: + err := errors.New("operator: " + comparator + " not valid or implemented") + return false, err + } +} + +// Validate if they are hardware MAC addresses and do a string compare +func compareMac(parts []string) (bool, error) { + lhs, err := net.ParseMAC(parts[0]) + if err != nil { + return false, err + } + + rhs, err := net.ParseMAC(parts[2]) + if err != nil { + return false, err + } + + newParts := []string{lhs.String(), parts[1], rhs.String()} + + return stringCompare(newParts) +} + +func compareHash(parts []string) (bool, error) { + lhs := parts[0] + rhs := parts[2] + if len(lhs) != len(rhs) { + log.Warning("hash lengths do not match") + } + + switch len(lhs) { + case 32: + log.Trace("MD5 type hash") + case 40: + log.Trace("SHA1 type hash") + case 56: + log.Trace("SHA224 type hash") + case 64: + log.Trace("SHA256 type hash") + case 96: + log.Trace("SHA384 type hash") + case 128: + log.Trace("SHA512 type hash") + default: + log.Warning("unknown hash length of: " + string(len(lhs))) + } + + return stringCompare(parts) +} + +func compareUri(parts []string) (bool, error) { + lhs, err := url.Parse(parts[0]) + if err != nil { + return false, err + } + comparator := parts[1] + rhs, err := url.Parse(parts[2]) + if err != nil { + return false, err + } + + switch comparator { + case Equal: + return lhs.String() == rhs.String(), nil + case NotEqual: + return lhs.String() != rhs.String(), nil + default: + err := errors.New("operator: " + comparator + " not valid or implemented") + return false, err + } +} + +func compareUuid(parts []string) (bool, error) { + lhs, err := uuid.Parse(parts[0]) + if err != nil { + return false, err + } + comparator := parts[1] + rhs, err := uuid.Parse(parts[2]) + if err != nil { + return false, err + } + + switch comparator { + case Equal: + return lhs == rhs, nil + case NotEqual: + return lhs != rhs, nil + default: + err := errors.New("operator: " + comparator + " not valid or implemented") return false, err } } From a2c052956e7a63851cfd77214b9f5324cfc30b6a Mon Sep 17 00:00:00 2001 From: Maarten de Kruijf Date: Sun, 5 May 2024 16:42:43 +0200 Subject: [PATCH 08/17] Fixed linting issues --- utils/stix/strix.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/utils/stix/strix.go b/utils/stix/strix.go index 5d3f32c4..ead34ded 100644 --- a/utils/stix/strix.go +++ b/utils/stix/strix.go @@ -278,7 +278,7 @@ func compareHash(parts []string) (bool, error) { case 128: log.Trace("SHA512 type hash") default: - log.Warning("unknown hash length of: " + string(len(lhs))) + log.Warning("unknown hash length of: " + fmt.Sprint(len(lhs))) } return stringCompare(parts) From 56afa63e1f8d6f0603e9fc34291099ac3b5231ac Mon Sep 17 00:00:00 2001 From: Maarten de Kruijf Date: Mon, 6 May 2024 12:02:17 +0200 Subject: [PATCH 09/17] Added stix expression mock --- .../mocks/mock_utils/stix/mock_stix.go | 20 +++++++++++++++++++ 1 file changed, 20 insertions(+) create mode 100644 test/unittest/mocks/mock_utils/stix/mock_stix.go diff --git a/test/unittest/mocks/mock_utils/stix/mock_stix.go b/test/unittest/mocks/mock_utils/stix/mock_stix.go new file mode 100644 index 00000000..c0fec892 --- /dev/null +++ b/test/unittest/mocks/mock_utils/stix/mock_stix.go @@ -0,0 +1,20 @@ +package mock_stix + +import ( + "soarca/models/cacao" + + "github.com/stretchr/testify/mock" +) + +type MockStix struct { + mock.Mock +} + +type MockHttpRequest struct { + mock.Mock +} + +func (stix *MockStix) Evaluate(expression string, vars cacao.Variables) (bool, error) { + args := stix.Called(expression, vars) + return args.Bool(0), args.Error(1) +} From 9e2e31df96cb736ed6b9053a615501533b894843 Mon Sep 17 00:00:00 2001 From: Maarten de Kruijf Date: Mon, 6 May 2024 12:03:26 +0200 Subject: [PATCH 10/17] Added IStix interface to allow for injection and mocking --- internal/controller/controller.go | 4 +++- internal/executors/condition/condition.go | 8 ++++---- test/unittest/utils/stix/stix_test.go | 10 ++++++++++ utils/stix/strix.go | 16 ++++++++++++---- 4 files changed, 29 insertions(+), 9 deletions(-) diff --git a/internal/controller/controller.go b/internal/controller/controller.go index bbfd4b6a..afd9a725 100644 --- a/internal/controller/controller.go +++ b/internal/controller/controller.go @@ -23,6 +23,7 @@ import ( "soarca/logger" "soarca/utils" httpUtil "soarca/utils/http" + "soarca/utils/stix" downstreamReporter "soarca/internal/reporter/downstream_reporter" @@ -79,7 +80,8 @@ func (controller *Controller) NewDecomposer() decomposer.IDecomposer { actionExecutor := action.New(capabilities, reporter) playbookActionExecutor := playbook_action.New(controller, controller, reporter) - conditionExecutor := condition.New() + stix := stix.New() + conditionExecutor := condition.New(stix) guid := new(guid.Guid) decompose := decomposer.New(actionExecutor, playbookActionExecutor, diff --git a/internal/executors/condition/condition.go b/internal/executors/condition/condition.go index eb19b01b..40329dac 100644 --- a/internal/executors/condition/condition.go +++ b/internal/executors/condition/condition.go @@ -16,9 +16,8 @@ func init() { log = logger.Logger(component, logger.Info, "", logger.Json) } -func New() *Executor { - var instance = Executor{} - return &instance +func New(stix stix.IStix) *Executor { + return &Executor{stix: stix} } type IExecuter interface { @@ -27,6 +26,7 @@ type IExecuter interface { } type Executor struct { + stix stix.IStix } func (executor *Executor) Execute(meta execution.Metadata, step cacao.Step, variables cacao.Variables) (string, bool, error) { @@ -37,7 +37,7 @@ func (executor *Executor) Execute(meta execution.Metadata, step cacao.Step, vari return step.OnFailure, false, err } - result, err := stix.Evaluate(step.Condition, step.StepVariables) + result, err := executor.stix.Evaluate(step.Condition, variables) if err != nil { log.Error(err) return "", false, err diff --git a/test/unittest/utils/stix/stix_test.go b/test/unittest/utils/stix/stix_test.go index 5ad9dfa8..2b4f4a7f 100644 --- a/test/unittest/utils/stix/stix_test.go +++ b/test/unittest/utils/stix/stix_test.go @@ -11,6 +11,8 @@ import ( func TestStringEquals(t *testing.T) { + stix := stix.New() + var1 := cacao.Variable{Type: cacao.VariableTypeString} var1.Value = "a" var1.Name = "__var1__" @@ -53,6 +55,7 @@ func TestStringEquals(t *testing.T) { } func TestIntEquals(t *testing.T) { + stix := stix.New() var1 := cacao.Variable{Type: cacao.VariableTypeLong} var1.Value = "1000" @@ -99,6 +102,7 @@ func TestIntEquals(t *testing.T) { } func TestFloatEquals(t *testing.T) { + stix := stix.New() var1 := cacao.Variable{Type: cacao.VariableTypeFloat} var1.Value = "1000.0" @@ -154,6 +158,7 @@ func TestFloatEquals(t *testing.T) { } func TestIp4AddressEquals(t *testing.T) { + stix := stix.New() var1 := cacao.Variable{Type: cacao.VariableTypeIpv4Address} var1.Value = "10.0.0.30" var1.Name = "__var1__" @@ -177,6 +182,7 @@ func TestIp4AddressEquals(t *testing.T) { } func TestIp6AddressEquals(t *testing.T) { + stix := stix.New() var1 := cacao.Variable{Type: cacao.VariableTypeIpv6Address} var1.Value = "2001:db8::1" var1.Name = "__var1__" @@ -200,6 +206,7 @@ func TestIp6AddressEquals(t *testing.T) { } func TestMacAddressEquals(t *testing.T) { + stix := stix.New() var1 := cacao.Variable{Type: cacao.VariableTypeMacAddress} var1.Value = "BC-24-11-00-00-01" var1.Name = "__var1__" @@ -233,6 +240,7 @@ func TestMacAddressEquals(t *testing.T) { } func TestHashEquals(t *testing.T) { + stix := stix.New() md5 := cacao.Variable{Type: cacao.VariableTypeMd5Has} md5.Value = "d41d8cd98f00b204e9800998ecf8427e" md5.Name = "__md5__" @@ -286,6 +294,7 @@ func TestHashEquals(t *testing.T) { } func TestUriEquals(t *testing.T) { + stix := stix.New() var1 := cacao.Variable{Type: cacao.VariableTypeUri} var1.Value = "https://google.com" @@ -307,6 +316,7 @@ func TestUriEquals(t *testing.T) { } func TestUuidEquals(t *testing.T) { + stix := stix.New() var1 := cacao.Variable{Type: cacao.VariableTypeUuid} var1.Value = "ec887691-9a21-4ccf-8fae-360c13a819d1" diff --git a/utils/stix/strix.go b/utils/stix/strix.go index ead34ded..31c72f1b 100644 --- a/utils/stix/strix.go +++ b/utils/stix/strix.go @@ -28,10 +28,8 @@ const ( IsSuperSet = "ISSUPERSET" ) -type Empty struct{} - var ( - component = reflect.TypeOf(Empty{}).PkgPath() + component = reflect.TypeOf(Stix{}).PkgPath() log *logger.Log ) @@ -39,7 +37,17 @@ func init() { log = logger.Logger(component, logger.Info, "", logger.Json) } -func Evaluate(expression string, vars cacao.Variables) (bool, error) { +type IStix interface { + Evaluate(string, cacao.Variables) (bool, error) +} + +func New() *Stix { + return &Stix{} +} + +type Stix struct{} + +func (s *Stix) Evaluate(expression string, vars cacao.Variables) (bool, error) { //"condition": "__variable__:value == '10.0.0.0/8'" //"condition": "__ip__:value/__subnet__:value == '10.0.0.0/8'" From 6fbe289f284eadda15a4b265a9ed1002b1566371 Mon Sep 17 00:00:00 2001 From: Maarten de Kruijf Date: Mon, 6 May 2024 12:03:38 +0200 Subject: [PATCH 11/17] Added condition executor test --- .../condition/condition_executor_test.go | 64 +++++++++++++++++++ 1 file changed, 64 insertions(+) create mode 100644 test/unittest/executor/condition/condition_executor_test.go diff --git a/test/unittest/executor/condition/condition_executor_test.go b/test/unittest/executor/condition/condition_executor_test.go new file mode 100644 index 00000000..4bd59308 --- /dev/null +++ b/test/unittest/executor/condition/condition_executor_test.go @@ -0,0 +1,64 @@ +package condition_test + +import ( + "soarca/internal/executors/condition" + "soarca/models/cacao" + "soarca/models/execution" + mock_stix "soarca/test/unittest/mocks/mock_utils/stix" + "testing" + + "github.com/go-playground/assert/v2" + "github.com/google/uuid" +) + +func TestExecuteConditionTrue(t *testing.T) { + mock_stix := new(mock_stix.MockStix) + + conditionExecutior := condition.New(mock_stix) + + executionId := uuid.New() + + meta := execution.Metadata{ExecutionId: executionId, + PlaybookId: "1", + StepId: "2"} + + step := cacao.Step{Type: cacao.StepTypeIfCondition, + Condition: "a = a", + OnTrue: "3", + OnFalse: "4"} + vars := cacao.NewVariables() + + mock_stix.On("Evaluate", "a = a", vars).Return(true, nil) + + nextStepId, goToBranch, err := conditionExecutior.Execute(meta, step, vars) + assert.Equal(t, nil, err) + assert.Equal(t, true, goToBranch) + assert.Equal(t, "3", nextStepId) + +} + +func TestExecuteConditionFalse(t *testing.T) { + mock_stix := new(mock_stix.MockStix) + + conditionExecutior := condition.New(mock_stix) + + executionId := uuid.New() + + meta := execution.Metadata{ExecutionId: executionId, + PlaybookId: "1", + StepId: "2"} + + step := cacao.Step{Type: cacao.StepTypeIfCondition, + Condition: "a = a", + OnTrue: "3", + OnFalse: "4"} + vars := cacao.NewVariables() + + mock_stix.On("Evaluate", "a = a", vars).Return(true, nil) + + nextStepId, goToBranch, err := conditionExecutior.Execute(meta, step, vars) + assert.Equal(t, nil, err) + assert.Equal(t, true, goToBranch) + assert.Equal(t, "3", nextStepId) + +} From 9f91cb40f00e669fabd3e15b0518030478a06a53 Mon Sep 17 00:00:00 2001 From: Maarten de Kruijf Date: Wed, 8 May 2024 11:43:46 +0200 Subject: [PATCH 12/17] Moved stix to comparison package --- internal/executors/condition/condition.go | 10 ++++----- .../comparison/comparison_test.go} | 22 +++++++++---------- .../comparison/comparison.go} | 16 ++++++++------ 3 files changed, 25 insertions(+), 23 deletions(-) rename test/unittest/utils/stix/{stix_test.go => expression/comparison/comparison_test.go} (97%) rename utils/stix/{strix.go => expression/comparison/comparison.go} (96%) diff --git a/internal/executors/condition/condition.go b/internal/executors/condition/condition.go index 40329dac..bb8e8a72 100644 --- a/internal/executors/condition/condition.go +++ b/internal/executors/condition/condition.go @@ -6,7 +6,7 @@ import ( "soarca/logger" "soarca/models/cacao" "soarca/models/execution" - "soarca/utils/stix" + "soarca/utils/stix/expression/comparison" ) var component = reflect.TypeOf(Executor{}).PkgPath() @@ -16,8 +16,8 @@ func init() { log = logger.Logger(component, logger.Info, "", logger.Json) } -func New(stix stix.IStix) *Executor { - return &Executor{stix: stix} +func New(comparison comparison.IComparison) *Executor { + return &Executor{comparison: comparison} } type IExecuter interface { @@ -26,7 +26,7 @@ type IExecuter interface { } type Executor struct { - stix stix.IStix + comparison comparison.IComparison } func (executor *Executor) Execute(meta execution.Metadata, step cacao.Step, variables cacao.Variables) (string, bool, error) { @@ -37,7 +37,7 @@ func (executor *Executor) Execute(meta execution.Metadata, step cacao.Step, vari return step.OnFailure, false, err } - result, err := executor.stix.Evaluate(step.Condition, variables) + result, err := executor.comparison.Evaluate(step.Condition, variables) if err != nil { log.Error(err) return "", false, err diff --git a/test/unittest/utils/stix/stix_test.go b/test/unittest/utils/stix/expression/comparison/comparison_test.go similarity index 97% rename from test/unittest/utils/stix/stix_test.go rename to test/unittest/utils/stix/expression/comparison/comparison_test.go index 2b4f4a7f..ef4b9591 100644 --- a/test/unittest/utils/stix/stix_test.go +++ b/test/unittest/utils/stix/expression/comparison/comparison_test.go @@ -1,9 +1,9 @@ -package stix_test +package comparison_test import ( "errors" "soarca/models/cacao" - "soarca/utils/stix" + "soarca/utils/stix/expression/comparison" "testing" "github.com/go-playground/assert/v2" @@ -11,7 +11,7 @@ import ( func TestStringEquals(t *testing.T) { - stix := stix.New() + stix := comparison.New() var1 := cacao.Variable{Type: cacao.VariableTypeString} var1.Value = "a" @@ -55,7 +55,7 @@ func TestStringEquals(t *testing.T) { } func TestIntEquals(t *testing.T) { - stix := stix.New() + stix := comparison.New() var1 := cacao.Variable{Type: cacao.VariableTypeLong} var1.Value = "1000" @@ -102,7 +102,7 @@ func TestIntEquals(t *testing.T) { } func TestFloatEquals(t *testing.T) { - stix := stix.New() + stix := comparison.New() var1 := cacao.Variable{Type: cacao.VariableTypeFloat} var1.Value = "1000.0" @@ -158,7 +158,7 @@ func TestFloatEquals(t *testing.T) { } func TestIp4AddressEquals(t *testing.T) { - stix := stix.New() + stix := comparison.New() var1 := cacao.Variable{Type: cacao.VariableTypeIpv4Address} var1.Value = "10.0.0.30" var1.Name = "__var1__" @@ -182,7 +182,7 @@ func TestIp4AddressEquals(t *testing.T) { } func TestIp6AddressEquals(t *testing.T) { - stix := stix.New() + stix := comparison.New() var1 := cacao.Variable{Type: cacao.VariableTypeIpv6Address} var1.Value = "2001:db8::1" var1.Name = "__var1__" @@ -206,7 +206,7 @@ func TestIp6AddressEquals(t *testing.T) { } func TestMacAddressEquals(t *testing.T) { - stix := stix.New() + stix := comparison.New() var1 := cacao.Variable{Type: cacao.VariableTypeMacAddress} var1.Value = "BC-24-11-00-00-01" var1.Name = "__var1__" @@ -240,7 +240,7 @@ func TestMacAddressEquals(t *testing.T) { } func TestHashEquals(t *testing.T) { - stix := stix.New() + stix := comparison.New() md5 := cacao.Variable{Type: cacao.VariableTypeMd5Has} md5.Value = "d41d8cd98f00b204e9800998ecf8427e" md5.Name = "__md5__" @@ -294,7 +294,7 @@ func TestHashEquals(t *testing.T) { } func TestUriEquals(t *testing.T) { - stix := stix.New() + stix := comparison.New() var1 := cacao.Variable{Type: cacao.VariableTypeUri} var1.Value = "https://google.com" @@ -316,7 +316,7 @@ func TestUriEquals(t *testing.T) { } func TestUuidEquals(t *testing.T) { - stix := stix.New() + stix := comparison.New() var1 := cacao.Variable{Type: cacao.VariableTypeUuid} var1.Value = "ec887691-9a21-4ccf-8fae-360c13a819d1" diff --git a/utils/stix/strix.go b/utils/stix/expression/comparison/comparison.go similarity index 96% rename from utils/stix/strix.go rename to utils/stix/expression/comparison/comparison.go index 31c72f1b..c7fa40b2 100644 --- a/utils/stix/strix.go +++ b/utils/stix/expression/comparison/comparison.go @@ -1,4 +1,4 @@ -package stix +package comparison import ( "errors" @@ -29,7 +29,7 @@ const ( ) var ( - component = reflect.TypeOf(Stix{}).PkgPath() + component = reflect.TypeOf(Comparison{}).PkgPath() log *logger.Log ) @@ -37,17 +37,17 @@ func init() { log = logger.Logger(component, logger.Info, "", logger.Json) } -type IStix interface { +type IComparison interface { Evaluate(string, cacao.Variables) (bool, error) } -func New() *Stix { - return &Stix{} +func New() *Comparison { + return &Comparison{} } -type Stix struct{} +type Comparison struct{} -func (s *Stix) Evaluate(expression string, vars cacao.Variables) (bool, error) { +func (s *Comparison) Evaluate(expression string, vars cacao.Variables) (bool, error) { //"condition": "__variable__:value == '10.0.0.0/8'" //"condition": "__ip__:value/__subnet__:value == '10.0.0.0/8'" @@ -66,6 +66,8 @@ func (s *Stix) Evaluate(expression string, vars cacao.Variables) (bool, error) { parts[0] = vars.Interpolate(parts[0]) + log.Trace(parts) + switch usedVariable.Type { case cacao.VariableTypeBool: return boolCompare(parts) From 79d360deb60e1acb809ceacb90ec6e8a5b9ce466 Mon Sep 17 00:00:00 2001 From: Maarten de Kruijf Date: Wed, 8 May 2024 11:44:00 +0200 Subject: [PATCH 13/17] |Fixed failing decomposer test after rebase --- test/unittest/decomposer/decomposer_test.go | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/test/unittest/decomposer/decomposer_test.go b/test/unittest/decomposer/decomposer_test.go index 1db983cc..90ec49a9 100644 --- a/test/unittest/decomposer/decomposer_test.go +++ b/test/unittest/decomposer/decomposer_test.go @@ -589,7 +589,7 @@ func TestExecuteIfCondition(t *testing.T) { metaStepIf := execution.Metadata{ExecutionId: executionId, PlaybookId: "test", StepId: stepIf.ID} uuid_mock.On("New").Return(executionId) - mock_reporter.On("ReportWorkflow", executionId, playbook).Return() + mock_reporter.On("ReportWorkflowStart", executionId, playbook).Return() mock_condition_executor.On("Execute", metaStepIf, @@ -623,7 +623,7 @@ func TestExecuteIfCondition(t *testing.T) { mock_action_executor.On("Execute", metaStepCompletion, stepCompletionDetails).Return(cacao.NewVariables(), nil) - + mock_reporter.On("ReportWorkflowEnd", executionId, playbook, nil).Return() details, err := decomposer.Execute(playbook) uuid_mock.AssertExpectations(t) fmt.Println(err) From 1428042d5c1ad2befe1a1b885a31644d1bb5b43c Mon Sep 17 00:00:00 2001 From: Maarten de Kruijf Date: Wed, 8 May 2024 13:38:23 +0200 Subject: [PATCH 14/17] Fixed compilation errors --- internal/controller/controller.go | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/internal/controller/controller.go b/internal/controller/controller.go index afd9a725..48b21264 100644 --- a/internal/controller/controller.go +++ b/internal/controller/controller.go @@ -23,7 +23,7 @@ import ( "soarca/logger" "soarca/utils" httpUtil "soarca/utils/http" - "soarca/utils/stix" + "soarca/utils/stix/expression/comparison" downstreamReporter "soarca/internal/reporter/downstream_reporter" @@ -80,8 +80,8 @@ func (controller *Controller) NewDecomposer() decomposer.IDecomposer { actionExecutor := action.New(capabilities, reporter) playbookActionExecutor := playbook_action.New(controller, controller, reporter) - stix := stix.New() - conditionExecutor := condition.New(stix) + stixComparison := comparison.New() + conditionExecutor := condition.New(stixComparison, reporter) guid := new(guid.Guid) decompose := decomposer.New(actionExecutor, playbookActionExecutor, From c16260d51a6631cf9e775843af10afcfcf06d493 Mon Sep 17 00:00:00 2001 From: Maarten de Kruijf Date: Wed, 8 May 2024 13:38:43 +0200 Subject: [PATCH 15/17] Added logging to comparison --- utils/stix/expression/comparison/comparison.go | 7 +------ 1 file changed, 1 insertion(+), 6 deletions(-) diff --git a/utils/stix/expression/comparison/comparison.go b/utils/stix/expression/comparison/comparison.go index c7fa40b2..0b16bd6b 100644 --- a/utils/stix/expression/comparison/comparison.go +++ b/utils/stix/expression/comparison/comparison.go @@ -48,11 +48,6 @@ func New() *Comparison { type Comparison struct{} func (s *Comparison) Evaluate(expression string, vars cacao.Variables) (bool, error) { - - //"condition": "__variable__:value == '10.0.0.0/8'" - //"condition": "__ip__:value/__subnet__:value == '10.0.0.0/8'" - //"condition": "__ip__:value = __another_ip__:value" - //"expresion_type": "ipv4" parts := strings.Split(expression, " ") if len(parts) != 3 { err := errors.New("comparisons can only contain 3 parts as per STIX specification") @@ -66,7 +61,7 @@ func (s *Comparison) Evaluate(expression string, vars cacao.Variables) (bool, er parts[0] = vars.Interpolate(parts[0]) - log.Trace(parts) + log.Trace("the interpolated expression is: ", parts) switch usedVariable.Type { case cacao.VariableTypeBool: From 7b24b84690deac8d01f19a3b7bb42d84d4c38b7b Mon Sep 17 00:00:00 2001 From: Maarten de Kruijf Date: Wed, 8 May 2024 13:47:16 +0200 Subject: [PATCH 16/17] Added reporting to the condition executor --- internal/executors/condition/condition.go | 24 +++++++-- .../condition/condition_executor_test.go | 54 +++++++++++++++++-- 2 files changed, 71 insertions(+), 7 deletions(-) diff --git a/internal/executors/condition/condition.go b/internal/executors/condition/condition.go index bb8e8a72..2ddb4b00 100644 --- a/internal/executors/condition/condition.go +++ b/internal/executors/condition/condition.go @@ -2,7 +2,9 @@ package condition import ( "errors" + "fmt" "reflect" + "soarca/internal/reporter" "soarca/logger" "soarca/models/cacao" "soarca/models/execution" @@ -16,8 +18,10 @@ func init() { log = logger.Logger(component, logger.Info, "", logger.Json) } -func New(comparison comparison.IComparison) *Executor { - return &Executor{comparison: comparison} +func New(comparison comparison.IComparison, + reporter reporter.IStepReporter) *Executor { + return &Executor{comparison: comparison, + reporter: reporter} } type IExecuter interface { @@ -27,6 +31,7 @@ type IExecuter interface { type Executor struct { comparison comparison.IComparison + reporter reporter.IStepReporter } func (executor *Executor) Execute(meta execution.Metadata, step cacao.Step, variables cacao.Variables) (string, bool, error) { @@ -37,22 +42,35 @@ func (executor *Executor) Execute(meta execution.Metadata, step cacao.Step, vari return step.OnFailure, false, err } + executor.reporter.ReportStepStart(meta.ExecutionId, step, variables) + result, err := executor.comparison.Evaluate(step.Condition, variables) + + // We are reporting early to not have double reporting + executor.reporter.ReportStepEnd(meta.ExecutionId, + step, + variables, + err) + if err != nil { log.Error(err) return "", false, err } + log.Debug("the result was: ", fmt.Sprint(result)) + if result { if step.OnTrue != "" { - log.Trace("") + log.Trace("will return on true step ", step.OnTrue) return step.OnTrue, true, nil } } else { if step.OnFalse != "" { + log.Trace("will return on false step ", step.OnFalse) return step.OnFalse, true, nil } } + log.Trace("will return on completion step ", step.OnCompletion) return step.OnCompletion, false, nil } diff --git a/test/unittest/executor/condition/condition_executor_test.go b/test/unittest/executor/condition/condition_executor_test.go index 4bd59308..d9073f94 100644 --- a/test/unittest/executor/condition/condition_executor_test.go +++ b/test/unittest/executor/condition/condition_executor_test.go @@ -1,9 +1,11 @@ package condition_test import ( + "errors" "soarca/internal/executors/condition" "soarca/models/cacao" "soarca/models/execution" + "soarca/test/unittest/mocks/mock_reporter" mock_stix "soarca/test/unittest/mocks/mock_utils/stix" "testing" @@ -13,8 +15,9 @@ import ( func TestExecuteConditionTrue(t *testing.T) { mock_stix := new(mock_stix.MockStix) + mock_reporter := new(mock_reporter.Mock_Reporter) - conditionExecutior := condition.New(mock_stix) + conditionExecutior := condition.New(mock_stix, mock_reporter) executionId := uuid.New() @@ -28,19 +31,25 @@ func TestExecuteConditionTrue(t *testing.T) { OnFalse: "4"} vars := cacao.NewVariables() + mock_reporter.On("ReportStepStart", executionId, step, vars) mock_stix.On("Evaluate", "a = a", vars).Return(true, nil) + mock_reporter.On("ReportStepEnd", executionId, step, vars, nil) nextStepId, goToBranch, err := conditionExecutior.Execute(meta, step, vars) assert.Equal(t, nil, err) assert.Equal(t, true, goToBranch) assert.Equal(t, "3", nextStepId) + mock_reporter.AssertExpectations(t) + mock_stix.AssertExpectations(t) + } func TestExecuteConditionFalse(t *testing.T) { mock_stix := new(mock_stix.MockStix) + mock_reporter := new(mock_reporter.Mock_Reporter) - conditionExecutior := condition.New(mock_stix) + conditionExecutior := condition.New(mock_stix, mock_reporter) executionId := uuid.New() @@ -54,11 +63,48 @@ func TestExecuteConditionFalse(t *testing.T) { OnFalse: "4"} vars := cacao.NewVariables() - mock_stix.On("Evaluate", "a = a", vars).Return(true, nil) + mock_reporter.On("ReportStepStart", executionId, step, vars) + mock_stix.On("Evaluate", "a = a", vars).Return(false, nil) + mock_reporter.On("ReportStepEnd", executionId, step, vars, nil) nextStepId, goToBranch, err := conditionExecutior.Execute(meta, step, vars) assert.Equal(t, nil, err) assert.Equal(t, true, goToBranch) - assert.Equal(t, "3", nextStepId) + assert.Equal(t, "4", nextStepId) + + mock_reporter.AssertExpectations(t) + mock_stix.AssertExpectations(t) +} + +func TestExecuteConditionError(t *testing.T) { + mock_stix := new(mock_stix.MockStix) + mock_reporter := new(mock_reporter.Mock_Reporter) + + conditionExecutior := condition.New(mock_stix, mock_reporter) + + executionId := uuid.New() + + meta := execution.Metadata{ExecutionId: executionId, + PlaybookId: "1", + StepId: "2"} + + step := cacao.Step{Type: cacao.StepTypeIfCondition, + Condition: "a = a", + OnTrue: "3", + OnFalse: "4"} + vars := cacao.NewVariables() + + evaluationError := errors.New("some ds error") + + mock_reporter.On("ReportStepStart", executionId, step, vars) + mock_stix.On("Evaluate", "a = a", vars).Return(false, evaluationError) + mock_reporter.On("ReportStepEnd", executionId, step, vars, evaluationError) + + nextStepId, goToBranch, err := conditionExecutior.Execute(meta, step, vars) + assert.Equal(t, evaluationError, err) + assert.Equal(t, false, goToBranch) + assert.Equal(t, "", nextStepId) + mock_reporter.AssertExpectations(t) + mock_stix.AssertExpectations(t) } From bd9df0d8bd4e4df30ee1a960ac9e60b311799b7e Mon Sep 17 00:00:00 2001 From: Maarten de Kruijf Date: Wed, 8 May 2024 14:19:39 +0200 Subject: [PATCH 17/17] Added implemented comparison features description --- docs/content/en/docs/core-components/executer.md | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/docs/content/en/docs/core-components/executer.md b/docs/content/en/docs/core-components/executer.md index e6d1200f..0ccd56d5 100644 --- a/docs/content/en/docs/core-components/executer.md +++ b/docs/content/en/docs/core-components/executer.md @@ -202,7 +202,11 @@ The result of the step comparison will be returned to the decomposer. A result c ### While condition executor The if-condition executor will process a cacao while-condition step and determine it's output. -The result of the step comparison will be returned to the decomposer. A result can be either a next step id and/or error status. +The result of the step comparison will be returned to the decomposer. A result can be either a next step id and/or error status. Only STIX comparison expressions are implemented at this time. + +{{% alert title="Warning" color="warning" %}} +Note only [Comparison Expression](http://docs.oasis-open.org/cti/stix/v2.0/cs01/part5-stix-patterning/stix-v2.0-cs01-part5-stix-patterning.html#_Toc496717749) are implemented for all CACAO variable types. +{{% /alert %}} ### Parallel step executor The parallel executor will execute the parallel step. This wil be done in sequence to simplify implementation. As parallel steps must not be depended on each other sequential execution is possible. Later this will be changed. \ No newline at end of file