Repository navigation
Workload identity: a last, read-only rule for every project of a self-managed issuer - #634
Merged
Merged
Conversation
…-managed issuer
A workload_identity rule had to pin a workload identity claim, so an
administrator of a self-managed GitLab who wanted every CI job of the
instance to restore listed namespace or project IDs and kept the list
current: a top-level group does not cover its subgroups, and a project
created later got 401 until the realm was edited, which revoked every key
it had issued.
A rule can now trust every workload of the realm's issuer:
{ "role": "gitlab-ci-read", "issuer_wide": true, "conditions": [] }
- It needs no identity condition; conditions such as ref_protected only
narrow it. Validation refuses it for GitHub Actions and GitLab.com,
whose tokens anyone can mint, and rule matching refuses it there too.
- Rules are first-match, so issuer-wide rules must come after every other
rule.
- A workload it matched carries issuer_wide on its principal, and the
authz seam caps its role's grants at read, as a read-only token scope
would: a role given write never hands it to every project. The flag
travels with the exchanged key (workload_api_key.issuer_wide, migration
0005) and with the registry tokens minted from either credential.
- Writes stay pinned to a workload identity: a rule for a project ahead
of the issuer-wide one picks a role of its own, and a trusted publisher
still adds write for its own project on top of the issuer-wide read.
- The field is left out of the normalized config while unset, so realms
without such a rule keep their config digest and their keys.
The realm form's Rules panel has a per-rule "Every project of this
issuer" switch, off and disabled for the GitHub Actions and GitLab.com
issuers, with a warning naming who gets the role. A rule added while one
is on goes ahead of it. The security model in trusted-publishing.md and
the GitLab CI guide describe it.
Fixes #626
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_012e6DJZBnEKyFNGDPWaFtJj
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Fixes #626
A
workload_identityrule had to pin a workload identity claim. An administrator of a self-managed GitLab who wanted every CI job of the instance to restore from Forge therefore had to list namespace or project IDs and keep the list current. A top-level group does not cover its subgroups, and a project created later got 401 until the realm was edited, which revoked every key the realm had issued.The rule
{ "role": "gitlab-ci-read", "issuer_wide": true, "conditions": [] }ref_protected = truenarrow the rule, and none is required.https://gitlab.comandhttps://token.actions.githubusercontent.com, in any spelling of the URL (Url-normalized).rule_matchesalso refuses it when the token'sissis one of them, the same defense in depth that covers a malformed row.read.Principal::Workloadcarriesissuer_wide.Authz::effective_withintersects the role's grants with a read-only scope (level: read, no admin), the same way a read-only token scope narrows a user's grants. The flag is stored on the exchanged key (workload_api_key.issuer_wide, migration0005) and in registry tokens (fp.wide, left out when false). The cap therefore holds with the CI token, with an exchanged key and with registry tokens.writefor its own project on top of the issuer-wide read.validate_trusted_publisher_conditionsis unchanged and still requires identity claims.issuer_wideis#[serde(default, skip_serializing_if = …)], so a realm without such a rule normalizes to the same bytes and keeps its config digest. A test pins the digest.issuer_wide(deny_unknown_fields). It refuses that issuer's tokens, and the keys and registry tokens issued under that realm fail its realm check.Web UI
The Rules panel of the realm form (
RealmForm.tsx) has a per-rule switch, Every project of this issuer:wifRuleHasIdentityScopeaccepts an issuer-wide rule for a self-managed issuer, and the form explains an issuer-wide rule placed ahead of another rule.Docs
docs/trusted-publishing.md: the security model describes the exception.docs/gitlab-ci.md: the "More projects" bullet points to a new section, "Every project of the instance".docs/changes/2.2.0/626.feature.md, following NuGet: a package of more than 10 000 entries is refused (a Nexus import fails on one of 29 198), and the cap cannot go much higher #619's placement for a Backlog issue.Tests
wif.rsunit tests:https://GitLab.com/and:443) and when it is not last;trusted_publishers.rsintegration test,issuer_wide_rule_reads_and_only_pinned_rules_or_publishers_write:write;wideand onlypull;writefor its packages only.crates/db/tests/workload_api_key.rs: a key keeps itsissuer_wide.authz.rs: the read-only scope capswriteanddelete.images/token.rs: the registry token round-tripswide.toRealmConfigsendsissuer_wideonly when on and seeding reads it back. Route tests cover creating a realm with the switch, the insertion order, the ordering error, and the disabled switch for GitLab.com.Run locally:
cargo fmt --check,cargo clippy --workspace --all-targets -D warnings,cargo xtask changelog --check,cargo xtask docs --check.forge-serverlib,trusted_publishers,auth_realms,realm_seed,registry;forge-dbworkload_api_keyandmigrations;forge-corelib.pnpm run tscand the full vitest suite (476 + 3 new).Generated by Claude Code