I recently came across a few articles and attended a conference talk about a relatively new feature in popular CI/CD tools — using OpenID Connect (OIDC) to gain access to cloud resources from a CI/CD job. At Curity, we work with OIDC a lot, so I find this topic really interesting. It was nice to hear that these tools rely on well-established standards, but at the same time, I was curious about the details.
After all, OIDC is a standard created for identifying users with flows that heavily rely on the browser, and CI/CD jobs have none of that. Moreover, access to your cloud resources should be guarded by proper authorization techniques (can the caller access this resource?), but OIDC deals with authentication (who is calling?). So, how do these technologies actually work together? I researched the topic more thoroughly, and here is what I found.
OAuth and OpenID Connect
OpenID Connect is an identity layer built on top of the OAuth standard. OAuth, in turn, is a standard that deals with authorization. OAuth outlines how to securely authorize access to resources so external applications can perform actions on them. OIDC, as the specification states, "enables Clients (applications) to verify the identity of the End-User based on the authentication performed by an Authorization Server (the OIDC provider)," where the End-User is defined as a "Human participant."
For example, when you have an application (such as a mobile app, website, or Single Page Application), OAuth enables the app to call some external APIs to access some data. At the same time, OIDC gives that application information about the user operating the app.
OAuth is associated with access tokens, which can come in different forms but are often represented by JSON Web Tokens (JWTs). OIDC deals with ID tokens, which always have the form of a JWT. What is important, the standards describe ways in which clients obtain these tokens (these are called flows or grant types). Adhering to these flows is vital to ensure the tokens are issued securely.
OpenID Connect in Cloud Providers
Cloud providers allow administrators to leverage OIDC to grant access to managing cloud resources. Users must authenticate when accessing cloud management tools, and OIDC enables this authentication. Instead of managing a separate user list in the cloud provider management tool directly, the platform acts as an OAuth client and uses the standard flows to perform authentication against an external OIDC provider. The user can then authenticate using the company's own OIDC provider. After authentication, the cloud management console uses that information to grant the user access to its resources. This is all done according to the OIDC standard — a human end-user utilizes a browser to access the cloud provider console. So far, so good.
Enter the CI/CD Tools With "OIDC Support"
A common task for a CI/CD job is to deploy an application to one of the cloud environments — testing, staging, or even production. The job needs credentials to access the cloud and deploy a new version. Usually, these are API keys or passwords kept as static secrets in the CI/CD tool. Using static credentials comes with a known security issue — an attacker might get unlimited access to your infrastructure if they become compromised. If your DevSecOps teams don't notice the malicious actions in time, the perpetrator might cause considerable damage.
You can avoid this vulnerability by instead using dynamic, short-lived access credentials. Access and ID tokens are dynamically obtained, short-lived credentials, and I assume this is where the idea for the new feature originated.
The CI/CD vendors noticed that the OIDC support offered by cloud providers could be leveraged in their products. Here is an overview of how it works:
The cloud provider is configured with information about the CI/CD tool as if the tool was an OIDC Provider. The cloud provider acts as a client and establishes trust with the CI/CD tool (such as by providing an endpoint from which the cloud provider can obtain public keys used to verify token signatures).
When a CI/CD job is started, the tool uses an internal mechanism to generate a JWT that conforms to the OIDC standard's ID token. This JWT contains claims identifying the job, including details like what project it concerns, the workload’s name, and the code branch used. The job is the entity identified by the ID token, but the authentication is accomplished using internal features of the CI/CD tool.
This "ID token" is then sent to an endpoint at the cloud provider, as if a user performed authentication and was redirected back from their OIDC Provider with an ID token. The cloud provider uses the identity information from the "ID token" to issue an access token allowing proper access to the cloud's resources.
The CI/CD job uses that access token to manage cloud resources. After the job is finished, the access token is discarded.
This solution solves the aforementioned issue. Now, the CI/CD job does not require any static, long-lived, hard-coded secrets to manage the cloud. Instead, all this is done using a short-lived access token. Even if someone manages to steal that token, they will only have seconds or minutes to perform actions on your infrastructure. This should significantly limit any possible impact of the breach.
It's Progress, but It Isn't Standard
Integrating CI/CD tools with cloud providers in the way described above helps solve security issues, but it comes with some minor problems that leave me with a brain itch. Cloud providers introduced integration with OIDC providers with a concrete reason in mind — to facilitate the management of users in line with the OIDC specification. However, CI/CD tools leveraged the feature for a different purpose — to authenticate automated jobs. I’m always cautious when features are used for different purposes than initially envisaged since new threats may emerge that the creators didn't foresee.
For the same reason, I have an issue with saying that the integration uses OIDC to authenticate the CI/CD job. The OIDC specification was created with a different use case in mind, and a large part of it specifies how ID tokens are safely obtained from the OIDC provider. This part of the specification is actually not used at all in the feature described in this article — the way in which a job obtains an "ID token" is bespoke and opaque to users.
Moreover, even though the CI/CD tool is registered as an OIDC provider at the cloud provider, it does not conform to the standard. The tools implement only a minimal amount of features that the cloud provider requires to verify the incoming token.
My biggest grievance here is the advertising of the use of OpenID Connect for this solution. From how the CI/CD vendors portray it, people get the wrong impression that they're using a well-established standard. In fact, they’re not using the standard, just some parts of it that happen to be used in the solution.
In today's world, developers touch so many technologies that it’s impossible to get a deep understanding of all of them. That's why people closer to these subjects should be careful not to confuse fellow IT specialists. It's also disappointing to see standards being misused as we already have another that fits the purpose much better — the well-established authorization standard of OAuth 2.0.
My Proposal: A Standards-Based Solution
As the cloud provider needs to authorize an external party to access resources on their servers, it’s natural to use OAuth. It was created for this purpose, and it also supports scenarios where no human interaction takes place. In my opinion, a proper solution for a CI/CD-cloud provider integration would look something like this:
In this setup, the Cloud Provider should expose an OAuth-compliant authorization server that is used to issue access tokens to the CI/CD tool. After all, this is what the job needs in the end — an access token to manage cloud resources. The CI/CD tool would then become an OAuth client. Such a client must be able to authenticate itself at the authorization server, and it should use methods that do not involve symmetric string secrets. In other words, it should use JWT Assertions or mutual TLS.
When a job starts, the client can request an access token (as it requests an "ID token" in the current solution) from the CI/CD engine. The engine can then authenticate the job and use a client credentials flow to get a proper access token from the cloud provider. Different standard-based techniques can be used to request access tokens with the minimal permissions set required by the job: requesting scopes, requesting claims, passing data in an assertion JWT, or using Rich Authorization Requests. The CI/CD job can then safely use the access token to manage cloud resources.
This approach allows for further enhancements to the solution’s security. For example, the authorization server can issue sender-constrained access tokens to ensure that only the CI/CD job can actually call the cloud provider's APIs. Thanks to this, even if someone manages to steal the access token, they won’t be able to manage the cloud unless they also break into the CI/CD system. Another improvement could leverage Dynamic Client Registration and allow the CI/CD engine to register separate clients for every workflow. This could allow using different key pairs for each workflow and ensure proper permissions are issued to a given job.
In general, it is good that the CI/CD tools offer a feature that allows for a more secure solution. It's better to access cloud resources using short-lived tokens instead of static secrets. I think it creates a safer environment with fewer possibilities for a breach, and you shouldn’t hesitate to use it as it's better to access the cloud management APIs this way.
It would be best if the CI/CD tools vendors worked more closely with cloud providers to create dedicated security features and not reuse ones designed for a different purpose. I’m sure that would help create an even safer and more robust standards-based solution, as it would utilize standards in accordance with their intended purpose.
A simple, temporary solution would be at least not to use the name "OIDC" to describe these solutions. Instead, something like "job identity" or "workload identity" is more fitting. A more accurate description would be, "At X, we’re now using workload identities instead of static secrets to access cloud resources." At least it wouldn’t confuse people about what technologies and standards are involved.