Динамічна оцінка коду на Java - розумна чи неохайна?


30

Я намагаюся створити гнучку рамку ACL на Java для свого застосування.

Багато рамок ACL побудовані за списком правил, де правило складається у формі власника: action: resource . Наприклад,

  • "JOHN може ПРОСМОТРИ ресурс FOOBAR-1"
  • "МЕРЕ може бачити ресурс FOOBAR-1"
  • "МЕРЕ може редагувати ресурс FOOBAR-1"

Це привабливо, тому що правила легко можна серіалізувати / зберігати до бази даних. Але моя програма має складну бізнес-логіку. Наприклад,

  • "Усі користувачі у відділі 1 із стажем понад 5 років можуть переглядати ресурс FOOBAR-1, ще не дозволений"
  • "Усі користувачі у відділі 2, якщо дата після 15.03.2016, можуть переглядати ресурс FOOBAR-2, ще не дозволений"

Спочатку задумавшись, було б кошмаром розробити схему бази даних, яка могла б обробляти нескінченно складні правила, такі як ці. Тому, здається, мені потрібно «запекти» їх у складеному додатку, оцінити їх для кожного користувача, а потім створити власнику: action: правила правил в результаті оцінки. Я хочу уникати логіки в складеній програмі.

Отже, я думав представляти правило у формі предиката : action: resource , де предикат - булевий вираз, який визначає, чи дозволений користувач. Присудок буде рядком вираження JavaScript, який можна оцінити за допомогою двигуна Rhino Java. Наприклад,

  • return user.getDept() == 1 && user.seniority > 5;

При цьому предикати легко зберігаються до бази даних.

Це розумний ? Це неохайно ? Це хитрість ? Це надмірно розроблено ? Це безпечно (мабуть, Java може пісочницю двигуна Rhino).


8
Яка вигода від спроби просунути ці бізнес-правила в базу даних над введенням логіки у складений додаток?
Вінстон Еверт

6
@WinstonEWert Екстерналізація правил позбавляє від необхідності перекомпілювати та перерозподілити додаток у разі зміни, додавання чи видалення правила.
Twittopher


2
Цікаве запитання! Я хотів би побачити відповідь, яка не зосереджена на безпеці, а на аспектах технічного обслуговування, надійності та простоти використання такого рішення.
Олівер

6
Це схоже на правила електронної пошти Outlook, що по суті є механізмом правил, який може налаштовуватися користувачем.

Відповіді:


37

Передача динамічних даних в інтерпретатор мови вашої реалізації зазвичай є поганою ідеєю, оскільки це перетворює потенціал на пошкодження даних в потенціал для зловмисного поглинання програм. Іншими словами, ви виходите з вашого шляху , щоб створити в ін'єкції коду уразливості.

Вашу проблему можна краще вирішити за допомогою механізму правил або, можливо, мови, що залежить від домену (DSL) . Подивіться ці поняття вгору, не потрібно винаходити колесо.


16
Але чи не використовувався би тут JavaScript як сценарій, що нагадує DSL? Ми встановлюємо необхідні дані (лише для читання), загортаємо фрагмент у функцію та безпечно оцінюємо їх. Оскільки код не може зробити нічого, крім повернення булевого, тут не буде ніяких шкідливих можливостей.
амон

6
@Twittopher Перетягування всього механізму JavaScript, щоб оцінити деякі предикати, все ще здається 1) повною надмірністю, 2) ризикованою та 3) схильною до помилок. У ==конкретному випадку ви використовували замість ===свого прикладу. Ви дійсно хочете забезпечити цілісну повноту, коли всі правила, безумовно, повинні завжди припинятися? Замість того, щоб стрибати через обручі, щоб переконатися, що всі взаємодії між Java та JavaScript кошерніші, чому б вам просто не написати простого аналізатора та інтерпретатора, як запропонував Кіліан? Буде набагато простіше підлаштувати під ваші потреби та забезпечити. Використовуйте ANTLR або щось подібне.
Доваль

6
@Doval Написання невеликого DSL - це не зовсім ракетна наука, і я міг би збити просту мову за 3 години до 5 днів. Але це здається 1) повним перебігом, 2) ризикованим та 3) схильним до помилок. Ви справді хочете написати цілу міні-мову? Що робити, якщо деякі ділові правила складніші, ніж очікувалося, і потрібна повнофункціональна мова? Ви страждаєте від непридуманого тут-синдрому? Замість того, щоб винаходити колесо, чому б не використати існуючу мову? Набагато простіше користуватися мовою, яка вже була перевірена битвою.
амон

14
@amon Коли це станеться, ви знайдете реальну систему правил (якої JavaScript не є), як сказав Кілліан. Зрівняти ризики обох підходів вводить в оману. Потрібно лише один упущення, щоб знищити всі ваші зусилля щодо забезпечення перекладача на мові, що завершує цю мову це віднімаючий процес. Набагато складніше випадково зробити невелику DSL небезпечною; це аддитивний процес. Помилка, яку ви можете зробити, - це неправильне тлумачення синтаксичного дерева, і це може бути перевірено одиницею. Ви, мабуть, не збираєтесь випадково надати інтерпретатору можливість форматування жорсткого диска.
Довал

4
@amon: Навіть якщо фрагмент js може не мати побічних ефектів, він може вирішити не повертати булеве значення:while (true) ;
Бергі,

44

Я зробив це, і рекомендую цього не робити.

Що я робив, це записати всю ділову логіку в Lua і зберегти цей скрипт Lua в базі даних. Коли моя програма запустилася, сценарій буде завантажений і виконаний. Таким чином я міг би оновити ділову логіку свого додатка, не поширюючи нового бінарного файлу.

Я незмінно виявляв, що мені завжди потрібно оновлювати двійковий файл під час внесення змін. Деякі зміни були в сценарії Lua, але я незмінно мав перелік змін, які потрібно було внести, і тому мені майже завжди доводилося вносити деякі зміни в бінарний та деякі зміни в сценарій Lua. Моя уява про те, що я міг би уникати розповсюдження бінарних файлів весь час, просто не зникала.

Що мені здалося набагато кориснішим, це полегшити розподіл бінарних файлів. Моя програма автоматично перевіряє наявність оновлень при запуску, завантаженні та встановленні будь-якого оновлення. Мої користувачі завжди перебувають на останніх бінарних файлах, які я натиснув. Різниці між зміною бінарного та зміною сценаріїв майже немає. Якби я це зробив ще раз, я би доклав ще більше зусиль, щоб зробити оновлення безпроблемним.


3

У мене не було б коду з базою даних. Але ви можете зробити щось подібне, якщо база даних містить імена функцій, а потім використовуйте відображення для виклику їх. Коли ви додаєте нову умову, ви повинні додати її до свого коду та вашої бази даних, але ви можете комбінувати умови та параметри, які передаються їм, щоб створити досить складні оцінки.

Іншими словами, якщо у вас є нумеровані відділи, було б легко перевірити UserDepartmentIs і чек TodayIsAfter, а потім об'єднати їх, щоб мати відділ = 2 і сьогодні> 15.03.2016. Якщо ви хочете перевірити TodayIsBefore, щоб ви могли закінчити дату дозволу, вам слід написати функцію TodayIsBefore.

Я цього не робив для дозволів користувачів, але робив це для перевірки даних, але це має працювати.


2

XACML - це рішення, яке ви дійсно шукаєте. Це тип двигуна правил, орієнтований лише на контроль доступу. XACML, стандарт, визначений OASIS, визначає три частини:

  • архітектура
  • мова політики (що дійсно є тим, що ви хочете)
  • схема запиту / відповіді (як ви просите рішення про авторизацію).

Архітектура така:

  • Точка прийняття рішень щодо політики (PDP) є основною частиною архітектури. Саме компонент оцінює вхідні запити на авторизацію щодо відомого набору політик
  • точка забезпечення виконання політики (PEP) - це фрагмент коду, який захищає вашу програму / API / послугу. PEP перехоплює бізнес-запит, створює запит на авторизацію XACML, відсилає його до PDP, отримує відповідь назад і виконує рішення всередині відповіді.
  • інформаційний пункт про політику (PIP) - це компонент, який може підключити PDP до зовнішніх джерел даних, наприклад, LDAP, базі даних або веб-службі. PIP стане в нагоді, коли PEP надсилає запит, наприклад "Чи може Аліса переглянути документ №12?" і PDP має політику, яка вимагає віку користувача. PDP попросить PIP "дати мені вік Аліси", а потім зможе обробити політику.
  • Точка адміністрування політики (PAP) - це місце, де ви керуєте всім рішенням XACML (визначення атрибутів, написання політик та налаштування PDP).

Мова розмітки eXtensible Control Access - архітектура XACML

Ось як виглядає ваш перший випадок використання:

/*
 * All users in department 1 with over 5 years of seniority can VIEW resource FOOBAR-1, else not authorized
 * 
 */
 policy departmentOne{
    target clause department == 1
    apply firstApplicable
    /**
     * All users in department 1 with over 5 years of seniority can VIEW resource FOOBAR-1, else not authorized
     */
    rule allowFooBar1{
        target clause resourceId=="FOOBAR-1" and seniority>=5 and actionId=="VIEW"
        permit
    }
    rule denyOtherAccess{
        deny
    }

 }

Вашим другим випадком використання буде:

 /*
  * "All users in department 2, if the date is after 03/15/2016, can VIEW resource FOOBAR-2, else not authorized"
  *  
  */
  policy departmentTwo{
    target clause department == 1
    apply firstApplicable
    rule allowFooBar2{
        target clause resourceId=="FOOBAR-1" and seniority>=5 and currentDate>"2016/03/15":date and actionId=="VIEW"
        permit
    }
    rule denyOtherAccess{
        deny
    }
  }

Ви можете об'єднати обидва випадки використання в одній політиці, використовуючи посилання:

  policyset global{
    apply firstApplicable
    departmentOne
    departmentTwo
  }

І ви закінчили!

Більше про XACML та ALFA можна прочитати з:


0

Чого ви насправді хочете, це XACML . Це в значній мірі дає вам саме те, що ви хочете. Вам не обов'язково потрібно реалізувати повну архітектуру, коли всі ролі будуть повністю розділені ... якщо у вас є лише одна програма, ви, ймовірно, можете піти з інтеграції PDP та PEP у ваш додаток з баланом, а PIP - це все що завгодно Ваша наявна база даних є.

Тепер, де завгодно у вашій програмі вам потрібно щось авторизувати, ви створюєте XACML-запит, у якому є користувач, дія та контекст, і механізм XACML прийме рішення на основі написаних вами файлів політики XACML. Ці файли політики можуть зберігатися в базі даних або у файловій системі або там, де ви хочете зберегти конфігурацію. Axiomatics має гарну альтернативу XACML-представлення XML під назвою ALFA, що трохи легше читати, ніж необроблений XML, та плагін Eclipse для створення XACML XML з політик ALFA.


1
як це відповідає на поставлене запитання?
гнат

Він спеціально намагається впровадити зовнішньо налаштовану систему авторизації. XACML - це готова зовнішньо налаштована система авторизації, яка дуже добре висвітлює його конкретне використання. Я визнаю, що це може бути не чудовою відповіддю на більш загальне питання про виконання динамічного коду, але це хороше рішення його конкретного питання.
gregsymons

0

Ми зробили це у моїй нинішній компанії, і ми дуже задоволені результатами.

Наші вирази написані у js, і ми навіть використовуємо їх для обмеження результатів, які користувачі можуть отримати при запиті ElasticSearch.

Трюк полягає в тому, щоб забезпечити достатню кількість інформації для прийняття рішення, щоб ви могли дійсно писати будь-які perms, які ви хочете, без змін коду, але в той же час зберігаючи його швидко.

Нас не дуже турбує атака введення коду, оскільки дозволи написані тими, хто не має потреби атакувати систему. І те саме стосується DOS-атак, як while(true)приклад. Адміністраторам системи цього робити не потрібно, вони можуть просто видалити всі права доступу ...

Оновлення:

Щось подібне до XACML, здається, є кращим як центральний центр управління організацією для організації. Наш випадок використання дещо відрізняється тим, що наші клієнти в цілому не мають ІТ-відділу, який би керував усім цим. Нам потрібно було щось самостійне, але намагалися зберегти якомога більше гнучкості.

Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.