GitHub Actions without AWS credentials


Use GitHub Actions without the need to share AWS credentials as secrets

 

Many times when we need to connect to AWS through GitHub Actions, the first thing that comes to mind is to take the access credentials of an IAM user that we have created, and use them as environment variables in our workflow file, in order to authenticate a user in AWS. But this method is not the most secure, as we need to hand over our AWS credentials.

Luckily for us, there is another method that we can use to authenticate to AWS through GitHub Actions without using our credentials. You can assume an IAM role.


Let's get started!

Identity provider

First, add GitHub as OpenID provider to IAM Identity providers. This connects AWS and GitHub, so they can exchange tokens.

Provider URL: https://token.actions.githubusercontent.com

Audience: sts.amazonaws.com



IAM role

Instead of a user, you have to create a role with a trust relationship. It’s a relationship between the role and the added GitHub identity provider.


Press Next to add permissions.



Once the IAM Role is created, the last step is changing the trust relationship condition to restrict usage of the role.

Click on “Edit trust relationship” to start editing.

And add:

..

"Condition": {

"StringLike": { "token.actions.githubusercontent.com:sub":"repo:organization/repository:*"

}

}


Update the workflow file

Finally, we can update the workflow file. This is a simplified version of a workflow file:


name: Deploy

on:

push:

jobs:

deploy:

name: Deploy

runs-on: ubuntu-18.04

permissions:

id-token: write

contents: read

steps:

- uses: actions/checkout@v2

- name: Configure AWS Credentials

uses: aws-actions/configure-aws-credentials@v1

with:

role-to-assume: arn:aws:iam::111111111111:role/deploy-xyz.tech

aws-region: eu-central-1

- name: Build


What changed?


The secrets containing AWS credentials have been removed:


env:

AWS_ACCESS_KEY_ID: ${{ secrets.AWS_ACCESS_KEY_ID }}

AWS_SECRET_ACCESS_KEY: ${{ secrets.AWS_SECRET_ACCESS_KEY }}


And we replace it with this:


permissions:

id-token: write

contents: read

steps:

- uses: actions/checkout@v2

- name: Configure AWS Credentials

uses: aws-actions/configure-aws-credentials@v1

with:

role-to-assume: arn:aws:iam::111111111111:role/deploy-xyz.tech

aws-region: eu-central-1


Using Terraform

The configuration via Terraform is quite simple and it does not require any advanced knowledge.

Let's start creating our OpenID Connect provider:


resource "aws_iam_openid_connect_provider" "github" {

url = "https://token.actions.githubusercontent.com"

client_id_list = ["sts.amazonaws.com"]

thumbprint_list = ["6938fd4d98bab03faadb97b34396831e3780aea1"]

}



Both information url and client_id_list are given by GitHub in their documentation that you can read it here


The thumbprint_list it's generated based on the certificate's ssl key of the openid from Github. This value is based on the url, so this is static and you can just copy & paste without any hassle.


The next step is to create our policy document, setting permission to our repositories do assume role:

data "aws_iam_policy_document" "github_actions_assume_role" {

statement {

actions = ["sts:AssumeRoleWithWebIdentity"]

principals {

type = "Federated"

identifiers = [aws_iam_openid_connect_provider.github.arn]

}

condition {

test = "StringEquals"

variable = "token.actions.githubusercontent.com:aud"

values = ["sts.amazonaws.com"]

}

condition {

test = "StringLike"

variable = "token.actions.githubusercontent.com:sub"

values = [

"repo:xyz1/*:*",

"repo:xyz2/*:*"

]

}

}


In the example above, any repository from xyz1 or xyz2 have permissions sts:AssumeRoleWithWebIdentity therefore they can assume a role that we will create next.


We now need to finally create our role and associate it to the policy document previously created:


resource "aws_iam_role" "github_actions" {

name = "github-actions"

assume_role_policy = data.aws_iam_policy_document.github_actions_assume_role.json

}


After that, another policy document must be created, but this time it will contain permissions for the Github Actions.


We will have permission to do some ECR operations on AWS, respecting the only rule that our registry must have a Tag permit-github-action=true:


data "aws_iam_policy_document" "github_actions" {

statement {

actions = [

"ecr:BatchGetImage",

"ecr:BatchCheckLayerAvailability",

"ecr:CompleteLayerUpload",

"ecr:GetDownloadUrlForLayer",

"ecr:InitiateLayerUpload",

"ecr:PutImage",

"ecr:UploadLayerPart",

]

resources = ["*"]

condition {

test = "StringEquals"

variable = "aws:ResourceTag/permit-github-action"

values = ["true"]

}

}


Notice that in the example above we are using ECR but nothing blocks you from give any other permission to other AWS services.


And finally, we need to create our policy based on the policy document we created earlier and attach it to the role:


resource "aws_iam_policy" "github_actions" {

name = "github-actions"

description = "Grant Github Actions the ability to push to ECR"

policy = data.aws_iam_policy_document.github_actions.json

}

resource "aws_iam_role_policy_attachment" "github_actions" {

role = aws_iam_role.github_actions.name

policy_arn = aws_iam_policy.github_actions.arn

}



As a last step for the Terraform part, we need to create our registry and add a Tag to it: