From 0700033c0a9ad63775a48ce740125c21dc4e8b7d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Torjus=20H=C3=A5kestad?= Date: Thu, 5 Feb 2026 18:43:09 +0100 Subject: [PATCH 1/3] 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 --- docs/plans/sops-to-openbao-migration.md | 70 ++++++++++++++++++++++ hosts/ha1/configuration.nix | 10 +++- hosts/http-proxy/configuration.nix | 2 + hosts/http-proxy/wireguard.nix | 11 ++-- hosts/monitoring01/configuration.nix | 10 +++- hosts/nix-cache01/configuration.nix | 2 + hosts/ns1/configuration.nix | 2 + hosts/ns2/configuration.nix | 2 + playbooks/provision-approle.yml | 78 +++++++++++++++++++++++++ services/actions-runner/default.nix | 10 ++-- services/monitoring/alerttonotify.nix | 10 +++- services/monitoring/pve.nix | 12 ++-- services/nix-cache/harmonia.nix | 10 ++-- services/ns/master-authorative.nix | 7 ++- services/ns/secondary-authorative.nix | 7 ++- system/root-user.nix | 7 +-- system/vault-secrets.nix | 42 +++++++++++-- terraform/vault/approle.tf | 53 ++++++++++++++++- terraform/vault/secrets.tf | 45 +++++++++++++- terraform/vault/variables.tf | 47 ++++++++++++--- 20 files changed, 393 insertions(+), 44 deletions(-) create mode 100644 docs/plans/sops-to-openbao-migration.md create mode 100644 playbooks/provision-approle.yml diff --git a/docs/plans/sops-to-openbao-migration.md b/docs/plans/sops-to-openbao-migration.md new file mode 100644 index 0000000..9f9e60e --- /dev/null +++ b/docs/plans/sops-to-openbao-migration.md @@ -0,0 +1,70 @@ +# Sops to OpenBao Secrets Migration Plan + +## Status: In Progress + +## Overview + +Migrate all hosts from sops-nix secrets to OpenBao (vault) secrets management. Pilot with ha1, then roll out to remaining hosts in waves. + +## Pre-requisites (completed) + +1. Hardcoded root password hash in `system/root-user.nix` (removes sops dependency for all hosts) +2. Added `extractKey` option to `system/vault-secrets.nix` (extracts single key as file) + +## Deployment Order + +### Pilot: ha1 +- Terraform: shared/backup/password secret, ha1 AppRole policy +- Provision AppRole credentials via `playbooks/provision-approle.yml` +- NixOS: vault.enable + backup-helper vault secret + +### Wave 1: nats1, jelly01, pgdb1 +- No service secrets (only root password, already handled) +- Just need AppRole policies + credential provisioning + +### Wave 2: monitoring01 +- 3 secrets: backup password, nats nkey, pve-exporter config +- Updates: alerttonotify.nix, pve.nix, configuration.nix + +### Wave 3: ns1, then ns2 (critical - deploy ns1 first, verify, then ns2) +- DNS zone transfer key (shared/dns/xfer-key) + +### Wave 4: http-proxy +- WireGuard private key + +### Wave 5: nix-cache01 +- Cache signing key + Gitea Actions token + +### Wave 6: ca (DEFERRED - waiting for PKI migration) + +### Skipped: auth01 (decommissioned) + +## Terraform variables needed + +User must extract from sops and add to `terraform/vault/terraform.tfvars`: + +| Variable | Source | +|----------|--------| +| `backup_helper_secret` | `sops -d secrets/secrets.yaml` | +| `ns_xfer_key` | `sops -d secrets/secrets.yaml` | +| `nats_nkey` | `sops -d secrets/secrets.yaml` | +| `pve_exporter_config` | `sops -d secrets/monitoring01/pve-exporter.yaml` | +| `wireguard_private_key` | `sops -d secrets/http-proxy/wireguard.yaml` | +| `cache_signing_key` | `sops -d secrets/nix-cache01/cache-secret` | +| `actions_token_1` | `sops -d secrets/nix-cache01/actions_token_1` | + +## Provisioning AppRole credentials + +```bash +export BAO_ADDR='https://vault01.home.2rjus.net:8200' +export BAO_TOKEN='' +nix develop -c ansible-playbook playbooks/provision-approle.yml -e hostname= +``` + +## Verification (per host) + +1. `systemctl status vault-secret-*` - all secret fetch services succeeded +2. Check secret files exist at expected paths with correct permissions +3. Verify dependent services are running +4. Check `/var/lib/vault/cache/` is populated (fallback ready) +5. Reboot host to verify boot-time secret fetching works diff --git a/hosts/ha1/configuration.nix b/hosts/ha1/configuration.nix index 1190a1d..fb754bc 100644 --- a/hosts/ha1/configuration.nix +++ b/hosts/ha1/configuration.nix @@ -55,8 +55,16 @@ git ]; + # Vault secrets management + vault.enable = true; + vault.secrets.backup-helper = { + secretPath = "shared/backup/password"; + extractKey = "password"; + outputDir = "/run/secrets/backup_helper_secret"; + services = [ "restic-backups-ha1" ]; + }; + # Backup service dirs - sops.secrets."backup_helper_secret" = { }; services.restic.backups.ha1 = { repository = "rest:http://10.69.12.52:8000/backup-nix"; passwordFile = "/run/secrets/backup_helper_secret"; diff --git a/hosts/http-proxy/configuration.nix b/hosts/http-proxy/configuration.nix index 3f4559e..ccb414a 100644 --- a/hosts/http-proxy/configuration.nix +++ b/hosts/http-proxy/configuration.nix @@ -62,6 +62,8 @@ "nix-command" "flakes" ]; + vault.enable = true; + nix.settings.tarball-ttl = 0; environment.systemPackages = with pkgs; [ vim diff --git a/hosts/http-proxy/wireguard.nix b/hosts/http-proxy/wireguard.nix index 5470996..dae284d 100644 --- a/hosts/http-proxy/wireguard.nix +++ b/hosts/http-proxy/wireguard.nix @@ -1,9 +1,12 @@ { config, ... }: { - sops.secrets.wireguard_private_key = { - sopsFile = ../../secrets/http-proxy/wireguard.yaml; - key = "wg_private_key"; + vault.secrets.wireguard = { + secretPath = "hosts/http-proxy/wireguard"; + extractKey = "private_key"; + outputDir = "/run/secrets/wireguard_private_key"; + services = [ "wireguard-wg0" ]; }; + networking.wireguard = { enable = true; useNetworkd = true; @@ -13,7 +16,7 @@ ips = [ "10.69.222.3/24" ]; mtu = 1384; listenPort = 51820; - privateKeyFile = config.sops.secrets.wireguard_private_key.path; + privateKeyFile = "/run/secrets/wireguard_private_key"; peers = [ { name = "docker2.t-juice.club"; diff --git a/hosts/monitoring01/configuration.nix b/hosts/monitoring01/configuration.nix index ab9f64b..b14294e 100644 --- a/hosts/monitoring01/configuration.nix +++ b/hosts/monitoring01/configuration.nix @@ -56,7 +56,15 @@ services.qemuGuest.enable = true; - sops.secrets."backup_helper_secret" = { }; + # Vault secrets management + vault.enable = true; + vault.secrets.backup-helper = { + secretPath = "shared/backup/password"; + extractKey = "password"; + outputDir = "/run/secrets/backup_helper_secret"; + services = [ "restic-backups-grafana" "restic-backups-grafana-db" ]; + }; + services.restic.backups.grafana = { repository = "rest:http://10.69.12.52:8000/backup-nix"; passwordFile = "/run/secrets/backup_helper_secret"; diff --git a/hosts/nix-cache01/configuration.nix b/hosts/nix-cache01/configuration.nix index bc4afe4..097362b 100644 --- a/hosts/nix-cache01/configuration.nix +++ b/hosts/nix-cache01/configuration.nix @@ -52,6 +52,8 @@ "nix-command" "flakes" ]; + vault.enable = true; + nix.settings.tarball-ttl = 0; environment.systemPackages = with pkgs; [ vim diff --git a/hosts/ns1/configuration.nix b/hosts/ns1/configuration.nix index c56f0b0..5dca77a 100644 --- a/hosts/ns1/configuration.nix +++ b/hosts/ns1/configuration.nix @@ -47,6 +47,8 @@ "nix-command" "flakes" ]; + vault.enable = true; + nix.settings.tarball-ttl = 0; environment.systemPackages = with pkgs; [ vim diff --git a/hosts/ns2/configuration.nix b/hosts/ns2/configuration.nix index 72bb2f5..29c9697 100644 --- a/hosts/ns2/configuration.nix +++ b/hosts/ns2/configuration.nix @@ -47,6 +47,8 @@ "nix-command" "flakes" ]; + vault.enable = true; + environment.systemPackages = with pkgs; [ vim wget diff --git a/playbooks/provision-approle.yml b/playbooks/provision-approle.yml new file mode 100644 index 0000000..d422e68 --- /dev/null +++ b/playbooks/provision-approle.yml @@ -0,0 +1,78 @@ +--- +# Provision OpenBao AppRole credentials to an existing host +# Usage: nix develop -c ansible-playbook playbooks/provision-approle.yml -e hostname=ha1 +# Requires: BAO_ADDR and BAO_TOKEN environment variables set + +- name: Fetch AppRole credentials from OpenBao + hosts: localhost + connection: local + gather_facts: false + + vars: + vault_addr: "{{ lookup('env', 'BAO_ADDR') | default('https://vault01.home.2rjus.net:8200', true) }}" + domain: "home.2rjus.net" + + tasks: + - name: Validate hostname is provided + ansible.builtin.fail: + msg: "hostname variable is required. Use: -e hostname=" + when: hostname is not defined + + - name: Get role-id for host + ansible.builtin.command: + cmd: "bao read -field=role_id auth/approle/role/{{ hostname }}/role-id" + environment: + BAO_ADDR: "{{ vault_addr }}" + BAO_SKIP_VERIFY: "1" + register: role_id_result + changed_when: false + + - name: Generate secret-id for host + ansible.builtin.command: + cmd: "bao write -field=secret_id -f auth/approle/role/{{ hostname }}/secret-id" + environment: + BAO_ADDR: "{{ vault_addr }}" + BAO_SKIP_VERIFY: "1" + register: secret_id_result + changed_when: true + + - name: Add target host to inventory + ansible.builtin.add_host: + name: "{{ hostname }}.{{ domain }}" + groups: vault_target + ansible_user: root + vault_role_id: "{{ role_id_result.stdout }}" + vault_secret_id: "{{ secret_id_result.stdout }}" + +- name: Deploy AppRole credentials to host + hosts: vault_target + gather_facts: false + + tasks: + - name: Create AppRole directory + ansible.builtin.file: + path: /var/lib/vault/approle + state: directory + mode: "0700" + owner: root + group: root + + - name: Write role-id + ansible.builtin.copy: + content: "{{ vault_role_id }}" + dest: /var/lib/vault/approle/role-id + mode: "0600" + owner: root + group: root + + - name: Write secret-id + ansible.builtin.copy: + content: "{{ vault_secret_id }}" + dest: /var/lib/vault/approle/secret-id + mode: "0600" + owner: root + group: root + + - name: Display success + ansible.builtin.debug: + msg: "AppRole credentials provisioned to {{ inventory_hostname }}" diff --git a/services/actions-runner/default.nix b/services/actions-runner/default.nix index b5bde79..581277c 100644 --- a/services/actions-runner/default.nix +++ b/services/actions-runner/default.nix @@ -1,8 +1,10 @@ { pkgs, config, ... }: { - sops.secrets."actions-token-1" = { - sopsFile = ../../secrets/nix-cache01/actions_token_1; - format = "binary"; + vault.secrets.actions-token = { + secretPath = "hosts/nix-cache01/actions-token"; + extractKey = "token"; + outputDir = "/run/secrets/actions-token-1"; + services = [ "gitea-runner-actions1" ]; }; virtualisation.podman = { @@ -13,7 +15,7 @@ services.gitea-actions-runner.instances = { actions1 = { enable = true; - tokenFile = config.sops.secrets.actions-token-1.path; + tokenFile = "/run/secrets/actions-token-1"; name = "actions1.home.2rjus.net"; settings = { log = { diff --git a/services/monitoring/alerttonotify.nix b/services/monitoring/alerttonotify.nix index 49cc276..8f5dfc7 100644 --- a/services/monitoring/alerttonotify.nix +++ b/services/monitoring/alerttonotify.nix @@ -1,12 +1,18 @@ { pkgs, config, ... }: { - sops.secrets."nats_nkey" = { }; + vault.secrets.nats-nkey = { + secretPath = "shared/nats/nkey"; + extractKey = "nkey"; + outputDir = "/run/secrets/nats_nkey"; + services = [ "alerttonotify" ]; + }; + systemd.services."alerttonotify" = { enable = true; wants = [ "network-online.target" ]; after = [ "network-online.target" - "sops-nix.service" + "vault-secret-nats-nkey.service" ]; wantedBy = [ "multi-user.target" ]; restartIfChanged = true; diff --git a/services/monitoring/pve.nix b/services/monitoring/pve.nix index 735b1c8..45f92ef 100644 --- a/services/monitoring/pve.nix +++ b/services/monitoring/pve.nix @@ -1,14 +1,16 @@ { config, ... }: { - sops.secrets.pve_exporter = { - format = "yaml"; - sopsFile = ../../secrets/monitoring01/pve-exporter.yaml; - key = ""; + vault.secrets.pve-exporter = { + secretPath = "hosts/monitoring01/pve-exporter"; + extractKey = "config"; + outputDir = "/run/secrets/pve_exporter"; mode = "0444"; + services = [ "prometheus-pve-exporter" ]; }; + services.prometheus.exporters.pve = { enable = true; - configFile = config.sops.secrets.pve_exporter.path; + configFile = "/run/secrets/pve_exporter"; collectors = { cluster = false; replication = false; diff --git a/services/nix-cache/harmonia.nix b/services/nix-cache/harmonia.nix index 552c257..caf37a4 100644 --- a/services/nix-cache/harmonia.nix +++ b/services/nix-cache/harmonia.nix @@ -1,14 +1,16 @@ { pkgs, config, ... }: { - sops.secrets."cache-secret" = { - sopsFile = ../../secrets/nix-cache01/cache-secret; - format = "binary"; + vault.secrets.cache-secret = { + secretPath = "hosts/nix-cache01/cache-secret"; + extractKey = "key"; + outputDir = "/run/secrets/cache-secret"; + services = [ "harmonia" ]; }; services.harmonia = { enable = true; package = pkgs.unstable.harmonia; - signKeyPaths = [ config.sops.secrets.cache-secret.path ]; + signKeyPaths = [ "/run/secrets/cache-secret" ]; }; systemd.services.harmonia = { environment.RUST_LOG = "info,actix_web=debug"; diff --git a/services/ns/master-authorative.nix b/services/ns/master-authorative.nix index c253970..c22938f 100644 --- a/services/ns/master-authorative.nix +++ b/services/ns/master-authorative.nix @@ -12,8 +12,11 @@ let }; in { - sops.secrets.ns_xfer_key = { - path = "/etc/nsd/xfer.key"; + vault.secrets.ns-xfer-key = { + secretPath = "shared/dns/xfer-key"; + extractKey = "key"; + outputDir = "/etc/nsd/xfer.key"; + services = [ "nsd" ]; }; networking.firewall.allowedTCPPorts = [ 8053 ]; diff --git a/services/ns/secondary-authorative.nix b/services/ns/secondary-authorative.nix index abaa007..fb52ba9 100644 --- a/services/ns/secondary-authorative.nix +++ b/services/ns/secondary-authorative.nix @@ -12,8 +12,11 @@ let }; in { - sops.secrets.ns_xfer_key = { - path = "/etc/nsd/xfer.key"; + vault.secrets.ns-xfer-key = { + secretPath = "shared/dns/xfer-key"; + extractKey = "key"; + outputDir = "/etc/nsd/xfer.key"; + services = [ "nsd" ]; }; networking.firewall.allowedTCPPorts = [ 8053 ]; networking.firewall.allowedUDPPorts = [ 8053 ]; diff --git a/system/root-user.nix b/system/root-user.nix index 2fda84d..060e41a 100644 --- a/system/root-user.nix +++ b/system/root-user.nix @@ -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" ]; diff --git a/system/vault-secrets.nix b/system/vault-secrets.nix index f6b5e7c..4b8c3c2 100644 --- a/system/vault-secrets.nix +++ b/system/vault-secrets.nix @@ -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); }; diff --git a/terraform/vault/approle.tf b/terraform/vault/approle.tf index cb9aac1..b1c31f8 100644 --- a/terraform/vault/approle.tf +++ b/terraform/vault/approle.tf @@ -25,17 +25,66 @@ locals { # ] # } - # TODO: actually use this policy "ha1" = { paths = [ "secret/data/hosts/ha1/*", + "secret/data/shared/backup/*", ] } - # TODO: actually use this policy "monitoring01" = { paths = [ "secret/data/hosts/monitoring01/*", + "secret/data/shared/backup/*", + "secret/data/shared/nats/*", + ] + } + + # Wave 1: hosts with no service secrets (only need vault.enable for future use) + "nats1" = { + paths = [ + "secret/data/hosts/nats1/*", + ] + } + + "jelly01" = { + paths = [ + "secret/data/hosts/jelly01/*", + ] + } + + "pgdb1" = { + paths = [ + "secret/data/hosts/pgdb1/*", + ] + } + + # Wave 3: DNS servers + "ns1" = { + paths = [ + "secret/data/hosts/ns1/*", + "secret/data/shared/dns/*", + ] + } + + "ns2" = { + paths = [ + "secret/data/hosts/ns2/*", + "secret/data/shared/dns/*", + ] + } + + # Wave 4: http-proxy + "http-proxy" = { + paths = [ + "secret/data/hosts/http-proxy/*", + ] + } + + # Wave 5: nix-cache01 + "nix-cache01" = { + paths = [ + "secret/data/hosts/nix-cache01/*", ] } } diff --git a/terraform/vault/secrets.tf b/terraform/vault/secrets.tf index 5be4cdd..557c153 100644 --- a/terraform/vault/secrets.tf +++ b/terraform/vault/secrets.tf @@ -35,22 +35,63 @@ locals { # } # } - # TODO: actually use the secret "hosts/monitoring01/grafana-admin" = { auto_generate = true password_length = 32 } - # TODO: actually use the secret "hosts/ha1/mqtt-password" = { auto_generate = true password_length = 24 } + # TODO: Remove after testing "hosts/vaulttest01/test-service" = { auto_generate = true password_length = 32 } + + # Shared backup password + "shared/backup/password" = { + auto_generate = false + data = { password = var.backup_helper_secret } + } + + # NATS NKey for alerttonotify + "shared/nats/nkey" = { + auto_generate = false + data = { nkey = var.nats_nkey } + } + + # PVE exporter config for monitoring01 + "hosts/monitoring01/pve-exporter" = { + auto_generate = false + data = { config = var.pve_exporter_config } + } + + # DNS zone transfer key + "shared/dns/xfer-key" = { + auto_generate = false + data = { key = var.ns_xfer_key } + } + + # WireGuard private key for http-proxy + "hosts/http-proxy/wireguard" = { + auto_generate = false + data = { private_key = var.wireguard_private_key } + } + + # Nix cache signing key + "hosts/nix-cache01/cache-secret" = { + auto_generate = false + data = { key = var.cache_signing_key } + } + + # Gitea Actions runner token + "hosts/nix-cache01/actions-token" = { + auto_generate = false + data = { token = var.actions_token_1 } + } } } diff --git a/terraform/vault/variables.tf b/terraform/vault/variables.tf index 52c90e6..d5055ef 100644 --- a/terraform/vault/variables.tf +++ b/terraform/vault/variables.tf @@ -16,11 +16,44 @@ variable "vault_skip_tls_verify" { default = true } -# Example variables for manual secrets -# Uncomment and add to terraform.tfvars as needed +variable "backup_helper_secret" { + description = "Backup helper password (shared across hosts)" + type = string + sensitive = true +} -# variable "smtp_password" { -# description = "SMTP password for notifications" -# type = string -# sensitive = true -# } +variable "nats_nkey" { + description = "NATS NKey for alerttonotify" + type = string + sensitive = true +} + +variable "pve_exporter_config" { + description = "PVE exporter YAML configuration" + type = string + sensitive = true +} + +variable "ns_xfer_key" { + description = "DNS zone transfer TSIG key" + type = string + sensitive = true +} + +variable "wireguard_private_key" { + description = "WireGuard private key for http-proxy" + type = string + sensitive = true +} + +variable "cache_signing_key" { + description = "Nix binary cache signing key" + type = string + sensitive = true +} + +variable "actions_token_1" { + description = "Gitea Actions runner token" + type = string + sensitive = true +} From ccb1c3fe2e095e484e9231e9c6bcfba7ac6e5d04 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Torjus=20H=C3=A5kestad?= Date: Thu, 5 Feb 2026 18:58:39 +0100 Subject: [PATCH 2/3] terraform: auto-generate backup password instead of manual Remove backup_helper_secret variable and switch shared/backup/password to auto_generate. New password will be added alongside existing restic repository key. Co-Authored-By: Claude Opus 4.5 --- terraform/vault/secrets.tf | 6 +++--- terraform/vault/variables.tf | 6 ------ 2 files changed, 3 insertions(+), 9 deletions(-) diff --git a/terraform/vault/secrets.tf b/terraform/vault/secrets.tf index 557c153..5db851c 100644 --- a/terraform/vault/secrets.tf +++ b/terraform/vault/secrets.tf @@ -51,10 +51,10 @@ locals { password_length = 32 } - # Shared backup password + # Shared backup password (auto-generated, add alongside existing restic key) "shared/backup/password" = { - auto_generate = false - data = { password = var.backup_helper_secret } + auto_generate = true + password_length = 32 } # NATS NKey for alerttonotify diff --git a/terraform/vault/variables.tf b/terraform/vault/variables.tf index d5055ef..5799067 100644 --- a/terraform/vault/variables.tf +++ b/terraform/vault/variables.tf @@ -16,12 +16,6 @@ variable "vault_skip_tls_verify" { default = true } -variable "backup_helper_secret" { - description = "Backup helper password (shared across hosts)" - type = string - sensitive = true -} - variable "nats_nkey" { description = "NATS NKey for alerttonotify" type = string From 2c9d86eaf2511e0fc17dee19c86113e2e3cad633 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Torjus=20H=C3=A5kestad?= Date: Thu, 5 Feb 2026 19:36:51 +0100 Subject: [PATCH 3/3] vault-fetch: fix multiline secret values being truncated The read-based loop split multiline values on newlines, causing only the first line to be written. Use jq -j to write each key's value directly to files, preserving multiline content. Co-Authored-By: Claude Opus 4.5 --- scripts/vault-fetch/vault-fetch.sh | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/scripts/vault-fetch/vault-fetch.sh b/scripts/vault-fetch/vault-fetch.sh index 92a1e3f..3c2bd33 100644 --- a/scripts/vault-fetch/vault-fetch.sh +++ b/scripts/vault-fetch/vault-fetch.sh @@ -137,9 +137,9 @@ fetch_from_vault() { # Write each secret key to a separate file log "Writing secrets to $OUTPUT_DIR" - echo "$SECRET_DATA" | jq -r 'to_entries[] | "\(.key)\n\(.value)"' | while read -r key; read -r value; do - echo -n "$value" > "$OUTPUT_DIR/$key" - echo -n "$value" > "$CACHE_DIR/$key" + for key in $(echo "$SECRET_DATA" | jq -r 'keys[]'); do + echo "$SECRET_DATA" | jq -j --arg k "$key" '.[$k]' > "$OUTPUT_DIR/$key" + echo "$SECRET_DATA" | jq -j --arg k "$key" '.[$k]' > "$CACHE_DIR/$key" chmod 600 "$OUTPUT_DIR/$key" chmod 600 "$CACHE_DIR/$key" log " - Wrote secret key: $key"