Skip to content

Add luks encryption support#148

Open
chbmuc wants to merge 5 commits intometal-stack:masterfrom
chbmuc:encryption
Open

Add luks encryption support#148
chbmuc wants to merge 5 commits intometal-stack:masterfrom
chbmuc:encryption

Conversation

@chbmuc
Copy link

@chbmuc chbmuc commented Feb 10, 2026

Description

This PR adds LUKS2 encryption support for volumes (raw block and filesystem).

The test framework has been extended and all tests pass in a local test run.

Closes #29.

@chbmuc chbmuc requested a review from a team as a code owner February 10, 2026 15:50
Copy link
Contributor

@majst01 majst01 left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

first small improvements

Co-authored-by: Stefan Majer <stefan.majer@gmail.com>
Copy link
Contributor

@Gerrit91 Gerrit91 left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Cool. Not an expert on this, but looks pretty good from the code perspective.

Copy link
Contributor

@majst01 majst01 left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We are almost there

Comment on lines +336 to +358
if encryptedPath != "" {
// For encrypted volumes: extend LV without filesystem resize, then resize LUKS
output, err := lvm.ExtendLVS(d.log, d.vgName, volID, uint64(capacity), true) //nolint:gosec
if err != nil {
return nil, fmt.Errorf("unable to extend lv: %w output:%s", err, output)
}

if err := lvm.LuksResize(d.log, mapperName); err != nil {
return nil, fmt.Errorf("unable to resize LUKS device: %w", err)
}

// For block volumes we're done; for filesystem volumes, resize the filesystem on the mapper device
if !isBlock {
resizer := mountutils.NewResizeFs(utilexec.New())
if _, err := resizer.Resize(encryptedPath, volPath); err != nil {
return nil, fmt.Errorf("unable to resize filesystem on encrypted device %s: %w", encryptedPath, err)
}
}
} else {
output, err := lvm.ExtendLVS(d.log, d.vgName, volID, uint64(capacity), isBlock) //nolint:gosec
if err != nil {
return nil, fmt.Errorf("unable to extend lv: %w output:%s", err, output)
}
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I would prefer to have the default "happy" case in the if condition and the encryption logic inside else

Copy link
Contributor

@ostempel ostempel left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Really nice feature. Have some feedback for you

args := []string{
"luksOpen", devicePath, mapperName,
"--disable-keyring", // LUKS2 volumes require passphrase on resize if keyring is not disabled on open
"--key-file", "/dev/stdin",
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

"--cipher", defaultLuksCipher,
"--key-size", defaultLuksKeySize,
"--key-file", os.Stdin.Name(),
"--pbkdf-memory=65535",
Copy link
Contributor

@ostempel ostempel Feb 24, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Should we make this configurable since it affects the security?

}

for _, line := range strings.Split(string(data), "\n") {
if strings.HasPrefix(line, "flags") && strings.Contains(line, " aes") {
Copy link
Contributor

@ostempel ostempel Feb 24, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This will most likely not work for arm architecture:
On my pi: output has the field features and this will silently fail.

Also gets this function called somewhere?

processor	: 0
BogoMIPS	: 108.00
Features	: fp asimd evtstrm aes pmull sha1 sha2 crc32 atomics fphp asimdhp cpuid asimdrdm lrcpc dcpop asimddp
CPU implementer	: 0x41
CPU architecture: 8
CPU variant	: 0x4
CPU part	: 0xd0b
CPU revision	: 1

var (
volID = req.GetVolumeId()
mapperName = lvm.LuksMapperName(volID)
devicePath = ""
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What if we move the unencrypted device path here?

Suggested change
devicePath = ""
devicePath = fmt.Sprintf("/dev/%s/%s", d.vgName, volID)

Then we can remove the defaulting logic in the lvm.go:

req.GetVolumeContext()["csi.storage.k8s.io/ephemeral"] == "" && d.ephemeral // Kubernetes 1.15 doesn't have csi.storage.k8s.io/ephemeral.

// if ephemeral is specified, create volume here
if ephemeralVolume {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

ephemeralVolumes won't go through the NodeStageVolume -> only through NodePublishVolume

So ephemeral volumes won't get encrypted. Do we want to handle this with logs?


// LuksClose closes the LUKS device with the given mapper name.
func LuksClose(log *slog.Logger, mapperName string) error {
mapperPath := diskMapperPath + mapperName
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
mapperPath := diskMapperPath + mapperName
mapperPath := path.Join(diskMapperPath, mapperName)


// LuksResize resizes the LUKS device with the given mapper name.
func LuksResize(log *slog.Logger, mapperName string) error {
mapperPath := diskMapperPath + mapperName
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
mapperPath := diskMapperPath + mapperName
mapperPath := path.Join(diskMapperPath, mapperName)

Comment on lines +59 to +79
linearEncrypted:
enabled: true
additionalAnnotations: []
reclaimPolicy: Delete
encryptionSecret:
name: csi-lvm-encryption-secret
namespace: default
stripedEncrypted:
enabled: true
additionalAnnotations: []
reclaimPolicy: Delete
encryptionSecret:
name: csi-lvm-encryption-secret
namespace: default
mirrorEncrypted:
enabled: true
additionalAnnotations: []
reclaimPolicy: Delete
encryptionSecret:
name: csi-lvm-encryption-secret
namespace: default
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Probably make these storageclasses/feature opt-in instead of default. Users first need to deploy the csi-lvm-encryption-secret.

run kubectl exec -t volume-encrypted-writing-test -- cat /remount/output.log | grep "Happily writing encrypted"
[ "$status" -eq 0 ]
}

Copy link
Contributor

@ostempel ostempel Feb 24, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Tests so far validate, that the driver is working accordingly via the LUKS mapper, but doesn't test if the volumes are encrypted from the outside.

Maybe we want to add here something like this:

  1. Exec into daemonset
  2. Confirm the raw device is LUKS-formatted with cryptsetup isLuks
  3. Confirm the plaintext data is not readable from the raw LV (maybe with strings command, but didn't dive into this)

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

Status: No status

Development

Successfully merging this pull request may close these issues.

Filesystem encryption support

4 participants