@@ -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
3537var 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 {
0 commit comments