diff options
author | Camille Mondon <camillemondon@free.fr> | 2023-11-18 19:37:56 +0000 |
---|---|---|
committer | Julien Malka <julien@malka.sh> | 2023-12-02 11:55:47 +0000 |
commit | 27493b4d49b6d92f4baa049424cbb2fa48b4c948 (patch) | |
tree | 2b6f983b61f29ec542efbf8ae666a2f4f9081e74 /nixos/modules/system | |
parent | bea9ec6d4ae820ee21bb1f8c37fd073678423180 (diff) | |
download | nixlib-27493b4d49b6d92f4baa049424cbb2fa48b4c948.tar nixlib-27493b4d49b6d92f4baa049424cbb2fa48b4c948.tar.gz nixlib-27493b4d49b6d92f4baa049424cbb2fa48b4c948.tar.bz2 nixlib-27493b4d49b6d92f4baa049424cbb2fa48b4c948.tar.lz nixlib-27493b4d49b6d92f4baa049424cbb2fa48b4c948.tar.xz nixlib-27493b4d49b6d92f4baa049424cbb2fa48b4c948.tar.zst nixlib-27493b4d49b6d92f4baa049424cbb2fa48b4c948.zip |
nixos/clevis: init
Co-Authored-By: Julien Malka <julien@malka.sh>
Diffstat (limited to 'nixos/modules/system')
-rw-r--r-- | nixos/modules/system/boot/clevis.md | 51 | ||||
-rw-r--r-- | nixos/modules/system/boot/clevis.nix | 107 | ||||
-rw-r--r-- | nixos/modules/system/boot/luksroot.nix | 48 |
3 files changed, 204 insertions, 2 deletions
diff --git a/nixos/modules/system/boot/clevis.md b/nixos/modules/system/boot/clevis.md new file mode 100644 index 000000000000..91eb728a919e --- /dev/null +++ b/nixos/modules/system/boot/clevis.md @@ -0,0 +1,51 @@ +# Clevis {#module-boot-clevis} + +[Clevis](https://github.com/latchset/clevis) +is a framework for automated decryption of resources. +Clevis allows for secure unattended disk decryption during boot, using decryption policies that must be satisfied for the data to decrypt. + + +## Create a JWE file containing your secret {#module-boot-clevis-create-secret} + +The first step is to embed your secret in a [JWE](https://en.wikipedia.org/wiki/JSON_Web_Encryption) file. +JWE files have to be created through the clevis command line. 3 types of policies are supported: + +1) TPM policies + +Secrets are pinned against the presence of a TPM2 device, for example: +``` +echo hi | clevis encrypt tpm2 '{}' > hi.jwe +``` +2) Tang policies + +Secrets are pinned against the presence of a Tang server, for example: +``` +echo hi | clevis encrypt tang '{"url": "http://tang.local"}' > hi.jwe +``` + +3) Shamir Secret Sharing + +Using Shamir's Secret Sharing ([sss](https://en.wikipedia.org/wiki/Shamir%27s_secret_sharing)), secrets are pinned using a combination of the two preceding policies. For example: +``` +echo hi | clevis encrypt sss \ +'{"t": 2, "pins": {"tpm2": {"pcr_ids": "0"}, "tang": {"url": "http://tang.local"}}}' \ +> hi.jwe +``` + +For more complete documentation on how to generate a secret with clevis, see the [clevis documentation](https://github.com/latchset/clevis). + + +## Activate unattended decryption of a resource at boot {#module-boot-clevis-activate} + +In order to activate unattended decryption of a resource at boot, enable the `clevis` module: + +``` +boot.initrd.clevis.enable = true; +``` + +Then, specify the device you want to decrypt using a given clevis secret. Clevis will automatically try to decrypt the device at boot and will fallback to interactive unlocking if the decryption policy is not fulfilled. +``` +boot.initrd.clevis.devices."/dev/nvme0n1p1".secretFile = ./nvme0n1p1.jwe; +``` + +Only `bcachefs`, `zfs` and `luks` encrypted devices are supported at this time. diff --git a/nixos/modules/system/boot/clevis.nix b/nixos/modules/system/boot/clevis.nix new file mode 100644 index 000000000000..0c72590f9385 --- /dev/null +++ b/nixos/modules/system/boot/clevis.nix @@ -0,0 +1,107 @@ +{ config, lib, pkgs, ... }: + +with lib; + +let + cfg = config.boot.initrd.clevis; + systemd = config.boot.initrd.systemd; + supportedFs = [ "zfs" "bcachefs" ]; +in +{ + meta.maintainers = with maintainers; [ julienmalka camillemndn ]; + meta.doc = ./clevis.md; + + options = { + boot.initrd.clevis.enable = mkEnableOption (lib.mdDoc "Clevis in initrd"); + + + boot.initrd.clevis.package = mkOption { + type = types.package; + default = pkgs.clevis; + defaultText = "pkgs.clevis"; + description = lib.mdDoc "Clevis package"; + }; + + boot.initrd.clevis.devices = mkOption { + description = "Encrypted devices that need to be unlocked at boot using Clevis"; + default = { }; + type = types.attrsOf (types.submodule ({ + options.secretFile = mkOption { + description = lib.mdDoc "Clevis JWE file used to decrypt the device at boot, in concert with the chosen pin (one of TPM2, Tang server, or SSS)."; + type = types.path; + }; + })); + }; + + boot.initrd.clevis.useTang = mkOption { + description = "Whether the Clevis JWE file used to decrypt the devices uses a Tang server as a pin."; + default = false; + type = types.bool; + }; + + }; + + config = mkIf cfg.enable { + + # Implementation of clevis unlocking for the supported filesystems are located directly in the respective modules. + + + assertions = (attrValues (mapAttrs + (device: _: { + assertion = (any (fs: fs.device == device && (elem fs.fsType supportedFs)) config.system.build.fileSystems) || (hasAttr device config.boot.initrd.luks.devices); + message = '' + No filesystem or LUKS device with the name ${device} is declared in your configuration.''; + }) + cfg.devices)); + + + warnings = + if cfg.useTang && !config.boot.initrd.network.enable && !config.boot.initrd.systemd.network.enable + then [ "In order to use a Tang pinned secret you must configure networking in initrd" ] + else [ ]; + + boot.initrd = { + extraUtilsCommands = mkIf (!systemd.enable) '' + copy_bin_and_libs ${pkgs.jose}/bin/jose + copy_bin_and_libs ${pkgs.curl}/bin/curl + copy_bin_and_libs ${pkgs.bash}/bin/bash + + copy_bin_and_libs ${pkgs.tpm2-tools}/bin/.tpm2-wrapped + mv $out/bin/{.tpm2-wrapped,tpm2} + cp {${pkgs.tpm2-tss},$out}/lib/libtss2-tcti-device.so.0 + + copy_bin_and_libs ${cfg.package}/bin/.clevis-wrapped + mv $out/bin/{.clevis-wrapped,clevis} + + for BIN in ${cfg.package}/bin/clevis-decrypt*; do + copy_bin_and_libs $BIN + done + + for BIN in $out/bin/clevis{,-decrypt{,-null,-tang,-tpm2}}; do + sed -i $BIN -e 's,${pkgs.bash},,' -e 's,${pkgs.coreutils},,' + done + + sed -i $out/bin/clevis-decrypt-tpm2 -e 's,tpm2_,tpm2 ,' + ''; + + secrets = lib.mapAttrs' (name: value: nameValuePair "/etc/clevis/${name}.jwe" value.secretFile) cfg.devices; + + systemd = { + extraBin = mkIf systemd.enable { + clevis = "${cfg.package}/bin/clevis"; + curl = "${pkgs.curl}/bin/curl"; + }; + + storePaths = mkIf systemd.enable [ + cfg.package + "${pkgs.jose}/bin/jose" + "${pkgs.curl}/bin/curl" + "${pkgs.tpm2-tools}/bin/tpm2_createprimary" + "${pkgs.tpm2-tools}/bin/tpm2_flushcontext" + "${pkgs.tpm2-tools}/bin/tpm2_load" + "${pkgs.tpm2-tools}/bin/tpm2_unseal" + ]; + }; + }; + }; +} diff --git a/nixos/modules/system/boot/luksroot.nix b/nixos/modules/system/boot/luksroot.nix index ca560d63f3bd..8bd9e71cb3a9 100644 --- a/nixos/modules/system/boot/luksroot.nix +++ b/nixos/modules/system/boot/luksroot.nix @@ -1,9 +1,11 @@ -{ config, options, lib, pkgs, ... }: +{ config, options, lib, utils, pkgs, ... }: with lib; let luks = config.boot.initrd.luks; + clevis = config.boot.initrd.clevis; + systemd = config.boot.initrd.systemd; kernelPackages = config.boot.kernelPackages; defaultPrio = (mkOptionDefault {}).priority; @@ -594,7 +596,7 @@ in ''; type = with types; attrsOf (submodule ( - { name, ... }: { options = { + { config, name, ... }: { options = { name = mkOption { visible = false; @@ -894,6 +896,19 @@ in ''; }; }; + + config = mkIf (clevis.enable && (hasAttr name clevis.devices)) { + preOpenCommands = mkIf (!systemd.enable) '' + mkdir -p /clevis-${name} + mount -t ramfs none /clevis-${name} + clevis decrypt < /etc/clevis/${name}.jwe > /clevis-${name}/decrypted + ''; + keyFile = "/clevis-${name}/decrypted"; + fallbackToPassword = !systemd.enable; + postOpenCommands = mkIf (!systemd.enable) '' + umount /clevis-${name} + ''; + }; })); }; @@ -1081,6 +1096,35 @@ in boot.initrd.preLVMCommands = mkIf (!config.boot.initrd.systemd.enable) (commonFunctions + preCommands + concatStrings (mapAttrsToList openCommand preLVM) + postCommands); boot.initrd.postDeviceCommands = mkIf (!config.boot.initrd.systemd.enable) (commonFunctions + preCommands + concatStrings (mapAttrsToList openCommand postLVM) + postCommands); + boot.initrd.systemd.services = let devicesWithClevis = filterAttrs (device: _: (hasAttr device clevis.devices)) luks.devices; in + mkIf (clevis.enable && systemd.enable) ( + (mapAttrs' + (name: _: nameValuePair "cryptsetup-clevis-${name}" { + wantedBy = [ "systemd-cryptsetup@${utils.escapeSystemdPath name}.service" ]; + before = [ + "systemd-cryptsetup@${utils.escapeSystemdPath name}.service" + "initrd-switch-root.target" + "shutdown.target" + ]; + wants = [ "systemd-udev-settle.service" ] ++ optional clevis.useTang "network-online.target"; + after = [ "systemd-modules-load.service" "systemd-udev-settle.service" ] ++ optional clevis.useTang "network-online.target"; + script = '' + mkdir -p /clevis-${name} + mount -t ramfs none /clevis-${name} + umask 277 + clevis decrypt < /etc/clevis/${name}.jwe > /clevis-${name}/decrypted + ''; + conflicts = [ "initrd-switch-root.target" "shutdown.target" ]; + unitConfig.DefaultDependencies = "no"; + serviceConfig = { + Type = "oneshot"; + RemainAfterExit = true; + ExecStop = "${config.boot.initrd.systemd.package.util-linux}/bin/umount /clevis-${name}"; + }; + }) + devicesWithClevis) + ); + environment.systemPackages = [ pkgs.cryptsetup ]; }; } |