The Vault Secrets Operator syncs secrets from HashiCorp Vault to Kubernetes Secrets, but it’s not just a simple copy-paste. It’s a dynamic, reconciliatory process that ensures your Kubernetes workloads always have the latest version of sensitive data.
Let’s see it in action. Imagine we have a Database custom resource in Kubernetes that defines our database connection details.
apiVersion: example.com/v1
kind: Database
metadata:
name: my-app-db
spec:
engine: postgresql
name: mydatabase
roles:
- name: myappuser
db_name: mydatabase
creation_days: 1
revocation_days: 10
The Vault Secrets Operator watches for Database resources. When it sees my-app-db, it talks to Vault to create a new PostgreSQL role and user. Vault then generates a username and password for this role. The operator takes these credentials and writes them into a Kubernetes Secret object.
Here’s what that Kubernetes Secret might look like:
apiVersion: v1
kind: Secret
metadata:
name: my-app-db-credentials
namespace: default
type: Opaque
data:
username: bXlhcHB1c2Vy # Base64 encoded
password: c29tZXNlY3JldHBhc3N3b3Jk # Base64 encoded
The operator doesn’t just create it once. It continuously monitors both the Database resource and the generated Kubernetes Secret. If the Database resource changes, or if Vault’s generated credentials expire or are revoked, the operator will update the Kubernetes Secret accordingly. This means your applications running in Kubernetes can mount this Secret and always get fresh, valid credentials without manual intervention.
The core problem this solves is securely distributing dynamic secrets to applications running in Kubernetes. Traditionally, you’d have to bake secrets into your deployment, which is insecure and difficult to update. Or, you’d have a complex CI/CD pipeline to fetch and update secrets, adding latency and risk. The Vault Secrets Operator automates this flow, making secrets management for Kubernetes much more robust and secure.
Internally, the operator uses Kubernetes Custom Resource Definitions (CRDs) to define resources like Database. It then acts as a controller, a piece of software that watches for changes to these custom resources. When a change is detected, the controller executes a reconciliation loop. This loop involves:
- Reading the
Databasecustom resource. - Interacting with the Vault API to create or update the corresponding secret in Vault (e.g., generating a PostgreSQL role and credentials).
- Reading the generated credentials from Vault.
- Creating or updating a standard Kubernetes
Secretobject with these credentials. - Managing the lifecycle of these secrets, including rotation and revocation, by periodically checking Vault and updating the Kubernetes
Secretas needed.
The operator supports various secret engines in Vault, not just databases. It can handle PKI secrets for certificates, AWS IAM credentials, SSH keys, and more. For each engine, it has specific logic to interpret the Vault-generated data and map it into a Kubernetes Secret.
The most surprising thing most people don’t realize is how the operator handles secret expiration and revocation. When Vault generates a secret with a lease (like a database username/password that expires), the operator doesn’t just wait for the lease to expire. It actively monitors the lease’s remaining TTL (Time To Live) in Vault. If the TTL is approaching zero, or if the secret is explicitly revoked in Vault, the operator will immediately update the corresponding Kubernetes Secret to reflect the new state, often by generating a new set of credentials. This proactive approach ensures that applications don’t suddenly find themselves with invalid credentials.
The next concept you’ll likely explore is how to manage the lifecycle of these secrets across different namespaces, and how to integrate this with your application’s deployment strategies.