Workload Identity


Configure workload identity rules to let CI workloads authenticate with Develocity using short-lived OIDC tokens instead of long-lived access keys.

Overview

Workload identity federation is based on the OpenID Connect (OIDC) standard. Instead of storing a long-lived Develocity access key, you exchange a short-lived OIDC token from your CI provider for a Develocity access token.

The exchange works in these steps:

  1. The CI provider issues a short-lived OIDC token at the start of the job. A GitHub Actions token lasts about five minutes.

  2. The CI integration exchanges the token for a Develocity access token via a POST /api/auth/token request.

  3. The build authenticates with the returned Develocity access token for the rest of the run.

Your CI job mints the OIDC token from your CI provider; no Develocity plugin mints it for you.

You supply that token in place of your access key, in host-qualified form, and the CI integration exchanges it for a Develocity access token the same way it exchanges an access key.

For how each provider mints and supplies the token, see the provider examples below and the Continuous Integration section.

Because the OIDC token is issued fresh for each job and is short-lived, there is no shared secret to store, rotate, or leak. Claim requirements let you scope a rule to a specific organization, repository, branch, or workflow, so each workload receives only the access it needs.

Use workload identity when your CI provider issues OIDC tokens and you want to remove long-lived Develocity access keys from CI configuration, or grant roles based on which repository, branch, or workflow is authenticating.

You can present the OIDC token directly in place of an access key, but it usually expires partway through a build, which is why the CI integration exchanges it for a longer-lived Develocity access token. On a CI system with no integration that performs the exchange, see Other CI Systems for the direct path.

Prerequisites

  • An account with permission to configure access control in Develocity.

  • A CI provider that issues OIDC tokens (GitHub Actions, Google Cloud Workload Identity, HashiCorp Vault, and similar).

  • Network connectivity from Develocity to the issuer’s JWKS endpoint (see Issuer Key-Refresh Failures).

Configure via the User Interface

Navigate to Administration  Access control  Workload identity and select Add.

Develocity evaluates each incoming token against every configured rule for its issuer. A rule matches when the issuer, audience, and all claim requirements match. If multiple rules match, Develocity applies all of them and grants the union of their roles and project groups.

  1. ID: Enter a name for the rule, up to 256 characters, that no other rule uses. The ID identifies the rule in the rule list and on its edit page. It never appears in the token and is not matched against it, so name it for the workload it admits, such as github-acme-app-main, rather than rule-1. You can rename a rule later.

  2. Issuer: Enter the iss claim value from the provider’s tokens (for example, https://token.actions.githubusercontent.com for GitHub Actions). Select Test to verify Develocity can reach the issuer’s JWKS endpoint before saving. If Develocity cannot reach the JWKS endpoint, see Issuer Key-Refresh Failures.

  3. Audience: Enter the aud claim value the token must carry. If your CI provider lets the job choose the audience, use your Develocity server URL, such as https://develocity.example.com. If the provider sets a fixed audience, enter the value its tokens carry. See Choosing the Audience Value.

  4. Assigned roles and Assigned project groups: Select the roles and project groups to grant to every token that matches this rule.

  5. Roles claim and Project groups claim: Optionally enter a claim name for dynamic grants. At authentication time, Develocity reads that claim from the token and matches its values against the identity provider (IdP) attribute values configured on each role or project group. Both string and list claims are supported: a list-valued claim grants all roles or project groups whose IdP attribute value matches any element.

After you save the rule, Develocity opens the claim requirements screen. Add one or more claim requirements that must all match. Available match types are Equals, Starts With, Ends With, Contains, and Regular Expression.

A rule with no claim requirements never matches any token. At least one claim requirement must be added before the rule can authenticate a workload. If you truly need to accept tokens with no other claim criteria, you can add a requirement for the issuer or audience claim.

Each rule also carries Read isolation level and Write isolation level under Universal Cache isolation, which appear only while trusted artifact isolation is on. See Universal Cache Isolation.

Save stores the rule as a draft without applying it. A banner then appears at the top of the page: There are saved configuration changes that have not been applied. Select Apply in that banner to put the rule into effect. If you leave the page or select Discard first, you lose the change. Develocity does not restart when you apply a workload identity change.

Claim Requirement Match Types

Match Type Behavior

Equals

Claim value must match exactly.

Starts With

Claim value must begin with the specified string.

Ends With

Claim value must end with the specified string.

Contains

Claim value must include the specified string.

Regular Expression

Claim value must match the regular expression.

Starts With, Ends With, and Contains compare the requirement string literally against the claim value. The boundary character is not a special or stripped token; it is simply the character that bounds a segment in the claim’s own format, matched as part of the required string. For example, the / in a Jenkins job URL and the @ in a GitHub workflow ref both follow from the claim’s own format. Any character can serve as the boundary as long as it naturally ends a segment in the real claim value.

Include the boundary character at the end of a Starts With string (or the start of an Ends With string), or the requirement over-matches. A Starts With requirement of https://jenkins.example.com/job/acme-app on a Jenkins sub also matches https://jenkins.example.com/job/acme-app-fork/ because the match is a literal prefix check. Adding the trailing / (https://jenkins.example.com/job/acme-app/) stops the match at the segment boundary. Develocity does not enforce this. It accepts a requirement with no boundary character and silently matches every value that shares the prefix, suffix, or substring. It does not reject the configuration.

Choosing Claim Requirements

Claim requirements decide which workloads a rule admits, so build them from values that a workload cannot choose for itself.

On a shared issuer such as GitHub.com or GitLab.com, any repository can request a token with your audience. Always require your organization or namespace there, and require it on its ID claim, such as repository_owner_id or namespace_id, rather than on its name. After a rename or deletion, someone else can register the freed name, and a requirement on the name then admits their workloads. A single-tenant issuer that only your own CI system uses, such as Jenkins or a self-managed GitLab instance, does not need this requirement, because it issues tokens only to your own workloads.

Whoever can create repositories, branches, or jobs chooses the other names in a token: the repository, project path, branch or ref, workflow path, or Jenkins job URL. Prefer an ID claim where one exists. An Equals requirement on a name is an acceptable trade for readability when the same rule pins the organization by ID, because only your organization can create names under it. A branch requirement is only as strong as the control over who can push to that branch, so protect the branch in your CI provider.

Use Equals wherever you can. Starts With, Ends With, Contains, and Regular Expression accept any value crafted to fit them: a repository Ends With /acme-app requirement also matches evil/acme-app, a repository anyone can create. A Regular Expression must match the entire claim value, and . in it matches any character, so escape a literal dot as \..

Choosing the Audience Value

The audience the CI job requests becomes the token’s aud claim, and it must match the rule’s Audience exactly. Develocity does not normalize case, scheme, or a trailing slash, so a token issued for https://develocity.example.com/ does not match a rule whose Audience is https://develocity.example.com.

If your CI provider lets the job choose the audience, use your Develocity server URL: the scheme and host name, with the port only if it is not the default, and no path or trailing slash. Do not rely on the audience a provider assigns when the job requests none. GitHub Actions, for example, defaults to the repository owner URL, which other services that trust your organization’s tokens also accept.

If the provider sets a fixed audience, enter the value its tokens carry as the rule’s Audience.

The audience stops anyone from replaying a token issued for another service against Develocity. It does not limit who can get a token: on GitHub.com or GitLab.com, any repository can request one with your audience. Claim requirements are what restrict access, as Choosing Claim Requirements explains.

Provider Examples

Each provider emits its own set of claims. Configure the issuer and audience, then add claim requirements that scope the rule to an organization, a repository, or a branch, following Choosing Claim Requirements. Develocity reads each required claim as a string, so scope only on string-valued claims: numeric, boolean, and array claims are treated as absent and the requirement fails.

GitHub Actions

GitHub Actions tokens use the issuer https://token.actions.githubusercontent.com. Pass your Develocity server URL to getIDToken() and enter the same value as the rule’s Audience. Calling getIDToken() with no argument issues the token for the repository owner URL, such as https://github.com/acme, which is not specific to Develocity. Scope on the dedicated string claims repository_owner_id, repository_owner, repository_id, repository, ref, workflow_ref, and job_workflow_ref. GitHub emits repository_owner_id and repository_id as string values that survive renames. The organization row uses repository_owner_id for that reason, and you can use repository_id with Equals in place of repository when rename safety matters more than a readable rule. Your organization’s ID is the id field returned by https://api.github.com/orgs/acme. Combine every other row with the organization row, so that each rule admits only your organization’s repositories.

Goal Claim requirement(s)

Limit to an organization

repository_owner_id Equals your organization’s ID, such as 1234567

Limit to a single repository

repository Equals acme/acme-app

Limit to a single branch

repository Equals acme/acme-app and ref Equals refs/heads/main

Limit to a single workflow file

workflow_ref Starts With acme/acme-app/.github/workflows/release.yml@

Limit to a shared reusable workflow

repository_owner_id Equals your organization’s ID and job_workflow_ref Starts With acme/ci-workflows/.github/workflows/build.yml@

The workflow_ref and job_workflow_ref claims both use the format {owner}/{repo}/.github/workflows/{file}@{ref}. workflow_ref identifies the top-level workflow that started the run, so scoping on it pins authentication to one workflow file and rejects every other workflow in the repository. job_workflow_ref identifies the reusable workflow the job actually calls, which lets a central repository own the build logic and authorize only jobs that run it. Any repository can call a public reusable workflow, so the job_workflow_ref requirement alone admits callers from other accounts: keep the repository_owner_id requirement, which identifies the calling repository’s owner. Include the trailing @ when scoping to a file regardless of ref, or use Equals with a full @{ref} suffix to pin a specific ref.

Do not scope on sub. GitHub gives repositories created after 2026-07-15 an immutable sub that embeds numeric owner and repository IDs, so a sub Starts With rule written for the older format stops matching with no warning. The claims ref_type (branch or tag) and environment are also string-valued if you need to narrow further.

For the workflow setup that mints and supplies the token, see Authenticate With Workload Identity, or the GitHub Actions OpenID Connect documentation for the underlying GitHub mechanism.

GitLab CI

GitLab CI tokens use the GitLab instance URL as the issuer, such as https://gitlab.com or https://gitlab.example.com. Set aud: in the job’s id_tokens block to your Develocity server URL, and enter the same value as the rule’s Audience. Scope on the string claims namespace_id, namespace_path, project_id, project_path, and ref. On GitLab.com, combine every other row with the namespace row, so that each rule admits only your group’s projects.

Goal Claim requirement(s)

Limit to a group or namespace

namespace_id Equals your group’s ID, such as 1234567

Limit to a single project

project_path Equals acme/acme-app

Limit to a single branch

project_path Equals acme/acme-app and ref Equals main

To also exclude tag pipelines, add ref_type Equals branch. GitLab emits project_id and namespace_id as string values that survive renames. The namespace row uses namespace_id for that reason, and you can use project_id with Equals in place of project_path when rename safety matters more than a readable rule.

For the job setup that mints and supplies the token, see Authenticate With Workload Identity, or the GitLab ID token authentication documentation for the underlying GitLab mechanism.

Jenkins

The OpenID Connect Provider plugin makes Jenkins an OIDC issuer. Its issuer is your Jenkins URL followed by /oidc, such as https://jenkins.example.com/oidc. Set the audience on the OpenID Connect ID Token credential in Jenkins to your Develocity server URL, and enter the same value as the rule’s Audience.

By default the plugin sets sub to the job URL (JOB_URL) and a numeric build_number that Develocity cannot match, and it exposes no repository or branch claims, so scope on sub. JOB_URL always ends in a trailing slash, and for a multibranch pipeline the branch is a child job whose name is percent-encoded, such as https://jenkins.example.com/job/acme-app/job/main/.

Goal Claim requirement(s)

Limit to all jobs in a folder

sub Starts With https://jenkins.example.com/job/acme-team/

Limit to a single job

sub Equals https://jenkins.example.com/job/acme-app/

Limit to all branches of a multibranch pipeline

sub Starts With https://jenkins.example.com/job/acme-app/

Limit to a single branch

sub Equals https://jenkins.example.com/job/acme-app/job/main/

Always include the trailing slash on an Equals value. To scope on anything other than the job URL, add a custom claim template in Jenkins under Manage Jenkins  Security  ID Token that exposes a claim such as branch. Admins who store the credential on a folder can use the folder-scoped issuer, which appends the folder path to the issuer, such as https://jenkins.example.com/oidc/job/acme-team.

For the pipeline setup that binds and supplies the token, see Authenticate With Workload Identity.

Universal Cache Isolation

The Workload identity page also carries the controls for trusted artifact isolation, which splits every Universal Cache key into a trusted entry and an untrusted one. They live here because isolation levels come from claim matching, so they can only be assigned to a workload identity rule, never to an access key.

Under Universal Cache isolation:

  • Enable Universal Cache isolation turns isolation on for the installation. It is off by default, and upgrading does not turn it on.

  • Read isolation level and Write isolation level on each rule decide which of the two entries a matching workload writes, and which it may read. Develocity hides these per-rule controls while isolation is off.

Enabling isolation changes cache behavior for every workload, and the order in which you enable it and set the levels matters. For what each level does, the one combination Develocity refuses, which levels suit each kind of workload, and the procedure for turning isolation on, see Trusted Artifact Isolation.

Troubleshooting

A problem reported against a workload identity rule is usually one of three things: Develocity cannot reach the rule’s issuer, the token’s audience does not match the rule, or the rule’s isolation levels are producing cache behavior you did not expect.

Issuer Key-Refresh Failures

Develocity displays an admin notice when it cannot refresh signing keys from a configured issuer. Develocity fetches signing keys (JWKS) from each configured issuer and retries every minute if the fetch fails. The admin notice appears once an issuer has been unreachable for 15 minutes. After 24 hours without a successful refresh, Develocity drops the cached keys and token validation for that issuer fails, breaking any builds that use it.

To diagnose:

  1. Navigate to Administration  Access control  Workload identity. The rule list shows the JWKS status per issuer.

  2. Select Test on the affected rule to attempt a live fetch and see the error.

  3. Check Develocity server logs for WARN-level messages from WorkloadIdentityRegistry.

Common causes:

  • The OIDC configuration endpoint at {issuer}/.well-known/openid-configuration is unreachable from the Develocity host (firewall, egress proxy, or DNS).

  • The JWKS endpoint (from the jwks_uri field in the discovery document) is unreachable.

  • The issuer URL is wrong: typo, trailing-slash mismatch, or HTTP instead of HTTPS.

  • A TLS trust failure: self-signed or expired certificate, or missing intermediate CA.

Per the OpenID Connect Discovery 1.0 spec, the issuer must be an HTTPS URL with no query string or fragment, and must match the iss claim in tokens exactly, including any trailing slash.

Develocity fetches {issuer}/.well-known/openid-configuration and reads the jwks_uri field from the JSON response. It then fetches the JWK Set at that URI, which must be a JSON document with a keys array containing RSA or EC public keys. Individual keys that cannot be parsed are logged and skipped; if all keys in the set fail to parse, the refresh fails.

Fix the issuer URL or network path, or add the CA certificate to Develocity’s trusted store if TLS is the cause. Develocity retries every minute and clears the notice once a fetch succeeds.

Audience Mismatch

When a build fails to authenticate, the issuer’s JWKS status is healthy, and the claim requirements look correct, compare the token’s audience with the rule’s. Develocity skips a rule whose Audience does not appear in the token’s aud claim. A token that matches no rule is rejected as an invalid credential, and the error does not say whether the audience or a claim requirement failed.

Common causes:

  • The CI job requests no audience, so the token carries the provider’s default, such as the repository owner URL on GitHub Actions.

  • The two values differ by a trailing slash, the scheme, the port, or a path.

  • The job was copied from another installation and still requests that server’s URL.

To see what the token carries, decode its payload in the CI job and print only the aud claim, never the token itself. Change whichever side is wrong so the two strings are identical, following Choosing the Audience Value.

Unexpected Cache Behavior

Trusted artifact isolation decides what a build may read and write from the levels on the rule its credentials matched, so a mistake in those levels surfaces as a cache problem rather than an authentication one. A build that starts missing every cache key, a cache response of 409 Conflict carrying an X-Cache-Isolation-Conflict header, and a job whose Artifact Cache summary reports zero trusted entries all share that shape: the token validated and the rule matched, and only the isolation levels are wrong.

Diagnose these from Trusted Artifact Isolation, which gives the cause and the remedy for each.