Files
dendritic/modules/services/ssh.nix
2026-03-15 15:06:48 -05:00

192 lines
5.8 KiB
Nix

{inputs, ... }:
let
userName = "john";
sshHostCAPubKey = "ecdsa-sha2-nistp256 AAAAE2VjZHNhLXNoYTItbmlzdHAyNTYAAAAIbmlzdHAyNTYAAABBBNug18oLH0vZxnibXJzMJvTWFPZTnSlhCDDVi+rHhgnIum6ZXQ4SF+VHOOAM5BbzZmMKitNJ5lcrGP15Eur7DzQ=";
in
{
flake.modules.nixos.ssh = { pkgs, config, lib, ... }:
let
cfg = config.ssh;
in
{
options.ssh = {
certificates = {
enable = lib.mkEnableOption "Enable SSH host certificates";
userCA = lib.mkOption {
type = lib.types.path;
default = ../../keys/ssh_user_ca.pub;
};
};
};
config = {
services.openssh = {
enable = true;
# require public key authentication for better security
settings = lib.mkMerge [
{
PasswordAuthentication = false;
KbdInteractiveAuthentication = false;
}
(lib.mkIf cfg.certificates.enable {
TrustedUserCAKeys = "/etc/ssh/ssh_user_ca.pub";
HostKey = "/etc/ssh/ssh_host_ed25519_key";
HostCertificate = "/etc/ssh/ssh_host_ed25519_key-cert.pub";
})
];
};
environment.etc."ssh/ssh_user_ca.pub" = lib.mkIf cfg.certificates.enable {
source = cfg.certificates.userCA;
};
programs.ssh.knownHosts = lib.mkIf cfg.certificates.enable {
"192.168.1.*" = {
certAuthority = true;
publicKey = sshHostCAPubKey;
};
};
};
};
flake.modules.homeManager.ssh = { pkgs, config, lib, ... }:
{
imports = with inputs.self.modules.homeManager; [
step-client
];
options.ssh = {
IdentityFile = lib.mkOption {
# Intentionally not using a path type here because that will end up with the private key getting copied into the store
type = lib.types.str;
default = "${config.home.homeDirectory}/.ssh/id_ed25519";
description = "Path to the SSH identity file.";
};
matchSets = {
appdaemon = lib.mkEnableOption "Enable AppDaemon SSH targets";
certs = lib.mkEnableOption "Enable Janus and Soteria SSH targets";
homelab = lib.mkEnableOption "Enable various Homelab targets";
dev = lib.mkEnableOption "Enable development targets";
};
certificates = {
enable = lib.mkEnableOption "Enable SSH user certificates";
};
};
# All this stuff has to be wrapped in a config attribute because of the presence of the options here?
config = let
cfg = config.ssh;
identityFile = cfg.IdentityFile;
publicKeyFile = "${identityFile}.pub";
certificateFile = "${identityFile}-cert.pub";
userKnownHostsFile = "${config.home.homeDirectory}/.ssh/known_hosts";
provisionerPasswordPath = config.sops.secrets."janus/admin_jwk".path;
sshHostProvisioner = config.step-client.sshHostProvisioner;
in {
sops.secrets."janus/admin_jwk" = {
mode = "0400";
};
home.packages = lib.optionals cfg.certificates.enable [
(pkgs.writeShellScriptBin "sign-ssh-cert" ''
${lib.getExe pkgs.step-cli} ssh certificate \
--sign \
--principal ${userName} \
--principal root \
--principal appdaemon \
--provisioner "${sshHostProvisioner}" \
--provisioner-password-file "${provisionerPasswordPath}" \
${userName} ${publicKeyFile}
'')
];
home.file.".ssh/known_hosts" = {
text = lib.concatStringsSep "\n" (
[
"fded:fb16:653e:25da:be24:11ff:fea0:753f ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIJ9ZqiWPrCwHjxFCiu0lT4rlQs7KyMapxKJQQ5PJP1eh"
]
++ (lib.optional cfg.certificates.enable "@cert-authority 192.168.1.* ${sshHostCAPubKey}")
++ (lib.optional cfg.certificates.enable "@cert-authority *.john-stream.com ${sshHostCAPubKey}")
);
};
programs.ssh = {
enable = true;
enableDefaultConfig = false;
extraConfig = ''
SetEnv TERM="xterm-256color"
IdentityAgent ~/.1password/agent.sock
'';
matchBlocks = lib.mkMerge [
{
"*" = {
user = "john";
compression = false;
serverAliveInterval = 0;
serverAliveCountMax = 3;
identitiesOnly = true;
inherit identityFile certificateFile;
hashKnownHosts = false;
userKnownHostsFile = userKnownHostsFile;
addKeysToAgent = "yes";
forwardAgent = false;
};
}
(lib.mkIf cfg.matchSets.appdaemon {
"appdaemon" = {
hostname = "192.168.1.242";
user = "appdaemon";
};
"ad-nix" = {
hostname = "192.168.1.201";
user = "appdaemon";
};
})
(lib.mkIf cfg.matchSets.certs {
"janus" = {
hostname = "janus.john-stream.com";
user = "root";
};
"soteria" = {
hostname = "soteria.john-stream.com";
user = "john";
};
})
(lib.mkIf cfg.matchSets.homelab {
"docs" = {
hostname = "192.168.1.110";
user = "root";
};
"gitea" = {
hostname = "192.168.1.104";
user = "john";
};
"hermes" = {
hostname = "192.168.1.150";
user = "root";
};
"panoptes" = {
hostname = "192.168.1.107";
user = "panoptes";
};
})
(lib.mkIf cfg.matchSets.dev {
"test-nix" = {
hostname = "fded:fb16:653e:25da:be24:11ff:fea0:753f";
user = "john";
identityFile = identityFile;
};
})
];
};
};
};
}