From c97f619de2c0d1ed5fd7078d40c3fa9cefeabc31 Mon Sep 17 00:00:00 2001 From: Alejandro Bonilla Date: Tue, 18 Aug 2026 20:47:54 -0500 Subject: [PATCH 1/2] docs(lvm): document encryption at rest for LVM volumes Add an "Encrypting an LVM Volume" section to the LVM local storage add-on page: how to create the CRYPTO_KEY_* encryption secret, how to create an encrypted dm-thin StorageClass from the UI (Volume Encryption toggle + secret) and via YAML, the cloneStrategy=copy requirement for image clones, and a note that images/VMs/snapshots/backups inherit encryption via the StorageClass. Signed-off-by: Alejandro Bonilla --- docs/advanced/addons/lvm-local-storage.md | 87 +++++++++++++++++++++++ 1 file changed, 87 insertions(+) diff --git a/docs/advanced/addons/lvm-local-storage.md b/docs/advanced/addons/lvm-local-storage.md index 4f4c4b786a..2a131ec739 100644 --- a/docs/advanced/addons/lvm-local-storage.md +++ b/docs/advanced/addons/lvm-local-storage.md @@ -106,6 +106,93 @@ You can only use one type of local volume in each volume group. If necessary, cr For more information, see [StorageClass](../storageclass.md). +## Encrypting an LVM Volume + +`dm-thin` LVM volumes can be encrypted at rest using LUKS2 (dm-crypt). Encryption is configured on the StorageClass, and it uses the same encryption secret convention as [Longhorn volume encryption](../../rancher/csi-driver/longhorn.md), so any volume, VM image, or virtual machine that uses an encrypted StorageClass is encrypted automatically. + +:::note + +Encryption is an opt-in property of the StorageClass. Existing (unencrypted) StorageClasses are unaffected, and you cannot convert a volume between the encrypted and unencrypted states. + +::: + +### Creating an Encryption Secret + +Create a `Secret` that holds the encryption passphrase in the `CRYPTO_KEY_VALUE` field. The remaining `CRYPTO_KEY_*` fields are optional and default to `aes-xts-plain64` / `sha256` / `256` / `argon2i` when omitted. + +```yaml +apiVersion: v1 +kind: Secret +metadata: + name: lvm-encryption + namespace: default +type: Opaque +stringData: + CRYPTO_KEY_VALUE: "Your encryption passphrase" + CRYPTO_KEY_PROVIDER: "secret" + CRYPTO_KEY_CIPHER: "aes-xts-plain64" + CRYPTO_KEY_HASH: "sha256" + CRYPTO_KEY_SIZE: "256" + CRYPTO_PBKDF: "argon2i" +``` + +:::caution + +Store the passphrase securely and back it up. If the secret is lost, the encrypted data is **unrecoverable**. + +::: + +### Creating an Encrypted StorageClass + +1. On the Harvester UI, go to the **Storage** screen. + +1. Create a new StorageClass, select **LVM** in the **Provisioner** list, and configure the **Node**, **Volume Group Name**, and **Volume Group Type** (`dm-thin`) as described in [Creating a StorageClass for LVM](#creating-a-storageclass-for-lvm). + +1. On the **Parameters** tab, set **Volume Encryption** to **Yes**, and then select the encryption secret that you created. + +1. Click **Save**. + +You can also create the encrypted StorageClass directly. The `encrypted: "true"` parameter and the `csi.storage.k8s.io/*-secret-*` parameters that reference the encryption secret are required. + +```yaml +apiVersion: storage.k8s.io/v1 +kind: StorageClass +metadata: + name: lvm-dm-thin-encrypted + annotations: + # Use host-assisted copy so image clones are written through the encryption + # layer. See the note below. + cdi.harvesterhci.io/storageProfileCloneStrategy: copy +parameters: + type: dm-thin + vgName: + encrypted: "true" + csi.storage.k8s.io/provisioner-secret-name: lvm-encryption + csi.storage.k8s.io/provisioner-secret-namespace: default + csi.storage.k8s.io/node-publish-secret-name: lvm-encryption + csi.storage.k8s.io/node-publish-secret-namespace: default + csi.storage.k8s.io/node-stage-secret-name: lvm-encryption + csi.storage.k8s.io/node-stage-secret-namespace: default +provisioner: lvm.driver.harvesterhci.io +reclaimPolicy: Delete +volumeBindingMode: WaitForFirstConsumer +allowedTopologies: + - matchLabelExpressions: + - key: topology.lvm.csi/node + values: + - +``` + +To use a different passphrase per volume, replace the fixed secret name and namespace with the `${pvc.name}` and `${pvc.namespace}` templates. + +:::note + +When you create a VM image or clone an existing image into an encrypted StorageClass, set the clone strategy to **copy** (as shown in the annotation above, or on the **CDI Settings** tab of the StorageClass form). A host-assisted copy writes the data through the encryption layer; a block-level snapshot clone would bypass it. + +::: + +Any VM image, volume, or virtual machine that uses this StorageClass is encrypted at rest. VM snapshots, backups, and restores also preserve encryption because they reuse the encrypted StorageClass. + ## Creating a Volume with LVM 1. On the Harvester UI, go to the **Volumes** screen. From aab00c2d273558eed18f3c64dcf21a097fdac365 Mon Sep 17 00:00:00 2001 From: Alejandro Bonilla Date: Wed, 2 Sep 2026 18:51:43 -0400 Subject: [PATCH 2/2] docs(lvm): correct encrypted StorageClass guidance Harvester resolves the encryption secret reference literally when the StorageClass is admitted, so ${pvc.name}/${pvc.namespace} templating is rejected and the secret must already exist; it also requires every CRYPTO_KEY_* field to be present. The node-expand secret reference was missing from the example, without which expanding an encrypted volume fails. Also document that snapshots and clones cannot change a volume's encryption state and that a restored volume needs its source's passphrase. Co-Authored-By: Claude Signed-off-by: Alejandro Bonilla --- docs/advanced/addons/lvm-local-storage.md | 23 +++++++++++++++++++++-- 1 file changed, 21 insertions(+), 2 deletions(-) diff --git a/docs/advanced/addons/lvm-local-storage.md b/docs/advanced/addons/lvm-local-storage.md index 2a131ec739..2ff65ef556 100644 --- a/docs/advanced/addons/lvm-local-storage.md +++ b/docs/advanced/addons/lvm-local-storage.md @@ -118,7 +118,9 @@ Encryption is an opt-in property of the StorageClass. Existing (unencrypted) Sto ### Creating an Encryption Secret -Create a `Secret` that holds the encryption passphrase in the `CRYPTO_KEY_VALUE` field. The remaining `CRYPTO_KEY_*` fields are optional and default to `aes-xts-plain64` / `sha256` / `256` / `argon2i` when omitted. +Create a `Secret` that holds the encryption passphrase in the `CRYPTO_KEY_VALUE` field. Include all of the fields shown below: the CSI driver applies the defaults `aes-xts-plain64` / `sha256` / `256` / `argon2i` when the tuning fields are omitted, but Harvester rejects a StorageClass whose encryption secret has a missing or empty field. + +The secret must exist before you create the StorageClass that references it. ```yaml apiVersion: v1 @@ -173,6 +175,8 @@ parameters: csi.storage.k8s.io/node-publish-secret-namespace: default csi.storage.k8s.io/node-stage-secret-name: lvm-encryption csi.storage.k8s.io/node-stage-secret-namespace: default + csi.storage.k8s.io/node-expand-secret-name: lvm-encryption + csi.storage.k8s.io/node-expand-secret-namespace: default provisioner: lvm.driver.harvesterhci.io reclaimPolicy: Delete volumeBindingMode: WaitForFirstConsumer @@ -183,7 +187,22 @@ allowedTopologies: - ``` -To use a different passphrase per volume, replace the fixed secret name and namespace with the `${pvc.name}` and `${pvc.namespace}` templates. +All four secret references are required. The driver uses the passphrase when it creates the volume, when it opens the encrypted device on the node, and when it expands the device; Harvester requires the `node-stage` reference as well. + +:::note + +Harvester resolves the secret reference when the StorageClass is created, so the `${pvc.name}` and `${pvc.namespace}` templates that upstream Kubernetes supports cannot be used here. To give different volumes different passphrases, create one encrypted StorageClass per encryption secret. + +::: + +### Snapshots, Clones, and Restores of Encrypted Volumes + +A volume snapshot or clone is a block-level copy, so the copy carries the same encryption state as its source and you cannot change that state during a restore: + +- Restoring an encrypted snapshot into an unencrypted StorageClass, or an unencrypted snapshot into an encrypted StorageClass, fails when the volume is provisioned. +- A volume restored from an encrypted snapshot can only be opened with the **source volume's** passphrase. If you restore through a StorageClass that references a different encryption secret, the volume is provisioned but fails to attach. + +To place unencrypted data (such as an existing VM image) into an encrypted StorageClass, use the **copy** clone strategy described below, which writes the data through the encryption layer instead of copying blocks. :::note