Skip to content
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
5 changes: 5 additions & 0 deletions .env.example
Original file line number Diff line number Diff line change
Expand Up @@ -35,3 +35,8 @@ AUTH_ENABLED: false
OIDC_PROVIDER: "https://authentikuri:9443/application/o/soarca/"
OIDC_CLIENT_ID: "some client ID"
OIDC_SKIP_TLS_VERIFY: false

# SSH Key Management
ENABLE_SSH_KMS: false
SSH_KMS_DIR: "deployments/docker/testing/ssh-kms-test/ssh-keystore/"

10 changes: 8 additions & 2 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -59,10 +59,16 @@ jobs:
- name: Install swaggo
run: go install github.com/swaggo/swag/cmd/swag@latest
timeout-minutes: 12
- name: Start docker containers for test
- name: Start docker container for http test
run: docker compose -f "deployments/docker/testing/httpbin-test/docker-compose.yml" up -d --build
- name: Start docker container for ssh test
run: docker compose -f "deployments/docker/testing/ssh-test/docker-compose.yml" up -d --build
- name: Start docker container for kms test
run: |
docker compose -f "deployments/docker/testing/ssh-kms-test/docker-compose.yml" up -d --build
docker compose -f "deployments/docker/testing/ssh-kms-test/docker-compose.yml" cp ssh_kms_server:/test deployments/docker/testing/ssh-kms-test
docker compose -f "deployments/docker/testing/ssh-kms-test/docker-compose.yml" cp ssh_kms_server:/test.pub deployments/docker/testing/ssh-kms-test
- name: Run tests
run: |
docker compose -f "deployments/docker/testing/ssh-test/docker-compose.yml" up -d --build
make ci-test

1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ bin/*
api/*
**.env
test/cacao/flatfile-db-example.json
pkg/core/capability/ssh/.ssh_test/


docs/public
Expand Down
2 changes: 1 addition & 1 deletion cmd/soarca/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -45,7 +45,7 @@ func main() {

err := godotenv.Load(".env")
if err != nil {
log.Warning("Failed to read env variable, but will continue")
log.Warning("Failed to read env variable, but will continue. Error: ", err)
}
Host = "localhost:" + utils.GetEnv("PORT", "8080")
api.SwaggerInfo.Host = Host
Expand Down
21 changes: 21 additions & 0 deletions deployments/docker/testing/ssh-kms-test/Dockerfile
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
FROM ubuntu:latest

RUN apt update && apt install openssh-server sudo -y

RUN sudo useradd -m sshtest

RUN ssh-keygen -q -N "" -f /test

RUN mkdir -p /home/sshtest/.ssh/

RUN cat /test.pub > /home/sshtest/.ssh/authorized_keys

RUN echo "PasswordAuthentication no" >> /etc/ssh/sshd_config

RUN echo "PubkeyAuthentication yes" >> /etc/ssh/sshd_config

RUN sudo service ssh start

EXPOSE 22

CMD ["sudo","/usr/sbin/sshd","-D"]
8 changes: 8 additions & 0 deletions deployments/docker/testing/ssh-kms-test/docker-compose.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
services:
ssh_kms_server:
container_name: ssh_kms_server
build:
dockerfile: Dockerfile
ports:
- 2223:22

117 changes: 117 additions & 0 deletions examples/ssh-kms-playbook.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,117 @@
{
"type": "playbook",
"spec_version": "cacao-2.0",
"id": "playbook--300270f9-0e64-42c8-93cc-0927edbe3ae7",
"name": "Example ssh",
"description": "This playbook demonstrates ssh functionality",
"playbook_types": [
"notification"
],
"created_by": "identity--96abab60-238a-44ff-8962-5806aa60cbce",
"created": "2023-11-20T15:56:00.123456Z",
"modified": "2023-11-20T15:56:00.123456Z",
"valid_from": "2023-11-20T15:56:00.123456Z",
"valid_until": "2123-11-20T15:56:00.123456Z",
"priority": 1,
"severity": 1,
"impact": 1,
"labels": [
"soarca",
"ssh",
"example"
],
"authentication_info_definitions": {
"user-auth--b7ddc2ea-9f6a-4e82-8eaa-be202e942090": {
"type": "user-auth",
"kms":true,
"kms_key_identifier": "test",
"username": "linuxserver.io"
}
},
"agent_definitions": {
"soarca--00010001-1000-1000-a000-000100010001": {
"type": "soarca",
"name": "soarca-ssh"
}
},
"target_definitions": {
"ssh--1c3900b4-f86b-430d-b415-12312b9e31f4": {
"type": "ssh",
"name": "system 1",
"address": {
"ipv4": [
"127.0.0.1"
]
},
"port": "2223",
"authentication_info": "user-auth--b7ddc2ea-9f6a-4e82-8eaa-be202e942090"
}
},
"external_references": [
{
"name": "TNO COSSAS",
"description": "TNO COSSAS",
"source": "TNO COSSAS",
"url": "https://cossas-project.org"
}
],
"workflow_start": "start--9e7d62b2-88ac-4656-94e1-dbd4413ba008",
"workflow_exception": "end--a6f0b81e-affb-4bca-b4f6-a2d5af908958",
"workflow": {
"start--9e7d62b2-88ac-4656-94e1-dbd4413ba008": {
"type": "start",
"name": "Start ssh example",
"on_completion": "action--eb9372d4-d524-49fc-bf24-be26ea084779"
},
"action--eb9372d4-d524-49fc-bf24-be26ea084779": {
"type": "action",
"name": "Execute command",
"description": "Execute command specified in variable",
"on_completion": "action--88f4c4df-fa96-44e6-b310-1c06d193ea55",
"commands": [
{
"type": "ssh",
"command": "__command__:value"
}
],
"targets": [
"ssh--1c3900b4-f86b-430d-b415-12312b9e31f4"
],
"agent": "soarca--00010001-1000-1000-a000-000100010001",
"step_variables": {
"__command__": {
"type": "string",
"value": "ls -la",
"constant": true
}
}
},
"action--88f4c4df-fa96-44e6-b310-1c06d193ea55": {
"type": "action",
"name": "Touch file",
"description": "Touch file at path specified by variable",
"on_completion": "end--a6f0b81e-affb-4bca-b4f6-a2d5af908958",
"commands": [
{
"type": "ssh",
"command": "touch __path__:value"
}
],
"targets": [
"ssh--1c3900b4-f86b-430d-b415-12312b9e31f4"
],
"agent": "soarca--00010001-1000-1000-a000-000100010001",
"step_variables": {
"__path__": {
"type": "string",
"value": "touchy",
"constant": true
}
}
},
"end--a6f0b81e-affb-4bca-b4f6-a2d5af908958": {
"type": "end",
"name": "End Flow"
}
}
}
29 changes: 25 additions & 4 deletions internal/controller/controller.go
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import (
"fmt"
"os"
"reflect"
keymanagementrepository "soarca/internal/database/keymanagement"
"soarca/internal/database/memory"
"soarca/internal/logger"

Expand All @@ -20,6 +21,7 @@ import (
"soarca/pkg/core/executors/action"
"soarca/pkg/core/executors/condition"
"soarca/pkg/core/executors/playbook_action"
"soarca/pkg/keymanagement"
"soarca/pkg/reporting/cases"
"soarca/pkg/reporting/reporter"
"soarca/pkg/utils"
Expand Down Expand Up @@ -60,8 +62,10 @@ func init() {
}

type Controller struct {
finController finChannelController.IFinController
playbookRepo playbookrepository.IPlaybookRepository
finController finChannelController.IFinController
playbookRepo playbookrepository.IPlaybookRepository
keyManagementRepo keymanagementrepository.IKeyManagementRepository
keyManagement *keymanagement.KeyManagement
}

var mainController = Controller{}
Expand All @@ -74,7 +78,7 @@ const defaultCacheSize int = 10
var mainInteraction = interaction.New(registerManualIntegration())

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

skip, _ := strconv.ParseBool(utils.GetEnv("HTTP_SKIP_CERT_VALIDATION", "false"))
Expand Down Expand Up @@ -141,6 +145,7 @@ func (controller *Controller) setupDatabase() error {
initMongoDatabase, _ := strconv.ParseBool(utils.GetEnv("DATABASE", "false"))

if initMongoDatabase {
log.Info("Setting up mongo database")

mongo.LoadComponent()

Expand All @@ -158,14 +163,22 @@ func (controller *Controller) setupDatabase() error {
return err
}
controller.playbookRepo = playbookrepository.SetupPlaybookRepository(mongo.GetCacaoRepo(), mongo.DefaultLimitOpts())
controller.keyManagementRepo = keymanagementrepository.SetupKeyManagementRepository(mongo.GetKeyManagementRepo(), mongo.DefaultLimitOpts())
} else {
// Use in memory database
controller.playbookRepo = memory.New()
log.Info("Setting up in-memory database")
controller.playbookRepo = memory.NewPlaybookDatabase()
controller.keyManagementRepo = memory.NewKeyManagementDatabase()
}

return nil
}

func (controller *Controller) setupKeyManagement() error {
controller.keyManagement = keymanagement.InitKeyManagement(controller.keyManagementRepo)
return nil
}

func (controller *Controller) GetDatabaseInstance() playbookrepository.IPlaybookRepository {
return controller.playbookRepo
}
Expand Down Expand Up @@ -249,6 +262,12 @@ func initializeCore(app *gin.Engine) error {
return err
}

err = mainController.setupKeyManagement()
if err != nil {
log.Error("Failed to setup key management:", err)
return err
}

err = routes.Api(app, &mainController, &mainController)
if err != nil {
log.Error(err)
Expand All @@ -269,6 +288,8 @@ func initializeCore(app *gin.Engine) error {
return err
}

routes.KeyManagement(app, mainController.keyManagement)

// Manual capability native routes
routes.Manual(app, mainInteraction)

Expand Down
69 changes: 69 additions & 0 deletions internal/database/keymanagement/keymanagement.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,69 @@
package keymanagementrepository

import (
"errors"
"soarca/internal/database"
"soarca/pkg/models/keymanagement"
)

type IKeyManagementRepository interface {
GetKeyNames() ([]string, error)
Create(name string, keypair keymanagement.KeyPair) error
Read(id string) (keymanagement.KeyPair, error)
Update(id string, keypair keymanagement.KeyPair) error
Delete(id string) error
}

type KeyManagementRepository struct {
db database.Database
options database.FindOptions
}

type KeyPairEntry struct {
name string
keypair keymanagement.KeyPair
}

func SetupKeyManagementRepository(db database.Database, options database.FindOptions) *KeyManagementRepository {
return &KeyManagementRepository{db: db, options: options}
}

func (keymanagementRepo *KeyManagementRepository) GetKeyNames() ([]string, error) {
keys, err := keymanagementRepo.db.Find(nil)
if err != nil {
return nil, err
}
ret := []string{}
for _, key := range keys {
ret = append(ret, key.(KeyPairEntry).name)
}
return ret, nil
}

func (keymanagementRepo *KeyManagementRepository) Create(name string, keypair keymanagement.KeyPair) error {
return keymanagementRepo.db.Create(KeyPairEntry{name, keypair})
}

func (keymanagementRepo *KeyManagementRepository) Read(id string) (keymanagement.KeyPair, error) {
returnedObject, err := keymanagementRepo.db.Read(id)
if err != nil {
return keymanagement.KeyPair{}, err
}

keypair, ok := returnedObject.(keymanagement.KeyPair)

if !ok {
err = errors.New("could not cast lookup object to keypair type")
return keymanagement.KeyPair{}, err
}

return keypair, nil
}

func (keymanagementRepo *KeyManagementRepository) Update(id string, keypair keymanagement.KeyPair) error {
return keymanagementRepo.db.Update(id, keypair)
}

func (keymanagementRepo *KeyManagementRepository) Delete(id string) error {
return keymanagementRepo.db.Delete(id)
}
41 changes: 41 additions & 0 deletions internal/database/memory/keymanagement.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
package memory

import (
"fmt"
"soarca/pkg/models/keymanagement"
)

type InMemoryKeyManagementDatabase struct {
keys map[string]keymanagement.KeyPair
}

func NewKeyManagementDatabase() *InMemoryKeyManagementDatabase {
return &InMemoryKeyManagementDatabase{keys: make(map[string]keymanagement.KeyPair)}
}

func (database *InMemoryKeyManagementDatabase) GetKeyNames() ([]string, error) {
ret := []string{}
for key := range database.keys {
ret = append(ret, key)
}
return ret, nil
}
func (database *InMemoryKeyManagementDatabase) Create(name string, keypair keymanagement.KeyPair) error {
database.keys[name] = keypair
return nil
}
func (database *InMemoryKeyManagementDatabase) Read(id string) (keymanagement.KeyPair, error) {
keypair, ok := database.keys[id]
if !ok {
return keymanagement.KeyPair{}, fmt.Errorf("could not find key named %s", id)
}
return keypair, nil
}
func (database *InMemoryKeyManagementDatabase) Update(id string, keypair keymanagement.KeyPair) error {
database.keys[id] = keypair
return nil
}
func (database *InMemoryKeyManagementDatabase) Delete(id string) error {
delete(database.keys, id)
return nil
}
Loading