- Una cuenta de AWS.
- Una identidad administrativa con permisos para administrar proveedores OIDC, roles y políticas de IAM.
- AWS CLI v2 autenticada en la cuenta correcta.
- Un repositorio de GitHub.com con permiso para editar
.github/workflows. - El propietario, nombre, rama, tag o Environment autorizado del repositorio.
jqpara validar los documentos JSON.- Una región AWS, por ejemplo
us-east-1.

Comprueba primero el entorno local:
aws --version
jq --version
aws sts get-caller-identity --output jsonVerifica que Account corresponda a la cuenta que deseas modificar.
El número de cuenta y el ARN del rol no son credenciales, pero evita publicar identificadores internos si la política de tu organización los considera sensibles.
Paso 1: definir qué workflow será confiable
AWS debe validar un valor sub exacto. El contexto del job cambia su formato: una rama, un tag, un pull request y un GitHub Environment no producen el mismo sujeto.
Si el job declara environment, el Environment sustituye a la rama o al tag dentro de sub.
| Escenario | Formato anterior | Formato inmutable desde 15/07/2026 |
|---|---|---|
Rama main | repo:ORG/REPO:ref:refs/heads/main | repo:ORG@ORG_ID/REPO@REPO_ID:ref:refs/heads/main |
Tag v1.2.0 | repo:ORG/REPO:ref:refs/tags/v1.2.0 | repo:ORG@ORG_ID/REPO@REPO_ID:ref:refs/tags/v1.2.0 |
Environment production | repo:ORG/REPO:environment:production | repo:ORG@ORG_ID/REPO@REPO_ID:environment:production |
| Pull request | repo:ORG/REPO:pull_request | repo:ORG@ORG_ID/REPO@REPO_ID:pull_request |
Para conocer el formato de tu repositorio, revisa la vista previa de OIDC en la configuración del repositorio u organización.
También puedes utilizar temporalmente el depurador citado por la documentación de GitHub. No imprimas el JWT completo: solo necesitas comprobar el valor exacto de sub.
Para producción, conviene usar un GitHub Environment con revisores y reglas de despliegue. En ese caso, la política debe confiar en environment:production, no en ref:refs/heads/main.
Paso 2: crear o comprobar el proveedor OIDC
El proveedor se crea una sola vez por cuenta de AWS.
- Issuer:
https://token.actions.githubusercontent.com - Audiencia habitual:
sts.amazonaws.com
AWS no necesita un thumbprint manual de GitHub mientras pueda validar la cadena TLS mediante su biblioteca de autoridades raíz confiables. Recurre al thumbprint cuando esa validación no es posible.
Define el ID de la cuenta y el ARN del proveedor:
AWS_ACCOUNT_ID=123456789012
OIDC_PROVIDER_ARN="arn:aws:iam::${AWS_ACCOUNT_ID}:oidc-provider/token.actions.githubusercontent.com"Comprueba si el proveedor ya existe:
aws iam get-open-id-connect-provider \
--open-id-connect-provider-arn "$OIDC_PROVIDER_ARN"Si devuelve Url y ClientIDList con sts.amazonaws.com, reutilízalo.
Si devuelve NoSuchEntity, créalo:
aws iam create-open-id-connect-provider \
--url https://token.actions.githubusercontent.com \
--client-id-list sts.amazonaws.comComprueba la configuración:
aws iam get-open-id-connect-provider \
--open-id-connect-provider-arn "$OIDC_PROVIDER_ARN" \
--query '{Url:Url,Audiences:ClientIDList}'No intentes crear otro proveedor con la misma URL si ya existe. No mejora el aislamiento y AWS rechazará la duplicación.
Paso 3: crear la política de confianza
La política de confianza responde quién puede asumir el rol.
El ejemplo siguiente autoriza únicamente la rama main del repositorio indicado y utiliza StringEquals para evitar comodines.
Sustituye el sujeto por el valor exacto emitido por tu repositorio.
Crea trust-policy.json:
{
"Version": "2012-10-17",
"Statement": [
{
"Effect": "Allow",
"Principal": {
"Federated": "arn:aws:iam::123456789012:oidc-provider/token.actions.githubusercontent.com"
},
"Action": "sts:AssumeRoleWithWebIdentity",
"Condition": {
"StringEquals": {
"token.actions.githubusercontent.com:aud": "sts.amazonaws.com",
"token.actions.githubusercontent.com:sub": "repo:mi-organizacion@123456/mi-repositorio@789012:ref:refs/heads/main"
}
}
}
]
}Valida primero la sintaxis:
jq empty trust-policy.jsonSi tienes permiso para utilizar IAM Access Analyzer, revisa también sus hallazgos:
aws accessanalyzer validate-policy \
--policy-type RESOURCE_POLICY \
--policy-document file://trust-policy.jsonEsta validación detecta errores y recomendaciones, pero no confirma que sub coincida con el JWT real. Esa coincidencia se comprueba ejecutando el workflow.
Crea el rol:
ROLE_NAME=github-actions-deploy
aws iam create-role \
--role-name "$ROLE_NAME" \
--assume-role-policy-document file://trust-policy.json \
--max-session-duration 3600Si el rol ya existe, conserva una copia de la confianza actual antes de sustituirla:
aws iam get-role \
--role-name "$ROLE_NAME" \
--query 'Role.AssumeRolePolicyDocument' \
--output json > trust-policy.backup.jsonAplica la política actualizada:
aws iam update-assume-role-policy \
--role-name "$ROLE_NAME" \
--policy-document file://trust-policy.jsonEvita la confianza amplia. No utilicesrepo:*/*:*,repo:ORG/*:*ni un asterisco global ensubpara “hacer que funcione”. Autorizaría más repositorios, referencias o contextos de los previstos. UsaStringLikesolo cuando el comodín sea una decisión explícita y acotada.
Paso 4: asignar permisos mínimos al rol
La confianza permite obtener una sesión. La política de permisos determina qué puede hacer esa sesión.
No adjuntes AdministratorAccess para probar OIDC. aws sts get-caller-identity permite verificar la sesión sin conceder acceso a un servicio de negocio.
Cuando agregues una operación real, limita acciones y recursos.
El siguiente ejemplo permite leer objetos de un bucket ficticio. No lo utilices si tu workflow no necesita S3.
Crea permissions-policy.json:
{
"Version": "2012-10-17",
"Statement": [
{
"Effect": "Allow",
"Action": [
"s3:GetObject"
],
"Resource": "arn:aws:s3:::ejemplo-artifacts/*"
}
]
}Aplica la política solo si esa operación es necesaria:
aws iam put-role-policy \
--role-name "$ROLE_NAME" \
--policy-name ArtifactReadOnly \
--policy-document file://permissions-policy.jsonPaso 5: crear el workflow
El permiso id-token: write permite solicitar un JWT. No concede por sí mismo escritura en AWS.
El ejemplo fija aws-actions/configure-aws-credentials al SHA completo del release oficial e inmutable v6.2.3, publicado el 22 de julio de 2026.
En runners autohospedados, la serie v6 requiere GitHub Actions Runner 2.327.1 o posterior.
Crea .github/workflows/verify-aws-oidc.yml:
name: Verificar AWS OIDC
on:
workflow_dispatch:
permissions:
contents: read
id-token: write
jobs:
verify:
runs-on: ubuntu-latest
steps:
- name: Configurar credenciales temporales de AWS
uses: aws-actions/configure-aws-credentials@e6de054238d6b7531b4efff3b6587d9aade6a06c
with:
role-to-assume: arn:aws:iam::123456789012:role/github-actions-deploy
role-session-name: github-${{ github.run_id }}
aws-region: us-east-1
allowed-account-ids: 123456789012
- name: Comprobar la identidad asumida
run: aws sts get-caller-identity --output jsonFijar una action a un SHA evita que una etiqueta mutable cambie el código ejecutado.
Configura Dependabot para recibir actualizaciones y compara cualquier nuevo SHA con el release oficial antes de sustituirlo.
Eventos no confiables. No concedas un rol de despliegue a código controlado por colaboradores externos. Revisa especialmente pull_request_target, workflows reutilizables, runners persistentes y cualquier paso que ejecute contenido del repositorio antes de usar las credenciales.Paso 6: ejecutar y comprobar
Ejecuta el workflow manualmente desde la referencia autorizada. Si la política confía en main, selecciona main.
En el step “Comprobar la identidad asumida”, la salida esperada tendrá esta forma:
{
"UserId": "AROAXXXXXXXXXXXXX:github-123456789",
"Account": "123456789012",
"Arn": "arn:aws:sts::123456789012:assumed-role/github-actions-deploy/github-123456789"
}Comprueba que:
Accountsea la cuenta esperada.Arncontengaassumed-role/github-actions-deploy/.- El nombre de sesión corresponda a
github-${{ github.run_id }}.
Ejecutar una prueba negativa
- Crea una rama que contenga el mismo archivo de workflow.
- Ejecuta
workflow_dispatchseleccionando esa rama. - Si la confianza solo acepta
main, AWS STS debe rechazar la solicitud.
El error esperado será similar a:
Not authorized to perform sts:AssumeRoleWithWebIdentityPara probar un Environment, declara el Environment en el job:
jobs:
verify:
environment: production
runs-on: ubuntu-latestConfigura antes los revisores y reglas de protección correspondientes.
Criterio de aceptación. La prueba positiva obtiene una sesión del rol esperado y la prueba negativa no puede asumirlo. Que GetCallerIdentity funcione confirma la autenticación, pero no demuestra que el rol tenga permiso para S3, ECS, Route 53 u otro servicio.Migrar desde claves estáticas
- Mantén las claves existentes únicamente durante la validación controlada.
- No las mezcles como fallback automático dentro del mismo job.
- Valida OIDC con la identidad, la operación real y la prueba negativa.
- Elimina
AWS_ACCESS_KEY_ID,AWS_SECRET_ACCESS_KEYyAWS_SESSION_TOKENde los secrets y variables correspondientes. - Desactiva o elimina la access key anterior en IAM.
- Revisa CloudTrail para confirmar que la clave dejó de utilizarse.
- Documenta el rol, el sujeto autorizado, el propietario de la política y el procedimiento de revocación.
Problemas frecuentes
Token OIDC
Comprueba que permissions incluya:
permissions:
id-token: writeLa declaración puede estar en el workflow o en el job.
Si definiste permissions en varios niveles, recuerda que una declaración más específica puede sustituir los permisos heredados.
Not authorized to perform sts:AssumeRoleWithWebIdentity
Compara carácter por carácter aud y sub con la política de confianza.
Los fallos habituales son:
- Una rama distinta.
- Un Environment presente en el job.
- Diferencias de mayúsculas o nombres.
- El ARN de un proveedor perteneciente a otra cuenta.
- El nuevo formato inmutable con
@ORG_IDy@REPO_ID. - Una audiencia diferente de
sts.amazonaws.com.
NoSuchEntity o proveedor inexistente
Confirma que el ARN:
- Use el ID de la misma cuenta del rol.
- Termine en
oidc-provider/token.actions.githubusercontent.com. - Corresponda a un proveedor cuya
ClientIDListcontengasts.amazonaws.com.
La sesión se crea, pero el servicio devuelve AccessDenied
En este caso, OIDC ya funcionó.
Revisa:
- La política de permisos del rol.
- El ARN del recurso.
- Las condiciones de la política.
- Los permission boundaries.
- Las políticas de AWS Organizations.
- Las políticas del recurso.
- Los eventos de CloudTrail.
Funciona desde main, pero falla con un Environment
Cuando el job declara environment, el sujeto pasa a:
repo:ORG/REPO:environment:NOMBREo, con el formato inmutable:
repo:ORG@ORG_ID/REPO@REPO_ID:environment:NOMBREActualiza la política de confianza y configura las reglas de protección del Environment.
Buenas prácticas para producción
- Crea roles separados por entorno y responsabilidad.
- No compartas un rol de producción con desarrollo.
- Usa GitHub Environments con revisores y reglas de despliegue.
- Fija las actions a un SHA completo.
- Automatiza propuestas de actualización con Dependabot.
- Mantén la duración de sesión tan corta como permita el job.
- Evita exportar credenciales como outputs salvo que exista una necesidad justificada.
- Revisa
AssumeRoleWithWebIdentityy las acciones posteriores en CloudTrail. - Elimina las trust policies de repositorios archivados, transferidos o renombrados.
- Adopta sujetos inmutables donde corresponda.
- No imprimas el JWT ni las variables
AWS_ACCESS_KEY_ID,AWS_SECRET_ACCESS_KEYoAWS_SESSION_TOKEN.
Conclusión
Una integración OIDC segura está completa cuando:
- El proveedor de GitHub existe.
- La confianza del rol acepta únicamente el sujeto previsto.
- El workflow solicita el token mediante
id-token: write. - La prueba positiva obtiene el rol correcto.
- Una referencia no autorizada no puede asumirlo.
- La política del rol concede únicamente los permisos necesarios.
El siguiente paso es sustituir el ejemplo de S3 por la política mínima de tu carga, probar la operación en un entorno no productivo y eliminar las claves estáticas después de confirmar que ya no se utilizan.