ddns-go: 6.7.7 -> 6.8.0 (#373902)
[NixPkgs.git] / nixos / modules / system / boot / initrd-ssh.nix
blob474f89e1ccf3aa586042e1a053f26718686973db
2   config,
3   lib,
4   pkgs,
5   ...
6 }:
8 with lib;
10 let
12   cfg = config.boot.initrd.network.ssh;
13   shell = if cfg.shell == null then "/bin/ash" else cfg.shell;
14   inherit (config.programs.ssh) package;
16   enabled =
17     let
18       initrd = config.boot.initrd;
19     in
20     (initrd.network.enable || initrd.systemd.network.enable) && cfg.enable;
26   options.boot.initrd.network.ssh = {
27     enable = mkOption {
28       type = types.bool;
29       default = false;
30       description = ''
31         Start SSH service during initrd boot. It can be used to debug failing
32         boot on a remote server, enter pasphrase for an encrypted partition etc.
33         Service is killed when stage-1 boot is finished.
35         The sshd configuration is largely inherited from
36         {option}`services.openssh`.
37       '';
38     };
40     port = mkOption {
41       type = types.port;
42       default = 22;
43       description = ''
44         Port on which SSH initrd service should listen.
45       '';
46     };
48     shell = mkOption {
49       type = types.nullOr types.str;
50       default = null;
51       defaultText = ''"/bin/ash"'';
52       description = ''
53         Login shell of the remote user. Can be used to limit actions user can do.
54       '';
55     };
57     hostKeys = mkOption {
58       type = types.listOf (types.either types.str types.path);
59       default = [ ];
60       example = [
61         "/etc/secrets/initrd/ssh_host_rsa_key"
62         "/etc/secrets/initrd/ssh_host_ed25519_key"
63       ];
64       description = ''
65         Specify SSH host keys to import into the initrd.
67         To generate keys, use
68         {manpage}`ssh-keygen(1)`
69         as root:
71         ```
72         ssh-keygen -t rsa -N "" -f /etc/secrets/initrd/ssh_host_rsa_key
73         ssh-keygen -t ed25519 -N "" -f /etc/secrets/initrd/ssh_host_ed25519_key
74         ```
76         ::: {.warning}
77         Unless your bootloader supports initrd secrets, these keys
78         are stored insecurely in the global Nix store. Do NOT use
79         your regular SSH host private keys for this purpose or
80         you'll expose them to regular users!
82         Additionally, even if your initrd supports secrets, if
83         you're using initrd SSH to unlock an encrypted disk then
84         using your regular host keys exposes the private keys on
85         your unencrypted boot partition.
86         :::
87       '';
88     };
90     ignoreEmptyHostKeys = mkOption {
91       type = types.bool;
92       default = false;
93       description = ''
94         Allow leaving {option}`config.boot.initrd.network.ssh.hostKeys` empty,
95         to deploy ssh host keys out of band.
96       '';
97     };
99     authorizedKeys = mkOption {
100       type = types.listOf types.str;
101       default = config.users.users.root.openssh.authorizedKeys.keys;
102       defaultText = literalExpression "config.users.users.root.openssh.authorizedKeys.keys";
103       description = ''
104         Authorized keys for the root user on initrd.
105         You can combine the `authorizedKeys` and `authorizedKeyFiles` options.
106       '';
107       example = [
108         "ssh-rsa AAAAB3NzaC1yc2etc/etc/etcjwrsh8e596z6J0l7 example@host"
109         "ssh-ed25519 AAAAC3NzaCetcetera/etceteraJZMfk3QPfQ foo@bar"
110       ];
111     };
113     authorizedKeyFiles = mkOption {
114       type = types.listOf types.path;
115       default = config.users.users.root.openssh.authorizedKeys.keyFiles;
116       defaultText = literalExpression "config.users.users.root.openssh.authorizedKeys.keyFiles";
117       description = ''
118         Authorized keys taken from files for the root user on initrd.
119         You can combine the `authorizedKeyFiles` and `authorizedKeys` options.
120       '';
121     };
123     extraConfig = mkOption {
124       type = types.lines;
125       default = "";
126       description = "Verbatim contents of {file}`sshd_config`.";
127     };
128   };
130   imports =
131     map
132       (
133         opt:
134         mkRemovedOptionModule
135           (
136             [
137               "boot"
138               "initrd"
139               "network"
140               "ssh"
141             ]
142             ++ [ opt ]
143           )
144           ''
145             The initrd SSH functionality now uses OpenSSH rather than Dropbear.
147             If you want to keep your existing initrd SSH host keys, convert them with
148               $ dropbearconvert dropbear openssh dropbear_host_$type_key ssh_host_$type_key
149             and then set options.boot.initrd.network.ssh.hostKeys.
150           ''
151       )
152       [
153         "hostRSAKey"
154         "hostDSSKey"
155         "hostECDSAKey"
156       ];
158   config =
159     let
160       # Nix complains if you include a store hash in initrd path names, so
161       # as an awful hack we drop the first character of the hash.
162       initrdKeyPath =
163         path:
164         if isString path then
165           path
166         else
167           let
168             name = builtins.baseNameOf path;
169           in
170           builtins.unsafeDiscardStringContext ("/etc/ssh/" + substring 1 (stringLength name) name);
172       sshdCfg = config.services.openssh;
174       sshdConfig =
175         ''
176           UsePAM no
177           Port ${toString cfg.port}
179           PasswordAuthentication no
180           AuthorizedKeysFile %h/.ssh/authorized_keys %h/.ssh/authorized_keys2 /etc/ssh/authorized_keys.d/%u
181           ChallengeResponseAuthentication no
183           ${flip concatMapStrings cfg.hostKeys (path: ''
184             HostKey ${initrdKeyPath path}
185           '')}
187         ''
188         + lib.optionalString (sshdCfg.settings.KexAlgorithms != null) ''
189           KexAlgorithms ${concatStringsSep "," sshdCfg.settings.KexAlgorithms}
190         ''
191         + lib.optionalString (sshdCfg.settings.Ciphers != null) ''
192           Ciphers ${concatStringsSep "," sshdCfg.settings.Ciphers}
193         ''
194         + lib.optionalString (sshdCfg.settings.Macs != null) ''
195           MACs ${concatStringsSep "," sshdCfg.settings.Macs}
196         ''
197         + ''
199           LogLevel ${sshdCfg.settings.LogLevel}
201           ${
202             if sshdCfg.settings.UseDns then
203               ''
204                 UseDNS yes
205               ''
206             else
207               ''
208                 UseDNS no
209               ''
210           }
212           ${optionalString (!config.boot.initrd.systemd.enable) ''
213             SshdSessionPath /bin/sshd-session
214           ''}
216           ${cfg.extraConfig}
217         '';
218     in
219     mkIf enabled {
220       assertions = [
221         {
222           assertion = cfg.authorizedKeys != [ ] || cfg.authorizedKeyFiles != [ ];
223           message = "You should specify at least one authorized key for initrd SSH";
224         }
226         {
227           assertion = (cfg.hostKeys != [ ]) || cfg.ignoreEmptyHostKeys;
228           message = ''
229             You must now pre-generate the host keys for initrd SSH.
230             See the boot.initrd.network.ssh.hostKeys documentation
231             for instructions.
232           '';
233         }
234       ];
236       warnings = lib.optional (config.boot.initrd.systemd.enable && cfg.shell != null) ''
237         Please set 'boot.initrd.systemd.users.root.shell' instead of 'boot.initrd.network.ssh.shell'
238       '';
240       boot.initrd.extraUtilsCommands = mkIf (!config.boot.initrd.systemd.enable) ''
241         copy_bin_and_libs ${package}/bin/sshd
242         copy_bin_and_libs ${package}/libexec/sshd-session
243         cp -pv ${pkgs.glibc.out}/lib/libnss_files.so.* $out/lib
244       '';
246       boot.initrd.extraUtilsCommandsTest = mkIf (!config.boot.initrd.systemd.enable) ''
247         # sshd requires a host key to check config, so we pass in the test's
248         tmpkey="$(mktemp initrd-ssh-testkey.XXXXXXXXXX)"
249         cp "${../../../tests/initrd-network-ssh/ssh_host_ed25519_key}" "$tmpkey"
250         # keys from Nix store are world-readable, which sshd doesn't like
251         chmod 600 "$tmpkey"
252         echo -n ${escapeShellArg sshdConfig} |
253           $out/bin/sshd -t -f /dev/stdin \
254           -h "$tmpkey"
255         rm "$tmpkey"
256       '';
258       boot.initrd.network.postCommands = mkIf (!config.boot.initrd.systemd.enable) ''
259         echo '${shell}' > /etc/shells
260         echo 'root:x:0:0:root:/root:${shell}' > /etc/passwd
261         echo 'sshd:x:1:1:sshd:/var/empty:/bin/nologin' >> /etc/passwd
262         echo 'passwd: files' > /etc/nsswitch.conf
264         mkdir -p /var/log /var/empty
265         touch /var/log/lastlog
267         mkdir -p /etc/ssh
268         echo -n ${escapeShellArg sshdConfig} > /etc/ssh/sshd_config
270         echo "export PATH=$PATH" >> /etc/profile
271         echo "export LD_LIBRARY_PATH=$LD_LIBRARY_PATH" >> /etc/profile
273         mkdir -p /root/.ssh
274         ${concatStrings (
275           map (key: ''
276             echo ${escapeShellArg key} >> /root/.ssh/authorized_keys
277           '') cfg.authorizedKeys
278         )}
279         ${concatStrings (
280           map (keyFile: ''
281             cat ${keyFile} >> /root/.ssh/authorized_keys
282           '') cfg.authorizedKeyFiles
283         )}
285         ${flip concatMapStrings cfg.hostKeys (path: ''
286           # keys from Nix store are world-readable, which sshd doesn't like
287           chmod 0600 "${initrdKeyPath path}"
288         '')}
290         /bin/sshd -e
291       '';
293       boot.initrd.postMountCommands = mkIf (!config.boot.initrd.systemd.enable) ''
294         # Stop sshd cleanly before stage 2.
295         #
296         # If you want to keep it around to debug post-mount SSH issues,
297         # run `touch /.keep_sshd` (either from an SSH session or in
298         # another initrd hook like preDeviceCommands).
299         if ! [ -e /.keep_sshd ]; then
300           pkill -x sshd
301         fi
302       '';
304       boot.initrd.secrets = listToAttrs (
305         map (path: nameValuePair (initrdKeyPath path) path) cfg.hostKeys
306       );
308       # Systemd initrd stuff
309       boot.initrd.systemd = mkIf config.boot.initrd.systemd.enable {
310         users.sshd = {
311           uid = 1;
312           group = "sshd";
313         };
314         groups.sshd = {
315           gid = 1;
316         };
318         users.root.shell = mkIf (
319           config.boot.initrd.network.ssh.shell != null
320         ) config.boot.initrd.network.ssh.shell;
322         contents = {
323           "/etc/ssh/sshd_config".text = sshdConfig;
324           "/etc/ssh/authorized_keys.d/root".text = concatStringsSep "\n" (
325             config.boot.initrd.network.ssh.authorizedKeys
326             ++ (map (file: lib.fileContents file) config.boot.initrd.network.ssh.authorizedKeyFiles)
327           );
328         };
329         storePaths = [
330           "${package}/bin/sshd"
331           "${package}/libexec/sshd-session"
332         ];
334         services.sshd = {
335           description = "SSH Daemon";
336           wantedBy = [ "initrd.target" ];
337           after = [
338             "network.target"
339             "initrd-nixos-copy-secrets.service"
340           ];
341           before = [ "shutdown.target" ];
342           conflicts = [ "shutdown.target" ];
344           # Keys from Nix store are world-readable, which sshd doesn't
345           # like. If this were a real nix store and not the initrd, we
346           # neither would nor could do this
347           preStart = flip concatMapStrings cfg.hostKeys (path: ''
348             /bin/chmod 0600 "${initrdKeyPath path}"
349           '');
350           unitConfig.DefaultDependencies = false;
351           serviceConfig = {
352             ExecStart = "${package}/bin/sshd -D -f /etc/ssh/sshd_config";
353             Type = "simple";
354             KillMode = "process";
355             Restart = "on-failure";
356           };
357         };
358       };
360     };