Say Goodbye to Hardcoded AWS Creds in GitHub Actions with OIDC Magic ⭐
Tags: github, aws, devops, beginners
Published: 2023-12-27 12:12:08

I've been tinkering with GitHub actions over the last few months, often times with some kind of AWS interaction in my workflow. As it turns out, there are simple and less simple methods of authenticating your workflows with AWS. Let's explore how to use GitHub's OIDC provider to free yourself from hardcoded AWS credentials.

## Hardcoded access keys

Lets be real, we've all hardcoded access keys before. For beginners and people who are just looking to get something running, hardcoded credentials are great, it's the most straightforward way to authenticate your workflow with AWS.

Here's a typical example:

```
- name: Configure AWS credentials
  uses: aws-actions/configure-aws-credentials@v4
  with:
    aws-access-key-id: ${{ secrets.AWS_ACCESS_KEY_ID }}
    aws-secret-access-key: ${{ secrets.AWS_SECRET_ACCESS_KEY }}
    aws-region: us-east-1
```

However, this does not align with best practices, duplicating and storing long lived credentials for your AWS account is not the most secure way to authenticate your workflows.

## Enter OpenID Connect (OIDC)

OIDC, or OpenID Connect, is an authentication protocol that lets a third party (in our case, AWS) verify a user's identity through a JSON Web Token (JWT). With OIDC, authentication is outsourced to the OIDC provider. Once authenticated, the user can obtain a JWT from the identity provider, using it to authenticate with the third party.


![Image description](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/wnleozfeej5hjhpvdbsa.png)

In our scenario, GitHub acts as an OIDC provider for workflows. This means our workflow can request a JWT from GitHub and then use it to assume an AWS role by obtaining temporary credentials from AWS.

## How to use GitHub's OIDC provider in your workflows

Let's walk through a simple example that builds a Docker image and pushes it to ECR using GitHub's OIDC provider. If you want to follow along, I've prepared a NodeJS application and Docker file in this following repository:

https://github.com/Nathan-Bridgewater/GitHub-AWS-OIDC.git

### 1. Create an identity provider in AWS

Firstly, we add GitHub as an identity provider. This configures AWS to accept OIDC tokens from a workflow and return temporary access credentials.
```
aws create-open-id-connect-provider \
--url https://token.actions.githubusercontent.com  \
--client-id-list sts.amazonaws.com \
--thumbprint-list 1b511abead59c6ce207077c0bf0e0043b1382612 \
```

### 2. Create the trust policy

Craft a JSON file containing a trust policy for an AWS role, specifying which GitHub workflows can assume our role.

**trust-policy.json**
```
{
  "Version": "2012-10-17",
  "Statement": [
      {
          "Effect": "Allow",
          "Action": "sts:AssumeRoleWithWebIdentity",
          "Principal": {
              "Federated": "arn:aws:iam::<ACCOUNT NUM>:oidc-provider/token.actions.githubusercontent.com"
          },
          "Condition": {
              "StringEquals": {
                  "token.actions.githubusercontent.com:aud": [
                      "sts.amazonaws.com"
                  ]
              },
              "StringLike": {
                  "token.actions.githubusercontent.com:sub": [
                      "repo:Nathan-Bridgewater/GitHub-AWS-OIDC:*"
                  ]
              }
          }
      }
  ]
}
```
### 3. Create the role

We need to establish a role for our federated workflow to assume, and attach a policy allowing our workflow to push images to ECR.
```
aws iam create-role \
--role-name github-workflow-role \
--assume-role-policy-document file://trust-policy.json 

aws iam attach-role-policy \
--role-name github-workflow-role \
--policy-arn arn:aws:iam::aws:policy/AmazonEC2ContainerRegistryPowerUser
```

### 4. Create an ECR repository

Set up an ECR repository to push Docker images.
```
aws ecr create-repository \
--repository-name nodejs-images
```
### 5. Create the GitHub workflow

**push-docker-actions.yaml**
```
name: push-docker-ecr-actions
run-name: ${{ github.actor }} is running the push-docker-ecr workflow
on:
  push:
    branches:
      - 'main'

permissions:
  id-token: write
  contents: read  
  
jobs:
  build-image-push-to-ecr:
    name: Build docker image and push to ecr
    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::<ACCOUNT NUM>:role/github-workflow-role
          aws-region: us-east-1
      - name: Login to Amazon ECR
        id: login-ecr
        uses: aws-actions/amazon-ecr-login@v1
      - name: Build, tag and push image to ECR
        env:
          ECR_REGISTRY: ${{ steps.login-ecr.outputs.registry }}
          ECR_REPOSITORY: nodejs-images
          IMAGE_TAG: latest
        run: |
          docker build -t $ECR_REGISTRY/$ECR_REPOSITORY:$IMAGE_TAG .
          docker push $ECR_REGISTRY/$ECR_REPOSITORY:$IMAGE_TAG
```

### 6. Test it out

Once added, your workflows should now authenticate with AWS without hardcoded credentials. Freedom awaits!