🚦 Administrando los accesos y el uso de los datos con SQL Server – Serie GDPR (3/5)
Una vez que hemos detectado y clasificado nuestros datos, entramos en el segundo paso en nuestro camino para cumplir con el GDPR, ahora es el turno de administrar los accesos a los datos y controlar el uso que se hace de ellos.
¿Por qué esto es importante para el GDPR?
The GDPR specifically addresses the need for mechanisms which limit access to data, requiring “measures [that] shall ensure that by default personal data are not made accessible without the individual’s intervention to an indefinite number of natural persons.”
GDPR Article 25(2)—“Data protection by design and by default.”
SQL Server dispone de varias herramientas para el control de acceso, pero es el administrador SQL Server el que hace uso de ellas, y en muchas ocasiones por comodidad se otorga permisos innecesarios a determinados usuarios, dando acceso a datos que este usuario no tendría que poder ver o a datos confidenciales.
Para ayudar con la administración de estos accesos y controlar los datos que se pueden ver, SQL Server ofrece lo siguiente:
- Mecanismos de autenticación para asegurar que solo los usuarios con credenciales válidas puedan acceder al servidor de base de datos. SQL Server admite la autenticación SQL Server y la autenticación con seguridad integrada de Windows, y en entorno Azure, para Azure SQL Database y Azure SQL Data Warehouse, el control de acceso por roles de Active Directory de Azure.
- Control de acceso basado en roles, otorgando a los usuarios distintos roles (niveles de acceso) dependiendo la función dentro del equipo.
- Row-Level security para evitar el acceso a filas específicas de una tabla basado en características del usuario que intenta acceder al dato, por ejemplo que en la tabla de empleados solo puedas ver tus datos y no los de otros empleados.
- Utilización de enmascaramiento de datos, por ejemplo para números de cuentas bancarias del tipo XXXX-XXXX-XXXX-1321, para ocultar datos a usuarios que no tengan permisos usando Dynamic Data Masking.
- Verificar los cambios que ocurren en una tabla usando SQL Server Audit o para Azure SQL Database utilizando Azure SQL Server Auditing.
En esta entrada vamos a centrarnos en las características más nuevas, disponibles desde SQL Server 2016, como son Row-Level Security y Dynamic Data Masking.
Row-Level Security
Para nuestro ejemplo vamos a utilizar la base de datos AdventureWorks2016, y en concreto las tablas [Sales].[SalesTerritory] y [Sales].[Customer].
USE [AdventureWorks2016]
GO
-- [Sales].[SalesTerritory]
SELECT TerritoryID
,NAME
,CountryRegionCode
,[Group]
FROM [Sales].[SalesTerritory]
-- [Sales].[Customer]
SELECT CustomerID
,PersonId
,StoreId
,TerritoryID
,AccountNumber
FROM [Sales].[Customer]
Al ejecutar las consultas se puede ver como cada Customer de la tabla [Sales].[Customer] pertenece a un Territory.
[Sales].[SalesTerritory] [Sales].[Customer]En nuestro ejemplo vamos a hacer uso de Row-Level Security para controlar que solo los usuarios que pertenezcan a un Territory puedan ver los Customer que les correspondan.
Procedemos a crear los usuarios:
-- USERS creation
USE [AdventureWorks2016]
GO
CREATE USER USCountryManager WITHOUT LOGIN;
GO
CREATE USER GBCountryManager WITHOUT LOGIN;
GO
CREATE USER DECountryManager WITHOUT LOGIN;
GO
CREATE USER FRCountryManager WITHOUT LOGIN;
GO
CREATE USER CACountryManager WITHOUT LOGIN;
GO
CREATE USER AUCountryManager WITHOUT LOGIN;
Le otorgamos permisos a estos usuarios:
-- Datareader permissions to the CountryManagers
USE [AdventureWorks2016]
GO
ALTER ROLE [db_datareader] ADD MEMBER USCountryManager
ALTER ROLE [db_datareader] ADD MEMBER GBCountryManager
ALTER ROLE [db_datareader] ADD MEMBER DECountryManager
ALTER ROLE [db_datareader] ADD MEMBER FRCountryManager
ALTER ROLE [db_datareader] ADD MEMBER CACountryManager
ALTER ROLE [db_datareader] ADD MEMBER AUCountryManager
Y el siguiente paso ya sería crear el Row Level Security, en nuestro caso primero vamos a crear un schema para tenerlo más ordenado:
-- Schema Creation
USE [AdventureWorks2016]
GO
CREATE SCHEMA GDPR;
Lo siguiente es crear una función sobre [Sales].[SalesTerritory] que posteriormente utilizaremos para filtrar [Sales].[Customer].
Esta función es la encargada de validar si el usuario podrá ver o no una determinada fila basándose en el TerritoryId y posteriormente en el CountryRegionCode
-- Function Creation
USE [AdventureWorks2016]
GO
CREATE FUNCTION GDPR.CountryValidation(@TerritoryID AS INT)
RETURNS TABLE
WITH SCHEMABINDING
AS
RETURN SELECT 1 AS ValidationResult FROM [Sales].[SalesTerritory] G
WHERE
G.TerritoryID = @TerritoryID AND
(
(
G.CountryRegionCode = 'US' AND USER_NAME() = 'USCountryManager'
)
OR
(
G.CountryRegionCode = 'GB' AND USER_NAME() = 'GBCountryManager'
)
OR
(
G.CountryRegionCode = 'DE' AND USER_NAME() = 'DECountryManager'
)
OR
(
G.CountryRegionCode = 'FR' AND USER_NAME() = 'FRCountryManager'
)
OR
(
G.CountryRegionCode = 'CA' AND USER_NAME() = 'CACountryManager'
)
OR
(
G.CountryRegionCode = 'AU' AND USER_NAME() = 'AUCountryManager'
)
)
Una vez tengamos la función creada, crearemos una Security Policy que utilizará esta función para filtrar la tabla [Sales].[Customer]
-- Security Policy Creation to filter data from Customer table
USE [AdventureWorks2016]
GO
CREATE SECURITY POLICY GDPR.AWSecurityPolicy
ADD FILTER PREDICATE GDPR.CountryValidation(TerritoryId) ON [Sales].[Customer]
Ahora que ya lo tenemos todo creado, vamos a realizar una serie de pruebas para entender mejor el funcionamiento.
Usuario DBO
Primero, tratamos de ejecutarlo con el usuario con el que hayamos creado todo, en nuestro caso es un usuario con permisos dbo
-- SELECT with DBO USER
USE [AdventureWorks2016]
GO
SELECT * FROM [Sales].[Customer]
No devuelve ningún resultado, y es correcto debido a que nuestro usuario no está contemplado dentro de la función.
Usuario USCountryManager
Ahora vamos a realizar la misma prueba pero utilizando el usuario USCountryManager que sí que está en la función, utilizaremos “EXECUTE AS USER” para ello:
-- SELECT WITH USCountryManager USER
USE [AdventureWorks2016]
GO
EXECUTE AS USER='USCountryManager'
SELECT * FROM [Sales].[Customer]
REVERT
Vemos que si que aparecen resultados, y que además aparecen varios TerritoryId, esto es normal ya que “US” tiene varios territorios:
-- More than 1 Territory, why?
USE [AdventureWorks2016]
GO
SELECT TerritoryID
,NAME
,CountryRegionCode
,[Group]
FROM [Sales].[SalesTerritory]
WHERE CountryRegionCode = 'US'
Usuario GBCountryManager
La última prueba la realizaremos con el usuario GBCountryManager, que únicamente tiene un Territorio
-- SELECT WITH GBCountryManager USER
USE [AdventureWorks2016]
GO
EXECUTE AS USER='GBCountryManager'
SELECT * FROM [Sales].[Customer]
REVERT
Por último y si quisiéramos borrar todo y dejarlo como al inicio, solo tendríamos que ejecutar lo siguiente:
-- Drop RLS Security Policy
DROP SECURITY POLICY GDPR.AWSecurityPolicy
-- DROP RLS Function
DROP FUNCTION GDPR.CountryValidation
Una vez que ya hemos entendido como funciona Row-Level Security y como se filtran los datos, vamos a pasar a utilizar la funcionalidad Dynamic Data Masking para enmascarar datos.
Dynamic Data Masking
Lo primero que hay que saber sobre Dynamic Data Masking es que el enmascaramiento se realiza en tiempo de ejecución, es decir, el dato en la tabla no se encuentra enmascarado.
Para controlar si un usuario puede o no ver un dato enmascarado SQL Server hace uso del permiso “UNMASK”.
En este ejemplo vamos a mostrar los tipos de enmascaramiento DEFAULT, EMAIL y PARTIAL.
Vamos a hacer uso de dos usuarios, el usuario DBA que si que tendrá permisos UNMASK, y el usuario Developer que no tendrá permisos UNMASK
Empezamos creando los usuarios
-- User creation
USE [AdventureWorks2016]
GO
CREATE USER DBA WITHOUT LOGIN;
GO
CREATE USER Developer WITHOUT LOGIN;
Damos permisos a estos usuarios:
-- Datareader permissions
USE [AdventureWorks2016]
GO
ALTER ROLE [db_datareader] ADD MEMBER DBA
ALTER ROLE [db_datareader] ADD MEMBER Developer
Y otorgamos al usuario DBA el permiso UNMASK
-- UNMASK permission to DBA
USE [AdventureWorks2016]
GO
GRANT UNMASK TO DBA
Ahora que ya tenemos los usuarios con los permisos adecuados, podemos ver los tipos de enmascaramiento y como aplican a los datos, para esto vamos a utilizar nuevamente la base de datos AdventureWorks2016
Enmascaramiento DEFAULT
Esta función reemplaza las columnas de tipo string por la cadena “XXXX” y las de tipo numérico por “0”.
Vamos a utilizarla sobre la columna PhoneNumber de la tabla Person.PersonPhone
-- MASK PhoneNumber column using DEFAULT() function
USE [AdventureWorks2016]
GO
ALTER TABLE Person.PersonPhone
ALTER COLUMN PhoneNumber ADD MASKED WITH (FUNCTION = 'default()')
Y vamos a ejecutar un TOP 10 sobre la tabla utilizando los usuarios DBA y Developer para ver la diferencia en la visualización
USE [AdventureWorks2016]
GO
-- SELECT Person.PersonPhone table with DBA USER
EXECUTE AS USER='DBA'
SELECT TOP 10 * FROM Person.PersonPhone
REVERT
-- SELECT Person.PersonPhone table with Developer USER
EXECUTE AS USER='Developer'
SELECT TOP 10 * FROM Person.PersonPhone
REVERT
Usuario DBA (con permiso UNMASK)
Usuario Developer
Enmascaramiento EMAIL
Esta función reemplaza la parte antes de la @ con la primera letra y “XXXX” y pone “@XXXX.com” al final
Vamos a utilizarla sobre la columna EmailAddress de la tabla Person.EmailAddress
-- MASK EmailAddress column using EMAIL() function
USE [AdventureWorks2016]
GO
ALTER TABLE Person.EmailAddress
ALTER COLUMN EmailAddress ADD MASKED WITH (FUNCTION = 'email()')
Y vamos a ejecutar un TOP 10 sobre la tabla utilizando los usuarios DBA y Developer para ver la diferencia en la visualización
USE [AdventureWorks2016]
GO
-- SELECT Person.EmailAddress table with DBA USER
EXECUTE AS USER='DBA'
SELECT TOP 10 * FROM Person.EmailAddress
REVERT
-- SELECT Person.EmailAddress table with Developer USER
EXECUTE AS USER='Developer'
SELECT TOP 10 * FROM Person.EmailAddress
REVERT
Usuario DBA (con permiso UNMASK)
Usuario Developer
Enmascaramiento PARTIAL
Esta función define un número de caracteres al inicio y al final que se quedarán sin enmascarar, y en el medio utilizará el string que se elija.
Vamos a utilizarla sobre la columna LastName de la tabla Person.Person, dejaremos un caracter al inicio y dos al final sin enmascarar y en medio pondremos el texto “GDPR”.
-- MASK LastName column using PARTIAL() function
USE [AdventureWorks2016]
GO
ALTER TABLE Person.Person
ALTER COLUMN LastName ADD MASKED WITH (FUNCTION = 'partial(1, "GDPR", 2)')
Y vamos a ejecutar un TOP 10 sobre la tabla utilizando los usuarios DBA y Developer para ver la diferencia en la visualización
USE [AdventureWorks2016]
GO
-- SELECT Person.Person table with DBA USER
EXECUTE AS USER='DBA'
SELECT TOP 10 * FROM Person.Person
REVERT
-- SELECT Person.Person table with Developer USER
EXECUTE AS USER='Developer'
SELECT TOP 10 * FROM Person.Person
REVERT
Usuario DBA (con permiso UNMASK)
Usuario Developer
Para finalizar vamos a ver como de una forma rápida podemos dar marchas atrás a todos los cambios realizados.
Le quitamos el permiso UNMASK al usuario DBA
-- Remove UNMASK permission to DBA
USE [AdventureWorks2016]
GO
REVOKE UNMASK TO DBA
Y borramos el enmascaramiento en las tres tablas que hemos utilizado
-- DROP MASK
USE [AdventureWorks2016]
GO
ALTER TABLE Person.PersonPhone
ALTER COLUMN PhoneNumber DROP MASKED;
GO
ALTER TABLE Person.EmailAddress
ALTER COLUMN EmailAddress DROP MASKED;
GO
ALTER TABLE Person.Person
ALTER COLUMN LastName DROP MASKED;
Ahora que ya sabemos como controlar los accesos y la visibilidad del dato, es hora de ir al siguiente punto del GDPR y ver como Proteger estos datos.
Serie completa de GDPR:
- Cómo adaptar SQL Server al RGPD (GDPR)
- Detectando y clasificando datos personales con SQL Server
- Administrando los accesos y el uso de los datos con SQL Server
- Protegiendo datos en Reposo, en Uso y en Tránsito
- Monitorizar y Reportar accesos no autorizados a Datos Personales
Consultor Senior SQL Server & BI con 9 años de experiencia, MCSE Data Platform con conocimientos de toda la herramienta y enfocado principalmente a la detección y mejora de problemas de rendimiento en Base de Datos. En mi tiempo libre soy un gran aficionado a la fotografía de estilo urbano y de lugares abandonados.