#!/bin/bash ################################################################################ # Script Name: vault-exporter.sh # Version: 1.0 # Description: Prometheus exporter for HashiCorp Vault — seal status, HA mode, # token and lease counts, secret engine mounts, audit log status, # storage backend health, and version info via the Vault API # # Author: Phil Connor # Contact: contact@mylinux.work # Website: https://mylinux.work # License: MIT # # Prerequisites: # - curl, jq # - VAULT_ADDR and VAULT_TOKEN environment variables # - netcat (nc) for HTTP mode # # Usage: # VAULT_ADDR="https://vault:8200" VAULT_TOKEN="hvs.xxx" ./vault-exporter.sh # ./vault-exporter.sh --http -p 9201 # ./vault-exporter.sh --textfile # # Configuration: # Default HTTP port: 9201 # Textfile directory: /var/lib/node_exporter # ################################################################################ TEXTFILE_DIR="/var/lib/node_exporter" OUTPUT_FILE="" HTTP_MODE=false HTTP_PORT=9201 show_usage() { cat <&2; exit 1 ;; esac done } vault_api() { local endpoint="$1" curl -s -H "X-Vault-Token: $VAULT_TOKEN" "${VAULT_ADDR}/v1${endpoint}" 2>/dev/null } check_vault() { if ! command -v curl >/dev/null 2>&1 || ! command -v jq >/dev/null 2>&1; then echo "ERROR: curl and jq are required" >&2 return 1 fi if [ -z "$VAULT_ADDR" ] || [ -z "$VAULT_TOKEN" ]; then echo "ERROR: VAULT_ADDR and VAULT_TOKEN required" >&2 return 1 fi if ! curl -s "${VAULT_ADDR}/v1/sys/health" >/dev/null 2>&1; then echo "ERROR: Cannot connect to Vault" >&2 return 1 fi return 0 } generate_metrics() { local script_start script_start=$(date +%s) if ! check_vault; then echo "# HELP vault_overview_up Vault exporter status" echo "# TYPE vault_overview_up gauge" echo "vault_overview_up 0" return fi # Health endpoint (unauthenticated) local health health=$(curl -s "${VAULT_ADDR}/v1/sys/health" 2>/dev/null) local sealed initialized version sealed=$(echo "$health" | jq '.sealed // false' | sed 's/true/1/;s/false/0/') initialized=$(echo "$health" | jq '.initialized // false' | sed 's/true/1/;s/false/0/') version=$(echo "$health" | jq -r '.version // "unknown"') # HA status local leader_status leader_status=$(vault_api "/sys/leader") local ha_enabled is_leader leader_addr ha_enabled=$(echo "$leader_status" | jq '.ha_enabled // false' | sed 's/true/1/;s/false/0/') is_leader=$(echo "$leader_status" | jq '.is_self // false' | sed 's/true/1/;s/false/0/') leader_addr=$(echo "$leader_status" | jq -r '.leader_address // "unknown"') # Mounts local secret_engines auth_methods secret_engines=$(vault_api "/sys/mounts" | jq '.data // . | keys | length' 2>/dev/null || echo 0) auth_methods=$(vault_api "/sys/auth" | jq '.data // . | keys | length' 2>/dev/null || echo 0) # Audit local audit_enabled local audit_data audit_data=$(vault_api "/sys/audit") local audit_count audit_count=$(echo "$audit_data" | jq '.data // . | keys | length' 2>/dev/null || echo 0) [ "$audit_count" -gt 0 ] 2>/dev/null && audit_enabled=1 || audit_enabled=0 cat <&2 if ! command -v nc >/dev/null 2>&1; then echo "ERROR: netcat (nc) required for HTTP mode" >&2 exit 1 fi while true; do { read -r request if [[ "$request" =~ ^GET\ /metrics ]]; then echo -e "HTTP/1.1 200 OK\r\nContent-Type: text/plain; version=0.0.4\r\n\r" generate_metrics else echo -e "HTTP/1.1 200 OK\r\nContent-Type: text/html\r\n\r" echo "Vault Exporter

Vault Prometheus Exporter

Metrics

" fi } | nc -l -p "$HTTP_PORT" -q 1 2>/dev/null done } main() { parse_args "$@" if [ "$HTTP_MODE" = true ]; then run_http_server elif [ -n "$OUTPUT_FILE" ]; then local output_dir output_dir="$(dirname "$OUTPUT_FILE")" mkdir -p "$output_dir" local temp_file temp_file=$(mktemp "${output_dir}/.vault_metrics.XXXXXX") if ! generate_metrics > "$temp_file" 2>/dev/null; then rm -f "$temp_file" echo "ERROR: Failed to generate metrics" >&2 exit 1 fi chmod 644 "$temp_file" mv -f "$temp_file" "$OUTPUT_FILE" echo "Metrics written to $OUTPUT_FILE" >&2 else generate_metrics fi } main "$@"