#!/bin/sh # OpenVPN Access Tracker - Sampled Replay (Arthur's Gold Standard v1) # Correlaciona usuários VPN com a tabela de estados do Packet Filter # Compatível com: pfSense 2.x / FreeBSD # # Instalação: # 1. Copie para /opt/zabbix/openvpn-access-tracker.sh # 2. chmod +x /opt/zabbix/openvpn-access-tracker.sh # 3. Adicione ao cron: */1 * * * * /opt/zabbix/openvpn-access-tracker.sh # # Output: /var/log/openvpn_user_activity.log # Format: TIMESTAMP|USER|REAL_IP|DST_IP:PORT|PROTOCOL METRICS_FILE="/tmp/openvpn_metrics.json" LOG_FILE="/var/log/openvpn_user_activity.log" TIMESTAMP=$(date "+%Y-%m-%d %H:%M:%S") # Verifica se temos dados de VPN if [ ! -f "$METRICS_FILE" ]; then exit 0 fi # Extrai lista de usuários e seus IPs virtuais # Formato esperado: "user": { ... "virtual_ip": "10.0.8.5" ... } user_ips=$(cat "$METRICS_FILE" | sed 's/},{/}\n{/g' | grep -oE '"[^"]+":{"bytes_recv"[^}]+' | \ sed 's/"//g' | awk -F: '{user=$1; for(i=2;i<=NF;i++) if($i ~ /virtual_ip/) {getline; print user":"$2}}' 2>/dev/null) # Alternativa mais robusta: usar jq se disponível, senão parse manual if command -v jq >/dev/null 2>&1; then # jq disponível - parse limpo user_list=$(jq -r 'to_entries[] | "\(.key):\(.value.virtual_ip)"' "$METRICS_FILE" 2>/dev/null) else # Parse manual simplificado user_list=$(cat "$METRICS_FILE" | tr ',' '\n' | tr '{' '\n' | tr '}' '\n' | \ grep -E '^"[a-zA-Z0-9._-]+"|virtual_ip' | paste - - 2>/dev/null | \ sed 's/"//g;s/virtual_ip://g' | awk '{print $1":"$2}') fi # Se não temos usuários, sai [ -z "$user_list" ] && exit 0 # Captura tabela de estados state_table=$(pfctl -ss 2>/dev/null) [ -z "$state_table" ] && exit 0 # Para cada usuário VPN, busca conexões na state table echo "$user_list" | while IFS=: read -r user vip; do [ -z "$user" ] || [ -z "$vip" ] && continue # Busca estados onde o IP virtual é a origem # Formato pfctl -ss: all tcp 10.0.8.5:54321 -> 192.168.1.100:443 ESTABLISHED:ESTABLISHED echo "$state_table" | grep " $vip:" | while read -r state_line; do # Extrai protocolo, destino proto=$(echo "$state_line" | awk '{print $2}') dst=$(echo "$state_line" | awk '{print $4}' | sed 's/->//g') state=$(echo "$state_line" | awk '{print $NF}') [ -z "$dst" ] && continue # Loga a atividade printf "%s|%s|%s|%s|%s|%s\n" "$TIMESTAMP" "$user" "$vip" "$dst" "$proto" "$state" >> "$LOG_FILE" done done # Rotação simples: mantém só últimas 50000 linhas (~5MB) if [ -f "$LOG_FILE" ] && [ $(wc -l < "$LOG_FILE") -gt 50000 ]; then tail -40000 "$LOG_FILE" > "$LOG_FILE.tmp" && mv "$LOG_FILE.tmp" "$LOG_FILE" fi