01 Architettura del sistema
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.
02 Requisiti di sistema
Software richiesto
| 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 |
Dipendenze Python
La suite utilizza esclusivamente la libreria standard di Python 3.
Non e' richiesto nessun pip install.
Connettivita' di rete
| Servizio | Destinazione | Porta | Uso |
|---|---|---|---|
| opzionale GeoIP | geo.netforge.it |
443/TCP | Geolocalizzazione IP per la mappa |
Struttura directory
/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/netforge-reports/ # Suite (pacchetto .deb) /etc/netforge-reports/ # config.ini
03 Configurazione OpenVPN
3.1 Status file (connessioni attive)
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,...
3.2 Log eventi CONNECT/DISCONNECT (custom)
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.
Formato richiesto
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 |
Configurazione server
# Hook script per logging eventi
script-security 2
client-connect /etc/openvpn/scripts/connect.sh
client-disconnect /etc/openvpn/scripts/disconnect.sh
Script connect.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}"
Script 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} DISCONNECT" >> "${LOGFILE}"
Impostazione permessi
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.
Script di debug variabili (opzionale)
#!/bin/bash
# Salva in un file tutte le variabili ambiente passate da OpenVPN
env | sort > /tmp/openvpn_env_$(date +%s).txt
3.3 Logrotate (raccomandato)
/var/log/openvpn/openvpn.log {
daily
rotate 90
compress
delaycompress
missingok
notifempty
create 0644 openvpn openvpn
}
04 Configurazione BIND DNS
4.1 Installazione BIND
dnf install bind bind-utils
4.2 Directory log
mkdir -p /var/log/named
chown bind:bind /var/log/named # o named:named su RHEL
chmod 755 /var/log/named
4.3 Configurazione logging
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.
4.4 Formato log atteso per le query
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. |
4.5 Verifica
# Verifica sintassi configurazione named-checkconf # Ricarica configurazione rndc reload # Verifica che i log vengano scritti tail -f /var/log/named/queries.log
05 Configurazione RPZ (Response Policy Zone)
Le RPZ permettono di bloccare domini malevoli a livello DNS. La suite traccia e reportizza i blocchi per client, dominio e zona RPZ.
5.1 Abilitare RPZ in named.conf
options {
// ... altre opzioni ...
response-policy {
zone "urlhaus-rpz";
zone "sslbl-rpz";
// Aggiungere altre zone RPZ a piacere
};
};
5.2 Definire le zone RPZ
// 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; }; };
5.3 Formato log RPZ
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 |
5.4 Zone RPZ compatibili (feed consigliati)
| 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.
06 Configurazione DDNS (Dynamic DNS)
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.
6.1 Creare la chiave TSIG
# 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=";
};
6.2 Configurare la zona per aggiornamenti dinamici
// 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"; };
6.3 Hook OpenVPN per DDNS
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
6.4 Formato log DDNS
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 |
07 Installazione della suite di reportistica
7.1 Download e installazione
Metodo consigliato — repo APT NetForge
Installa e ricevi aggiornamenti automatici tramite il repository APT di NetForge:
# 1. Aggiungi la chiave GPG del repo curl -fsSL https://apt.netforge.it/netforge.gpg.key | sudo gpg --dearmor --yes -o /usr/share/keyrings/netforge.gpg # 2. Aggiungi il repository echo "deb [signed-by=/usr/share/keyrings/netforge.gpg] https://apt.netforge.it/free stable main" \ | sudo tee /etc/apt/sources.list.d/netforge-free.list # 3. Installa sudo apt update sudo apt install netforge-ovpn-reports
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
7.2 Preparare la directory output
mkdir -p /var/www/html
# L'utente che esegue gli script deve avere permessi di scrittura
7.3 Verificare i permessi di lettura sui log
# 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.
7.4 Primo test
# Report VPN (output testuale) netforge-vpn-report # Report VPN (HTML con geolocalizzazione) netforge-vpn-report --format html # Report DNS (output testuale) netforge-dns-report # Report DNS (HTML) netforge-dns-report --format html
Da sorgente (senza pacchetto) gli stessi comandi sono python3 vpn-report.py e python3 dns-report.py, lanciati dalla directory della suite.
08 Configurazione config.ini
Tutti i parametri sono centralizzati in config.ini.
Sezione [general]
| 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 |
Sezione [paths]
| 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 |
Sezione [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 |
Sezione [filter]
| 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) |
Sezione [alerts]
| Chiave | Default | Descrizione |
|---|---|---|
office_ips | vuoto | IP pubblici noti (virgola-separati). Le connessioni da questi IP vengono evidenziate con warning nei report. |
Sezione [geoip]
| Chiave | Default | Descrizione |
|---|---|---|
api_url | https://geo.netforge.it | Endpoint API geolocalizzazione |
batch_size | 200 | IP per richiesta (max 200) |
Opzioni da riga di comando
Valgono per netforge-vpn-report e netforge-dns-report.
| Flag | Descrizione | Default |
|---|---|---|
-f, --format | text | html | text |
-o, --output | File di output | stdout (text), config.ini (html) |
-d, --days | Solo gli ultimi N giorni | tutto il log |
--since | Da data YYYY-MM-DD | tutto il log |
-u, --user | Filtra per utente (CN) | tutti |
-c, --config | Path config.ini alternativo | /etc/netforge-reports/config.ini |
-d, -u, --since, ecc.) hanno priorita'
rispetto a quelli di config.ini.
09 Automazione con cron
Il pacchetto installa già il cron: i report vengono rigenerati ogni 15 minuti.
*/15 * * * * root /usr/local/bin/netforge-vpn-report --format html > /dev/null 2>&1 */15 * * * * root /usr/local/bin/netforge-dns-report --format html > /dev/null 2>&1
Installazione da sorgente: aggiungi le righe a mano.
# 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.
10 Verifica e troubleshooting
Checklist di verifica
| # | 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 | netforge-vpn-report --format html && ls -la /var/www/html/ovpn-geomap.html |
| 7 | GeoIP funziona (opzionale) | curl -s "https://geo.netforge.it/geo/8.8.8.8" | python3 -m json.tool |
Problemi comuni
Log eventi VPN vuoto
- Verificare
script-security 2nel server.conf - Verificare permessi di esecuzione sugli script hook
- Verificare che l'utente OpenVPN possa scrivere nel file di log
- Controllare il log di sistema:
journalctl -u openvpn
Query log BIND vuoto
- Verificare
named-checkconfnon dia errori - Verificare che la
category queriessia configurata - Verificare permessi su
/var/log/named/ - Dopo modifiche:
rndc reload
DDNS non aggiorna i record
- Verificare che
nsupdatesia installato (dnf install bind-utils) - Verificare la chiave TSIG (deve corrispondere tra named.conf e script)
- Verificare
allow-updatenella definizione della zona - Test manuale:
nsupdate -y "hmac-sha256:key:secret" -v
Errore permessi sui log
# 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
11 Riferimento formati log
Riepilogo completo dei formati di log attesi dalla suite, con le regex di parsing.
OpenVPN — Log eventi
# 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)$
OpenVPN — Status file (v2)
# 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
BIND — Query log
# 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+)
BIND — RPZ log
# 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+)
BIND — DDNS update log
# 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+(.+)$
OpenVPN GeoMap