Skip to content

Scopes and permissions

Connector credentials tells you which keys to create. This page tells you with what permissions to create them.

Two credentials with different natures: the API key has no scopes, the Application key does.

Scope Enables Required?
none metrics.* Datadog exposes no metrics-read scope: a standard app key already reads metrics.
logs_read_data + logs_read_index_data logs.* If you use logs
apm_read traces.*, topology.* (Software Catalog) If you use APM
monitors_read monitors.* Always (see below)
incident_read incidents If you use incidents
rum_apps_read rum.* If you use RUM
synthetics_read synthetics.* If Datadog serves synthetics
api_keys_read only key hygiene (security) Optional, and sensitive

No scopes. It is only used at /api/v1/validate, in the health check.

Not a permission, but it fails alongside one: if your organization is EU or US3/US5/AP1, a valid key returns 403 against the wrong site. Check this before investigating scopes.

Access Policy scopes. Only the first two are required.

Scope Enables Required?
metrics:read Mimir: metrics.*, service graph, Faro and Synthetics metrics Yes
logs:read Loki: logs.* No; degrades per operation
traces:read Tempo: traces.* No; degrades per operation
alerts:read + rules:read Alerting: monitors.* No
stacks:read + stack-dashboards:read dashboard search in the stack No
Synthetic Monitoring synthetics.* via /api/v1/check/list Scope name to confirm in your account

Mimir is the only required one because it’s the backbone: without it the connector has nothing to serve. The others degrade with an actionable error, not a boot failure.

A read-only IAM policy, served as a file so you don’t have to copy it off the screen:

Terminal window
curl -O https://docs.rootpilot.sh/aws/rootpilot-edge-readonly.json
aws iam create-policy \
--policy-name RootPilotEdgeReadOnly \
--policy-document file://rootpilot-edge-readonly.json

It covers the whole catalog. If you don’t use a capability, drop the matching Statement before creating it. That’s what the Sids are for: each one is a removable domain.

The file is canonical; the block below is the same content, and a build check fails if the two drift apart.

{
"Version": "2012-10-17",
"Statement": [
{
"Sid": "IdentidadeEContexto",
"Effect": "Allow",
"Action": [
"sts:GetCallerIdentity",
"organizations:DescribeOrganization",
"organizations:ListAccounts"
],
"Resource": "*"
},
{
"Sid": "CustoEFinOps",
"Effect": "Allow",
"Action": [
"ce:GetCostAndUsage",
"ce:GetAnomalies",
"ce:GetReservationCoverage",
"ce:GetReservationPurchaseRecommendation",
"ce:GetSavingsPlansCoverage",
"ce:GetSavingsPlansPurchaseRecommendation",
"compute-optimizer:GetEnrollmentStatus",
"compute-optimizer:GetEC2InstanceRecommendations",
"compute-optimizer:GetEBSVolumeRecommendations",
"compute-optimizer:GetLambdaFunctionRecommendations",
"compute-optimizer:GetAutoScalingGroupRecommendations",
"compute-optimizer:GetRDSDatabaseRecommendations"
],
"Resource": "*"
},
{
"Sid": "Compute",
"Effect": "Allow",
"Action": [
"ec2:DescribeInstances",
"ec2:DescribeAddresses",
"ec2:DescribeNetworkInterfaces",
"ec2:DescribeSecurityGroups",
"ec2:DescribeSubnets",
"ec2:DescribeVpcs",
"ec2:DescribeVpcEndpoints",
"ec2:DescribeVpcPeeringConnections",
"ec2:DescribeRouteTables",
"ec2:DescribeInternetGateways",
"ec2:DescribeNatGateways",
"ec2:DescribeCustomerGateways",
"ec2:DescribeVpnConnections",
"ec2:DescribeVpnGateways",
"ec2:DescribeTransitGateways",
"ec2:DescribeTransitGatewayAttachments",
"ec2:DescribeManagedPrefixLists",
"ecs:ListClusters",
"eks:ListClusters",
"eks:DescribeCluster",
"eks:ListNodegroups",
"eks:DescribeNodegroup",
"ecr:DescribeRepositories",
"ecr:DescribeImages",
"lambda:ListFunctions",
"lambda:GetFunctionConfiguration",
"lambda:ListEventSourceMappings"
],
"Resource": "*"
},
{
"Sid": "BancosDeDados",
"Effect": "Allow",
"Action": [
"rds:DescribeDBInstances",
"rds:DescribeDBClusters",
"rds:DescribeDBSubnetGroups",
"rds:ListTagsForResource",
"dms:DescribeReplicationInstances",
"dms:DescribeReplicationTasks",
"es:ListDomainNames",
"es:DescribeDomains",
"elasticache:DescribeCacheClusters",
"elasticache:DescribeReplicationGroups",
"pi:DescribeDimensionKeys",
"pi:GetResourceMetrics"
],
"Resource": "*"
},
{
"Sid": "Rede",
"Effect": "Allow",
"Action": [
"elasticloadbalancing:DescribeLoadBalancers",
"elasticloadbalancing:DescribeTargetGroups",
"elasticloadbalancing:DescribeTargetHealth",
"elasticloadbalancing:DescribeInstanceHealth",
"elasticloadbalancing:DescribeTags",
"route53:ListHostedZones",
"route53:GetHostedZone",
"route53:ListResourceRecordSets",
"sqs:ListQueues",
"sqs:GetQueueUrl",
"sqs:GetQueueAttributes"
],
"Resource": "*"
},
{
"Sid": "Observabilidade",
"Effect": "Allow",
"Action": [
"cloudwatch:GetMetricData",
"cloudwatch:DescribeAlarms",
"logs:DescribeLogGroups",
"logs:FilterLogEvents",
"logs:StartQuery",
"logs:GetQueryResults",
"application-signals:ListServices",
"application-signals:ListServiceLevelObjectives",
"application-signals:BatchGetServiceLevelObjectiveBudgetReport"
],
"Resource": "*"
},
{
"Sid": "AuditoriaEConsulta",
"Effect": "Allow",
"Action": [
"cloudtrail:LookupEvents",
"cloudtrail:ListEventDataStores",
"cloudtrail:StartQuery",
"cloudtrail:DescribeQuery",
"cloudtrail:GetQueryResults",
"athena:StartQueryExecution",
"athena:StopQueryExecution",
"athena:GetQueryExecution",
"athena:BatchGetQueryExecution",
"athena:GetQueryResults",
"athena:ListQueryExecutions"
],
"Resource": "*"
},
{
"Sid": "SegurancaIamSomenteLeitura",
"Effect": "Allow",
"Action": ["iam:ListUsers", "iam:ListAccessKeys", "iam:GetAccessKeyLastUsed"],
"Resource": "*"
}
]
}

1. athena:StartQueryExecution and cloudtrail:StartQuery look like writes, and aren’t. That is how you run a read query in those services: the action creates an execution, it doesn’t change data. The handler validates the SQL first: DDL and DML are rejected. If your security review reflexively blocks Start*, that’s the argument.

2. Athena needs S3, and it isn’t above. Results go to a bucket, and without s3:GetObject/s3:PutObject/s3:ListBucket on the results bucket, the query fails. Depending on your catalog, also glue:GetTable/GetDatabase/GetPartitions. Left out deliberately: the ARN is yours, and a wildcard here would be worse than an explicit omission.

3. DocumentDB uses the rds: namespace. There is no docdb:, and looking for it without finding it leads to over-granting.

4. Three prefixes don’t match the service name: CloudWatch Logs is logs:, Cost Explorer is ce:, OpenSearch is es:.

5. No QuickSight embed. The connector has code for GenerateEmbedUrlFor* and GetDashboardEmbedUrl, but those four operations were deferred by posture and are not in the catalog: enforcement refuses them before they reach the connector. Granting those permissions would be privilege nothing can exercise. If you use the bi capability, add only the quicksight:Describe*, List*, and Search* actions.

Two different AWS credentials: don’t conflate them

Section titled “Two different AWS credentials: don’t conflate them”
For what How Permission
Reading your sources RUNNER_AWS_CRED_MODE: static (keys) or chain (IRSA, instance profile) The policy above
Reading your secrets RUNNER_SECRET_STORE_MODE=aws secretsmanager:GetSecretValue, scoped to your secret prefix

The second should be tightened to the ARN, not *:

{
"Effect": "Allow",
"Action": "secretsmanager:GetSecretValue",
"Resource": "arn:aws:secretsmanager:REGION:ACCOUNT:secret:rootpilot/*"
}

The sso:GetRoleCredentials and sso:ListAccountRoles you’ll find in the code belong to the development path with aws sso login. Don’t grant them in production.

Permissions here come in two layers, and granting only the first is the most common mistake.

Layer 1, IAM: eks:DescribeCluster (already in the policy above), to discover the cluster’s endpoint and CA.

Layer 2, RBAC inside the cluster. The connector mints a token the way aws eks get-token does and talks to the API server. An IAM identity with no binding in the cluster gets 401/403 from Kubernetes, not from AWS, and the error doesn’t mention IAM, which sends the investigation the wrong way.

The connector reads only this:

apiVersion: rbac.authorization.k8s.io/v1
kind: ClusterRole
metadata:
name: rootpilot-read
rules:
- apiGroups: [""]
resources: ["pods", "services", "endpoints"]
verbs: ["get", "list"]
- apiGroups: ["apps"]
resources: ["deployments"]
verbs: ["get"]
- apiGroups: ["networking.k8s.io"]
resources: ["ingresses"]
verbs: ["get", "list"]

Bind it to the runner’s IAM identity with an EKS access entry (or an aws-auth entry on older clusters) plus a ClusterRoleBinding.

Without EKS_CLUSTER in the environment, the health check answers skipped: no cluster configured, which is not a permission error, but looks like one.

Project-level roles. roles/viewer covers most of it; these are the ones usually missing:

Role Enables
roles/viewer Compute, GKE, Cloud SQL, Run, Functions, DNS, Artifact Registry, Firestore, Redis
roles/monitoring.viewer metrics.*
roles/logging.viewer logs.*
roles/cloudasset.viewer asset inventory
roles/recommender.viewer recommendations
roles/securitycenter.findingsViewer security.*
roles/billing.viewer on the billing account cost. Note it’s on the billing account, not the project
roles/bigquery.dataViewer + roles/bigquery.jobUser reading GCP_BILLING_EXPORT_TABLE
roles/iam.securityReviewer IAM reads

Cost is the fiddly one: it needs the role on the billing account and BigQuery access to the export table. Without GCP_BILLING_EXPORT_TABLE, the operation fails explicitly.

With RUNNER_GCP_CRED_MODE=chain, these roles go on the Workload Identity GSA and no key circulates.

Fine-grained token (recommended):

Permission Level For what
Metadata Read Required. The base for all the others
Contents Read Commits, content, refs, tags, comparison, code search
Pull requests Read PRs, PR files, PRs associated with a commit
Actions Read Workflow runs
Checks Read Checks for a ref
Administration Read Only branch protection (see below)
Dependabot alerts Read Security alerts

Classic token, if you prefer: repo + read:org + security_events.

None of these permissions are writes. The connector contains not a single mutating call. See Security model.

Bot token scopes:

Scope For what
channels:read, groups:read listing conversations
channels:history, groups:history reading history
chat:write chat.postMessage, write operation
channels:manage chat.createChannel, write operation
Platform What the credential needs Notes
Checkly Read access to checks and results (/checks, /check-results/{id}) CHECKLY_ACCOUNT_ID is required in practice for a user token; without it, an unexplained 401
Vercel Read access to deployments and projects (/v6/deployments, /v9/projects) Read-only token scoped to the team; VERCEL_TEAM_ID required for a team token, otherwise a silent 403
Amplitude API key + secret with access to the Dashboard/Export APIs Covers /events/segmentation, /funnels, /dashboards, /charts. Reads
SonarQube User token with Browse on the analyzed projects /api/issues/search, /api/measures/component, /api/qualitygates/project_status, /api/hotspots/search
Jira Browse projects, plus Create issues, Add comments, and Transition issues The catalog’s only three ticket writes. Without them, reads still work
Cloudflare Zone:Read + DNS:Read Scope it to the zones you need, not the whole account
Azion Token with read access to workspace (applications, connectors, workloads) and the metrics and events GraphQL APIs The connector’s POSTs are GraphQL query transport, not mutations
MongoDB User with read on the queried databases MONGO_URI embeds the credential; use a dedicated read-only user

Before an audit, check against the tree rather than trusting the page:

Terminal window
# AWS commands actually used, grouped by service
grep -rhoE "\bnew [A-Z][A-Za-z0-9]+Command\b" packages/connectors/src | sort -u
# A connector's REST endpoints
grep -ohE "'/[a-zA-Z0-9/_.{}-]+'" packages/connectors/src/sonarqube.ts | sort -u
# What's actually exposed (enforcement refuses the rest)
grep -oE "'[a-z]+\.[a-zA-Z]+'" apps/runner/src/connectors/manifests/real.ts | sort -u

That last line is the one that matters for least privilege: permission for an operation that isn’t in the catalog is privilege nothing can exercise. That’s how the QuickSight embed actions came off this page.