Volume Shield Configuration Reference¶
All Volume Shield behavior is controlled by PVC annotations (per-volume) and Helm values (cluster-wide). Component version strings below refer to wrapper v0.2.168 / chart v1.0.264.
PVC annotations¶
| Annotation | Values | Default | Description |
|---|---|---|---|
cloudtaser.io/encrypt-pvc |
"true" |
— | Enables Volume Shield for this PVC. Read at pod admission time. |
cloudtaser.io/pvc-chunk-size-kb |
integer KiB | 64 |
Encryption chunk size. Larger chunks reduce per-chunk overhead but increase the minimum read-modify-write granularity. Choose once, before first write. |
cloudtaser.io/vs-io-mode |
cached | strict |
cached |
Per-PVC I/O mode, see below. Invalid values fail closed to cached. |
cloudtaser.io/vs-freshness |
local | vault | secretstore |
inherit cluster default (vault, since the 2026-07-06 default flip — roadmap#197) |
Per-PVC trusted-freshness selection. vault is the default at both cluster and PVC level — see upgrade cohorts and the TOFU window for what that means for pre-existing volumes. local opts the volume OUT of rollback/replay detection — required for workloads that need atomic directory renames (ClickHouse part commits, Prometheus TSDB compaction, VictoriaMetrics) or hardlink-based snapshots (e.g. Cassandra nodetool snapshot); see opting out. Invalid values fail closed to vault (the most secure mode), never to the opt-out. |
cloudtaser.io/pvc-transit-key |
key name | — | Reserved for Vault Transit envelope encryption (not yet active). |
I/O modes: cached vs strict¶
cached (default) |
strict |
|
|---|---|---|
| Kernel page cache | Active (write-through) | Bypassed (FOPEN_DIRECT_IO per file) |
mmap(MAP_SHARED) |
Works | Fails with ENODEV |
| Plaintext residency | Decrypted pages may reside in the kernel page cache while the file is in use | No plaintext page-cache residency beyond the active I/O |
| Suitable for | SQLite/WAL, Postgres, Cassandra, LMDB — anything that mmaps or relies on page-cache performance | Workloads that never mmap, under regulatory policy requiring zero page-cache plaintext |
Do not set strict for mmap-dependent workloads
strict intentionally breaks mmap(MAP_SHARED). Databases that memory-map their files (most of them) will fail to start. The default is cached for this reason; measure before switching.
The annotation is per-PVC, so a pod can mix modes — e.g. strict for a compliance-scoped volume and cached for a WAL volume in the same pod.
Helm values¶
volumeShield:
enabled: false # master switch
nonceBudget:
mode: "" # "" (process-local) | "vault" | "secretstore"
leaseSize: 1048576 # GCM seal invocations reserved per lease
leaseTimeout: "10s"
freshness:
mode: "vault" # "vault" (default) | "local" (cluster-wide opt-out) | "secretstore"
timeout: "10s"
broker:
mode: "fdpass" # "fdpass" (default) | "serve" (legacy)
daemonset:
enabled: false # per-node DaemonSet (recommended)
image:
repository: europe-west4-docker.pkg.dev/skipopsmain/cloudtaser/cloudtaser-wrapper
tag: "" # REQUIRED — wrapper release tag
healthPort: 8081 # HTTPS kubelet probes
vaultAddr: "" # REQUIRED with daemonset.enabled=true
vaultRole: "cloudtaser-vs-broker"
vaultCACert: ""
audienceScopedToken:
enabled: false
audience: "cloudtaser-vs-broker"
expirationSeconds: 3600
sidecar:
image:
repository: europe-west4-docker.pkg.dev/skipopsmain/cloudtaser/cloudtaser-wrapper
tag: "" # REQUIRED — wrapper release tag
healthPort: 8082 # must differ from broker.healthPort
Nonce budget (volumeShield.nonceBudget)¶
AES-GCM with random nonces is bounded by NIST SP 800-38D to 2³² seal operations per key. Volume Shield always enforces this bound in-process and fails closed at exhaustion (warns at 2³¹).
mode: ""(default) — the counter is process-local. A remount resets it while the DEK is unchanged, so the cumulative cross-session count is not bounded.mode: "vault"— the sidecar reserves high-water leases in OpenBao KV v2 (cloudtaser/data/volumeshield-state/<namespace>/<pvc-uid>). Remounts continue from the trusted high-water mark; crashes can waste leased invocations but never reuse them. A DEK change starts a fresh epoch.
leaseSize trades vault round-trips against waste on crash: each lease is one vault CAS write per leaseSize encrypted chunks.
No automatic DEK rotation yet
Trusted leasing accounts for nonce use; it does not rotate the DEK before exhaustion. Rotation currently requires re-encrypting the volume under a new DEK.
Trusted freshness (volumeShield.freshness)¶
Default flipped 2026-07-06 (roadmap#197)
vault mode is now the default, cluster-wide and per-PVC. Previously the default was disabled (chunk-level integrity only, no rollback/replay detection). The safe rollout path for that flip — volume-scoped baseline gating — is what the upgrade cohorts below describe.
mode: "vault"(default) — every decrypt verifies the exact ciphertext bytes against per-file digests in OpenBao (.../files/<sha256(relpath)>); every successful mutation commits a new digest; unlink/rename tombstone old paths. Missing state, tombstones, digest mismatch, DEK-epoch mismatch, and unauthenticated plaintext all fail closed — subject to the TOFU window described below for volumes upgrading into the default.mode: "local"(cluster-wide opt-out;""/unset also resolves to thevaultdefault, not tolocal— the opt-out must be explicit) — chunk-level integrity only (CTVS v3 AAD). Rollback/replay of correctly-encrypted old bytes, and plaintext injection via CTVS header stripping, are not detected. See opting out below.
Upgrade cohorts and the TOFU window¶
The moment freshness.mode resolves to vault for a volume — by default, or by explicit configuration — that volume falls into exactly one of three cohorts:
| Cohort | Condition | Behavior |
|---|---|---|
| Already tracked | Volume already has freshness state in vault (was already running freshness.mode: vault) |
Strict immediately. Fail-closed on missing state, unchanged from before the default flip — zero behavior change for existing vault-mode users. |
| New | Volume is empty at first mount | Strict from birth. Every file gets trusted state the moment it's written; nothing is ever "old" without state. |
| Upgrade cohort | Volume is populated but has no freshness state (e.g. running Volume Shield in local mode, or newly onboarded to the default) |
Trust-on-first-use (TOFU) window. Files bearing a valid CTVS header are baselined into vault on first verify, each baseline logged at WARN and counted by cloudtaser_vs_freshness_baselined_files_total{pvc_uid,namespace}. The window closes only when an operator runs the graduation migration. |
TOFU window security meaning — precise boundary
During the TOFU window, and only for the upgrade cohort:
- Not yet protected: replay of authentic, same-volume ciphertext at a path that has never been baselined — an attacker holding legitimately-encrypted bytes from elsewhere on the same volume can plant them at an unbaselined path and have them accepted as that path's first version.
- Unchanged, still protected: plaintext or header-stripped injection stays blocked — the not-encrypted/bad-header check runs before any freshness state lookup, so it is never gated by baseline status. Tombstoned (deleted) paths stay tombstoned. Any path already baselined — including everything baselined earlier in the same window — gets full rollback/replay detection from that point forward.
This is strictly better than local mode (which detects none of this, indefinitely), and it is why the default-on rollout is TOFU-then-graduate rather than either an indefinite reduced-security default or a hard break on upgrade.
Run the migration entrypoint (see graduating from the TOFU window) to close the window on a volume: it walks every file, baselines anything not yet tracked, and flips the volume to strict once the walk completes with zero errors.
Opting out (local mode)¶
local exists for workloads whose I/O pattern is fundamentally incompatible with per-path freshness state — not as a general performance or convenience knob:
- Directory-rename-as-commit workloads — ClickHouse part commits, Prometheus TSDB compaction, VictoriaMetrics — hit
EXDEVon subtree renames undervaultmode. - Hardlink-based snapshot workloads — e.g. Cassandra
nodetool snapshot— hitEOPNOTSUPPon hard-link creation undervaultmode.
See the troubleshooting symptom table for both. Set cloudtaser.io/vs-freshness: "local" on the PVC, or volumeShield.freshness.mode: "local" cluster-wide. Invalid annotation values fail closed to vault, never to the opt-out. Opting out does not exist on a spectrum — a local-mode volume has no rollback/replay or plaintext-injection detection, permanently, not a transitional gap like the TOFU window above.
Operational notes for vault mode:
- Directory renames return
EXDEV(moving a subtree would need a multi-file trusted-state transaction).EXDEVmakesmv, Pythonshutil.move, Java NIOFiles.move(withoutATOMIC_MOVE), and Kafka's move-with-fallback degrade to copy+delete automatically, with every copied file tracked by freshness. Workloads that REQUIRE atomic directory renames or hard links should use thelocalopt-out instead (see above). File renames work natively. - Each verified read/committed write adds a vault round-trip — budget for the latency on hot paths, or scope volumes that don't need anti-rollback to the
localopt-out.
Broker mode (volumeShield.broker.mode)¶
fdpass(default) — privilege-separated: broker mounts, unprivileged sidecar serves and holds the DEK. Use this.serve— legacy: the broker both mounts and serves FUSE, holding DEKs in the per-node privileged process. Retained for backward compatibility only.
Environment variables (advanced)¶
These are set by the operator/chart; listed for debugging and non-Helm installs.
| Variable | Component | Purpose |
|---|---|---|
CLOUDTASER_VOLUME_SHIELD_ENABLED |
wrapper | Master gate, set to true by webhook |
CLOUDTASER_VOLUME_SHIELD_MOUNTS |
wrapper, sidecar | JSON mount descriptor list (all encrypted PVCs of the pod, including per-mount io_mode) |
CLOUDTASER_VS_BROKER_SOCK |
wrapper | Broker Unix socket (hostPath) |
CLOUDTASER_VS_BROKER_MODE |
wrapper | fdpass when a sidecar is injected |
CLOUDTASER_VS_SIDECAR_SOCK |
sidecar, broker | fd-pass Unix socket |
CLOUDTASER_VS_STRICT_MODE |
sidecar | Process-wide strict-mode fallback; per-mount io_mode takes precedence |
CLOUDTASER_VS_NONCE_BUDGET / CLOUDTASER_VS_NONCE_LEASE_SIZE / CLOUDTASER_VS_NONCE_LEASE_TIMEOUT |
sidecar | Trusted nonce leasing |
CLOUDTASER_VS_FRESHNESS / CLOUDTASER_VS_FRESHNESS_TIMEOUT |
sidecar | Trusted freshness |
Choosing a production posture¶
| Posture | Settings | Protects against |
|---|---|---|
| Baseline | volumeShield.enabled=true (freshness defaults to vault) |
CSP reading disks/snapshots/backups; in-flight tamper (swap/reorder/bitflip/truncate); rollback/replay and header-strip plaintext injection — immediately for new/already-tracked volumes, after the TOFU window + graduation migration for pre-existing populated volumes (see upgrade cohorts) |
| Recommended | + nonceBudget.mode: vault |
+ cross-remount nonce exhaustion accounting |
| Maximum | + vs-io-mode: strict on compliance-scoped PVCs |
+ plaintext page-cache residency (at the cost of mmap) |