Architecting Zero Trust Database Access in Kubernetes with SPIFFE/SPIRE and Vault
Architecting Zero Trust Database Access in Kubernetes with SPIFFE/SPIRE and Vault
Introduction
In traditional cloud-native architectures, applications rely on long-lived database credentials stored in environment variables, configuration files, or secret managers. If an attacker compromises a single container or gains unauthorized access to a Git repository, these static credentials grant indefinite lateral access. To address this risk, modern enterprise security architectures are shifting toward a Zero Trust model.
By combining SPIFFE (Secure Production Identity Framework for Everyone), its reference implementation SPIRE, and HashiCorp Vault, organizations can eliminate static secrets entirely. This article explores how to architect and implement an identity-driven access system that issues short-lived, cryptographically verifiable identities and dynamic database credentials on-the-fly.
Core Concepts & Architecture
The fundamental principle of this architecture is the decoupling of workload identity from the secrets themselves. Instead of using a static token to fetch database credentials, the application relies on its cryptographic identity to authenticate with HashiCorp Vault.
-
SPIFFE/SPIRE (The Identity Control Plane): SPIRE validates the workload's runtime properties (such as Kubernetes Namespace, Service Account, and Pod UID) through node and workload attestation. It then issues a SPIFFE Verifiable Identity Document (SVID) in the form of an X.509 certificate or JWT token via a local Unix domain socket.
-
HashiCorp Vault (The Secrets Authority): Vault is configured to trust the SPIRE OpenID Connect (OIDC) or JWT provider. It validates incoming authentication requests by verifying the signature of the SVID.
-
Database Secrets Engine (The Credential Generator): Upon successful authentication, Vault uses its Database Secrets Engine to dynamically create a transient user in the target database (e.g., PostgreSQL) with constrained permissions and a short Time-To-Live (TTL).
Hands-on Implementation
To implement this workflow, we configure HashiCorp Vault to trust SPIRE-issued JWT SVIDs, map the workload's SPIFFE ID to a specific Vault role, and configure the database secrets engine.
First, configure the Vault JWT Auth Method to trust the SPIRE OIDC discovery endpoint:
hcl
Configure Vault JWT Authentication for SPIRE
path "auth/jwt/config" { capabilities = ["create", "read", "update", "delete", "list"] }
Create an authentication role mapping the workload SPIFFE ID to a Vault policy
resource "vault_jwt_auth_backend_role" "spire_workload" { backend = "jwt" role_name = "payment-service-role" token_policies = ["payment-db-access"] bound_claims = { "sub" = "spiffe://example.org/ns/production/sa/payment-service-sa" } user_claim = "sub" role_type = "jwt" }
Next, configure the PostgreSQL dynamic database secrets engine in Vault. This engine executes a template query to provision dynamic users with a strict 15-minute TTL:
hcl
Enable the database secrets engine
path "database/config/postgres-db" { capabilities = ["create", "read", "update"] }
Define the dynamic role and generation SQL template
path "database/creds/payment-db-role" { capabilities = ["read"] }
The corresponding Vault role configuration for PostgreSQL dynamically creates the user with structured permissions:
CREATE USER "{{name}}" WITH PASSWORD '{{password}}' VALID UNTIL '{{expiration}}';
GRANT SELECT, INSERT, UPDATE ON ALL TABLES IN SCHEMA public TO "{{name}}";
When the payment application starts up, it communicates with the local SPIRE Agent over the workload API socket, retrieves its JWT SVID, presents this token to the Vault /v1/auth/jwt/login endpoint, and receives a Vault client token. It then requests credentials from /v1/database/creds/payment-db-role. When the 15-minute TTL expires, Vault automatically drops the user from the PostgreSQL database, leaving no lingering footprint.
Security & Best Practices
Zero Trust dictates that identity must be validated continuously and privileges should expire as quickly as possible to minimize the radius of potential exposure.
-
Tighten Attestation Selectors: Do not rely solely on namespace-level selectors in SPIRE. Use strict attestation selectors including
k8s:container-image,k8s:sa, andk8s:pod-uidto ensure only the authentic binary can retrieve the SPIFFE ID. -
Enforce Aggressive TTLs: Configure the database engine credentials' TTL to the absolute minimum duration required by your application context (e.g., 5 to 15 minutes), and implement auto-renewal loops within your workload client code.
-
Establish Auditing and Correlated Logging: Ensure that Vault's audit logs are shipped to a centralized SIEM. Since the dynamically generated database username contains the Vault transaction ID, security teams can easily trace back any database query to the specific SPIRE identity and Kubernetes pod that authorized it.
Conclusion
Transitioning from static environment secrets to cryptographically verified, dynamic identities is a crucial step in modern cloud-native security. By combining SPIFFE/SPIRE and HashiCorp Vault, organizations establish a highly resilient Zero Trust database access model. This architecture removes the risk of hardcoded secrets, limits the lifecycle of access credentials, and provides platform engineering teams with granular, verifiable control over backend datastores.
