Una delle problematiche più comuni che si hanno gestendo il database sotto source control è quello derivante dalla security. Nel caso in cui si seguano pratiche in cui è l'utente il detentore delle autorizzazioni, non risulta per nulla semplice applicare soluzioni "portabili". Il problema sta soprattutto in fase di deploy o di "get" delle versioni sulle macchine degli sviluppatori. Proviamo a pensare ai due casi:
Primo caso - sviluppo
Immaginiamo di avere due differenti workstation guidate dallo stesso repository remoto di controllo del codice sorgente (SC), una delle quali ha un'istanza SQL Server ai quali database si accede con sql authentication ed una in cui, per accedere ai database, si usa la sicurezza integrata di windows. Gli sviluppatori continuano a modificare il database e, spesso, incorrono nella necessità di dare le dovute permission ai propri utenti (uno windows ed uno sql creato ad hoc tramite login).
In questo scenario proposto, nessuno dei due utenti è sysadmin, altrimenti le permission sarebbero superflue. Supponendo di avere una tabella HumanResources.Employees, ogni dev, per attribuire i permessi di INSERT/UPDATE e SELECT, dovrebbe scrivere il seguente listato:
GRANT INSERT, UPDATE, SELECT ON HumanResources.Employees TO 'AALPI-NB\reporter'; -- Windows
GRANT INSERT, UPDATE, SELECT ON HumanResources.Employees TO myDatabaseUser; -- SQL
In generale, già dall'aggiunta dell'utente, i dati verranno messi sotto source control, se non diversamente specificato:
Entrambi, dopo le modifiche, proseguiranno con il "check in", o meglio, con l'operazione di invio dei cambiamenti al repository remoto.
Ma ora, ogni qual volta gli sviluppatori proveranno a percepire le nuove modifiche, che succede?
Presto detto, ognuno dovrà "sorbirsi" l'utente e la login dell'altro (e non è detto che esista sull'istanza). Ecco come verrà proposta la modifica sulla prima istanza:
Questo significa che lo sviluppatore che sta utilizzando la sicurezza integrata sarà costretto, al fine di proseguire con l'operazione di get, a creare una login identica a quella inviata dall'altro collaboratore. Allo stesso modo accade il viceversa, solo che, mentre nel primo caso è possibile creare la login, se l'utente windows proposto non esiste già, risulta un tantino invasivo allineare la propria macchina per ricevere la modifica.
Da notare inoltre che, quando vengono incluse informazioni sensibili (nell'esempio la password della login proposta) esse vengono completamente mascherate e stravolte. Cosa che porta ulteriori problemi; si pensi ad esempio ad un'applicazione in sviluppo che utilizza l'utente per connettersi. Il config è sul repository centrale salvato con una password e quindi risulta molto scomodo dover cambiare ogni volta l'informazione di autenticazione.
Provando ad accettare le modifiche così come sono, otterremo i seguenti feedback. Prima di tutto un warning per le informazioni sensibili:
ed, in secondo luogo, una nuova login/utente che non avevamo prima:
Nel caso del passaggio di una credenziale windows, avremmo addirittura ricevuto un errore bloccante. Non è detto che l'utente windows sia presente sulla macchina di sviluppo di un nostro collaboratore.
Secondo caso - deploy in ambienti di staging/test/produzione
Il primo caso, seppure scomodo, è comunque da considerarsi non troppo pericoloso. Purtroppo non è possibile dirlo per i deploy negli ambienti successivi allo sviluppo. Un deploy di questo tipo può portare dei disastri sulla sicurezza dell'ambiente verso il quale andiamo a pubblicare le nostre applicazioni/database. Dopo aver accettato in sviluppo le modifiche di cui al punto precedente, immaginiamo ora di fare deploy con un prodotto di diff, per generare gli script da applicare.
Risulta facile capire che, esattamente per come accade durante le operazioni di get, gli algoritmi di diff andranno a proporci le nuove utenze create in sviluppo per applicarle sugli ambienti successivi allo sviluppo.
Soluzioni possibili
I suddetti problemi, molto simili, provocano non pochi problemi, addirittura possono danneggiare i nostri ambienti di deploy. Per fortuna abbiamo qualche possibilità, fornita da SQL Source Control.
Primo step - da utente a ruoli
Sicuramente, il modo migliore per non essere contagiati da questo problema è utilizzare i ruoli a database. Un ruolo è un punto sul quale andare ad attribuire permessi senza "scomodare" utenze, ma soltanto facendo in modo che queste ultime ne facciano parte, esattamente come per un gruppo su domain controller. Supponendo di avere un ruolo ApplicationAccessRole, le operazioni di cui al primo punto, diventano:
GRANT INSERT, UPDATE, SELECT ON HumanResources.Employees TO ApplicationAccessRole;
Proviamo quindi a creare un ruolo, con l'istruzione CREATE ROLE ApplicationAccessRole; e, successivamente, eseguire il comando di GRANT appena descritto. Una volta eseguio, associamo al ruolo l'utente che stiamo utilizzando (negli esempi di sviluppo sopra,myDatabaseUser) con la sp_addrolemember o con la nuova sintassi di ALTER ROLE.
EXEC sys.sp_addrolemember @rolename = 'ApplicationAccessRole', @membername = 'myDatabaseUser';
oppure
ALTER ROLE ApplicationAccessRole ADD MEMBER myDatabaseUser; --<-- from SQL Server 2012
Questa operazione porterà il SQL Source Control a proporci quanto segue:
ci siamo quasi, anche se l'utente appare ancora. Passiamo allo step successivo.
Secondo step - Ignorare gli utenti
Nel tab di setup del database sotto source control è possibile aggiungere la spunta su "Ignore users and role membership":
Una volta aggiunta, il SQL Source Control ci proporrà di fare salvataggio dei cambiamenti di un particolare oggetto, chiamato Database Comparison Options. Facciamo checkin solamente di quel file e aggiorniamo l'anteprima del cambiamento che stiamo per committare. Otterremo finalmente quanto desiderato:
La membership dell'utente non è più proposta, quindi non siamo legati ad esso. Siamo veramente molto vicini alla conclusione, tuttavia, è ancora possibile aggiungere utenti manualmente al nostro database (e quindi anche al source control).
Terzo step - filtrare gli utenti
Il SQL Source Control ci consente l'utilizzo di filtri molto comodi per decidere cosa e come pulire i nostri changeset. Per definirli, è sufficiente premere il tasto destro sul database e selezionare in "Other SQL Source Control tasks" la voce "Edit filter rules..":
La schermata che apparirà consente sia di filtrare intere classi di oggetto, sia di personalizzare il filtro con pattern di stringhe, sia sullo schema sia sul nome dell'oggetto:
Una volta salvato, oltre ai cambiamenti generati dagli script precedenti, troveremo anche il filtro nell'anteprima dei changeset. Come suggerisce il tool, è necessario fare checkin prima del filtro stesso, per avere i filtri applicati sugli oggetti che sono in modifica. Quando il filtro raggiunge il source control, anche un nuovo utente creato manualmente da SQL Server Management Studio non sarà più visibile.
Stay Tuned!