1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
# LUKS backup sticks for pika-backup, unlocked and mounted on access.
{ lib, ... }:

let
  sticks = [
    { name = "backup-stick1"; uuid = "c9c0c874-cb2c-4702-9f4c-103d196f808f"; }
  ];
in
{
  config = {
    # Each stick is unlocked and mounted on first access of /mnt/<name>,
    # unmounted after 5 idle minutes, and closed when pulled out. No prompt:
    # its LUKS key is a system credential encrypted with
    # --with-key=host+tpm2, so it needs both
    # /var/lib/systemd/credential.secret (root-only, on the encrypted root)
    # and this machine's TPM. A live system on this hardware has the TPM but
    # not the host key; a copy of the root disk elsewhere has the host key
    # but not the TPM. Secure Boot is off, so a TPM-only (PCR) binding would
    # have unlocked it for any live system. The stick's own passphrase keyslot
    # stays, for unlocking it anywhere else. Enrolling (as root, once per
    # stick; asks for its passphrase; the key is text, as
    # cryptsetup.passphrase is read as a passphrase, and only ever on tmpfs):
    #   n=<name> uuid=<uuid>      # from `sticks` above
    #   umask 077; k=/run/$n.key
    #   head --bytes=48 /dev/urandom | base64 --wrap=0 > $k
    #   cryptsetup luksAddKey /dev/disk/by-uuid/$uuid $k
    #   mkdir --parents /etc/credstore.encrypted
    #   systemd-creds encrypt --with-key=host+tpm2 --name=cryptsetup.passphrase \
    #     $k /etc/credstore.encrypted/$n.cred
    #   shred --remove $k
    # Any user's access to the mount point unlocks it; file modes decide the
    # rest (the repo directories are philip's, 0700).
    # Not "headless": systemd-cryptsetup checks it before it looks at the
    # cryptsetup.passphrase credential, so the credential would never be read.
    # The credential is tried first; only without it does a prompt come up, and
    # tries=1 plus the short timeout keep a missing one from hanging the mount.
    # The device timeout is for the stick itself: absent, access fails in 5s.
    environment.etc.crypttab.text = lib.concatMapStrings (s: ''
      ${s.name} UUID=${s.uuid} none luks,noauto,nofail,tries=1,timeout=10s,x-systemd.device-timeout=5s
    '') sticks;
    systemd.services = lib.listToAttrs (map (s: {
      name = "systemd-cryptsetup@${lib.replaceStrings [ "-" ] [ "\\x2d" ] s.name}";
      value = {
        overrideStrategy = "asDropin";
        serviceConfig.LoadCredentialEncrypted =
          "cryptsetup.passphrase:/etc/credstore.encrypted/${s.name}.cred";
      };
    }) sticks);
    fileSystems = lib.listToAttrs (map (s: {
      name = "/mnt/${s.name}";
      value = {
        device = "/dev/mapper/${s.name}";
        fsType = "ext4";
        options = [
          "noauto"
          "nofail"
          "x-systemd.automount"
          "x-systemd.idle-timeout=5min"
          # waits for the unlocked /dev/mapper device: unlocking takes ~6s
          # (argon2), so this must be well above that
          "x-systemd.device-timeout=30s"
        ];
      };
    }) sticks);
  };
}