Full-disk encryption is the right default. The annoying part is what happens after every reboot: a human has to type a LUKS passphrase before the host can join the fleet again.
That does not scale for homelab servers, bare-metal nodes, or any machine you expect to come back unattended after power loss or a kernel update.
Network-Bound Disk Encryption (NBDE) solves a specific version of that problem: unlock the volume automatically only while the host can reach a trusted network service. The open-source stack is Clevis (client policy framework) plus Tang (stateless binding server).
This is not TPM-only unlock, not a passphrase escrow, and not “store the LUKS key on NFS.” Tang never holds your disk key.
What problem NBDE actually solves
You want all three of these at once:
- Disks stay encrypted at rest
- Reboots do not need a person at the console
- A stolen laptop or yanked drive should not unlock just because someone powers it on elsewhere
NBDE’s answer is network presence. If the Clevis client can complete a cryptographic exchange with your Tang server, the LUKS volume unlocks. If it cannot — different network, Tang offline, host in a stranger’s basement — unlock fails and the passphrase is still required.
That is a policy choice, not magic. It fits servers that only boot on a controlled LAN or management VLAN. It is a poor fit for travel laptops that must unlock offline.
Clevis, Tang, and why this is not key escrow
From the Clevis and Tang projects and the RHEL NBDE docs:
| Piece | Role |
|---|---|
| Tang | Small HTTP service that advertises public keys and helps clients recover a binding key. Stateless. Does not store client secrets. |
| Clevis | Pluggable automated decryption framework. Pins implement policies (tang, tpm2, sss, …). |
| clevis-luks | Binds a LUKS volume to a Clevis policy by adding another unlock key and storing Clevis metadata in the LUKS header. |
| Unlockers | Things that actually call Clevis at the right time: Dracut/initramfs (early boot), systemd askpass path (late boot), manual CLI, desktop udisks2. |
Tang’s design goal is the opposite of classic escrow:
- Escrow stores the secret key on a server → needs auth, TLS, backups of live secrets
- Tang performs an asymmetric exchange → server never sees the client’s final disk key
Provisioning roughly works like this:
- Client fetches Tang’s public advertisement (
GET /adv) - Client derives a strong key using that advertisement, encrypts data, discards the ephemeral bits it must not keep
- Client stores small recovery metadata (for LUKS2, as a token in the header)
- On unlock, client posts a blinded recovery request (
POST /rec/{kid}) and reconstitutes the key only with Tang’s help
You still keep a normal LUKS passphrase. Clevis adds a policy-based unlock path; it does not replace recovery credentials.
Lab topology
Use two machines (or VMs):
- tang.example.net — Tang server on a trusted management network
- node1 — LUKS-encrypted client (root and/or data volume)
Do not put Tang’s key database on the same physical medium you are trying to protect with that Tang instance. If the thief gets both the disk and the Tang keys, network binding stops mattering.
Part 1 — Deploy a Tang server
Fedora / RHEL-family
sudo dnf install tang
sudo systemctl enable --now tangd.socket
The packaged service is socket-activated. First start generates signing and exchange keys under the Tang database directory (commonly /var/db/tang).
Default listen port on many RHEL-family packages is 80. Confirm:
systemctl show tangd.socket -p Listen
sudo tang-show-keys 80
tang-show-keys prints the signing-key thumbprint clients will ask you to trust.
Debian / Ubuntu
sudo apt update
sudo apt install tang curl
sudo systemctl enable --now tangd.socket
systemctl show tangd.socket -p Listen
Check which port the unit actually bound. Then:
# PORT must match ListenStream from the socket unit
sudo tang-show-keys PORT
If tang-show-keys is not on $PATH on your release, fetch the advertisement directly:
curl -fsS http://127.0.0.1:PORT/adv | jose jwk use -i- -r -u verify -o-
Optional: move Tang off port 80
From the Tang man page, network settings are adjusted by overriding the systemd socket — Tang itself is intentionally almost configuration-free:
sudo systemctl edit tangd.socket
[Socket]
ListenStream=
ListenStream=7500
sudo systemctl daemon-reload
sudo systemctl restart tangd.socket
systemctl show tangd.socket -p Listen
# expect Listen=[::]:7500 (Stream) or similar
sudo tang-show-keys 7500
Open that port only on the management network. Tang’s protocol does not require TLS, which is a feature of the crypto design — it is not a reason to expose Tang to the public internet. Keep it on a trusted segment, ideally with host firewall allow-lists.
Sanity-check Tang before touching disks
On any host that can reach Tang and has Clevis installed:
echo test | clevis encrypt tang '{"url":"http://tang.example.net:7500"}' | clevis decrypt
# expect: test
On first encrypt, Clevis shows the advertisement signing thumbprint and asks whether to trust it (trust-on-first-use). Compare that thumbprint with tang-show-keys on the server before answering y.
For automation, either:
# skip interactive trust prompt (only after you already verified the thumbprint out of band)
echo test | clevis encrypt tang '{"url":"http://tang.example.net:7500"}' -y | clevis decrypt
or pin the known thumbprint:
cfg='{"url":"http://tang.example.net:7500","thp":"PASTE_THUMBPRINT_HERE"}'
echo test | clevis encrypt tang "$cfg" | clevis decrypt
or use a pre-fetched advertisement file for offline provisioning:
curl -fsS http://tang.example.net:7500/adv -o adv.jws
echo test | clevis encrypt tang '{"url":"http://tang.example.net:7500","adv":"adv.jws"}' | clevis decrypt
Part 2 — Bind a LUKS volume on the client
Install client packages
Fedora / RHEL-family:
sudo dnf install clevis clevis-luks clevis-dracut clevis-systemd
Debian / Ubuntu:
sudo apt install clevis clevis-luks clevis-systemd
# early-boot root unlock helpers — install the one that matches your initramfs stack:
# dracut systems: clevis-dracut
# initramfs-tools: clevis-initramfs (package name on Debian/Ubuntu)
sudo apt install clevis-dracut || sudo apt install clevis-initramfs
Identify the LUKS device
lsblk -f
sudo cryptsetup luksDump /dev/DISK_PARTITION | sed -n '1,80p'
You need:
- the LUKS partition (for example
/dev/sda2or/dev/nvme0n1p3) - at least one free LUKS keyslot
- the existing passphrase (Clevis will ask for it unless you pass
-k)
Bind with the Tang pin
sudo clevis luks bind -d /dev/DISK_PARTITION tang '{"url":"http://tang.example.net:7500"}'
What clevis luks bind does, per the man page:
- Creates a new key with entropy matching the LUKS master key (max 256 bits)
- Encrypts that key with Clevis under the chosen policy
- Stores the Clevis JWE metadata in the LUKS header (LUKS2 token; LUKS1 uses LUKSMeta)
- Enables the new key in a LUKS keyslot
The existing passphrase still works. You added a second unlock path.
List bindings:
sudo clevis luks list -d /dev/DISK_PARTITION
# example:
# 1: tang '{"url":"http://tang.example.net:7500"}'
Manual unlock test (no reboot yet):
# close only if this is a disposable data volume, not your mounted root
# sudo cryptsetup close NAME
sudo clevis luks unlock -d /dev/DISK_PARTITION
If that fails, fix network/URL/trust before you touch initramfs.
Part 3 — Early boot unlock for the root volume
Binding alone is not enough for unattended root unlock. The initramfs must:
- Bring up networking
- Run Clevis against the LUKS root device
- Continue boot
Dracut path (Fedora/RHEL and some Debian setups)
Clevis unlockers docs are explicit: Dracut does not bring up the network by default. For Tang you need network in the initramfs.
Minimal DHCP approach:
echo 'kernel_cmdline="rd.neednet=1"' | sudo tee /etc/dracut.conf.d/clevis.conf
sudo dracut -f
RHEL docs also note that with --hostonly-cmdline, Dracut can add rd.neednet=1 when it detects a Tang binding. The conf.d snippet above is the boring, explicit version.
Static IP early boot is possible via Dracut network cmdline parameters (ip=, etc.) when DHCP is not available on the management NIC. Prefer a dedicated management interface with DHCP if you can — fewer recovery stories.
Verify Clevis landed in the initramfs:
sudo lsinitrd | grep -E 'clevis|tang' || sudo lsinitrd /boot/initramfs-$(uname -r).img | grep -E 'clevis|tang'
initramfs-tools path (classic Debian/Ubuntu)
After installing the Clevis initramfs integration package for your release:
sudo update-initramfs -u
Confirm the kernel cmdline still has working networking in the initramfs for your NIC. If root unlock hangs waiting for Tang, the usual causes are: no link, wrong interface, no DHCP, or firewall blocking the Tang port from the early-boot network namespace.
Reboot test for root
Only after:
-
clevis encrypt … | clevis decryptworks -
clevis luks listshows the tang binding - initramfs was rebuilt with network + Clevis
sudo reboot
Expected result: root unlocks without a passphrase prompt when Tang is reachable.
Negative test (important): shut down Tang or move the client off the trusted network and reboot once in a maintenance window. You should fall back to the LUKS passphrase. If the host unlocks with Tang down, stop and re-check what you actually bound.
Part 4 — Late boot unlock for non-root volumes
Data disks listed in /etc/crypttab should use the systemd unlocker, not the early-boot root path.
Enable the path unit:
sudo systemctl enable --now clevis-luks-askpass.path
From the unlockers documentation, after reboot Clevis attempts to unlock crypttab devices that have Clevis bindings when systemd would otherwise prompt for a password.
For network-backed unlock of non-root volumes, mark the crypttab/fstab entries so systemd waits for the network. RHEL’s NBDE flow adds _netdev (and enables remote-cryptsetup.target / Clevis path units). A typical crypttab shape:
data UUID=YOUR-LUKS-UUID none luks,_netdev
And the filesystem line:
UUID=YOUR-FS-UUID /srv/data xfs defaults,_netdev 0 0
Then:
sudo systemctl daemon-reload
sudo reboot
High availability without sharing Tang private keys
Tang’s own docs recommend client redundancy over cloning Tang key material across servers.
Clevis supports that with the sss pin (Shamir’s Secret Sharing). Example policy: unlock if any one of two Tang servers is reachable (t=1):
cfg='{"t":1,"pins":{"tang":[{"url":"http://tang1.example.net:7500"},{"url":"http://tang2.example.net:7500"}]}}'
sudo clevis luks bind -d /dev/DISK_PARTITION sss "$cfg"
Threshold semantics:
-
t=1with two Tang pins → either server is enough (availability) -
t=2with two Tang pins → both must be reachable (stricter)
You can also mix pins (Tang + TPM2) under SSS when your threat model wants network presence and local platform state. Keep a passphrase slot regardless — PCR-bound TPM policies are famous for locking people out after firmware updates.
Key rotation (do this on purpose)
Tang man page and RHEL docs agree: rotate periodically; interval depends on policy and key sizes.
Manual flow on the Tang server:
DB=/var/db/tang # confirm path on your distro if different
cd "$DB"
ls -l
# 1) generate new signing + exchange keys
sudo jose jwk gen -i '{"alg":"ES512"}' -o "$DB/new_sig.jwk"
sudo jose jwk gen -i '{"alg":"ECMR"}' -o "$DB/new_exc.jwk"
# 2) hide old advertised keys (leading dot = not advertised)
sudo mv old_sig.jwk .old_sig.jwk
sudo mv old_exc.jwk .old_exc.jwk
# Tang picks this up live — no restart required per tang(8)
sudo tang-show-keys 7500
On each client, detect and regenerate metadata:
sudo clevis luks list -d /dev/DISK_PARTITION
sudo clevis luks report -d /dev/DISK_PARTITION -s SLOT
# if rotation detected:
sudo clevis luks regen -d /dev/DISK_PARTITION -s SLOT
Only after every client has regenerated should you delete dotted old keys on the server. Delete too early and clients that still depend on those keys cannot unlock — that is real data-loss territory for automated unlock (passphrase recovery still works if you kept it).
Some releases ship tangd-rotate-keys / tangd-keygen helpers; the jose + rename procedure above is the portable core.
Operational checklist
- Keep a LUKS passphrase in a password manager or sealed break-glass process. NBDE is convenience + policy, not your only key.
- Firewall Tang to trusted subnets / client inventories only.
- Monitor Tang availability. If Tang dies and hosts reboot, you get passphrase prompts or failed boots at scale.
- Test both paths: Tang up (auto unlock) and Tang down (passphrase fallback).
- Document the URL and port in the same place you document BMC and switch configs.
- Do not clone golden LUKS images across VMs after binding. Clevis docs warn that LUKS master keys are shared if you duplicate images — that is a LUKS design property, not a Clevis bug.
- Separate Tang storage from protected disks. Containerized Tang is fine; putting its JWKs on the same disk you bind is not.
- Prefer SSS multi-Tang over copying private keys between Tang nodes.
Security model, honestly
NBDE protects against a common threat: offline disk theft when the thief does not also get network access to your Tang infrastructure.
It does not:
- stop an attacker who already has root on a booted host
- replace Secure Boot, disk passphrases, or backup encryption
- hide the fact that a host unlocked (you still want audit logs)
- help if Tang is reachable from the internet and your network trust assumption is wrong
Think of Tang as a network-presence factor for disk unlock, similar in spirit to “this machine must be on the corp VLAN,” implemented with real cryptography instead of a bash script that curls a key file.
Minimal end-to-end recipe
Server:
sudo dnf install -y tang # or: sudo apt install tang
sudo systemctl enable --now tangd.socket
sudo tang-show-keys 80 # or your ListenStream port
Client:
sudo dnf install -y clevis clevis-luks clevis-dracut clevis-systemd
# Debian/Ubuntu: sudo apt install clevis clevis-luks clevis-systemd clevis-initramfs
echo test | clevis encrypt tang '{"url":"http://tang.example.net"}' | clevis decrypt
sudo clevis luks bind -d /dev/DISK_PARTITION tang '{"url":"http://tang.example.net"}'
sudo clevis luks list -d /dev/DISK_PARTITION
echo 'kernel_cmdline="rd.neednet=1"' | sudo tee /etc/dracut.conf.d/clevis.conf
sudo dracut -f # or: sudo update-initramfs -u
sudo systemctl enable clevis-luks-askpass.path
sudo reboot
References
- Clevis project: https://github.com/latchset/clevis
- Tang project: https://github.com/latchset/tang
- Debian man pages: clevis(1), clevis-encrypt-tang(1), clevis-luks-bind(1), clevis-luks-unlockers(7), clevis-luks-list(1), tang(8), tang-show-keys(1)
- Red Hat Enterprise Linux 9 docs: Configuring automated unlocking of encrypted volumes using policy-based decryption (NBDE)
Encrypt the disks. Keep a real recovery passphrase. Put Tang on a network you actually trust. Bind with Clevis, rebuild initramfs with networking, and prove both the happy path and the Tang-down fallback before you roll it past the lab. Future reboots should ask the network, not you.
Top comments (0)