Pull to refresh

Oracle 12c Data Redaction. Сокрытие информации от непривилегированных пользователей

Reading time 4 min
Views 14K
Задача разделения доступа к данным в информационных системах возникает всегда. Так или иначе ее нужно решать. Если доступ к базе данных возможен только из сервера приложений, то можно возложить эту обязаннасть на него. Но почти всегда есть потребность прямого доступа к данным, например для аналитиков или персонала поставщика системы.
В статье рассматривается возможность частичного сокрытия информации, доступ к которой строго ограничен. Тут же вспоминаем про 152-ФЗ.

Начиная с версии 8 в Oracle Database существует Virtual Private Database (VPD). VPD позволяет, в зависимости от разных условий, скрывать часть записей таблицы или view от пользователей. Недостаток проистекает из достоинств — сокрытие возможно только полных записей. Делается это через добавление дополнительных условий в where часть sql запроса.

В Oracle 12c добавлена возможность изменять выдаваемые sql запросом значения полей (полностью или частично), в зависимости от условий. Эта возможность получила название Oracle Data Redaction и состоит в применении специальных policy.

Все управление Data Redaction сосредоточено в пакете DBMS_REDACT. Важно то, что для применения policy к объекту, не нужно иметь какого либo доступа к самому объекту. Пользователи обладающие привилегией EXEMPT REDACTION POLICY не подпадают под действие данного механизма.

Рассмотрим использование Data Redaction на следующем примере:

Есть таблица CLIENT_INFO содержащая персональные данные — дату рождения, телефон, email и номер кредитной карты. В базе существует роль R_VIP, обладатели которой видят полную информацию. Остальные должны видеть отредактированный вариант.
таблица и тестовая запись
CREATE TABLE CLIENT_INFO 
(
  ID NUMBER,
  F_NAME VARCHAR2(64), /* фамилия*/
  NAME VARCHAR2(64),  /* имя */
  S_NAME VARCHAR2(64), /* отчество */ 
  BIRTHDAY DATE, /* дата рождения */
  PHONE VARCHAR2(32), /* телефон */
  EMAIL VARCHAR2(64), /* email */
  CCARD VARCHAR2(32), /* номер кредитной карты */
  CONSTRAINT "CLIENT_INFO_PK" PRIMARY KEY ("ID")
);
insert into CLIENT_INFO values(1, 'Иванов', 'Иван', 'Иванович', to_date('15-05-1986', 'DD-MM-YYYY'), '79763334589', 'ivan@dom2.ru', '5767881897856776');

Создаем policy под названием redact_client_info и добавляем правило, по которому день в дате рождения заменяется на 1ое число:
policy
BEGIN
DBMS_REDACT.ADD_POLICY(
     object_schema        => 'TEST',
     object_name          => 'CLIENT_INFO',
     column_name          => 'BIRTHDAY',
     policy_name          => 'redact_client_info',
     function_type        => DBMS_REDACT.PARTIAL, /* Частичное маскирование */
     function_parameters  => 'Md01Y', /* Маска изменений */
     expression           => 'SYS_CONTEXT(''SYS_SESSION_ROLES'',''R_VIP'') = ''FALSE''' /* Условие - замена при отсутствии роли R_VIP */
);
END;
/

Добавляем в policy условие показывать только 5 последних цифр телефона:
policy
BEGIN
DBMS_REDACT.ALTER_POLICY(
     object_schema         => 'TEST',
     object_name           => 'CLIENT_INFO',
     column_name           => 'PHONE',
     policy_name           => 'redact_client_info',
     function_type         => DBMS_REDACT.REGEXP, /* Маскирование с помощью регулярного выражения */
     regexp_pattern	   => '\d+(\d{5})$', 
     regexp_replace_string => '******\1',
     regexp_position       => DBMS_REDACT.RE_BEGINNING,
     regexp_occurrence     => DBMS_REDACT.RE_ALL,
     expression            => 'SYS_CONTEXT(''SYS_SESSION_ROLES'',''R_VIP'') = ''FALSE''',
     action                => DBMS_REDACT.ADD_COLUMN
);
END;
/

Добавляем в policy условие скрывать домен email:
policy
BEGIN
DBMS_REDACT.ALTER_POLICY(
     object_schema         => 'TEST',
     object_name           => 'CLIENT_INFO',
     column_name           => 'EMAIL',
     policy_name           => 'redact_client_info',
     function_type         => DBMS_REDACT.REGEXP,
     regexp_pattern	   => DBMS_REDACT.RE_PATTERN_EMAIL_ADDRESS, /* Используем готовое */
     regexp_replace_string => DBMS_REDACT.RE_REDACT_EMAIL_DOMAIN, /* Используем готовое */
     expression            => 'SYS_CONTEXT(''SYS_SESSION_ROLES'',''R_VIP'') = ''FALSE''',
     action                => DBMS_REDACT.ADD_COLUMN
);
END;
/

Ну и наконец маскируем номер кредитки:
policy
BEGIN
DBMS_REDACT.ALTER_POLICY(
     object_schema         => 'TEST',
     object_name           => 'CLIENT_INFO',
     column_name           => 'CCARD',
     policy_name           => 'redact_client_info',
     function_type         => DBMS_REDACT.REGEXP,
     regexp_pattern	   => DBMS_REDACT.RE_PATTERN_CC_L6_T4,
     regexp_replace_string => DBMS_REDACT.RE_REDACT_CC_MIDDLE_DIGITS,
     expression            => 'SYS_CONTEXT(''SYS_SESSION_ROLES'',''R_VIP'') = ''FALSE''',
     action                => DBMS_REDACT.ADD_COLUMN
);
END;
/

Выполняем select от пользователя не имеющего роли R_VIP:
SQL> select f_name, name, birthday, email, phone, ccard from test.client_info;

F_NAME	   NAME       BIRTHDAY	      EMAIL	      PHONE	      CCARD
---------- ---------- --------------- --------------- --------------- ----------------
Иванов     Иван       01-MAY-86       ivan@xxxxx.com  ******34589     576788XXXXXX6776

И от пользователя обладающего этой ролью:
SQL> select f_name, name, birthday, email, phone, ccard from test.client_info;

F_NAME	   NAME       BIRTHDAY	      EMAIL	      PHONE	      CCARD
---------- ---------- --------------- --------------- --------------- ----------------
Иванов     Иван       15-MAY-86       ivan@dom2.ru    79763334589     5767881897856776

В результате в запросах видны все записи, но значения полей, для пользователя не имеющего роли R_VIP, изменены.

Плюс использования Data Redaction в сохранении семантики данных, но при этом непригодности конкретных значений для несанкционированного использования.

Важное дополнение


В процecce дальнейшего обсуждения и исследования работы этого механизма выяснилось, что данные маскируются только при выводе. То есть в части where используются открытые данные. И как результат — можно получить все открытые данные подставив свою функцию в where:
create table log (s varchar2(128));

create or replace function f(ds varchar2) return varchar2 as
  PRAGMA AUTONOMOUS_TRANSACTION;
 begin
  insert into log values(ds);
  commit;
  return ds;
 end;

SQL> select name, email from client_info c where f(email) like '%dom%';
NAME	   EMAIL
---------- ---------------
Иван       ivan@xxxxx.com

SQL> select * from log;

S
----------------
ivan@dom2.ru
ivan@gmail.com

Tags:
Hubs:
+22
Comments 20
Comments Comments 20

Articles