Moving Away From AWS Access Keys

Following Amazon IAM best practices for AWS credentials

I started using GitHub Actions to handle the Hugo build and deployment of this website. I was using a dedicated AWS IAM user with the required policies and added their Access Key ID and Secret Access Key as repository secrets. The relevant step in my GitHub action looked like this:

      - name: Configure AWS Credentials
        uses: aws-actions/configure-aws-credentials@v2
        with:
          aws-access-key-id: ${{ secrets.AWS_ACCESS_KEY_ID }}
          aws-secret-access-key: ${{ secrets.AWS_SECRET_ACCESS_KEY }}
          aws-region: eu-west-2

Every 90 days I would have to generate new keys in IAM and then update the repository secrets. What if I didn’t have to do this? The answer is to configure an Identity Provider for GitHub in AWS IAM and then use OpenID Connect to authenticate in the GitHub workflow. You can add GitHub as an Identity Provider with a single CLI command:

$ aws iam create-open-id-connect-provider --url "https://token.actions.GitHubusercontent.com" \
  --thumbprint-list "6938fd4d98bab03faadb97b34396831e3780aea1" \
  --client-id-list "sts.amazonaws.com"

Next create a role for GitHub to assume, with the required trust relationships. For example:

{
    "Version": "2012-10-17",
    "Statement": [
        {
            "Effect": "Allow",
            "Principal": {
                "Federated": "arn:aws:iam::123456789ABC:oidc-provider/token.actions.githubusercontent.com"
            },
            "Action": "sts:AssumeRoleWithWebIdentity",
            "Condition": {
                "StringEquals": {
                    "token.actions.githubusercontent.com:sub": "repo:myorg/example.com:ref:refs/heads/main",
                    "token.actions.githubusercontent.com:aud": "sts.amazonaws.com"
                }
            }
        }
    ]
}

Then add the required permissions policy:

{
  "Version": "2012-10-17",
  "Statement": [
    {
          "Sid": "myGitHubActionsPolicy",
          "Effect": "Allow",
          "Action": [
              "s3:GetBucketPolicy",
              "s3:PutBucketPolicy",
              "s3:ListBucket",
              "s3:PutObject",
              "s3:DeleteObject",
              "cloudfront:CreateInvalidation"
          ],
          "Resource": [
              "arn:aws:s3:::example.com",
              "arn:aws:s3:::example.com/*",
              "arn:aws:cloudfront::123456789ABC:distribution/CLOUDFRONTID01"
          ]
      }
  ]
}

Finally, add a permissions stanza to the workflow to allow the workflow to write the identity token and update the ‘Configure AWS Credentials’ step. The full workflow should look something like this:

name: Build and Deploy example.com

on:
  push:
    branches: [ "main" ]

  workflow_dispatch:

jobs:
  build:
    runs-on: ubuntu-latest

    permissions:
      id-token: write
      contents: read

    steps:
      - uses: actions/checkout@v3

      - name: Configure AWS Credentials
        uses: aws-actions/configure-aws-credentials@v2
        with:
          role-to-assume: arn:aws:iam::123456789ABC:role/myGitHubActionsRole
          role-session-name:
          aws-region: eu-west-2

      - name: Setup Hugo
        uses: peaceiris/actions-hugo@v2
        with:
          hugo-version: '0.111.3'

      - name: Build website
        run: hugo --minify --verbose

      - name: Deploy to S3
        run: hugo deploy --maxDeletes -1 --invalidateCDN --verbose

Once this is tested and working the IAM user and the associated repository secrets can be deleted.