Skip to content

Commit

Permalink
auto-enroll: use safe auto enrollment rather than YOLO enrollment
Browse files Browse the repository at this point in the history
This uses the systemd semantics for automatic enrollment at boot time.

For now, it is very simple, in the future, we can better use this option to push
the proper auth files with names or have Type #1 entries for enrollment. :)
  • Loading branch information
RaitoBezarius committed Feb 11, 2024
1 parent f2bc0af commit 996d722
Show file tree
Hide file tree
Showing 2 changed files with 108 additions and 38 deletions.
92 changes: 68 additions & 24 deletions nix/modules/lanzaboote.nix
Original file line number Diff line number Diff line change
Expand Up @@ -3,10 +3,6 @@ with lib;
let
cfg = config.boot.lanzaboote;

sbctlWithPki = pkgs.sbctl.override {
databasePath = "/tmp/pki";
};

loaderSettingsFormat = pkgs.formats.keyValue {
mkKeyValue = k: v: if v == null then "" else
lib.generators.mkKeyValueDefault { } " " k v;
Expand All @@ -15,12 +11,46 @@ let
loaderConfigFile = loaderSettingsFormat.generate "loader.conf" cfg.settings;

configurationLimit = if cfg.configurationLimit == null then 0 else cfg.configurationLimit;

loaderKeyOpts = { ... }:
let
mkAuthOption = variableName: mkOption {
type = types.nullOr types.path;
default = null;
description = "Auth variable file for ${variableName}";
};
in
{
options = {
db = mkAuthOption "db";
KEK = mkAuthOption "KEK";
PK = mkAuthOption "PK";
};
};
in
{
options.boot.lanzaboote = {
enable = mkEnableOption "Enable the LANZABOOTE";

enrollKeys = mkEnableOption "Automatic enrollment of the keys using sbctl";
safeAutoEnroll = mkOption {
type = types.nullOr (types.submodule loaderKeyOpts);
default = null;
description = ''
Perform safe automatic (or manual) enrollment of Secure Boot variables
via .auth variables.
Files will be put in /loader/keys/auto/{db,KEK,PK}.auth.
If you are using systemd-boot, they will be enrolled if it's deemed
safe or
[`secure-boot-enroll`](https://www.freedesktop.org/software/systemd/man/latest/loader.conf.html#secure-boot-enroll)
is set to `force`.
Usually, detected virtual machine environments are deemed safe.
Not all bootloaders support safe automatic enrollment.
'';
};

configurationLimit = mkOption {
default = config.boot.loader.systemd-boot.configurationLimit;
Expand Down Expand Up @@ -107,27 +137,41 @@ in
enable = true;
};
boot.loader.supportsInitrdSecrets = true;
systemd.package = pkgs.systemd.overrideAttrs (old: {
patches = old.patches ++ [
(pkgs.fetchpatch {
url = "https://github.com/systemd/systemd/pull/29370.patch";
hash = "sha256-dTmeG/ZANbXdkSMWl0VyVkCresYy1jvxnCvrDJSMOb4=";
})
];
});
boot.loader.external = {
enable = true;
installHook = pkgs.writeShellScript "bootinstall" ''
${optionalString cfg.enrollKeys ''
mkdir -p /tmp/pki
cp -r ${cfg.pkiBundle}/* /tmp/pki
${sbctlWithPki}/bin/sbctl enroll-keys --yes-this-might-brick-my-machine
''}
# Use the system from the kernel's hostPlatform because this should
# always, even in the cross compilation case, be the right system.
${cfg.package}/bin/lzbt install \
--system ${config.boot.kernelPackages.stdenv.hostPlatform.system} \
--systemd ${config.systemd.package} \
--systemd-boot-loader-config ${loaderConfigFile} \
--public-key ${cfg.publicKeyFile} \
--private-key ${cfg.privateKeyFile} \
--configuration-limit ${toString configurationLimit} \
${config.boot.loader.efi.efiSysMountPoint} \
/nix/var/nix/profiles/system-*-link
'';
installHook =
let
copyAutoEnrollIfNeeded = varName: optionalString (cfg.safeAutoEnroll.${varName} != null) ''cp -a ${cfg.safeAutoEnroll.${varName}} "$ESP/loader/keys/auto/${varName}.auth"'';
in
pkgs.writeShellScript "bootinstall" ''
export ESP="${config.boot.loader.efi.efiSysMountPoint}"
${optionalString (cfg.safeAutoEnroll != null) ''
mkdir -p "$ESP/loader/keys/auto"
${copyAutoEnrollIfNeeded "PK"}
${copyAutoEnrollIfNeeded "KEK"}
${copyAutoEnrollIfNeeded "db"}
''}
# Use the system from the kernel's hostPlatform because this should
# always, even in the cross compilation case, be the right system.
${cfg.package}/bin/lzbt install \
--system ${config.boot.kernelPackages.stdenv.hostPlatform.system} \
--systemd ${config.systemd.package} \
--systemd-boot-loader-config ${loaderConfigFile} \
--public-key ${cfg.publicKeyFile} \
--private-key ${cfg.privateKeyFile} \
--configuration-limit ${toString configurationLimit} \
"$ESP" \
/nix/var/nix/profiles/system-*-link
'';
};

systemd.services.fwupd = lib.mkIf config.services.fwupd.enable {
Expand Down
54 changes: 40 additions & 14 deletions nix/tests/lanzaboote.nix
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
}:

let
inherit (pkgs) lib system;
inherit (pkgs) lib system runCommand;
defaultTimeout = 5 * 60; # = 5 minutes

inherit (pkgs.stdenv.hostPlatform) efiArch;
Expand Down Expand Up @@ -88,6 +88,21 @@ let
testScript = ''
${lib.optionalString useTPM2 tpm2Initialization}
${lib.optionalString readEfiVariables efiVariablesHelpers}
machine.start()
if machine.qmp_client is None:
import sys; sys.exit(1)
# We expect a shutdown for guest-reset reasons.
while True:
try:
event = next(machine.qmp_client.events())
print(event)
except:
continue
if event['event'] == 'SHUTDOWN' and event.get('data', {}).get('reason') == 'guest-reset':
break
print('Shutdown detected.')
machine.booted = False
machine.start()
${testScript}
'';

Expand Down Expand Up @@ -147,7 +162,30 @@ let
};
boot.lanzaboote = {
enable = true;
enrollKeys = lib.mkDefault true;
safeAutoEnroll =
let
signVariable = varName:
let
GUID = ./fixtures/uefi-keys/GUID;
publicKey = ./fixtures/uefi-keys/keys/${varName}/${varName}.pem;
privateKey = ./fixtures/uefi-keys/keys/${varName}/${varName}.key;
in
runCommand "sign-${varName}-via-snakeoil-pki"
{
nativeBuildInputs = [ pkgs.efitools ];
} ''
cert-to-efi-sig-list -g ${GUID} ${publicKey} ${varName}.esl
sign-efi-sig-list -t "$(date --date '@1' '+%Y-%m-%d %H:%M:%S')" \
-k ${privateKey} -c ${publicKey} ${varName} ${varName}.esl ${varName}.auth
mv ${varName}.auth $out
'';
in
{
db = signVariable "db";
KEK = signVariable "KEK";
PK = signVariable "PK";
};
pkiBundle = ./fixtures/uefi-keys;
};
};
Expand Down Expand Up @@ -200,7 +238,6 @@ in
basic = mkSecureBootTest {
name = "lanzaboote";
testScript = ''
machine.start()
assert "Secure Boot: enabled (user)" in machine.succeed("bootctl status")
'';
};
Expand All @@ -211,7 +248,6 @@ in
boot.initrd.systemd.enable = true;
};
testScript = ''
machine.start()
assert "Secure Boot: enabled (user)" in machine.succeed("bootctl status")
'';
};
Expand All @@ -234,7 +270,6 @@ in
'';
};
testScript = ''
machine.start()
machine.wait_for_unit("multi-user.target")
machine.succeed("cmp ${secret} /secret-from-initramfs")
Expand Down Expand Up @@ -276,7 +311,6 @@ in
};
};
testScript = ''
machine.start()
machine.wait_for_unit("multi-user.target")
# Assert that only three boot files exists (a single kernel and a two
Expand Down Expand Up @@ -326,7 +360,6 @@ in
};
};
testScript = ''
machine.start()
print(machine.succeed("ls -lah /boot/EFI/Linux"))
# TODO: make it more reliable to find this filename, i.e. read it from somewhere?
machine.succeed("bootctl set-default nixos-generation-1-specialisation-variant-\*.efi")
Expand Down Expand Up @@ -359,7 +392,6 @@ in
boot.bootspec.enable = lib.mkForce false;
};
testScript = ''
machine.start()
assert "Secure Boot: enabled (user)" in machine.succeed("bootctl status")
'';
};
Expand All @@ -371,8 +403,6 @@ in
boot.loader.systemd-boot.consoleMode = "auto";
};
testScript = ''
machine.start()
actual_loader_config = machine.succeed("cat /boot/loader/loader.conf").split("\n")
expected_loader_config = ["timeout 0", "console-mode auto"]
Expand All @@ -393,8 +423,6 @@ in
# Finally, we will reboot.
# We will also assert that systemd-boot is not running
# by checking for the sd-boot's specific EFI variables.
machine.start()
# By construction, nixos-generation-1.efi is the stub we are interested in.
# TODO: this should work -- machine.succeed("efibootmgr -d /dev/vda -c -l \\EFI\\Linux\\nixos-generation-1.efi") -- efivars are not persisted
# across reboots atm?
Expand Down Expand Up @@ -447,8 +475,6 @@ in
useTPM2 = true;
readEfiVariables = true;
testScript = ''
machine.start()
# TODO: the other variables are not yet supported.
expected_variables = [
"StubPcrKernelImage"
Expand Down

0 comments on commit 996d722

Please sign in to comment.