about summary refs log tree commit diff
path: root/nixos/modules/system
diff options
context:
space:
mode:
authorCamille Mondon <camillemondon@free.fr>2023-11-18 19:37:56 +0000
committerJulien Malka <julien@malka.sh>2023-12-02 11:55:47 +0000
commit27493b4d49b6d92f4baa049424cbb2fa48b4c948 (patch)
tree2b6f983b61f29ec542efbf8ae666a2f4f9081e74 /nixos/modules/system
parentbea9ec6d4ae820ee21bb1f8c37fd073678423180 (diff)
downloadnixlib-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.md51
-rw-r--r--nixos/modules/system/boot/clevis.nix107
-rw-r--r--nixos/modules/system/boot/luksroot.nix48
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 ];
   };
 }