Connecting Github Actions to AWS S3 using OIDC
Oct 26, 2021
Typically, connecting Github Actions to AWS requires storing an AWS Access Key ID and Secret Access Key as environment variables. A more secure way is to use federated access, or in other words, provision something in AWS that tells it to trust calls coming from your Github Actions.
Inspired by Aidan Steele’s AWS federation comes to GitHub Actions , I want to show how to achieve the same result with Terraform. In addition, this also shows how to attach some permissions to allow your Github Actions to upload files to S3.
This guide assumes you are familiar with Terraform, Github, and AWS.
Step 1.0: Set up your backend and AWS provider
This first step is a bit opinionated. If you know what you’re doing when setting up your Terraform backend and AWS provider, feel free to skip this step. Note in this example to fill in the following placeholder values:
<state bucket name goes here>
- The name of the S3 bucket that contains your Terraform state.
<your AWS region>
- The AWS region in which you deploy your infrastructure. For instance, I use
us-east-1
.
- The AWS region in which you deploy your infrastructure. For instance, I use
resource "aws_s3_bucket" "terraform_state" {
bucket = "<state bucket name goes here>"
acl = "private"
versioning {
enabled = true
}
server_side_encryption_configuration {
rule {
apply_server_side_encryption_by_default {
sse_algorithm = "AES256"
}
}
}
}
terraform {
required_providers {
# https://registry.terraform.io/providers/hashicorp/aws/latest/docs
aws = {
source = "hashicorp/aws"
version = "3.63.0"
}
}
backend "s3" {
bucket = "<state bucket name goes here>"
key = "terraform/tfstate"
region = "<your AWS region>"
}
}
# Access key id and secret access key are pulled in from AWS environment variables
# that are set using `aws configure`.
provider "aws" {
# alternatively, `export AWS_DEFAULT_REGION="<your AWS region>"` in your shell environment
region = "<your AWS region>"
}
Step 2.0: Create the requisite AWS components with Terraform
In this step, put the following code into a file called oidc.tf
. Note that this step assumes you already have one or two S3 buckets; the code snippets here will not create them.
You will create:
aws_iam_openid_connect_provider.github
- This component lets AWS trust Github Actions initiated from your repo.
aws_iam_policy_document.github_actions
- This policy document is the
assume_role_policy
for the role assumed by Github Actions.
- This policy document is the
aws_iam_policy_document.github_s3_upload_policy
- This policy document is attached to the role assumed by Github Actions. In this example, it permits read & write actions on the buckets specified in the
local.s3_buckets
variable.
- This policy document is attached to the role assumed by Github Actions. In this example, it permits read & write actions on the buckets specified in the
aws_iam_role.github_actions
- This is the AWS role that can be assumed from within Github Actions.
Remember to fill in the following placeholder values:
<your github organization>
- This value is your Github account name.
<your github repo>
- This is the repository from which your Github Actions will execute.
<your bucket name here>
- This is the S3 bucket with which Github Actions will interact.
<another bucket name if you have multiple>
- This code snippet supports allowing Github Actions to interact with multiple S3 buckets, but leave this out if you’re only using one bucket.
locals {
github_organization = "<your github organization>"
github_repo = "<your github repo>"
s3_buckets = ["<your bucket name here>", "<another bucket name if you have multiple>"]
}
resource "aws_iam_openid_connect_provider" "github" {
url = "https://token.actions.githubusercontent.com"
client_id_list = [
"https://github.com/${local.github_organization}",
"sigstore" # https://github.com/aws-actions/configure-aws-credentials/issues/271#issuecomment-931272443
]
thumbprint_list = ["a031c46782e6e6c662c2c87c76da9aa62ccabd8e"]
}
data "aws_iam_policy_document" "github_actions" {
statement {
actions = [
"sts:AssumeRoleWithWebIdentity",
]
principals {
type = "Federated"
identifiers = [aws_iam_openid_connect_provider.github.arn]
}
condition {
test = "StringLike"
variable = "token.actions.githubusercontent.com:sub"
values = [
"repo:${local.github_organization}/${local.github_repo}:*"
]
}
}
}
data "aws_iam_policy_document" "github_s3_upload_policy" {
dynamic "statement" {
for_each = local.s3_buckets
content {
actions = [
"s3:Get*",
"s3:List*",
"s3:Put*"
]
resources = [
"${statement.value}",
"${statement.value}/*",
]
}
}
}
resource "aws_iam_role" "github_actions" {
name = "github-actions"
assume_role_policy = data.aws_iam_policy_document.github_actions.json
inline_policy {
name = "github-s3-upload-policy"
policy = data.aws_iam_policy_document.github_s3_upload_policy.json
}
}
Step 3.0: Creating a Github workflow to upload files to S3
Create a file at this location in your Git repo: .github/workflows/s3_workflow.yml
with the following contents. Remember to change the following placeholder values:
<your AWS account id>
- The 12-digit identifier for your AWS account.
<your AWS region>
- The AWS region in which you deploy your infrastructure. For instance, I use
us-east-1
.
- The AWS region in which you deploy your infrastructure. For instance, I use
<your branch name>
- The branch for which a
git push
will trigger an S3 upload
- The branch for which a
<source directory of files to upload>
- The directory to upload to S3, relative to the root directory of your Git repo.
<your bucket name here>
- This is the S3 bucket with which Github Actions will interact.
<another bucket name if you have multiple>
- This code snippet supports allowing Github Actions to interact with multiple S3 buckets, but leave this out if you’re only using one bucket.
<another source directory of files to upload>
- The directory to upload to S3, relative to the root directory of your Git repo.
- Leave this out if you’re only using one bucket.
name: Upload sites to S3
on:
push:
branches:
- <your branch name>
jobs:
deploy:
runs-on: ubuntu-latest
permissions:
id-token: write
contents: read
steps:
- name: Checkout
uses: actions/checkout@v1
- name: Configure AWS Credentials
# https://github.com/aws-actions/configure-aws-credentials/issues/271#issuecomment-931272443
uses: aws-actions/configure-aws-credentials@b8c74de753fbcb4868bf2011fb2e15826ce973af
with:
aws-region: <your AWS region>
role-to-assume: arn:aws:iam::<your AWS account id>:role/github-actions
role-session-name: github-actions
- name: Upload files to <your bucket name here>
run: aws s3 sync <source directory of files to upload> s3://<your bucket name here>
- name: Upload files to <another bucket name if you have multiple>
run: aws s3 sync <another source directory of files to upload> s3://<another bucket name if you have multiple>
Step 4.0: Try it out!
If everything went well, you should now be able to sync files to S3 after running git push
, and you don’t have to store any AWS access credentials as environment variables.
Summary
In this guide, you learned how to:
- Create an AWS OIDC provider to allow Github Actions to assume an AWS role in your account
- Create a role that has permissions to read and write files in one or more S3 buckets
- Set up a Github workflow to sync files from a directory in your repo to a bucket in S3 on every push to a particular branch
Next Steps
You can give Github Actions access to do anything in AWS as long as you attach the appropriate permissions to the role that Github Actions assumes. With this guide as a foundation, you can expand further and create a tighter integration between your Github repositories and AWS.