RAS: Ocultar datos sin cambiar las aplicaciones

En un artículo anterior —que les recomiendo leer antes de continuar— les mostré lo simple y rápido que es ocultar datos usando Virtual Private Database (VPD), y además sin tener que recurrir a cambios en las aplicaciones, pero recientemente atendí a la extraordinaria presentación de Karen Cannell y Jim Czuprynski: RAS before RAG: Real Application Security Fundamentals for Gen AI Apps, que puedes ver y descargar aquí, en el que presentan un problema similar pero que es resuelto empleando algo nuevo: Real Application Security (RAS).

¿Pero, qué es RAS?¿Será una mejor forma de lograr el objetivo? ¿Será que VPD ya es historia? Pues sigue leyendo para averiguarlo.

Real Application Security

La documentación oficial nos lo presenta así:

Oracle Database Real Application Security es un modelo de autorización de bases de datos que:

  • Admite políticas de seguridad declarativas.
  • Permite una seguridad integral para aplicaciones de varios niveles.
  • Proporciona una solución integrada para proteger los recursos de bases de datos y aplicaciones.
  • Mejora la arquitectura de seguridad de Oracle Database para satisfacer las demandas actuales y emergentes de las aplicaciones desarrolladas para Internet.

Esta es la teoría, pero ¿cómo será en la práctica? Pues nada mejor para saberlo que tratar de implementarlo, vayamos a ello.

La solución al estilo RAS

Si no lo has hecho aún, dale una leída al artículo anterior, para que así te familiarices con el problema que vamos a resolver.

Tomemos en cuenta que inicialmente podemos ver las columnas sensibles sin restricción alguna.

Ahora sí, como primer paso debemos crear un Security Class, que viene a ser un marco para un conjunto de privilegios de aplicación.

En nuestro caso se llamará SH_PRIVILEGES, y el privilegio de aplicación que definiremos es: VIEW_SENSITIVE_COLUMNS.

BEGIN
  xs_security_class.create_security_class(
    name        => 'SH_PRIVILEGES',
    priv_list   => xs$privilege_list(xs$privilege(name => 'VIEW_SENSITIVE_COLUMNS')),
    parent_list => xs$name_list('SYS.DML'),
    description => 'Security Class to protect SH tables'
  );
END;
/

Ahora crearemos una Access Control List (ACL), que viene a ser una lista de Access Control Entries (ACE), que permiten o deniegan privilegios de aplicación a uno o más usuarios o roles (PRINCIPAL).

Nuestra primera ACL servirá para permitir el acceso a las columnas sensibles, la llamaremos SH_UNRESTRICTED_ACL, le asignaremos el privilegio condicional que definimos en el paso previo: VIEW_SENSITIVE_COLUMNS, y como necesitamos que aplique a todos los usuarios de la base de datos, lo asociaremos al rol: PUBLIC.

DECLARE
  aces xs$ace_list := xs$ace_list();
BEGIN
  aces.extend(1);

  aces(1) := xs$ace_type(
    privilege_list => xs$name_list('SELECT', 'VIEW_SENSITIVE_COLUMNS'),
    principal_name => 'PUBLIC',
    principal_type => xs_acl.ptype_db
  );

  xs_acl.create_acl(
    name        => 'SH_UNRESTRICTED_ACL',
    ace_list    => aces,
    sec_class   => 'SH_PRIVILEGES',
    description => 'Conditional viewing'
  );
END;
/

A continuación crearemos una segunda ACL, que servirá para prohibir el acceso a las columnas sensibles, la llamaremos SH_RESTRICTED_ACL, y como necesitamos que aplique a todos los usuarios de la base de datos, la asociaremos al rol: PUBLIC.

DECLARE
  aces xs$ace_list := xs$ace_list();
BEGIN
  aces.extend(1);

  aces(1) := xs$ace_type(
    privilege_list => xs$name_list('SELECT'),
    principal_name => 'PUBLIC',
    principal_type => xs_acl.ptype_db
  );

  xs_acl.create_acl(
    name        => 'SH_RESTRICTED_ACL',
    ace_list    => aces,
    sec_class   => 'SH_PRIVILEGES',
    description => 'Limited viewing'
  );
END;
/

Como siguiente paso debemos crear una Data Security Policy, que define quién puede hacer qué (ACL) y sobre qué filas (realms) o columnas (cols). Le llamaremos SH_DS.

Vamos a considerar dos grupos de filas: el primero abarca todas las filas para las que las columnas sensibles podrán ser vistas condicionalmente (realm 1, con ACL SH_UNRESTRICTED_SESSION), y el segundo incluye todo el universo de filas que tienen prohibido ver las columnas sensibles (realm 2, con ACL SH_RESTRICTED_SESSION).

Definimos las columnas que serán visibles de forma condicional (QUANTITY_SOLD, AMOUNT_SOLD, TIME_ID), de acuerdo a la disponibilidad o no del privilegio VIEW_SENSITIVE_COLUMNS.

DECLARE
  realms xs$realm_constraint_list := xs$realm_constraint_list();
  cols   xs$column_constraint_list := xs$column_constraint_list();
BEGIN
  realms.extend(2);
  cols.extend(1);

  realms(1) := xs$realm_constraint_type(
    realm    => 'prod_id NOT IN ( SELECT prod_id FROM sh.products WHERE prod_category = ''Golf'' )',
    acl_list => xs$name_list('SH_UNRESTRICTED_ACL')
  );

  realms(2) := xs$realm_constraint_type(
    realm    => '1=1',
    acl_list => xs$name_list('SH_RESTRICTED_ACL')
  );

  cols(1) := xs$column_constraint_type(
    column_list => xs$list('QUANTITY_SOLD', 'AMOUNT_SOLD', 'TIME_ID'),
    privilege   => 'VIEW_SENSITIVE_COLUMNS'
  );

  xs_data_security.create_policy(
    name                   => 'SH_DS',
    realm_constraint_list  => realms,
    column_constraint_list => cols,
    description            => 'Controls access to sensitive columns' 
  );
END;
/
Finalmente aplicamos la Data Security Policy a la tabla SH.SALES, indicando que esta no debe aplicar a su dueño (owner_bypass).
BEGIN
  xs_data_security.apply_object_policy(
    policy          => 'SH_DS',
    schema          => 'SH',
    object          => 'SALES',
    owner_bypass    => TRUE,
    statement_types => 'SELECT'
  );
END;
/

No hay más que hacer, ya tenemos todo listo, así que procedamos a repetir la consulta para ver qué pasa.

¡Excelente! Ahora ya no es posible ver los datos de las columnas sensibles, pero solo para las filas que tienen un producto de la categoría Golf, para todos los demás casos son plenamente visibles.

RAS vs VPD: flexibilidad

Cuando originalmente usamos VPD y ahora que lo hacemos con RAS, el resultado es el mismo: absolutamente todos los usuarios están impedidos de ver las columnas sensibles para el universo de filas definido, pero ¿qué tal si necesitamos que haya usuarios para los cuales esta regla no se aplique?, es decir: que puedan ver los contenidos de estas columnas siempre.

Con VPD tendríamos que reescribir la función para que la cadena que retorna se vaya ajustando. Por ejemplo, si deseamos que el usuario SUPER_USER escape a las reglas, el código sería:

CREATE OR REPLACE FUNCTION sh.vpd_condition (
  object_schema IN VARCHAR2, 
  object_name   IN VARCHAR2)
RETURN VARCHAR2 AS
  condition VARCHAR2 (100);
BEGIN
  IF sys_context('USERENV','SESSION_USER') = 'SUPER_USER' THEN
    condition := NULL;
  ELSE  
    condition := 'prod_id NOT IN ( SELECT prod_id FROM sh.products WHERE prod_category = ''Golf'' )';
  END IF;
  RETURN (condition);
END vpd_condition;
/

Si bien lo hemos resuelto, esto es poco práctico y elegante, ¿qué pasa si deseamos modificar la lista de usuarios que escapan de las reglas? ¿no sería mejor considerar roles en lugar de listar toda una serie de usuarios que puede ir cambiando en el tiempo?

Es aquí donde podemos apreciar la flexibilidad que nos da RAS, ya que es cuestión de crear un nuevo ACL y añadirlo al Data Security Policy.

Por ejemplo, si deseamos que todos los usuarios que tienen asignado el rol SH_FULL_ROLE puedan ver las columnas sensibles sin restricción alguna, crearemos un ACL al que llamaremos SH_FULL_ACL, y le asignaremos el privilegio condicional VIEW_SENSITIVE_COLUMNS.

DECLARE
  aces xs$ace_list := xs$ace_list();
BEGIN
  aces.extend(1);

  aces(1) := xs$ace_type(
    privilege_list => xs$name_list('SELECT', 'VIEW_SENSITIVE_COLUMNS'),
    principal_name => 'SH_FULL_ROLE',
    principal_type => xs_acl.ptype_db
  );

  xs_acl.create_acl(
    name        => 'SH_FULL_ACL',
    ace_list    => aces,
    sec_class   => 'SH.SH_PRIVILEGES',
    description => 'Limited viewing'
  );
END;
/

Como siguiente paso vamos a añadir la ACL al Data Security Policy, para lo cual vamos a considerar un grupo de filas (realm) que  abarca todas las filas existentes.

DECLARE
  realm_cons xs$realm_constraint_type;
BEGIN
  realm_cons := 
    xs$realm_constraint_type(
      realm    => '1=1',
      acl_list => xs$name_list('SH.SH_FULL_ACL')
    );

  xs_data_security.append_realm_constraints(
    policy           => 'SH.SH_DS',
    realm_constraint => realm_cons 
  );
END;
/

Ahora repetimos la consulta, pero con un usuario que tiene asignado el rol SH_FULL_ROLE, y comprobamos que tiene acceso irrestricto a las columnas sensibles.

Conclusiones

Hemos comprobado que es cierto aquello de que «hay más de una forma de despellejar un gato», pero si bien VPD nos ayudó a resolver nuestro problema inicial muy rápidamente, RAS es una solución mucho más flexible. Y eso que en este artículo solo hemos arañado la superficie, hay todo un mundo por explorar y aprovechar.

Si bien hay que leer una extensa documentación y no hay muchos artículos con ejemplos que nos ayuden a comprenderlo mejor, el esfuerzo bien vale la pena. ¡Adiós VPD, bienvenido RAS!

Finalmente agradezco la ayuda de Jim Czuprynski y Thomas Minne para absolver mis dudas iniciales, y sin la cual esta tarea de aprendizaje hubiera tomado mucho más tiempo.

¿Te pareció interesante este artículo?, ¿te quedaron algunas dudas?, ¿quieres sugerirme un tema a tratar?, pues déjame tus comentarios o ¡contáctame ahora mismo!

Deja una respuesta

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

This site uses Akismet to reduce spam. Learn how your comment data is processed.

Posts Relacionados

new: dbms_sqldiag.report_sql
Desde Oracle 19.28, el uso de IF [NOT] EXISTS y la función REPORT_SQL del paquete DBMS_SQLDIAG ya están disponibles Aprende todo sobre ellos.
email ACL rules
¿Acabas de hacer upgrade o de implementar nueva funcionalidad y te topaste con el error ORA-24247? Aprende cómo resolverlo en un solo paso.
MView - Current
Si observas logs de vista materializada inmensos, debes seguir estos pasos para diagnosticar el problema y depurar sus contenidos.

¿Necesitas Ayuda?

Completa estos datos y estaré en contacto a la brevedad.