#!/usr/bin/env bash # ============================================================================= # lib/init/nomad/vault-engines.sh — Enable required Vault secret engines # # Part of the Nomad+Vault migration (S2.1). Enables the KV v2 secret engine # at the `kv/` path, which is required by all policies in vault/policies/*.hcl, # all roles in vault/roles.yaml, and by vault-import.sh and forgejo.hcl # template stanzas that read from kv/disinto/* paths. # # Idempotency contract: # - If kv/ is already enabled at path=kv (version=2), log "already enabled" # and exit 0 without any Vault API calls. # - If kv/ is enabled at a different path or version, log an error and exit 1. # - Second run on a fully-configured box is a silent no-op. # # Preconditions: # - Vault is unsealed and reachable (VAULT_ADDR + VAULT_TOKEN set). # - Must run AFTER vault-init.sh (unseal complete) but BEFORE # vault-apply-policies.sh (policies reference kv/* paths). # # Environment: # VAULT_ADDR — default http://127.0.0.1:8200. # VAULT_TOKEN — env OR /etc/vault.d/root.token (resolved by lib/hvault.sh). # # Usage: # sudo lib/init/nomad/vault-engines.sh # # Exit codes: # 0 success (kv enabled, or already so) # 1 precondition / API failure # ============================================================================= set -euo pipefail SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" REPO_ROOT="$(cd "${SCRIPT_DIR}/../../.." && pwd)" # shellcheck source=../../hvault.sh source "${REPO_ROOT}/lib/hvault.sh" log() { printf '[vault-engines] %s\n' "$*"; } die() { printf '[vault-engines] ERROR: %s\n' "$*" >&2; exit 1; } # ── Flag parsing ───────────────────────────────────────────────────────────── case "${1:-}" in -h|--help) cat <<'EOF' Usage: sudo $(basename "$0") [--dry-run] Enables the KV v2 secret engine at path=kv/. Required by all Vault policies, roles, and Nomad job templates that reference kv/disinto/* paths. --dry-run Print the enable command without making changes. EOF exit 0 ;; --dry-run) # Dry-run: just echo what would happen if vault secrets list -format=json | jq -e '."kv/"' >/dev/null 2>&1; then log "[dry-run] kv-v2 at kv/ already enabled" else log "[dry-run] would run: vault secrets enable -path=kv -version=2 kv" fi exit 0 ;; '') ;; *) die "unknown flag: $1" ;; esac # ── Preconditions ──────────────────────────────────────────────────────────── for bin in curl jq; do command -v "$bin" >/dev/null 2>&1 \ || die "required binary not found: ${bin}" done # Set default Vault environment (fixes issue #2) _hvault_default_env # Check Vault connectivity and unsealed status hvault_token_lookup >/dev/null \ || die "Vault auth probe failed — check VAULT_ADDR + VAULT_TOKEN" # ── Check if kv/ is already enabled ────────────────────────────────────────── log "── Checking if kv-v2 is already enabled ──" secrets_list="$(curl -sS -H "X-Vault-Token: ${VAULT_TOKEN}" \ "${VAULT_ADDR}/v1/sys/secrets-list" | jq -r 'to_entries | map(select(.key | startswith("kv/"))) | from_entries')" if [ -n "$secrets_list" ] && printf '%s' "$secrets_list" | jq -e '."kv/"' >/dev/null 2>&1; then # kv/ exists — verify it's v2 kv_type="$(printf '%s' "$secrets_list" | jq -r '."kv/".type // ""')" kv_version="$(printf '%s' "$secrets_list" | jq -r '."kv/".options.version // "unknown"')" if [ "$kv_type" = "kv" ] && [ "$kv_version" = "2" ]; then log "kv-v2 at kv/ already enabled (type=${kv_type}, version=${kv_version})" exit 0 else die "kv/ exists but is not kv-v2 (type=${kv_type}, version=${kv_version}) — manual intervention required" fi fi # ── Enable kv-v2 ────────────────────────────────────────────────────────────── log "── Enabling kv-v2 at path=kv ──" enable_payload='{"type":"kv","options":{"version":"2"}}' curl -sS -X POST -H "X-Vault-Token: ${VAULT_TOKEN}" \ -d "$enable_payload" "${VAULT_ADDR}/v1/sys/mounts/kv" >/dev/null \ || die "failed to enable kv-v2 secret engine" log "kv-v2 enabled at kv/" log "── done ──"