mirror of
https://github.com/NixOS/nixpkgs.git
synced 2025-01-29 16:24:10 +00:00
nixos/clevis: init
Co-Authored-By: Julien Malka <julien@malka.sh>
This commit is contained in:
parent
bea9ec6d4a
commit
27493b4d49
@ -18,6 +18,8 @@ In addition to numerous new and upgraded packages, this release has the followin
|
||||
|
||||
- [Anki Sync Server](https://docs.ankiweb.net/sync-server.html), the official sync server built into recent versions of Anki. Available as [services.anki-sync-server](#opt-services.anki-sync-server.enable).
|
||||
|
||||
- [Clevis](https://github.com/latchset/clevis), a pluggable framework for automated decryption, used to unlock encrypted devices in initrd. Available as [boot.initrd.clevis.enable](#opt-boot.initrd.clevis.enable).
|
||||
|
||||
## Backward Incompatibilities {#sec-release-24.05-incompatibilities}
|
||||
|
||||
<!-- To avoid merge conflicts, consider adding your item at an arbitrary place in the list instead. -->
|
||||
|
@ -1423,6 +1423,7 @@
|
||||
./system/activation/bootspec.nix
|
||||
./system/activation/top-level.nix
|
||||
./system/boot/binfmt.nix
|
||||
./system/boot/clevis.nix
|
||||
./system/boot/emergency-mode.nix
|
||||
./system/boot/grow-partition.nix
|
||||
./system/boot/initrd-network.nix
|
||||
|
51
nixos/modules/system/boot/clevis.md
Normal file
51
nixos/modules/system/boot/clevis.md
Normal file
@ -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.
|
107
nixos/modules/system/boot/clevis.nix
Normal file
107
nixos/modules/system/boot/clevis.nix
Normal file
@ -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"
|
||||
];
|
||||
};
|
||||
};
|
||||
};
|
||||
}
|
@ -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 ];
|
||||
};
|
||||
}
|
||||
|
@ -57,7 +57,15 @@ let
|
||||
# bcachefs does not support mounting devices with colons in the path, ergo we don't (see #49671)
|
||||
firstDevice = fs: lib.head (lib.splitString ":" fs.device);
|
||||
|
||||
openCommand = name: fs: ''
|
||||
openCommand = name: fs: if config.boot.initrd.clevis.enable && (lib.hasAttr (firstDevice fs) config.boot.initrd.clevis.devices) then ''
|
||||
if clevis decrypt < /etc/clevis/${firstDevice fs}.jwe | bcachefs unlock ${firstDevice fs}
|
||||
then
|
||||
printf "unlocked ${name} using clevis\n"
|
||||
else
|
||||
printf "falling back to interactive unlocking...\n"
|
||||
tryUnlock ${name} ${firstDevice fs}
|
||||
fi
|
||||
'' else ''
|
||||
tryUnlock ${name} ${firstDevice fs}
|
||||
'';
|
||||
|
||||
|
@ -17,6 +17,9 @@ let
|
||||
cfgZED = config.services.zfs.zed;
|
||||
|
||||
selectModulePackage = package: config.boot.kernelPackages.${package.kernelModuleAttribute};
|
||||
clevisDatasets = map (e: e.device) (filter (e: (hasAttr e.device config.boot.initrd.clevis.devices) && e.fsType == "zfs" && (fsNeededForBoot e)) config.system.build.fileSystems);
|
||||
|
||||
|
||||
inInitrd = any (fs: fs == "zfs") config.boot.initrd.supportedFilesystems;
|
||||
inSystem = any (fs: fs == "zfs") config.boot.supportedFilesystems;
|
||||
|
||||
@ -120,12 +123,12 @@ let
|
||||
# but don't *require* it, because mounts shouldn't be killed if it's stopped.
|
||||
# In the future, hopefully someone will complete this:
|
||||
# https://github.com/zfsonlinux/zfs/pull/4943
|
||||
wants = [ "systemd-udev-settle.service" ];
|
||||
wants = [ "systemd-udev-settle.service" ] ++ optional (config.boot.initrd.clevis.useTang) "network-online.target";
|
||||
after = [
|
||||
"systemd-udev-settle.service"
|
||||
"systemd-modules-load.service"
|
||||
"systemd-ask-password-console.service"
|
||||
];
|
||||
] ++ optional (config.boot.initrd.clevis.useTang) "network-online.target";
|
||||
requiredBy = getPoolMounts prefix pool ++ [ "zfs-import.target" ];
|
||||
before = getPoolMounts prefix pool ++ [ "zfs-import.target" ];
|
||||
unitConfig = {
|
||||
@ -154,6 +157,9 @@ let
|
||||
poolImported "${pool}" || poolImport "${pool}" # Try one last time, e.g. to import a degraded pool.
|
||||
fi
|
||||
if poolImported "${pool}"; then
|
||||
${concatMapStringsSep "\n" (elem: "clevis decrypt < /etc/clevis/${elem}.jwe | zfs load-key ${elem} || true ") (filter (p: (elemAt (splitString "/" p) 0) == pool) clevisDatasets)}
|
||||
|
||||
|
||||
${optionalString keyLocations.hasKeys ''
|
||||
${keyLocations.command} | while IFS=$'\t' read ds kl ks; do
|
||||
{
|
||||
@ -623,6 +629,9 @@ in
|
||||
fi
|
||||
poolImported "${pool}" || poolImport "${pool}" # Try one last time, e.g. to import a degraded pool.
|
||||
fi
|
||||
|
||||
${concatMapStringsSep "\n" (elem: "clevis decrypt < /etc/clevis/${elem}.jwe | zfs load-key ${elem}") (filter (p: (elemAt (splitString "/" p) 0) == pool) clevisDatasets)}
|
||||
|
||||
${if isBool cfgZfs.requestEncryptionCredentials
|
||||
then optionalString cfgZfs.requestEncryptionCredentials ''
|
||||
zfs load-key -a
|
||||
|
@ -16,6 +16,7 @@
|
||||
, ninja
|
||||
, pkg-config
|
||||
, tpm2-tools
|
||||
, nixosTests
|
||||
}:
|
||||
|
||||
stdenv.mkDerivation rec {
|
||||
@ -29,6 +30,12 @@ stdenv.mkDerivation rec {
|
||||
hash = "sha256-3J3ti/jRiv+p3eVvJD7u0ko28rPd8Gte0mCJaVaqyOs=";
|
||||
};
|
||||
|
||||
patches = [
|
||||
# Replaces the clevis-decrypt 300s timeout to a 10s timeout
|
||||
# https://github.com/latchset/clevis/issues/289
|
||||
./tang-timeout.patch
|
||||
];
|
||||
|
||||
postPatch = ''
|
||||
for f in $(find src/ -type f); do
|
||||
grep -q "/bin/cat" "$f" && substituteInPlace "$f" \
|
||||
@ -65,6 +72,14 @@ stdenv.mkDerivation rec {
|
||||
"man"
|
||||
];
|
||||
|
||||
passthru.tests = {
|
||||
inherit (nixosTests.installer) clevisBcachefs clevisBcachefsFallback clevisLuks clevisLuksFallback clevisZfs clevisZfsFallback;
|
||||
clevisLuksSystemdStage1 = nixosTests.installer-systemd-stage-1.clevisLuks;
|
||||
clevisLuksFallbackSystemdStage1 = nixosTests.installer-systemd-stage-1.clevisLuksFallback;
|
||||
clevisZfsSystemdStage1 = nixosTests.installer-systemd-stage-1.clevisZfs;
|
||||
clevisZfsFallbackSystemdStage1 = nixosTests.installer-systemd-stage-1.clevisZfsFallback;
|
||||
};
|
||||
|
||||
meta = with lib; {
|
||||
description = "Automated Encryption Framework";
|
||||
homepage = "https://github.com/latchset/clevis";
|
||||
|
13
pkgs/tools/security/clevis/tang-timeout.patch
Normal file
13
pkgs/tools/security/clevis/tang-timeout.patch
Normal file
@ -0,0 +1,13 @@
|
||||
diff --git a/src/pins/tang/clevis-decrypt-tang b/src/pins/tang/clevis-decrypt-tang
|
||||
index 72393b4..40b660f 100755
|
||||
--- a/src/pins/tang/clevis-decrypt-tang
|
||||
+++ b/src/pins/tang/clevis-decrypt-tang
|
||||
@@ -101,7 +101,7 @@ xfr="$(jose jwk exc -i '{"alg":"ECMR"}' -l- -r- <<< "$clt$eph")"
|
||||
|
||||
rec_url="$url/rec/$kid"
|
||||
ct="Content-Type: application/jwk+json"
|
||||
-if ! rep="$(curl -sfg -X POST -H "$ct" --data-binary @- "$rec_url" <<< "$xfr")"; then
|
||||
+if ! rep="$(curl --connect-timeout 10 -sfg -X POST -H "$ct" --data-binary @- "$rec_url" <<< "$xfr")"; then
|
||||
echo "Error communicating with server $url" >&2
|
||||
exit 1
|
||||
fi
|
Loading…
Reference in New Issue
Block a user