Привет всем! В предыдущей статье мы подробно рассмотрели реализацию непрерывной доставки CD в платформе Gitorion на базе Jenkins. В данной статье мы подробно рассмотрим тонкости внедрения системы единого входа Single Sign-On (SSO) во все сервисы платформы Gitorion при помощи Keycloak.
Обоснование необходимости внедрения
Платформа Gitorion состоит из сервисов, реализующих CI/CD, мониторинг и управление базами данных:
Gitea/Forgejo - легковесный хостинг кода и система контроля версий на базе Git;
Jenkins - непрерывная доставка CD;
Grafana - мониторинг и визуализация метрик Prometheus;
phpMyAdmin - управление базой данных MySQL;
pgAdmin - управление базой данных PostgreSQL.
Итого, базовый набор из 5-ти сервисов, каждый из которых имеет собственную систему аутентификации и базу данных пользователей. При небольшом числе участников проекта можно создавать логины пользователей в каждом из пяти сервисов отдельно. При большом количестве пользователей, задача станет невыполнимой. Требуется единая система аутентификации и база данных пользователей. Приняли решение реализовать единый вход Single Sign-On (SSO) при помощи Keycloak.
Принцип работы Keycloak
Большинство современных приложений оснащены клиентами (адаптерами) Keycloak, позволяющими подключаться к серверу Keycloak. На странице входа в приложение располагается кнопка "Подключиться с помощью Keycloak". Пользователь нажимает на эту кнопку, адаптер подключается к Keycloak и перенаправляет пользователя на окно аутентификации Keycloak. Пользователь вводит свой логин и пароль. Keycloak проверяет логин и пароль пользователя и перенаправляет уже аутентифицированного пользователя обратно в личный кабинет сервиса, из которого пришел пользователь.
Область (Realm)
Адаптеры разных сервисов (в нашем случае все пять сервисов платформы Gitorion) объединяются в одну область (Realm). Залогинившись в Web-интерфейсе любого из сервисов, подключенного в область Realm, пользователь получает от Keycloak токен, который записывается в Cookie браузера. В Web-интерфейсы всех остальных сервисов, чьи адаптеры подключены в ту же область, пользователь ходит токеном, без необходимости повторно вводить логин и пароль. Так и реализуется механизм бесшовной аутентификации и единого входа SSO.
Залогиньтесь в консоль администратора Keycloak Administration Console.
В селекторе выбора областей вверху слева нажмите кнопку "Create realm".
В минимуме достаточно задать только имя области "Realm name" и нажать кнопку Create.
Подключаем клиент (адаптер) приложения к Keycloak
Адаптерами оснащены из коробки 4 сервиса из 5ти - Gitea/Forgejo, Jenkins, Grafana и pgAdmin. Ниже мы рассмотрим для примера подключение адаптера Gitea/Forgejo к Keycloak. Адаптеры остальных сервисов хорошо документированы и подключаются аналогично. phpMyAdmin не имеет под капотом адаптера Keycloak. Подключение приложений, не имеющих адаптера Keycloak или вообще не защищенных окном аутентификации, мы рассмотрим ниже.
Выберите созданную выше область, перейдите в меню Clients и нажмите кнопку "Create client".
Задайте "Client ID" – это уникальный идентификатор клиента (адаптера). Нажмите кнопку Next.
В следующем окне включите "Client authentication" и нажмите кнопку Next.
В "Root URL" задайте URL приложения, на который пользователь будет перенаправлен в случае удачной аутентификации в Keycloak. Нажмите кнопку Save.
После того, как будет создан клиент, перейдите на вкладу Credentials и скопируйте "Client Secret", который потребуется ниже при настройке Gitea/Forgejo. Данным ключом клиент Gitea/Forgejo будет подтверждать свою подлинность при подключении к Keycloak.
Переходим к настройке Gitea/Forgejo. Залогиньтесь в Gitea/Forgejo пользователем с административными правами и перейдите в панель управления.
Перейдите в "Identity & Access" подменю Аутентификация и нажмите кнопку "Добавить новый источник".
В поле "ID клиента (ключ)" задайте значение, которое ввели выше при настройке клиента в Keycloak в поле "Client ID". В поле "Клиентский ключ" задайте значение "Client Secret", скопированное выше из вкладки Credentials настроек клиента в Keycloak. В поле "URL иконки" задайте пиктограмму, которая будет отображаться на окне аутентификации Gitea/Forgejo напротив кнопки "Войти с помощью keycloak".
"Open ID Connect URL для автоматизации входа" возьмите в меню настроек "Realm Settings", скопировав ссылку "Open ID Endpoint Configuration" в Keycloak.
Нажмите кнопку "Добавить новый источник" в Gitea/Forgejo и будет создан новый источник аутентификации, привязанный к Keycloak. На странице аутентификации Gitea/Forgejo появится кнопка "Войти с помощью keycloak".
Пользователь нажимает на кнопку "Войти с помощью keycloak", и его перенаправляет на окно аутентификации Keycloak.
Пользователь вводит логин и пароль, и в случае удачной аутентификации Keycloak перенаправляет пользователя в его личный кабинет в Gitea/Forgejo.
А в Keycloak появится сессия пользователя.
База данных логинов и групп пользователей
Keycloak может как самостоятельно хранить в собственной базе данных логины, пароли и прочую информацию о пользователях, так и выступать в качестве провайдера к внешним источникам аутентификации, вроде Microsoft Active Directory, LDAP и т.д. В нашем случае внешний источник аутентификации не потребовался, и мы создали логины и группы пользователей прямо в Keycloak.
Зайдите в консоль администратора Keycloak, выберите область и перейдите в меню Groups. В нашем случае мы разделили всех пользователей на две группы:
admins - пользователи с административными правами во всех сервисах (для тимлидов);
devs - пользователи с правами только на чтение (для разработчиков).
Нажмите кнопку "Create group" и введите имя группы.
Нажмите кнопку Create. Повторите те же действия и создайте группу devs.
Далее, для примера создадим пользователя owneruser и добавим его в группу admins. А также пользователя user1 и добавим его в группу devs. Перейдите в меню Users и нажмите кнопку "Add users".
Введите данные пользователя. Обязательно задайте e-mail (Grafana использует e-mail пользователя вместо логина для аутентификации).
Нажмите кнопку "Join Groups", добавьте пользователя в группу admins и нажмите кнопку Join.
Нажмите кнопку Create, чтобы создать пользователя. Аналогично создайте пользователя user1 и добавьте его в группу devs.
Авторизация на базе ролей Roles
Авторизация и разграничение полномочий потребовалась в Jenkins и Grafana. Тимлидам нужно было предоставить полный доступ, чтобы они могли создавать пайплайны в Jenkins и дашборды в Grafana. А разработчикам дать права только на чтение, чтобы они могли наблюдать за своими пайплайнами и мониторить нагрузку по дашбордам в Grafana.
Залогиньтесь в консоль администратора Keycloak, перейдите в меню "Realm roles".
Нажмите кнопку "Create role". В следующем окне задайте имя роли и нажмите кнопку Save.
Перейдите в меню Groups, выберите группу admins и перейдите на вкладку "Role mapping".
Нажмите кнопку "Assign role", выберите роль admins и нажмите кнопку Assign.
Аналогично ассоциируйте группу devs с ролью devs. Теперь в случае удачной авторизации Keycloak среди прочей информации о пользователе передаст еще и роль пользователя в сервис, из которого пришел аутентифицироваться пользователь. И сервис сможет выполнить авторизацию пользователя и назначить полномочия в соответствии с его ролью. Информацию о клиенте Keycloak передает в Scopes, список которых можно посмотреть в меню "Client scopes".
Среди прочих Scopes по умолчанию есть roles.
Настройте разграничение полномочий на основе ролей в Jenkins. Перейдите в настройках Jenkins в раздел "Manage and Assign Roles" и выберите подпункт "Maname Roles". В появившемся окне в поле "Role to add" введите имя роли admins и нажмите кнопку Add. Аналогично добавьте роль devs. В матрице доступа установите для роли admins галочку Administer в столбце Полные, а для роли devs галочки Read в столбцах Полные и Задача.
Залогиньтесь в Jenkins пользователем owneruser из группы admins, и вам будет доступен пункт добавления пайплайнов "+ Создать Item" и пункт настроек "Настроить Jenkins".
Залогиньтесь пользователем user1, и пункты создания пайплайнов и настроек Jenkins исчезнут. Останется возможность только посмотреть информацию о пайплайнах.
Для Grafana нужно в файле конфигурации grafana.ini ассоциировать роли Keycloak со встроенными ролями Grafana.
role_attribute_path: contains(realm_access.roles[], 'admins') && 'Admin' || contains(realm_access.roles[], 'editor') && 'Editor' || 'Viewer'
Роль admins из Keycloak ассоциируйте с ролью Admin из Grafana. Все остальные роли ассоциируются с ролью Viewer в Grafana.
Залогиньтесь в Grafana через Keycloak пользователем owneruser из группы admins, и вы получите полный доступ к настройкам Grafana.
У пользователя user1 из группы devs права только на чтение, а пункт Administration в меню отсутствует.
Аутентификация приложений без адаптера
Некоторые приложения не имеют под капотом адаптера Keycloak, а порой и вообще не защищены окном аутентификации. Закрыть такие приложения логином и паролем и настроить для них аутентификацию через Keycloak можно с помощью модуля mod_auth_openidc в составе apache2. В этом случае с Keycloak взаимодействует сервер apache2 и реализует аутентификацию для Location, в котором и нужно расположить приложение, требующее аутентификации. Таким приложением в нашем случае оказался phpMyAdmin, который имеет собственную аутентификацию, но не имеет клиента Keycloak. Разработчики предлагают реализовать SSO самостоятельно, используя данный скрипт.
В конфигурационном файле config.inc.php для phpMyAdmin переключите стандартную аутентификацию с помощью логина и пароля на аутентификацию с помощью скрипта signon.php
$cfg['Servers'][$i]['auth_type'] = 'signon';
$cfg['Servers'][$i]['SignonSession'] = 'SignonSession';
$cfg['Servers'][$i]['SignonURL'] = 'examples/signon.php';
И настройте Location для phpMyAdmin в apache2
LoadModule auth_openidc_module modules/mod_auth_openidc.so
ServerName gitrion.ru
<VirtualHost *:80>
ServerName phpmyadmin
ServerAdmin webmaster@localhost
DocumentRoot /var/www/html/
DirectoryIndex examples/signon.php
OIDCProviderMetadataURL https://auth.gitorion.ru/realms/gitrion/.well-known/openid-configuration
OIDCRedirectURI /redirect_uri
OIDCDefaultURL https://phpmyadmin.staging.gitorion.ru:443/
OIDCCryptoPassphrase 0123456789
OIDCClientID phpmyadmin
OIDCClientSecret 2jb0IUaoAfYUxPpAuJSwtcF2EEMEMdOb
OIDCRemoteUserClaim preferred_username
OIDCXForwardedHeaders X-Forwarded-Host
OIDCXForwardedHeaders X-Forwarded-Port
OIDCXForwardedHeaders X-Forwarded-Proto
<Location />
FallbackResource index.php
DirectoryIndex index.php
AuthType openid-connect
Require valid-user
</Location>
ErrorLog ${APACHE_LOG_DIR}/error.log
CustomLog ${APACHE_LOG_DIR}/access.log combined
</VirtualHost>
При переходе на Web-интерфейс phpMyAdmin пользователь перенаправляется на окно аутентификации Keycloak. В случае удачной аутентификации Keycloak перенаправит пользователя в личный кабинет phpMyAdmin.
Browser Flow и ограничение доступа в пределах области на базе ролей Roles
Адаптеры Web-интерфейсов Gitea/Forgejo, Jenkins, Grafana и Web-интерфейсов баз данных phpMyAdmin и pgAdmin для контуров development, staging и production находятся в одной области. Разработчик, залогинившись в любом из Web-интерфейсов платформы, получит доступ к Web-интерфейсам управления базами данных в контуре development, а так же в контурах staging и production. Что недопустимо! Закрыть доступ разработчикам к Web-интерфейсам баз данных в контурах staging и production удалось, добавив в Browser Flow для клиентов (адаптеров) phpMyAdmin и pgAdmin проверку доступа на базе ролей Roles. Browser Flow задает порядок проверки параметров доступа клиента. По умолчанию, сначала проверяется наличие токена в Cookei. Если пользователь ранее залогинился и у него есть токен, то пользователю предоставляется доступ к сервису. Если нет, то предлагается ввести логин и пароль. Мы создали кастомный Browser Flow на базе дефолтного, который после проверки токена в Cookie и логина с паролем, проверяет еще и роль Role пользователя и запрещает доступ пользователям с ролью devs к Web-интерфейсам баз данных в контурах staging и production.
Залогиньтесь в административную консоль Keycloak, выберите область, перейдите в меню Configure, подменю Authentication, нажмите пиктограмму с тремя точками справа от Flow с именем browser и нажмите кнопку Duplicate.
Задайте имя кастомного Flow и нажмите кнопку Dupliacate.
Перейдите в Flow browser-custom и добавьте Sub-flow с именем RBAC в browser-custom forms.
В Sub-flow RBAC добавьте Condition user-role.
Нажмите на шестеренку справа от Condition user-role, задайте Alias и свяжите с ролью devs.
Добавьте Step Deny Access
Нажмите шестеренку справа от Step Deny Access, задайте Alias и текст сообщения, которое будет выдано пользователю из группы devs при попытке доступа к базам данных в контурах staging и production.
Подключите кастомный Flow browser-custom к клиентам (адаптерам) phpMyAdmin и pgAdmin в контурах staging и production. Перейдите в настройках клиента на вкладку Advanced, спуститесь в самый низ и в поле Browser Flow задайте browser-custom.
При попытке доступа пользователя с ролью devs к Web-интерфейсу базы данных в контурах staging или production появится предупреждение.
Заключение
В данной статье мы осветили свой опыт реализации единого входа Single Sign-On (SSO) во все сервисы платформы Gitorion при помощи Keycloak. Будем рады обратной связи, замечаниям и конструктивной критике. Спасибо.