Описанная выше простейшая в рамках 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.
Для работы с размеченными строками разумно завести специального административного пользователя в дополнение к 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 типа “прогнозировщик блокировок”, а также нового узла в ЦПУ, целью которого является динамически идентифицировать начало и конец критической секции кода, выполняемой под блокировкой. Идея этого метода в том, продолжать вычисления, не обращая внимания на блокировку. Ключевым моментом здесь является то, что значительная часть исполнимого кода может не зависеть от значения этой переменной блокировки. И после каждой выполненной команды (или после каждой сотни команд) можно будет опрашивать переменную блокировки. Таким образом, запрашивающий процесс будет выполнять не холостой цикл, а полезную работу. В результате влияние блокировок на производительность будет минимальным. Главная задача при этом состоит в определении в исполнимом коде процесса максимально длинной последовательности команд, которые можно безопасно выполнить независимо от значения блокировки.
Поскольку за каждым запросом блокировки следует ее отмена по тому же адресу, то команды запроса блокировки и ее отмены всегда существуют парами и обращаются к одному и тому же адресу. Таким образам эта пара:
идентифицирует диапазон команд, которые выполняются под блокировкой. Эти команды могут быть выполнены в другом месте и проверены позднее, чтобы гарантировать их неизменность; идентифицирует набор команд, выполняющихся независимо от значения переменной блокировки. Эти команды могут быть выполнены вообще независимо от значения блокировки.
Таким образом, узел ЦПУ - “прогнозировщик блокировок” будет динамически идентифицировать начало и конец критической секции с целью оптимизации блокирования.
Поведением процесса, запрашивающего блокировку, управляет параметр _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;
Экземпляр 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.
Эта статья (как и несколько последующих) является непосредственным продолжением статьи «», и рассматривает некоторые особенности средства 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, он такую возможность имеет. Он даже может сделать строку более секретной, но тогда потеряет к ней доступ.
Ситуация напоминает выводимую таблицу, 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'
,
Преподаватель технологий 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
Эта статья является непосредственным продолжением статьи «», и рассматривает примеры поведения средства 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, который появляется в его определении.
Хотя это, очевидно, один из способов реализации защиты на уровне строк, более полезная форма защиты на уровне строк должна, вероятно, быть достаточно гибкой, чтобы работать с группами, а не с отдельными пользователями. Механизм, позволяющий пользователям конкретной группы видеть данные, созданные другими пользователями в этой группе, это, вероятно, минимальное требование полезности.
Идея, лежащая в основе использования контекстов, очень проста и обеспечивает серьезную защиту. В исходном виде ее можно определить тремя особенностями: (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 ведет себя практически так же, как раньше. Пользователи, вставляющие данные в представление, автоматически получают при этом присоединенный к строке код группы. Пользователи, запрашивающие представление, увидят только строки, соответствующие коду их грруппы. Но, в этой реализации пока есть пробел. Как код группы пользователей устанавливается в их локальном контексте?
Для завершения картины надо использовать триггеры на регистрацию. Например, в схеме, в которой создана таблица, процедура и представление, можно выполнить следующий код:
В этом примере функции, генерирующей условие (в которой используется оператор 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; во-вторых, необходимо связать эту функцию с целевой таблицей, перечислив действия, для которых эта функция должна выполняться. Два необязательных шага были описаны в предыдущей статье - создание табличного триггера для установки соответствующих значений управляющих столбцов и триггера базы данных для установки пользовательской "среды" или переменных контекста. Может также иметь смысл создать одно представление на основе таблицы, чтобы скрыть столбцы, устанавливаемые табличным триггером.
В этой статье мы будем предполагать, что пакетный процесс загружает таблицу, а конечным пользователям разрешено только читать таблицу, так что можно проигнорировать особенности вставок, изменений и удалений, и сконцентрироваться на обработке только операторов 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$, лежащими в основе представления.
В исходном примере изоляции данных в первой статье была использована таблица со столбцом 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).
Джонатан Льюис (Jonathan Lewis)
www.jlcomp.demon.co.uk
Перевод: , OpenXS Initiative
В этой мини-серии я продемонстрировал пару простых методов обеспечения изоляции данных различных пользователей или групп пользователей. В этой статье я перехожу к "правильной" защите на уровне строк (RLS), также известной как средства детального контроля доступа (fine-grained access control - FGAC) или средства создания виртуальной приватной базы данных (virtual private database - VPD). Примеры в этих статьях были протестированы с помощью Oracle 9.2.0.3.