typioca: 2.7.0 -> 2.8.0
[NixPkgs.git] / nixos / modules / system / boot / initrd-ssh.nix
bloba8cd2e8f05fcc6c6cd820fae9bbfb94611e3c29d
1 { config, lib, pkgs, ... }:
3 with lib;
5 let
7   cfg = config.boot.initrd.network.ssh;
8   shell = if cfg.shell == null then "/bin/ash" else cfg.shell;
9   inherit (config.programs.ssh) package;
11   enabled = let initrd = config.boot.initrd; in (initrd.network.enable || initrd.systemd.network.enable) && cfg.enable;
17   options.boot.initrd.network.ssh = {
18     enable = mkOption {
19       type = types.bool;
20       default = false;
21       description = lib.mdDoc ''
22         Start SSH service during initrd boot. It can be used to debug failing
23         boot on a remote server, enter pasphrase for an encrypted partition etc.
24         Service is killed when stage-1 boot is finished.
26         The sshd configuration is largely inherited from
27         {option}`services.openssh`.
28       '';
29     };
31     port = mkOption {
32       type = types.port;
33       default = 22;
34       description = lib.mdDoc ''
35         Port on which SSH initrd service should listen.
36       '';
37     };
39     shell = mkOption {
40       type = types.nullOr types.str;
41       default = null;
42       defaultText = ''"/bin/ash"'';
43       description = lib.mdDoc ''
44         Login shell of the remote user. Can be used to limit actions user can do.
45       '';
46     };
48     hostKeys = mkOption {
49       type = types.listOf (types.either types.str types.path);
50       default = [];
51       example = [
52         "/etc/secrets/initrd/ssh_host_rsa_key"
53         "/etc/secrets/initrd/ssh_host_ed25519_key"
54       ];
55       description = lib.mdDoc ''
56         Specify SSH host keys to import into the initrd.
58         To generate keys, use
59         {manpage}`ssh-keygen(1)`
60         as root:
62         ```
63         ssh-keygen -t rsa -N "" -f /etc/secrets/initrd/ssh_host_rsa_key
64         ssh-keygen -t ed25519 -N "" -f /etc/secrets/initrd/ssh_host_ed25519_key
65         ```
67         ::: {.warning}
68         Unless your bootloader supports initrd secrets, these keys
69         are stored insecurely in the global Nix store. Do NOT use
70         your regular SSH host private keys for this purpose or
71         you'll expose them to regular users!
73         Additionally, even if your initrd supports secrets, if
74         you're using initrd SSH to unlock an encrypted disk then
75         using your regular host keys exposes the private keys on
76         your unencrypted boot partition.
77         :::
78       '';
79     };
81     ignoreEmptyHostKeys = mkOption {
82       type = types.bool;
83       default = false;
84       description = lib.mdDoc ''
85         Allow leaving {option}`config.boot.initrd.network.ssh` empty,
86         to deploy ssh host keys out of band.
87       '';
88     };
90     authorizedKeys = mkOption {
91       type = types.listOf types.str;
92       default = config.users.users.root.openssh.authorizedKeys.keys;
93       defaultText = literalExpression "config.users.users.root.openssh.authorizedKeys.keys";
94       description = lib.mdDoc ''
95         Authorized keys for the root user on initrd.
96       '';
97     };
99     extraConfig = mkOption {
100       type = types.lines;
101       default = "";
102       description = lib.mdDoc "Verbatim contents of {file}`sshd_config`.";
103     };
104   };
106   imports =
107     map (opt: mkRemovedOptionModule ([ "boot" "initrd" "network" "ssh" ] ++ [ opt ]) ''
108       The initrd SSH functionality now uses OpenSSH rather than Dropbear.
110       If you want to keep your existing initrd SSH host keys, convert them with
111         $ dropbearconvert dropbear openssh dropbear_host_$type_key ssh_host_$type_key
112       and then set options.boot.initrd.network.ssh.hostKeys.
113     '') [ "hostRSAKey" "hostDSSKey" "hostECDSAKey" ];
115   config = let
116     # Nix complains if you include a store hash in initrd path names, so
117     # as an awful hack we drop the first character of the hash.
118     initrdKeyPath = path: if isString path
119       then path
120       else let name = builtins.baseNameOf path; in
121         builtins.unsafeDiscardStringContext ("/etc/ssh/" +
122           substring 1 (stringLength name) name);
124     sshdCfg = config.services.openssh;
126     sshdConfig = ''
127       UsePAM no
128       Port ${toString cfg.port}
130       PasswordAuthentication no
131       AuthorizedKeysFile %h/.ssh/authorized_keys %h/.ssh/authorized_keys2 /etc/ssh/authorized_keys.d/%u
132       ChallengeResponseAuthentication no
134       ${flip concatMapStrings cfg.hostKeys (path: ''
135         HostKey ${initrdKeyPath path}
136       '')}
138       KexAlgorithms ${concatStringsSep "," sshdCfg.settings.KexAlgorithms}
139       Ciphers ${concatStringsSep "," sshdCfg.settings.Ciphers}
140       MACs ${concatStringsSep "," sshdCfg.settings.Macs}
142       LogLevel ${sshdCfg.settings.LogLevel}
144       ${if sshdCfg.settings.UseDns then ''
145         UseDNS yes
146       '' else ''
147         UseDNS no
148       ''}
150       ${cfg.extraConfig}
151     '';
152   in mkIf enabled {
153     assertions = [
154       {
155         assertion = cfg.authorizedKeys != [];
156         message = "You should specify at least one authorized key for initrd SSH";
157       }
159       {
160         assertion = (cfg.hostKeys != []) || cfg.ignoreEmptyHostKeys;
161         message = ''
162           You must now pre-generate the host keys for initrd SSH.
163           See the boot.initrd.network.ssh.hostKeys documentation
164           for instructions.
165         '';
166       }
167     ];
169     warnings = lib.optional (config.boot.initrd.systemd.enable && cfg.shell != null) ''
170       Please set 'boot.initrd.systemd.users.root.shell' instead of 'boot.initrd.network.ssh.shell'
171     '';
173     boot.initrd.extraUtilsCommands = mkIf (!config.boot.initrd.systemd.enable) ''
174       copy_bin_and_libs ${package}/bin/sshd
175       cp -pv ${pkgs.glibc.out}/lib/libnss_files.so.* $out/lib
176     '';
178     boot.initrd.extraUtilsCommandsTest = mkIf (!config.boot.initrd.systemd.enable) ''
179       # sshd requires a host key to check config, so we pass in the test's
180       tmpkey="$(mktemp initrd-ssh-testkey.XXXXXXXXXX)"
181       cp "${../../../tests/initrd-network-ssh/ssh_host_ed25519_key}" "$tmpkey"
182       # keys from Nix store are world-readable, which sshd doesn't like
183       chmod 600 "$tmpkey"
184       echo -n ${escapeShellArg sshdConfig} |
185         $out/bin/sshd -t -f /dev/stdin \
186         -h "$tmpkey"
187       rm "$tmpkey"
188     '';
190     boot.initrd.network.postCommands = mkIf (!config.boot.initrd.systemd.enable) ''
191       echo '${shell}' > /etc/shells
192       echo 'root:x:0:0:root:/root:${shell}' > /etc/passwd
193       echo 'sshd:x:1:1:sshd:/var/empty:/bin/nologin' >> /etc/passwd
194       echo 'passwd: files' > /etc/nsswitch.conf
196       mkdir -p /var/log /var/empty
197       touch /var/log/lastlog
199       mkdir -p /etc/ssh
200       echo -n ${escapeShellArg sshdConfig} > /etc/ssh/sshd_config
202       echo "export PATH=$PATH" >> /etc/profile
203       echo "export LD_LIBRARY_PATH=$LD_LIBRARY_PATH" >> /etc/profile
205       mkdir -p /root/.ssh
206       ${concatStrings (map (key: ''
207         echo ${escapeShellArg key} >> /root/.ssh/authorized_keys
208       '') cfg.authorizedKeys)}
210       ${flip concatMapStrings cfg.hostKeys (path: ''
211         # keys from Nix store are world-readable, which sshd doesn't like
212         chmod 0600 "${initrdKeyPath path}"
213       '')}
215       /bin/sshd -e
216     '';
218     boot.initrd.postMountCommands = mkIf (!config.boot.initrd.systemd.enable) ''
219       # Stop sshd cleanly before stage 2.
220       #
221       # If you want to keep it around to debug post-mount SSH issues,
222       # run `touch /.keep_sshd` (either from an SSH session or in
223       # another initrd hook like preDeviceCommands).
224       if ! [ -e /.keep_sshd ]; then
225         pkill -x sshd
226       fi
227     '';
229     boot.initrd.secrets = listToAttrs
230       (map (path: nameValuePair (initrdKeyPath path) path) cfg.hostKeys);
232     # Systemd initrd stuff
233     boot.initrd.systemd = mkIf config.boot.initrd.systemd.enable {
234       users.sshd = { uid = 1; group = "sshd"; };
235       groups.sshd = { gid = 1; };
237       users.root.shell = mkIf (config.boot.initrd.network.ssh.shell != null) config.boot.initrd.network.ssh.shell;
239       contents."/etc/ssh/authorized_keys.d/root".text =
240         concatStringsSep "\n" config.boot.initrd.network.ssh.authorizedKeys;
241       contents."/etc/ssh/sshd_config".text = sshdConfig;
242       storePaths = ["${package}/bin/sshd"];
244       services.sshd = {
245         description = "SSH Daemon";
246         wantedBy = ["initrd.target"];
247         after = ["network.target" "initrd-nixos-copy-secrets.service"];
249         # Keys from Nix store are world-readable, which sshd doesn't
250         # like. If this were a real nix store and not the initrd, we
251         # neither would nor could do this
252         preStart = flip concatMapStrings cfg.hostKeys (path: ''
253           /bin/chmod 0600 "${initrdKeyPath path}"
254         '');
255         unitConfig.DefaultDependencies = false;
256         serviceConfig = {
257           ExecStart = "${package}/bin/sshd -D -f /etc/ssh/sshd_config";
258           Type = "simple";
259           KillMode = "process";
260           Restart = "on-failure";
261         };
262       };
263     };
265   };