La suite genera report HTML interattivi che aggregano dati da OpenVPN e BIND DNS. Il flusso dati e' il seguente:
OpenVPN e BIND possono risiedere sullo stesso server o su server separati. La suite di reportistica necessita di accesso in lettura ai file di log.
| Componente | Versione minima | Note |
|---|---|---|
| OpenVPN | 2.4+ | Community o Access Server |
| BIND (named) | 9.11+ | Con supporto RPZ e DDNS |
| Python | 3.7+ | Libreria standard, nessun pip install |
| Web server | qualsiasi | Nginx, Apache, o file locale |
La suite utilizza esclusivamente la libreria standard di Python 3.
Non e' richiesto nessun pip install.
| Servizio | Destinazione | Porta | Uso |
|---|---|---|---|
| opzionale GeoIP | ip-api.com |
80/TCP | Geolocalizzazione IP per la mappa Leaflet |
/var/log/openvpn/ # Log OpenVPN openvpn.log # Evento connessioni (custom) openvpn-status.log # Status file nativo /var/log/named/ # Log BIND DNS queries.log # Query DNS rpz.log # Blocchi RPZ update.log # Aggiornamenti DDNS /var/www/html/ # Output report HTML /opt/openvpn-geomap-dns/ # Suite (suggerito)
Il file di stato e' una funzionalita' nativa di OpenVPN. Aggiungere alla configurazione del server:
# Genera il file status ogni 10 secondi status /var/log/openvpn/openvpn-status.log 10 # Formato v2 (raccomandato) - supportati anche v1 e v3 status-version 2
Questo produce un file CSV con le connessioni attive in tempo reale. Esempio output v2:
HEADER,CLIENT_LIST,Common Name,Real Address,Virtual Address,...,Connected Since,... CLIENT_LIST,mario.rossi,93.42.1.100:54321,172.16.0.20,...,Thu Mar 27 10:22:45 2026,...
La suite richiede un log eventi con un formato specifico. Non e' un log nativo di OpenVPN
ma viene generato tramite script hook client-connect e client-disconnect.
YYYY-MM-DD HH:MM:SS CN IP_VPN IP_PUB CONNECT|DISCONNECT
Esempio:
2026-03-27 10:22:45 mario.rossi 172.16.0.20 93.42.1.100 CONNECT 2026-03-27 18:30:12 mario.rossi 172.16.0.20 93.42.1.100 DISCONNECT
| Campo | Descrizione | Esempio |
|---|---|---|
YYYY-MM-DD HH:MM:SS | Timestamp ISO 8601 | 2026-03-27 10:22:45 |
CN | Common Name del certificato | mario.rossi |
IP_VPN | IP assegnato nella VPN | 172.16.0.20 |
IP_PUB | IP pubblico del client | 93.42.1.100 |
CONNECT|DISCONNECT | Tipo evento | CONNECT |
# Hook script per logging eventi
script-security 2
client-connect /etc/openvpn/scripts/connect.sh
client-disconnect /etc/openvpn/scripts/disconnect.sh
#!/bin/bash
LOGFILE="/var/log/openvpn/openvpn.log"
TIMESTAMP=$(date '+%Y-%m-%d %H:%M:%S')
echo "${TIMESTAMP} ${common_name} ${ifconfig_pool_remote_ip} ${trusted_ip} CONNECT" >> "${LOGFILE}"
#!/bin/bash
LOGFILE="/var/log/openvpn/openvpn.log"
TIMESTAMP=$(date '+%Y-%m-%d %H:%M:%S')
echo "${TIMESTAMP} ${common_name} ${ifconfig_pool_remote_ip} ${trusted_ip} DISCONNECT" >> "${LOGFILE}"
chmod +x /etc/openvpn/scripts/connect.sh
chmod +x /etc/openvpn/scripts/disconnect.sh
mkdir -p /var/log/openvpn
touch /var/log/openvpn/openvpn.log
# Se OpenVPN gira come utente 'openvpn' o 'nobody':
chown openvpn:openvpn /var/log/openvpn/openvpn.log
ifconfig_pool_remote_ip e' disponibile solo se si usa
topology subnet o server directive.
Con topology net30 usare ifconfig_pool_local (varia in base alla versione).
Verificare le variabili disponibili con uno script di debug.
#!/bin/bash
# Salva in un file tutte le variabili ambiente passate da OpenVPN
env | sort > /tmp/openvpn_env_$(date +%s).txt
/var/log/openvpn/openvpn.log {
daily
rotate 90
compress
delaycompress
missingok
notifempty
create 0644 openvpn openvpn
}
dnf install bind bind-utils
mkdir -p /var/log/named
chown bind:bind /var/log/named # o named:named su RHEL
chmod 755 /var/log/named
Aggiungere il blocco logging alla configurazione di BIND.
Ogni tipo di log deve essere indirizzato al proprio file dedicato.
logging {
// ── Query log ──────────────────────────────────────────────
channel query_log {
file "/var/log/named/queries.log" versions 5 size 200m;
print-time yes;
print-category no;
print-severity no;
severity info;
};
// ── RPZ log (blocco malware/minacce) ─────────────────────
channel rpz_log {
file "/var/log/named/rpz.log" versions 5 size 100m;
print-time yes;
print-category yes;
print-severity yes;
severity info;
};
// ── DDNS update log ──────────────────────────────────────
channel update_log {
file "/var/log/named/update.log" versions 5 size 50m;
print-time yes;
print-category yes;
print-severity yes;
severity info;
};
// ── Routing delle categorie ──────────────────────────────
category queries { query_log; };
category rpz { rpz_log; };
category update { update_log; };
};
print-category e print-severity vanno impostate come
indicato sopra. Il parser RPZ e update si aspetta print-time yes,
print-category yes e print-severity yes.
Per il query log, print-category no e print-severity no.
Con la configurazione sopra, BIND produce righe come:
client @0x7f8a... 172.16.0.20#12345 (example.com): query: example.com IN A +E(0)K (10.0.0.1)
| Campo estratto | Descrizione |
|---|---|
| IP client | IP sorgente della query (es. 172.16.0.20) |
| Dominio | FQDN richiesto (es. example.com) |
| Tipo query | Record type: A, AAAA, MX, CNAME, SOA, ecc. |
# Verifica sintassi configurazione named-checkconf # Ricarica configurazione rndc reload # Verifica che i log vengano scritti tail -f /var/log/named/queries.log
Le RPZ permettono di bloccare domini malevoli a livello DNS. La suite traccia e reportizza i blocchi per client, dominio e zona RPZ.
options {
// ... altre opzioni ...
response-policy {
zone "urlhaus-rpz";
zone "sslbl-rpz";
// Aggiungere altre zone RPZ a piacere
};
};
// URLhaus - malware URLs zone "urlhaus-rpz" { type master; file "/etc/bind/rpz/urlhaus-rpz.db"; allow-query { none; }; }; // SSL Blacklist - certificati malevoli zone "sslbl-rpz" { type master; file "/etc/bind/rpz/sslbl-rpz.db"; allow-query { none; }; };
Quando BIND blocca una query via RPZ, scrive una riga nel seguente formato:
27-Mar-2026 10:22:45.123 rpz: info: client @0x... 172.16.0.20#12345 (malware.com): rpz QNAME NXDOMAIN rewrite malware.com/A/IN via malware.com.urlhaus-rpz
| Campo estratto | Descrizione |
|---|---|
| Timestamp | DD-Mon-YYYY HH:MM:SS |
| IP client | IP del client bloccato |
| Dominio | Dominio bloccato |
| Azione | Tipo risposta RPZ (NXDOMAIN, NODATA, ecc.) |
| Zona RPZ | Nome della zona che ha causato il blocco |
| Feed | Fonte | Descrizione |
|---|---|---|
urlhaus-rpz | abuse.ch | Database URL malware |
sslbl-rpz | abuse.ch | Certificati SSL associati a botnet |
coinblocker.srv | ZeroDot1 | Cryptominer blocking |
drop.ip.dtq | Spamhaus | DShield DROP list |
rndc reload nomezona.
Il DDNS automatico registra un record A per ogni utente VPN che si connette, consentendo
la risoluzione username.ovpn.example.com → IP VPN. La suite traccia
le operazioni di aggiunta/rimozione nel report.
# Genera una chiave HMAC-SHA256
tsig-keygen -a hmac-sha256 sysadmin.ovpn.example.com > /etc/bind/keys/ddns.key
Contenuto generato (esempio):
key "sysadmin.ovpn.example.com" {
algorithm hmac-sha256;
secret "85ZWDQUBBqrKBhC1BN+wHDM7rYBrPiwvhADgTAvVqwQ=";
};
// Includi la chiave TSIG include "/etc/bind/keys/ddns.key"; // Zona DDNS per utenti VPN zone "ovpn.example.com" { type master; file "/var/lib/bind/ovpn.example.com.zone"; allow-update { key "sysadmin.ovpn.example.com"; }; journal "/var/lib/bind/ovpn.example.com.zone.jnl"; };
Estendere gli script di connect/disconnect per aggiornare anche il DNS:
#!/bin/bash LOGFILE="/var/log/openvpn/openvpn.log" TIMESTAMP=$(date '+%Y-%m-%d %H:%M:%S') # Log evento echo "${TIMESTAMP} ${common_name} ${ifconfig_pool_remote_ip} ${trusted_ip} CONNECT" >> "${LOGFILE}" # Aggiornamento DDNS DDNS_KEY="hmac-sha256:sysadmin.ovpn.example.com:CHIAVE_BASE64_QUI" DNS_ZONE="ovpn.example.com" FQDN="${common_name}.${DNS_ZONE}." TTL=300 nsupdate -y "${DDNS_KEY}" <<NSEOF server 127.0.0.1 zone ${DNS_ZONE} update delete ${FQDN} A update add ${FQDN} ${TTL} A ${ifconfig_pool_remote_ip} send NSEOF
#!/bin/bash LOGFILE="/var/log/openvpn/openvpn.log" TIMESTAMP=$(date '+%Y-%m-%d %H:%M:%S') # Log evento echo "${TIMESTAMP} ${common_name} ${ifconfig_pool_remote_ip} ${trusted_ip} DISCONNECT" >> "${LOGFILE}" # Rimozione record DDNS DDNS_KEY="hmac-sha256:sysadmin.ovpn.example.com:CHIAVE_BASE64_QUI" DNS_ZONE="ovpn.example.com" FQDN="${common_name}.${DNS_ZONE}." nsupdate -y "${DDNS_KEY}" <<NSEOF server 127.0.0.1 zone ${DNS_ZONE} update delete ${FQDN} A send NSEOF
BIND registra le operazioni DDNS nel seguente formato:
27-Mar-2026 10:22:45.123 update: info: client @0x... 172.16.0.20#12345/key sysadmin: updating zone 'ovpn.example.com/IN': adding an RR at 'mario.rossi.ovpn.example.com' A 172.16.0.20
| Campo estratto | Descrizione |
|---|---|
| Timestamp | DD-Mon-YYYY HH:MM:SS |
| Chiave TSIG | Nome della chiave usata per l'autenticazione |
| Zona | Zona DNS aggiornata |
| Azione | adding (connect) o deleting (disconnect) + record |
Scarica il pacchetto .deb dalla pagina di download:
# Installa il pacchetto
sudo dpkg -i netforge-ovpn-reports_1.0.0_all.deb
Il pacchetto installa:
# File installati /opt/netforge-reports/ # Script Python e librerie /etc/netforge-reports/config.ini # Configurazione (mantenuta in upgrade) /etc/cron.d/netforge-reports # Cron automatico ogni 15 minuti /usr/local/bin/netforge-vpn-report # Comando report VPN /usr/local/bin/netforge-dns-report # Comando report DNS
scp -r config.ini vpn-report.py dns-report.py lib/ root@server:/opt/openvpn-geomap-dns/
mkdir -p /var/www/html
# L'utente che esegue gli script deve avere permessi di scrittura
# L'utente che esegue gli script deve poter leggere:
ls -la /var/log/openvpn/openvpn.log
ls -la /var/log/openvpn/openvpn-status.log
ls -la /var/log/named/queries.log
ls -la /var/log/named/rpz.log
ls -la /var/log/named/update.log
openvpn e bind (o named),
oppure impostare ACL con setfacl.
# Report VPN (output testuale) python3 vpn-report.py # Report VPN (HTML con geolocalizzazione) python3 vpn-report.py --format html # Report DNS (output testuale) python3 dns-report.py # Report DNS (HTML) python3 dns-report.py --format html
Tutti i parametri sono centralizzati in config.ini.
| Chiave | Default | Descrizione |
|---|---|---|
company_name | Acme Corp | Nome azienda nell'header |
company_subtitle | Network Operations | Sottotitolo (reparto/divisione) |
logo_url | vuoto | URL o path del logo (vuoto = nessuno) |
server_name | ovpn.example.com | Nome server nei report |
| Chiave | Default | Descrizione |
|---|---|---|
ovpn_events_log | /var/log/openvpn/openvpn.log | Log eventi VPN |
ovpn_status_log | /var/log/openvpn/openvpn-status.log | File status OpenVPN |
dns_log_dir | /var/log/named | Directory log BIND |
html_output_dir | /var/www/html | Directory output HTML |
| Chiave | Default | Descrizione |
|---|---|---|
vpn_report_file | ovpn-geomap.html | Nome file report VPN |
dns_report_file | dns-report.html | Nome file report DNS |
cross_link | true | Link navigazione tra report |
| Chiave | Default | Descrizione |
|---|---|---|
days | vuoto | Filtra ultimi N giorni |
since | vuoto | Filtra da data YYYY-MM-DD |
user | vuoto | Filtra per utente CN |
exclude_users | vuoto | Escludi utenti (virgola-separati) |
| Chiave | Default | Descrizione |
|---|---|---|
office_ips | vuoto | IP pubblici noti (virgola-separati). Le connessioni da questi IP vengono evidenziate con warning nei report. |
| Chiave | Default | Descrizione |
|---|---|---|
api_url | http://ip-api.com/batch | Endpoint API geolocalizzazione |
batch_size | 100 | IP per richiesta (max 100) |
rate_limit_sleep | 1.5 | Pausa tra batch (secondi) |
-d, -u, --since, ecc.) hanno priorita'
rispetto a quelli di config.ini.
Per generare i report automaticamente:
# Genera report VPN HTML ogni 15 minuti */15 * * * * cd /opt/openvpn-geomap-dns && python3 vpn-report.py --format html 2>&1 | logger -t vpn-report # Genera report DNS HTML ogni 15 minuti */15 * * * * cd /opt/openvpn-geomap-dns && python3 dns-report.py --format html 2>&1 | logger -t dns-report
-d 7 al comando.
Per escludere utenti di servizio: impostare exclude_users in config.ini.
| # | Verifica | Comando |
|---|---|---|
| 1 | OpenVPN status file esiste e si aggiorna | stat /var/log/openvpn/openvpn-status.log |
| 2 | Hook connect/disconnect scrive nel log | tail -f /var/log/openvpn/openvpn.log |
| 3 | BIND query log attivo | tail -f /var/log/named/queries.log |
| 4 | RPZ log registra blocchi | dig @localhost malware-test.urlhaus-rpz e verifica in rpz.log |
| 5 | DDNS update funziona | tail -f /var/log/named/update.log durante una connessione VPN |
| 6 | Report HTML generato | python3 vpn-report.py --format html && ls -la /var/www/html/ovpn-geomap.html |
| 7 | GeoIP funziona (opzionale) | curl -s "http://ip-api.com/json/8.8.8.8" | python3 -m json.tool |
script-security 2 nel server.confjournalctl -u openvpnnamed-checkconf non dia erroricategory queries sia configurata/var/log/named/rndc reloadnsupdate sia installato (dnf install bind-utils)allow-update nella definizione della zonansupdate -y "hmac-sha256:key:secret" -v# Soluzione con ACL (non modifica proprietario)
setfacl -m u:UTENTE:r /var/log/openvpn/openvpn.log
setfacl -m u:UTENTE:r /var/log/openvpn/openvpn-status.log
setfacl -m u:UTENTE:rx /var/log/named/
setfacl -m u:UTENTE:r /var/log/named/*.log
Riepilogo completo dei formati di log attesi dalla suite, con le regex di parsing.
# Formato YYYY-MM-DD HH:MM:SS CN IP_VPN IP_PUB CONNECT|DISCONNECT # Regex ^(\d{4}-\d{2}-\d{2} \d{2}:\d{2}:\d{2})\s+(\S+)\s+(\d+\.\d+\.\d+\.\d+)\s+(\d+\.\d+\.\d+\.\d+)\s+(CONNECT|DISCONNECT)$
# Header HEADER,CLIENT_LIST,Common Name,Real Address,Virtual Address,Virtual IPv6 Address,Bytes Received,Bytes Sent,Connected Since,Connected Since (time_t),Username,Client ID,Peer ID,Data Channel Cipher # Dati CLIENT_LIST,CN,IP_PUB:PORTA,IP_VPN,...,Connected Since,... # Campi utilizzati: [0]=CN, [1]=Real Address, [2]=Virtual Address, [7]=Connected Since
# Formato client @0x... IP#PORTA (DOMINIO): query: DOMINIO IN TIPO +flags (RESOLVER) # Regex client\s+\S+\s+(\d+\.\d+\.\d+\.\d+)#\d+\s+\(([^)]+)\):\s+query:\s+\S+\s+IN\s+(\w+)
# Formato DD-Mon-YYYY HH:MM:SS.mmm rpz: info: client @0x... IP#PORTA (DOMINIO): rpz TIPO AZIONE rewrite TARGET via ZONA_RPZ # Regex ^(\d{2}-\w{3}-\d{4} \d{2}:\d{2}:\d{2})\.\d+\s+rpz:\s+\w+:\s+client\s+\S+\s+(\d+\.\d+\.\d+\.\d+)#\d+\s+\(([^)]+)\):\s+rpz\s+\w+\s+(\w+)\s+rewrite\s+\S+\s+via\s+(\S+)
# Formato DD-Mon-YYYY HH:MM:SS.mmm update: info: client @0x... IP#PORTA/key CHIAVE: updating zone 'ZONA/IN': AZIONE # Regex ^(\d{2}-\w{3}-\d{4} \d{2}:\d{2}:\d{2})\.\d+\s+update:\s+\w+:\s+client\s+\S+\s+[^/]+/key\s+(\S+):\s+updating zone '([^/]+)/IN':\s+(.+)$