Problema

Muchas veces el proceso de llevar código desde un git push hasta una función Lambda en producción se vuelve manual: se sube el zip, se actualiza la versión y se prueba en la consola. Ese flujo manual introduce latencia, errores de copia y dificulta la trazabilidad. El patrón que se repite es la necesidad de un pipeline que, con cada confirmación en el repositorio, compile, ejecute pruebas unitarias y, si todo pasa, despliegue la nueva versión a AWS Lambda sin almacenar credenciales estáticas en GitHub. La falta de automatización también obliga a crear scripts ad‑hoc que rara vez se versionan, lo que complica la recuperación ante fallos.

Causa

  1. Credenciales estáticas – Usar Access Keys en los secretos de GitHub obliga a rotarlas manualmente y expone claves en caso de filtración.
  2. Falta de confianza entre proveedores – GitHub y AWS no comparten automáticamente una identidad, por lo que el workflow necesita permisos explícitos para actuar sobre recursos.
  3. Política de permisos demasiado amplia – Crear un rol IAM con privilegios de administrador para un solo despliegue genera superficies de ataque innecesarias.
  4. Separación de etapas – Cuando el pipeline combina compilación, pruebas y despliegue en un solo job, cualquier error en la fase de test impide que el código llegue a producción, pero el diagnóstico suele quedar oculto entre logs de AWS y de GitHub.
  5. Configuración de OIDC incompleta – OIDC requiere registrar el proveedor, crear una política de confianza y limitar los aud y sub a la organización y al repositorio correcto; omitir cualquiera de estos pasos rompe la autenticación.

Solución

Adoptar un pipeline basado en GitHub Actions que utilice OpenID Connect (OIDC) para obtener credenciales temporales en AWS. El flujo genérico es:

  1. Registrar GitHub como proveedor OIDC en la cuenta AWS.
  2. Crear un rol IAM con una política que permita lambda:UpdateFunctionCode (y opcionalmente lambda:PublishVersion) sobre la función objetivo. La política de confianza del rol debe aceptar tokens emitidos por GitHub para la organización y repositorio específicos.
  3. Definir un workflow en .github/workflows/deploy.yml con tres jobs: build, test y deploy. Cada job corre en un runner Linux, usa la acción oficial aws-actions/configure-aws-credentials con role-to-assume apuntando al rol creado.
  4. Mantener los nombres de la función y el bucket S3 como variables de entorno, de modo que el mismo workflow sirva a varios entornos (dev, staging, prod) mediante matrices o if‑conditions.
  5. Agregar una etapa de verificación que invoque la función recién desplegada con un payload de prueba y compare la respuesta contra un valor esperado; si falla, el job marca el pipeline como error y evita la promoción.

Este enfoque elimina secretos permanentes, limita el alcance del rol a la única acción requerida y mantiene todo el proceso bajo control de versiones.

Alternativas prácticas

  • Uso de SAM CLI: empaquetar la función con sam package y sam deploy dentro del job deploy.
  • Deploy a través de CloudFormation: crear o actualizar una stack que contenga la función Lambda; el rol IAM solo necesita permisos de cloudformation:UpdateStack.
  • Múltiples entornos con environment de GitHub: definir entornos dev, staging y prod y asociar cada uno a un rol distinto.

Cuándo aplicar esta solución

  • Frecuencia de cambios: repositorios con despliegues diarios o varias veces al día.
  • Equipos distribuidos: cuando varios desarrolladores necesitan desplegar sin compartir claves.
  • Regulación de acceso: entornos donde la auditoría de credenciales es obligatoria.
  • Errores recurrentes: despliegues que fallan por credenciales expiradas o por permisos excesivos.

No es la mejor opción si la función Lambda se actualiza una vez al mes y el equipo prefiere usar la consola de AWS; la sobrecarga de configurar OIDC puede no justificar el beneficio.

Código

# 1. Registrar GitHub como proveedor OIDC
aws iam create-open-id-connect-provider \
  --url https://token.actions.githubusercontent.com \
  --client-id-list sts.amazonaws.com \
  --thumbprint-list 6938fd4d98bab03faadb97b34396831e3780aea1

# 2. Crear política que permita actualizar la Lambda
cat > lambda-update-policy.json <<EOF
{
  "Version": "2012-10-17",
  "Statement": [
    {
      "Effect": "Allow",
      "Action": [
        "lambda:UpdateFunctionCode",
        "lambda:PublishVersion"
      ],
      "Resource": "arn:aws:lambda:us-east-1:123456789012:function:my-function"
    }
  ]
}
EOF
aws iam create-policy --policy-name LambdaUpdatePolicy --policy-document file://lambda-update-policy.json

# 3. Crear rol IAM con confianza OIDC
cat > trust-policy.json <<EOF
{
  "Version": "2012-10-17",
  "Statement": [
    {
      "Effect": "Allow",
      "Principal": {
        "Federated": "arn:aws:iam::123456789012:oidc-provider/token.actions.githubusercontent.com"
      },
      "Action": "sts:AssumeRoleWithWebIdentity",
      "Condition": {
        "StringLike": {
          "token.actions.githubusercontent.com:sub": "repo:my-org/my-repo:*"
        },
        "StringEquals": {
          "token.actions.githubusercontent.com:aud": "sts.amazonaws.com"
        }
      }
    }
  ]
}
EOF
aws iam create-role --role-name GitHubLambdaDeployRole --assume-role-policy-document file://trust-policy.json
aws iam attach-role-policy --role-name GitHubLambdaDeployRole --policy-arn arn:aws:iam::123456789012:policy/LambdaUpdatePolicy

# 4. Workflow de GitHub Actions (guárdalo en .github/workflows/deploy.yml)
cat > .github/workflows/deploy.yml <<'EOF'
name: CI/CD Lambda

on:
  push:
    branches: [ main ]

jobs:
  build:
    runs-on: ubuntu-latest
    outputs:
      artifact: ${{ steps.package.outputs.artifact }}
    steps:
      - uses: actions/checkout@v4
      - name: Install dependencies
        run: npm ci
      - name: Build
        run: npm run build
      - name: Package Lambda
        id: package
        run: |
          zip -r function.zip .
          echo "artifact=function.zip" >> $GITHUB_OUTPUT

  test:
    runs-on: ubuntu-latest
    needs: build
    steps:
      - uses: actions/checkout@v4
      - name: Install deps
        run: npm ci
      - name: Run unit tests
        run: npm test

  deploy:
    runs-on: ubuntu-latest
    needs: [build, test]
    permissions:
      id-token: write
      contents: read
    steps:
      - uses: actions/checkout@v4
      - name: Configure AWS credentials
        uses: aws-actions/configure-aws-credentials@v4
        with:
          role-to-assume: arn:aws:iam::123456789012:role/GitHubLambdaDeployRole
          aws-region: us-east-1
      - name: Upload artifact to S3
        run: |
          aws s3 cp ${{ needs.build.outputs.artifact }} s3://my-bucket/function.zip
      - name: Update Lambda code
        run: |
          aws lambda update-function-code \
            --function-name my-function \
            --s3-bucket my-bucket \
            --s3-key function.zip \
            --publish
      - name: Smoke test
        run: |
          RESPONSE=$(aws lambda invoke \
            --function-name my-function \
            --payload '{"test":true}' \
            /dev/stdout)
          echo "$RESPONSE"
          if [[ "$RESPONSE" != *"expectedResult"* ]]; then
            echo "Smoke test failed"
            exit 1
          fi
EOF

Verificación

  1. Commit a la rama main. GitHub debe iniciar el workflow y mostrar tres jobs en ejecución.
  2. En la pestaña Actions, verifica que build y test finalicen con ✅.
  3. Cuando deploy termine, abre la consola de Lambda y revisa la versión publicada; el número de versión debe haber incrementado.
  4. Ejecuta manualmente aws lambda invoke con el mismo payload usado en el job Smoke test. La salida debe coincidir con la esperada.
  5. Revisa CloudTrail para confirmar que el rol GitHubLambdaDeployRole fue asumido mediante AssumeRoleWithWebIdentity y que solo se ejecutó UpdateFunctionCode.

Notas adicionales

  • Rotación de roles: si cambias de repositorio o de organización, actualiza la condición sub en la política de confianza; de lo contrario el token será rechazado.
  • Limitaciones de tamaño: el zip de la Lambda no puede superar 50 MiB (directo) o 250 MiB (a través de S3). Usa aws lambda update-function-code --zip-file fileb://... solo para paquetes pequeños