guildhouse-spire-plugins/docs/oidc-attestation.md

4.2 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.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):

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.