Skip to content
Open
Show file tree
Hide file tree
Changes from 3 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
11 changes: 11 additions & 0 deletions modules/boot/kernel.nix
Original file line number Diff line number Diff line change
Expand Up @@ -128,6 +128,13 @@
'';
};


boot.resumeDevice = lib.mkOption {
type = lib.types.str;
default = "";
description = "Device from which to resume after hibernation. Empty = disabled. Not implemented in finix.";
};
Comment thread
FixeQD marked this conversation as resolved.

boot.kernelParams = lib.mkOption {
type = lib.types.listOf (
lib.types.strMatching ''([^"[:space:]]|"[^"]*")+''
Expand Down Expand Up @@ -278,6 +285,10 @@
"rtc_cmos"
];

boot.kernelParams = lib.mkIf (config.boot.resumeDevice != "") [
"resume=${config.boot.resumeDevice}"
];

boot.initrd.kernelModules = [
# For LVM.
"dm_mod"
Expand Down
63 changes: 59 additions & 4 deletions modules/filesystems/default.nix
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,11 @@ let
sw:
let
device = if sw.label != null then "/dev/disk/by-label/${sw.label}" else sw.device;
options = sw.options ++ lib.optional (sw.priority != null) "pri=${toString sw.priority}";
options = sw.options
++ lib.optional (sw.priority != null) "pri=${toString sw.priority}"
++ lib.optional (sw.discardPolicy != null) (
if sw.discardPolicy == "both" then "discard" else "discard=${sw.discardPolicy}"
);
in
"${escape device} none swap ${escape (lib.concatStringsSep "," options)} 0 0\n";

Expand Down Expand Up @@ -98,6 +102,44 @@ let
)
+ "\n"
) fstabFileSystems;

# Swap entries with randomEncryption.enable can't be stable fstab lines: the backing device is a fresh /dev/mapper/<name> created with a brand new random key on every boot, so they're set up imperatively instead, mirroring how modules/zram.nix builds its swap device
Comment thread
FixeQD marked this conversation as resolved.
Outdated
isEncryptedSwap = sw: sw.randomEncryption.enable;
plainSwapDevices = lib.filter (sw: !isEncryptedSwap sw) config.swapDevices;
encryptedSwapDevices = lib.filter isEncryptedSwap config.swapDevices;

sanitizeName = s: lib.replaceStrings [ "/" " " ] [ "-" "-" ] (lib.removePrefix "/" s);

makeEncryptedSwapTask = sw: rec {
name = "cryptswap-${sanitizeName sw.device}";
value =
let
re = sw.randomEncryption;
options = sw.options
++ lib.optional (sw.priority != null) "pri=${toString sw.priority}"
++ lib.optional (sw.discardPolicy != null) (
if sw.discardPolicy == "both" then "discard" else "discard=${sw.discardPolicy}"
);
in
{
description = "Encrypted swap device on ${sw.device}";
runlevels = "S";
command = toString (
pkgs.writeShellScript name ''
set -eu
${pkgs.cryptsetup}/bin/cryptsetup plainOpen \
-c ${re.cipher} \
-s ${toString re.keySize} \
${lib.optionalString (re.sectorSize != 0) "--sector-size ${toString re.sectorSize}"} \
${lib.optionalString re.allowDiscards "--allow-discards"} \
-d ${re.source} \
${sw.device} ${name}
${pkgs.util-linux}/bin/mkswap /dev/mapper/${name}
${pkgs.util-linux}/bin/swapon -o ${lib.concatStringsSep "," options} /dev/mapper/${name}
Comment thread
FixeQD marked this conversation as resolved.
Outdated
''
);
};
};
in
{
imports = [
Expand Down Expand Up @@ -126,13 +168,26 @@ in
# system.fsPackages = [ pkgs.dosfstools ];
# environment.systemPackages = with pkgs; [ fuse3 fuse ] ++ config.system.fsPackages;

assertions = lib.map (sw: {
assertion =
sw.randomEncryption.enable -> builtins.match "/dev/disk/by-(uuid|label)/.*" sw.device == null;
message = ''
Random-encrypted swap device ${sw.device} should not be referenced
by UUID or label, since those are erased and regenerated on every
boot once the partition is encrypted. Use a stable path such as
/dev/disk/by-partuuid/... instead.
'';
}) encryptedSwapDevices;
Comment thread
FixeQD marked this conversation as resolved.

environment.systemPackages = lib.unique (
lib.flatten (
lib.concatMap (v: lib.optional v.enable v.packages or [ ]) (
lib.attrValues config.boot.supportedFilesystems
)
)
);
) ++ lib.optional (encryptedSwapDevices != [ ]) pkgs.cryptsetup;

finit.tasks = lib.listToAttrs (lib.map makeEncryptedSwapTask encryptedSwapDevices);
Comment thread
FixeQD marked this conversation as resolved.

environment.etc.fstab.text = ''
# This is a generated file. Do not edit!
Expand All @@ -151,8 +206,8 @@ in
]
) fileSystems) { }}

# swap devices
${lib.concatMapStrings makeSwapEntry config.swapDevices}
# swap devices (random-encrypted swap is handled by finit.tasks instead)
${lib.concatMapStrings makeSwapEntry plainSwapDevices}
'';

boot.supportedFilesystems = lib.mapAttrs' (
Expand Down
78 changes: 78 additions & 0 deletions modules/filesystems/options.nix
Original file line number Diff line number Diff line change
Expand Up @@ -143,6 +143,84 @@ let
type = with lib.types; nonEmptyListOf nonEmptyStr;
description = "Options used to set up the swap device.";
};

discardPolicy = lib.mkOption {
default = null;
example = "both";
type = with lib.types; nullOr (enum [ "once" "pages" "both" ]);
description = ''
Specify the discard policy for the swap device. If "once",
then the discard operation will be performed once, at swapon
invocation. If "pages", then the discard operation will be
performed on every freed page (this is "discard" semantics
in swap parlance, not TRIM). If "both", then both will be
enabled. If unset, then no discard policy is applied.
'';
};

randomEncryption = lib.mkOption {
default = { };
description = ''
Encrypt swap device with a random key. This way you won't have a
persistent swap device, the entire contents become unreadable
after every reboot. Mutually exclusive with {option}`label`,
since the underlying mapped device is regenerated on every boot
and is therefore not activated through `/etc/fstab` like a
regular swap device, but through a dedicated `finit` task.
'';
type = lib.types.submodule {
options = {
enable = lib.mkOption {
default = false;
type = lib.types.bool;
description = "Encrypt swap device with a random key.";
};

cipher = lib.mkOption {
default = "aes-xts-plain64";
type = lib.types.nonEmptyStr;
description = ''
Specify the cipher to use, see {command}`cryptsetup
benchmark` for a list of options.
'';
};

keySize = lib.mkOption {
default = 256;
type = lib.types.ints.positive;
description = "Specify the size of the randomly generated key, in bits.";
};

sectorSize = lib.mkOption {
default = 0;
type = lib.types.ints.unsigned;
description = ''
Specify the sector size in bytes, or `0` to let
{command}`cryptsetup` pick the default.
'';
};

source = lib.mkOption {
default = "/dev/urandom";
type = lib.types.nonEmptyStr;
description = ''
Source of randomness used to generate the swap
encryption key on every boot.
'';
};

allowDiscards = lib.mkOption {
default = false;
type = lib.types.bool;
description = ''
Whether to allow TRIM requests to the underlying device.
This option has security implications, see the
upstream NixOS manual section on swap encryption.
'';
};
};
};
};
};
};
in
Expand Down