diff --git a/clanServices/admin/default.nix b/clanServices/admin/default.nix index 0b9bb4bca..c7c6d16f0 100644 --- a/clanServices/admin/default.nix +++ b/clanServices/admin/default.nix @@ -9,15 +9,37 @@ interface = { lib, ... }: { - options.allowedKeys = lib.mkOption { - default = { }; - type = lib.types.attrsOf lib.types.str; - description = "The allowed public keys for ssh access to the admin user"; - example = { - "key_1" = "ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABAQD..."; + + options = { + allowedKeys = lib.mkOption { + default = { }; + type = lib.types.attrsOf lib.types.str; + description = "The allowed public keys for ssh access to the admin user"; + example = { + "key_1" = "ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABAQD..."; + }; + }; + + rsaHostKey.enable = lib.mkEnableOption "Generate RSA host key"; + + # TODO: allow per-server domains that we than collect in the inventory + #certicficateDomains = lib.mkOption { + # type = lib.types.listOf lib.types.str; + # default = [ ]; + # example = [ "git.mydomain.com" ]; + # description = "List of domains to include in the certificate. This option will not prepend the machine name in front of each domain."; + #}; + + certificateSearchDomains = lib.mkOption { + type = lib.types.listOf lib.types.str; + default = [ ]; + example = [ "mydomain.com" ]; + description = '' + List of domains to include in the certificate. + This option will prepend the machine name in front of each domain before adding it to the certificate. + ''; }; }; - }; perInstance = @@ -27,10 +49,15 @@ { ... }: { imports = [ - ../../clanModules/sshd - ../../clanModules/root-password + # We don't have a good way to specify dependencies between + # clanServices for now. When it get's implemtende, we should just + # use the ssh and users modules here. + ./ssh.nix + ./root-password.nix ]; + _module.args = { inherit settings; }; + users.users.root.openssh.authorizedKeys.keys = builtins.attrValues settings.allowedKeys; }; }; diff --git a/clanServices/admin/root-password.nix b/clanServices/admin/root-password.nix new file mode 100644 index 000000000..3d7d6c09f --- /dev/null +++ b/clanServices/admin/root-password.nix @@ -0,0 +1,39 @@ +# We don't have a way of specifying dependencies between clanServices for now. +# When it get's added this file should be removed and the users module used instead. +{ + config, + pkgs, + ... +}: +{ + + users.mutableUsers = false; + users.users.root.hashedPasswordFile = + config.clan.core.vars.generators.root-password.files.password-hash.path; + + clan.core.vars.generators.root-password = { + files.password-hash.neededFor = "users"; + + files.password.deploy = false; + + runtimeInputs = [ + pkgs.coreutils + pkgs.mkpasswd + pkgs.xkcdpass + ]; + + prompts.password.type = "hidden"; + prompts.password.persist = true; + prompts.password.description = "You can autogenerate a password, if you leave this prompt blank."; + + script = '' + prompt_value="$(cat "$prompts"/password)" + if [[ -n "''${prompt_value-}" ]]; then + echo "$prompt_value" | tr -d "\n" > "$out"/password + else + xkcdpass --numwords 5 --delimiter - --count 1 | tr -d "\n" > "$out"/password + fi + mkpasswd -s -m sha-512 < "$out"/password | tr -d "\n" > "$out"/password-hash + ''; + }; +} diff --git a/clanServices/admin/ssh.nix b/clanServices/admin/ssh.nix new file mode 100644 index 000000000..fef85f399 --- /dev/null +++ b/clanServices/admin/ssh.nix @@ -0,0 +1,115 @@ +{ + config, + pkgs, + lib, + settings, + ... +}: +let + stringSet = list: builtins.attrNames (builtins.groupBy lib.id list); + + domains = stringSet settings.certificateSearchDomains; + +in +{ + + services.openssh = { + enable = true; + settings.PasswordAuthentication = false; + + settings.HostCertificate = lib.mkIf ( + settings.certificateSearchDomains != [ ] + ) config.clan.core.vars.generators.openssh-cert.files."ssh.id_ed25519-cert.pub".path; + + hostKeys = + [ + { + path = config.clan.core.vars.generators.openssh.files."ssh.id_ed25519".path; + type = "ed25519"; + } + ] + ++ lib.optional settings.rsaHostKey.enable { + path = config.clan.core.vars.generators.openssh-rsa.files."ssh.id_rsa".path; + type = "rsa"; + }; + }; + + clan.core.vars.generators.openssh = { + files."ssh.id_ed25519" = { }; + files."ssh.id_ed25519.pub".secret = false; + migrateFact = "openssh"; + runtimeInputs = [ + pkgs.coreutils + pkgs.openssh + ]; + script = '' + ssh-keygen -t ed25519 -N "" -C "" -f "$out"/ssh.id_ed25519 + ''; + }; + + programs.ssh.knownHosts.clan-sshd-self-ed25519 = { + hostNames = [ + "localhost" + config.networking.hostName + ] ++ (lib.optional (config.networking.domain != null) config.networking.fqdn); + publicKey = config.clan.core.vars.generators.openssh.files."ssh.id_ed25519.pub".value; + }; + + clan.core.vars.generators.openssh-rsa = lib.mkIf settings.rsaHostKey.enable { + files."ssh.id_rsa" = { }; + files."ssh.id_rsa.pub".secret = false; + runtimeInputs = [ + pkgs.coreutils + pkgs.openssh + ]; + script = '' + ssh-keygen -t rsa -b 4096 -N "" -C "" -f "$out"/ssh.id_rsa + ''; + }; + + clan.core.vars.generators.openssh-cert = lib.mkIf (settings.certificateSearchDomains != [ ]) { + files."ssh.id_ed25519-cert.pub".secret = false; + dependencies = [ + "openssh" + "openssh-ca" + ]; + validation = { + name = config.clan.core.settings.machine.name; + domains = lib.genAttrs settings.certificateSearchDomains lib.id; + }; + runtimeInputs = [ + pkgs.openssh + pkgs.jq + ]; + script = '' + ssh-keygen \ + -s $in/openssh-ca/id_ed25519 \ + -I ${config.clan.core.settings.machine.name} \ + -h \ + -n ${lib.concatMapStringsSep "," (d: "${config.clan.core.settings.machine.name}.${d}") domains} \ + $in/openssh/ssh.id_ed25519.pub + mv $in/openssh/ssh.id_ed25519-cert.pub "$out"/ssh.id_ed25519-cert.pub + ''; + }; + + clan.core.vars.generators.openssh-ca = lib.mkIf (settings.certificateSearchDomains != [ ]) { + share = true; + files.id_ed25519.deploy = false; + files."id_ed25519.pub" = { + deploy = false; + secret = false; + }; + runtimeInputs = [ + pkgs.openssh + ]; + script = '' + ssh-keygen -t ed25519 -N "" -C "" -f "$out"/id_ed25519 + ''; + }; + + programs.ssh.knownHosts.ssh-ca = lib.mkIf (settings.certificateSearchDomains != [ ]) { + certAuthority = true; + extraHostNames = builtins.map (domain: "*.${domain}") settings.certificateSearchDomains; + publicKey = config.clan.core.vars.generators.openssh-ca.files."id_ed25519.pub".value; + }; +}