In the rapidly evolving landscape of cloud computing and application security, managing user identities and ensuring secure authentication are critical for business success. The era of AWS static credentials being exposed in GitHub and filesystems should be behind us, thanks to better alternatives.
OpenID Connect (OIDC) is emerging as a crucial protocol in this field, bridging the gap between robust security and user convenience by enabling auto-generated, short-lived, and non-persistent credentials.
But what exactly is OIDC, and how does it work? Let's delve deeper into this four-letter acronym and explore why it is an essential tool for modern cloud applications.
Understanding OIDC
To understand OIDC, we must first describe all the components involved and their role.
OpenID Provider - The service that authenticates the request and issues identity tokens. There are many of these such as GitHub, Vercel, Google, AWS Cognito & Microsoft.
Relying Party - The application or service that requires authentication. This could be any web or mobile app or a service like a GitHub Actions needing to access AWS resources.
JSON Web Token - A JSON Web Token (JWT) issued by the OpenID Provider, containing information about the authenticated user, service or app.
UserInfo Endpoint - An API endpoint provided by the OpenID Provider to fetch additional user details after authentication.
So how do these components fit together to provide access to resources. The key is the contents of the claims defined within the JWT.
What is a JWT?
A JSON Web Token (JWT) is a compact, URL-safe token that can be used to securely transmit information between parties. Typically represented as a string with three parts separated by dots (.), like this:
It is made up of three parts: the header, the payload, and the signature.
Header: Encodes information about the type of token and the algorithm used for signing.
Payload: Contains the claims.
Signature: Ensures that the token has not been altered.
Claims
Claims are statements about an entity (typically, the user) and additional data. These details are used to validate the source of the request.
There are three types of claims:
Registered claims: Predefined claims that are recommended to provide a set of useful, interoperable claims.
Public claims: Claims that are defined by those using JWTs. They must be collision-resistant.
Private claims: Custom claims are created to share information between parties that agree on using them.
The following shows an example of a JWT that would be sent to AWS IAM, via a GitHub Actions workflow.
{
"sub": "repo:sjramblings/aws-repo:ref:refs/heads/main",
"aud": "sts.amazonaws.com",
"iss": "https://token.actions.githubusercontent.com",
"iat": 1625724353,
"exp": 1625727953
}
sub
: Identifies the subject of the token, which is the GitHub Organisation/Repository in this case,sjramblings/aws-repo
.aud
: The audience for the token, in this case,sts.amazonaws.com
.iss
: The issuer of the token, which ishttps://token.actions.githubusercontent.com
.iat
: The timestamp when the token was issued.exp
: The timestamp when the token expires.
Signature
The signature is created by taking the encoded header, the encoded payload, a secret, and the algorithm specified in the header, then signing them.
In the above example, the signature would be generated using the RSA private key corresponding to the public key hosted by GitHub -https://token.actions.githubusercontent.com
.
OIDC Authentication Flow
OIDC relies on establishing trust with an OpenID Provider. Once established, the OIDC authentication process typically follows these steps:
The End User requests a protected resource from the Relying Party.
The Relying Party requests authentication from the OIDC Provider.
The OIDC Provider prompts the End User to log in.
The End User submits their credentials to the OIDC Provider.
The OIDC Provider sends an ID token and access token to the Relying Party.
The Relying Party provides the protected resource to the End User.
That’s the high-level flow when there is an end user, what does it look like when we want to authenticate a GitHub Actions workflow to access resources in AWS.
In the following example the components required for OIDC map to:-
GitHub acts as the OpenID Provider and Endpoint. When a workflow runs, GitHub can issue an OIDC token for the job.
AWS IAM is the resource that validates the OIDC token issued by GitHub to grant temporary credentials.
The GitHub Actions workflow acts as the relying party, which uses the OIDC token to request temporary AWS credentials.
Let's walk through each step:
Firstly trust is established with GitHub and AWS IAM, this allows an IAM Role to be assumed by GitHub Actions with the correct claims.
A user pushes some code or a repo or creates a Pull Request triggering a GitHub Actions Workflow
The GitHub OIDC Provider issues an OIDC JWT token for the job.
The JWT token is used to request temporary credentials from AWS IAM.
AWS IAM validates the claims in the JWT and if they match the requested IAM Role returns a temporary access key and ID.
The GitHub Actions workflow can now use those credentials to perform actions on AWS resources as per the permissions assigned in the IAM Role.
AWS IAM Role Trust Policy
The IAM role specified in the workflow must have a trust policy that allows it to be assumed by entities presenting a valid OIDC token from GitHub.
Here’s an example:
{
"Version": "2012-10-17",
"Statement": [
{
"Effect": "Allow",
"Principal": {
"Federated": "arn:aws:iam::123456789012:oidc-provider/token.actions.githubusercontent.com"
},
"Action": "sts:AssumeRoleWithWebIdentity",
"Condition": {
"StringEquals": {
"token.actions.githubusercontent.com:sub": "repo:sjramblings/aws-repo:ref:refs/heads/main"
}
}
}
]
}
Let’s break down the components.
Principal.Federated: Specifies the OIDC provider (GitHub) that can assume the role.
Action: Allows the sts:AssumeRoleWithWebIdentity action, which is necessary for assuming roles with OIDC tokens.
Condition: Adds a condition to ensure that only tokens i.e. the claims for a specific repository and branch (in this case, main) can assume the role.
Summary
This article covered the components of OIDC and how it can be used to grant access to AWS Cloud resources. By understanding and implementing OIDC you can ensure secure access to your resources for your apps, users, or DevOps workflows.
Hope this helps someone.
Cheers! 👋
Related Reads
If you found the introduction to OIDC insightful, you might also be interested in exploring other AWS services and IAM policies. These articles will help you understand the differences between AWS messaging services, master IAM roles, and get practical with IAM policies: