Introducción
Al desplegar recursos de AWS desde GitHub Actions, el enfoque tradicional ha sido almacenar las claves de acceso de usuarios IAM en los Secrets de GitHub. Sin embargo, este método tiene varios desafíos:
- Riesgo de filtración de claves de acceso
- Necesidad de trabajo de rotación periódica
- Incremento en los secrets a gestionar
La autenticación OIDC (OpenID Connect) resuelve estos desafíos. Este artículo proporciona una explicación detallada de cómo configurar la integración OIDC entre GitHub Actions y AWS.
¿Qué es OIDC?
OIDC es un protocolo de autenticación basado en OAuth 2.0. GitHub Actions emite un token que puede demostrar "Soy un workflow de este repositorio" a AWS, y AWS lo verifica y proporciona credenciales temporales.
Beneficios de OIDC
- No se requieren secrets: No es necesario almacenar claves de acceso en GitHub
- Seguridad mejorada: Solo se usan credenciales temporales
- Control de permisos granular: Los permisos se pueden restringir por repositorio o rama
- Costos de gestión reducidos: No se necesita rotación de claves
Configuración de AWS
1. Crear un proveedor de identidad IAM
Primero, crea un proveedor de identidad IAM en la consola de AWS Management.
- Abre la consola IAM
- Haz clic en "Proveedores de identidad" → "Añadir proveedor"
- Introduce la siguiente información:
Tipo de proveedor: OpenID Connect
URL del proveedor: https://token.actions.githubusercontent.com
Audiencia: sts.amazonaws.com
Ejemplo con Terraform
resource "aws_iam_openid_connect_provider" "github_actions" {
url = "https://token.actions.githubusercontent.com"
client_id_list = [
"sts.amazonaws.com",
]
thumbprint_list = [
"6938fd4d98bab03faadb97b34396831e3780aea1"
]
}
Ejemplo con AWS SAM
AWSTemplateFormatVersion: '2010-09-09'
Transform: AWS::Serverless-2016-10-31
Description: GitHub Actions OIDC Provider
Resources:
GitHubOIDCProvider:
Type: AWS::IAM::OIDCProvider
Properties:
Url: https://token.actions.githubusercontent.com
ClientIdList:
- sts.amazonaws.com
ThumbprintList:
- 6938fd4d98bab03faadb97b34396831e3780aea1
2. Crear un rol IAM
A continuación, crea un rol IAM que GitHub Actions asumirá.
Configurar la política de confianza
{
"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"
},
"StringLike": {
"token.actions.githubusercontent.com:sub": "repo:your-org/your-repo:*"
}
}
}
]
}
Detalles de las condiciones
Puedes restringir el acceso usando el valor token.actions.githubusercontent.com:sub.
# Repositorio específico completo
repo:your-org/your-repo:*
# Solo rama específica
repo:your-org/your-repo:ref:refs/heads/main
# Solo entorno específico
repo:your-org/your-repo:environment:production
# Solo pull requests
repo:your-org/your-repo:pull_request
Ejemplo con Terraform
data "aws_iam_policy_document" "github_actions_assume_role" {
statement {
actions = ["sts:AssumeRoleWithWebIdentity"]
principals {
type = "Federated"
identifiers = [aws_iam_openid_connect_provider.github_actions.arn]
}
condition {
test = "StringEquals"
variable = "token.actions.githubusercontent.com:aud"
values = ["sts.amazonaws.com"]
}
condition {
test = "StringLike"
variable = "token.actions.githubusercontent.com:sub"
values = ["repo:your-org/your-repo:*"]
}
}
}
resource "aws_iam_role" "github_actions" {
name = "github-actions-deploy-role"
assume_role_policy = data.aws_iam_policy_document.github_actions_assume_role.json
}
# Adjuntar políticas de permisos requeridas
resource "aws_iam_role_policy_attachment" "deploy_policy" {
role = aws_iam_role.github_actions.name
policy_arn = "arn:aws:iam::aws:policy/AmazonECS_FullAccess"
}
Ejemplo con AWS SAM
Resources:
GitHubActionsRole:
Type: AWS::IAM::Role
Properties:
RoleName: github-actions-deploy-role
AssumeRolePolicyDocument:
Version: '2012-10-17'
Statement:
- Effect: Allow
Principal:
Federated: !GetAtt GitHubOIDCProvider.Arn
Action: sts:AssumeRoleWithWebIdentity
Condition:
StringEquals:
token.actions.githubusercontent.com:aud: sts.amazonaws.com
StringLike:
token.actions.githubusercontent.com:sub: repo:your-org/your-repo:*
ManagedPolicyArns:
- arn:aws:iam::aws:policy/AmazonECS_FullAccess
Outputs:
GitHubActionsRoleArn:
Description: ARN of the GitHub Actions IAM Role
Value: !GetAtt GitHubActionsRole.Arn
Export:
Name: GitHubActionsRoleArn
Para desplegar con SAM:
sam build
sam deploy --guided
Después del despliegue inicial, registra el ARN del rol de salida en los Secrets de GitHub como AWS_ROLE_ARN.
3. Adjuntar políticas de permisos
Adjunta los permisos necesarios para el despliegue al rol. Por ejemplo:
- Para despliegue en S3:
AmazonS3FullAccess - Para despliegue en ECS:
AmazonECS_FullAccess - Para Lambda:
AWSLambda_FullAccess
Para entornos de producción, recomendamos crear políticas personalizadas basadas en el principio de mínimo privilegio.
Configuración de GitHub Actions
Crear un archivo de workflow
Crea .github/workflows/deploy.yml.
name: Deploy to AWS
on:
push:
branches:
- main
# Configuración de permisos para obtener token OIDC (¡Importante!)
permissions:
id-token: write
contents: read
jobs:
deploy:
runs-on: ubuntu-latest
steps:
- name: Checkout code
uses: actions/checkout@v4
- name: Configure AWS credentials
uses: aws-actions/configure-aws-credentials@v4
with:
role-to-assume: arn:aws:iam::123456789012:role/github-actions-deploy-role
aws-region: ap-northeast-1
- name: Deploy to S3
run: |
aws s3 sync ./dist s3://your-bucket-name --delete
- name: Verify deployment
run: |
aws s3 ls s3://your-bucket-name
Puntos importantes
1. Configuración de permissions
permissions:
id-token: write # Requerido para obtener el token OIDC
contents: read # Requerido para checkout del repositorio
Sin esta configuración, no podrás obtener el token OIDC y obtendrás un error.
2. Versión de la acción configure-aws-credentials
Usa v4 o posterior. Las versiones anteriores pueden no soportar OIDC.
uses: aws-actions/configure-aws-credentials@v4
Ejemplo práctico: Despliegue de una aplicación SAM
Aquí hay un ejemplo de despliegue de una aplicación serverless usando AWS SAM.
name: Deploy SAM Application
on:
push:
branches:
- main
permissions:
id-token: write
contents: read
env:
AWS_REGION: ap-northeast-1
SAM_STACK_NAME: my-sam-app
jobs:
deploy:
runs-on: ubuntu-latest
steps:
- name: Checkout
uses: actions/checkout@v4
- name: Setup Python
uses: actions/setup-python@v5
with:
python-version: '3.11'
- name: Setup SAM CLI
uses: aws-actions/setup-sam@v2
with:
use-installer: true
- name: Configure AWS credentials
uses: aws-actions/configure-aws-credentials@v4
with:
role-to-assume: ${{ secrets.AWS_ROLE_ARN }}
aws-region: ${{ env.AWS_REGION }}
- name: SAM Build
run: sam build --use-container
- name: SAM Deploy
run: |
sam deploy \
--stack-name ${{ env.SAM_STACK_NAME }} \
--capabilities CAPABILITY_IAM \
--resolve-s3 \
--no-fail-on-empty-changeset \
--no-confirm-changeset
- name: Get Stack Outputs
run: |
aws cloudformation describe-stacks \
--stack-name ${{ env.SAM_STACK_NAME }} \
--query 'Stacks[0].Outputs' \
--output table
Ejemplo práctico: Despliegue en ECS
Como ejemplo más práctico, aquí hay un workflow de despliegue en ECS.
name: Deploy to ECS
on:
push:
branches:
- main
permissions:
id-token: write
contents: read
env:
AWS_REGION: ap-northeast-1
ECR_REPOSITORY: my-app
ECS_SERVICE: my-service
ECS_CLUSTER: my-cluster
CONTAINER_NAME: my-container
jobs:
deploy:
runs-on: ubuntu-latest
steps:
- name: Checkout
uses: actions/checkout@v4
- name: Configure AWS credentials
uses: aws-actions/configure-aws-credentials@v4
with:
role-to-assume: ${{ secrets.AWS_ROLE_ARN }}
aws-region: ${{ env.AWS_REGION }}
- name: Login to Amazon ECR
id: login-ecr
uses: aws-actions/amazon-ecr-login@v2
- name: Build and push image to ECR
id: build-image
env:
ECR_REGISTRY: ${{ steps.login-ecr.outputs.registry }}
IMAGE_TAG: ${{ github.sha }}
run: |
docker build -t $ECR_REGISTRY/$ECR_REPOSITORY:$IMAGE_TAG .
docker push $ECR_REGISTRY/$ECR_REPOSITORY:$IMAGE_TAG
echo "image=$ECR_REGISTRY/$ECR_REPOSITORY:$IMAGE_TAG" >> $GITHUB_OUTPUT
- name: Download task definition
run: |
aws ecs describe-task-definition \
--task-definition my-task \
--query taskDefinition > task-definition.json
- name: Update task definition
id: task-def
uses: aws-actions/amazon-ecs-render-task-definition@v1
with:
task-definition: task-definition.json
container-name: ${{ env.CONTAINER_NAME }}
image: ${{ steps.build-image.outputs.image }}
- name: Deploy to ECS
uses: aws-actions/amazon-ecs-deploy-task-definition@v1
with:
task-definition: ${{ steps.task-def.outputs.task-definition }}
service: ${{ env.ECS_SERVICE }}
cluster: ${{ env.ECS_CLUSTER }}
wait-for-service-stability: true
Solución de problemas
Error: "Not authorized to perform sts:AssumeRoleWithWebIdentity"
Causa: Las condiciones de la política de confianza no coinciden
Solución:
- Verifica el valor
token.actions.githubusercontent.com:suben la política de confianza del rol IAM - Comprueba que el nombre del repositorio y el nombre de la rama sean correctos
- Comprueba el valor real del claim
suben los logs de GitHub Actions
- name: Debug OIDC token
run: |
curl -H "Authorization: bearer $ACTIONS_ID_TOKEN_REQUEST_TOKEN" \
"$ACTIONS_ID_TOKEN_REQUEST_URL&audience=sts.amazonaws.com" | \
jq -R 'split(".") | .[1] | @base64d | fromjson'
Error: "Error: Credentials could not be loaded"
Causa: Falta la configuración de permissions
Solución: Añade lo siguiente a tu archivo de workflow
permissions:
id-token: write
contents: read
La autenticación tiene éxito pero ocurren errores de permisos
Causa: Las políticas de permisos requeridas no están adjuntas al rol IAM
Solución: Adjunta las políticas apropiadas al rol IAM
aws iam attach-role-policy \
--role-name github-actions-deploy-role \
--policy-arn arn:aws:iam::aws:policy/AmazonS3FullAccess
Mejores prácticas de seguridad
1. Principio de mínimo privilegio
Otorgar solo los permisos mínimos necesarios.
{
"Version": "2012-10-17",
"Statement": [
{
"Effect": "Allow",
"Action": [
"s3:PutObject",
"s3:GetObject",
"s3:ListBucket"
],
"Resource": [
"arn:aws:s3:::your-specific-bucket",
"arn:aws:s3:::your-specific-bucket/*"
]
}
]
}
2. Restringir por rama o tag
Restringir los despliegues de producción a ramas específicas solamente.
{
"Condition": {
"StringEquals": {
"token.actions.githubusercontent.com:sub": "repo:your-org/your-repo:ref:refs/heads/main"
}
}
}
3. Separar roles por entorno
Usar diferentes roles para desarrollo, staging y producción.
- name: Configure AWS credentials (Production)
if: github.ref == 'refs/heads/main'
uses: aws-actions/configure-aws-credentials@v4
with:
role-to-assume: ${{ secrets.AWS_ROLE_ARN_PROD }}
aws-region: ap-northeast-1
- name: Configure AWS credentials (Development)
if: github.ref == 'refs/heads/develop'
uses: aws-actions/configure-aws-credentials@v4
with:
role-to-assume: ${{ secrets.AWS_ROLE_ARN_DEV }}
aws-region: ap-northeast-1
4. Auditoría con CloudTrail
Registrar todas las llamadas a la API y revisarlas regularmente.
# Bucket S3 para CloudTrail
resource "aws_s3_bucket" "cloudtrail" {
bucket = "my-cloudtrail-logs-bucket"
}
resource "aws_s3_bucket_policy" "cloudtrail" {
bucket = aws_s3_bucket.cloudtrail.id
policy = jsonencode({
Version = "2012-10-17"
Statement = [
{
Sid = "AWSCloudTrailAclCheck"
Effect = "Allow"
Principal = {
Service = "cloudtrail.amazonaws.com"
}
Action = "s3:GetBucketAcl"
Resource = aws_s3_bucket.cloudtrail.arn
},
{
Sid = "AWSCloudTrailWrite"
Effect = "Allow"
Principal = {
Service = "cloudtrail.amazonaws.com"
}
Action = "s3:PutObject"
Resource = "${aws_s3_bucket.cloudtrail.arn}/*"
Condition = {
StringEquals = {
"s3:x-amz-acl" = "bucket-owner-full-control"
}
}
}
]
})
}
# CloudTrail
resource "aws_cloudtrail" "github_actions_audit" {
name = "github-actions-audit"
s3_bucket_name = aws_s3_bucket.cloudtrail.id
include_global_service_events = true
is_multi_region_trail = true
enable_logging = true
depends_on = [aws_s3_bucket_policy.cloudtrail]
}
Conclusión
La integración OIDC entre GitHub Actions y AWS proporciona los siguientes beneficios:
- Despliegue seguro sin claves de acceso
- Solo se usan credenciales temporales
- Control de permisos granular por repositorio o rama
- Costos de gestión reducidos
Aunque la configuración inicial es algo compleja, una vez configurada, conduce a mejoras a largo plazo en seguridad y operabilidad. Recomiendo encarecidamente considerar su adopción.
