
В первой части мы разобрали, как устроен Keycloak, какие у него сущности и зачем вообще нужен SSO в DevOps-инфраструктуре. Теперь — к делу. Во второй части переходим от теории к практике. Разворачиваем Keycloak в Kubernetes, настраиваем Terraform-провайдер, подключаем к нему Grafana, Argo CD и другие сервисы. Разбираемся, как выглядят реальные конфигурации клиентов, scopes и mappings, чтобы SSO действительно работал — с нужными токенами, ролями и группами.
Меня зовут Алексей Цыкунов, я сооснователь и CTO в Hilbert Team. У меня за плечами более 20 лет опыта в проектировании и реализации отказоустойчивых и высоконагруженных информационных систем в таких отраслях, как телеком и FinTech. Являюсь автором курсов по Linux в Otus.ru. А также имею более 8 лет опыта оптимизации работы продуктовых команд и R&D-департаментов с помощью DevOps-инструментов и методик и облачных технологий.
Hilbert Team — провайдер IT-решений для крупного и среднего бизнеса в области облачных технологий, DevOps, DevSecOps, DataOps, MLOps и FinOps. Партнёр Yandex Cloud со специализациями Yandex Cloud Professional по направлениям DevOps и Data Platform.
Итак, начнем.
Я предоставлю рабочий репозиторий с инструкцией для деплоя в kubernetes в Yandex Cloud и Minikube, которую можно легко повторить: взять исходники, всё у себя настроить и проверить, получить работоспособную систему, интегрированную с Single Sign-On (SSO), то есть с Keycloak.
Разворачиваем стенд
У нас есть 2 рабочих репозитория: для развертывания инфраструктуры с помощью Argo CD и для настройки Keycloak с помощью Terraform.
Репозитории приватные, но у вас есть возможность клонировать их с помощью токена:
git clone https://argo:gldt-sWmbaMCqi4TqpZy7NoZn@hilbertteam.gitlab.yandexcloud.net/hilbert-team/devopsconf2025/argocd.git
git clone https://argo:gldt-sWmbaMCqi4TqpZy7NoZn@hilbertteam.gitlab.yandexcloud.net/hilbert-team/devopsconf2025/keycloak-sso.git
В директории argocd находится пошаговая инструкция для работы с обоими репозиториями. В ходе работы вам нужно будет переключаться между ветками, инструкция находится в ветке main Не забывайте, что для Terraform, нужно зеркало или VPN. Информация по настройке зеркала также есть в инструкции. Если будете разворачивать инфраструктуру на Minikube, где жёстко прописаны доменные имена, не забудьте воспользоваться etc/hosts и прописать там свои адреса и Load Balancer, который выдаст мини-Куб на эти адреса.
Стенд развернут в Yandex Cloud, поэтому показывать буду на примере Managed Service for Kubernetes. Из инструментов понадобится также Terraform и Helm. Большую часть сделает Argo CD, но чтобы развернуть сам Argo CD, понадобится Helm.
Команды для развертывания стенда в k8s
Клонируйте репозиторий, если вы этого еще не сделали и выполняйте установку Argo CD
git clone https://argo:gldt-sWmbaMCqi4TqpZy7NoZn@hilbertteam.gitlab.yandexcloud.net/hilbert-team/devopsconf2025/argocd.git
cd argocd/argocd-bootstrap
helm -n argocd upgrade --install argocd ./bootstrap/base/ \
-f ./bootstrap/base/values.yaml \
-f ./bootstrap/base/secrets.yaml \
--create-namespace
kubectl apply -k bootstrap/overlays/default/
Разберем команды:

Так мы устанавливаем с помощью helm сам Argo CD. В директории ./bootstrap/base/
у нас лежит файл Chart.yaml
, в котором через dependencies прописана зависимость на публичный чарт, а также secrets.yaml
и values.yaml
, в которых мы передаем настройки чарта. Обратите внимание, что из-за того что мы используем dependencies, настройки чарта мы передаем именно в чарт зависимости и параметры поэтому указываются вложенными атрибутами.

Далее
kubectl apply -k bootstrap/overlays/default/
Этой командой мы разворачиваем ресурс Kustomization, который ссылается на компонент ApplicationSet

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

Для каждого тенанта создается Application, который разворачивает уже чарт.
Таким образом у нас автоматически разворачиваются ingress-nginx, сам Keycloak и Postgres-оператор, необходимый для хранения базы Keycloak.
Вы можете подключится к kubernetes и с помощью kubectl проверить что у вас развернулось.
kubectl get ingress -A
kubectl get pods -A
kubectl get applications -A
Дождитесь, когда pod для keycloak станет Ready.

Когда вы увидите, что у вас появился ingress для keycloak, попробуйте зайти по этому адресу sso.mc.hilbertteam.com
.

ADDRESS
у вас появится в том случае если у вас появился EXTERNAL_IP
для сервиса ingerss-nginx-controller
. В случае Yandex Cloud у вас будет создан NLB и выделен внешний IP. Если у вас minikube, смотрите README в репозитории в ветке main.

Учтите, что вам потребуется прописать адреса у себя в /etc/hosts,
чтобы они смотрели на IP вашего Load Balancer, т.к. мы используем наш домен.
Пароль для доступа у нас прописан в коде в открытом виде, естественно, только для лабораторных целей.

Надеюсь, что вам удалось зайти в ваш Keycloak.
А теперь нам надо попасть в Argo CD.
Нам нужен пароль и мы можем вытащить его с помощью команды
kubectl -n argocd get secrets argocd-initial-admin-secret -o json | jq '.data.password' -r | base64 -d
Argo CD автоматически создаёт дефолтный пароль в secret, просто копируем его в буфер и логинимся под пользователем admin по адресу argocd.mc.hilbertteam.com

В Argo CD мы можем теперь увидеть статусы устанавливаемых приложений.
Итак, у нас развернулось как минимум, два инфраструктурных сервиса — Keycloak и Argo CD и еще Ingress Controller. В каждый мы можем залогиниться используя свои учетные данные. Но теперь хотелось бы настроить интеграцию, чтобы попадать в Argo CD используя учетные данных из Keycloak. Это мы сделаем чуть позже.
А пока давайте детальнее рассмотрим параметры установки Keycloak
Обратите внимание на опцию proxy, если ставите Keycloak в Kubernetes, за ingress’ом. Иначе не сработает.

Всегда следите за версией Keycloak, так как она может сильно меняться от релиза к релизу. Например, эти параметры появились недавно, а до этого требовалось использовать другие настройки.
Не используйте такого в проде: здесь фейковые сертификаты, поэтому пришлось ставить костыли.

Чтобы развернуть Keycloak, нужно передать env непосредственно в приложение, чтобы он их правильно подтянул.

Дальше — база данных и указания, какой ingress использовать.

Больше ничего не нужно, остальное подтянется дефолтными параметрами.
Чтобы настроить Keycloak, нужно настроить провайдера, а чтобы настроить провайдера нужно в этом провайдере задать админские логин и пароль. Вы можете зайти в Keycloak, и выполнить Reset password или оставить для terraform те, что были заданы при развертывании.


Обязательно отключаем Temporary, иначе при следующем заходе будет запрос, чтобы вы изменили логин/пароль.
И вот мы всё настроили и получили доступы. Пора разворачивать.
Keycloak Terraform Provider
Для начала настроим зеркало
Создайте или обновите файл ~/.terraformrc
следующим содержимым:
provider_installation {
network_mirror {
url = "https://terraform-mirror.yandexcloud.net/"
include = ["registry.terraform.io/*/*", "registry.opentofu.org/*/*"]
}
direct {
exclude = ["registry.terraform.io/*/*", "registry.opentofu.org/*/*"]
}
}
Команды для конфигурации Keycloak:
Нам потребуется другой репозиторий.
git clone https://argo:glpat-sJahviNmDGmJLSPJC54Y@hilbertteam.gitlab.yandexcloud.net/hilbert-team/devopsconf2025/keycloak-sso.git
cd keycloak-sso/keycloak-config
terraform init
terraform plan
terraform apply
Если вы меняли админский пароль для keycloak, исправьте его также в коде терраформа.

В результате выполнения команды terraform apply создаётся очень много объектов, все из которых описаны в коде
Наша задача — определить, какие ресурсы, указывающие на сущности Keycloak, описаны в нашем коде и для чего они используются.

До этого мы видели, что есть только Master-realm с админским пользователем. Сейчас у нас создался отдельный Realm и в нём произошел ряд настроек.
Произошла магия: был Keycloak, а теперь появился Realm devopsconf25.

Здесь появились сразу все клиенты, но нас интересуют именно grafana, argocd и oauth2.

А ещё появились две группы: argo_admins и argo_viewers. Их нужно включить для Argo CD.

Argo CD будет получать разделение прав через группы. У нас есть два пользователя — dc25_admin и dc25_user. Их и будем использовать.

Появились realm'овские роли.

Здесь oauth2_access — отдельная realm'овская роль.
В клиенте Grafana тоже появились отдельные роли Grafana.

Рассмотрим на конкретных приложениях, как работают все аспекты, в том числе, разница в конфигурациях ролей и групп.
Итак, мы хотим интегрировать Argo CD.
Для того чтобы заставить Argo CD подтягивать изменения из других веток мы будем накатывать изменения из этих веток. Сейчас нам понадобится ветка argocd-oidc-config в репозитории argocd.
cd argocd/argocd-bootstrap
git fetch
git checkout argocd-oidc-config
helm -n argocd upgrade --install argocd ./bootstrap/base/ \
-f ./bootstrap/base/values.yaml \
-f ./bootstrap/base/secrets.yaml \
--create-namespace
kubectl apply -k bootstrap/overlays/default/
Так как у нас меняются параметры Argo CD, надо обновить его с помощью команды helm upgrade
.
Посмотрим, что изменилось.
Вот параметры Argo CD:

В этой конфигурации Dex выключен. Argo CD умеет интегрироваться через Dex напрямую. Мы используем прямой connect. Наш любимый атрибут insecure
позволяет работать с самоподписанными сертификатами. Затем начинается ingress enabled, остальное остаётся таким же.
Начинается новый интересный блок настройки rbac
и конфигурация с oidc
.

Нас интересует oidc.config
. Здесь мы пропустили tls
для oidc
, что крайне нежелательно в проде. Указываем наш конфиг — issuer
, то есть адрес самого Keycloak.

Обратите внимание на префикс /auth
. Это legacy-префикс, он раскатывается дефолтными версиями. Если вы при раскатке Keycloak его не выключили, то все ваши аутентификации, обращения к аутентификации, вход в сам Keycloak должны проходить через этот префикс. Это может быть очень болезненно в первый раз и отнять много времени.
Дальше указываем креды нашего клиента.

Клиент называется argocd
. Перейдя в Clients, найдём там этого клиента.

У клиента есть заданные Credentials — client-secret.

Именно с этими кредами он коннектится. У клиента нет встроенных ролей (Roles).

Дальше займёмся Client scopes:

Посмотрим, как работает Evaluate.
Делаем Evaluate и под dc25_admin попрошу сгенерировать токен.

В токене мы получаем groups, и именно группу argo_admins парсит приложение и выдаёт роли, а дальше — дополнительные атрибуты.

Самое главное, что помимо аутентификации, мы получаем группы. Разберемся как мы получили группу dc25_admin в токене.
Возвращаемся к Client scopes.

Там есть groups, прописанная как дефолтная. Если перейти в groups, посмотреть mapper, найдём там mapper groups.



Это пришлось предварительно настроить, оно не появилось само. Здесь включены птички, в какие токены эта группа будет добавлена, как будет называться и каким будет имя claim, которые мы указываем в конфиге для argocd.

В requestedScopes описаны обязательные параметры. Здесь я могу добавить groups или roles, то есть указать какой scope необходимо получить. Можно также дополнительно включить scope для ролей.
Переходим в нашу Argo CD, выйдем из админа и появится волшебная кнопочка «аутентифицироваться в Keycloak».

Небольшой нюанс.
Так как всем сервисам тоже нужно знать, где находится Keycloak, то для этих целей я использую внутреннюю зону в Yandex Cloud DNS, в которой указал IP нашего балансировщика.

Теперь у нас есть пользователь dc25_admin и полный набор всего, что нужно.

Это админ. Соответственно, он имеет возможность полного управления в Keycloak.

Если вы не настроили Single Sign Out, в Keycloak сессия не завершается. Потом можно либо открыть новое окно в приватном режиме, либо зайти во вкладку Sessions и увидеть все сессии.

Это ещё одно удобство Keycloak: можно видеть сессии пользователей, которые сейчас залогинены, и управлять им из единого окна. Например, сделать sign out, чтобы пользователь потерял доступ.

Делаю logout, выхожу и захожу под пользователем dc25-user.


Разница видна — у пользователя теперь ограниченные права, я ему выдал не все applications, обрезал модификацию, удаление и так далее. Так мы управляем правами и доступами.
Сам Keycloak — это прежде всего инструмент аутентификации. Ошибочно полагать, что это инструмент авторизации. Но его дополнительные фишки, такие как роли и группы, т позволяют приложениям настроить авторизацию, определяя на что именно у пользователей будут права.
Теперь рассмотрим Argo CD. Прежде всего обращаем внимание на mappings. Вот два mappings:

argo_viewers
— группа, которая прилетает в токене,
read
— группа, куда делаем mappings в самой Argo CD.
Здесь сразу видно, какие предоставлены applications, что запрещено, например, даже получение get. То есть можно очень гибко управлять, давать разрешения на определённые действия и запрещать другие. Но это возможности Argo CD, а не Keycloak.
Авторизацией управляет ваше приложение. Keycloak на это никак не влияет, но может докинуть атрибутов.
В Argo CD всё раскидывается в ApplicationSet. Когда мы меняем ветку, repoURL
остаётся тем же, меняется revision, мы смещаемся на другую ветку.

Теперь раскатываем следующий элемент — observability stack.
Мы хотим, чтобы он сразу был закрыт Keycloak’ом. Для этого идём в ветку kube-prometheus-stack
и деплоимся. Обновлять Argo CD больше не нужно, там ничего не меняется. Просто переключим аппликейшенсет на другую ветку с помощью редеплоя.

Пока посмотрим, что происходит в Argo CD. Войдём под админом и увидим как раскатывается новое приложение.

То есть мы зашли обратно под юзером, но юзер видит новое приложение.
Дожидаемся пока позеленеет:

И может теперь попасть в графану по адресу grafana.mc.hilbertteam.com. Не забудьте также его указать у себя в /etc/hosts
.
А мы тем временем попали в Grafana.

И у нас сразу уже есть кнопка доступа через Keycloak. Если ее нажать, то попадаем под пользоваттеля dc25_user, если вы не убивали сессию в Keycloak.

Если посмотрим на профиль, то зашли — под viewer’ом. Базовые роли в графане — viewer, editor, admin.
При выходе (sign out) сессия в Grafana закрывается, в отличие от Argo CD, так как это предусмотрено нашей конфигурацией.
После этого можно заходить под dc25_admin.

Сразу видно, что появились совершенно другие возможности, можно делать всё, что угодно.
Теперь посмотрим, как это всё разворачивалось для Argo CD.
В Argo CD увидим мониторинг, values, Grafana, ingress, конфигурацию auth.generic_oauth
.

Дальше в 68 строке мы видим прописанные roles. То есть запрашиваем не группы, а роли. Ниже прописаны auth, дополнительный токен, API и тот самый sign out. Он так интересно прописывается, а затем сюда автоматически добавляется специальный токен, который выкидывает сессию из Keycloak.
Дальше разбирается nested вложенная структура. Рассмотрим её подробнее.
role_attribute_path: >-
contains ("resource_access". "grafana". roles [*1, 'grafana admin') && 'Admin' ||
contains (Eesourcenaccess. grafana. roles (*], 'grafana editor") && 'Editor' ||
'Viewer'
Есть структура, в которую приезжают роли, то есть массив ролей, который находится внутри resource_access
в Grafana. Если он содержит grafana_admin
, значит, мы даём роль Admin. В случае grafana_editor
— Editor. А остальным — просто Viewer. Это тоже базовая настройка.
Теперь посмотрим, что мы делали в terraform. Первым делом настроили провайдер.

В ситуации не для прода здесь подписано — insecure.
Обязательно указывается, что base_path
сейчас такой.
Далее создаём первый Realm:

Затем добавляем клиента для Argo CD.

Здесь мы видим access_type = “CONFIDENTIAL”
и те самые птички, которые для него включали — standard_flow
, direct_access
, use_refresh_tokens
, и redirect_uris
и logout (без sign out, без специальных опций, поэтому нас не выкидывает).
Дальше мы создали двух простых пользователей, которых потом добавили, создали группу и назначили group_membership
.

Выглядит просто. Гораздо проще один раз настроить всё в Terraform, чем идти другими путями. Чтобы потом не забыть, где и какие параметры были изменены.
Дальше начинается интересное — argocd-default-scopes
:

Помимо profile
и email
включаем дефолтный scope, который уже настроили.
Мы создали этот scope и добавили в него groups_mapper
.
Вот наш конфиг, oidc-group-membership-mapper
:

OpenID — в Grafana. Вот наш клиент для Grafana.

Здесь всё практически то же самое: включили эти галочки, только добавили roles, а не groups. Соответственно, эти роли теперь надо добавить. Для этого у нас есть keycloak_role
:

Мы расписываем нашу роль. Этот ресурс умеет Realm’овые роли и клиентские роли. От этого зависит, назначите вы ему client_id
или нет, то есть роли будут разными.
Дальше добавим grafana_editor
и прилетает такая дефолтная роль:

Иначе если offline_access
не прилетит в Grafana, которая дефолтно раздается Keycloak’ом, то нас всё равно не пустят внутрь. Поэтому надо обращать внимание, что некоторые дефолтные штуки тоже нужно использовать.
Мы делаем user_roles
, назначаем роли, в том числе админу.

Здесь oauth2 тоже добавили, он ещё понадобится.
Переходим к последней части — OAuth2-Proxy.
Выглядит это следующим образом. Есть открытый Prometheus, который мы не хотим его открывать. Один из инструментов, который позволяет его закрыть — это OAuth2-Proxy. У него есть два режима работы. Мы рассмотрим только один, когда он работает прямо как reverse proxy, то есть все запросы начинают проходить через него.
Есть второй режим работы, тесно связанный с настройкой ingress-контроллера. У ingress-контроллера в nginx есть специальные опции auth_request
, которые говорят, что перед отправкой в этот location, нужно сделать auth_request
на middle-party, third-party. Этим third-party может выступать OAuth2-Proxy, который будет ходить в Keycloak, и дальше, исходя из того, какие атрибуты присланы в токене, берёт весь токен, перекладывает в хедер и отправляет в приложение. Вы же берёте токен из хедера.
А есть более простой режим, когда вам надо закрыть какой-то один сервис, чтобы туда могли попасть лишь конкретные группы.
Переключаемся на ветку oauth2-proxy.

Что произошло?

Мониторинг пожелтел, потому что мы выключали ingress для Prometheus, то есть у Prometheus теперь нет ingress. Зато теперь есть ingress у oauth2-proxy.

Входим на него, и у нас появляется такая защита:

Если мы входим под админом, то всё нормально, мы попадаем в Prometheus.

Если же входим под dc25_user, то получаем отбивку, что у этого пользователя нет прав входа.

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

Посмотрим, как это реализовано. Вот наш OAuth2-Proxy:

В нём настраивается upstream, который смотрит на сервис Prometheus (не на ingress).
В целом мы могли использовать и домен prometheus.mc.hilbertteam.com. И он привел бы нас в oauth2-proxy, который нас бы аутентифицровал и перенаправил в Prometheus.
Через Keycloak мы видим, что привязаны к ролям, и ждём подтверждения, что у нас есть разрешённые роли. Параметр allowed_roles
говорит, что сюда можно заходить только с этими ролями.
Это пример, что можно строить доступ на ролях, а не только на группах. Дальше OAuth2-Proxy просто отправляет нас куда-то ещё. Это его базовая конфигурация.
Из нюансов Keycloak требовался audience_mapper
для oauth2, который мы тоже должны были включить. Это дополнительное требование.

Это проверка, что только этому клиенту можно отдавать заданные атрибуты. Роль oauth2_access
— это роль Realm.

Выводы
Keycloak — стандарт де-факто для SSO в DevOps-инфраструктуре. Он поддерживает OpenID Connect, OAuth2 и SAML, позволяет централизованно управлять аутентификацией и авторизацией во всех инфраструктурных сервисах: Grafana, Argo CD, Vault, Prometheus и других.
SSO нужен везде, где раньше приходилось «костылить» доступ. Если вы вручную прописывали логины в Grafana или «лепили» авторизацию поверх Prometheus, пора перейти на централизованный подход с Keycloak.
Интеграция с Keycloak требует понимания клиента, scopes и token mappings. У разных сервисов — разные требования к токенам и claims. Универсальной схемы настройки нет. Придётся настраивать протоколы, scopes и mapper’ы индивидуально для каждого клиента.
Ошибки с Master-realm и default-мэппингами встречаются часто. Не используйте Master-realm для пользователей и приложений — создавайте отдельные realms. Группы и роли не попадут в токены сами собой: без настройки scopes и мэпперов вы этого не увидите.
Keycloak — это про аутентификацию. Авторизацию реализует само приложение. Keycloak лишь передаёт в токене информацию:роли, группы, audience. Всё остальное — дело клиента: кто и что может делать, с какими правами.
Автоматизация конфигурации через Terraform упрощает жизнь. Один раз описав Realm, клиентов, пользователей, роли и scopes в коде, можно быстро и повторяемо разворачивать инфраструктуру с SSO. Но есть и другие варианты автоматизации и хранения конфигураций в коде.
Keycloak активно развивается — следите за версией. Некоторые параметры меняются между версиями. Обращайте внимание на префиксы (/auth), новые фичи, настройки proxy и обратной совместимости.
OAuth2-Proxy + Keycloak — рабочий вариант для защиты сервисов без встроенной аутентификации. Простой способ «закрыть» Prometheus, Alertmanager или любые SPA, ограничив вход группами или ролями.
Спасибо, что прочитали мою статью до конца. Приходите на DevOps Conf — меньше теории, больше практики: разворачиваем, настраиваем, автоматизируем. Всё по-взрослому. И не забывайте подписываться на телеграм-канал Hilbert Team, у нас там много интересного.