-
-
Notifications
You must be signed in to change notification settings - Fork 18.2k
systemd: make systemd-ssh-generator work #372979
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from all commits
c581c71
5e43294
aab69d7
63842ed
afeb76d
0ea1aed
8a641dd
a0962df
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -49,6 +49,15 @@ in | |
| description = "Whether to configure SSH_ASKPASS in the environment."; | ||
| }; | ||
|
|
||
| systemd-ssh-proxy.enable = lib.mkOption { | ||
| type = lib.types.bool; | ||
| default = true; | ||
| description = '' | ||
| Whether to enable systemd's ssh proxy plugin. | ||
| See {manpage}`systemd-ssh-proxy(1)`. | ||
| ''; | ||
| }; | ||
|
|
||
| askPassword = lib.mkOption { | ||
| type = lib.types.str; | ||
| default = "${pkgs.x11_ssh_askpass}/libexec/x11-ssh-askpass"; | ||
|
|
@@ -332,6 +341,11 @@ in | |
| # Custom options from `extraConfig`, to override generated options | ||
| ${cfg.extraConfig} | ||
|
|
||
| ${lib.optionalString cfg.systemd-ssh-proxy.enable '' | ||
| # See systemd-ssh-proxy(1) | ||
| Include ${config.systemd.package}/lib/systemd/ssh_config.d/20-systemd-ssh-proxy.conf | ||
|
||
| ''} | ||
|
|
||
| # Generated options from other settings | ||
| Host * | ||
| GlobalKnownHostsFile ${builtins.concatStringsSep " " knownHostsFiles} | ||
|
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -560,67 +560,16 @@ in | |
| "ssh/sshd_config".source = sshconf; | ||
| }; | ||
|
|
||
| systemd = | ||
| let | ||
| service = | ||
| { description = "SSH Daemon"; | ||
| wantedBy = lib.optional (!cfg.startWhenNeeded) "multi-user.target"; | ||
| after = [ "network.target" ]; | ||
| stopIfChanged = false; | ||
| path = [ cfg.package pkgs.gawk ]; | ||
| environment.LD_LIBRARY_PATH = nssModulesPath; | ||
|
|
||
| restartTriggers = lib.optionals (!cfg.startWhenNeeded) [ | ||
| config.environment.etc."ssh/sshd_config".source | ||
| ]; | ||
|
|
||
| preStart = | ||
| '' | ||
| # Make sure we don't write to stdout, since in case of | ||
| # socket activation, it goes to the remote side (#19589). | ||
| exec >&2 | ||
|
|
||
| ${lib.flip lib.concatMapStrings cfg.hostKeys (k: '' | ||
| if ! [ -s "${k.path}" ]; then | ||
| if ! [ -h "${k.path}" ]; then | ||
| rm -f "${k.path}" | ||
| fi | ||
| mkdir -p "$(dirname '${k.path}')" | ||
| chmod 0755 "$(dirname '${k.path}')" | ||
| ssh-keygen \ | ||
| -t "${k.type}" \ | ||
| ${lib.optionalString (k ? bits) "-b ${toString k.bits}"} \ | ||
| ${lib.optionalString (k ? rounds) "-a ${toString k.rounds}"} \ | ||
| ${lib.optionalString (k ? comment) "-C '${k.comment}'"} \ | ||
| ${lib.optionalString (k ? openSSHFormat && k.openSSHFormat) "-o"} \ | ||
| -f "${k.path}" \ | ||
| -N "" | ||
| fi | ||
| '')} | ||
philiptaron marked this conversation as resolved.
Outdated
Show resolved
Hide resolved
|
||
| ''; | ||
|
|
||
| serviceConfig = | ||
| { ExecStart = | ||
| (lib.optionalString cfg.startWhenNeeded "-") + | ||
| "${cfg.package}/bin/sshd " + (lib.optionalString cfg.startWhenNeeded "-i ") + | ||
| "-D " + # don't detach into a daemon process | ||
| "-f /etc/ssh/sshd_config"; | ||
| KillMode = "process"; | ||
| } // (if cfg.startWhenNeeded then { | ||
| StandardInput = "socket"; | ||
| StandardError = "journal"; | ||
| } else { | ||
| Restart = "always"; | ||
| Type = "simple"; | ||
| }); | ||
|
|
||
| }; | ||
| in | ||
|
|
||
| if cfg.startWhenNeeded then { | ||
| systemd.tmpfiles.settings."ssh-root-provision" = { | ||
| "/root"."d-" = { user = "root"; group = ":root"; mode = ":700"; }; | ||
| "/root/.ssh"."d-" = { user = "root"; group = ":root"; mode = ":700"; }; | ||
| "/root/.ssh/authorized_keys"."f^" = { user = "root"; group = ":root"; mode = ":600"; argument = "ssh.authorized_keys.root"; }; | ||
| }; | ||
|
|
||
| sockets.sshd = | ||
| { description = "SSH Socket"; | ||
| systemd = | ||
| { | ||
| sockets.sshd = lib.mkIf cfg.startWhenNeeded { | ||
| description = "SSH Socket"; | ||
| wantedBy = [ "sockets.target" ]; | ||
| socketConfig.ListenStream = if cfg.listenAddresses != [] then | ||
| lib.concatMap | ||
|
|
@@ -633,14 +582,81 @@ in | |
| socketConfig.Accept = true; | ||
| # Prevent brute-force attacks from shutting down socket | ||
| socketConfig.TriggerLimitIntervalSec = 0; | ||
| }; | ||
|
|
||
| services."sshd@" = { | ||
| description = "SSH per-connection Daemon"; | ||
| after = [ "network.target" "sshd-keygen.service" ]; | ||
| wants = [ "sshd-keygen.service" ]; | ||
| stopIfChanged = false; | ||
| path = [ cfg.package ]; | ||
philiptaron marked this conversation as resolved.
Outdated
Show resolved
Hide resolved
|
||
| environment.LD_LIBRARY_PATH = nssModulesPath; | ||
|
|
||
| serviceConfig = { | ||
philiptaron marked this conversation as resolved.
Outdated
Show resolved
Hide resolved
|
||
| Type = "notify"; | ||
| ExecStart = lib.concatStringsSep " " [ | ||
| "-${lib.getExe' cfg.package "sshd"}" | ||
| "-i" | ||
|
||
| "-D" | ||
philiptaron marked this conversation as resolved.
Outdated
Show resolved
Hide resolved
|
||
| "-f /etc/ssh/sshd_config" | ||
philiptaron marked this conversation as resolved.
Outdated
Show resolved
Hide resolved
|
||
| ]; | ||
| KillMode = "process"; | ||
philiptaron marked this conversation as resolved.
Outdated
Show resolved
Hide resolved
|
||
| StandardInput = "socket"; | ||
| StandardError = "journal"; | ||
| }; | ||
| }; | ||
|
|
||
| services.sshd = lib.mkIf (! cfg.startWhenNeeded) { | ||
| description = "SSH Daemon"; | ||
| wantedBy = [ "multi-user.target" ]; | ||
| after = [ "network.target" "sshd-keygen.service" ]; | ||
| wants = [ "sshd-keygen.service" ]; | ||
| stopIfChanged = false; | ||
| path = [ cfg.package ]; | ||
| environment.LD_LIBRARY_PATH = nssModulesPath; | ||
|
|
||
| restartTriggers = [ config.environment.etc."ssh/sshd_config".source ]; | ||
|
|
||
| serviceConfig = { | ||
| Type = "notify"; | ||
| Restart = "always"; | ||
philiptaron marked this conversation as resolved.
Outdated
Show resolved
Hide resolved
|
||
| ExecStart = lib.concatStringsSep " " [ | ||
| (lib.getExe' cfg.package "sshd") | ||
| "-D" | ||
| "-f" "/etc/ssh/sshd_config" | ||
| ]; | ||
| KillMode = "process"; | ||
| }; | ||
| }; | ||
|
|
||
| services."sshd@" = service; | ||
|
|
||
| } else { | ||
|
|
||
| services.sshd = service; | ||
|
|
||
| services.sshd-keygen = { | ||
| description = "SSH Host Keys Generation"; | ||
| unitConfig = { | ||
| ConditionFileNotEmpty = map (k: "|!${k.path}") cfg.hostKeys; | ||
| }; | ||
| serviceConfig = { | ||
| Type = "oneshot"; | ||
NyCodeGHG marked this conversation as resolved.
Outdated
Show resolved
Hide resolved
|
||
| }; | ||
| path = [ cfg.package ]; | ||
| script = | ||
| lib.flip lib.concatMapStrings cfg.hostKeys (k: '' | ||
| if ! [ -s "${k.path}" ]; then | ||
| if ! [ -h "${k.path}" ]; then | ||
| rm -f "${k.path}" | ||
| fi | ||
| mkdir -p "$(dirname '${k.path}')" | ||
| chmod 0755 "$(dirname '${k.path}')" | ||
| ssh-keygen \ | ||
| -t "${k.type}" \ | ||
| ${lib.optionalString (k ? bits) "-b ${toString k.bits}"} \ | ||
| ${lib.optionalString (k ? rounds) "-a ${toString k.rounds}"} \ | ||
| ${lib.optionalString (k ? comment) "-C '${k.comment}'"} \ | ||
| ${lib.optionalString (k ? openSSHFormat && k.openSSHFormat) "-o"} \ | ||
| -f "${k.path}" \ | ||
| -N "" | ||
| fi | ||
| ''); | ||
| }; | ||
| }; | ||
|
|
||
| networking.firewall.allowedTCPPorts = lib.optionals cfg.openFirewall cfg.ports; | ||
|
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -628,7 +628,12 @@ in | |
| systemd.managerEnvironment = { | ||
| # Doesn't contain systemd itself - everything works so it seems to use the compiled-in value for its tools | ||
| # util-linux is needed for the main fsck utility wrapping the fs-specific ones | ||
| PATH = lib.makeBinPath (config.system.fsPackages ++ [cfg.package.util-linux]); | ||
| PATH = lib.makeBinPath ( | ||
| config.system.fsPackages | ||
| ++ [cfg.package.util-linux] | ||
| # systemd-ssh-generator needs sshd in PATH | ||
| ++ lib.optional config.services.openssh.enable config.services.openssh.package | ||
|
||
| ); | ||
| LOCALE_ARCHIVE = "/run/current-system/sw/lib/locale/locale-archive"; | ||
| TZDIR = "/etc/zoneinfo"; | ||
| # If SYSTEMD_UNIT_PATH ends with an empty component (":"), the usual unit load path will be appended to the contents of the variable | ||
|
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,76 @@ | ||
| { | ||
| pkgs, | ||
| lib, | ||
| config, | ||
| ... | ||
| }: | ||
| # This tests that systemd-ssh-proxy and systemd-ssh-generator work correctly with: | ||
| # - a local unix socket on the same system | ||
| # - a vsock socket inside a vm | ||
| let | ||
| inherit (import ./ssh-keys.nix pkgs) | ||
| snakeOilEd25519PrivateKey | ||
| snakeOilEd25519PublicKey | ||
|
||
| ; | ||
| qemu = config.nodes.virthost.virtualisation.qemu.package; | ||
| iso = | ||
| (import ../lib/eval-config.nix { | ||
| inherit (pkgs.stdenv.hostPlatform) system; | ||
| modules = [ | ||
| ../modules/installer/cd-dvd/iso-image.nix | ||
| { | ||
| services.openssh = { | ||
| enable = true; | ||
| settings.PermitRootLogin = "prohibit-password"; | ||
| }; | ||
| isoImage.isoBaseName = lib.mkForce "nixos"; | ||
| isoImage.makeBiosBootable = true; | ||
| system.stateVersion = lib.trivial.release; | ||
| } | ||
| ]; | ||
| }).config.system.build.isoImage; | ||
| in | ||
| { | ||
| name = "systemd-ssh-proxy"; | ||
| meta.maintainers = with pkgs.lib.maintainers; [ marie ]; | ||
|
|
||
| nodes = { | ||
| virthost = { | ||
| services.openssh = { | ||
| enable = true; | ||
| settings.PermitRootLogin = "prohibit-password"; | ||
| }; | ||
| users.users = { | ||
| root.openssh.authorizedKeys.keys = [ snakeOilEd25519PublicKey ]; | ||
| nixos = { | ||
| isNormalUser = true; | ||
| }; | ||
| }; | ||
| systemd.services.test-vm = { | ||
| script = "${lib.getExe qemu} --nographic -smp 1 -m 512 -cdrom ${iso}/iso/nixos.iso -device vhost-vsock-pci,guest-cid=3 -smbios type=11,value=\"io.systemd.credential:ssh.authorized_keys.root=${snakeOilEd25519PublicKey}\""; | ||
|
||
| }; | ||
| }; | ||
| }; | ||
|
|
||
| testScript = '' | ||
| virthost.systemctl("start test-vm.service") | ||
|
|
||
| virthost.succeed("mkdir -p ~/.ssh") | ||
| virthost.succeed("cp '${snakeOilEd25519PrivateKey}' ~/.ssh/id_ed25519") | ||
| virthost.succeed("chmod 600 ~/.ssh/id_ed25519") | ||
|
|
||
| with subtest("ssh into a vm with vsock"): | ||
| virthost.wait_until_succeeds("systemctl is-active test-vm.service") | ||
| virthost.wait_until_succeeds("ssh -i ~/.ssh/id_ed25519 vsock/3 echo meow | grep meow") | ||
| virthost.wait_until_succeeds("ssh -i ~/.ssh/id_ed25519 vsock/3 shutdown now") | ||
| virthost.wait_until_succeeds("! systemctl is-active test-vm.service") | ||
|
|
||
| with subtest("elevate permissions using local ssh socket"): | ||
| virthost.wait_for_unit("sshd-unix-local.socket") | ||
| virthost.succeed("sudo --user=nixos mkdir -p /home/nixos/.ssh") | ||
| virthost.succeed("cp ~/.ssh/id_ed25519 /home/nixos/.ssh/id_ed25519") | ||
| virthost.succeed("chmod 600 /home/nixos/.ssh/id_ed25519") | ||
| virthost.succeed("chown nixos /home/nixos/.ssh/id_ed25519") | ||
| virthost.succeed("sudo --user=nixos ssh -o StrictHostKeyChecking=no -o IdentitiesOnly=yes -i /home/nixos/.ssh/id_ed25519 root@.host whoami | grep root") | ||
| ''; | ||
| } | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,32 @@ | ||
| From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 | ||
| From: Marie Ramlow <me@nycode.dev> | ||
| Date: Sun, 24 Nov 2024 20:04:35 +0100 | ||
| Subject: [PATCH] meson: Don't link ssh dropins | ||
|
|
||
|
||
| --- | ||
| meson.build | 4 ++-- | ||
| 1 file changed, 2 insertions(+), 2 deletions(-) | ||
|
|
||
| diff --git a/meson.build b/meson.build | ||
| index d392610625..c17d0a1feb 100644 | ||
| --- a/meson.build | ||
| +++ b/meson.build | ||
| @@ -211,13 +211,13 @@ sshconfdir = get_option('sshconfdir') | ||
| if sshconfdir == '' | ||
| sshconfdir = sysconfdir / 'ssh/ssh_config.d' | ||
| endif | ||
| -conf.set10('LINK_SSH_PROXY_DROPIN', sshconfdir != 'no' and not sshconfdir.startswith('/usr/')) | ||
| +conf.set10('LINK_SSH_PROXY_DROPIN', 0) | ||
|
|
||
| sshdconfdir = get_option('sshdconfdir') | ||
| if sshdconfdir == '' | ||
| sshdconfdir = sysconfdir / 'ssh/sshd_config.d' | ||
| endif | ||
| -conf.set10('LINK_SSHD_USERDB_DROPIN', sshdconfdir != 'no' and not sshdconfdir.startswith('/usr/')) | ||
| +conf.set10('LINK_SSHD_USERDB_DROPIN', 0) | ||
|
|
||
| sshdprivsepdir = get_option('sshdprivsepdir') | ||
| conf.set10('CREATE_SSHDPRIVSEPDIR', sshdprivsepdir != 'no' and not sshdprivsepdir.startswith('/usr/')) | ||
| -- | ||
| 2.47.0 | ||
|
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,25 @@ | ||
| From 7be486fb25dc4ea212cb17f6a3f4a434a557b0d9 Mon Sep 17 00:00:00 2001 | ||
| From: Marie Ramlow <me@nycode.dev> | ||
| Date: Fri, 10 Jan 2025 15:51:33 +0100 | ||
| Subject: [PATCH] install: unit_file_exists_full: follow symlinks | ||
|
|
||
|
||
| --- | ||
| src/shared/install.c | 2 +- | ||
| 1 file changed, 1 insertion(+), 1 deletion(-) | ||
|
|
||
| diff --git a/src/shared/install.c b/src/shared/install.c | ||
| index 53566b7eef..0975cd47c7 100644 | ||
| --- a/src/shared/install.c | ||
| +++ b/src/shared/install.c | ||
| @@ -3217,7 +3217,7 @@ int unit_file_exists_full(RuntimeScope scope, const LookupPaths *lp, const char | ||
| &c, | ||
| lp, | ||
| name, | ||
| - /* flags= */ 0, | ||
| + /* flags= */ SEARCH_FOLLOW_CONFIG_SYMLINKS, | ||
| ret_path ? &info : NULL, | ||
| /* changes= */ NULL, | ||
| /* n_changes= */ NULL); | ||
| -- | ||
| 2.47.0 | ||
|
|
||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I don't think there's a proper issue about this yet, but systemd itself would also support passing credentials via kernel params which could be very useful for VMs. But that doesn't work with our current systemd-in-stage-1 implementation. @ElvishJerricco wrote a reproducer for that in https://gist.github.com/ElvishJerricco/dca95eb4ea9fc410bd525c3b15b68fdd