From 9274b5a87011e1c05ef4a1790b73b1096bdd1844 Mon Sep 17 00:00:00 2001 From: Steffen Beyer Date: Fri, 27 Mar 2026 15:12:40 +0100 Subject: [PATCH] Add NBDE channel modules --- README.md | 27 ++++- examples/phase0-system.scm | 48 +++++++++ nbde/packages/crypto.scm | 180 +++++++++++++++++++++++++++++++++ nbde/services/tang.scm | 63 ++++++++++++ nbde/system/initrd.scm | 94 +++++++++++++++++ nbde/system/mapped-devices.scm | 94 +++++++++++++++++ 6 files changed, 505 insertions(+), 1 deletion(-) create mode 100644 examples/phase0-system.scm create mode 100644 nbde/packages/crypto.scm create mode 100644 nbde/services/tang.scm create mode 100644 nbde/system/initrd.scm create mode 100644 nbde/system/mapped-devices.scm diff --git a/README.md b/README.md index aaee4fc..9434b7b 100644 --- a/README.md +++ b/README.md @@ -1 +1,26 @@ -# Tribes Guix Channel +## NBDE Channel + +This repository provides the Guix-side pieces for network-bound disk +encryption: + +- `nbde/packages/crypto.scm` + Package definitions for `luksmeta`, `tang`, and `clevis`. +- `nbde/services/tang.scm` + A standalone Tang service for Guix systems. +- `nbde/system/mapped-devices.scm` + A Clevis-backed mapped-device kind with manual `cryptsetup` fallback. +- `nbde/system/initrd.scm` + A helper around `raw-initrd` for early-boot Clevis support. +- `examples/phase0-system.scm` + Minimal reference system using the Clevis-backed mapped-device kind and + custom initrd. + +Current development status: + +1. `luksmeta`, `tang`, and `clevis` build successfully on `pguix`. +2. A disposable Tang + LUKS smoke test passes. +3. A QEMU Phase-0 system with encrypted root now boots unattended through + Clevis/Tang and reaches a login prompt. + +For pinned bootstrap usage, generate a `channels.scm` that combines upstream +Guix with this repository's current commit. diff --git a/examples/phase0-system.scm b/examples/phase0-system.scm new file mode 100644 index 0000000..c2b9e81 --- /dev/null +++ b/examples/phase0-system.scm @@ -0,0 +1,48 @@ +(use-modules (gnu) + (gnu services base) + (gnu services networking) + (gnu services ssh) + (gnu system mapped-devices) + (nbde system initrd) + (nbde system mapped-devices)) + +;; Phase-0 reference system for NBDE development. The device names and +;; interface name are intentionally simple defaults for an x86_64 QEMU guest. +(operating-system + (host-name "nbde-phase0") + (timezone "Etc/UTC") + (locale "en_US.UTF-8") + (keyboard-layout (keyboard-layout "us")) + (kernel-arguments (list "console=ttyS0")) + (initrd + (lambda (file-systems . rest) + (apply clevis-initrd file-systems + #:network (nbde-network-configuration + (interface "eth0") + (timeout 20)) + rest))) + (bootloader + (bootloader-configuration + (bootloader grub-bootloader) + (targets (list "/dev/vda")))) + (mapped-devices + (list + (mapped-device + (source "/dev/vda2") + (target "cryptroot") + (type clevis-luks-device-mapping)))) + (file-systems + (cons (file-system + (mount-point "/") + (device "/dev/mapper/cryptroot") + (type "ext4")) + %base-file-systems)) + (services + (append + (list (service dhcpcd-service-type) + (service agetty-service-type + (agetty-configuration + (tty "ttyS0") + (term "vt100"))) + (service openssh-service-type)) + %base-services))) diff --git a/nbde/packages/crypto.scm b/nbde/packages/crypto.scm new file mode 100644 index 0000000..0052e81 --- /dev/null +++ b/nbde/packages/crypto.scm @@ -0,0 +1,180 @@ +(define-module (nbde packages crypto) + #:use-module ((guix licenses) #:prefix license:) + #:use-module (guix build-system gnu) + #:use-module (guix build-system meson) + #:use-module (guix download) + #:use-module (guix gexp) + #:use-module (guix packages) + #:use-module (guix utils) + #:use-module (gnu packages admin) + #:use-module (gnu packages autotools) + #:use-module (gnu packages bash) + #:use-module (gnu packages base) + #:use-module (gnu packages build-tools) + #:use-module (gnu packages compression) + #:use-module (gnu packages curl) + #:use-module (gnu packages cryptsetup) + #:use-module (gnu packages documentation) + #:use-module (gnu packages hardware) + #:use-module (gnu packages jose) + #:use-module (gnu packages linux) + #:use-module (gnu packages ninja) + #:use-module (gnu packages networking) + #:use-module (gnu packages password-utils) + #:use-module (gnu packages pkg-config) + #:use-module (gnu packages tls) + #:use-module (gnu packages web) + #:export (luksmeta tang clevis)) + +(define-public luksmeta + (package + (name "luksmeta") + (version "10") + (source + (origin + (method url-fetch) + (uri (string-append "https://github.com/latchset/luksmeta/archive/refs/tags/v" + version + ".tar.gz")) + (sha256 + (base32 "18mkb5xl2aln61gnqf9v2245akcmh8cbmmhs3p8v5qysgk1qns3d")))) + (build-system gnu-build-system) + (native-inputs + (list autoconf automake libtool pkg-config asciidoc)) + (inputs + (append + (list cryptsetup + eudev) + (libcryptsetup-propagated-inputs))) + (arguments + (list + #:phases + #~(modify-phases %standard-phases + (add-before 'configure 'bootstrap + (lambda _ + (invoke "autoreconf" "-vfi")))))) + (home-page "https://github.com/latchset/luksmeta/") + (synopsis "Metadata helper for LUKS headers") + (description + "LUKSMeta stores small pieces of metadata in the LUKS header. Clevis +uses it to manage bindings on LUKS1 devices.") + (license license:lgpl2.1+))) + +(define-public tang + (package + (name "tang") + (version "15") + (source + (origin + (method url-fetch) + (uri (string-append "https://github.com/latchset/tang/archive/refs/tags/v" + version + ".tar.gz")) + (sha256 + (base32 "1abx1ajmakhyi8697lwhsbmn0zvhrlnwi79b208wqv73rnkfmfhb")))) + (build-system meson-build-system) + (native-inputs + (list asciidoc curl iproute meson ninja pkg-config)) + (inputs + (list http-parser jansson jose openssl zlib)) + (arguments + (list + #:phases + #~(modify-phases %standard-phases + (add-after 'unpack 'patch-http-parser-detection + (lambda _ + (substitute* "meson.build" + (("inc_dir = meson.get_external_property\\('inc_dir', '-I/usr/local/include'\\)") + "inc_dir = ''") + (("lib_dir = meson.get_external_property\\('lib_dir','/usr/local/lib'\\)") + "lib_dir = ''") + (("if compiler.has_header\\('llhttp.h', args: inc_dir\\)") + "if compiler.has_header('llhttp.h')") + (("if not compiler.has_header\\('http_parser.h', args: inc_dir\\)") + "if not compiler.has_header('http_parser.h')") + (("http_parser = compiler.find_library\\(http_lib, dirs: \\[lib_dir\\]\\)") + "http_parser = compiler.find_library(http_lib)")))) + (add-after 'install 'wrap-tools + (lambda _ + (use-modules (guix build utils)) + (let ((jose-bin (string-append #$jose "/bin")) + (out #$output)) + (wrap-program (string-append out "/bin/tang-show-keys") + `("PATH" ":" prefix (,jose-bin))) + (wrap-program (string-append out "/libexec/tangd-keygen") + `("PATH" ":" prefix (,jose-bin))) + (wrap-program (string-append out "/libexec/tangd-rotate-keys") + `("PATH" ":" prefix (,jose-bin))))))))) + (home-page "https://github.com/latchset/tang") + (synopsis "Server for binding data to network presence") + (description + "Tang is a stateless server for network-bound encryption. It provides +the server side of the Clevis and Tang NBDE workflow and can run in standalone +mode without systemd.") + (license license:gpl3+))) + +(define-public clevis + (package + (name "clevis") + ;; Nixpkgs currently packages v21. Bump once the v22 release hash is + ;; confirmed in the channel workflow. + (version "21") + (source + (origin + (method url-fetch) + (uri (string-append "https://github.com/latchset/clevis/archive/refs/tags/v" + version + ".tar.gz")) + (sha256 + (base32 "04q0xzi4c3b8nhlgdwdm7v0wh33763543az1k2g7jyik7028z8qb")))) + (build-system meson-build-system) + (native-inputs + (list asciidoc meson ninja pkg-config)) + (inputs + (append + (list bash-minimal + coreutils + cryptsetup + curl + eudev + grep + jansson + jose + libpwquality + luksmeta + openssl + sed + tpm2-tools + zlib) + (libcryptsetup-propagated-inputs))) + (arguments + (list + #:phases + #~(modify-phases %standard-phases + (add-after 'unpack 'patch-absolute-cat + (lambda _ + (substitute* (find-files "src" ".*") + (("/bin/cat") (string-append #$coreutils "/bin/cat"))))) + (add-after 'install 'wrap-clevis + (lambda _ + (use-modules (guix build utils)) + (let ((out #$output)) + (wrap-program (string-append out "/bin/clevis") + `("PATH" ":" prefix + (,(string-append out "/bin") + ,(string-append #$coreutils "/bin") + ,(string-append #$cryptsetup "/bin") + ,(string-append #$cryptsetup "/sbin") + ,(string-append #$curl "/bin") + ,(string-append #$grep "/bin") + ,(string-append #$jose "/bin") + ,(string-append #$luksmeta "/bin") + ,(string-append #$sed "/bin") + ,(string-append #$tpm2-tools "/bin")))))))))) + (home-page "https://github.com/latchset/clevis") + (synopsis "Automated decryption framework") + (description + "Clevis is a pluggable framework for automated decryption. It can bind +LUKS devices to Tang, TPM2, and other pins, and provides the client-side +commands needed for unattended unlock.") + (license license:gpl3+))) diff --git a/nbde/services/tang.scm b/nbde/services/tang.scm new file mode 100644 index 0000000..c7e0489 --- /dev/null +++ b/nbde/services/tang.scm @@ -0,0 +1,63 @@ +(define-module (nbde services tang) + #:use-module (gnu services) + #:use-module (gnu services shepherd) + #:use-module (guix gexp) + #:use-module (guix records) + #:use-module (nbde packages crypto) + #:export (tang-configuration + tang-configuration? + tang-configuration-package + tang-configuration-port + tang-configuration-key-directory + tang-service-type)) + +(define-record-type* + tang-configuration make-tang-configuration + tang-configuration? + (package tang-configuration-package + (default tang)) + (port tang-configuration-port + (default 7654)) + (key-directory tang-configuration-key-directory + (default "/var/lib/tang"))) + +(define (tang-activation config) + #~(begin + (use-modules (guix build utils)) + (let ((key-directory #$(tang-configuration-key-directory config)) + (keygen (string-append + #$(tang-configuration-package config) + "/libexec/tangd-keygen"))) + (mkdir-p key-directory) + (when (<= (length (scandir key-directory)) 2) + (invoke keygen key-directory))))) + +(define (tang-shepherd-service config) + (list + (shepherd-service + (documentation "Run Tang in standalone mode.") + (provision '(tang)) + (requirement '(networking)) + (start #~(make-forkexec-constructor + (list (string-append + #$(tang-configuration-package config) + "/libexec/tangd") + "-l" + "-p" #$(number->string + (tang-configuration-port config)) + #$(tang-configuration-key-directory config)))) + (stop #~(make-kill-destructor)) + (respawn? #f)))) + +(define tang-service-type + (service-type + (name 'tang) + (extensions + (list (service-extension activation-service-type tang-activation) + (service-extension shepherd-root-service-type + tang-shepherd-service))) + (default-value (tang-configuration)) + (description + "Run a standalone Tang server and initialize its key directory during +system activation."))) + diff --git a/nbde/system/initrd.scm b/nbde/system/initrd.scm new file mode 100644 index 0000000..f5f4821 --- /dev/null +++ b/nbde/system/initrd.scm @@ -0,0 +1,94 @@ +(define-module (nbde system initrd) + #:use-module (gnu packages admin) + #:use-module (gnu packages bash) + #:use-module (gnu packages base) + #:use-module (gnu packages curl) + #:use-module (gnu packages cryptsetup) + #:use-module (gnu packages jose) + #:use-module (gnu packages linux) + #:use-module (gnu system linux-initrd) + #:use-module (guix gexp) + #:use-module (guix modules) + #:use-module (guix records) + #:use-module (nbde packages crypto) + #:export (nbde-network-configuration + nbde-network-configuration? + nbde-network-configuration-interface + nbde-network-configuration-timeout + clevis-initrd-helper-packages + clevis-initrd-network-pre-mount + clevis-initrd)) + +(define-record-type* + nbde-network-configuration make-nbde-network-configuration + nbde-network-configuration? + (interface nbde-network-configuration-interface + (default "eth0")) + (timeout nbde-network-configuration-timeout + (default 20))) + +(define (clevis-initrd-helper-packages) + (list bash-minimal + coreutils + cryptsetup-static + curl + dhcpcd + e2fsck/static + grep + iproute + jose + sed + clevis)) + +(define (clevis-initrd-network-pre-mount config) + "Return a pre-mount gexp that performs a minimal DHCP-based network bring-up +for initrds that need Tang." + #~(let ((ip-bin #$(file-append iproute "/sbin/ip")) + (dhcpcd-bin #$(file-append dhcpcd "/sbin/dhcpcd")) + (interface #$(nbde-network-configuration-interface config)) + (timeout #$(number->string + (nbde-network-configuration-timeout config)))) + (mkdir-p "/run") + (mkdir-p "/run/dhcpcd") + (mkdir-p "/var") + (mkdir-p "/var/db") + (mkdir-p "/var/db/dhcpcd") + (mkdir-p "/var/run") + (mkdir-p "/var/run/dhcpcd") + (mkdir-p "/etc") + (unless (file-exists? "/etc/resolv.conf") + (call-with-output-file "/etc/resolv.conf" + (lambda (_) #t))) + (invoke ip-bin "link" "set" "dev" interface "up") + (invoke dhcpcd-bin "-w" "-t" timeout interface) + (invoke ip-bin "-4" "addr" "show" "dev" interface) + (invoke ip-bin "-4" "route" "show"))) + +(define* (clevis-initrd file-systems + #:key + linux + (linux-modules '()) + (mapped-devices '()) + keyboard-layout + (helper-packages '()) + network + qemu-networking? + volatile-root? + (on-error 'debug)) + "Build an initrd with the helper packages needed for Clevis/Tang based root +unlock. NETWORK is an optional @code{} record used +to request a minimal DHCP pre-mount hook." + (raw-initrd + file-systems + #:linux linux + #:linux-modules linux-modules + #:mapped-devices mapped-devices + #:keyboard-layout keyboard-layout + #:helper-packages + (append (clevis-initrd-helper-packages) helper-packages) + #:pre-mount + (and network + (clevis-initrd-network-pre-mount network)) + #:qemu-networking? qemu-networking? + #:volatile-root? volatile-root? + #:on-error on-error)) diff --git a/nbde/system/mapped-devices.scm b/nbde/system/mapped-devices.scm new file mode 100644 index 0000000..f1771a3 --- /dev/null +++ b/nbde/system/mapped-devices.scm @@ -0,0 +1,94 @@ +(define-module (nbde system mapped-devices) + #:use-module (guix gexp) + #:use-module (guix modules) + #:use-module (gnu packages bash) + #:use-module (gnu system mapped-devices) + #:use-module (gnu system uuid) + #:autoload (gnu packages cryptsetup) (cryptsetup-static) + #:use-module (ice-9 match) + #:use-module (nbde packages crypto) + #:export (clevis-luks-device-mapping)) + +(define* (open-clevis-luks-device source targets + #:key + (clevis-package clevis) + key-file + allow-discards? + (extra-options '())) + "Return a gexp that first tries to unlock SOURCE using Clevis and falls back +to interactive cryptsetup when that fails. The fallback path intentionally +keeps a manual recovery slot available." + (with-imported-modules (source-module-closure + '((gnu build file-systems) + (guix build utils))) + (match targets + ((target) + #~(let ((source #$(if (uuid? source) + (uuid-bytevector source) + source)) + (keyfile #$key-file)) + (mkdir-p "/run/cryptsetup/") + (let* ((partition + (if (bytevector? source) + (or (let loop ((tries-left 10)) + (and (positive? tries-left) + (or (find-partition-by-luks-uuid source) + (begin + (sleep 1) + (loop (- tries-left 1)))))) + (error "LUKS partition not found" source)) + source)) + (shell-bin #$(file-append bash-minimal "/bin/sh")) + (clevis-bin #$(file-append clevis-package "/bin/clevis")) + (cryptsetup-bin #$(file-append cryptsetup-static + "/sbin/cryptsetup")) + (cryptsetup-flags + (append + (list "open" "--type" "luks") + (if #$allow-discards? + '("--allow-discards") + '()) + '#$extra-options + (list partition #$target)))) + (or (zero? (system* shell-bin "-c" + (string-append + "attempt=0; " + "while :; do " + "attempt=$((attempt + 1)); " + "echo \"nbde: clevis unlock attempt ${attempt} " + partition " -> " #$target "\" >/dev/console; " + "'" clevis-bin "' luks list -d '" partition + "' >/dev/console 2>&1 || true; " + "if '" clevis-bin "' luks unlock" + " -d '" partition "'" + " -n '" #$target "'" + " >/dev/console 2>&1; then " + "exit 0; " + "fi; " + "if [ \"$attempt\" -ge 5 ]; then " + "exit 1; " + "fi; " + "sleep 2; " + "done"))) + (and keyfile + (zero? (apply system*/tty cryptsetup-bin + "--key-file" keyfile + cryptsetup-flags))) + (zero? (apply system*/tty cryptsetup-bin + cryptsetup-flags))))))))) + +(define* (close-clevis-luks-device source targets #:rest _) + (match targets + ((target) + #~(zero? (system* #$(file-append cryptsetup-static "/sbin/cryptsetup") + "close" #$target))))) + +(define clevis-luks-device-mapping + (mapped-device-kind + (open open-clevis-luks-device) + (close close-clevis-luks-device) + (modules '((rnrs bytevectors) + ((gnu build file-systems) + #:select (find-partition-by-luks-uuid system*/tty)) + ((guix build utils) + #:select (mkdir-p))))))