Skip to content
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
11 changes: 10 additions & 1 deletion backend/backend.go
Original file line number Diff line number Diff line change
@@ -1,6 +1,9 @@
package backend

import "soarca-gui/models/reporter"
import (
"soarca-gui/models/manual"
"soarca-gui/models/reporter"
)

type Report interface {
GetReports(bearerToken string) ([]reporter.PlaybookExecutionReport, error)
Expand All @@ -10,3 +13,9 @@ type Report interface {
type Status interface {
GetPongFromStatus(bearerToken string) (string, error)
}

type Manual interface {
GetManualActions() ([]manual.ManualAction, error)
GetManualActionsByIDs(executionID, stepID string) (*manual.ManualAction, error)
ContinueManualAction(request manual.ManualContinueRequest) error
}
38 changes: 37 additions & 1 deletion backend/soarca/helper.go
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
package soarca

import (
"bytes"
"context"
"encoding/json"
"fmt"
Expand All @@ -10,7 +11,8 @@ import (
)

const (
timeout time.Duration = 500
// timeout time.Duration = 500
timeout time.Duration = 5000
)

func fetchToJson(client *http.Client, url string, target interface{}, modifyRequest func(*http.Request)) error {
Expand Down Expand Up @@ -61,3 +63,37 @@ func fetch(ctx context.Context, client *http.Client, url string, modifyRequest f

return body, nil
}

func postJson(client *http.Client, url string, payload interface{}) error {
ctx, cancel := context.WithTimeout(context.Background(), timeout*time.Millisecond)
defer cancel()

return postJsonWithContext(ctx, client, url, payload)
}

func postJsonWithContext(ctx context.Context, client *http.Client, url string, payload interface{}) error {
jsonData, err := json.Marshal(payload)
if err != nil {
return fmt.Errorf("failed to marshal payload: %w", err)
}

req, err := http.NewRequestWithContext(ctx, http.MethodPost, url, bytes.NewBuffer(jsonData))
if err != nil {
return fmt.Errorf("failed to create request: %w", err)
}

req.Header.Set("Content-Type", "application/json")

response, err := client.Do(req)
if err != nil {
return fmt.Errorf("failed to make POST request: %w", err)
}
defer response.Body.Close()

if response.StatusCode != http.StatusOK {
body, _ := io.ReadAll(response.Body)
return fmt.Errorf("unexpected status code: %d, body: %s", response.StatusCode, string(body))
}

return nil
}
54 changes: 54 additions & 0 deletions backend/soarca/manual.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
package soarca

import (
"fmt"
"net/http"
models "soarca-gui/models/manual"
)

const (
manualPath = "/manual"
)

type Manual struct {
Host string
client *http.Client
}

func NewManual(host string, client *http.Client) *Manual {
return &Manual{Host: host, client: client}
}

func (m *Manual) GetManualActions() ([]models.ManualAction, error) {
url := fmt.Sprintf("%s%s", m.Host, manualPath)
var actions []models.ManualAction

err := fetchToJson(m.client, url, &actions, nil)
if err != nil {
return nil, fmt.Errorf("failed to fetch manual actions: %w", err)
}

return actions, nil
}

func (m *Manual) GetManualActionsByIDs(executionID, stepID string) (*models.ManualAction, error) {
url := fmt.Sprintf("%s%s/%s/%s", m.Host, manualPath, executionID, stepID)
var action models.ManualAction

err := fetchToJson(m.client, url, &action, nil)
if err != nil {
return nil, fmt.Errorf("failed to fetch manual action: %w", err)
}
return &action, nil
}

func (m *Manual) ContinueManualAction(request models.ManualContinueRequest) error {
url := fmt.Sprintf("%s%s/continue", m.Host, manualPath)

err := postJson(m.client, url, &request)
if err != nil {
return fmt.Errorf("failed to fetch manual action: %w", err)
}

return nil
}
60 changes: 60 additions & 0 deletions handlers/manual.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,60 @@
package handlers

import (
"net/http"
"soarca-gui/backend"
"soarca-gui/models/manual"

"github.com/gin-gonic/gin"
)

type manualHandler struct {
manual backend.Manual
authenticated bool
}

func NewManualHandler(backend backend.Manual, authenticated bool) manualHandler {
return manualHandler{
manual: backend,
authenticated: authenticated,
}
}

func (h *manualHandler) ManualActionsHandler(ctx *gin.Context) {
_, err := h.manual.GetManualActions()
if err != nil {
return
}
}

func (h *manualHandler) ManualContinueHandler(ctx *gin.Context) {
var request struct {
ExecutionID string `json:"execution_id"`
PlaybookID string `json:"playbook_id"`
StepID string `json:"step_id"`
ResponseStatus string `json:"response_status"`
ResponseOutArgs map[string]interface{} `json:"response_out_args"`
}

if err := ctx.ShouldBindJSON(&request); err != nil {
ctx.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
return
}

// Convert to ManualContinueRequest
continueRequest := manual.ManualContinueRequest{
ExecutionID: request.ExecutionID,
PlaybookID: request.PlaybookID,
StepID: request.StepID,
ResponseStatus: request.ResponseStatus,
ResponseOutArgs: make(map[string]interface{}),
}

err := h.manual.ContinueManualAction(continueRequest)
if err != nil {
ctx.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()})
return
}

ctx.JSON(http.StatusOK, gin.H{"status": "success"})
}
20 changes: 20 additions & 0 deletions models/manual/manual.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
package manual

type ManualAction struct {
ExecutionID string `json:"execution_id"`
PlaybookID string `json:"playbook_id"`
StepID string `json:"step_id"`
Description string `json:"description"`
Command string `json:"command"`
CommandIsBase64 bool `json:"command_is_base64"`
Targets map[string]interface{} `json:"targets"`
OutArgs map[string]interface{} `json:"out_args"`
}

type ManualContinueRequest struct {
ExecutionID string `json:"execution_id"`
PlaybookID string `json:"playbook_id"`
StepID string `json:"step_id"`
ResponseStatus string `json:"response_status"`
ResponseOutArgs map[string]interface{} `json:"response_out_args"`
}
22 changes: 13 additions & 9 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

16 changes: 14 additions & 2 deletions routes/routes.go
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@ func Setup(app *gin.Engine) {
authEnabled, _ := strconv.ParseBool(utils.GetEnv("AUTH_ENABLED", "false"))
reporter := soarca.NewReport(utils.GetEnv("SOARCA_URI", "http://localhost:8080"), &http.Client{}, authEnabled)
status := soarca.NewStatus(utils.GetEnv("SOARCA_URI", "http://localhost:8080"), &http.Client{}, authEnabled)
manual := soarca.NewManual(utils.GetEnv("SOARCA_URI", "http://localhost:8080"), &http.Client{})

var auth *gauth.Authenticator
var authHandler *handlers.OIDCAuthHandler
Expand All @@ -48,6 +49,7 @@ func Setup(app *gin.Engine) {
DashboardRoutes(protectedRoutes, authHandler)
ReportingRoutes(reporter, protectedRoutes, authEnabled)
StatusRoutes(status, protectedRoutes, authEnabled)
ManualRoutes(manual, protectedRoutes, authEnabled)
SettingsRoutes(protectedRoutes)
}

Expand All @@ -72,8 +74,8 @@ func DashboardRoutes(app *gin.RouterGroup, authHandler *handlers.OIDCAuthHandler
app.GET("logout", authHandler.OIDCLogoutHandler)
}

func ReportingRoutes(backend backend.Report, app *gin.RouterGroup, authentication bool) {
reportingHandlers := handlers.NewReportingHandler(backend, authentication)
func ReportingRoutes(reporter backend.Report, app *gin.RouterGroup, authentication bool) {
reportingHandlers := handlers.NewReportingHandler(reporter, authentication)

reportingRoute := app.Group("/reporting")
{
Expand All @@ -99,3 +101,13 @@ func SettingsRoutes(app *gin.RouterGroup) {
reportingRoute.GET("/", handlers.SettingsDashboard)
}
}

func ManualRoutes(manual backend.Manual, app *gin.RouterGroup, authentication bool) {
manualHandler := handlers.NewManualHandler(manual, authentication)

manualRoutes := app.Group(("/manual"))
{
manualRoutes.GET("/", manualHandler.ManualActionsHandler)
manualRoutes.POST("/continue", manualHandler.ManualContinueHandler)
}
}
Loading