Premessa

Abbiamo visto nello scorso articolo che cosa vuol dire sviluppare sofware su architettura scalabile (orizzontalmente oltre che verticalmente) e fault tolerant, e abbiamo cominciato a conoscere quali sono le problematiche che un architetto software si troverà ad affrontare quando cercherà di migrare o realizzare applicazioni che non risiedono più su singoli server. Abbiamo già parlato di storage condiviso, e in questo articolo invece ci concentreremo su un’altra problematica che sta dietro a domande di questo tipo:

come facciamo ad essere sicuri che un utente che ha fatto il login su una macchina, continui ad essere riconosciuto dalla nostra applicazione nel momento in cui non possiamo essere certi che le sue interazioni successive continuino ad intercorrere con la stessa macchina?

O ancora:

come facciamo a conservare le informazioni di navigazione, come i prodotti inseriti in un carrello, se le richieste alla nostra applicazione possono giungere in momenti diversi a server diversi?

Queste domande sono perfettamente legittime e colgono una problematica reale: quando utilizziamo dei webservices, quando navighiamo su un sito, quando usiamo un’applicazione mobile o un’applicazione web, ci sono delle informazioni essenziali (es. chi siamo, che preferenze abbiamo salvato, quale menu dobbiamo vedere, quali prodotti abbiamo in carrello) che il server (in questo caso molti server) devono conoscere per poter funzionare a dovere. Queste informazioni definiscono in un dato istante ciò che per un’applicazione è uno “stato” o “state“. Lo stato, in parole spicciole, è rappresentato dall’insieme dei valori che hanno un certo set di variabili nell’istante in cui viene effettuata una richiesta.

Stateful vs Stateless

Cerchiamo di comprendere bene la differenza tra questi due concetti con un esempio che non ha nulla a che vedere con il mondo tecnologico.

Un uomo si reca in un ufficio di collocamento, si registra dando i suoi dati e la sua carta di identità e indica quali sono le sue abilità e le sue competenze, che vengono verificate con controlli incrociati e infine salvati.

Nei giorni successivi, torna nello stesso ufficio e mostrando la sua carta di identità, chiede se ci sono lavori per lui. L’impiegato è in grado di dargli le informazioni che cerca senza chiedere nulla e senza dover nuovamente controllare la veridicità delle competenze del candidato.

Questo è un sistema stateful: l’ufficio ha salvato tutte le informazioni di cui ha bisogno e il candidato deve solo mostrare un documento di identità.

Ma cosa succede se il disoccupato di turno attraversa la strada e si reca in un ufficio di collocamento diverso? Non basterà più mostrare la propria carta di identità: occorrerà rifare la trafila della registrazione.

Decide quindi di recarsi presso un altro tipo di uffici: in questi nuovi uffici il candidato presenta un curriculum ed una carta di identità e dopo una serie di controlli sul curriculum che portano via del tempo, l’ufficio gli da le informazioni sui lavori. In qualunque tipo di ufficio di questo tipo lui si rechi, non occorre neppure registrarsi, visto che si porta il curriculum.

Questo secondo sistema viene definito stateless: l’ufficio lavora senza sapere cosa sappiamo fare, apprendendolo all’istante da un curriculum presentato.

E’ chiaro che se portiamo quanto appena visto ad esempio sulle logiche di un sito WEB, se ci troviamo a salvare le informazioni di SESSIONE (lo stato del sistema) in un file sul server a cui l’applicazione accede attraverso un identificativo (un cookie di sessione univoco e temporaneo presentato dal browser) allora il nostro sito è un sistema stateful. Se al contrario tutte le informazioni di sessione le passiamo attraverso ad esempio l’url (ad esempio se stiamo visitando la pagina www.ilmiopetshop.it?animali=gatti&categoria=ciotole&pagina=1) il nostro server può assolvere il suo compito senza accedere ad altre informazioni e si tratta di un sistema stateless.

Ovviamente… è impossibile passare sulla URL delle informazioni sensibili (come ad esempio la password), nè possiamo pensare di nasconderle semplicemente in un cookie solo perchè non si vede. Analizziamo allora quali sono i pro e i contro dei due tipi di applicazione.

Stateful vs Stateless
ConfrontoSTATEFULSTATELESS
Modalità di salvataggio di defaultLe informazioni di stato sono salvate solitamente in file di testo che vengono periodicamente puliti sul serverLe informazioni di stato vengono fornite dal client sotto forma di url, cookie, post e altro
SicurezzaSicurezza elevata, le informazioni sono salvate lato server. Occorre preoccuparsi di accedervi con identificativi di sessione sicuri e temporaneiLa sicurezza è un problema che va affrontato con logiche specifiche. Salvare dati sui client è intrinsecamente insicuro.
RAM consumata per singola richiestaAlta. La quantità di informazioni per utente può crescere enormemente nel tempo ed ogni singola richiesta carica TUTTE le variabili presenti in sessione anche se non servono per il soddisfacimento della specifica richiestaSolitamente bassa. Il server deve però effettuare delle routine di controllo aggiuntive
ScalabilitàNon scalabili di default. Il problema è aggirabile ma presenta le stesse difficoltà di scalabilità dei dati di un databasePerfettamente scalabili. Le informazioni necessarie sono sui client
Framework / CMS conosciutiMagento 1 e 2, Joomla, DrupalWordpress

Dunque… se andiamo a leggere la tabella cominciamo a capire che la faccenda è seria:

quasi tutti i framework conosciuti fanno uso di sessioni (sono stateful), come facciamo allora ad installarli su un architettura scalabile se le sessioni risiedono su un singolo server?

e se invece stiamo sviluppando un framework noi e decidiamo di evitare di usare le sessioni per sviluppare un prodotto intrinsecamente leggero, scalabile all’infinito e stateless,

come facciamo a gestire in modo sicuro le informazioni di stato che per forza di cose saranno salvate lato client?

Affrontiamo un argomento alla volta.

SESSIONI SU ARCHITETTURE DISTRIBUITE

Si, le sessioni al giorno d’oggi sono considerate il male assoluto dagli architetti, ma stiamo usando un framework che ne fa uso e non vogliamo rinunciare alla sicurezza che ci da un ambiente distribuito rispetto ad uno monolitico. Vediamo dunque che possibilità abbiamo:

SALVIAMO LE SESSIONI SU STORAGE CONDIVISO

Questa può sembrare una soluzione semplice e veloce: dobbiamo usare uno storage condiviso per immagini e video, perché non sfruttarlo anche per salvare i file di sessione? Io non lo farei perché ci sono alternative migliori: lo storage condiviso

  • Costa molto
  • Solitamente l’accesso alle risorse non è rapido come lo sarebbe per un volume locale
  • I file di sessione possono facilmente diventare milioni e gli storage condivisi non sono performanti nell’accesso a milioni di piccoli file
SALVIAMO LE SESSIONI SU DATABASE

In PHP, così come in altri linguaggi, è possibile salvare le sessioni con poco sforzo sul Database. Le limitazioni di questo metodo sono le stesse che riguardano la scalabilità dei Database stessi, che affronteremo in un articolo successivo.

SALVIAMO LE SESSIONI SU MEMCACHED / REDIS

In PHP è abbastanza semplice configurare il webserver per utilizzare ALTRI SERVER come gestori delle informazioni di sessione (session handlers). Ovviamente per mantenere la nostra architettura fault tolerant / highly available dobbiamo assicurarci di ridondare anche queste istanze. La scalabilità di questo tipo di istanze merita un articolo a parte, ma è una cosa fattibile senza troppe difficoltà sui principali fornitori di istanze cloud. Memcached e Redis sono entrambi dei sistemi di storage di informazioni (di tipo chiave:valore) in RAM quindi l’accesso a tali informazioni è davvero rapido.

E’ possibile utilizzare praticamente qualunque tipo di database noSQL che ci viene in mente (che lavorano molto meglio in clusters rispetto ai server SQL) per salvare le sessioni: se non è supportato nativamente da PHP è possibile scrivere con poche righe di codice un handler customizzato.

CONFIGURIAMO IL LOAD BALANCER PER USARE LE STICKY SESSIONS

La quasi totalità dei Load Balancers ha la possibilità di “bindare” (associare) un utente ad una macchina attraverso il cookie di sessione: possiamo praticamente fare in modo che una volta che è presente una sessione, l’utente venga sempre redirezionato verso la stessa macchina. Questa è una soluzione di compromesso che bisogna configurare attentamente e “cum granu salis” e non è adatta a tutti i casi. Occorre che la durata dell’associazione ricalchi quella della durata della sessione e comunque si perdono alcuni dei vantaggi delle architetture ridondate. Che succede se una delle istanze diventa indisponibile? Tutti gli utenti associati all’istanza avranno o un’interruzione del servizio o nel migliore dei casi verranno redirezionati su un server che non ha le informazioni di sessione.

Le informazioni di stato in applicazioni stateless

Abbiamo la possibilità di creare da zero il nostro framework e decidiamo di fare a meno delle sessioni.

Come facciamo a salvare le informazioni di sessione in maniera sicura?

Non esiste una risposta univoca a questa domanda, in primo luogo perchè la parola “sicura” è molto ambigua. Definirei il passaggio dei dati via url TOTALMENTE INSICURO, così come il passaggio dei dati via headers / cookies altrettanto insicuro. Crittografare i dati presenti nel cookie con una parola chiave presente solo sul server, può sembrare sicuro… ma non è sicuro al 100% perchè è soggetto ad attacchi di tipo brute force.

Ad ogni modo, non svicoliamo:

se i dati non vengono salvati sul server, vengono in qualche modo salvati sul client e poi inviati al server.

A qualcuno si stanno drizzando i peli sulla schiena. Tranquilli. Esistono sistemi (tanti) per farlo in maniera più o meno sicura. Se usate framework per sviluppare le vostre applicazioni come Laravel o Symphony o Zend troverete molteplici moduli che vi permettono per esempio di usare JWT.

Cos’è JWT?

JWT sta per JSON Web Token. JSON sappiamo cos’è: è un modo per salvare informazioni complesse (come un oggetto) in forma testuale. Volete un esempio?

{ user_id: 1, username: 'pippo', password: 'pluto', carrello: {prodotti: [{idp: 1, qta: 1}, {idp:2, qta: 6}]}}

potrebbe rappresentare lo stato di un carrello di un ecommerce con 2 prodotti di un utente loggato al sistema. Possiamo usarlo per i nostri scopi così com’è? NO. Ci sono informazioni sensibili che non possono essere salvate ne transitare in chiaro. E’ qui che viene in aiuto JWT. I token JWT sono sempre JSON ma sono firmati e crittografati per cui alcune informazioni posso restare in chiaro (es. il contenuto del carrello che il client può usare per popolare l’interfaccia grafica senza richiederlo al server) altre invece vengono crittografate (e solo il server può decrittografarle perché la chiave è solo sul server). Il fatto che sia inoltre presente una scadenza e soprattutto una FIRMA fa in modo che tale token abbia una validità limitata (riduce il rischio di session hijacking e di attacchi brute force alla chiave) e che il server sia sicuro – attraverso la firma – che lo stesso token non sia stato alterato. Il token viene passato dopo essere stato encodato in Base64(attenzione: è molto diverso da crittografare perché è una procedura che può avvenire – codifica e decodifica – anche sul client; si tratta cioè solo di una rappresentazione dei dati idonea ad essere passata negli header) attraverso un apposito header di richiesta al server che lo verifica e ne ottiene le informazioni utili alla produzione del servizio per cui è pensato.

Soluzioni alternative si basano comunque sullo stesso principio: codificare le informazioni di stato in file salvati sul client e inviati al server attraverso dei cookie / headers.

Nel prossimo articolo vedremo alcune soluzioni per rendere fault tolerant e high available i nostri database.

Facebook Comments