Skip to main content

Encrypt data at rest with LUKS

Inspect 1.36

Many security standards, ISO 27001 among them, require that data at rest is encrypted. On Syself Autopilot you meet that on the hardware you own: you encrypt the disks in your bare metal servers, and only you hold the key that can read them.

Encryption at rest stores the data on a disk as ciphertext. Without the key it is just noise, worthless to anyone who ends up with the physical disk. Hetzner already controls who can enter its data centers and wipes every disk before it is reused, so this is not about a disk walking out the door. It is a control you own and can point to in an audit, and it holds even once a disk leaves your hands.

You do this with LUKS, the standard way Linux encrypts a disk. You do not need to be a storage expert. You encrypt each data disk once, and from then on it unlocks automatically every time the server starts.

Warning

You hold the key, and only you. If you lose it, the data on those disks is gone for good. There is no recovery, and nothing Syself can do to bring it back. Before you rely on this, keep a backup of anything you cannot lose on a separate system, and try the full encrypt-and-unlock cycle once on a test server before you use it in production.

Disk encryption as a built-in Syself Autopilot feature is planned for a future release. Until then you set it up yourself with the steps below, and the Syself Consulting team can design it with you, set it up, and operate it as a full service.

How it works, in short#

There are two steps, and only the second one keeps happening.

  1. You encrypt each disk once. You run a couple of commands to turn a data disk into an encrypted disk. You do this a single time per disk, when the server joins.
  2. A DaemonSet you run unlocks it at startup. Each time a server starts, a small privileged DaemonSet on it finds the encrypted disks and opens them with your key, before your storage comes up. Nobody types a password at a console, and your apps use the disk exactly as before.

The encrypting and the unlocking are simple and the same on every server. The one thing you decide and control is the key.

The key is the part you own#

Everything else is reliable and the same on every server. The one decision that is yours is the key: where it comes from, and how far you trust the path that brings it to a server at boot. The recommendation is to bring your own key and manage it yourself, so control stays with you.

You also choose how many keys to run. A LUKS disk holds its own passphrase, so the granularity is yours: one key for the whole cluster, a key per server, or a key per disk, whatever your policy asks. More keys means a single leaked key unlocks less, at the cost of more keys to look after. To keep things simple, start with one key for the whole cluster and all its disks, and split it finer later if your security policy asks for it.

Important

The servers have no secure chip (a TPM) to hold the key, so the key arrives on the server as plain text at the moment it unlocks the disk. Your encryption is therefore only as safe as the way you store and deliver the key. Keep the key in a store you control, limit who can read it, and change it from time to time. Getting the key path right is the real work here; the disk commands are the easy part.

If you would rather not build the key handling yourself, the Syself Consulting team can design it with you and set the whole thing up.

Encrypt a disk once#

Do this once per data disk, when you first set the server up, in place of the plain disk-preparation step. First open a shell on the server, with a privileged debug pod (kubectl debug node/<node-name> -it --image=busybox --profile=sysadmin -- chroot /host) or the Hetzner rescue system. Then run these. Adjust the disk name and volume group to match the disk type, the same way does.

Lock the disk with a key #

Find the data disk's stable id first. Kernel names like /dev/nvme1n1 can swap between disks when a node is reprovisioned, so address the disk by its hardware id instead:

		$ ls -l /dev/disk/by-id/ | grep eui
	

Each id is a symlink to a kernel name. Check those names against lsblk: the OS disk is the one carrying / and /boot, and it must never be encrypted. Take the id of the other disk.

Turn that disk into an encrypted container, protected by a passphrase you choose:

		$ cryptsetup luksFormat /dev/disk/by-id/nvme-eui.0025388b01b5c3e8
	
Caution

This erases everything on the disk, so make sure it is a data disk and never the OS disk. Save the passphrase somewhere safe that you will still have after the server is gone. Without it, the data cannot be recovered.

Unlock it #

Open the encrypted disk into a new device you can build storage on:

		$ cryptsetup luksOpen /dev/disk/by-id/nvme-eui.0025388b01b5c3e8 encrypted-nvme
	

Build the storage on the unlocked device #

Set up LVM on the unlocked device, /dev/mapper/encrypted-nvme, not the raw disk. This is the point of the whole exercise: build on the unlocked device and everything underneath stays encrypted; build on the raw disk and the data is written in the clear.

		$ pvcreate /dev/mapper/encrypted-nvme
$ vgcreate vg-nvme /dev/mapper/encrypted-nvme
$ lvcreate --thinpool pool-nvme --extents 100%FREE vg-nvme
	

Unlock the disks at startup#

After a restart the disk is locked again, so something on the server has to unlock it before your storage can use it. The way to automate this is a small privileged DaemonSet that you run on your bare metal nodes: at startup it finds the encrypted disks and opens them with your key, before TopoLVM comes up. You build and run this yourself; you deploy it once and point it at the key, and it behaves the same on every server. If you would rather not, the Syself Consulting team can set it up for you.

Cloud volumes are not encrypted for you#

Cloud volumes, the standard class, are not encrypted by default, and Hetzner offers no switch to turn it on. The CSI driver can do it instead, when you give a StorageClass a Secret holding an encryption-passphrase. It then puts LUKS on the volume the first time it mounts it and opens it on every mount after that:

encrypted-storageclass.yamlyaml
		apiVersion: v1
kind: Secret
metadata:
  name: encryption-secret
  namespace: kube-system
type: Opaque
stringData:
  encryption-passphrase: "your-passphrase-here"
---
apiVersion: storage.k8s.io/v1
kind: StorageClass
metadata:
  name: standard-encrypted
provisioner: csi.hetzner.cloud
reclaimPolicy: Retain
volumeBindingMode: WaitForFirstConsumer
allowVolumeExpansion: true
parameters:
  csi.storage.k8s.io/node-publish-secret-name: encryption-secret
  csi.storage.k8s.io/node-publish-secret-namespace: kube-system
	

Claims on standard-encrypted get a LUKS layer you hold the key to; claims on standard do not. There is no need for a DaemonSet and nothing to unlock at boot, because the driver opens the volume as part of mounting it. A cloud server has no secure chip either, so the passphrase still arrives as plain text, and everything above about where the key comes from applies here too.

What it costs#

Scrambling and unscrambling the data uses some CPU on every read and write. Modern processors do this in hardware, so the cost is usually small, but it is not zero. On a disk that is already the busy part of a database, measure with and without encryption on your own hardware so you know the real effect.

Related: for the storage this sits under, for the threat this closes and the key-custody model, and if the extra CPU starts to show.