mirror of
https://github.com/CyberMind-FR/secubox-deb.git
synced 2026-07-29 18:36:55 +00:00
- Fix log messages going to stdout which corrupted JSON capture - Redirect extraction and generation logs to stderr - Successfully syncs 94 vhosts to mitmproxy routes and health prober - Tested on server with full HAProxy config Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
351 lines
14 KiB
Bash
Executable File
351 lines
14 KiB
Bash
Executable File
#!/bin/bash
|
|
# vhost-matrix-sync.sh — Synchronize HAProxy vhosts to mitmproxy routes and health prober
|
|
# SecuBox-DEB :: Infrastructure Workflow Automation
|
|
# CyberMind — Gérald Kerma
|
|
set -euo pipefail
|
|
|
|
VERSION="1.0.0"
|
|
SCRIPT_NAME=$(basename "$0")
|
|
|
|
# Paths
|
|
HAPROXY_CFG="${HAPROXY_CFG:-/etc/haproxy/haproxy.cfg}"
|
|
MITMPROXY_ROUTES="/srv/mitmproxy-waf/data/routes.json"
|
|
HEALTH_VHOSTS="/var/cache/secubox/health/vhost-matrix.json"
|
|
VHOST_MATRIX="/var/lib/secubox/haproxy/vhost-matrix.json"
|
|
LOG_FILE="/var/log/secubox/vhost-matrix-sync.log"
|
|
|
|
# LXC mitmproxy bridge IP (not localhost - routes are used inside LXC)
|
|
MITMPROXY_HOST_IP="${MITMPROXY_HOST_IP:-10.100.0.1}"
|
|
|
|
# Colors
|
|
RED='\033[0;31m'
|
|
GREEN='\033[0;32m'
|
|
YELLOW='\033[0;33m'
|
|
BLUE='\033[0;34m'
|
|
NC='\033[0m'
|
|
|
|
log() { echo -e "${BLUE}[INFO]${NC} $*"; echo "[$(date '+%Y-%m-%d %H:%M:%S')] INFO: $*" >> "$LOG_FILE" 2>/dev/null || true; }
|
|
warn() { echo -e "${YELLOW}[WARN]${NC} $*"; echo "[$(date '+%Y-%m-%d %H:%M:%S')] WARN: $*" >> "$LOG_FILE" 2>/dev/null || true; }
|
|
error() { echo -e "${RED}[ERROR]${NC} $*" >&2; echo "[$(date '+%Y-%m-%d %H:%M:%S')] ERROR: $*" >> "$LOG_FILE" 2>/dev/null || true; }
|
|
success() { echo -e "${GREEN}[OK]${NC} $*"; }
|
|
|
|
# ═══════════════════════════════════════════════════════════════════════════════
|
|
# EXTRACT VHOST MATRIX FROM HAPROXY
|
|
# ═══════════════════════════════════════════════════════════════════════════════
|
|
|
|
extract_vhost_matrix() {
|
|
# Log to stderr so stdout only contains the JSON result
|
|
echo -e "${BLUE}[INFO]${NC} Extracting vhost→backend matrix from HAProxy config..." >&2
|
|
|
|
if [ ! -f "$HAPROXY_CFG" ]; then
|
|
error "HAProxy config not found: $HAPROXY_CFG"
|
|
return 1
|
|
fi
|
|
|
|
# Use Python for reliable parsing
|
|
local matrix
|
|
matrix=$(python3 << PYEOF
|
|
import re
|
|
import json
|
|
|
|
with open("$HAPROXY_CFG") as f:
|
|
content = f.read()
|
|
|
|
# Extract backends with their servers
|
|
backends = {}
|
|
for m in re.finditer(r"^backend\s+(\S+).*?(?=^backend|\Z)", content, re.MULTILINE | re.DOTALL):
|
|
backend_name = m.group(1)
|
|
backend_block = m.group(0)
|
|
server_match = re.search(r"server\s+\S+\s+(\d+\.\d+\.\d+\.\d+):(\d+)", backend_block)
|
|
if server_match:
|
|
backends[backend_name] = {"ip": server_match.group(1), "port": int(server_match.group(2))}
|
|
|
|
# Extract ACLs
|
|
acls = {}
|
|
for m in re.finditer(r"acl\s+(\S+)\s+hdr\(host\)\s+-i\s+(\S+)", content):
|
|
acls[m.group(1)] = m.group(2)
|
|
|
|
# Extract use_backend rules and build vhost matrix
|
|
vhosts = {}
|
|
for m in re.finditer(r"use_backend\s+(\S+)\s+if\s+(\S+)", content):
|
|
backend_name = m.group(1)
|
|
acl_name = m.group(2)
|
|
|
|
# Skip WAF inspector backends
|
|
if "waf" in backend_name.lower() or "mitmproxy" in backend_name.lower():
|
|
continue
|
|
|
|
hostname = acls.get(acl_name)
|
|
backend = backends.get(backend_name)
|
|
|
|
if hostname and backend and hostname not in vhosts:
|
|
vhosts[hostname] = {
|
|
"backend": backend_name,
|
|
"ip": backend["ip"],
|
|
"port": backend["port"],
|
|
"waf": True
|
|
}
|
|
|
|
print(json.dumps(vhosts, indent=2))
|
|
PYEOF
|
|
)
|
|
|
|
# Validate JSON
|
|
if ! echo "$matrix" | jq . >/dev/null 2>&1; then
|
|
error "Failed to generate valid JSON matrix"
|
|
return 1
|
|
fi
|
|
|
|
local count
|
|
count=$(echo "$matrix" | jq 'keys | length')
|
|
echo -e "${BLUE}[INFO]${NC} Extracted $count vhost mappings" >&2
|
|
|
|
echo "$matrix"
|
|
}
|
|
|
|
# ═══════════════════════════════════════════════════════════════════════════════
|
|
# GENERATE MITMPROXY ROUTES
|
|
# ═══════════════════════════════════════════════════════════════════════════════
|
|
|
|
generate_mitmproxy_routes() {
|
|
local matrix="$1"
|
|
|
|
# Log to stderr so stdout only contains the JSON result
|
|
echo -e "${BLUE}[INFO]${NC} Generating mitmproxy routes..." >&2
|
|
|
|
# Convert matrix to mitmproxy format: {"hostname": ["ip", port]}
|
|
# Use MITMPROXY_HOST_IP instead of 127.0.0.1 for LXC container access
|
|
local routes
|
|
routes=$(echo "$matrix" | jq --arg host "$MITMPROXY_HOST_IP" '
|
|
to_entries | map({
|
|
key: .key,
|
|
value: [
|
|
(if .value.ip == "127.0.0.1" then $host else .value.ip end),
|
|
.value.port
|
|
]
|
|
}) | from_entries
|
|
')
|
|
|
|
local count
|
|
count=$(echo "$routes" | jq 'keys | length')
|
|
echo -e "${BLUE}[INFO]${NC} Generated $count mitmproxy routes" >&2
|
|
|
|
echo "$routes"
|
|
}
|
|
|
|
# ═══════════════════════════════════════════════════════════════════════════════
|
|
# SYNC TO MITMPROXY
|
|
# ═══════════════════════════════════════════════════════════════════════════════
|
|
|
|
sync_mitmproxy() {
|
|
local routes="$1"
|
|
|
|
log "Syncing routes to mitmproxy..."
|
|
|
|
# Ensure directory exists
|
|
mkdir -p "$(dirname "$MITMPROXY_ROUTES")"
|
|
|
|
# Write routes
|
|
echo "$routes" | jq . > "$MITMPROXY_ROUTES"
|
|
success "Routes written to $MITMPROXY_ROUTES"
|
|
|
|
# Reload mitmproxy if running in LXC
|
|
if lxc-info -n mitmproxy-waf -s 2>/dev/null | grep -q "RUNNING"; then
|
|
log "Signaling mitmproxy to reload..."
|
|
lxc-attach -n mitmproxy-waf -- pkill -HUP mitmdump 2>/dev/null || true
|
|
success "mitmproxy reloaded"
|
|
fi
|
|
}
|
|
|
|
# ═══════════════════════════════════════════════════════════════════════════════
|
|
# SYNC TO HEALTH PROBER
|
|
# ═══════════════════════════════════════════════════════════════════════════════
|
|
|
|
sync_health_prober() {
|
|
local matrix="$1"
|
|
|
|
log "Syncing vhost matrix to health prober..."
|
|
|
|
# Ensure directory exists
|
|
mkdir -p "$(dirname "$HEALTH_VHOSTS")"
|
|
|
|
# Write matrix for health prober consumption
|
|
echo "$matrix" | jq . > "$HEALTH_VHOSTS"
|
|
success "Matrix written to $HEALTH_VHOSTS"
|
|
|
|
# Restart health prober if running
|
|
if systemctl is-active --quiet secubox-health-prober 2>/dev/null; then
|
|
log "Restarting health prober..."
|
|
systemctl restart secubox-health-prober
|
|
success "Health prober restarted"
|
|
fi
|
|
}
|
|
|
|
# ═══════════════════════════════════════════════════════════════════════════════
|
|
# SAVE MASTER MATRIX
|
|
# ═══════════════════════════════════════════════════════════════════════════════
|
|
|
|
save_matrix() {
|
|
local matrix="$1"
|
|
|
|
log "Saving master vhost matrix..."
|
|
|
|
mkdir -p "$(dirname "$VHOST_MATRIX")"
|
|
echo "$matrix" | jq . > "$VHOST_MATRIX"
|
|
success "Master matrix saved to $VHOST_MATRIX"
|
|
}
|
|
|
|
# ═══════════════════════════════════════════════════════════════════════════════
|
|
# SHOW MATRIX STATUS
|
|
# ═══════════════════════════════════════════════════════════════════════════════
|
|
|
|
show_status() {
|
|
log "=== VHost Matrix Status ==="
|
|
|
|
echo ""
|
|
printf "%-40s %-20s %-18s %s\n" "HOSTNAME" "BACKEND" "ENDPOINT" "WAF"
|
|
printf "%s\n" "$(printf '%.0s-' {1..95})"
|
|
|
|
if [ -f "$VHOST_MATRIX" ]; then
|
|
jq -r 'to_entries[] | [.key, .value.backend, (.value.ip + ":" + (.value.port|tostring)), (if .value.waf then "✓" else "✗" end)] | @tsv' "$VHOST_MATRIX" | \
|
|
while IFS=$'\t' read -r host backend endpoint waf; do
|
|
printf "%-40s %-20s %-18s %s\n" "$host" "$backend" "$endpoint" "$waf"
|
|
done | sort
|
|
else
|
|
warn "No matrix file found. Run 'sync' first."
|
|
fi
|
|
|
|
echo ""
|
|
|
|
# Summary
|
|
local haproxy_vhosts mitmproxy_routes health_vhosts
|
|
haproxy_vhosts=$(grep -cE 'hdr\(host\).*-i' "$HAPROXY_CFG" 2>/dev/null || echo "0")
|
|
mitmproxy_routes=$(jq 'keys | length' "$MITMPROXY_ROUTES" 2>/dev/null || echo "0")
|
|
health_vhosts=$(jq 'keys | length' "$HEALTH_VHOSTS" 2>/dev/null || echo "0")
|
|
|
|
log "Summary:"
|
|
echo " HAProxy vhosts: $haproxy_vhosts"
|
|
echo " Mitmproxy routes: $mitmproxy_routes"
|
|
echo " Health prober: $health_vhosts"
|
|
|
|
# Check sync status
|
|
if [ "$mitmproxy_routes" -lt "$((haproxy_vhosts / 2))" ]; then
|
|
warn "Mitmproxy routes appear out of sync. Run 'sync' to update."
|
|
fi
|
|
}
|
|
|
|
# ═══════════════════════════════════════════════════════════════════════════════
|
|
# FULL SYNC
|
|
# ═══════════════════════════════════════════════════════════════════════════════
|
|
|
|
cmd_sync() {
|
|
log "=== VHost Matrix Full Sync ==="
|
|
|
|
# Extract matrix from HAProxy
|
|
local matrix
|
|
matrix=$(extract_vhost_matrix)
|
|
|
|
if [ -z "$matrix" ] || [ "$matrix" = "{}" ]; then
|
|
error "No vhost mappings found in HAProxy config"
|
|
return 1
|
|
fi
|
|
|
|
# Save master matrix
|
|
save_matrix "$matrix"
|
|
|
|
# Generate and sync mitmproxy routes
|
|
local routes
|
|
routes=$(generate_mitmproxy_routes "$matrix")
|
|
sync_mitmproxy "$routes"
|
|
|
|
# Sync to health prober
|
|
sync_health_prober "$matrix"
|
|
|
|
echo ""
|
|
success "VHost matrix sync completed"
|
|
|
|
# Show summary
|
|
show_status
|
|
}
|
|
|
|
# ═══════════════════════════════════════════════════════════════════════════════
|
|
# RELOAD SERVICES
|
|
# ═══════════════════════════════════════════════════════════════════════════════
|
|
|
|
cmd_reload() {
|
|
log "=== Reloading Services ==="
|
|
|
|
# Reload HAProxy
|
|
if systemctl is-active --quiet haproxy; then
|
|
log "Reloading HAProxy..."
|
|
systemctl reload haproxy
|
|
success "HAProxy reloaded"
|
|
fi
|
|
|
|
# Reload mitmproxy
|
|
if lxc-info -n mitmproxy-waf -s 2>/dev/null | grep -q "RUNNING"; then
|
|
log "Signaling mitmproxy..."
|
|
lxc-attach -n mitmproxy-waf -- pkill -HUP mitmdump 2>/dev/null || true
|
|
success "mitmproxy signaled"
|
|
fi
|
|
|
|
# Restart health prober
|
|
if systemctl is-active --quiet secubox-health-prober; then
|
|
log "Restarting health prober..."
|
|
systemctl restart secubox-health-prober
|
|
success "Health prober restarted"
|
|
fi
|
|
|
|
success "All services reloaded"
|
|
}
|
|
|
|
# ═══════════════════════════════════════════════════════════════════════════════
|
|
# USAGE
|
|
# ═══════════════════════════════════════════════════════════════════════════════
|
|
|
|
usage() {
|
|
cat <<EOF
|
|
$SCRIPT_NAME v$VERSION — VHost Matrix Synchronization
|
|
|
|
Usage: $SCRIPT_NAME <command>
|
|
|
|
Commands:
|
|
sync Extract HAProxy vhosts and sync to mitmproxy + health prober
|
|
status Show current vhost matrix status
|
|
reload Reload all related services (HAProxy, mitmproxy, health prober)
|
|
help Show this help
|
|
|
|
Environment:
|
|
HAPROXY_CFG HAProxy config path (default: /etc/haproxy/haproxy.cfg)
|
|
MITMPROXY_HOST_IP Host IP for mitmproxy routes (default: 10.100.0.1)
|
|
|
|
Examples:
|
|
$SCRIPT_NAME sync # Full sync from HAProxy to all consumers
|
|
$SCRIPT_NAME status # Show current matrix state
|
|
$SCRIPT_NAME reload # Reload all services
|
|
|
|
EOF
|
|
}
|
|
|
|
# ═══════════════════════════════════════════════════════════════════════════════
|
|
# MAIN
|
|
# ═══════════════════════════════════════════════════════════════════════════════
|
|
|
|
main() {
|
|
mkdir -p "$(dirname "$LOG_FILE")" 2>/dev/null || true
|
|
|
|
case "${1:-}" in
|
|
sync) cmd_sync ;;
|
|
status) show_status ;;
|
|
reload) cmd_reload ;;
|
|
help|-h|--help) usage ;;
|
|
*)
|
|
[ -n "${1:-}" ] && error "Unknown command: $1"
|
|
usage
|
|
exit 1
|
|
;;
|
|
esac
|
|
}
|
|
|
|
main "$@"
|