Compare commits

...

2 Commits

Author SHA1 Message Date
John Lancaster 04bbf00c3d ssh options 2026-03-29 17:38:45 -05:00
John Lancaster e0abbd6b90 ssh options 2026-03-29 16:53:54 -05:00
6 changed files with 67 additions and 66 deletions
+3
View File
@@ -61,6 +61,9 @@ in
}; };
ssh = { ssh = {
certificates.enable = true; certificates.enable = true;
knownHosts = [
"fded:fb16:653e:25da:be24:11ff:fea0:753f ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIJ9ZqiWPrCwHjxFCiu0lT4rlQs7KyMapxKJQQ5PJP1eh"
];
matchSets = { matchSets = {
certs = true; certs = true;
appdaemon = true; appdaemon = true;
+2 -8
View File
@@ -1,16 +1,10 @@
{ self, inputs, ... }: { { self, inputs, ... }: {
flake.modules.homeManager.onepassword = { config, pkgs, lib, ... }: { flake.modules.homeManager.onepassword = { config, ... }: {
home.file.".config/1Password/ssh/agent.toml".text = '' home.file.".config/1Password/ssh/agent.toml".text = ''
# https://developer.1password.com/docs/ssh/agent/config # https://developer.1password.com/docs/ssh/agent/config
[[ssh-keys]] [[ssh-keys]]
vault = "Private" vault = "Private"
''; '';
programs.ssh = { programs.ssh.matchBlocks."*".identityAgent = "${config.home.homeDirectory}/.1password/agent.sock";
enable = true;
extraConfig = ''
Host *
IdentityAgent ${config.home.homeDirectory}/.1password/agent.sock
'';
};
}; };
} }
+4 -4
View File
@@ -64,8 +64,8 @@ in
(writeShellScriptBin "gen-age-key" '' (writeShellScriptBin "gen-age-key" ''
set -eu set -eu
if [ ! -f "${config.ssh.IdentityFile}" ]; then if [ ! -f "${config.ssh.identityFile}" ]; then
${echo} "SSH identity file not found: ${config.ssh.IdentityFile}" >&2 ${echo} "SSH identity file not found: ${config.ssh.identityFile}" >&2
exit 1 exit 1
fi fi
@@ -75,7 +75,7 @@ in
fi fi
${mkdir} -p "$(${dirname} "${cfg.ageKeyFile}")" ${mkdir} -p "$(${dirname} "${cfg.ageKeyFile}")"
${lib.getExe pkgs.ssh-to-age} -i ${config.ssh.IdentityFile} -private-key > ${cfg.ageKeyFile} ${lib.getExe pkgs.ssh-to-age} -i ${config.ssh.identityFile} -private-key > ${cfg.ageKeyFile}
${echo} -n "Created ${cfg.ageKeyFile}: " ${echo} -n "Created ${cfg.ageKeyFile}: "
${echo} $(${lib.getExe show-age-key}) ${echo} $(${lib.getExe show-age-key})
'') '')
@@ -90,7 +90,7 @@ in
sops = { sops = {
defaultSopsFile = sopsSecretsPath; defaultSopsFile = sopsSecretsPath;
defaultSopsFormat = "yaml"; defaultSopsFormat = "yaml";
age.sshKeyPaths = [ "${config.ssh.IdentityFile}" ]; age.sshKeyPaths = [ "${config.ssh.identityFile}" ];
}; };
}; };
}; };
+1 -1
View File
@@ -29,7 +29,7 @@
${principalArgs} \ ${principalArgs} \
--provisioner "${cfg.provisioner}" \ --provisioner "${cfg.provisioner}" \
--provisioner-password-file "${config.sops.secrets."janus/admin_jwk".path}" \ --provisioner-password-file "${config.sops.secrets."janus/admin_jwk".path}" \
"${firstPrincipal}" "${config.ssh.IdentityFile}.pub" "${firstPrincipal}" "${config.ssh.identityFile}.pub"
'') '')
]; ];
}; };
+48 -44
View File
@@ -1,40 +1,38 @@
{inputs, ... }: { inputs, ... }:
let let
userName = "john"; userName = "john";
sshHostCAPubKey = "ecdsa-sha2-nistp256 AAAAE2VjZHNhLXNoYTItbmlzdHAyNTYAAAAIbmlzdHAyNTYAAABBBNug18oLH0vZxnibXJzMJvTWFPZTnSlhCDDVi+rHhgnIum6ZXQ4SF+VHOOAM5BbzZmMKitNJ5lcrGP15Eur7DzQ="; sshHostCAPubKey = "ecdsa-sha2-nistp256 AAAAE2VjZHNhLXNoYTItbmlzdHAyNTYAAAAIbmlzdHAyNTYAAABBBNug18oLH0vZxnibXJzMJvTWFPZTnSlhCDDVi+rHhgnIum6ZXQ4SF+VHOOAM5BbzZmMKitNJ5lcrGP15Eur7DzQ=";
in in
{ {
flake.modules.nixos.ssh = { pkgs, config, lib, ... }: flake.modules.nixos.ssh = { config, pkgs, lib, ... }:
let let
cfg = config.ssh; cfg = config.ssh;
userCAPath = "ssh/ssh_user_ca.pub"; configDir = "/etc/ssh";
in in
{ {
options.ssh = { options.ssh = {
configDir = lib.mkOption {
description = "String path to the host SSH config directory";
type = lib.types.str;
default = "/etc/ssh";
};
hostKey = lib.mkOption { hostKey = lib.mkOption {
description = "String path to the host private key file"; description = "String path to the host private key file";
type = lib.types.str; type = lib.types.str;
default = "${cfg.configDir}/ssh_host_ed25519_key"; default = "ssh_host_ed25519_key";
}; };
certificates = { certificates = {
enable = lib.mkEnableOption "Enable SSH host certificates"; enable = lib.mkEnableOption "Enable SSH host certificates";
userCA = lib.mkOption { userCA = lib.mkOption {
description = "Content for the SSH user CA file (public key)";
type = lib.types.path; type = lib.types.path;
default = ../../keys/ssh_user_ca.pub; default = ../hosts/janus/ssh_user_ca.pub;
}; };
userCAPath = lib.mkOption { userCAFile = lib.mkOption {
description = "String path to the SSh user CA";
type = lib.types.str; type = lib.types.str;
default = "${cfg.configDir}/ssh_user_ca.pub"; default = "ssh_user_ca.pub";
}; };
}; };
}; };
config = { config = {
cfg.certificates = lib.mkDefault true;
services.openssh = { services.openssh = {
enable = true; enable = true;
# require public key authentication for better security # require public key authentication for better security
@@ -42,16 +40,16 @@ in
{ {
PasswordAuthentication = false; PasswordAuthentication = false;
KbdInteractiveAuthentication = false; KbdInteractiveAuthentication = false;
HostKey = cfg.hostKey; HostKey = "${configDir}/${cfg.hostKey}";
} }
(lib.mkIf cfg.certificates.enable { (lib.mkIf cfg.certificates.enable {
TrustedUserCAKeys = cfg.certificates.userCAPath; TrustedUserCAKeys = "${configDir}/${cfg.certificates.userCAFile}";
HostCertificate = "${cfg.hostKey}-cert.pub"; HostCertificate = "${configDir}/${cfg.hostKey}-cert.pub";
}) })
]; ];
}; };
environment.etc."${userCAPath}" = lib.mkIf cfg.certificates.enable { environment.etc."ssh/${cfg.certificates.userCAFile}" = lib.mkIf cfg.certificates.enable {
source = cfg.certificates.userCA; source = cfg.certificates.userCA;
}; };
@@ -68,52 +66,56 @@ in
}; };
}; };
flake.modules.homeManager.ssh = { pkgs, config, lib, ... }: 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 = { options.ssh = with lib; {
IdentityFile = lib.mkOption { identityFile = mkOption {
# Intentionally not using a path type here because that will end up with the private key getting copied into the store # 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; type = types.str;
default = "${config.home.homeDirectory}/.ssh/id_ed25519"; default = "${config.home.homeDirectory}/.ssh/id_ed25519";
description = "Path to the SSH identity file."; description = "Path to the SSH identity file.";
}; };
certificates = { certificates = {
enable = lib.mkEnableOption "Enable SSH user certificates"; enable = mkEnableOption "Enable SSH client certificates";
# sshCertProvisioner = lib.mkOption {
# type = lib.types.str;
# default = "admin";
# };
}; };
knownHostsFile = lib.mkOption { knownHostsFile = mkOption {
type = lib.types.str; type = types.str;
default = "${config.home.homeDirectory}/.ssh/known_hosts"; default = "${configDir}/known_hosts";
};
knownHosts = mkOption {
description = "";
type = types.listOf types.str;
default = [ ];
}; };
matchSets = { matchSets = {
appdaemon = lib.mkEnableOption "Enable AppDaemon SSH targets"; appdaemon = mkEnableOption "Enable AppDaemon SSH targets";
certs = lib.mkEnableOption "Enable Janus and Soteria SSH targets"; certs = mkEnableOption "Enable Janus and Soteria SSH targets";
homelab = lib.mkEnableOption "Enable various Homelab targets"; homelab = mkEnableOption "Enable various Homelab targets";
dev = lib.mkEnableOption "Enable development targets"; dev = mkEnableOption "Enable development targets";
}; };
}; };
# All this stuff has to be wrapped in a config attribute because of the presence of the options here? # All this stuff has to be wrapped in a config attribute because of the presence of the options here?
config = let config = let
cfg = config.ssh;
identityFile = cfg.IdentityFile;
publicKeyFile = "${identityFile}.pub";
certificateFile = "${identityFile}-cert.pub";
provisionerPasswordPath = config.sops.secrets."janus/admin_jwk".path; provisionerPasswordPath = config.sops.secrets."janus/admin_jwk".path;
in { in {
home.file.".ssh/known_hosts" = { home.file.".ssh/known_hosts" = {
text = lib.concatStringsSep "\n" ( text = lib.concatStringsSep "\n" (
[ cfg.knownHosts ++ lib.optionals cfg.certificates.enable [
"fded:fb16:653e:25da:be24:11ff:fea0:753f ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIJ9ZqiWPrCwHjxFCiu0lT4rlQs7KyMapxKJQQ5PJP1eh" "@cert-authority 192.168.1.* ${sshHostCAPubKey}"
"@cert-authority *.john-stream.com ${sshHostCAPubKey}"
] ]
++ (lib.optional cfg.certificates.enable "@cert-authority 192.168.1.* ${sshHostCAPubKey}")
++ (lib.optional cfg.certificates.enable "@cert-authority *.john-stream.com ${sshHostCAPubKey}")
); );
}; };
@@ -122,12 +124,12 @@ in
enableDefaultConfig = false; enableDefaultConfig = false;
extraConfig = '' extraConfig = ''
SetEnv TERM="xterm-256color" SetEnv TERM="xterm-256color"
IdentityAgent ~/.1password/agent.sock
''; '';
matchBlocks = lib.mkMerge [ matchBlocks = lib.mkMerge [
{ {
"*" = { "*" = lib.mkMerge [
{
user = "john"; user = "john";
compression = false; compression = false;
@@ -135,14 +137,16 @@ in
serverAliveCountMax = 3; serverAliveCountMax = 3;
identitiesOnly = true; identitiesOnly = true;
inherit identityFile certificateFile; inherit identityFile;
hashKnownHosts = false; hashKnownHosts = false;
userKnownHostsFile = cfg.knownHostsFile; userKnownHostsFile = cfg.knownHostsFile;
addKeysToAgent = "yes"; addKeysToAgent = "yes";
forwardAgent = false; forwardAgent = false;
}; }
(lib.mkIf cfg.certificates.enable { inherit certificateFile; })
];
} }
(lib.mkIf cfg.matchSets.appdaemon { (lib.mkIf cfg.matchSets.appdaemon {
"appdaemon" = { "appdaemon" = {