commit 869a508c0a9bef69530bce119410d632e7937e03
parent e83547394530b8ebb1428a7c8f98eb7f9ef4fed1
Author: Daniel Barlow <dan@telent.net>
Date: Fri, 23 Aug 2024 20:35:07 +0100
add authorizedKeys option to ssh service
this has no apparent use as it stands, but opens the door to
having the keys managed by an external secrets service
Diffstat:
3 files changed, 35 insertions(+), 4 deletions(-)
diff --git a/examples/router-with-l2tp.nix b/examples/router-with-l2tp.nix
@@ -184,7 +184,11 @@ in rec {
dependencies = [ config.services.hostname ];
};
- services.sshd = svc.ssh.build { };
+ services.sshd = svc.ssh.build {
+ authorizedKeys = {
+ root = rsecrets.root.openssh.authorizedKeys.keys;
+ };
+ };
services.lns-address = let
ns = "$(output_word ${services.bootstrap-dhcpc} dns 1)";
diff --git a/modules/ssh/default.nix b/modules/ssh/default.nix
@@ -19,6 +19,7 @@ in {
type = liminix.lib.types.serviceDefn;
};
};
+ config.programs.busybox.options.FEATURE_FANCY_ECHO = "y";
config.system.service = {
ssh = config.system.callService ./ssh.nix {
address = mkOption {
@@ -41,6 +42,16 @@ in {
type = types.bool; default = false;
description = "Allow remote hosts to connect to local forwarded ports (by default they are bound to loopback)";
};
+ authorizedKeys = mkOption {
+ type = types.nullOr (types.attrsOf (types.listOf types.nonEmptyStr));
+ example = {
+ root = ["ssh-rsa AAAAB3N...aZaZ"];
+ alice = ["ssh-rsa AAAAB3N...qS4r"];
+ bob = [];
+ };
+ default = null;
+ description = "Authorized SSH public keys for each username. If this optin is provided it overrides any keys found in /home/{username}/.ssh";
+ };
extraConfig = mkOption {
type = types.separatedString " ";
default = "";
diff --git a/modules/ssh/ssh.nix b/modules/ssh/ssh.nix
@@ -3,10 +3,11 @@
, dropbear
, lib
}:
-p :
+{authorizedKeys, ...} @ p :
let
+ name = "sshd";
inherit (liminix.services) longrun;
- inherit (lib) concatStringsSep;
+ inherit (lib) concatStringsSep mapAttrs mapAttrsToList;
options =
[
"-e" # pass environment to child
@@ -21,19 +22,34 @@ let
(lib.optional (! p.allowLocalPortForward) "-j") ++
(lib.optional (! p.allowRemotePortForward) "-k") ++
(lib.optional (! p.allowRemoteConnectionToForwardedPorts) "-a") ++
+ (lib.optionals (authorizedKeys != null)
+ ["-U" "/run/${name}/authorized_keys/%n"]) ++
[(if p.address != null
then "-p ${p.address}:${p.port}"
else "-p ${builtins.toString p.port}")] ++
[p.extraConfig];
+ authKeysConcat =
+ if authorizedKeys != null
+ then mapAttrs
+ (n : v : concatStringsSep "\\n" v)
+ authorizedKeys
+ else {};
in
longrun {
- name = "sshd";
+ inherit name;
# we need /run/dropbear to point to hostkey storage, as that
# pathname is hardcoded into the binary.
# env -i clears the environment so we don't pass anything weird to
# ssh sessions
run = ''
ln -s $(mkstate dropbear) /run
+ mkdir -p /run/${name}/authorized_keys
+ ${concatStringsSep "\n"
+ (mapAttrsToList
+ (n : v : "echo -e '${v}' > /run/${name}/authorized_keys/${n} ")
+ authKeysConcat
+ )
+ }
. /etc/profile # sets PATH but do we need this? it's the same file as ashrc
exec env -i ENV=/etc/ashrc PATH=$PATH ${dropbear}/bin/dropbear ${concatStringsSep " " options}
'';