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
# Generate local key paths for the config
sshKeysMap = lib.mapAttrs (name: _: "${hostSpec.home}/.ssh/${name}") secretsSpec.ssh.privateKeys;
## Get the current user's SSH config ##
userSsh = secretsSpec.users.${hostSpec.user}.ssh;
# Create the SSH config file with local paths
sshConfig = pkgs.writeText "ssh-config" ''
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
'';
## Generate local key paths for the config ##
sshKeysMap = lib.mapAttrs (name: _: "${hostSpec.home}/.ssh/${name}") userSsh.privateKeys;
in
{
home.file =
{
# SSH config file
## SSH config file ##
".ssh/config_source" = {
source = sshConfig;
source = userSsh.config;
onChange = ''
cp $HOME/.ssh/config_source $HOME/.ssh/config
chmod 400 $HOME/.ssh/config
'';
};
## Known hosts ##
".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 = ''
cp $HOME/.ssh/known_hosts_source $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: {
name = ".ssh/${name}_source";
value = {
@ -62,5 +45,5 @@ in
chmod 600 $HOME/.ssh/${name}
'';
};
}) secretsSpec.ssh.privateKeys;
}) userSsh.privateKeys;
}

View file

@ -6,7 +6,7 @@
...
}:
let
# Function to create a private key file in the Nix store with proper permissions
## SSH key creation function ##
mkSshKeyFile =
name: content:
pkgs.writeTextFile {
@ -14,56 +14,24 @@ let
text = content;
executable = false;
checkPhase = ''
# Verify it's a valid SSH key (optional)
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
{
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 {
type = lib.types.attrsOf (
lib.types.submodule {
@ -84,15 +52,74 @@ in
type = lib.types.str;
description = "Full name of the user";
};
sshKeys = lib.mkOption {
## SSH configuration ##
ssh = lib.mkOption {
type = lib.types.submodule {
options = {
publicKeys = lib.mkOption {
type = lib.types.listOf lib.types.str;
description = "SSH public keys for the user";
default = [ ];
};
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 (
{ config, ... }:
{
type = lib.types.submodule {
options = {
host = lib.mkOption {
type = lib.types.str;
@ -116,8 +143,7 @@ in
description = "Email address to send from";
};
};
}
);
};
description = "SMTP configuration for the user";
default = null;
};
@ -128,6 +154,7 @@ in
default = { };
};
## Firewall configurations by host ##
firewall = lib.mkOption {
type = lib.types.attrsOf (
lib.types.submodule {
@ -205,9 +232,41 @@ in
description = "Firewall configuration by host";
default = { };
};
## API keys ##
api = lib.mkOption {
type = lib.types.attrsOf lib.types.str;
description = "API keys keyed by service name";
default = { };
};
config.secretsSpec.ssh.privateKeys = lib.mapAttrs (
name: content: mkSshKeyFile name content
) config.secretsSpec.ssh.privateKeyContents;
## 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.users = lib.mapAttrs (
userName: userConfig:
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.