Динамическая оценка кода в Java - умная или небрежная?

30

Я пытаюсь создать гибкую инфраструктуру ACL в Java для моего приложения.

Многие платформы ACL построены на белом списке правил, где правило имеет форму владельца: действие: ресурс . Например,

  • «ДЖОН МОЖЕТ ПОСМОТРЕТЬ ресурс FOOBAR-1»
  • «МЭРИ МОЖЕТ ПРОСМОТРЕТЬ ресурс FOOBAR-1»
  • "МЭРИ МОЖЕТ РЕДАКТИРОВАТЬ ресурс FOOBAR-1"

Это привлекательно, потому что правила могут быть легко сериализованы / сохранены в базе данных. Но у моего приложения сложная бизнес-логика. Например,

  • «Все пользователи в отделе 1 со стажем более 5 лет могут просматривать ресурс FOOBAR-1, иначе не авторизован»
  • «Все пользователи в отделе 2, если дата после 15.03.2016, могут ПРОСМОТРЕТЬ ресурс FOOBAR-2, иначе не авторизованы»

На первый взгляд, было бы кошмаром разработать схему базы данных, которая могла бы обрабатывать бесконечно сложные правила, подобные этим. Поэтому мне кажется, что мне нужно «запечь» их в скомпилированном приложении, оценить их для каждого пользователя, а затем создать в результате оценки правила для ресурса owner: action: resource . Я хочу избежать запекания логики в скомпилированном приложении.

Итак, я думал о представлении правила в форме предиката : действия: ресурса , где предикат является логическим выражением, которое определяет, разрешен ли пользователь. Предикатом будет строка выражения JavaScript, которая может быть оценена движком Java Rhino. Например,

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

При этом предикаты могут быть легко сохранены в базе данных.

Это умно ? Это небрежно ? Это бесполезно ? Это чрезмерно спроектировано ? Является ли это безопасным (по-видимому, Java может изолировать движок Rhino).

Twittopher
источник
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) ;
Bergi
44

Я сделал это, и я рекомендую вам не делать этого.

Я написал всю бизнес-логику на Lua и сохранил этот сценарий Lua в базе данных. Когда мое приложение запускалось, оно загружало и выполняло скрипт. Таким образом, я мог бы обновить бизнес-логику своего приложения, не распространяя новый двоичный файл.

Я неизменно обнаруживал, что мне всегда нужно обновлять бинарный файл при внесении изменений. Некоторые изменения были в скрипте Lua, но у меня всегда был список изменений, которые нужно было сделать, и поэтому мне почти всегда приходилось вносить некоторые изменения в двоичный файл и некоторые изменения в скрипте Lua. Мое воображение, что я мог бы избегать распространения бинарных файлов все время, просто не удавалось.

То, что я нашел намного более полезным, состояло в том, чтобы облегчить распределение двоичных файлов. Мое приложение автоматически проверяет наличие обновлений при запуске, загружает и устанавливает любое обновление. Таким образом, мои пользователи всегда используют последние двоичные файлы, которые я загрузил. Почти нет разницы между изменением двоичного файла и изменением сценариев. Если бы я сделал это снова, я бы приложил еще больше усилий, чтобы сделать обновление без проблем.

Уинстон Эверт
источник
3

Я не хотел бы, чтобы база данных содержала код. Но вы можете сделать что-то подобное, если база данных будет содержать имена функций, а затем использовать отражение для их вызова. Когда вы добавляете новое условие, вы должны добавить его в свой код и базу данных, но вы можете комбинировать условия и параметры, которые передаются им, для создания довольно сложных оценок.

Другими словами, если у вас есть пронумерованные отделы, было бы легко иметь проверку UserDepartmentIs и проверку TodayIsAfter, а затем объединить их, чтобы иметь Department = 2 и Today> 15/03/2016. Если затем вы хотите выполнить проверку TodayIsBefore, чтобы вы могли завершить дату разрешения, вам нужно написать функцию TodayIsBefore.

Я не сделал это для пользовательских разрешений, но сделал это для проверки данных, но это должно работать.

jmoreno
источник
2

XACML - это решение, которое вы действительно ищете. Это тип правил, который ориентирован только на контроль доступа. XACML, стандарт, определенный OASIS, определяет три части:

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

Архитектура выглядит следующим образом:

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

Расширяемый язык разметки контроля доступа - архитектура 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 в ваше приложение с Balana а PIP - что угодно ваша существующая база данных пользователей

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

gregsymons
источник
1
как это отвечает на заданный вопрос?
комнат
Он специально пытается внедрить внешне настроенную систему авторизации. XACML - это готовая к работе внешне настроенная система авторизации, которая очень хорошо подходит для его конкретного случая использования. Я признаю, что это может быть не лучшим ответом на более общий вопрос о динамическом выполнении кода, но это хорошее решение его конкретного вопроса.
gregsymons
0

Мы сделали это в моей нынешней компании, и мы очень довольны результатами.

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

Хитрость заключается в том, чтобы убедиться в наличии достаточного количества информации для принятия решения, так что вы действительно можете писать любые перманенты без изменений в коде, но в то же время сохранять скорость.

На самом деле нас не беспокоят атаки с внедрением кода, поскольку разрешения пишутся теми, кому нет необходимости атаковать систему. И то же самое относится к атакам DOS, какwhile(true) примере. Администраторы системы не должны этого делать, они могут просто удалить все права доступа ...

Обновить:

Что-то вроде XACML кажется лучше в качестве центральной точки управления авторизацией для организации. Наш вариант использования немного отличается тем, что у наших клиентов, как правило, нет ИТ-отдела для всего этого. Нам нужно было что-то автономное, но мы старались сохранить как можно больше гибкости.

адажио
источник