In the last post, I’d talked quite theoretically about OIDC and how it enables secure machine to machine interactions. However, I felt I could do more to explain this in a more real world scenario.
Why GitHub Actions?
GitHub Actions provides OIDC out of the box and is has several examples for enabling it with cloud providers like AWS, Hashicorp Vault, PyPi etc. It comes with its own ID provider system with endpoints to validate the JWT, which makes it simple to showcase what’s going on.
Workflow
The repository lives on GitHub at oh-eye-dee-see. The workflow that’s of concern to us is stored here. GitHub Actions is the ID provider here, and the Go program, which lives in the repository is the Authenticator. (It simply responds with whether the certificate is valid for now, more complex logic to make it a token provider to come later).
High Level Flow
- Client Registration: I’ve hard coded this by saying that the application can trust an ID token coming from this repository.
- Generating the ID token: The steps in the job prefixed with
id-providerare the parts of the workflow responsible for generating the ID token. For 3rd party integrations, this is how you’d have to generate the ID token, it’s taken care of you for built-in Actions for AWS, Hashicorp Vault, PyPi etc. - Authenticator: The generate ID token is passed to the authentication layer to get a temporary access token.
- Resource server: The access token is used to make authorized calls to the resource server. If any other resource other than what’s allowed is requested, a 403 Unauthorized error is returned.
Detailed Look at the Authentication Flow
- The
/tokenendpoint is called with the ID token generated from GitHub Actions as an authorization header. - The JWKS endpoint is accessed to fetch all the keys that could have signed the ID token at GitHub.
- The ID of the key used to sign the JWT (also called
kid) is identified. - The JWT is validated using the key, and it is then decoded. This checks for things like tampering of the body, expiry time etc.
- The claims from the JWT body are populated.
- An access token is generated using a secret signing key that we’ve set in our environment. This ensures that we can validate the access token with the signing key when it’s used to make authenticated calls.
- The resource
/org/svivekkrishnais called using the access token as the authorization header. - If we call this path with any other org value instead of
svivekkrishna, it returns an error because the GitHub Actions workflow can only return this as the org name as a part of thesubfield in the ID token and subsequently the access token.
What’s Next for this Project?
I’m setting myself some reminders for how to take this example forward.
- Create a reusable JWT verification and decoding module.
- Write negative tests.
Convert the project into a web application that can run on Docker during the workflow.Change the authenticator logic into a/tokenendpoint.Generate an access token from the endpoint which is what would give GitHub Actions access to other parts of the application.Create aresourceendpoint that can only be called with a valid access token.
Leave a comment