SEGURIDAD SQL SERVER: Seguridad a nivel de fila (RLS)

En el próximo SQL BI Saturday de Costa Rica voy a estar exponiendo junto con mi hermano Carlos, una sesión sobre aseguramiento del Data Warehouse “Mantenga su Data Warehouse seguro, YA! y vamos a tratar sobre tecnologías de SQL Server que ofrecen nuevas alternativas para asegurar los Almacenes de Datos. Una de las tecnologias de la charla y sobre lo que trata este artículo es sobre la seguridad a nivel de fila o en inglés: Row Level Security (RLS), una tecnología introducida en SQL 2016 que permite restringir el acceso a las filas de las tablas y cuyas principales ventajas son:

  • Que lo hace de una forma que es bastante más programable y sencillo que el método tradicional de escribir vistas con predicados de seguridad;
  • Que no requiere cambios en la aplicación.

Componentes de Seguridad a Nivel de Fila

Para entender RLS hay que entender tres grandes elementos que la implementan:

Función de Predicado (Predicate Function):

Es el componente responsable de establecer el predicado de seguridad, o sea de definir el filtro o condición que da acceso a las filas. Se define como una INLINE FUNCTION (Función en Línea) y que puede recibir como parámetros las columnas para construir dicho predicado. El ejemplo más sencillo de una función de predicado:

--- Best Practice: Use Restricted schema to prevent tampering
CREATE FUNCTION RestrictedArea.AllowAll()
--- Inline Function bound to objects used by the function
--- Return a Row if access is allowed
RETURNS TABLE WITH SCHEMABINDING
AS
    --- Everyone has access to everything
    RETURN SELECT 1 AS Dummy;
GO

La función tiene las siguientes características:

  • Como mejor practica se define en un esquema independiente que debe ser de acceso limitado a los usuarios. Los usuarios NO requieren acceso a la función.
  • La función es una función en línea, lo que significa que aun cuando se programa como línea por línea, realmente se ejecuta en bloque y suelen tener un excelente desempeño a diferencia de sus hermanas las funciones escalares.
  • La función es enlazada con los objetos que se usen dentro de la función (SCHEMABINDING), lo que significa que las tablas subyacentes no permitirán cierto tipo de cambios (como borrar las columnas que están siendo usadas en la función.
  • La función debe retornar una fila cuando se autoriza el acceso y en caso contrario no retornar fila

Una implementación más interesante de una funcion de predicado:

CREATE FUNCTION RestrictedArea.FilterByUser(@User AS sysname)
RETURNS TABLE WITH SCHEMABINDING
AS
    RETURN SELECT 1 AS PredicateResult
       --- Access only to rows owned by the user
            WHERE @User = USER_NAME()
       --- Or User if user is Admin
                           OR @User='Admin';

En este caso, la función es más interesante porque acepta un parámetro pero podría usar múltiples; y permite construir lógica en T-SQL para determinar si se tiene acceso. En el ejemplo anterior se autoriza las filas al propietario (asumiendo que la tabla que va a ser filtrada tiene una columna con el nombre del titular) o si el usuario es Admin.

Puede encontrar una versión todavia más funcional en el siguiente ejemplo:

--- Even more functional
CREATE FUNCTION RestrictedArea.FilterBy(@CountryName AS sysname)
RETURNS TABLE WITH SCHEMABINDING
AS
    RETURN SELECT 1 AS FilterBy
                     --- Use an authorization table
                 FROM RestrictedArea.CountriesAuthorization AS CA
                 WHERE
                           -- Is authorized by country: Use DATABASE_PRINCIPAL_ID instead of USER_NAME
                           (CA.DatabasePrincipalId=DATABASE_PRINCIPAL_ID()
                           AND @CountryName=CA.CountryCode)
                                  -- Has global access
                           OR IS_MEMBER('GlobalAccess')=1;

En este caso tenemos varios elementos que vale la pena rescatar:

  • Usa una tabla de autorización lo cual facilitar la administración. Los usuarios no requieren acceso a esta tabla.
  • Usa la función DATABASE_PRINCIPAL_ID que tiene la ventaja que en caso de cambios de nombre de sesión (LoginName) no presenta problemas como lo hace USER_NAME();
  • Usa la función IS_MEMBER para facilitar la administración usando roles de base de datos para accessos generales.

Predicado de Seguridad (Security Predicate)

El segundo elemento de la seguridad a nivel de fila (RLS) es el predicado de seguridad. Hay que rescatar que el predicado de seguridad es parte del comando de Política de Seguridad (CREATE SECURITY POLICY), y tiene como propósito enlazar la función de predicado con la tabla que se está asegurando.

Cuando definimos un predicado de seguridad establecemos la forma en que los comandos deben aplicar la función de predicado para filtrar las filas de una tabla. El RLS soporta dos tipos de predicados de seguridad:

  • FILTER (Filtro): que se aplica de forma “pasiva” a todas las operaciones de lectura, incluyendo: SELECT, UPDATE y DELETE. Si no es un error, se aplica a la lectura de filas de los UPDATES y de los DELETES. Una forma de interpretar el predicado filtro es como una forma automática de agregar un WHERE a los comandos de SELECT, UPDATE y DELETE. Un ejemplo de predicado FILTER es:
CREATE SECURITY POLICY FilterByUser
   --- Predicate FILTER uses column to establish the link between the table and the predicate function
   ADD FILTER PREDICATE RestrictedArea.FilterByUser(SalesPerson)
      ON Sales.Customers;
  • BLOCK(Bloqueo): que se aplica de forma “activa” a todas las operaciones de escritura que rompan el predicado. El predicado de BLOCK se aplica como un equivalente a combinaciones de BEFORE y AFTER TRIGGER que verifican que ninguna fila modificada incumpla el predicado establecido en la función. Más específicamente el block funciona como un BEFORE UPDATE/DELETE TRIGER y como un AFTER INSERT/UPDATE Trigger. Un ejemplo de predicado BLOCK es:
CREATE SECURITY POLICY FilterByCountry
   -- Restricts Writes to rows outside the predicate
   ADD BLOCK PREDICATE RestrictedArea.FilterByCountry(CountryCode)
      ON Sales.Customers;

Política de Seguridad (Policy Security)

El último elemento son las políticas de seguridad que son las combinaciones de predicados de seguridad que sirven para manejar desde un solo punto la seguridad de múltiples tablas, facilitando la administración y el habilitar o deshabilitar en bloque. Por ejemplo, si el usuario solo tiene acceso de lectura al esquema de Sales (Ventas) la siguiente política de seguridad sirve para controlar múltiples tablas con una única política:

CREATE SECURITY POLICY FilterByCountry
   ADD FILTER PREDICATE RestrictedArea.FilterByCountry(CountryCode)
      ON Sales.Customers,
  ADD FILTER PREDICATE RestrictedArea.FilterByCountry(CountryCode) 
      ON Sales.Orders,
  ADD FILTER PREDICATE RestrictedArea.FilterByOrder(OrderNumber)   
      ON Sales.OrderDetails,
  ADD FILTER PREDICATE RestrictedArea.FilterByCountry(CountryCode) 
      ON Sales.Invoices,
  ADD FILTER PREDICATE RestrictedArea.FilterByInvoice(InvoiceNumber)
      ON Sales.InvoiceDetails;

Si por otra parte el usuario tiene acceso de lectura y escritura sería necesario habilitar también políticas de bloqueo (BLOCK) para cubrir adecuadamente las escrituras. En ese caso una la política más apropiada seria:

CREATE SECURITY POLICY FilterByCountry
   ADD FILTER PREDICATE RestrictedArea.FilterByCountry(CountryCode) ON Sales.Customers,
   ADD BLOCK PREDICATE RestrictedArea.FilterByCountry(CountryCode) ON Sales.Customers,
   ADD FILTER PREDICATE RestrictedArea.FilterByCountry(CountryCode) ON Sales.Orders,
   ADD BLOCK PREDICATE RestrictedArea.FilterByCountry(CountryCode) ON Sales.Orders,
   ADD FILTER PREDICATE RestrictedArea.FilterByOrder(OrderNumber)   ON Sales.OrderDetails,
   ADD BLOCK PREDICATE RestrictedArea.FilterByOrder(OrderNumber)   ON Sales.OrderDetails,
   ADD FILTER PREDICATE RestrictedArea.FilterByCountry(CountryCode) ON Sales.Invoices,
   ADD BLOCK PREDICATE RestrictedArea.FilterByCountry(CountryCode) ON Sales.Invoices,
   ADD FILTER PREDICATE RestrictedArea.FilterByInvoice(InvoiceNumber)
        ON Sales.InvoiceDetails,
   ADD BLOCK PREDICATE RestrictedArea.FilterByInvoice(InvoiceNumber)
        ON Sales.InvoiceDetails;

Una ventaja de las políticas de seguridad es que se pueden habilitar y deshabilitar afectando todas las tablas de un solo golpe. La sintaxis para habilitar y deshabilitar políticas es:

ALTER SECURITY POLICY FilterByCountry WITH (STATE=ON); 
ALTER SECURITY POLICY FilterByCountry WITH (STATE=OFF);

Conclusiones

A partir de SQL 2016, es mucho más fácil brindar la seguridad por filas, lo cual es particularmente importante en los almacenes de datos (Data Warehouse). La alternativa tradicional de usar vistas requiere mucho más esfuerzo de programación, tiene mayores costos de mantenimiento y es más difícil de administrar.  Si quieres usar RLS (Row Level Security) debes: crear una función de predicado con el código en T-SQL para limitar las filas; definir si desea filtrar (FILTER), bloquear (BLOCK) o ambas en el predicado de seguridad y definir que predicado aplicar a cada tabla.

Si están por Costa Rica, espero verlos en el próximo SQL BI Saturday de Costa Rica en la sesión  de “Mantenga su Data Warehouse seguro, YA!

 

 

Deja una respuesta

Tu dirección de correo electrónico no será publicada. Los campos obligatorios están marcados con *