Secrets SDK (Python)
Overview
Secrets SDK provides two client interfaces for resolving Canonical Secret URIs:
SecretsClient - Direct vault access for service configuration:
- File-based bootstrap (solves chicken-egg problem)
- Provider routing:
file://,openbao+kv2://,hashicorp+kv2:// - Memory caching with TTL
- Lazy authentication (auth on first vault access)
VaultClient - PDP-enforced access for multi-tenant operations:
- Canonical URI parsing and tenant-mount guard
- PEP grants with sender-binding, anti-replay, and negative caching
- Provider strategies for OpenBao/HashiCorp KVv2 (version-pinned reads, deleted/destroyed typing)
- Optional non-leaky audits using short
resource_ref
Design goals
- Strong typing and small public API surface
- Minimal required configuration; sensible defaults
- No plaintext secret leakage (logs/metrics/OTEL)
Non-goals
- Managing secret values at rest (creation/rotation workflows live in services that own policy)
- Exposing HTTP routes (that's CRUDService's role)
Architecture
secrets_sdk/
__init__.py # Exports: SecretsClient, SecretURI, BootstrapConfig
client.py # SecretsClient - direct vault access
bootstrap.py # BootstrapConfig, load_bootstrap()
context.py # ExecutionContext helpers
errors.py # Typed exceptions
secret_uri.py # Canonical URI parser/normalizer
audit.py # Pluggable audit publisher (Kafka/no-op)
vault_client.py # VaultClient - PDP-enforced access
grants/
grant_cache.py # In-memory grants, negative cache, anti-replay
services/
secret_policy_service.py # PDP facade
vault_strategies/
base_vault_strategy.py
openbao_vault_strategy.py
hashicorp_vault_strategy.py
Public API
# SecretsClient - service config (no PDP)
class SecretsClient:
@classmethod
def create(cls, bootstrap_path: str | None = None, ...) -> "SecretsClient": ...
async def resolve(self, uri: str, *, refresh: bool = False) -> Any: ...
async def close(self) -> None: ...
# VaultClient - request handling (PDP-enforced)
class VaultClient:
def __init__(self, *, enable_kafka: bool | None = None) -> None: ...
async def get_credentials(self, canonical_uri: str, ctx: ExecutionContext | None = None) -> dict | str: ...
def get_credentials_sync(self, canonical_uri: str, ctx: ExecutionContext | None = None) -> dict | str: ...
class ExecutionContext:
subject: str | None
aud: list[str] | None
token_jti: str | None
cnf_jkt: str | None
mtls_thumbprint: str | None
@staticmethod
def from_fastapi_request(request) -> "ExecutionContext": ...
@staticmethod
def from_headers(headers: dict[str, str]) -> "ExecutionContext": ...
VaultClient Behavior (PEP flow)
- Parse → Canonicalize → Tenant mount guard
- Build grant key
(subject, tenant_id, canonical_uri, "execute", cnf_binding) - Check negative cache; fetch/issue grant via
SecretPolicyServiceon miss - Enforce audience (
SECRETS_AUDIENCE), JTI anti-replay, sender binding drift - Increment grant uses atomically; on exceed → deny
- Provider read (KVv2 with optional
version=); map deleted/destroyed to typed errors - Publish non-leaky audit (if enabled)
Configuration
SecretsClient
Required:
OPENBAO_URLorVAULT_URLSECRETS_ALLOWED_MOUNTS(comma list)
Optional:
OPENBAO_TOKEN/VAULT_TOKEN(dev)FILE_MOUNT_PATH(default/run/secrets)SECRETS_BOOTSTRAP_PATH
VaultClient
Required:
VAULT_URL,VAULT_TOKEN,TENANT_ID,TENANT_ALLOWED_MOUNTSSECRET_TENANT_SALT(HMAC key forresource_ref)
Optional:
VAULT_TIMEOUT(default 30),VAULT_VERIFY_SSL(default true)GRANT_TTL_DEFAULT(default 300),GRANT_MAX_USES_DEFAULT(default 1)NEGATIVE_CACHE_TTL_S(default 5),ANTI_REPLAY_TTL_S(default 300)SECRETS_AUDIENCE(defaultcrud.secrets)- Audits:
ENABLE_KAFKA_PRODUCER,KAFKA_BOOTSTRAP_SERVERS,KAFKA_TOPIC_PREFIX
Dependencies
dependencies = ["tenacity", "cachetools", "httpx", "opentelemetry-api"]
Usage
SecretsClient
from secrets_sdk import SecretsClient
client = SecretsClient.create()
# Sync resolution (config loading, file:// only)
db_url = client.resolve_file_sync("file://primary/db-conn-string")
# Async resolution (runtime, all providers)
ldap = await client.resolve("file://dc-credentials/ldap.json")
token = await client.resolve("openbao+kv2://secret/app/api#token")
await client.close()
VaultClient (FastAPI)
from secrets_sdk.vault_client import VaultClient
from secrets_sdk.context import ExecutionContext
client = VaultClient()
async def handler(request):
ctx = ExecutionContext.from_fastapi_request(request)
payload = await client.get_credentials("openbao+kv2://secret/myapp/api#token?version=3", ctx)
return payload["token"]
VaultClient (Sync)
token = VaultClient().get_credentials_sync("openbao+kv2://secret/app/api#token", ctx=None)
Errors
from secrets_sdk import SecretURIError
from secrets_sdk.errors import AuthzDeniedError, PDPUnavailableError, BindingDriftError
from secrets_sdk.vault_strategies.errors import (
VaultSecretNotFoundError,
VaultSecretVersionDeletedError,
VaultSecretVersionDestroyedError,
)
Testing
Mock _read_secret_kvv2 for vault strategy tests:
with patch.object(strategy, "_read_secret_kvv2", new_callable=AsyncMock) as mock:
mock.return_value = {"data": {"data": {"token": "test"}, "metadata": {"version": 1}}}
result = await strategy.get_credentials("openbao+kv2://secret/app#token")
CI: set VAULT_SKIP_AUTH_CHECK=true to bypass auth check (tests only).
Build & Test
pip install -e .[dev]
pytest -q
See docs/ for detailed guides.
Metadata
Release files for empowernow-secrets-sdk 0.2.0
For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.
Source distribution (sdist)
| File | Size | Uploaded | |
|---|---|---|---|
| empowernow_secrets_sdk-0.2.0.tar.gz | 18.8 kB | Details |
Built distribution (wheel)
| File | Interpreter | ABI | Platform | Reset |
|---|---|---|---|---|
| empowernow_secrets_sdk-0.2.0-py3-none-any.whl | Python 3 | none | any | Details |
Total release size: 44.5 kB
Release files / empowernow_secrets_sdk-0.2.0.tar.gz
| Download URL | empowernow_secrets_sdk-0.2.0.tar.gz |
|---|---|
| Size | 18.8 kB |
| Tags | Source |
|
SHA-256 checksum How to use checksums |
f367dfbcc94dd1d2fb60172a62500e4a1f44697c6564849f05eb63971611bca9
|
|
BLAKE2b-256 checksum How to use checksums |
b3d05e9ca1de752233a9a1bedbce0c630d0209bd0de47c834fd3a814e721f3f4
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
No |
| Uploaded via |
twine/6.2.0 CPython/3.13.9
|
Release files / empowernow_secrets_sdk-0.2.0-py3-none-any.whl
| Download URL | empowernow_secrets_sdk-0.2.0-py3-none-any.whl |
|---|---|
| Size | 25.7 kB |
| Tags | Python 3 |
|
SHA-256 checksum How to use checksums |
972e2a13daf6286cde93263a1ab474f1b9000a799899e89865998f1749cc7dd6
|
|
BLAKE2b-256 checksum How to use checksums |
faa023e698738c5a2a2d651dafeb93344e51d83a4b5111cddccde989ced8f5e8
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
No |
| Uploaded via |
twine/6.2.0 CPython/3.13.9
|