Contenuti

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.
  • 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

/it/simple-log-collector-cpp/overviewNet.png

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

1
./setup.sh

installa tramite pip grpc e protobuf.

HelloWorld

Prima di avviare l’HelloWorld in Python, è necessario avviare l’helloworld in cpp agent-cpp.

1
2
cd netserver
./compileproto.sh

In questo modo i file .proto verranno compilati anche per python. Infine:

1
python greeter_client

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

1
./run.sh

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.
  • 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).

1
./setup_environment.sh

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:

1
./setup_old_environment.sh

Questa modalità è necessaria per la macchina virtuale Oshi VM 8.

Compilazione sorgente

Dopo aver configurato l’ambiente per compilare in cpp

1
2
cd src
make

HelloWorld

Per verificare che il sistema sia operativo, provare a eseguire l’helloworld di grpc.

1
2
cd src
./greeter_server

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

1
2
cd src
./netagent

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:

https://gitlab.com/Ablablab/grpc/