Feature Specification: Resource spiffe
Created: 2026-10-07
Status: Implemented
Input: Reconstructed from the code of core/resources/spiffe/, the setup wiring and the spiffe requests of the components at 093e68b6f, pull requests #1184, #1205, #1238 and #1405.
User Scenarios & Testing (mandatory)
The spiffe resource gives a node a SPIFFE workload identity. It puts a spire-agent on the node, which attests with the TPM of the host. Consumers log in to OpenBao with the X.509 SVID of the agent. The resource wraps no contract and has one implementation. The shared resource rules are in the resource family spec.
User Story 1 - Give a node an identity (Priority: P1)
A component must log in to OpenBao, for example to read generated secrets (see generateFiles) or to get a certificate from the OpenBao PKI (see ssl). The component requests an identity. The consumer side then enables the agent on the node, with the routing of the policy. The agent serves the Workload API on a local socket.
Why this priority: without an identity, a node cannot read its secrets from OpenBao.
Independent Test: a unit test that each operator node runs the agent that it logs in to OpenBao with; a unit test that an enabled request gives one agent with the routing of the policy, and that no request gives nothing; a VM test of the login to OpenBao.
Acceptance Scenarios:
- Given a component that requests an identity, When its node is evaluated, Then the agent runs, attests the node with the TPM, and attests workloads by their systemd unit and their Unix user.
- Given a node with no request, for example the
spireserver node, When it is evaluated, Then the node has no agent. - Given an enabled agent, When the node is evaluated, Then the node has the TPM plugin, and the agent can open the TPM devices.
User Story 2 - Point the agent at the server of the deployment (Priority: P1)
The agent must attest to the spire-server of its deployment. The setups give each node a default routing that holds the server address. The server port and the trust domain come from one source that the server reads too. The server address is the host of the node that provides spiffe in the group. A group without such a node uses the spiffe provider of the hosting-provider group, else the spire name under the apex.
Why this priority: an agent that reaches no server gets no identity.
Independent Test: a unit test of the routing.
Acceptance Scenarios:
- Given a group with a node that provides
spiffe, When the routing is evaluated, Then the server address is the address of that node. - Given a group without a node that provides
spiffe, When the routing is evaluated, Then the server address is the name of thespiffeprovider of thehosting-providergroup, else thespirename under the apex.
User Story 3 - Give the agent its trust bundle over an authenticated channel (Priority: P2)
The SPIRE CA is made at run time, so no build can contain it. The server rotates it about daily, so no value can be pinned. The deploy gives the bundle to the node. When it registers the node, and again on each apply, the deployer copies the bundle from the spire node to the node over channels that it authenticates. On the hypervisor host, a timer does this for the host at a short interval. A second timer on the hypervisor host does this for each running container that has an agent, at the interval of the bundle export of the server, so a container has a current bundle when no deploy runs. The agent does not fetch the bundle from the network.
Before the agent starts, a step removes the persisted agent data that does not chain to the current bundle. This step does not get the bundle and does not wait for it. When the server loses its state and makes a new CA, a running agent fails its next sync and stops, and systemd starts it again. At that start the step removes the old SVID, so the agent attests again with the bundle that the deploy delivered (see spire, User Story 4). A running agent gets each CA rotation over its own stream, so its cache can hold a CA that is newer than each CA in the delivered bundle. The step keeps such a cache, also when its SVID does not chain to the delivered bundle, because a removal would make the agent go back to the older bundle. The step also removes the persisted agent data when each CA in it has expired.
Why this priority: the agent cannot attest without the CA of the server. A bundle from an unauthenticated source lets a peer on the path become the trust root of the node.
Independent Test: a test of the delivery from the hypervisor host against a stub of incus; a unit test of its timer; VM tests of the bundle delivery, of a deployed container that has the bundle of the server, of a node that activates before it has a bundle, and of the step that removes or keeps the persisted agent data.
Acceptance Scenarios:
- Given a bundle on the spire node that holds at least one valid CA, When the deployer delivers it, Then the node gets the bundle in one atomic write, readable by all and writable by none, and the agent starts again when it is not active.
- Given a bundle on the spire node that is empty, holds a block that does not parse, or holds no CA that is valid now, When the deployer delivers it, Then the delivery fails and the node keeps the bundle it has.
- Given the same bundle on the node and on the spire node, When the deployer delivers it, Then nothing changes on the node.
- Given no bundle on the node, When the step before the agent runs, Then it logs the missing bundle and succeeds, so activation completes.
- Given a persisted SVID that does not verify against the current bundle, When the step before the agent runs, Then it removes the persisted agent data, so the agent attests again.
- Given a persisted SVID that does not verify against the current bundle and a persisted bundle that holds a CA newer than each CA in the current bundle, When the step before the agent runs, Then it keeps the persisted agent data.
- Given a persisted bundle in which each CA has expired, When the step before the agent runs, Then it removes the persisted agent data.
Edge Cases
- The agent has no start limit. While no server exists, it restarts without limit, by design.
- The step before the agent runs again on each agent restart.
- The step does not wait, so its start timeout bounds only a tool that hangs.
- A container that is offline across a CA rotation gets the current bundle on the next apply. The hypervisor host gets it at the next tick of its timer.
- A reboot after a CA rotation, before the next apply, keeps the persisted bundle of the agent, because that bundle leads the delivered bundle.
- A VM, or a node that the deploy reaches over
ssh, gets no EK registration and no bundle from the deploy. - An agent can hold more than one SVID. The selection takes the host SVID of the node, else the one namespaced match of the node name.
Requirements (mandatory)
Functional Requirements
- FR-001: The request MUST accept an enable flag, off by default.
- FR-002: The policy MUST accept a routing module, empty by default.
- FR-003: The resource MUST give one consumer module when any request is enabled, and none otherwise.
- FR-004: The consumer module MUST enable the agent and import the routing of the policy.
- FR-005: The agent MUST accept the server address, the server port, a bound on the wait for its health, and the trust domain.
- FR-006: The agent MUST keep its keys in memory, attest the node with the TPM, and attest workloads by their systemd unit and their Unix user. With
nodeAttestor = "k8s_psat", the agent MUST attest the node with the service account token of the pod in place of the TPM, MUST read the trust bundle and the name of the cluster from the ConfigMap volume of the pod, and MUST read a copy of the token that a root service makes again on a timer. The setup ofk8s-hostsMUST set this attestor. - FR-007: The step before the agent MUST run before the agent starts, and MUST NOT get the bundle from the network.
- FR-008: The deployer MUST write the trust bundle to the node only when each block in it parses and at least one CA in it is valid now. The delivery from the hypervisor host MUST obey the same rule, and MUST NOT fail its unit when the spire node or a container does not answer.
- FR-009: One SVID selection rule MUST serve the OpenBao login of
generateFilesand of thesslPKI provider. - FR-010: The setups MUST add the consumer side of the resource to each node.
Key Entities
spire-agent: the SPIRE agent on the node, with its Workload API socket.- Trust bundle: the CA bundle of the
spire-server, which the deploy writes to the node. - SVID: the X.509 identity of the host under the
fediversitytrust domain, which the agent issues.
Success Criteria (mandatory)
Measurable Outcomes
- SC-001: the unit tests of the routing and the resource pass.
- SC-002: the unit test of the operator node configuration passes.
- SC-003: the VM tests of the OpenBao login, the container bundle, the cold start and the bundle delivery pass.
Assumptions
- The host has a TPM 2.0 device, or a vTPM on incus, or runs as a pod of
k8s-hosts. In a pod, the deploy gives the bundle in a ConfigMap (k8s, FR-016). Only root on the node reads the token: a service that reads the token can attest an agent of its own, and then get each SVID of the node. - The spire node exports its CA bundle to a local file.
- The node identity is registered at the server. The
spirecomponent does this, not this resource.
Known Deviations
None.