Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

auto-enroll: use safe auto enrollment rather than YOLO enrollment #229

Open
wants to merge 1 commit into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
84 changes: 60 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 @@ -109,25 +139,31 @@ in
boot.loader.supportsInitrdSecrets = true;
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
56 changes: 42 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}

def is_guest_induced_reset(event):
return event['event'] == 'SHUTDOWN' and event.get('data', {}).get('reason') == 'guest-reset'

machine.start()
if machine.qmp_client is None:
import sys; sys.exit(1)

# We expect a shutdown for guest-reset reasons.
print('Waiting for a guest-reset...')
machine.wait_for_qmp_event(is_guest_induced_reset)
print('Shutdown detected.')

machine.booted = False
machine.start()
${testScript}
'';

Expand Down Expand Up @@ -147,7 +162,32 @@ let
};
boot.lanzaboote = {
enable = true;
enrollKeys = lib.mkDefault true;
# Under aarch64, various things goes wrong...
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nit: You could elaborate here a bit, so people know why this wrinkle exists and when it may go away.

settings.secure-boot-enroll = lib.mkIf pkgs.hostPlatform.isAarch64 "force";
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 +240,6 @@ in
basic = mkSecureBootTest {
name = "lanzaboote";
testScript = ''
machine.start()
assert "Secure Boot: enabled (user)" in machine.succeed("bootctl status")
'';
};
Expand All @@ -211,7 +250,6 @@ in
boot.initrd.systemd.enable = true;
};
testScript = ''
machine.start()
assert "Secure Boot: enabled (user)" in machine.succeed("bootctl status")
'';
};
Expand All @@ -234,7 +272,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 +313,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 +362,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 +394,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 +405,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 +425,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 +477,6 @@ in
useTPM2 = true;
readEfiVariables = true;
testScript = ''
machine.start()

# TODO: the other variables are not yet supported.
expected_variables = [
"StubPcrKernelImage"
Expand Down