SSH Proof of Possession
The SSH PoP method attests agents using an SSH host certificate. The agent proves possession of the host's SSH private key by signing a challenge using a dual-nonce protocol; the Trust Domain Server validates the certificate against a set of trusted SSH CAs.
This method is well-suited for bare-metal servers, VMs, or any Linux host with an SSH host certificate issued by an internal CA.
Attributes available for SVID issuance
| Attribute | Description |
|---|---|
ssh_pop.type | Attestation type: always host_cert |
ssh_pop.key.type | Key algorithm: ed25519, ecdsa-sha2-nistp256, etc. |
ssh_pop.key.id | Key ID field from the SSH certificate |
ssh_pop.serial | Certificate serial number |
ssh_pop.principal | Valid principals from the certificate (multi-valued) |
ssh_pop.valid.after | Certificate validity start (Unix timestamp) |
ssh_pop.valid.before | Certificate validity end (Unix timestamp) |
All attributes have the origin ssh_pop.
Example SPIFFE ID template:
/ssh/{{ssh_pop.principal}}
Supported Key Types
- Ed25519 (Recommended)
- ECDSA P-256
- ECDSA P-384
- ECDSA P-521
- RSA, 2048 bit
- RSA, 4096 bit
- RSA, 8192 bit
How to Deploy
Step 1 — Update cluster configuration
Add your SSH CA public key(s) to the server config using SSH authorized_keys format. Multiple CAs can be listed.
section: AgentAttestation
schema: v1
spec:
policies:
- name: ssh_policy
requiredAttestors:
- type: ssh_pop
config:
certAuthorities:
- "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAA... ca@example.com"
canonicalDomain: example.com
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: ssh_policy
requiredAttestors:
- type: ssh_pop
config:
certAuthorities:
- "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAA... ca@example.com"
canonicalDomain: example.com
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.
Server Configuration Reference
| Field | Required | Description |
|---|---|---|
certAuthorities | Yes | List of trusted SSH CA public keys in authorized_keys format. Certificates must be signed by one of these CAs. |
canonicalDomain | No | If set, each principal in the certificate must end with this domain suffix (e.g., example.com). |
Step 2 — Configure the Agent
Point the agent at the host's SSH private key and certificate.
- Helm Installation
- Linux Installation
agent:
auth:
clusterId: c-xxxxxx
attestors:
- type: ssh_pop
config:
hostKeyPath: /etc/ssh/ssh_host_ed25519_key
hostCertPath: /etc/ssh/ssh_host_ed25519_key-cert.pub
The SSH host certificate and key must be made available to the agent pod via a hostPath volume mount. If your organization restricts hostPath mounts, the certificate and key can be delivered via a Kubernetes Secret mounted at the configured paths instead.
cluster-id: c-xxxxxx
agent-attestors:
- type: ssh_pop
config:
hostKeyPath: "/etc/ssh/ssh_host_rsa_key"
hostCertPath: "/etc/ssh/ssh_host_rsa_key-cert.pub"
Agent Configuration Reference
| Field | Required | Description |
|---|---|---|
hostKeyPath | Yes | Path to the SSH host private key file. |
hostCertPath | Yes | Path to the SSH host certificate file (.pub format). |
Step 3 — Verify
Server logs — look for these in order:
"Login started with multi-attestation support"— confirms the agent offeredprovidedMethods: ["ssh_pop"]"Authorization received and verified"— includesagentAttestationAttributeswith the certificate identity:{
"msg": "Authorization received and verified",
"agentAttestationAttributes": [
"ssh_pop:ssh_pop.key.id=\"server1.example.com\"",
"ssh_pop:ssh_pop.serial=\"42\"",
"ssh_pop:ssh_pop.principal=\"webserver-01.example.com\""
]
}"Connected to agent"— session is fully established
Agent logs — enable debug logging to see "Sending Login" with attestors: ["ssh_pop"]. At the default log level, "Connected to server" confirms the session is live.
Metrics — confirm proofs are succeeding:
spirl_attestation_signer.proof{attestor_type="ssh_pop",outcome="success"}
spirl_attestation_agent.proof{attestor_type="ssh_pop",outcome="success"}
Alert on outcome="failed" to detect CA validation or challenge-response failures.
Common errors:
| Error | Likely cause |
|---|---|
no cluster policy authorizes the provided attestors | Agent's ssh_pop method doesn't match any policy — verify the certificate's signing CA appears in certAuthorities |
Attestor rejected proof, policy failed | Certificate signed by unknown CA, cert is expired, or canonicalDomain doesn't match the certificate's principals |
Authorization failed: challenge response failed validation | Private key at hostKeyPath doesn't match the certificate at hostCertPath |
Attestation Flow
The SSH PoP method uses a dual-nonce challenge to prevent replay and relay attacks:
- The agent sends the SSH host certificate as an initial proof.
- The server validates the certificate (CA chain, expiry, type, principals) and responds with a server-generated nonce.
- The agent generates its own agent nonce and computes the combined challenge:
SHA256(server_nonce || agent_nonce). - The agent signs the combined challenge with the host private key and sends the signature along with the agent nonce.
- The server verifies the signature against the certificate's public key.
Certificate Requirements
The server enforces the following rules during certificate validation:
- The certificate must be a host certificate (
ssh-keygen -t host), not a user certificate. - The certificate must be signed by a CA listed in
certAuthorities. - The certificate must not be expired.
- The certificate must not include a
source-addresscritical option. - If
canonicalDomainis set, all principals must match the domain suffix.
Security Considerations
- Dual-nonce replay protection: The combined nonce (
SHA256(server_nonce || agent_nonce)) ensures the signed challenge is unique per attestation attempt. A captured exchange cannot be replayed because the server nonce changes each time. - Relay attack mitigation: Because the agent generates and includes its own nonce, a relay attacker who intercepts the server nonce cannot forward a modified challenge and obtain a valid signature — the agent's nonce ties the signature to the original session.
- Certificate expiry: The Trust Domain Server enforces certificate validity windows. Use short-lived certificates with automated renewal for stronger security.
- Revocation: To revoke a compromised CA, remove its entry from
certAuthoritiesin the Managed Config and reapply. Certificates signed by that CA will no longer be accepted.
Troubleshooting
Private key not readable — The agent process must have read access to hostKeyPath. Check file permissions; the key is typically root-owned (600).
Certificate signed by unknown CA — The CA public key in hostCertPath must appear in certAuthorities. Verify using ssh-keygen -L -f <cert> to inspect the signing CA fingerprint.
Certificate is a user cert, not a host cert — Confirm the certificate was issued with -h (host cert) flag. ssh-keygen -L -f <cert> shows Type: ssh-ed25519-cert-v01@openssh.com host certificate for a valid host cert.
Principal domain mismatch — If canonicalDomain is set, ensure all principals in the certificate end with that domain. Check with ssh-keygen -L -f <cert> under the Principals field.