Airflow credential resolver lifecycle¶
This runbook explains how dpone resolves, rotates, and recovers credentials for
Airflow workloads. Pipeline authors normally need only a logical
connection_ref; platform engineers own the backend registry and runtime
identity.
Contract at a glance¶
The supported local Vault runtime policy is:
credentials:
resolver: vault_kv
mount: kv
kv_version: 2
path: dpone/prod/credentials/pg_source
fields:
username: username
password: password
version_policy: latest
resolution_scope: workload_start
The path belongs only in the restricted platform connection registry. It does not enter pipeline authoring, DAG code, operator arguments, XCom, normal logs, or Airflow parse reports.
flowchart LR
REF["Pipeline connection_ref"] --> BIND["Environment binding-set"]
BIND --> REG["Restricted connection registry"]
REG --> POD["dpone runtime pod"]
POD --> SA["Kubernetes ServiceAccount"]
SA --> AUTH["Vault Kubernetes Auth"]
AUTH --> KV["Vault KV v2"]
KV --> MEM["In-memory connection"]
MEM --> WORK["One workload"]
WORK --> EV["Safe version evidence"]
Airflow DAG parsing reads none of these secrets and performs no Vault, Kubernetes, Connection, Variable, database, or network I/O.
Rotation semantics¶
workload_start is a snapshot boundary, not a refresh timer:
- The first use of one
connection_refresolves the backend once. - Every later use inside that workload receives the same in-memory object.
- A secret rotation does not change an already running workload.
- A new workload creates a fresh scope and reads the new version.
- A failed lookup is not cached; retry after recovery resolves again.
sequenceDiagram
participant A as Workload A
participant V as Vault KV
participant B as Workload B
A->>V: Resolve pg_prod
V-->>A: Version 17
Note over V: Rotate to version 18
A-->>A: Reuse version 17
B->>V: Resolve pg_prod
V-->>B: Version 18
Parallel workloads may therefore use different versions if rotation happens between their start times. This is intentional and is recorded per workload.
Evidence¶
A successful Vault KV v2 resolution records only bounded metadata:
connection_ref: pg_prod
resolver: vault_kv
version_policy: latest
resolution_scope: workload_start
resolved_version: 18
resolved_at: 2026-07-17T12:03:11Z
Evidence never records secret values, secret hashes, Vault paths, tokens, full responses, signed URLs, or sensitive lease identifiers. An absent or non-positive KV v2 version fails closed; dpone does not invent one from secret content.
Resolver certification matrix¶
| Resolver | Execution location | Intended status | Local lifecycle evidence | Live evidence |
|---|---|---|---|---|
vault_kv |
Runtime pod through vault-kv-client |
Primary production | PASS for injected versioned port, rotation, outage, recovery, concurrency, redaction | UNVERIFIED for Vault Kubernetes Auth and current vault-kv-client metadata support |
kubernetes_secret_volume |
Platform projects files; runtime reads them | Production | PASS for confinement, missing fields and workload snapshot behavior | UNVERIFIED for CSI/External Secrets rotation |
kubernetes_secret_api |
Runtime calls Kubernetes API | Restricted escape hatch | PASS for injected-reader contract | UNVERIFIED; broad Secret RBAC is not certified |
airflow_connection |
Operator-side bridge at task execution | Compatibility | PASS for bounded bridge projection and cleanup | UNVERIFIED for an external Airflow Secrets Backend rotation |
env_var |
Runtime environment | Development/legacy only | PASS for policy rejection in production | Not production-certifiable |
PASS above means deterministic local contract coverage at the exact dpone
commit. It does not assert network, authentication, RBAC, rotation propagation,
or backend availability in a real cluster.
Current Vault client limitation¶
dpone 0.73 development currently installs vault-kv-client 0.1.0. Its public
get_secret() API returns KV data but removes the KV v2 response metadata.
Therefore the default adapter cannot yet produce the mandatory
resolved_version proof for an explicit kv_version: 2 registry entry. Runtime
fails with DPONE_CREDENTIAL_VERSION_METADATA_MISSING; this is safer than
claiming an unknown version.
Production certification requires a public client API that returns an atomic
secret snapshot containing both data and version metadata. dpone's resolver
port models that value as VaultSecretSnapshot; legacy mappings recognize only
the reserved _metadata.version envelope. Ordinary secret fields named
version or metadata never count as evidence. Do not work around this by
hashing secret values or calling private hvac/client internals. Until that API
is integrated and live-certified, the Vault production profile remains
UNVERIFIED.
Vault KV v1 remains readable for compatibility and reports
resolved_version: null; it is not rotation-version certified.
Unsupported policies¶
The v1 schema still parses two planned values so migrations remain explainable, but runtime refuses to execute semantics it cannot guarantee:
| Configuration | Error | Action |
|---|---|---|
version_policy: pinned |
DPONE_CREDENTIAL_PINNED_VERSION_UNSUPPORTED |
Use latest, or keep the workload blocked until a historical-version contract exists |
resolution_scope: dag_run_start |
DPONE_CREDENTIAL_DAG_RUN_SCOPE_UNSUPPORTED |
Use workload_start; DAG-wide credential snapshots are not implemented |
Both are rejected by dpone check --connections and again by runtime before
credential backend I/O.
Recovery runbook¶
1. Check configuration safely¶
This check validates aliases, resolver policy, logical references, field mappings, bridge policy, and environment identity. It does not read Vault.
2. Classify the failure¶
| Code | Meaning | Safe recovery |
|---|---|---|
DPONE_CREDENTIAL_BACKEND_UNAVAILABLE |
Vault read failed before a connection was built | Restore Vault/auth/network, then retry the workload |
DPONE_CREDENTIAL_VERSION_METADATA_MISSING |
KV v2 response did not expose an auditable positive version | Upgrade/fix the client adapter; do not bypass evidence |
DPONE_CREDENTIAL_FIELD_MISSING |
A declared mapping resolved to missing or empty data | Platform owner repairs the secret/field mapping; do not print values |
DPONE_CREDENTIAL_PINNED_VERSION_UNSUPPORTED |
Requested semantics are not implemented | Migrate to latest or leave blocked |
DPONE_CREDENTIAL_DAG_RUN_SCOPE_UNSUPPORTED |
Requested cache boundary is not implemented | Migrate to workload_start |
3. Recover the dependency¶
- verify the runtime ServiceAccount, Vault role and policy out of band;
- verify the logical mount/path with a platform identity, never in Airflow logs;
- restore Vault availability or repair the platform-owned registry;
- rotate the credential again if its exposure is suspected;
- retry the failed workload, not an arbitrary inner step that would reuse partially initialized connector state.
4. Verify the retry¶
The retried workload must contain a new successful credential-resolution record
with the actual version and timestamp. Already completed/active workload
evidence remains immutable. A skipped live test is UNVERIFIED, never PASS.
Kubernetes volume note¶
Kubernetes Secret volumes are projected read-only. A subPath mount does not
receive updates, so do not use it when the platform expects rotation
propagation. dpone still reads projected fields once per workload: a file update
during execution does not replace the in-memory connection. New pods/workloads
observe the projected state available at their own start.
Local contract verification¶
uv run pytest \
tests/test_airflow_credential_resolver_lifecycle.py \
tests/test_runtime_workload_scoped_credential_resolver.py -q
The release evidence report is
test_artifacts/airflow-credential-resolver-lifecycle-v1/validation-report.md.
It must label real Vault/Kubernetes/Airflow profiles UNVERIFIED unless they
were executed in an approved environment for the exact commit.