diff --git a/pkg/analyzer/analyzers/datadog/datadog.go b/pkg/analyzer/analyzers/datadog/datadog.go index a83a63fc4a97..587c44325ab8 100644 --- a/pkg/analyzer/analyzers/datadog/datadog.go +++ b/pkg/analyzer/analyzers/datadog/datadog.go @@ -26,47 +26,72 @@ func (a Analyzer) Type() analyzers.AnalyzerType { // Analyze performs the analysis of the Datadog API key and returns the analyzer result. func (a Analyzer) Analyze(ctx context.Context, credInfo map[string]string) (*analyzers.AnalyzerResult, error) { - apiKey, exist := credInfo["apiKey"] - if !exist { - return nil, errors.New("API key not found in credentials info") - } - - // Get appKey if provided + apiKey := credInfo["apiKey"] appKey := credInfo["appKey"] + endpoint := credInfo["endpoint"] - info, err := AnalyzePermissions(a.Cfg, apiKey, appKey) + info, err := AnalyzePermissions(a.Cfg, apiKey, appKey, endpoint) if err != nil { return nil, err } - return secretInfoToAnalyzerResult(info), nil } -func AnalyzeAndPrintPermissions(cfg *config.Config, apiKey string, appKey string) { - info, err := AnalyzePermissions(cfg, apiKey, appKey) +func AnalyzeAndPrintPermissions(cfg *config.Config, apiKey, appKey, endpoint string) { + info, err := AnalyzePermissions(cfg, apiKey, appKey, endpoint) if err != nil { // just print the error in cli and continue as a partial success color.Red("[x] Error : %s", err.Error()) } - color.Green("[i] Valid Datadog API Key\n") + if info == nil { + color.Red("[x] No information retrieved") + return + } + color.Green("[i] Valid Datadog API Key\n") printUser(info.User) printResources(info.Resources) printPermissions(info.Permissions) } // AnalyzePermissions will collect all the scopes assigned to token along with resource it can access -func AnalyzePermissions(cfg *config.Config, apiKey string, appKey string) (*SecretInfo, error) { +func AnalyzePermissions(cfg *config.Config, apiKey, appKey, endpoint string) (*SecretInfo, error) { + if apiKey == "" { + return nil, errors.New("api key not found in credentials info") + } + // create the http client client := analyzers.NewAnalyzeClient(cfg) var secretInfo = &SecretInfo{} - // First detect which DataDog domain works with this API key - baseURL, err := DetectDomain(client, apiKey, appKey) - if err != nil { - return nil, fmt.Errorf("[x] %v", err) + var baseURL string + var err error + + // If endpoint is provided, use it directly; otherwise detect domain + if endpoint != "" { + baseURL = endpoint + "/api" + } else { + baseURL, err = DetectDomain(client, apiKey, appKey) + if err != nil { + return nil, fmt.Errorf("[x] %v", err) + } + } + + if appKey == "" { + // If no application key is provided, we can only validate the API key + isValidApiKey, err := ValidateApiKey(client, baseURL, apiKey) + if err != nil { + return nil, fmt.Errorf("failed to validate api key: %v", err) + } + if !isValidApiKey { + return nil, errors.New("invalid api key provided") + } + if err := CaptureApiKeyPermissions(client, baseURL, apiKey, appKey, secretInfo); err != nil { + return nil, fmt.Errorf("failed to fetch permissions: %v", err) + } + return secretInfo, nil } // capture user information in secretInfo @@ -85,6 +110,10 @@ func AnalyzePermissions(cfg *config.Config, apiKey string, appKey string) (*Secr return nil, fmt.Errorf("failed to fetch permissions: %v", err) } + // Capture API key permissions + if err := CaptureApiKeyPermissions(client, baseURL, apiKey, appKey, secretInfo); err != nil { + return nil, fmt.Errorf("failed to fetch permissions: %v", err) + } return secretInfo, nil } @@ -114,7 +143,17 @@ func secretInfoToAnalyzerResult(info *SecretInfo) *analyzers.AnalyzerResult { } permissionBindings := secretInfoPermissionsToAnalyzerPermission(info.Permissions) - result.Bindings = analyzers.BindAllPermissions(*userResource, *permissionBindings...) + if userResource != nil && len(*permissionBindings) > 0 { + result.Bindings = analyzers.BindAllPermissions(*userResource, *permissionBindings...) + } + if userResource == nil && len(*permissionBindings) > 0 { + result.Bindings = analyzers.BindAllPermissions(analyzers.Resource{ + FullyQualifiedName: "Unknown User", + Name: "Unknown User", + Type: "User", + Metadata: map[string]any{}, + }, *permissionBindings...) + } // Extract information from resources to create bindings for _, resource := range info.Resources { diff --git a/pkg/analyzer/analyzers/datadog/datadog_test.go b/pkg/analyzer/analyzers/datadog/datadog_test.go index 46b089447046..8fd1fc650f79 100644 --- a/pkg/analyzer/analyzers/datadog/datadog_test.go +++ b/pkg/analyzer/analyzers/datadog/datadog_test.go @@ -17,6 +17,9 @@ import ( //go:embed expected_output.json var expectedOutput []byte +//go:embed expected_output_apikey.json +var expectedOutputAPIKey []byte + func TestAnalyzer_Analyze(t *testing.T) { ctx, cancel := context.WithTimeout(context.Background(), time.Minute*2) defer cancel() @@ -38,11 +41,12 @@ func TestAnalyzer_Analyze(t *testing.T) { } tests := []struct { - name string - apiKey string - appKey string - want []byte // JSON string - wantErr bool + name string + apiKey string + appKey string + endpoint string + want []byte // JSON string + wantErr bool }{ { name: "valid datadog credentials", @@ -51,6 +55,27 @@ func TestAnalyzer_Analyze(t *testing.T) { want: expectedOutput, wantErr: false, }, + { + name: "valid datadog credentials with endpoint", + apiKey: apiKey, + appKey: appKey, + endpoint: "https://api.us5.datadoghq.com", + want: expectedOutput, + wantErr: false, + }, + { + name: "valid datadog credentials with invalid endpoint", + apiKey: apiKey, + appKey: appKey, + endpoint: "https://api.eu.datadoghq.com", + want: []byte(fmt.Sprintf(`{ + "AnalyzerType": %s, + "Bindings": [], + "UnboundedResources": null, + "Metadata": {} + }`, analyzers.AnalyzerTypeDatadog)), + wantErr: true, + }, { name: "invalid credentials", apiKey: "invalid_api_key", @@ -63,7 +88,7 @@ func TestAnalyzer_Analyze(t *testing.T) { for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { a := Analyzer{Cfg: &config.Config{}} - got, err := a.Analyze(ctx, map[string]string{"apiKey": tt.apiKey, "appKey": tt.appKey}) + got, err := a.Analyze(ctx, map[string]string{"apiKey": tt.apiKey, "appKey": tt.appKey, "endpoint": tt.endpoint}) if (err != nil) != tt.wantErr { t.Errorf("Analyzer.Analyze() error = %v, wantErr %v", err, tt.wantErr) @@ -96,8 +121,6 @@ func TestAnalyzer_Analyze(t *testing.T) { t.Fatalf("could not marshal got to JSON: %s", err) } - fmt.Println(string(gotJSON)) - // Parse the expected JSON string var wantObj analyzers.AnalyzerResult if err := json.Unmarshal(tt.want, &wantObj); err != nil { @@ -131,6 +154,85 @@ func TestAnalyzer_Analyze(t *testing.T) { } } +func TestAnalyzer_Analyze_ApiKeyOnly(t *testing.T) { + ctx, cancel := context.WithTimeout(context.Background(), time.Minute*2) + defer cancel() + + // Get API keys from GCP + var apiKey string + testSecrets, err := common.GetSecret(ctx, "trufflehog-testing", "analyzers1") + if err != nil { + t.Fatalf("Could not get test secrets from GCP: %s", err) + } + + // Get the required credentials + apiKey = testSecrets.MustGetField("DATADOG_API_KEY") + + // Fail if credentials are not available + if apiKey == "" { + t.Fatalf("Datadog credentials are required for this test") + } + want := expectedOutputAPIKey + + a := Analyzer{Cfg: &config.Config{}} + got, err := a.Analyze(ctx, map[string]string{"apiKey": apiKey, "endpoint": "https://api.us5.datadoghq.com"}) + if err != nil { + t.Errorf("Analyzer.Analyze() error = %v, wantErr %v", err, false) + return + } + + // For valid cases, verify we got a result + if got == nil { + t.Errorf("Analyzer.Analyze() = nil, want non-nil") + return + } + + // Verify type is correct + if got.AnalyzerType != analyzers.AnalyzerTypeDatadog { + t.Errorf("Analyzer.Analyze() returned wrong analyzer type, got %d want %d", + got.AnalyzerType, analyzers.AnalyzerTypeDatadog) + } + + // Bindings need to be in the same order to be comparable + sortBindings(got.Bindings) + + // Marshal the actual result to JSON + gotJSON, err := json.Marshal(got) + if err != nil { + t.Fatalf("could not marshal got to JSON: %s", err) + } + + // Parse the expected JSON string + var wantObj analyzers.AnalyzerResult + if err := json.Unmarshal(want, &wantObj); err != nil { + t.Fatalf("could not unmarshal want JSON string: %s", err) + } + + // Bindings need to be in the same order to be comparable + sortBindings(wantObj.Bindings) + + // Marshal the expected result to JSON (to normalize) + wantJSON, err := json.Marshal(wantObj) + if err != nil { + t.Fatalf("could not marshal want to JSON: %s", err) + } + + // Compare the JSON strings + if string(gotJSON) != string(wantJSON) { + // Pretty-print both JSON strings for easier comparison + var gotIndented, wantIndented []byte + gotIndented, err = json.MarshalIndent(got, "", " ") + if err != nil { + t.Fatalf("could not marshal got to indented JSON: %s", err) + } + wantIndented, err = json.MarshalIndent(wantObj, "", " ") + if err != nil { + t.Fatalf("could not marshal want to indented JSON: %s", err) + } + t.Errorf("Analyzer.Analyze() = %s, want %s", gotIndented, wantIndented) + } +} + // Helper function to sort bindings func sortBindings(bindings []analyzers.Binding) { sort.SliceStable(bindings, func(i, j int) bool { diff --git a/pkg/analyzer/analyzers/datadog/expected_output.json b/pkg/analyzer/analyzers/datadog/expected_output.json index bea9ea9416af..f06be264dfeb 100644 --- a/pkg/analyzer/analyzers/datadog/expected_output.json +++ b/pkg/analyzer/analyzers/datadog/expected_output.json @@ -3,1043 +3,1109 @@ "Bindings": [ { "Resource": { - "Name": "Dashboards Read", - "FullyQualifiedName": "Dashboards Read", - "Type": "Dashboards", - "Metadata": { "Resource": "Dashboards" }, + "Name": "My Monitor", + "FullyQualifiedName": "4429851", + "Type": "Monitor", + "Metadata": {}, "Parent": { "Name": "Truffle Sec", - "FullyQualifiedName": "", - "Type": "user", - "Metadata": { "email": "detectors@trufflesec.com" }, + "FullyQualifiedName": "a4b3b545-24ec-11f0-9f57-22795724d251", + "Type": "User", + "Metadata": { + "email": "detectors@trufflesec.com" + }, "Parent": null } }, - "Permission": { "Value": "dashboards_read", "Parent": null } + "Permission": { + "Value": "", + "Parent": null + }, + "Condition": "" }, { "Resource": { - "Name": "Dashboards Write", - "FullyQualifiedName": "Dashboards Write", - "Type": "Dashboards", - "Metadata": { "Resource": "Dashboards" }, - "Parent": { - "Name": "Truffle Sec", - "FullyQualifiedName": "", - "Type": "user", - "Metadata": { "email": "detectors@trufflesec.com" }, - "Parent": null - } + "Name": "Truffle Sec", + "FullyQualifiedName": "a4b3b545-24ec-11f0-9f57-22795724d251", + "Type": "User", + "Metadata": { + "email": "detectors@trufflesec.com" + }, + "Parent": null }, - "Permission": { "Value": "dashboards_write", "Parent": null } + "Permission": { + "Value": "API Keys Read", + "Parent": null + }, + "Condition": "" }, { "Resource": { - "Name": "Dashboards Public Share", - "FullyQualifiedName": "Dashboards Public Share", - "Type": "Dashboards", - "Metadata": { "Resource": "Dashboards" }, - "Parent": { - "Name": "Truffle Sec", - "FullyQualifiedName": "", - "Type": "user", - "Metadata": { "email": "detectors@trufflesec.com" }, - "Parent": null - } + "Name": "Truffle Sec", + "FullyQualifiedName": "a4b3b545-24ec-11f0-9f57-22795724d251", + "Type": "User", + "Metadata": { + "email": "detectors@trufflesec.com" + }, + "Parent": null }, - "Permission": { "Value": "dashboards_public_share", "Parent": null } + "Permission": { + "Value": "API Keys Write", + "Parent": null + }, + "Condition": "" }, { "Resource": { - "Name": "Monitors Read", - "FullyQualifiedName": "Monitors Read", - "Type": "Monitors", - "Metadata": { "Resource": "Monitors" }, - "Parent": { - "Name": "Truffle Sec", - "FullyQualifiedName": "", - "Type": "user", - "Metadata": { "email": "detectors@trufflesec.com" }, - "Parent": null - } + "Name": "Truffle Sec", + "FullyQualifiedName": "a4b3b545-24ec-11f0-9f57-22795724d251", + "Type": "User", + "Metadata": { + "email": "detectors@trufflesec.com" + }, + "Parent": null }, - "Permission": { "Value": "monitors_read", "Parent": null } + "Permission": { + "Value": "APM Generate Metrics", + "Parent": null + }, + "Condition": "" }, { "Resource": { - "Name": "Monitors Write", - "FullyQualifiedName": "Monitors Write", - "Type": "Monitors", - "Metadata": { "Resource": "Monitors" }, - "Parent": { - "Name": "Truffle Sec", - "FullyQualifiedName": "", - "Type": "user", - "Metadata": { "email": "detectors@trufflesec.com" }, - "Parent": null - } + "Name": "Truffle Sec", + "FullyQualifiedName": "a4b3b545-24ec-11f0-9f57-22795724d251", + "Type": "User", + "Metadata": { + "email": "detectors@trufflesec.com" + }, + "Parent": null }, - "Permission": { "Value": "monitors_write", "Parent": null } + "Permission": { + "Value": "APM Pipelines Read", + "Parent": null + }, + "Condition": "" }, { "Resource": { - "Name": "Logs Modify Indexes", - "FullyQualifiedName": "Logs Modify Indexes", - "Type": "Logs", - "Metadata": { "Resource": "Logs" }, - "Parent": { - "Name": "Truffle Sec", - "FullyQualifiedName": "", - "Type": "user", - "Metadata": { "email": "detectors@trufflesec.com" }, - "Parent": null - } + "Name": "Truffle Sec", + "FullyQualifiedName": "a4b3b545-24ec-11f0-9f57-22795724d251", + "Type": "User", + "Metadata": { + "email": "detectors@trufflesec.com" + }, + "Parent": null }, - "Permission": { "Value": "logs_modify_indexes", "Parent": null } + "Permission": { + "Value": "APM Pipelines Write", + "Parent": null + }, + "Condition": "" }, { "Resource": { - "Name": "Logs Write Pipelines", - "FullyQualifiedName": "Logs Write Pipelines", - "Type": "Logs", - "Metadata": { "Resource": "Logs" }, - "Parent": { - "Name": "Truffle Sec", - "FullyQualifiedName": "", - "Type": "user", - "Metadata": { "email": "detectors@trufflesec.com" }, - "Parent": null - } + "Name": "Truffle Sec", + "FullyQualifiedName": "a4b3b545-24ec-11f0-9f57-22795724d251", + "Type": "User", + "Metadata": { + "email": "detectors@trufflesec.com" + }, + "Parent": null }, - "Permission": { "Value": "logs_write_pipelines", "Parent": null } + "Permission": { + "Value": "APM Read", + "Parent": null + }, + "Condition": "" }, { "Resource": { - "Name": "Logs Write Archives", - "FullyQualifiedName": "Logs Write Archives", - "Type": "Logs", - "Metadata": { "Resource": "Logs" }, - "Parent": { - "Name": "Truffle Sec", - "FullyQualifiedName": "", - "Type": "user", - "Metadata": { "email": "detectors@trufflesec.com" }, - "Parent": null - } + "Name": "Truffle Sec", + "FullyQualifiedName": "a4b3b545-24ec-11f0-9f57-22795724d251", + "Type": "User", + "Metadata": { + "email": "detectors@trufflesec.com" + }, + "Parent": null }, - "Permission": { "Value": "logs_write_archives", "Parent": null } + "Permission": { + "Value": "APM Retention Filters Read", + "Parent": null + }, + "Condition": "" }, { "Resource": { - "Name": "Logs Generate Metrics", - "FullyQualifiedName": "Logs Generate Metrics", - "Type": "Logs", - "Metadata": { "Resource": "Logs" }, - "Parent": { - "Name": "Truffle Sec", - "FullyQualifiedName": "", - "Type": "user", - "Metadata": { "email": "detectors@trufflesec.com" }, - "Parent": null - } + "Name": "Truffle Sec", + "FullyQualifiedName": "a4b3b545-24ec-11f0-9f57-22795724d251", + "Type": "User", + "Metadata": { + "email": "detectors@trufflesec.com" + }, + "Parent": null }, - "Permission": { "Value": "logs_generate_metrics", "Parent": null } + "Permission": { + "Value": "APM Retention Filters Write", + "Parent": null + }, + "Condition": "" }, { "Resource": { - "Name": "Manage Downtimes", - "FullyQualifiedName": "Manage Downtimes", - "Type": "Monitors", - "Metadata": { "Resource": "Monitors" }, - "Parent": { - "Name": "Truffle Sec", - "FullyQualifiedName": "", - "Type": "user", - "Metadata": { "email": "detectors@trufflesec.com" }, - "Parent": null - } + "Name": "Truffle Sec", + "FullyQualifiedName": "a4b3b545-24ec-11f0-9f57-22795724d251", + "Type": "User", + "Metadata": { + "email": "detectors@trufflesec.com" + }, + "Parent": null }, - "Permission": { "Value": "monitors_downtime", "Parent": null } + "Permission": { + "Value": "AWS Configurations Manage", + "Parent": null + }, + "Condition": "" }, { "Resource": { - "Name": "Logs Read Data", - "FullyQualifiedName": "Logs Read Data", - "Type": "Logs", - "Metadata": { "Resource": "Logs" }, - "Parent": { - "Name": "Truffle Sec", - "FullyQualifiedName": "", - "Type": "user", - "Metadata": { "email": "detectors@trufflesec.com" }, - "Parent": null - } + "Name": "Truffle Sec", + "FullyQualifiedName": "a4b3b545-24ec-11f0-9f57-22795724d251", + "Type": "User", + "Metadata": { + "email": "detectors@trufflesec.com" + }, + "Parent": null }, - "Permission": { "Value": "logs_read_data", "Parent": null } + "Permission": { + "Value": "Audit Trail Read", + "Parent": null + }, + "Condition": "" }, { "Resource": { - "Name": "Logs Read Archives", - "FullyQualifiedName": "Logs Read Archives", - "Type": "Logs", - "Metadata": { "Resource": "Logs" }, - "Parent": { - "Name": "Truffle Sec", - "FullyQualifiedName": "", - "Type": "user", - "Metadata": { "email": "detectors@trufflesec.com" }, - "Parent": null - } + "Name": "Truffle Sec", + "FullyQualifiedName": "a4b3b545-24ec-11f0-9f57-22795724d251", + "Type": "User", + "Metadata": { + "email": "detectors@trufflesec.com" + }, + "Parent": null }, - "Permission": { "Value": "logs_read_archives", "Parent": null } + "Permission": { + "Value": "Azure Configurations Manage", + "Parent": null + }, + "Condition": "" }, { "Resource": { - "Name": "Security Rules Read", - "FullyQualifiedName": "Security Rules Read", - "Type": "Security Monitoring", - "Metadata": { "Resource": "Security Monitoring" }, - "Parent": { - "Name": "Truffle Sec", - "FullyQualifiedName": "", - "Type": "user", - "Metadata": { "email": "detectors@trufflesec.com" }, - "Parent": null - } + "Name": "Truffle Sec", + "FullyQualifiedName": "a4b3b545-24ec-11f0-9f57-22795724d251", + "Type": "User", + "Metadata": { + "email": "detectors@trufflesec.com" + }, + "Parent": null }, "Permission": { - "Value": "security_monitoring_rules_read", + "Value": "Connections Read", "Parent": null - } + }, + "Condition": "" }, { "Resource": { - "Name": "Security Signals Read", - "FullyQualifiedName": "Security Signals Read", - "Type": "Security Monitoring", - "Metadata": { "Resource": "Security Monitoring" }, - "Parent": { - "Name": "Truffle Sec", - "FullyQualifiedName": "", - "Type": "user", - "Metadata": { "email": "detectors@trufflesec.com" }, - "Parent": null - } + "Name": "Truffle Sec", + "FullyQualifiedName": "a4b3b545-24ec-11f0-9f57-22795724d251", + "Type": "User", + "Metadata": { + "email": "detectors@trufflesec.com" + }, + "Parent": null }, "Permission": { - "Value": "security_monitoring_signals_read", + "Value": "Connections Write", "Parent": null - } + }, + "Condition": "" }, { "Resource": { - "Name": "Security Signals Write", - "FullyQualifiedName": "Security Signals Write", - "Type": "Security Monitoring", - "Metadata": { "Resource": "Security Monitoring" }, - "Parent": { - "Name": "Truffle Sec", - "FullyQualifiedName": "", - "Type": "user", - "Metadata": { "email": "detectors@trufflesec.com" }, - "Parent": null - } + "Name": "Truffle Sec", + "FullyQualifiedName": "a4b3b545-24ec-11f0-9f57-22795724d251", + "Type": "User", + "Metadata": { + "email": "detectors@trufflesec.com" + }, + "Parent": null }, "Permission": { - "Value": "security_monitoring_signals_write", + "Value": "Dashboards Public Share", "Parent": null - } + }, + "Condition": "" }, { "Resource": { - "Name": "User Access Invite", - "FullyQualifiedName": "User Access Invite", - "Type": "Users", - "Metadata": { "Resource": "Users" }, - "Parent": { - "Name": "Truffle Sec", - "FullyQualifiedName": "", - "Type": "user", - "Metadata": { "email": "detectors@trufflesec.com" }, - "Parent": null - } + "Name": "Truffle Sec", + "FullyQualifiedName": "a4b3b545-24ec-11f0-9f57-22795724d251", + "Type": "User", + "Metadata": { + "email": "detectors@trufflesec.com" + }, + "Parent": null + }, + "Permission": { + "Value": "Dashboards Read", + "Parent": null }, - "Permission": { "Value": "user_access_invite", "Parent": null } + "Condition": "" }, { "Resource": { - "Name": "User App Keys", - "FullyQualifiedName": "User App Keys", - "Type": "Key Management", - "Metadata": { "Resource": "Key Management" }, - "Parent": { - "Name": "Truffle Sec", - "FullyQualifiedName": "", - "Type": "user", - "Metadata": { "email": "detectors@trufflesec.com" }, - "Parent": null - } + "Name": "Truffle Sec", + "FullyQualifiedName": "a4b3b545-24ec-11f0-9f57-22795724d251", + "Type": "User", + "Metadata": { + "email": "detectors@trufflesec.com" + }, + "Parent": null + }, + "Permission": { + "Value": "Dashboards Write", + "Parent": null }, - "Permission": { "Value": "user_app_keys", "Parent": null } + "Condition": "" }, { "Resource": { - "Name": "Org App Keys Read", - "FullyQualifiedName": "Org App Keys Read", - "Type": "Key Management", - "Metadata": { "Resource": "Key Management" }, - "Parent": { - "Name": "Truffle Sec", - "FullyQualifiedName": "", - "Type": "user", - "Metadata": { "email": "detectors@trufflesec.com" }, - "Parent": null - } + "Name": "Truffle Sec", + "FullyQualifiedName": "a4b3b545-24ec-11f0-9f57-22795724d251", + "Type": "User", + "Metadata": { + "email": "detectors@trufflesec.com" + }, + "Parent": null + }, + "Permission": { + "Value": "Data Scanner Read", + "Parent": null }, - "Permission": { "Value": "org_app_keys_read", "Parent": null } + "Condition": "" }, { "Resource": { - "Name": "Org App Keys Write", - "FullyQualifiedName": "Org App Keys Write", - "Type": "Key Management", - "Metadata": { "Resource": "Key Management" }, - "Parent": { - "Name": "Truffle Sec", - "FullyQualifiedName": "", - "Type": "user", - "Metadata": { "email": "detectors@trufflesec.com" }, - "Parent": null - } + "Name": "Truffle Sec", + "FullyQualifiedName": "a4b3b545-24ec-11f0-9f57-22795724d251", + "Type": "User", + "Metadata": { + "email": "detectors@trufflesec.com" + }, + "Parent": null + }, + "Permission": { + "Value": "Data Scanner Write", + "Parent": null }, - "Permission": { "Value": "org_app_keys_write", "Parent": null } + "Condition": "" }, { "Resource": { - "Name": "User Access Manage", - "FullyQualifiedName": "User Access Manage", - "Type": "Users", - "Metadata": { "Resource": "Users" }, - "Parent": { - "Name": "Truffle Sec", - "FullyQualifiedName": "", - "Type": "user", - "Metadata": { "email": "detectors@trufflesec.com" }, - "Parent": null - } + "Name": "Truffle Sec", + "FullyQualifiedName": "a4b3b545-24ec-11f0-9f57-22795724d251", + "Type": "User", + "Metadata": { + "email": "detectors@trufflesec.com" + }, + "Parent": null + }, + "Permission": { + "Value": "GCP Configurations Manage", + "Parent": null }, - "Permission": { "Value": "user_access_manage", "Parent": null } + "Condition": "" }, { "Resource": { - "Name": "Synthetics Private Locations Read", - "FullyQualifiedName": "Synthetics Private Locations Read", - "Type": "Synthetics", - "Metadata": { "Resource": "Synthetics" }, - "Parent": { - "Name": "Truffle Sec", - "FullyQualifiedName": "", - "Type": "user", - "Metadata": { "email": "detectors@trufflesec.com" }, - "Parent": null - } + "Name": "Truffle Sec", + "FullyQualifiedName": "a4b3b545-24ec-11f0-9f57-22795724d251", + "Type": "User", + "Metadata": { + "email": "detectors@trufflesec.com" + }, + "Parent": null }, "Permission": { - "Value": "synthetics_private_location_read", + "Value": "Incident Settings Write", "Parent": null - } + }, + "Condition": "" }, { "Resource": { - "Name": "Synthetics Private Locations Write", - "FullyQualifiedName": "Synthetics Private Locations Write", - "Type": "Synthetics", - "Metadata": { "Resource": "Synthetics" }, - "Parent": { - "Name": "Truffle Sec", - "FullyQualifiedName": "", - "Type": "user", - "Metadata": { "email": "detectors@trufflesec.com" }, - "Parent": null - } + "Name": "Truffle Sec", + "FullyQualifiedName": "a4b3b545-24ec-11f0-9f57-22795724d251", + "Type": "User", + "Metadata": { + "email": "detectors@trufflesec.com" + }, + "Parent": null }, "Permission": { - "Value": "synthetics_private_location_write", + "Value": "Incidents Read", "Parent": null - } + }, + "Condition": "" }, { "Resource": { - "Name": "Usage Read", - "FullyQualifiedName": "Usage Read", - "Type": "Usage Metering", - "Metadata": { "Resource": "Usage Metering" }, - "Parent": { - "Name": "Truffle Sec", - "FullyQualifiedName": "", - "Type": "user", - "Metadata": { "email": "detectors@trufflesec.com" }, - "Parent": null - } + "Name": "Truffle Sec", + "FullyQualifiedName": "a4b3b545-24ec-11f0-9f57-22795724d251", + "Type": "User", + "Metadata": { + "email": "detectors@trufflesec.com" + }, + "Parent": null + }, + "Permission": { + "Value": "Integrations Manage", + "Parent": null }, - "Permission": { "Value": "usage_read", "Parent": null } + "Condition": "" }, { "Resource": { - "Name": "Metric Tags Write", - "FullyQualifiedName": "Metric Tags Write", - "Type": "Metrics", - "Metadata": { "Resource": "Metrics" }, - "Parent": { - "Name": "Truffle Sec", - "FullyQualifiedName": "", - "Type": "user", - "Metadata": { "email": "detectors@trufflesec.com" }, - "Parent": null - } + "Name": "Truffle Sec", + "FullyQualifiedName": "a4b3b545-24ec-11f0-9f57-22795724d251", + "Type": "User", + "Metadata": { + "email": "detectors@trufflesec.com" + }, + "Parent": null + }, + "Permission": { + "Value": "Logs Generate Metrics", + "Parent": null }, - "Permission": { "Value": "metric_tags_write", "Parent": null } + "Condition": "" }, { "Resource": { - "Name": "Audit Trail Read", - "FullyQualifiedName": "Audit Trail Read", - "Type": "Audit", - "Metadata": { "Resource": "Audit" }, - "Parent": { - "Name": "Truffle Sec", - "FullyQualifiedName": "", - "Type": "user", - "Metadata": { "email": "detectors@trufflesec.com" }, - "Parent": null - } + "Name": "Truffle Sec", + "FullyQualifiedName": "a4b3b545-24ec-11f0-9f57-22795724d251", + "Type": "User", + "Metadata": { + "email": "detectors@trufflesec.com" + }, + "Parent": null + }, + "Permission": { + "Value": "Logs Modify Indexes", + "Parent": null }, - "Permission": { "Value": "audit_logs_read", "Parent": null } + "Condition": "" }, { "Resource": { - "Name": "API Keys Read", - "FullyQualifiedName": "API Keys Read", - "Type": "Key Management", - "Metadata": { "Resource": "Key Management" }, - "Parent": { - "Name": "Truffle Sec", - "FullyQualifiedName": "", - "Type": "user", - "Metadata": { "email": "detectors@trufflesec.com" }, - "Parent": null - } + "Name": "Truffle Sec", + "FullyQualifiedName": "a4b3b545-24ec-11f0-9f57-22795724d251", + "Type": "User", + "Metadata": { + "email": "detectors@trufflesec.com" + }, + "Parent": null + }, + "Permission": { + "Value": "Logs Read Archives", + "Parent": null }, - "Permission": { "Value": "api_keys_read", "Parent": null } + "Condition": "" }, { "Resource": { - "Name": "API Keys Write", - "FullyQualifiedName": "API Keys Write", - "Type": "Key Management", - "Metadata": { "Resource": "Key Management" }, - "Parent": { - "Name": "Truffle Sec", - "FullyQualifiedName": "", - "Type": "user", - "Metadata": { "email": "detectors@trufflesec.com" }, - "Parent": null - } + "Name": "Truffle Sec", + "FullyQualifiedName": "a4b3b545-24ec-11f0-9f57-22795724d251", + "Type": "User", + "Metadata": { + "email": "detectors@trufflesec.com" + }, + "Parent": null + }, + "Permission": { + "Value": "Logs Read Data", + "Parent": null }, - "Permission": { "Value": "api_keys_write", "Parent": null } + "Condition": "" }, { "Resource": { - "Name": "Synthetics Global Variable Read", - "FullyQualifiedName": "Synthetics Global Variable Read", - "Type": "Synthetics", - "Metadata": { "Resource": "Synthetics" }, - "Parent": { - "Name": "Truffle Sec", - "FullyQualifiedName": "", - "Type": "user", - "Metadata": { "email": "detectors@trufflesec.com" }, - "Parent": null - } + "Name": "Truffle Sec", + "FullyQualifiedName": "a4b3b545-24ec-11f0-9f57-22795724d251", + "Type": "User", + "Metadata": { + "email": "detectors@trufflesec.com" + }, + "Parent": null }, "Permission": { - "Value": "synthetics_global_variable_read", + "Value": "Logs Write Archives", "Parent": null - } + }, + "Condition": "" }, { "Resource": { - "Name": "Synthetics Global Variable Write", - "FullyQualifiedName": "Synthetics Global Variable Write", - "Type": "Synthetics", - "Metadata": { "Resource": "Synthetics" }, - "Parent": { - "Name": "Truffle Sec", - "FullyQualifiedName": "", - "Type": "user", - "Metadata": { "email": "detectors@trufflesec.com" }, - "Parent": null - } + "Name": "Truffle Sec", + "FullyQualifiedName": "a4b3b545-24ec-11f0-9f57-22795724d251", + "Type": "User", + "Metadata": { + "email": "detectors@trufflesec.com" + }, + "Parent": null }, "Permission": { - "Value": "synthetics_global_variable_write", + "Value": "Logs Write Pipelines", "Parent": null - } + }, + "Condition": "" }, { "Resource": { - "Name": "Synthetics Read", - "FullyQualifiedName": "Synthetics Read", - "Type": "Synthetics", - "Metadata": { "Resource": "Synthetics" }, - "Parent": { - "Name": "Truffle Sec", - "FullyQualifiedName": "", - "Type": "user", - "Metadata": { "email": "detectors@trufflesec.com" }, - "Parent": null - } + "Name": "Truffle Sec", + "FullyQualifiedName": "a4b3b545-24ec-11f0-9f57-22795724d251", + "Type": "User", + "Metadata": { + "email": "detectors@trufflesec.com" + }, + "Parent": null + }, + "Permission": { + "Value": "Manage Downtimes", + "Parent": null }, - "Permission": { "Value": "synthetics_read", "Parent": null } + "Condition": "" }, { "Resource": { - "Name": "Synthetics Write", - "FullyQualifiedName": "Synthetics Write", - "Type": "Synthetics", - "Metadata": { "Resource": "Synthetics" }, - "Parent": { - "Name": "Truffle Sec", - "FullyQualifiedName": "", - "Type": "user", - "Metadata": { "email": "detectors@trufflesec.com" }, - "Parent": null - } + "Name": "Truffle Sec", + "FullyQualifiedName": "a4b3b545-24ec-11f0-9f57-22795724d251", + "Type": "User", + "Metadata": { + "email": "detectors@trufflesec.com" + }, + "Parent": null + }, + "Permission": { + "Value": "Metric Tags Write", + "Parent": null }, - "Permission": { "Value": "synthetics_write", "Parent": null } + "Condition": "" }, { "Resource": { - "Name": "Synthetics Default Settings Read", - "FullyQualifiedName": "Synthetics Default Settings Read", - "Type": "Synthetics", - "Metadata": { "Resource": "Synthetics" }, - "Parent": { - "Name": "Truffle Sec", - "FullyQualifiedName": "", - "Type": "user", - "Metadata": { "email": "detectors@trufflesec.com" }, - "Parent": null - } + "Name": "Truffle Sec", + "FullyQualifiedName": "a4b3b545-24ec-11f0-9f57-22795724d251", + "Type": "User", + "Metadata": { + "email": "detectors@trufflesec.com" + }, + "Parent": null }, "Permission": { - "Value": "synthetics_default_settings_read", + "Value": "Monitor Configuration Policy Write", "Parent": null - } + }, + "Condition": "" }, { "Resource": { - "Name": "Service Account Write", - "FullyQualifiedName": "Service Account Write", - "Type": "Service Accounts", - "Metadata": { "Resource": "Service Accounts" }, - "Parent": { - "Name": "Truffle Sec", - "FullyQualifiedName": "", - "Type": "user", - "Metadata": { "email": "detectors@trufflesec.com" }, - "Parent": null - } + "Name": "Truffle Sec", + "FullyQualifiedName": "a4b3b545-24ec-11f0-9f57-22795724d251", + "Type": "User", + "Metadata": { + "email": "detectors@trufflesec.com" + }, + "Parent": null }, - "Permission": { "Value": "service_account_write", "Parent": null } + "Permission": { + "Value": "Monitors Read", + "Parent": null + }, + "Condition": "" }, { "Resource": { - "Name": "APM Read", - "FullyQualifiedName": "APM Read", - "Type": "APM", - "Metadata": { "Resource": "APM" }, - "Parent": { - "Name": "Truffle Sec", - "FullyQualifiedName": "", - "Type": "user", - "Metadata": { "email": "detectors@trufflesec.com" }, - "Parent": null - } + "Name": "Truffle Sec", + "FullyQualifiedName": "a4b3b545-24ec-11f0-9f57-22795724d251", + "Type": "User", + "Metadata": { + "email": "detectors@trufflesec.com" + }, + "Parent": null }, - "Permission": { "Value": "apm_read", "Parent": null } + "Permission": { + "Value": "Monitors Write", + "Parent": null + }, + "Condition": "" }, { "Resource": { - "Name": "APM Retention Filters Read", - "FullyQualifiedName": "APM Retention Filters Read", - "Type": "APM", - "Metadata": { "Resource": "APM" }, - "Parent": { - "Name": "Truffle Sec", - "FullyQualifiedName": "", - "Type": "user", - "Metadata": { "email": "detectors@trufflesec.com" }, - "Parent": null - } + "Name": "Truffle Sec", + "FullyQualifiedName": "a4b3b545-24ec-11f0-9f57-22795724d251", + "Type": "User", + "Metadata": { + "email": "detectors@trufflesec.com" + }, + "Parent": null }, - "Permission": { "Value": "apm_retention_filter_read", "Parent": null } + "Permission": { + "Value": "Notebooks Read", + "Parent": null + }, + "Condition": "" }, { "Resource": { - "Name": "APM Retention Filters Write", - "FullyQualifiedName": "APM Retention Filters Write", - "Type": "APM", - "Metadata": { "Resource": "APM" }, - "Parent": { - "Name": "Truffle Sec", - "FullyQualifiedName": "", - "Type": "user", - "Metadata": { "email": "detectors@trufflesec.com" }, - "Parent": null - } + "Name": "Truffle Sec", + "FullyQualifiedName": "a4b3b545-24ec-11f0-9f57-22795724d251", + "Type": "User", + "Metadata": { + "email": "detectors@trufflesec.com" + }, + "Parent": null }, - "Permission": { "Value": "apm_retention_filter_write", "Parent": null } + "Permission": { + "Value": "Notebooks Write", + "Parent": null + }, + "Condition": "" }, { "Resource": { - "Name": "RUM Apps Write", - "FullyQualifiedName": "RUM Apps Write", - "Type": "RUM", - "Metadata": { "Resource": "RUM" }, - "Parent": { - "Name": "Truffle Sec", - "FullyQualifiedName": "", - "Type": "user", - "Metadata": { "email": "detectors@trufflesec.com" }, - "Parent": null - } + "Name": "Truffle Sec", + "FullyQualifiedName": "a4b3b545-24ec-11f0-9f57-22795724d251", + "Type": "User", + "Metadata": { + "email": "detectors@trufflesec.com" + }, + "Parent": null }, - "Permission": { "Value": "rum_apps_write", "Parent": null } + "Permission": { + "Value": "Org App Keys Read", + "Parent": null + }, + "Condition": "" }, { "Resource": { - "Name": "Data Scanner Read", - "FullyQualifiedName": "Data Scanner Read", - "Type": "Sensitive Data Scanner", - "Metadata": { "Resource": "Sensitive Data Scanner" }, - "Parent": { - "Name": "Truffle Sec", - "FullyQualifiedName": "", - "Type": "user", - "Metadata": { "email": "detectors@trufflesec.com" }, - "Parent": null - } + "Name": "Truffle Sec", + "FullyQualifiedName": "a4b3b545-24ec-11f0-9f57-22795724d251", + "Type": "User", + "Metadata": { + "email": "detectors@trufflesec.com" + }, + "Parent": null + }, + "Permission": { + "Value": "Org App Keys Write", + "Parent": null }, - "Permission": { "Value": "data_scanner_read", "Parent": null } + "Condition": "" }, { "Resource": { - "Name": "Data Scanner Write", - "FullyQualifiedName": "Data Scanner Write", - "Type": "Sensitive Data Scanner", - "Metadata": { "Resource": "Sensitive Data Scanner" }, - "Parent": { - "Name": "Truffle Sec", - "FullyQualifiedName": "", - "Type": "user", - "Metadata": { "email": "detectors@trufflesec.com" }, - "Parent": null - } + "Name": "Truffle Sec", + "FullyQualifiedName": "a4b3b545-24ec-11f0-9f57-22795724d251", + "Type": "User", + "Metadata": { + "email": "detectors@trufflesec.com" + }, + "Parent": null }, - "Permission": { "Value": "data_scanner_write", "Parent": null } + "Permission": { + "Value": "Org Management", + "Parent": null + }, + "Condition": "" }, { "Resource": { - "Name": "Org Management", - "FullyQualifiedName": "Org Management", - "Type": "Organizations", - "Metadata": { "Resource": "Organizations" }, - "Parent": { - "Name": "Truffle Sec", - "FullyQualifiedName": "", - "Type": "user", - "Metadata": { "email": "detectors@trufflesec.com" }, - "Parent": null - } + "Name": "Truffle Sec", + "FullyQualifiedName": "a4b3b545-24ec-11f0-9f57-22795724d251", + "Type": "User", + "Metadata": { + "email": "detectors@trufflesec.com" + }, + "Parent": null + }, + "Permission": { + "Value": "RUM Apps Read", + "Parent": null }, - "Permission": { "Value": "org_management", "Parent": null } + "Condition": "" }, { "Resource": { - "Name": "Security Filters Read", - "FullyQualifiedName": "Security Filters Read", - "Type": "Security Monitoring", - "Metadata": { "Resource": "Security Monitoring" }, - "Parent": { - "Name": "Truffle Sec", - "FullyQualifiedName": "", - "Type": "user", - "Metadata": { "email": "detectors@trufflesec.com" }, - "Parent": null - } + "Name": "Truffle Sec", + "FullyQualifiedName": "a4b3b545-24ec-11f0-9f57-22795724d251", + "Type": "User", + "Metadata": { + "email": "detectors@trufflesec.com" + }, + "Parent": null }, "Permission": { - "Value": "security_monitoring_filters_read", + "Value": "RUM Apps Write", "Parent": null - } + }, + "Condition": "" }, { "Resource": { - "Name": "Security Filters Write", - "FullyQualifiedName": "Security Filters Write", - "Type": "Security Monitoring", - "Metadata": { "Resource": "Security Monitoring" }, - "Parent": { - "Name": "Truffle Sec", - "FullyQualifiedName": "", - "Type": "user", - "Metadata": { "email": "detectors@trufflesec.com" }, - "Parent": null - } + "Name": "Truffle Sec", + "FullyQualifiedName": "a4b3b545-24ec-11f0-9f57-22795724d251", + "Type": "User", + "Metadata": { + "email": "detectors@trufflesec.com" + }, + "Parent": null }, "Permission": { - "Value": "security_monitoring_filters_write", + "Value": "SLOs Read", "Parent": null - } + }, + "Condition": "" }, { "Resource": { - "Name": "Incidents Read", - "FullyQualifiedName": "Incidents Read", - "Type": "Incidents", - "Metadata": { "Resource": "Incidents" }, - "Parent": { - "Name": "Truffle Sec", - "FullyQualifiedName": "", - "Type": "user", - "Metadata": { "email": "detectors@trufflesec.com" }, - "Parent": null - } + "Name": "Truffle Sec", + "FullyQualifiedName": "a4b3b545-24ec-11f0-9f57-22795724d251", + "Type": "User", + "Metadata": { + "email": "detectors@trufflesec.com" + }, + "Parent": null + }, + "Permission": { + "Value": "SLOs Status Corrections", + "Parent": null }, - "Permission": { "Value": "incident_read", "Parent": null } + "Condition": "" }, { "Resource": { - "Name": "Incidents Write", - "FullyQualifiedName": "Incidents Write", - "Type": "Incidents", - "Metadata": { "Resource": "Incidents" }, - "Parent": { - "Name": "Truffle Sec", - "FullyQualifiedName": "", - "Type": "user", - "Metadata": { "email": "detectors@trufflesec.com" }, - "Parent": null - } + "Name": "Truffle Sec", + "FullyQualifiedName": "a4b3b545-24ec-11f0-9f57-22795724d251", + "Type": "User", + "Metadata": { + "email": "detectors@trufflesec.com" + }, + "Parent": null + }, + "Permission": { + "Value": "SLOs Write", + "Parent": null }, - "Permission": { "Value": "incident_write", "Parent": null } + "Condition": "" }, { "Resource": { - "Name": "Incident Settings Write", - "FullyQualifiedName": "Incident Settings Write", - "Type": "Incidents", - "Metadata": { "Resource": "Incidents" }, - "Parent": { - "Name": "Truffle Sec", - "FullyQualifiedName": "", - "Type": "user", - "Metadata": { "email": "detectors@trufflesec.com" }, - "Parent": null - } + "Name": "Truffle Sec", + "FullyQualifiedName": "a4b3b545-24ec-11f0-9f57-22795724d251", + "Type": "User", + "Metadata": { + "email": "detectors@trufflesec.com" + }, + "Parent": null + }, + "Permission": { + "Value": "Security Filters Read", + "Parent": null }, - "Permission": { "Value": "incident_settings_write", "Parent": null } + "Condition": "" }, { "Resource": { - "Name": "RUM Apps Read", - "FullyQualifiedName": "RUM Apps Read", - "Type": "RUM", - "Metadata": { "Resource": "RUM" }, - "Parent": { - "Name": "Truffle Sec", - "FullyQualifiedName": "", - "Type": "user", - "Metadata": { "email": "detectors@trufflesec.com" }, - "Parent": null - } + "Name": "Truffle Sec", + "FullyQualifiedName": "a4b3b545-24ec-11f0-9f57-22795724d251", + "Type": "User", + "Metadata": { + "email": "detectors@trufflesec.com" + }, + "Parent": null + }, + "Permission": { + "Value": "Security Filters Write", + "Parent": null }, - "Permission": { "Value": "rum_apps_read", "Parent": null } + "Condition": "" }, { "Resource": { - "Name": "APM Generate Metrics", - "FullyQualifiedName": "APM Generate Metrics", - "Type": "APM", - "Metadata": { "Resource": "APM" }, - "Parent": { - "Name": "Truffle Sec", - "FullyQualifiedName": "", - "Type": "user", - "Metadata": { "email": "detectors@trufflesec.com" }, - "Parent": null - } + "Name": "Truffle Sec", + "FullyQualifiedName": "a4b3b545-24ec-11f0-9f57-22795724d251", + "Type": "User", + "Metadata": { + "email": "detectors@trufflesec.com" + }, + "Parent": null + }, + "Permission": { + "Value": "Security Rules Read", + "Parent": null }, - "Permission": { "Value": "apm_generate_metrics", "Parent": null } + "Condition": "" }, { "Resource": { - "Name": "APM Pipelines Write", - "FullyQualifiedName": "APM Pipelines Write", - "Type": "APM", - "Metadata": { "Resource": "APM" }, - "Parent": { - "Name": "Truffle Sec", - "FullyQualifiedName": "", - "Type": "user", - "Metadata": { "email": "detectors@trufflesec.com" }, - "Parent": null - } + "Name": "Truffle Sec", + "FullyQualifiedName": "a4b3b545-24ec-11f0-9f57-22795724d251", + "Type": "User", + "Metadata": { + "email": "detectors@trufflesec.com" + }, + "Parent": null + }, + "Permission": { + "Value": "Security Signals Read", + "Parent": null }, - "Permission": { "Value": "apm_pipelines_write", "Parent": null } + "Condition": "" }, { "Resource": { - "Name": "APM Pipelines Read", - "FullyQualifiedName": "APM Pipelines Read", - "Type": "APM", - "Metadata": { "Resource": "APM" }, - "Parent": { - "Name": "Truffle Sec", - "FullyQualifiedName": "", - "Type": "user", - "Metadata": { "email": "detectors@trufflesec.com" }, - "Parent": null - } + "Name": "Truffle Sec", + "FullyQualifiedName": "a4b3b545-24ec-11f0-9f57-22795724d251", + "Type": "User", + "Metadata": { + "email": "detectors@trufflesec.com" + }, + "Parent": null + }, + "Permission": { + "Value": "Security Signals Write", + "Parent": null }, - "Permission": { "Value": "apm_pipelines_read", "Parent": null } + "Condition": "" }, { "Resource": { - "Name": "Workflows Read", - "FullyQualifiedName": "Workflows Read", - "Type": "Workflows", - "Metadata": { "Resource": "Workflows" }, - "Parent": { - "Name": "Truffle Sec", - "FullyQualifiedName": "", - "Type": "user", - "Metadata": { "email": "detectors@trufflesec.com" }, - "Parent": null - } + "Name": "Truffle Sec", + "FullyQualifiedName": "a4b3b545-24ec-11f0-9f57-22795724d251", + "Type": "User", + "Metadata": { + "email": "detectors@trufflesec.com" + }, + "Parent": null + }, + "Permission": { + "Value": "Service Account Write", + "Parent": null }, - "Permission": { "Value": "workflows_read", "Parent": null } + "Condition": "" }, { "Resource": { - "Name": "Workflows Write", - "FullyQualifiedName": "Workflows Write", - "Type": "Workflows", - "Metadata": { "Resource": "Workflows" }, - "Parent": { - "Name": "Truffle Sec", - "FullyQualifiedName": "", - "Type": "user", - "Metadata": { "email": "detectors@trufflesec.com" }, - "Parent": null - } + "Name": "Truffle Sec", + "FullyQualifiedName": "a4b3b545-24ec-11f0-9f57-22795724d251", + "Type": "User", + "Metadata": { + "email": "detectors@trufflesec.com" + }, + "Parent": null + }, + "Permission": { + "Value": "Submit Events", + "Parent": null }, - "Permission": { "Value": "workflows_write", "Parent": null } + "Condition": "" }, { "Resource": { - "Name": "Connections Read", - "FullyQualifiedName": "Connections Read", - "Type": "Connections", - "Metadata": { "Resource": "Connections" }, - "Parent": { - "Name": "Truffle Sec", - "FullyQualifiedName": "", - "Type": "user", - "Metadata": { "email": "detectors@trufflesec.com" }, - "Parent": null - } + "Name": "Truffle Sec", + "FullyQualifiedName": "a4b3b545-24ec-11f0-9f57-22795724d251", + "Type": "User", + "Metadata": { + "email": "detectors@trufflesec.com" + }, + "Parent": null }, - "Permission": { "Value": "connections_read", "Parent": null } + "Permission": { + "Value": "Submit Logs", + "Parent": null + }, + "Condition": "" }, { "Resource": { - "Name": "Connections Write", - "FullyQualifiedName": "Connections Write", - "Type": "Connections", - "Metadata": { "Resource": "Connections" }, - "Parent": { - "Name": "Truffle Sec", - "FullyQualifiedName": "", - "Type": "user", - "Metadata": { "email": "detectors@trufflesec.com" }, - "Parent": null - } + "Name": "Truffle Sec", + "FullyQualifiedName": "a4b3b545-24ec-11f0-9f57-22795724d251", + "Type": "User", + "Metadata": { + "email": "detectors@trufflesec.com" + }, + "Parent": null + }, + "Permission": { + "Value": "Submit Metrics", + "Parent": null }, - "Permission": { "Value": "connections_write", "Parent": null } + "Condition": "" }, { "Resource": { - "Name": "Notebooks Read", - "FullyQualifiedName": "Notebooks Read", - "Type": "Notebooks", - "Metadata": { "Resource": "Notebooks" }, - "Parent": { - "Name": "Truffle Sec", - "FullyQualifiedName": "", - "Type": "user", - "Metadata": { "email": "detectors@trufflesec.com" }, - "Parent": null - } + "Name": "Truffle Sec", + "FullyQualifiedName": "a4b3b545-24ec-11f0-9f57-22795724d251", + "Type": "User", + "Metadata": { + "email": "detectors@trufflesec.com" + }, + "Parent": null + }, + "Permission": { + "Value": "Submit Service Checks", + "Parent": null }, - "Permission": { "Value": "notebooks_read", "Parent": null } + "Condition": "" }, { "Resource": { - "Name": "Notebooks Write", - "FullyQualifiedName": "Notebooks Write", - "Type": "Notebooks", - "Metadata": { "Resource": "Notebooks" }, - "Parent": { - "Name": "Truffle Sec", - "FullyQualifiedName": "", - "Type": "user", - "Metadata": { "email": "detectors@trufflesec.com" }, - "Parent": null - } + "Name": "Truffle Sec", + "FullyQualifiedName": "a4b3b545-24ec-11f0-9f57-22795724d251", + "Type": "User", + "Metadata": { + "email": "detectors@trufflesec.com" + }, + "Parent": null + }, + "Permission": { + "Value": "Synthetics Default Settings Read", + "Parent": null }, - "Permission": { "Value": "notebooks_write", "Parent": null } + "Condition": "" }, { "Resource": { - "Name": "AWS Configurations Manage", - "FullyQualifiedName": "AWS Configurations Manage", - "Type": "Integrations", - "Metadata": { "Resource": "Integrations" }, - "Parent": { - "Name": "Truffle Sec", - "FullyQualifiedName": "", - "Type": "user", - "Metadata": { "email": "detectors@trufflesec.com" }, - "Parent": null - } + "Name": "Truffle Sec", + "FullyQualifiedName": "a4b3b545-24ec-11f0-9f57-22795724d251", + "Type": "User", + "Metadata": { + "email": "detectors@trufflesec.com" + }, + "Parent": null + }, + "Permission": { + "Value": "Synthetics Global Variable Read", + "Parent": null }, - "Permission": { "Value": "aws_configurations_manage", "Parent": null } + "Condition": "" }, { "Resource": { - "Name": "Azure Configurations Manage", - "FullyQualifiedName": "Azure Configurations Manage", - "Type": "Integrations", - "Metadata": { "Resource": "Integrations" }, - "Parent": { - "Name": "Truffle Sec", - "FullyQualifiedName": "", - "Type": "user", - "Metadata": { "email": "detectors@trufflesec.com" }, - "Parent": null - } + "Name": "Truffle Sec", + "FullyQualifiedName": "a4b3b545-24ec-11f0-9f57-22795724d251", + "Type": "User", + "Metadata": { + "email": "detectors@trufflesec.com" + }, + "Parent": null + }, + "Permission": { + "Value": "Synthetics Global Variable Write", + "Parent": null }, - "Permission": { "Value": "azure_configurations_manage", "Parent": null } + "Condition": "" }, { "Resource": { - "Name": "GCP Configurations Manage", - "FullyQualifiedName": "GCP Configurations Manage", - "Type": "Integrations", - "Metadata": { "Resource": "Integrations" }, - "Parent": { - "Name": "Truffle Sec", - "FullyQualifiedName": "", - "Type": "user", - "Metadata": { "email": "detectors@trufflesec.com" }, - "Parent": null - } + "Name": "Truffle Sec", + "FullyQualifiedName": "a4b3b545-24ec-11f0-9f57-22795724d251", + "Type": "User", + "Metadata": { + "email": "detectors@trufflesec.com" + }, + "Parent": null + }, + "Permission": { + "Value": "Synthetics Private Locations Read", + "Parent": null }, - "Permission": { "Value": "gcp_configurations_manage", "Parent": null } + "Condition": "" }, { "Resource": { - "Name": "Integrations Manage", - "FullyQualifiedName": "Integrations Manage", - "Type": "Integrations", - "Metadata": { "Resource": "Integrations" }, - "Parent": { - "Name": "Truffle Sec", - "FullyQualifiedName": "", - "Type": "user", - "Metadata": { "email": "detectors@trufflesec.com" }, - "Parent": null - } + "Name": "Truffle Sec", + "FullyQualifiedName": "a4b3b545-24ec-11f0-9f57-22795724d251", + "Type": "User", + "Metadata": { + "email": "detectors@trufflesec.com" + }, + "Parent": null + }, + "Permission": { + "Value": "Synthetics Private Locations Write", + "Parent": null }, - "Permission": { "Value": "manage_integrations", "Parent": null } + "Condition": "" }, { "Resource": { - "Name": "SLOs Read", - "FullyQualifiedName": "SLOs Read", - "Type": "SLOs", - "Metadata": { "Resource": "SLOs" }, - "Parent": { - "Name": "Truffle Sec", - "FullyQualifiedName": "", - "Type": "user", - "Metadata": { "email": "detectors@trufflesec.com" }, - "Parent": null - } + "Name": "Truffle Sec", + "FullyQualifiedName": "a4b3b545-24ec-11f0-9f57-22795724d251", + "Type": "User", + "Metadata": { + "email": "detectors@trufflesec.com" + }, + "Parent": null + }, + "Permission": { + "Value": "Synthetics Read", + "Parent": null }, - "Permission": { "Value": "slos_read", "Parent": null } + "Condition": "" }, { "Resource": { - "Name": "SLOs Write", - "FullyQualifiedName": "SLOs Write", - "Type": "SLOs", - "Metadata": { "Resource": "SLOs" }, - "Parent": { - "Name": "Truffle Sec", - "FullyQualifiedName": "", - "Type": "user", - "Metadata": { "email": "detectors@trufflesec.com" }, - "Parent": null - } + "Name": "Truffle Sec", + "FullyQualifiedName": "a4b3b545-24ec-11f0-9f57-22795724d251", + "Type": "User", + "Metadata": { + "email": "detectors@trufflesec.com" + }, + "Parent": null + }, + "Permission": { + "Value": "Synthetics Write", + "Parent": null }, - "Permission": { "Value": "slos_write", "Parent": null } + "Condition": "" }, { "Resource": { - "Name": "SLOs Status Corrections", - "FullyQualifiedName": "SLOs Status Corrections", - "Type": "SLOs", - "Metadata": { "Resource": "SLOs" }, - "Parent": { - "Name": "Truffle Sec", - "FullyQualifiedName": "", - "Type": "user", - "Metadata": { "email": "detectors@trufflesec.com" }, - "Parent": null - } + "Name": "Truffle Sec", + "FullyQualifiedName": "a4b3b545-24ec-11f0-9f57-22795724d251", + "Type": "User", + "Metadata": { + "email": "detectors@trufflesec.com" + }, + "Parent": null + }, + "Permission": { + "Value": "Usage Read", + "Parent": null + }, + "Condition": "" + }, + { + "Resource": { + "Name": "Truffle Sec", + "FullyQualifiedName": "a4b3b545-24ec-11f0-9f57-22795724d251", + "Type": "User", + "Metadata": { + "email": "detectors@trufflesec.com" + }, + "Parent": null + }, + "Permission": { + "Value": "User Access Invite", + "Parent": null + }, + "Condition": "" + }, + { + "Resource": { + "Name": "Truffle Sec", + "FullyQualifiedName": "a4b3b545-24ec-11f0-9f57-22795724d251", + "Type": "User", + "Metadata": { + "email": "detectors@trufflesec.com" + }, + "Parent": null + }, + "Permission": { + "Value": "User Access Manage", + "Parent": null + }, + "Condition": "" + }, + { + "Resource": { + "Name": "Truffle Sec", + "FullyQualifiedName": "a4b3b545-24ec-11f0-9f57-22795724d251", + "Type": "User", + "Metadata": { + "email": "detectors@trufflesec.com" + }, + "Parent": null + }, + "Permission": { + "Value": "User App Keys", + "Parent": null + }, + "Condition": "" + }, + { + "Resource": { + "Name": "Truffle Sec", + "FullyQualifiedName": "a4b3b545-24ec-11f0-9f57-22795724d251", + "Type": "User", + "Metadata": { + "email": "detectors@trufflesec.com" + }, + "Parent": null + }, + "Permission": { + "Value": "Workflows Read", + "Parent": null + }, + "Condition": "" + }, + { + "Resource": { + "Name": "Truffle Sec", + "FullyQualifiedName": "a4b3b545-24ec-11f0-9f57-22795724d251", + "Type": "User", + "Metadata": { + "email": "detectors@trufflesec.com" + }, + "Parent": null + }, + "Permission": { + "Value": "Workflows Write", + "Parent": null }, - "Permission": { "Value": "slos_corrections", "Parent": null } + "Condition": "" }, { "Resource": { - "Name": "Monitor Configuration Policy Write", - "FullyQualifiedName": "Monitor Configuration Policy Write", - "Type": "Monitors", - "Metadata": { "Resource": "Monitors" }, + "Name": "Truffle's Dashboard", + "FullyQualifiedName": "fvx-idw-ani", + "Type": "Dashboard", + "Metadata": { + "Author Handle": "detectors@trufflesec.com", + "Layout Type": "ordered", + "URL": "/dashboard/fvx-idw-ani/truffles-dashboard" + }, "Parent": { "Name": "Truffle Sec", - "FullyQualifiedName": "", - "Type": "user", - "Metadata": { "email": "detectors@trufflesec.com" }, + "FullyQualifiedName": "a4b3b545-24ec-11f0-9f57-22795724d251", + "Type": "User", + "Metadata": { + "email": "detectors@trufflesec.com" + }, "Parent": null } }, - "Permission": { "Value": "monitor_config_policy_write", "Parent": null } + "Permission": { + "Value": "", + "Parent": null + }, + "Condition": "" } ], "UnboundedResources": null, "Metadata": {} -} +} \ No newline at end of file diff --git a/pkg/analyzer/analyzers/datadog/expected_output_apikey.json b/pkg/analyzer/analyzers/datadog/expected_output_apikey.json new file mode 100644 index 000000000000..a4a76d7c056c --- /dev/null +++ b/pkg/analyzer/analyzers/datadog/expected_output_apikey.json @@ -0,0 +1,63 @@ +{ + "AnalyzerType": 37, + "Bindings": [ + { + "Resource": { + "Name": "Unknown User", + "FullyQualifiedName": "Unknown User", + "Type": "User", + "Metadata": {}, + "Parent": null + }, + "Permission": { + "Value": "Submit Events", + "Parent": null + }, + "Condition": "" + }, + { + "Resource": { + "Name": "Unknown User", + "FullyQualifiedName": "Unknown User", + "Type": "User", + "Metadata": {}, + "Parent": null + }, + "Permission": { + "Value": "Submit Logs", + "Parent": null + }, + "Condition": "" + }, + { + "Resource": { + "Name": "Unknown User", + "FullyQualifiedName": "Unknown User", + "Type": "User", + "Metadata": {}, + "Parent": null + }, + "Permission": { + "Value": "Submit Metrics", + "Parent": null + }, + "Condition": "" + }, + { + "Resource": { + "Name": "Unknown User", + "FullyQualifiedName": "Unknown User", + "Type": "User", + "Metadata": {}, + "Parent": null + }, + "Permission": { + "Value": "Submit Service Checks", + "Parent": null + }, + "Condition": "" + } + ], + "UnboundedResources": null, + "Metadata": {} +} \ No newline at end of file diff --git a/pkg/analyzer/analyzers/datadog/requests.go b/pkg/analyzer/analyzers/datadog/requests.go index 6904a2910d9e..553c13be1ca3 100644 --- a/pkg/analyzer/analyzers/datadog/requests.go +++ b/pkg/analyzer/analyzers/datadog/requests.go @@ -172,6 +172,44 @@ func (h *HttpStatusTest) RunTest(client *http.Client, baseURL string, headers ma } } +// -------------------------------- +// Validate ApiKey +// -------------------------------- +func ValidateApiKey(client *http.Client, baseURL, apiKey string) (bool, error) { + // Use a simple endpoint to test if the domain works + endpoint := baseURL + endpoints[ResourceTypeValidate] + + ctx, cancel := context.WithTimeout(context.Background(), defaultTimeout) + defer cancel() + + // Create request + req, err := http.NewRequestWithContext(ctx, "GET", endpoint, http.NoBody) + if err != nil { + return false, err + } + + // Add required keys in the header + req.Header.Set(apiKeyHeader, apiKey) + + resp, err := client.Do(req) + + if err != nil { + return false, err + } + + defer func() { + _, _ = io.Copy(io.Discard, resp.Body) + _ = resp.Body.Close() + }() + + // If we get a response that's not a connection error, this domain works + if resp.StatusCode == http.StatusOK { + return true, nil + } + + return false, errors.New("unable to validate any provided DataDog API key") +} + // -------------------------------- // Data capture functions // -------------------------------- @@ -238,16 +276,44 @@ func CapturePermissions(client *http.Client, baseURL, apiKey, appKey string, sec } for _, scope := range scopes { - status, err := scope.HttpTest.RunTest(client, baseURL, headers) - if err != nil { - return fmt.Errorf("running test for scope %s: %w", scope.Name, err) + if scope.HttpTest.Endpoint != "" { + status, err := scope.HttpTest.RunTest(client, baseURL, headers) + if err != nil { + return fmt.Errorf("running test for scope %s: %w", scope.Name, err) + } + + metadata := map[string]string{ + "Resource": scope.Resource, + } + + if status { + permission := Permission{ + Name: scope.Name, + Title: scope.Title, + Description: scope.Description, + MetaData: metadata, + } + permissions = append(permissions, permission) + } } + } + + secretInfo.Permissions = permissions + return nil +} +// API key is not finely grained, so we assign some default permissions +func CaptureApiKeyPermissions(client *http.Client, baseURL, apiKey, appKey string, secretInfo *SecretInfo) error { + scopes, err := readInScopes() + if err != nil { + return fmt.Errorf("reading in scopes: %w", err) + } + permissions := make([]Permission, 0) + for _, scope := range scopes { metadata := map[string]string{ "Resource": scope.Resource, } - - if status { + if scope.HttpTest.Endpoint == "" { permission := Permission{ Name: scope.Name, Title: scope.Title, @@ -257,8 +323,7 @@ func CapturePermissions(client *http.Client, baseURL, apiKey, appKey string, sec permissions = append(permissions, permission) } } - - secretInfo.Permissions = permissions + secretInfo.Permissions = append(secretInfo.Permissions, permissions...) return nil } diff --git a/pkg/analyzer/analyzers/datadog/scopes.json b/pkg/analyzer/analyzers/datadog/scopes.json index 632606988a3d..251a4eb55651 100644 --- a/pkg/analyzer/analyzers/datadog/scopes.json +++ b/pkg/analyzer/analyzers/datadog/scopes.json @@ -814,5 +814,29 @@ "valid_statuses": [400, 404, 429], "invalid_statuses": [403] } + }, + { + "name": "metrics_write", + "title": "Submit Metrics", + "description": "Submit custom metrics to Datadog.", + "resource": "Metrics" + }, + { + "name": "logs_write", + "title": "Submit Logs", + "description": "Send logs to Datadog for indexing and processing.", + "resource": "Logs" + }, + { + "name": "events_write", + "title": "Submit Events", + "description": "Post events to the Datadog event stream.", + "resource": "Events" + }, + { + "name": "service_checks_write", + "title": "Submit Service Checks", + "description": "Send service check statuses to Datadog.", + "resource": "Service Checks" } ] diff --git a/pkg/analyzer/cli.go b/pkg/analyzer/cli.go index f4e626f701d6..78dc4976d349 100644 --- a/pkg/analyzer/cli.go +++ b/pkg/analyzer/cli.go @@ -138,7 +138,7 @@ func Run(keyType string, secretInfo SecretInfo) { case "monday": monday.AnalyzeAndPrintPermissions(secretInfo.Cfg, secretInfo.Parts["key"]) case "datadog": - datadog.AnalyzeAndPrintPermissions(secretInfo.Cfg, secretInfo.Parts["apiKey"], secretInfo.Parts["appKey"]) + datadog.AnalyzeAndPrintPermissions(secretInfo.Cfg, secretInfo.Parts["apiKey"], secretInfo.Parts["appKey"], secretInfo.Parts["endpoint"]) case "ngrok": ngrok.AnalyzeAndPrintPermissions(secretInfo.Cfg, secretInfo.Parts["key"]) case "mux": diff --git a/pkg/detectors/datadogapikey/datadogapikey.go b/pkg/detectors/datadogapikey/datadogapikey.go new file mode 100644 index 000000000000..fc3392c0a05a --- /dev/null +++ b/pkg/detectors/datadogapikey/datadogapikey.go @@ -0,0 +1,127 @@ +package datadogapikey + +import ( + "context" + "fmt" + "io" + "net/http" + "strings" + + "github.com/trufflesecurity/trufflehog/v3/pkg/common" + "github.com/trufflesecurity/trufflehog/v3/pkg/detectors" + "github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb" + regexp "github.com/wasilibs/go-re2" +) + +type Scanner struct { + client *http.Client + detectors.EndpointSetter + detectors.DefaultMultiPartCredentialProvider +} + +// Ensure the Scanner satisfies the interface at compile time. +var _ detectors.Detector = (*Scanner)(nil) +var _ detectors.EndpointCustomizer = (*Scanner)(nil) +var _ detectors.CloudProvider = (*Scanner)(nil) + +func (Scanner) CloudEndpoint() string { return "https://api.datadoghq.com" } + +var ( + client = common.SaneHttpClient() + + // Make sure that your group is surrounded in boundary characters such as below to reduce false positives. + apiKeyPat = regexp.MustCompile(detectors.PrefixRegex([]string{"datadog", "dd"}) + `\b([a-zA-Z-0-9]{32})\b`) + datadogURLPat = regexp.MustCompile(`\b(api(?:\.[a-z0-9-]+)?\.(?:datadoghq|ddog-gov)\.[a-z]{2,3})\b`) +) + +// Keywords are used for efficiently pre-filtering chunks. +// Use identifiers in the secret preferably, or the provider name. +func (s Scanner) Keywords() []string { + return []string{"datadog", "ddog-gov"} +} + +func (s Scanner) getClient() *http.Client { + if s.client != nil { + return s.client + } + return client +} + +// FromData will find and optionally verify DatadogToken secrets in a given set of bytes. +func (s Scanner) FromData(ctx context.Context, verify bool, data []byte) (results []detectors.Result, err error) { + dataStr := string(data) + + apiMatches := apiKeyPat.FindAllStringSubmatch(dataStr, -1) + var uniqueFoundUrls = make(map[string]struct{}) + for _, matches := range datadogURLPat.FindAllStringSubmatch(dataStr, -1) { + uniqueFoundUrls[matches[1]] = struct{}{} + } + endpoints := make([]string, 0, len(uniqueFoundUrls)) + for endpoint := range uniqueFoundUrls { + endpoints = append(endpoints, "https://"+endpoint) + } + + for _, apiMatch := range apiMatches { + resApiMatch := strings.TrimSpace(apiMatch[1]) + s1 := detectors.Result{ + DetectorType: detectorspb.DetectorType_DatadogApikey, + Raw: []byte(resApiMatch), + } + + if verify { + for _, baseURL := range s.Endpoints(endpoints...) { + client := s.getClient() + isVerified, verificationErr := verifyMatch(ctx, client, resApiMatch, baseURL) + s1.Verified = isVerified + s1.SetVerificationError(verificationErr, resApiMatch) + if isVerified { + s1.AnalysisInfo = map[string]string{"apiKey": resApiMatch, "endpoint": baseURL} + s1.ExtraData = map[string]string{"endpoint": baseURL} + // break the loop once we've successfully validated the token against a baseURL + break + } + } + } + results = append(results, s1) + } + return results, nil +} + +func verifyMatch(ctx context.Context, client *http.Client, apiKey, baseUrl string) (bool, error) { + // Reference: https://docs.datadoghq.com/api/latest/authentication/ + + req, err := http.NewRequestWithContext(ctx, http.MethodGet, baseUrl+"/api/v1/validate", http.NoBody) + if err != nil { + return false, err + } + + req.Header.Add("DD-API-KEY", apiKey) + res, err := client.Do(req) + if err != nil { + return false, err + } + + defer func() { + _, _ = io.Copy(io.Discard, res.Body) + _ = res.Body.Close() + }() + + switch res.StatusCode { + case http.StatusOK: + return true, nil + case http.StatusForbidden: + return false, nil + case http.StatusTooManyRequests: + return false, fmt.Errorf("too many requests") + default: + return false, fmt.Errorf("unexpected status code: %d", res.StatusCode) + } +} + +func (s Scanner) Type() detectorspb.DetectorType { + return detectorspb.DetectorType_DatadogApikey +} + +func (s Scanner) Description() string { + return "Datadog is a monitoring and security platform for cloud applications. Datadog API and Application keys can be used to access and manage data and configurations within Datadog." +} diff --git a/pkg/detectors/datadogapikey/datadogapikey_integration_test.go b/pkg/detectors/datadogapikey/datadogapikey_integration_test.go new file mode 100644 index 000000000000..71387b585c41 --- /dev/null +++ b/pkg/detectors/datadogapikey/datadogapikey_integration_test.go @@ -0,0 +1,129 @@ +//go:build detectors +// +build detectors + +package datadogapikey + +import ( + "context" + "fmt" + "testing" + "time" + + "github.com/kylelemons/godebug/pretty" + "github.com/trufflesecurity/trufflehog/v3/pkg/detectors" + + "github.com/trufflesecurity/trufflehog/v3/pkg/common" + "github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb" +) + +func TestDataDogApiKey_FromChunk(t *testing.T) { + ctx, cancel := context.WithTimeout(context.Background(), time.Second*5) + defer cancel() + testSecrets, err := common.GetSecret(ctx, "trufflehog-testing", "detectors5") + if err != nil { + t.Fatalf("could not get test secrets from GCP: %s", err) + } + apiKey := testSecrets.MustGetField("DATADOGTOKEN_TOKEN") + invalidApiKey := "FKNwdbyfYTmGUm5DK3yHEuK-BBQf0fVG" + datdogEndpoint := "https://api.us5.datadoghq.com" + type args struct { + ctx context.Context + data []byte + verify bool + } + tests := []struct { + name string + s Scanner + args args + want []detectors.Result + wantErr bool + }{ + { + name: "found, verified", + s: Scanner{}, + args: args{ + ctx: context.Background(), + data: []byte(fmt.Sprintf("You can find a datadogtoken secret within datadog %s and endpoint is %v", apiKey, datdogEndpoint)), + verify: true, + }, + want: []detectors.Result{ + { + DetectorType: detectorspb.DetectorType_DatadogApikey, + Verified: true, + AnalysisInfo: map[string]string{ + "apiKey": apiKey, + "endpoint": datdogEndpoint, + }, + ExtraData: map[string]string{ + "endpoint": datdogEndpoint, + }, + Raw: []byte(apiKey), + }, + }, + wantErr: false, + }, + { + name: "found, unverified", + s: Scanner{}, + args: args{ + ctx: context.Background(), + data: []byte(fmt.Sprintf("You can find a datadogtoken secret within datadog %s and endpoint is %v", invalidApiKey, datdogEndpoint)), + verify: true, + }, + want: []detectors.Result{ + { + DetectorType: detectorspb.DetectorType_DatadogApikey, + Verified: false, + Raw: []byte(invalidApiKey), + }, + }, + wantErr: false, + }, + { + name: "not found", + s: Scanner{}, + args: args{ + ctx: context.Background(), + data: []byte("You cannot find the secret within"), + verify: true, + }, + want: nil, + wantErr: false, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + s := Scanner{} + + // use default cloud endpoint + s.UseCloudEndpoint(true) + s.SetCloudEndpoint(s.CloudEndpoint()) + s.UseFoundEndpoints(true) + + got, err := s.FromData(tt.args.ctx, tt.args.verify, tt.args.data) + if (err != nil) != tt.wantErr { + t.Errorf("DatadogToken.FromData() error = %v, wantErr %v", err, tt.wantErr) + return + } + if diff := pretty.Compare(got, tt.want); diff != "" { + t.Errorf("DatadogToken.FromData() %s diff: (-got +want)\n%s", tt.name, diff) + } + }) + } +} + +func BenchmarkFromData(benchmark *testing.B) { + ctx := context.Background() + s := Scanner{} + for name, data := range detectors.MustGetBenchmarkData() { + benchmark.Run(name, func(b *testing.B) { + b.ResetTimer() + for n := 0; n < b.N; n++ { + _, err := s.FromData(ctx, false, data) + if err != nil { + b.Fatal(err) + } + } + }) + } +} diff --git a/pkg/detectors/datadogapikey/datadogapikey_test.go b/pkg/detectors/datadogapikey/datadogapikey_test.go new file mode 100644 index 000000000000..0a76eb1829f7 --- /dev/null +++ b/pkg/detectors/datadogapikey/datadogapikey_test.go @@ -0,0 +1,94 @@ +package datadogapikey + +import ( + "context" + "testing" + + "github.com/google/go-cmp/cmp" + "github.com/google/go-cmp/cmp/cmpopts" + + "github.com/trufflesecurity/trufflehog/v3/pkg/detectors" + "github.com/trufflesecurity/trufflehog/v3/pkg/engine/ahocorasick" + "github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb" +) + +func TestDataDogApiKey_Pattern_WithValidAPIKey(t *testing.T) { + d := Scanner{} + ahoCorasickCore := ahocorasick.NewAhoCorasickCore([]detectors.Detector{d}) + + input := ` + dd_api_secret: "FKNwdbyfYTmGUm5DK3yHEuK-BBQf0fVG" + dd_app: "iHxNanzZ8vjrmbjXK7NJLrwpGw2czdSh90PKH6VL" + base_url1: "https://api.us5.datadoghq.com" + base_url2: "https://api.app.ddog-gov.com" + ` + apiKey := "FKNwdbyfYTmGUm5DK3yHEuK-BBQf0fVG" + wantedResult := []detectors.Result{ + { + DetectorType: detectorspb.DetectorType_DatadogApikey, + Raw: []byte(apiKey), + }, + } + matchedDetectors := ahoCorasickCore.FindDetectorMatches([]byte(input)) + if len(matchedDetectors) == 0 { + t.Errorf("keywords '%v' not matched by: %s", d.Keywords(), input) + return + } + results, err := d.FromData(context.Background(), false, []byte(input)) + if err != nil { + t.Errorf("error = %v", err) + return + } + + if diff := cmp.Diff(wantedResult, results, cmpopts.IgnoreFields(detectors.Result{}, "verificationError", "primarySecret")); diff != "" { + t.Errorf("%s diff: (-want +got)\n%s", "TestDataDogApiKey_Pattern_WithValidAPIKeyOnly", diff) + } +} + +func TestDataDogApiKey_NoSecrets(t *testing.T) { + d := Scanner{} + ahoCorasickCore := ahocorasick.NewAhoCorasickCore([]detectors.Detector{d}) + + input := ` + base_url1: "https://api.us5.datadoghq.com" + base_url2: "https://api.app.ddog-gov.com" + ` + matchedDetectors := ahoCorasickCore.FindDetectorMatches([]byte(input)) + if len(matchedDetectors) == 0 { + t.Errorf("keywords '%v' not matched by: %s", d.Keywords(), input) + return + } + results, err := d.FromData(context.Background(), false, []byte(input)) + if err != nil { + t.Errorf("error = %v", err) + return + } + if len(results) != 0 { + t.Errorf("expected 0 results, received %d", len(results)) + } +} + +func TestDataDogApiKey_InvalidSecrets(t *testing.T) { + d := Scanner{} + ahoCorasickCore := ahocorasick.NewAhoCorasickCore([]detectors.Detector{d}) + + input := ` + dd_api_secret: "@FKNwdbyfYTmGUm5DK3yHEuK" + dd_app: "iHxNanzZ8vjrmbjXK7NJLrwpGw2czdSh90PKH6VL" + base_url1: "https://api.us5.datadoghq.com" + base_url2: "https://api.app.ddog-gov.com" + ` + matchedDetectors := ahoCorasickCore.FindDetectorMatches([]byte(input)) + if len(matchedDetectors) == 0 { + t.Errorf("keywords '%v' not matched by: %s", d.Keywords(), input) + return + } + results, err := d.FromData(context.Background(), false, []byte(input)) + if err != nil { + t.Errorf("error = %v", err) + return + } + if len(results) != 0 { + t.Errorf("expected 0 results, received %d", len(results)) + } +} diff --git a/pkg/pb/detectorspb/detectors.pb.go b/pkg/pb/detectorspb/detectors.pb.go index d2ae6abe8ed9..4807a239e032 100644 --- a/pkg/pb/detectorspb/detectors.pb.go +++ b/pkg/pb/detectorspb/detectors.pb.go @@ -1146,6 +1146,7 @@ const ( DetectorType_PhraseAccessToken DetectorType = 1037 DetectorType_Photoroom DetectorType = 1038 DetectorType_JWT DetectorType = 1039 + DetectorType_DatadogApikey DetectorType = 1040 ) // Enum value maps for DetectorType. @@ -2187,6 +2188,7 @@ var ( 1037: "PhraseAccessToken", 1038: "Photoroom", 1039: "JWT", + 1040: "DatadogApikey", } DetectorType_value = map[string]int32{ "Alibaba": 0, @@ -3225,6 +3227,7 @@ var ( "PhraseAccessToken": 1037, "Photoroom": 1038, "JWT": 1039, + "DatadogApikey": 1040, } ) @@ -3678,7 +3681,7 @@ var file_detectors_proto_rawDesc = []byte{ 0x4c, 0x41, 0x49, 0x4e, 0x10, 0x01, 0x12, 0x0a, 0x0a, 0x06, 0x42, 0x41, 0x53, 0x45, 0x36, 0x34, 0x10, 0x02, 0x12, 0x09, 0x0a, 0x05, 0x55, 0x54, 0x46, 0x31, 0x36, 0x10, 0x03, 0x12, 0x13, 0x0a, 0x0f, 0x45, 0x53, 0x43, 0x41, 0x50, 0x45, 0x44, 0x5f, 0x55, 0x4e, 0x49, 0x43, 0x4f, 0x44, 0x45, - 0x10, 0x04, 0x2a, 0xc5, 0x86, 0x01, 0x0a, 0x0c, 0x44, 0x65, 0x74, 0x65, 0x63, 0x74, 0x6f, 0x72, + 0x10, 0x04, 0x2a, 0xd9, 0x86, 0x01, 0x0a, 0x0c, 0x44, 0x65, 0x74, 0x65, 0x63, 0x74, 0x6f, 0x72, 0x54, 0x79, 0x70, 0x65, 0x12, 0x0b, 0x0a, 0x07, 0x41, 0x6c, 0x69, 0x62, 0x61, 0x62, 0x61, 0x10, 0x00, 0x12, 0x08, 0x0a, 0x04, 0x41, 0x4d, 0x51, 0x50, 0x10, 0x01, 0x12, 0x07, 0x0a, 0x03, 0x41, 0x57, 0x53, 0x10, 0x02, 0x12, 0x09, 0x0a, 0x05, 0x41, 0x7a, 0x75, 0x72, 0x65, 0x10, 0x03, 0x12, @@ -4754,12 +4757,13 @@ var file_detectors_proto_rawDesc = []byte{ 0x6c, 0x74, 0x41, 0x75, 0x74, 0x68, 0x10, 0x8c, 0x08, 0x12, 0x16, 0x0a, 0x11, 0x50, 0x68, 0x72, 0x61, 0x73, 0x65, 0x41, 0x63, 0x63, 0x65, 0x73, 0x73, 0x54, 0x6f, 0x6b, 0x65, 0x6e, 0x10, 0x8d, 0x08, 0x12, 0x0e, 0x0a, 0x09, 0x50, 0x68, 0x6f, 0x74, 0x6f, 0x72, 0x6f, 0x6f, 0x6d, 0x10, 0x8e, - 0x08, 0x12, 0x08, 0x0a, 0x03, 0x4a, 0x57, 0x54, 0x10, 0x8f, 0x08, 0x42, 0x3d, 0x5a, 0x3b, 0x67, - 0x69, 0x74, 0x68, 0x75, 0x62, 0x2e, 0x63, 0x6f, 0x6d, 0x2f, 0x74, 0x72, 0x75, 0x66, 0x66, 0x6c, - 0x65, 0x73, 0x65, 0x63, 0x75, 0x72, 0x69, 0x74, 0x79, 0x2f, 0x74, 0x72, 0x75, 0x66, 0x66, 0x6c, - 0x65, 0x68, 0x6f, 0x67, 0x2f, 0x76, 0x33, 0x2f, 0x70, 0x6b, 0x67, 0x2f, 0x70, 0x62, 0x2f, 0x64, - 0x65, 0x74, 0x65, 0x63, 0x74, 0x6f, 0x72, 0x73, 0x70, 0x62, 0x62, 0x06, 0x70, 0x72, 0x6f, 0x74, - 0x6f, 0x33, + 0x08, 0x12, 0x08, 0x0a, 0x03, 0x4a, 0x57, 0x54, 0x10, 0x8f, 0x08, 0x12, 0x12, 0x0a, 0x0d, 0x44, + 0x61, 0x74, 0x61, 0x64, 0x6f, 0x67, 0x41, 0x70, 0x69, 0x6b, 0x65, 0x79, 0x10, 0x90, 0x08, 0x42, + 0x3d, 0x5a, 0x3b, 0x67, 0x69, 0x74, 0x68, 0x75, 0x62, 0x2e, 0x63, 0x6f, 0x6d, 0x2f, 0x74, 0x72, + 0x75, 0x66, 0x66, 0x6c, 0x65, 0x73, 0x65, 0x63, 0x75, 0x72, 0x69, 0x74, 0x79, 0x2f, 0x74, 0x72, + 0x75, 0x66, 0x66, 0x6c, 0x65, 0x68, 0x6f, 0x67, 0x2f, 0x76, 0x33, 0x2f, 0x70, 0x6b, 0x67, 0x2f, + 0x70, 0x62, 0x2f, 0x64, 0x65, 0x74, 0x65, 0x63, 0x74, 0x6f, 0x72, 0x73, 0x70, 0x62, 0x62, 0x06, + 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x33, } var ( diff --git a/pkg/tui/pages/analyze_form/analyze_form.go b/pkg/tui/pages/analyze_form/analyze_form.go index 788a0b8a38e5..89fbcb401776 100644 --- a/pkg/tui/pages/analyze_form/analyze_form.go +++ b/pkg/tui/pages/analyze_form/analyze_form.go @@ -106,6 +106,10 @@ func New(c common.Common, keyType string) *AnalyzeForm { Key: "appKey", Required: true, RedactInput: true, + }, { + Label: "Endpoint (press Enter to skip if unknown; TruffleHog will attempt to auto-detect)", + Key: "endpoint", + Required: false, }} case "mux": inputs = []textinputs.InputConfig{{ diff --git a/proto/detectors.proto b/proto/detectors.proto index 1d93440a9141..03164dcef925 100644 --- a/proto/detectors.proto +++ b/proto/detectors.proto @@ -1049,6 +1049,7 @@ enum DetectorType { PhraseAccessToken = 1037; Photoroom = 1038; JWT = 1039; + DatadogApikey = 1040; } message Result {