Базы данных Oracle - статьи

         

Более сложная логика


Описанная выше простейшая в рамках Label Security логика разграничения доступа к отдельным строкам допускает развитие в нескольких направлениях.



Более сложная структура метки доступа


Важным развитием является возможность формировать метку доступа более сложным путем, нежели чем на основе только уровня секретности. В состав метки можно еще включать следующие компоненты: Разделы данных (compartment). Позволяют сгруппировать данные по категориям с общим режимом доступа, например «административные данные», «финансовые данные», «операционные данные». Группы безопасности пользователей (security groups). Позволяют сгруппировать пользователей данных по принципу общих правил доступа, например «главное управление», «южное отделение», «северное отделение», «предприятие X», «предприятие Y». В отличие от разделов данных группы пользователей могут формировать древовидную подчиненность.

И разделы данных, и группы пользователей описываются тройками

<номер, краткое название, развернутое название> ,

однако в отличие от уровня секретности номера здесь не несут никакого прикладного смысла и выбираются произвольно.

Таким образом в общем случае метка доступа может иметь вид:

уровень: раздел1, раздел2, ...: группа1, группа2 ...

В версии 10.1 информацию об уровнях, разделах данных и группах пользователей стало возможным помещать в сервер имен OID/LDAP, что существенно для ценности самого подхода.

В отличие от простых меток, соответствующих уровням доступа, составные метки имеют более сложные правила упорядочения, но и обеспечивают большую функциональность.



Другие усложнения


К числу других усложнений и дополнений описанной логики можно отнести инверсные группы, возможность отменять фильтрацию строк метками, возможность создания «доверительных» программных единиц, обращающихся к защищенным строкам; возможность указывать для таблицы, снабжаемой столбцом с меткой безопасности, дополнительный предикат отбора строк по своему желанию, а также ряд других возможностей.



К каждой строке охранника приставишь!


, преподаватель технологий Oracle

Одним рассказ казался длинен,
другие утверждали, что он слишком краток,
большинство же, как всегда, скромно молчало...

М. Горький. Публика

Одни утверждали, что он [дом] трехэтажный,
другие – что четырех.

А. и Б. Стругацкие. Град обреченный



Метки сеанса


Использовавшаяся выше процедура:

SA_USER_ADMIN.SET_USER_LABELS

приписывает метку доступа пользователю. На деле это не совсем так: она приписывает метку сеансам, порождаемым от имени этого пользователя. По ходу сеанса связи с СУБД эту метку сеанса можно изменить процедурой SA_SESSION.SET_LABEL, правда в пределах, которые есть возможность указать процедурой SA_USER_ADMIN.SET_USER_LABELS, что в примере выше не использовалось.



Политика безопасности


Создадим политику безопасности, регулирующую доступ пользователей EMPLOYEE и HEAD к номерам телефонов. Она будет базироваться на значении метки в столбце EMPSEC_LABEL, который мы специально добавим в таблицу телефонов позже:

CONNECT lbacsys/lbacsys

BEGIN SA_SYSDBA.CREATE_POLICY (POLICY_NAME => 'EMPSEC_POLICY' , COLUMN_NAME => 'EMPSEC_LABEL'); END; /

Отключить, снова включить и удалить политику безопасности можно процедурами DISABLE_POLICY, ENABLE_POLICY и DROP_POLICY.

Определим в рамках политики безопасности два уровня секретности:

BEGIN SA_COMPONENTS.CREATE_LEVEL (POLICY_NAME => 'EMPSEC_POLICY' , LEVEL_NUM => 1000 , SHORT_NAME => 'OPEN' , LONG_NAME => 'Open level');

SA_COMPONENTS.CREATE_LEVEL (POLICY_NAME => 'EMPSEC_POLICY' , LEVEL_NUM => 2000 , SHORT_NAME => 'LIMITED' , LONG_NAME => 'Limited level'); END; /

Каждый уровень задается тройкой

<номер, краткое название, развернутое название>.

Номер носит технический характер, а развернутое название – характер комментария (но до 30 символов).

Предполагается, что создаваемые уровни линейно упорядочены в соответствии с номером. Содержательно за уровнем удобно видеть степень секретности, причем чем выше степень секретности («открытые данные» - «ограниченный доступ» - «секретно» ...), тем больший нужно связать с ней номер.



Приписываем метки доступа пользователям


Пользователю EMPLOYEE дадим право доступа только к «открытым» строкам, а пользователю HEAD – к «открытым» и «ограниченного пользования»:

BEGIN SA_USER_ADMIN.SET_USER_LABELS (POLICY_NAME => 'EMPSEC_POLICY' , USER_NAME => 'EMPLOYEE' , MAX_READ_LABEL => 'OPEN');

SA_USER_ADMIN.SET_USER_LABELS (POLICY_NAME => 'EMPSEC_POLICY' , USER_NAME => 'HEAD' , MAX_READ_LABEL => 'LIMITED'); END; /



Приписываем метки доступа строкам


Для этого сначала «применим политику доступа» к таблице PHONE в целом:

BEGIN SA_POLICY_ADMIN.APPLY_TABLE_POLICY (POLICY_NAME => 'EMPSEC_POLICY' , SCHEMA_NAME => 'SCOTT' , TABLE_NAME => 'PHONE' , TABLE_OPTIONS => 'LABEL_DEFAULT, READ_CONTROL, WRITE_CONTROL'); END; /

В результате она автоматически окажется пополнена столбцом EMP_DATA_LABEL, указанным в самом начале для нашей политики EMPSEC_POLICY. Его можно легко наблюдать командой SQL*Plus DESCRIBE. Если же в параметре TABLE_OPTIONS в число свойств через запятую включить HIDE, новый служебный столбец обычным пользователям виден не будет. В этом же параметре свойство WRITE_CONTROL можно для нашего случая спокойно опустить.

Теперь появилась возможность разметить конкретными метками конкретные строки:

UPDATE scott.phone SET emp_data_label = CHAR_TO_LABEL('EMPSEC_POLICY', 'OPEN');

UPDATE scott.phone SET emp_data_label = CHAR_TO_LABEL('EMPSEC_POLICY', 'LIMITED') WHERE empno IN (SELECT empno FROM scott.emp WHERE job IN ('MANAGER', 'PRESIDENT'));

Вместо обращения к функции

CHAR_TO_LABEL('EMPSEC_POLICY', 'OPEN')

можно было сразу указать число 10000, номер метки, так как именно число будет храниться в поле метки фактически; аналогично же и во второй команде UPDATE. Однако для содержательной ясности удобнее воспользоваться функцией.



Простой пример


Данные для регламентируемого доступа

Пусть у сотрудников из таблицы EMP имеются телефоны: CONNECT scott/tiger

CREATE TABLE phone AS SELECT empno FROM emp;

ALTER TABLE phone ADD (pno VARCHAR(20));

ALTER TABLE phone ADD PRIMARY KEY (empno, pno);

UPDATE phone SET pno = TRUNC(dbms_random.value(100, 999)) '-' TRUNC(dbms_random.value(1000, 9999));

Какие-то из них будут общедоступны, а какие-то нет, но об этом будет сообщено позже.

Заведем пользователей Oracle, представляющих сотрудника и административное лицо. Дадим им минимум необходимых для данного примера полномочий:

CONNECT / AS SYSDBA

CREATE USER employee IDENTIFIED BY employee;

CREATE USER head IDENTIFIED BY head;

CREATE ROLE minimal;

GRANT CREATE SESSION TO minimal;

GRANT SELECT ON scott.phone TO minimal;

GRANT SELECT ON scott.emp TO minimal;

GRANT minimal TO employee, head;

GRANT UPDATE ON scott.phone TO lbacsys;

GRANT UPDATE ON scott.emp TO lbacsys;



Проверяем, как работает


SQL> CONNECT employee/employee Connected. SQL> SELECT ename, pno 2 FROM scott.emp LEFT OUTER JOIN scott.phone 3 USING (empno);

ENAME PNO ---------- -------------------- SMITH 409-2351 ALLEN 625-1171 WARD 506-9715 MARTIN 108-8113 SCOTT 187-3972 TURNER 609-2430 ADAMS 421-3324 JAMES 550-4204 FORD 713-9878 MILLER 924-5401 KING CLARK BLAKE JONES

14 rows selected.

SQL> SAVE phones REPLACE Wrote file phones.sql SQL> CONNECT head/head Connected. SQL> /

ENAME PNO ---------- -------------------- SMITH 409-2351 ALLEN 625-1171 WARD 506-9715 JONES 600-1573 MARTIN 108-8113 BLAKE 738-6815 CLARK 650-1728 SCOTT 187-3972 KING 393-8155 TURNER 609-2430 ADAMS 421-3324 JAMES 550-4204 FORD 713-9878 MILLER 924-5401

14 rows selected.

Упражнение. Выдать вслед за предыдущим:

CONNECT scott/tiger / CONNECT lbacsys/lbacsys /

Объяснить результаты.

Пусть теперь в рамках той же политики EMPSEC_POLICY часть сотрудников также секретны, например сотрудники отдела разработок RESEARCH:

BEGIN SA_POLICY_ADMIN.APPLY_TABLE_POLICY (POLICY_NAME => 'EMPSEC_POLICY' , SCHEMA_NAME => 'SCOTT' , TABLE_NAME => 'EMP' , TABLE_OPTIONS => 'LABEL_DEFAULT, READ_CONTROL'); END; /

UPDATE scott.emp SET EMPSEC_LABEL = CHAR_TO_LABEL('EMPSEC_POLICY', 'OPEN');

UPDATE scott.emp SET EMPSEC_LABEL = CHAR_TO_LABEL('EMPSEC_POLICY', 'LIMITED') WHERE deptno IN (SELECT deptno FROM scott.dept WHERE dname = 'RESEARCH');

Новая проверка:

SQL> CONNECT employee/employee SQL> @phones

ENAME PNO ---------- -------------------- ALLEN 625-1171 WARD 506-9715 MARTIN 108-8113 TURNER 609-2430 JAMES 550-4204 MILLER 924-5401 BLAKE KING CLARK

9 rows selected.

SQL> CONNECT head/head SQL> /

ENAME PNO ---------- -------------------- SMITH 409-2351 ALLEN 625-1171 WARD 506-9715 JONES 600-1573 MARTIN 108-8113 BLAKE 738-6815 CLARK 650-1728 SCOTT 187-3972 KING 393-8155 TURNER 609-2430 ADAMS 421-3324 JAMES 550-4204 FORD 713-9878 MILLER 924-5401

14 rows selected.



в Oracle позволяет регламентировать доступ


Механизм virtual private database (VPD) в Oracle позволяет регламентировать доступ к частям таблицы, но использует для этого весьма примитивную систему понятий. В версии 8.1.7 в Oracle появилось другое средство, Label Security, система понятий которого более продумана и лучше приспособлена под задачи защиты частей таблицы. Технически оно опирается на VPD, но реализует подход, известный в ИТ под названием «мандатного управления доступом», регулирующим в данном случае доступ к отдельным строкам таблиц разным категориям пользователей. Реализация соответствует .
Каждая строка нужной таблицы помечается специальной меткой, допускающей впоследствии изменение. Пользователям выдается разрешение работать со строками, помеченными только определенными метками. При разборе запроса к таблице СУБД выполнит проверки обычных полномочий доступа (выдаваемых командой GRANT), а при выполнении запроса отфильтрует из таблицы только строки со значениями меток, разрешенными для пользователя. Логически это выглядит как автоматическое добавление предиката AND P(метка_строки, контекст_сеанса) в конструкцию WHERE запроса. В конечном счете не предназначенные для него строки пользователь не сможет ни увидеть, ни изменить.
Label Security – дополнительная возможность Oracle. Для того, чтобы ею пользоваться, нужно (а) завести при установке ПО СУБД Oracle нужные компоненты и (б) завести необходимые структуры в БД. Первое делается установщиком OUI, а второе – конфигуратором БД DCA или простым прогоном сценария catols.sql из каталога $ORACLE_HOME/rdbms/admin. Наблюдаемый результат – новый пользователь LBACSYS и целая серия принадлежащих ему объектов, включая пакеты, имена которых начинаются с 'SA_'.

Замечания по технологии


Для работы с размеченными строками разумно завести специального административного пользователя в дополнение к LBACSYS и передать ему необходимые полномочия для работы с объектами LBACSYS.

Справочную информацию о заведенных метках, группах и т. д. можно получить из справочных таблиц пользователя LBACSYS: DBA(USER)_SA_POLICIES, DBA_SA_LEVELS , DBA_SA_LABELS и др.

Для удобства работы в состав ПО Oracle включена программа Policy Manager с графическим интерфейсом.



Заводим метки доступа


Заданные в рамках политики EMPSEC_POLICY уровни секретности используем для формирования меток доступа. Именно метки и будут регламентировать доступ к строкам таблицы. В нашем простейшем случае каждая метка соответствуют попросту уровню секретности:

BEGIN SA_LABEL_ADMIN.CREATE_LABEL (POLICY_NAME => 'EMPSEC_POLICY' , LABEL_TAG => 10000 , LABEL_VALUE => 'OPEN' , DATA_LABEL => TRUE);

SA_LABEL_ADMIN.CREATE_LABEL (POLICY_NAME => 'EMPSEC_POLICY' , LABEL_TAG => 20000 , LABEL_VALUE => 'LIMITED' , DATA_LABEL => TRUE); END; /

Номер метки LABEL_TAG имеет технический смысл, выбирается произвольно и безотносительно к номерам уровней. Его даже можно порождать автоматически специальной функцией TO_DATA_LABEL. Значение TRUE для DATA_LABEL указывает, что меткой можно будет помечать строки таблиц.



Литература


1.Настройка систем баз данных с помощью анализа событий ожидания
2.Стив Адамс, “Отпечатки пальцев”
3.Э.Таненбаум “Современные операционные системы”, СПб., Питер, 2006.
4.Том Кайт, “Oracle для профессионалов”, Киев, Диасофт, 2004.
5.J. Mauro, R. McDougal, Solaris Internals, Sun Microsystems Press, 2000.
6.http://metalink.oracle.com, Note 22908.1, “What are Latches and What Causes Latch Contention”, 2004.
7.Doug Lea, “Cookbook for Compiler Writers”
8.David A. Patterson and John L. Hennessy, “Survey of RISC Architectures”
9.http://www.princeton.edu/~psg/unix/Solaris/troubleshoot/lockcontend.html, Princeton University
10.J. Kim, E. Nurvitadhi, E. Chung, “Opportunity of Hardaware-Based Optimistic Concurency in OLTP”, Computer Architecture Laboratory of Carnegie Mellon University.



Механизм разблокировки


Поскольку блокирующий процесс не оповещает остальные процессы об освобождении блокировки, то ожидание освобождения блокировки может происходить только двумя способами:

непрерывно выполнять команду TSL в цикле с тем, чтобы непрерывно опрашивать значение переменной; с помощью системного вызова sleep(time) процесс может на некоторое время “заснуть” и тем самым освободить процессор, отдав управление в ОС, чтобы она “разбудила” этот процесс по истечении периода time. Sleep() – это системный вызов, в результате которого вызывающий процесс снимается с процессора и переводится в неактивное состояние на время указанное в параметре этой команды. Wakeup(pid) – обратная к sleep команда, которой на вход передается один параметр – pid процесса, который следует запустить на ЦПУ.

Первый способ, очевидно, является достаточно затратным, с точки зрения потребления ресурсов ЦПУ, потому что он загружает холостой работой все процессоры, на которых выполняются процессы, запрашивающие блокировку. Достоинство spin-подхода в том, что в этом случае отсутствует простой процесса (процесс получает блокировку сразу же, как только она освободится). Кроме того, отсутствует переключение контекста (переключение процессора с одного процесса на другой). Переключение контекста является длительной операцией, поскольку требует сохранения контекста текущего процесса (сохранение регистров процессора в стеке), загрузки нового контекста (загрузки в регистры процессора значений нового процесса). Кроме того, новый процесс начнет выполнение с непопадания в кеш, потому что кеш хранит данные старого процесса.

Второй способ является более экономным для ЦПУ, но время ожидания освобождения блокировки здесь будет больше. Достоинство второго подхода в том, что занятый процессор освобождается и может быть загружен полезной работой, но взамен происходит переключение контекста, что долго и дорого.

В общем, жертвовать придется всегда, либо общей производительностью ЭВМ, либо временем отдельного процесса, и главная задача здесь оптимальным образом сбалансировать запросы на блокировки, выполняемые тем или другим способом.



Механизмы блокирования


Искомое решение требует участия аппаратного обеспечения. Процессоры многопроцессорных ЭВМ имеют специальную команду, которая в разных источниках называется TSL (Test and Set Lock), CAS (Compare and Swap) или LL/SC (Load Link /Store Conditions).

Процессор, выполняющий эту команду, блокирует шину памяти так, чтобы остальные процессоры не могли обратиться к оперативной памяти, и затем выполняет команду ‘test’, читая соответствующую ячейку памяти. Если возвращаемое значение равно нулю (false), то это значит, что переменная свободна, и процессор выполняет команду ‘set’, которая записывает в эту переменную значение 1 (true), и шина памяти разблокируется. Освобождение блокировки выполняется путем записи 0 (false) в переменную блокировки.

Если другой процессор позже попытается запросить блокировку, то команда ‘test’ возвратит ему значение 1 (true), означающее, что блокировка уже установлена. В этом случае второму процессу придется подождать некоторое время, а затем снова запросить блокировку. При выполнении каждой TSL-команды происходит блокирование шины ЭВМ.

Таким образом, команда типа TSL аппаратно обеспечивает неделимость обращения к переменной блокировки, ибо процесс может быть снят с выполнения либо до начала команды, либо после ее окончания. В результате чего блокировки СУБД ORACLE спускаются на уровень аппаратного обеспечения и блокируют шину ЭВМ. Блокирование шины сервера означает, что во время выполнения команды TSL все остальные процессоры и процессы не могут получить доступ к оперативной памяти и вынуждены ждать завершения операции (однако они могут обращаться к данным в своем локальном кеше).

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



Проблема


В общем случае блокировка - это некая булевская переменная, которая показывает, что ресурс свободен или занят. Если значение переменной 0 (false), то это означает, что блокировка свободна и любой процесс может изменить ее значение на 1 (true), а затем обращаться к защищаемому ресурсу. Если значение блокировки true, то процессу следует подождать, поскольку кто-то еще пользуется этим ресурсом.

Вопрос: можно ли программным путем гарантированно заблокировать ресурс?

Ответ: нет, невозможно! Например, два процесса могут одновременно опросить одну и ту же переменную и, убедившись, что ее значение равно 0, установят ее значение в 1. Такой сценарий не редкость в многопроцессорных ЭВМ.

Возможен и другой сценарий. Допустим, что один процесс считывает значение переменной блокировки и обнаруживает, что она равна 0. Но прежде, чем первый процесс успевает изменить ее на 1 (отвлекся на обработку прерывания или был снят с процессора по истечении отведенного ему кванта времени), управление получает второй процесс, который тоже считывает значение переменной блокировки и изменяет ее на 1. Когда первый процесс снова получит управление, он тоже заменит переменную блокировки на 1, и оба процесса будут считать себя исключительными владельцами ресурса.

Таким образом, надежного программного решения, которое исключало бы одновременный доступ, не существует.



Способы оптимизации


По большому счету, присутствие блокировок в программном обеспечении – это дань одномерной архитектуре и однопроцессорному мышлению. Принципиально изменить ситуацию может только изобретение новых принципов функционирования и архитектуры ЭВМ. Возможно ЭВМ, специализированных под обработку баз данных и конкретные СУБД. Идея аппаратного ускорителя для СУБД Oracle уже назрела и требует своей реализации.

Возможно, в будущем мы можем стать свидетелями появления нового процесса в Oracle типа “прогнозировщик блокировок”, а также нового узла в ЦПУ, целью которого является динамически идентифицировать начало и конец критической секции кода, выполняемой под блокировкой. Идея этого метода в том, продолжать вычисления, не обращая внимания на блокировку. Ключевым моментом здесь является то, что значительная часть исполнимого кода может не зависеть от значения этой переменной блокировки. И после каждой выполненной команды (или после каждой сотни команд) можно будет опрашивать переменную блокировки. Таким образом, запрашивающий процесс будет выполнять не холостой цикл, а полезную работу. В результате влияние блокировок на производительность будет минимальным. Главная задача при этом состоит в определении в исполнимом коде процесса максимально длинной последовательности команд, которые можно безопасно выполнить независимо от значения блокировки.

Поскольку за каждым запросом блокировки следует ее отмена по тому же адресу, то команды запроса блокировки и ее отмены всегда существуют парами и обращаются к одному и тому же адресу. Таким образам эта пара:

идентифицирует диапазон команд, которые выполняются под блокировкой. Эти команды могут быть выполнены в другом месте и проверены позднее, чтобы гарантировать их неизменность; идентифицирует набор команд, выполняющихся независимо от значения переменной блокировки. Эти команды могут быть выполнены вообще независимо от значения блокировки.

Таким образом, узел ЦПУ - “прогнозировщик блокировок” будет динамически идентифицировать начало и конец критической секции с целью оптимизации блокирования.


Вторым по порядку и простейшим “тупым” методом повышения быстродействия, на мой взгляд, является создание отдельной быстрой шины и выделенной только под переменные блокировки памяти. Тогда блокировки можно будет быстро ставить и быстро снимать, а все остальные процессоры будут обращаться к общей памяти по неблокируемой шине.

В ОС Solaris, начиная с 7 версии, появилось понятие adaptive mutex. В этом случае процесс, прежде чем запросить блокировку, проверяет, удерживает ли ее какой-нибудь другой процесс, и если да, то первый процесс проверяет, находится ли удерживающий процесс на процессоре или нет. Если процесс, удерживающий блокировку, выполняется на процессоре, то запрашивающий процесс переходит к стандартному алгоритму – в цикле выполняет TSL. Если удерживающий процесс не находится на процессоре, то запрашивающий процесс освобождает свой процессор и переходит в состояние ожидания.

В качестве еще одной оптимизации в ОС Solaris процессу, удерживающему блокировку, дается дополнительный квант процессорного времени, чтобы он как можно быстрее освободил эту блокировку. Используются ли эти технологии в СУБД Oracle мне пока не известно.

Справедливым возражением, относительно единого и универсального параметра _spin_count будет сказать, что не все блокировки одинаково длительны. Для того чтобы правильно настроить ожидание за защелку, следует знать особенности каждой конкретной защелки, то есть разные защелки могут иметь существенно разное среднее время удержания. В связи с этим вызывает сомнение, что один единственный параметр будет достаточным для всех защелок в СУБД. На месте разработчиков Oracle я бы, вероятно, для каждой защелки (или для каждого класса защелок с похожим поведением) определил свой параметр _spin_count.


Управление поведением


Поведением процесса, запрашивающего блокировку, управляет параметр _spin_count. Если запрашиваемая блокировка занята, то процесс повторяет запросы на защелку в цикле _spin_count раз, после чего засыпает на 1/100 секунды, после чего опять опрашивает и опять засыпает и т.д., причем в каждом следующем периоде длительность интервала удваивается.

Документация по СУБД Oracle не дает информации о вычислении значения _spin_count. Для версий 8, 9 и 10 это значение равно 2000. Интересно, как получено это значение?

Методики анализа блокировок могут быть статическими (анализ кода), либо динамическими (анализ работы программы).

С точки зрения исследования исполняемого кода, если разработчики СУБД Oracle знают, что процесс, работающий под защитой блокировки, в среднем выполняет не более Х команд, то и параметр _spin_count можно было бы установить в значение, покрывающее этот промежуток. Однако реально сложно однозначно сказать, сколько времени займет выполнение 2000 команд. Команды процессора различаются по длительности, иногда довольно сильно, от нескольких тактов, например, инкремент – 1 такт, до нескольких десятков – деление требует 42 такта.

Удовлетворительный результат можно получить только в том случае, если только код, выполняющийся под блокировкой, специально написан так, чтобы он выполнялся за время, не большее, чем _spin_count. А еще и прерывания оказывают свою роль. В общем, статический механизм дает очень приблизительное значение для _spin_count.

Перейдем к динамике. Если установить для этой переменной слишком высокое значение, то процессоры сервера будут перегружены холостой работой. Если _spin_count установить слишком низким, например 1, то после одной неуспешной попытки процессы, запрашивающие защелку, будут приостанавливаться (уходить в сон), и в результате процесс потеряет много времени на ожидание.

Очевидными границами здесь являются переключение контекста и среднее время использования защелки. Для того чтобы снять процесс с процессора, а затем возвратить процесс на процессор, требуется некоторое количество команд и соответствующее для их выполнения время. Поэтому _spin_count можно безопасно выставить на время, которое меньше или равно длительности двух переключений контекста. Если среднее время ожидания защелки меньше, чем два переключения контекста, то для сервера дешевле выполнить spin. Если среднее время ожидания освобождения защелки больше, чем два переключения контекста, то дешевле снимать процесс с процессора, а затем вернуть обратно.

Насколько я понимаю, СУБД Oracle исходит из той предпосылки, что защелки захватываются ненадолго. Поэтому факторами, отрицательно сказывающимися на успешном получении защелки, являются:

высокая загрузка сервера. Если процесс, завладевший защелкой, выполняется на загруженном процессоре и снимается с процессора, не освободив ее, то конкуренция за такую защелку будет весьма высокой. продолжительный захват защелки. Чаще всего это происходит из-за того, что структура памяти (связный список), защищенный конкретной защелкой, стал слишком длинным. Пример – список свободных участков памяти внутри buffer cache, shared pool.



Влияние на производительность


О необходимости борьбы с hard & soft parse и использования связываемых переменных написаны сотни статей. Мотивировка: в ожидании блокировки сессии выстраиваются в очередь и простаивают до ее освобождения. А также: использование литералов приводит к конкуренции пользовательских сессий за shared pool latch и library cache latch. Однако негативные последствия процесса hard & soft parse на этом не заканчиваются.

Механизм блокирования системной шины фактически замораживает функционирование сервера на короткий период времени. А это означает, что если теоретически в одном часе имеется 3600 секунд, то в результате блокирования шины сервер фактически функционирует не 3600 секунд в час, а 3599, 3598, … и, возможно, менее. То есть, слишком часто блокируемый сервер работает не все отведенное для работы время. Причем частота блокирования растет пропорционально количеству процессоров и процессов. В результате чего добавление очередного процессора может не приводить к увеличению производительности всего сервера в целом.

Блокировки используются в СУБД Oracle во многих случаях, например:

для управления буферным кешем. Блокировки вызываются при вставке/удалении/перемещении блока в кеше. Если учесть, что кешей может быть пять штук (2k,4k,8k,16k,32k), в каждом по три типа (Default, Keep,Recycle), поэтому для всех 15 областей памяти потребуется до 30 блокировок, по две блокировки на кеш; для управления журнальным буфером: минимум по две блокировки на каждый log_buffer (2*log_parallelism); для управления Library Cache & Shared Pool: 16 блокировок на library cache lock + 26 блокировок на library cache pin. (В одном отчете Statspack мне пришлось увидеть такую картину “Hard parses: 12.48/секунду” - очевидно, что высокой производительности от такой системы ждать не приходится). Особенно стоит отметить блокировки на library cache pin. Эта блокировка вызывается при каждом выполнении PL/SQL; 26 блокировок для выполнения операций над Row Cache;

блокировка на SCN;

блокировка на SMON;


блокировки на обращение к файлам БД ( по одной блокировке на файл данных);

блокировка на транзакцию над контрольным файлом;

блокировка, управляющая job (работами);

блокировка на выделение/удаление сегментов в табличных пространствах TEMP и UNDO;

блокировка на выполнение действий над файлом паролей и файлом инициализации (ALTER SYSTEM SET …).

Для полноты картины попробуем численно оценить влияние блокировок на производительность сервера, для чего рассмотрим типичный отчет Statspack, секцию “Latch Activity for DB”. Понятно, что этот расчет довольно приблизительный, но, на мой взгляд, довольно показательный.

У меня в наличии есть подходящий отчет для 16-процессорного сервера, частота каждого процессора которого составляет 1200МГц. Из отчета Statspack для этого сервера следует, что СУБД Oracle выполняет более 650 тысяч блокировок в секунду (точное значение 651801,9). По справочникам можно уточнить, что команды типа TSL для процессора UltraSparcIII - CASA и CASXA – требуют для своего выполнения 32 цикла. Тогда доля времени, в течение которого системная шина заблокирована, составит 651802*32/1200МГц = 0,0174, то есть 1,74% всего рабочего времени, другими словами 62,64 секунды в час.


Введение и основные понятия


Экземпляр Oracle состоит из ряда процессов, обращающихся к общим сегментам памяти (SGA и другие общедоступные ресурсы). Поэтому они могут испортить информацию друг друга. Следовательно, во многих случаях требуется обеспечить механизм, который при использовании одним процессом общедоступных ресурсов (например, участки памяти) запрещает другим процессам изменять эти данные. Таким механизмом в СУБД Oracle являются блокировки, то есть специальные переменные, показывающие, занят или свободен некоторый ресурс.

Обычно под понятием “блокировки” в сервере Oracle понимаются две сущности:

механизм управления одновременным доступом к таблицам и строкам данных (DML locks, DDL locks); внутренние блокировки сервера (enqueues, internal locks, latchs (защелки)), то есть механизм защиты сервером своих внутренних структур данных и блокирования общедоступных ресурсов.

В данной статье под блокировками будем понимать внутренние блокировки сервера и защелки.

В СУБД Oracle блокировки делятся на два непересекающихся класса: защелки (latch) и очереди (enqueues).

Защелки - это двоичные переменные, фактически переключатели-триггеры, которые применяются на короткое время и защищают структуры памяти. Защелка имеет только два состояния – занята или свободна. Наиболее известные из защелок – shared pool latch, library cache pin, library cache lock, cache buffer chains, redo allocation latch, redo copy.

Защелки в СУБД Oracle могут запрашиваться в двух режимах: “willing-to-wait” и “no-wait” (= immediate). Если процесс имеет возможность продолжать работу, не получив запрашиваемую защелку, то это запрос no-wait (например, redo copy latch). Если процесс не может продолжать работу, не получив запрашиваемую блокировку, то это режим willing-to-wait.

Среди процессов, запрашивающих защелку, не поддерживается очередность. Множество процессов, пытающихся получить защелку, образуют толпу процессов, запрашивающих защелку в случайные моменты времени. Вот как это происходит:

Если защелка свободна, то запрос на нее удовлетворяется. Конец. Если защелка занята, то процесс циклично повторяет запросы на защелку _spin_count раз. Если запрос удовлетворен, то Конец. Если запрос на защелку не удовлетворен, то процесс “засыпает” на 1/100 секунды, после чего переходит к п.2. Если запрос опять не удовлетворен, то в каждом следующем цикле длительность интервала удваивается, после чего следует переход к п.2.

В отличие от защелок, очереди запросов (enqueue) действительно образуют упорядоченную очередь FIFO. Каждый запрос в очереди, кроме порядкового номера, отражает еще и режим запроса (share, exclusive). Например, запросы на чтение могут выполняться одновременно, не блокируя друг друга. Если запрос на блокировку enqueue не может быть удовлетворен, то он ставится в очередь. Порядковые номера в очереди запрашиваются через системные вызовы ОС (семафоры).

С блокировками типов enqueues и latches всегда связана процедура, которая возвращает блокировку к предыдущему состоянию, если процесс, удерживающий блокировку, зависнет или аварийно завершится. В СУБД Oracle эту функцию выполняет процесс PMON.



В процессе работы над этой


В процессе работы над этой статьей мне попало в руки исследование Лаборатории Компьютерных Архитектур университета Карнеги Мелон [], в котором сравниваются СУБД Oracle и DB2 по активности блокировок, которая возникает в процессе работы. И в этом состязании СУБД Oracle показывает относительно неплохой результат: при одинаковой нагрузке в БД дополнительная активность, создаваемая блокировками в СУБД DB2, составляет 40% от системной (system) нагрузки и 18% от пользовательской (user), а в СУБД Oracle системное время выполнения только 20% и пользовательское время 12%.
Автор выражает благодарность сотруднику компании“Открытые технологии” Александру Иванову за внимание и полезные советы при подготовке данной статьи.
Автор будет признателен коллегам за разумные возражения и поправки относительно вопросов, затронутых в данной статье. Все мнения и замечания можно направлять автору на адрес ypudovchenko@ot.ru

Аннотация


Эта статья (как и несколько последующих) является непосредственным продолжением статьи «», и рассматривает некоторые особенности средства Label Security в Oracle. Здесь показана возможность секретить служебный столбец с метками доступа к строкам, а также рассмотрены некоторые правила правки меток. В первую очередь статья затрагивает использование параметра TABLE_OPTIONS процедуры APPLY_TABLE_POLICY из пакета SA_POLICY_ADMIN.



Исчезающий столбец


В предыдущей статье оговаривалось, что служебный столбец, добавленный в результате применения политики к таблице, можно скрыть от пользователей. Покажем, как это сделать. Выдадим в SQL*Plus:

@phonepolicyoptions 'read_control, hide'

Проверка:

SQL> COLUMN POLICY_NAME FORMAT A22 SQL> COLUMN SCHEMA_NAME FORMAT A12 SQL> COLUMN TABLE_NAME FORMAT A12 SQL> COLUMN TABLE_OPTIONS FORMAT A15 WORD SQL> SELECT policy_name, schema_name, table_name, table_options 2 FROM dba_sa_table_policies;

POLICY_NAME SCHEMA_NAME TABLE_NAME TABLE_OPTIONS ---------------------- ------------ ------------ -------------- EMPSEC_POLICY SCOTT EMP READ_CONTROL, LABEL_DEFAULT EMPSEC_POLICY SCOTT PHONE READ_CONTROL, HIDE

SQL> SAVE showoptions Создано file showoptions.sql SQL> CONNECT employee/employee Соединено. SQL> SELECT column_name FROM all_tab_columns 2 WHERE owner = 'SCOTT' AND table_name = 'PHONE';

COLUMN_NAME ------------------------------ EMPNO PNO

SQL> SAVE showcolumns Создано file showcolumns.sql SQL> CONNECT head/head Соединено. SQL> /

COLUMN_NAME ------------------------------ EMPNO PNO

Обратите внимание: столбец EMPSEC_LABEL стал невидим, то есть ни у хозяина таблицы, ни у пользователей не стало причин догадываться, что им предъявляется только часть строк ! Дополнительной скрытности добавляет то обстоятельство, что даже SYS не увидит скрытого столбца (проверьте это !) А вот для сравнения, что будет, если свойство HIDE «уберем» (то есть не укажем):

@phonepolicyoptions 'read_control'

Проверка:

SQL> CONNECT head/head Соединено. SQL> @showcolumns

COLUMN_NAME ------------------------------ EMPNO PNO EMPSEC_LABEL

Обратите внимание на то, что когда столбец EMPSEC_LABEL был невидим, это обстоятельство не помешало администратору Label Security (пользователю LBACSYS) обратиться к столбцу командой UPDATE scott.phone SET empsec_label = ... в файле phonepolicyoptions.sql.



Изучаем метки доступа к строкам: задание свойств столбца доступа в таблице


,

Преподаватель технологий Oracle

"Кому велено чирикать -

Не мурлыкайте!

Кому велено мурлыкать -

Не чирикайте!

Не бывать вороне коровою,

Не летать лягушатам под облаком!"

Корней Чуковский,
«Путаница»



Подготовка к работе


Предполагается, что имеются структуры и объекты, построенные в статье «», а именно:

первоначально отсутствовавшая в схеме SCOTT таблица PHONE, заполненная номерами телефонов для сотрудников из таблицы EMP два уровня секретности с краткими названиями 'OPEN' и 'LIMITED' политика меточного доступа под названием EMPSEC_POLICY и заданное в рамках этой политики название служебного столбца для меток доступа под названием EMPSEC_LABEL две разные метки, построенные на базе имеющихся уровней секретности, и которыми в рамках политики EMPSEC_POLICY помечены строки таблиц PHONE и EMP схемы SCOTT два пользователя EMPLOYEE и HEAD, отмеченные разными правами доступа к строкам

Чтобы все это построить в своей БД, достаточно шаг за шагом повторить действия из предыдущей статьи.

Для удобства работы в SQL*Plus подготовим несколько файлов. Файл phonepolicyoptions.sql:

CONNECT lbacsys/lbacsys

BEGIN SA_POLICY_ADMIN.REMOVE_TABLE_POLICY ( POLICY_NAME => 'empsec_policy' , SCHEMA_NAME => 'scott' , TABLE_NAME => 'phone' , DROP_COLUMN => TRUE

); END; /

BEGIN SA_POLICY_ADMIN.APPLY_TABLE_POLICY ( POLICY_NAME => 'empsec_policy' , SCHEMA_NAME => 'scott' , TABLE_NAME => 'phone' , TABLE_OPTIONS => '&1' ); END; /

UPDATE scott.phone SET empsec_label = CASE WHEN empno IN ( SELECT empno FROM scott.emp WHERE job IN ( 'MANAGER', 'PRESIDENT' ) ) THEN CHAR_TO_LABEL ( 'EMPSEC_POLICY', 'LIMITED' ) ELSE CHAR_TO_LABEL ( 'EMPSEC_POLICY', 'OPEN' ) END ;

В этом сценарии сначала из политики EMPSEC_POLICY исключается таблица SCOTT.PHONE, причем благодаря явно указанному значению параметра DROP_COLUMN => TRUE служебный (в рамках этой политики) столбец EMPSEC_LABEL также удалится. Затем политика применяется к таблице заново, а ее (новые, те, что нам требуется) свойства будем указывать через параметр для SQL*Plus. Поскольку служебный столбец в таблице PHONE воссоздается заново, его придется и заново заполнять, причем (обратите внимание !) ради удобства всегда однаково.

Другой файл, phones.sql, послужит для наблюдения результата:

SELECT ename, pno FROM scott.emp LEFT OUTER JOIN scott.phone USING (empno) /

Он взят из предыдущей статьи, но в силу своей краткости и ради ясности повторяется здесь.

Создадим также файл updateallen.sql:

UPDATE scott.phone SET empsec_label = CHAR_TO_LABEL ( 'empsec_policy', '&1' ) WHERE empno = ( SELECT empno FROM scott.emp WHERE ename = 'ALLEN' ) /

Несколько прочих сценарных файлов будет создано по ходу дела.

Выдадим в SQL*Plus:

SQL> SET VERIFY OFF

Можно ставить опыты.



Умолчательная реакция на изменение метки обычным пользователем


Метку секретности строки можно поменять, и администратор (пользователь LBACSYS) это уже делал. А вот что произойдет, если поменять значение метки попробуют «обычные» пользователи:

CONNECT / AS SYSDBA

GRANT INSERT, UPDATE, DELETE ON scott.phone TO minimal;

Проверка:

SQL> CONNECT head/head Соединено. SQL> @updateallen OPEN

1 строка обновлена.

SQL> @updateallen LIMITED

1 строка обновлена.

SQL> @updateallen LIMITED

1 строка обновлена.

SQL> @updateallen OPEN

1 строка обновлена.

Вывод: пользователь HEAD беспрепятственно изменяет метку строки, как ему угодно. А пользователь EMPLOYEE ?

SQL> CONNECT employee/employee Соединено. SQL> @updateallen OPEN

1 строк обновлено.

SQL> @updateallen OPEN

1 строк обновлено.

SQL> @updateallen LIMITED

1 строк обновлено.

SQL> @updateallen LIMITED

0 строк обновлено.

SQL> @updateallen OPEN

0 строк обновлено.

Очевидно, как только метка становится LIMITED, пользователь EMPLOYEE теряет возможность ее изменять, а когда значение метки - OPEN, он такую возможность имеет. Он даже может сделать строку более секретной, но тогда потеряет к ней доступ.



рассказывалось об одном из двух


В статье «» рассказывалось об одном из двух способов регулировать доступ к отдельным частям таблиц в Oracle, а именно о Label Security. Label Security есть реализация фирмой Oracle меточного, или мандатного метода доступа, известного специалистам по защите данных. Описание Label Security в документации Oracle имеет характерный справочный характер, что в данном случае можно считать обоснованным, так как самый мандатный доступ не придуман фирмой (аналогично тому, как фирма Oracle не придумала SQL или, скажем, JDBC). В то же время описание мандатного доступа, на которое ссылается фирма Oracle, , весьма непросто для восприятия специалистом по БД. Несколько более понятны тексты на русском языке, подготовленные , однако и они рассчитаны в первую очередь на специалистов по безопасности.
Настоящая работа предназначена показать некоторые возможности Label Security именно администраторам и разработчикам на Oracle, а главное - помочь им в самостоятельном изучении этой темы. Без последнего, увы, не обойтись: даже выборочное рассмотрение меточного доступа на простых примерах потребовало сразу серии статей !
Эта статья (как и несколько последующих) является непосредственным продолжением упомянутой выше. Здесь будет показана возможность секретить служебный столбец с метками доступа к строкам, а также рассмотрены некоторые возможности правки конкретных меток. В первую очередь статья сосредотачивается на использовании параметра TABLE_OPTIONS процедуры APPLY_TABLE_POLICY из пакета SA_POLICY_ADMIN.

Запрет делать то, результат чего не увидишь


Ситуация напоминает выводимую таблицу, view, где обладатель права изменять view может добавить через view в базовую таблицу строки, которые сам через view не увидит. Воспрепятствовать этому способно специальное ограничение целостности WITH CHECK OPTION. А можно ли здесь запретить выводить строки из зоны собственной видимости ? Да: для этого в параметре TABLE_OPTIONS достаточно указать специальный режим CHECK_CONTROL использования метки в таблице PHONE. Выдаем в SQL*Plus:

@phonepolicyoptions 'read_control, check_control'

Проверка:

SQL> CONNECT employee/employee Connected. SQL> @updateallen OPEN

1 row updated.

SQL> @updateallen LIMITED

UPDATE scott.phone * ERROR at line 1: ORA-28115: policy with check option violation

SQL> @updateallen OPEN

1 row updated.

В то же время пользователь HEAD, который «видит все», нового ограничения не заметит:

SQL> CONNECT head/head Connected. SQL> @updateallen OPEN

1 row updated.

SQL> @updateallen LIMITED

1 row updated.

SQL> @updateallen LIMITED

1 row updated.

SQL> @updateallen OPEN

1 row updated.



Жесткий запрет на изменение метки


Другой режим использования метки в TABLE_OPTIONS еще более ограничителен. Он вовсе запрещает изменять значение метки, не важно в сторону ли увеличения или понижения ее секретности. Выдадим:

@phonepolicyoptions 'read_control, label_update'

Проверяем:

SQL> CONNECT employee/employee Connected. SQL> @updateallen OPEN

1 row updated.

SQL> @updateallen LIMITED

UPDATE scott.phone * ERROR at line 1: ORA-12406: unauthorized SQL statement for policy EMPSEC_POLICY

... ... ... ...

SQL> @updateallen OPEN

1 row updated.

SQL> CONNECT head/head Connected. SQL> @updateallen OPEN

1 row updated.

SQL> @updateallen LIMITED

UPDATE scott.phone * ERROR at line 1: ORA-12406: unauthorized SQL statement for policy EMPSEC_POLICY

... ... ... ...

Пример пока лишь доказывает, что метку нельзя сделать более «секретной». Но возможно, разрешено «играть на понижение» секретности ? Проверям снова:

CONNECT lbacsys/lbacsys Connected. SQL> @updateallen LIMITED

1 row updated.

SQL> CONNECT head/head Connected. SQL> @updateallen LIMITED

1 row updated.

SQL> @updateallen OPEN

UPDATE scott.phone * ERROR at line 1: ORA-12406: unauthorized SQL statement for policy EMPSEC_POLICY

... ... ... ...

Что и требовалось доказать: режим LABEL_UPDATE использования метки, заданный для таблицы PHONE в рамках нашей политики, не позволяет обычным пользователям изменять значение меток доступа в строках таблицы.



Аннотация


Эта статья является непосредственным продолжением статьи «», и рассматривает некоторые дополнительные свойства средства Label Security в Oracle, соответствующие мандатному доступу. Именно, показана возможность преодолевать в индивидуальном порядке запреты на изменения меток, сформулированные для групп доступа.

Подразумеваемые в статье состояние базы и сценарные файлы соответствуют концу предыдущей статьи.



Другие привилегии


Помимо трех указанных привилегий, связанных с запретом LABEL_UPDATE использования меток, имеются и некоторые прочие:

READ,

FULL,

COMPACCESS,

PROFILE_ACCESS.

Ограничимся здесь анализом первой из них. Остальные окажут эффект только при более сложной организации доступа, здесь не рассматриваемой, например, при наличии структурных меток.

Привилегия READ дает возможность ее обладателю преодолеть запрет на чтение строк со стороны действующей политики. Продолжим:

SQL> CONNECT employee/employee Connected. SQL> @phones

ENAME PNO ---------- -------------------- ALLEN WARD 610-1718 MARTIN 103-1983 BLAKE CLARK KING TURNER 293-1398 JAMES 932-6728 MILLER 865-6706

9 rows selected.

SQL> @userprivilege employee 'read'

Connected.

PL/SQL procedure successfully completed.

SQL> @showprivs

USER_NAME POLICY_NAME USER_PRIVILEGES --------------- ------------------------------ -------------------- EMPLOYEE EMPSEC_POLICY READ

HEAD EMPSEC_POLICY WRITEACROSS

SQL> CONNECT employee/employee Connected. SQL> @phones

ENAME PNO ---------- -------------------- SMITH 665-7282 ALLEN 882-3154 WARD 610-1718 JONES 100-6539 MARTIN 103-1983 BLAKE 193-3112 CLARK 310-2673 SCOTT 680-4853 KING 542-6672 TURNER 293-1398 ADAMS 278-5105 JAMES 932-6728 FORD 485-9127 MILLER 865-6706

14 rows selected.

Название привилегии READ не должно вызывать иллюзий: в нашем случае она позволяет не только читать строки, включая поле метки, но и изменять. Для доказательства этого факта продолжим (от имени EMPLOYEE):

SQL> @updateallen OPEN

1 row updated.

SQL> @updateallen LIMITED

1 row updated.



Изменение метки в качестве исключения из правила


Итак, если мы применяем политику секретности к таблице и указываем при этом режим LABEL_UPDATE использования меток, обычные пользователи теряют возможность эти метки конкретных строк изменять. Однако для каких-то пользователей Label Security позволяет сделать исключение. Для этого используются специальные привилегии. В отличие от традиционных привилегий Oracle (системных и объектных) они носят не разрешающий характер (a priori ничего делать нельзя, а все, что можно, специально разрешено привилегиями), а характер преодоления явно указанного запрета (определенные действия явно запрещены, но в виде исключения допускаются); именно для них и естественно сказать: "привилегии", в то время как для традиционных "привилегий" просится более подходящее слово "полномочия". Вот их названия:

WRITEUP,

WRITEDOWN,

WRITEACROSS.

Выдаются они не командой GRANT, а с помощью специальной процедуры SET_USER_PRIVS из пакета SA_USER_ADMIN. Это к сожалению, но отчасти разработчиков можно понять: эти привилегии даются пользователям в рамках конкретных политик, коих может иметься много.

Подготовим файл userprivilege.sql:

CONNECT lbacsys/lbacsys

BEGIN SA_USER_ADMIN.SET_USER_PRIVS ( POLICY_NAME => 'empsec_policy' , USER_NAME => '&1' , PRIVILEGES => '&2' ); END; /

Выдадим привилегию WRITEUP и понаблюдаем, как изменятся возможности пользователя HEAD по изменению значений меток при наличии у таблицы PHONE режима LABEL_UPDATE их использования:

SQL> @userprivilege head 'writeup' Connected.

PL/SQL procedure successfully completed.

SQL> CONNECT head/head Connected. SQL> @updateallen OPEN

1 row updated.

SQL> @updateallen LIMITED

1 row updated.

SQL> @updateallen OPEN

UPDATE scott.phone * ERROR at line 1: ORA-12406: unauthorized SQL statement for policy EMPSEC_POLICY

... ... ... ...

Привилегия WRITEUP, выданная пользователю HEAD, позволяет сделать для него исключение и «играть на повышение».

Выдадим:

@userprivilege head 'writedown'


Проверка действия привилегии WRITEDOWN:

SQL> CONNECT head/head Connected. SQL> @updateallen LIMITED

1 row updated.

SQL> @updateallen OPEN

1 row updated.

SQL> @updateallen LIMITED

UPDATE scott.phone * ERROR at line 1: ORA-12406: unauthorized SQL statement for policy EMPSEC_POLICY

... ... ... ...

Пользователь HEAD в виде исключения получил право понижать секретность, но не повышать.

Выдадим:

@userprivilege head 'writeacross'

Проверка действия привилегии WRITEACROSS:

SQL> CONNECT head/head Connected. SQL> @updateallen LIMITED

1 row updated.

SQL> @updateallen OPEN

1 row updated.

SQL> @updateallen LIMITED

1 row updated.

В отличие от своих возможных коллег по доступу к ограниченной информации, пользователь HEAD теперь может как повышать, так и понижать секретность строки.

Следующий запрос позволяет проверить наличие привилегий преодоления запрета изменять метки:

SQL> COLUMN USER_NAME FORMAT A15 SQL> COLUMN USER_PRIVILEGES FORMAT A20 SQL> SELECT * FROM DBA_SA_USER_PRIVS;

USER_NAME POLICY_NAME USER_PRIVILEGES --------------- ------------------------------ -------------------- HEAD EMPSEC_POLICY WRITEACROSS

Для того, чтобы изъять у пользователя привилегию, потребуется не выдать привычную команду REVOKE, а опустить значение (проставить NULL) в параметре PRIVILEGES процедуры SET_USER_PRIVS. Продолжим:

SQL> SAVE showprivs Created file showprivs.sql

SQL> @userprivilege head '' Connected.

PL/SQL procedure successfully completed.

SQL> @showprivs

no rows selected

SQL> CONNECT head/head Connected. SQL> @updateallen LIMITED

1 row updated.

SQL> @updateallen OPEN

UPDATE scott.phone * ERROR at line 1: ORA-12406: unauthorized SQL statement for policy EMPSEC_POLICY

... ... ... ...

Пользователь HEAD лишился возможности изменять метку - при наличии у таблицы режима LABEL_UPDATE работы с метками.

В нашем примере WRITEACROSS, казалось бы, заменяет WRITEUP + WRITEDOWN, но в общем случае (например, при структурной метке) все три привилегии самодостаточны. В общем случае возможно и приобретает смысл комбинирование привилегий WRITEUP, WRITEDOWN и WRITEACROSS, не сводящееся к выбору какой-нибудь одной из них.



Проверка действия привилегии WRITEDOWN:

SQL> CONNECT head/head Connected. SQL> @updateallen LIMITED

1 row updated.

SQL> @updateallen OPEN

1 row updated.

SQL> @updateallen LIMITED

UPDATE scott.phone * ERROR at line 1: ORA-12406: unauthorized SQL statement for policy EMPSEC_POLICY

... ... ... ...

Пользователь HEAD в виде исключения получил право понижать секретность, но не повышать.

Выдадим:

@userprivilege head 'writeacross'

Проверка действия привилегии WRITEACROSS:

SQL> CONNECT head/head Connected. SQL> @updateallen LIMITED

1 row updated.

SQL> @updateallen OPEN

1 row updated.

SQL> @updateallen LIMITED

1 row updated.

В отличие от своих возможных коллег по доступу к ограниченной информации, пользователь HEAD теперь может как повышать, так и понижать секретность строки.

Следующий запрос позволяет проверить наличие привилегий преодоления запрета изменять метки:

SQL> COLUMN USER_NAME FORMAT A15 SQL> COLUMN USER_PRIVILEGES FORMAT A20 SQL> SELECT * FROM DBA_SA_USER_PRIVS;

USER_NAME POLICY_NAME USER_PRIVILEGES --------------- ------------------------------ -------------------- HEAD EMPSEC_POLICY WRITEACROSS

Для того, чтобы изъять у пользователя привилегию, потребуется не выдать привычную команду REVOKE, а опустить значение (проставить NULL) в параметре PRIVILEGES процедуры SET_USER_PRIVS. Продолжим:

SQL> SAVE showprivs Created file showprivs.sql

SQL> @userprivilege head '' Connected.

PL/SQL procedure successfully completed.

SQL> @showprivs

no rows selected

SQL> CONNECT head/head Connected. SQL> @updateallen LIMITED

1 row updated.

SQL> @updateallen OPEN

UPDATE scott.phone * ERROR at line 1: ORA-12406: unauthorized SQL statement for policy EMPSEC_POLICY

... ... ... ...

Пользователь HEAD лишился возможности изменять метку - при наличии у таблицы режима LABEL_UPDATE работы с метками.

В нашем примере WRITEACROSS, казалось бы, заменяет WRITEUP + WRITEDOWN, но в общем случае (например, при структурной метке) все три привилегии самодостаточны. В общем случае возможно и приобретает смысл комбинирование привилегий WRITEUP, WRITEDOWN и WRITEACROSS, не сводящееся к выбору какой-нибудь одной из них.


Изучаем метки доступа к строкам: специальные привилегии для избранных


,

Преподаватель технологий Oracle

"Тараканы прибегали,

Все стаканы выпивали,

А букашки -

По три чашки

С молоком

И крендельком ... "

Корней Чуковский,
«Муха-Цокотуха»



Аннотация


Эта статья является непосредственным продолжением статьи «», и рассматривает некоторые возможности управления правкой полей записей, защищенных метками доступа средствами Label Security в Oracle в соответствии с мандатным доступом.

Подразумеваемые в статье состояние базы и сценарные файлы соответствуют концу предыдущей статьи.



Изучаем метки доступа к строкам: правка обычных столбцов таблицы


,

Преподаватель технологий Oracle

"Я за свечку,

Свечка - в печку!

Я за книжку,

Та - бежать ..."

Корней Чуковский,
«Мойдодыр»



Правка обычных полей


Раньше мы рассмотрели некоторые возможности правки меток, задающих секретность строк (и возможности в виде исключения обходить запреты на правку). А что касается возможностей править обычные поля таблицы, строки которой защищены метками секретности?

Свойство доступа к обычным полям (не к служебному полю секретности строки), ранее использованное, называлось READ_CONTROL. Уточним поведение этого свойства.

Выдадим:

@userprivilege employee ''

@phonepolicyoptions 'read_control'

Продолжим:

SQL> CONNECT employee/employee Connected. SQL> @phones

ENAME PNO ---------- -------------------- ALLEN 882-3154 WARD 610-1718 MARTIN 103-1983 BLAKE CLARK KING TURNER 293-1398 JAMES 932-6728 MILLER 865-6706

9 rows selected.

SQL> UPDATE scott.phone 2 SET pno = pno

3 WHERE 4 empno 5 = ( SELECT empno FROM scott.emp WHERE ename = 'ALLEN' );

1 row updated.

SQL> save updateallenpnumber

Created file updateallenpnumber.sql

SQL> @updateallen OPEN

1 row updated.

SQL> @updateallen LIMITED

1 row updated.

SQL> @updateallen OPEN

0 row updated.

SQL> @updateallenpnumber

0 row updated.

Пока ничего нового относительно меток (пользователю EMPLOYEE, как это и было раньше, позволено "играть" только "на повышение" секретности), а что касается обычных полей разрешенных ему строк, то правка, как показывает пример, возможна.

Однако в нашем распоряжении имеются и другие свойства меток в конкретной таблице, например:

INSERT_CONTROL,

UPDATE_CONTROL,

DELETE_CONTROL,

WRITE_CONTROL.

Рассмотрим для примера свойство UPDATE_CONTROL. Выдадим:

@phonepolicyoptions 'update_control'

Повторим проверку:

SQL> CONNECT employee/employee Connected. SQL> @phones

ENAME PNO ---------- -------------------- ALLEN 882-3154 WARD 610-1718 MARTIN 103-1983 BLAKE 193-3112 CLARK 310-2673 KING 542-6672 TURNER 293-1398 JAMES 932-6728 MILLER 865-6706

9 rows selected.

SQL> @updateallen OPEN

1 row updated.

SQL> @updateallenpnumber

1 row updated.

SQL> @updateallen LIMITED

unauthorized SQL statement for policy


1 row updated.

SQL> @updateallen LIMITED UPDATE scott.phone * ERROR at line 1: ORA-12406: unauthorized SQL statement for policy EMPSEC_POLICY

... ... ... ...

SQL> @updateallenpnumber

UPDATE scott.phone * ERROR at line 1: ORA-12406: unauthorized SQL statement for policy EMPSEC_POLICY

... ... ... ...

Факт налицо: включение UPDATE_CONTROL не изменило наши возможности по изменению метки, однако ж обычные поля запретных для него строк он править уже не в состоянии. Заметьте, что это коснулось только правки, но не чтения. Чтобы вновь защитить обычные поля от прочтения, достаточно выдать:

@phonepolicyoptions 'update_control, read_control'

Доказательство:

SQL> CONNECT employee/employee Connected. SQL> @phones

ENAME PNO ---------- -------------------- ALLEN 882-3154 WARD 610-1718 MARTIN 103-1983 BLAKE CLARK KING

TURNER 293-1398 JAMES 932-6728 MILLER 865-6706

9 rows selected.

Свойства INSERT_CONTROL и DELETE_CONTROL аналогичны рассмотренному UPDATE_CONTROL. Свойство же WRITE_CONTROL введено для удобства и обозначает INSERT_CONTROL + UPDATE_CONTROL + DELETE_CONTROL. Это легко обнаруживается следующим образом. Продолжим:

SQL> @showoptions

POLICY_NAME SCHEMA_NAME TABLE_NAME TABLE_OPTIONS ---------------------- ------------ ------------ --------------- EMPSEC_POLICY SCOTT EMP READ_CONTROL, LABEL_DEFAULT EMPSEC_POLICY SCOTT PHONE READ_CONTROL, UPDATE_CONTROL

А теперь выдадим:

@phonepolicyoptions 'write_control, read_control'

Снова проверим свойства («особенности») применения нашей политики к конкретным таблицам:

SQL> @showoptions

POLICY_NAME SCHEMA_NAME TABLE_NAME TABLE_OPTIONS ---------------------- ------------ ------------ --------------- EMPSEC_POLICY SCOTT EMP READ_CONTROL, LABEL_DEFAULT EMPSEC_POLICY SCOTT PHONE READ_CONTROL, INSERT_CONTROL, UPDATE_CONTROL, DELETE_CONTROL


Аннотация


Эта статья является непосредственным продолжением статьи «», и рассматривает примеры поведения средства Label Security в Oracle, не являющиеся очевидными для неспециалиста по мандатному доступу к данным. Показана возможность страховать пользователя от непредусмотренных для его уровня секретности действий и неочевидная особенность выдачи пользователю засекреченных данных.

Подразумеваемые в статье состояние базы и сценарные файлы соответствуют концу предыдущей статьи.



Изучаем метки доступа к строкам: примеры не самого очевидного поведения защиты


,

Преподаватель технологий Oracle

"Наступила темнота,

Не ходи за ворота:

Кто на улицу попал -

Заблудился и пропал."

Корней Чуковский,
«Краденое солнце»

  "And neither the angels in heaven above,

Nor the demons down under the sea,

Can ever dissever my soul from the soul

Of the beautiful Annabel Lee... "

Edgar Allan Poe,
«Annabel Lee»



Не только защита строк, но и страховка пользователей


Одна из интересных особенностей меточного («мандатного») доступа в том, что он позволяет обладателю определенного уровня доступа запретить правку строк, не только более секретных, чем ему положено, но также и строк, менее секретных. Это напоминает кастовость по части действия; так сказать, «что положено быку, нельзя делать Юпитеру». В Oracle Label Security эта особенность нашла воплощение, в чем легко убедиться.

Создадим нового пользователя и дадим ему полномочия работы исключительно с «секретными» строками:

CONNECT / AS SYSDBA

CREATE USER secretmanager IDENTIFIED BY secretmanager;

GRANT minimal TO secretmanager;

CONNECT lbacsys/lbacsys

BEGIN SA_USER_ADMIN.SET_USER_LABELS ( POLICY_NAME => 'empsec_policy' , USER_NAME => 'secretmanager'

, MAX_READ_LABEL => 'limited'

, MIN_WRITE_LABEL => 'limited'

); END; /

(Заметьте, что ранее параметр MIN_WRITE_LABEL в процедуре SET_USER_LABELS мы не использовали; в результате умолчательного поведения Label Security для пользователя HEAD было MIN_WRITE_LABEL = 'OPEN', что легко проверяется по справочным таблицам).

Проверим теперь возможности SECRETMANAGER по чтению и по изменению полей строк, в том числе поля метки:

SQL> CONNECT secretmanager/secretmanager Connected. SQL> @phones

ENAME PNO ---------- -------------------- SMITH 665-7282 ALLEN 882-3154 WARD 610-1718 JONES 100-6539 MARTIN 103-1983 BLAKE 193-3112 CLARK 310-2673 SCOTT 680-4853 KING 542-6672 TURNER 293-1398 ADAMS 278-5105 JAMES 932-6728 FORD 485-9127 MILLER 865-6706

14 rows selected.

SQL> @updateallenpnumber UPDATE scott.phone * ERROR at line 1: ORA-12406: unauthorized SQL statement for policy EMPSEC_POLICY

... ... ... ...

SQL> @updateallen OPEN UPDATE scott.phone * ERROR at line 1: ORA-12406: unauthorized SQL statement for policy EMPSEC_POLICY

... ... ... ...

SQL> @updateallen LIMITED UPDATE scott.phone * ERROR at line 1: ORA-12406: unauthorized SQL statement for policy EMPSEC_POLICY

... ... ... ...



Выдача данных: ничего лишнего ?


В завершение знакомства с меточным доступом к строкам в Oracle мне хотелось бы подчеркнуть, что даже без учета структуры меток, привилегий, группирования пользователей и многого прочего, большая часть которого в этих статьях не рассматривалась, мандатный метод доступа не так прямолинеен в поведении, как мог бы показаться поначалу.

Рассмотрим обычную выдачу данных, помеченных метками доступа. Вспомним, что до сих пор в таблице PHONE у нас имелось по одному номеру телефона на каждого сотрудника. Попробуем добавить второй телефон Аллену, а так как сценарий phones.sql использует для выдачи на экран полуоткрытое соединение, употребление его останется корректным и для этих новых данных. Интерес представляет случай, когда телефоны Аллена (когда их станет два) будут иметь метку LIMITED, и когда к ним обратится пользователь EMPLOYEE. Интуиция подсказывает, что «невидимые» телефоны Аллена будут обрабатываться полуоткрытым соединением подобно пропущенному значению (NULL), и в результате выборки мы увидим несколько строк для Аллена с пропущенными значениями номеров телефона. А что на самом деле?

Добавим в таблицу PHONE для Аллена новый телефон и «засекретим» оба:

CONNECT head/head

INSERT INTO scott.phone SELECT empno, '111-2222', empsec_label FROM scott.phone WHERE empno = (SELECT empno FROM scott.emp WHERE ename='ALLEN')

@updateallen LIMITED

Проверяем:

SQL> @phones

ENAME PNO ---------- -------------------- SMITH 665-7282 ALLEN 111-2222 ALLEN 882-3154 WARD 610-1718 JONES 100-6539 MARTIN 103-1983 BLAKE 193-3112 CLARK 310-2673 SCOTT 680-4853 KING 542-6672 TURNER 293-1398 ADAMS 278-5105 JAMES 932-6728 FORD 485-9127 MILLER 865-6706

15 rows selected.

SQL> CONNECT employee/employee Connected. SQL> @phones

ENAME PNO ---------- -------------------- ALLEN

WARD 610-1718 MARTIN 103-1983 BLAKE CLARK KING TURNER 293-1398 JAMES 932-6728 MILLER 865-6706

9 rows selected.

Меточный доступ в Oracle справедливо показал пользователю EMPLOYEE имя Аллена (его строка в таблице EMP в открытом доступе), и не показал его телефоны (мы закрыли доступ к телефонам Аллена в таблице PHONE), но вот только открытое соединение таблиц EMP и PHONE показало "отсутствие" (на деле - недоступность) телефона одной строкой, а не двумя! Ничего лишнего: телефон недоступен, а сколько их недоступно - не сообщается. Если же сделать один из телефонов Аллена открытым, информация о наличии второго, недоступного телефона и вовсе пропадет:

SQL> CONNECT head/head Connected. SQL> UPDATE scott.phone 2 SET 3 empsec_label 4 = CHAR_TO_LABEL ( 'empsec_policy', 'OPEN' ) 5 WHERE 6 pno = '111-2222'

7 /

1 row updated.

SQL> CONNECT employee/employee Connected. SQL> @phones

ENAME PNO ---------- -------------------- ALLEN 111-2222

WARD 610-1718 MARTIN 103-1983 BLAKE CLARK KING TURNER 293-1398 JAMES 932-6728 MILLER 865-6706

9 rows selected.

Не думаю, что такое поведение для всех очевидно, а значит приведенные выше примеры могут оказаться небесполезными.



История


С момента появления триггеров в Oracle было легко реализовать простой вид контроля доступа к данным на основе имени пользователя, комбинируя таблицу, представление и триггер. Рассмотрим, например, представленный ниже код:

create table public_table ( id number(6), v1 varchar2(30), owner varchar2(32));

create or replace trigger pt_bri before insert on public_table for each row begin new.owner := user; end; /

create or replace view private_view as select id, v1 from public_table where owner = user;

grant insert, select, update, delete on private_view to public;

Если теперь создать пару пользователей с привилегией create session, вы увидите, что они могут выбирать, вставлять, изменять и удалять строки из представления private_view (если они не забудут указать имя схемы, которой он принадлежит), но увидеть они смогут только строки, которые они создали сами. Они не смогут увидеть строки друг друга. Триггер гарантирует, что имя пользователя, создающего строку, добавляется к строке; условие "owner = user" гарантирует, что только исходный создатель строки сможет ее увидеть.

(Учтите - строка pl/sql-кода :new.owner := user приведет к выполнению "select user from dual", так что, в эффективной производственной системе, вероятно, придется использовать более "хитрый" код).

Только владелец таблицы сможет увидеть все строки; но это потому, что владелец таблицы может запрашивать непосрредственно таблицу, а не ограничиваться представлением. Фактически, у нас есть один, фиксированный текст представления, который интерпретируется по-разному во время выполнения, из-за "псевдо-параметра" user, который появляется в его определении.

Хотя это, очевидно, один из способов реализации защиты на уровне строк, более полезная форма защиты на уровне строк должна, вероятно, быть достаточно гибкой, чтобы работать с группами, а не с отдельными пользователями. Механизм, позволяющий пользователям конкретной группы видеть данные, созданные другими пользователями в этой группе, это, вероятно, минимальное требование полезности.


Конечно, основанный на группах механизм вроде описанного тоже можно было реализовать давно, и обычно для реализации использовалась функция в пакете, которую можно было использовать как в представлении, так и в триггере практически так же, как выше использовался псевдостолбец user. Но этот метод требует значительных дополнительных ресурсов из-за большого количества вызовов функции пакета, которые необходимы (по одному вызову на каждую затрагиваемую строку). Эта специфическая проблема производительности исчезла, когда в Oracle 8.1 появились "переменные среды" и вызов sys_context().

С этой версии, если в вашем коде используются вызовы функции userenv(), вы должны стараться вместо нее использовать вызов sys_context() для контекста 'userenv'. Например:

select sys_context('userenv', 'sessionid') from dual;

вместо

select userenv('sessionid') from dual;

Функция userenv() является устаревшей, а контекст 'userenv'

имеет намного больше опций.


Контексты


Идея, лежащая в основе использования контекстов, очень проста и обеспечивает серьезную защиту. В исходном виде ее можно определить тремя особенностями: (i) контекст- список переменных в памяти, значения которых привязаны к сеансам, (ii) сеанс может получить текущие значения этих переменных, вызывая функцию sys_context(), (iii) переменные в контексте могут устанавливаться только путем вызова процедуры, связанной с этим контекстом. Например, используя служебную таблицу защиты, можно переписать предыдущий код следующим образом:

create or replace context security_ctx using security_proc; -- Процедура, защищающая контекст. -- Это может быть процедура пакета.

create or replace procedure security_proc as m_group_id varchar2(32); begin -- должна включать обработку исключительных ситуаций select group_id into m_group_id from security_table -- эту таблицу надо создать. where user_name = user; dbms_session.set_context( namespace => 'SECURITY_CTX', attribute => 'GROUP_ID', value => m_group_id ); end; /

create or replace trigger pt_bri before insert on public_table for each row begin :new.owner := sys_context('security_ctx','group_id'); -- должна включать перехват ошибок, например, null-значений end; /

create or replace view private_view as select id, v1 from public_table where owner = sys_context('security_ctx','group_id');

grant insert, select, update, delete on private_view to public;

При наличии этого кода, представление private_view ведет себя практически так же, как раньше. Пользователи, вставляющие данные в представление, автоматически получают при этом присоединенный к строке код группы. Пользователи, запрашивающие представление, увидят только строки, соответствующие коду их грруппы. Но, в этой реализации пока есть пробел. Как код группы пользователей устанавливается в их локальном контексте?

Для завершения картины надо использовать триггеры на регистрацию. Например, в схеме, в которой создана таблица, процедура и представление, можно выполнить следующий код:


create or replace trigger security_trigger after logon on database begin security_proc; end; /

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

Хотя это достаточно мощный механизм для обеспечения защиты на уровне строк или поддержки виртуальной приватной базы данных, помните, что он зависит от срабатывания строчного триггера при каждой вставке в таблицу. Строчные триггеры требуют существенных дополнительных расходов на обработку и, в частности, они блокируют некоторые из высокоэффективных механизмов Oracle и преобразуют обработку массивов в построчную обработку.

Поэтому вы не должны использовать этот механизм, не учитывая и не проверив влияние на производительность - в частности, на процессы пакетной загрузки. Помните также, что вы можете найти примеры SQL-операторов, для которых оптимизатор вырабатывает менее эффективный способ выполнения, поскольку в коде приложения теперь надо использовать представление (private_view), а не таблицу (public_table), как раньше.

Будьте очень внимательны при написании триггеров на регистрацию, всегда тестируйте их на одной схеме (конструкция after logon on test_user.schema создаст триггер на регистрацию в схеме test_user), пррежде чем создавать их на уровне базы данных, или придется подключаться как SYS>, чтобы решить проблему, если что-то пойдет не так.

Для создания и удаления контекстов вам потребуются привилегии:

create any context drop any context

А для создания и удаления триггеров базы данных необходима привилегия

administer database trigger

Если вы хотите иметь возможность просматривать текущие значения переменных в используемых сейчас контекстах, можете предоставить пользователям привилегию для просмотра соответствующего представления динамической производительности, чтобы они могли выполнять запросы вида:

select namespace, attribute, value from sys.v_$context order by namespace, attribute;

NAMESPACE ATTRIBUTE VALUE -------------------- ----------------- ----------- SECURITY_CTX GROUP_ID T


Понятие переменных среды, или контекстов


Понятие переменных среды, или контекстов - это мощное и гибкое дополнение к рабочей среде Oracle. Создавая триггеры, устанавливающие переменные контекста, мы можем гарантировать, что различные компоненты важной информации можно установить автоматически при регистрации; и в то же время мы можем гарантировать, что эти переменные доступны для чтения, но не для изменения конечному пользователю. Используя контексты вместе со статически определенными представлениями и табличными триггерами, можно создать простую, но весьма мощную разновидность виртуальной приватной базы данных, не используя все воможности официального механизма RLS.
Джонатан Льюис () - независимый консультант с более чем 18-летним опытом использования Oracle. Он специализируется на физическом проектировании баз данных и стратегии использования сервера Oracle. Джонатан - автор книги "Practical Oracle 8i - Building Efficient Databases", опубликованной издательством Addison-Wesley, и один из наиболее известных лекторов среди специалистов по Oracle в Великобритании. Подробнее о его публикациях, презентациях и семинарах можно узнать на сайте
www.jlcomp.demon.co.uk, где также находится список ЧаВО The Co-operative Oracle Users' FAQ
по дискуссионным группам Usenet, связанным с СУБД Oracle.
Эта статья первоначально была опубликована на сайте DBAzine.com, сетевом портале, посвященном проблемам различных СУБД и их решениям. Перевод публикуется с разрешения автора.

В этой короткой серии статей


Джонатан Льюис (Jonathan Lewis)

www.jlcomp.demon.co.uk

Перевод: , OpenXS Initiative

В этой короткой серии статей я буду рассматривать все более тонкие и строгие уровни защиты строк, которые можно установить для данных в Oracle 8 и 9. В части 1 я начну с контекстов и триггеров на регистрацию. В части 2 я перейду к возможностям "официальной" защиты RLS (также известной как детальный контроль доступа или виртуальная приватная база данных), появившейся в Oracle. Наконец, в части 3 я рассмотрю предложенный корпорацией Oracle способ расширения RLS для реализации label security. Все примеры кода в этих статьях были протестированы с помощью Oracle 9.2.0.3.


Подготовка к использованию RLS


В этом примере функции, генерирующей условие (в которой используется оператор case, появившийся в версии 9), мы по имени пользователя будем генерировать условие, ограничивающее строки по отделу или поставщику. В более реалистической ситуации для определения того, какое условие генерировать, вероятно, использовались бы какие-то справочные таблицы, а для различения локальных сотрудников и внешних поставщиков можно использовать глобальную установку контекста на уровне промежуточном уровне (middle tier).

create or replace function stock_predicate( i_schema in varchar2, i_object in varchar2) return varchar2 as begin return case (sys_context('userenv','session_user')) when 'U1' then 'supplier_code = ''Hershey''' when 'U2' then 'dept_id = ''Confection''' when 'TEST_USER' then null else '1 = 0' end; end; /

Следует обратить особое внимание на три особенности.

Во-первых, в качестве входных параметров передаются имя схемы и имя объекта (обычно - таблицы или представления), к которому, в конечном итоге, будет применено сгенерированное условие. Поскольку имя объекта передается как входной параметр, можно создавать условия с коррелированными подзапросами, используя входной параметр в качестве коррелирующего имени для ведущей таблицы. (Генерировать условия такого типа - не лучшая идея, однако, поскольку они могут катастрофически снизить производительность).

Вторая особенность связана с личными предпочтениями - я люблю всегда подстраховываться. Если все проверки в функции, генерирующей условие, не сработают, я люблю возвращать всегда ложное условие (такое как '1 = 0'), которое позволит оптимизатору вообще не возвращать данные, обычно - очень эффективно, а наилучшим действием по умолчанию с точки зрения защиты и будет сокрытие всего.

Наконец, обратите внимание, что я добавил одну проверку, возвращающую пустое

условие. Пользователь test_user является владельцем таблицы и функции, поэтому по практическим соображениям я гарантировал, что у владельца данных не будет ограничений при их просмотре или обработке.


Создав функцию, которая может возвращать строку для конструкции where, мы теперь связываем эту функцию с таблицей. (При необходимости, можно связать одну и ту же функцию с множеством разных таблиц). Для этого мы вызываем процедуру пакета dbms_rls.

begin dbms_rls.add_policy ( object_schema => 'test_user', object_name => 'stock_level', function_schema => 'test_user', policy_function => 'stock_predicate', statement_types => 'select, insert, update, delete', update_check => TRUE, policy_name => 'stock_restrict', enable => TRUE, static_policy => FALSE -- только в v9 ); end; /

В этом вызове процедуры add_policy мы указали нашу таблицу и написанную ранее функцию. Мы также указываем, что мы хотим создавать условие для операторов select, insert, update и delete. Параметр update_check немного напоминает конструкцию "with check option" для представлений; он гарантирует, что мы не сможем вставить или изменить строку так, что после вставки или изменения не сможем ее увидеть. Наконец, мы дали набору правил (сочетанию объекта, функции и действий) имя и разрешили его использовать. Последний параметр процедуры,

static_policy, очень важен - к нему я ещё вернусь.

Если теперь мы вставим тестовые данные, то сможем увидеть влияние правил. Мы начнем с подключения от имени владельца таблицы (test_user) для загрузки данных.

insert into stock_level values(sysdate,1,100,'Confection','Hershey'); insert into stock_level values(sysdate,2,60,'Deli','Hershey'); insert into stock_level values(sysdate,3,60,'Confection','Cadbury'); insert into stock_level values(sysdate,4,60,'Deli','Cadbury'); commit;

Если подключиться и запросить данные от имени этого пользователя, мы увидим все четыре строки. Однако, если подключиться как пользователь u1 и выполнить select * from test_user.stock_level, мы увидим:

STOCK_DAT PRODUCT_ID QTY DEPT_ID SUPPLIER_CODE --------- ---------- ---------- -------------------- ------------- 19-OCT-03 1 100 Confection Hershey 19-OCT-03 2 60 Deli Hershey



А если подключиться от имени пользователя u2 и выполнить тот же запрос, мы увидим:

STOCK_DAT PRODUCT_ID QTY DEPT_ID SUPPLIER_CODE --------- ---------- ---------- -------------------- ------------- 19-OCT-03 1 100 Confection Hershey 19-OCT-03 3 60 Confection Cadbury

Как видите, каждый пользователь получает свой набор данных. Исходный запрос был изменен "на лету", ссылка на таблицу stock_level была заменена сслыкой на вложенное представление, содержащее наше сгенерированное условие (подумайте, как это может сказаться на эффективности, особенно при использовании сложных внешних соединений). Например, простой оператор

select, выполненный пользователем u2, будет преобразован в:

Select * from ( select * from stock_level where dept_id = 'Confection')

Кстати, если вы получаете сообщение об ошибке Oracle ORA-28113: policy predicate has error, то, вероятно, вы сделали опечатку при копировании всех повторяющихся апострофов в функции, задающей правила - сервер Oracle сообщает вам, что сгенерированный им текст не позволяет построить допустимую конструкцию where.


Правила защиты


Цель средств детального контроля доступа- позволить вам централизованно скрыть все сложности "сокрытия данных".

Для этого необходимо выполнить два обязательных шага, а также два дополнительных необязательных шага, которые могут и не потребоваться в системе построения отчетов. Во-первых, необходимо создать функцию, получающую на входе имя схемы и имя объекта, и выдающую текст допустимой конструкции where; во-вторых, необходимо связать эту функцию с целевой таблицей, перечислив действия, для которых эта функция должна выполняться. Два необязательных шага были описаны в предыдущей статье - создание табличного триггера для установки соответствующих значений управляющих столбцов и триггера базы данных для установки пользовательской "среды" или переменных контекста. Может также иметь смысл создать одно представление на основе таблицы, чтобы скрыть столбцы, устанавливаемые табличным триггером.

В этой статье мы будем предполагать, что пакетный процесс загружает таблицу, а конечным пользователям разрешено только читать таблицу, так что можно проигнорировать особенности вставок, изменений и удалений, и сконцентрироваться на обработке только операторов select.



Проблемы


С этим механизмом связано несколько неизбежных проблем. Для начала, в Oracle 8.1 нигде в системе вообще нельзя увидеть сгенерированное условие- ни в представлении v$sql, ни в трассировочных файлах

измененного SQL-оператора просто нет. Эту проблему можно обойти, установив для sql_trace значение true, а затем установив событие 10730

в сеансе, использующем RLS. После этого каждый полный разбор (hard parse) оператора будет генерировать раздел в трассировочном файле, который будет иметь примерно следующий вид:

Logon user : U1 Table/View : TEST_USER.STOCK_LEVEL Policy name : STOCK_RESTRICT Policy function: TEST_USER.STOCK_PREDICATE RLS view : SELECT "STOCK_DATE","PRODUCT_ID","QTY","DEPT_ID","SUPPLIER_CODE" FROM "TEST_USER". "STOCK_LEVEL" "STOCK_LEVEL" WHERE (supplier_code = 'Hershey')

Эта проблема была решена в Oracle 9 (хотя эффективность решения вызывает некторые сомнения) путем добавления представления v$vpd_policy. Простой запрос к этому представлению может дать следующую информацию:

ADDRESS : 6F5664F0 PARADDR : 6F5638AC SQL_HASH : 1816753535 CHILD_NUMBER : 0 OBJECT_OWNER : TEST_USER OBJECT_NAME : STOCK_LEVEL POLICY_GROUP : SYS_DEFAULT POLICY : STOCK_RESTRICT POLICY_FUNCTION_OWNER : TEST_USER PREDICATE : supplier_code = 'Hershey'

По значениям столбцов paraddr, sql_hash и child_number

из этого представления можно сделать примерно такой запрос к представлению v$sql, находящий соответствующий SQL-оператор:

Select sql_text from v$sql where address = '6F5F0020' and hash_value = 2621366196 and child_number= 0

Неэффективность здесь в том, что представление v$vpd_policy строиться, в том числе, по объекту x$kglcursor, который и так является базовым для представления v$sql - так что вы вполне можете задать собственную версию представления v$vpd_policy во избежание бессмысленного дополнительного соединения. Но и в этом случае нет эффективной связи между двумя объектами x$, лежащими в основе представления.


Но есть и другие проблемы - я обещал еще раз упомянуть о параметре static_policy процедуры add_policy. Этот булев параметр появился в Oracle 9, чтобы вы могли выбрать из двух зол. Если установить этому параметру значение true, то обеспечивающее защиту условие, похоже, будет генерироваться только один раз, при первом полном разборе, а это означает, что пользователь u2 в конечном итоге сможет выполнять в точности тот же запрос, что и пользователь u1, если окажется, что пользователь u1

первым выполнил запрос.

С другой стороны, если установить этому параметру значение false, то функция защиты выполняется (предположительно, дважды) при каждом выполнении (а не только разборе) запроса, и выполняется она в следующем, достаточно объемном анонимном pl/sql-блоке, который не слишком способствует параллелизму и масштабируемости .

begin p := STOCK_PREDICATE(:sn,:on); :v1 := substr(p,1,4000); :v2 := substr(p,4001,4000); :v3 := substr(p,8001,4000); :v4 := substr(p,12001,4000); :v5 := substr(p,16001,4000); :v6 := substr(p,20001,4000); :v7 := substr(p,24001,4000); :v8 := substr(p,28001,4000); :v9 := substr(p,32001,767); :v10 := substr(p, 4000, 1); :v11 := substr(p,8000,1); :v12 := substr(p, 12000, 1); :v13 := substr(p,16000,1); :v14 := substr(p, 20000, 1); :v15 := substr(p,24000,1); :v16 := substr(p, 28000, 1); :v17 := substr(p,32000,1); end;

Я подозреваю, что это изменение было сделано как "аварийное" в ответ на обнаружившееся отсутствие необходимой перегенерации условий защиты, связанных с моментом времени. Ответ, однако, несколько экстремальный. Проблема была решена несколько более тонко в версии 10g сервера Oracle, где появилось несколько уровней "изменяемости" в качестве типа набора правил.

Завершающее соображение - есть рекомендация Oracle не использовать, по возможности, соединения с таблицами, для которых предполагается подобная защита (подумайте, к чему могут привести все эти вложенные представления) и постараться свести условия защиты к простому использованию функции sys_context(). Но посмотрите, что говорит руководство SQL Reference

(версия 9.2, стр. 6-154) о функции sys_context():

Примечание: SYS_CONTEXT возвращает атрибуты сеанса. Поэтому ее нельзя использовать в параллельных запросаз или в среде Real Application Clusters.


Требования


В исходном примере изоляции данных в первой статье была использована таблица со столбцом owner, на основе которой было построено статическое представление, выдающее результирующее множество, зависящее от обращающегося пользователя. Определение этого представления было очень простым и требовало одного условия на этот единственный столбец, а в качестве дополнительного кода потребовался только триггер уровня таблицы. Но что делать при наличии множества различных требований разделения данных, и одного простого условия недостаточно?

Рассмотрим (несколько запутанный) пример системы, используемой сетью супермаркетов для поддержки хранилища данных. Ключевая таблица, показывающая ежедневный уровень запасов товаров, имеет следующее определение:

create table stock_level ( stock_date date, product_id number(6), qty number(8), dept_id varchar2(20), supplier_code varchar2(20));

Таблица показывает уровень запасов по датам и товарам. Таблица денормализована и содержит название отдела в супермаркете, отвечающего за данный товар, а также внешнего поставщика товара.

Супермаркет использует web-систему, позволяющую внешним поставщикам запрашивать уровни запасов по продуктам, которые они поставляют в супермаркет. Внутренне то же приложение позволяет разным отделам запрашивать хранилище данных, но обращаться только к тем данным, которые имеют отношение к соответствующему отделу.

Можно, конечно, применить методы, изложенные в предыдущей статье, и создать два представления этих данных, по одному для каждого способа доступа. Побочный эффект этого подхода, однако, состоит в потенциальной необходимости создания двух экземпляров всех заранее заготовленных отчетов, всех процедур передачи данных и т.д. Ну и, конечно, может потребоваться система с более чем двумя разными наборами требований. Чтобы свести усложения к минимуму или, по крайней мере, сконцентрировать их в одном месте, можно использовать пакет dbms_rls для создания правил защиты (security policies).



Защиту на уровне строк легко


Защиту на уровне строк легко спроектировать и установить, но у нее есть последствия, о которых следует знать. В частности, если вы используете механизм RLS в версии 8, то можете получить неприятный сюрприз при переходе на версию 9. Для сравнительно простых требований я не думаю, что вам действительно надо делать что-то кроме продуманного создания представлений, описанных в первой статье этой серии.
Джонатан Льюис () - независимый консультант с более чем 18-летним опытом использования Oracle. Он специализируется на физическом проектировании баз данных и стратегии использования сервера Oracle. Джонатан - автор книги "Practical Oracle 8i - Building Efficient Databases", опубликованной издательством Addison-Wesley, и один из наиболее известных лекторов среди специалистов по Oracle в Великобритании. Подробнее о его публикациях, презентациях и семинарах можно узнать на сайте www.jlcomp.demon.co.uk, где также находится список ЧаВО The Co-operative Oracle Users' FAQ
по дискуссионным группам Usenet, связанным с СУБД Oracle.
Эта статья первоначально была опубликована на сайте DBAzine.com, сетевом портале, посвященном проблемам различных СУБД и их решениям. Перевод публикуется с разрешения автора.

Защита на уровне строкЧасть 2: Правила защиты


Джонатан Льюис (Jonathan Lewis)

www.jlcomp.demon.co.uk

Перевод: , OpenXS Initiative

В этой мини-серии я продемонстрировал пару простых методов обеспечения изоляции данных различных пользователей или групп пользователей. В этой статье я перехожу к "правильной" защите на уровне строк (RLS), также известной как средства детального контроля доступа (fine-grained access control - FGAC) или средства создания виртуальной приватной базы данных (virtual private database - VPD). Примеры в этих статьях были протестированы с помощью Oracle 9.2.0.3.