Imagine the following scenario: You set up your GitHub Actions in your repository. And it’s all cool until you want to access your cloud provider resources. Now you might be tempted to create an access key and secret access key, place it as a secret in your repository and forget about it. However, this can have drawbacks, for example your credentials could get leaked without your knowledge and your AWS account could be accessed by third parties. Additionally you probably haven’t thought about how to rotate these credentials. You might ask yourself: Isn’t there a better way to access your cloud resources without storing these credentials anywhere…?
Fear no more, GitHub and your favorite cloud provider got you covered!
When you wanted to access cloud resources (AWS, Azure, Google Cloud Platform/GCP) from your GitHub Actions, up until recently, you needed to store some sort of credentials (such as an access key and secret access key) as secrets in your repository. Not anymore!
GitHub announced the possibility to get rid of this type of credentials.
Instead, you can configure your GitHub Actions so that an OpenID Connect JSON Web Token (or short OIDC JWT) is generated by GitHub while your workflow runs.
In your cloud provider, you can register GitHub as an Identity Provider. This way, the JWT generated by GitHub is allowed to access your cloud account.
GitHub provides instructions on how to use this feature for AWS, Azure and GCP. In this blog post I will focus on AWS, as it’s the most widely used cloud provider and the one I’m most familiar with.
How the authentication between GitHub and AWS works
So before we get our hands dirty, let’s have a look at how the secretless connection using OIDC works:
- In AWS (or your cloud provider of choice), create a trust relationship between your cloud role and the tokens issued by GitHub and used in your GitHub Actions workflow.
- A GitHub Action (such as aws-actions/configure-aws-credentials ) can request a signed JWT with multiple claims during an action job/workflow run.
- GitHub OIDC provider issues a signed JWT with multiple claims to the github actions workflow.
- The action sends the JWT and the requested role to AWS.
- The JWT is validated in AWS and AWS sends a short-lived access token in exchange. With this token, it’s possible to access AWS resources.
The JWT contains claims e.g. the standard claims like “subject”. Each claim holds different information about the token itself. GitHub adds additional custom claims like ”reference”. You can find an example of the available claims here . These claims will later be used for the trust conditions.
Creating the trust
Now that we know how it works theoretically, we can start coding / setting it up in code.
The following infrastructure code examples are available in a demo repository . The examples are written in both AWS CDK and Terraform . You can also click through the AWS management console as well, but please don’t hold me accountable for your next AWS bill ;).
In order to access AWS resources from GitHub, we need to make sure we have
- registered GitHub as a (trusted) OIDC Provider in AWS by
- including under which conditions a GitHub token is considered trustworthy (trust condition),
- created the resource itself,
- created an AWS IAM role which can be assumed by your CI pipeline,
- including a policy for that role which defines the AWS resources which are allowed to access.
Create OIDC Provider connection
First, lets create the OIDC Provider in AWS:
- In IAM → Identity providers → Add provider
- Provider URL: https://token.actions.githubusercontent.com
Audience: sts.amazonaws.com
In CDK:
1const githubOIDCProvider = new iam.OpenIdConnectProvider( 2 this, 3 "GithubActions", 4 { 5 url: "https://token.actions.githubusercontent.com", 6 clientIds: ["sts.amazonaws.com"], 7 } 8);
This is how you tell AWS to allow tokens issued by GitHub to be accepted by AWS.
The audience/clientId “sts.amazonaws.com” is used by the official AWS GitHub Action aws-actions/configure-aws-credentials .
Create a resource
The next step is to create a resource in AWS for demonstrating the access during a workflow run.
I’ve chosen the service Parameter Store given that it’s cheap (most of the time free) and simple to store and retrieve a string. Give it a name (“hello_aws-gh-oidc” in this case) and a value and you’re good to go.
1// Something to read for the pipeline :) 2const helloParameter = new ssm.StringParameter(this, "HelloParameter", { 3 description: `Sample value for demo purpose of project ${projectName}`, 4 parameterName: `hello_${projectName}`, 5 stringValue: "Hi from aws :wave:", 6});
Create an IAM role
The last thing we need to do in our AWS account is to create an IAM role which is allowed to access the parameter you just created (permission policy) and is allowed to be assumed by GitHub (trust policy).
When using the GitHub OIDC approach, the trust policy is used to verify the content/claims of the issued JWT from GitHub.
The most common way is to verify the content of the subject and audience claim.
- The subject claim provides GitHub Actions job metadata, e.g. the branch name in case of a git push event. It looks different depending on which event triggered your job (GitHub provides example subjects here ).
- The audience claim provides the URL of the organization or repository owner by default. The aws-actions/configure-aws-credentials action overrides the audience claim with the value “sts.amazonaws.com”
In CDK you define a secure IAM policy like this:
1const githubActionsRole = new iam.Role(this, "GithubActionsRole", { 2 // Trust policy 3 assumedBy: new iam.WebIdentityPrincipal( 4 githubOIDCProvider.openIdConnectProviderArn, 5 { 6 StringEquals: { 7 // Only allow tokens issued by aws-actions/configure-aws-credentials 8 "token.actions.githubusercontent.com:aud": audience, 9 // Only allow specified branches to assume this role 10 "token.actions.githubusercontent.com:sub": 11 allowedBranchPatternToPush, 12 }, 13 } 14 ), 15 roleName: "aws-gh-oidc", // same as in .github/workflows/hello.yml 16 description: `Role to assume from github actions pipeline of ${projectName}`, 17}); 18 19// Permission policy 20helloParameter.grantRead(githubActionsRole);
This results in the following secure IAM role trust policy:
1{ 2 "Statement": [ 3 { 4 // ... 5 "Condition": { 6 "StringEquals": { 7 "token.actions.githubusercontent.com:sub": [ 8 "repo:WtfJoke/aws-gh-oidc:ref:refs/heads/main", 9 ], 10 "token.actions.githubusercontent.com:aud": "sts.amazonaws.com" 11 } 12 } 13 } 14 ] 15}
Only a push to the main branch of the repository named aws-gh-oidc from user WtfJoke can assume the role.
Please make sure you define your IAM role trust policies carefully.
Above is an example for a secure policy. Below is an example of an unsecure policy (the policy allows assuming the IAM role from other users repositories):
Unsecure IAM role trust policy:
1{ 2 "Statement": [ 3 { 4 // ... 5 "Condition": { 6 "StringEquals": { 7 "token.actions.githubusercontent.com:sub": [ 8 "repo:*/aws-gh-oidc:ref:refs/heads/main", // UNSECURE DO NOT USE 9 ], 10 "token.actions.githubusercontent.com:aud": "sts.amazonaws.com" 11 } 12 } 13 } 14 ] 15}
A push to the main branch of a repository named aws-gh-oidc from any users can assume the role
Setting up the Pipeline/Workflow
Now that we have everything set up on AWS, the only thing left to do is to create a simple GitHub workflow which assumes the AWS role, and reads the parameter store parameter and prints the value. The final workflow looks like this:
1name: Hello from AWS 2 3on: 4 push: 5 6permissions: 7 id-token: write 8 9jobs: 10 greeting: 11 runs-on: ubuntu-latest 12 13 steps: 14 - name: Configure AWS credentials 15 uses: aws-actions/configure-aws-credentials@v1 16 with: 17 aws-region: eu-central-1 18 role-to-assume: arn:aws:iam::589918074230:role/aws-gh-oidc 19 20 - name: Print AWS SSM Parameter 21 run: aws ssm get-parameter --name=hello_aws-gh-oidc --query Parameter.Value 22 23 - name: Print assumed role 24 run: aws sts get-caller-identity
We will go through all important steps in the workflow below.
Adding permissions
We need to allow the workflow to request a signed JWT from GitHub by setting the permissions:
1permissions: 2 id-token: write
Note: Depending on your use case, you might need additional permissions for your workflow. (see GitHub Docs for the complete list of default permissions ).
Logging in at AWS
To exchange a GitHub JWT for a short-lived AWS access token, we can use the aws-actions/configure-aws-credentials action.
We need to pass the role (ARN) which we want to assume and have created earlier.
1- name: Configure AWS credentials 2 uses: aws-actions/configure-aws-credentials@v1 3 with: 4 aws-region: eu-central-1 5 role-to-assume: arn:aws:iam::589918074230:role/aws-gh-oidc
Interacting with AWS resources
Finally we can interact with AWS by using the AWS CLI as usual.
In our case, we can read the parameter we created earlier and print it:
1- name: Print AWS SSM Parameter 2 run: aws ssm get-parameter --name=hello_aws-gh-oidc --query Parameter.Value
Running the secretless workflow
Now that we have set everything up and as soon as we push any commit to the repository, we should see a successful action run like the following:
What’s next?
By now, you have learned everything to get started using OIDC to access your cloud resources.
The most important takeaways:
- Favor using OIDC over regular credentials to access cloud resources.
- Double check the trust policies of your roles.
You can find all the code examples in this demo repository .
Although our use case was simple by just printing a single string parameter, only your imagination limits the things you can do in your workflow. 🌈😄
There are even more advanced use cases: As soon as a pull request (PR) is opened, your application is deployed in a temporary environment (just like vercel does) with the changes for the PR, so people can test the changes before it gets deployed to a live environment.
In contrast a push to the main branch is only allowed to deploy/update the existing live environment.
For different use cases, you can use different IAM roles and limit their access to the required minimum.
If you have comments or questions feel free to reach out.
Sources:
https://docs.github.com/en/actions/deployment/security-hardening-your-deployments/about-security-hardening-with-openid-connect
https://docs.github.com/en/actions/security-guides/automatic-token-authentication#permissions-for-the-github_token
https://docs.github.com/en/actions/deployment/security-hardening-your-deployments/configuring-openid-connect-in-amazon-web-services
https://docs.aws.amazon.com/IAM/latest/UserGuide/id_roles_providers_create_oidc.html
https://github.com/aws-actions/configure-aws-credentials#assuming-a-role
https://connect2id.com/learn/openid-connect
https://jwt.io/introduction
More articles
fromManuel
Your job at codecentric?
Jobs
Agile Developer und Consultant (w/d/m)
Alle Standorte
More articles in this subject area
Discover exciting further topics and let the codecentric world inspire you.
Gemeinsam bessere Projekte umsetzen.
Wir helfen deinem Unternehmen.
Du stehst vor einer großen IT-Herausforderung? Wir sorgen für eine maßgeschneiderte Unterstützung. Informiere dich jetzt.
Hilf uns, noch besser zu werden.
Wir sind immer auf der Suche nach neuen Talenten. Auch für dich ist die passende Stelle dabei.
Blog author
Manuel
Do you still have questions? Just send me a message.
Do you still have questions? Just send me a message.