Alcances y permisos
Credenciales por connector te dice qué claves crear. Esta página te dice con qué permisos crearlas.
Datadog
Sección titulada «Datadog»Dos credenciales de naturaleza distinta: la API key no tiene alcances, la Application key sí.
Application key: alcances
Sección titulada «Application key: alcances»| Alcance | Habilita | ¿Obligatorio? |
|---|---|---|
| ninguno | metrics.* |
Datadog no expone un alcance de lectura de métricas: una app key estándar ya lee métricas. |
logs_read_data + logs_read_index_data |
logs.* |
Si usas logs |
apm_read |
traces.*, topology.* (Software Catalog) |
Si usas APM |
monitors_read |
monitors.* |
Siempre (ver abajo) |
incident_read |
incidentes | Si usas incidentes |
rum_apps_read |
rum.* |
Si usas RUM |
synthetics_read |
synthetics.* |
Si Datadog sirve synthetics |
api_keys_read |
solo la higiene de claves (security) |
Opcional, y sensible |
API key
Sección titulada «API key»Sin alcances. Solo se usa en /api/v1/validate, en el health check.
DD_SITE
Sección titulada «DD_SITE»No es un permiso, pero falla junto con uno: si tu organización es EU o US3/US5/AP1, una key válida
devuelve 403 contra el site equivocado. Revisa esto antes de investigar alcances.
Grafana Cloud
Sección titulada «Grafana Cloud»Alcances de Access Policy. Solo los dos primeros son obligatorios.
| Alcance | Habilita | ¿Obligatorio? |
|---|---|---|
metrics:read |
Mimir: metrics.*, service graph, métricas de Faro y Synthetics |
Sí |
logs:read |
Loki: logs.* |
No; se degrada por operación |
traces:read |
Tempo: traces.* |
No; se degrada por operación |
alerts:read + rules:read |
Alerting: monitors.* |
No |
stacks:read + stack-dashboards:read |
búsqueda de dashboards en el stack | No |
| Synthetic Monitoring | synthetics.* vía /api/v1/check/list |
Nombre del alcance por confirmar en tu cuenta |
Mimir es el único obligatorio porque es la columna vertebral: sin él el connector no tiene nada que servir. Los demás se degradan con un error accionable, no con una falla de boot.
Una policy de IAM de solo lectura, servida como archivo para que no tengas que copiarla de la pantalla:
curl -O https://docs.whystack.ai/aws/whystack-edge-readonly.json
aws iam create-policy \ --policy-name WhyStackEdgeReadOnly \ --policy-document file://whystack-edge-readonly.jsonCubre todo el catálogo. Si no usas una capability, quita el Statement correspondiente antes de
crearla. Para eso están los Sid: cada uno es un dominio removible.
El archivo es el canónico; el bloque de abajo es el mismo contenido, y una verificación del build falla si los dos se separan.
{ "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", "pricing:GetProducts" ], "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", "ec2:DescribeVolumes", "ec2:DescribeSnapshots", "ec2:DescribeImages", "autoscaling:DescribeAutoScalingGroups", "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": "*" }, { "Sid": "BiQuickSightSomenteLeitura", "Effect": "Allow", "Action": ["quicksight:Describe*", "quicksight:List*", "quicksight:Search*"], "Resource": "*" } ]}Cinco trampas en esta policy
Sección titulada «Cinco trampas en esta policy»1. athena:StartQueryExecution y cloudtrail:StartQuery parecen escrituras, y no lo son. Así es
como se ejecuta una consulta de lectura en esos servicios: la acción crea una ejecución, no modifica
datos. El handler valida el SQL primero: DDL y DML se rechazan. Si tu revisión de seguridad bloquea
Start* por reflejo, ese es el argumento.
2. Athena necesita S3, y no está arriba. Los resultados van a un bucket, y sin
s3:GetObject/s3:PutObject/s3:ListBucket sobre el bucket de resultados, la consulta falla.
Según tu catálogo, también glue:GetTable/GetDatabase/GetPartitions. Quedó fuera a propósito: el
ARN es tuyo, y un comodín aquí sería peor que una omisión explícita.
3. DocumentDB usa el namespace rds:. No existe docdb:, y buscarlo sin encontrarlo lleva a
conceder de más.
4. Tres prefijos no coinciden con el nombre del servicio: CloudWatch Logs es logs:, Cost Explorer
es ce:, OpenSearch es es:.
5. Sin embed de QuickSight. El connector tiene código para GenerateEmbedUrlFor* y
GetDashboardEmbedUrl, pero esas cuatro operaciones se difirieron por postura y no están en el
catálogo: el enforcement las rechaza antes de que lleguen al connector. Conceder esos permisos sería
un privilegio que nada puede ejercer. La lectura para bi es el statement BiQuickSightSomenteLeitura
(quicksight:Describe*, List* y Search*, que es lo que las operaciones del catálogo realmente
llaman), y queda fuera solo si lo quitas. Hasta septiembre de 2026 ese statement no estaba en la
policy servida (la página decía que había que añadirlo a mano): si aplicaste la policy antes de esa
fecha, toda lectura de QuickSight recibe AccessDenied, y la solución es añadir el statement, no rotar
la credencial.
Dos credenciales de AWS distintas: no las confundas
Sección titulada «Dos credenciales de AWS distintas: no las confundas»| Para qué | Cómo | Permiso |
|---|---|---|
| Leer tus fuentes | RUNNER_AWS_CRED_MODE: static (claves) o chain (IRSA, instance profile) |
La policy de arriba |
| Leer tus secretos | RUNNER_SECRET_STORE_MODE=aws |
secretsmanager:GetSecretValue, acotado al prefijo de tus secretos |
La segunda debería restringirse al ARN, no a *:
{ "Effect": "Allow", "Action": "secretsmanager:GetSecretValue", "Resource": "arn:aws:secretsmanager:REGION:ACCOUNT:secret:whystack/*"}Los sso:GetRoleCredentials y sso:ListAccountRoles que vas a encontrar en el código pertenecen al
camino de desarrollo con aws sso login. No los concedas en producción.
Kubernetes (EKS)
Sección titulada «Kubernetes (EKS)»Aquí los permisos vienen en dos capas, y conceder solo la primera es el error más común.
Capa 1, IAM: eks:DescribeCluster (ya está en la policy de arriba), para descubrir el endpoint y
la CA del clúster.
Capa 2, RBAC dentro del clúster. El connector genera un token como lo hace aws eks get-token y
habla con el API server. Una identidad IAM sin binding en el clúster recibe 401/403 de Kubernetes,
no de AWS, y el error no menciona IAM, lo que manda la investigación en la dirección equivocada.
El connector lee solo esto:
apiVersion: rbac.authorization.k8s.io/v1kind: ClusterRolemetadata: name: whystack-readrules: - apiGroups: [""] resources: ["pods", "services", "endpoints"] verbs: ["get", "list"] - apiGroups: ["apps"] resources: ["deployments"] verbs: ["get"] - apiGroups: ["networking.k8s.io"] resources: ["ingresses"] verbs: ["get", "list"]Vincúlalo a la identidad IAM del runner con una EKS access entry (o una entrada en aws-auth en
clústeres más antiguos) más un ClusterRoleBinding.
Sin EKS_CLUSTER en el entorno, el health check responde skipped: no cluster configured, que no es
un error de permisos, pero lo parece.
Roles a nivel de proyecto. roles/viewer cubre la mayor parte; estos son los que suelen faltar:
| Rol | Habilita |
|---|---|
roles/viewer |
Compute, GKE, Cloud SQL, Run, Functions, DNS, Artifact Registry, Firestore, Redis |
roles/monitoring.viewer |
metrics.* |
roles/logging.viewer |
logs.* |
roles/cloudasset.viewer |
inventario de assets |
roles/recommender.viewer |
recomendaciones |
roles/securitycenter.findingsViewer |
security.* |
roles/billing.viewer en la billing account |
costo. Fíjate que es en la billing account, no en el proyecto |
roles/bigquery.dataViewer + roles/bigquery.jobUser |
leer GCP_BILLING_EXPORT_TABLE |
roles/iam.securityReviewer |
lecturas de IAM |
El costo es el más delicado: necesita el rol en la billing account y acceso de BigQuery a la
tabla de export. Sin GCP_BILLING_EXPORT_TABLE, la operación falla de forma explícita.
El precio de lista por vCPU y por GiB (cost.getGcpComputePricing, que le pone una cifra en dólares
a la holgura de un clúster de GKE) viene del catálogo público de Cloud Billing: no necesita ningún rol,
solo la API cloudbilling.googleapis.com habilitada en el proyecto de la service account, la misma que
ya usa el listado de billing accounts.
La lectura de la tabla de export corre en tu cuenta, y BigQuery cobra por byte leído. Por eso está limitada de tres formas:
- Podada por la partición de la tabla. El Agent lee los metadatos de la tabla, que no se cobran, y filtra por su partición: el tiempo de ingesta en el export estándar, o la columna de partición en un export personalizado.
- Un tope de 50 GB por consulta (
maximumBytesBilled), unos US$0.31 con precios on-demand. Por encima de eso, BigQuery rechaza la consulta y la respuesta pide una ventana más estrecha. - Costo bruto y neto lado a lado. La columna
costdel export es bruta; los descuentos (uso sostenido, compromisos, contrato) vienen encredits. La respuesta trae los dos y el neto, que es lo que pagas.
Con RUNNER_GCP_CRED_MODE=chain, estos roles van en la GSA de Workload Identity y no circula ninguna
clave.
Token fine-grained (recomendado):
| Permiso | Nivel | Para qué |
|---|---|---|
| Metadata | Read | Obligatorio. La base de todos los demás |
| Contents | Read | Commits, contenido, refs, tags, comparación, búsqueda de código |
| Pull requests | Read | PRs, archivos de PR, PRs asociados a un commit |
| Actions | Read | Ejecuciones de workflow |
| Checks | Read | Checks de un ref |
| Administration | Read | Solo la protección de branch (ver abajo) |
| Dependabot alerts | Read | Alertas de seguridad |
| Issues | Read and write | Opcional. Solo para abrir issues (create_github_issue) |
Token clásico, si lo prefieres: repo + read:org + security_events. El alcance repo concede
escritura sobre todo el repositorio; el connector no la usa, pero la credencial la permite. Por eso el
token fine-grained es el recomendado.
El Agent verifica el tipo de token en cada health check, y la Flota muestra el resultado en el check
fine_grained_token. Un token que no es fine-grained sigue funcionando, y el connector muestra una
configuración pendiente que nombra el tipo de token y los alcances que concede. El token en sí nunca
sale del Agent: el check lleva solo el prefijo (ghp_, github_pat_) y la lista de alcances.
Aparte de Issues, ninguno de estos permisos es de escritura, y el connector tiene una sola llamada
que modifica algo: la que abre un issue. Sin Issues: Read and write, esa escritura no ocurre y el
resto de la tabla funciona igual. Consulta Modelo de seguridad y
Qué escribe el agente.
Alcances del bot token:
| Alcance | Para qué |
|---|---|
channels:read, groups:read |
listar conversaciones |
channels:history, groups:history |
leer el historial |
chat:write |
chat.postMessage, operación de escritura |
channels:manage |
chat.createChannel, operación de escritura |
Las demás plataformas
Sección titulada «Las demás plataformas»| Plataforma | Qué necesita la credencial | Notas |
|---|---|---|
| Checkly | Lectura de checks y resultados (/checks, /check-results/{id}) |
En la práctica CHECKLY_ACCOUNT_ID es obligatorio para un token de usuario; sin él, un 401 sin explicación |
| Vercel | Lectura de deployments y proyectos (/v6/deployments, /v9/projects) |
Token de solo lectura acotado al equipo; VERCEL_TEAM_ID es obligatorio para un token de equipo, si no, un 403 silencioso |
| Argo CD | Cuenta local con applications, get y nada más |
Ver la política abajo. sync, rollback, delete y action no las usa el producto, así que concederlas es un privilegio que nada puede ejercer |
| Amplitude | API key + secret con acceso a las APIs de Dashboard/Export | Cubre /events/segmentation, /funnels, /dashboards, /charts. Lecturas |
| SonarQube | Token de usuario con Browse sobre los proyectos analizados | /api/issues/search, /api/measures/component, /api/qualitygates/project_status, /api/hotspots/search |
| Jira | Browse projects, más Create issues, Add comments y Transition issues | Las únicas tres escrituras de tickets del catálogo. Sin ellas, las lecturas siguen funcionando |
| Cloudflare | Zone:Read + DNS:Read |
Acótalo a las zonas que necesitas, no a toda la cuenta |
| Azion | Token con lectura del workspace (applications, connectors, workloads) y de las APIs GraphQL de métricas y eventos | Los POST del connector son transporte de query GraphQL, no mutations |
| MongoDB | Usuario con read sobre las bases de datos consultadas |
MONGO_URI incluye la credencial; usa un usuario dedicado de solo lectura |
Argo CD: la política mínima
Sección titulada «Argo CD: la política mínima»Crea una cuenta local, dale solo lectura de applications y emite el token:
# argocd-cm: enable the accountdata: accounts.whystack: apiKey# argocd-rbac-cm: what it may dodata: policy.csv: | p, role:whystack, applications, get, */*, allow g, whystack, role:whystackargocd account generate-token --account whystackTres notas que importan más que la política en sí:
applications, getalcanza. Las tres operaciones de WhyStack (listar applications, detalle e historial de sync) leen la mismaApplication; el historial de sync no tiene endpoint propio, es un campo destatus. Concedersync,overrideodeleteno habilita nada: ninguna op del catálogo los usa, y el runner rechaza lo que no está en el catálogo antes incluso de validar los argumentos.- Una lectura puede disparar una reconciliación, y no es decisión nuestra. WhyStack nunca envía el
parámetro
refresh. Pero leer el árbol de recursos pasa por la caché Redis de Argo CD, y en un cache miss el propio servidor dispara un refreshnormal(comparar contra git) antes de responder, sin ningún parámetro para desactivarlo. Conviene saberlo al dimensionar: es una comparación con git, no un nuevo render de los manifests. - Sin permiso la lista vuelve vacía, y eso no es un error. El servidor de Argo CD filtra los
listados por RBAC en silencio: un token sin
applications, getrecibe una lista vacía con un 200. Para que eso no se lea como “este clúster no tiene applications”, WhyStack le pregunta al propio Argo CD (can-i) cada vez que la lista vuelve vacía, y reporta la diferencia entre no hay y no puedo verlas.
Regenerar esta lista
Sección titulada «Regenerar esta lista»Antes de una auditoría, verifica contra el árbol en lugar de confiar en la página:
# AWS commands actually used, grouped by servicegrep -rhoE "\bnew [A-Z][A-Za-z0-9]+Command\b" packages/connectors/src | sort -u
# A connector's REST endpointsgrep -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 -uEsa última línea es la que importa para el mínimo privilegio: un permiso para una operación que no está en el catálogo es un privilegio que nada puede ejercer. Así es como las acciones de embed de QuickSight salieron de esta página.