Using IAM the secure way in GitHub Actions

Using IAM the secure way in GitHub Actions

hayden
hayden

There comes a time in every DevOps Engineer’s life when they must ask: ‘why static IAM credentials’? Well, this is an asking I have answered a couple of times across environments, but we’ve recently started asking this at ALT-F4 too.

For context, when I refer to ‘static IAM credentials’, I’m talking about access keys assigned to an IAM user. The issue with these is that they’re, y’know… static. If someone manages to sniff them out somehow, they now have your credentials and can use them however they want. They also need rotating to combat that from happening under your nose for too long.

XKCD 538: Security

But what if there was a better way to manage AWS access? Enter IAM roles. IAM roles don’t use static credentials whatsoever, they’re dynamically used in a process called assumption. Assumption takes something called a ‘trust policy’, which is essentially a normal IAM policy that applies to whether or not you are able to assume the role, and generates dynamic credentials for you that last a very limited amount of time. This is great for things like EC2 or ECS, where you’ve got a long-running process that needs access to other AWS services, but it can also be used for fun and profit tasks that run outside of AWS too.

At one point in time, all our GitHub Actions workflows (used for CI & CD of things like Terraform and deploys to things like this blog) were using an IAM user access key to reach AWS. But there’s a better way. GitHub Actions jobs get something called an ‘ID token’ that they use to authenticate against GitHub itself to run actions. These actions are things like actions/checkout, and actions/upload-artifact. However, because this ID token is provided by an OpenID Connect provider GitHub runs specifically for Actions, we can use that to authenticate our jobs against AWS through an IAM role.

IAM Identity Providers

IAM has this nice little feature built-in that allows you to configure what I’ll be referring to as ‘web identities’. This comes in the form of the Identity Providers API. It allows you to configure either a SAML IdP or OpenID Connect provider as an IAM Identity Provider, and use that to verify (and trust) tokens that are issued by it. GitHub, handily, provides an OpenID Connect provider for its actions to run, as I mentioned before.

In our case, we want to authenticate GitHub Actions against IAM, so we can configure an OpenID Connect provider for job ID tokens.

To do this, let’s go to the IAM console, and find the Identity Providers section in the sidebar.

/assets/280662659.png

Once we’re there, we can click that nice big orange “Add Provider” button in the top right.

To fill in the form, we’re going to choose OpenID Connect for our Provider Type and set the following values:

  • Provider URL: https://token.actions.githubusercontent.com
  • Audience: sts.amazonaws.com

/assets/1356774090.png

Once that form is filled, we can hit “Get Thumbprint” next to Provider URL and then “Add Provider” at the bottom of the page to save it.

We should now see a new entry back on the Identity Providers page for our GitHub Actions provider:

/assets/3228650504.png

Creating an IAM Role

While we’re still in the AWS console, let’s go and create our first IAM role. We’ll plan out our permissions now, so it’s easier. We’re going to be really insecure and give our role the AdministratorAccess policy, because we’re just lazy like that, what can I say?

We’re also going to want to use this role when we run our deploy-site job in GitHub Actions. The repo we’re doing that from is my-cool-org/my-cool-site, and we want it to run against the main branch.

Cool, so now we know what permissions it wants as well as where we’re accessing AWS from, we can continue and get role-making.

Head to the IAM console again and go to the Roles page. Hit the big orange Create Role button, and you should be greeted with the new role form.

For Trusted Entity Type, we’re going to select Web Identity.

/assets/124684806.png

We’re then going to fill in the next part of the form, which cleverly gives us more options once we select our GitHub Actions provider.

We’re going to fill it in as follows:

  • Identity provider: token.actions.githubusercontent.com
  • Audience: sts.amazonaws.com
  • GitHub organization: my-cool-org
  • GitHub repository: my-cool-site
  • GitHub branch: main

/assets/3834770020.png

Note

To find out more about the trust policy being generated here, as well as some other cool things you can do with it, have a look at the GitHub documentation for this whole process.

Once that’s done, we can hit Next and cement ourselves as the security team’s worst nightmare by giving it the AdministratorAccess policy and hitting _Next _again.

/assets/2517799075.png

Important

Please, do not do this on any resources. Use of the AdministratorAccess policy should be _heavily _controlled, and is only in use here for the purposes of demonstration. You’ve been warned.

Finally, let’s give our role a meaningful name and description (because we love the auditing team, just not security) and click Create Role.

/assets/2142529919.png

Cool. The role’s been created. Let’s quickly search my-cool-site-gha-deploy in the roles list and grab the ARN from it.

/assets/988343938.png

Hooking it up to GitHub Actions

Finally, our trip to the IAM console is over! Now… now we need to delve into YAML instead. Not much more fun, if you ask me.

/assets/1978209870.png

In our my-cool-site repo, we can go ahead and open the mystical workflow file we have.

---
on:
	push:
		branches: [main]

jobs:
	deploy:
		runs-on: ubuntu-latest
		env:
			AWS_ACCESS_KEY_ID: ${{ secrets.AWS_ACCESS_KEY_ID }}
			AWS_SECRET_ACCESS_KEY: ${{ secrets.AWS_SECRET_ACCESS_KEY }}
			AWS_REGION: us-west-2
		steps:
			- uses: actions/checkout@v4
			- run: aws s3 sync . s3://my-cool-site/ --delete --acl public-read

So we’re currently using static credentials to deploy this. Blegh. Let’s go over what we need to ditch this ugly stuff.

  • First, we need to get access to our ID token as part of the job so we can use it to hit AWS with
  • Then, we need to use that ID token to try and assume our new role.

That’s… surprisingly little. It boils down to adding two things to our action; first, we need to set the permissions for our action correctly:

jobs:
	deploy:
		permissions:
			id-token: write
			contents: read

Cool, done. Next, we need to somehow assume our IAM role using that token. Luckily, AWS have already built this out for us in the form of aws-actions/configure-aws-credentials. Looking through the documentation shows us that we only need to add a small snippet at the start of our job’s steps:

jobs:
	deploy:
		steps:
			- uses: aws-actions/configure-aws-credentials@v4
				with:
					aws-region: us-west-2
					role-to-assume: arn:aws:iam::123456789012:role/my-cool-site-gha-deploy

Nice! So now, we can get shot of all the AWS_* environment variables we declared, because they’ve all been replaced by this one action.

At this point, that’s all we actually need to play around with. When we push our new workflow to main, we should now be in a state where (without having given it any static credentials whatsoever) the job should be able to assume our IAM role and essentially infer access to AWS services.

In Conclusion

So what have we actually achieved here? Well;

  • We’ve done away with static credentials that can be stolen easily and need manual rotation on a regular basis
  • We’ve implemented a more secure way for GitHub to talk to AWS on our CI/CD pipelines
  • We’ve learned a bit about how AWS operates with regards to roles & assumption.

Pretty good spread for reading a single blog post, don’t you think?

Hopefully, you’ll agree that this is a pretty useful (not to mention, as I have several times in this post, more secure) way of accessing AWS from GitHub Actions, and now I bid you farewell. Go forth and continuously integrate IAM roles into your continuous deployments!

Further Reading

There’s some useful documentation on all this if you dig around a bit, so I felt I should list some of these pages for you.