GitHub Actions authenticating on AWS without secrets using OIDC with Terraform
Tags: github, aws, oidc, iam
Published: 2021-11-10 15:13:24

> **_NOTE:_** For Portuguese readers: you can find a translated version [here](https://dev.to/mmiranda/github-actions-autenticando-na-aws-via-oidc-2cf).

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:

```terraform
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](https://docs.github.com/en/actions/deployment/security-hardening-your-deployments/configuring-openid-connect-in-amazon-web-services)

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](https://docs.aws.amazon.com/pt_br/IAM/latest/UserGuide/id_roles_providers_create_oidc_verify-thumbprint.html) and also this [Changelog](https://github.blog/changelog/2022-01-13-github-actions-update-on-oidc-based-deployments-to-aws).

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

```terraform
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/*:*"
      ]
    }
  }
```
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:

 ```terraform
    condition {
      test     = "StringLike"
      variable = "token.actions.githubusercontent.com:sub"
      values = [
        "repo:org/my-repo:*",
      ]
    }
 ```
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](https://docs.github.com/en/actions/deployment/security-hardening-your-deployments/configuring-openid-connect-in-amazon-web-services)

Following our tutorial, we now need to finally create our role and associate it to the policy document previously created:

 ```terraform
resource "aws_iam_role" "github_actions" {
  name               = "github-actions"
  assume_role_policy = data.aws_iam_policy_document.github_actions_assume_role.json
}
 ```

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`

 ```terraform
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"]
    }
  }
 ```

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:

```terraform
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:

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

  image_scanning_configuration {
    scan_on_push = true
  }

  tag = {
    "permit-github-action" = true
  }
}
```


## 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: 

```yaml
    permissions:
      id-token: write
      contents: read
```
And after, on the step you use to configure the credentials, you need to inform the role we created with terraform.


```yaml
      - 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
```

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

```yaml
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
```

I hope you enjoyed this article! :)