X.509 Proof of Possession
The X.509 Proof of Possession (X.509 PoP) method attests agents that have been provisioned with an X.509 certificate through an out-of-band mechanism. The Trust Domain Server verifies that the certificate chains to a trusted CA, then issues a cryptographic challenge to confirm the agent holds the corresponding private key.
Use this method in environments where you already operate a PKI and can provision host certificates, or when you need to carry structured identity (via URI SANs) from your existing certificate infrastructure into SPIFFE IDs.
Attributes available for SVID issuance
The following attributes are produced from the agent's leaf certificate and its verified chain. All attributes have the origin x509_pop.
| Attribute | Description |
|---|---|
x509_pop.subject.common_name | Common Name from the leaf certificate's Subject. Only emitted when non-empty. |
x509_pop.serial_number | Leaf certificate serial number as a lowercase hex string. |
x509_pop.ca.<serialNumber> | One attribute per CA in the verified chain (excluding the leaf). Key suffix is the CA's serial number in hex; value is the SHA1 fingerprint of that CA. |
x509_pop.san.<type> | One attribute per URI SAN matching x509pop://{trust_domain}/{type}/{value}. Key suffix is {type}; value is {value}. |
URI SAN Format
URI SANs in the leaf certificate that match the following pattern are parsed into x509_pop.san.* attributes:
x509pop://{trust_domain}/{type}/{value}
For example, a certificate for trust domain example.com with the URI SAN x509pop://example.com/environment/production produces:
x509_pop.san.environment = "production"
URI SANs that do not match the trust domain prefix are silently ignored.
Where Attributes Are Available
Agent attributes are available in SPIFFE ID path templates, X.509 SVID customization templates, and JWT SVID additional claims — alongside workload attributes from the same request.
/agents/{{x509_pop.subject.common_name}}/{{x509_pop.serial_number}}
# e.g. /agents/my-host.example.com/3a7f2c1b
How to Deploy
X.509 PoP spans two configuration surfaces. Deploy them in this order to avoid downtime: server-side Managed Config first, then agent configuration.
Step 1 — Update cluster configuration
Create or update the AgentAttestation Managed Config policy with your PEM-encoded CA certificate(s) inline in the caCerts field:
section: AgentAttestation
schema: v1
spec:
policies:
- name: x509pop_policy
requiredAttestors:
- type: x509pop
config:
caCerts: |
-----BEGIN CERTIFICATE-----
<base64-encoded CA certificate>
-----END CERTIFICATE-----
Apply it using spirlctl:
spirlctl config set cluster --id <cluster-id> attestation-policy.yaml
Or using Terraform:
resource "spirl_cluster_config" "agent_attestation" {
cluster_id = spirl_cluster.my_cluster.id
sections = {
AgentAttestation = <<-YAML
section: AgentAttestation
schema: v1
spec:
policies:
- name: x509pop_policy
requiredAttestors:
- type: x509pop
config:
caCerts: |
-----BEGIN CERTIFICATE-----
<base64-encoded CA certificate>
-----END CERTIFICATE-----
YAML
}
}
Once a configuration document passes validation and is stored, the Defakto control plane syncs it to your Trust Domain Servers automatically. No server or agent restart is required.
Verify by watching server logs for:
"Metadata updated"
"Updating configuration snapshot"
"Updating configuration cache"
Server Configuration Reference
| Field | Required | Default | Description |
|---|---|---|---|
caCerts | Yes | — | PEM-encoded trusted root CA certificates. One or more concatenated. The agent's certificate chain must verify against these roots. |
maxIntermediates | No | 4 | Maximum number of intermediate certificates in the agent's chain. |
maxRSAKeySize | No | 8192 | Maximum RSA key size in bits. Non-RSA keys are not subject to this limit. |
Step 2 — Configure the Agent
The agent needs paths to its certificate and private key. These are node-local filesystem paths and must be set as static bootstrap configuration.
- Helm Installation
- Linux Installation
agent:
auth:
clusterId: c-xxxxxx
attestors:
- type: x509pop
config:
certificatePath: "/etc/spirl/agent.crt"
privateKeyPath: "/etc/spirl/agent.key"
# intermediatesPath: "/etc/spirl/intermediates.pem" # optional
cluster-id: c-xxxxxx
disable-kubernetes-attestation: true
agent-attestors:
- type: x509pop
config:
certificatePath: "/etc/spirl/agent.crt"
privateKeyPath: "/etc/spirl/agent.key"
Agent Configuration Reference
| Field | Required | Description |
|---|---|---|
privateKeyPath | Yes | Path to the PEM-encoded private key (PKCS#1 or PKCS#8). Must match the certificate's public key. |
certificatePath | Yes | Path to the PEM-encoded X.509 leaf certificate. Must include digitalSignature in its Key Usage extension. |
intermediatesPath | No | Path to a PEM file of intermediate certificates forming the chain to the trusted root. |
Step 3 — Verify
Server logs — look for these in order:
"Login started with multi-attestation support"— confirms the agent offeredprovidedMethods: ["x509pop"]"Authorization received and verified"— includesagentAttestationAttributeswith the extracted certificate identity:{
"msg": "Authorization received and verified",
"agentAttestationAttributes": [
"x509pop:x509_pop.subject.common_name=\"my-host.example.com\"",
"x509pop:x509_pop.serial_number=\"3a7f2c1b\""
]
}"Connected to agent"— session is fully established
Agent logs — enable debug logging to see "Sending Login" with attestors: ["x509pop"]. At the default log level, "Connected to server" confirms the session is live.
Metrics — confirm proofs are succeeding:
spirl_attestation_signer.proof{attestor_type="x509pop",outcome="success"}
spirl_attestation_agent.proof{attestor_type="x509pop",outcome="success"}
Alert on outcome="failed" to detect certificate validation or key-matching failures.
Common errors:
| Error | Likely cause |
|---|---|
no cluster policy authorizes the provided attestors | Agent's x509pop method doesn't match any policy in the cluster's AgentAttestation config |
Attestor rejected proof, policy failed | Certificate chain didn't verify against caCerts — check attestorType: x509pop in logs |
Authorization failed: challenge response failed validation | Private key at privateKeyPath does not match the certificate |
Security Considerations
X.509 PoP uses a cryptographic challenge-response protocol:
- The agent presents its certificate chain.
- The Trust Domain Server validates the chain against the configured trust bundle.
- The server issues a random nonce challenge.
- The agent signs the nonce with its private key.
- The server verifies the signature.
Merely possessing a copy of the certificate is insufficient — the private key must be present on the attesting host.
Both RSA (RSA-PSS with SHA-256) and ECDSA (with SHA-256) key types are supported.
Resource exhaustion protection:
maxIntermediates(default 4) limits chain depth to prevent CPU/memory exhaustion from deeply nested chains.maxRSAKeySize(default 8192 bits) prevents signature-verification exhaustion from abnormally large RSA keys.
Troubleshooting
| Error in Trust Domain Server logs | Likely cause |
|---|---|
attestation policy not found or no matching policy | Managed Config not applied |
caCerts is required | caCerts field is present but empty |
could not parse any PEM certificates | caCerts contains invalid PEM |
certificate chain verification failed | Agent cert is not signed by a trusted CA in caCerts |
failed to verify signature | Private key at privateKeyPath does not match the certificate |
certificate does not include digitalSignature key usage | Agent certificate was issued without the required Key Usage extension |