nixos/initrd: add secretPaths and extraSecretsHook
This commit is contained in:
@@ -86,7 +86,7 @@ in
|
||||
description = ''
|
||||
Append an additional file's contents to `/etc/iscsid.conf`. Use a non-store path
|
||||
and store passwords in this file. Note: the file specified here must be available
|
||||
in the initrd, see: `boot.initrd.secrets`.
|
||||
in the initrd, see: `boot.initrd.secretPaths`.
|
||||
'';
|
||||
default = null;
|
||||
type = nullOr str;
|
||||
|
||||
@@ -14,7 +14,9 @@ let
|
||||
children = lib.mapAttrs (
|
||||
childName: childConfig: childConfig.configuration.system.build.toplevel
|
||||
) config.specialisation;
|
||||
hasAtLeastOneInitrdSecret = lib.length (lib.attrNames config.boot.initrd.secrets) > 0;
|
||||
hasInitrdSecrets =
|
||||
(lib.length (lib.attrNames config.boot.initrd.secretPaths) > 0)
|
||||
|| (config.boot.initrd.extraSecretsHook != "");
|
||||
schemas = {
|
||||
v1 = rec {
|
||||
filename = "boot.json";
|
||||
@@ -33,7 +35,7 @@ let
|
||||
// lib.optionalAttrs config.boot.initrd.enable {
|
||||
initrd = "${config.system.build.initialRamdisk}/${config.system.boot.loader.initrdFile}";
|
||||
}
|
||||
// lib.optionalAttrs hasAtLeastOneInitrdSecret {
|
||||
// lib.optionalAttrs hasInitrdSecrets {
|
||||
initrdSecrets = "${config.system.build.initialRamdiskSecretAppender}/bin/append-initrd-secrets";
|
||||
};
|
||||
}
|
||||
|
||||
@@ -414,7 +414,7 @@ in
|
||||
|
||||
ln -s ${initrdPath} $out/initrd
|
||||
|
||||
${optionalString (config.boot.initrd.secrets != { }) ''
|
||||
${optionalString (config.boot.initrd.secretPaths != { }) ''
|
||||
ln -s ${config.system.build.initialRamdiskSecretAppender}/bin/append-initrd-secrets $out
|
||||
''}
|
||||
|
||||
|
||||
@@ -482,7 +482,7 @@ sub addEntry {
|
||||
die "failed to create initrd secrets $!\n";
|
||||
} else {
|
||||
say STDERR "warning: failed to create initrd secrets for \"$name\", an older generation";
|
||||
say STDERR "note: this is normal after having removed or renamed a file in `boot.initrd.secrets`";
|
||||
say STDERR "note: this is normal after having modified or removed an entry in `boot.initrd.secretPaths`";
|
||||
}
|
||||
}
|
||||
# Check whether any secrets were actually added
|
||||
|
||||
@@ -202,7 +202,7 @@ def write_entry(profile: str | None, generation: int, specialisation: str | None
|
||||
print("warning: failed to create initrd secrets "
|
||||
f'for "{title} - Configuration {generation}", an older generation', file=sys.stderr)
|
||||
print("note: this is normal after having removed "
|
||||
"or renamed a file in `boot.initrd.secrets`", file=sys.stderr)
|
||||
"or modified an entry in `boot.initrd.secretPaths`", file=sys.stderr)
|
||||
entry_file = BOOT_MOUNT_POINT / "loader/entries" / generation_conf_filename(profile, generation, specialisation)
|
||||
tmp_path = entry_file.with_suffix(".tmp")
|
||||
kernel_params = "init=%s " % bootspec.init
|
||||
|
||||
@@ -148,23 +148,22 @@ let
|
||||
# Copy secrets if needed.
|
||||
#
|
||||
# TODO: move out to a separate script; see #85000.
|
||||
${optionalString (!config.boot.loader.supportsInitrdSecrets) (
|
||||
concatStringsSep "\n" (
|
||||
mapAttrsToList (
|
||||
dest: source:
|
||||
let
|
||||
source' = if source == null then dest else source;
|
||||
in
|
||||
''
|
||||
mkdir -p $(dirname "$out/secrets/${dest}")
|
||||
# Some programs (e.g. ssh) doesn't like secrets to be
|
||||
# symlinks, so we use `cp -L` here to match the
|
||||
# behaviour when secrets are natively supported.
|
||||
cp -Lr ${source'} "$out/secrets/${dest}"
|
||||
''
|
||||
) config.boot.initrd.secrets
|
||||
)
|
||||
)}
|
||||
${optionalString (!config.boot.loader.supportsInitrdSecrets) ''
|
||||
${concatStringsSep "\n" (
|
||||
mapAttrsToList (_: scfg: ''
|
||||
mkdir -p $(dirname "$out/secrets${scfg.path}")
|
||||
# Some programs (e.g. ssh) doesn't like secrets to be
|
||||
# symlinks, so we use `cp -L` here to match the
|
||||
# behaviour when secrets are natively supported.
|
||||
# The assertion further up in this file (stage-1.nix)
|
||||
# checks that all secretPaths are Nix store paths set via
|
||||
# boot.initrd.secretPaths.*.source if the bootloader doesn't
|
||||
# support initrd secrets.
|
||||
cp -Lr ${scfg.source} "$out/secrets${scfg.path}"
|
||||
'') config.boot.initrd.secretPaths
|
||||
)}
|
||||
${config.boot.initrd.extraSecretsHook}
|
||||
''}
|
||||
|
||||
${config.boot.initrd.extraUtilsCommands}
|
||||
|
||||
@@ -436,7 +435,9 @@ let
|
||||
exit 0
|
||||
fi
|
||||
|
||||
${lib.optionalString (config.boot.initrd.secrets == { }) "exit 0"}
|
||||
${lib.optionalString (
|
||||
config.boot.initrd.secretPaths == { } && config.boot.initrd.extraSecretsHook == ""
|
||||
) "exit 0"}
|
||||
|
||||
export PATH=${pkgs.coreutils}/bin:${pkgs.cpio}/bin:${pkgs.gzip}/bin:${pkgs.findutils}/bin
|
||||
|
||||
@@ -451,17 +452,25 @@ let
|
||||
|
||||
${lib.concatStringsSep "\n" (
|
||||
mapAttrsToList (
|
||||
dest: source:
|
||||
_: scfg:
|
||||
let
|
||||
source' = if source == null then dest else toString source;
|
||||
prefix = lib.optionalString scfg.intermediateSecretsDir "/.initrd-secrets";
|
||||
in
|
||||
''
|
||||
mkdir -p $(dirname "$tmp/.initrd-secrets/${dest}")
|
||||
cp -a ${source'} "$tmp/.initrd-secrets/${dest}"
|
||||
mkdir -p $(dirname "$tmp${prefix}${scfg.path}")
|
||||
(
|
||||
export out="$tmp${prefix}${scfg.path}"
|
||||
${scfg.generateSecretCommand}
|
||||
)
|
||||
''
|
||||
) config.boot.initrd.secrets
|
||||
) config.boot.initrd.secretPaths
|
||||
)}
|
||||
|
||||
(
|
||||
cd "$tmp"
|
||||
${config.boot.initrd.extraSecretsHook}
|
||||
)
|
||||
|
||||
# mindepth 1 so that we don't change the mode of /
|
||||
(cd "$tmp" && find . -mindepth 1 | xargs touch -amt 197001010000 && find . -mindepth 1 -print0 | sort -z | cpio --quiet -o -H newc -R +0:+0 --reproducible --null) | \
|
||||
${compressorExe} ${lib.escapeShellArgs initialRamdisk.compressorArgs} >> "$1"
|
||||
@@ -668,6 +677,104 @@ in
|
||||
'';
|
||||
};
|
||||
|
||||
boot.initrd.secretPaths = mkOption {
|
||||
default = { };
|
||||
type = types.attrsOf (
|
||||
types.submodule (
|
||||
{ config, name, ... }:
|
||||
{
|
||||
options = {
|
||||
path = mkOption {
|
||||
type = types.path;
|
||||
default = name;
|
||||
description = ''
|
||||
The path the secret should be placed at in the initrd. Defaults
|
||||
to the attribute name.
|
||||
'';
|
||||
};
|
||||
|
||||
intermediateSecretsDir = mkOption {
|
||||
type = types.bool;
|
||||
default = true;
|
||||
description = ''
|
||||
By default, the secrets will be copied over to the
|
||||
`/.initrd-secrets` dir at initrd generation time, and then copied
|
||||
over to their final location at boot time. This is because initrd secrets
|
||||
that are supposed to be placed in `/run` would be overridden by
|
||||
the tmpfs mount over `/run` otherwise.
|
||||
|
||||
Set this option to `false` to skip this intermediate step and
|
||||
place the secret at its final location straightaway.
|
||||
'';
|
||||
};
|
||||
|
||||
source = mkOption {
|
||||
type = types.nullOr types.path;
|
||||
default = null;
|
||||
description = ''
|
||||
The absolute path on the filesystem to copy the secret from.
|
||||
'';
|
||||
example = "/var/lib/secrets/initrd/ssh_host_ed25519_key";
|
||||
};
|
||||
|
||||
generateSecretCommand = mkOption {
|
||||
type = types.path;
|
||||
description = ''
|
||||
The command to run to generate the secret. It should write
|
||||
the secret to `$out`.
|
||||
|
||||
This is useful if you have a more advanced secrets provisioning
|
||||
mechanism.
|
||||
'';
|
||||
example = ''
|
||||
pkgs.writeShellScript "generate-secret" '''
|
||||
''${lib.getExe pkgs.age} -d -i /etc/ssh/ssh_host_ed25519_key -o "$out" ''${./secret.age}
|
||||
'''
|
||||
'';
|
||||
};
|
||||
};
|
||||
|
||||
config = {
|
||||
generateSecretCommand = lib.mkIf (config.source != null) (
|
||||
pkgs.writeShellScript "copy-secret" ''
|
||||
cp -Lr ${config.source} "$out"
|
||||
''
|
||||
);
|
||||
};
|
||||
}
|
||||
)
|
||||
);
|
||||
description = ''
|
||||
Secret paths to append to the initrd. The attribute name is the
|
||||
path the secret should have inside the initrd.
|
||||
|
||||
Note that `nixos-rebuild switch` will generate the initrd
|
||||
also for past generations, so if secrets are moved or deleted
|
||||
you will also have to garbage collect the generations that
|
||||
use those secrets.
|
||||
'';
|
||||
example = {
|
||||
"/etc/ssh/ssh_host_ed25519_key".source = "/var/lib/secrets/initrd/ssh_host_ed25519_key";
|
||||
};
|
||||
};
|
||||
|
||||
boot.initrd.extraSecretsHook = mkOption {
|
||||
default = "";
|
||||
type = types.lines;
|
||||
description = ''
|
||||
Extra commands to be executed after the initrd secrets generation phase.
|
||||
|
||||
This script should place files into the current workdir. These files
|
||||
will then be copied over to the initrd to the corresponding absolute
|
||||
paths, e.g. `etc/ssh/ssh_host_ed25519_key` will be copied over to
|
||||
`/etc/ssh/ssh_host_ed25519_key`.
|
||||
'';
|
||||
example = ''
|
||||
# Generate a new SSH host key for every generation.
|
||||
ssh-keygen -f etc/ssh/ssh_host_ed25519_key
|
||||
'';
|
||||
};
|
||||
|
||||
boot.initrd.supportedFilesystems = mkOption {
|
||||
default = { };
|
||||
inherit (options.boot.supportedFilesystems) example type description;
|
||||
@@ -746,15 +853,18 @@ in
|
||||
assertion =
|
||||
!config.boot.loader.supportsInitrdSecrets
|
||||
-> all (
|
||||
source: builtins.isPath source || (builtins.isString source && hasPrefix builtins.storeDir source)
|
||||
) (attrValues config.boot.initrd.secrets);
|
||||
scfg:
|
||||
builtins.isPath scfg.source
|
||||
|| (builtins.isString scfg.source && hasPrefix builtins.storeDir scfg.source)
|
||||
) (attrValues config.boot.initrd.secretPaths);
|
||||
message = ''
|
||||
boot.initrd.secrets values must be unquoted paths when
|
||||
using a bootloader that doesn't natively support initrd
|
||||
secrets, e.g.:
|
||||
When using a bootloader that doesn't natively support initrd secrets,
|
||||
all `boot.initrd.secretPaths` values must be defined via
|
||||
`boot.initrd.secretsPaths.*.source`, and the `source` values must be
|
||||
unquoted paths, e.g.
|
||||
|
||||
boot.initrd.secrets = {
|
||||
"/etc/secret" = /path/to/secret;
|
||||
boot.initrd.secretPaths = {
|
||||
"/etc/secret".source = /path/to/secret;
|
||||
};
|
||||
|
||||
Note that this will result in all secrets being stored
|
||||
@@ -763,6 +873,14 @@ in
|
||||
}
|
||||
];
|
||||
|
||||
# Backwards compatibility to the legacy `boot.initrd.secrets` option.
|
||||
boot.initrd.secretPaths = lib.mapAttrs' (dest: source: {
|
||||
# The legacy boot.initrd.secrets option didn't type-check the attr
|
||||
# names, so we need to optionally prepend a slash.
|
||||
name = "${lib.optionalString (!lib.hasPrefix "/" dest) "/"}${dest}";
|
||||
value.source = if dest != null then source else dest;
|
||||
}) config.boot.initrd.secrets;
|
||||
|
||||
system.build = mkMerge [
|
||||
{
|
||||
inherit
|
||||
|
||||
@@ -10,14 +10,21 @@
|
||||
# Copy secrets into the initrd if they cannot be appended
|
||||
boot.initrd.systemd.contents = lib.mkIf (!config.boot.loader.supportsInitrdSecrets) (
|
||||
lib.mapAttrs' (
|
||||
dest: source:
|
||||
lib.nameValuePair "/.initrd-secrets/${dest}" { source = if source == null then dest else source; }
|
||||
) config.boot.initrd.secrets
|
||||
_: scfg:
|
||||
let
|
||||
prefix = lib.optionalString scfg.intermediateSecretsDir "/.initrd-secrets";
|
||||
in
|
||||
lib.nameValuePair "${prefix}${scfg.path}" { inherit (scfg) source; }
|
||||
) config.boot.initrd.secretPaths
|
||||
);
|
||||
|
||||
# Copy secrets to their respective locations
|
||||
boot.initrd.systemd.services.initrd-nixos-copy-secrets =
|
||||
lib.mkIf (config.boot.initrd.secrets != { })
|
||||
lib.mkIf
|
||||
(
|
||||
(builtins.any (x: x.intermediateSecretsDir) (builtins.attrValues config.boot.initrd.secretPaths))
|
||||
|| config.boot.initrd.extraSecretsHook != ""
|
||||
)
|
||||
{
|
||||
description = "Copy secrets into place";
|
||||
# Run as early as possible
|
||||
@@ -34,10 +41,12 @@
|
||||
# drop this service, we'd mount the /run tmpfs over the secret, making it
|
||||
# invisible in stage 2.
|
||||
script = ''
|
||||
for secret in $(cd /.initrd-secrets; find . -type f -o -type l); do
|
||||
mkdir -p "$(dirname "/$secret")"
|
||||
cp "/.initrd-secrets/$secret" "/$secret"
|
||||
done
|
||||
if [ -d /.initrd-secrets ]; then
|
||||
for secret in $(cd /.initrd-secrets; find . -type f -o -type l); do
|
||||
mkdir -p "$(dirname "/$secret")"
|
||||
cp "/.initrd-secrets/$secret" "/$secret"
|
||||
done
|
||||
fi
|
||||
'';
|
||||
|
||||
serviceConfig = {
|
||||
|
||||
Reference in New Issue
Block a user