guildhouse-spire-plugins/docs/oidc-attestation.md
Tyler King a58d548518 feat: network-policy extension, governance lifecycle, audit remediation
- Network-policy SPIRE plugin extension
- Governance event notification with merkle anchoring
- Shellstream specs for consent channels + HFL embedded ABI
- All 17 audit findings from AUDIT.md remediated
- SSH credential composer + substrate key manager updates
- Test coverage for config + sshcert packages

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-18 15:54:46 -04:00

6.4 KiB

OIDC Workload Attestation

How the oidc-attestor plugin identifies workloads via OIDC tokens.

Flow

Workload         SPIRE Agent         oidc-attestor         OIDC Provider
   |                  |                    |                     |
   |  FetchX509SVID   |                    |                     |
   |----------------->|                    |                     |
   |                  |  Attest(pid=1234)  |                     |
   |                  |------------------->|                     |
   |                  |                    |  GET /.well-known/  |
   |                  |                    |  openid-configuration|
   |                  |                    |-------------------->|
   |                  |                    |  JWKS endpoint      |
   |                  |                    |<--------------------|
   |                  |                    |                     |
   |                  |                    | Read token from     |
   |                  |                    | /var/run/secrets/   |
   |                  |                    | oidc/token          |
   |                  |                    |                     |
   |                  |                    | Verify signature    |
   |                  |                    | via JWKS            |
   |                  |                    |                     |
   |                  |  Selectors         |                     |
   |                  |<-------------------|                     |
   |  X509-SVID       |                    |                     |
   |<-----------------|                    |                     |

Token Discovery

The plugin discovers the workload's OIDC token via a configurable path. In Kubernetes, the token is typically projected into the pod filesystem:

# Pod spec
volumes:
  - name: oidc-token
    projected:
      sources:
        - serviceAccountToken:
            audience: spire
            expirationSeconds: 3600
            path: token
containers:
  - name: app
    volumeMounts:
      - name: oidc-token
        mountPath: /var/run/secrets/oidc
        readOnly: true

Plugin configuration:

WorkloadAttestor "guildhouse_oidc" {
    plugin_cmd = "/opt/spire/plugins/oidc-attestor"
    plugin_data {
        issuer = "https://keycloak.guildhouse.example.org/realms/platform"
        audience = "spire"
        token_path = "/var/run/secrets/oidc/token"
    }
}

Token Verification

  1. Plugin reads the JWT from the configured path.
  2. Fetches the OIDC discovery document from {issuer}/.well-known/openid-configuration.
  3. Retrieves the JWKS from the jwks_uri in the discovery document.
  4. Verifies the JWT signature using the matching key from the JWKS.
  5. Validates standard claims: iss matches configured issuer, aud includes configured audience, exp is in the future.

Selector Output

The plugin returns selectors that SPIRE uses to match workloads against registration entries:

Selector Source Claim Example
oidc_attestor:iss:<value> iss oidc_attestor:iss:https://keycloak.example.org/realms/platform
oidc_attestor:sub:<value> sub oidc_attestor:sub:f47ac10b-58cc-4372-a567-0e02b2c3d479
oidc_attestor:email:<value> email oidc_attestor:email:operator@guildhouse.coop
oidc_attestor:group:<value> groups[] oidc_attestor:group:platform-engineers

Registration Entry Matching

To grant an SVID to workloads authenticated via OIDC:

spire-server entry create \
  -spiffeID spiffe://guildhouse.dev/ns/prod/sa/web-server \
  -parentID spiffe://guildhouse.dev/spire/agent/k8s_psat/guildhouse/... \
  -selector oidc_attestor:sub:f47ac10b-58cc-4372-a567-0e02b2c3d479

Multiple selectors can be combined (AND logic):

spire-server entry create \
  -spiffeID spiffe://guildhouse.dev/ns/prod/sa/admin-tool \
  -parentID spiffe://guildhouse.dev/spire/agent/k8s_psat/guildhouse/... \
  -selector oidc_attestor:iss:https://keycloak.example.org/realms/platform \
  -selector oidc_attestor:group:platform-engineers

JWKS Caching

The plugin caches JWKS responses for the duration specified by the Cache-Control header (or 5 minutes if not present). This avoids hitting the OIDC provider on every attestation.

Error Handling

Error Condition Plugin Behavior Impact
OIDC provider unreachable Returns an error from Attest(). SPIRE Agent logs the failure but may succeed with other attestors (e.g., k8s). Workloads relying solely on OIDC attestation will not receive SVIDs until connectivity is restored.
Token file not found Returns an empty selector set (no error). The workload may still match via other attestors. No OIDC-derived selectors; registration entries requiring oidc_attestor:* selectors will not match.
Token expired Returns an error. The exp claim is validated against the current time with a small clock skew tolerance. Workload must refresh its projected token. Kubernetes projected tokens auto-rotate, so this typically resolves within the expirationSeconds window.
JWKS key not found Returns an error. The token's kid header does not match any key in the cached JWKS. May indicate key rotation at the OIDC provider. The plugin will refetch JWKS on the next cache expiry.
Invalid token signature Returns an error. The token was not signed by a key in the JWKS. Possible token tampering or misconfigured issuer. Check that issuer in plugin config matches the token's iss claim.
Audience mismatch Returns an error. The token's aud claim does not include the configured audience. Check that the projected ServiceAccountToken uses the correct audience value.

Security Note: Custom Claims

OIDC tokens may contain custom claims (e.g., tenant_id, roles) that are populated by the identity provider's protocol mappers. These claims MUST NOT be trusted for authorization without server-side verification. In self-managed Keycloak deployments, users can modify their own protocol mappers to inject arbitrary values into custom claims.

The tenant_id claim in particular MUST be cross-referenced against the server-side tenant registry. The plugin SHOULD derive tenant identity from the SPIRE registration entry or a trusted server-side mapping, not solely from the OIDC token's tenant_id claim.