224 lines
6.6 KiB
Nix
224 lines
6.6 KiB
Nix
{ config, lib, pkgs, ... }:
|
|
|
|
with lib;
|
|
|
|
let
|
|
cfg = config.vault;
|
|
|
|
# Import vault-fetch package
|
|
vault-fetch = pkgs.callPackage ../scripts/vault-fetch { };
|
|
|
|
# Secret configuration type
|
|
secretType = types.submodule ({ name, config, ... }: {
|
|
options = {
|
|
secretPath = mkOption {
|
|
type = types.str;
|
|
description = ''
|
|
Path to the secret in Vault (without /v1/secret/data/ prefix).
|
|
Example: "hosts/monitoring01/grafana-admin"
|
|
'';
|
|
};
|
|
|
|
outputDir = mkOption {
|
|
type = types.str;
|
|
default = "/run/secrets/${name}";
|
|
description = ''
|
|
Directory where secret files will be written.
|
|
Each key in the secret becomes a separate file.
|
|
'';
|
|
};
|
|
|
|
cacheDir = mkOption {
|
|
type = types.str;
|
|
default = "/var/lib/vault/cache/${name}";
|
|
description = ''
|
|
Directory for caching secrets when Vault is unreachable.
|
|
'';
|
|
};
|
|
|
|
owner = mkOption {
|
|
type = types.str;
|
|
default = "root";
|
|
description = "Owner of the secret files";
|
|
};
|
|
|
|
group = mkOption {
|
|
type = types.str;
|
|
default = "root";
|
|
description = "Group of the secret files";
|
|
};
|
|
|
|
mode = mkOption {
|
|
type = types.str;
|
|
default = "0400";
|
|
description = "Permissions mode for secret files";
|
|
};
|
|
|
|
restartTrigger = mkOption {
|
|
type = types.bool;
|
|
default = false;
|
|
description = ''
|
|
Whether to create a systemd timer that periodically restarts
|
|
services using this secret to rotate credentials.
|
|
'';
|
|
};
|
|
|
|
restartInterval = mkOption {
|
|
type = types.str;
|
|
default = "weekly";
|
|
description = ''
|
|
How often to restart services for secret rotation.
|
|
Uses systemd.time format (e.g., "daily", "weekly", "monthly").
|
|
Only applies if restartTrigger is true.
|
|
'';
|
|
};
|
|
|
|
services = mkOption {
|
|
type = types.listOf types.str;
|
|
default = [];
|
|
description = ''
|
|
List of systemd service names that depend on this secret.
|
|
Used for periodic restart if restartTrigger is enabled.
|
|
'';
|
|
};
|
|
};
|
|
});
|
|
|
|
in
|
|
{
|
|
options.vault = {
|
|
enable = mkEnableOption "Vault secrets management" // {
|
|
default = false;
|
|
};
|
|
|
|
secrets = mkOption {
|
|
type = types.attrsOf secretType;
|
|
default = {};
|
|
description = ''
|
|
Secrets to fetch from Vault.
|
|
Each attribute name becomes a secret identifier.
|
|
'';
|
|
example = literalExpression ''
|
|
{
|
|
grafana-admin = {
|
|
secretPath = "hosts/monitoring01/grafana-admin";
|
|
owner = "grafana";
|
|
group = "grafana";
|
|
restartTrigger = true;
|
|
restartInterval = "daily";
|
|
services = [ "grafana" ];
|
|
};
|
|
}
|
|
'';
|
|
};
|
|
|
|
criticalServices = mkOption {
|
|
type = types.listOf types.str;
|
|
default = [ "bind" "openbao" "step-ca" ];
|
|
description = ''
|
|
Services that should never get auto-restart timers for secret rotation.
|
|
These are critical infrastructure services where automatic restarts
|
|
could cause cascading failures.
|
|
'';
|
|
};
|
|
|
|
vaultAddress = mkOption {
|
|
type = types.str;
|
|
default = "https://vault01.home.2rjus.net:8200";
|
|
description = "Vault server address";
|
|
};
|
|
|
|
skipTlsVerify = mkOption {
|
|
type = types.bool;
|
|
default = true;
|
|
description = "Skip TLS certificate verification (useful for self-signed certs)";
|
|
};
|
|
};
|
|
|
|
config = mkIf (cfg.enable && cfg.secrets != {}) {
|
|
# Create systemd services for fetching secrets and rotation
|
|
systemd.services =
|
|
# Fetch services
|
|
(mapAttrs' (name: secretCfg: nameValuePair "vault-secret-${name}" {
|
|
description = "Fetch Vault secret: ${name}";
|
|
before = map (svc: "${svc}.service") secretCfg.services;
|
|
wantedBy = [ "multi-user.target" ];
|
|
|
|
# Ensure vault-fetch is available
|
|
path = [ vault-fetch ];
|
|
|
|
serviceConfig = {
|
|
Type = "oneshot";
|
|
RemainAfterExit = true;
|
|
|
|
# Fetch the secret
|
|
ExecStart = pkgs.writeShellScript "fetch-${name}" ''
|
|
set -euo pipefail
|
|
|
|
# Set Vault environment variables
|
|
export VAULT_ADDR="${cfg.vaultAddress}"
|
|
export VAULT_SKIP_VERIFY="${if cfg.skipTlsVerify then "1" else "0"}"
|
|
|
|
# Fetch secret using vault-fetch
|
|
${vault-fetch}/bin/vault-fetch \
|
|
"${secretCfg.secretPath}" \
|
|
"${secretCfg.outputDir}" \
|
|
"${secretCfg.cacheDir}"
|
|
|
|
# Set ownership and permissions
|
|
chown -R ${secretCfg.owner}:${secretCfg.group} "${secretCfg.outputDir}"
|
|
chmod ${secretCfg.mode} "${secretCfg.outputDir}"/*
|
|
'';
|
|
|
|
# Logging
|
|
StandardOutput = "journal";
|
|
StandardError = "journal";
|
|
};
|
|
}) cfg.secrets)
|
|
//
|
|
# Rotation services
|
|
(mapAttrs' (name: secretCfg: nameValuePair "vault-secret-rotate-${name}"
|
|
(mkIf (secretCfg.restartTrigger && secretCfg.services != [] &&
|
|
!any (svc: elem svc cfg.criticalServices) secretCfg.services) {
|
|
description = "Rotate Vault secret and restart services: ${name}";
|
|
|
|
serviceConfig = {
|
|
Type = "oneshot";
|
|
};
|
|
|
|
script = ''
|
|
# Restart the secret fetch service
|
|
systemctl restart vault-secret-${name}.service
|
|
|
|
# Restart all dependent services
|
|
${concatMapStringsSep "\n" (svc: "systemctl restart ${svc}.service") secretCfg.services}
|
|
'';
|
|
})
|
|
) cfg.secrets);
|
|
|
|
# Create systemd timers for periodic secret rotation (if enabled)
|
|
systemd.timers = mapAttrs' (name: secretCfg: nameValuePair "vault-secret-rotate-${name}"
|
|
(mkIf (secretCfg.restartTrigger && secretCfg.services != [] &&
|
|
!any (svc: elem svc cfg.criticalServices) secretCfg.services) {
|
|
description = "Rotate Vault secret and restart services: ${name}";
|
|
wantedBy = [ "timers.target" ];
|
|
|
|
timerConfig = {
|
|
OnCalendar = secretCfg.restartInterval;
|
|
Persistent = true;
|
|
RandomizedDelaySec = "1h";
|
|
};
|
|
})
|
|
) cfg.secrets;
|
|
|
|
# Ensure runtime and cache directories exist
|
|
systemd.tmpfiles.rules =
|
|
[ "d /run/secrets 0755 root root -" ] ++
|
|
[ "d /var/lib/vault/cache 0700 root root -" ] ++
|
|
flatten (mapAttrsToList (name: secretCfg: [
|
|
"d ${secretCfg.outputDir} 0755 root root -"
|
|
"d ${secretCfg.cacheDir} 0700 root root -"
|
|
]) cfg.secrets);
|
|
};
|
|
}
|