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 <noreply@anthropic.com>
153 lines
4.5 KiB
Bash
153 lines
4.5 KiB
Bash
#!/usr/bin/env bash
|
|
set -euo pipefail
|
|
|
|
# vault-fetch: Fetch secrets from OpenBao/Vault and write to filesystem
|
|
#
|
|
# Usage: vault-fetch <secret-path> <output-directory> [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 <secret-path> <output-directory> [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
|