GitHub with AWS: No Access Keys Needed
You can setup GitHub Actions to use your AWS resources without using static credentials (keys and secret keys)! This is a LOT more secure, as leaked static credentials are a major attack vector.
What we do is setup OpenID Connect, which lets GitHub request temporary credentials from your AWS account and "assume" a Role you setup within your account.
Here's what it takes:
- Register GitHub as an OIDC identity provider in your AWS account
- Create a Role with permissions (Policies) needed by your GitHub Actions setup
- Set the roles Trust Policy to allow GitHub Actions within your Repository to assume that role and thus take on its permissions
- Add a few lines to your GitHub Action yaml file(s)
Let's see what it looks like!
Register a GitHub OIDC Provider
Within your AWS account, head to IAM and choose Identity Providers from the side-menu.
Create a new Identity Provider and setup a new OIDC Connect provider using the following details:
- Provider URL:
https://token.actions.githubusercontent.com
- Audience:
sts.amazonaws.com
Create a Role
We next need a role for our GitHub Action workflows to assume. There's nothing special here - we'll create a Role, name it something, and attach some policies to it.
What those policies allow depends completely on your needs. For example, if you need GitHub actions to push container images to ECR, then you'll need to attach policies to this role allowing that.
Set the Role's Trust Policy
The most important part of your new role is the Trust Policy.
A Trust Policy tell AWS who is allowed to assume (to take on its permissions) a given role. We want GitHub Actions to be allowed to assume the role when taking action in our GitHub repository! To do that, the Trust Policy for the role we created would look like this:
1{ 2 "Version": "2012-10-17", 3 "Statement": [ 4 { 5 "Effect": "Allow", 6 "Principal": { 7 "Federated": "arn:aws:iam::YOUR_AWS_ACCOUNT_ID:oidc-provider/token.actions.githubusercontent.com" 8 }, 9 "Action": "sts:AssumeRoleWithWebIdentity",10 "Condition": {11 "StringLike": {12 "token.actions.githubusercontent.com:sub": "repo:your-org/your-repo:*"13 },14 "StringEquals": {15 "token.actions.githubusercontent.com:aud": "sts.amazonaws.com"16 }17 }18 }19 ]20}
The Principal is of type Federated and is basically the same string you see above, but with your AWS account ID within it (replace YOUR_AWS_ACCOUNT_ID
).
It's actually the arn
of the OIDC Identity Provider we registered in the steps above - you can find it there after registering the identity provider.
Behind the scenes, JWT's are being used - so we need to set a sub
(subject) and (optionally, I believe) an aud
(audience). The rest of the JWT token generation/authentication is done by AWS and GitHub.
The Condition
section is where we set the sub
and aud
. The sub
says what GitHub repository can assume our role. You only want to allow YOUR repository to assume this role!
The pattern for the sub
value is repo:<your-org-or-username>/<your-repo-name>:<environment>
. The "environment" isn't always a feature used in GitHub (it's a repository setting), so you can use a wildcard like in our above example.
If you do NOT use any wildcards, and want an exact match, you'll want to use StringEquals
instead of StringLike
in that Condition.
Update your GitHub Action Workflows
Once that is all set, we're ready to update our GHA workflow and have it assume our AWS role. Once the role is assumed, we can then perform actions against our AWS resources allowed by that role.
What we need to do is add a few settings and then use the official GitHub action to setup AWS credentials.
1name: some-name 2 3on: 4 push: 5 branches: 6 - '**' 7 8# We need this for GHA + IAM OIDC 9permissions:10 id-token: write # This is required for requesting the JWT11 contents: read # This is required for actions/checkout12 13jobs:14 do-something:15 name: do-something16 run-on: ubuntu-latest17 18 steps:19 20 - uses: actions/checkout@v421 22 # Use this action to assume the given role via OIDC Connect23 - name: Configure AWS dev credentials24 uses: aws-actions/configure-aws-credentials@v425 with:26 role-to-assume: "arn:aws:iam::ACCOUNT_ID_HERE:role/some-role"27 role-session-name: "gha-doing-something-this-is-arbitrary"28 aws-region: "us-east-1"29 30 # Continue on and use AWS things!31 - run: aws sts get-caller-identity
You may want to set your region, role ARN, and other settings as secrets or variables in your repository. Either way, you should be on your way to using GitHub Actions with your AWS account(s) without needing to generate and manage static credentials!
Terraform
Here's what this looks like Terraform/OpenTofu! We do the same steps as above:
- Register the OIDC identity provider
- Create a Role with a Trust Policy that allows our repo's GitHub Action's to assume it (via OIDC)
- Add some permissions to the role (by attaching a policy we create for our needs)
1############################################### 2# Create the OIDC identity provider for GitHub 3############################################### 4locals { 5 # This is essentially a hard-coded value, but you 6 # can programmatically get it via data.tls_certificate: 7 # https://registry.terraform.io/providers/hashicorp/tls/latest/docs/data-sources/certificate 8 github_thumbprint = "6938fd4d98bab03faadb97b34396831e3780aea1" 9}10 11resource "aws_iam_openid_connect_provider" "github" {12 client_id_list = [13 "sts.amazonaws.com",14 ]15 thumbprint_list = [local.github_thumbprint]16 url = "https://token.actions.githubusercontent.com"17}18 19###############################################20# Create a role21###############################################22locals {23 github_repo = "my_org/my_repo"24}25 26# role permission policy document (to create the JSON)27# this allows GHA to assume the role via the above28# OIDC we created29data "aws_iam_policy_document" "role_trust_policy" {30 statement {31 actions = ["sts:AssumeRoleWithWebIdentity"]32 effect = "Allow"33 34 condition {35 test = "StringLike"36 values = [37 "repo:${local.github_repo}:*",38 ]39 variable = "token.actions.githubusercontent.com:sub"40 }41 42 condition {43 test = "StringEquals"44 values = ["sts.amazonaws.com"]45 variable = "token.actions.githubusercontent.com:aud"46 }47 48 principals {49 identifiers = [aws_iam_openid_connect_provider.github.arn]50 type = "Federated"51 }52 }53}54 55# Create the role with the above trust policy56resource "aws_iam_role" "some_role" {57 name = "my-gha-role"58 assume_role_policy = data.aws_iam_policy_document.role_trust_policy.json59}60 61###############################################62# Create a policy (as needed) and attach it63# to the role we created above64###############################################65 66# role policy document (to create the JSON)67data "aws_iam_policy_document" "some_policy_document" {68 version = "2012-10-17"69 70 # ⚠️ Adjust this as needed for your use case71 statement {72 effect = "Allow"73 actions = ["xxx:SomePermissions"]74 resources = ["some-resource-arn"]75 }76}77 78# create the policy79resource "aws_iam_policy" "some_policy" {80 name = "my_gha_policy"81 policy = data.aws_iam_policy_document.some_policy_document.json82}83 84# attach the policy to our role85resource "aws_iam_role_policy_attachment" "github_ecr" {86 policy_arn = aws_iam_policy.some_policy.arn87 role = aws_iam_role.some_role.name88}
Overall, not too bad!
AWS is complex. Sign up for free, useful lessons like this.