Привет! Меня зовут Алексей, я DevOps-инженер компании Nixys. «Как правильно и своевременно предоставлять и отнимать доступ у различных сотрудников?» — этот вопрос беспокоит всех. Особую важность эта задача приобретает, когда продукт начинает быстро расти — если ваш штат регулярно меняется, вопросы безопасности данных и работоспособности системы должны стоять на особом контроле.

В статье я хочу рассказать, как мы совершенствовали систему контроля доступа в рамках одного проекта, и показать, как реализовали единую точку авторизации через Keycloak.


Глава 1: Нельзя всё всегда контролировать, но иногда можно

Все пользуются ПО в повседневной жизни. Правда, рядовые пользователи обычно не задумываются, как это работает. Ну а те, кто понимают и задумываются — знают, что даже за простым (на первый взгляд) продуктом всегда стоит большая команда: менеджеры, разработчики, системные администраторы — и у всех есть доступ к внутреннему ПО, необходимому для работы. 

Обычно доступ у разных специалистов разный: каждая компания по-своему подходит к созданию системы привилегий доступа к различным компонентам, но эта  задача беспокоит всех.

Не так давно к нам пришёл проект, который уже имел огромную инфраструктуру, поднятую давным-давно другими людьми. Нам же, как инженерам, надо было в ней разобраться и предложить улучшения. Кроме этого, была поставлена задача — усовершенствовать систему контроля доступа: в компании был большой штат, регулярно менялись сотрудники, поэтому потребность в удобной системе распределения привилегий доступа была понятна. Причем привилегии должны были делиться таким образом, что одно и тоже ПО, как пример Nexus, располагаемое в различных изолированных контурах, должно быть доступно определенным сотрудникам. По большей части, стоял вопрос безопасности не только внутри инфраструктуры, но и вопрос распределения зон ответственности за различные разрабатываемые продукты.

Глава 2: Внедрение

Итак, первичные требования понятны, за ними последовали более конкретные задачи. Прежде всего было необходимо в выделенном нам контуре сделать мониторинг и файловое хранилище, здесь же была потребность в определенном стеке технологий — Prometheus, Grafana, Alertmanager, Ceph. Звучит как стандартная задача, подразумевающая стандартные процессы развертывания, но тут же по требованиям описанным выше у нас появляется подзадача в виде процесса аутентификации.

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

FreeIPA — открытый проект для создания централизованной системы по управлению идентификацией пользователей

Для начала нам нужен аккаунт в FreeIPA, и тут возникает первый вопрос «Что именно нам нужно?» Первый вариант — это обычный пользователь в FreeIPA, которого можно создать либо через консоль, либо через web интерфейс:

Второй вариант — это системный аккаунт. Принцип его работы схож с обычным пользователем, но существует важная отличительная черта. Через данный аккаунт можно произвести авторизацию в LDAP на уровне взаимодействия между приложениями. Создать его можно следующим способом согласно документации:

# ldapmodify -x -W
dn: uid=keycloak,cn=users,cn=compat,dc=test,dc=ru
changetype: add
objectclass: account
objectclass: simplesecurityobject
uid: keycloak
userPassword: supersecurepassword
passwordExpirationTime: 20380119031407Z
nsIdleTimeout: 0

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

Многие продукты с web - интерфейсом на данный момент имеют весьма удобную и красивую интеграцию с ldap. Идеальным и наглядным примером в нашем стеке является Grafana, которая содержит предпосылки для подобной настройки из коробки в ldap.toml:

[[servers]]
host = "10.10.10.3"
port = 636
use_ssl = true
ssl_skip_verify = true
# Search user bind dn
bind_dn = "uid=keycloak-service,cn=users,cn=compat,dc=test,dc=ru"
bind_password = 'supersecurepassword'
search_base_dns = ["cn=users,cn=compat,dc=test,dc=ru"]
search_filter = "(uid=%s)"
group_search_base_dns = ["cn=groups,cn=compat,dc=test,dc=ru"]
group_search_filter_user_attribute = "uid"
group_search_filter = "(&(objectClass=posixGroup)(memberUid=%s))"

[servers.attributes]
member_of = "memberOf"
email =  "mail"
name = "givenName"
surname = "sn"
username = "uid"

[[servers.group_mappings]]
group_dn = "cn=allow_rw_grafana,cn=groups,cn=compat,dc=test,dc=ru"
org_role = "Admin"
grafana_admin = true
org_id = 1

[[servers.group_mappings]]
group_dn = "cn=allow_ro_grafana,cn=groups,cn=compat,dc=test,dc=ru"
org_role = "Viewer"
grafana_admin = false
org_id = 1

Данная же конфигурация говорит нам о следующих моментах.

Для аутентификации пользователей используется FreeIPA через SSL порт 636, для аутентификации необходим пользователь с паролем и доступом до групп allow_rw_grafana, allow_ro_grafana.

В рамках первой нашей группы (allow_rw_grafana) всем пользователям, прошедшим аутентификацию по имени УЗ, будет предоставлена роль Admin, во второй группе будет предоставлена роль Viewer.

— Закрывает ли это наши потребности?

— Да

— Является ли данный процесс удобным для администрирования?

— Да

Всё описанное выше можно найти в документации или написать на основе примеров описанных в рамках ldap.toml конфигурации, но идеальное ли это для нас решение или нет?

Ответ на этот вопрос у каждого свой, так как у каждого решения имеются как плюсы так и минусы, если смотреть с точки зрения системного архитектора, то упрощение системы и сетевого взаимодействия всегда радует глаз. С другой стороны, если мы хотим добиться единого входа в систему (SSO), то этот вариант выглядит принципиально неудачно в нашем случае. Ведь зачем авторизовываться в каждой вебке индивидуально?

Вопрос с Grafana можно считать закрытым, но как быть с теми продуктами, функционал которых не имеет из коробки подобных интеграций c FreeIPA или в них в принципе отсутствует авторизация?

Глава 3: Свой среди чужих, чужой среди своих

Здесь на помощь приходят нестандартные решения. В нашем случае этим решением является Keycloak.

Keycloak — продукт с открытым кодом для реализации single sign-on с возможностью управления доступом, нацелен на современные применения и сервисы.

Он имеет широкий функционал и это не может не радовать любого пользователя, который хочет закрыть одним инструментом все свои потребности. Прочитав все хвалебные отзывы возникает вопрос, который волнует каждого желающего ознакомиться с продуктом — «А как обстоят дела с документацией и примерами?» 

Первой нашей проблемой был именно этот момент. Вся документация и примеры по настройке простейшего Client OpenID Connect является весьма старой и не соответствует как функционалу, так и новому интерфейсу продукта, что вызывает ряд сложностей. В целом, чтобы данная проблема не возникала у вас, хочется поделиться своими наработками по ряду настроек и рассказать как это работает.

Основные задачи, которые перед нами стоят:

  • Создание процессов авторизации, если они отсутствуют.

  • Гибкость управления при выдаче и отзыве доступов.

  • Распределение по ролям авторизованных пользователей.

  • Использование LDAP в виде Freeipa, как источника информации о пользователях.

Глава 3.1: С чего лучше всего всегда начинать? — с начала

С чего же лучше всего начать? Разворачиваем наш Keycloak версии 21.1.2 через docker-compose и образ от Bitnami:

version: "3"

services:
  keycloak:
    image: bitnami/keycloak:21.1.2
    container_name: 'keycloak'
    depends_on:
      - postgres
    volumes:
      - ./volumes/cacerts:/opt/bitnami/keycloak/certs-spi/cacerts
      - ./volumes/keycloak:/opt/bitnami/keycloak
    env_file:
      - .env
    ports:
      - "8080:8443"
    restart: always
    networks:
      lan:
        ipv4_address: 172.26.0.11
  postgres:
    image: postgres:13.2
    container_name: 'postgres-keycloak'
    restart: always
    volumes:
      - ./volumes/postgresql/data:/var/lib/postgresql/data
    env_file:
      - .env
    networks:
      lan:
        ipv4_address: 172.26.0.12
networks:
  lan:
    driver: bridge
    ipam:
      config:
        - subnet: 172.26.0.0/24
    driver_opts:
      com.docker.network.bridge.name: "br-${COMPOSE_PROJECT_NAME}"

Первая часть простая, но и здесь есть интересный момент о котором хочется рассказать чуть позже.

Перед тем как настраивать наш первый Realm, нам нужно понять каких пользователей мы хотим аутентифицировать в том или ином сервисе. Для этого нам нужно завести пользователей в нашем Keycloak, и тут у нас есть два варианта.

  1. Ручное создание в веб интерфейсе.

  2. User Federation - синхронизация пользователей с серверов LDAP и Active Directory.

Глава 3.2: Автоматизируем и синхронизируем

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

  • Указываем имя нашей федерации.

  • URL для коннекта к ldap, по протоколу ldaps, для шифрования соединения по SSL и повышению безопасности нашего трафика.

  • Указываем использовать ли центр доверенных сертификатов или нет.

Моменты, описанные выше, являются очень важными по ряду причин.Если ваша FreeIPA имеет обыкновенный общедоступный сертификат, то проблем с SSL сертификатом не возникнет, но как быть если ваш сертификат является самоподписанным? Ответ весьма простой, но не явно описанный в документации.

На текущий момент Keycloak не умеет производить включение ldaps с самоподписанным сертификатом — без ручного добавления его в центр хранения ключей и сертификатов keystore.

В виде volume данный файл хранится следующим образом на нашем сервере:

    volumes:
      - ./volumes/cacerts:/opt/bitnami/keycloak/certs-spi/cacerts

Заходим в контейнер и идем в каталог, где у нас есть права на редактирование и создание файлов.

docker exec -ti keycloak bash
cd /opt/bitnami/keycloak/

Выкачиваем новый сертификат в файл и импортируем его в текущий центр хранения Keystore:

openssl s_client -connect freeipa-replica.test.ru:636 2>/dev/null | openssl x509 > ca.crt
keytool -trustcacerts -keystore "/opt/bitnami/keycloak/cacerts" -importcert -alias freeipa-replica.test.ru -file /opt/bitnami/keycloak/ca.crt

На всплывающее подтверждение пишем "Yes". После чего наш новый Keystore готов, и нам необходимо лишь перепрокидывать его в контейнер, чтобы в дальнейшем он применялся на уровне keycloak. Полдела сделано, наш сертификат теперь является доверенным, и можно произвести тестирование коннекта с помощью “Test connection”.

Следующим шагом является описание параметров подключения к нашей FreeIPA.

Указываем ранее созданного пользователя, Common Name и Domain Component, которые можно посмотреть в самом LDAP и пароль для аутентификации, после чего проверяем успешность нашего подключения.

Осталось задать последние параметры синхронизации пользователей LDAP с Keycloak для извлечения всех наших uid юзеров из группы пользователей allow_users.

Здесь в настройках нас интересуют следующие поля:

  • Edit mode — режим работы с LDAP, в нашем случае чтение.

  • Users Distinguished Name — где описывается содержимое атрибутов в дереве

  • Username LDAP attribute — атрибут пользователя, который отображается как имя пользователя.

  • Relative Distinguished Names LDAP attribute — относительные уникальные имена из которых состоит DN.

  • UUID LDAP attribute — имя уникального идентификатора объекта.

  • User object classes — класс объекта.

  • User LDAP filter — фильтр из дерева объектов, в указанном пространстве пользователей.

  • Search scope — глубина поиска.

Все параметры для поиска объектов LDAP можно посмотреть в рамках нашей Freeipa при выводе полного списка пользователей со всеми их метаданными и нам будет достаточно лишь сделать настройку периода синхронизации пользователей из Freeipa.

Если все параметры заданы успешно, то первичная синхронизация произойдет самостоятельно после сохранения настроек и Keycloak уведомит вас о добавлении новых пользователей. Всех пользователей можно также посмотреть в Users, но только при поиске конкретного имени или же при поиске по регулярному выражению “*”.

Глава 3.3: Настройка Realm OpenID Connect для Prometheus

Когда мы имеем в нашей системе уже всю информацию о ряде пользователей, можно приступать к работам непосредственно с Сlients внутри Realm. Создадим новый клиент с любым Name и Client ID на ваш выбор:

После чего включаем Client authentication — данный параметр настройки, является не самым обязательным на данном этапе. Его можно включить и позже в рамках уже созданного Client в Realm, но мы сделаем это сразу, чтобы в будущем не возвращаться к этому вопросу и уже перейти к настройке интересующих нас параметров.

И смотрим, что у нас установлены настройки по умолчанию.

  • Authorization — включение или отключение детальной авторизации в Client. Данная настройка необходима для добавление дополнительных параметров настройки авторизации между приложением и Keycloak, где решения об авторизации могут приниматься на основе различных механизмов управления доступом.

  • Authentication flowStandard flow и Direct access grants включают поддержку аутентификации OpenID Connect c Authorization token и предоставляют Client-у доступ до имен и паролей пользователя в Keycloak для последующей выдачи токена авторизации.

  • Root URL — корневой URL в нашем случае обозначен переменной, и в результате это будет домен нашего Keycloak.

  • Home URL — Добавочная часть к основной части для перенаправления внешнего пользователя на нужный Realm.

  • Valid redirect URIs — допустимые URL после авторизации.

  • Valid post logout redirect URIs — допустимые URL после выхода из авторизованной УЗ.

P.S.

В рамках демонстрационной настройки были написан “*”, что означает, что доступные все URL в обоих случаях, но данный вариант решения не является безопасным и в prod- среде рекомендуется уточнять данные значения.

Ранее уже была речь про то, что Keycloak является провайдером идентификации, значит он имеет возможность аутентифицировать пользователя на основе имеющейся информации и произвести передачу данных об успешной авторизации дальше, но куда передать эту информацию?

Здесь на сцену выходит уже известный обратный прокси сервер oauth2-proxy, который в взаимодействии с провайдером идентификации производит авторизацию пользователя. Подключить его можно следующим образом, добавив в наш ранее описанный docker-compose новый контейнер, который будет настроен на работу с нашим Realm.

  oauth2:                                                                                                                                                                                                          
    image: bitnami/oauth2-proxy:7.4.0                                                                    
    container_name: 'oauth2-proxy-prometheus'                                                                                                                                                                      
    env_file:                                                                                            
      - .env                                                                                                                                                                                                       
    command:                                                                                             
      - '--http-address=0.0.0.0:4180'                                                                                                                                                                              
      - '--provider=keycloak-oidc'                                                                                                                                                                                 
      - '--client-id=test’                                                                                                                                                                                 
      - '--client-secret=e8hhbO7KmCrBUCTzZNFLHYQpmOAumUdl'                                                                                                                                                         
      - '--redirect-url=https://prometheus-dashboard.test.ru'                                                                                                                                                 
      - '--upstream=http://10.10.10.2:9090'                                                                                                                                                                       
      - '--oidc-issuer-url=https://keycloak-dashboard.test.ru/realms/prometheus'                                                                                                                              
      - '--insecure-oidc-skip-issuer-verification=false'                                                                                                                                                           
      - '--email-domain=*'                                                                                                                                                                                         
      - '--cookie-secret=yx_eP3yIaFuj0FqeWVvjT3ycF0Lh1SuENwSPbs5TYB4='                                                                                                                                             
      - '--pass-access-token=true'                                                                                                                                                                                 
      - '--insecure-oidc-allow-unverified-email=true'                                                                                                                                                              
      - '--cookie-secure=true'                                                                                                                                                                                     
      - '--set-authorization-header=true'                                                                                                                                                                          
      - '--pass-authorization-header=true'                                                                                                                                                                         
      - '--reverse-proxy=true'                                                                                                                                                                                     
      - '--skip-provider-button=true'                                                                                                                                                                              
      - '--set-xauthrequest=true'                                                                                                                                                                                  
    ports:                                                                                                                                                                                                         
      - "4180:4180"                                                                                                                                                                                                
    restart: always                                                                                                                                                                                                
    networks:                                                                                                                                                                                                      
      lan:                                                                                                                                                                                                         
        ipv4_address: 172.26.0.10

И здесь есть также пара интересных настроек, на которые хочется с акцентировать внимание.

  • client-id — ранее мы задавали его во время создания Realm, и в нашем случае он “test”

  • redirect-urlURL перенаправления после успешной авторизации

  • upstream — адрес проксирования запросов на наш Prometheus.

  • oidc-issuer-urlURL нашего Realm куда идёт запрос на идентификацию

  • cookie-secret — кука для шифрования токена

  • client-secret — secret для шифрования трафика между значениями

В целом понятно всё, кроме как «Откуда взять client secret?» Ранее при создании Client было выбрано использование параметра Client authentication. Смысл его весьма прост и заключается он в том, что при взаимодействии через внешнюю или внутреннюю сеть между Keycloak и Oauth2-proxy, будет создан шифрованный коннект для передачи токена аутентификации. Извлечь secret можно из настроек самого клиента:

Чтобы обеспечить дополнительную безопасность в приведенном выше сценарии, поставщики OAuth предоставляют утверждение aud, или «Аудитория», в токене носителя, когда пользователь входит в систему. Утверждение aud — это значение, указывающее, для какого приложения предназначен токен носителя. А значит заключительной частью настроек Client является добавление данного сценария  в Client Scopes.

Путь

В итоге, что у нас получается? Всё сетевое взаимодействие запроса от момента первичного запроса площадки можно рассмотреть на следующей схеме:

  1. Первый, неавторизованный запрос пользователя отправляется на Nginx после чего он проксируется в oauth2-proxy и отправляется в Realm Keycloak.

  2. Далее в случае успешной авторизации запрос отправляется вновь на Nginx и проксируется на oauth2-proxy для проверки токена авторизации пользователя.

  3. После этого, в случае успеха, проксирование идёт в желаемый Prometheus dashboard.

Все шаги для авторизации уже у нас преднастроены, и осталось лишь добавить корректную конфигурацию Nginx в наш виртуальный хост для правильного проксирования всех запросов площадки prometheus-dashboard на oauth2-proxy. Остаётся лишь один вопрос, получилось ли у нас закрыть все необходимые потребности описанные нами ранее?

  • Создание процессов авторизации, если они отсутствуют.  - Выполнено, так как исходно из коробки сам Prometheus не имеет авторизацию.

  • Гибкость управления при выдаче и отзыве доступов. - Выполнено

  • Распределение по ролям авторизованных пользователей. - Не выполнено, так как отсутствуют роли в рамках самого Prometheus.

  • Использование LDAP в виде Freeipa, как источника информации о пользователях. - Выполнено.

Три пункта из четырёх — это уже отличный результат в поставленных для нас условиях и так как настройка для Prometheus завершена, по аналогии её можно повторить для Alertmanager.

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

— Как тогда правильно на уровне федерации извлекать пользователей?

Ведь описанный выше вариант является более частным случаем. В нашем случае 5 настроенных клиентов на различное ПО не может контролироваться одной группой пользователей с точки зрения КБ и тогда нам понадобиться сделать следующее. Если одна группа пользователей нас не устраивает, то можно извлечь все группы пользователей и фильтровать разрешенных пользователей не на уровне Keycloak, а на уровне oauth2-proxy.

Для этого необходимо лишь внести пару правок в нашу ранее настроенную федерацию, удаляем фильтры, так как они нам больше не нужны:

И производим добавление нового маппера типа group-ldap-mapper.

Имя выбирается произвольно, настройки DN для извлечения групп как и ранее можем посмотреть в LDAP.

  • Group Name LDAP Attribute — параметр, где описано имя группы объекта по умолчанию cn, но в частных случаях он может отличаться.

  • Group Object Classes — класс объекта групп.

  • Preserve Group Inheritance — включение или выключение распространения группового наследования. То есть в случае, если у вас группа пользователей состоит из других групп пользователей, то вся эта иерархия наследования также перенесётся в Keycloak. Но тут есть важный момент: если ваша структура распределения привилегий имеет рекурсию или же включение нескольких родительских и дочерних групп, то синхронизация будет падать с ошибкой Keycloak. В нашем случае необходимо лишь знать в каких группах состоят пользователи, а не их иерархию, именно поэтому можно произвести выключение данного параметра.

  • Ignore Missing Groups — игнорирование отсутствующих групп.

  • Membership LDAP Attribute — имя атрибута LDAP для сопоставления пользователей с группами.

  • Membership Attribute Type — тип атрибута сопоставления.

Все остальные параметры уже были описаны ранее и логичны по своему описанию за исключением User Groups Retrieve Strategy, на который стоит обратить внимание:

Ведь он отвечает за способ извлечения групп пользователей:

  • LOAD_GROUPS_BY_MEMBER_ATTRIBUTE — группы пользователя будут получены путем отправки запроса LDAP для получения всех групп, где состоит наш пользователь.

  • GET_GROUPS_FROM_USER_MEMBEROF_ATTRIBUTE — извлечение всех групп будет происходить из артибута каждого пользователя.

После синхронизации user federation все данные о пользователях отобразятся в groups и необходимо будет лишь передать их после успешной авторизации пользователя и для этого добавляем ранее описанный ресурс mapper на уровне Clients.

После чего авторизованный запрос пользователя, получаемый oauth2-proxy должен пройти проверку по группе пользователя и в этом может помочь настройка следующего фильтрации групп allowed_groups.

  oauth2:                                                                                                                                                                                                          
    image: bitnami/oauth2-proxy:7.4.0                                                                    
    container_name: 'oauth2-proxy-prometheus'                                                                                                                                                                      
    env_file:                                                                                            
      - .env                                                                                                                                                                                                       
    command:                                                                                             
      - '--http-address=0.0.0.0:4180'                                                                                                                                                                              
      - '--provider=keycloak-oidc'                                                                                                                                                                                 
      - '--client-id=test’                                                                                                                                                                                 
      - '--client-secret=e8hhbO7KmCrBUCTzZNFLHYQpmOAumUdl'                                                                                                                                                         
      - '--redirect-url=https://prometheus-dashboard.test.ru'                                                                                                                                                 
      - '--upstream=http://10.10.10.2:9090'                                                                                                                                                                       
      - '--oidc-issuer-url=https://keycloak-dashboard.test.ru/realms/prometheus'                                                                                                                              
      - '--insecure-oidc-skip-issuer-verification=false'                                                                                                                                                           
      - '--email-domain=*'                                                                                                                                                                                         
      - '--cookie-secret=yx_eP3yIaFuj0FqeWVvjT3ycF0Lh1SuENwSPbs5TYB4='                                                                                                                                             
      - '--pass-access-token=true'                                                                                                                                                                                 
      - '--insecure-oidc-allow-unverified-email=true'                                                                                                                                                              
      - '--cookie-secure=true'                                                                                                                                                                                     
      - '--set-authorization-header=true'                                                                                                                                                                          
      - '--pass-authorization-header=true'                                                                                                                                                                         
      - '--reverse-proxy=true'                                                                                                                                                                                     
      - '--skip-provider-button=true'                                                                                                                                                                              
      - '--set-xauthrequest=true'                            
      - '--allowed_groups=admins,allow_users'                                                                                                                                                         
    ports:                                                                                                                                                                                                         
      - "4180:4180"                                                                                                                                                                                                
    restart: always                                                                                                                                                                                                
    networks:                                                                                                                                                                                                      
      lan:                                                                                                                                                                                                         
        ipv4_address: 172.26.0.10

По итогам описанных выше работ, мы разобрались как можно произвести настройку с OpenID Connect, но нужен ли он нам всегда? Практически в любом ПО можно отключить панель авторизации для пользователей и создать свою панель для аутентификации, но иногда приложение предоставляет настройку авторизации через SAML и данный вопрос можно также закрыть на уровне Keycloak, не усложняя схему сетевого взаимодействия.

Глава 3.4: Настройка Client SAML Keycloak для Ceph

Этот способ в разы проще, в отличие от описанного выше, и для него не нужны дополнительные ПО помимо самого Keycloak. Причина в том, что SAML уже сам по себе является стандартом обмена данными аутентификации и авторизации между участниками. В нашем случае между провайдером идентификации (Keycloak) и поставщиком сервиса (Ceph).

Так как ранее мы уже настроили User Federation в Realm, можем переиспользовать текущую группу пользователей для авторизации в Ceph и поэтому сразу приступаем к шагу создания Client.

И здесь есть 2 ключевых момента, на которые нам надо обратить внимание. Первый момент — это изменение типа клиента на SAML и второй момент — Client ID. Он должен быть сформированной ссылкой на ваши метаданные Ceph.

Метаданные SAML - это XML-файл, содержащий информацию, необходимую для безопасного взаимодействия между провайдером идентификации и безопасности.

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

curl https://ceph-dashboard.test.ru/auth/saml2/metadata
<?xml version="1.0"?>
<md:EntityDescriptor xmlns:md="urn:oasis:names:tc:SAML:2.0:metadata"

                     cacheDuration="PT604800S"
                     entityID="https://ceph-dashboard.test.ru/auth/saml2/metadata">
    <md:SPSSODescriptor AuthnRequestsSigned="false" WantAssertionsSigned="false" protocolSupportEnumeration="urn:oasis:names:tc:SAML:2.0:protocol">
        <md:SingleLogoutService Binding="urn:oasis:names:tc:SAML:2.0:bindings:HTTP-Redirect"
                                Location="https://ceph-dashboard.test.ru/auth/saml2/logout" />
        <md:NameIDFormat>urn:oasis:names:tc:SAML:2.0:nameid-format:persistent</md:NameIDFormat>
        <md:AssertionConsumerService Binding="urn:oasis:names:tc:SAML:2.0:bindings:HTTP-POST"
                                     Location="https://ceph-dashboard.test.ru/auth/saml2"
                                     index="1" />
        <md:AttributeConsumingService index="1">
            <md:ServiceName xml:lang="en">Ceph Dashboard</md:ServiceName>
            <md:ServiceDescription xml:lang="en">Ceph Dashboard Service</md:ServiceDescription>
            <md:RequestedAttribute Name="username" isRequired="true" />
        </md:AttributeConsumingService>
    </md:SPSSODescriptor>

После успешного создания редактируем настройки по умолчанию нашего текущего клиента.

  1. Производим отключение Client signature required.

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

  2. Добавляем Mapper категории Hardcoded attribute.

Делать то, что написано — это хорошо, но зачем? 

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

В голове каждого, кто внимательно читал, сразу возникает вопрос: почему мы задаем данный параметр здесь?
Почему нельзя задать атрибут роли на уровне каждого пользователя Keycloak, которые были синхронизированы ранее?

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

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

После того как все настройки на уровне Keycloak закончены, необходимо лишь включить SAML авторизацию на уровне Ceph, и осуществляется это следующим образом:

ceph dashboard sso setup saml2 https://ceph-dashboard.test.ru https://keycloak-dashboard.test.ru/realms/${REALM_NAME}/protocol/saml/descriptor username

Чтобы убедиться, что все SAML-данные корректно импортировались из Keycloak в Ceph, можно использовать следующую команду:

ceph dashboard sso show saml2

Также рекомендую проверить, что SSO действительно включено и можно тестировать процесс авторизации.

ceph dashboard sso status

Но что получается в результате?

  • Создание процессов авторизации, если они отсутствуют. - Выполнено.

  • Гибкость управления при выдаче и отзыве доступов. - Выполнено.

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

  • Использование LDAP в виде Freeipa, как источника информации о пользователях. - Выполнено.

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

Глава 4: А получилось ли?

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

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

Если есть вопросы — с радостью отвечу в комментариях. Также приглашаю вас подписаться на наш блог Хабр, TG-канал DevOps FM, интернет-издание VC и познакомиться с YouTube — мы всегда рады новым друзьям!

Комментарии (5)


  1. atshaman
    07.08.2023 15:13
    +2

    Ну да... "Для реализации SSO мы развернули SSO, а для централизованного хранения пользовательских УЗ мы взяли имевшийся LDAP" - свежо, по заграничному.

    С какой частотой обновляется информация о изменении членства в группах? Были ли требования от ИБ по уменьшению этого лага? Как они решались?

    Время жизни токенов у администраторов инфраструктуры и у бизнес-пользователей одинаковое? Инфра и приложения в одном realm'е или в разных?

    Были ли проблемы с производительностью при sync'е пользователей и\или большом количестве авторизаций? С openldap у меня - да, с freeipa не знаю. Пробовали ли вы прикрутить к этому делу kerberos как для решения проблем с производительностью и\или для повышения удобства работы пользователей? В adfs неплохо работает, с freeipa опять же не знаком.

    Были ли проблемы с интернационализованными именами? Т.е. лучше когда в этих труднодоступных местах зубов нет - и все на английском, но если есть - работает? KC да - на счет интеграшки интересно.

    Не было ли возражений по использованию group mapper'а с т.з. "единой точки ответственности" от ИБ? Обычно просят ldap role mapper.

    Как обеспечивалась HA решения? Была ли георепликация?

    Обычно в процессе решения этих и им подобных вопросов становится больно - а просто "настроить федерацию" проблем как раз не вызывает.


    1. Gacblk Автор
      07.08.2023 15:13
      +1

      Привет,

      Информация обновлялась периодически каждые 60 секунд. О каких требованиях со стороны ИБ по уменьшению лага стоит вопрос? Как эти 2 момента связаны в контексте Информационной Безопасности подскажите пожалуйста?

      С какой частотой обновляется информация о изменении членства в группах? Были ли требования от ИБ по уменьшению этого лага? Как они решались?

      Про бизнес-пользователей в рамках статьи речи не идёт, статья рассказывает лишь о внедрении возможных способов авторизации в продуктовую или тестовую среду для администраторов. С вопросом использования Keycloak для бизнес-пользователей пока честно говоря познакомиться не получилось, но насколько я знаю эти параметры стараются разделять точно на уровне Realm для того, чтобы как минимум структурно разделить извлекаемых или заведённых пользователей в keycloak.

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

      Время жизни токенов у администраторов инфраструктуры и у бизнес-пользователей одинаковое? Инфра и приложения в одном realm'е или в разных?

      Проблем с производительностью не было при извлечении как отдельной группы пользователей, так и всей архитектуры, но тут можно и явно отметить ряд условий нашей инфраструктуры.
      1. Исходная freeipa имела всего 500 пользователей, но в ней также присутствовала сложная структура вложенности групп.
      2. У нас везде имеется широкий запас по ресурсам.
      3. Отличная и стабильная ширина канала на сетевом уровне.

      Были ли проблемы с производительностью при sync'е пользователей и\или большом количестве авторизаций? С openldap у меня - да, с freeipa не знаю. Пробовали ли вы прикрутить к этому делу kerberos как для решения проблем с производительностью и\или для повышения удобства работы пользователей? В adfs неплохо работает, с freeipa опять же не знаком.

      Не проверялся данный момент, так как по стандартам имена пользователей only English.

      Были ли проблемы с интернационализованными именами? Т.е. лучше когда в этих труднодоступных местах зубов нет - и все на английском, но если есть - работает? KC да - на счет интеграшки интересно.

      По условию ТЗ таких требований отражено не было.

      Не было ли возражений по использованию group mapper'а с т.з. "единой точки ответственности" от ИБ? Обычно просят ldap role mapper.

      Этот вопрос на данный момент оформлен в виде задачи и он будет ещё решаться. Вопрос георепликации - это очень масштабная вещь и учитывая, что у нас закрытый контур - явно возникают проблемы связанные с сетью. Для поднятия георезерва как минимум нужно произвести долгое согласование с КБ и проработать новую архитектуру, учитывая все риски.

      Как обеспечивалась HA решения? Была ли георепликация?

      Статья не просто про то как настроить федерацию)
      Вариантов решения задач авторизации существует очень много, главная проблема возникает, когда ты пытаешься разобрать все возможные вариант, учитывая что это должно быть безопасно, стабильно, удобно и актуально. Когда производилась настройка всех процессов и погружение в аспекты описанные выше, было страшное огорчение, что сама документация Keycloak не соответствует актуальным версиям своего ПО и возникают сложности в настройке всевозможных процессов.

      Обычно в процессе решения этих и им подобных вопросов становится больно - а просто "настроить федерацию" проблем как раз не вызывает.


  1. anonymous
    07.08.2023 15:13

    НЛО прилетело и опубликовало эту надпись здесь


    1. Gacblk Автор
      07.08.2023 15:13
      +1

      Привет,

      Работает весь этот процесс уже чуть больше года.

      Как долго у вас это работает (возможно проглядел)?

      Вопрос хороший. В основе основ везде Debian 11, но сама freeipa развернута может быть только на ОС Centos, в нашем случае использовали Centos 8.

      На каких дистрибутивах?

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

      Был опыт с FreeIPA раньше, и опыт был печальный. Может сейчас лучше стало?


  1. ddubrava
    07.08.2023 15:13

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