Захист на рівні рядків – Частина 2: Правила захисту, Інші СУБД, Бази даних, статті

Джонатан Льюїс (Jonathan Lewis)
www.jlcomp.demon.co.uk


У попередній статті цієї міні-серії я продемонстрував пару простих методів забезпечення ізоляції даних різних користувачів або груп користувачів. У цій статті я переходжу до “правильної” захисту на рівні рядків (RLS), також відомої як засобу детального контролю доступу (fine-grained access control – FGAC) або засоби створення віртуальної приватної бази даних (virtual private database – VPD). Приклади в цих статтях були протестовані за допомогою Oracle 9.2.0.3.


Вимоги


У вихідному прикладі ізоляції даних в першій статті була використана таблиця зі стовпцем 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).


Правила захисту


Мета коштів детального контролю доступу – дозволити вам централізовано приховати всі складнощі “приховування даних”.


Для цього необхідно виконати дві обов’язкових кроку, а також два додаткових необов’язкових кроки, які можуть і не знадобитися в системі побудови звітів. По-перше, необхідно створити функцію, одержує на вході ім’я схеми і ім’я об’єкта, І видає текст допустимої конструкції whereПо-друге, необхідно пов’язати цю функцію з цільовою таблицею, перерахувавши дії, для яких ця функція повинна виконуватися. Два необов’язкових кроку були описані в попередній статті – створення табличного тригера для установки відповідних значень керуючих стовпців і тригера бази даних для установки користувальницької “середовища” або змінних контексту. Може також мати сенс створити одне подання на основі таблиці, щоб приховати стовпці, що встановлюються табличним тригером.


У цій статті ми будемо припускати, що пакетний процес завантажує таблицю, а кінцевим користувачам дозволено тільки читати таблицю, так що можна проігнорувати особливості вставок, змін і вилучень, і сконцентруватися на обробці тільки операторів select.


Підготовка до використання 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.


Проблеми


З цим механізмом пов’язано кілька неминучих проблем. Для початку, в 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.

Висновок


Захист на рівні рядків легко спроектувати і встановити, але в неї є наслідки, про які слід знати. Зокрема, якщо ви використовуєте механізм RLS у версії 8, то можете отримати неприємний сюрприз при переході на версію 9. Для порівняно простих вимог я не думаю, що вам дійсно треба робити щось крім продуманого створення уявлень, описаних в першій статті цієї серії.

Схожі статті:


Сподобалася стаття? Ви можете залишити відгук або підписатися на RSS , щоб автоматично отримувати інформацію про нові статтях.

Коментарів поки що немає.

Ваш отзыв

Поділ на параграфи відбувається автоматично, адреса електронної пошти ніколи не буде опублікований, допустимий HTML: <a href="" title=""> <abbr title=""> <acronym title=""> <b> <blockquote cite=""> <cite> <code> <del datetime=""> <em> <i> <q cite=""> <s> <strike> <strong>

*

*