Kafka: Часть 14 — Кастомная авторизация — пишем свой модуль авторизации

В предыдущей статье мы научились читать настройки JAAS в Kafka PLAIN самописном аутентификаторе и даже проверяли учетные данные на внешнем сервере.
Пришло время узнать, как писать и свой авторизатор.

Репозиторий примера к текущей статье — https://github.com/BlockWit/esh-kse-14 .

Предыдущая статья. Следующая статья.

Полный список статей по теме тут.

Заходите в наш телеграмм канал — Enterprise Stack Helper! Делитесь опытом или задавайте вопросы, если что-то непонятно.

Репозитории с примерам из статей по способам аутентификаци и авторизации https://github.com/BlockWit/kafka-security-examples.

 

Задача

В предыдущей статье мы уже написали кастомный аутентификатор, который соединяется с сервером с учетными данными и отправляет ему запрос на проверку аккаунта клиента.

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

Например, если мы дадим группе ALICE_TOPIC_WRITE_GROUP доступ на запись в топик topic.alice, то все пользователи, которые состоят в группе ALICE_TOPIC_WRITE_GROUP смогут писать в топик topic.alice. Т.е. задача нашего авторизатора получить список групп по пользователю у сервера с учетными данными и проверить, имеет ли право пользователь выполнять действия на основе списка ACL.

 

В итоге, что нам нужно сделать:

  1. Написать кастомный авторизатор
  2. Подсунуть его Kafka
  3. Указать в конфиге Kafka, что будем использовать свой авторизатор

Реализация

Сервер с учетными данными в репозитории к статье https://github.com/BlockWit/esh-kse-14/blob/master/src/test/java/com/blockwit/kafka/security/examples/credserver/CredentialsServer.java  уже умеет отдавать группы по имени пользователя и пароля. Делает он это по URL:

/groups?username=username&password=password

И возвращает группы, разделенные запятой, для соответствующего пользователя.

Поскольку мы будем пользоваться не чисто своей реализацией авторизатора, а реализацией AclAuthorizer, то нам достаточно заменить имя пользователя, которое приходит в авторизатор, на список групп. Мы ведь будем устанавливать ACL списки по именам групп.

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

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

А если пользователей много? Бесконтрольное увеличение нашего Map’а крайне нежелательно. Чтобы удалять пользователей по мере необходимости из Map, мы еще добавим поле username. Это поле будет хранить имя пользователя для текущего экземпляра аутентификатора. А если Вы внимательно посмотрите на класс аутентификатора, то увидите там метод close. В нем мы и будем удалять нашего пользователя из Map.

Отлично. Теперь, чтобы получить пароль по имени пользователя, авторизатор будет смотреть в публичный статичный Map нашего аутентификатора примерно следующим образом:

Давайте приведем код измененного аутентификатора:

Отлично. Одну проблему решили. Теперь у нас есть все для того, чтобы написать авторизатор.

Авторизатор у нас наследуется от kafka.security.authorizer.AclAuthorizer. Даваейте назовем его CustomAclAuthorizer:

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

custom.acl.authorizer.auth.server

Давайте добавим поле authServer в наш авторизатор и в методе чтения настроек прочтем адрес сервера групп:

Обратите внимание, что мы сначала вызываем у AclAuthorizer чтение настроек, поскольку мы наследуемся от AclAuthorizer и нам нужен его функционал.

Осталось написать метод замены имен пользователя на группы. Посмотрите метод authorize. Он получает на вход список actions — список операций, которые запрашивает пользователь. И возвращает список AuthorizationResult. AuthorizationResult может принимать два значения: либо AuthorizationResult.ALLOW, либо  AuthorizationResult.DENIED. Возвращаемый список полностью соответствует списку actions. Т.е. каждому action’у соответствует свой AuthorizationResult.
Начнем с алгоритма:

  1. Получаем имя пользователя
  2. Если он суперпользователь, то возвращаем список AuthorizationResult со всеми разрешениями.
  3. Если это не суперпользователь, то получаем пароль
  4. По паролю и логину получаем у сервера учетных данных список групп, в которых состоит пользователь
  5. Для каждой группы вызываем метод authorize у AclAuthorizer
  6. Если хотя бы для одной из групп,  в которой состоит пользователь, действие разрешено, то возвращаем ALLOW.
  7. Возвращаем получившийся список разрешений

Приступим к реализации! Проверка, не является ли наш пользовтаель суперпользователем, уже написана в AclAuthhorizer. Нам нужно только ее правильно вызвать:

Давайте создадим список разрешений, соответствующих списку actions, и, если пользователь окажется суперпользователем, то заполним весь список ALLOW и вернем его.

Теперь давайте получим по имени пользователя пароль:

Подключимся к серверу и вернем список групп, но перед этим заполним наш список разрешений DENIED. В случае ошибки обращения к серверу, мы вернем этот список сразу. Итак, заполнение списка и получение групп пользователя с сервера:

Отлично. Группы есть. Теперь нам нужно на каждую группу вызвать метод authorize так, будто мы его вызывали для пользователя. К сожалению, метод authorize на вход не принимает имя пользователя или, в нашем случае, группы. А принимает он AuthorizableRequestContext. Тогда давайте будем создавать AuthorizableRequestContext каждый раз при вызове authorize. При этом у нашего AuthorizableRequestContext мы заменим только одно поле — имя пользователя на имя группы:

Остается только вернуть список authResults. Давайте посмотрим полный код нашего авторизатора:

Напоминаю, что весь код доступен в репозитории для этой статьи: https://github.com/BlockWit/esh-kse-14.

Подготовка к тестированию

Для подготовки к тестированию нам сначала нужно поправить конфигурацию сервера Kafka, а именно:

  1. Добавить к конфигурации прошлой статьи конфигурацию ACL и указать наш авторизатор
  2. Добавить суперпользователя ACL
  3. Добавить настройку нашего авторизатора, в которой указан сервер учетных данных

Теперь конфиг будет выглядеть так:

Не забывайте менять localhost на Ваш при необходимости. Также конфигурация сервера Kafka есть в репозитории к статье — https://github.com/BlockWit/esh-kse-14/blob/master/src/test/resources/server.properties

Теперь для тестирования нам нужно раздать доступы группам. Но прежде всего я хочу рассказать про настройки сервера учетных данных. Там сейчас стоит такая конфигурация:

  • Пользователь alice, пароль alice-secret, группы:
    • STATS_VIEWERS
    • STATS_WRITERS
  • Пользователь robin , пароль robin-secret, группы:
    • STATS_VIEWERS

Для экспериментов возьмем топик test.topic.  Пусть все, кто в группе STATS_WRITERS, могут писать в топик, а все, кто в группе STATS_VIEWERS, могут читать из топика. Для чтения будем использовать группу test.group. Тогда, чтобы сконфигурировать списки ACL, нужно выполнить команды, описанные ниже. Но перед их выполнением убедитесь, что у Вас пустые списки ACL. Это делается командой:

Итак, назначаем права:

  1. Даем доступ на чтение из топика test.topic группе STATS_VIEWERS
  2. Даем доступ на чтение из группы топика test.group группе пользователей STATS_VIEWERS
  3. Даем доступ на запись в топик test.topic и на создание топика группе STATS_WRITERS

     

Теперь выведем все списки ACL командой

Должно получиться:

Теперь нам нужно запустить сервер учетных данных. Вы можете сделать это из IDE. Сервер учетных данных — это обычный исполняемый java класс.  Склонируйте репозиторий к статье и запустите класс src/test/com/blockwit/kafka/security/examples/credserver/CredentialsServer.java. Репозиторий к статье лежит тут — https://github.com/BlockWit/esh-kse-14.

После запуска сервер выдаст:

Теперь нужно подсунуть Kafka наш авторизатор. Для этого необходимо (команды справедливы, если ставили Kafka по этой статье):

  1. Выполнить mvn install — тогда проект соберется и появится файл target/esh-kse-14-jar-with-dependencies.jar
  2. Скопировать наш файл в папку с плагинами Kafka
  3. Назначить Kafka владельцем нашего плагина. Чтобы Kafka имела доступ

В составе репозитория есть скрипт deploy.sh, который выполняет компиляцию, сборку, копирование конфига из примера и перезапуск Kafka. Но делает он это, при условии, что Вы запускаете скрипт из той директории, где он лежит и, что настройка Kafka выполнена по этой статьеОбратите внимание, что скрипт меняет конфиг Kafka! Скрипт можно выполнять только после запуска сервера учеток!

Теперь перезапустите Kafka. Если все отлично, то в консоли сервера учеток Вы увидите

Теперь приступим к тестированию наших настроек ACL.

Тестирование

Запустите клиент отправки сообщения из репозитория — src/test/java/com/blockwit/kafka/security/examples/SimpleProducerTest_SASL_PLAINTEXT_PLAIN_ACL.java. В результате Вы должны получить:

А теперь запустите клиент приема сообщений src/test/java/com/blockwit/kafka/security/examples/SimpleProducerTest_SASL_PLAINTEXT_PLAIN_ACL.java. Результат работы — одно прочитанное сообщение:

После выполнения всех операций в консоли сервера учетных данных Вы увидите сообщения об успешной отправке списков групп для alice и robin:

Это значит, что наш кастомный авторизатор работает!

Резюме

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

Репозиторий примера к текущей статье — https://github.com/BlockWit/esh-kse-14.

Предыдущая статья. Следующая статья.

Полный список статей по теме тут.

Заходите в наш телеграмм канал — Enterprise Stack Helper! Делитесь опытом или задавайте вопросы, если что-то непонятно.

Репозитории с примерам из статей по способам аутентификаци и авторизации https://github.com/BlockWit/kafka-security-examples.

 

Добавить комментарий