CI identity federation setup: OCI Identity Domains + GitHub Actions OIDC¶
This document is a recommendation for whoever administers the tenancy's OCI Identity
Domain. Every object described below cannot be provisioned by this repository — creating a
Confidential Application, an Identity Propagation Trust, or Service Users all require Identity
Domain Administrator privileges that oci-nuke's own code, CI workflows, and read-only research
credentials do not have and must not be given. github-workflows' oci-nuke-plan.yaml/oci-nuke-apply.yaml
(--auth github-oidc, Plan 07-01/07-03) is written to consume the objects described here once a
domain administrator creates them; it does not create them itself, and it has not yet been
exercised against a real trust (see "What this document does not prove" at the end).
Why this exists¶
oci-nuke run --auth github-oidc exchanges a GitHub Actions OIDC JWT for an OCI User Principal
Session Token (UPST) via OCI Identity Domains' JWT-to-UPST token exchange
(POST https://<domainURL>/oauth2/v1/token, grant_type=urn:ietf:params:oauth:grant-type:token-exchange).
That exchange is brokered by two Identity Domain objects a domain administrator must create
out-of-band, plus ordinary OCI IAM underneath them. None of this is oci-nuke-specific
configuration — it is standard OCI Identity Domain administration, the same shape any other
GitHub-Actions-to-OCI federation setup would need.
1. Confidential Application¶
Create one Confidential Application (an OAuth client) in the Identity Domain:
- Client Credentials grant enabled — this is the only grant the token exchange needs.
- Deliberately grant it no Identity Domain Administrator role. Its only job is authenticating the token-exchange call itself (via HTTP Basic auth, client ID + client secret); it has no need for, and must not receive, any administrative capability over the domain.
- The resulting client ID and client secret become
OCI_OIDC_CLIENT_IDandOCI_OIDC_CLIENT_SECRETin GitHub Actions secrets (see the README's "CI: GitHub Actions OIDC federation" section for howoci-nukeconsumes them — env vars only, never CLI flags).
The client secret is the one static credential this whole setup stores in CI. Treat it with the same handling rigor as any other CI secret: least-privilege environment scoping (below), a rotation plan, and never log or print it. Its blast radius is bounded by the trust rule in the next section — alone, it authenticates nothing against any OCI API.
2. Identity Propagation Trust¶
Create one Identity Propagation Trust:
| Field | Value |
|---|---|
type |
JWT |
issuer |
https://token.actions.githubusercontent.com |
publicKeyEndpoint |
https://token.actions.githubusercontent.com/.well-known/jwks |
oauthClients |
the Confidential Application's client ID from step 1 |
allowImpersonation |
true |
Plus two impersonatingServiceUsers rules mapping a GitHub Actions sub claim to a target
Service User (step 3). A job running under a named GitHub Environment produces
sub = repo:ORG/REPO:environment:ENVIRONMENT-NAME, so a plan/apply Environment split maps
directly onto a rule per Environment:
Rule 1: sub eq 'repo:naviteq/oci-nuke:environment:plan' -> read-only Service User
Rule 2: sub eq 'repo:naviteq/oci-nuke:environment:apply' -> delete-capable Service User
The exact repository name and Environment names above are illustrative. They match this
repository's own plan_environment/apply_environment inputs used as-is, but any
caller of the reusable workflow — Phase 8's terraform-modules migration included — needs its
own rule pointing at its own repository and Environment names, mapped to its own pair of Service
Users if it targets a different compartment scope than this repository does.
3. Service Users, groups, and policies¶
Create two ordinary OCI IAM Service Users, each in its own IAM group with a scoped policy — nothing new to this phase's federation mechanism, standard OCI IAM underneath the trust:
- Plan Service User (impersonation target for the
planEnvironment'ssubrule): read-only verbs (inspect,read) against the target compartment subtree only. This principal physically cannot delete anything, independent of any bug inoci-nukeitself. - Apply Service User (impersonation target for the
applyEnvironment'ssubrule):manage/delete-capable verbs against the target compartment subtree only — never tenancy-wide. Scope the policy to the specific compartment OCID(s)oci-nukewill target, matching the same least-privilege posture the compartment blocklist already enforces insideoci-nukeitself.
Draft policy statements belong in each caller's own IAM configuration, scoped to that caller's real compartment topology — this document sketches the shape, not literal, ready-to-apply policy text, since the exact compartment OCIDs are per-caller and not something this repository's research session had visibility into.
4. Audience recommendation¶
Request an explicit --oidc-audience value when invoking oci-nuke run --auth github-oidc,
defaulting to the Identity Domain's own URL (e.g. https://idcs-xxxx.identity.oraclecloud.com),
rather than leaving it unset. This mirrors the convention AWS (sts.amazonaws.com) and Azure
(api://AzureADTokenExchange) both use: an explicit, service-specific audience value on the
requested OIDC token.
This is a defensive default, not a confirmed enforcement mechanism — this phase's research
could not determine whether OCI's Identity Propagation Trust object exposes or enforces an aud
(audience) restriction at all; every primary source found describes issuer + JWKS + sub-rule
matching, and none discuss audience validation on OCI's side. Requesting an explicit audience
costs nothing and is easy for a domain administrator to start enforcing later without any
oci-nuke code change, so it is recommended regardless of whether OCI currently checks it.
5. What this document does not prove¶
None of the objects described above have been created or exercised against a real tenancy this
phase. A live, read-only probe (2026-08-09) confirmed the tenancy has exactly one Identity
Domain (Default, Free tier, ACTIVE, home region us-ashburn-1) and found no evidence one way
or the other about any existing Confidential Application or Identity Propagation Trust — the
IDCS admin API needed to check either requires credentials this research session did not have
and, per the read-only/no-mutating-calls constraint, must not request. --auth github-oidc is
implemented and unit-tested end-to-end against stubbed HTTP servers standing in for both GitHub's
OIDC endpoint and OCI's /oauth2/v1/token endpoint (Plan 07-01), and github-workflows' oci-nuke-plan.yaml/oci-nuke-apply.yaml
is actionlint-clean and invokes oci-nuke with exactly those flags (Plan 07-03) — but the full
chain (a real GitHub Actions job → a real JWT → a real token exchange → a real UPST →
VerifyTenancy → a real OCI API call) has never run end to end. Phase 8's e2e harness, once
terraform-modules becomes the real caller and a domain administrator has provisioned the trust
for real, is where that live proof happens — not this document, and not any Phase 7 plan's DoD.
6. Fork-PR warning (caller-side responsibility)¶
The shared workflows' declared secrets (oidc_domain_url/oidc_client_id/
oidc_client_secret) become reachable by whatever triggered the calling workflow. Any caller
of oci-nuke-plan.yaml or oci-nuke-apply.yaml MUST NOT invoke them from a
pull_request-triggered job with secrets: inherit against untrusted forks. Doing so would let
fork-authored code run with access to OCI_OIDC_CLIENT_SECRET, the one static credential this whole
setup depends on keeping confidential. No workflow YAML can enforce a caller's trigger choice; it
can only be documented — here, and in github-workflows' docs/oci-nuke.md — and enforced by
whoever configures each calling workflow's trigger.