Mi ero proposto di valutare la sicurezza orizzontale (row-level security) e la codifica trasparente dei dati (Transparent Data Encryption - TDE), ma non è ancora stato annunciato un supporto per la TDE (anche se posso vedere le principali tabelle di sistema nei database V12). Quindi, in questa parte, mi limiterò a darvi la mia valutazione sulla sicurezza orizzontale.
La sicurezza orizzontale fornisce un metodo per filtrare le righe in una tabella basata sull’identità di un utente connesso al database.
La mia conclusione: intuisco la comodità di centralizzare questa logica del filtro per riga, ma non me la sentirei di includerla nei miei progetti di sviluppo. SQL Server ha metodi alternativi per incapsulare viste filtrate dei vostri dati che sono trasparenti, cosa importante quando si sviluppano, si controllano, si ottimizzano e si testano query SQL. Secondo me sarebbe troppo facile lasciarsi sfuggire l’esistenza e l’effetto di tutti i predicati di sicurezza orizzontale.
In sintesi, la sicurezza row-level si implementa creando un oggetto di Security Policy che aggiunge una funzione di predicato alla tabella di database da filtrare. Una volta che la security policy è attivata, la funzione è applicata come una clausola WHERE implicita per tutte le istruzioni DML che si riferiscono alla tabella. È possibile aggiungere una sola security policy per ogni tabella, quindi la funzione deve servire per tutti i vostri criteri di filtro.
La possibilità o no di implementare sicurezza a livello di riga nella vostra applicazione dipende dal fatto che soddisfi due requisiti: a) siete in grado di identificare l’utente connesso e b) siete in grado di mettere quell’utente in relazione alle righe della tabella da filtrare. Qualunque funzione in SQL Server può accedere prontamente alle informazioni della sessione che identificano l’utilizzatore del database ma, perché sia attiva la sicurezza a livello di riga, occorre anche che nella vostra tabella ci sia qualche colonna (o più colonne) che possa essere collegata direttamente o indirettamente all’utente. Queste colonne vengono poi trasferite come argomenti alla funzione di predicato.
Nell’esempio di codifica qui sotto, ho aggiunto una security policy che filtra clienti verso il venditore, che è l’utente connesso.
In pratica, più applicazioni useranno un database utente condiviso, così perché questo sia efficace, l’applicazione di chiamata deve anche popolare il valore delle informazioni di contesto (Context_Info) che è disponibile, per mettere in archivio tutti i dati che riguardano la sessione. La funzione di predicato farà poi riferimento a queste informazioni più che allo user name o lo user id.
Se si implementa la sicurezza a livello di riga, bisogna tenere presente che c’è un predicato implicito aggiunto a ogni query sulla tabella filtrata. Questo significa che l’ottimizzatore di query lo sta sempre fattorizzando nel suo piano di esecuzione. È difficile ottimizzare le query quando si lavora con un predicato implicito, così non c’è da sorprendersi se si vedono più scansioni di indice nel caso non ci siano indici utilizzabili sulle colonne filtrate. Per giunta, le funzioni di predicato devono essere di tipo inline table-valued,, così vengono incluse in tutti i piani di query, con il risultato di un piano di esecuzione più efficiente.
Seguendo l’esempio sopra, il piano di query ha incluso la funzione di security policy come un predicato per l’operazione di filtro. Se ci fosse stato un indice appropriato sulla colonna “venditore”, a quel punto il piano di query avrebbe persino potuto includere una ricerca per indice.
Come considerazione finale, dal punto di vista della sicurezza, la sicurezza a livello orizzontale ga dipende dall’integrità dell’identificatore dell’utente al quale fa riferimento la funzione di predicato. La documentazione di supporto fornita da Microsoft solleva alcune problematiche in questo ambito, perciò non date per scontato che si tratti di un metodo di sicurezza infallibile per controllare l’accesso ai dati.