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
This commit is contained in:
@@ -0,0 +1,982 @@
|
||||
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
|
||||
}
|
||||
Reference in New Issue
Block a user