guildhouse-spire-plugins/docs/plugin-types.md
Tyler King 420a4e2ea0 Remediate all 17 audit findings from AUDIT.md
Critical fixes:
- F-01: SatScope array form support (single pointer → slice with polymorphic JSON)
- F-02: Add governance-intent@guildhouse.dev as 10th Shellstream extension
- F-06: Replace os.Exit(1) stubs with go-plugin Serve() boilerplate in all cmd/
- F-13: Validate SatScope.ResourcePattern is non-empty

High priority:
- F-03: Add normative Accord policy syntax note to credential-governance.md §8.2
- F-04: Replace OID XXXXX placeholder with explicit PEN reference and IANA TODO
- F-05: Document CredentialComposer hook mapping in spec and plugin-types.md
- F-07/F-08: Commit CI pipeline (.github/workflows/ci.yaml)
- F-09: Add hashicorp/go-plugin v1.6.3 to go.mod

Medium priority:
- F-10: Wire sample-ssh-cert-extensions.json fixture into shellstream tests
- F-11: Cross-reference merkle proof depth limit (256 leaves) in governance spec
- F-12: Add YAML format clarification headers to deploy configs
- F-14: Expand README with project status, docs links, and quick-start

Low priority:
- F-15: Standardize "SSH SVID" → "SSH-SVID" terminology across docs
- F-16: Add GovernanceEpochSeconds to PluginConfig and deploy configs
- F-17: Add troubleshooting section to deployment.md, error handling to OIDC docs

Global: Rename all extension keys from @guildhouse.io to @guildhouse.dev

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-18 11:45:33 -05:00

6.4 KiB

SPIRE Plugin Types

This document describes the four SPIRE plugin interfaces implemented by this repository, their callback methods, invocation timing, and the Guildhouse plugin that implements each.

Overview

SPIRE's plugin architecture uses the go-plugin (hashicorp/go-plugin) framework. Plugins are compiled as separate binaries and loaded by the SPIRE Agent or Server at startup via the plugin configuration in server.conf or agent.conf.

Interface Side Guildhouse Plugin Purpose
WorkloadAttestor Agent oidc-attestor Identify workloads by OIDC token
CredentialComposer Server ssh-credential-composer Modify credentials during minting
Notifier Server governance-notifier React to SPIRE lifecycle events
KeyManager Server substrate-keymanager Manage server signing keys

WorkloadAttestor

Side: SPIRE Agent

Plugin: cmd/oidc-attestor

Interface methods:

Attest(ctx context.Context, pid int32) ([]*common.Selector, error)

When called: Every time a workload calls the Workload API (typically FetchX509SVID or FetchJWTSVID). The agent identifies the calling process by PID, then passes that PID to all registered WorkloadAttestor plugins. Each plugin returns zero or more selectors.

What oidc-attestor does: Given the workload PID, it discovers an OIDC token associated with that process (via a projected volume path or environment variable), validates the token signature against a JWKS endpoint, and returns selectors derived from token claims (issuer, subject, audience, custom claims).

Selector format:

oidc_attestor:iss:<issuer>
oidc_attestor:sub:<subject>
oidc_attestor:aud:<audience>
oidc_attestor:claim:<key>:<value>

CredentialComposer

Side: SPIRE Server

Plugin: cmd/ssh-credential-composer

Interface methods:

ComposeServerX509CA(ctx context.Context, attributes X509CAAttributes) (X509CAAttributes, error)
ComposeServerX509SVID(ctx context.Context, attributes X509SVIDAttributes) (X509SVIDAttributes, error)
ComposeAgentX509SVID(ctx context.Context, attributes X509SVIDAttributes) (X509SVIDAttributes, error)
ComposeWorkloadX509SVID(ctx context.Context, attributes X509SVIDAttributes) (X509SVIDAttributes, error)
ComposeWorkloadJWTSVID(ctx context.Context, attributes JWTSVIDAttributes) (JWTSVIDAttributes, error)

When called: During credential minting on the server. After the server decides to issue a credential (X.509 SVID, JWT SVID, or CA certificate), it passes the proposed attributes through all registered CredentialComposer plugins. Each plugin may modify the attributes before the credential is signed.

What ssh-credential-composer does: Intercepts ComposeWorkloadX509SVID to handle SSH credential composition. Because SPIRE v1.9 does not define an SSH-specific CredentialComposer hook, the plugin dispatches on registration entry selectors or hints (e.g., a ssh-svid: true selector) to identify SSH-destined requests. It reads the SPIFFE ID and registration entry metadata, calls GovernanceService to create a MutationIntent for the issuance, constructs a MutationEnvelope, anchors it via NotaryService, then encodes the governance metadata as Shellstream extensions in the SSH certificate's critical options using the pkg/shellstream encoder.

Hook mapping detail: The composer implements all 5 CredentialComposer methods but only performs governance logic in ComposeWorkloadX509SVID when the request matches SSH selectors. All other methods (ComposeServerX509CA, ComposeServerX509SVID, ComposeAgentX509SVID, ComposeWorkloadJWTSVID) return attributes unmodified. See specs/spiffe-ssh-svid.md Section 4, Step 4 for the normative hook mapping note.

Notifier

Side: SPIRE Server

Plugin: cmd/governance-notifier

Interface methods:

Notify(ctx context.Context, event *NotifyRequest) (*NotifyResponse, error)
NotifyAndAdvise(ctx context.Context, event *NotifyAndAdviseRequest) (*NotifyAndAdviseResponse, error)

When called:

  • Notify: After a bundle update or registration entry change has been committed. This is fire-and-forget; the server does not block on the response.
  • NotifyAndAdvise: Before certain operations where the plugin can influence the outcome. The server waits for the response.

Event types the plugin handles:

  • BundleUpdated — A trust bundle was rotated.
  • EntryUpdated — A registration entry was created, updated, or deleted.

What governance-notifier does: On bundle rotation events, it creates a MutationIntent via GovernanceService to record the rotation as an auditable governance event. For sensitive entry changes (e.g., high-privilege SPIFFE IDs), it triggers a CeremonyService flow requiring multi-stakeholder approval. All events are anchored in the merkle tree via NotaryService.

KeyManager

Side: SPIRE Server

Plugin: cmd/substrate-keymanager

Interface methods:

GenerateKey(ctx context.Context, req *GenerateKeyRequest) (*GenerateKeyResponse, error)
GetPublicKey(ctx context.Context, req *GetPublicKeyRequest) (*GetPublicKeyResponse, error)
GetPublicKeys(ctx context.Context, req *GetPublicKeysRequest) (*GetPublicKeysResponse, error)
SignData(ctx context.Context, req *SignDataRequest) (*SignDataResponse, error)

When called:

  • GenerateKey: When the server needs a new signing key (initial startup, key rotation).
  • GetPublicKey/GetPublicKeys: When the server needs to retrieve existing key material.
  • SignData: Every time the server signs a credential (SVID, CA cert).

What substrate-keymanager does: Manages the SPIRE Server's signing keys with governance-aware lifecycle. Key generation and rotation events are recorded as MutationIntents. The plugin stores keys in a configured backend (filesystem, Kubernetes Secret, or remote KMS) and ensures every key operation is traceable through the governance audit trail.

Plugin Loading Order

SPIRE loads plugins in the order they appear in configuration. For correctness:

  1. substrate-keymanager must load first (server needs signing keys before issuing anything).
  2. ssh-credential-composer loads next (must be present before any credential minting).
  3. governance-notifier loads last (reacts to events that other plugins may trigger).

On the agent side, oidc-attestor is loaded alongside any other workload attestors (e.g., the built-in k8s attestor). Multiple attestors can coexist; their selectors are unioned.