SVID Block List
The SVIDBlockList section requires defakto-server (or spirl-server) v0.42.0 or above on every Trust Domain Server in the trust domain. Older servers ignore the section without an error and keep issuing SVIDs for blocked SPIFFE IDs.
The SVIDBlockList managed configuration section lists SPIFFE IDs that the Trust Domain Server must not issue SPIFFE Verifiable Identity Documents (SVIDs) for. Use it as a break-glass control when a workload is compromised. Adding an entry stops new X.509-SVIDs, JWT-SVIDs, and WIT-SVIDs for matching SPIFFE IDs within seconds, with no server or agent restart and no change to the cluster's SVID Issuance Policy. If the compromise may extend to the trust domain's signing keys, also follow the Signing Key Rotation Runbook.
The Trust Domain Server checks the block list against the SPIFFE ID it is about to issue, after the path template is rendered. The check applies to every way the Trust Domain Server issues an SVID:
- Workloads served by an agent
- Serverless workloads
- Developer Identity
The section applies at the trust domain scope. One entry blocks matching SPIFFE IDs in every cluster and on every Trust Domain Server in the trust domain.
Configuration
section: SVIDBlockList
schema: v1
spec:
entries:
- spiffeID: "spiffe://example.org/prod/ns/payments/sa/api"
match: exact
reason: "INC-123: leaked credentials"
- spiffeID: "spiffe://example.org/prod/ns/checkout/**"
match: wildcard
reason: "INC-124: namespace compromised"
- spiffeID: "spiffe://example.org/*/ns/billing/sa/worker"
match: wildcard
reason: "INC-125: service account compromised in every cluster"
| Field | Required | Description |
|---|---|---|
entries[].spiffeID | Yes | The SPIFFE ID to block, or a pattern when match is wildcard. Must belong to the trust domain the section is applied to. |
entries[].match | Yes | exact or wildcard. See Match types. |
entries[].reason | No | Free text recorded with each denial. The reason is not sent to the workload. |
Quote each spiffeID value so entries with and without wildcards read the same way. Quoting does not change how an entry matches.
An empty entries list blocks nothing.
Match types
match | spiffeID holds | Blocks |
|---|---|---|
exact | A literal SPIFFE ID. Wildcards are not allowed. | Only that SPIFFE ID. |
wildcard | A pattern of path segments, where * and a final ** are wildcards. | Every SPIFFE ID the pattern matches. |
A wildcard pattern is compared segment by segment:
*matches exactly one whole segment with any value.**matches zero or more segments. It is allowed only as the final segment.- Any other segment must match exactly.
Without a final **, a SPIFFE ID matches only if it has the same number of segments as the pattern.
| Pattern | SPIFFE ID | Blocked |
|---|---|---|
spiffe://example.org/prod/ns/checkout/** | spiffe://example.org/prod/ns/checkout | Yes |
spiffe://example.org/prod/ns/checkout/** | spiffe://example.org/prod/ns/checkout/sa/web | Yes |
spiffe://example.org/prod/ns/checkout/** | spiffe://example.org/prod/ns/checkout-v2/sa/web | No |
spiffe://example.org/*/ns/billing/sa/worker | spiffe://example.org/staging/ns/billing/sa/worker | Yes |
spiffe://example.org/*/ns/billing/sa/worker | spiffe://example.org/staging/ns/billing/sa/worker/extra | No |
Blocking a workload in every cluster
The default Kubernetes path template starts with the cluster name:
/{{cluster.name}}/ns/{{kubernetes.pod.namespace}}/sa/{{kubernetes.pod.service_account}}
A * in the cluster segment blocks a workload in every cluster, including clusters registered after the entry is added:
spiffeID (match: wildcard) | Blocks |
|---|---|
spiffe://example.org/*/ns/billing/sa/worker | The worker service account in the billing namespace, in every cluster. |
spiffe://example.org/*/ns/checkout/** | Every workload in the checkout namespace, in every cluster. |
* must be a whole segment. There is no partial-segment wildcard such as pay*, and no regular expression.
A pattern needs only one literal segment, so a short pattern can still match most of a trust domain. If every SPIFFE ID in the trust domain starts with /k8s, then spiffe://example.org/k8s/** blocks all of them. Before applying a wildcard entry, compare it against the SPIFFE IDs your path templates produce.
Blocking a SPIFFE ID
Applying a section replaces that section as a whole. To add an entry without dropping the entries already in place, start from the current section.
-
Confirm that every Trust Domain Server in the trust domain runs v0.42.0 or above.
-
Read the current configuration:
defakto config get trust-domain --id <trust-domain-id> -
Write
svid-block-list.yamlwith every existingSVIDBlockListentry plus the new one. -
Apply it:
defakto config set trust-domain --id <trust-domain-id> svid-block-list.yamldefakto config setleaves the trust domain's other sections in place. -
Confirm the block took effect. See Observing denials.
To unblock a SPIFFE ID, remove its entry and apply the section again.
Using Terraform
If the trust domain's configuration is managed with the spirl_trust_domain_config Terraform resource, add the section there:
resource "spirl_trust_domain_config" "example" {
trust_domain_id = spirl_trust_domain.example.id
sections = {
SVIDBlockList = <<-YAML
section: SVIDBlockList
schema: v1
spec:
entries:
- spiffeID: "spiffe://example.org/prod/ns/payments/sa/api"
match: exact
reason: "INC-123: leaked credentials"
YAML
}
}
A section added with the defakto CLI outside Terraform appears as drift. Copy the entry into the Terraform configuration before the next terraform apply, or the apply can remove the block.
Observing denials
A block-list denial is recorded separately from an SVID Issuance Policy denial, so a dashboard or event filter can tell a break-glass block from a workload that no policy section covers.
A denial is recorded as an SVID error event with the category blocklist_denied, carrying the workload's attested attributes, the matched entry, and the entry's reason. The event's issuer type identifies the path that was denied: a cluster for agent workloads, serverless, or Developer Identity. Identical repeated denials are suppressed for 10 minutes so a retrying caller does not flood the event feed. That suppression applies to the event feed only.
A denial increments these counters:
| Counter | Labels | Notes |
|---|---|---|
defakto_server_svid_blocklist_denials_total | issuer_type, cluster_id, svid_type | Denials only, on every issuance path. issuer_type is cluster, serverless, or developer-identity, the same values the activity feed uses. cluster_id is empty for Developer Identity, and for serverless workloads matched by a trust-domain-scoped policy. |
defakto_server_mint_svid_total | svid_type, status_code | Agent workloads only. Denials land on the primary issuance counter as status_code = blocklist_denied, alongside the other outcomes in the server runbook. |
The Trust Domain Server returns a FailedPrecondition error. Serverless workloads and Developer Identity receive that error directly. An agent treats the error as final, and the workload's Workload API call fails with PermissionDenied, carrying the Trust Domain Server's message:
the trust domain server rejected the SVID issuance request with reason: SVID
issuance denied: the SPIFFE ID is on the trust domain's SVID block list
The matched entry and its reason appear only in the SVID error event, not in the error the workload receives.
In Console, a denial appears on the trust domain's Audit Logs tab with the event type Workload credential denied. Its summary starts with "X509 issuance blocked by the SVID block list" (or JWT or WIT), followed by the workload, the matched entry, and its reason.
Limitations
- SVIDs already issued stay valid. The block stops new SVIDs and rotations. An X.509-SVID, JWT-SVID, or WIT-SVID issued before the entry was added stays valid until it expires, 24 hours by default. Revoke or rotate whatever those SVIDs grant access to at the relying party.
- Servers older than v0.42.0 do not enforce the block list. The Defakto Control Plane accepts the section regardless of server version, so entries added while a server runs an older version take effect when that server is upgraded. Review the block list before upgrading.
- A server skips an entry it cannot read. An entry this server version cannot parse, such as one using a match type added in a later version, blocks nothing on that server. The server logs
Skipped SVID block list entry: it blocks nothing on this signerat Warn level, with the entry's SPIFFE ID, match type, and error. - The block list cannot change while the Defakto Control Plane is unreachable. Trust Domain Servers keep enforcing the last block list they received.
- Token exchange is not covered. On-behalf-of tokens and OAuth access tokens are governed by the trust domain's token policy.
Validation
The Defakto Control Plane rejects a section that:
- Contains more than 100 entries.
- Contains an entry whose
spiffeIDbelongs to a different trust domain. - Sets
matchto a value other thanexactorwildcard. - Contains an
exactentry that is not a valid SPIFFE ID, or that contains*. - Contains a
wildcardentry with a*that is not a whole path segment, or a**anywhere but the final segment. - Contains a
wildcardentry with no literal path segment, such asspiffe://example.org/*orspiffe://example.org/**, which would block the whole trust domain.