Files
dendritic/modules/services/ssh.nix
T
2026-04-26 18:58:09 -05:00

222 lines
6.6 KiB
Nix

{ inputs, ... }:
let
userName = "john";
sshHostCAPubKey = "ecdsa-sha2-nistp256 AAAAE2VjZHNhLXNoYTItbmlzdHAyNTYAAAAIbmlzdHAyNTYAAABBBNug18oLH0vZxnibXJzMJvTWFPZTnSlhCDDVi+rHhgnIum6ZXQ4SF+VHOOAM5BbzZmMKitNJ5lcrGP15Eur7DzQ=";
in
{
flake.modules.nixos.ssh = { config, pkgs, lib, ... }:
let
cfg = config.ssh;
configDir = "/etc/ssh";
in
{
options.ssh = {
hostKey = lib.mkOption {
description = "String path to the host private key file";
type = lib.types.str;
default = "ssh_host_ed25519_key";
};
certificates = {
enable = lib.mkEnableOption "Enable SSH host certificates";
userCA = lib.mkOption {
description = "Content for the SSH user CA file (public key)";
type = lib.types.path;
default = ../hosts/janus/ssh_user_ca.pub;
};
userCAFile = lib.mkOption {
description = "String path to the SSh user CA";
type = lib.types.str;
default = "ssh_user_ca.pub";
};
};
};
config = {
services.openssh = {
enable = true;
# require public key authentication for better security
settings = lib.mkMerge [
{
PasswordAuthentication = false;
KbdInteractiveAuthentication = false;
HostKey = "${configDir}/${cfg.hostKey}";
}
(lib.mkIf cfg.certificates.enable {
TrustedUserCAKeys = "${configDir}/${cfg.certificates.userCAFile}";
HostCertificate = "${configDir}/${cfg.hostKey}-cert.pub";
})
];
};
environment.etc."ssh/${cfg.certificates.userCAFile}" = lib.mkIf cfg.certificates.enable {
source = cfg.certificates.userCA;
};
programs.ssh.knownHosts = lib.mkIf cfg.certificates.enable {
"192.168.1.*" = {
certAuthority = true;
publicKey = sshHostCAPubKey;
};
"*.john-stream.com" = {
certAuthority = true;
publicKey = sshHostCAPubKey;
};
};
};
};
flake.modules.homeManager.ssh = { config, pkgs, lib, ... }:
let
cfg = config.ssh;
configDir = "${config.home.homeDirectory}/.ssh";
identityFile = cfg.identityFile;
publicKeyFile = "${identityFile}.pub";
certificateFile = "${identityFile}-cert.pub";
in
{
options.ssh = with lib; {
identityFile = mkOption {
# Intentionally not using a path type here because that will end up with the private key getting copied into the store
type = types.str;
default = "${config.home.homeDirectory}/.ssh/id_ed25519";
description = "Path to the SSH identity file.";
};
certificates = {
enable = mkEnableOption "Enable SSH client certificates";
};
knownHostsFile = mkOption {
type = types.str;
default = "${configDir}/known_hosts";
};
knownHosts = mkOption {
description = "";
type = types.listOf types.str;
default = [ ];
};
matchSets = {
appdaemon = mkEnableOption "Enable AppDaemon SSH targets";
certs = mkEnableOption "Enable Janus and Soteria SSH targets";
homelab = mkEnableOption "Enable various Homelab targets";
dev = mkEnableOption "Enable development targets";
tailscale = mkEnableOption "Enable tailscale targets";
};
};
# All this stuff has to be wrapped in a config attribute because of the presence of the options here?
config = let
provisionerPasswordPath = config.sops.secrets."janus/admin_jwk".path;
in {
home.file.".ssh/known_hosts" = {
text = lib.concatStringsSep "\n" (
cfg.knownHosts ++ lib.optionals cfg.certificates.enable [
"@cert-authority 192.168.1.* ${sshHostCAPubKey}"
"@cert-authority *.john-stream.com ${sshHostCAPubKey}"
]
);
};
programs.ssh = {
enable = true;
enableDefaultConfig = false;
extraConfig = ''
SetEnv TERM="xterm-256color"
'';
matchBlocks = lib.mkMerge [
{
"john-pc-ubuntu" = {
hostname = "192.168.1.85";
};
"*" = lib.mkMerge [
{
user = "john";
compression = false;
serverAliveInterval = 0;
serverAliveCountMax = 3;
identitiesOnly = true;
inherit identityFile;
hashKnownHosts = false;
userKnownHostsFile = cfg.knownHostsFile;
addKeysToAgent = "yes";
forwardAgent = false;
}
(lib.mkIf cfg.certificates.enable { inherit certificateFile; })
];
}
(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";
extraOptions = {
RequestTTY = "force";
RemoteCommand = "~/.nix-profile/bin/jsl-zsh";
};
};
"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";
extraOptions = {
RequestTTY = "auto";
# RemoteCommand = "/run/current-system/sw/bin/jsl-zsh";
};
};
})
(lib.mkIf cfg.matchSets.tailscale {
"jdl-docker" = {
hostname = "jdl-docker.tailcf205.ts.net";
user = "john";
extraOptions = {
RequestTTY = "auto";
# RemoteCommand = "~/.nix-profile/bin/jsl-zsh";
};
};
})
];
};
};
};
}