Created
November 25, 2021 04:16
-
-
Save thefloweringash/503a060d8d9a393ddd04559b0a83d8ee to your computer and use it in GitHub Desktop.
openssh module for nix-darwin
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
{ config, lib, pkgs, ... }: | |
# Puppet style imperative config for setting sshd settings | |
let | |
cfg = config.services.openssh; | |
boolToStr = x: if x then "yes" else "no"; | |
# User config imported from nixpkgs | |
userOptions = with lib; { | |
options.openssh.authorizedKeys = { | |
keys = mkOption { | |
type = types.listOf types.str; | |
default = []; | |
description = '' | |
A list of verbatim OpenSSH public keys that should be added to the | |
user's authorized keys. The keys are added to a file that the SSH | |
daemon reads in addition to the the user's authorized_keys file. | |
You can combine the <literal>keys</literal> and | |
<literal>keyFiles</literal> options. | |
Warning: If you are using <literal>NixOps</literal> then don't use this | |
option since it will replace the key required for deployment via ssh. | |
''; | |
}; | |
keyFiles = mkOption { | |
type = types.listOf types.path; | |
default = []; | |
description = '' | |
A list of files each containing one OpenSSH public key that should be | |
added to the user's authorized keys. The contents of the files are | |
read at build time and added to a file that the SSH daemon reads in | |
addition to the the user's authorized_keys file. You can combine the | |
<literal>keyFiles</literal> and <literal>keys</literal> options. | |
''; | |
}; | |
}; | |
}; | |
# End of nixpkgs import | |
usersWithKeys = with lib; attrValues (flip filterAttrs config.users.users (n: u: | |
length u.openssh.authorizedKeys.keys != 0 || length u.openssh.authorizedKeys.keyFiles != 0 | |
)); | |
authKeysFiles = with lib; let | |
mkAuthKeyFile = u: pkgs.writeTextFile { | |
name = "${u.name}-authorized_keys"; | |
destination = "/${u.name}"; | |
text = '' | |
${concatStringsSep "\n" u.openssh.authorizedKeys.keys} | |
${concatMapStrings (f: readFile f + "\n") u.openssh.authorizedKeys.keyFiles} | |
''; | |
}; | |
in pkgs.symlinkJoin { name = "authorized_keys"; paths = (map mkAuthKeyFile usersWithKeys); }; | |
assertedValues = | |
(lib.optionalAttrs (cfg.passwordAuthentication != null) { | |
"PasswordAuthentication" = boolToStr cfg.passwordAuthentication; | |
}) // | |
(lib.optionalAttrs (cfg.challengeResponseAuthentication != null) { | |
"ChallengeResponseAuthentication" = boolToStr cfg.challengeResponseAuthentication; | |
}) // | |
(lib.optionalAttrs (cfg.forwardX11 != null) { | |
"X11Forwarding" = boolToStr cfg.forwardX11; | |
"XAuthLocation" = "/opt/X11/bin/xauth"; | |
}) // | |
(lib.optionalAttrs (cfg.authorizedKeysFiles != null) { | |
"AuthorizedKeysFile" = toString cfg.authorizedKeysFiles; | |
}); | |
# Manually specify only ssh and how to read it. Saves me ~450ms on | |
# activation. | |
augeasScript = pkgs.writeText "augeas-configure-ssh" '' | |
set /augeas/load/Sshd/lens "Sshd.lns" | |
set /augeas/load/Sshd/incl "/etc/ssh/sshd_config" | |
load | |
${lib.concatStrings (lib.mapAttrsToList (k: v: '' | |
set /files/etc/ssh/sshd_config/${k} "${v}" | |
'') assertedValues)} | |
save | |
''; | |
in | |
{ | |
options = { | |
services.openssh = { | |
passwordAuthentication = lib.mkOption { | |
type = lib.types.nullOr lib.types.bool; | |
default = null; | |
}; | |
challengeResponseAuthentication = lib.mkOption { | |
type = lib.types.nullOr lib.types.bool; | |
default = null; | |
}; | |
forwardX11 = lib.mkOption { | |
type = lib.types.nullOr lib.types.bool; | |
default = null; | |
}; | |
manageAuthorizedKeys = lib.mkEnableOption "management of ssh authorized keys"; | |
authorizedKeysFiles = lib.mkOption { | |
type = lib.types.nullOr (lib.types.listOf lib.types.str); | |
default = null; | |
description = "Files from which authorized keys are read."; | |
}; | |
}; | |
users.users = lib.mkOption { | |
type = with lib.types; attrsOf (submodule userOptions); | |
}; | |
}; | |
config = lib.mkMerge [ | |
(lib.mkIf (assertedValues != {}) { | |
assertions = [{ | |
assertion = cfg.passwordAuthentication == false -> cfg.challengeResponseAuthentication == false; | |
message = "passwordAuthentication might be ineffective if challengeResponseAuthentication is enabled"; | |
}]; | |
system.activationScripts.extraActivation.text = '' | |
echo "Configuring sshd..." | |
${pkgs.augeas}/bin/augtool --noautoload --noload -f ${augeasScript} | |
''; | |
}) | |
{ | |
assertions = [{ | |
assertion = (usersWithKeys != []) -> cfg.manageAuthorizedKeys; | |
message = '' | |
Users have ssh keys defined via users.users.<name>.openssh.authorizedKeys, | |
but ssh key management is not enabled via services.openssh.manageAuthorizedKeys. | |
Users with keys enabled: ${lib.concatMapStringsSep "," (u: u.name) usersWithKeys} | |
''; | |
}]; | |
} | |
(lib.mkIf cfg.manageAuthorizedKeys { | |
# macOS doesn't use authorized_keys.d, so we take it over entirely | |
services.openssh.authorizedKeysFiles = | |
[ "%h/.ssh/authorized_keys" "%h/.ssh/authorized_keys2" "/etc/ssh/authorized_keys.d/%u" ]; | |
system.activationScripts.extraActivation.text = '' | |
echo "Configuring user keys in /etc/ssh/authorized_keys.d" | |
${pkgs.rsync}/bin/rsync --delete -r --copy-links ${authKeysFiles}/ /etc/ssh/authorized_keys.d | |
''; | |
}) | |
]; | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment