Vault Tutorial#
Learn to use Vault Secrets Operator (VSO) for Kubernetes secret management.
This tutorial covers migrating from deprecated Vault integrations to VSO, the recommended approach for SKAO deployments.
Understanding the migration#
SKAO previously supported two Vault integration methods:
Vault Sidecar Injector — Injects secrets via sidecar containers
Vault CSI Driver — Mounts secrets as CSI volumes
SKAO deprecated both methods. The Vault Secrets Operator (VSO) replaces them with a cleaner, more efficient approach.
Note
If you use ska-tango-util for TANGO device deployments, upgrade to version 0.4.13 to automatically migrate to VSO.
Why migrate to VSO?#
VSO offers significant advantages:
Feature |
Benefit |
|---|---|
No sidecar containers |
Reduces resource overhead and simplifies pod specs |
No volume mounts |
Syncs secrets independently of workloads |
Automatic refresh |
Secrets update without pod restarts (configurable) |
Rollout restart triggers |
Restarts deployments when secrets change |
Secret transformation |
Filters and transforms secret data before creating Kubernetes secrets |
Comparison: Before and after#
Consider a deployment needing USERNAME and PASSWORD from Vault.
Starting point (plain Helm values)#
apiVersion: apps/v1
kind: Deployment
metadata:
name: my-app
spec:
replicas: 3
selector:
matchLabels:
app: my-app
template:
metadata:
labels:
app: my-app
spec:
containers:
- name: nginx
image: nginx:1.14.2
ports:
- containerPort: 80
env:
- name: USERNAME
value: "{{ .Values.username }}"
- name: PASSWORD
value: "{{ .Values.password }}"
This approach requires passing secrets via Helm values or GitLab CI variables — insecure and difficult to audit.
Deprecated: Vault Sidecar Injector#
apiVersion: apps/v1
kind: Deployment
metadata:
name: my-app
spec:
replicas: 3
selector:
matchLabels:
app: my-app
template:
metadata:
labels:
app: my-app
annotations:
vault.hashicorp.com/agent-inject: "true"
vault.hashicorp.com/role: "kube-role"
vault.hashicorp.com/agent-inject-status: "update"
vault.hashicorp.com/agent-inject-secret-config: "<engine>/data/<path/to/secret>"
vault.hashicorp.com/agent-inject-template-config: |
{{`{{- with secret `}}"<engine>/data/<path/to/secret>"{{` -}}`}}
{{`{{- range $k, $v := .Data.data }}`}}
{{`export {{ $k }}={{ $v }}`}}
{{`{{- end }}`}}
{{`{{- end }}`}}
spec:
containers:
- name: nginx
image: nginx:1.14.2
ports:
- containerPort: 80
Problems:
Requires multiple annotations on every pod
Injects a sidecar container, increasing resource usage
Application must source the injected file or read it directly
Deprecated: Vault CSI Provider#
apiVersion: secrets-store.csi.x-k8s.io/v1
kind: SecretProviderClass
metadata:
name: my-app-secret-class
spec:
provider: vault
secretObjects:
- secretName: my-app-secret
type: Opaque
data:
- objectName: username
key: username
- objectName: password
key: password
parameters:
vaultAddress: https://vault.skao.int
roleName: kube-role
objects: |
- objectName: username
secretPath: <engine>/data/<path/to/secret>
secretKey: username
- objectName: password
secretPath: <engine>/data/<path/to/secret>
secretKey: password
apiVersion: apps/v1
kind: Deployment
metadata:
name: my-app
spec:
replicas: 3
selector:
matchLabels:
app: my-app
template:
metadata:
labels:
app: my-app
spec:
containers:
- name: nginx
image: nginx:1.14.2
ports:
- containerPort: 80
env:
- name: USERNAME
valueFrom:
secretKeyRef:
name: my-app-secret
key: username
- name: PASSWORD
valueFrom:
secretKeyRef:
name: my-app-secret
key: password
volumeMounts:
- name: secrets-store-inline
mountPath: "/mnt/secrets-store"
readOnly: true
volumes:
- name: secrets-store-inline
csi:
driver: secrets-store.csi.k8s.io
readOnly: true
volumeAttributes:
secretProviderClass: my-app-secret-class
Problems:
Requires mounting a CSI volume to a running pod
Syncs secret only when scheduler places the pod
Splits configuration across multiple resources
Current: Vault Secrets Operator#
apiVersion: secrets.hashicorp.com/v1beta1
kind: VaultStaticSecret
metadata:
name: my-app-secret
spec:
type: kv-v2
mount: <engine>
path: <path/to/secret>
refreshAfter: 60s
destination:
name: my-app-secret
create: true
overwrite: true
transformation:
excludeRaw: true
includes:
- username
- password
apiVersion: apps/v1
kind: Deployment
metadata:
name: my-app
spec:
replicas: 3
selector:
matchLabels:
app: my-app
template:
metadata:
labels:
app: my-app
spec:
containers:
- name: nginx
image: nginx:1.14.2
ports:
- containerPort: 80
env:
- name: USERNAME
valueFrom:
secretKeyRef:
name: my-app-secret
key: username
- name: PASSWORD
valueFrom:
secretKeyRef:
name: my-app-secret
key: password
Benefits:
Eliminates volumes and volume mounts
Creates secrets independently of workloads
Produces clean deployment manifests
Supports automatic refresh and rollout restarts
Implementing VSO in your project#
1. Create a VaultStaticSecret#
Define which Vault secret to sync:
apiVersion: secrets.hashicorp.com/v1beta1
kind: VaultStaticSecret
metadata:
name: my-app-secret
spec:
type: kv-v2
mount: dev # KV engine name
path: my-team/my-app # Path within the engine
refreshAfter: 60s # Check for updates every 60s
destination:
name: my-app-secret # Kubernetes secret name
create: true
overwrite: true
2. Reference the secret in your deployment#
Use the synced Kubernetes secret as normal:
env:
- name: DATABASE_URL
valueFrom:
secretKeyRef:
name: my-app-secret
key: database_url
3. Add rollout restart (optional)#
Automatically restart your deployment when secrets change:
spec:
rolloutRestartTargets:
- kind: Deployment
name: my-app
TANGO device servers#
For TANGO devices using ska-tango-util, configure secrets in your device specification:
secrets:
- secretPath: skao-team-system/my-device
secretMount: dev
env:
- secretKey: api_key
envName: API_KEY
default: "fallback-value"
Upgrade to ska-tango-util version 0.4.13 or later to use VSO automatically.
Next steps#
Read the Vault How-to Guides for specific tasks like secret rotation
Check the Vault Reference for VaultStaticSecret configuration details
Understand the How It Works for SKAO’s Vault structure