Realizzare semplice log collector distribuito in rete con C++-Python/Grafana
Requisiti
Realizzare una applicazione client-server allo scopo di monitorare nodi di rete e raccogliere in modo centralizzato segnalazioni e log.
Requisiti Server
Funzionali
- Il sistema deve permettere il monitoraggio di eventi e configurazioni di nodi (eventi da rilevare) tramite un singolo centro.
- Il server deve rappresentare il controller dell’intera rete.
- Le informazioni ricevute dagli agent devono essere aggiornate periodicamente (polling)
-
- Opzionale: La comunicazione deve essere di tipo real time, continua.
Non Funzionali
- La comunicazione con gli agent deve avvenire tramite GPRC.
-
- Opzionale: Il sistema deve interfacciarsi con i servizi Grafana (https://grafana.com/)
- Il server deve poter essere adattato facilmente ad altre piattaforme o librerie per la gestione dei log, è ragionevole l’uso di un linguaggio ad alto livello come Python.
Requisiti Client Agent
Funzionali
- L’agent deve monitorare gli eventi di rete sul nodo, come malfunzionamenti.
- L’agent deve essere in grado di fornire la configurazione attuale di rete.
- L’agent deve rendere disponibili le informazioni del nodo al server.
Non Funzionali
- Dato che i nodi da monitorare potrebbero essere semplici switch di rete openflow, è richiesto che l’agent abbia minime dipendenze. Possibilmente il programma deve essere scritto in C++ ed essere distribuito precompilato.
- Per lo stesso motivo del requisito precedente, l’agent non deve impattare sulle prestazioni del nodo in cui è installato.
- L’agent deve comunicare con il nodo server tramite GRPC.
-
- Opzionale: L’agent deve essere avviato durante il boot di OSHI.
Funzionalità di monitoraggio eventi
Notifica degli eventi (dagli Agent al Server)
Come da requisito, gli Agent sono in grado di rilevare eventi di rete (malfunzionamenti o anomalie) e notificarli al Server.
Per evento, genericamente, si intende la realizzazione di una condizione booleana su un parametro da analizzare (ad esempio in caso di attacco arp poisoning la grandezza della tabella di arp sarebbe più grande dei valori di norma).
Se il sistema utilizzasse soluzioni ad-hoc e hard-coded per tutti gli eventi da monitorare, sarebbe necessario produrre diversi moduli per ogni parametro da monitorare, in più l’Agente dovrebbe essere di volta in volta ricompilato in un caso di aggiunta di un tipo di evento da analizzare.
Il nostro sistema invece offre una singola API versatile per registrare sull’Agente un rilevatore di eventi. In questo modo, oltre a ridurre la complessità dell’Agente, permettiamo al server di registrare listener per nuovi eventi, senza la necessità di ricompilare o riavviare gli Agent. L’Agent deve solo fornire la struttura necessaria per permettere ai listener di eventi, richiesti dal server, di comunicare con lo stesso.
Struttura dell’Evento
L’evento è stato modellato con i seguenti attributi:
- id = id del tipo di evento, id che viene associato al listener dell’Agent.
- intervallo di polling = intervallo di tempo in cui verificare la condizione dell’evento.
- comando = script bash da eseguire sul client
- comparatore = serve per confrontare il risultato del comando con il risultato atteso.
- risultato atteso = ciò che ci si aspetta ritorni il comando per non considerare l’evento come innescato, può essere costituito da una soglia o un valore (sia decimale che stringa).
- assoluto/relativo = il valore deve essere considerato come assoluto o come differenza del valore all’iterazione precedente (nel caso di interi). Ad esempio la condizione di evento sui pacchetti persi è da intendersi come relativa, data dal numero di pacchetti ad iterazione persi, e non in modo assoluto (in quanto il comando restituisce il numero assoluto di pacchetti persi dall’avvio).
- risultato decimale = booleano che identifica il tipo del risultato del comando (stringa o decimale) in modo da fare o meno la conversione prima del confronto.
Comparatore
Viene definito un mapping id : tipo di comparatore (es. 1 : =), come standard sia per il client che per il server. A sinistra abbiamo un id e a destra un operatore booleano. Si fa in modo che il comando eseguito dal client ritorni un unico risultato per fare in modo che quest’ultimo venga confrontato con il risultato atteso. Per impedire un mismatch tra il risultato del comando eseguito dal client e il reale tipo su cui verrà effettuato il confronto si è pensato di differenziare l’operazione di uguaglianza per stringa e intero.
Tabella dei comparatori
id | comparatore | descrizione |
---|---|---|
1 | > | Se il valore di ritorno del comando è maggiore stretto del valore atteso, scatta l’evento |
2 | >= | Se il valore di ritorno del comando è maggiore del valore atteso, scatta l’evento |
3 | < | Se il valore di ritorno del comando è minore stretto del valore atteso, scatta l’evento |
4 | <= | Se il valore di ritorno del comando è minore del valore atteso, scatta l’evento |
5 | == | Se il valore di ritorno del comando è uguale al valore atteso, scatta l’evento |
6 | != | Se il valore di ritorno del comando è diverso dal valore atteso, scatta l’evento |
Eventi
id | comando | intervallo | comparatore | tipo | risultato atteso | descrizione |
---|---|---|---|---|---|---|
1 | nstat –zero PIPE grep ‘TcpOutRsts’ PIPE awk ‘{ print $2 }’ | 1s | > | Assoluto | 100 | Se il numero di pacchetti TCP contenenti il flag Reset (TcpOutRsts) è troppo elevato, l’evento viene segnalato (un port scanning può esserne la causa) |
2 | netstat -n -q -p 2>/dev/null PIPE grep SYN_REC PIPE wc -l | 10s | > | Relativo | 300 | Se il numero di connessioni tcp nella fase di Syn Rec è troppo elevato, potrebbe essere in atto un attacco dos o ddos |
3 | who PIPE wc -l | 10s | > | Relativo | 0 | Se il numero di utenti connessi cambia, l’evento viene registrato |
0 | - | - | - | - | - | Errore nell’esecuzione di un comando |
Architettura implementazione
Api dei listener
- Il server consente di aggiungere dei listener ai client per il monitoraggio di eventi.
- Il server consente di rimuovere dei listener ai client per il monitoraggio di eventi.
- Il server consente di impostare una connessione di tipo stream con i client per la ricezione delle notifiche.
Server
Il server mantiene una connessione con ogni Agent, con la quale riceve (in polling o in tempo reale) notifiche di eventi monitorati dall’agent. La comunicazione con gli Agent viene iniziata dal Server in modo tale che sia immediata la constatazione di irraggiungibilità del nodo.
Una volta ricevuti eventi dagli Agent, il Server (utilizzando un thread apposito in modo da non rallentare le operazioni di ricezione di eventi) salva gli eventi della base di dati o sul servizio scelto per la permanenza dei dati. Ogni evento viene contrassegnato principalmente in base al tipo di listener che lo ha rilevato, il valore restituito dal command e l’orario.
Agent
Il client di base ha un thread dedito alla trasmissione verso il Server degli eventi monitorati. Una volta che il Server ha invocato l’aggiunta di un listener per un evento tramite grpc, viene creato un thread sull’agent che ha come scopo il monitoraggio dell’evento scelto. Una volta rilevato un evento, questo viene condiviso tramite una struttura dati condivisa (coda) con il thread che gestisce la trasmissione di eventi. Il server ha inoltre la possibilità di “spegnere” eventuali listener sui client.
Sicurezza
Dato che gli Agent permettono l’esecuzione di comandi da remoto, il rischio di uno sfruttamento di tali API a scopo malevolo è da non sottovalutare. Per tale scopo è stata introdotta un’autenticazione di grpc basata su certificati e chiavi private. Per creare dei certificati utilizzare lo script presente dentro /auth/generate_auth.sh oppure con l’installazione dell’ambiente per il client Agent. Il sistema necessita di una chiave/certificato per gli Agent, e una chiave/certificato per il server. Per semplicità si è scelto in via esemplificativa di impostare il sistema per assegnare ad ogni client la stessa chiave privata/certificato, in modo da semplificare le operazioni di setup. Il sistema è pronto però è predisposto all’utilizzo di chiavi diverse per ogni client.
Installazione Server in Python
Configurazione Ambiente Python
|
|
installa tramite pip grpc e protobuf.
HelloWorld
Prima di avviare l’HelloWorld in Python, è necessario avviare l’helloworld in cpp agent-cpp.
|
|
In questo modo i file .proto verranno compilati anche per python. Infine:
|
|
Il server, dalla vista di grpc, assumerà infatti il ruolo di client (in quanto richiede di eseguire all’agente dei comandi).
Se l’helloworld ha funzionato in entrambe le sue componenti, verrà restituito un messaggio di “Hello”.
Esecuzione Server
|
|
Compilerà i file .proto del progetto e avvierà il server python.
Tutorial e Riferimenti
E' importante seguire la guida grpc: https://grpc.io/docs/languages/python/basics/
Requisiti Server
Funzionali
- Il sistema deve permettere il monitoraggio di eventi e configurazioni di nodi tramite un singolo centro.
- Il server deve rappresentare il controller dell’intera rete.
- Le informazioni ricevute dagli agent devono essere aggiornate periodicamente (polling)
-
- Opzionale: La comunicazione deve essere di tipo real time, continua.
Non Funzionali
- La comunicazione con gli agent deve avvenire tramite GPRC.
-
- Opzionale: Il sistema deve interfacciarsi con i servizi Grafana https://grafana.com/
- Il server deve poter essere adattato facilmente ad altre piattaforme o librerie per la gestione dei log, è ragionevole l’uso di un linguaggio ad alto livello come Python.
Installazione dei client C++
Setup
Configurazione Ambiente per Client C++
Questo passaggio è fondamentale per riuscire a compilare codice in cpp. La compilazione di grpc impiegherà molto tempo (circa 10 minuti su un i7 7700K).
|
|
Eseguirà l’installazione dei pacchetti necessari, il clone della repository gprc in gprc/gprc/, e compilerà queste librerie. Inoltre installa protobuf.
Setup ambiente per sistemi deprecati
In caso di sistemi vecchi (con gcc < 4.6 ad esempio) è obbligatorio l’utilizzo dell’apposito script di setup:
|
|
Questa modalità è necessaria per la macchina virtuale Oshi VM 8.
Compilazione sorgente
Dopo aver configurato l’ambiente per compilare in cpp
|
|
HelloWorld
Per verificare che il sistema sia operativo, provare a eseguire l’helloworld di grpc.
|
|
L’agente, dalla vista di grpc, assumerà infatti il ruolo di server (in quanto espone una porta di rete e esegue i comandi richiesti dal client, nel nostro caso il server python).
Una volta che il server è partito, avviando il corrispettivo helloworld nelle cartelle server del repository si vedrà il programma python restituire la risposta all’HelloWorld inviato dal programma in CPP.
Esecuzione Agente
|
|
Tutorial e Riferimenti
E' importante leggere https://grpc.io/docs/languages/cpp/quickstart/
Requisiti Client Agent
Funzionali
- L’agent deve monitorare gli eventi di rete sul nodo, come malfunzionamenti.
- L’agent deve essere in grado di fornire la configurazione attuale di rete.
- L’agent deve rendere disponibili le informazioni del nodo al server.
Non Funzionali
- Dato che i nodi da monitorare potrebbero essere semplici switch di rete openflow, è richiesto che l’agent abbia minime dipendenze. Possibilmente il programma deve essere scritto in C++ ed essere distribuito precompilato.
- Per lo stesso motivo del requisito precedente, l’agent non deve impattare sulle prestazioni del nodo in cui è installato.
- L’agent deve comunicare con il nodo server tramite GRPC.
-
- Opzionale: L’agent deve essere avviato durante il boot di OSHI.
Autori
A.F. e A.G.
Repository
La repository con il codice sorgente completo è pubblicamente disponibile su GitLab: