Skip to content

Instantly share code, notes, and snippets.

@thefloweringash
Created November 25, 2021 04:16
Show Gist options
  • Save thefloweringash/503a060d8d9a393ddd04559b0a83d8ee to your computer and use it in GitHub Desktop.
Save thefloweringash/503a060d8d9a393ddd04559b0a83d8ee to your computer and use it in GitHub Desktop.
openssh module for nix-darwin
{ 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