Refactors SSH/GPG secrets handling

- Switches to per-user SSH configuration by retrieving user-specific secrets
- Integrates GPG key creation and config into secret spec structure
- Simplifies SSH config and key copying logic for better maintainability
- Streamlines SMTP and firewall option mappings
This commit is contained in:
Chris Toph 2025-05-28 21:26:31 -04:00
parent a54522f072
commit 5695caa8cf
3 changed files with 151 additions and 109 deletions

View file

@ -7,52 +7,35 @@
... ...
}: }:
let let
# Generate local key paths for the config ## Get the current user's SSH config ##
sshKeysMap = lib.mapAttrs (name: _: "${hostSpec.home}/.ssh/${name}") secretsSpec.ssh.privateKeys; userSsh = secretsSpec.users.${hostSpec.user}.ssh;
# Create the SSH config file with local paths ## Generate local key paths for the config ##
sshConfig = pkgs.writeText "ssh-config" '' sshKeysMap = lib.mapAttrs (name: _: "${hostSpec.home}/.ssh/${name}") userSsh.privateKeys;
Host git.ryot.foo
IdentityFile ${sshKeysMap.git}
Host *
ForwardAgent no
AddKeysToAgent yes
Compression no
ServerAliveInterval 5
ServerAliveCountMax 3
HashKnownHosts no
UserKnownHostsFile ~/.ssh/known_hosts
ControlMaster no
ControlPath ~/.ssh/master-%r@%n:%p
ControlPersist no
IdentityFile ${sshKeysMap.pve}
UpdateHostKeys ask
'';
in in
{ {
home.file = home.file =
{ {
# SSH config file ## SSH config file ##
".ssh/config_source" = { ".ssh/config_source" = {
source = sshConfig; source = userSsh.config;
onChange = '' onChange = ''
cp $HOME/.ssh/config_source $HOME/.ssh/config cp $HOME/.ssh/config_source $HOME/.ssh/config
chmod 400 $HOME/.ssh/config chmod 400 $HOME/.ssh/config
''; '';
}; };
## Known hosts ##
".ssh/known_hosts_source" = { ".ssh/known_hosts_source" = {
source = pkgs.writeText "known-hosts" (lib.concatStringsSep "\n" secretsSpec.ssh.knownHosts); source = pkgs.writeText "known-hosts" (lib.concatStringsSep "\n" userSsh.knownHosts);
onChange = '' onChange = ''
cp $HOME/.ssh/known_hosts_source $HOME/.ssh/known_hosts cp $HOME/.ssh/known_hosts_source $HOME/.ssh/known_hosts
chmod 644 $HOME/.ssh/known_hosts chmod 644 $HOME/.ssh/known_hosts
''; '';
}; };
} }
# Dynamically add all SSH private keys using the existing store paths
# Ensures the keys have correct permissions and are not symlinks ## Dynamically copy all SSH private keys from store ensuring symlinks are not used ##
// lib.mapAttrs' (name: path: { // lib.mapAttrs' (name: path: {
name = ".ssh/${name}_source"; name = ".ssh/${name}_source";
value = { value = {
@ -62,5 +45,5 @@ in
chmod 600 $HOME/.ssh/${name} chmod 600 $HOME/.ssh/${name}
''; '';
}; };
}) secretsSpec.ssh.privateKeys; }) userSsh.privateKeys;
} }

View file

@ -6,7 +6,7 @@
... ...
}: }:
let let
# Function to create a private key file in the Nix store with proper permissions ## SSH key creation function ##
mkSshKeyFile = mkSshKeyFile =
name: content: name: content:
pkgs.writeTextFile { pkgs.writeTextFile {
@ -14,56 +14,24 @@ let
text = content; text = content;
executable = false; executable = false;
checkPhase = '' checkPhase = ''
# Verify it's a valid SSH key (optional)
grep -q "BEGIN OPENSSH PRIVATE KEY" "$out" || (echo "Invalid SSH key format"; exit 1) grep -q "BEGIN OPENSSH PRIVATE KEY" "$out" || (echo "Invalid SSH key format"; exit 1)
''; '';
}; };
## GPG key creation function ##
mkGpgKeyFile =
name: content:
pkgs.writeTextFile {
name = "gpg-key-${name}";
text = content;
executable = false;
checkPhase = ''
grep -q "BEGIN PGP PRIVATE KEY BLOCK" "$out" || (echo "Invalid GPG key format"; exit 1)
'';
};
in in
{ {
options.secretsSpec = { options.secretsSpec = {
ssh = lib.mkOption {
type = lib.types.submodule {
options = {
# privateKeys are set up automagically 🌠, see config below
privateKeyContents = lib.mkOption {
type = lib.types.attrsOf lib.types.str;
description = "SSH private key contents keyed by name";
default = { };
};
privateKeys = lib.mkOption {
type = lib.types.attrsOf lib.types.path;
description = "SSH private key file paths keyed by name";
# default = { };
readOnly = true;
};
publicKeys = lib.mkOption {
type = lib.types.attrsOf lib.types.str;
description = "SSH public keys keyed by name";
default = { };
};
knownHosts = lib.mkOption {
type = lib.types.listOf lib.types.str;
description = "SSH known hosts entries";
default = [ ];
};
};
};
default = { };
description = "SSH key related secrets";
};
api = lib.mkOption {
type = lib.types.attrsOf lib.types.str;
description = "API keys keyed by service name";
default = { };
};
docker = lib.mkOption {
type = lib.types.attrsOf (lib.types.attrsOf lib.types.str);
description = "Docker environment variables keyed by container name";
default = { };
};
users = lib.mkOption { users = lib.mkOption {
type = lib.types.attrsOf ( type = lib.types.attrsOf (
lib.types.submodule { lib.types.submodule {
@ -84,40 +52,98 @@ in
type = lib.types.str; type = lib.types.str;
description = "Full name of the user"; description = "Full name of the user";
}; };
sshKeys = lib.mkOption {
type = lib.types.listOf lib.types.str; ## SSH configuration ##
description = "SSH public keys for the user"; ssh = lib.mkOption {
default = [ ]; type = lib.types.submodule {
}; options = {
smtp = lib.mkOption { publicKeys = lib.mkOption {
type = lib.types.submodule ( type = lib.types.listOf lib.types.str;
{ config, ... }: description = "SSH public keys for the user";
{ default = [ ];
options = {
host = lib.mkOption {
type = lib.types.str;
description = "SMTP server hostname";
};
user = lib.mkOption {
type = lib.types.str;
description = "SMTP username for authentication";
};
password = lib.mkOption {
type = lib.types.str;
description = "SMTP password for authentication";
};
port = lib.mkOption {
type = lib.types.port;
description = "SMTP server port";
default = 587;
};
from = lib.mkOption {
type = lib.types.str;
description = "Email address to send from";
};
}; };
} privateKeyContents = lib.mkOption {
); type = lib.types.attrsOf lib.types.str;
description = "SSH private key contents keyed by name";
default = { };
};
privateKeys = lib.mkOption {
type = lib.types.attrsOf lib.types.path;
description = "SSH private key file paths keyed by name";
readOnly = true;
};
config = lib.mkOption {
type = lib.types.path;
description = "SSH config file path";
};
knownHosts = lib.mkOption {
type = lib.types.listOf lib.types.str;
description = "SSH known hosts entries";
default = [ ];
};
};
};
default = { };
description = "SSH configuration for the user";
};
## GPG configuration ##
gpg = lib.mkOption {
type = lib.types.submodule {
options = {
publicKey = lib.mkOption {
type = lib.types.str;
description = "GPG public key content";
default = "";
};
privateKeyContents = lib.mkOption {
type = lib.types.str;
description = "GPG private key content";
default = "";
};
privateKey = lib.mkOption {
type = lib.types.path;
description = "GPG private key file path";
readOnly = true;
};
trust = lib.mkOption {
type = lib.types.str;
description = "GPG trust database content (base64)";
default = "";
};
};
};
default = { };
description = "GPG configuration for the user";
};
## SMTP configuration ##
smtp = lib.mkOption {
type = lib.types.submodule {
options = {
host = lib.mkOption {
type = lib.types.str;
description = "SMTP server hostname";
};
user = lib.mkOption {
type = lib.types.str;
description = "SMTP username for authentication";
};
password = lib.mkOption {
type = lib.types.str;
description = "SMTP password for authentication";
};
port = lib.mkOption {
type = lib.types.port;
description = "SMTP server port";
default = 587;
};
from = lib.mkOption {
type = lib.types.str;
description = "Email address to send from";
};
};
};
description = "SMTP configuration for the user"; description = "SMTP configuration for the user";
default = null; default = null;
}; };
@ -128,6 +154,7 @@ in
default = { }; default = { };
}; };
## Firewall configurations by host ##
firewall = lib.mkOption { firewall = lib.mkOption {
type = lib.types.attrsOf ( type = lib.types.attrsOf (
lib.types.submodule { lib.types.submodule {
@ -205,9 +232,41 @@ in
description = "Firewall configuration by host"; description = "Firewall configuration by host";
default = { }; default = { };
}; };
## API keys ##
api = lib.mkOption {
type = lib.types.attrsOf lib.types.str;
description = "API keys keyed by service name";
default = { };
};
## Docker environment variables ##
docker = lib.mkOption {
type = lib.types.attrsOf (lib.types.attrsOf lib.types.str);
description = "Docker environment variables keyed by container name";
default = { };
};
}; };
config.secretsSpec.ssh.privateKeys = lib.mapAttrs ( config.secretsSpec.users = lib.mapAttrs (
name: content: mkSshKeyFile name content userName: userConfig:
) config.secretsSpec.ssh.privateKeyContents; userConfig
// {
## Auto-generate SSH private key files ##
ssh = userConfig.ssh // {
privateKeys = lib.mapAttrs (
name: content: mkSshKeyFile "${userName}-${name}" content
) userConfig.ssh.privateKeyContents;
};
## Auto-generate GPG private key file ##
gpg = userConfig.gpg // {
privateKey =
if userConfig.gpg.privateKeyContents != "" then
mkGpgKeyFile "${userName}-gpg" userConfig.gpg.privateKeyContents
else
null;
};
}
) config.secretsSpec.users;
} }

Binary file not shown.