Головна › Архітектура корпоративної аналітики
Контроль доступу та застосування політик
Де саме обвалюється RBAC, чим платить ABAC, як живе рушій політик у продакшні та чому правило доступу має стояти в площині даних, а не в застосунку.
У цьому напрямі
Контроль доступу ламається не тоді, коли хтось написав неправильне правило, а тоді, коли правило стоїть не в тому місці. Застосунок захищає рівно ті шляхи, які ви написали; сховище має ще п'ять — BI-інструмент, ноутбук, Spark-джоба, репліка та відновлення з бекапу. Цей модуль розбирає три рішення, які визначають долю всієї моделі: коли переходити з ролей на атрибути, як винести рішення в окремий рушій політик без втрати аудиту та затримки, і як перенести застосування правила на рівень рядків і колонок разом із обмеженням мети.
RBAC і точка його обвалу: від вибуху ролей до атрибутів
RBAC працює доти, доки право доступу є функцією лише посади. Модель гранично проста: суб'єкт отримує роль, роль несе набір дозволів, перевірка зводиться до пошуку в таблиці. Її головна цінність не в простоті, а в аналізованості: на питання «хто бачить цю таблицю?» відповідає один SELECT, і відповідь є повною.
Обвал настає в момент, коли рішення починає залежати від властивостей ресурсу або контексту — грифа запису, юрисдикції, належності до проєкту, часу доби, заявленої мети звернення. У RBAC для цих властивостей немає місця, тому єдиний спосіб їх виразити — закодувати комбінацію в саму назву ролі. Каталог ролей стає декартовим добутком:
|посади| × |юрисдикції| × |грифи| × |команди|= 14 × 9 × 4 × 30 = 15 120 ролей там, де реально існує 14 посад;- кожна нова юрисдикція коштує не одну роль, а 14 × 4 × 30 = 1 680 нових ролей;
- перегляд прав перестає бути можливим: людина не здатна осмислено підтвердити 15 тисяч рядків.
ABAC (NIST SP 800-162) переносить ці властивості з назви ролі в дані. Рішення обчислюється як функція чотирьох груп атрибутів — суб'єкта, ресурсу, дії та середовища. Роль при цьому не зникає: вона лишається одним атрибутом суб'єкта серед інших.
- Що ABAC купує: нова юрисдикція чи новий гриф стають значенням у довіднику, а не новим об'єктом у каталозі прав. Політика лишається тією самою.
- Чим ABAC платить — вибух атрибутів: кожен атрибут потребує власника, системи-джерела, TTL і процедури відкликання. Атрибут без власника — це рішення, яке неможливо проаудитувати.
- Втрата аналізованості: у RBAC «хто бачить X» — це запит; в ABAC це задача перебору політик над простором атрибутів. Зворотне питання стає обчислювально важким, і його доводиться матеріалізувати окремо.
- Гібрид, який виживає в продакшні: RBAC грубо визначає, який виклик вам дозволено зробити (50–200 ролей); ABAC точно визначає, які рядки й колонки цей виклик поверне.
Практичний критерій вибору один. Якщо нову вимогу можна задовольнити, дописавши значення в довідник атрибутів — ви в ABAC. Якщо для неї треба створити роль — ви вже всередині вибуху ролей, просто ще не порахували його.
-- RBAC: every new axis multiplies the entitlement catalogue.
-- 14 job families x 9 jurisdictions x 4 classification tiers x 30 deal teams
SELECT count(*) AS role_count
FROM roles
WHERE name ~ '^analyst_[a-z]{2}_(public|internal|confidential|restricted)_deal[0-9]+$';
-- role_count = 15120 <- fourteen real jobs hide behind these
-- ABAC: the same 15120 grants collapse into one predicate over attributes.
-- The role does not vanish; 'analyst' survives as ONE subject attribute.
SELECT d.*
FROM deals d
WHERE current_setting('app.job_family') = 'analyst'
AND d.jurisdiction = ANY (string_to_array(current_setting('app.jurisdictions'), ','))
AND d.classification <= current_setting('app.clearance')::int
AND d.deal_team_id = ANY (string_to_array(current_setting('app.teams'), ',')::int[]);
-- Adding jurisdiction 'PL': one row in a reference table, zero new roles.
-- Under RBAC the same change would have minted 1680 of them.
На практиці
Європейський інвестиційний банк проводив піврічний перегляд прав на каталозі з 15 120 ролей (14 посадових родин × 9 юрисдикцій × 4 грифи × 30 угодних команд). 41% ролей не використали жодного разу за 12 місяців, середній користувач тримав 3.2 ролі, а сам перегляд забирав 6 тижнів роботи двох аналітиків. Перемоделювання в гібрид — 62 ролі плюс 4 атрибути з довідників HR і CRM — скоротило каталог у 244 рази, а перегляд до 2 днів.Антипатерн
Створювати роль на кожну нову комбінацію (`analyst_de_confidential_deal17`) і називати це «тонким налаштуванням RBAC». Діагностична ознака проста: щоб задовольнити вимогу бізнесу, ви відкриваєте каталог ролей, а не довідник атрибутів.Рушій політик: PDP/PEP/PIP, policy-as-code та бюджет затримки
Винесення рішення із застосунку — це розділення чотирьох ролей, зафіксоване ще в архітектурі XACML і відтворене кожним сучасним рушієм: PEP перехоплює звернення та застосовує вердикт, PDP обчислює вердикт, PIP постачає атрибути, PAP адмініструє політики. Цінність не в абревіатурах, а в наслідку: правило перестає бути розсипаним по if-ах у 340 сервісах і стає одним артефактом, який можна прочитати, протестувати й версіонувати.
Policy-as-code означає рівно п'ять інженерних зобов'язань.
- Deny-by-default.
default allow := false— це найважливіший рядок у файлі. Політика, що починається з дозволу, при будь-якій прогалині в покритті пропускає. - Алгоритм комбінування задає семантику, а не стиль.
deny-overridesозначає, що явна заборона (наприклад, судова заборона на видалення чи ембарго) перемагає будь-який дозвіл вище.permit-overridesу тому ж наборі політик дає протилежний результат на тих самих даних. - Тестованість. Політика — це чиста функція
(input) → decision, тож на неї пишуться регресійні тести й вона проходить CI так само, як код. - Відтворюваність рішення. Журнал має нести три речі: вхід, хеш версії політики та момент актуальності атрибутів. Без них ви не доведете через рік, чому доступ був наданий.
- Межа давності самого бандла. Sidecar-PDP працює на тій політиці, яку востаннє встиг забрати. Штампуйте кожен бандл максимально допустимою давністю і закривайте вузол при її перевищенні: без цієї межі відрізаний від роздачі вузол днями шанує вже відкликані права й увесь цей час звітує про повну справність.
Затримка — місце, де наївна архітектура вмирає. Мережевий виклик PDP коштує 1–5 мс. Якщо PEP питає дозвіл на кожен рядок результату з 50 000 рядків, ви щойно додали до запиту від 50 до 250 секунд. Правильна відповідь не в масштабуванні PDP, а в частковій евалуації: PDP отримує відомі атрибути суб'єкта, обчислює все, що можна обчислити, і повертає не «так/ні», а залишковий предикат — умову, яку виконавець запиту вставляє у WHERE. Одне звернення до рушія замість 50 тисяч, а фільтрація йде там, де живуть дані.
Свіжість атрибутів визначає лаг відкликання. Якщо PIP кешує атрибути з TTL 15 хвилин, то звільнений співробітник зберігає доступ до 15 хвилин — це не баг, це властивість конструкції, яку треба або прийняти свідомо, або зняти push-інвалідацією на подіях життєвого циклу.
package data_access
# Deny-by-default. The single most consequential line in this file.
default allow := false
# Coarse capability still comes from the role - RBAC survives here.
allow if {
input.action == "read"
input.subject.roles[_] == "claims_analyst"
clearance_ok
purpose_ok
not embargoed
}
clearance_ok if input.subject.clearance >= input.resource.classification
# Purpose is a request-time claim, never a property of the person.
purpose_ok if input.purpose in data.consent.allowed_purposes[input.resource.basis]
# deny-overrides: an explicit prohibition beats every permit above.
embargoed if input.resource.legal_hold == true
# Decision log contract - all three fields, or the decision is not reproducible.
decision := {
"allow": allow,
"policy_sha": data.build.git_sha,
"attrs_as_of": input.subject.attributes_fetched_at
}
# Partial evaluation with resource.* left unknown returns a RESIDUAL, not a boolean:
# classification <= 2 AND legal_hold = false
# The query engine splices that into WHERE. One engine call, not 50k.
На практиці
Страховий маркетплейс тримав логіку авторизації в 340 мікросервісах — у середньому в 11 місцях на сервіс. Після винесення в один Rego-бандл із sidecar-PDP: p99 затримки авторизації 0.4 мс завдяки частковій евалуації та фільтру, що йде в запит; час викатки зміни політики впав з 3 тижнів до 12 хвилин; а перший же прогін регресійних тестів політики виявив 7 сервісів, які при таймауті PDP працювали fail-open.Антипатерн
Ставити PDP у мережевий виклик на кожен рядок результату, а потім, коли запит починає займати хвилини, вводити fail-open по таймауту «щоб не блокувати користувачів». Це перетворює перевантаження системи на автоматичне скасування контролю доступу — рівно в момент найбільшого ризику.Застосування в площині даних: RLS, маскування колонок і обмеження мети
Застосунок захищає рівно ті шляхи, які ви написали. Одна й та сама таблиця сховища доступна щонайменше з п'яти інших дверей: BI-інструмент із власним конектором, ноутбук дата-сайєнтиста, Spark-джоба, логічна репліка та відновлення з бекапу. Перевірка в контролері порталу не стосується жодних із них. Це не питання дисципліни команди — це питання того, що точка застосування стоїть вище за точку зберігання.
Рішення: опустити предикат у площину даних. Row-level security в Postgres, row-фільтри та маски колонок у Snowflake, Databricks чи Ranger, або доступ виключно через один керований об'єктний шар — саме цю задачу розв'язує онтологічний рівень Palantir Foundry разом зі своїм рівнем політик. Механізм різний, інваріант однаковий: жоден споживач не має шляху до рядка в обхід правила.
- Власник таблиці — не виняток. У Postgres власник таблиці обходить RLS, доки не ввімкнено
FORCE ROW LEVEL SECURITY; суперкористувачі та ролі з атрибутомBYPASSRLSобходять політики завжди, і FORCE на них не поширюється. ETL, що працює від імені власника, читає все — це найпоширеніша тиха діра, а сервісну роль із BYPASSRLS треба прибирати з конвеєра, а не сподіватися на прапорець. - Кілька політик на одну таблицю комбінуються через
OR. Ширша політика не звужує сусідню; додаючи «дослідницьку» політику до «лікувальної», ви розширюєте доступ, а не уточнюєте його. - Маскувати, а не прибирати колонку. Видалення колонки ламає контракт зі споживачами нижче за течією; детермінований токен (HMAC зі спільним pepper) зберігає схему й можливість з'єднання, не розкриваючи значення. Випадковий UUID на кожне читання руйнує join мовчки.
Обмеження мети (GDPR ст. 5(1)(b)) не виражається роллю. Той самий лікар у вівторок лікує пацієнта, а в середу робить ретроспективне дослідження. Особа, посада й навіть набір атрибутів ідентичні — законна підстава різна. Отже, мета має бути заявою часу запиту: клієнт передає її явно, політика перевіряє її проти підстави обробки конкретного запису, а журнал зберігає її разом із рішенням. Ефективний доступ — це перетин заявленої мети та підстави: якщо політика дозволяє дослідження, а запис не має відповідної згоди, вердикт — відмова.
Похідні дані успадковують обмеження. Вітрина, побудована з обмежених рядків, без успадкування правил стає каналом відмивання доступу: те, чого не можна прочитати рядком, читається сумою. Мінімальний захист — поріг k-анонімності на агрегати та наскрізний лінеаж грифа від джерела до вітрини.
-- One enforcement point in the data plane. The BI tool, the notebook and the
-- nightly export all traverse the same predicate. No application code involved.
ALTER TABLE encounters ENABLE ROW LEVEL SECURITY;
ALTER TABLE encounters FORCE ROW LEVEL SECURITY; -- applies to the owner too
-- Purpose is a request-time claim bound to the session, never a role.
CREATE POLICY encounters_treatment ON encounters FOR SELECT
USING (
current_setting('app.purpose', true) = 'treatment'
AND care_team_id = ANY (string_to_array(current_setting('app.care_teams', true), ',')::int[])
);
CREATE POLICY encounters_research ON encounters FOR SELECT
USING (
current_setting('app.purpose', true) = 'research'
AND consent_research = true -- lawful basis on the ROW
AND discharged_at < now() - interval '30 days'
);
-- Two policies on one table are OR-ed: adding one WIDENS access. Verify, do not assume.
-- Column level: mask, never drop. A deterministic token keeps the join key alive.
CREATE VIEW encounters_governed AS
SELECT id,
care_team_id,
CASE WHEN current_setting('app.purpose', true) = 'treatment'
THEN patient_mrn
ELSE encode(hmac(patient_mrn, current_setting('app.pepper', true), 'sha256'), 'hex')
END AS patient_mrn,
diagnosis_code
FROM encounters;
На практиці
Лікарняне сховище обслуговувало 3 BI-інструменти, ноутбуки дата-сайєнс-команди та нічний експорт до страховика, тоді як перевірки доступу існували лише в порталі. Аудит показав, що 100% обмежених рядків читалися з ноутбука в обхід порталу. Після перенесення правил у RLS з обов'язковою заявленою метою: одна точка застосування, падіння пропускної здатності 6% на потоці 220 тис. рядків/с, і кожне звернення в журналі несло код мети — час відповіді на запит суб'єкта даних скоротився з 9 днів до 40 хвилин.Антипатерн
Робити «чисту» копію таблиці під кожну аудиторію замість одного правила над одним джерелом. Копії розходяться вже на другому релізі, відкликання доступу не поширюється на них жодним чином, а лінеаж грифа обривається на першому ж `CREATE TABLE AS SELECT`.Джерела, з яких виведено напрям
- A Comparison of Attribute Based Access C
- A Role and Attribute Based Access Contro
- A Comprehensive Review of Access Control
- A Literature Review on Access Control in
- A Distributed Access Control Architectur
- A Multipolicy Authorization Framework fo
- Authentication and Authorization in Microservices
- A Comparison of Logical Formula and Enum
- Cryptographically Secure Information Flow Control on Key V
- A Framework for Policies over Provenance
- A Semantic Hierarchy for Erasure Policies
- A Practical Attribute Based Document Col
Пройти інтерактивно
У кожного напряму є питання, картки з інтервальним повторенням і облік прогресу. Для них потрібен акаунт — безкоштовний і на одну хвилину.
Відкрити інтерактивний трек Створити безкоштовний акаунт