Serverless Attestation Extension
The Extension method plugs custom attestation logic into serverless attestation. The workload submits proof data through the SDK. The Defakto Server forwards the proof to an HTTPS webhook you operate. The webhook validates or enriches the proof and returns custom attributes that become available for SPIFFE Verifiable Identity Document (SVID) issuance.
Use this method when the built-in attestation methods can't express your requirements, for example:
- Cross-checking workload identity against an internal inventory or CMDB.
- Enriching SVIDs with deployment metadata (environment, team, cost center) from an external system.
- Attesting platforms that have no built-in serverless method.
This method is used during serverless attestation (when workloads request SVIDs directly from the Defakto Server). For extending agent attestation, see the Agent Attestation Extension. For extending workload attestation, see the Server Workload Attestation Extension and the Agent Workload Attestation Extension. Unlike the agent flow, there is no extension executable. The workload produces the proof in-process through the SDK.
Attestation Flow
The extension is always processed last: The webhook receives the attributes already verified by the other methods in the policy, so it can cross-check the workload's proof against attested platform identity.
Attributes available for SVID issuance
Custom attributes returned by the webhook have the origin custom. The attribute names are defined by your webhook implementation.
| Attribute | Description |
|---|---|
custom.<key> | Any key returned in the webhook's JSON response |
Example SPIFFE ID path template using webhook-returned attributes:
/serverless/{{custom.environment}}/{{custom.team}}
How to Deploy
Step 1 — Update Trust Domain configuration
Add type: extension as a required attestor in a ServerlessAttestation policy. Combine the extension with a built-in method so the webhook receives platform-verified attributes to validate the proof against:
section: ServerlessAttestation
schema: v1
spec:
policies:
- name: aws_with_extension
svidPolicy:
pathTemplate: "/serverless/{{custom.environment}}"
requiredAttestors:
- type: aws_token
config:
issuerURLs:
- "https://a1e777e5-1234-5678-9bf8-cdda2afef4bb.tokens.sts.global.api.aws"
- type: extension
config:
webhookURL: "https://attestation.example.com/webhook"
timeout: "10s"
authType: "BEARER"
maxRetries: 3
Apply it using spirlctl:
spirlctl config set trust-domain --id <trust-domain-id> serverless.yaml
Or using Terraform:
resource "spirl_trust_domain_config" "serverless_attestation" {
trust_domain_id = spirl_trust_domain.my_trust_domain.id
sections = {
ServerlessAttestation = <<-YAML
section: ServerlessAttestation
schema: v1
spec:
policies:
- name: aws_with_extension
svidPolicy:
pathTemplate: "/serverless/{{custom.environment}}"
requiredAttestors:
- type: aws_token
config:
issuerURLs:
- "https://a1e777e5-1234-5678-9bf8-cdda2afef4bb.tokens.sts.global.api.aws"
- type: extension
config:
webhookURL: "https://attestation.example.com/webhook"
timeout: "10s"
authType: "BEARER"
YAML
}
}
Once a configuration document is validated and stored, the Defakto control plane syncs it to your Trust Domain Servers automatically. No restart is required.
Server Configuration Reference
| Field | Type | Required | Default | Description |
|---|---|---|---|---|
webhookURL | string | Yes | — | HTTPS endpoint to call during attestation |
timeout | duration | No | 5s | Maximum wait time per webhook call (Go duration format: 10s, 1m) |
caCerts | string | No | System roots | PEM-encoded CA certificate bundle for validating the webhook's TLS certificate |
insecureSkipVerify | bool | No | false | Skip TLS verification. Never use in production. |
authType | string | No | NONE | Authentication type: BEARER or NONE |
tokenPath | string | No | In-cluster service account token | Path to bearer token file. Used when authType is BEARER |
maxRetries | int | No | 2 | Maximum retry attempts for transient errors (5xx, timeouts). Set 0 to disable |
Step 2 — Implement the Webhook
Your webhook must accept HTTP POST requests at the configured webhookURL. The Trust Domain Server sends a JSON body and expects a JSON response.
The webhook protocol is the same as the Agent Attestation Extension webhook, including its OpenAPI specification. One webhook implementation can serve both agent and serverless attestation.
Request format
{
"_meta": {
"version": "1.0"
},
"cluster": {
"cluster_id": ""
},
"payload": "<string from the workload's extension attestor>",
"<attestor_key>": {
"<nested>": "<attributes>"
}
}
| Field | Description |
|---|---|
_meta.version | Protocol version — currently "1.0" |
cluster.cluster_id | Empty in serverless attestation requests, since serverless workloads have no agent cluster context. Treat it as informational rather than a field to branch on |
payload | Proof data submitted by the workload's extension attestor, forwarded verbatim |
<attestor_key> | Attributes from the other verified methods in the policy (e.g. aws_token, gcp_iit) |
Example request body for a workload attesting with aws_token alongside the extension:
{
"_meta": { "version": "1.0" },
"cluster": { "cluster_id": "" },
"aws_token": {
"account": { "id": "123456789012" },
"source_region": "us-west-2"
},
"payload": "eyJkZXBsb3ltZW50IjoiY2hlY2tvdXQtYXBpIiwiZW52IjoicHJvZHVjdGlvbiJ9"
}
Success response
Return HTTP 200 with a flat JSON object. All keys become custom.* attributes:
{
"environment": "production",
"region": "us-west-2",
"team": "platform"
}
An empty object {} is valid. Attestation succeeds with no additional attributes.
HTTP 200 is the only success status. Any other status, including other 2xx codes such as 201 and 204, rejects the attestation. The response body must also be valid JSON: A 200 with an empty body fails to parse and rejects the attestation, so return {} rather than no body at all.
Error response
Return a JSON object with an error key to reject the attestation:
{
"error": "workload is not registered in CMDB"
}
A non-empty error value rejects the attestation even when the HTTP status is 200. An absent or empty error is treated as success. The error message is logged and returned to the caller. Any keys other than error are ignored when error is present.
HTTP status codes
| Status | Behavior |
|---|---|
| 200 | Attestation proceeds, unless the body contains a non-empty error. The body is parsed for custom attributes |
| Any other status below 500 | Attestation rejected immediately — not retried. This includes 4xx and other 2xx codes such as 204 |
| 5xx | Retried up to maxRetries times with exponential back-off |
Retry behavior
Retryable (retried up to maxRetries times with exponential back-off):
- HTTP 5xx responses — server-side errors
- Connection timeouts — webhook took too long to respond
- Temporary network failures — connection refused, connection reset
- Temporary DNS failures — DNS resolution temporarily unavailable
Non-retryable (fail immediately):
- Any response status below 500 other than 200 — including 4xx and non-200 2xx codes
- TLS certificate validation failures — invalid or expired certificate
- Authentication failures — invalid bearer token
- Invalid webhook responses — malformed JSON or unexpected format
- Configuration errors — invalid webhook URL
Because a webhook call can be retried, handlers must be idempotent. A timeout or 5xx returned after the handler has already committed a side effect (an audit record, a counter, a provisioning call) will be retried, so deduplicate on a request identifier or make the side effect safe to repeat.
Step 3 — Implement the workload's extension attestor
The Defakto SDKs have no built-in extension attestor because the proof format is defined entirely by your webhook. Instead, implement the SDK's Attestor interface with the plugin name extension and version 1.0, and pass it to AttestingWorkloadAPIClient alongside the policy's other attestors.
See SDK Examples — Extension for complete Python and TypeScript examples.
Requirements for the extension attestor:
- The plugin name must be
extensionto matchtype: extensionin the policy. - The plugin version must be
1.0. Other versions are rejected by the server. - The payload must be non-empty. The serverless API rejects attestations without evidence.
- The
DEFAKTO_ATTESTORSenvironment variable does not support extensions. UseAttestingWorkloadAPIClientwith an explicit attestor list.
Step 4 — Verify
Server logs — look for webhook activity:
DEBUG Calling external webhook {"url": "https://attestation.example.com/webhook", "attempt": 0}
INFO Custom attributes added {"count": 2}
Common errors:
| Error | Likely cause |
|---|---|
no trust domain policy authorizes the provided attestors | The workload did not submit evidence for every method the policy requires. Verify the SDK submits the extension attestor together with the policy's other attestors |
unsupported proof version | The extension attestor's plugin version is not 1.0 |
invalid attestation: evidence is required | The extension attestor produced an empty payload |
failed to process requirement of type extension | The webhook rejected the proof, returned malformed JSON, or was unreachable. Check the server logs for the webhook's error message |
Security Considerations
- Combine with a built-in method. An extension-only policy makes your webhook the sole gatekeeper for SVID issuance. Requiring a platform method (e.g.
aws_token) alongside the extension lets the webhook cross-check the workload's proof against platform-verified attributes. - Validate the payload in your webhook. The payload is self-asserted by the workload. Treat it as untrusted input and verify it against the attested attributes or an external source of truth.
- Use HTTPS for the webhook URL in all non-development environments, and use
caCertswhen the webhook's certificate is signed by a private CA. - Set a short
timeout. A slow webhook delays every serverless SVID request that matches the policy. - Limit error detail in webhook responses. Error messages are logged and may be returned to the workload, so avoid exposing sensitive internal details.