Row-Level Security b> в b> b> РСУБД b> p>
Антон Злигостев p>
Вступ h2>
Розмежування прав доступу є необхідною
функціональністю будь-якої корпоративної СУБД. Практичні всі сучасні СУБД
надають набір базових засобів з управління правами доступу. Як правило,
підтримуються такі концепції, як користувачі та групи, а також так
звана декларативна безпека - можливість надати цим
користувачам і групам права доступу до певних об'єктів бази даних.
Важливим питанням є шорсткого цієї безпеки, тобто наскільки
детально можна призначити права. p>
Основними об'єктами безпеки в СУБД є
таблиці, уявлення (view), і процедури, що зберігаються. Залежно від типу об'єкта
можна керувати правами на конкретні дії з ним. Наприклад, у випадку таблиць
можна незалежно керувати правами на читання, додавання, видалення і зміна
записів. У тих СУБД, де підтримуються будь-які інші об'єкти (наприклад,
призначені для користувача функції), доступом до них можна керувати аналогічним чином.
У деяких системах можна керувати доступом на рівні окремого стовпця
подання або таблиці. p>
Це досить гнучка і розвинена система, що дозволяє
адміністратора СУБД налаштовувати права доступу користувачів відповідно до їх
службовими обов'язками. p>
Однак у деяких випадках вбудованої в СУБД
функціональності недостатньо для реалізації вимог корпоративної політики
доступу до даних. Справа в тому, що дуже небагато СУБД дозволяють обмежити
доступ користувачів до окремих рядках таблиць, хоча подібна вимога
досить часто зустрічається у прикладних задачах. Наприклад, менеджери з
продажу не мають права «бачити» накладні, виписані в іншому офісі тієї ж
компанії, а директору з продажу всі ці дані доступні. Рядовий бухгалтер не
повинен змінювати документи заднім числом, але головному бухгалтеру це
дозволено. В англомовній традиції дана функціональність називається
Row-Level Security. Усталеною російськомовній термінології немає, тому ми
будемо користуватися англійським терміном, іноді скорочуючи його до RLS. p>
Там, де вбудованих засобів підтримки такої
функціональності немає, розробнику доведеться придумати свій спосіб гарантувати
виконання вимог замовника. Як і в будь-якій іншій задачі, існує
великий вибір таких способів. У цій статті пропонується декілька з них. P>
Реалізація RLS на клієнті
h2>
Одним з найбільш популярних рішень завдання RLS
є вбудовування правил безпеки в клієнтську програму. Оскільки в
наші дні користувачі практично ніколи не спілкуються з СУБД прямо, це
виглядає досить привабливо. Всі дії користувача проходять через
інтерфейс програми, і розробник має можливість закласти як завгодно
складні правила перевірки допустимості дій. Основними перевагами
даного підходу є: p>
велика гнучкість - звичний процедурний мову
програмування дозволяє робити все, що завгодно; p>
можливість визначати, здійснимо чи якийсь
дію, до спроби його виконання (а всі керівництва з usability
настійно рекомендують заздалегідь інформувати користувача про обмеження на
дії - наприклад, виділяючи сірим відповідний пункт меню); p>
Однак разом з тим цей підхід володіє декількома
суттєвими недоліками: p>
небезпечності. Передбачається, що користувачі можуть
підключитися до СУБД тільки за допомогою конкретного клієнтського додатку. Якщо
зловмисний користувач достатньо кваліфікований, то він завжди зможе
отримати доступ до бази, минаючи клієнтську програму, і ігнорувати
реалізовані в ньому правила безпеки. Ще більш імовірним сценарієм у
корпоративних програмах є одночасна робота декількох версій
клієнтського додатка з відмінними наборами правил безпеки. Таким чином,
безпека починає залежати вже не від розробника, а від моторності
системних адміністраторів, що здійснюють експлуатацію. p>
Продуктивність. Для запитів на зміну даних
даний підхід трохи підвищує швидкодію, тому що у разі заборони на
дію взагалі немає необхідності зв'язуватися з сервером. Однак запити на
читання будуть приводити до передачі по мережі надмірних даних - тих рядків,
які будуть відкинуті клієнтським додатком відповідно до правил
безпеки. p>
Цілісність даних. Розмежування прав доступу на
рівні СУБД виконується в декларативному стилі, тобто СУБД гарантовано
перевіряє одні й ті ж обмеження за будь-яких операціях з даними. Але в
клієнтському додатку буде використаний імперативний стиль. Це означає, що
потенційно більше можливостей зробити помилку, або забути застосувати єдиний
набір правил для всіх можливих способів звернення до даних. У вищеописаному
прикладі з менеджерами продажів легко забути внести відповідні обмеження в
будь-які звіти, засновані на даних з таблиці накладних, і дати
користувачеві можливість обійти правила безпеки. p>
Єдиним способом боротьби з цими недоліками
є реалізація перевірки всіх правил безпеки на сервері. p>
Сучасні технології розробки додатків
пропонують два варіанти реалізації RLS на стороні сервера - засобами сервера
баз даних і засобами сервера додатків. p>
Якщо для вашого застосування обрана трирівнева
модель, то реалізація RLS на рівні сервера додатків є цілком прийнятним,
а можливо і найкращим, рішенням. p>
Однак, якщо програма розробляється в класичній
клієнт-серверної моделі, або передбачається наявність кількох серверів
додатків, що працюють з одним сервером даних, то бажано реалізувати RLS
на рівні бази даних. Можливі підходи до вирішення цього завдання ми і розглянемо
в наступному розділі. p>
Реалізація RLS засобами сервера БД h2>
Отже, перед нами стоїть завдання - давати чи не давати
певному користувачеві право на виконання тих чи інших дій з різними
рядками таблиці. Теоретично, у загальному випадку, це означає, що перед
виконанням будь-якої дії перевіряється значення деякого предиката (булевої
функції). Стандартні засоби безпеки СУБД вміють перевіряти предикати,
параметрами яких є ідентифікатор поточного користувача,
ідентифікатор (и) таблиць, і, можливо, окремих стовпців, до яких
здійснюється доступ. Це дозволяє обчислити значення один раз перед початком
виконання будь-якого SQL запиту, і до закінчення його обробки до питання
безпеки не повертатися. Таким чином, зводиться до мінімуму вплив
перевірки безпеки на продуктивність системи. p>
Нам доведеться розширити функціональність таким чином,
щоб предикат безпеки обчислювався незалежно для кожного рядка таблиці. p>
У термінах SQL це означає, наприклад, що до кожного
select-замовлення потрібно неявно додати відповідне умова: p>
select
* From Clients where CompanyName like 'Micro%' AND p>
У даному випадку під мається на увазі
деякий логічне вираження. Далі ми будемо досліджувати різні варіанти
побудови таких виразів, але перед цим варто переконатися в тому, що ми можемо
гарантувати перевірку цієї умови. p>
Нам необхідно ізолювати користувача від прямого
доступу до даних і гарантувати застосування встановлених правил безпеки.
Якщо використовувана СУБД не надає вбудованих механізмів забезпечення
безпеки, то отримати повноцінне рішення завдання не вдасться. p>
Звичайною технікою для ізоляції користувача від збережених
в СУБД даних є побудова відповідного подання (view), і
заборона прямого доступу до нижележащий таблиці. В MS SQL Server це можна зробити
приблизно таким чином: p>
create
view SecureClients as select * from Clients where p>
deny
public read on Clients p>
grant
public read on SecureClients p>
Тоді при виконанні запиту p>
select
* From SecureClients where CompanyName like 'Micro%' p>
користувачі автоматично побачать тільки ті рядки,
для яких повертає TRUE. p>
Тепер розглянемо різні варіанти предикатів,
які можуть використовуватися для перевірки доступу. p>
Користувачі та групи
h2>
Теоретично, основою обчислення предиката
безпеки є ідентифікатор поточного користувача (він, як правило,
доступний в будь-якій СУБД, що підтримує аутентифікацію). Однак його пряме
використання не рекомендується, тому що як корпоративна політика, відповідно
з якою повинна будуватися реалізація системи, рідко маніпулює персоналіями.
У такому випадку важко сформулювати відносно стабільні правила,
які не доведеться переглядати при кожній зміні списку співробітників. p>
Зазвичай всі правила побудовані на основі посад. Їх
аналогом в програмуванні є групи або ролі. Тому в предиката
безпеки нам часто доводиться використовувати вирази типу
IsUserInRole (rolename). Якщо в використовувану СУБД вбудована подібна
функціональність - прекрасно, найкраще використовувати саме її. У такому
випадку суб'єкти безпеки будуть утворювати єдиний простір як для
вбудованої безпеки СУБД, так і для наших розширень. p>
Якщо ж СУБД не надає коштів підтримки Груп
або ролей, то їх теж доведеться реалізовувати вручну. Одним із найпростіших
способів є створення спеціальної таблиці, яка містить список груп або
ролей, і зв'язок її з таблицею користувачів. У залежності від потреб,
можна вибрати різні схеми. Якщо користувач може входити тільки в одну
групу, то досить додати посилання на групу до таблиці користувачів. А якщо
йому може бути призначено одночасно декілька ролей, то для зв'язку треба буде
створити окрему таблицю. p>
У таких випадках предикат IsUserInRole (rolename)
приймає вигляд: p>
exists (select
* From users where ID = CurrentUserID () and user_group = rolename) p>
або p>
exists (select
* P>
from UserRoles p>
where RoleName = rolename and UserID =
CurrentUserID ()) p>
Надалі ми будемо мати на увазі під виразом
IsUserInRole (rolename) або відповідну вбудовану функцію СУБД, або предикат,
побудований вручну відповідно до обраної моделлю. p>
Обмеження на основі існуючих атрибутів
h2>
Якщо правила корпоративної політики виражаються в
термінах предметної області, то можна сформувати відповідний предикат
безпеки в термінах даних, що зберігаються в СУБД. p>
У самому простому випадку достатньо даних з тієї ж
таблиці. Припустимо, доступ до щотижневим документами фінансової звітності
компанії визначається наступними правилами: p>
молодші фінансові аналітики мають право читання
звітів старше шести місяців; p>
старші фінансові аналітики мають право читання
звітів старшою за місяць; p>
всім іншим співробітникам доступ до документів
заборонений. p>
У такому випадку можна побудувати приблизно такий
предикат: p>
(IsUserInRole ( 'senior_analyst') and ReportDate
OR p>
(IsUserInRole ( 'junior_analyst') and ReportDate
У принципі, той же самий результат можна отримати і
іншими способами. Наприклад, ось такий вираз SQL відображає ті ж самі
правила: p>
case p>
when ReportDate>
DateAdd (month, GetDate (), 1) then false p>
when ReportDate>
DateAdd (month, GetDate (), 6) then p>
IsUserInRole ( 'senior_analyst')
p>
else
IsUserInRole ( 'junior_analyst') p>
end p>
Однак тут простежити логіку вже важче, і ситуація
стане ще гірше, коли до розгляду увійдуть інші поля таблиці. Тому ми
обмежимо можливі предикати ось такою структурою: p>
(IsUserInRole () AND ) p>
OR p>
(IsUserInRole () AND ) p>
OR ... p>
(IsUserInRole () AND ) p>
Таким чином, предикат визначає для кожної групи
користувачів вимоги, яким повинна задовольняти рядок таблиці, щоб
доступ до неї була розв'язана. p>
Позбавлення доступу p>
Форма предиката безпеки, розглянута вище,
має на увазі «песимістичний» режим, тобто доступ до даних мають тільки ті
категорії користувачів, які явно перераховані в предикат. p>
ПРИМІТКА p>
Строго кажучи, якщо ми не опишемо ні
одного правила, то предикат буде порожнім і доступ отримають всі користувачі.
Однак видача таким способом доступу хоча б однієї ролі автоматично позбавить
доступу всіх інших користувачів. Вирішити цю проблему можна раз і
назавжди - ввівши додатковий член ... OR FALSE в предикат безпеки.
Однак подібний випадок вироджених неважко обробити спеціальним чином, і
далі в тексті всюди передбачається наявність у списку доступу хоча б одного
явного дозволу. p>
Іноді зручніше описувати правила безпеки
термінах «оптимістичного» режиму, тобто позбавити доступу тільки деякий
багато користувачів. У цьому випадку предикат придбає такий вигляд: p>
NOT p>
( p>
(IsUserInRole () [AND
]) p>
OR ... p>
(IsUserInRole () [AND
]) p>
) p>
У такому випадку доступ надається всім, крім
зазначених ролей, та й вони позбавлені доступу тільки до обмеженої частини даних. p>
Поєднавши ці два підходи, ми отримаємо можливість як
«Карати», так і «милувати» користувачів: p>
(- секція дозволів p>
(IsUserInRole ()
AND ) p>
OR ... p>
(IsUserInRole ()
AND ) p>
) p>
AND NOT p>
(- секція заборон p>
(IsUserInRole () [AND
]) p>
OR ... p>
(IsUserInRole ()
[AND ]) p>
) p>
Такий принцип захисту дуже схожий на використовуваний у
Windows NT. P>
Розглянемо варіанти застосування даної техніки на
прикладі бази даних Northwind, яка входить в постачання MS SQL Server. p>
Локальні атрибути p>
У простому випадку предикати для всіх ролей залежать
тільки від значень полів захищається запису. Розглянемо таблицю Orders
(несуттєві для розглянутої задачі обмеження пропущені): p>
CREATE TABLE Orders ( p>
OrderID int IDENTITY (1, 1) NOT
NULL, p>
CustomerID nchar (5) NULL, p>
EmployeeID int NULL, p>
OrderDate datetime NULL, p>
RequiredDate datetime NULL, p>
ShippedDate datetime NULL, p>
ShipVia int NULL, p>
Freight money NULL CONSTRAINT
DF_Orders_Freight DEFAULT (0), p>
ShipName nvarchar (40) NULL, p>
ShipAddress nvarchar (60) NULL, p>
ShipCity nvarchar (15) NULL, p>
ShipRegion nvarchar (15) NULL, p>
ShipPostalCode nvarchar (10)
NULL, p>
ShipCountry nvarchar (15) NULL, p>
CONSTRAINT FK_Orders_Employees
FOREIGN KEY (EmployeeID) p>
REFERENCES Employees (EmployeeID) p>
) p>
Припустимо, що правила корпоративної безпеки по
відношенню до даних замовлень визначені наступним чином: p>
Менеджери з продажу (роль 'Sales Representative')
мають право переглядати тільки «свої» замовлення і не можуть бачити замовлення, введені
іншими менеджерами з продажу. p>
Директор з продажу (роль 'Vice President, Sales')
має право переглядати будь-які замовлення. p>
Всі інші співробітники доступу до замовлень не мають. p>
Цим правилам відповідає ось таке уявлення: p>
CREATE VIEW [Secure Orders] AS p>
SELECT * FROM Orders where p>
(IsUserInRole ( 'Sales
Representative ') AND EmployeeID = CurrentEmployeeID ()) p>
OR p>
(IsUserInRole ( 'Vice President,
Sales ') AND TRUE) p>
Тут мається на увазі, що функція CurrentEmployeeID ()
таким собі «магічним» чином повертає ідентифікатор співробітника, відповідний
користувачеві, від імені якого вироблено підключення. Реалізація цієї
функції, як і функції IsUserInRole (), залежить від використовуваної СУБД. Зверніть
увагу на другу частину предиката: для директора з продажу ніяких
додаткових обмежень не передбачено, але для спільності ми використовували
вираз TRUE для представлення цього факту. При підготовці предиката вручну
фрагмент AND TRUE можна опустити, хоча оптимізатори, що використовуються в сучасних
СУБД, достатньо інтелектуальні, щоб викинути надлишкові вирази з плану
запиту. p>
РАДА p>
Іноді предикат безпеки може
виявитися настільки складним, що оптимізатор СУБД не зможе самостійно
побудувати оптимальний план виконання запиту. У таких випадках може
знадобитися ручне перетворення вираження предиката в більш адекватну
форму. Поки що ми будемо вважати, що оптимізатор ідеальний, і всі вирази
будуть представлені в найбільш зручному для читання вигляді. p>
Якщо в майбутньому керівництво компанії вирішить, що доступ
до замовлень, відвантаженим більше шести календарних місяців тому, можна
надати всім співробітникам компанії, то предикат безпеки візьметься за це
вигляд: p>
(IsUserInRole ( 'Sales Representative') AND EmployeeID =
CurrentEmployeeID ()) p>
OR p>
(IsUserInRole ( 'Vice President, Sales') AND TRUE) p>
OR p>
(IsUserInRole ( 'Everyone') AND ShippedDate
Варто звернути увагу на дві особливості предиката: p>
По-перше, (знову ж таки з міркувань спільності) в
додатковому умови перевіряється належність поточного користувача до
групі Everyone. Ця спеціальна група з визначення включає всіх
співробітників, і вираз IsUserInRole ( 'Everyone') є тотожне
істинним. Це дозволяє виключити його з предиката з метою оптимізації. p>
По-друге, умова порівняння дати відвантаження замовлення з
поточною датою сформульовано так, щоб до поля не застосовувалося жодної функції.
Альтернативні подання того ж вирази: p>
DateAdd (month, 6, ShippedDate) <
GetDate () p>
і p>
DateDiff (month, ShippedDate, GetDate ())
> = 6 p>
хоча і є математично еквівалентними, швидше за
за все, завадять оптимізатору СУБД використовувати індекс по полю ShippedDate (якщо
він є). p>
Атрибути пов'язаних таблиць p>
У корпоративну політику безпеки можуть входити і
більш складні правила, які пов'язують різні сутності предметної області
між собою. Наприклад, припустимо, що компанія Northwind виросла, і в ній
є кілька філій. Структура таблиці співробітників зазнає
відповідні зміни: p>
ALTER
TABLE [Employee] p>
ADD [DivisionID] int CONSTRAINT
[DivisionID_FK] p>
REFERENCES
[Division] ([DivisionID]) p>
Новий варіант правила № 1 з попереднього розділу
формулюється так: p>
Менеджери з продажу (роль 'Sales Representative')
мають право переглядати тільки замовлення, введені менеджерами з того ж
філії. p>
Відповідна частина предиката безпеки тепер
прийме такий вигляд: p>
(IsUserInRole ( 'Sales
Representative ') p>
AND p>
(select DivisionID from Employees where
EmployeeID = CurrentEmployeeID ()) p>
= (select DivisionID from Employees where
EmployeeID = EmployeeID) p>
Ще один приклад правил безпеки, що вимагає
звернення до інших таблиць, пов'язаний із захистом підлеглих таблиць. Разом з
записами в таблиці замовлень необхідно захистити і запису в таблиці деталей
замовлень. Застосуємо правила з попереднього прикладу (де ще не було філій) до
таблиці Order Details: p>
(IsUserInRole ( 'Sales
Representative ') p>
AND p>
select (EmployeeID from Orders o where
o.OrderID = OrderID) p>
= CurrentEmployeeID ()) p>
OR p>
(IsUserInRole ( 'Vice President, Sales') AND
TRUE) p>
OR p>
(IsUserInRole ( 'Everyone') p>
AND p>
select (ShippedDate from Orders o where
o.OrderID = OrderID) p>
<
DateAdd (month, -6, GetDate ()) p>
На жаль, такий вид предиката не дуже наочний.
Він не відображає взаємозв'язки між правилами безпеки для деталей замовлень і
для самих замовлень. Тому краще застосувати трохи інший спосіб побудови
уявлення, а що було розглянуто раніше: p>
create view [Secure Order Details] as select od .* from [Order Details]
od p>
join [Secure Orders] so on
od.OrderID = so.OrderID p>
У такому вигляді сутність використовуваного обмеження
безпеки очевидна. Крім того, зміна правил безпеки для замовлень
(яке вплине на визначення подання Secure Orders) автоматично
відіб'ється і на їх деталях. p>
У даному випадку ми не накладаються ніяких
додаткових обмежень на деталі замовлення. Однак при необхідності ми можемо
точно так само додати локальний предикат безпеки в умову where. p>
Деякі підсумки h2>
Отже, розглянута техніка дозволяє забезпечити
розділення прав доступу в термінах значень захищаються даних. У багатьох
випадках цей спосіб є найбільш зручним, і його головна перевага --
малі зусилля з адміністративної підтримки. Модифікації будуть потрібні тільки при
зміні корпоративної політики, а це досить рідкісне явище. Не вимагається
динамічного керування доступом на рівні окремих об'єктів - наприклад,
замовлення, відвантажені сьогодні, автоматично стануть доступними всім співробітникам
через півроку. p>
Проте в деяких випадках потрібно надавати або
відмовляти в доступі до конкретних записів в адміністративному порядку,
незалежно від зберігаються в них значень. Такі вимоги можуть бути пов'язані з
швидкоплинні правилами безпеки, для яких недопустимі затримки в
реалізації, неминучі при модифікації схеми бази даних. Крім того, іноді
швидкодії СУБД буде недостатньо для обчислення предикатів безпеки
на основі існуючих атрибутів. p>
Природним рішенням даної задачі є внесення
в базу додаткової інформації, не пов'язаної безпосередньо з моделюється
предметною областю. Модифікація цих даних і дозволить керувати доступом до
конкретним записів без зміни схеми БД. Способи реалізації такого роду
рішень розглядаються в наступному розділі. p>
Обмеження на основі додаткових атрибутів
h2>
Усі міркування, розглянуті раніше для існуючих
атрибутів, залишаються справедливими і для додаткових атрибутів. Ми
як і раніше, будемо розглядати предикати безпеки загального вигляду. Однак
тепер завдання ставиться дещо ширше: якщо раніше основною проблемою було
перетворити правила корпоративної безпеки у відповідні вирази з
використанням існуючої моделі, то тепер нам належить доповнити модель
даних. Тепер потрібно окремо зберігати інформацію про те, яким ролям
надано доступ до яких записів. p>
Допоміжна таблиця h2>
З точки зору реляційної моделі, записи, що захищається
таблиці та ролі користувачів знаходяться у відношенні багато-ко-багатьом.
Традиційним способом реалізації таких відносин є створення
допоміжної таблиці: p>
CREATE TABLE [Orders Security] ( p>
OrderID int CONSTRAINT Order_FK
REFERENCES Orders (OrderID), p>
RoleID int CONSTRAINT Role_FK
REFERENCES UserRoles (RoleID), p>
CONSTRAINT [Orders_Security_PK]
PRIMARY KEY (OrderID, RoleID) p>
) p>
Тепер предикат безпеки буде виглядати так: p>
exists ( p>
select * p>
from [Orders Security] os p>
join UserRoles ur on ur.RoleID
= Os.RoleID p>
where ur.UserID =
GetCurrentUserID () and os.OrderID = OrderID p>
) p>
Ми перевіряємо, що хоча б однієї ролі з тих, в які
входить користувач, наданий доступ до відповідного запису в таблиці
замовлень. p>
Тепер можна динамічно видавати або відбирати
дозволу на записи таблиці замовлень кожній з ролей. Очевидно, що відразу після
предиката введення в дію ніхто нічого не побачить - таблиця Orders Security
порожній. Давайте видамо віце-президенту дозвіл на доступ до всіх замовленнями: p>
insert
into [Orders Security] (OrderID, RoleID) p>
select OrderID, @ VicePresidentRoleID from
Orders p>
Ми припускаємо, що змінна @ VicePresidentRoleID
вже проініціалізувати відповідним значенням. Зверніть увагу на те, що
виконання такої команди вимагає привілеїв адміністратора БД - потрібен доступ на
читання з таблиці Orders і запис до таблиці Orders Security. Відзначимо також те,
що, на відміну від обмежень на основі існуючих атрибутів, які
автоматично застосовуються до нових записів, динамічні обмеження вимагають
модифікації допоміжної таблиці при кожній вставці в основну. p>
Зберігання даних в тій же таблиці h2>
Розглянутий вище спосіб реалізації динамічних
обмежень може виявитися не найефективнішим. Якщо у випадку природних
обмежень, що виражаються за допомогою локального предиката, накладні витрати були
пов'язані тільки з обчисленням додаткових виразів, то тепер у будь-якому разі
потрібно звернення до допоміжної таблиці. У більшості випадків список
ролей, яким доступна конкретна запис, досить короткий. Правило № 3 нашої
уявної корпоративної політики безпеки дозволяє зв'язати замовлення
старше півроку з єдиною роллю Everyone, тому що всі інші ролі вже
присутні в ній. Це означає, що рано чи пізно більшість записів
таблиці замовлень будуть пов'язані з цією роллю. p>
У зв'язку з цим виникає спокуса зберегти список
ролей прямо в захищається запису. Деякі СУБД надають можливість
зберігати списки значень в полі запису, однак це є екзотикою. Тому
ми виберемо інший спосіб порушити вимоги першого нормальної форми - будемо
зберігати список ролей у вигляді рядка. Мова SQL надає нам можливість
перевірити рядок на наявність заданої підрядка за допомогою оператора like. p>
До таблиці Orders доведеться внести наступні зміни: p>
alter table orders add ACL varchar (1024) default',' p>
У полі ACL (Access Control List, список контролю
доступу) ми будемо зберігати список ідентифікаторів ролей, розділений комами.
Тому предикат безпеки прийме такий вигляд: p>
ACL
like '%,' + cast (GetCurrentUserRoleID () as varchar (12)) +',%' p>
Щоб спростити завдання серверу, нам доведеться додати
«Зайві» коми на початку і в кінці рядка. p>
Варто відзначити, що подібна техніка виправдовує себе
тільки в деяких випадках, так що не варто застосовувати її без експериментальної
перевірки. p>
Бітові маски h2>
Ще один варіант подання ACL в захищається
таблиці полягає у використанні бітових операцій з цілими числами. Припустимо,
що повна кількість ролей у системі обмежена якимось розумним числом,
наприклад 32. Тоді можна зберігати ACL у вигляді цілого числа (або декількох цілих
чисел, якщо ролей більше 32х), зіставивши кожної ролі фіксовану позицію в
цьому числі. Предикат безпеки візьме ось такий вигляд: p>
(ACL & GetCurrentUserRoleMask ())
> 0 p>
Як і в попередньому випадку, продуктивність
подібного способу може бути далека від оптимальної, і перед його впровадженням
необхідно провести експериментальну перевірку. p>
Обмеження на модифікацію
h2>
До цих пір ми розглядали виключно запити на
вибірку даних. Модель безпеки, не захищає дані від
несанкціонованої модифікації, не має сенсу. Давайте розглянемо посібник
поширити наші досягнення на інші типи запитів SQL. p>
Точно так само, як і для запитів Select, ми повинні
обчислити предикат безпеки для кожної з рядків, які користувач
спробує модифіковані. Нам потрібно повідомити користувачеві про помилку, а також
подбати про неминучу скасування результатів некоректного дії. Цього
можна домогтися, застосовуючи тригери. p>
У слеующіх прикладах вираз
означає предикат, аналогічний розглянутим вище.
Як і для select, предикат може бути представляти або природне, або
динамічне обмеження. p>
Для природних обмежень принципово важливі дві
предиката: умова, яка перевіряє стан даних до зміни, і
окреме умова, яка перевіряє стан даних після зміни. Перше
умова необхідно перевіряти в тригерах на видалення та зміна записів, а
другий - в тригерах на внесення змін та вставку записів. Може з'явитися спокуса
використовувати більш складні правила для зміни даних, але робити цього
звичайно не варто. Надто легко отримати нецелостную схему безпеки, яка
дозволяє користувачеві отримати різні результати залежно від порядку
дій. Наприклад, заборона на збільшення суми замовлення більш ніж на 500 рублів
(в один прийом) легко обходиться шляхом послідовного неодноразового
збільшення суми. А заборона на вилучення замовлень VIP-клієнта, не супроводжувалися
забороною на зміну таких замовлень, може призвести до спокусі просто
«Переписати» замовлення на іншого клієнта. P>
Для динамічних обмежень доведеться зберігати трохи
більше інформації, щоб врахувати можливість роздільного надання прав на
різні типи модифікацій. p>
Приблизно так виглядатимуть наші тригери на T-SQL: p>
Delete p>
create trigger CheckSecurity on for delete p>
as p>
if exists (select * from deleted where not ) p>
begin p>
RAISERROR ( 'Access denied', 16,
1) p>
ROLLBACK TRANSACTION p>
end p>
Insert p>
create trigger CheckSecurity on for insert p>
as p>
if exists (select * from inserted where not ) p>
begin p>
RAISERROR ( 'Access denied', 16,
1) p>
ROLLBACK TRANSACTION p>
end p>
Update p>
Цей приклад трохи складніше, тому що нам треба перевірити
не тільки старі, але й нові значення в таблиці: p>
create trigger CheckSecurity on for insert p>
as p>
if exists (select * from inserted where not ) p>
or p>
exists (select * from deleted
where not ) p>
begin p>
RAISERROR ( 'Access denied', 16,
1) p>
ROLLBACK TRANSACTION p>
end p>
Висновок
h2>
Ці міркування носили значною мірою
теоретичний характер. З одного боку, це вселяє впевненість у тому, що
ніякі важливі особливості поставленої задачі не залишилися без уваги і
зайві подробиці не відволікали від вирішення. p>
З іншого боку, тим читачам, яким захочеться
RLS застосувати на практиці, доведеться чимало попрацювати. Для них
призначений наступний розділ. p>
Міркування щодо реалізації
h2>
Перший і основний рада: експериментуйте, перш ніж
запускати модифікацію в експлуатацію. Переконайтеся, що предикати складені
вірно. p>
Друге: продуктивність. Найбільш безневинні запити
тепер будуть приховувати в собі додаткові обчислення. Тому не забувайте
про індекси. Поза сумнівом, крім створення і модифікації таблиць буде потрібно
створити необхідні індекси. Можливо, деякі з існуючих індексів
доведеться змінити, щоб плани запитів не дуже постраждали. Перевіряти не
тільки фактичний час виконання запитів, але і плани, які будує СУБД. p>
Ну і останнє: не забудьте акуратно призначити
привілеї на об'єкти СУБД таким чином, щоб користувачі не могли отримати
прямий доступ до даних, минаючи перевірки. Крім того, подбайте про захист самих
визначень тригерів і подань від модифікації. p>
Альтернативи
h2>
Провідні виробники СУБД не залишили розглянутий у
статті питання без своєї уваги. Oracle 8i і вище підтримує так званий
fine grained access control. Фактично, це застосування такий самий
математики, але дещо іншими технічними засобами. Замість механізму View
використовуються спеціальні можливості додаткових пакетів Oracle по
встановлення політики безпеки. p>
MS SQL Server 2005 (codename "Yukon") також буде
підтримувати можливості RLS. Детальний документація на цей механізм ще не
опублікована. Точно відомо лише те, що у T-SQL будуть введені спеціальні
конструкції. Судячи за наявними документами, предикати безпеки (RULES) будуть
створюватись як окремі сутності, а спеціальні версії команд grant і revoke
будуть призначати ці предикати користувачам і групам. p>
Список літератури h2>
Для підготовки даної роботи були використані
матеріали з сайту http://www.rsdn.ru/
p>