Skip to content

Volume Shield Installation

This guide enables transparent PVC encryption for annotated workloads. Read the architecture overview first if you are new to Volume Shield.

Prerequisites

Requirement Notes
CloudTaser operator installed See Operator Installation — Volume Shield is injected by the same webhook
EU-hosted OpenBao reachable Same instance used for secret injection
Nodes allow a privileged DaemonSet The vs-broker owns /dev/fuse and mount(2); workload pods stay unprivileged
Wrapper image v0.2.168 or newer Broker and sidecar binaries ship in the wrapper image

Serverless node pools are not supported

Like the eBPF agent, the vs-broker DaemonSet requires host-level access. GKE Autopilot and AWS Fargate cannot run Volume Shield.

Step 1 — Vault policy and role

The sidecar (and broker, for lifecycle state) authenticates via the Kubernetes auth backend using the cloudtaser-vs-broker role. Grant read access to the per-volume DEK path:

# volume DEKs
path "cloudtaser/data/volumes/*" {
  capabilities = ["read"]
}

Also grant read + check-and-set write on the Volume Shield state path — this is needed by default now, since trusted freshness (vault mode) ships as the default (see the threat model); skip only if every PVC and the cluster-wide default are explicitly set to freshness.mode: local and you never enable trusted nonce budgeting:

path "cloudtaser/data/volumeshield-state/*" {
  capabilities = ["create", "read", "update"]
}
path "cloudtaser/metadata/volumeshield-state/*" {
  capabilities = ["read", "list"]
}

Missing state-path access fails closed: mounts with nonceBudget.mode or freshness.mode set to vault (the default) will refuse to serve.

Step 2 — Enable in Helm values

volumeShield:
  enabled: true
  broker:
    mode: "fdpass"          # default — privilege-separated architecture
    daemonset:
      enabled: true          # per-node broker DaemonSet
    image:
      tag: "v0.2.168"
    vaultAddr: "https://your-openbao.example.eu:8200"
    vaultRole: "cloudtaser-vs-broker"
  sidecar:
    image:
      tag: "v0.2.168"
  # freshness.mode defaults to "vault" (trusted anti-rollback) — shown explicitly for clarity.
  # nonceBudget.mode: "vault" is an additional recommended production setting (not default):
  nonceBudget:
    mode: "vault"
  freshness:
    mode: "vault"

Then upgrade the release:

helm repo update
helm upgrade cloudtaser cloudtaser/cloudtaser \
  --namespace cloudtaser-system \
  --version v1.0.264 \
  -f values.yaml

Verify the broker is running on every node:

kubectl get daemonset -n cloudtaser-system -l app.kubernetes.io/component=vs-broker

Step 3 — Annotate PVCs

Volume Shield is opt-in per PVC. Annotate the PVCs you want encrypted before the consuming pod starts:

apiVersion: v1
kind: PersistentVolumeClaim
metadata:
  name: postgres-data
  annotations:
    cloudtaser.io/encrypt-pvc: "true"
    # optional:
    # cloudtaser.io/pvc-chunk-size-kb: "64"
    # cloudtaser.io/vs-io-mode: "cached"   # or "strict"
spec:
  accessModes: ["ReadWriteOnce"]
  resources:
    requests:
      storage: 10Gi

Any pod that mounts this PVC (and is in a namespace the webhook watches) is mutated at admission: the sidecar is injected, the PVC is shadow-remounted to a ciphertext path, and the application sees a transparent plaintext view at its original mount path. Pods with multiple annotated PVCs get one FUSE mount per PVC, each with its own DEK and its own per-mount I/O mode.

Filesystem volume mode only

Volume Shield encrypts volumeMode: Filesystem PVCs. Block-mode PVCs are ignored.

Step 4 — Verify

Start a workload and confirm:

# sidecar injected and ready (readiness = ALL mounts serving)
kubectl get pod <pod> -o jsonpath='{.spec.containers[*].name}'
# ... should include cloudtaser-vs-sidecar

# the application path shows plaintext
kubectl exec <pod> -c <app> -- head -c 100 /data/somefile

# the backing store holds ciphertext (CTVS magic)
NODE=$(kubectl get pod <pod> -o jsonpath='{.spec.nodeName}')
BROKER=$(kubectl get pod -n cloudtaser-system -l app.kubernetes.io/component=vs-broker \
  --field-selector spec.nodeName=$NODE -o name | head -1)
kubectl exec -n cloudtaser-system $BROKER -- \
  head -c 4 /var/lib/cloudtaser/raw/<pvc-name>/somefile   # prints: CTVS

Encrypting pre-existing data

PVCs with existing plaintext data adopt encryption on write: reads pass through until a file is first written via the mount, at which point it is encrypted. To bulk-encrypt existing data instead, run the migration mode of the sidecar binary as a Kubernetes Job against the raw path — see bulk migration and TOFU-window graduation.

Trusted freshness no longer waits on this step

Before the 2026-07-06 default flip (roadmap#197), bootstrapping via this migration was a prerequisite to trusted freshness meaning anything on a populated volume. Now that vault mode is the default, a populated volume with no prior freshness state gets protection immediately via a trust-on-first-use (TOFU) window — see upgrade cohorts. Running this migration is how you graduate out of that window (bulk-encrypt remaining plaintext and flip the volume to full strict enforcement), not how you turn trusted freshness on in the first place.

Next steps