Securely Access Your AWS Resources From Github Actions
Tags: aws, security, github, cicd
Published: 2021-12-27 15:15:33

Security is a very important topic for all cloud engineers. Making sure that your infrastructure and data are kept out of reach of malicious people is one of the most serious things to get right. In AWS, we are used to dealing with IAM roles and permissions that make our resources accessible to users or to other resources. However, sometimes you need to grant access from outside your organization.

One example is when you want to deploy your infrastructure from a CI/CD pipeline, like Github Actions. How do you allow your workflow to gain access to your AWS account?

One approach is to create a dedicated IAM user, store its credentials in the [Github secrets store](https://docs.github.com/en/actions/security-guides/encrypted-secrets), and allow the workflow to use them. Easy, enough! Secrets are encrypted by Github, so it is secure, right? 

Not really... The problem is that those credentials are meant to be long-lived. It means that if anyone is able to get hold of them for whatever reason (eg: a leak in a workflow logs, someone gaining access to a GitHub action runner, etc), they will be able to access all your resources (at least those that the credentials are allowed to control). Sure, you could rotate them from time to time, but you'd have to do that manually. This is probably not something you want to spend time doing, and let's face it, you probably won't!

Luckily, there is a better solution. If you are using Github Actions, you can allow Github to grab temporary, short-lived, credentials that it can use during the execution of the workflow. After that, the credentials will expire and no one will ever be able to use them again.

In this post, I will guide you through the steps to set this up. Don't worry, it's actually easier than you think!

Here is a schema representing what we are going to accomplish

![GitHub Actions OIDC Integration with AWS](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/0lczntnt01oz53iwo6qw.png)


## Setting up your AWS account

> 💡 TL;DR; I created a CloudFormation quick-create link that you can use to automate the following steps. See at the bottom of this article. If you want to know how it works, and what CloudFormation is going to do, keep reading this section.

### Create an OpenID Connect Identity provider

The first step is to create an OpenID Connect (OIDC) identity provider in your AWS Account. This will allow Github to identify itself.

- Got to the [IAM console -> Identity providers](https://console.aws.amazon.com/iamv2/home?#/identity_providers)
- Click *Add new provider*
- Select *OpenID Connect*
- Provider Url: `https://token.actions.githubusercontent.com` (Don't forget to click `Get Thumbprint`)
- Audience: `sts.amazonaws.com`
- Add tags if you want to and click *Add Provider*

![OIDC provider creation](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/h6c71ery9wid0918mbuw.png) 

> 💡 You will need to do this step only once per AWS account.

### Create a role

You now need to create a role that Github will be able to assume in order to access the resources it needs to control. 

- Go back to IAM and select [Roles](https://console.aws.amazon.com/iamv2/home?#/roles)
- Create a new Role
- Chose *Web Identity*, select the Identity provider you created in the previous step, and its audience.
- Click *Next:Permissions*

![Create an IAM role](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/h7wf4ezzdv4g2kfojo04.png) 

You now need to give the role the appropriate permissions (Policies). These are the ones that Github needs in order to do whatever it has to do. This will vary based on your use case, so I will leave that up to you. Keep in mind that you should stick to the [principle of least privileges](https://docs.aws.amazon.com/IAM/latest/UserGuide/best-practices.html#grant-least-privilege).

When that is done, give your role a name and click *Create Role*.

There is now an additional step to do. You need to edit the trust policy of the role to reduce its scope to your repository only. Make sure you don't skip this part, it is **very important**. Without that, **any repository on GitHub will be able to assume your role and access your resources**. (Unfortunately, there does not seem to be a way to do that at creation time).

Go back to IAM Roles and select the created Role. Choose *Trust Relationships* and *Edit Trust Relationship*.

Under `Condition`, add the following segment:

```json
"StringLike": {
  "token.actions.githubusercontent.com:sub": "repo:[your-org]/[your-repo]:*"
}
```

Replace the organization and repo names to match yours, and click `Update Trust Policy`.

> ✍️ Note: You can take this even further and reduce the scope, by using [git references](https://git-scm.com/book/en/v2/Git-Internals-Git-References), to a branch or tag only, for example.
eg: `repo:[your-org]/[your-repo]:ref:refs/heads/master`

The final result will look like this:

```json
{
  "Version": "2012-10-17",
  "Statement": [
    {
      "Effect": "Allow",
      "Principal": {
        "Federated": "arn:aws:iam::1234567890:oidc-provider/token.actions.githubusercontent.com"
      },
      "Action": "sts:AssumeRoleWithWebIdentity",
      "Condition": {
        "StringEquals": {
          "token.actions.githubusercontent.com:aud": "sts.amazonaws.com"
        },
        "StringLike": {
          "token.actions.githubusercontent.com:sub": "repo:[your-org]/[your-repo]:*"
        }
      }
    }
  ]
}
```

This concludes the required configurations on your AWS account. Take note of the role ARN, you'll need it later.

> 💡 You can create different roles per account and use a different one for each use case. For example, one per application, per usage (configurations, deployment, integration tests), etc. You can play with that to reduce the scope of each session even more.

## Configure Github action workflow

Your Github workflow requires additional permissions in order to be able to use OIDC. Add the following at the top of your workflow's YML file. You can also add it at the job level to reduce the scope if needed.

```yml
permissions:
  id-token: write # required to use OIDC authentication
  contents: read # required to checkout the code from the repo
```

You can now use the [configure-aws-credentials](https://github.com/aws-actions/configure-aws-credentials) Github action in the job that needs to assume the role. Add this step to generate credentials before doing any call to AWS:

```yml
- name: configure aws credentials
  uses: aws-actions/configure-aws-credentials@v1
  with:
    role-to-assume: arn:aws:iam::1234567890:role/your-role-arn
    role-duration-seconds: 900 # the ttl of the session, in seconds.
    aws-region: us-east-1 # use your region here.
# You can now execute commands that use the credentials👇
- name: Serverless deploy
  run: sls deploy --stage dev
```

The `configure AWS credentials` step will use the OIDC integration to assume the given role, generate **short-lived** credentials, and make them available to the current job.

> 💡 If you want to take security even further, you can also keep your role's ARN used in `role-to-assume` in a Github secret.

## Automate

The guys at `configure-aws-credentials` shared a [CloudFormation template](https://github.com/aws-actions/configure-aws-credentials#sample-iam-role-cloudformation-template) that you can use to automate the AWS configuration steps.

I took it one step further; I [hosted that template](http://githubactions-oidc-cfn.s3.amazonaws.com/template.yml) and created a deployment link for you.

[Click here](https://us-east-1.console.aws.amazon.com/cloudformation/home?region=eu-west-1#/stacks/quickcreate?templateURL=http://githubactions-oidc-cfn.s3.amazonaws.com/template.yml&stackName=GithubActionsOIDC) to deploy it into your account.

Fill in the parameters:
- `GitHubOrg`: your organization name, or your Github username
- `RepositoryName`: the repository that needs access to your AWS account
- `OIDCProviderArn`: your existing OIDC provider's ARN, if you have one already. If you don't, leave it empty and one will be created for you. (Remember that you only need one per account).

> ✍️ Note: The created role will not have any Policy attached to it. You will still need to attach the ones that your workflow needs to it after that.

## Conclusion

As you can see, securing your account doesn't have to be hard. The part that might require a little more effort is to define the right Policies if you want to follow the principle of least privileges (which you should!).

For more content like this, follow me on Twitter [@Benoit_Boure](https://twitter.com/Benoit_Boure).