Server plugin: NodeAttestor "k8s_psat"

July 22, 2026 ยท View on GitHub

Must be used in conjunction with the agent-side k8s_psat plugin

The k8s_psat plugin attests nodes running inside of Kubernetes. The server validates the signed projected service account token provided by the agent. This validation is performed using Kubernetes Token Review API. In addition to validation, this API provides other useful information (namespace, service account name and pod name) that SPIRE server uses to build selectors. Kubernetes API server is also queried to get extra data like node UID, which is used by default to generate a SPIFFE ID with the form:

spiffe://<trust_domain>/spire/agent/k8s_psat/<cluster>/<node UID>

A cluster can instead be configured to use the attesting pod UID for the generated agent SPIFFE ID:

spiffe://<trust_domain>/spire/agent/k8s_psat/<cluster>/pod/<pod UID>

The server does not need to be running in Kubernetes in order to perform node attestation. In fact, the plugin can be configured to attest nodes running in multiple clusters.

The main configuration accepts the following values:

ConfigurationDescriptionDefault
clustersA map of clusters, keyed by an arbitrary ID, that are authorized for attestation.

Warning

When clusters is empty, no clusters are authorized for attestation.

Each cluster in the main configuration requires the following configuration:

ConfigurationDescriptionDefault
service_account_allow_listA list of service account names, qualified by namespace (for example, "default:blog" or "production:web") to allow for node attestation. Attestation will be rejected for tokens bound to service accounts that aren't in the allow list.
audienceAudience for token validation. If it is set to an empty array ([]), Kubernetes API server audience is used["spire-server"]
kube_config_filePath to a k8s configuration file for API Server authentication. A kubernetes configuration file must be specified if SPIRE server runs outside of the k8s cluster. If empty, SPIRE server is assumed to be running inside the cluster and in-cluster configuration is used.""
allowed_node_label_keysNode label keys considered for selectors
allowed_pod_label_keysPod label keys considered for selectors
use_pod_uid_for_agent_idUse the attesting pod UID instead of the node UID when generating the agent SPIFFE ID. The pod UID is prefixed with pod/ in the ID path.false

A sample configuration for SPIRE server running inside a Kubernetes cluster:

    NodeAttestor "k8s_psat" {
        plugin_data {
            clusters = {
                "MyCluster" = {
                    service_account_allow_list = ["production:spire-agent"]
                    # use_pod_uid_for_agent_id = true
                }
            }
        }
    }

A sample configuration for SPIRE server running outside of a Kubernetes cluster:

    NodeAttestor "k8s_psat" {
        plugin_data {
            clusters = {
                "MyCluster" = {
                    service_account_allow_list = ["production:spire-agent"]
                    kube_config_file = "path/to/kubeconfig/file"
                    # use_pod_uid_for_agent_id = true
                }
            }
        }
    }

Running node-UID and pod-UID agents in one Kubernetes cluster

The keys in clusters are logical attestation profiles. They do not have to correspond one-to-one with Kubernetes API servers. A single Kubernetes cluster can have one server-side entry for agents that should use node UIDs in their agent SPIFFE IDs and another entry for agents that should use pod UIDs.

    NodeAttestor "k8s_psat" {
        plugin_data {
            clusters = {
                "MyClusterNodes" = {
                    service_account_allow_list = ["spire:spire-agent"]
                }
                "MyClusterPods" = {
                    service_account_allow_list = ["spire:spire-broker-agent"]
                    use_pod_uid_for_agent_id = true
                }
            }
        }
    }

DaemonSet agents normally select the node-UID entry with the agent-side cluster = "MyClusterNodes" setting. This preserves the usual one agent identity per Kubernetes node. Deployment-based agents, or any other agents where multiple agent pods may run on the same node, select the pod-UID entry with cluster = "MyClusterPods" so each attesting pod gets a distinct concrete agent SPIFFE ID.

Because the k8s_psat:cluster:<name> selector contains the logical cluster entry name selected by the agent, node alias entries must match that name. For example, a Deployment-based agent using the spire:spire-broker-agent Service Account can have one stable alias entry for the group of pods with:

  • Parent ID: spiffe://<trust_domain>/spire/server
  • SPIFFE ID: the stable alias for that logical agent group
  • Selectors: k8s_psat:cluster:MyClusterPods, k8s_psat:agent_ns:spire, and k8s_psat:agent_sa:spire-broker-agent

Registration entries can then use the alias as their parent ID. This keeps the registration surface stable even though the concrete pod-UID agent IDs change when Deployment pods are replaced.

This pattern is useful for a TCP-only SPIFFE Broker API deployment. DaemonSet agents can keep using node-UID agent IDs for ordinary Workload API traffic, while one or more Deployment-based agents use pod-UID agent IDs to serve the Broker API over TCP. Broker SVID entries can remain parented to the DaemonSet agent alias they use through the Workload API, and object entries served by the Broker API can be parented to the Deployment-based agent alias. When the k8s workload attestor uses experimental.broker.access_policy = "enforced", Kubernetes RBAC must also allow the broker SPIFFE IDs to use SPIRE-specific impersonate-via-spire verb on the referenced objects, as described in the k8s workload attestor Broker API documentation.

The Kubernetes user defined in the kube config file needs to have ClusterRoleBindings assigned to ClusterRoles containing at least the following permissions:

- apiGroups: [""]
  resources: ["pods", "nodes"]
  verbs: ["get"]
- apiGroups: ["authentication.k8s.io"]
  resources: ["tokenreviews"]
  verbs: ["create"]

This plugin generates the following selectors:

SelectorExampleDescription
k8s_psat:clusterk8s_psat:cluster:MyClusterName of the cluster (from the plugin config) used to verify the token signature
k8s_psat:agent_nsk8s_psat:agent_ns:productionNamespace that the agent is running under
k8s_psat:agent_sak8s_psat:agent_sa:spire-agentService Account the agent is running under
k8s_psat:agent_pod_namek8s_psat:agent_pod_name:spire-agent-v5wgrName of the pod in which the agent is running
k8s_psat:agent_pod_uidk8s_psat:agent_pod_uid:79261129-6b60-11e9-9054-0800277ac80fUID of the pod in which the agent is running
k8s_psat:agent_pod_labelk8s_psat:agent_pod_label:key:valuePod Label
k8s_psat:agent_node_ipk8s_psat:agent_node_ip:172.16.10.1IP address of the node in which the agent is running
k8s_psat:agent_node_namek8s_psat:agent_node_name:minikubeName of the node in which the agent is running
k8s_psat:agent_node_uidk8s_psat:agent_node_uid:5dbb7b21-65fe-11e9-b1b0-0800277ac80fUID of the node in which the agent is running
k8s_psat:agent_node_labelk8s_psat:agent_node_label:key:valueNode Label

The node and pod selectors are only provided for label keys in the allowed_node_label_keys and allowed_pod_label_keys configurables.

A full example of this attestor is provided in the SPIRE examples repository