Skip to content

Commit 8414900

Browse files
committed
Minor changes to how iocs are handled in a workflow
1 parent 0a41a7e commit 8414900

File tree

5 files changed

+139
-11
lines changed

5 files changed

+139
-11
lines changed

app_upload/stitcher.go

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1007,8 +1007,8 @@ func main() {
10071007
bucketName = os.Args[5]
10081008
}
10091009

1010-
appname := "shuffle-ai"
1011-
appversion := "1.0.0"
1010+
appname := "shuffle-tools"
1011+
appversion := "1.2.0"
10121012
err := deployConfigToBackend(appfolder, appname, appversion)
10131013
if err != nil {
10141014
log.Printf("[WARNING] Failed uploading config: %s", err)

blobs.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2109,7 +2109,7 @@ for k, v in all_items.items():
21092109
del subval["urls"]
21102110
new_list.append({
21112111
"key": subkey,
2112-
"category": "%s_indicators" % k,
2112+
"category": "ioc_%s" % k,
21132113
"value": json.dumps(subval),
21142114
})
21152115

codegen.go

Lines changed: 129 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -28,8 +28,10 @@ import (
2828

2929
"cloud.google.com/go/storage"
3030
docker "github.com/docker/docker/client"
31-
"github.com/frikky/kin-openapi/openapi3"
3231
"gopkg.in/yaml.v2"
32+
33+
"github.com/frikky/kin-openapi/openapi3"
34+
//iocParser "github.com/Shuffle/indicator-parser/go/ioc"
3335
)
3436

3537
var downloadedImages = []string{}
@@ -4746,6 +4748,10 @@ func handleRunDatastoreAutomation(cacheData CacheKeyData, automation DatastoreAu
47464748
return errors.New("CacheKeyData.OrgId is required for handleRunAutomation")
47474749
}
47484750

4751+
if len(cacheData.Category) == 0 {
4752+
return errors.New("CacheKeyData.Category is required for handleRunAutomation")
4753+
}
4754+
47494755
ctx := context.Background()
47504756
parsedName := strings.ReplaceAll(strings.ToLower(automation.Name), " ", "_")
47514757

@@ -4759,6 +4765,7 @@ func handleRunDatastoreAutomation(cacheData CacheKeyData, automation DatastoreAu
47594765
parsedOutput["shuffle_datastore"] = map[string]interface{}{
47604766
"action": "update",
47614767
"key": cacheData.Key,
4768+
"category": cacheData.Category,
47624769
"org_id": cacheData.OrgId,
47634770
"timestamp": cacheData.Edited,
47644771
"workflow_id": cacheData.WorkflowId,
@@ -4774,12 +4781,60 @@ func handleRunDatastoreAutomation(cacheData CacheKeyData, automation DatastoreAu
47744781
if parsedName == "correlate_categories" {
47754782
// Correlations don't matter anymore as ngrams are automatic. Cleaned up
47764783
// november 2025 after adding graphic system to datastore
4784+
47774785
} else if parsedName == "enrich" {
4778-
log.Printf("Should enrich the following data: %s", string(marshalledBody))
4786+
// Prevent recursion
4787+
cacheKey := fmt.Sprintf("enrich_wait_%s_%s_%s", cacheData.OrgId, cacheData.Category, cacheData.Key)
4788+
data, err := GetCache(ctx, cacheKey)
4789+
if err == nil && data != nil {
4790+
//log.Printf("[DEBUG] Enrich automation recently run for key %s in category %s - skipping.", cacheData.Key, cacheData.Category)
4791+
return nil
4792+
}
4793+
4794+
// Set cache key for 1 hour to avoid re-running enrich too often
4795+
SetCache(ctx, cacheKey, []byte("1"), 5)
47794796

47804797
// Use key "enrichments" =>
47814798
// [{"name": "answers.ip", "value": "92.24.47.250", "type": "location", "data": {"city": "Socotra", "continent": "Asia", "coordinates": [-25.4153, 17.0743], "country": "YE", "desc": "Yemen"}}]
47824799

4800+
parsedData := map[string]interface{}{}
4801+
if err := json.Unmarshal(marshalledBody, &parsedData); err != nil {
4802+
log.Printf("[WARNING] Failed to unmarshal marshalledBody for enrich for key %s in category %s: %s", cacheData.Key, cacheData.Category, err)
4803+
return err
4804+
}
4805+
4806+
if _, ok := parsedData["enrichments"]; ok {
4807+
log.Printf("[DEBUG] Enrichments key already exists - skipping enrichment automation for key %s in category %s", cacheData.Key, cacheData.Category)
4808+
return nil
4809+
}
4810+
4811+
org, err := GetOrg(ctx, cacheData.OrgId)
4812+
if err != nil {
4813+
return err
4814+
}
4815+
4816+
foundApikey := ""
4817+
for _, user := range org.Users {
4818+
foundUser, err := GetUser(ctx, user.Id)
4819+
if err != nil {
4820+
continue
4821+
}
4822+
4823+
if len(foundUser.Role) == 0 || foundUser.Role == "org-reader" {
4824+
continue
4825+
}
4826+
4827+
if len(foundUser.ApiKey) > 0 {
4828+
foundApikey = foundUser.ApiKey
4829+
break
4830+
}
4831+
}
4832+
4833+
if len(foundApikey) == 0 {
4834+
log.Printf("[ERROR] No admin user with API key found for org %s", cacheData.OrgId)
4835+
return errors.New("No admin user with API key found")
4836+
}
4837+
47834838
// Send the data into shuffle_tools => parse_ioc?
47844839
// Or generate a subflow that runs for it? :thinking:
47854840

@@ -4792,9 +4847,80 @@ func handleRunDatastoreAutomation(cacheData CacheKeyData, automation DatastoreAu
47924847
// 6. Push enriched alert to SIEM/EDR/SOAR for automated playbook or analyst triage.
47934848
// 7. If high confidence, add to blocklists / trigger containment / share via STIX/TAXII or MISP.
47944849

4795-
47964850
// Getting started:
47974851
// 1. Check for enrichments key. Stop if it exists.
4852+
/*
4853+
types := []iocParser.IndicatorType{
4854+
iocParser.IPV4,
4855+
iocParser.URL_LINK,
4856+
iocParser.Domain,
4857+
iocParser.Email,
4858+
}
4859+
foundIocs := iocParser.Parse(string(marshalledBody), types)
4860+
log.Printf("RESP: %#v", foundIocs)
4861+
if len(foundIocs) == 0 {
4862+
log.Printf("[DEBUG] No IOCs found to enrich.")
4863+
return nil
4864+
}
4865+
4866+
log.Printf("[DEBUG] Found %d IOCs to enrich.", len(foundIocs))
4867+
for _, foundIoc := range foundIocs {
4868+
log.Printf("[DEBUG] Found IOC: %#v", foundIoc)
4869+
}
4870+
*/
4871+
4872+
backendUrl := "https://shuffler.io"
4873+
if len(os.Getenv("BASE_URL")) > 0 {
4874+
backendUrl = os.Getenv("BASE_URL")
4875+
}
4876+
4877+
if len(os.Getenv("SHUFFLE_CLOUDRUN_URL")) > 0 && strings.Contains(os.Getenv("SHUFFLE_CLOUDRUN_URL"), "http") {
4878+
backendUrl = os.Getenv("SHUFFLE_CLOUDRUN_URL")
4879+
}
4880+
4881+
relevantWorkflowId := "fd44510b-dab7-4e77-8882-e205cb844c84"
4882+
fullUrl := fmt.Sprintf("%s/api/v1/workflows/%s/execute", backendUrl, relevantWorkflowId)
4883+
4884+
executionRequest := ExecutionRequest{
4885+
ExecutionArgument: string(marshalledBody),
4886+
ExecutionSource: fmt.Sprintf("datastore|%s|%s", cacheData.Category, cacheData.Key),
4887+
}
4888+
4889+
newParsedBody, err := json.Marshal(executionRequest)
4890+
if err != nil {
4891+
log.Printf("[ERROR] Failed to marshal body for enrichment workflow execution: %s", err)
4892+
return err
4893+
}
4894+
4895+
client := GetExternalClient(fullUrl)
4896+
req, err := http.NewRequest(
4897+
"POST",
4898+
fullUrl,
4899+
bytes.NewBuffer(newParsedBody),
4900+
)
4901+
4902+
if err != nil {
4903+
log.Printf("[ERROR] Failed to create request for enrichment workflow execution: %s", err)
4904+
return err
4905+
}
4906+
4907+
req.Header.Add("Authorization", fmt.Sprintf("Bearer %s", foundApikey))
4908+
req.Header.Add("Org-Id", cacheData.OrgId)
4909+
4910+
resp, err := client.Do(req)
4911+
if err != nil {
4912+
log.Printf("[ERROR] Failed to send enrichment workflow execution request: %s", err)
4913+
return err
4914+
}
4915+
4916+
defer resp.Body.Close()
4917+
body, err := ioutil.ReadAll(resp.Body)
4918+
if err != nil {
4919+
log.Printf("[ERROR] Failed to read response body from enrichment workflow execution request: %s", err)
4920+
return err
4921+
}
4922+
4923+
log.Printf("RESP FOR RUNNING ENRICHMENT (%d): %s", resp.StatusCode, string(body))
47984924

47994925
} else if parsedName == "run_workflow" {
48004926
if debug {

go.mod

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@ require (
1010
cloud.google.com/go/scheduler v1.11.7
1111
cloud.google.com/go/storage v1.55.0
1212
github.com/Masterminds/semver v1.5.0
13+
github.com/Shuffle/indicator-parser v0.0.4
1314
github.com/adrg/strutil v0.3.1
1415
github.com/algolia/algoliasearch-client-go/v3 v3.31.4
1516
github.com/bradfitz/gomemcache v0.0.0-20250403215159-8d39553ac7cf

structs.go

Lines changed: 6 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -4466,16 +4466,13 @@ type PipelineInfo struct {
44664466
Hidden bool `json:"hidden"`
44674467
Definition string `json:"definition"`
44684468
Configured bool `json:"configured"`
4469-
Package any `json:"package"`
44704469
Unstoppable bool `json:"unstoppable"`
44714470
CreatedAt int64 `json:"created_at"`
44724471
LastModified int64 `json:"last_modified"`
44734472
StartTime string `json:"start_time"`
44744473
TotalRuns int `json:"total_runs"`
44754474
State string `json:"state"`
44764475
Error string `json:"error"`
4477-
Diagnostics []any `json:"diagnostics"`
4478-
Labels []any `json:"labels"`
44794476
RetryDelay string `json:"retry_delay"`
44804477
Autostart struct {
44814478
Created bool `json:"created"`
@@ -4487,8 +4484,12 @@ type PipelineInfo struct {
44874484
Failed bool `json:"failed"`
44884485
Stopped bool `json:"stopped"`
44894486
} `json:"autodelete"`
4490-
TTL any `json:"ttl"`
4491-
RemainingTTL any `json:"remaining_ttl"`
4487+
4488+
//Package any `json:"package"`
4489+
//Diagnostics []any `json:"diagnostics"`
4490+
//Labels []any `json:"labels"`
4491+
//TTL any `json:"ttl"`
4492+
//RemainingTTL any `json:"remaining_ttl"`
44924493

44934494
// Shuffle ref
44944495
Environment string `json:"environment"`

0 commit comments

Comments
 (0)