GitHub ActionsAWSOIDCCI/CDSecurityIAMTerraformSAM

Déployer sur AWS depuis GitHub Actions avec l'authentification OIDC

Sloth255
Sloth255
·5 min read·963 words

Introduction

Pour déployer des ressources AWS depuis GitHub Actions, l'approche traditionnelle consistait à stocker les clés d'accès IAM dans les GitHub Secrets. Cependant, cette méthode présente plusieurs défis :

  • Risque de fuite des clés d'accès
  • Besoin de travaux de rotation réguliers
  • Augmentation des secrets à gérer

L'authentification OIDC (OpenID Connect) résout ces défis. Cet article fournit une explication détaillée de la façon de configurer l'intégration OIDC entre GitHub Actions et AWS.

Qu'est-ce que OIDC ?

OIDC est un protocole d'authentification basé sur OAuth 2.0. GitHub Actions émet un token qui peut prouver « Je suis un workflow de ce dépôt » à AWS, et AWS le vérifie et fournit des identifiants temporaires.

Avantages d'OIDC

  • Pas de secrets requis : Pas besoin de stocker des clés d'accès dans GitHub
  • Sécurité renforcée : Utilise uniquement des identifiants temporaires
  • Contrôle fin des permissions : Les permissions peuvent être restreintes par dépôt ou branche
  • Réduction des coûts de gestion : Pas besoin de rotation des clés

Configuration AWS

1. Création d'un fournisseur d'identité IAM

Commencez par créer un fournisseur d'identité IAM dans la console AWS Management.

  1. Ouvrez la console IAM
  2. Cliquez sur « Fournisseurs d'identité » → « Ajouter un fournisseur »
  3. Saisissez les informations suivantes :
Provider type: OpenID Connect
Provider URL: https://token.actions.githubusercontent.com
Audience: sts.amazonaws.com

Exemple avec Terraform

resource "aws_iam_openid_connect_provider" "github_actions" {
  url = "https://token.actions.githubusercontent.com"

  client_id_list = [
    "sts.amazonaws.com",
  ]

  thumbprint_list = [
    "6938fd4d98bab03faadb97b34396831e3780aea1"
  ]
}

Exemple avec AWS SAM

template.yaml
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. Création d'un rôle IAM

Ensuite, créez un rôle IAM que GitHub Actions assumera.

Configuration de la politique de confiance

{
  "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:*"
        }
      }
    }
  ]
}

Détails des conditions

Vous pouvez restreindre l'accès en utilisant la valeur token.actions.githubusercontent.com:sub.

# Dépôt spécifique entier
repo:your-org/your-repo:*

# Branche spécifique uniquement
repo:your-org/your-repo:ref:refs/heads/main

# Environnement spécifique uniquement
repo:your-org/your-repo:environment:production

# Pull requests uniquement
repo:your-org/your-repo:pull_request

Exemple avec 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
}

# Attacher les politiques de permissions requises
resource "aws_iam_role_policy_attachment" "deploy_policy" {
  role       = aws_iam_role.github_actions.name
  policy_arn = "arn:aws:iam::aws:policy/AmazonECS_FullAccess"
}

Exemple avec AWS SAM

template.yaml
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

Pour déployer avec SAM :

sam build
sam deploy --guided

Après le déploiement initial, enregistrez l'ARN du rôle de sortie dans les GitHub Secrets sous AWS_ROLE_ARN.

3. Attacher des politiques de permissions

Attachez les permissions nécessaires au déploiement au rôle. Par exemple :

  • Pour le déploiement S3 : AmazonS3FullAccess
  • Pour le déploiement ECS : AmazonECS_FullAccess
  • Pour Lambda : AWSLambda_FullAccess

Pour les environnements de production, nous recommandons de créer des politiques personnalisées basées sur le principe du moindre privilège.

Configuration GitHub Actions

Création d'un fichier workflow

Créez .github/workflows/deploy.yml.

name: Deploy to AWS

on:
  push:
    branches:
      - main

# Paramètres de permissions pour obtenir le token OIDC (Important !)
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

Points importants

1. Configuration des permissions

permissions:
  id-token: write  # Requis pour obtenir le token OIDC
  contents: read   # Requis pour checkout le dépôt

Sans ce paramètre, vous ne pourrez pas obtenir le token OIDC et obtiendrez une erreur.

2. Version de l'action configure-aws-credentials

Utilisez la version v4 ou ultérieure. Les versions plus anciennes peuvent ne pas supporter OIDC.

uses: aws-actions/configure-aws-credentials@v4

Exemple pratique : Déployer une application SAM

Voici un exemple de déploiement d'une application serverless utilisant 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

Exemple pratique : Déploiement sur ECS

Voici un exemple plus pratique de workflow de déploiement 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

Résolution des problèmes

Erreur : "Not authorized to perform sts:AssumeRoleWithWebIdentity"

Cause : Les conditions de la politique de confiance ne correspondent pas

Solution :

  1. Vérifier la valeur token.actions.githubusercontent.com:sub dans la politique de confiance du rôle IAM
  2. Vérifier que le nom du dépôt et le nom de la branche sont corrects
  3. Vérifier la valeur réelle du claim sub dans les logs 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'

Erreur : "Error: Credentials could not be loaded"

Cause : Configuration permissions manquante

Solution : Ajoutez ce qui suit à votre fichier workflow

permissions:
  id-token: write
  contents: read

L'authentification réussit mais des erreurs de permissions surviennent

Cause : Les politiques de permissions requises ne sont pas attachées au rôle IAM

Solution : Attachez les politiques appropriées au rôle IAM

aws iam attach-role-policy \
  --role-name github-actions-deploy-role \
  --policy-arn arn:aws:iam::aws:policy/AmazonS3FullAccess

Bonnes pratiques de sécurité

1. Principe du moindre privilège

Accordez uniquement les permissions minimales nécessaires.

{
  "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. Restreindre par branche ou tag

Restreindre les déploiements en production à des branches spécifiques uniquement.

{
  "Condition": {
    "StringEquals": {
      "token.actions.githubusercontent.com:sub": "repo:your-org/your-repo:ref:refs/heads/main"
    }
  }
}

3. Séparer les rôles par environnement

Utiliser des rôles différents pour le développement, la pré-production et la production.

- 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. Audit avec CloudTrail

Enregistrer tous les appels API et les examiner régulièrement.

# S3 bucket pour 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]
}

Conclusion

L'intégration OIDC entre GitHub Actions et AWS offre les avantages suivants :

  • Déploiement sécurisé sans clés d'accès
  • Utilise uniquement des identifiants temporaires
  • Contrôle fin des permissions par dépôt ou branche
  • Réduction des coûts de gestion

Bien que la configuration initiale soit quelque peu complexe, une fois configurée, elle conduit à des améliorations à long terme en matière de sécurité et d'exploitabilité. Je recommande fortement d'envisager son adoption.

Références