cicd-external-ci-federates-as
An external CI system (GitHub Actions/GitLab CI/Azure DevOps) with an OIDC/WIF trust into a cloud role/SA can federate in as that identity; escalation roll-ups are federation-chains'.
match
A conjunctive graph pattern. Variables (?x) bind node ids; every clause must hold.
where
node_type(?ci_ext) in [
ExternalIdentity, FederatedIdentity] # the CI provider's OIDC identity
?ci_ext is an external CI issuer (GitHub Actions / GitLab CI / Azure DevOps OIDC)
node_type(?internal) in [Role, ServiceAccount, MachineIdentity] # the cloud identity the CI deploys as
exists TrustsExternalIdentity(?anchor -> ?ci_ext) backing this mapping (the role/SA OIDC trust)
emit
| source type | FederatedIdentity |
|---|---|
| target type | Role |
| source | ?ci_ext |
| target | ?internal |
| conditions | trust_condition |
| state logic | DELEGATED to federation-chains federation-maps-to-identity: ACTIVE when the CI OIDC trust condition is BROAD/UNPINNED (no `sub` pin so any repo/project/branch workflow token matches, a wildcard/shared `aud`, an attacker-runnable branch), CONDITIONAL(trust_condition) when pinned to a specific repo:owner/repo:ref / project / pipeline the attacker cannot run, BLOCKED if the OIDC provider is disabled/deleted. This rule inherits that exact state — it is the same collapse keyed to a CI-provider ?ci_ext; do not compute a stronger state here. |
Narrative
The external CI system {ci_ext.name} holds an OIDC/WIF trust into the cloud identity {internal.name}; a workflow it runs presents a token {internal.name}'s trust accepts, letting the CI federate in as {internal.name} with no static credential (federation-chains rolls up the foothold/impersonation).
Raw rule rules/derived/cicd-chains.yaml
id: cicd-external-ci-federates-as
emits: CanFederateAs
description: An external CI system (GitHub Actions/GitLab CI/Azure DevOps) with an OIDC/WIF trust into
a cloud role/SA can federate in as that identity; escalation roll-ups are federation-chains'.
applies_to:
- '*'
match:
- - ci_ext: null
- ExternalIdentityMapsTo
- internal: null
where:
- 'node_type(?ci_ext) in [ExternalIdentity, FederatedIdentity] # the CI provider''s OIDC identity'
- ?ci_ext is an external CI issuer (GitHub Actions / GitLab CI / Azure DevOps OIDC)
- 'node_type(?internal) in [Role, ServiceAccount, MachineIdentity] # the cloud identity the CI deploys
as'
- exists TrustsExternalIdentity(?anchor -> ?ci_ext) backing this mapping (the role/SA OIDC trust)
emit:
source_type: FederatedIdentity
target_type: Role
source: ?ci_ext
target: ?internal
permissions: []
conditions:
- trust_condition
state_logic: "DELEGATED to federation-chains federation-maps-to-identity: ACTIVE when the CI OIDC trust\
\ condition is BROAD/UNPINNED (no `sub` pin so any repo/project/branch workflow token matches, a wildcard/shared\
\ `aud`, an attacker-runnable branch), CONDITIONAL(trust_condition) when pinned to a specific repo:owner/repo:ref\
\ / project / pipeline the attacker cannot run, BLOCKED if the OIDC provider is disabled/deleted.\
\ This rule inherits that exact state \u2014 it is the same collapse keyed to a CI-provider ?ci_ext;\
\ do not compute a stronger state here."
confidence: min(contributing_confidences)
derived_from:
- ?ci_ext ExternalIdentityMapsTo ?internal
- ?anchor TrustsExternalIdentity ?ci_ext (CI OIDC trust)
false_positive_note: "CITE, DO NOT DUPLICATE federation-chains: this rule exists only to name the external-CI\
\ case explicitly and dedupes with federation-chains federation-maps-to-identity on (CanFederateAs,\
\ ?ci_ext, ?internal) \u2014 emit once. The CanEnter*/CanImpersonate/escalation roll-ups off this\
\ CanFederateAs are federation-chains' (federate-then-enter-*, federate-inherits-capabilities) \u2014\
\ never re-derive them here. The entire judgment is the OIDC `sub`/`aud`/issuer pin: a correctly pinned\
\ GitHub-Actions trust (sub == repo:owner/repo:ref:...) is a legitimate control => CONDITIONAL, not\
\ ACTIVE. Do not treat the mere existence of a CI federation as compromise. If ?internal is a plain\
\ User, CanFederateAs's target set excludes it \u2014 keep it as FederatesTo (see federation-chains)."
narrative: The external CI system {ci_ext.name} holds an OIDC/WIF trust into the cloud identity {internal.name};
a workflow it runs presents a token {internal.name}'s trust accepts, letting the CI federate in as
{internal.name} with no static credential (federation-chains rolls up the foothold/impersonation).