#!/usr/bin/env bash set -euo pipefail # vault-fetch: Fetch secrets from OpenBao/Vault and write to filesystem # # Usage: vault-fetch [cache-directory] # # Example: vault-fetch hosts/monitoring01/grafana-admin /run/secrets/grafana /var/lib/vault/cache/grafana # # This script: # 1. Authenticates to Vault using AppRole credentials from /var/lib/vault/approle/ # 2. Fetches secrets from the specified path # 3. Writes each secret key as an individual file in the output directory # 4. Updates cache for fallback when Vault is unreachable # 5. Falls back to cache if Vault authentication fails or is unreachable # Parse arguments if [ $# -lt 2 ]; then echo "Usage: vault-fetch [cache-directory]" >&2 echo "Example: vault-fetch hosts/monitoring01/grafana /run/secrets/grafana /var/lib/vault/cache/grafana" >&2 exit 1 fi SECRET_PATH="$1" OUTPUT_DIR="$2" CACHE_DIR="${3:-/var/lib/vault/cache/$(basename "$OUTPUT_DIR")}" # Vault configuration VAULT_ADDR="${VAULT_ADDR:-https://vault01.home.2rjus.net:8200}" VAULT_SKIP_VERIFY="${VAULT_SKIP_VERIFY:-1}" APPROLE_DIR="/var/lib/vault/approle" # TLS verification flag for curl if [ "$VAULT_SKIP_VERIFY" = "1" ]; then CURL_TLS_FLAG="-k" else CURL_TLS_FLAG="" fi # Logging helper log() { echo "[vault-fetch] $*" >&2 } # Error handler error() { log "ERROR: $*" exit 1 } # Check if cache is available has_cache() { [ -d "$CACHE_DIR" ] && [ -n "$(ls -A "$CACHE_DIR" 2>/dev/null)" ] } # Use cached secrets use_cache() { if ! has_cache; then error "No cache available and Vault is unreachable" fi log "WARNING: Using cached secrets from $CACHE_DIR" mkdir -p "$OUTPUT_DIR" cp -r "$CACHE_DIR"/* "$OUTPUT_DIR/" chmod -R u=rw,go= "$OUTPUT_DIR"/* } # Fetch secrets from Vault fetch_from_vault() { # Read AppRole credentials if [ ! -f "$APPROLE_DIR/role-id" ] || [ ! -f "$APPROLE_DIR/secret-id" ]; then log "WARNING: AppRole credentials not found at $APPROLE_DIR" use_cache return fi ROLE_ID=$(cat "$APPROLE_DIR/role-id") SECRET_ID=$(cat "$APPROLE_DIR/secret-id") # Authenticate to Vault log "Authenticating to Vault at $VAULT_ADDR" AUTH_RESPONSE=$(curl -s $CURL_TLS_FLAG -X POST \ -d "{\"role_id\":\"$ROLE_ID\",\"secret_id\":\"$SECRET_ID\"}" \ "$VAULT_ADDR/v1/auth/approle/login" 2>&1) || { log "WARNING: Failed to connect to Vault" use_cache return } # Check for errors in response if echo "$AUTH_RESPONSE" | jq -e '.errors' >/dev/null 2>&1; then ERRORS=$(echo "$AUTH_RESPONSE" | jq -r '.errors[]' 2>/dev/null || echo "Unknown error") log "WARNING: Vault authentication failed: $ERRORS" use_cache return fi # Extract token VAULT_TOKEN=$(echo "$AUTH_RESPONSE" | jq -r '.auth.client_token' 2>/dev/null) if [ -z "$VAULT_TOKEN" ] || [ "$VAULT_TOKEN" = "null" ]; then log "WARNING: Failed to extract Vault token from response" use_cache return fi log "Successfully authenticated to Vault" # Fetch secret log "Fetching secret from path: $SECRET_PATH" SECRET_RESPONSE=$(curl -s $CURL_TLS_FLAG \ -H "X-Vault-Token: $VAULT_TOKEN" \ "$VAULT_ADDR/v1/secret/data/$SECRET_PATH" 2>&1) || { log "WARNING: Failed to fetch secret from Vault" use_cache return } # Check for errors if echo "$SECRET_RESPONSE" | jq -e '.errors' >/dev/null 2>&1; then ERRORS=$(echo "$SECRET_RESPONSE" | jq -r '.errors[]' 2>/dev/null || echo "Unknown error") log "WARNING: Failed to fetch secret: $ERRORS" use_cache return fi # Extract secret data SECRET_DATA=$(echo "$SECRET_RESPONSE" | jq -r '.data.data' 2>/dev/null) if [ -z "$SECRET_DATA" ] || [ "$SECRET_DATA" = "null" ]; then log "WARNING: No secret data found at path $SECRET_PATH" use_cache return fi # Create output and cache directories mkdir -p "$OUTPUT_DIR" mkdir -p "$CACHE_DIR" # Write each secret key to a separate file log "Writing secrets to $OUTPUT_DIR" 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" done log "Successfully fetched and cached secrets" } # Main execution fetch_from_vault