# 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: ```yaml # 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: ```hcl 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:` | `iss` | `oidc_attestor:iss:https://keycloak.example.org/realms/platform` | | `oidc_attestor:sub:` | `sub` | `oidc_attestor:sub:f47ac10b-58cc-4372-a567-0e02b2c3d479` | | `oidc_attestor:email:` | `email` | `oidc_attestor:email:operator@guildhouse.coop` | | `oidc_attestor:group:` | `groups[]` | `oidc_attestor:group:platform-engineers` | ## Registration Entry Matching To grant an SVID to workloads authenticated via OIDC: ```bash spire-server entry create \ -spiffeID spiffe://guildhouse.io/ns/prod/sa/web-server \ -parentID spiffe://guildhouse.io/spire/agent/k8s_psat/guildhouse/... \ -selector oidc_attestor:sub:f47ac10b-58cc-4372-a567-0e02b2c3d479 ``` Multiple selectors can be combined (AND logic): ```bash spire-server entry create \ -spiffeID spiffe://guildhouse.io/ns/prod/sa/admin-tool \ -parentID spiffe://guildhouse.io/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. |