DEV Community

Cover image for GitHub Actions authenticating on AWS without secrets using OIDC with Terraform
Mateus Miranda
Mateus Miranda

Posted on • Edited on

GitHub Actions authenticating on AWS without secrets using OIDC with Terraform

NOTE: For Portuguese readers: you can find a translated version here.

GitHub recently launched a new feature to authenticate via oidc on AWS from the actions workflows, giving us the chance to finally get rid of the process of managing a whole user specific for that interaction.

Basically, the auth now happens using OIDC and the only thing you need for that is to set up a role on AWS side and pass that info in your workflow.

In this article we will setup everything needed on AWS using Terraform, and of course, we will see how it works.

Terraform

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

Let's start creating our oidc provider:

resource "aws_iam_openid_connect_provider" "github" {
  url             = "https://token.actions.githubusercontent.com"
  client_id_list  = ["sts.amazonaws.com"]
  thumbprint_list = ["6938fd4d98bab03faadb97b34396831e3780aea1"]
}
Enter fullscreen mode Exit fullscreen mode

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

The thumbprint_list was more tricky to understand since it's generated based on the certificate's ssl key of the openid from Github. What matters here is: this value is based on the url, so this is static and you can just copy & paste without any hassle.

In case you wanna go deeper in this topic, read a bit Obtaining the thumbprint for an OpenID IdP and also this Changelog.

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:org1/*:*",
        "repo:org2/*:*"
      ]
    }
  }
Enter fullscreen mode Exit fullscreen mode

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

It's possible to restrict this permission to allow only certains repositories (or even specific branches), in case you wanna try that you just need to change your condition block to:

    condition {
      test     = "StringLike"
      variable = "token.actions.githubusercontent.com:sub"
      values = [
        "repo:org/my-repo:*",
      ]
    }
Enter fullscreen mode Exit fullscreen mode

Another important thing to notice here is the value of token.actions.githubusercontent.com:aud which I'm using as sts.amazonaws.com and not as specified on Github Documentation

Following our tutorial, 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
}
Enter fullscreen mode Exit fullscreen mode

Now we need to create another policy document, but this time it will contain permissions for the Github Actions.

In our case, 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"]
    }
  }
Enter fullscreen mode Exit fullscreen mode

Please notice that in our example we are using ECR but nothing blocks you from give any other permission to other AWS services: S3, SQS, etc.

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
}
Enter fullscreen mode Exit fullscreen mode

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

resource "aws_ecr_repository" "repo" {
  name                 = "meu/repositorio"
  image_tag_mutability = "IMMUTABLE"

  image_scanning_configuration {
    scan_on_push = true
  }

  tag = {
    "permit-github-action" = true
  }
}
Enter fullscreen mode Exit fullscreen mode

Github Actions

Basically the configuration on the Github Actions side is quite simple and it does not require too much effort on your workflows if you are already authenticating using access key and secret keys.

The first thing you need to setup, according to Github Documentation is the permissions:

    permissions:
      id-token: write
      contents: read
Enter fullscreen mode Exit fullscreen mode

And after, on the step you use to configure the credentials, you need to inform the role we created with terraform.

      - name: Configure AWS credentials
        uses: aws-actions/configure-aws-credentials@master
        with:
          role-to-assume: arn:aws:iam::XXXXXXXXXXXX:role/github-actions
          aws-region: eu-west-1

      - name: Login to Amazon ECR
        id: login-ecr
        uses: aws-actions/amazon-ecr-login@v1
Enter fullscreen mode Exit fullscreen mode

And here you can check the final content of the YAML file.

name: Continuous Delivery

on: [push, pull_request]

jobs:
  build-and-push:
    runs-on: ubuntu-latest

    permissions:
      id-token: write
      contents: read

    steps:
      - name: Configure AWS credentials
        uses: aws-actions/configure-aws-credentials@master
        with:
          role-to-assume: arn:aws:iam::XXXXXXXXXXXX:role/github-actions
          aws-region: eu-west-1

      - name: Login to Amazon ECR
        id: login-ecr
        uses: aws-actions/amazon-ecr-login@v1

      - name: Checkout
        uses: actions/checkout@v2

      - name: Build, tag, and push image to Amazon ECR
        env:
          ECR_REGISTRY: ${{ steps.login-ecr.outputs.registry }}
          ECR_REPOSITORY: my/repo
          IMAGE_TAG: ${{ github.sha }}
        run: |
          docker build -t $ECR_REGISTRY/$ECR_REPOSITORY:$IMAGE_TAG .
          docker push $ECR_REGISTRY/$ECR_REPOSITORY:$IMAGE_TAG
Enter fullscreen mode Exit fullscreen mode

I hope you enjoyed this article! :)

Top comments (5)

Collapse
 
adj009 profile image
adj009

thank you....this was really helpful...

one change that was recently published by Github was related to the OIDC Thumbprint - github.blog/changelog/2022-01-13-g...

Collapse
 
mmiranda profile image
Mateus Miranda

Thank you for pointing this out!

Collapse
 
tomontheinternet profile image
Thomas Steven

Great article! Thanks!
Really made things clear.
Got it working in no time.

Collapse
 
nwhobart profile image
Nick Hobart

Great article. Followed this pretty closely to get up and running really quickly. You may want to add the following action:

ecr:GetAuthorizationToken

Without it I wasn't able to run my basic action. It showed up in the debug console.

Collapse
 
nbari profile image
nbari

Thanks for sharing, when creating the registry, you need to use tags instead tag, example:

tags = {
    "permit-github-action" = true
 }
Enter fullscreen mode Exit fullscreen mode