OpenVPN GeoMap & DNS Report Suite

Guida di Installazione e Requisiti
v1.0 — Marzo 2026

1 Architettura del sistema

La suite genera report HTML interattivi che aggregano dati da OpenVPN e BIND DNS. Il flusso dati e' il seguente:

Modulare: la suite funziona anche con solo OpenVPN o solo BIND. Se un log non esiste o e' vuoto, la sezione corrispondente non viene generata nel report. Non serve configurare entrambi.
OpenVPN Server connect / disconnect hooks BIND DNS Server query / rpz / update logging /var/log/openvpn/ openvpn.log eventi CONNECT / DISCONNECT openvpn-status.log /var/log/named/ queries.log rpz.log update.log Report Suite Python 3 • parser regex • GeoIP /var/www/html/ ovpn-geomap.html • dns-report.html

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.

2 Requisiti di sistema

Software richiesto

ComponenteVersione minimaNote
OpenVPN2.4+Community o Access Server
BIND (named)9.11+Con supporto RPZ e DDNS
Python3.7+Libreria standard, nessun pip install
Web serverqualsiasiNginx, 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

ServizioDestinazionePortaUso
opzionale GeoIP ip-api.com 80/TCP Geolocalizzazione IP per la mappa Leaflet
Se il server non ha accesso a internet, il report VPN verra' generato senza la mappa geografica. Il resto della reportistica funziona completamente offline.

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/openvpn-geomap-dns/       # Suite (suggerito)

3 Configurazione OpenVPN

Suggerimento: con NetForge puoi generare automaticamente la configurazione completa del server OpenVPN (PKI, certificati, hook di logging, firewall) gia' compatibile con questa suite.

3.1 Status file (connessioni attive)

Il file di stato e' una funzionalita' nativa di OpenVPN. Aggiungere alla configurazione del server:

/etc/openvpn/server.conf
# 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
CampoDescrizioneEsempio
YYYY-MM-DD HH:MM:SSTimestamp ISO 86012026-03-27 10:22:45
CNCommon Name del certificatomario.rossi
IP_VPNIP assegnato nella VPN172.16.0.20
IP_PUBIP pubblico del client93.42.1.100
CONNECT|DISCONNECTTipo eventoCONNECT

Configurazione server

/etc/openvpn/server.conf
# 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

/etc/openvpn/scripts/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

/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} 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
La variabile 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)

/etc/logrotate.d/openvpn-events
/var/log/openvpn/openvpn.log {
    daily
    rotate 90
    compress
    delaycompress
    missingok
    notifempty
    create 0644 openvpn openvpn
}

4 Configurazione BIND DNS

Suggerimento: con NetForge puoi generare la configurazione BIND completa (logging, zona DDNS, chiave TSIG, zone RPZ) gia' compatibile con questa suite.

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.

/etc/bind/named.conf.local (o named.conf)
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; };

};
Le opzioni 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 estrattoDescrizione
IP clientIP sorgente della query (es. 172.16.0.20)
DominioFQDN richiesto (es. example.com)
Tipo queryRecord 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

5 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

/etc/bind/named.conf.options
options {
    // ... altre opzioni ...

    response-policy {
        zone "urlhaus-rpz";
        zone "sslbl-rpz";
        // Aggiungere altre zone RPZ a piacere
    };
};

5.2 Definire le zone RPZ

/etc/bind/named.conf.local
// 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 estrattoDescrizione
TimestampDD-Mon-YYYY HH:MM:SS
IP clientIP del client bloccato
DominioDominio bloccato
AzioneTipo risposta RPZ (NXDOMAIN, NODATA, ecc.)
Zona RPZNome della zona che ha causato il blocco

5.4 Zone RPZ compatibili (feed consigliati)

FeedFonteDescrizione
urlhaus-rpzabuse.chDatabase URL malware
sslbl-rpzabuse.chCertificati SSL associati a botnet
coinblocker.srvZeroDot1Cryptominer blocking
drop.ip.dtqSpamhausDShield DROP list
Configurare un cron per aggiornare periodicamente i file di zona RPZ dai feed upstream. Dopo l'aggiornamento, eseguire rndc reload nomezona.

6 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

/etc/bind/named.conf.local
// 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:

/etc/openvpn/scripts/connect.sh (versione completa)
#!/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
/etc/openvpn/scripts/disconnect.sh (versione completa)
#!/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 estrattoDescrizione
TimestampDD-Mon-YYYY HH:MM:SS
Chiave TSIGNome della chiave usata per l'autenticazione
ZonaZona DNS aggiornata
Azioneadding (connect) o deleting (disconnect) + record

7 Installazione della suite di reportistica

7.1 Download e installazione

Scarica il pacchetto .deb dalla pagina di download:

ovpn.netforge.it/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
Installazione manuale (senza .deb): copia i file direttamente sul server:
scp -r config.ini vpn-report.py dns-report.py lib/ root@server:/opt/openvpn-geomap-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
Se la suite gira come utente non-root, aggiungere l'utente ai gruppi openvpn e bind (o named), oppure impostare ACL con setfacl.

7.4 Primo test

# 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

8 Configurazione config.ini

Tutti i parametri sono centralizzati in config.ini.

Sezione [general]

ChiaveDefaultDescrizione
company_nameAcme CorpNome azienda nell'header
company_subtitleNetwork OperationsSottotitolo (reparto/divisione)
logo_urlvuotoURL o path del logo (vuoto = nessuno)
server_nameovpn.example.comNome server nei report

Sezione [paths]

ChiaveDefaultDescrizione
ovpn_events_log/var/log/openvpn/openvpn.logLog eventi VPN
ovpn_status_log/var/log/openvpn/openvpn-status.logFile status OpenVPN
dns_log_dir/var/log/namedDirectory log BIND
html_output_dir/var/www/htmlDirectory output HTML

Sezione [html]

ChiaveDefaultDescrizione
vpn_report_fileovpn-geomap.htmlNome file report VPN
dns_report_filedns-report.htmlNome file report DNS
cross_linktrueLink navigazione tra report

Sezione [filter]

ChiaveDefaultDescrizione
daysvuotoFiltra ultimi N giorni
sincevuotoFiltra da data YYYY-MM-DD
uservuotoFiltra per utente CN
exclude_usersvuotoEscludi utenti (virgola-separati)

Sezione [alerts]

ChiaveDefaultDescrizione
office_ipsvuotoIP pubblici noti (virgola-separati). Le connessioni da questi IP vengono evidenziate con warning nei report.

Sezione [geoip]

ChiaveDefaultDescrizione
api_urlhttp://ip-api.com/batchEndpoint API geolocalizzazione
batch_size100IP per richiesta (max 100)
rate_limit_sleep1.5Pausa tra batch (secondi)
I parametri CLI (-d, -u, --since, ecc.) hanno priorita' rispetto a quelli di config.ini.

9 Automazione con cron

Per generare i report automaticamente:

crontab -e
# 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
Per report filtrati sugli ultimi 7 giorni, aggiungere -d 7 al comando. Per escludere utenti di servizio: impostare exclude_users in config.ini.

10 Verifica e troubleshooting

Checklist di verifica

#VerificaComando
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

Problemi comuni

Log eventi VPN vuoto

Query log BIND vuoto

DDNS non aggiorna i record

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+(.+)$