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

Feature/168 revise the action iexecutor interface to allow for passing of in args and out args #186

Open
wants to merge 17 commits into
base: development
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 5 additions & 1 deletion docs/content/en/docs/core-components/executer.md
Original file line number Diff line number Diff line change
Expand Up @@ -174,7 +174,7 @@ end
The playbook executor handles execution of playbook action steps. The variables from the top level playbook are injected into the be executed playbook.
It could happen that in the downstream playbook the variables `collide` with the top level playbook. In this case the top level playbook variables are `NOT` transferred to the downstream playbook. `Agents and Targets cannot be transferred` between playbooks at this time. Playbooks are only loaded in the executor and then a new Decomposer is created to execute the playbook.

The result of the step execution will be returned to the decomposer. A result can be either output variables or error status.
The result of the step execution will be returned to the decomposer. A result can be either output variables or error status. Only variables defined in the out_args of the playbook action step will be merged into the global scope of the caller.

```plantuml
package playbookaction{
Expand All @@ -194,6 +194,10 @@ database <- exe

```

NOTE: in the future variables will be merged into the downstream playbook only if they are defined external on the downstream playbook.



### If condition executor
The if-condition executor will process a cacao if-condition step and determine it's output.

Expand Down
45 changes: 37 additions & 8 deletions docs/content/en/docs/core-components/modules.md
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,7 @@ In every other circumstance the step is considered to have failed.

#### Variables

This module does not define specific variables as input, but variable interpolation is supported in the command and target definitions. It has the following output variables:
This module does not define specific variables as input, but variable interpolation is supported in the command and target definitions. It has `one` output variable of type string (see the `__soarca_ssh_result__`). If you want to use the output variable in your next step you will need to define a variable in your playbook as `step or playbook variable` that is of the same type. Also you need to specify an `out_args` key see `__your_step_output_variable__` in the example. Note you can only use `one out_arg key` for this capability.
Copy link
Collaborator

Choose a reason for hiding this comment

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

Two points:

  1. We say that the output variable defined in the step can be used in the next steps only because in SOARCA we use global scope for any defined variable, right? We should mention this, as it's not exactly adhering to the spec.
  2. Besides than being the same type, it should also have the exact same name, right? Perhaps we can say that as well


```json
{
Expand All @@ -43,12 +43,19 @@ This module does not define specific variables as input, but variable interpolat
}
```

#### Example
#### Example with step variable

```json
{
"workflow": {
"action--7777c6b6-e275-434e-9e0b-d68f72e691c1": {
"step_variables": {
"__your_step_output_variable__": {
"type": "string",
"constant": false,
"external": false
}
},
"type": "action",
"agent": "soarca--00010001-1000-1000-a000-000100010001",
"targets": ["linux--c7e6af1b-9e5a-4055-adeb-26b97e1c4db7"],
Expand All @@ -57,6 +64,9 @@ This module does not define specific variables as input, but variable interpolat
"type": "ssh",
"command": "ls -la"
}
],
"out_args": [
Copy link
Collaborator

Choose a reason for hiding this comment

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

The agreed-upon, upcoming change in CACAO v2.1 (or v3), is to set out_args as a common property of the command. So if we want to anticipate this very reasonable change, we should set out_args within a command in commands. This also changes the description that we would have to provide

Copy link
Collaborator

Choose a reason for hiding this comment

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

This comment applies to the rest of this document

"__your_step_output_variable__"
]
}
},
Expand Down Expand Up @@ -92,8 +102,7 @@ The command is considered to have successfully completed if a successful HTTP re
#### Variables

This capability supports variable interpolation in the command, port, authentication info, and target definitions.

The result of the step is stored in the following output variables:
It has `one` output variable of type string (see the `__soarca_http_api_result__`). If you want to use the output variable in your next step you will need to define a variable in your playbook as `step or playbook variable` that is of the same type. Also you need to specify an `out_args` key see `__your_step_output_variable__` in the example. Note you can only use `one out_arg key` for this capability.

```json
{
Expand All @@ -104,11 +113,18 @@ The result of the step is stored in the following output variables:
}
```

#### Example
#### Example with playbook variable

```json
{
"workflow": {
"playbook_variables": {
"__your_step_output_variable__": {
"type": "string",
"constant": false,
"external": false
}
},
"action--8baa7c78-751b-4de9-81d4-775806cee0fb": {
"type": "action",
"agent": "soarca--00020001-1000-1000-a000-000100010001",
Expand All @@ -119,6 +135,9 @@ The result of the step is stored in the following output variables:
"command": "GET /overview HTTP/1.1",
"port": "8080"
}
],
"out_args": [
"__your_step_output_variable__"
]
}
},
Expand Down Expand Up @@ -150,7 +169,7 @@ Any successful HTTP response from an OpenC2 compliant endpoint (with a status co

#### Variables

It supports variable interpolation in the command, headers, and target definitions.
This capability does not define specific variables as input, but variable interpolation is supported in the command and target definitions. It has `one` output variable of type string (see the `__soarca_openc2_http_result__`). If you want to use the output variable in your next step you will need to define a variable in your playbook as `step or playbook variable` that is of the same type. Also you need to specify an `out_args` key see `__your_step_output_variable__` in the example. Note you can only use `one out_arg key` for this capability.

The result of the step is stored in the following output variables:

Expand All @@ -163,12 +182,19 @@ The result of the step is stored in the following output variables:
}
```

#### Example
#### Example with step variables

```json
{
"workflow": {
"action--aa1470d8-57cc-4164-ae07-05745bef24f4": {
"step_variables": {
"__your_step_output_variable__": {
"type": "string",
"constant": false,
"external": false
}
},
"type": "action",
"agent": "soarca--00030001-1000-1000-a000-000100010001",
"targets": ["http-api--5a274b6d-dc65-41f7-987e-9717a7941876"],
Expand All @@ -179,7 +205,10 @@ The result of the step is stored in the following output variables:
"headers": {
"Content-Type": ["application/openc2+json;version=1.0"]
}
}]
}],
"out_args": [
"__your_step_output_variable__"
]
}
},
"agent_definitions": {
Expand Down
4 changes: 3 additions & 1 deletion internal/capability/capability.go
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,8 @@ type ICapability interface {
command cacao.Command,
authentication cacao.AuthenticationInformation,
target cacao.AgentTarget,
variables cacao.Variables) (cacao.Variables, error)
variables cacao.Variables,
inputVariableKeys []string,
outputVariablesKeys []string) (cacao.Variables, error)
GetType() string
}
5 changes: 4 additions & 1 deletion internal/capability/fin/fin.go
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,9 @@ func (finCapability *FinCapability) Execute(
command cacao.Command,
authentication cacao.AuthenticationInformation,
target cacao.AgentTarget,
variables cacao.Variables) (cacao.Variables, error) {
variables cacao.Variables,
inputVariableKeys []string,
Copy link
Collaborator

Choose a reason for hiding this comment

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

is the difference in the property names "inputVariable" in the interface, and "inputVariableKeys" in the capabilities, on purpose? If not, I would name them the same way, just for consistency?

outputVariablesKeys []string) (cacao.Variables, error) {

finCommand := finModel.NewCommand()
finCommand.CommandSubstructure.Command = command.Command
Expand All @@ -44,5 +46,6 @@ func (finCapability *FinCapability) Execute(
finCommand.CommandSubstructure.Context.StepId = metadata.StepId

log.Trace("created command ", finCommand)

return finCapability.finProtocol.SendCommand(finCommand)
}
16 changes: 11 additions & 5 deletions internal/capability/http/http.go
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import (
"soarca/models/cacao"
"soarca/models/execution"
"soarca/utils/http"
"soarca/utils/mapper"
)

// Receive HTTP API command data from decomposer/executer
Expand Down Expand Up @@ -44,7 +45,9 @@ func (httpCapability *HttpCapability) Execute(
command cacao.Command,
authentication cacao.AuthenticationInformation,
target cacao.AgentTarget,
variables cacao.Variables) (cacao.Variables, error) {
variables cacao.Variables,
inputVariableKeys []string,
Copy link
Collaborator

Choose a reason for hiding this comment

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

Am I missing something, or are inputvariablekeys never used in the Execute function? This comment applies to all capabilities

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

That is true as there is no use for it in the spec at this point. We will need to see how we can implement this for now I wanted to include it in the interface as changing it again is a lot of work.

outputVariablesKeys []string) (cacao.Variables, error) {

soarca_http_options := http.HttpOptions{
Target: &target,
Expand All @@ -57,11 +60,14 @@ func (httpCapability *HttpCapability) Execute(
log.Error(err)
return cacao.NewVariables(), err
}
respString := string(responseBytes)
variable := cacao.Variable{Type: cacao.VariableTypeString,

response := string(responseBytes)

results := cacao.NewVariables(cacao.Variable{Type: cacao.VariableTypeString,
Name: httpApiResultVariableName,
Value: respString}
Value: string(response)})
log.Trace("Finished https execution, will return the variables: ", results)

return cacao.NewVariables(variable), nil
return mapper.Variables(variables, outputVariablesKeys, results, []string{httpApiResultVariableName})

}
7 changes: 5 additions & 2 deletions internal/capability/openc2/openc2.go
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ import (
"soarca/models/cacao"
"soarca/models/execution"
"soarca/utils/http"
"soarca/utils/mapper"
)

type OpenC2Capability struct {
Expand Down Expand Up @@ -43,7 +44,8 @@ func (OpenC2Capability *OpenC2Capability) Execute(
authentication cacao.AuthenticationInformation,
target cacao.AgentTarget,
variables cacao.Variables,
) (cacao.Variables, error) {
inputVariableKeys []string,
outputVariablesKeys []string) (cacao.Variables, error) {
log.Trace(metadata.ExecutionId)

httpOptions := http.HttpOptions{
Expand All @@ -61,5 +63,6 @@ func (OpenC2Capability *OpenC2Capability) Execute(
Name: openc2ResultVariableName,
Value: string(response)})
log.Trace("Finished openc2 execution, will return the variables: ", results)
return results, nil

return mapper.Variables(variables, outputVariablesKeys, results, []string{openc2ResultVariableName})
}
16 changes: 13 additions & 3 deletions internal/capability/ssh/ssh.go
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import (
"reflect"
"soarca/models/cacao"
"soarca/models/execution"
"soarca/utils/mapper"
"strings"
"time"

Expand All @@ -28,6 +29,10 @@ func init() {
log = logger.Logger(component, logger.Info, "", logger.Json)
}

func New() *SshCapability {
return &SshCapability{}
}

func (sshCapability *SshCapability) GetType() string {
return sshCapabilityName
}
Expand All @@ -36,7 +41,9 @@ func (sshCapability *SshCapability) Execute(metadata execution.Metadata,
command cacao.Command,
authentication cacao.AuthenticationInformation,
target cacao.AgentTarget,
variables cacao.Variables) (cacao.Variables, error) {
variables cacao.Variables,
inputVariableKeys []string,
outputVariablesKeys []string) (cacao.Variables, error) {
log.Trace(metadata.ExecutionId)

host := CombinePortAndAddress(target.Address, target.Port)
Expand Down Expand Up @@ -99,11 +106,14 @@ func (sshCapability *SshCapability) Execute(metadata execution.Metadata,
log.Error(err)
return cacao.NewVariables(), err
}

results := cacao.NewVariables(cacao.Variable{Type: cacao.VariableTypeString,
Name: sshResultVariableName,
Value: string(response)})
log.Trace("Finished ssh execution will return the variables: ", results)
return results, err
log.Trace("Finished https execution, will return the variables: ", results)

return mapper.Variables(variables, outputVariablesKeys, results, []string{sshResultVariableName})

}

func CombinePortAndAddress(addresses map[cacao.NetAddressType][]string, port string) string {
Expand Down
2 changes: 1 addition & 1 deletion internal/controller/controller.go
Original file line number Diff line number Diff line change
Expand Up @@ -57,7 +57,7 @@ var mainCache = cache.Cache{}
const defaultCacheSize int = 10

func (controller *Controller) NewDecomposer() decomposer.IDecomposer {
ssh := new(ssh.SshCapability)
ssh := ssh.New()
capabilities := map[string]capability.ICapability{ssh.GetType(): ssh}

skip, _ := strconv.ParseBool(utils.GetEnv("HTTP_SKIP_CERT_VALIDATION", "false"))
Expand Down
20 changes: 14 additions & 6 deletions internal/executors/action/action.go
Original file line number Diff line number Diff line change
Expand Up @@ -68,13 +68,9 @@ func (executor *Executor) Execute(meta execution.Metadata, metadata PlaybookStep
auth,
target,
metadata.Variables,
metadata.Step,
metadata.Agent)

if len(metadata.Step.OutArgs) > 0 {
Copy link
Collaborator

Choose a reason for hiding this comment

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

If we move the out_args in the CACAO v3 position (inside a command object) then I think we will need to rethink where these checks will be performed

// If OutArgs is set, only update execution args that are explicitly referenced
outputVariables = outputVariables.Select(metadata.Step.OutArgs)
}

returnVariables.Merge(outputVariables)

if err != nil {
Expand All @@ -87,6 +83,10 @@ func (executor *Executor) Execute(meta execution.Metadata, metadata PlaybookStep
}
}
executor.reporter.ReportStepEnd(meta.ExecutionId, metadata.Step, returnVariables, nil)
if len(metadata.Step.OutArgs) > 0 {
// If OutArgs is set, only update execution args that are explicitly referenced
returnVariables = returnVariables.Select(metadata.Step.OutArgs)
}
return returnVariables, nil
}

Expand All @@ -95,6 +95,7 @@ func (executor *Executor) ExecuteActionStep(metadata execution.Metadata,
authentication cacao.AuthenticationInformation,
target cacao.AgentTarget,
variables cacao.Variables,
step cacao.Step,
agent cacao.AgentTarget) (cacao.Variables, error) {

if capability, ok := executor.capabilities[agent.Name]; ok {
Expand All @@ -117,7 +118,14 @@ func (executor *Executor) ExecuteActionStep(metadata execution.Metadata,
authentication.OauthHeader = variables.Interpolate(authentication.OauthHeader)
authentication.PrivateKey = variables.Interpolate(authentication.PrivateKey)

returnVariables, err := capability.Execute(metadata, command, authentication, target, variables)
returnVariables, err := capability.Execute(metadata,
command,
authentication,
target,
variables,
step.InArgs,
step.OutArgs)

return returnVariables, err
} else {
empty := cacao.NewVariables()
Expand Down
2 changes: 1 addition & 1 deletion internal/executors/playbook_action/playbook_action.go
Original file line number Diff line number Diff line change
Expand Up @@ -63,6 +63,6 @@ func (playbookAction *PlaybookAction) Execute(metadata execution.Metadata,
return cacao.NewVariables(), err
}
playbookAction.reporter.ReportStepEnd(metadata.ExecutionId, step, playbook.PlaybookVariables, nil)
return details.Variables, nil
return details.Variables.Select(step.OutArgs), nil

}
Loading