Kerberos
The Kerberos method attests agents by proving the agent holds a Kerberos identity in your realm, such as a machine account, a gMSA, or a service account. The agent never hands over a password or a key.
When an agent runs on a domain-joined host (Active Directory or MIT Kerberos) you already have an identity infrastructure. Rather than provisioning a separate credential, the agent authenticates with the Kerberos principal the host already owns. The agent obtains a Kerberos service ticket for the Trust Domain Server's Service Principal Name (SPN) and presents it. The Trust Domain Server verifies that ticket against its own keytab. The principal, realm, and (on Active Directory) other account properties become attributes you can template into SPIFFE IDs.
Attributes available for SVID issuance
The following attributes are produced for a successfully attested agent. All attributes have the origin kerberos.
| Attribute | Description | Availability |
|---|---|---|
kerberos.principal | The client principal that authenticated, e.g. MYHOST$ or svc-agent. | Always |
kerberos.realm | The client's Kerberos realm, e.g. CORP.EXAMPLE.COM. | Always |
kerberos.spn | The Service Principal Name the client's ticket was issued for. | Always |
kerberos.logon_domain.name | The account's logon domain name, from the ticket's Privilege Attribute Certificate (PAC). | Active Directory only |
kerberos.logon_domain.sid | The account's logon domain SID, from the PAC. | Active Directory only |
kerberos.logon_server | The domain controller that authenticated the client, from the PAC. | Active Directory only |
kerberos.user_sid | The client's account SID, from the PAC. Unlike the principal, the SID survives a machine rename or rebuild. | Active Directory only |
The core attributes — kerberos.principal, kerberos.realm, and kerberos.spn — come from the ticket and are always present. The PAC attributes require the KDC to issue Active Directory-style logon information, which Active Directory always does and a plain MIT realm never does.
Example SPIFFE ID template:
/agent/{{kerberos.principal}}
How to Deploy
Both sides of the exchange need Kerberos identities issued by your KDC. The agent needs a keytab and a krb5.conf on its host, and the Trust Domain Server needs a keytab containing the key for the SPN that agents will target. There is no Defakto-managed key material involved.
Step 1 — Update cluster configuration
The policy config pins which service ticket the Trust Domain Server accepts, and where the server reads the key to verify it. Only spn is required.
section: AgentAttestation
schema: v1
spec:
policies:
- name: corp_fleet_policy
requiredAttestors:
- type: kerberos
config:
spn: defakto/tdserver.corp.example.com
# Optionally include further constraints:
# allowedRealms:
# - CORP.EXAMPLE.COM
# Only if the keytab is mounted somewhere other than the default:
# keytabPath: /opt/defakto/krb5.keytab
Apply it using spirlctl. For production, keep the YAML in source control and point spirlctl at the file:
spirlctl config set cluster --id <cluster-id> corp-fleet-attestation.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: corp_fleet_policy
requiredAttestors:
- type: kerberos
config:
spn: defakto/tdserver.corp.example.com
allowedRealms:
- CORP.EXAMPLE.COM
YAML
}
}
Once a configuration document passes validation and is stored, the Defakto control plane syncs it to your Trust Domain Servers automatically.
Server Configuration Reference
| Field | Required | Description |
|---|---|---|
spn | Yes | The Service Principal Name whose key in the server's keytab must decrypt the client's ticket. Write it unqualified, with no @REALM suffix. The keytab and the ticket settle which key decrypts, so a realm here would be unreadable and is rejected. |
keytabPath | No | Absolute path to the keytab holding the SPN's key. Defaults to /etc/spirl/kerberos/krb5.keytab, which is where the Helm values below mount it, so a standard deployment sets nothing here. Set it only if you mount the keytab somewhere else. If you do, mountPath and this field have to agree. |
allowedRealms | No | When set, constrains the accepted client realms to this list. A ticket from any other realm is rejected. Matched case-insensitively, so a realm written the way your krb5.conf spells it still matches. |
Step 2 — Mount the server's keytab
The keytab is a secret rather than Managed Config, so mount it into the Trust Domain Server's pod through the Helm chart values. This step is required. The default keytabPath expects the keytab at /etc/spirl/kerberos/krb5.keytab, so mounting the Secret at /etc/spirl/kerberos needs no policy change:
trustDomainDeployment:
deployment:
additionalVolumes:
- name: kerberos-keytab
secret:
secretName: td-server-keytab
additionalVolumeMounts:
- name: kerberos-keytab
mountPath: /etc/spirl/kerberos
readOnly: true
Mount the directory, not the file via subPath. A subPath mount pins the file as it was when the container started and never receives Secret updates. A directory mount keeps rotation working.
Step 3 — Configure the Agent
The agent needs the SPN of the Trust Domain Server. Everything else has a sensible default drawn from the host's Kerberos setup.
- Helm Installation
- Linux Installation
agent:
auth:
clusterId: c-xxxxxx
attestors:
- type: kerberos
config:
spn: defakto/tdserver.corp.example.com
# Only when the server is in a different realm to this host:
# serverRealm: SERVER.EXAMPLE.COM
# clientPrincipal, keytabPath and krb5ConfPath are optional
cluster-id: c-xxxxxx
agent-attestors:
- type: kerberos
config:
spn: "defakto/tdserver.corp.example.com"
# Only when the server is in a different realm to this host:
# serverRealm: "SERVER.EXAMPLE.COM"
On a normally domain-joined host the defaults are enough. The agent reads the machine keytab at /etc/krb5.keytab, discovers the machine account principal, and reads realm and KDC settings from /etc/krb5.conf. Which keytab the agent reads, and how it was provisioned (domain join, ktpass, net ads keytab, mounted secret), is up to you.
klist -k and ktutil print principals fully qualified, for example defakto/tdserver.corp.example.com@CORP.EXAMPLE.COM, so the form you have in hand is usually not the form spn wants. Drop the @REALM part. In a single-realm deployment that is the whole change.
Agent Configuration Reference
| Field | Required | Description |
|---|---|---|
spn | Yes | The Trust Domain Server's Service Principal Name, e.g. defakto/tdserver.corp.example.com. The agent requests a service ticket for this SPN. |
serverRealm | No | The Kerberos realm the spn lives in. Leave it unset when the agent and the server share a realm, which is the common case. Set it when they do not: The agent has to pick a KDC to ask for the service ticket, and with no realm configured it asks its own. |
clientPrincipal | No | The principal to log in as, e.g. MYHOST$ or svc-agent. If empty, the principal is discovered from the keytab, preferring the Active Directory machine account (HOST$), then a host/ entry. Set this when the agent runs under a gMSA or a dedicated service account whose principal is not the machine account. |
keytabPath | No | Absolute path to the agent's keytab. Defaults to /etc/krb5.keytab. |
krb5ConfPath | No | Absolute path to the Kerberos client config. Defaults to /etc/krb5.conf. |
Step 4 — Verify
Server logs — look for these in order:
"Login started with multi-attestation support"— confirms the agent offeredprovidedMethods: ["kerberos"]"Authorization received and verified"— includesagentAttestationAttributeswith the Kerberos identity:{"msg": "Authorization received and verified","agentAttestationAttributes": ["kerberos:kerberos.principal=\"MYHOST$\"","kerberos:kerberos.realm=\"CORP.EXAMPLE.COM\"","kerberos:kerberos.spn=\"defakto/tdserver.corp.example.com\""]}"Connected to agent"— session is fully established
Metrics — confirm proofs are succeeding:
spirl_attestation_signer.proof{attestor_type="kerberos",outcome="success"}
spirl_attestation_agent.proof{attestor_type="kerberos",outcome="success"}
Alert on outcome="failed" to detect ticket verification or challenge-response failures.
Attestation Flow
Kerberos attestation runs as a two-round challenge-response over the agent login stream:
- Proof. The agent obtains a service ticket for the server's SPN from the KDC and presents a fresh AP-REQ. No nonce is bound yet.
- Verify and challenge. The Trust Domain Server verifies the AP-REQ against the SPN key in its keytab, confirming the ticket was minted by the KDC for a principal in the realm. The server then issues a random nonce challenge.
- Response. The agent builds a second AP-REQ, binding the server's nonce into the authenticator checksum, and returns it.
- Confirm. The Trust Domain Server verifies the second AP-REQ and confirms it carries exactly the nonce it issued, proving the response is live and not a replay.
The Trust Domain Server enforces AES only. AP-REQs whose ticket uses the legacy RC4-HMAC encryption type are rejected before the identity is trusted.
Provisioning identities
Agent (client). On an Active Directory-joined host the machine account keytab at /etc/krb5.keytab is usually all the agent needs, and no extra provisioning is required. To run the agent under a dedicated service account or gMSA instead, provision a keytab for that account and set clientPrincipal and keytabPath accordingly.
Server. Create a service account for the Trust Domain Server in your directory, register the SPN against it, export a keytab containing that key, and mount it into the server's pod with the Helm values shown in Step 2.
The Trust Domain Server rejects anything that is not AES, and it checks in two places: The encryption type of the ticket itself, and the encryption type of the session key the KDC chose for it.
On Active Directory:
- Set the SPN account's
msDS-SupportedEncryptionTypesto include AES (0x18= AES128 + AES256), or tick "This account supports Kerberos AES 256 bit encryption" in Active Directory Users & Computers. This controls what the KDC will issue, independently of what is in the keytab. - Export the keytab with
ktpass /crypto AES256-SHA1. - Every reset bumps the
kvno, so re-export the keytab after any out-of-band password change or the server's key goes stale. - Check that the "Network security: Configure encryption types allowed for Kerberos" GPO does not exclude AES domain-wide.
- The client account, used by the agent, needs AES support too. Without it the KDC can still hand out an RC4 session key for an otherwise-AES exchange, which is what
session key etype <n> rejectedmeans.
On MIT: Set supported_enctypes in kdc.conf and default_tgs_enctypes in krb5.conf, and export with ktadd -e aes256-cts-hmac-sha1-96:normal.
Active Directory and MIT
The attestor works against both, with two behaviors worth knowing:
- DNS-based KDC discovery. A realm-joined host typically finds its KDC through DNS SRV records with no explicit
kdclines inkrb5.conf. The agent matches MIT's default and enables DNS KDC lookup whenkrb5.confdoes not setdns_lookup_kdcexplicitly, sokinit-style discovery keeps working. Explicit[realms]KDC entries still take precedence. - The PAC (Active Directory only). Active Directory tickets carry a Privilege Attribute Certificate (PAC) holding the account's logon domain, the domain controller that authenticated it, and the account's SID. The Trust Domain Server decodes the PAC and surfaces those as attributes. MIT 1.20 and later also issue a PAC by default, but the MIT PAC carries no logon information, because an MIT realm has no domain SID or logon server to report, so those attributes are absent there. No configuration is needed either way: MIT realms attest with or without
disable_pacset.
Cross-realm deployments
The agent and the Trust Domain Server are assumed to be in the same Kerberos realm. That case needs no configuration at all, because the agent asks its own KDC for the service ticket and it works.
When they are in different realms, both of these have to hold:
- The agent's
serverRealmnames the server's realm. - There is a direct trust between the agent's realm and the server's realm.
On the first point: An agent host whose krb5.conf already maps the server's host to its realm in [domain_realm] needs no serverRealm, because that mapping is where the agent looks when the field is unset. Where both are present, serverRealm wins.
Troubleshooting
These errors might show up in the logs.
Server
| Error | Likely cause |
|---|---|
load keytab "/etc/spirl/kerberos/krb5.keytab" (default; set keytabPath to override): ... | The server could not read its keytab at the default path. Either mount the Secret at /etc/spirl/kerberos as shown in Step 2, or set keytabPath to wherever the keytab actually is. The message names the default precisely because nothing in the policy does. |
ticket etype <n> rejected; AES required | The client's service ticket was encrypted with a non-AES type, so the SPN's account or its keytab is not AES-capable. Work through Provisioning identities → Server. |
session key etype <n> rejected; AES required | The ticket was AES but the KDC chose a non-AES session key for it. The keytab can be perfectly AES-capable and still hit this. Check the KDC's default_tgs_enctypes and the client account's supported encryption types. |
AP-REQ verification: ... / AP-REQ verification rejected the request | The presented ticket did not verify against the server's keytab. The server's keytab is missing the SPN's current key, or the key rotated at the KDC without the keytab being re-exported. |
AP-REQ SPN "<name>" does not match configured spn "<name>" | The client's ticket was for a different SPN than the policy's spn. Point the agent's spn at the server's SPN. |
authenticator realm "<realm>" does not match ticket realm "<realm>" | The client asserted a realm the KDC did not sign into the ticket. Legitimate clients do not produce this. Treat it as a misbehaving or hostile client rather than a misconfiguration. |
client realm "<realm>" not in allowedRealms | The client authenticated from a realm not in the policy's allowedRealms. Add the realm, or remove the constraint. |
round-2 nonce missing or mismatched | The agent's second AP-REQ did not carry the nonce the server issued. Usually a client/server version mismatch or a replayed proof. |
Agent
| Error | Likely cause |
|---|---|
kerberos login: ... | The agent could not obtain an initial ticket. Check that the keytab at keytabPath holds a usable principal, that krb5.conf names the realm and KDC, and that the host clock is within the KDC's skew tolerance. |
get service ticket for "<spn>" in realm <REALM> (<source>): ... | The agent authenticated but the KDC would not issue a ticket for the SPN. Confirm the SPN is registered against a real account in the directory and that spn matches it exactly. See Ticket request sources below for what <source> tells you. |
... could not find TGT session for <REALM> (no direct trust from <A> to <B>: only one referral hop is supported, krb5.conf [capaths] chains are not) | The trust path from the agent's realm to the server's realm runs through an intermediate realm. This is the one cross-realm failure serverRealm cannot fix. See Cross-realm deployments. |
krb5.conf [domain_realm] maps "<host>" to <REALM> but the spn spells the host "<Host>", and the lookup is case-sensitive: spell the host in the spn in lowercase, or set serverRealm | The mapping is in the file but cannot apply, because spn spells the host in a different case. The agent stops here rather than falling back to its own realm and asking the wrong KDC. Do either of the two things the message names. |
... no KDCs defined in configuration for realm <REALM> (add a [realms] entry for <REALM> to krb5.conf, or ensure its KDC is discoverable by DNS SRV lookup) | The realm resolved, but nothing on the host knows how to reach a KDC for it. |
principal "<name>" not found in keytab | The clientPrincipal override does not match any entry in the keytab. Remove it to auto-discover, or set it to a principal the keytab actually contains. |
Ticket request sources
The get service ticket error names the realm the KDC was asked for and where that realm came from, which is what to check when it is not the realm you expected. The source is one of three:
the serverRealm config key— the realm was declared, not inferred.krb5.conf [domain_realm]— inferred from a mapping on the agent's host.the agent's own realm; krb5.conf has no [domain_realm] entry for the spn's host— nothing named a realm for the server, so the agent asked its own KDC about it. In a cross-realm deployment this is the thing to fix: SetserverRealm.