#!/usr/bin/env bash # vault/policies/validate.sh — Validate Vault policy HCL files # # Usage: vault/policies/validate.sh [--check-exists] # # This script provides CI validation for Vault policy files: # 1. `vault policy fmt -check` — ensures consistent formatting (non-destructive) # 2. `vault policy validate` — syntax + semantic validation (requires Vault dev mode) # 3. Optional: check that referenced policies exist in roles.yaml # # Exit codes: # 0 — all checks pass # 1 — formatting or validation error # 2 — policy reference validation error (roles.yaml check) # # Environment: # VAULT_ADDR — Vault server URL (defaults to http://127.0.0.1:8200 for dev mode) # VAULT_TOKEN — Dev mode token (defaults to "root" for CI) # # CI usage: # vault/policies/validate.sh # vault/policies/validate.sh --check-exists # when roles.yaml exists # # Notes: # - fmt -check is non-destructive; it only reports diff # - validate requires a running Vault instance (dev mode is sufficient for CI) # - Exit 2 is tolerated for advisory warnings (TLS-disabled listeners) # ============================================================================= set -euo pipefail SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" ROLES_YAML="${SCRIPT_DIR}/../roles.yaml" VAULT_ADDR="${VAULT_ADDR:-http://127.0.0.1:8200}" VAULT_TOKEN="${VAULT_TOKEN:-root}" usage() { cat < /dev/null 2>&1; then rc=$? case "$rc" in 0|2) return 0 ;; # OK for CI *) echo "vault/policies/validate.sh: Vault not available (exit $rc)" >&2; return 1 ;; esac fi return 0 } # ───────────────────────────────────────────────────────────────────────────── # Step 1: vault policy fmt -check # ───────────────────────────────────────────────────────────────────────────── fmt_check() { local failed=0 local hcl_files hcl_files=$(find "$SCRIPT_DIR" -maxdepth 1 -name '*.hcl' -type f 2>/dev/null || true) if [ -z "$hcl_files" ]; then echo "vault/policies/validate.sh: no .hcl files found in $SCRIPT_DIR" >&2 return 0 fi for f in $hcl_files; do echo "fmt-check: $f" if ! vault policy fmt -check "$f" > /dev/null 2>&1; then echo " ERROR: file not formatted correctly" >&2 vault policy fmt -check "$f" 2>&1 | head -20 >&2 || true failed=1 fi done return $failed } # ───────────────────────────────────────────────────────────────────────────── # Step 2: vault policy validate (syntax + semantic) # ───────────────────────────────────────────────────────────────────────────── validate_syntax() { local failed=0 local hcl_files hcl_files=$(find "$SCRIPT_DIR" -maxdepth 1 -name '*.hcl' -type f 2>/dev/null || true) if [ -z "$hcl_files" ]; then return 0 fi # Check Vault is available first if ! check_vault_available; then echo "vault/policies/validate.sh: skipping validation (Vault unavailable)" >&2 return 0 fi for f in $hcl_files; do echo "validate: $f" local rc=0 if ! vault policy validate "$f" > /dev/null 2>&1; then rc=$? case "$rc" in 0) ;; # Should not happen, but be safe 1|2) echo " ERROR: validation failed (exit $rc)" >&2 vault policy validate "$f" 2>&1 | head -20 >&2 || true failed=1 ;; *) echo " ERROR: unexpected exit code $rc" >&2 failed=1 ;; esac fi done return $failed } # ───────────────────────────────────────────────────────────────────────────── # Step 3: Check that roles.yaml references exist # ───────────────────────────────────────────────────────────────────────────── check_policy_references() { if [ ! -f "$ROLES_YAML" ]; then echo "vault/policies/validate.sh: roles.yaml not found, skipping reference check" >&2 return 0 fi local failed=0 local policy_names # Get list of policy names (basenames without .hcl) policy_names=$(find "$SCRIPT_DIR" -maxdepth 1 -name '*.hcl' -type f -exec basename {} .hcl \; | sort) # Extract policy names from roles.yaml using yq or grep+sed local referenced_policies if command -v yq > /dev/null 2>&1; then # yq is available, use it referenced_policies=$(yq -r '.roles[].policies[]?' "$ROLES_YAML" 2>/dev/null | sort -u || true) else # Fallback: grep for 'policies:' lines and extract values referenced_policies=$(grep -E '^\s*policies:' "$ROLES_YAML" 2>/dev/null | \ sed -E 's/.*policies:\s*\[(.*)\].*/\1/' | \ tr ',' '\n' | \ sed 's/^[[:space:]]*"//;s/"[[:space:]]*$//' | \ sort -u || true) fi if [ -z "$referenced_policies" ]; then echo "vault/policies/validate.sh: no policies referenced in roles.yaml" >&2 return 0 fi for policy in $referenced_policies; do if ! echo "$policy_names" | grep -q "^${policy}$"; then echo "vault/policies/validate.sh: ERROR: policy '$policy' referenced in roles.yaml but not found" >&2 failed=1 fi done return $failed } # ───────────────────────────────────────────────────────────────────────────── # Main # ───────────────────────────────────────────────────────────────────────────── check_refs=0 while [ $# -gt 0 ]; do case "$1" in --check-exists) check_refs=1 shift ;; --help|-h) usage ;; *) echo "Unknown option: $1" >&2 usage ;; esac done echo "vault/policies/validate.sh — validating policy HCL files" echo " VAULT_ADDR: $VAULT_ADDR" echo " roles.yaml: $ROLES_YAML (exists: $([ -f "$ROLES_YAML" ] && echo yes || echo no))" echo "" # Run fmt check fmt_check || exit 1 # Run syntax validation validate_syntax || exit 1 # Run reference check if requested if [ "$check_refs" -eq 1 ]; then check_policy_references || exit 2 fi echo "" echo "vault/policies/validate.sh: all checks passed" exit 0