SVID Issuance Policy
The SVIDIssuancePolicy managed configuration section centralizes per-cluster SPIFFE Verifiable Identity Document (SVID) issuance settings and adds an optional list of workload-specific overrides. Once a cluster has a policy, that policy is the complete description of SVID issuance for the cluster. It is the preferred way to configure:
- SPIFFE ID path templates — the path structure of issued SPIFFE IDs
- X.509-SVID customization — X.509 certificate TTL and Subject
- JWT-SVID customization — JWT-SVID TTL and additional claims
- WIT-SVID — WIT-SVID TTL and additional claims (preview)
See those pages for field-level documentation and examples.
Why use it?
- Single configuration source: Manage TTLs, subjects, and path templates per cluster from a single managed-config document instead of separate CLI flags.
- Per-workload variations: Issue shorter-lived certificates for specific namespaces, distinct path templates for sensitive workloads, or different subjects for one service account — without writing a separate cluster.
- Live updates: Configuration changes propagate to servers without restart.
Implicit Deny
From 0.39.0, a cluster's SVIDIssuancePolicy is the complete description of SVID issuance for that cluster, and issuance fails closed. A workload that matches no override and no base policy receives no X.509-SVID, no JWT-SVID, and no WIT-SVID. The cluster's legacy path template is never consulted for it, and neither are the cluster's legacy X.509 and JWT customization templates.
Earlier versions fall back to the cluster's legacy configuration. A cluster whose policy is incomplete keeps issuing today and starts denying on upgrade, with no configuration change of its own.
Before upgrading, audit every cluster that has a policy:
- Read the cluster's legacy configuration with
spirlctl cluster info CLUSTER_NAME --trust-domain TD_NAMEand its policy withspirlctl config get cluster --id <cluster-id>. - Confirm the base
policyand every override declares apathTemplate. New saves are rejected without one, but a policy stored before that requirement still loads, and it denies every workload it resolves for. See Validation. - Confirm the base
policyis set, unless the section is deliberately an allowlist. - Carry the cluster's legacy X.509 Subject, SANs, and JWT claims into the policy's
x509andjwtfields, since they stop being consulted.
A workload receives an SVID only if a policy section resolves a pathTemplate for it.
This gives two policy shapes:
| Shape | Effect |
|---|---|
Base policy plus policyOverrides | The base policy is the catch-all. Every workload the overrides do not match still receives an SVID. |
policyOverrides only | An allowlist. Only workloads matching an override receive SVIDs. |
pathTemplate is the field that decides issuance, and it is the one field with no fallback. An unset TTL or Subject is not a denial. An unset pathTemplate is.
The Trust Domain Server flags remain in the fallback chain, and they are the only fallback left on a policy cluster:
| Unset in the resolved section | Falls back to | Then to |
|---|---|---|
x509.ttl | --x509-svid-ttl | 24h |
jwt.ttl | --jwt-svid-ttl | 24h |
x509.subject | --x509-svid-subject | The default SVID subject |
wit.ttl | No server flag exists | 24h |
x509.dnsNames, x509.ipAddresses, jwt.additionalClaims, wit.additionalClaims | Nothing. The field is simply unset | — |
Clusters with no SVIDIssuancePolicy are unaffected. They continue to issue from the cluster's legacy configuration.
Observing denials
A denial is recorded as an SVID error event with the category policy_denied, carrying the workload's attested attributes so you can identify which workload was denied. Identical repeated denials are suppressed for 10 minutes so a retrying agent does not flood the event feed. That suppression applies to the event feed only. Metrics and logs have their own rate limits.
A denial increments two counters:
| Counter | Labels | Notes |
|---|---|---|
spirl_server_svid_policy_denials_total | cluster_id, svid_type | Denials only. |
spirl_server_mint_svid_total | svid_type, status_code | Denials land on the primary issuance counter as status_code = policy_denied, alongside the other outcomes in the server runbook. A dashboard that filters this counter to status_code != "ok" already counts denials. |
Server logs carry a rate-limited finished call warning with code = FailedPrecondition and no workload identity, so use the SVID error event to identify the denied workload.
An issuance failure is recorded differently, so the two are never confused. A malformed template or an attribute that fails to render lands as status_code = template_error with the SVID error category issuance_error, and the workload sees InvalidArgument.
The agent receives a FailedPrecondition error, which distinguishes a policy denial from an infrastructure failure. The workload's Workload API call fails with the same code, carrying this message from the Trust Domain Server at the end of the agent's error chain:
SVID issuance denied by the cluster's SVID issuance policy: no policy section
resolved a path template for this workload
A denial does not revoke SVIDs already issued.
Per-Workload Overrides
policyOverrides is an ordered list of conditional policy variations. When a workload's attested attributes match an override's when clause, that override's policy applies in place of the base policy. The first match wins; later overrides and the base policy are skipped for that workload.
section: SVIDIssuancePolicy
schema: v1
spec:
policy:
pathTemplate: "/{{cluster.name}}/default/ns/{{kubernetes.pod.namespace}}/sa/{{kubernetes.pod.service_account}}"
x509:
ttl: "24h"
policyOverrides:
- when:
kubernetes.pod.namespace: production
policy:
pathTemplate: "/{{cluster.name}}/prod/ns/{{kubernetes.pod.namespace}}/sa/{{kubernetes.pod.service_account}}"
x509:
ttl: "4h"
- when:
kubernetes.pod.service_account: critical-service
policy:
pathTemplate: "/{{cluster.name}}/critical/ns/{{kubernetes.pod.namespace}}/sa/{{kubernetes.pod.service_account}}"
x509:
ttl: "1h"
In this example:
- A pod in the
productionnamespace receives a SPIFFE ID under/prod/...with a 4h TTL. - A pod whose service account is
critical-service(in any namespace exceptproduction) receives a SPIFFE ID under/critical/...with a 1h TTL. - Every other pod receives the base policy:
/default/...with a 24h TTL.
When-Clause Match Semantics
Each entry in a when map is one condition. All conditions in a single when clause must match (logical AND).
| Entry | Match Type |
|---|---|
key: value | Exact match — the workload must carry that attribute with that exact string value. |
key: (no value) | Presence match — the workload must carry that attribute with any value. |
Allowed When Keys
when keys are restricted to attributes whose values represent a real authorization boundary — that is, attributes a pod author or process launcher cannot freely pick to claim a more privileged policy.
| Source | Attribute | Set by |
|---|---|---|
| Agent / cluster identity | agent.id | SPIRL agent provisioning |
| Kubernetes admission | kubernetes.pod.namespace | Kubernetes RBAC |
kubernetes.pod.service_account | Kubernetes RBAC | |
| Workload Attestation | jwt.claim.iss | OIDC JWT Issuer |
Attributes that look like identity but are pod-spec- or process-launcher-chosen — including kubernetes.pod.name, kubernetes.pod.node.name, kubernetes.container.name, kubernetes.container.image.name, linux.user.*, and linux.binary.path — are not on the allowlist. Anyone with create pod permission can pick those values, so matching on them adds no security restriction beyond what the underlying RBAC already provides.
Workload-supplied or content-addressed attributes (custom.*, pod annotations, pod labels, JWT claims, kubernetes.container.image.id, linux.binary.sha256) are also excluded: when-clause matching is scoped to platform-set identity, not workload content.
Override Semantics
When an override's when clause matches at issuance time, the override's policy fully replaces the base policy for that workload. There is no merging of fields. Each override must therefore be self-contained. It must declare its own pathTemplate, and any of x509 / jwt / wit it wants to apply.
| Base declares | Override declares | Effective for matching workload |
|---|---|---|
pathTemplate, x509, jwt | pathTemplate, x509, jwt | All from override |
pathTemplate, x509, jwt, wit | pathTemplate, x509: {ttl: 4h} | Override's pathTemplate and x509. JWT and WIT not configured for matching workload. |
pathTemplate, x509, jwt | x509: {ttl: 4h} and no pathTemplate | Rejected at admission. Every override must carry its own pathTemplate. |
An override that omits wit turns off WIT-SVID issuance for the workloads it matches. That refusal is not a policy denial: The agent receives Unimplemented, and no policy_denied event is recorded.
SVID Key Type
The svidKeyType field requires spirl-system v0.42.0 or above.
svidKeyType configures the key algorithm agents use when generating SVID key pairs. Unlike policy and policyOverrides, it is an agent-level setting that applies uniformly to all workloads on the cluster and cannot be varied per workload.
section: SVIDIssuancePolicy
schema: v1
spec:
svidKeyType:
x509: ec-p256
X.509 key type
This setting controls the key pair generated by the agent for each X.509 SVID. It does not affect the root CA signing key algorithm used by the Trust Domain Server.
Supported values:
| Value | Algorithm |
|---|---|
rsa-2048 | RSA 2048-bit (default when not set) |
rsa-3072 | RSA 3072-bit |
rsa-4096 | RSA 4096-bit |
ec-p256 | ECDSA P-256 |
ec-p384 | ECDSA P-384 |
Validation
The control plane rejects configurations that:
- Set neither
policynorpolicyOverrides. - Include an override with an empty
whenclause. - Include an override whose
policydeclares no fields. - Use a
whenkey not on the allow-list above, or awhenkey that isn't a valid attribute name. - Contain two overrides with identical
whenclauses. - Omit
pathTemplate, or set it to whitespace, on the basepolicyor on any override (overrides do not inherit from the base policy). - Set
x509.dnsNamesorx509.ipAddressesto an empty list. Omit the field instead to leave it unset. - Reference a deprecated attribute name, such as
provider.aws.*, inx509.subject,x509.dnsNames, orx509.ipAddresses, even where a path template still accepts that name. - Use a
jwt.additionalClaimstemplate that is malformed or references reserved JWT claim names (iss,sub,aud,exp,nbf,iat,jti).
pathTemplate is required in the generated JSON schema, so the console editor flags a missing one before you save.
The pathTemplate requirement is enforced on writes only. A policy saved before the requirement existed still loads, and on spirl-server 0.39.0 it denies every workload it resolves for. Re-saving the configuration is what surfaces the error. Audit stored policies rather than relying on admission to have caught them.
Path-template syntax and platform-specific allowed tags are validated the same way they are for SPIFFE ID templates, against both the base policy and each override.