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)#

Deployment with hardcoded 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#

Deployment with 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#

SecretProviderClass for CSI#
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
Deployment with CSI Provider#
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#

VaultStaticSecret resource#
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
Deployment with VaultStaticSecret#
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#