Home About

Published

- 4 min read

Stop using IAM credentials with GitHub Actions! OIDC FTW!

aws security terraform
img of Stop using IAM credentials with GitHub Actions! OIDC FTW!

Today I came across a very interesting post on Reddit. The thread here mentions about not using long lived credentials with GitHub Actions when accessing into AWS.

This is actually very important because if the credential stored on GitHub ever gets compromised, we must manually rotate and update all the secrets. This becomes even more troublesome if you are managing hundreds of such credentials!

I got very intrigued and so I started looking into this and made changes to my personal GitHub and AWS project for this blog. So here is how I did it.

Using OpenID Connect (OIDC)

OIDC on AWS allows us to configure an identity provider for GitHub. Through this, we no longer need to have a dedicated API user which we generate and store the long lived AWS secrets.

All we need is:

  1. Create the identity provider on AWS IAM for GitHub Actions.
  2. Create a role for GitHub Actions to assume into.
  3. Create an IAM policy for the role. Here you will specify what the role is allowed to do.
  4. Create an assume role policy for the role. Here we are restricting it so only our GitHub account and repo is allowed to use this role.

To learn more on this topic, check out the GitHub’s documentation here: https://docs.github.com/en/actions/deployment/security-hardening-your-deployments/configuring-openid-connect-in-amazon-web-services

How I achieved this using Terraform

As always, everything I do is based on IaC, so here I will be show casing the Terraform code I used.

Note: Terraform AWS provider 4.2 or greater is required.

my_vars.auto.tfvars

Here are the variables I am passing into Terraform

   github_actions_url  = "https://token.actions.githubusercontent.com"
github_source_repo  = "88lexd/lexd-solutions"
aws_region          = "ap-southeast-2"

oidc_sample.tf

Note: All the code here is placed in one file for this demo simplicity.

   ##########################
# Terraform configuration
terraform {
  required_providers {
    aws = {
      source  = "hashicorp/aws"
      version = "~> 4.0"
    }
  }
}

provider "aws" {
  region = var.aws_region
}


###########
# Variables
variable "github_actions_url" {
  description = "The GitHub Actions URL for OIDC"
  type        = string
}

variable "github_source_repo" {
  description = "The source repo for OIDC using format <accountname>/<repo_name>"
  type        = string
}

variable "aws_region" {
  description = "AWS region to create these resources"
  type        = string
}


##############
# Data sources
data "tls_certificate" "github_actions" {
  url = var.github_actions_url
}


###################
# IAM Configuration
resource "aws_iam_openid_connect_provider" "github_oidc" {
  url             = var.github_actions_url
  client_id_list  = ["sts.amazonaws.com"]
  thumbprint_list = [data.tls_certificate.github_actions.certificates[0].sha1_fingerprint]
}

resource "aws_iam_role" "github_oidc_role" {
  name = "api_github_oidc"

  inline_policy {
    name = "allow_github_put_s3_object"
    policy = jsonencode({
      Version = "2012-10-17"
      Statement = [
        {
          Action = "s3:PutObject"
          Effect = "Allow"
          Resource = [
            "${aws_s3_bucket.lambda_s3_bucket.arn}",
            "${aws_s3_bucket.lambda_s3_bucket.arn}/*",
            "${aws_s3_bucket.codedeploy_s3_bucket.arn}",
            "${aws_s3_bucket.codedeploy_s3_bucket.arn}/*"
          ]
        }
      ]
    })
  }

  assume_role_policy = jsonencode({
    Version = "2012-10-17"
    Statement = [
      {
        Action = "sts:AssumeRoleWithWebIdentity"
        Effect = "Allow"
        Principal = {
          "Federated" : "${aws_iam_openid_connect_provider.github_oidc.arn}"
        }
        Condition = {
          "StringLike" : {
            "token.actions.githubusercontent.com:sub" : "repo:${var.github_source_repo}:*"
          }
        }
      }
    ]
  })
}

The most important part here is how we are allowing the role to be assumed from the OIDC provider and further restricting it so only our GitHub account and repository is allowed to use this role.

We can actually further lock down the role so only a specific repo and branch can be used. For example:

   # Replaced <GITHUB_ACCOUNT> with your GitHub account and <REPO_NAME> with the name of your repository.
Condition = {
  "StringLike" : {
    "token.actions.githubusercontent.com:sub: repo:<GITHUB_ACCOUNT>/<REPO_NAME>:ref:refs/heads/main"
  }
}

GitHub Actions workflow

This is actually very simple! All we need to do is call the action “aws-actions/configure-aws-credentials@v1” in our workflow. Here is what I have in my personal workflow to upload my Lambda source code onto S3.

   name: CI for Lambda to S3 for Jumpbox Uptime

on:
  push:
    branches: [ main ]
    paths:
    - 'aws-wordpress/3-app-configuration/lambda-jumpbox-uptime/src/**'
  pull_request:
    branches: [ main ]
    paths:
    - 'aws-wordpress/3-app-configuration/lambda-jumpbox-uptime/src/**'

  # Allows workflow to run manually from the Actions tab
  workflow_dispatch:

jobs:
  build:
    runs-on: ubuntu-latest

    # These permissions are needed to interact with GitHub's OIDC Token endpoint.
    permissions:
      id-token: write
      contents: read

    steps:
      - name: Checkout
        uses: actions/checkout@v2

      - name: Configure AWS credentials using OIDC
        uses: aws-actions/configure-aws-credentials@v1
        with:
          role-to-assume: ${{ secrets.AWS_GITHUB_ACTIONS_ROLE_ARN }}
          aws-region: ap-southeast-2

      - name: zip main.py in src/ to current workspace dir and upload to S3
        run: |
          CURRENT_DIR=$(pwd)
          mkdir artifact
          cd ./aws-wordpress/3-app-configuration/lambda-jumpbox-uptime/src/
          zip "${CURRENT_DIR}/artifact/lambda-jumpbox-uptime.zip" main.py
          aws s3 cp "${CURRENT_DIR}/artifact/lambda-jumpbox-uptime.zip" s3://lexd-solutions-lambdas/lambda-jumpbox-uptime.zip

Conclusion

This is a very nice and secure way in managing access into AWS from GitHub Actions and I highly recommend it! It was actually much easier to configure and setup than I initially thought.

If you would like to see the actual source code I have, feel free to check out my GitHub repo.