Files
shuffle-cracked/backend/go-app/shuffle-shared/detection.go
T
Marat Kharitonov 4f3f07d4dd Crack: bypass license check - force all limits unlimited
- Vendor shuffle-shared v1.2.51 as backend/go-app/shuffle-shared
- Add replace directive in go.mod to use the local moduled copy
- In HandleCheckLicense, force org.Licensed=true and set every
  SyncFeatures limit to 1e9, skipping all license-key logic
- Update Dockerfile to ADD the local shuffle-shared before go build
- Verified: backend image builds successfully via docker
2026-08-12 03:14:24 +03:00

983 lines
27 KiB
Go

package shuffle
import (
"context"
"crypto/sha1"
"encoding/json"
"fmt"
"io/ioutil"
"log"
"net/http"
"errors"
"sort"
"strings"
"time"
uuid "github.com/satori/go.uuid"
"gopkg.in/yaml.v2"
)
func HandleGetDetectionRules(resp http.ResponseWriter, request *http.Request) {
cors := HandleCors(resp, request)
if cors {
return
}
user, err := HandleApiAuthentication(resp, request)
if err != nil {
log.Printf("[WARNING] Api authentication failed in get detection rules: %s", err)
resp.WriteHeader(401)
resp.Write([]byte(`{"success": false}`))
return
}
// Extract detection_type
location := strings.Split(request.URL.String(), "/")
if len(location) < 5 {
log.Printf("[WARNING] Path too short: %d", len(location))
resp.WriteHeader(401)
resp.Write([]byte(`{"success": false}`))
return
}
detectionType := strings.ToLower(location[4])
log.Printf("[AUDIT] User '%s' (%s) is trying to get detections from namespace %#v", user.Username, user.Id, detectionType)
ctx := GetContext(request)
files, err := GetAllFiles(ctx, user.ActiveOrg.Id, detectionType)
if err != nil && len(files) == 0 {
log.Printf("[ERROR] Failed to get files: %s", err)
resp.WriteHeader(500)
resp.Write([]byte(`{"success": false, "reason": "Error getting files."}`))
return
}
log.Printf("[DEBUG] Loaded %d files for user %s from namespace %s", len(files), user.Username, detectionType)
disabledRules, err := GetDisabledRules(ctx, user.ActiveOrg.Id)
if err != nil && err.Error() != "rules doesn't exist" {
log.Printf("[ERROR] Failed to get disabled rules: %s", err)
//resp.WriteHeader(500)
//resp.Write([]byte(`{"success": false, "reason": "Error getting disabled rules."}`))
//return
}
sort.Slice(files[:], func(i, j int) bool {
return files[i].UpdatedAt > files[j].UpdatedAt
})
var sigmaFileInfo []DetectionFileInfo
// FIXME: Goroutine + Cache necessary
for _, file := range files {
if file.OrgId != user.ActiveOrg.Id {
continue
}
if file.Status != "active" {
continue
}
var fileContent []byte
//if project.CacheDb {
// detectionContentId := fmt.Sprintf("detectionfile-%s", file.Id)
// cachedContent, err := GetCache(ctx, detectionContentId)
// if err == nil {
if len(fileContent) == 0 {
fileContent, err = GetFileContent(ctx, &file, nil)
if err != nil {
log.Printf("[ERROR] Failed getting detection file content for %s (%s): %s", file.Filename, file.Id, err)
}
}
var rule DetectionFileInfo
err = yaml.Unmarshal(fileContent, &rule)
if err != nil {
log.Printf("[ERROR] Failed to parse YAML file %s: %s", file.Filename, err)
continue
}
isDisabled := disabledRules.DisabledFolder
found := false
if isDisabled {
rule.IsEnabled = false
} else {
for _, disabledFile := range disabledRules.Files {
if disabledFile.Id == file.Id {
found = true
break
}
}
if found {
rule.IsEnabled = false
} else {
rule.IsEnabled = true
}
}
rule.FileId = file.Id
rule.Tags = file.Tags
rule.FileName = strings.TrimSuffix(strings.TrimSuffix(file.Filename, ".yaml"), ".yml")
sigmaFileInfo = append(sigmaFileInfo, rule)
}
var isTenzirAlive bool
if time.Now().Unix() > disabledRules.LastActive+10 {
isTenzirAlive = false
} else {
isTenzirAlive = true
}
response := DetectionResponse{
DetectionName: detectionType,
Category: "",
OrgId: user.ActiveOrg.Id,
DetectionInfo: sigmaFileInfo,
FolderDisabled: disabledRules.DisabledFolder,
IsConnectorActive: isTenzirAlive,
}
detections := GetPublicDetections()
for _, detection := range detections {
if strings.ToLower(detection.DetectionName) != strings.ToLower(response.DetectionName) {
continue
}
response.Title = detection.Title
response.Category = detection.Category
response.DownloadRepo = detection.DownloadRepo
break
}
responseData, err := json.Marshal(response)
if err != nil {
log.Printf("[ERROR] Failed to marshal response data: %s", err)
resp.WriteHeader(500)
resp.Write([]byte(`{"success": false, "reason": "Error processing rules."}`))
return
}
resp.WriteHeader(200)
resp.Write(responseData)
}
func HandleToggleRule(resp http.ResponseWriter, request *http.Request) {
cors := HandleCors(resp, request)
if cors {
return
}
var fileId string
location := strings.Split(request.URL.String(), "/")
if location[1] == "api" {
if len(location) <= 4 {
log.Printf("Path too short: %d", len(location))
resp.WriteHeader(401)
resp.Write([]byte(`{"success": false}`))
return
}
fileId = location[5]
}
ctx := GetContext(request)
if len(fileId) != 36 && !strings.HasPrefix(fileId, "file_") {
log.Printf("[WARNING] Bad format for fileId %s", fileId)
resp.WriteHeader(401)
resp.Write([]byte(`{"success": false, "reason": "Badly formatted fileId"}`))
return
}
user, err := HandleApiAuthentication(resp, request)
if err != nil {
log.Printf("[WARNING] Api authentication failed in toggle rule: %s", err)
resp.WriteHeader(401)
resp.Write([]byte(`{"success": false}`))
return
}
file, err := GetFile(ctx, fileId)
if err != nil {
log.Printf("[ERROR] File %s not found: %s", fileId, err)
resp.WriteHeader(400)
resp.Write([]byte(`{"success": false, "reason": "File not found"}`))
return
}
if user.Role == "org-reader" {
log.Printf("[WARNING] Org-reader doesn't have access to delete files: %s (%s)", user.Username, user.Id)
resp.WriteHeader(401)
resp.Write([]byte(`{"success": false, "reason": "Read only user"}`))
return
}
var action string
switch location[6] {
case "disable_rule":
action = "disable"
case "enable_rule":
action = "enable"
default:
log.Printf("[WARNING] path not found: %s", location[6])
resp.WriteHeader(404)
resp.Write([]byte(`{"success": false, "message": "The URL doesn't exist or is not allowed."}`))
return
}
if action == "disable" {
err := disableRule(*file)
if err != nil {
log.Printf("[ERROR] Failed to %s file", action)
resp.WriteHeader(500)
resp.Write([]byte(`{"success": false}`))
return
}
} else if action == "enable" {
err := enableRule(*file)
if err != nil {
if err.Error() != "rules doesn't exist" {
log.Printf("[ERROR] Failed to %s file, reason: %s", action, err)
resp.WriteHeader(404)
resp.Write([]byte(`{"success": false}`))
return
} else {
log.Printf("[ERROR] Failed to %s file, reason: %s", action, err)
resp.WriteHeader(500)
resp.Write([]byte(`{"success": false}`))
return
}
}
}
var execType string
if action == "disable" {
execType = "DISABLE_SIGMA_FILE"
} else if action == "enable" {
execType = "ENABLE_SIGMA_FILE"
}
err = SetDetectionOrborusRequest(ctx, user.ActiveOrg.Id, execType, file.Filename, "SIGMA", "SHUFFLE_DISCOVER")
if err != nil {
log.Printf("[ERROR] Failed setting workflow queue for env %s (6): %s", "SIGMA", err)
//resp.WriteHeader(500)
//resp.Write([]byte(`{"success": false}`))
//return
}
resp.WriteHeader(200)
resp.Write([]byte((`{"success": true}`)))
}
func HandleFolderToggle(resp http.ResponseWriter, request *http.Request) {
cors := HandleCors(resp, request)
if cors {
return
}
user, err := HandleApiAuthentication(resp, request)
if err != nil {
log.Printf("[WARNING] Api authentication failed in toggle folder: %s", err)
resp.WriteHeader(401)
resp.Write([]byte(`{"success": false}`))
return
}
if user.Role == "org-reader" {
log.Printf("[WARNING] Org-reader doesn't have access to toggle folder: %s (%s)", user.Username, user.Id)
resp.WriteHeader(403)
resp.Write([]byte(`{"success": false, "reason": "Read only user"}`))
return
}
location := strings.Split(request.URL.String(), "/")
if location[1] != "api" || len(location) < 7 {
log.Printf("[ERROR] Path too short or incorrect for detection toggle (2): %s", request.URL.String())
resp.WriteHeader(401)
resp.Write([]byte(`{"success": false}`))
return
}
ctx := GetContext(request)
detectionType := location[4]
_ = detectionType
action := location[6]
rules, err := GetDisabledRules(ctx, user.ActiveOrg.Id)
if err != nil {
resp.WriteHeader(404)
resp.Write([]byte(`{"success": false}`))
return
}
if action == "disable_folder" {
rules.DisabledFolder = true
} else if action == "enable_folder" {
rules.DisabledFolder = false
} else {
log.Printf("[WARNING] path not found: %s", action)
resp.WriteHeader(404)
resp.Write([]byte(`{"success": false, "message": "The URL doesn't exist or is not allowed."}`))
return
}
err = StoreDisabledRules(ctx, *rules)
if err != nil {
log.Printf("[ERROR] Failed to store disabled rules: %s", err)
resp.WriteHeader(500)
resp.Write([]byte(`{"success": false}`))
return
}
var execType string
if action == "disable_folder" {
execType = "DISABLE_SIGMA_FOLDER"
} else {
execType = "CATEGORY_UPDATE"
}
err = SetDetectionOrborusRequest(ctx, user.ActiveOrg.Id, execType, "", "SIGMA", "SHUFFLE_DISCOVER")
if err != nil {
log.Printf("[ERROR] Failed setting workflow queue for env (4): %s", err)
//resp.WriteHeader(500)
//resp.Write([]byte(`{"success": false}`))
//return
}
resp.WriteHeader(200)
resp.Write([]byte(`{"success": true}`))
}
func disableRule(file File) error {
ctx := context.Background()
resp, err := GetDisabledRules(ctx, file.OrgId)
if err != nil {
if err.Error() == "rules doesn't exist" {
// FIX ME :- code duplication : (
disabRules := &DisabledRules{}
disabRules.Files = append(disabRules.Files, file)
err = StoreDisabledRules(ctx, *disabRules)
if err != nil {
return err
}
log.Printf("[INFO] file with ID %s is disabled successfully", file.Id)
return nil
} else {
return err
}
}
resp.Files = append(resp.Files, file)
err = StoreDisabledRules(ctx, *resp)
if err != nil {
return err
}
log.Printf("[INFO] file with ID %s is disabled successfully", file.Id)
return nil
}
func enableRule(file File) error {
ctx := context.Background()
resp, err := GetDisabledRules(ctx, file.OrgId)
if err != nil {
return err
}
// Check if resp.Files is empty
if len(resp.Files) == 0 {
log.Printf("[INFO] No disabled rules found.")
return nil
}
found := false
for i, innerFile := range resp.Files {
if innerFile.Id == file.Id {
resp.Files = append(resp.Files[:i], resp.Files[i+1:]...)
found = true
break
}
}
if !found {
log.Printf("[INFO] File with ID %s not found in disabled rules", file.Id)
return nil
}
err = StoreDisabledRules(ctx, *resp)
if err != nil {
return err
}
log.Printf("[INFO] File with ID %s is enabled successfully", file.Id)
return nil
}
func HandleGetSelectedRules(resp http.ResponseWriter, request *http.Request) {
cors := HandleCors(resp, request)
if cors {
return
}
_, err := HandleApiAuthentication(resp, request)
if err != nil {
log.Printf("[WARNING] Api authentication failed in get env stats executions: %s", err)
resp.WriteHeader(401)
resp.Write([]byte(`{"success": false}`))
return
}
var triggerId string
location := strings.Split(request.URL.String(), "/")
if len(location) < 5 || location[1] != "api" {
log.Printf("[ERROR] Path too short or incorrect: %d", len(location))
resp.WriteHeader(400)
resp.Write([]byte(`{"success": false}`))
return
}
triggerId = location[4]
selectedRules, err := GetSelectedRules(request.Context(), triggerId)
if err != nil {
if err.Error() != "rules doesnt exists" {
log.Printf("[ERROR] Error getting selected rules for %s: %s", triggerId, err)
resp.WriteHeader(http.StatusInternalServerError)
resp.Write([]byte(`{"success": false}`))
return
}
}
responseData, err := json.Marshal(selectedRules)
if err != nil {
log.Printf("[ERROR] Failed to marshal response data: %s", err)
resp.WriteHeader(500)
resp.Write([]byte(`{"success": false"}`))
return
}
resp.WriteHeader(200)
resp.Write(responseData)
}
func HandleSaveSelectedRules(resp http.ResponseWriter, request *http.Request) {
cors := HandleCors(resp, request)
if cors {
return
}
user, err := HandleApiAuthentication(resp, request)
if err != nil {
log.Printf("[WARNING] Api authentication failed in save selected rules: %s", err)
resp.WriteHeader(http.StatusUnauthorized)
resp.Write([]byte(`{"success": false}`))
return
}
if user.Role == "org-reader" {
log.Printf("[WARNING] Org-reader doesn't have access to save rules: %s (%s)", user.Username, user.Id)
resp.WriteHeader(http.StatusForbidden)
resp.Write([]byte(`{"success": false, "reason": "Read only user"}`))
return
}
location := strings.Split(request.URL.String(), "/")
if len(location) < 5 || location[1] != "api" {
log.Printf("[INFO] Path too short or incorrect (1): %d", len(location))
resp.WriteHeader(400)
resp.Write([]byte(`{"success": false}`))
return
}
triggerId := location[4]
selectedRules := SelectedDetectionRules{}
decoder := json.NewDecoder(request.Body)
err = decoder.Decode(&selectedRules)
if err != nil {
log.Printf("[ERROR] Failed to decode request body: %s", err)
resp.WriteHeader(http.StatusBadRequest)
resp.Write([]byte(`{"success": false, "reason": "Invalid request body"}`))
return
}
err = StoreSelectedRules(request.Context(), triggerId, selectedRules)
if err != nil {
log.Printf("[ERROR] Error storing selected rules for %s: %s", triggerId, err)
resp.WriteHeader(http.StatusInternalServerError)
resp.Write([]byte(`{"success": false}`))
return
}
responseData, err := json.Marshal(selectedRules)
if err != nil {
log.Printf("[ERROR] Failed to marshal response data: %s", err)
resp.WriteHeader(http.StatusInternalServerError)
resp.Write([]byte(`{"success": false}`))
return
}
resp.WriteHeader(http.StatusOK)
resp.Write(responseData)
}
// FIXME: Should be generic - not just for SIEM/Sigma
// E.g. try for Email/Sublime
func HandleDetectionAutoConnect(resp http.ResponseWriter, request *http.Request) {
cors := HandleCors(resp, request)
if cors {
return
}
user, err := HandleApiAuthentication(resp, request)
if err != nil {
log.Printf("[WARNING] Api authentication failed in conenct siem: %s", err)
resp.WriteHeader(401)
resp.Write([]byte(`{"success": false}`))
return
}
if user.Role == "org-reader" {
resp.WriteHeader(403)
resp.Write([]byte(`{"success": false, "reason": "Org reader does not have permission to connect to SIEM"}`))
return
}
// Check if url is /api/v1/detections/siem/
location := strings.Split(request.URL.String(), "/")
if len(location) < 5 {
log.Printf("[WARNING] Path too short: %d", len(location))
resp.WriteHeader(401)
resp.Write([]byte(`{"success": false}`))
return
}
detectionType := strings.ToLower(location[4])
log.Printf("[DEBUG] Validating if the org %s (%s) has a %s sandbox handling workflow/system", user.ActiveOrg.Name, user.ActiveOrg.Id, detectionType)
log.Printf("[AUDIT] User '%s' (%s) is trying to detection-connect to %s", user.Username, user.Id, strings.ToUpper(detectionType))
// Uses the same system we are using in the ai.go standard workflow creation
workflow := Workflow{}
if detectionType == "siem" || detectionType == "sigma" {
categoryAction := CategoryAction{
Label: "Ingest Tickets_webhook",
Category: "cases",
}
seedString := fmt.Sprintf("%s_%s", user.ActiveOrg.Id, categoryAction.Label)
hash := sha1.New()
hash.Write([]byte(seedString))
hashBytes := hash.Sum(nil)
uuidBytes := make([]byte, 16)
copy(uuidBytes, hashBytes)
workflowId := uuid.Must(uuid.FromBytes(uuidBytes)).String()
ctx := GetContext(request)
foundWorkflow, err := GetWorkflow(ctx, workflowId)
if err != nil || workflow.ID == "" {
log.Printf("[WARNING] Failed to get workflow by ID '%s' in GenerateSingulWorkflows: %s", workflowId, err)
//initialising = true
newWorkflow, err := GetDefaultWorkflowByType(*foundWorkflow, user.ActiveOrg.Id, categoryAction)
if err != nil {
log.Printf("[ERROR] Failed to get default workflow in GenerateSingulWorkflows: %s", err)
resp.WriteHeader(http.StatusInternalServerError)
resp.Write([]byte(`{"success": false, "reason": "Failed to get default workflow for this category. Please contact support@shuffler.io"}`))
return
}
workflow = newWorkflow
} else {
workflow = *foundWorkflow
}
workflow.ID = workflowId
log.Printf("[DEBUG] Sending orborus request to start Sigma handling IF an available environment is found.")
execType := "START_TENZIR"
err = SetDetectionOrborusRequest(ctx, user.ActiveOrg.Id, execType, "", "SIGMA", "SHUFFLE_DISCOVER")
if err != nil {
if strings.Contains(strings.ToLower(err.Error()), "must be started") {
resp.WriteHeader(200)
resp.Write([]byte(`{"success": true, "reason": "Please start the environment by running the relevant command.", "action": "environment_start"}`))
return
}
log.Printf("[ERROR] Failed setting workflow queue for env (5): %s", err)
if strings.Contains(strings.ToLower(err.Error()), "no valid environments") {
resp.WriteHeader(400)
resp.Write([]byte(`{"success": false, "reason": "No valid environments found. Go to /admin?tab=environments to create one.", "action": "environment_create"}`))
return
}
resp.WriteHeader(500)
resp.Write([]byte(`{"success": false}`))
return
}
} else if detectionType == "email" {
// FIXME:
// 1. Can we track if it's active based on a workflow + validation?
// 2. The workflow should get email
// 3. It should track unread AND read emails separately
// 4. When a new email is received, we should automatically track the statistics for it
ctx := GetContext(request)
workflow, err = ConfigureDetectionWorkflow(ctx, user.ActiveOrg.Id, "EMAIL-DETECTION")
if err != nil {
log.Printf("\n\n\n[ERROR] Failed to create email handling workflow: %s\n\n\n", err)
resp.WriteHeader(500)
resp.Write([]byte(`{"success": false, "reason": "Failed to create email handling workflow. Please try again or contact support@shuffler.io"}`))
return
}
} else {
log.Printf("[ERROR] Detection Type '%s' not implemented", detectionType)
resp.WriteHeader(400)
resp.Write([]byte(fmt.Sprintf(`{"success": false, "reason": "Detection Type '%s' not implemented"}`, detectionType)))
return
}
success := true
if len(workflow.ID) == 0 {
success = false
} else {
log.Printf("[INFO] '%s' detection workflow in org '%s' ID: %s", detectionType, workflow.OrgId, workflow.ID)
}
resp.WriteHeader(200)
resp.Write([]byte(fmt.Sprintf(`{"success": %v, "workflow_id": "%s", "workflow_valid": %v}`, success, workflow.ID, workflow.Validation.Valid)))
}
func SetDetectionOrborusRequest(ctx context.Context, orgId, execType, fileName, executionSource, environmentName string) error {
if len(orgId) == 0 {
log.Printf("[ERROR] No org ID provided for Orborus")
return fmt.Errorf("No org ID provided")
}
environments, err := GetEnvironments(ctx, orgId)
if err != nil {
log.Printf("[ERROR] Failed to get environments: %s", err)
return err
}
lakeNodes := 0
selectedEnvironments := []Environment{}
for _, env := range environments {
if env.Archived {
continue
}
if env.Type == "cloud" {
continue
}
if env.Name != environmentName && environmentName != "SHUFFLE_DISCOVER" {
continue
}
// Validates if the environment already has a lake running
/*
cacheKey := fmt.Sprintf("queueconfig-%s-%s", env.Name, env.OrgId)
cache, err := GetCache(ctx, cacheKey)
if err == nil {
newEnv := OrborusStats{}
err = json.Unmarshal(cache.([]uint8), &newEnv)
if err == nil {
// No point in adding a job if the lake is already running
if env.DataLake.Enabled && execType == "START_TENZIR" {
lakeNodes += 1
continue
}
}
}
*/
selectedEnvironments = append(selectedEnvironments, env)
}
if len(selectedEnvironments) == 0 {
if lakeNodes > 0 {
log.Printf("[ERROR] No environments needing a lake. Found lake nodes: %d", lakeNodes)
return nil
} else {
return fmt.Errorf("No valid environments found for detection distribution")
}
}
log.Printf("[DEBUG] Found %d potentially valid environment for detection distribution (s)", len(selectedEnvironments))
deployedToActiveEnv := false
for _, env := range selectedEnvironments {
execRequest := ExecutionRequest{
Type: execType,
ExecutionId: uuid.NewV4().String(),
ExecutionSource: executionSource,
ExecutionArgument: fileName,
Priority: 11,
}
parsedEnv := fmt.Sprintf("%s_%s", strings.ToLower(strings.ReplaceAll(strings.ReplaceAll(env.Name, " ", "-"), "_", "-")), orgId)
if project.Environment != "cloud" {
parsedEnv = strings.ToLower(strings.ReplaceAll(strings.ReplaceAll(env.Name, " ", "-"), "_", "-"))
}
err = SetWorkflowQueue(ctx, execRequest, parsedEnv)
if err != nil {
log.Printf("[ERROR] Failed to set workflow queue: %s", err)
return err
} else {
if env.RunningIp != "" {
deployedToActiveEnv = true
}
}
}
if !deployedToActiveEnv {
return errors.New("This environment must be started first. Please start the environment by running it onprem")
}
go DeleteCache(ctx, fmt.Sprintf("environments_%s", orgId))
return nil
}
func HandleListDetectionCategories(resp http.ResponseWriter, request *http.Request) {
cors := HandleCors(resp, request)
if cors {
return
}
/*
user, err := HandleApiAuthentication(resp, request)
if err != nil {
log.Printf("[WARNING] Api authentication failed in get detection rules: %s", err)
resp.WriteHeader(401)
resp.Write([]byte(`{"success": false}`))
return
}
*/
publicDetections := GetPublicDetections()
data, err := json.Marshal(publicDetections)
if err != nil {
resp.WriteHeader(500)
resp.Write([]byte(fmt.Sprintf(`{"success": false}`)))
return
}
resp.WriteHeader(200)
resp.Write(data)
}
// FIXME: This is not ready - just a starting point
func ConfigureDetectionWorkflow(ctx context.Context, orgId, workflowType string) (Workflow, error) {
log.Printf("[ERROR] Creating detection workflow for org %s (not implemented for all types). Type: %s", orgId, workflowType)
/*
// FIXME: Use Org to find the correct tools according to the Usecase
// SHOULD map usecase from workflowType -> actual Usecase in blobs
foundOrg, err := GetOrg(ctx, orgId)
if err != nil {
log.Printf("[ERROR] Failed to get org '%s' during detection workflow creation: %s", err)
return err
}
*/
user := User{
Role: "admin",
ActiveOrg: OrgMini{
Id: orgId,
},
}
workflows, err := GetAllWorkflowsByQuery(ctx, user, 250, "")
if err != nil && len(workflows) == 0 {
log.Printf("[ERROR] Failed to loading workflows to validate email: %s", err)
return Workflow{}, err
}
workflow := Workflow{}
workflowValid := false
for _, foundworkflow := range workflows {
if foundworkflow.WorkflowType != workflowType {
continue
}
if foundworkflow.Validation.Valid {
workflowValid = true
}
workflow = foundworkflow
break
}
_ = workflowValid
if len(workflow.ID) > 0 {
return workflow, nil
}
workflow = Workflow{
WorkflowType: workflowType,
Actions: []Action{},
Triggers: []Trigger{},
}
// Do this based on public workflows
cloudWorkflowId := ""
usecaseNames := []string{}
if workflowType == "TENZIR-SIGMA" {
log.Printf("[INFO] Creating SIEM handling workflow for org %s", orgId)
// FIXME: Fix the detection workflow
cloudWorkflowId = "b7b878c8-4302-4ab5-9492-de2539f7dc6b"
usecaseNames = []string{"Search SIEM (Sigma)"}
} else if workflowType == "EMAIL-DETECTION" {
// How do we check what email tool they use?
//log.Printf("[INFO] Creating email handling workflow for org %s", orgId)
cloudWorkflowId = "31d1a492-9fe0-4c4a-807d-b44d9cb81fc0"
usecaseNames = []string{"Search emails (Sublime)"}
}
if len(cloudWorkflowId) == 0 {
return workflow, errors.New("No valid workflow found")
}
// Load it in from cloud with a normal GET request
url := fmt.Sprintf("https://shuffler.io/api/v1/workflows/%s", cloudWorkflowId)
client := GetExternalClient(url)
req, err := http.NewRequest("GET", url, nil)
if err != nil {
log.Printf("[ERROR] Failed to create request for workflow: %s", err)
return workflow, err
}
resp, err := client.Do(req)
if err != nil {
log.Printf("[ERROR] Failed to get workflow from cloud: %s", err)
return workflow, err
}
defer resp.Body.Close()
if resp.StatusCode != 200 {
log.Printf("[ERROR] Failed to get workflow from cloud: %s", resp.Status)
return workflow, errors.New("Failed to get workflow from cloud")
}
body, err := ioutil.ReadAll(resp.Body)
if err != nil {
log.Printf("[ERROR] Failed to read response body: %s", err)
return workflow, err
}
err = json.Unmarshal(body, &workflow)
if err != nil {
log.Printf("[ERROR] Failed to unmarshal response body: %s", err)
return workflow, err
}
// Clear out and reset IDs
workflow.Created = time.Now().Unix()
workflow.ID = uuid.NewV4().String()
workflow.OrgId = orgId
workflow.Org = []OrgMini{
OrgMini{
Id: orgId,
},
}
workflow.ExecutingOrg = OrgMini{
Id: orgId,
}
workflow.Public = false
workflow.WorkflowType = workflowType
workflow.Validation = TypeValidation{}
for _, usecaseName := range usecaseNames {
workflow.UsecaseIds = append(workflow.UsecaseIds, usecaseName)
}
workflow.ParentWorkflowId = ""
for actionIndex, _ := range workflow.Actions {
newId := uuid.NewV4().String()
if workflow.Start == workflow.Actions[actionIndex].ID {
workflow.Start = newId
}
for branchIndex, _ := range workflow.Branches {
if workflow.Actions[actionIndex].ID == workflow.Branches[branchIndex].SourceID {
workflow.Branches[branchIndex].SourceID = newId
}
if workflow.Actions[actionIndex].ID == workflow.Branches[branchIndex].DestinationID {
workflow.Branches[branchIndex].DestinationID = newId
}
}
workflow.Actions[actionIndex].ID = newId
}
for triggerIndex, _ := range workflow.Triggers {
newId := uuid.NewV4().String()
for branchIndex, _ := range workflow.Branches {
if workflow.Triggers[triggerIndex].ID == workflow.Branches[branchIndex].SourceID {
workflow.Branches[branchIndex].SourceID = newId
}
if workflow.Triggers[triggerIndex].ID == workflow.Branches[branchIndex].DestinationID {
workflow.Branches[branchIndex].DestinationID = newId
}
}
workflow.Triggers[triggerIndex].ID = newId
// FIXME: Check if it's a schedule, then set the interval + start it
if workflow.Triggers[triggerIndex].TriggerType == "schedule" {
//workflow.Triggers[triggerIndex].Interval = 60
for paramIndex, param := range workflow.Triggers[triggerIndex].Parameters {
if param.Name == "interval" {
if project.Environment == "cloud" {
param.Value = "*/5 * * * *"
} else {
param.Value = "300"
}
}
workflow.Triggers[triggerIndex].Parameters[paramIndex] = param
}
// FIXME: Start the schedule automatically
}
}
/*
for branchIndex, _ := range workflow.Branches {
workflow.Branches[branchIndex].ID = uuid.NewV4().String()
}
*/
// FIXME: Add a changeout for ANY schemaless node to use the correct
// action in it
workflow.BackgroundProcessing = true
log.Printf("[DEBUG] Saving workflow for org %s", orgId)
err = SetWorkflow(ctx, workflow, workflow.ID)
if err != nil {
log.Printf("[ERROR] Failed to set workflow during detection save: %s", err)
return Workflow{}, err
}
return workflow, nil
}