1 - Fin Python Library
Documentation of the Python Fin library
For the documentation about the Fin protocol we refer to documention page of SOARCA Fin Protocol.
Quick Start
To include the SOARCA Fin library, you can use the following command to install it via pip:
pip install soarca-fin-library
Example
An example on how to use the library is given below.
For more examples and the source code, we will refer to the Github page of the SOARCA-Fin-python-library, where we provide /examples
folder.
import os
from dotenv import load_dotenv
from soarca_fin_python_library.soarca_fin import SoarcaFin
from soarca_fin_python_library.models.agent_structure import AgentStructure
from soarca_fin_python_library.models.external_reference import ExternalReference
from soarca_fin_python_library.models.step_structure import StepStructure
from soarca_fin_python_library.models.capability_structure import CapabilityStructure
from soarca_fin_python_library.enums.workflow_step_enum import WorkFlowStepEnum
from soarca_fin_python_library.models.command import Command
from soarca_fin_python_library.models.result_structure import ResultStructure
from soarca_fin_python_library.models.variable import Variable
from soarca_fin_python_library.enums.variable_type_enum import VariableTypeEnum
def capability_pong_callback(command: Command) -> ResultStructure:
print("Received ping, returning pong!")
result = Variable(
type=VariableTypeEnum.string,
name="pong_output",
description="If ping, return pong",
value="pong",
constant=True,
external=False)
context = command.command.context
return ResultStructure(
state="success", context=context, variables={"result": result})
def main(mqtt_broker: str, mqtt_port: int, username: str, password: str) -> None:
finId = "soarca-fin--pingpong-f877bb3a-bb37-429e-8ece-2d4286cf326d"
agentName = "soarca-fin-pong-f896bb3b-bb37-429e-8ece-2d4286cf326d"
externalReferenceName = "external-reference-example-name"
capabilityId = "mod-pong--e896aa3b-bb37-429e-8ece-2d4286cf326d"
# Create AgentStructure
agent = AgentStructure(
name=agentName)
# Create ExternalReference
external_reference = ExternalReference(name=externalReferenceName)
# Create StepStructure
step_structure = StepStructure(
name="step_name",
description="step description",
external_references=[external_reference],
command="pong",
target=agentName)
# Create CapabilityStructure
capability_structure = CapabilityStructure(
capability_id=capabilityId,
type=WorkFlowStepEnum.action,
name="Ping Pong capability",
version="0.0.1",
step={
"test": step_structure},
agent={
"testagent": agent})
# Create Soarca fin
fin = SoarcaFin(finId)
# Set config for MQTT Server
fin.set_config_MQTT_server(mqtt_broker, mqtt_port, username, password)
# Register Capabilities
fin.create_fin_capability(capability_structure, capability_pong_callback)
# Start the fin
fin.start_fin()
if __name__ == "__main__":
load_dotenv()
MQTT_BROKER = os.getenv("MQTT_BROKER", "localhost")
MQTT_PORT = int(os.getenv("MQTT_PORT", "1883"))
USERNAME = os.getenv("MQTT_USERNAME", "soarca")
PASSWD = os.getenv("MQTT_PASSWD", "password")
main(MQTT_BROKER, MQTT_PORT, USERNAME, PASSWD)
Below we have provided an example env file. Note that this changes according to your setup.
MQTT_BROKER = "localhost"
MQTT_PORT = "1883"
MQTT_USERNAME = "soarca"
MQTT_PASSWD = "password"
Env file can be exported by running:
export $(cat .env | grep -v "#" | xargs)
Architecture
The main object of the application is the SoarcaFin
object, which is responsible for configuring and creating and controlling the capabilities.
The SoarcaFin creates MQTTClient
s for each capability registered, plus one for registering, unregistering and controlling the fi itself.
MQTTClient
s each have their own connection to the MQTT Broker and own Parser
and Executor
objects.
The Parser
object parsers the raw MQTT messages and tries to convert them to one of the objects in src/models
.
The Executor
runs in their own thread and handles the actual execution of the messages.
The Executor
polls a thread-safe queue for new messages and performs IO operations, such as sending messages to the MQTT broker and calling capability callbacks.
Setup SOARCA Capabilities
To register a fin to SOARCA, first create a SoarcaFin
object and pass the fin_id
in the constructor. The SOARCA fin_id
must be in the format of: sourca-fin-<capability>-<uuid4>
.
Call set_config_MQTT_server()
to set the required configurations for the fin to connect to the MQTT broker.
For each capability to be registered, call create_fin_capability()
. The capability callback funtion should return an object of type ResultStructure
.
When all capabilities are initialized, call start_fin()
for the SOARCA Fin to connect to the MQTT broker and register itself to SOARCA.
An example is given in this project in the file [examples/pong_example.py
]
Class Overview
interface IParser {
Message parse_on_message()
}
interface IMQTTClient {
void on_connect()
void on_message()
}
interface ISoarcaFin {
void set_config_MQTTServer()
void set_fin_capabilities()
void start_fin()
}
interface IExecutor {
void queue_message()
}
class SoarcaFin
class MQTTClient
class Parser
class Executor
ISoarcaFin <|.. SoarcaFin
IMQTTClient <|.. MQTTClient
IParser <|.. Parser
IExecutor <|.. Executor
IMQTTClient <- SoarcaFin
MQTTClient -> IExecutor
IParser <-MQTTClient
Sequence Diagrams
Command
Soarca -> "MQTTClient (Capability 1)" : Command Message [Capability ID Topic]
"MQTTClient (Capability 1)" -> Parser : parse_on_message(message)
"MQTTClient (Capability 1)" <-- Parser : Message.Command
"MQTTClient (Capability 1)" -> "Executor (Capability 1)" : Command message
Soarca <-- "Executor (Capability 1)" : Ack
"Executor (Capability 1)" -> "Capability Callback" : Command
"Executor (Capability 1)" <-- "Capability Callback" : Result
Soarca <- "Executor (Capability 1)" : Result
Soarca --> "MQTTClient (Capability 1)" : Ack
"MQTTClient (Capability 1)" -> Parser : parse_on_message(message)
"MQTTClient (Capability 1)" <-- Parser : Message.Ack
"MQTTClient (Capability 1)" -> "Executor (Capability 1)" : Ack message
Register
Soarca -> Soarca : Create Soarca Topic
Library -> SoarcaFin : Set MQTT Server config
Library -> SoarcaFin : Set Capability1
SoarcaFin -> "MQTTClient (Capability 1)" : Create capability
Library -> SoarcaFin : Set Capability2
SoarcaFin -> "MQTTClient (Capability 2)" : Create capability
Library -> SoarcaFin : Start Fin
SoarcaFin -> "MQTTClient (Capability 1)" : Start capability
"MQTTClient (Capability 1)" -> "MQTTClient (Capability 1)" : Register Capability Topic
SoarcaFin -> "MQTTClient (Capability 2)" : Start capability
"MQTTClient (Capability 2)" -> "MQTTClient (Capability 2)" : Register Capability Topic
SoarcaFin -> "MQTTClient (Fin)" : Register Fin
"MQTTClient (Fin)" -> "MQTTClient (Fin)" : Register SoarcaFin Topic
"MQTTClient (Fin)" -> "Executor (Fin)" : Send Register Message
Soarca <- "Executor (Fin)" : Message.Register [Soarca Topic]
Soarca --> "MQTTClient (Fin)" : Message.Ack [Fin ID Topic]
"MQTTClient (Fin)" -> "Parser (Fin)" : parse_on_message(ack)
"MQTTClient (Fin)" <-- "Parser (Fin)" : Message.Ack
"MQTTClient (Fin)" -> "Executor (Fin)" : Message.Ack
Bugs or Contributing
Want to contribute to this project? It is possible to contribute here.
Have you found a bug or want to request a feature? Please create an issue here.
2 - Fin protocol
Specification of the SOARCA Fin protocol
Goals
The goal of the protocol is to provide a simple and robust way to communicate between the SOARCA orchestrator and the capabilities (Fins) that can provide extra functions.
MQTT
To allow for dynamic communication MQTT is used to provide the backbone for the fin communication. SOARCA can be configured using the environment to use MQTT or just run stand-alone.
The Fin will use the protocol to register itself to SOARCA via the register message. Once register, it will communicate over the channel new channel designated by the fin UUID.
Commands to a specific capability will be communicated of the capability UUID channel.
Messages
Messages defined in the protocol
- ack
- nack
- register
- unregister
- command
- pause
- resume
- stop
legend
|field |content |type |description
|field name have the (optional)
key if the field is not required |content indication |type of the value could be string, int etc. |A description for the field to provide extra information and context
ack
The ack message is used to acknowledge messages.
field | content | type | description |
---|
type | ack | string | The ack message type |
message_id | UUID | string | message id that the ack is referring to |
@startjson
{
"type": "ack",
"message_id": "uuid"
}
@endjson
nack
The nack message is used to non acknowledgements, message was unimplemented or unsuccessful.
field | content | type | description |
---|
type | nack | string | The ack message type |
message_id | UUID | string | message id that the nack is referring to |
@startjson
{
"type": "nack",
"message_id": "uuid"
}
@endjson
register
The message is used to register a fin to SOARCA. It has the following payload.
field | content | type | description |
---|
type | register | string | The register message type |
message_id | UUID | string | Message UUID |
fin_id | UUID | string | Fin uuid separate form the capability id |
Name | Name | string | Fin name |
protocol_version | version | string | Version information of the protocol in semantic version schema e.g. 1.2.4-beta |
security | security information | Security | Security information for protocol see security structure |
capabilities | list of capability structure | list of capability structure | Capability structure information for protocol see security structure |
meta | meta dict | Meta | Meta information for the fin protocol structure |
capability structure
field | content | type | description |
---|
capability_id | UUID | string | Capability id to identify the unique capability a fin can have multiple |
type | action | workflow-step-type-enum | Most common is action |
name | name | string | capability name |
version | version | string | Version information of the Fin implementation used in semantic version schema e.g. 1.2.4-beta |
step | step structure | step structure | Step to specify an example for the step so it can be queried in the SOARCA API |
agent | agent structure | agent structure | Agent to specify the agent definition to match in playbooks for SOARCA |
step structure
field | content | type | description |
---|
type | action | string | Action type |
name | name | string | message id |
description | description | string | Description of the step |
external_references | | list of external reference | References to external recourses to further enhance the step also see CACAO V2 10.9. |
command | command | string | Command to execute |
target | UUID | string | Target UUID cto execute command against |
agent structure
field | content | type | description |
---|
type | soarca-fin | string | SOARCA Fin type, a custom type used to specify Fins |
name | name | string | SOARCA Fin name in the following form: soarca-fin-<name>-<uuid> , this grantees the fin is unique |
@startjson
{
"type": "register",
"message_id": "uuid",
"fin_id" : "uuid",
"name": "Fin name",
"protocol_version": "<semantic-version>",
"security": {
"version": "0.0.0",
"channel_security": "plaintext"
},
"capabilities": [
{
"capability_id": "uuid",
"name": "ssh executer",
"version": "0.1.0",
"step": {
"type": "action",
"name": "<step_name>",
"description": "<description>",
"external_references": {
"name": "<reference name>",
"...": "..."
},
"command": "<command string example>",
"target": "<target uuid>"
},
"agent" : {
"soarca-fin--<uuid>": {
"type": "soarca-fin",
"name": "soarca-fin--<name>-<capability_uuid>"
}
}
}
],
"meta": {
"timestamp": "string: <utc-timestamp-nanoes + timezone-offset>",
"sender_id": "uuid"
}
}
@endjson
unregister
The message is used to unregister a fin to SOARCA. It has the following payload.
field | content | type | description |
---|
type | unregister | string | Unregister message type |
message_id | UUID | string | Message UUID |
capability_id | UUID | string | Capability id or null (either capability_id != null, fin_id != null or all == true need to be set) |
fin_id | UUID | string | Fin id or null (either capability_id != null, fin_id != null or all == true need to be set) |
all | bool | bool | True to address all fins to unregister otherwise false (either capability_id != null, fin_id != null or all == true need to be set) |
@startjson
{
"type": "unregister",
"message_id": "uuid",
"capability_id" : "capability uuid",
"fin_id" : "fin uuid",
"all" : "true | false"
}
@endjson
command
The message is used to send a command from SOARCA. It has the following payload.
field | content | type | description |
---|
type | command | string | Command message type |
message_id | UUID | string | Message UUID |
command | command | command substructure | command structure |
meta | meta dict | Meta | Meta information for the fin protocol structure |
command substructure
field | content | type | description |
---|
command | command | string | The command to be executed |
authentication (optional) | authentication information | authentication information | CACAO authentication information |
context | cacao context | Context | Context form the playbook |
variables | dict of variables | dict of Variables | From the playbook |
@startjson
{
"type": "command",
"message_id": "uuid",
"command": {
"command": "command",
"authentication": {"auth-uuid": "<cacao authentication struct"},
"context": {
"generated_on": "string: <utc-timestamp-nanoes + timezone-offset>",
"timeout": "string: <utc-timestamp-nanoes + timezone-offset>",
"step_id": "uuid",
"playbook_id": "uuid",
"execution_id": "uuid"
},
"variables": {
"__<var1>__": {
"type": "<cacao.variable-type-ov>",
"name": "__<var1>__",
"description": "<string>",
"value": "<string>",
"constant": "<bool>",
"external": "<bool>"
},
"__<var2>__": {
"type": "<cacao.variable-type-ov>",
"name": "__<var2>__",
"description": "<string>",
"value": "<string>",
"constant": "<bool>",
"external": "<bool>"
}
}
},
"meta": {
"timestamp": "string: <utc-timestamp-nanoes + timezone-offset>",
"sender_id": "uuid"
}
}
@endjson
result
The message is used to send a response from the Fin to SOARCA. It has the following payload.
field | content | type | description |
---|
type | result | string | Unregister message type |
message_id | UUID | string | Message UUID |
result | result structure | result structure | The result of the execution |
meta | meta dict | Meta | Meta information for the fin protocol structure |
result structure
field | content | type | description |
---|
state | succes or failure | string | The execution state of the playbook |
context | cacao context | Context | Context form the playbook |
variables | dict of variables | dict of variables | Dictionary of CACAO compatible variables |
@startjson
{
"type": "result",
"message_id": "uuid",
"result": {
"state": "enum(success | failure)",
"context": {
"generated_on": "string: <utc-timestamp-nanoes + timezone-offset>",
"timeout": "string: <utc-timestamp-nanoes + timezone-offset>",
"step_id": "uuid",
"playbook_id": "uuid",
"execution_id": "uuid"
},
"variables": {
"__<var1>__": {
"type": "<cacao.variable-type-ov>",
"name": "__<var1>__",
"description": "<string>",
"value": "<string>",
"constant": "<bool>",
"external": "<bool>"
},
"__<var2>__": {
"type": "<cacao.variable-type-ov>",
"name": "__<var2>__",
"description": "<string>",
"value": "<string>",
"constant": "<bool>",
"external": "<bool>"
}
}
},
"meta": {
"timestamp": "string: <utc-timestamp-nanoes + timezone-offset>",
"sender_id": "uuid"
}
}
@endjson
control
field | content | type | description |
---|
type | pause or resume or stop or progress | string | Message type |
message_id | UUID | string | message uuid |
capability_id | UUID | string | Capability uuid to control |
pause
The message is used to halt the further execution of the Fin. The following command will be responded to with a nack, unless it is resumed or stopped.
@startjson
{
"type": "pause",
"message_id" : "uuid",
"capability_id": "uuid"
}
@endjson
resume
The message is used to resume a paused Fin, the response will be an ack if ok or a nack when the Fin could not be resumed.
@startjson
{
"type": "resume",
"message_id" : "uuid",
"capability_id": "uuid"
}
@endjson
stop
The message is used to shut down the Fin. this will be responded to by ack, after that there will follow an unregister.
@startjson
{
"type": "stop",
"message_id" : "uuid",
"capability_id": "uuid"
}
@endjson
progress
Ask for the progress of the execution of the
@startjson
{
"type": "progress",
"message_id" : "uuid",
"capability_id": "uuid"
}
@endjson
Status response
field | content | type | description |
---|
type | status | string | Message type |
message_id | UUID | string | message uuid |
capability_id | UUID | string | Capability uuid to control |
progress | ready, working, paused, stopped | string | Progress of the execution or state it’s in. |
Report the progress of the execution of the capability
@startjson
{
"type": "status",
"message_id" : "uuid",
"capability_id": "uuid",
"progress": "<execution status>"
}
@endjson
Common
These contain command parts that are used in different messages.
Security
field | content | type | description |
---|
version | version | string | Version information of the protocol in semantic version schema e.g. 1.2.4-beta |
channel_security | plaintext | string | Security mechanism used for encrypting the channel and topic, plaintext is only supported at this time |
@startjson
{
"security": {
"version": "0.0.0",
"channel_security": "plaintext"
}
}
@endjson
Variables
Variables information structure
field | content | type | description |
---|
type | variable type | variable-type-ov | The cacao variable type see CACAO V2 chapter 10.18, 10.18.4 Variable Type Vocabulary |
name | name | string | Name of the variable this must be the same as the key on the map |
description | description | string | Description of the variable |
value | value | string | Value of the variable |
constant | true or false | bool | whether it is constant |
external | true or false | bool | whether it is external to the playbook |
@startjson
{
"__<var1>__": {
"type": "<cacao.variable-type-ov>",
"name": "<string>",
"description": "<string>",
"value": "<string>",
"constant": "<bool>",
"external": "<bool>"
}
}
@endjson
Context
CACAO playbook context information structure
field | content | type | description |
---|
completed_on (optional) | timestamp | string | <utc-timestamp-nanoes + timezone-offset> |
generated_on (optional) | timestamp | string | <utc-timestamp-nanoes + timezone-offset> |
timeout (optional) | duration | string | <utc-timestamp-nanoes + timezone-offset> |
step_id | UUID | string | Step uuid that is referred to |
playbook_id | UUID | string | Playbook uuid that is referred to |
execution_id | UUID | string | SOARCA execution uuid |
@startjson
{
"context": {
"completed_on": "string: <utc-timestamp-nanoes + timezone-offset>",
"generated_on": "string: <utc-timestamp-nanoes + timezone-offset>",
"timeout": "string: <utc-timestamp-nanoes + timezone-offset>",
"step_id": "uuid",
"playbook_id": "uuid",
"execution_id": "uuid"
}
}
@endjson
Meta information for the fin protocol structure
field | content | type | description |
---|
timestamp | timestamp | string | <utc-timestamp-nanoes + timezone-offset> |
sender_id | UUID | string | Step uuid that is referred to |
@startjson
{
"meta": {
"timestamp": "string: <utc-timestamp-nanoes + timezone-offset>",
"sender_id": "uuid"
}
}
@endjson
Sequences
Registering a capability
@startuml
participant "SOARCA" as soarca
participant Capability as fin
soarca -> soarca : create [soarca] topic
fin -> fin : create [fin UUID] topic
soarca <- fin : [soarca] register
soarca --> fin : [fin UUID] ack
@enduml
Sending command
@startuml
participant "SOARCA" as soarca
participant Capability as fin
soarca -> fin : [capability UUID] command
soarca <-- fin : [capability UUID] ack
.... processing ....
soarca <- fin : [capability UUID] result
soarca --> fin: ack
@enduml
Unregistering a capability
@startuml
participant "SOARCA" as soarca
participant Capability as fin
participant "Second capability" as fin2
... SOARCA initiate unregistering one fin ...
soarca -> fin : [SOARCA] unregister fin-id
soarca <-- fin : [SOARCA] ack
note right fin2
This capability does not respond to this message
end note
... Fin initiate unregistering ...
soarca <- fin : [SOARCA] unregister fin-id
soarca --> fin : [SOARCA] ack
note right fin2
This capability does not respond to this message
end note
... SOARCA unregister all ...
soarca -> fin : [SOARCA] unregister all == true
soarca <-- fin : [SOARCA] ack
soarca <-- fin2 : [SOARCA] ack
note over soarca, fin2
soarca will go down after this command
end note
@enduml
Control
@startuml
participant "SOARCA" as soarca
participant Capability as fin
soarca -> fin : [fin UUID] control message
soarca <-- fin : [fin UUID] status
@enduml
3 - Native capabilities
Capabilities and transport mechanisms baked right into SOARCA
This page contains a list of capabilities that are natively implemented in SOARCA see details here. For MQTT-message-based capabilities, check here.
OpenC2 capability
The OpenC2 HTTP capability uses the http(s) transport layer as specified in OpenC2 HTTPS. It allows executing actions on an OpenC2-compatible security actuator.
CACAO documentation: OpenC2 HTTP Command
HTTP API capability
The HTTP capability allows sending arbitrary HTTP requests to other servers.
CACAO documentation: HTTP API Command
SSH capability
The SSH capability allows executing commands on systems running an SSH-server.
CACAO documentation: SSH Command
Powershell capability
The PowerShell capability allows executing commands on systems running an WinRM server.
CACAO documentation: PowerShell Command