Analyzer/datadog (#4132)

* task: Datadog analyzer init commit with limited resources and permissions
No cases covered

* task: added more permissions

* task: added more permissions | refactoring

* task: added domain handling

* refactor: incorporated PR feedback

* task: Analyzer Result implementation for enterprise

* task: added tests | Analysis Info configuration changes | permissions.yaml

* revert: reverted datadog detector base URL

* refactor: streamline Datadog API key analysis and permission handling

- Simplified API key retrieval logic by removing redundant checks for "key".
- Enhanced permission binding extraction by introducing a new function for permissions.
- Cleaned up resource binding logic for better clarity and maintainability.
- Removed unnecessary ID fields from scopes.json to reduce clutter.
This commit is contained in:
Amaan Ullah
2025-06-10 11:51:33 -05:00
committed by GitHub
parent 52d0ee5f54
commit bb506f63a1
11 changed files with 2780 additions and 0 deletions
+2
View File
@@ -97,6 +97,7 @@ const (
AnalyzerTypeNetlify
AnalyzerTypeFastly
AnalyzerTypeMonday
AnalyzerTypeDatadog
AnalyzerTypeNgrok
AnalyzerTypeMux
AnalyzerTypePosthog
@@ -145,6 +146,7 @@ var analyzerTypeStrings = map[AnalyzerType]string{
AnalyzerTypeNetlify: "Netlify",
AnalyzerTypeFastly: "Fastly",
AnalyzerTypeMonday: "Monday",
AnalyzerTypeDatadog: "Datadog",
AnalyzerTypeNgrok: "Ngrok",
AnalyzerTypeMux: "Mux",
AnalyzerTypePosthog: "Posthog",
+222
View File
@@ -0,0 +1,222 @@
//go:generate generate_permissions permissions.yaml permissions.go datadog
package datadog
import (
"errors"
"fmt"
"os"
"github.com/fatih/color"
"github.com/jedib0t/go-pretty/v6/table"
"github.com/trufflesecurity/trufflehog/v3/pkg/analyzer/analyzers"
"github.com/trufflesecurity/trufflehog/v3/pkg/analyzer/config"
"github.com/trufflesecurity/trufflehog/v3/pkg/context"
)
var _ analyzers.Analyzer = (*Analyzer)(nil)
type Analyzer struct {
Cfg *config.Config
}
func (a Analyzer) Type() analyzers.AnalyzerType {
return analyzers.AnalyzerTypeDatadog
}
// 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
appKey := credInfo["appKey"]
info, err := AnalyzePermissions(a.Cfg, apiKey, appKey)
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)
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")
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) {
// 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)
}
// capture user information in secretInfo
// If the application key is scoped, user information cannot be retrieved even if all the permissions are granted
// This is a non-documented Endpoint and can lead to unexpected behavior in future updates
// If user information is not retrieved, we will move ahead with the rest of the analysis and print the error
_ = CaptureUserInformation(client, baseURL, apiKey, appKey, secretInfo)
// capture resources in secretInfo
if err := CaptureResources(client, baseURL, apiKey, appKey, secretInfo); err != nil {
return nil, fmt.Errorf("failed to fetch resources: %v", err)
}
// capture permissions in secretInfo
if err := CapturePermissions(client, baseURL, apiKey, appKey, secretInfo); err != nil {
return nil, fmt.Errorf("failed to fetch permissions: %v", err)
}
return secretInfo, nil
}
// secretInfoToAnalyzerResult translate secret info to Analyzer Result
func secretInfoToAnalyzerResult(info *SecretInfo) *analyzers.AnalyzerResult {
if info == nil {
return nil
}
result := analyzers.AnalyzerResult{
AnalyzerType: analyzers.AnalyzerTypeDatadog,
Metadata: map[string]any{},
Bindings: make([]analyzers.Binding, 0),
}
// Create user resource to use as parent
var userResource *analyzers.Resource
if info.User.Id != "" {
userResource = &analyzers.Resource{
FullyQualifiedName: info.User.Id,
Name: info.User.Name,
Type: "User",
Metadata: map[string]any{
"email": info.User.Email,
},
}
}
permissionBindings := secretInfoPermissionsToAnalyzerPermission(info.Permissions)
result.Bindings = analyzers.BindAllPermissions(*userResource, *permissionBindings...)
// Extract information from resources to create bindings
for _, resource := range info.Resources {
resource := secretInfoResourceToAnalyzerResource(resource)
// Set the user resource as parent if available
if userResource != nil {
resource.Parent = userResource
}
binding := analyzers.Binding{
Resource: *resource,
}
result.Bindings = append(result.Bindings, binding)
}
return &result
}
// secretInfoPermissionsToAnalyzerPermission translate secret info Permission to analyzer resource for binding
func secretInfoPermissionsToAnalyzerPermission(perms []Permission) *[]analyzers.Permission {
permissions := make([]analyzers.Permission, 0, len(perms))
for _, perm := range perms {
permissions = append(permissions, analyzers.Permission{
Value: perm.Title,
})
}
return &permissions
}
// secretInfoResourceToAnalyzerResource translate secret info Resource to analyzer resource for binding
func secretInfoResourceToAnalyzerResource(resource Resource) *analyzers.Resource {
analyzerRes := analyzers.Resource{
FullyQualifiedName: resource.ID,
Name: resource.Name,
Type: resource.Type,
Metadata: map[string]any{},
}
for key, value := range resource.MetaData {
analyzerRes.Metadata[key] = value
}
return &analyzerRes
}
func printUser(user User) {
if user.Id == "" {
color.Red("\n[x] User information not available")
return
}
color.Green("\n[i] User Information:")
userTable := table.NewWriter()
userTable.SetOutputMirror(os.Stdout)
userTable.AppendHeader(table.Row{"User Id", "Name", "Email"})
userTable.AppendRow(table.Row{color.GreenString(user.Id), color.GreenString(user.Name), color.GreenString(user.Email)})
userTable.Render()
}
func printResources(resources []Resource) {
if len(resources) == 0 {
color.Red("[x] No resources found")
return
}
color.Green("\n[i] Resources:")
resourceTable := table.NewWriter()
resourceTable.SetOutputMirror(os.Stdout)
resourceTable.AppendHeader(table.Row{"Name", "Type"})
for _, resource := range resources {
resourceTable.AppendRow(table.Row{
color.GreenString(resource.Name),
color.GreenString(resource.Type),
})
}
resourceTable.Render()
}
func printPermissions(permissions []Permission) {
if len(permissions) == 0 {
color.Red("[x] No permissions found")
return
}
color.Green("\n[i] Permissions:")
permissionTable := table.NewWriter()
permissionTable.SetOutputMirror(os.Stdout)
permissionTable.AppendHeader(table.Row{"Title", "Name", "Description"})
// Set wrapping for long descriptions
permissionTable.SetColumnConfigs([]table.ColumnConfig{
{Number: 3, WidthMax: 50},
})
for _, permission := range permissions {
permissionTable.AppendRow(table.Row{
color.GreenString(permission.Title),
color.GreenString(permission.Name),
color.GreenString(permission.Description),
})
}
permissionTable.Render()
}
@@ -0,0 +1,142 @@
package datadog
import (
_ "embed"
"encoding/json"
"fmt"
"sort"
"testing"
"time"
"github.com/trufflesecurity/trufflehog/v3/pkg/analyzer/analyzers"
"github.com/trufflesecurity/trufflehog/v3/pkg/analyzer/config"
"github.com/trufflesecurity/trufflehog/v3/pkg/common"
"github.com/trufflesecurity/trufflehog/v3/pkg/context"
)
//go:embed expected_output.json
var expectedOutput []byte
func TestAnalyzer_Analyze(t *testing.T) {
ctx, cancel := context.WithTimeout(context.Background(), time.Minute*2)
defer cancel()
// Get API keys from GCP
var apiKey, appKey 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")
appKey = testSecrets.MustGetField("DATADOG_APP_KEY")
// Fail if credentials are not available
if apiKey == "" || appKey == "" {
t.Fatalf("Datadog credentials are required for this test")
}
tests := []struct {
name string
apiKey string
appKey string
want []byte // JSON string
wantErr bool
}{
{
name: "valid datadog credentials",
apiKey: apiKey,
appKey: appKey,
want: expectedOutput,
wantErr: false,
},
{
name: "invalid credentials",
apiKey: "invalid_api_key",
appKey: "invalid_app_key",
want: nil,
wantErr: true,
},
}
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})
if (err != nil) != tt.wantErr {
t.Errorf("Analyzer.Analyze() error = %v, wantErr %v", err, tt.wantErr)
return
}
// Skip verification for error cases
if tt.wantErr {
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)
}
fmt.Println(string(gotJSON))
// Parse the expected JSON string
var wantObj analyzers.AnalyzerResult
if err := json.Unmarshal(tt.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 {
if bindings[i].Resource.Name == bindings[j].Resource.Name {
return bindings[i].Permission.Value < bindings[j].Permission.Value
}
return bindings[i].Resource.Name < bindings[j].Resource.Name
})
}
File diff suppressed because it is too large Load Diff
+84
View File
@@ -0,0 +1,84 @@
package datadog
import "sync"
// Resource type constants for consistent usage
const (
ResourceTypeValidate = "Validate"
ResourceTypeCurrentUser = "Current User"
ResourceTypeDashboard = "Dashboard"
ResourceTypeMonitor = "Monitor"
)
// Permission represents a permission granted to an API key
type Permission struct {
Name string
Title string
Description string
MetaData map[string]string
}
// SecretInfo holds all information gathered about a Datadog API key
type SecretInfo struct {
User User
Permissions []Permission
mu sync.RWMutex
Resources []Resource
}
// User is the information about the user to whom the token belongs
type User struct {
Id string
Name string
Email string
}
// Resource represents a Datadog resource
type Resource struct {
ID string
Name string
Type string
MetaData map[string]string
}
// API response structures
type currentUserResponse struct {
Data struct {
Id string `json:"id"`
Attributes struct {
Name string `json:"name"`
Email string `json:"email"`
} `json:"attributes"`
} `json:"data"`
}
type dashboardResponse struct {
Dashboards []DashboardItem `json:"dashboards"`
}
type DashboardItem struct {
ID string `json:"id"`
Title string `json:"title"`
URL string `json:"url"`
IsReadOnly bool `json:"is_read_only"`
CreatedAt string `json:"created_at"`
ModifiedAt string `json:"modified_at"`
AuthorHandle string `json:"author_handle"`
Description *string `json:"description"`
LayoutType string `json:"layout_type"`
DeletedAt *string `json:"deleted_at"`
}
type monitorResponse []struct {
ID int `json:"id"`
Name string `json:"name"`
}
// appendResource adds a resource to secret info resources list
func (s *SecretInfo) appendResource(resource Resource) {
s.mu.Lock()
defer s.mu.Unlock()
s.Resources = append(s.Resources, resource)
}
@@ -0,0 +1,69 @@
permissions:
- dashboards_read
- dashboards_write
- dashboards_public_share
- monitors_read
- monitors_write
- logs_modify_indexes
- logs_write_pipelines
- logs_write_archives
- logs_generate_metrics
- monitors_downtime
- logs_read_data
- logs_read_archives
- security_monitoring_rules_read
- security_monitoring_rules_write
- security_monitoring_signals_read
- security_monitoring_signals_write
- user_access_invite
- user_app_keys
- org_app_keys_read
- org_app_keys_write
- user_access_manage
- synthetics_private_location_read
- synthetics_private_location_write
- usage_read
- metric_tags_write
- audit_logs_read
- api_keys_read
- api_keys_write
- synthetics_global_variable_read
- synthetics_global_variable_write
- synthetics_read
- synthetics_write
- synthetics_default_settings_read
- service_account_write
- apm_read
- apm_retention_filter_read
- apm_retention_filter_write
- rum_apps_write
- data_scanner_read
- data_scanner_write
- org_management
- security_monitoring_filters_read
- security_monitoring_filters_write
- incident_read
- incident_write
- incident_settings_write
- rum_apps_read
- security_monitoring_notification_profiles_read
- security_monitoring_notification_profiles_write
- apm_generate_metrics
- apm_pipelines_write
- apm_pipelines_read
- observability_pipelines_read
- workflows_read
- workflows_write
- workflows_run
- connections_read
- connections_write
- notebooks_read
- notebooks_write
- aws_configurations_manage
- azure_configurations_manage
- gcp_configurations_manage
- manage_integrations
- slos_read
- slos_write
- slos_corrections
- monitor_config_policy_write
+381
View File
@@ -0,0 +1,381 @@
package datadog
import (
"context"
_ "embed"
"encoding/json"
"errors"
"fmt"
"io"
"net/http"
"slices"
"strconv"
"sync"
"time"
)
// Constants and configuration
const (
defaultTimeout = 12 * time.Second
apiKeyHeader = "DD-API-KEY"
appKeyHeader = "DD-APPLICATION-KEY"
)
// List of all DataDog domains to try
var datadogDomains = []string{
"https://api.us5.datadoghq.com/api", // Default domain
"https://api.app.datadoghq.com/api",
"https://api.us3.datadoghq.com/api",
"https://api.app.datadoghq.eu/api",
"https://api.app.ddog-gov.com/api",
"https://api.ap1.datadoghq.com/api",
}
// Endpoints map for API paths
var endpoints = map[string]string{
ResourceTypeCurrentUser: "/v2/current_user",
ResourceTypeDashboard: "/v1/dashboard",
ResourceTypeMonitor: "/v1/monitor",
ResourceTypeValidate: "/v1/validate",
}
//go:embed scopes.json
var scopesConfig []byte
// --------------------------------
// Data models
// --------------------------------
// HttpStatusTest defines a test for checking HTTP endpoint permissions
type HttpStatusTest struct {
Method string `json:"method"`
Endpoint string `json:"endpoint"`
ValidStatuses []int `json:"valid_statuses"`
InvalidStatuses []int `json:"invalid_statuses"`
}
// Scope represents a permission scope with a test
type Scope struct {
Name string `json:"name"`
Title string `json:"title"`
Description string `json:"description"`
Resource string `json:"resource"`
HttpTest HttpStatusTest `json:"test"`
}
// --------------------------------
// Domain detection
// --------------------------------
// DetectDomain tries each DataDog domain to find a working one
func DetectDomain(client *http.Client, apiKey string, appKey string) (string, error) {
for _, domain := range datadogDomains {
// Use a simple endpoint to test if the domain works
endpoint := domain + 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 {
continue // Skip to next domain if request creation fails
}
// Add required keys in the header
req.Header.Set(apiKeyHeader, apiKey)
if appKey != "" {
req.Header.Set(appKeyHeader, appKey)
}
resp, err := client.Do(req)
if err != nil {
continue // Skip to next domain if request fails
}
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 domain, nil
}
}
return "", errors.New("unable to validate any DataDog domain with the provided API key")
}
// --------------------------------
// HTTP request utilities
// --------------------------------
// makeDataDogRequest sends an HTTP GET API request to the specified endpoint with auth tokens
func makeDataDogRequest(client *http.Client, baseURL, endpoint, method, apiKey string, appKey string) ([]byte, int, error) {
ctx, cancel := context.WithTimeout(context.Background(), defaultTimeout)
defer cancel()
// create request
req, err := http.NewRequestWithContext(ctx, method, baseURL+endpoint, http.NoBody)
if err != nil {
return nil, 0, err
}
// add required keys in the header
req.Header.Set(apiKeyHeader, apiKey)
if appKey != "" {
req.Header.Set(appKeyHeader, appKey)
}
resp, err := client.Do(req)
if err != nil {
return nil, 0, err
}
defer func() {
_, _ = io.Copy(io.Discard, resp.Body)
_ = resp.Body.Close()
}()
responseBodyByte, err := io.ReadAll(resp.Body)
if err != nil {
return nil, 0, err
}
return responseBodyByte, resp.StatusCode, nil
}
// RunTest executes an HTTP test against an API endpoint with provided headers
func (h *HttpStatusTest) RunTest(client *http.Client, baseURL string, headers map[string]string) (bool, error) {
apiKey := headers[apiKeyHeader]
appKey := headers[appKeyHeader]
_, statusCode, err := makeDataDogRequest(client, baseURL, h.Endpoint, h.Method, apiKey, appKey)
if err != nil {
fmt.Printf("Error making request: %v\n", err)
return false, err
}
// Check response status code
switch {
case slices.Contains(h.ValidStatuses, statusCode):
return true, nil
case slices.Contains(h.InvalidStatuses, statusCode):
return false, nil
default:
return false, fmt.Errorf("unexpected status code: %d", statusCode)
}
}
// --------------------------------
// Data capture functions
// --------------------------------
// CaptureUserInformation retrieves and stores user information
func CaptureUserInformation(client *http.Client, baseURL, apiKey, appKey string, secretInfo *SecretInfo) error {
caller, err := getCurrentUserInfo(client, baseURL, apiKey, appKey)
if err != nil {
return err
}
addUserToSecretInfo(caller, secretInfo)
return nil
}
// CaptureResources retrieves and stores dashboard and monitor resources
func CaptureResources(client *http.Client, baseURL, apiKey, appKey string, secretInfo *SecretInfo) error {
var wg sync.WaitGroup
errChan := make(chan error, 2) // Buffer size matches the number of tasks
// helper to launch tasks concurrently
launchTask := func(task func() error) {
wg.Add(1)
go func() {
defer wg.Done()
if err := task(); err != nil {
errChan <- err
}
}()
}
launchTask(func() error { return captureDashboard(client, baseURL, apiKey, appKey, secretInfo) })
launchTask(func() error { return captureMonitor(client, baseURL, apiKey, appKey, secretInfo) })
// Wait for all tasks to complete
wg.Wait()
close(errChan)
// Collect any errors
var errs []error
for err := range errChan {
errs = append(errs, err)
}
if len(errs) > 0 {
return errors.Join(errs...)
}
return nil
}
// CapturePermissions tests and records available permissions
func CapturePermissions(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)
headers := map[string]string{
apiKeyHeader: apiKey,
appKeyHeader: appKey,
}
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)
}
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
}
// --------------------------------
// Resource capture helper functions
// --------------------------------
// getCurrentUserInfo retrieves information about the current user
func getCurrentUserInfo(client *http.Client, baseURL, apiKey, appKey string) (*currentUserResponse, error) {
response, statusCode, err := makeDataDogRequest(client, baseURL, endpoints[ResourceTypeCurrentUser], http.MethodGet, apiKey, appKey)
if err != nil {
return nil, err
}
switch statusCode {
case http.StatusOK:
var caller = &currentUserResponse{}
if err := json.Unmarshal(response, caller); err != nil {
return nil, fmt.Errorf("unmarshalling user response: %w", err)
}
return caller, nil
case http.StatusUnauthorized:
return nil, errors.New("invalid API key or application key")
default:
return nil, fmt.Errorf("unexpected status code: %d", statusCode)
}
}
// addUserToSecretInfo adds user information to the secret info object
func addUserToSecretInfo(caller *currentUserResponse, secretInfo *SecretInfo) {
user := User{
Id: caller.Data.Id,
Name: caller.Data.Attributes.Name,
Email: caller.Data.Attributes.Email,
}
secretInfo.User = user
}
// captureDashboard retrieves dashboard information
func captureDashboard(client *http.Client, baseURL, apiKey, appKey string, secretInfo *SecretInfo) error {
response, statusCode, err := makeDataDogRequest(client, baseURL, endpoints[ResourceTypeDashboard], http.MethodGet, apiKey, appKey)
if err != nil {
return err
}
switch statusCode {
case http.StatusOK:
var dashboardResponse = &dashboardResponse{}
if err := json.Unmarshal(response, dashboardResponse); err != nil {
return fmt.Errorf("unmarshalling dashboard response: %w", err)
}
for _, dashboard := range dashboardResponse.Dashboards {
metadata := map[string]string{
"Layout Type": dashboard.LayoutType,
"URL": dashboard.URL,
"Author Handle": dashboard.AuthorHandle,
}
resource := Resource{
ID: dashboard.ID,
Name: dashboard.Title,
Type: ResourceTypeDashboard,
MetaData: metadata,
}
secretInfo.appendResource(resource)
}
return nil
case http.StatusForbidden:
return nil
default:
return fmt.Errorf("unexpected status code for dashboard API: %d", statusCode)
}
}
// captureMonitor retrieves monitor information
func captureMonitor(client *http.Client, baseURL, apiKey, appKey string, secretInfo *SecretInfo) error {
response, statusCode, err := makeDataDogRequest(client, baseURL, endpoints[ResourceTypeMonitor], http.MethodGet, apiKey, appKey)
if err != nil {
return err
}
switch statusCode {
case http.StatusOK:
var monitorResponse = &monitorResponse{}
if err := json.Unmarshal(response, monitorResponse); err != nil {
return fmt.Errorf("unmarshalling monitor response: %w", err)
}
for _, monitor := range *monitorResponse {
resource := Resource{
ID: strconv.Itoa(monitor.ID),
Name: monitor.Name,
Type: ResourceTypeMonitor,
}
secretInfo.appendResource(resource)
}
return nil
case http.StatusForbidden:
return nil
default:
return fmt.Errorf("unexpected status code for monitor API: %d", statusCode)
}
}
// --------------------------------
// Utility functions
// --------------------------------
// readInScopes loads permission scopes from the embedded configuration
func readInScopes() ([]Scope, error) {
var scopes []Scope
if err := json.Unmarshal(scopesConfig, &scopes); err != nil {
return nil, fmt.Errorf("unmarshalling scopes config: %w", err)
}
return scopes, nil
}
+818
View File
@@ -0,0 +1,818 @@
[
{
"name": "dashboards_read",
"title": "Dashboards Read",
"description": "View dashboards.",
"resource": "Dashboards",
"test": {
"endpoint": "/v1/dashboard",
"method": "GET",
"valid_statuses": [200, 429],
"invalid_statuses": [403]
}
},
{
"name": "dashboards_write",
"title": "Dashboards Write",
"description": "Create and change dashboards.",
"resource": "Dashboards",
"test": {
"endpoint": "/v1/dashboard",
"method": "POST",
"valid_statuses": [200, 400, 429],
"invalid_statuses": [403]
}
},
{
"name": "dashboards_public_share",
"title": "Dashboards Public Share",
"description": "Create, modify and delete shared dashboards with share type 'Public'. These dashboards can be accessed by anyone on the internet.",
"resource": "Dashboards",
"test": {
"endpoint": "/v1/dashboard/public",
"method": "POST",
"valid_statuses": [200, 400, 404, 429],
"invalid_statuses": [403]
}
},
{
"name": "monitors_read",
"title": "Monitors Read",
"description": "View monitors.",
"resource": "Monitors",
"test": {
"endpoint": "/v1/monitor",
"method": "GET",
"valid_statuses": [200, 400, 429],
"invalid_statuses": [403]
}
},
{
"name": "monitors_write",
"title": "Monitors Write",
"description": "Edit and delete individual monitors.",
"resource": "Monitors",
"test": {
"endpoint": "/v1/monitor",
"method": "POST",
"valid_statuses": [200, 400, 429],
"invalid_statuses": [403]
}
},
{
"name": "logs_modify_indexes",
"title": "Logs Modify Indexes",
"description": "Read and modify all indexes in your account.",
"resource": "Logs",
"test": {
"endpoint": "/v1/logs/config/indexes/does-not-exist",
"method": "DELETE",
"valid_statuses": [200, 400, 404, 429],
"invalid_statuses": [403]
}
},
{
"name": "logs_write_pipelines",
"title": "Logs Write Pipelines",
"description": "Add and change log pipeline configurations.",
"resource": "Logs",
"test": {
"endpoint": "/v1/logs/config/pipelines/does-not-exist",
"method": "DELETE",
"valid_statuses": [200, 400, 404, 429],
"invalid_statuses": [403]
}
},
{
"name": "logs_write_archives",
"title": "Logs Write Archives",
"description": "Add and edit Log Archives.",
"resource": "Logs",
"test": {
"endpoint": "/v2/logs/config/archives/does-not-exist",
"method": "DELETE",
"valid_statuses": [200, 400, 404, 429],
"invalid_statuses": [403]
}
},
{
"name": "logs_generate_metrics",
"title": "Logs Generate Metrics",
"description": "Create custom metrics from logs.",
"resource": "Logs",
"test": {
"endpoint": "/v2/logs/config/metrics",
"method": "POST",
"valid_statuses": [200, 400, 429],
"invalid_statuses": [403]
}
},
{
"name": "monitors_downtime",
"title": "Manage Downtimes",
"description": "Set downtimes to suppress alerts from any monitor in an organization.",
"resource": "Monitors",
"test": {
"endpoint": "/v1/downtime",
"method": "POST",
"valid_statuses": [200, 400, 429],
"invalid_statuses": [403]
}
},
{
"name": "logs_read_data",
"title": "Logs Read Data",
"description": "Read log data. In order to read log data, a user must have both this permission and Logs Read Index Data.",
"resource": "Logs",
"test": {
"endpoint": "/v2/logs/events",
"method": "GET",
"valid_statuses": [200, 400, 404, 429],
"invalid_statuses": [403]
}
},
{
"name": "logs_read_archives",
"title": "Logs Read Archives",
"description": "Read Log Archives location and use it for rehydration.",
"resource": "Logs",
"test": {
"endpoint": "/v2/logs/config/archives",
"method": "GET",
"valid_statuses": [200, 400, 429],
"invalid_statuses": [403]
}
},
{
"name": "security_monitoring_rules_read",
"title": "Security Rules Read",
"description": "Read Detection Rules.",
"resource": "Security Monitoring",
"test": {
"endpoint": "/v2/cloud_security_management/custom_frameworks/must/not-exist",
"method": "GET",
"valid_statuses": [200, 400, 429],
"invalid_statuses": [403]
}
},
{
"name": "security_monitoring_rules_write",
"title": "Security Rules Write",
"description": "Create and edit Detection Rules.",
"resource": "Security Monitoring",
"test": {
"endpoint": "/v2/security_monitoring/rules",
"method": "POST",
"valid_statuses": [200, 400, 404, 429],
"invalid_statuses": [403]
}
},
{
"name": "security_monitoring_signals_read",
"title": "Security Signals Read",
"description": "View Security Signals.",
"resource": "Security Monitoring",
"test": {
"endpoint": "/v2/security_monitoring/signals",
"method": "GET",
"valid_statuses": [200, 404, 429],
"invalid_statuses": [403]
}
},
{
"name": "security_monitoring_signals_write",
"title": "Security Signals Write",
"description": "Modify Security Signals.",
"resource": "Security Monitoring",
"test": {
"endpoint": "/v1/security_analytics/signals/must-not-exist/add_to_incident",
"method": "PATCH",
"valid_statuses": [200, 400, 404, 429],
"invalid_statuses": [403]
}
},
{
"name": "user_access_invite",
"title": "User Access Invite",
"description": "Invite other users to your organization.",
"resource": "Users",
"test": {
"endpoint": "/v2/user_invitations/does-not-exist",
"method": "GET",
"valid_statuses": [200, 400, 404, 429],
"invalid_statuses": [403]
}
},
{
"title": "User App Keys",
"name": "user_app_keys",
"description": "View and manage Application Keys owned by the user.",
"resource": "Key Management",
"test": {
"endpoint": "/v2/current_user/application_keys/does-not-exist",
"method": "DELETE",
"valid_statuses": [200, 400, 404, 429],
"invalid_statuses": [403]
}
},
{
"name": "org_app_keys_read",
"title": "Org App Keys Read",
"description": "View Application Keys owned by all users in the organization.",
"resource": "Key Management",
"test": {
"endpoint": "/v2/application_keys",
"method": "GET",
"valid_statuses": [200, 429],
"invalid_statuses": [403]
}
},
{
"name": "org_app_keys_write",
"title": "Org App Keys Write",
"description": "Manage Application Keys owned by all users in the organization.",
"resource": "Key Management",
"test": {
"endpoint": "/v2/application_keys/does-not-exist",
"method": "DELETE",
"valid_statuses": [200, 400, 404, 429],
"invalid_statuses": [403]
}
},
{
"name": "user_access_manage",
"title": "User Access Manage",
"description": "Disable users, manage user roles, manage SAML-to-role mappings, and configure logs restriction queries.",
"resource": "Users",
"test": {
"endpoint": "/v2/users/does-not-exist",
"method": "PATCH",
"valid_statuses": [200, 400, 429],
"invalid_statuses": [403]
}
},
{
"name": "synthetics_private_location_read",
"title": "Synthetics Private Locations Read",
"description": "View, search, and use Synthetics private locations.",
"resource": "Synthetics",
"test": {
"endpoint": "/v1/synthetics/private-locations/does-not-exit",
"method": "GET",
"valid_statuses": [200, 404, 429],
"invalid_statuses": [403]
}
},
{
"name": "synthetics_private_location_write",
"title": "Synthetics Private Locations Write",
"description": "Create and delete private locations in addition to having access to the associated installation guidelines.",
"resource": "Synthetics",
"test": {
"endpoint": "/v1/synthetics/private-locations/does-not-exit",
"method": "PUT",
"valid_statuses": [200, 404, 429],
"invalid_statuses": [403]
}
},
{
"name": "usage_read",
"title": "Usage Read",
"description": "View your organization's usage and usage attribution.",
"resource": "Usage Metering",
"test": {
"endpoint": "/v2/usage/hourly_usage",
"method": "GET",
"valid_statuses": [200, 400, 429],
"invalid_statuses": [403]
}
},
{
"name": "metric_tags_write",
"title": "Metric Tags Write",
"description": "Edit and save tag configurations for custom metrics.",
"resource": "Metrics",
"test": {
"endpoint": "/v2/metrics/does-not-exit/tags",
"method": "POST",
"valid_statuses": [200, 400, 429],
"invalid_statuses": [403]
}
},
{
"name": "audit_logs_read",
"title": "Audit Trail Read",
"description": "View Audit Trail in your organization.",
"resource": "Audit",
"test": {
"endpoint": "/v2/audit/events",
"method": "GET",
"valid_statuses": [200, 429],
"invalid_statuses": [403]
}
},
{
"name": "api_keys_read",
"title": "API Keys Read",
"description": "List and retrieve the key values of all API Keys in your organization.",
"resource": "Key Management",
"test": {
"endpoint": "/v2/api_keys",
"method": "GET",
"valid_statuses": [200, 429],
"invalid_statuses": [403]
}
},
{
"name": "api_keys_write",
"title": "API Keys Write",
"description": "Create and rename API Keys for your organization.",
"resource": "Key Management",
"test": {
"endpoint": "/v2/api_keys/does-not-exist",
"method": "PATCH",
"valid_statuses": [200, 400, 404, 429],
"invalid_statuses": [403]
}
},
{
"name": "synthetics_global_variable_read",
"title": "Synthetics Global Variable Read",
"description": "View, search, and use Synthetics global variables.",
"resource": "Synthetics",
"test": {
"endpoint": "/v1/synthetics/variables",
"method": "GET",
"valid_statuses": [200, 429],
"invalid_statuses": [403]
}
},
{
"name": "synthetics_global_variable_write",
"title": "Synthetics Global Variable Write",
"description": "Create, edit, and delete global variables for Synthetics.",
"resource": "Synthetics",
"test": {
"endpoint": "/v1/synthetics/variables",
"method": "POST",
"valid_statuses": [200, 400, 429],
"invalid_statuses": [403]
}
},
{
"name": "synthetics_read",
"title": "Synthetics Read",
"description": "List and view configured Synthetic tests and test results.",
"resource": "Synthetics",
"test": {
"endpoint": "/v1/synthetics/tests",
"method": "GET",
"valid_statuses": [200, 429],
"invalid_statuses": [403]
}
},
{
"name": "synthetics_write",
"title": "Synthetics Write",
"description": "Create, edit, and delete Synthetic tests.",
"resource": "Synthetics",
"test": {
"endpoint": "/v1/synthetics/tests/mobile/does-not-exit",
"method": "PUT",
"valid_statuses": [200, 400, 404, 429],
"invalid_statuses": [403]
}
},
{
"name": "synthetics_default_settings_read",
"title": "Synthetics Default Settings Read",
"description": "View the default settings for Synthetic Monitoring.",
"resource": "Synthetics",
"test": {
"endpoint": "/v1/synthetics/settings/default_locations",
"method": "GET",
"valid_statuses": [200, 429],
"invalid_statuses": [403]
}
},
{
"name": "service_account_write",
"title": "Service Account Write",
"description": "Create, disable, and use Service Accounts in your organization.",
"resource": "Service Accounts",
"test": {
"endpoint": "/v2/service_accounts/does-not-exist/application_keys",
"method": "POST",
"valid_statuses": [200, 400, 404, 429],
"invalid_statuses": [403]
}
},
{
"name": "apm_read",
"title": "APM Read",
"description": "Read and query APM and Trace Analytics.",
"resource": "APM",
"test": {
"endpoint": "/v2/apm/config/metrics",
"method": "GET",
"valid_statuses": [200, 429],
"invalid_statuses": [403]
}
},
{
"name": "apm_retention_filter_read",
"title": "APM Retention Filters Read",
"description": "Read trace retention filters. A user with this permission can view the retention filters page, list of filters, their statistics, and creation info.",
"resource": "APM",
"test": {
"endpoint": "/v2/apm/config/retention-filters/should-not-exist",
"method": "GET",
"valid_statuses": [200, 404, 429],
"invalid_statuses": [403]
}
},
{
"name": "apm_retention_filter_write",
"title": "APM Retention Filters Write",
"description": "Create, edit, and delete trace retention filters. A user with this permission can create new retention filters, and update or delete to existing retention filters.",
"resource": "APM",
"test": {
"endpoint": "/v2/apm/config/retention-filters/should-not-exit",
"method": "DELETE",
"valid_statuses": [404, 429],
"invalid_statuses": [403]
}
},
{
"name": "rum_apps_write",
"title": "RUM Apps Write",
"description": "Create, edit, and delete RUM applications. Creating a RUM application automatically generates a Client Token. In order to create Client Tokens directly, a user needs the Client Tokens Write permission.",
"resource": "RUM",
"test": {
"endpoint": "/v2/rum/applications/does-not-exist",
"method": "DELETE",
"valid_statuses": [404, 429],
"invalid_statuses": [403]
}
},
{
"name": "data_scanner_read",
"title": "Data Scanner Read",
"description": "View Sensitive Data Scanner configurations and scanning results.",
"resource": "Sensitive Data Scanner",
"test": {
"endpoint": "/v2/sensitive-data-scanner/config/standard-patterns",
"method": "GET",
"valid_statuses": [200, 429],
"invalid_statuses": [403]
}
},
{
"name": "data_scanner_write",
"title": "Data Scanner Write",
"description": "Edit Sensitive Data Scanner configurations.",
"resource": "Sensitive Data Scanner",
"test": {
"endpoint": "/v2/sensitive-data-scanner/config/groups/does-not-exist",
"method": "DELETE",
"valid_statuses": [404, 429],
"invalid_statuses": [403]
}
},
{
"name": "org_management",
"title": "Org Management",
"description": "Edit org configurations, including authentication and certain security preferences such as configuring SAML, renaming an org, configuring allowed login methods, creating child orgs, subscribing & unsubscribing from apps in the marketplace, and enabling & disabling Remote Configuration for the entire organization.",
"resource": "Organizations",
"test": {
"endpoint": "/v1/org",
"method": "GET",
"valid_statuses": [200, 404, 429],
"invalid_statuses": [403]
}
},
{
"name": "security_monitoring_filters_read",
"title": "Security Filters Read",
"description": "Read Security Filters.",
"resource": "Security Monitoring",
"test": {
"endpoint": "/v2/security_monitoring/configuration/security_filters",
"method": "GET",
"valid_statuses": [200, 404, 429],
"invalid_statuses": [403]
}
},
{
"name": "security_monitoring_filters_write",
"title": "Security Filters Write",
"description": "Create, edit, and delete Security Filters.",
"resource": "Security Monitoring",
"test": {
"endpoint": "/v2/security_monitoring/configuration/security_filters/does-not-exist",
"method": "DELETE",
"valid_statuses": [404, 429],
"invalid_statuses": [403]
}
},
{
"name": "incident_read",
"title": "Incidents Read",
"description": "View incidents in Datadog.",
"resource": "Incidents",
"test": {
"endpoint": "/v2/incidents",
"method": "GET",
"valid_statuses": [200, 429],
"invalid_statuses": [403]
}
},
{
"name": "incident_write",
"title": "Incidents Write",
"description": "Create, view, and manage incidents in Datadog.",
"resource": "Incidents",
"test": {
"endpoint": "/v2/incidents/does-not-exist",
"method": "DELETE",
"valid_statuses": [404, 429],
"invalid_statuses": [403]
}
},
{
"name": "incident_settings_write",
"title": "Incident Settings Write",
"description": "Configure Incident Settings.",
"resource": "Incidents",
"test": {
"endpoint": "/v2/incidents/config/types/does-not-exist",
"method": "DELETE",
"valid_statuses": [400, 404, 429],
"invalid_statuses": [403]
}
},
{
"name": "rum_apps_read",
"title": "RUM Apps Read",
"description": "View RUM Applications data.",
"resource": "RUM",
"test": {
"endpoint": "/v2/rum/applications",
"method": "GET",
"valid_statuses": [200, 429],
"invalid_statuses": [403]
}
},
{
"name": "security_monitoring_notification_profiles_read",
"title": "Security Notification Rules Read",
"description": "Read Notification Rules.",
"resource": "Security Monitoring",
"test": {
"endpoint": "/v2/security/signals/notification_rules",
"method": "GET",
"valid_statuses": [200, 429],
"invalid_statuses": [403]
}
},
{
"name": "security_monitoring_notification_profiles_write",
"title": "Security Notification Rules Write",
"description": "Create, edit, and delete Notification Rules.",
"resource": "Security Monitoring",
"test": {
"endpoint": "/v2/security/signals/notification_rules/does-not-exist",
"method": "DELETE",
"valid_statuses": [404, 429],
"invalid_statuses": [403]
}
},
{
"name": "apm_generate_metrics",
"title": "APM Generate Metrics",
"description": "Create custom metrics from spans.",
"resource": "APM",
"test": {
"endpoint": "/v2/apm/config/metrics/does-not-exist",
"method": "DELETE",
"valid_statuses": [404, 429],
"invalid_statuses": [403]
}
},
{
"name": "apm_pipelines_write",
"title": "APM Pipelines Write",
"description": "Add and change APM pipeline configurations.",
"resource": "APM",
"test": {
"endpoint": "/v2/apm/config/retention-filters/does-not-exist",
"method": "DELETE",
"valid_statuses": [404, 429],
"invalid_statuses": [403]
}
},
{
"name": "apm_pipelines_read",
"title": "APM Pipelines Read",
"description": "View APM pipeline configurations.",
"resource": "APM",
"test": {
"endpoint": "/v2/apm/config/retention-filters",
"method": "GET",
"valid_statuses": [200, 429],
"invalid_statuses": [403]
}
},
{
"name": "observability_pipelines_read",
"title": "Observability Pipelines Read",
"description": "View pipelines in your organization.",
"resource": "Observability Pipelines",
"test": {
"endpoint": "/v2/remote_config/products/obs_pipelines/pipelines",
"method": "GET",
"valid_statuses": [200, 429],
"invalid_statuses": [403]
}
},
{
"name": "workflows_read",
"title": "Workflows Read",
"description": "View workflows.",
"resource": "Workflows",
"test": {
"endpoint": "/v2/workflows/does-not-exist",
"method": "GET",
"valid_statuses": [200, 400, 429],
"invalid_statuses": [403]
}
},
{
"name": "workflows_write",
"title": "Workflows Write",
"description": "Create, edit, and delete workflows.",
"resource": "Workflows",
"test": {
"endpoint": "/v2/workflows/does-not-exist",
"method": "DELETE",
"valid_statuses": [400, 404, 429],
"invalid_statuses": [403]
}
},
{
"name": "workflows_run",
"title": "Workflows Run",
"description": "Run workflows.",
"resource": "Workflows",
"test": {
"endpoint": "/v2/workflows/should-not-exist/instances",
"method": "POST",
"valid_statuses": [400, 404, 429],
"invalid_statuses": [403]
}
},
{
"name": "connections_read",
"title": "Connections Read",
"description": "List and view available connections. Connections contain secrets that cannot be revealed.",
"resource": "Connections",
"test": {
"endpoint": "/v2/actions/connections/does-not-exist",
"method": "GET",
"valid_statuses": [200, 400, 429],
"invalid_statuses": [403]
}
},
{
"name": "connections_write",
"title": "Connections Write",
"description": "Create and delete connections.",
"resource": "Connections",
"test": {
"endpoint": "/v2/actions/connections/does-not-exist",
"method": "DELETE",
"valid_statuses": [400, 404, 429],
"invalid_statuses": [403]
}
},
{
"name": "notebooks_read",
"title": "Notebooks Read",
"description": "View notebooks.",
"resource": "Notebooks",
"test": {
"endpoint": "/v1/notebooks",
"method": "GET",
"valid_statuses": [200, 429],
"invalid_statuses": [403]
}
},
{
"name": "notebooks_write",
"title": "Notebooks Write",
"description": "Create and change notebooks.",
"resource": "Notebooks",
"test": {
"endpoint": "/v1/notebooks/does-not-exist",
"method": "DELETE",
"valid_statuses": [400, 404, 429],
"invalid_statuses": [403]
}
},
{
"name": "aws_configurations_manage",
"title": "AWS Configurations Manage",
"description": "Add or remove but not edit AWS integration configurations.",
"resource": "Integrations",
"test": {
"endpoint": "/v2/integration/aws/accounts/does-not-exist",
"method": "DELETE",
"valid_statuses": [400, 404, 429],
"invalid_statuses": [403]
}
},
{
"name": "azure_configurations_manage",
"title": "Azure Configurations Manage",
"description": "Add or remove but not edit Azure integration configurations.",
"resource": "Integrations",
"test": {
"endpoint": "/v1/integration/azure",
"method": "DELETE",
"valid_statuses": [400, 404, 429],
"invalid_statuses": [403]
}
},
{
"name": "gcp_configurations_manage",
"title": "GCP Configurations Manage",
"description": "Add or remove but not edit GCP integration configurations.",
"resource": "Integrations",
"test": {
"endpoint": "/v2/integration/gcp/accounts/does-not-exist",
"method": "DELETE",
"valid_statuses": [400, 404, 429],
"invalid_statuses": [403]
}
},
{
"name": "manage_integrations",
"title": "Integrations Manage",
"description": "Install, uninstall, and configure integrations.",
"resource": "Integrations",
"test": {
"endpoint": "/v2/integrations/cloudflare/accounts/does-not-exist",
"method": "DELETE",
"valid_statuses": [400, 404, 429],
"invalid_statuses": [403]
}
},
{
"name": "slos_read",
"title": "SLOs Read",
"description": "View SLOs and status corrections.",
"resource": "SLOs",
"test": {
"endpoint": "/v1/slo",
"method": "GET",
"valid_statuses": [200, 429],
"invalid_statuses": [403]
}
},
{
"name": "slos_write",
"title": "SLOs Write",
"description": "Create, edit, and delete SLOs.",
"resource": "SLOs",
"test": {
"endpoint": "/v1/slo/does-not-exist",
"method": "DELETE",
"valid_statuses": [404, 429],
"invalid_statuses": [403]
}
},
{
"name": "slos_corrections",
"title": "SLOs Status Corrections",
"description": "Apply, edit, and delete SLO status corrections. A user with this permission can make status corrections, even if they do not have permission to edit those SLOs.",
"resource": "SLOs",
"test": {
"endpoint": "/v1/slo/correction",
"method": "POST",
"valid_statuses": [400, 429],
"invalid_statuses": [403]
}
},
{
"name": "monitor_config_policy_write",
"title": "Monitor Configuration Policy Write",
"description": "Create, update, and delete monitor configuration policies.",
"resource": "Monitors",
"test": {
"endpoint": "/v2/monitor/policy/does-not-exist",
"method": "DELETE",
"valid_statuses": [400, 404, 429],
"invalid_statuses": [403]
}
}
]
+3
View File
@@ -12,6 +12,7 @@ import (
"github.com/trufflesecurity/trufflehog/v3/pkg/analyzer/analyzers/asana"
"github.com/trufflesecurity/trufflehog/v3/pkg/analyzer/analyzers/bitbucket"
"github.com/trufflesecurity/trufflehog/v3/pkg/analyzer/analyzers/databricks"
"github.com/trufflesecurity/trufflehog/v3/pkg/analyzer/analyzers/datadog"
"github.com/trufflesecurity/trufflehog/v3/pkg/analyzer/analyzers/digitalocean"
"github.com/trufflesecurity/trufflehog/v3/pkg/analyzer/analyzers/dockerhub"
"github.com/trufflesecurity/trufflehog/v3/pkg/analyzer/analyzers/dropbox"
@@ -136,6 +137,8 @@ func Run(keyType string, secretInfo SecretInfo) {
fastly.AnalyzeAndPrintPermissions(secretInfo.Cfg, secretInfo.Parts["key"])
case "monday":
monday.AnalyzeAndPrintPermissions(secretInfo.Cfg, secretInfo.Parts["key"])
case "datadog":
datadog.AnalyzeAndPrintPermissions(secretInfo.Cfg, secretInfo.Parts["apiKey"], secretInfo.Parts["appKey"])
case "ngrok":
ngrok.AnalyzeAndPrintPermissions(secretInfo.Cfg, secretInfo.Parts["key"])
case "mux":
@@ -134,6 +134,7 @@ func (s Scanner) FromData(ctx context.Context, verify bool, data []byte) (result
defer res.Body.Close()
if res.StatusCode >= 200 && res.StatusCode < 300 {
s1.Verified = true
s1.AnalysisInfo = map[string]string{"apiKey": resApiMatch, "appKey": resAppMatch}
var serviceResponse userServiceResponse
if err := json.NewDecoder(res.Body).Decode(&serviceResponse); err == nil {
// setup emails
@@ -176,6 +177,7 @@ func (s Scanner) FromData(ctx context.Context, verify bool, data []byte) (result
defer res.Body.Close()
if res.StatusCode >= 200 && res.StatusCode < 300 {
s1.Verified = true
s1.AnalysisInfo = map[string]string{"apiKey": resApiMatch}
}
}
}
@@ -95,6 +95,18 @@ func New(c common.Common, keyType string) *AnalyzeForm {
Required: true,
RedactInput: true,
}}
case "datadog":
inputs = []textinputs.InputConfig{{
Label: "API Key",
Key: "apiKey",
Required: true,
RedactInput: true,
}, {
Label: "Application Key",
Key: "appKey",
Required: true,
RedactInput: true,
}}
case "mux":
inputs = []textinputs.InputConfig{{
Label: "Secret",