diff options
Diffstat (limited to 'modules')
| -rw-r--r-- | modules/borgbackup/btrfs-snapshots.nix | 52 | ||||
| -rw-r--r-- | modules/borgbackup/default.nix | 206 | ||||
| -rw-r--r-- | modules/borgbackup/lvm-snapshots.nix | 133 | ||||
| -rw-r--r-- | modules/borgbackup/repokeys/borg_munin__borg.yaml | 33 | ||||
| -rw-r--r-- | modules/kill-user.nix | 13 | ||||
| -rw-r--r-- | modules/luksroot.nix | 1075 | ||||
| -rw-r--r-- | modules/networkd.nix | 297 | ||||
| -rw-r--r-- | modules/tinc-networkmanager.nix | 36 | ||||
| -rw-r--r-- | modules/uucp.nix | 391 | ||||
| -rw-r--r-- | modules/yggdrasil/default.nix | 50 | ||||
| -rw-r--r-- | modules/yggdrasil/hosts/sif/default.nix | 13 | ||||
| -rw-r--r-- | modules/yggdrasil/hosts/sif/private-keys.yaml | 34 | ||||
| -rw-r--r-- | modules/yggdrasil/hosts/ymir.nix | 19 |
13 files changed, 2352 insertions, 0 deletions
diff --git a/modules/borgbackup/btrfs-snapshots.nix b/modules/borgbackup/btrfs-snapshots.nix new file mode 100644 index 00000000..96d2b2ba --- /dev/null +++ b/modules/borgbackup/btrfs-snapshots.nix | |||
| @@ -0,0 +1,52 @@ | |||
| 1 | { config, lib, pkgs, ... }: | ||
| 2 | |||
| 3 | with lib; | ||
| 4 | |||
| 5 | let | ||
| 6 | cfg = config.services.btrfs-snapshots; | ||
| 7 | |||
| 8 | snapshotMount = str: "${str}${cfg.mountSuffix}"; | ||
| 9 | in { | ||
| 10 | options = { | ||
| 11 | |||
| 12 | services.btrfs-snapshots = { | ||
| 13 | enable = mkEnableOption "a systemd unit for btrfs snapshots"; | ||
| 14 | |||
| 15 | mountSuffix = mkOption { | ||
| 16 | type = types.str; | ||
| 17 | default = ".snapshot"; | ||
| 18 | }; | ||
| 19 | |||
| 20 | readOnly = mkOption { | ||
| 21 | type = types.bool; | ||
| 22 | default = true; | ||
| 23 | }; | ||
| 24 | |||
| 25 | persist = mkOption { | ||
| 26 | type = types.bool; | ||
| 27 | default = false; | ||
| 28 | }; | ||
| 29 | }; | ||
| 30 | |||
| 31 | }; | ||
| 32 | |||
| 33 | |||
| 34 | config = mkIf cfg.enable { | ||
| 35 | systemd.services."btrfs-snapshot@" = { | ||
| 36 | enable = true; | ||
| 37 | |||
| 38 | unitConfig = { | ||
| 39 | StopWhenUnneeded = !cfg.persist; | ||
| 40 | }; | ||
| 41 | |||
| 42 | serviceConfig = with pkgs; { | ||
| 43 | Type = "oneshot"; | ||
| 44 | ExecStartPre = "-${btrfs-progs}/bin/btrfs subvolume delete -c ${snapshotMount "%f"}"; | ||
| 45 | ExecStart = "${btrfs-progs}/bin/btrfs subvolume snapshot ${optionalString cfg.readOnly "-r"} %f ${snapshotMount "%f"}"; | ||
| 46 | RemainAfterExit = true; | ||
| 47 | ExecStop = "${btrfs-progs}/bin/btrfs subvolume delete -c ${snapshotMount "%f"}"; | ||
| 48 | }; | ||
| 49 | }; | ||
| 50 | |||
| 51 | }; | ||
| 52 | } | ||
diff --git a/modules/borgbackup/default.nix b/modules/borgbackup/default.nix new file mode 100644 index 00000000..a0419d0e --- /dev/null +++ b/modules/borgbackup/default.nix | |||
| @@ -0,0 +1,206 @@ | |||
| 1 | { config, lib, utils, pkgs, ... }: | ||
| 2 | |||
| 3 | with utils; | ||
| 4 | with lib; | ||
| 5 | |||
| 6 | let | ||
| 7 | cfg = config.services.borgbackup; | ||
| 8 | |||
| 9 | lvmPath = { | ||
| 10 | options = { | ||
| 11 | LV = mkOption { | ||
| 12 | type = types.str; | ||
| 13 | }; | ||
| 14 | VG = mkOption { | ||
| 15 | type = types.str; | ||
| 16 | }; | ||
| 17 | }; | ||
| 18 | }; | ||
| 19 | |||
| 20 | pathType = if cfg.snapshots == "lvm" then types.submodule lvmPath else types.path; | ||
| 21 | |||
| 22 | systemdPath = path: escapeSystemdPath (if cfg.snapshots == "lvm" then "${path.VG}-${path.LV}" else path); | ||
| 23 | |||
| 24 | withSuffix = path: path + (if cfg.snapshots == "btrfs" then config.services.btrfs-snapshots.mountSuffix else config.services.lvm-snapshots.mountSuffix); | ||
| 25 | |||
| 26 | mountPoint = if cfg.snapshots == "lvm" then config.services.lvm-snapshots.mountPoint else ""; | ||
| 27 | |||
| 28 | targetOptions = { | ||
| 29 | options = { | ||
| 30 | repo = mkOption { | ||
| 31 | type = types.str; | ||
| 32 | }; | ||
| 33 | |||
| 34 | paths = mkOption { | ||
| 35 | type = types.listOf pathType; | ||
| 36 | default = []; | ||
| 37 | }; | ||
| 38 | |||
| 39 | prune = mkOption { | ||
| 40 | type = types.attrsOf (types.listOf types.str); | ||
| 41 | default = {}; | ||
| 42 | }; | ||
| 43 | |||
| 44 | interval = mkOption { | ||
| 45 | type = types.str; | ||
| 46 | default = "6h"; | ||
| 47 | }; | ||
| 48 | |||
| 49 | jitter = mkOption { | ||
| 50 | type = with types; nullOr str; | ||
| 51 | default = "6h"; | ||
| 52 | }; | ||
| 53 | |||
| 54 | lock = mkOption { | ||
| 55 | type = types.nullOr types.str; | ||
| 56 | default = "backup"; | ||
| 57 | }; | ||
| 58 | |||
| 59 | network = mkOption { | ||
| 60 | type = types.bool; | ||
| 61 | default = true; | ||
| 62 | }; | ||
| 63 | |||
| 64 | lockWait = mkOption { | ||
| 65 | type = types.int; | ||
| 66 | default = 600; | ||
| 67 | }; | ||
| 68 | |||
| 69 | keyFile = mkOption { | ||
| 70 | type = types.nullOr types.path; | ||
| 71 | default = null; | ||
| 72 | }; | ||
| 73 | }; | ||
| 74 | }; | ||
| 75 | in { | ||
| 76 | disabledModules = [ "services/backup/borgbackup.nix" ]; | ||
| 77 | |||
| 78 | options = { | ||
| 79 | services.borgbackup = { | ||
| 80 | snapshots = mkOption { | ||
| 81 | type = types.nullOr (types.enum ["btrfs" "lvm"]); | ||
| 82 | default = null; | ||
| 83 | }; | ||
| 84 | |||
| 85 | targets = mkOption { | ||
| 86 | type = types.attrsOf (types.submodule targetOptions); | ||
| 87 | default = {}; | ||
| 88 | }; | ||
| 89 | |||
| 90 | prefix = mkOption { | ||
| 91 | type = types.str; | ||
| 92 | }; | ||
| 93 | }; | ||
| 94 | }; | ||
| 95 | |||
| 96 | imports = | ||
| 97 | [ ./lvm-snapshots.nix | ||
| 98 | ./btrfs-snapshots.nix | ||
| 99 | ]; | ||
| 100 | |||
| 101 | config = mkIf (any (t: t.paths != []) (attrValues cfg.targets)) { | ||
| 102 | services.btrfs-snapshots.enable = mkIf (cfg.snapshots == "btrfs") true; | ||
| 103 | |||
| 104 | services.lvm-snapshots.snapshots = mkIf (cfg.snapshots == "lvm") (listToAttrs (map (path: nameValuePair (path.VG + "-" + path.LV) { | ||
| 105 | inherit (path) LV VG; | ||
| 106 | mountName = withSuffix (path.VG + "-" + path.LV); | ||
| 107 | }) (unique (flatten (mapAttrsToList (target: tCfg: tCfg.paths) cfg.targets))))); | ||
| 108 | |||
| 109 | systemd.targets."timers-borg" = { | ||
| 110 | wantedBy = [ "timers.target" ]; | ||
| 111 | }; | ||
| 112 | |||
| 113 | systemd.slices."system-borgbackup" = {}; | ||
| 114 | |||
| 115 | systemd.timers = (listToAttrs (map ({ target, path, tCfg }: nameValuePair "borgbackup-${target}@${systemdPath path}" { | ||
| 116 | requiredBy = [ "timers-borg.target" ]; | ||
| 117 | |||
| 118 | timerConfig = { | ||
| 119 | Persistent = false; | ||
| 120 | OnBootSec = tCfg.interval; | ||
| 121 | OnUnitActiveSec = tCfg.interval; | ||
| 122 | RandomizedDelaySec = mkIf (tCfg.jitter != null) tCfg.jitter; | ||
| 123 | }; | ||
| 124 | }) (flatten (mapAttrsToList (target: tCfg: map (path: { inherit target path tCfg; }) tCfg.paths) cfg.targets)))) // (mapAttrs' (target: tCfg: nameValuePair "borgbackup-prune-${target}" { | ||
| 125 | enable = tCfg.prune != {}; | ||
| 126 | |||
| 127 | requiredBy = [ "timers-borg.target" ]; | ||
| 128 | |||
| 129 | timerConfig = { | ||
| 130 | Persistent = false; | ||
| 131 | OnBootSec = tCfg.interval; | ||
| 132 | OnUnitActiveSec = tCfg.interval; | ||
| 133 | RandomizedDelaySec = mkIf (tCfg.jitter != null) tCfg.jitter; | ||
| 134 | }; | ||
| 135 | }) cfg.targets); | ||
| 136 | |||
| 137 | systemd.services = (mapAttrs' (target: tCfg: nameValuePair "borgbackup-${target}@" (let | ||
| 138 | deps = flatten [ | ||
| 139 | (optional (cfg.snapshots == "btrfs") "btrfs-snapshot@%i.service") | ||
| 140 | (optional tCfg.network "network-online.target") | ||
| 141 | ]; | ||
| 142 | in { | ||
| 143 | bindsTo = deps; | ||
| 144 | after = deps; | ||
| 145 | |||
| 146 | path = with pkgs; [borgbackup] ++ optional (tCfg.lock != null) utillinux; | ||
| 147 | |||
| 148 | script = let | ||
| 149 | borgCmd = '' | ||
| 150 | borg create \ | ||
| 151 | --lock-wait ${toString tCfg.lockWait} \ | ||
| 152 | --stats \ | ||
| 153 | --list \ | ||
| 154 | --filter 'AME' \ | ||
| 155 | --exclude-caches \ | ||
| 156 | --keep-exclude-tags \ | ||
| 157 | --patterns-from .backup-${target} \ | ||
| 158 | --one-file-system \ | ||
| 159 | --compression auto,lzma \ | ||
| 160 | ${tCfg.repo}::${cfg.prefix}$1-{utcnow} | ||
| 161 | ''; | ||
| 162 | in if tCfg.lock == null then borgCmd else "flock -xo /var/lock/${tCfg.lock} ${borgCmd}"; | ||
| 163 | scriptArgs = if cfg.snapshots == "lvm" then "%I" else "%i"; | ||
| 164 | |||
| 165 | unitConfig = { | ||
| 166 | AssertPathIsDirectory = mkIf (tCfg.lock != null) "/var/lock"; | ||
| 167 | DefaultDependencies = false; | ||
| 168 | RequiresMountsFor = mkIf (cfg.snapshots == "lvm") [ "${mountPoint}/${withSuffix "%I"}" ]; | ||
| 169 | }; | ||
| 170 | |||
| 171 | serviceConfig = { | ||
| 172 | Type = "oneshot"; | ||
| 173 | WorkingDirectory = if (cfg.snapshots == null) then "%I" else (if (cfg.snapshots == "lvm") then "${mountPoint}/${withSuffix "%I"}" else "${withSuffix "%f"}"); | ||
| 174 | Nice = 15; | ||
| 175 | IOSchedulingClass = 2; | ||
| 176 | IOSchedulingPriority = 7; | ||
| 177 | SuccessExitStatus = [1 2]; | ||
| 178 | Slice = "system-borgbackup.slice"; | ||
| 179 | Environment = lib.mkIf (tCfg.keyFile != null) "BORG_KEY_FILE=${tCfg.keyFile}"; | ||
| 180 | }; | ||
| 181 | })) cfg.targets) // (mapAttrs' (target: tCfg: nameValuePair "borgbackup-prune-${target}" { | ||
| 182 | enable = tCfg.prune != {}; | ||
| 183 | |||
| 184 | bindsTo = ["network-online.target"]; | ||
| 185 | after = ["network-online.target"]; | ||
| 186 | |||
| 187 | path = with pkgs; [borgbackup]; | ||
| 188 | |||
| 189 | script = concatStringsSep "\n" (mapAttrsToList (path: args: '' | ||
| 190 | borg prune \ | ||
| 191 | --lock-wait ${toString tCfg.lockWait} \ | ||
| 192 | --list \ | ||
| 193 | --stats \ | ||
| 194 | --prefix ${escapeShellArg "${cfg.prefix}${path}"} \ | ||
| 195 | ${escapeShellArgs args} \ | ||
| 196 | ${tCfg.repo} | ||
| 197 | '') tCfg.prune); | ||
| 198 | |||
| 199 | serviceConfig = { | ||
| 200 | Type = "oneshot"; | ||
| 201 | Slice = "system-borgbackup.slice"; | ||
| 202 | Environment = lib.mkIf (tCfg.keyFile != null) "BORG_KEY_FILE=${tCfg.keyFile}"; | ||
| 203 | }; | ||
| 204 | }) cfg.targets); | ||
| 205 | }; | ||
| 206 | } | ||
diff --git a/modules/borgbackup/lvm-snapshots.nix b/modules/borgbackup/lvm-snapshots.nix new file mode 100644 index 00000000..9b2a6562 --- /dev/null +++ b/modules/borgbackup/lvm-snapshots.nix | |||
| @@ -0,0 +1,133 @@ | |||
| 1 | { config, lib, utils, pkgs, ... }: | ||
| 2 | |||
| 3 | with utils; | ||
| 4 | with lib; | ||
| 5 | |||
| 6 | let | ||
| 7 | cfg = config.services.lvm-snapshots; | ||
| 8 | |||
| 9 | snapshotMount = name: "${cfg.mountPoint}/${if isNull cfg.snapshots."${name}".mountName then name else cfg.snapshots."${name}".mountName}"; | ||
| 10 | snapshotName = name: "${name}-${cfg.mountSuffix}"; | ||
| 11 | |||
| 12 | snapshotConfig = { | ||
| 13 | options = { | ||
| 14 | LV = mkOption { | ||
| 15 | type = types.str; | ||
| 16 | }; | ||
| 17 | |||
| 18 | VG = mkOption { | ||
| 19 | type = types.str; | ||
| 20 | }; | ||
| 21 | |||
| 22 | mountName = mkOption { | ||
| 23 | type = types.nullOr types.str; | ||
| 24 | default = null; | ||
| 25 | }; | ||
| 26 | |||
| 27 | cowSize = mkOption { | ||
| 28 | type = types.str; | ||
| 29 | default = "-l20%ORIGIN"; | ||
| 30 | }; | ||
| 31 | |||
| 32 | readOnly = mkOption { | ||
| 33 | type = types.bool; | ||
| 34 | default = true; | ||
| 35 | }; | ||
| 36 | |||
| 37 | persist = mkOption { | ||
| 38 | type = types.bool; | ||
| 39 | default = false; | ||
| 40 | }; | ||
| 41 | }; | ||
| 42 | }; | ||
| 43 | in { | ||
| 44 | options = { | ||
| 45 | |||
| 46 | services.lvm-snapshots = { | ||
| 47 | snapshots = mkOption { | ||
| 48 | type = types.attrsOf (types.submodule snapshotConfig); | ||
| 49 | default = {}; | ||
| 50 | }; | ||
| 51 | |||
| 52 | mountPoint = mkOption { | ||
| 53 | type = types.path; | ||
| 54 | default = "/mnt"; | ||
| 55 | }; | ||
| 56 | |||
| 57 | mountSuffix = mkOption { | ||
| 58 | type = types.str; | ||
| 59 | default = "-snapshot"; | ||
| 60 | }; | ||
| 61 | }; | ||
| 62 | }; | ||
| 63 | |||
| 64 | |||
| 65 | config = mkIf (cfg != {}) { | ||
| 66 | |||
| 67 | boot.kernelModules = [ "dm_snapshot" ]; | ||
| 68 | |||
| 69 | # system.activationScripts = mapAttrs' (name: scfg: nameValuePair ("lvm-mountpoint" + name) '' | ||
| 70 | # mkdir -p ${snapshotMount name} | ||
| 71 | # '') cfg.snapshots; | ||
| 72 | |||
| 73 | systemd.services = mapAttrs' (name: scfg: nameValuePair ("lvm-snapshot@" + escapeSystemdPath name) { | ||
| 74 | enable = true; | ||
| 75 | |||
| 76 | description = "LVM-snapshot of ${scfg.VG}/${scfg.LV}"; | ||
| 77 | |||
| 78 | bindsTo = ["${escapeSystemdPath "/dev/${scfg.VG}/${scfg.LV}"}.device"]; | ||
| 79 | after = ["${escapeSystemdPath "/dev/${scfg.VG}/${scfg.LV}"}.device"]; | ||
| 80 | |||
| 81 | unitConfig = { | ||
| 82 | StopWhenUnneeded = !scfg.persist; | ||
| 83 | AssertPathIsDirectory = "/var/lock"; | ||
| 84 | }; | ||
| 85 | |||
| 86 | path = with pkgs; [ devicemapper utillinux ]; | ||
| 87 | |||
| 88 | script = '' | ||
| 89 | ( | ||
| 90 | flock -xn -E 4 9 | ||
| 91 | if [[ "$?" -ne 0 ]]; then | ||
| 92 | exit $? | ||
| 93 | fi | ||
| 94 | |||
| 95 | lvcreate -s ${scfg.cowSize} --name ${snapshotName name} ${scfg.VG}/${scfg.LV} | ||
| 96 | |||
| 97 | sleep infinity & | ||
| 98 | ) 9>/var/lock/lvm-snapshot.${scfg.VG} | ||
| 99 | ''; | ||
| 100 | |||
| 101 | preStart = '' | ||
| 102 | lvremove -f ${scfg.VG}/${snapshotName name} | ||
| 103 | ''; | ||
| 104 | |||
| 105 | preStop = '' | ||
| 106 | lvremove -f ${scfg.VG}/${snapshotName name} | ||
| 107 | ''; | ||
| 108 | |||
| 109 | serviceConfig = with pkgs; { | ||
| 110 | Type = "forking"; | ||
| 111 | RestartForceExitStatus = [ "4" ]; | ||
| 112 | RestartSec = "5min"; | ||
| 113 | }; | ||
| 114 | }) cfg.snapshots; | ||
| 115 | |||
| 116 | systemd.mounts = mapAttrsToList (name: scfg: { | ||
| 117 | enable = true; | ||
| 118 | |||
| 119 | unitConfig = { | ||
| 120 | # AssertPathIsDirectory = snapshotMount name; | ||
| 121 | StopWhenUnneeded = !scfg.persist; | ||
| 122 | }; | ||
| 123 | |||
| 124 | bindsTo = [ ("lvm-snapshot@" + escapeSystemdPath name + ".service") ]; | ||
| 125 | after = [ ("lvm-snapshot@" + escapeSystemdPath name + ".service") ]; | ||
| 126 | |||
| 127 | options = concatStringsSep "," ([ "noauto" ] ++ optional scfg.readOnly "ro"); | ||
| 128 | |||
| 129 | where = snapshotMount name; | ||
| 130 | what = "/dev/" + scfg.VG + "/" + snapshotName name; | ||
| 131 | }) cfg.snapshots; | ||
| 132 | }; | ||
| 133 | } | ||
diff --git a/modules/borgbackup/repokeys/borg_munin__borg.yaml b/modules/borgbackup/repokeys/borg_munin__borg.yaml new file mode 100644 index 00000000..f302fe06 --- /dev/null +++ b/modules/borgbackup/repokeys/borg_munin__borg.yaml | |||
| @@ -0,0 +1,33 @@ | |||
| 1 | key: ENC[AES256_GCM,data:mxh+Jtxx+HyD246yPwo0vy7vSTz3IG8VmfbxPMwqJRreh9ZwkGnH5aCTDOvWOHIrkmzaRMF3oCi1P8D29+abMUZdt0MuJ3UE6iL8+SXlflR+WACgALM2Df+x9B3BwQM3yeoCiWG+ebr0iQPHM3jqqpkjoRv1CcythxG2deZueur9lzgC2CwG1g3O8Prnl9z0JQGOa+gjic8Zwfn38B1BECeNPrbjzICGBOrSbN/6EnfBDygI2QzseamzK2I6R6jT+QxHvkl+Zi1m2TRB+4o82VgTjPhIReJyT7PrlDnUyrKObhCOlb3v+LiSdp16IPIDVs968kyDzgyi7QPOpGr+5tutWCZrau5xhPDrONKByl/0nVVwEZfRIYATvEXtn5okJru/mglcpeD0I7AtLt+Vfv9CB9pQczvkHo0cDtgudQDf9ADt/nkmqHugm5VfMg9m9aGbKqzXt6pPOMsXSbS43K7wgDaduLZ/PW4Ookx9gTNLtJHnZ64GBorOv4QSrZIZF8pE1FsQdUhmp/YzVhaNBnjCr+Jh77sYjoOwzF77Xy+VP2C/yVIf492P+FcgkSj6XhYYqHffpFW9l/xmUvyQF5gjj2k5T21UvgChhI1HeLPzQ7W9+xuGSMtg58aD/VPe1loCy8zLITNl71bneararRS5vItoZyzMdmIRMLAZD1klPmDNe1yufTpubOXzNYbWUqFUZtwH/mDL5GRZBD9dqs2b3F26c1CUyw==,iv:NJBHesKSZ1zuKk8qHnYKqIwMnFkH+rkQD1bam5XpLXU=,tag:EiYbIFY/r/eTSTJIhYV+GA==,type:str] | ||
| 2 | sops: | ||
| 3 | kms: [] | ||
| 4 | gcp_kms: [] | ||
| 5 | azure_kv: [] | ||
| 6 | hc_vault: [] | ||
| 7 | lastmodified: '2021-01-02T20:38:48Z' | ||
| 8 | mac: ENC[AES256_GCM,data:3rkFTOk3r2dx3hOqu1u7XIIibTDfqNlRcWY9X2N/LFa/BKojgDt5tcpbphV4HqWvl8nS+fPcVrIElJfQ/QGFEOx68G95BhByntT9+JhSbHJt73dGnCSroZCw5QefdydREGvA5n00Vo9yT9IMvQsQbmpRzo6hcrSSUvagZqmZckA=,iv:F/HllDzyxgulIWZbfz9bFKR+SFg4PoaUYZ5N5hfIzw0=,tag:h2NXmvj/thhBg1rIkwdXXA==,type:str] | ||
| 9 | pgp: | ||
| 10 | - created_at: '2021-01-02T20:38:09Z' | ||
| 11 | enc: | | ||
| 12 | -----BEGIN PGP MESSAGE----- | ||
| 13 | |||
| 14 | hF4Dgwm4NZSaLAcSAQdAwmvyXlr9MyfPfLgkfQkoktKBV2WA2xhZrGL7NeeGfhAw | ||
| 15 | REk+clJ9WgiJ0iceRAONPnEjeiK0J6Fsj+5Ulq8flFGkoj5Pta0pm/9fudKmcPdC | ||
| 16 | 0l4BF0G5LSpG1EmY+LmVdSdas16rWgthnojoXPvbbHG6jZs3aDETshdiN8Bdlqsf | ||
| 17 | aVhq2LYzscnYezNcdernR4uojtiFny8qcmdF3tFacr+mkgfgIQr0W9yWFhDH15gm | ||
| 18 | =4TwU | ||
| 19 | -----END PGP MESSAGE----- | ||
| 20 | fp: F1AF20B9511B63F681A14E8D51AEFBCD1DEF68F8 | ||
| 21 | - created_at: '2021-01-02T20:38:09Z' | ||
| 22 | enc: | | ||
| 23 | -----BEGIN PGP MESSAGE----- | ||
| 24 | |||
| 25 | hF4DXxoViZlp6dISAQdAruPXj9IsllEN7R5jk4gF7bW0ZirhvX7qsu22/6HbSw8w | ||
| 26 | 66RwN3WGjYO1CcVbHKuLqVVaUBCnrR/4XHN0JYUaqjubrSZBTWFKTBFsKSTT0LZq | ||
| 27 | 0l4BKcsXrbGpYC5+yQvg0RHJ7LplxpKOmqMY8KGckvGnVf2xg7k6wuWQREFzqwt+ | ||
| 28 | lOa3x+xFy9c0JwE8AafyKjb/cgqJiMb96lhsH57BpXJa2E39ImQbXqzDzdx2jEUt | ||
| 29 | =3rxi | ||
| 30 | -----END PGP MESSAGE----- | ||
| 31 | fp: 30D3453B8CD02FE2A3E7C78C0FB536FB87AE8F51 | ||
| 32 | unencrypted_suffix: _unencrypted | ||
| 33 | version: 3.6.1 | ||
diff --git a/modules/kill-user.nix b/modules/kill-user.nix new file mode 100644 index 00000000..dd897b36 --- /dev/null +++ b/modules/kill-user.nix | |||
| @@ -0,0 +1,13 @@ | |||
| 1 | { lib, pkgs, config, ... }: | ||
| 2 | { | ||
| 3 | options = { | ||
| 4 | systemd.kill-user.enable = lib.mkEnableOption "Systemd kill-user@ services"; | ||
| 5 | }; | ||
| 6 | |||
| 7 | config.systemd.services."kill-user@" = lib.mkIf config.systemd.kill-user.enable { | ||
| 8 | serviceConfig = { | ||
| 9 | Type = "oneshot"; | ||
| 10 | ExecStart = "${pkgs.systemd}/bin/loginctl kill-user %I"; | ||
| 11 | }; | ||
| 12 | }; | ||
| 13 | } | ||
diff --git a/modules/luksroot.nix b/modules/luksroot.nix new file mode 100644 index 00000000..abaee692 --- /dev/null +++ b/modules/luksroot.nix | |||
| @@ -0,0 +1,1075 @@ | |||
| 1 | { config, lib, pkgs, ... }: | ||
| 2 | |||
| 3 | with lib; | ||
| 4 | |||
| 5 | let | ||
| 6 | luks = config.boot.initrd.luks; | ||
| 7 | kernelPackages = config.boot.kernelPackages; | ||
| 8 | |||
| 9 | commonFunctions = '' | ||
| 10 | die() { | ||
| 11 | echo "$@" >&2 | ||
| 12 | exit 1 | ||
| 13 | } | ||
| 14 | |||
| 15 | dev_exist() { | ||
| 16 | local target="$1" | ||
| 17 | if [ -e $target ]; then | ||
| 18 | return 0 | ||
| 19 | else | ||
| 20 | local uuid=$(echo -n $target | sed -e 's,UUID=\(.*\),\1,g') | ||
| 21 | blkid --uuid $uuid >/dev/null | ||
| 22 | return $? | ||
| 23 | fi | ||
| 24 | } | ||
| 25 | |||
| 26 | wait_target() { | ||
| 27 | local name="$1" | ||
| 28 | local target="$2" | ||
| 29 | local secs="''${3:-10}" | ||
| 30 | local desc="''${4:-$name $target to appear}" | ||
| 31 | |||
| 32 | if ! dev_exist $target; then | ||
| 33 | echo -n "Waiting $secs seconds for $desc..." | ||
| 34 | local success=false; | ||
| 35 | for try in $(seq $secs); do | ||
| 36 | echo -n "." | ||
| 37 | sleep 1 | ||
| 38 | if dev_exist $target; then | ||
| 39 | success=true | ||
| 40 | break | ||
| 41 | fi | ||
| 42 | done | ||
| 43 | if [ $success == true ]; then | ||
| 44 | echo " - success"; | ||
| 45 | return 0 | ||
| 46 | else | ||
| 47 | echo " - failure"; | ||
| 48 | return 1 | ||
| 49 | fi | ||
| 50 | fi | ||
| 51 | return 0 | ||
| 52 | } | ||
| 53 | |||
| 54 | wait_yubikey() { | ||
| 55 | local secs="''${1:-10}" | ||
| 56 | |||
| 57 | ykinfo -v 1>/dev/null 2>&1 | ||
| 58 | if [ $? != 0 ]; then | ||
| 59 | echo -n "Waiting $secs seconds for YubiKey to appear..." | ||
| 60 | local success=false | ||
| 61 | for try in $(seq $secs); do | ||
| 62 | echo -n . | ||
| 63 | sleep 1 | ||
| 64 | ykinfo -v 1>/dev/null 2>&1 | ||
| 65 | if [ $? == 0 ]; then | ||
| 66 | success=true | ||
| 67 | break | ||
| 68 | fi | ||
| 69 | done | ||
| 70 | if [ $success == true ]; then | ||
| 71 | echo " - success"; | ||
| 72 | return 0 | ||
| 73 | else | ||
| 74 | echo " - failure"; | ||
| 75 | return 1 | ||
| 76 | fi | ||
| 77 | fi | ||
| 78 | return 0 | ||
| 79 | } | ||
| 80 | |||
| 81 | wait_gpgcard() { | ||
| 82 | local secs="''${1:-10}" | ||
| 83 | |||
| 84 | gpg --card-status > /dev/null 2> /dev/null | ||
| 85 | if [ $? != 0 ]; then | ||
| 86 | echo -n "Waiting $secs seconds for GPG Card to appear" | ||
| 87 | local success=false | ||
| 88 | for try in $(seq $secs); do | ||
| 89 | echo -n . | ||
| 90 | sleep 1 | ||
| 91 | gpg --card-status > /dev/null 2> /dev/null | ||
| 92 | if [ $? == 0 ]; then | ||
| 93 | success=true | ||
| 94 | break | ||
| 95 | fi | ||
| 96 | done | ||
| 97 | if [ $success == true ]; then | ||
| 98 | echo " - success"; | ||
| 99 | return 0 | ||
| 100 | else | ||
| 101 | echo " - failure"; | ||
| 102 | return 1 | ||
| 103 | fi | ||
| 104 | fi | ||
| 105 | return 0 | ||
| 106 | } | ||
| 107 | ''; | ||
| 108 | |||
| 109 | preCommands = '' | ||
| 110 | # A place to store crypto things | ||
| 111 | |||
| 112 | # A ramfs is used here to ensure that the file used to update | ||
| 113 | # the key slot with cryptsetup will never get swapped out. | ||
| 114 | # Warning: Do NOT replace with tmpfs! | ||
| 115 | mkdir -p /crypt-ramfs | ||
| 116 | mount -t ramfs none /crypt-ramfs | ||
| 117 | |||
| 118 | # Cryptsetup locking directory | ||
| 119 | mkdir -p /run/cryptsetup | ||
| 120 | |||
| 121 | # For YubiKey salt storage | ||
| 122 | mkdir -p /crypt-storage | ||
| 123 | |||
| 124 | ${optionalString luks.gpgSupport '' | ||
| 125 | export GPG_TTY=$(tty) | ||
| 126 | export GNUPGHOME=/crypt-ramfs/.gnupg | ||
| 127 | |||
| 128 | gpg-agent --daemon --scdaemon-program $out/bin/scdaemon > /dev/null 2> /dev/null | ||
| 129 | ''} | ||
| 130 | |||
| 131 | # Disable all input echo for the whole stage. We could use read -s | ||
| 132 | # instead but that would ocasionally leak characters between read | ||
| 133 | # invocations. | ||
| 134 | stty -echo | ||
| 135 | ''; | ||
| 136 | |||
| 137 | postCommands = '' | ||
| 138 | stty echo | ||
| 139 | umount /crypt-storage 2>/dev/null | ||
| 140 | umount /crypt-ramfs 2>/dev/null | ||
| 141 | ''; | ||
| 142 | |||
| 143 | openCommand = name': { name, device, header, keyFile, keyFileSize, keyFileOffset, allowDiscards, yubikey, gpgCard, fido2, clevis, dmi, fallbackToPassword, preOpenCommands, postOpenCommands, ... }: assert name' == name; | ||
| 144 | let | ||
| 145 | csopen = "cryptsetup luksOpen ${device} ${name} ${optionalString allowDiscards "--allow-discards"} ${optionalString (header != null) "--header=${header}"}"; | ||
| 146 | cschange = "cryptsetup luksChangeKey ${device} ${optionalString (header != null) "--header=${header}"}"; | ||
| 147 | in '' | ||
| 148 | # Wait for luksRoot (and optionally keyFile and/or header) to appear, e.g. | ||
| 149 | # if on a USB drive. | ||
| 150 | wait_target "device" ${device} || die "${device} is unavailable" | ||
| 151 | |||
| 152 | ${optionalString (header != null) '' | ||
| 153 | wait_target "header" ${header} || die "${header} is unavailable" | ||
| 154 | ''} | ||
| 155 | |||
| 156 | do_open_passphrase() { | ||
| 157 | local passphrase | ||
| 158 | |||
| 159 | while true; do | ||
| 160 | echo -n "Passphrase for ${device}: " | ||
| 161 | passphrase= | ||
| 162 | while true; do | ||
| 163 | if [ -e /crypt-ramfs/passphrase ]; then | ||
| 164 | echo "reused" | ||
| 165 | passphrase=$(cat /crypt-ramfs/passphrase) | ||
| 166 | break | ||
| 167 | else | ||
| 168 | # ask cryptsetup-askpass | ||
| 169 | echo -n "${device}" > /crypt-ramfs/device | ||
| 170 | |||
| 171 | # and try reading it from /dev/console with a timeout | ||
| 172 | IFS= read -t 1 -r passphrase | ||
| 173 | if [ -n "$passphrase" ]; then | ||
| 174 | ${if luks.reusePassphrases then '' | ||
| 175 | # remember it for the next device | ||
| 176 | echo -n "$passphrase" > /crypt-ramfs/passphrase | ||
| 177 | '' else '' | ||
| 178 | # Don't save it to ramfs. We are very paranoid | ||
| 179 | ''} | ||
| 180 | echo | ||
| 181 | break | ||
| 182 | fi | ||
| 183 | fi | ||
| 184 | done | ||
| 185 | echo -n "Verifying passphrase for ${device}..." | ||
| 186 | echo -n "$passphrase" | ${csopen} --key-file=- | ||
| 187 | if [ $? == 0 ]; then | ||
| 188 | echo " - success" | ||
| 189 | ${if luks.reusePassphrases then '' | ||
| 190 | # we don't rm here because we might reuse it for the next device | ||
| 191 | '' else '' | ||
| 192 | rm -f /crypt-ramfs/passphrase | ||
| 193 | ''} | ||
| 194 | break | ||
| 195 | else | ||
| 196 | echo " - failure" | ||
| 197 | # ask for a different one | ||
| 198 | rm -f /crypt-ramfs/passphrase | ||
| 199 | fi | ||
| 200 | done | ||
| 201 | } | ||
| 202 | |||
| 203 | # LUKS | ||
| 204 | open_normally() { | ||
| 205 | ${if (keyFile != null) then '' | ||
| 206 | if wait_target "key file" ${keyFile}; then | ||
| 207 | ${csopen} --key-file=${keyFile} \ | ||
| 208 | ${optionalString (keyFileSize != null) "--keyfile-size=${toString keyFileSize}"} \ | ||
| 209 | ${optionalString (keyFileOffset != null) "--keyfile-offset=${toString keyFileOffset}"} | ||
| 210 | else | ||
| 211 | ${if fallbackToPassword then "echo" else "die"} "${keyFile} is unavailable" | ||
| 212 | echo " - failing back to interactive password prompt" | ||
| 213 | do_open_passphrase | ||
| 214 | fi | ||
| 215 | '' else '' | ||
| 216 | do_open_passphrase | ||
| 217 | ''} | ||
| 218 | } | ||
| 219 | |||
| 220 | ${optionalString (luks.yubikeySupport && (yubikey != null)) '' | ||
| 221 | # YubiKey | ||
| 222 | rbtohex() { | ||
| 223 | ( od -An -vtx1 | tr -d ' \n' ) | ||
| 224 | } | ||
| 225 | |||
| 226 | hextorb() { | ||
| 227 | ( tr '[:lower:]' '[:upper:]' | sed -e 's/\([0-9A-F]\{2\}\)/\\\\\\x\1/gI' | xargs printf ) | ||
| 228 | } | ||
| 229 | |||
| 230 | do_open_yubikey() { | ||
| 231 | # Make all of these local to this function | ||
| 232 | # to prevent their values being leaked | ||
| 233 | local salt | ||
| 234 | local iterations | ||
| 235 | local k_user | ||
| 236 | local challenge | ||
| 237 | local response | ||
| 238 | local k_luks | ||
| 239 | local opened | ||
| 240 | local new_salt | ||
| 241 | local new_iterations | ||
| 242 | local new_challenge | ||
| 243 | local new_response | ||
| 244 | local new_k_luks | ||
| 245 | |||
| 246 | mount -t ${yubikey.storage.fsType} ${yubikey.storage.device} /crypt-storage || \ | ||
| 247 | die "Failed to mount YubiKey salt storage device" | ||
| 248 | |||
| 249 | salt="$(cat /crypt-storage${yubikey.storage.path} | sed -n 1p | tr -d '\n')" | ||
| 250 | iterations="$(cat /crypt-storage${yubikey.storage.path} | sed -n 2p | tr -d '\n')" | ||
| 251 | challenge="$(echo -n $salt | openssl-wrap dgst -binary -sha512 | rbtohex)" | ||
| 252 | response="$(ykchalresp -${toString yubikey.slot} -x $challenge 2>/dev/null)" | ||
| 253 | |||
| 254 | for try in $(seq 3); do | ||
| 255 | ${optionalString yubikey.twoFactor '' | ||
| 256 | echo -n "Enter two-factor passphrase: " | ||
| 257 | k_user= | ||
| 258 | while true; do | ||
| 259 | if [ -e /crypt-ramfs/passphrase ]; then | ||
| 260 | echo "reused" | ||
| 261 | k_user=$(cat /crypt-ramfs/passphrase) | ||
| 262 | break | ||
| 263 | else | ||
| 264 | # Try reading it from /dev/console with a timeout | ||
| 265 | IFS= read -t 1 -r k_user | ||
| 266 | if [ -n "$k_user" ]; then | ||
| 267 | ${if luks.reusePassphrases then '' | ||
| 268 | # Remember it for the next device | ||
| 269 | echo -n "$k_user" > /crypt-ramfs/passphrase | ||
| 270 | '' else '' | ||
| 271 | # Don't save it to ramfs. We are very paranoid | ||
| 272 | ''} | ||
| 273 | echo | ||
| 274 | break | ||
| 275 | fi | ||
| 276 | fi | ||
| 277 | done | ||
| 278 | ''} | ||
| 279 | |||
| 280 | if [ ! -z "$k_user" ]; then | ||
| 281 | k_luks="$(echo -n $k_user | pbkdf2-sha512 ${toString yubikey.keyLength} $iterations $response | rbtohex)" | ||
| 282 | else | ||
| 283 | k_luks="$(echo | pbkdf2-sha512 ${toString yubikey.keyLength} $iterations $response | rbtohex)" | ||
| 284 | fi | ||
| 285 | |||
| 286 | echo -n "$k_luks" | hextorb | ${csopen} --key-file=- | ||
| 287 | |||
| 288 | if [ $? == 0 ]; then | ||
| 289 | opened=true | ||
| 290 | ${if luks.reusePassphrases then '' | ||
| 291 | # We don't rm here because we might reuse it for the next device | ||
| 292 | '' else '' | ||
| 293 | rm -f /crypt-ramfs/passphrase | ||
| 294 | ''} | ||
| 295 | break | ||
| 296 | else | ||
| 297 | opened=false | ||
| 298 | echo "Authentication failed!" | ||
| 299 | fi | ||
| 300 | done | ||
| 301 | |||
| 302 | [ "$opened" == false ] && die "Maximum authentication errors reached" | ||
| 303 | |||
| 304 | echo -n "Gathering entropy for new salt (please enter random keys to generate entropy if this blocks for long)..." | ||
| 305 | for i in $(seq ${toString yubikey.saltLength}); do | ||
| 306 | byte="$(dd if=/dev/random bs=1 count=1 2>/dev/null | rbtohex)"; | ||
| 307 | new_salt="$new_salt$byte"; | ||
| 308 | echo -n . | ||
| 309 | done; | ||
| 310 | echo "ok" | ||
| 311 | |||
| 312 | new_iterations="$iterations" | ||
| 313 | ${optionalString (yubikey.iterationStep > 0) '' | ||
| 314 | new_iterations="$(($new_iterations + ${toString yubikey.iterationStep}))" | ||
| 315 | ''} | ||
| 316 | |||
| 317 | new_challenge="$(echo -n $new_salt | openssl-wrap dgst -binary -sha512 | rbtohex)" | ||
| 318 | |||
| 319 | new_response="$(ykchalresp -${toString yubikey.slot} -x $new_challenge 2>/dev/null)" | ||
| 320 | |||
| 321 | if [ ! -z "$k_user" ]; then | ||
| 322 | new_k_luks="$(echo -n $k_user | pbkdf2-sha512 ${toString yubikey.keyLength} $new_iterations $new_response | rbtohex)" | ||
| 323 | else | ||
| 324 | new_k_luks="$(echo | pbkdf2-sha512 ${toString yubikey.keyLength} $new_iterations $new_response | rbtohex)" | ||
| 325 | fi | ||
| 326 | |||
| 327 | echo -n "$new_k_luks" | hextorb > /crypt-ramfs/new_key | ||
| 328 | echo -n "$k_luks" | hextorb | ${cschange} --key-file=- /crypt-ramfs/new_key | ||
| 329 | |||
| 330 | if [ $? == 0 ]; then | ||
| 331 | echo -ne "$new_salt\n$new_iterations" > /crypt-storage${yubikey.storage.path} | ||
| 332 | else | ||
| 333 | echo "Warning: Could not update LUKS key, current challenge persists!" | ||
| 334 | fi | ||
| 335 | |||
| 336 | rm -f /crypt-ramfs/new_key | ||
| 337 | umount /crypt-storage | ||
| 338 | } | ||
| 339 | |||
| 340 | open_with_hardware() { | ||
| 341 | if wait_yubikey ${toString yubikey.gracePeriod}; then | ||
| 342 | do_open_yubikey | ||
| 343 | else | ||
| 344 | echo "No YubiKey found, falling back to non-YubiKey open procedure" | ||
| 345 | open_normally | ||
| 346 | fi | ||
| 347 | } | ||
| 348 | ''} | ||
| 349 | |||
| 350 | ${optionalString (luks.gpgSupport && (gpgCard != null)) '' | ||
| 351 | |||
| 352 | do_open_gpg_card() { | ||
| 353 | # Make all of these local to this function | ||
| 354 | # to prevent their values being leaked | ||
| 355 | local pin | ||
| 356 | local opened | ||
| 357 | |||
| 358 | gpg --import /gpg-keys/${device}/pubkey.asc > /dev/null 2> /dev/null | ||
| 359 | |||
| 360 | gpg --card-status > /dev/null 2> /dev/null | ||
| 361 | |||
| 362 | for try in $(seq 3); do | ||
| 363 | echo -n "PIN for GPG Card associated with device ${device}: " | ||
| 364 | pin= | ||
| 365 | while true; do | ||
| 366 | if [ -e /crypt-ramfs/passphrase ]; then | ||
| 367 | echo "reused" | ||
| 368 | pin=$(cat /crypt-ramfs/passphrase) | ||
| 369 | break | ||
| 370 | else | ||
| 371 | # and try reading it from /dev/console with a timeout | ||
| 372 | IFS= read -t 1 -r pin | ||
| 373 | if [ -n "$pin" ]; then | ||
| 374 | ${if luks.reusePassphrases then '' | ||
| 375 | # remember it for the next device | ||
| 376 | echo -n "$pin" > /crypt-ramfs/passphrase | ||
| 377 | '' else '' | ||
| 378 | # Don't save it to ramfs. We are very paranoid | ||
| 379 | ''} | ||
| 380 | echo | ||
| 381 | break | ||
| 382 | fi | ||
| 383 | fi | ||
| 384 | done | ||
| 385 | echo -n "Verifying passphrase for ${device}..." | ||
| 386 | echo -n "$pin" | gpg -q --batch --passphrase-fd 0 --pinentry-mode loopback -d /gpg-keys/${device}/cryptkey.gpg 2> /dev/null | ${csopen} --key-file=- > /dev/null 2> /dev/null | ||
| 387 | if [ $? == 0 ]; then | ||
| 388 | echo " - success" | ||
| 389 | ${if luks.reusePassphrases then '' | ||
| 390 | # we don't rm here because we might reuse it for the next device | ||
| 391 | '' else '' | ||
| 392 | rm -f /crypt-ramfs/passphrase | ||
| 393 | ''} | ||
| 394 | break | ||
| 395 | else | ||
| 396 | echo " - failure" | ||
| 397 | # ask for a different one | ||
| 398 | rm -f /crypt-ramfs/passphrase | ||
| 399 | fi | ||
| 400 | done | ||
| 401 | |||
| 402 | [ "$opened" == false ] && die "Maximum authentication errors reached" | ||
| 403 | } | ||
| 404 | |||
| 405 | open_with_hardware() { | ||
| 406 | if wait_gpgcard ${toString gpgCard.gracePeriod}; then | ||
| 407 | do_open_gpg_card | ||
| 408 | else | ||
| 409 | echo "No GPG Card found, falling back to normal open procedure" | ||
| 410 | open_normally | ||
| 411 | fi | ||
| 412 | } | ||
| 413 | ''} | ||
| 414 | |||
| 415 | ${optionalString (luks.fido2Support && (fido2.credential != null)) '' | ||
| 416 | |||
| 417 | open_with_hardware() { | ||
| 418 | local passsphrase | ||
| 419 | |||
| 420 | ${if fido2.passwordLess then '' | ||
| 421 | export passphrase="" | ||
| 422 | '' else '' | ||
| 423 | read -rsp "FIDO2 salt for ${device}: " passphrase | ||
| 424 | echo | ||
| 425 | ''} | ||
| 426 | ${optionalString (lib.versionOlder kernelPackages.kernel.version "5.4") '' | ||
| 427 | echo "On systems with Linux Kernel < 5.4, it might take a while to initialize the CRNG, you might want to use linuxPackages_latest." | ||
| 428 | echo "Please move your mouse to create needed randomness." | ||
| 429 | ''} | ||
| 430 | echo "Waiting for your FIDO2 device..." | ||
| 431 | fido2luks open ${device} ${name} ${fido2.credential} --await-dev ${toString fido2.gracePeriod} --salt string:$passphrase | ||
| 432 | if [ $? -ne 0 ]; then | ||
| 433 | echo "No FIDO2 key found, falling back to normal open procedure" | ||
| 434 | open_normally | ||
| 435 | fi | ||
| 436 | } | ||
| 437 | ''} | ||
| 438 | |||
| 439 | ${optionalString (luks.clevisSupport && clevis) '' | ||
| 440 | |||
| 441 | open_with_hardware() { | ||
| 442 | mkdir -p /crypt-ramfs/clevis | ||
| 443 | |||
| 444 | TMPDIR=/crypt-ramfs/clevis clevis luks unlock -d ${device} -n ${name} | ||
| 445 | |||
| 446 | if [ $? -ne 0 ]; then | ||
| 447 | echo "Unlocking with clevis failed, falling back to normal open procedure" | ||
| 448 | open_normally | ||
| 449 | fi | ||
| 450 | } | ||
| 451 | |||
| 452 | ''} | ||
| 453 | |||
| 454 | ${optionalString (luks.dmiSupport && dmi) '' | ||
| 455 | |||
| 456 | open_with_hardware() { | ||
| 457 | dmidecode -s system-uuid > /crypt-ramfs/passphrase | ||
| 458 | |||
| 459 | ${csopen} --key-file=- < /crypt-ramfs/passphrase > /dev/null 2> /dev/null | ||
| 460 | |||
| 461 | if [ $? -ne 0 ]; then | ||
| 462 | echo "Unlocking with system-uuid failed, falling back to normal open procedure" | ||
| 463 | rm -f /crypt-ramfs/passphrase | ||
| 464 | open_normally | ||
| 465 | ${optionalString (!luks.reusePassphrases) '' | ||
| 466 | else | ||
| 467 | rm -f /crypt-ramfs/passphrase | ||
| 468 | ''} | ||
| 469 | fi | ||
| 470 | } | ||
| 471 | |||
| 472 | ''} | ||
| 473 | |||
| 474 | # commands to run right before we mount our device | ||
| 475 | ${preOpenCommands} | ||
| 476 | |||
| 477 | ${if (luks.yubikeySupport && (yubikey != null)) || (luks.gpgSupport && (gpgCard != null)) || (luks.fido2Support && (fido2.credential != null)) || (luks.clevisSupport && clevis) || (luks.dmiSupport && dmi) then '' | ||
| 478 | open_with_hardware | ||
| 479 | '' else '' | ||
| 480 | open_normally | ||
| 481 | ''} | ||
| 482 | |||
| 483 | # commands to run right after we mounted our device | ||
| 484 | ${postOpenCommands} | ||
| 485 | ''; | ||
| 486 | |||
| 487 | askPass = pkgs.writeScriptBin "cryptsetup-askpass" '' | ||
| 488 | #!/bin/sh | ||
| 489 | |||
| 490 | ${commonFunctions} | ||
| 491 | |||
| 492 | while true; do | ||
| 493 | wait_target "luks" /crypt-ramfs/device 10 "LUKS to request a passphrase" || die "Passphrase is not requested now" | ||
| 494 | device=$(cat /crypt-ramfs/device) | ||
| 495 | |||
| 496 | echo -n "Passphrase for $device: " | ||
| 497 | IFS= read -rs passphrase | ||
| 498 | echo | ||
| 499 | |||
| 500 | rm /crypt-ramfs/device | ||
| 501 | echo -n "$passphrase" > /crypt-ramfs/passphrase | ||
| 502 | done | ||
| 503 | ''; | ||
| 504 | |||
| 505 | preLVM = filterAttrs (n: v: v.preLVM) luks.devices; | ||
| 506 | postLVM = filterAttrs (n: v: !v.preLVM) luks.devices; | ||
| 507 | |||
| 508 | in | ||
| 509 | { | ||
| 510 | disabledModules = [ "system/boot/luksroot.nix" ]; | ||
| 511 | |||
| 512 | imports = [ | ||
| 513 | (mkRemovedOptionModule [ "boot" "initrd" "luks" "enable" ] "") | ||
| 514 | ]; | ||
| 515 | |||
| 516 | options = { | ||
| 517 | |||
| 518 | boot.initrd.luks.mitigateDMAAttacks = mkOption { | ||
| 519 | type = types.bool; | ||
| 520 | default = true; | ||
| 521 | description = '' | ||
| 522 | Unless enabled, encryption keys can be easily recovered by an attacker with physical | ||
| 523 | access to any machine with PCMCIA, ExpressCard, ThunderBolt or FireWire port. | ||
| 524 | More information is available at <link xlink:href="http://en.wikipedia.org/wiki/DMA_attack"/>. | ||
| 525 | |||
| 526 | This option blacklists FireWire drivers, but doesn't remove them. You can manually | ||
| 527 | load the drivers if you need to use a FireWire device, but don't forget to unload them! | ||
| 528 | ''; | ||
| 529 | }; | ||
| 530 | |||
| 531 | boot.initrd.luks.cryptoModules = mkOption { | ||
| 532 | type = types.listOf types.str; | ||
| 533 | default = | ||
| 534 | [ "aes" "aes_generic" "blowfish" "twofish" | ||
| 535 | "serpent" "cbc" "xts" "lrw" "sha1" "sha256" "sha512" | ||
| 536 | "af_alg" "algif_skcipher" | ||
| 537 | ]; | ||
| 538 | description = '' | ||
| 539 | A list of cryptographic kernel modules needed to decrypt the root device(s). | ||
| 540 | The default includes all common modules. | ||
| 541 | ''; | ||
| 542 | }; | ||
| 543 | |||
| 544 | boot.initrd.luks.forceLuksSupportInInitrd = mkOption { | ||
| 545 | type = types.bool; | ||
| 546 | default = false; | ||
| 547 | internal = true; | ||
| 548 | description = '' | ||
| 549 | Whether to configure luks support in the initrd, when no luks | ||
| 550 | devices are configured. | ||
| 551 | ''; | ||
| 552 | }; | ||
| 553 | |||
| 554 | boot.initrd.luks.reusePassphrases = mkOption { | ||
| 555 | type = types.bool; | ||
| 556 | default = true; | ||
| 557 | description = '' | ||
| 558 | When opening a new LUKS device try reusing last successful | ||
| 559 | passphrase. | ||
| 560 | |||
| 561 | Useful for mounting a number of devices that use the same | ||
| 562 | passphrase without retyping it several times. | ||
| 563 | |||
| 564 | Such setup can be useful if you use <command>cryptsetup | ||
| 565 | luksSuspend</command>. Different LUKS devices will still have | ||
| 566 | different master keys even when using the same passphrase. | ||
| 567 | ''; | ||
| 568 | }; | ||
| 569 | |||
| 570 | boot.initrd.luks.devices = mkOption { | ||
| 571 | default = { }; | ||
| 572 | example = { luksroot.device = "/dev/disk/by-uuid/430e9eff-d852-4f68-aa3b-2fa3599ebe08"; }; | ||
| 573 | description = '' | ||
| 574 | The encrypted disk that should be opened before the root | ||
| 575 | filesystem is mounted. Both LVM-over-LUKS and LUKS-over-LVM | ||
| 576 | setups are supported. The unencrypted devices can be accessed as | ||
| 577 | <filename>/dev/mapper/<replaceable>name</replaceable></filename>. | ||
| 578 | ''; | ||
| 579 | |||
| 580 | type = with types; attrsOf (submodule ( | ||
| 581 | { name, ... }: { options = { | ||
| 582 | |||
| 583 | name = mkOption { | ||
| 584 | visible = false; | ||
| 585 | default = name; | ||
| 586 | example = "luksroot"; | ||
| 587 | type = types.str; | ||
| 588 | description = "Name of the unencrypted device in <filename>/dev/mapper</filename>."; | ||
| 589 | }; | ||
| 590 | |||
| 591 | device = mkOption { | ||
| 592 | example = "/dev/disk/by-uuid/430e9eff-d852-4f68-aa3b-2fa3599ebe08"; | ||
| 593 | type = types.str; | ||
| 594 | description = "Path of the underlying encrypted block device."; | ||
| 595 | }; | ||
| 596 | |||
| 597 | header = mkOption { | ||
| 598 | default = null; | ||
| 599 | example = "/root/header.img"; | ||
| 600 | type = types.nullOr types.str; | ||
| 601 | description = '' | ||
| 602 | The name of the file or block device that | ||
| 603 | should be used as header for the encrypted device. | ||
| 604 | ''; | ||
| 605 | }; | ||
| 606 | |||
| 607 | keyFile = mkOption { | ||
| 608 | default = null; | ||
| 609 | example = "/dev/sdb1"; | ||
| 610 | type = types.nullOr types.str; | ||
| 611 | description = '' | ||
| 612 | The name of the file (can be a raw device or a partition) that | ||
| 613 | should be used as the decryption key for the encrypted device. If | ||
| 614 | not specified, you will be prompted for a passphrase instead. | ||
| 615 | ''; | ||
| 616 | }; | ||
| 617 | |||
| 618 | keyFileSize = mkOption { | ||
| 619 | default = null; | ||
| 620 | example = 4096; | ||
| 621 | type = types.nullOr types.int; | ||
| 622 | description = '' | ||
| 623 | The size of the key file. Use this if only the beginning of the | ||
| 624 | key file should be used as a key (often the case if a raw device | ||
| 625 | or partition is used as key file). If not specified, the whole | ||
| 626 | <literal>keyFile</literal> will be used decryption, instead of just | ||
| 627 | the first <literal>keyFileSize</literal> bytes. | ||
| 628 | ''; | ||
| 629 | }; | ||
| 630 | |||
| 631 | keyFileOffset = mkOption { | ||
| 632 | default = null; | ||
| 633 | example = 4096; | ||
| 634 | type = types.nullOr types.int; | ||
| 635 | description = '' | ||
| 636 | The offset of the key file. Use this in combination with | ||
| 637 | <literal>keyFileSize</literal> to use part of a file as key file | ||
| 638 | (often the case if a raw device or partition is used as a key file). | ||
| 639 | If not specified, the key begins at the first byte of | ||
| 640 | <literal>keyFile</literal>. | ||
| 641 | ''; | ||
| 642 | }; | ||
| 643 | |||
| 644 | # FIXME: get rid of this option. | ||
| 645 | preLVM = mkOption { | ||
| 646 | default = true; | ||
| 647 | type = types.bool; | ||
| 648 | description = "Whether the luksOpen will be attempted before LVM scan or after it."; | ||
| 649 | }; | ||
| 650 | |||
| 651 | allowDiscards = mkOption { | ||
| 652 | default = false; | ||
| 653 | type = types.bool; | ||
| 654 | description = '' | ||
| 655 | Whether to allow TRIM requests to the underlying device. This option | ||
| 656 | has security implications; please read the LUKS documentation before | ||
| 657 | activating it. | ||
| 658 | ''; | ||
| 659 | }; | ||
| 660 | |||
| 661 | fallbackToPassword = mkOption { | ||
| 662 | default = false; | ||
| 663 | type = types.bool; | ||
| 664 | description = '' | ||
| 665 | Whether to fallback to interactive passphrase prompt if the keyfile | ||
| 666 | cannot be found. This will prevent unattended boot should the keyfile | ||
| 667 | go missing. | ||
| 668 | ''; | ||
| 669 | }; | ||
| 670 | |||
| 671 | gpgCard = mkOption { | ||
| 672 | default = null; | ||
| 673 | description = '' | ||
| 674 | The option to use this LUKS device with a GPG encrypted luks password by the GPG Smartcard. | ||
| 675 | If null (the default), GPG-Smartcard will be disabled for this device. | ||
| 676 | ''; | ||
| 677 | |||
| 678 | type = with types; nullOr (submodule { | ||
| 679 | options = { | ||
| 680 | gracePeriod = mkOption { | ||
| 681 | default = 10; | ||
| 682 | type = types.int; | ||
| 683 | description = "Time in seconds to wait for the GPG Smartcard."; | ||
| 684 | }; | ||
| 685 | |||
| 686 | encryptedPass = mkOption { | ||
| 687 | default = ""; | ||
| 688 | type = types.path; | ||
| 689 | description = "Path to the GPG encrypted passphrase."; | ||
| 690 | }; | ||
| 691 | |||
| 692 | publicKey = mkOption { | ||
| 693 | default = ""; | ||
| 694 | type = types.path; | ||
| 695 | description = "Path to the Public Key."; | ||
| 696 | }; | ||
| 697 | }; | ||
| 698 | }); | ||
| 699 | }; | ||
| 700 | |||
| 701 | fido2 = { | ||
| 702 | credential = mkOption { | ||
| 703 | default = null; | ||
| 704 | example = "f1d00200d8dc783f7fb1e10ace8da27f8312d72692abfca2f7e4960a73f48e82e1f7571f6ebfcee9fb434f9886ccc8fcc52a6614d8d2"; | ||
| 705 | type = types.nullOr types.str; | ||
| 706 | description = "The FIDO2 credential ID."; | ||
| 707 | }; | ||
| 708 | |||
| 709 | gracePeriod = mkOption { | ||
| 710 | default = 10; | ||
| 711 | type = types.int; | ||
| 712 | description = "Time in seconds to wait for the FIDO2 key."; | ||
| 713 | }; | ||
| 714 | |||
| 715 | passwordLess = mkOption { | ||
| 716 | default = false; | ||
| 717 | type = types.bool; | ||
| 718 | description = '' | ||
| 719 | Defines whatever to use an empty string as a default salt. | ||
| 720 | |||
| 721 | Enable only when your device is PIN protected, such as <link xlink:href="https://trezor.io/">Trezor</link>. | ||
| 722 | ''; | ||
| 723 | }; | ||
| 724 | }; | ||
| 725 | |||
| 726 | yubikey = mkOption { | ||
| 727 | default = null; | ||
| 728 | description = '' | ||
| 729 | The options to use for this LUKS device in YubiKey-PBA. | ||
| 730 | If null (the default), YubiKey-PBA will be disabled for this device. | ||
| 731 | ''; | ||
| 732 | |||
| 733 | type = with types; nullOr (submodule { | ||
| 734 | options = { | ||
| 735 | twoFactor = mkOption { | ||
| 736 | default = true; | ||
| 737 | type = types.bool; | ||
| 738 | description = "Whether to use a passphrase and a YubiKey (true), or only a YubiKey (false)."; | ||
| 739 | }; | ||
| 740 | |||
| 741 | slot = mkOption { | ||
| 742 | default = 2; | ||
| 743 | type = types.int; | ||
| 744 | description = "Which slot on the YubiKey to challenge."; | ||
| 745 | }; | ||
| 746 | |||
| 747 | saltLength = mkOption { | ||
| 748 | default = 16; | ||
| 749 | type = types.int; | ||
| 750 | description = "Length of the new salt in byte (64 is the effective maximum)."; | ||
| 751 | }; | ||
| 752 | |||
| 753 | keyLength = mkOption { | ||
| 754 | default = 64; | ||
| 755 | type = types.int; | ||
| 756 | description = "Length of the LUKS slot key derived with PBKDF2 in byte."; | ||
| 757 | }; | ||
| 758 | |||
| 759 | iterationStep = mkOption { | ||
| 760 | default = 0; | ||
| 761 | type = types.int; | ||
| 762 | description = "How much the iteration count for PBKDF2 is increased at each successful authentication."; | ||
| 763 | }; | ||
| 764 | |||
| 765 | gracePeriod = mkOption { | ||
| 766 | default = 10; | ||
| 767 | type = types.int; | ||
| 768 | description = "Time in seconds to wait for the YubiKey."; | ||
| 769 | }; | ||
| 770 | |||
| 771 | /* TODO: Add to the documentation of the current module: | ||
| 772 | |||
| 773 | Options related to the storing the salt. | ||
| 774 | */ | ||
| 775 | storage = { | ||
| 776 | device = mkOption { | ||
| 777 | default = "/dev/sda1"; | ||
| 778 | type = types.path; | ||
| 779 | description = '' | ||
| 780 | An unencrypted device that will temporarily be mounted in stage-1. | ||
| 781 | Must contain the current salt to create the challenge for this LUKS device. | ||
| 782 | ''; | ||
| 783 | }; | ||
| 784 | |||
| 785 | fsType = mkOption { | ||
| 786 | default = "vfat"; | ||
| 787 | type = types.str; | ||
| 788 | description = "The filesystem of the unencrypted device."; | ||
| 789 | }; | ||
| 790 | |||
| 791 | path = mkOption { | ||
| 792 | default = "/crypt-storage/default"; | ||
| 793 | type = types.str; | ||
| 794 | description = '' | ||
| 795 | Absolute path of the salt on the unencrypted device with | ||
| 796 | that device's root directory as "/". | ||
| 797 | ''; | ||
| 798 | }; | ||
| 799 | }; | ||
| 800 | }; | ||
| 801 | }); | ||
| 802 | }; | ||
| 803 | |||
| 804 | clevis = mkOption { | ||
| 805 | type = types.bool; | ||
| 806 | default = false; | ||
| 807 | description = '' | ||
| 808 | Unlock device via clevis (e.g. with a tpm) | ||
| 809 | ''; | ||
| 810 | }; | ||
| 811 | |||
| 812 | dmi = mkOption { | ||
| 813 | type = types.bool; | ||
| 814 | default = false; | ||
| 815 | description = '' | ||
| 816 | Unlock device via system-uuid (via dmidecode) | ||
| 817 | ''; | ||
| 818 | }; | ||
| 819 | |||
| 820 | preOpenCommands = mkOption { | ||
| 821 | type = types.lines; | ||
| 822 | default = ""; | ||
| 823 | example = '' | ||
| 824 | mkdir -p /tmp/persistent | ||
| 825 | mount -t zfs rpool/safe/persistent /tmp/persistent | ||
| 826 | ''; | ||
| 827 | description = '' | ||
| 828 | Commands that should be run right before we try to mount our LUKS device. | ||
| 829 | This can be useful, if the keys needed to open the drive is on another partion. | ||
| 830 | ''; | ||
| 831 | }; | ||
| 832 | |||
| 833 | postOpenCommands = mkOption { | ||
| 834 | type = types.lines; | ||
| 835 | default = ""; | ||
| 836 | example = '' | ||
| 837 | umount /tmp/persistent | ||
| 838 | ''; | ||
| 839 | description = '' | ||
| 840 | Commands that should be run right after we have mounted our LUKS device. | ||
| 841 | ''; | ||
| 842 | }; | ||
| 843 | }; | ||
| 844 | })); | ||
| 845 | }; | ||
| 846 | |||
| 847 | boot.initrd.luks.gpgSupport = mkOption { | ||
| 848 | default = false; | ||
| 849 | type = types.bool; | ||
| 850 | description = '' | ||
| 851 | Enables support for authenticating with a GPG encrypted password. | ||
| 852 | ''; | ||
| 853 | }; | ||
| 854 | |||
| 855 | boot.initrd.luks.yubikeySupport = mkOption { | ||
| 856 | default = false; | ||
| 857 | type = types.bool; | ||
| 858 | description = '' | ||
| 859 | Enables support for authenticating with a YubiKey on LUKS devices. | ||
| 860 | See the NixOS wiki for information on how to properly setup a LUKS device | ||
| 861 | and a YubiKey to work with this feature. | ||
| 862 | ''; | ||
| 863 | }; | ||
| 864 | |||
| 865 | boot.initrd.luks.fido2Support = mkOption { | ||
| 866 | default = false; | ||
| 867 | type = types.bool; | ||
| 868 | description = '' | ||
| 869 | Enables support for authenticating with FIDO2 devices. | ||
| 870 | ''; | ||
| 871 | }; | ||
| 872 | |||
| 873 | boot.initrd.luks.clevisSupport = mkOption { | ||
| 874 | default = false; | ||
| 875 | type = types.bool; | ||
| 876 | description = '' | ||
| 877 | Enables support for unlocking luks volumes via clevis (e.g. with a tpm) | ||
| 878 | ''; | ||
| 879 | }; | ||
| 880 | |||
| 881 | boot.initrd.luks.dmiSupport = mkOption { | ||
| 882 | default = false; | ||
| 883 | type = types.bool; | ||
| 884 | description = '' | ||
| 885 | Enables support for unlocking luks volumes via system-uuid (via dmidecode) | ||
| 886 | ''; | ||
| 887 | }; | ||
| 888 | |||
| 889 | }; | ||
| 890 | |||
| 891 | config = mkIf (luks.devices != {} || luks.forceLuksSupportInInitrd) { | ||
| 892 | |||
| 893 | assertions = | ||
| 894 | [ { assertion = !(luks.gpgSupport && luks.yubikeySupport); | ||
| 895 | message = "YubiKey and GPG Card may not be used at the same time."; | ||
| 896 | } | ||
| 897 | |||
| 898 | { assertion = !(luks.gpgSupport && luks.fido2Support); | ||
| 899 | message = "FIDO2 and GPG Card may not be used at the same time."; | ||
| 900 | } | ||
| 901 | |||
| 902 | { assertion = !(luks.gpgSupport && luks.clevisSupport); | ||
| 903 | message = "Clevis and GPG Card may not be used at the same time."; | ||
| 904 | } | ||
| 905 | |||
| 906 | { assertion = !(luks.gpgSupport && luks.dmiSupport); | ||
| 907 | message = "DMI and GPG Card may not be used at the same time."; | ||
| 908 | } | ||
| 909 | |||
| 910 | { assertion = !(luks.fido2Support && luks.yubikeySupport); | ||
| 911 | message = "FIDO2 and YubiKey may not be used at the same time."; | ||
| 912 | } | ||
| 913 | |||
| 914 | { assertion = !(luks.fido2Support && luks.clevisSupport); | ||
| 915 | message = "FIDO2 and Clevis may not be used at the same time."; | ||
| 916 | } | ||
| 917 | |||
| 918 | { assertion = !(luks.fido2Support && luks.dmiSupport); | ||
| 919 | message = "FIDO2 and DMI may not be used at the same time."; | ||
| 920 | } | ||
| 921 | |||
| 922 | { assertion = !(luks.yubikeySupport && luks.clevisSupport); | ||
| 923 | message = "Clevis and YubiKey may not be used at the same time."; | ||
| 924 | } | ||
| 925 | |||
| 926 | { assertion = !(luks.yubikeySupport && luks.dmiSupport); | ||
| 927 | message = "DMI and YubiKey may not be used at the same time."; | ||
| 928 | } | ||
| 929 | |||
| 930 | ]; | ||
| 931 | |||
| 932 | # actually, sbp2 driver is the one enabling the DMA attack, but this needs to be tested | ||
| 933 | boot.blacklistedKernelModules = optionals luks.mitigateDMAAttacks | ||
| 934 | ["firewire_ohci" "firewire_core" "firewire_sbp2"]; | ||
| 935 | |||
| 936 | # Some modules that may be needed for mounting anything ciphered | ||
| 937 | boot.initrd.availableKernelModules = [ "dm_mod" "dm_crypt" "cryptd" "input_leds" ] | ||
| 938 | ++ luks.cryptoModules | ||
| 939 | # workaround until https://marc.info/?l=linux-crypto-vger&m=148783562211457&w=4 is merged | ||
| 940 | # remove once 'modprobe --show-depends xts' shows ecb as a dependency | ||
| 941 | ++ (if builtins.elem "xts" luks.cryptoModules then ["ecb"] else []); | ||
| 942 | |||
| 943 | # copy the cryptsetup binary and it's dependencies | ||
| 944 | boot.initrd.extraUtilsCommands = | ||
| 945 | let | ||
| 946 | extraUtils = config.system.build.extraUtils; | ||
| 947 | |||
| 948 | ipkgs = pkgs.appendOverlays [ | ||
| 949 | (final: prev: { | ||
| 950 | tpm2-tss = prev.tpm2-tss.overrideAttrs (oldAttrs: { | ||
| 951 | doCheck = false; | ||
| 952 | patches = []; | ||
| 953 | postPatch = '' | ||
| 954 | patchShebangs script | ||
| 955 | ''; | ||
| 956 | configureFlags = []; | ||
| 957 | }); | ||
| 958 | }) | ||
| 959 | ]; | ||
| 960 | in '' | ||
| 961 | copy_bin_and_libs ${pkgs.cryptsetup}/bin/cryptsetup | ||
| 962 | copy_bin_and_libs ${askPass}/bin/cryptsetup-askpass | ||
| 963 | sed -i s,/bin/sh,$out/bin/sh, $out/bin/cryptsetup-askpass | ||
| 964 | |||
| 965 | ${optionalString luks.yubikeySupport '' | ||
| 966 | copy_bin_and_libs ${pkgs.yubikey-personalization}/bin/ykchalresp | ||
| 967 | copy_bin_and_libs ${pkgs.yubikey-personalization}/bin/ykinfo | ||
| 968 | copy_bin_and_libs ${pkgs.openssl.bin}/bin/openssl | ||
| 969 | |||
| 970 | cc -O3 -I${pkgs.openssl.dev}/include -L${pkgs.openssl.out}/lib ${./pbkdf2-sha512.c} -o pbkdf2-sha512 -lcrypto | ||
| 971 | strip -s pbkdf2-sha512 | ||
| 972 | copy_bin_and_libs pbkdf2-sha512 | ||
| 973 | |||
| 974 | mkdir -p $out/etc/ssl | ||
| 975 | cp -pdv ${pkgs.openssl.out}/etc/ssl/openssl.cnf $out/etc/ssl | ||
| 976 | |||
| 977 | cat > $out/bin/openssl-wrap <<EOF | ||
| 978 | #!$out/bin/sh | ||
| 979 | export OPENSSL_CONF=$out/etc/ssl/openssl.cnf | ||
| 980 | $out/bin/openssl "\$@" | ||
| 981 | EOF | ||
| 982 | chmod +x $out/bin/openssl-wrap | ||
| 983 | ''} | ||
| 984 | |||
| 985 | ${optionalString luks.fido2Support '' | ||
| 986 | copy_bin_and_libs ${pkgs.fido2luks}/bin/fido2luks | ||
| 987 | ''} | ||
| 988 | |||
| 989 | |||
| 990 | ${optionalString luks.gpgSupport '' | ||
| 991 | copy_bin_and_libs ${pkgs.gnupg}/bin/gpg | ||
| 992 | copy_bin_and_libs ${pkgs.gnupg}/bin/gpg-agent | ||
| 993 | copy_bin_and_libs ${pkgs.gnupg}/libexec/scdaemon | ||
| 994 | |||
| 995 | ${concatMapStringsSep "\n" (x: | ||
| 996 | if x.gpgCard != null then | ||
| 997 | '' | ||
| 998 | mkdir -p $out/secrets/gpg-keys/${x.device} | ||
| 999 | cp -a ${x.gpgCard.encryptedPass} $out/secrets/gpg-keys/${x.device}/cryptkey.gpg | ||
| 1000 | cp -a ${x.gpgCard.publicKey} $out/secrets/gpg-keys/${x.device}/pubkey.asc | ||
| 1001 | '' | ||
| 1002 | else "" | ||
| 1003 | ) (attrValues luks.devices) | ||
| 1004 | } | ||
| 1005 | ''} | ||
| 1006 | |||
| 1007 | ${optionalString luks.clevisSupport '' | ||
| 1008 | for bin in ${ipkgs.tpm2-tools}/bin/* ${ipkgs.jose}/bin/* ${ipkgs.libpwquality}/bin/*; do | ||
| 1009 | if [ -L $bin ]; then | ||
| 1010 | cp -v $bin $out/bin | ||
| 1011 | else | ||
| 1012 | copy_bin_and_libs $bin | ||
| 1013 | fi | ||
| 1014 | done | ||
| 1015 | |||
| 1016 | copy_bin_and_libs ${ipkgs.bash}/bin/bash | ||
| 1017 | for bin in ${ipkgs.clevis}/bin/* ${ipkgs.clevis}/bin/.*; do | ||
| 1018 | [ -f $bin -o -L $bin ] || continue | ||
| 1019 | |||
| 1020 | substitute $bin $out/bin/$(basename $bin) \ | ||
| 1021 | --replace ${ipkgs.bash}/bin $out/bin \ | ||
| 1022 | --replace ${ipkgs.clevis}/bin $out/bin \ | ||
| 1023 | --replace ${ipkgs.tpm2-tools}/bin $out/bin \ | ||
| 1024 | --replace ${ipkgs.jose}/bin $out/bin \ | ||
| 1025 | --replace ${ipkgs.libpwquality}/bin $out/bin \ | ||
| 1026 | --replace ${ipkgs.coreutils}/bin $out/bin | ||
| 1027 | |||
| 1028 | [ -x $bin ] && chmod +x $out/bin/$(basename $bin) | ||
| 1029 | done | ||
| 1030 | |||
| 1031 | for lib in ${ipkgs.tpm2-tss}/lib/*.so*; do | ||
| 1032 | if [ -f $lib ]; then | ||
| 1033 | patchelf --output $out/lib/$(basename $lib) $lib \ | ||
| 1034 | --set-rpath $out/lib | ||
| 1035 | else | ||
| 1036 | cp -pdv $lib $out/lib | ||
| 1037 | fi | ||
| 1038 | done | ||
| 1039 | ''} | ||
| 1040 | |||
| 1041 | ${optionalString luks.dmiSupport '' | ||
| 1042 | copy_bin_and_libs ${pkgs.dmidecode}/bin/dmidecode | ||
| 1043 | ''} | ||
| 1044 | ''; | ||
| 1045 | |||
| 1046 | boot.initrd.extraUtilsCommandsTest = '' | ||
| 1047 | $out/bin/cryptsetup --version | ||
| 1048 | ${optionalString luks.yubikeySupport '' | ||
| 1049 | $out/bin/ykchalresp -V | ||
| 1050 | $out/bin/ykinfo -V | ||
| 1051 | $out/bin/openssl-wrap version | ||
| 1052 | ''} | ||
| 1053 | ${optionalString luks.gpgSupport '' | ||
| 1054 | $out/bin/gpg --version | ||
| 1055 | $out/bin/gpg-agent --version | ||
| 1056 | $out/bin/scdaemon --version | ||
| 1057 | ''} | ||
| 1058 | ${optionalString luks.fido2Support '' | ||
| 1059 | $out/bin/fido2luks --version | ||
| 1060 | ''} | ||
| 1061 | ${optionalString luks.clevisSupport '' | ||
| 1062 | $out/bin/jose alg | ||
| 1063 | ''} | ||
| 1064 | ${optionalString luks.dmiSupport '' | ||
| 1065 | $out/bin/dmidecode --version | ||
| 1066 | ''} | ||
| 1067 | ''; | ||
| 1068 | |||
| 1069 | boot.initrd.preFailCommands = postCommands; | ||
| 1070 | boot.initrd.preLVMCommands = commonFunctions + preCommands + concatStrings (mapAttrsToList openCommand preLVM) + postCommands; | ||
| 1071 | boot.initrd.postDeviceCommands = commonFunctions + preCommands + concatStrings (mapAttrsToList openCommand postLVM) + postCommands; | ||
| 1072 | |||
| 1073 | environment.systemPackages = [ pkgs.cryptsetup ]; | ||
| 1074 | }; | ||
| 1075 | } | ||
diff --git a/modules/networkd.nix b/modules/networkd.nix new file mode 100644 index 00000000..d0a48f98 --- /dev/null +++ b/modules/networkd.nix | |||
| @@ -0,0 +1,297 @@ | |||
| 1 | { config, lib, utils, pkgs, ... }: | ||
| 2 | |||
| 3 | with utils; | ||
| 4 | with lib; | ||
| 5 | |||
| 6 | let | ||
| 7 | |||
| 8 | cfg = config.networking; | ||
| 9 | interfaces = attrValues cfg.interfaces; | ||
| 10 | |||
| 11 | interfaceIps = i: | ||
| 12 | i.ipv4.addresses | ||
| 13 | ++ optionals cfg.enableIPv6 i.ipv6.addresses; | ||
| 14 | |||
| 15 | dhcpStr = useDHCP: if useDHCP == true || useDHCP == null then "yes" else "no"; | ||
| 16 | |||
| 17 | slaves = | ||
| 18 | concatLists (map (bond: bond.interfaces) (attrValues cfg.bonds)) | ||
| 19 | ++ concatLists (map (bridge: bridge.interfaces) (attrValues cfg.bridges)) | ||
| 20 | ++ map (sit: sit.dev) (attrValues cfg.sits) | ||
| 21 | ++ map (vlan: vlan.interface) (attrValues cfg.vlans) | ||
| 22 | # add dependency to physical or independently created vswitch member interface | ||
| 23 | # TODO: warn the user that any address configured on those interfaces will be useless | ||
| 24 | ++ concatMap (i: attrNames (filterAttrs (_: config: config.type != "internal") i.interfaces)) (attrValues cfg.vswitches); | ||
| 25 | |||
| 26 | in | ||
| 27 | |||
| 28 | { | ||
| 29 | disabledModules = [ "tasks/network-interfaces-systemd.nix" ]; | ||
| 30 | |||
| 31 | config = mkIf cfg.useNetworkd { | ||
| 32 | |||
| 33 | assertions = [ { | ||
| 34 | assertion = cfg.defaultGatewayWindowSize == null; | ||
| 35 | message = "networking.defaultGatewayWindowSize is not supported by networkd."; | ||
| 36 | } { | ||
| 37 | assertion = cfg.vswitches == {}; | ||
| 38 | message = "networking.vswitches are not supported by networkd."; | ||
| 39 | } { | ||
| 40 | assertion = cfg.defaultGateway == null || cfg.defaultGateway.interface == null; | ||
| 41 | message = "networking.defaultGateway.interface is not supported by networkd."; | ||
| 42 | } { | ||
| 43 | assertion = cfg.defaultGateway6 == null || cfg.defaultGateway6.interface == null; | ||
| 44 | message = "networking.defaultGateway6.interface is not supported by networkd."; | ||
| 45 | } { | ||
| 46 | assertion = cfg.useDHCP == false; | ||
| 47 | message = '' | ||
| 48 | networking.useDHCP is not supported by networkd. | ||
| 49 | Please use per interface configuration and set the global option to false. | ||
| 50 | ''; | ||
| 51 | } ] ++ flip mapAttrsToList cfg.bridges (n: { rstp, ... }: { | ||
| 52 | assertion = !rstp; | ||
| 53 | message = "networking.bridges.${n}.rstp is not supported by networkd."; | ||
| 54 | }); | ||
| 55 | |||
| 56 | networking.dhcpcd.enable = mkDefault false; | ||
| 57 | |||
| 58 | systemd.network = | ||
| 59 | let | ||
| 60 | domains = cfg.search ++ (optional (cfg.domain != null) cfg.domain); | ||
| 61 | genericNetwork = override: | ||
| 62 | let gateways = optional (cfg.defaultGateway != null && (cfg.defaultGateway.address or "") != "") cfg.defaultGateway.address | ||
| 63 | ++ optional (cfg.defaultGateway6 != null && (cfg.defaultGateway6.address or "") != "") cfg.defaultGateway6.address; | ||
| 64 | in optionalAttrs (gateways != [ ]) { | ||
| 65 | routes = override (map (gateway: { | ||
| 66 | routeConfig = { | ||
| 67 | Gateway = gateway; | ||
| 68 | GatewayOnLink = false; | ||
| 69 | }; | ||
| 70 | }) gateways); | ||
| 71 | } // optionalAttrs (domains != [ ]) { | ||
| 72 | domains = override domains; | ||
| 73 | }; | ||
| 74 | in mkMerge [ { | ||
| 75 | enable = true; | ||
| 76 | } | ||
| 77 | (mkMerge (forEach interfaces (i: { | ||
| 78 | netdevs = mkIf i.virtual ({ | ||
| 79 | "40-${i.name}" = { | ||
| 80 | netdevConfig = { | ||
| 81 | Name = i.name; | ||
| 82 | Kind = i.virtualType; | ||
| 83 | }; | ||
| 84 | "${i.virtualType}Config" = optionalAttrs (i.virtualOwner != null) { | ||
| 85 | User = i.virtualOwner; | ||
| 86 | }; | ||
| 87 | }; | ||
| 88 | }); | ||
| 89 | networks."40-${i.name}" = mkMerge [ (genericNetwork mkDefault) { | ||
| 90 | name = mkDefault i.name; | ||
| 91 | DHCP = mkForce (dhcpStr | ||
| 92 | (if i.useDHCP != null then i.useDHCP else false)); | ||
| 93 | address = forEach (interfaceIps i) | ||
| 94 | (ip: "${ip.address}/${toString ip.prefixLength}"); | ||
| 95 | networkConfig.IPv6PrivacyExtensions = "kernel"; | ||
| 96 | linkConfig = optionalAttrs (i.macAddress != null) { | ||
| 97 | MACAddress = i.macAddress; | ||
| 98 | } // optionalAttrs (i.mtu != null) { | ||
| 99 | MTUBytes = toString i.mtu; | ||
| 100 | }; | ||
| 101 | }]; | ||
| 102 | }))) | ||
| 103 | (mkMerge (flip mapAttrsToList cfg.bridges (name: bridge: { | ||
| 104 | netdevs."40-${name}" = { | ||
| 105 | netdevConfig = { | ||
| 106 | Name = name; | ||
| 107 | Kind = "bridge"; | ||
| 108 | }; | ||
| 109 | }; | ||
| 110 | networks = listToAttrs (forEach bridge.interfaces (bi: | ||
| 111 | nameValuePair "40-${bi}" (mkMerge [ (genericNetwork (mkOverride 999)) { | ||
| 112 | DHCP = mkOverride 0 (dhcpStr false); | ||
| 113 | networkConfig.Bridge = name; | ||
| 114 | } ]))); | ||
| 115 | }))) | ||
| 116 | (mkMerge (flip mapAttrsToList cfg.bonds (name: bond: { | ||
| 117 | netdevs."40-${name}" = { | ||
| 118 | netdevConfig = { | ||
| 119 | Name = name; | ||
| 120 | Kind = "bond"; | ||
| 121 | }; | ||
| 122 | bondConfig = let | ||
| 123 | # manual mapping as of 2017-02-03 | ||
| 124 | # man 5 systemd.netdev [BOND] | ||
| 125 | # to https://www.kernel.org/doc/Documentation/networking/bonding.txt | ||
| 126 | # driver options. | ||
| 127 | driverOptionMapping = let | ||
| 128 | trans = f: optName: { valTransform = f; optNames = [optName]; }; | ||
| 129 | simp = trans id; | ||
| 130 | ms = trans (v: v + "ms"); | ||
| 131 | in { | ||
| 132 | Mode = simp "mode"; | ||
| 133 | TransmitHashPolicy = simp "xmit_hash_policy"; | ||
| 134 | LACPTransmitRate = simp "lacp_rate"; | ||
| 135 | MIIMonitorSec = ms "miimon"; | ||
| 136 | UpDelaySec = ms "updelay"; | ||
| 137 | DownDelaySec = ms "downdelay"; | ||
| 138 | LearnPacketIntervalSec = simp "lp_interval"; | ||
| 139 | AdSelect = simp "ad_select"; | ||
| 140 | FailOverMACPolicy = simp "fail_over_mac"; | ||
| 141 | ARPValidate = simp "arp_validate"; | ||
| 142 | # apparently in ms for this value?! Upstream bug? | ||
| 143 | ARPIntervalSec = simp "arp_interval"; | ||
| 144 | ARPIPTargets = simp "arp_ip_target"; | ||
| 145 | ARPAllTargets = simp "arp_all_targets"; | ||
| 146 | PrimaryReselectPolicy = simp "primary_reselect"; | ||
| 147 | ResendIGMP = simp "resend_igmp"; | ||
| 148 | PacketsPerSlave = simp "packets_per_slave"; | ||
| 149 | GratuitousARP = { valTransform = id; | ||
| 150 | optNames = [ "num_grat_arp" "num_unsol_na" ]; }; | ||
| 151 | AllSlavesActive = simp "all_slaves_active"; | ||
| 152 | MinLinks = simp "min_links"; | ||
| 153 | }; | ||
| 154 | |||
| 155 | do = bond.driverOptions; | ||
| 156 | assertNoUnknownOption = let | ||
| 157 | knownOptions = flatten (mapAttrsToList (_: kOpts: kOpts.optNames) | ||
| 158 | driverOptionMapping); | ||
| 159 | # options that apparently don’t exist in the networkd config | ||
| 160 | unknownOptions = [ "primary" ]; | ||
| 161 | assertTrace = bool: msg: if bool then true else builtins.trace msg false; | ||
| 162 | in assert all (driverOpt: assertTrace | ||
| 163 | (elem driverOpt (knownOptions ++ unknownOptions)) | ||
| 164 | "The bond.driverOption `${driverOpt}` cannot be mapped to the list of known networkd bond options. Please add it to the mapping above the assert or to `unknownOptions` should it not exist in networkd.") | ||
| 165 | (mapAttrsToList (k: _: k) do); ""; | ||
| 166 | # get those driverOptions that have been set | ||
| 167 | filterSystemdOptions = filterAttrs (sysDOpt: kOpts: | ||
| 168 | any (kOpt: do ? ${kOpt}) kOpts.optNames); | ||
| 169 | # build final set of systemd options to bond values | ||
| 170 | buildOptionSet = mapAttrs (_: kOpts: with kOpts; | ||
| 171 | # we simply take the first set kernel bond option | ||
| 172 | # (one option has multiple names, which is silly) | ||
| 173 | head (map (optN: valTransform (do.${optN})) | ||
| 174 | # only map those that exist | ||
| 175 | (filter (o: do ? ${o}) optNames))); | ||
| 176 | in seq assertNoUnknownOption | ||
| 177 | (buildOptionSet (filterSystemdOptions driverOptionMapping)); | ||
| 178 | |||
| 179 | }; | ||
| 180 | |||
| 181 | networks = listToAttrs (forEach bond.interfaces (bi: | ||
| 182 | nameValuePair "40-${bi}" (mkMerge [ (genericNetwork (mkOverride 999)) { | ||
| 183 | DHCP = mkOverride 0 (dhcpStr false); | ||
| 184 | networkConfig.Bond = name; | ||
| 185 | } ]))); | ||
| 186 | }))) | ||
| 187 | (mkMerge (flip mapAttrsToList cfg.macvlans (name: macvlan: { | ||
| 188 | netdevs."40-${name}" = { | ||
| 189 | netdevConfig = { | ||
| 190 | Name = name; | ||
| 191 | Kind = "macvlan"; | ||
| 192 | }; | ||
| 193 | macvlanConfig = optionalAttrs (macvlan.mode != null) { Mode = macvlan.mode; }; | ||
| 194 | }; | ||
| 195 | networks."40-${macvlan.interface}" = (mkMerge [ (genericNetwork (mkOverride 999)) { | ||
| 196 | macvlan = [ name ]; | ||
| 197 | } ]); | ||
| 198 | }))) | ||
| 199 | (mkMerge (flip mapAttrsToList cfg.sits (name: sit: { | ||
| 200 | netdevs."40-${name}" = { | ||
| 201 | netdevConfig = { | ||
| 202 | Name = name; | ||
| 203 | Kind = "sit"; | ||
| 204 | }; | ||
| 205 | tunnelConfig = | ||
| 206 | (optionalAttrs (sit.remote != null) { | ||
| 207 | Remote = sit.remote; | ||
| 208 | }) // (optionalAttrs (sit.local != null) { | ||
| 209 | Local = sit.local; | ||
| 210 | }) // (optionalAttrs (sit.ttl != null) { | ||
| 211 | TTL = sit.ttl; | ||
| 212 | }); | ||
| 213 | }; | ||
| 214 | networks = mkIf (sit.dev != null) { | ||
| 215 | "40-${sit.dev}" = (mkMerge [ (genericNetwork (mkOverride 999)) { | ||
| 216 | tunnel = [ name ]; | ||
| 217 | } ]); | ||
| 218 | }; | ||
| 219 | }))) | ||
| 220 | (mkMerge (flip mapAttrsToList cfg.vlans (name: vlan: { | ||
| 221 | netdevs."40-${name}" = { | ||
| 222 | netdevConfig = { | ||
| 223 | Name = name; | ||
| 224 | Kind = "vlan"; | ||
| 225 | }; | ||
| 226 | vlanConfig.Id = vlan.id; | ||
| 227 | }; | ||
| 228 | networks."40-${vlan.interface}" = (mkMerge [ (genericNetwork (mkOverride 999)) { | ||
| 229 | vlan = [ name ]; | ||
| 230 | } ]); | ||
| 231 | }))) | ||
| 232 | ]; | ||
| 233 | |||
| 234 | # We need to prefill the slaved devices with networking options | ||
| 235 | # This forces the network interface creator to initialize slaves. | ||
| 236 | networking.interfaces = listToAttrs (map (i: nameValuePair i { }) slaves); | ||
| 237 | |||
| 238 | systemd.services = let | ||
| 239 | # We must escape interfaces due to the systemd interpretation | ||
| 240 | subsystemDevice = interface: | ||
| 241 | "sys-subsystem-net-devices-${escapeSystemdPath interface}.device"; | ||
| 242 | # support for creating openvswitch switches | ||
| 243 | createVswitchDevice = n: v: nameValuePair "${n}-netdev" | ||
| 244 | (let | ||
| 245 | deps = map subsystemDevice (attrNames (filterAttrs (_: config: config.type != "internal") v.interfaces)); | ||
| 246 | ofRules = pkgs.writeText "vswitch-${n}-openFlowRules" v.openFlowRules; | ||
| 247 | in | ||
| 248 | { description = "Open vSwitch Interface ${n}"; | ||
| 249 | wantedBy = [ "network.target" (subsystemDevice n) ]; | ||
| 250 | # and create bridge before systemd-networkd starts because it might create internal interfaces | ||
| 251 | before = [ "systemd-networkd.service" ]; | ||
| 252 | # shutdown the bridge when network is shutdown | ||
| 253 | partOf = [ "network.target" ]; | ||
| 254 | # requires ovs-vswitchd to be alive at all times | ||
| 255 | bindsTo = [ "ovs-vswitchd.service" ]; | ||
| 256 | # start switch after physical interfaces and vswitch daemon | ||
| 257 | after = [ "network-pre.target" "ovs-vswitchd.service" ] ++ deps; | ||
| 258 | wants = deps; # if one or more interface fails, the switch should continue to run | ||
| 259 | serviceConfig.Type = "oneshot"; | ||
| 260 | serviceConfig.RemainAfterExit = true; | ||
| 261 | path = [ pkgs.iproute2 config.virtualisation.vswitch.package ]; | ||
| 262 | preStart = '' | ||
| 263 | echo "Resetting Open vSwitch ${n}..." | ||
| 264 | ovs-vsctl --if-exists del-br ${n} -- add-br ${n} \ | ||
| 265 | -- set bridge ${n} protocols=${concatStringsSep "," v.supportedOpenFlowVersions} | ||
| 266 | ''; | ||
| 267 | script = '' | ||
| 268 | echo "Configuring Open vSwitch ${n}..." | ||
| 269 | ovs-vsctl ${concatStrings (mapAttrsToList (name: config: " -- add-port ${n} ${name}" + optionalString (config.vlan != null) " tag=${toString config.vlan}") v.interfaces)} \ | ||
| 270 | ${concatStrings (mapAttrsToList (name: config: optionalString (config.type != null) " -- set interface ${name} type=${config.type}") v.interfaces)} \ | ||
| 271 | ${concatMapStrings (x: " -- set-controller ${n} " + x) v.controllers} \ | ||
| 272 | ${concatMapStrings (x: " -- " + x) (splitString "\n" v.extraOvsctlCmds)} | ||
| 273 | |||
| 274 | |||
| 275 | echo "Adding OpenFlow rules for Open vSwitch ${n}..." | ||
| 276 | ovs-ofctl --protocols=${v.openFlowVersion} add-flows ${n} ${ofRules} | ||
| 277 | ''; | ||
| 278 | postStop = '' | ||
| 279 | echo "Cleaning Open vSwitch ${n}" | ||
| 280 | echo "Shuting down internal ${n} interface" | ||
| 281 | ip link set ${n} down || true | ||
| 282 | echo "Deleting flows for ${n}" | ||
| 283 | ovs-ofctl --protocols=${v.openFlowVersion} del-flows ${n} || true | ||
| 284 | echo "Deleting Open vSwitch ${n}" | ||
| 285 | ovs-vsctl --if-exists del-br ${n} || true | ||
| 286 | ''; | ||
| 287 | }); | ||
| 288 | in mapAttrs' createVswitchDevice cfg.vswitches | ||
| 289 | // { | ||
| 290 | "network-local-commands" = { | ||
| 291 | after = [ "systemd-networkd.service" ]; | ||
| 292 | bindsTo = [ "systemd-networkd.service" ]; | ||
| 293 | }; | ||
| 294 | }; | ||
| 295 | }; | ||
| 296 | |||
| 297 | } | ||
diff --git a/modules/tinc-networkmanager.nix b/modules/tinc-networkmanager.nix new file mode 100644 index 00000000..ff03abd2 --- /dev/null +++ b/modules/tinc-networkmanager.nix | |||
| @@ -0,0 +1,36 @@ | |||
| 1 | { lib, config, pkgs, ... }: | ||
| 2 | let | ||
| 3 | cfg = config.services.tinc; | ||
| 4 | in { | ||
| 5 | options = { | ||
| 6 | services.tinc.networks = lib.mkOption { | ||
| 7 | type = lib.types.attrsOf (lib.types.submodule { | ||
| 8 | options.nmDispatch = lib.mkOption { | ||
| 9 | type = lib.types.bool; | ||
| 10 | default = config.networking.networkmanager.enable; | ||
| 11 | description = '' | ||
| 12 | Install a network-manager dispatcher script to automatically | ||
| 13 | connect to all remotes when networking is available | ||
| 14 | ''; | ||
| 15 | }; | ||
| 16 | }); | ||
| 17 | }; | ||
| 18 | }; | ||
| 19 | |||
| 20 | config = { | ||
| 21 | networking.networkmanager.dispatcherScripts = lib.concatLists (lib.flip lib.mapAttrsToList cfg.networks (network: data: lib.optional data.nmDispatch { | ||
| 22 | type = "basic"; | ||
| 23 | source = pkgs.writeScript "connect-${network}.sh" '' | ||
| 24 | #!${pkgs.stdenv.shell} | ||
| 25 | |||
| 26 | shopt -s extglob | ||
| 27 | |||
| 28 | case "''${2}" in | ||
| 29 | (?(vpn-)up) | ||
| 30 | ${data.package}/bin/tinc -n ${network} --pidfile /run/tinc.${network}.pid --batch retry | ||
| 31 | ;; | ||
| 32 | esac | ||
| 33 | ''; | ||
| 34 | })); | ||
| 35 | }; | ||
| 36 | } | ||
diff --git a/modules/uucp.nix b/modules/uucp.nix new file mode 100644 index 00000000..0334a3db --- /dev/null +++ b/modules/uucp.nix | |||
| @@ -0,0 +1,391 @@ | |||
| 1 | { flake, config, lib, pkgs, ... }: | ||
| 2 | |||
| 3 | with lib; | ||
| 4 | |||
| 5 | let | ||
| 6 | portSpec = name: node: concatStringsSep "\n" (map (port: '' | ||
| 7 | port ${name}.${port} | ||
| 8 | type pipe | ||
| 9 | protocol ${node.protocols} | ||
| 10 | reliable true | ||
| 11 | command ${pkgs.openssh}/bin/ssh -x -o batchmode=yes ${name}.${port} | ||
| 12 | '') node.hostnames); | ||
| 13 | sysSpec = name: node: '' | ||
| 14 | system ${name} | ||
| 15 | time any | ||
| 16 | chat-seven-bit false | ||
| 17 | chat . "" | ||
| 18 | protocol ${node.protocols} | ||
| 19 | command-path ${concatStringsSep " " cfg.commandPath} | ||
| 20 | commands ${concatStringsSep " " node.commands} | ||
| 21 | ${concatStringsSep "\nalternate\n" (map (port: '' | ||
| 22 | port ${name}.${port} | ||
| 23 | '') node.hostnames)} | ||
| 24 | ''; | ||
| 25 | sshConfig = name: node: concatStringsSep "\n" (map (port: '' | ||
| 26 | Host ${name}.${port} | ||
| 27 | Hostname ${port} | ||
| 28 | IdentitiesOnly Yes | ||
| 29 | IdentityFile ${cfg.sshKeyDir}/${name} | ||
| 30 | '') node.hostnames); | ||
| 31 | sshKeyGen = name: node: '' | ||
| 32 | if [[ ! -e ${cfg.sshKeyDir}/${name} ]]; then | ||
| 33 | ${pkgs.openssh}/bin/ssh-keygen ${escapeShellArgs node.generateKey} -f ${cfg.sshKeyDir}/${name} | ||
| 34 | fi | ||
| 35 | ''; | ||
| 36 | restrictKey = key: '' | ||
| 37 | restrict,command="${chat}" ${key} | ||
| 38 | ''; | ||
| 39 | chat = pkgs.writeScript "chat" '' | ||
| 40 | #!${pkgs.stdenv.shell} | ||
| 41 | |||
| 42 | echo . | ||
| 43 | exec ${config.security.wrapperDir}/uucico | ||
| 44 | ''; | ||
| 45 | |||
| 46 | nodeCfg = { | ||
| 47 | options = { | ||
| 48 | commands = mkOption { | ||
| 49 | type = types.listOf types.str; | ||
| 50 | default = cfg.defaultCommands; | ||
| 51 | description = "Commands to allow for this remote"; | ||
| 52 | }; | ||
| 53 | |||
| 54 | protocols = mkOption { | ||
| 55 | type = types.separatedString ""; | ||
| 56 | default = cfg.defaultProtocols; | ||
| 57 | description = "UUCP protocols to use for this remote"; | ||
| 58 | }; | ||
| 59 | |||
| 60 | publicKeys = mkOption { | ||
| 61 | type = types.listOf types.str; | ||
| 62 | default = []; | ||
| 63 | description = "SSH client public keys for this node"; | ||
| 64 | }; | ||
| 65 | |||
| 66 | generateKey = mkOption { | ||
| 67 | type = types.listOf types.str; | ||
| 68 | default = [ "-t" "ed25519" "-N" "" ]; | ||
| 69 | description = "Arguments to pass to `ssh-keygen` to generate a keypair for communication with this host"; | ||
| 70 | }; | ||
| 71 | |||
| 72 | hostnames = mkOption { | ||
| 73 | type = types.listOf types.str; | ||
| 74 | default = []; | ||
| 75 | description = "Hostnames to try in order when connecting"; | ||
| 76 | }; | ||
| 77 | }; | ||
| 78 | }; | ||
| 79 | |||
| 80 | cfg = config.services.uucp; | ||
| 81 | in { | ||
| 82 | options = { | ||
| 83 | services.uucp = { | ||
| 84 | enable = mkOption { | ||
| 85 | type = types.bool; | ||
| 86 | default = false; | ||
| 87 | description = '' | ||
| 88 | If enabled we set up an account accesible via uucp over ssh | ||
| 89 | ''; | ||
| 90 | }; | ||
| 91 | |||
| 92 | nodeName = mkOption { | ||
| 93 | type = types.str; | ||
| 94 | default = "nixos"; | ||
| 95 | description = "uucp node name"; | ||
| 96 | }; | ||
| 97 | |||
| 98 | sshUser = mkOption { | ||
| 99 | type = types.attrs; | ||
| 100 | default = {}; | ||
| 101 | description = "Overrides for the local uucp linux-user"; | ||
| 102 | }; | ||
| 103 | |||
| 104 | extraSSHConfig = mkOption { | ||
| 105 | type = types.str; | ||
| 106 | default = ""; | ||
| 107 | description = "Extra SSH config"; | ||
| 108 | }; | ||
| 109 | |||
| 110 | remoteNodes = mkOption { | ||
| 111 | type = types.attrsOf (types.submodule nodeCfg); | ||
| 112 | default = {}; | ||
| 113 | description = '' | ||
| 114 | Ports to set up | ||
| 115 | Names will probably need to be configured in sshConfig | ||
| 116 | ''; | ||
| 117 | }; | ||
| 118 | |||
| 119 | commandPath = mkOption { | ||
| 120 | type = types.listOf types.path; | ||
| 121 | default = [ "${pkgs.rmail}/bin" ]; | ||
| 122 | description = '' | ||
| 123 | Command search path for all systems | ||
| 124 | ''; | ||
| 125 | }; | ||
| 126 | |||
| 127 | defaultCommands = mkOption { | ||
| 128 | type = types.listOf types.str; | ||
| 129 | default = ["rmail"]; | ||
| 130 | description = "Commands allowed for remotes without explicit override"; | ||
| 131 | }; | ||
| 132 | |||
| 133 | defaultProtocols = mkOption { | ||
| 134 | type = types.separatedString ""; | ||
| 135 | default = "te"; | ||
| 136 | description = "UUCP protocol to use within ssh unless overriden"; | ||
| 137 | }; | ||
| 138 | |||
| 139 | incomingProtocols = mkOption { | ||
| 140 | type = types.separatedString ""; | ||
| 141 | default = "te"; | ||
| 142 | description = "UUCP protocols to use when called"; | ||
| 143 | }; | ||
| 144 | |||
| 145 | homeDir = mkOption { | ||
| 146 | type = types.path; | ||
| 147 | default = "/var/uucp"; | ||
| 148 | description = "Home of the uucp user"; | ||
| 149 | }; | ||
| 150 | |||
| 151 | sshKeyDir = mkOption { | ||
| 152 | type = types.path; | ||
| 153 | default = "${cfg.homeDir}/.ssh/"; | ||
| 154 | description = "Directory to store ssh keypairs"; | ||
| 155 | }; | ||
| 156 | |||
| 157 | spoolDir = mkOption { | ||
| 158 | type = types.path; | ||
| 159 | default = "/var/spool/uucp"; | ||
| 160 | description = "Spool directory"; | ||
| 161 | }; | ||
| 162 | |||
| 163 | lockDir = mkOption { | ||
| 164 | type = types.path; | ||
| 165 | default = "/var/spool/uucp"; | ||
| 166 | description = "Lock directory"; | ||
| 167 | }; | ||
| 168 | |||
| 169 | pubDir = mkOption { | ||
| 170 | type = types.path; | ||
| 171 | default = "/var/spool/uucppublic"; | ||
| 172 | description = "Public directory"; | ||
| 173 | }; | ||
| 174 | |||
| 175 | logFile = mkOption { | ||
| 176 | type = types.path; | ||
| 177 | default = "/var/log/uucp"; | ||
| 178 | description = "Log file"; | ||
| 179 | }; | ||
| 180 | |||
| 181 | statFile = mkOption { | ||
| 182 | type = types.path; | ||
| 183 | default = "/var/log/uucp.stat"; | ||
| 184 | description = "Statistics file"; | ||
| 185 | }; | ||
| 186 | |||
| 187 | debugFile = mkOption { | ||
| 188 | type = types.path; | ||
| 189 | default = "/var/log/uucp.debug"; | ||
| 190 | description = "Debug file"; | ||
| 191 | }; | ||
| 192 | |||
| 193 | interval = mkOption { | ||
| 194 | type = types.nullOr types.str; | ||
| 195 | default = "1h"; | ||
| 196 | description = '' | ||
| 197 | Specification of when to run `uucico' in format used by systemd timers | ||
| 198 | The default is to do so every hour | ||
| 199 | ''; | ||
| 200 | }; | ||
| 201 | |||
| 202 | nmDispatch = mkOption { | ||
| 203 | type = types.bool; | ||
| 204 | default = config.networking.networkmanager.enable; | ||
| 205 | description = '' | ||
| 206 | Install a network-manager dispatcher script to automatically | ||
| 207 | call all remotes when networking is available | ||
| 208 | ''; | ||
| 209 | }; | ||
| 210 | |||
| 211 | extraConfig = mkOption { | ||
| 212 | type = types.lines; | ||
| 213 | default = '' | ||
| 214 | run-uuxqt 1 | ||
| 215 | ''; | ||
| 216 | description = "Extra configuration to append verbatim to `/etc/uucp/config'"; | ||
| 217 | }; | ||
| 218 | |||
| 219 | extraSys = mkOption { | ||
| 220 | type = types.lines; | ||
| 221 | default = '' | ||
| 222 | protocol-parameter g packet-size 4096 | ||
| 223 | ''; | ||
| 224 | description = "Extra configuration to prepend verbatim to `/etc/uucp/sys`"; | ||
| 225 | }; | ||
| 226 | }; | ||
| 227 | }; | ||
| 228 | |||
| 229 | config = mkIf cfg.enable { | ||
| 230 | environment.etc."uucp/config" = { | ||
| 231 | text = '' | ||
| 232 | hostname ${cfg.nodeName} | ||
| 233 | |||
| 234 | spool ${cfg.spoolDir} | ||
| 235 | lockdir ${cfg.lockDir} | ||
| 236 | pubdir ${cfg.pubDir} | ||
| 237 | logfile ${cfg.logFile} | ||
| 238 | statfile ${cfg.statFile} | ||
| 239 | debugfile ${cfg.debugFile} | ||
| 240 | |||
| 241 | ${cfg.extraConfig} | ||
| 242 | ''; | ||
| 243 | }; | ||
| 244 | |||
| 245 | users.users."uucp" = { | ||
| 246 | name = "uucp"; | ||
| 247 | isSystemUser = true; | ||
| 248 | isNormalUser = false; | ||
| 249 | createHome = true; | ||
| 250 | home = cfg.homeDir; | ||
| 251 | description = "User for uucp over ssh"; | ||
| 252 | useDefaultShell = true; | ||
| 253 | openssh.authorizedKeys.keys = map restrictKey (concatLists (mapAttrsToList (name: node: node.publicKeys) cfg.remoteNodes)); | ||
| 254 | } // cfg.sshUser; | ||
| 255 | |||
| 256 | system.activationScripts."uucp-sshconfig" = '' | ||
| 257 | mkdir -p ${config.users.users."uucp".home}/.ssh | ||
| 258 | chown ${config.users.users."uucp".name}:${config.users.users."uucp".group} ${config.users.users."uucp".home}/.ssh | ||
| 259 | chmod 700 ${config.users.users."uucp".home}/.ssh | ||
| 260 | ln -fs ${builtins.toFile "ssh-config" '' | ||
| 261 | ${concatStringsSep "\n" (mapAttrsToList sshConfig cfg.remoteNodes)} | ||
| 262 | |||
| 263 | ${cfg.extraSSHConfig} | ||
| 264 | ''} ${config.users.users."uucp".home}/.ssh/config | ||
| 265 | |||
| 266 | mkdir -p ${cfg.sshKeyDir} | ||
| 267 | chown ${config.users.users."uucp".name}:${config.users.users."uucp".group} ${cfg.sshKeyDir} | ||
| 268 | chmod 700 ${cfg.sshKeyDir} | ||
| 269 | |||
| 270 | ${concatStringsSep "\n" (mapAttrsToList sshKeyGen cfg.remoteNodes)} | ||
| 271 | ''; | ||
| 272 | |||
| 273 | system.activationScripts."uucp-logs" = '' | ||
| 274 | touch ${cfg.logFile} | ||
| 275 | chown ${config.users.users."uucp".name}:${config.users.users."uucp".group} ${cfg.logFile} | ||
| 276 | chmod 644 ${cfg.logFile} | ||
| 277 | touch ${cfg.statFile} | ||
| 278 | chown ${config.users.users."uucp".name}:${config.users.users."uucp".group} ${cfg.statFile} | ||
| 279 | chmod 644 ${cfg.statFile} | ||
| 280 | touch ${cfg.debugFile} | ||
| 281 | chown ${config.users.users."uucp".name}:${config.users.users."uucp".group} ${cfg.debugFile} | ||
| 282 | chmod 644 ${cfg.debugFile} | ||
| 283 | ''; | ||
| 284 | |||
| 285 | environment.etc."uucp/port" = { | ||
| 286 | text = '' | ||
| 287 | port ssh | ||
| 288 | type stdin | ||
| 289 | protocol ${cfg.incomingProtocols} | ||
| 290 | '' + concatStringsSep "\n" (mapAttrsToList portSpec cfg.remoteNodes); | ||
| 291 | }; | ||
| 292 | environment.etc."uucp/sys" = { | ||
| 293 | text = cfg.extraSys + "\n" + concatStringsSep "\n" (mapAttrsToList sysSpec cfg.remoteNodes); | ||
| 294 | }; | ||
| 295 | |||
| 296 | security.wrappers = let | ||
| 297 | wrapper = p: { | ||
| 298 | name = p; | ||
| 299 | value = { | ||
| 300 | source = "${pkgs.uucp}/bin/${p}"; | ||
| 301 | owner = "root"; | ||
| 302 | group = "root"; | ||
| 303 | setuid = true; | ||
| 304 | setgid = false; | ||
| 305 | }; | ||
| 306 | }; | ||
| 307 | in listToAttrs (map wrapper ["uucico" "cu" "uucp" "uuname" "uustat" "uux" "uuxqt"]); | ||
| 308 | |||
| 309 | nixpkgs.overlays = [(self: super: { | ||
| 310 | uucp = super.lib.overrideDerivation super.uucp (oldAttrs: { | ||
| 311 | configureFlags = "--with-newconfigdir=/etc/uucp"; | ||
| 312 | patches = [ | ||
| 313 | (super.writeText "mailprogram" '' | ||
| 314 | policy.h | 2 +- | ||
| 315 | 1 file changed, 1 insertion(+), 1 deletion(-) | ||
| 316 | |||
| 317 | diff --git a/policy.h b/policy.h | ||
| 318 | index 5afe34b..8e92c8b 100644 | ||
| 319 | --- a/policy.h | ||
| 320 | +++ b/policy.h | ||
| 321 | @@ -240,7 +240,7 @@ | ||
| 322 | the sendmail choice below. Otherwise, select one of the other | ||
| 323 | choices as appropriate. */ | ||
| 324 | #if 1 | ||
| 325 | -#define MAIL_PROGRAM "/usr/lib/sendmail -t" | ||
| 326 | +#define MAIL_PROGRAM "${config.security.wrapperDir}/sendmail -t" | ||
| 327 | /* #define MAIL_PROGRAM "/usr/sbin/sendmail -t" */ | ||
| 328 | #define MAIL_PROGRAM_TO_BODY 1 | ||
| 329 | #define MAIL_PROGRAM_SUBJECT_BODY 1 | ||
| 330 | '') | ||
| 331 | ]; | ||
| 332 | }); | ||
| 333 | rmail = super.writeScriptBin "rmail" '' | ||
| 334 | #!${super.stdenv.shell} | ||
| 335 | |||
| 336 | # Dummy UUCP rmail command for postfix/qmail systems | ||
| 337 | |||
| 338 | IFS=" " read junk from junk junk junk junk junk junk junk relay | ||
| 339 | |||
| 340 | case "$from" in | ||
| 341 | *[@!]*) ;; | ||
| 342 | *) from="$from@$relay";; | ||
| 343 | esac | ||
| 344 | |||
| 345 | exec ${config.security.wrapperDir}/sendmail -G -i -f "$from" -- "$@" | ||
| 346 | ''; | ||
| 347 | })]; | ||
| 348 | |||
| 349 | environment.systemPackages = with pkgs; [ | ||
| 350 | uucp | ||
| 351 | ]; | ||
| 352 | |||
| 353 | systemd.services."uucico@" = { | ||
| 354 | serviceConfig = { | ||
| 355 | User = "uucp"; | ||
| 356 | Type = "oneshot"; | ||
| 357 | ExecStart = "${config.security.wrapperDir}/uucico -D -S %i"; | ||
| 358 | }; | ||
| 359 | }; | ||
| 360 | |||
| 361 | systemd.timers."uucico@" = { | ||
| 362 | timerConfig.OnActiveSec = cfg.interval; | ||
| 363 | timerConfig.OnUnitActiveSec = cfg.interval; | ||
| 364 | }; | ||
| 365 | |||
| 366 | systemd.targets."multi-user" = { | ||
| 367 | wants = mapAttrsToList (name: node: "uucico@${name}.timer") cfg.remoteNodes; | ||
| 368 | }; | ||
| 369 | |||
| 370 | systemd.kill-user.enable = true; | ||
| 371 | systemd.targets."sleep" = { | ||
| 372 | after = [ "kill-user@uucp.service" ]; | ||
| 373 | wants = [ "kill-user@uucp.service" ]; | ||
| 374 | }; | ||
| 375 | |||
| 376 | networking.networkmanager.dispatcherScripts = optional cfg.nmDispatch { | ||
| 377 | type = "basic"; | ||
| 378 | source = pkgs.writeScript "callRemotes.sh" '' | ||
| 379 | #!${pkgs.stdenv.shell} | ||
| 380 | |||
| 381 | shopt -s extglob | ||
| 382 | |||
| 383 | case "''${2}" in | ||
| 384 | (?(vpn-)up) | ||
| 385 | ${concatStringsSep "\n " (mapAttrsToList (name: node: "${pkgs.systemd}/bin/systemctl start uucico@${name}.service") cfg.remoteNodes)} | ||
| 386 | ;; | ||
| 387 | esac | ||
| 388 | ''; | ||
| 389 | }; | ||
| 390 | }; | ||
| 391 | } | ||
diff --git a/modules/yggdrasil/default.nix b/modules/yggdrasil/default.nix new file mode 100644 index 00000000..f4100e73 --- /dev/null +++ b/modules/yggdrasil/default.nix | |||
| @@ -0,0 +1,50 @@ | |||
| 1 | { config, lib, customUtils, ... }: | ||
| 2 | let | ||
| 3 | cfg = config.services.tinc.yggdrasil; | ||
| 4 | in { | ||
| 5 | options = { | ||
| 6 | services.tinc.yggdrasil = lib.mkOption { | ||
| 7 | default = {}; | ||
| 8 | type = lib.types.submodule { | ||
| 9 | options = { | ||
| 10 | enable = lib.mkEnableOption "Yggdrasil tinc network"; | ||
| 11 | |||
| 12 | connect = lib.mkOption { | ||
| 13 | default = true; | ||
| 14 | type = lib.types.bool; | ||
| 15 | description = '' | ||
| 16 | Connect to central server | ||
| 17 | ''; | ||
| 18 | }; | ||
| 19 | }; | ||
| 20 | }; | ||
| 21 | }; | ||
| 22 | }; | ||
| 23 | |||
| 24 | config = lib.mkIf cfg.enable { | ||
| 25 | services.tinc.networks.yggdrasil = { | ||
| 26 | name = config.networking.hostName; | ||
| 27 | hostSettings = customUtils.nixImport { dir = ./hosts; }; | ||
| 28 | debugLevel = 2; | ||
| 29 | interfaceType = "tap"; | ||
| 30 | settings = { | ||
| 31 | Mode = "switch"; | ||
| 32 | PingTimeout = 30; | ||
| 33 | ConnectTo = lib.mkIf cfg.connect "ymir"; | ||
| 34 | }; | ||
| 35 | }; | ||
| 36 | |||
| 37 | sops.secrets = { | ||
| 38 | tinc-yggdrasil-rsa = { | ||
| 39 | key = "rsa"; | ||
| 40 | path = "/etc/tinc/yggdrasil/rsa_key.priv"; | ||
| 41 | sopsFile = ./hosts + "/${config.services.tinc.networks.yggdrasil.name}/private-keys.yaml"; | ||
| 42 | }; | ||
| 43 | tinc-yggdrasil-ed25519 = { | ||
| 44 | key = "ed25519"; | ||
| 45 | path = "/etc/tinc/yggdrasil/rsa_key.priv"; | ||
| 46 | sopsFile = ./hosts + "/${config.services.tinc.networks.yggdrasil.name}/private-keys.yaml"; | ||
| 47 | }; | ||
| 48 | }; | ||
| 49 | }; | ||
| 50 | } | ||
diff --git a/modules/yggdrasil/hosts/sif/default.nix b/modules/yggdrasil/hosts/sif/default.nix new file mode 100644 index 00000000..32b844de --- /dev/null +++ b/modules/yggdrasil/hosts/sif/default.nix | |||
| @@ -0,0 +1,13 @@ | |||
| 1 | { | ||
| 2 | settings.Ed25519PublicKey = "qJqty+wiTNcYaHQCvQNiMqXYz30C9M3+LI/qjmU/9hK"; | ||
| 3 | rsaPublicKey = '' | ||
| 4 | -----BEGIN RSA PUBLIC KEY----- | ||
| 5 | MIIBCgKCAQEA0ACaacg9EN0hBQct8ZwQ/i6EsXKP4DIwKwabM2rp8azValTHU2uI | ||
| 6 | WW6JRY+Eii6zRx9B5kJ96C4rJJeAGV6lZPAogaC2LbM7lcsZ7oRDWZGaQKcZFNGi | ||
| 7 | laEcDg2dRuDx1W4at0rb03SDLNPt8sXSV6BcK9n/7m7+s9cwM/+PB8FHDMnWvwbC | ||
| 8 | usbP23020s+CVr/PU1z/7J0y3Eat+Acut6x5X8DNewpqV96wQpqdAggbhtYERMFH | ||
| 9 | +i0sa1WUDQtJ6HGChbENRTMlsPJ6lnzXY+J0pzatzzvetLsOljES9uJ8dtk6qBC7 | ||
| 10 | KRZo5lvdUwR6j9XiHMQeRerUt23b9ATFXQIDAQAB | ||
| 11 | -----END RSA PUBLIC KEY----- | ||
| 12 | ''; | ||
| 13 | } | ||
diff --git a/modules/yggdrasil/hosts/sif/private-keys.yaml b/modules/yggdrasil/hosts/sif/private-keys.yaml new file mode 100644 index 00000000..9be82bc1 --- /dev/null +++ b/modules/yggdrasil/hosts/sif/private-keys.yaml | |||
| @@ -0,0 +1,34 @@ | |||
| 1 | ed25519: ENC[AES256_GCM,data:1CqB4y6CIm5JUsznpXPqqLJqCKmmoAJOZQTWb7+Jbn0oZMX27qSMK4CchHF7Bmo24EK8rk5EyW5aQLnoxp/2NA62p8SXdaoI8Qgz3EgsQ5QrlJrt1jvERpNs4vttT9V6+aK3Yojr9IuQSvJ4jyKSLrzrTnLzF9pXlaOf1Ru5SxySRWtVzynzurRpdUVS6goE+lb+Irg6x2geV719iQ9bu1C2smeQDREdS+dlfoxp02/pU6kTFA7KAm5vA91HKEfMqfSEzuBgUB0=,iv:n6Yh0zZ9AbT+83P42QNO2rCCISJV5nbO9wYcwaRYD2E=,tag:dJpXV9ZzLSO1B+LsyV3vAg==,type:str] | ||
| 2 | rsa: ENC[AES256_GCM,data:7faQJAhoYt3MJidg4TVwysmLGZ4V1fA9NYYKgEMgky4q0Q9tBGhEsA60uj7iKcMMRhGku7feIFkj2+1qjKy+e1Bajfs2rqxgyqYmM6yOTrmorbXBVyrPOTOwJp3yp7O1vIXwoUS9vWIYxFszpfaLL0/8aARYVrYmpxf3gsBfQ4LciM1VKEgjG3uRBf1tDLaNuMNyzdan0DFghwuDojPOXUFv/6yuPxU2U0TagVjwAk4FThGwEasvV454RSm/GmqYtX+P4Vc3pEWNYAK1rXJAuXm1392Uash+HGQ+3ln5N9yWneewgPPr0pePAugxxN0qnwhy5MRKGQE3ZHCZ0beslfOm6pkmYTfww3lKNIJGabMfMD3COoAI7zWebUvksZPsgH6f1olbzABkZdS1s//WNMnWQHGxsWePXkLFe8bfnNXouEXHtLvQ7On0KPyt8y5QBI9bDPpTn92/O9jCevXSttrez4buBdCHFmCE8xgW5JKKEXgMubPPjEF3MABiGu0TMeWM4a1ibY7HfvNrRkO1pE9RhdRT/dFV/MrPxk7P0k16x9H4+QnE7VglfNZO3Wd3bnYxcH7hmAbIzpFnUJvolyNfmynwL2WwaYuBskXASD1FuqpM0tbhantqGyHVPe62+KimU0zDAJ1HMyqhIN0MD1MSXsdoItAsw033GYLB83L8xPatARJR9qEdKwrhmgSDY36AbJ8VI/RUzicZoYdhK8+M7bNGIkD5MgrQO35q+3oa6Xcib+5MtW0RVJKLP4y5/XNkjd4EPl6nahcVi63/FG7LJmO+/I7bkLIAWmIq8BHcXEwbz0womYp404pSfEPr3cy1N5S3yqRdzVxavTJb0PLMpHq2rWuHK2DIY77hEOAt0XcReWYsRkmTl+v9iQLF+D4GBLr+O2oZNJrocNVZYkfdjsrUd2cUOCV7ZQphO5Yc+yKrqzmCqUUvdoJ3vlaPxMXx4LACeMImo1sAFxoOgIpyfklo/bdhi9osiL55I8pAIh5hGes/uCbwaRnW+wbaYcMliCuUO8XelfXwBot8W+0l0wk2zKRSKtYKcX1n/Ax5mIt6mIoQkvyL82lccS9ppJLjt7DYlvK8L6imeV11ATf1ZhSGB3c67/XYik5BXz827Rj29K6fg/CvU65f/bEAuE39gSJ4mHsRl3bvkNLiUMEBrDuZnText33fCbqVA5DUIfqSbLUzXtqNl8vHnlOBICYwjv8PtUMJ6VTCDu33SmtQzJAfnmuewOKAC51FPsyaDhouTKllUaqx34NfEP8k2C8/4oNPgDcLjInm3f43tIuJbScdp8ltNVCLoChS8jbBOvrVYTI0eP+BuAuEfWYldUYq96oH/x9d0yvPqZ1rnwmqg4y6GfkACw6+/QvrDdtcM+1uI86RxZ7KGurb8KG7NPdSWhzz+72+TO5Tq29K8QETLzzalnVzaVWj/xGsjgkslxmDMKxLJQw0o24lgg/R30aU9BL6YwDVi10nu+Tv5kayb/NVLdMNWxfKNg1KZcf8M2ApgonjingbpUlinZ25/IIcQB9lMT4HSyvtGtIqnsPL4SQNsgBLcMzdwbL0EvS3qMAEVWKfUm2v9AA2+RMsKEKtD4UNF2xF7oACJiyTcw/xUOmkaTIZZ2ev0JVb4IYs1qx5Skz+IMAvWQ2FjBMXna5e/LYgBl6kdLSTcDvlymHpbjjuRdRq+uq+ZMXIACyZ+qUnZ0qcfWGPxOCI0hXPc5ac/zSGkPKYiWT/rCSuo+MoijjK4YZ2fub9TCYjZRS+QvLlXOM8F06Or0jQQOveezqJFZdoBGj248BtcPAVbYqfaytIlYjARlhQL/lKaaOrbONk6kIlDpwkhlzO50OkhALItlbW4Aa8zZ/WeXkfkb/6A7NLce42XDoOnvZt9UdYVTRphf8yxjRE2YMwZsmeTIieg8KwwJdnoJIhiQFdVDFgXb2xPZA2CbdvZwGwuFkLWgJUg6H+aHdw39UnNM+S9PYaOQ9oaS7IyeWhXMgP7TKM98uILsBg/Xn9tafHaslQfjVRDEaYtrmDZMYhb+h/MZKngx7uwmUyqHszAYN/M+RMJVy3s4uBu/EufWYVMorunpPEXGYA4Rg1HUuAOvWSvpM3PJG9Wnrazw6xmkwIUSKju5irpWATYmqSX3pPkG5C0sTatszVDAvTs9+/9Xdbney7/6QskSHMph8Kn/Udpq7PPrZWADkIi1k4oibgABOXOWBk5ZbNbiDrZA==,iv:ZUAqvOpcVCXQD2PFzUh0e2m20t6gVT3mYb7S50iV/m8=,tag:AssxMqjVUEwQ4R6Y7eG9Tg==,type:str] | ||
| 3 | sops: | ||
| 4 | kms: [] | ||
| 5 | gcp_kms: [] | ||
| 6 | azure_kv: [] | ||
| 7 | hc_vault: [] | ||
| 8 | lastmodified: '2021-01-02T14:46:16Z' | ||
| 9 | mac: ENC[AES256_GCM,data:Phng7z7UlE6nO3FFIQPOHgKCqDm2uOGL57ryJbokjipSSdoWPinpz0zIJv9Z67b9uOf3CQoGtV4YwcudNkzDBKOyD8uA6RYwCKpbYcZIdiy8DLL46+VT/wq9toTkeDXM6jKupzzOARZhHT8DCOLqW7u8Q3S645cbTJmw0+LMIGk=,iv:y4KEh0+bKhtnSobKVdfaPuRsueNC1lcrEbUGfEAn+Bg=,tag:3Oi4e/hSgPVsoFQpnVQj+g==,type:str] | ||
| 10 | pgp: | ||
| 11 | - created_at: '2021-01-02T14:45:04Z' | ||
| 12 | enc: | | ||
| 13 | -----BEGIN PGP MESSAGE----- | ||
| 14 | |||
| 15 | hF4Dgwm4NZSaLAcSAQdAwWM12Zara3T2xDIX3rhakGxXFyme4LE5QZgE2GjnnWEw | ||
| 16 | T/vhPfsKFCjA2kAmj41NupjvTPL/nzfd7+MrdHRfC462Jrq+UF1W8A4bUa3OMH5J | ||
| 17 | 0l4BuFhl93w/VBftvnG8oSBAFCPNDapNADjTVJQStgsZa0/uD93NnCxyQmtuJYsQ | ||
| 18 | URlH0KMT6Kouaec4qk3SqkAHzaIIAukahBHAPf2C5cvXYw7AAOOBOdRaWycsmZDc | ||
| 19 | =S4Ig | ||
| 20 | -----END PGP MESSAGE----- | ||
| 21 | fp: F1AF20B9511B63F681A14E8D51AEFBCD1DEF68F8 | ||
| 22 | - created_at: '2021-01-02T14:45:04Z' | ||
| 23 | enc: | | ||
| 24 | -----BEGIN PGP MESSAGE----- | ||
| 25 | |||
| 26 | hF4DXxoViZlp6dISAQdA7apd+ipJ0lUiuPI5Sq6uj6iOQYFfuNDuzse1JFJMfn4w | ||
| 27 | McsGPcbMorZV0OVFmg9vuZ0GP9sb7mkm+oRuY9OeMDEifjWGHJ2UN4TvdEcCO1zx | ||
| 28 | 0l4BvYyzFbShlQjge7+nrzVi2lzEvqsozEW76K3arWb/iYLCRyl0/Vhw5WT4K/UE | ||
| 29 | fw4cbqz7JrogVLFNeWSRPk3Y+Dg4Pf9rQnw1EJhUEIczYjnfajPhYe5K4M01mOby | ||
| 30 | =B0n7 | ||
| 31 | -----END PGP MESSAGE----- | ||
| 32 | fp: 30D3453B8CD02FE2A3E7C78C0FB536FB87AE8F51 | ||
| 33 | unencrypted_suffix: _unencrypted | ||
| 34 | version: 3.6.1 | ||
diff --git a/modules/yggdrasil/hosts/ymir.nix b/modules/yggdrasil/hosts/ymir.nix new file mode 100644 index 00000000..b77a9216 --- /dev/null +++ b/modules/yggdrasil/hosts/ymir.nix | |||
| @@ -0,0 +1,19 @@ | |||
| 1 | { | ||
| 2 | addresses = [{ address = "ymir.yggdrasil.li"; }]; | ||
| 3 | settings.Ed25519PublicKey = "b/SobnMqByzHOQeO+iU7OZ1liD8a++knbi5ebNawnaC"; | ||
| 4 | rsaPublicKey = '' | ||
| 5 | -----BEGIN RSA PUBLIC KEY----- | ||
| 6 | MIICCgKCAgEAuInSfQf5euFXEVkLLzf9TumQJ+3WRsxX4uKdOXBqrIC7yjSBP8j9 | ||
| 7 | ql5rNWPzgXxFF5ERmwW+E3cyzJLU9Htu7r3muqM6nhSZizhCskifPRFc3e5ssSke | ||
| 8 | XhHICHfe90+qvab/hWx/NjkW59bBYIzDuJfq+ijDFMVNgOxaiM2f3/2prUUhP7bN | ||
| 9 | r3wVI8KCkOaknc0SOOmOhLzfJaD5wosqLOjgaNhlro2eMgMjQlxbyW8dVVgjwseR | ||
| 10 | Cl/mpu7r1pSMhS66RFH68wDoC3X81f7Zs9ZGDLTD8KXWhx0qgUMUAH4n6YGY0RM6 | ||
| 11 | BZ3qR/3KFRU64QPVAERpb0JdsU9ggCVydHkjrWW23ptHOPAOO5+yQj7tSDCKTRy9 | ||
| 12 | dHMQnbtPrgAb6iMhO1XTxA8Hdta1sCHsewsQekarwsA1bmk3hTgi/k8vwoGDUWtk | ||
| 13 | jgiDEPuutfmH4C6qxq9s+6lRboNKH8wgkVGpHiaq7mmePFdhzFdrj4+fYAMZTbil | ||
| 14 | 2iygsJ+yFOjA7U+iT6QDK33/MLsrQg0Ue6RPiG1qnDyax7gBAjz52iWkiuSkUXk0 | ||
| 15 | E5ImdP4XMILgGcWk8iPq5iRS03edE0pCpxGX3ZZwFE5+CoXgO6wR1ToL1vZEEHMQ | ||
| 16 | SHJPufKjkavPKbejPps/mLaJQVw3W10PAJssB9nxW2aHX3n0ugGaIvMCAwEAAQ== | ||
| 17 | -----END RSA PUBLIC KEY----- | ||
| 18 | ''; | ||
| 19 | } | ||
