Vault
S4E On-Prem integrates with HashiCorp Vault for centralized secrets management. This integration eliminates the need to store sensitive values in Kubernetes Secrets, Helm values files, or environment variables in plain text.
Overview
HashiCorp Vault provides:
- Centralized secret storage -- passwords, API keys, and certificates in one secure location.
- Dynamic secrets -- auto-generated, short-lived database credentials.
- Audit logging -- every secret access is logged for compliance.
- Access policies -- fine-grained control over which services can access which secrets.
- Encryption as a service -- transit encryption without managing keys.
Architecture
+-------------+ +------------+ +-----------+
| S4E Pods | ----> | Vault | ----> | Secret |
| (sidecar or | | Server | | Backend |
| init) | +------------+ | (Consul, |
+-------------+ | | File, |
| | Raft) |
+------v-------+ +-----------+
| Audit Log |
+--------------+
S4E services access Vault through one of two methods:
- Vault Agent Sidecar Injector -- a sidecar container fetches secrets and writes them to a shared volume.
- Init Container -- an init container fetches secrets before the application starts.
Prerequisites
- HashiCorp Vault server (v1.12+) deployed and unsealed.
- Vault Agent Injector installed in the Kubernetes cluster.
- Kubernetes authentication method enabled in Vault.
- Network connectivity from S4E pods to the Vault server.
Setting Up Vault
Step 1: Enable the KV Secrets Engine
Step 2: Store S4E Secrets
vault kv put s4e/database \
host=postgresql.s4e.svc.cluster.local \
port=5432 \
name=s4e_production \
username=s4e_app \
password="<strong-db-password>"
vault kv put s4e/redis \
connection_string="redis://redis:6379" \
password="<redis-password>"
vault kv put s4e/rabbitmq \
host=rabbitmq.s4e.svc.cluster.local \
port=5672 \
username=s4e_mq \
password="<rabbitmq-password>"
vault kv put s4e/app \
secret_key="<application-secret>" \
smtp_password="<smtp-password>"
Step 3: Configure Kubernetes Authentication
vault auth enable kubernetes
vault write auth/kubernetes/config \
kubernetes_host="https://kubernetes.default.svc:443" \
token_reviewer_jwt="$(cat /var/run/secrets/kubernetes.io/serviceaccount/token)" \
kubernetes_ca_cert=@/var/run/secrets/kubernetes.io/serviceaccount/ca.crt
Step 4: Create a Vault Policy
# s4e-policy.hcl
path "s4e/data/database" {
capabilities = ["read"]
}
path "s4e/data/redis" {
capabilities = ["read"]
}
path "s4e/data/rabbitmq" {
capabilities = ["read"]
}
path "s4e/data/app" {
capabilities = ["read"]
}
Step 5: Create a Kubernetes Auth Role
vault write auth/kubernetes/role/s4e \
bound_service_account_names=s4e-core,s4e-trigger,s4e-scan,s4e-crawler \
bound_service_account_namespaces=s4e \
policies=s4e-read \
ttl=1h
Configuring S4E to Use Vault
Method 1: Vault Agent Sidecar Injector
Add annotations to S4E pod templates to inject secrets:
# In s4e-values.yaml
core:
podAnnotations:
vault.hashicorp.com/agent-inject: "true"
vault.hashicorp.com/role: "s4e"
vault.hashicorp.com/agent-inject-secret-db: "s4e/data/database"
vault.hashicorp.com/agent-inject-template-db: |
{{- with secret "s4e/data/database" -}}
export DB_HOST={{ .Data.data.host }}
export DB_PORT={{ .Data.data.port }}
export DB_NAME={{ .Data.data.name }}
export DB_USER={{ .Data.data.username }}
export DB_PASS={{ .Data.data.password }}
{{- end }}
The sidecar writes secrets to /vault/secrets/db, which the application sources on startup.
Method 2: External Secrets Operator
If you use the External Secrets Operator (ESO), configure an ExternalSecret to sync Vault secrets into Kubernetes Secrets:
apiVersion: external-secrets.io/v1beta1
kind: ExternalSecret
metadata:
name: s4e-db-secrets
namespace: s4e
spec:
refreshInterval: 1h
secretStoreRef:
name: vault-backend
kind: ClusterSecretStore
target:
name: s4e-db-credentials
creationPolicy: Owner
data:
- secretKey: DB_HOST
remoteRef:
key: s4e/data/database
property: host
- secretKey: DB_PASS
remoteRef:
key: s4e/data/database
property: password
External Secrets Operator
ESO is recommended when you want Vault secrets to appear as standard Kubernetes Secrets, allowing seamless integration with existing Helm charts and pod configurations.
Dynamic Database Credentials
Vault can generate short-lived PostgreSQL credentials automatically:
Enable the Database Secrets Engine
vault secrets enable database
vault write database/config/s4e-postgres \
plugin_name=postgresql-database-plugin \
allowed_roles="s4e-app" \
connection_url="postgresql://{{username}}:{{password}}@postgresql.s4e.svc:5432/s4e_production?sslmode=disable" \
username="vault_admin" \
password="<vault-admin-password>"
vault write database/roles/s4e-app \
db_name=s4e-postgres \
creation_statements="CREATE ROLE \"{{name}}\" WITH LOGIN PASSWORD '{{password}}' VALID UNTIL '{{expiration}}'; GRANT ALL ON ALL TABLES IN SCHEMA public TO \"{{name}}\";" \
default_ttl="1h" \
max_ttl="24h"
Connection pool considerations
Dynamic credentials rotate periodically. Ensure your application handles credential rotation gracefully by reconnecting when authentication fails.
Secret Rotation
Automatic Rotation with Vault Agent
Vault Agent automatically renews and re-fetches secrets before they expire. No application changes are required when using the sidecar injector method.
Manual Rotation
To rotate a secret manually:
vault kv put s4e/database \
host=postgresql.s4e.svc.cluster.local \
port=5432 \
name=s4e_production \
username=s4e_app \
password="<new-password>"
Then restart the affected pods to pick up the new values:
kubectl -n s4e rollout restart deployment/s4e-core
kubectl -n s4e rollout restart deployment/s4e-trigger
Troubleshooting
| Issue | Cause | Solution |
|---|---|---|
permission denied in Vault Agent logs |
Incorrect role or policy binding | Verify bound_service_account_names and policy paths |
| Secrets file not created in pod | Vault Agent Injector not installed | Install the Vault Agent Injector Helm chart |
403 Forbidden on secret access |
Service account not authorized | Check the Kubernetes auth role configuration |
| Expired credentials | TTL too short | Increase default_ttl in the database role |
Next Steps
- Database configuration -- set up PostgreSQL with Vault-managed credentials.
- Environment variables -- reference for all configurable values.