One of the biggest pain points of using any CI/CD platform alongside a cloud service is managing credentials, and up until this week Github Actions was no exception. That all changes with the announcement of Github Actions OpenID Connect. With this feature Github Repositories can be given permissions directly from AWS IAM.
This is a really powerful tool that can save a lot of aggrevation and busy work when it comes to setting up CI/CD. I recently used it to simplify pushing images from Github to AWS ECR and thought others could benefit from the lessons I learned.
Setting up AWS
I'm going to use Terraform to manage the ECR Repositories. At the end we're going to have a workspace with a reusable ECR module, the OIDC setup, and a few repositories.
The first thing we need to do is connect Github's Open ID Connector to our AWS account using the Terraform aws_iam_openid_connect_provider resource. This is pretty straight forward but requires you dig around to find the right URL, Client List, and Thumbprint- these values are the same for every account, so you can copy and paste this over without problem.
And that's it! With that one simple resource you've linked your account to Github.
ECR Repository Module
By creating a separate module for building a ECR Repository we can avoid a lot of boilerplate when creating multiple repositories.
Before we start creating resources we need a few variables- specifically the Github Organization, the name of the ECR Repository (which should match the Github Repository), and the OIDC Provider we created above. If you are only using a single Organization then you can put it as the default to save some boilerplate.
Next we create the repository. As mentioned above we're going to give it the same name as the Git Repository.
Again pretty simple- just a single resource.
This is where the magic happens. Here we create the Role for the Github Action, doing a few very important things-
- Add the Github OpenID Connect Provider as the Principal for the role.
- Give Github the ability to assume this role by giving it the
sts:AssumeRoleWIthWebItentityaction. This is what actually allows Github to give this role to the Github Action.
- Limit that access further with a condition against the repository name. After all, it wouldn't be a good idea to let any Github Action take on this role.
Another thing to note is the Role Name- we're going to need that later. We're using a standard naming scheme so we can easily refer to the role from inside of our Github Actions.
Now we're at the point where we want to define the permissions that the Github Action has. In our example we're pushing to an ECR Repository, but this method can be used for any AWS resources- pushing to S3, updating an ECS Task Definition, and even running Terraform itself are all possible with this method.
The policy we use is locked down to the specific ECR Repository- the Github Action that uses this can only act on the single repository. The
ecr:GetAuthorizationToken permission is only needed to log in to the registry. Once the policy is created we attach it to the role from above.
Tying it Together
Now we've built out module, so lets head back to where we've defined our OIDC resource and create some repositories! To make this easy to maintain we put a list of repository names right at the top, and then use the Terraform for_each functionality to call the module for every name in that list.
Assuming the AWS Role
Now that we're ready to run the actions on Github we need to assume the role we created for the repository. AWS has a published action for logging in that handles most of the work.
Unfortunately this OIDC functionality is so new that they haven't released a working version yet, so we have to work directly from their master branch until they do. This functionality was released in v1.6.0, so make sure to tie to v1 or (if using a more specific tag) a version at or later than v1.6.0.
The most important value here is the IAM Role Name. Since we're using a standardized naming scheme across Github and ECR Repositories we can infer the role name instead of having to hard code it. That means the only things we need to provide are the AWS Account ID and the AWS Region that our container registry is in.
Pushing the Container
WIth all that out of the way here's full action to build and deploy an image to ECR. In this we're chaining together a variety of published actions from other vendors-
- actions/checkout to actually pull the repository.
- docker/setup-qemu-action to install an emulation layer to build multiplatform images.
- docker/setup-buildx-action to use the
docker buildxsystem, again for multiplatform images.
- aws-actions/configure-aws-credentials to log into AWS.
- aws-actions/amazon-ecr-login to log into AWS ECR.
- docker/metadata-action to create a bunch of tags for our docker container.
- docker/build-push-action to push the container.
Less Boilerplate, More Action
One thing you've probably noticed is that in all of that code there are only two unique values that can't be picked up with Github Context variables- the AWS Account and Region. This is a sign that this one off action could be turned into it's own standalone and reusable action. Using that action you end up with this much simpler action code.
Wrapping it Up
If you stuck with me this far you should have a good idea on how to link Github Actions to AWS, and maybe even some ideas on how to simplify your image building in general. If you have any questions please feel free to reach out- you can find me on twitter at @tedivm.