Secrets PDP enrichment
Secrets PDP Enrichment: Subject, Resource, Action, Context
This document describes exactly what we send to the PDP for authorization decisions in the secrets engine, with code-backed details and concrete examples.
Where the PDP call happens
- Provider-enforced reads (PEP):
VaultService.get_credentials(...)callsSecretPolicyService.authorize_use(...)with purposeexecuteand an enriched context before any provider access. - Admin/CRUD operations (API):
src/api/secrets_routes.pyguards write, delete, rotate, versions, etc., by callingSecretPolicyService.authorize_batch(...)with a purpose specific to the endpoint (e.g.,write,delete,rotate,read_metadata,destroy_versions,undelete,owner_update).
What we send (fields)
Subject
subject_arn: Extracted fromrequest.state.subject. If missing, defaults toanonymous.
Action (purpose)
- PEP read path (provider-backed): purpose is
execute. - API routes purposes:
- Create/Update:
write - Delete (soft):
delete - Destroy versions (hard):
destroy_versions - Undelete versions:
undelete - Rotate:
rotate - Metadata listing/detail/search/keys/versions:
read_metadata - Owner update:
owner_update
- Create/Update:
Resource
canonical_uri: The Canonical Secret URI (e.g.,openbao+kv2://secret/app/api#tokenor without fragment for path-level operations).- Tenant guard ensures the mount is allowed (
TENANT_ALLOWED_MOUNTS).
Context (enriched)
For provider-backed reads (PEP in VaultService), we send:
issuer:request.state.issuerclient_id:request.state.client_id(orazp)aud:request.state.aud(list/string); we also enforce that it includesSECRETS_AUDIENCEat the PEP layer when presenttoken_jti:request.state.token_jti(for anti-replay)correlation_id:request.state.correlation_idworkflow_run_id:request.state.workflow_run_idnode_id:request.state.node_idsystem_id:request.state.system_idresource_attributes: owner and provenance attributes when available (see below)
Sender-binding (if available):
cnf_binding: extracted as either DPoPjkt(request.state.cnf_jkt) or mTLS thumbprint (request.state.mtls_thumbprint). This value is bound to the grant and enforced for drift.
Resource attributes (KVv2 custom metadata):
- From provider metadata read, we attach:
owner: mutable owner ARNcreated_by: immutable creator ARNcreated_at: ISO timestamp
These are automatically stamped on first write/rotation for KVv2 providers and fetched during PEP evaluation.
Code references
-
PEP: Context build and PDP call (execute):
pdp_context = {
"issuer": issuer,
"client_id": client_id,
"aud": audiences,
"token_jti": token_jti,
"correlation_id": correlation_id,
"workflow_run_id": workflow_run_id,
"node_id": node_id,
"system_id": system_id,
}
# resource_attributes added if present
grant = await self._policy.authorize_use(subject_arn or "anonymous", tenant_id, canonical_uri, "execute", cnf_binding, context=pdp_context) -
Resource attributes enrichment (metadata → context.resource_attributes):
meta = await strat.read_secret_metadata(path_only)
custom = (meta or {}).get("custom_metadata", {})
pdp_context["resource_attributes"] = {k: v for k, v in custom.items() if k in {"owner","created_by","created_at"}} -
Purpose mapping on API routes:
- Create/Update (write):
grants, failures = await svc.authorize_batch(..., purpose="write") - Delete/Destroy/Undelete:
purpose="delete" | "destroy_versions" | "undelete" - Rotate:
purpose="rotate" - Metadata (list/detail/search/keys/versions):
purpose="read_metadata" - Owner update:
purpose="owner_update"
- Create/Update (write):
Example payloads
1) Provider-backed read (PEP) of a fragment
Request: GET /api/secrets/value?uri=openbao+kv2://secret/app/api#token
PDP input (conceptual):
{
"subject_arn": "auth:account:idp:alice",
"tenant_id": "acme",
"canonical_uri": "openbao+kv2://secret/app/api#token",
"purpose": "execute",
"cnf_binding": "dp0p-jkt-sha256...",
"context": {
"issuer": "https://idp.example.com/api/oidc",
"client_id": "spa-client",
"aud": ["crud.secrets"],
"token_jti": "af2d...",
"correlation_id": "6c0d...",
"workflow_run_id": null,
"node_id": null,
"system_id": null,
"resource_attributes": {
"owner": "auth:account:idp:platform-team",
"created_by": "auth:account:idp:alice",
"created_at": "2025-01-25T12:34:56Z"
}
}
}
2) Rotate a secret value (KVv2)
Request: POST /api/secrets/rotate { uri, value }
PDP input (conceptual):
{
"subject_arn": "auth:account:idp:bob",
"tenant_id": "dev",
"canonical_uri": "openbao+kv2://secret/app/api#token",
"purpose": "rotate"
}
3) Update owner metadata
Request: POST /api/secrets/metadata/owner { uri: "openbao+kv2://secret/app/api", owner: "auth:account:idp:data-team" }
PDP input (conceptual):
{
"subject_arn": "auth:account:idp:admin",
"tenant_id": "acme",
"canonical_uri": "openbao+kv2://secret/app/api",
"purpose": "owner_update"
}
Security notes
- Audience check at the PEP enforces that
audincludesSECRETS_AUDIENCEwhen present. - Anti-replay:
token_jtiis checked in the grant cache; replays are denied. - Sender-binding drift: if the
cnf_bindingdiffers mid‑TTL, further uses are denied. - Resource attributes (owner/created_by/created_at) are fetched server-side from the provider to avoid trusting client-provided metadata.
See also:
- Authorization model overview:
./11-authorization-model-authzen.md - API reference (purposes and scopes):
./09-api-reference.md