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/176 trigger api trigger playbook by UUID #201

Merged
Merged
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
8 changes: 4 additions & 4 deletions database/memory/memory.go
Original file line number Diff line number Diff line change
Expand Up @@ -11,8 +11,8 @@ type InMemoryDatabase struct {
playbooks map[string]cacao.Playbook
}

func New() InMemoryDatabase {
return InMemoryDatabase{playbooks: make(map[string]cacao.Playbook)}
func New() *InMemoryDatabase {
return &InMemoryDatabase{playbooks: make(map[string]cacao.Playbook)}
}

func (memory *InMemoryDatabase) GetPlaybooks() ([]cacao.Playbook, error) {
Expand All @@ -25,7 +25,7 @@ func (memory *InMemoryDatabase) GetPlaybooks() ([]cacao.Playbook, error) {
return playbookList, nil
}

func (memory *InMemoryDatabase) GetPlaybooksMetas() ([]api.PlaybookMeta, error) {
func (memory *InMemoryDatabase) GetPlaybookMetas() ([]api.PlaybookMeta, error) {
size := len(memory.playbooks)
playbookList := make([]api.PlaybookMeta, 0, size)
for _, playbook := range memory.playbooks {
Expand Down Expand Up @@ -54,7 +54,7 @@ func (memory *InMemoryDatabase) Create(json *[]byte) (cacao.Playbook, error) {
return cacao.Playbook{}, errors.New("playbook already exists")
}
memory.playbooks[result.ID] = *result
return *result, nil
return memory.playbooks[result.ID], nil
}

func (memory *InMemoryDatabase) Read(id string) (cacao.Playbook, error) {
Expand Down
61 changes: 35 additions & 26 deletions internal/controller/controller.go
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,7 @@ import (

"github.com/gin-gonic/gin"

"soarca/database/memory"
mongo "soarca/database/mongodb"
playbookrepository "soarca/database/playbook"
"soarca/routes"
Expand Down Expand Up @@ -106,22 +107,31 @@ func (controller *Controller) NewDecomposer() decomposer.IDecomposer {
}

func (controller *Controller) setupDatabase() error {
mongo.LoadComponent()
initMongoDatabase, _ := strconv.ParseBool(utils.GetEnv("DATABASE", "false"))

log.Info("SOARCA API Trying to start")
mongo_uri := os.Getenv("MONGODB_URI")
db_username := os.Getenv("DB_USERNAME")
db_password := os.Getenv("DB_PASSWORD")
if initMongoDatabase {

mongo.LoadComponent()

log.Info("SOARCA API Trying to start")
uri := os.Getenv("MONGODB_URI")
username := os.Getenv("DB_USERNAME")
password := os.Getenv("DB_PASSWORD")

if uri == "" || username == "" || password == "" {
log.Error("you must set 'MONGODB_URI' or 'DB_USERNAME' or 'DB_PASSWORD' in the environment variable")
return errors.New("could not obtain required environment settings")
}
err := mongo.SetupMongodb(uri, username, password)
if err != nil {
return err
}
controller.playbookRepo = playbookrepository.SetupPlaybookRepository(mongo.GetCacaoRepo(), mongo.DefaultLimitOpts())
} else {
// Use in memory database
controller.playbookRepo = memory.New()

if mongo_uri == "" || db_username == "" || db_password == "" {
log.Error("you must set 'MONGODB_URI' or 'DB_USERNAME' or 'DB_PASSWORD' in the environment variable")
return errors.New("could not obtain required environment settings")
}
err := mongo.SetupMongodb(mongo_uri, db_username, db_password)
if err != nil {
return err
}
controller.playbookRepo = playbookrepository.SetupPlaybookRepository(mongo.GetCacaoRepo(), mongo.DefaultLimitOpts())

return nil
}
Expand Down Expand Up @@ -168,24 +178,23 @@ func initializeCore(app *gin.Engine) error {
origins := strings.Split(strings.ReplaceAll(utils.GetEnv("SOARCA_ALLOWED_ORIGINS", "*"), " ", ""), ",")

routes.Cors(app, origins)
err := routes.Api(app, &mainController)

err := mainController.setupDatabase()
if err != nil {
log.Error(err)
return err
}

initDatabase := utils.GetEnv("DATABASE", "false")
if initDatabase == "true" {
err = mainController.setupDatabase()
if err != nil {
log.Error(err)
return err
}
err = routes.Database(app, &mainController)
if err != nil {
log.Error(err)
return err
}
err = routes.Api(app, &mainController, &mainController)
if err != nil {
log.Error(err)
return err
}

err = routes.Database(app, &mainController)
if err != nil {
log.Error(err)
return err
}

// NOTE: Assuming that the cache is the main information mediator for
Expand Down
3 changes: 2 additions & 1 deletion routes/router.go
Original file line number Diff line number Diff line change
Expand Up @@ -40,11 +40,12 @@ func Reporter(app *gin.Engine, informer informer.IExecutionInformer) error {

func Api(app *gin.Engine,
controller decomposer_controller.IController,
database database.IController,
) error {
log.Trace("Trying to setup all Routes")
// gin.SetMode(gin.ReleaseMode)

trigger_api := trigger.New(controller)
trigger_api := trigger.New(controller, database)
coa_routes.Routes(app)
status.Routes(app)
operator.Routes(app)
Expand Down
62 changes: 60 additions & 2 deletions routes/trigger/trigger_api.go
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import (
"reflect"
"time"

"soarca/internal/controller/database"
"soarca/internal/controller/decomposer_controller"
"soarca/internal/decomposer"
"soarca/logger"
Expand All @@ -29,19 +30,76 @@ func init() {

type TriggerApi struct {
controller decomposer_controller.IController
database database.IController
Executionsch chan decomposer.ExecutionDetails
}

func New(controller decomposer_controller.IController) *TriggerApi {
func New(controller decomposer_controller.IController, database database.IController) *TriggerApi {
instance := TriggerApi{}
instance.controller = controller
instance.database = database
// Channel to get back execution details
instance.Executionsch = make(chan decomposer.ExecutionDetails)
return &instance
}

func (trigger *TriggerApi) Execute(context *gin.Context) {
// trigger
//
// @Summary trigger a playbook by id that is stored in SOARCA
// @Schemes
// @Description trigger playbook by id
// @Tags trigger
// @Accept json
// @Produce json
// @Param id path string true "playbook ID"
// @Success 200 {object} api.Execution
// @failure 400 {object} api.Error
// @Router /trigger/playbook/{id} [POST]
func (trigger *TriggerApi) ExecuteById(context *gin.Context) {

id := context.Param("id")

db := trigger.database.GetDatabaseInstance()
playbook, err := db.Read(id)
if err != nil {
log.Error("failed to load playbook")
error.SendErrorResponse(context, http.StatusBadRequest,
"Failed to load playbook",
"POST /trigger/playbook/"+id, err.Error())
return
}

// create new decomposer when execute is called
decomposer := trigger.controller.NewDecomposer()
executionDetail, errDecomposer := decomposer.Execute(playbook)
if errDecomposer != nil {
error.SendErrorResponse(context, http.StatusBadRequest,
"Failed to decode playbook",
"POST /trigger/playbook/"+id,
executionDetail.ExecutionId.String())
} else {
msg := gin.H{
"execution_id": executionDetail.ExecutionId.String(),
"payload": executionDetail.PlaybookId,
}
context.JSON(http.StatusOK, msg)
}

}

// trigger
//
// @Summary trigger a playbook by supplying a cacao playbook payload
// @Schemes
// @Description trigger playbook
// @Tags trigger
// @Accept json
// @Produce json
// @Param playbook body cacao.Playbook true "execute playbook by payload"
// @Success 200 {object} api.Execution
// @failure 400 {object} api.Error
// @Router /trigger/playbook [POST]
func (trigger *TriggerApi) Execute(context *gin.Context) {
decomposer := trigger.controller.NewDecomposer()
jsonData, errIo := io.ReadAll(context.Request.Body)
if errIo != nil {
Expand Down
13 changes: 1 addition & 12 deletions routes/trigger/trigger_endpoints.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,21 +4,10 @@ import (
"github.com/gin-gonic/gin"
)

// trigger
//
// @Summary trigger a playbook by supplying a cacao playbook payload
// @Schemes
// @Description trigger playbook
// @Tags trigger
// @Accept json
// @Produce json
// @Param playbook body cacao.Playbook true "execute playbook by payload"
// @Success 200 {object} api.Execution
// @failure 400 {object} api.Error
// @Router /trigger/playbook [POST]
func Routes(route *gin.Engine, trigger *TriggerApi) {
group := route.Group("/trigger")
{
group.POST("/playbook", trigger.Execute)
group.POST("/playbook/:id", trigger.ExecuteById)
}
}
36 changes: 36 additions & 0 deletions test/integration/api/trigger_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
package api_test

import (
"bytes"
"net/http"
"testing"
"time"

"github.com/stretchr/testify/assert"
)

func TestTest(t *testing.T) {
// Start SOARCA in separate threat
t.Setenv("PORT", "8085")
go initializeSoarca(t)

// Wait for the server to be online
time.Sleep(400 * time.Millisecond)

client := http.Client{}
buffer := bytes.NewBufferString("")
request, err := http.NewRequest("POST", "http://localhost:8085/trigger/", buffer)
if err != nil {
t.Fail()
}

request.Header.Add("Origin", "http://example.com")
response, err := client.Do(request)
if err != nil {
t.Log(err)
t.Fail()
}
origins := response.Header.Get("Access-Control-Allow-Origin")
assert.Equal(t, "*", origins)

}
2 changes: 1 addition & 1 deletion test/unittest/database/memory/memory_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -239,7 +239,7 @@ func TestGetAllPlaybookMetas(t *testing.T) {
// assert.Equal(t, playbook, workflow)
}

playbooks, err := mem.GetPlaybooksMetas()
playbooks, err := mem.GetPlaybookMetas()
assert.Equal(t, err, nil)
assert.Equal(t, len(playbooks), 10)

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,8 +8,8 @@ import (
mock_database_controller "soarca/test/unittest/mocks/mock_controller/database"
mock_decomposer_controller "soarca/test/unittest/mocks/mock_controller/decomposer"
"soarca/test/unittest/mocks/mock_decomposer"
mocks_playbook_test "soarca/test/unittest/mocks/mock_playbook_database"
"soarca/test/unittest/mocks/mock_reporter"
mocks_playbook_test "soarca/test/unittest/mocks/playbook"

"soarca/models/cacao"
"soarca/models/execution"
Expand Down
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
package mocks_playbook_test
package mock_playbook_database

import (
"soarca/models/api"
Expand Down
2 changes: 1 addition & 1 deletion test/unittest/routes/playbook_api/playbook_api_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ import (
"soarca/models/decoder"
playbookRouter "soarca/routes/playbook"
mock_database_controller "soarca/test/unittest/mocks/mock_controller/database"
mock_playbook "soarca/test/unittest/mocks/playbook"
mock_playbook "soarca/test/unittest/mocks/mock_playbook_database"

"github.com/gin-gonic/gin"
"github.com/stretchr/testify/assert"
Expand Down
39 changes: 38 additions & 1 deletion test/unittest/routes/trigger_api/tigger_api_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -12,8 +12,10 @@ import (
"soarca/internal/decomposer"
"soarca/models/cacao"
"soarca/routes/trigger"
mock_database_controller "soarca/test/unittest/mocks/mock_controller/database"
mock_decomposer_controller "soarca/test/unittest/mocks/mock_controller/decomposer"
"soarca/test/unittest/mocks/mock_decomposer"
"soarca/test/unittest/mocks/mock_playbook_database"

"github.com/gin-gonic/gin"
"github.com/go-playground/assert/v2"
Expand All @@ -33,10 +35,11 @@ func TestTriggerExecutionOfPlaybook(t *testing.T) {
gin.SetMode(gin.DebugMode)
mock_decomposer := new(mock_decomposer.Mock_Decomposer)
mock_controller := new(mock_decomposer_controller.Mock_Controller)
mock_database_controller := new(mock_database_controller.Mock_Controller)
mock_controller.On("NewDecomposer").Return(mock_decomposer)
playbook := cacao.Decode(byteValue)

trigger_api := trigger.New(mock_controller)
trigger_api := trigger.New(mock_controller, mock_database_controller)
recorder := httptest.NewRecorder()
trigger.Routes(app, trigger_api)

Expand All @@ -54,3 +57,37 @@ func TestTriggerExecutionOfPlaybook(t *testing.T) {
assert.Equal(t, 200, recorder.Code)
mock_decomposer.AssertExpectations(t)
}

func TestExecutionOfPlaybookById(t *testing.T) {
jsonFile, err := os.Open("../playbook.json")
if err != nil {
fmt.Println(err)
t.Fail()
}
defer jsonFile.Close()
byteValue, _ := io.ReadAll(jsonFile)

gin.SetMode(gin.DebugMode)
app := gin.New()
mock_decomposer := new(mock_decomposer.Mock_Decomposer)
mock_controller := new(mock_decomposer_controller.Mock_Controller)
mock_database := new(mock_playbook_database.MockPlaybook)
mock_database_controller := new(mock_database_controller.Mock_Controller)
mock_database_controller.On("GetDatabaseInstance").Return(mock_database)
playbook := cacao.Decode(byteValue)
mock_database.On("Read", "1").Return(*playbook, nil)
mock_controller.On("NewDecomposer").Return(mock_decomposer)
mock_decomposer.On("Execute", *playbook).Return(&decomposer.ExecutionDetails{}, nil)

recorder := httptest.NewRecorder()
trigger_api := trigger.New(mock_controller, mock_database_controller)
trigger.Routes(app, trigger_api)

request, err := http.NewRequest("POST", "/trigger/playbook/1", nil)
if err != nil {
t.Fail()
}
app.ServeHTTP(recorder, request)
assert.Equal(t, 200, recorder.Code)
mock_decomposer.AssertExpectations(t)
}