secrets: migrate all hosts from sops to OpenBao vault

Replace sops-nix secrets with OpenBao vault secrets across all hosts.
Hardcode root password hash, add extractKey option to vault-secrets
module, update Terraform with secrets/policies for all hosts, and
create AppRole provisioning playbook.

Hosts migrated: ha1, monitoring01, ns1, ns2, http-proxy, nix-cache01
Wave 1 hosts (nats1, jelly01, pgdb1) get AppRole policies only.

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
2026-02-05 18:43:09 +01:00
parent 4d33018285
commit 0700033c0a
20 changed files with 393 additions and 44 deletions

View File

@@ -1,11 +1,10 @@
{ pkgs, config, ... }: {
{ pkgs, config, ... }:
{
programs.zsh.enable = true;
sops.secrets.root_password_hash = { };
sops.secrets.root_password_hash.neededForUsers = true;
users.users.root = {
shell = pkgs.zsh;
hashedPasswordFile = config.sops.secrets.root_password_hash.path;
hashedPassword = "$y$j9T$N09APWqKc4//z9BoGyzSb0$3dMUzojSmo3/10nbIfShd6/IpaYoKdI21bfbWER3jl8";
openssh.authorizedKeys.keys = [
"ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIAwfb2jpKrBnCw28aevnH8HbE5YbcMXpdaVv2KmueDu6 torjus@gunter"
];

View File

@@ -73,6 +73,16 @@ let
'';
};
extractKey = mkOption {
type = types.nullOr types.str;
default = null;
description = ''
Extract a single key from the vault secret JSON and write it as a
plain file instead of a directory of files. When set, outputDir
becomes a file path rather than a directory path.
'';
};
services = mkOption {
type = types.listOf types.str;
default = [];
@@ -152,14 +162,35 @@ in
RemainAfterExit = true;
# Fetch the secret
ExecStart = pkgs.writeShellScript "fetch-${name}" ''
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"}"
'' + (if secretCfg.extractKey != null then ''
# Fetch to temporary directory, then extract single key
TMPDIR=$(mktemp -d)
trap "rm -rf $TMPDIR" EXIT
# Fetch secret using vault-fetch
${vault-fetch}/bin/vault-fetch \
"${secretCfg.secretPath}" \
"$TMPDIR" \
"${secretCfg.cacheDir}"
# Extract the specified key and write as a single file
if [ ! -f "$TMPDIR/${secretCfg.extractKey}" ]; then
echo "ERROR: Key '${secretCfg.extractKey}' not found in secret" >&2
exit 1
fi
# Ensure parent directory exists
mkdir -p "$(dirname "${secretCfg.outputDir}")"
cp "$TMPDIR/${secretCfg.extractKey}" "${secretCfg.outputDir}"
chown ${secretCfg.owner}:${secretCfg.group} "${secretCfg.outputDir}"
chmod ${secretCfg.mode} "${secretCfg.outputDir}"
'' else ''
# Fetch secret as directory of files
${vault-fetch}/bin/vault-fetch \
"${secretCfg.secretPath}" \
"${secretCfg.outputDir}" \
@@ -168,7 +199,7 @@ in
# Set ownership and permissions
chown -R ${secretCfg.owner}:${secretCfg.group} "${secretCfg.outputDir}"
chmod ${secretCfg.mode} "${secretCfg.outputDir}"/*
'';
''));
# Logging
StandardOutput = "journal";
@@ -216,7 +247,10 @@ in
[ "d /run/secrets 0755 root root -" ] ++
[ "d /var/lib/vault/cache 0700 root root -" ] ++
flatten (mapAttrsToList (name: secretCfg: [
"d ${secretCfg.outputDir} 0755 root root -"
# When extractKey is set, outputDir is a file path - create parent dir instead
(if secretCfg.extractKey != null
then "d ${dirOf secretCfg.outputDir} 0755 root root -"
else "d ${secretCfg.outputDir} 0755 root root -")
"d ${secretCfg.cacheDir} 0700 root root -"
]) cfg.secrets);
};