Skip to content

RFE: Per-volume encryption via SPDK crypto vbdev at the lvol layer #2027

Description

@AdityaHarindar

Problem

Mayastor supports pool-level (lvolstore) encryption via SPDK's crypto vbdev. All PVCs on a given DiskPool share one key, which does not serve:

  1. Distinct DEK per PVC for compliance controls that require per-data-owner key identifiers — PCI-DSS §3.5, HIPAA §164.312(a)(2)(iv), FedRAMP equivalents. Audit trails today distinguish pools, not tenants-within-pool.
  2. Cryptographic erase per PVC. Right-to-erasure, dataset expiration, and offboarding workflows require O(1) provable data destruction by destroying one key. Pool-level encryption offers this only at pool granularity.

Threat model in scope: encryption at rest — disk theft, backup exfiltration, decommissioned-media leakage, and the compliance controls above.

Proposed Solution

Add per-volume encryption by stacking the existing SPDK crypto vbdev one level higher: above the lvol, below the NVMe-oF target. Each PVC carries its own DEK, sourced from a K8s Secret referenced by its StorageClass. External key stores (Vault, cloud KMS, HSM) are reached via the standard External Secrets Operator (ESO) bridge — Mayastor's interface is a K8s Secret reference, and the ecosystem handles the rest.

I intend to implement this across openebs/mayastor and openebs/mayastor-control-plane.

openebs/mayastor — data plane

  • Extend ReplicaArgs with an optional encryption key, mirroring PoolArgs::with_encryption.
  • Wrap the lvol via create_crypto_vbdev_on_base_bdev and publish the wrapping bdev over NVMe-oF.
  • Persist a key-name reference (never key material) as an lvol xattr. On pool import, hold encrypted lvols in a keyed-out state until the control plane replays the key via a new SetReplicaKey RPC.
  • Advertise a MayastorFeatures.per_replica_encryption capability bit and gate the code behind ENABLE_REPLICA_ENCRYPTION, mirroring the existing diskpool_encryption pattern.

openebs/mayastor-control-plane — control plane

  • Add a VolumeEncryption { cipher, secret_ref } type to VolumeSpec (shape — additive vs. collapsed — is the open question below). Promote CreateReplica.encrypted from a bookkeeping-only field into a plumbed request property.
  • Add a SecretReader in the core agent that resolves K8s Secret refs to plaintext DEKs held only in process memory.
  • Thread the resolved key through every replica-creating path: primary create, rebuild, move, clone, snapshot restore.
  • Extend the scheduler with an additive node.per_replica_encryption capability filter. The current pool-affinity filter and encryption_preference_soft() knob stay for pool-level users.
  • Add a nexus child-alignment reconciliation pass so mixed encrypted / plaintext children in the same nexus do not fail alignment during rollout.

openebs/mayastor-extensions — CSI

  • Template a per-PVC K8s Secret via StorageClass parameters (csi.storage.k8s.io/provisioner-secret-name and namespace).

The CSI node plugin and consumer-side nvme connect are unaffected. The crypto boundary sits below the target.

Non-goals

  • Not tenant isolation from the Mayastor operator. The io-engine holds the plaintext DEK in memory while serving reads. True operator isolation requires consumer-side encryption or confidential computing — a separate feature.
  • Not in-place key rotation. SPDK's crypto vbdev has no rekey primitive. v1 rotation is create-new + copy + swap.
  • Ciphertext at the lvol layer, which precludes future lvol-layer compression or cross-volume dedup for encrypted volumes.

Rollout

Phased, feature-flagged at every layer. Opt-in for at least one release before default-on. Perf benchmark (AES-NI on/off, replica count sweep) lands with the data-plane PRs. Correctness tests through the existing lvs-eval harness; hardware-dependent code paths gated in CI.

Alternatives

  • Pool-level encryption alone (status quo). Does not provide per-PVC key scope or per-PVC cryptographic erase.
  • LUKS on the consumer (wrapping CSI or init-container, secrets-store-csi-driver pattern). Stronger — the platform never sees plaintext DEKs. Complementary, not competing; addresses an operator-isolation threat model this proposal explicitly does not.
  • NVMe-oF TLS. Orthogonal; solves wire encryption only.
  • Confidential-computing io-engine (SEV-SNP, TDX). Deferred; requires SPDK-side work and hardware availability.

Longhorn and Ceph have shipped similar per-volume approaches.

Additional context

Open question — VolumeSpec shape

Two options for expressing per-volume encryption on VolumeSpec:

  • Additive: keep the existing encrypted: bool and add encryption: Option<VolumeEncryption> alongside. Backward-compatible; no etcd migration; introduces two fields with overlapping semantics.

  • Collapsed: replace both with a single enum:

    EncryptionMode { None | PoolAffinity | PerVolume { cipher, secret_ref } }

    Single source of truth; requires an etcd schema migration and breaks any client that reads the boolean.

Which shape do you prefer to maintain?

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions