Keycloak — внешний IdP людей¶
Keycloak в поставке — внешний identity provider людей: форма входа, пароли и
сессии браузера. Больше он ничего не решает: полномочия человека живут в IAM
(principal, PAT) и в Control Plane (binding и права). Статья описывает профиль
compose idp, realm platform и его клиенты, служебные скрипты Admin API,
регистрацию Keycloak в IAM и порядок заведения человека. Для администраторов
инсталляции.
Роль Keycloak в цепочке identity¶
Токены Keycloak потребляют сервер консоли (клиент
runtime-console) и launcher личных харнессов (harness-launcher, профиль harness,
клиент human-harness). Оба проводят человека через вход в Keycloak и сразу обменивают
полученный токен в IAM — например, launcher:
sequenceDiagram
participant U as Браузер
participant L as harness-launcher
participant K as Keycloak (realm platform)
participant I as iam-service
participant H as контейнер человека
participant CP as control-plane-api
U->>L: /harness/
L->>K: Authorization Code + PKCE (клиент human-harness)
K-->>L: access token (iss realm, aud iam-service)
L->>I: POST /api/v1/tenants/{t}/federation:exchange
I-->>L: IAM principal человека
L->>H: запрос в контейнер этого principal'а
H->>CP: Bearer (обмен PAT человека)
Keycloak отвечает только на вопрос «кто этот человек». Какой principal ему соответствует, решает IAM по связи external identity; что человек может в Control Plane — binding его principal'а (см. Авторизация и права). Агенты, сервисы и MCP-плагин оператора Keycloak не используют: их identity — Platform Access Token или client credentials IAM.
Развёртывание: профиль idp¶
Профиль idp deploy/local/compose.yml поднимает три сервиса:
| Сервис | Что делает |
|---|---|
keycloak-db |
PostgreSQL 16 только для Keycloak: БД и роль keycloak, пароль KEYCLOAK_DB_PASSWORD, том keycloak_db |
realm-render |
Одноразовый контейнер: подставляет публичный адрес в шаблон realm и кладёт результат в том realm_import |
keycloak |
Keycloak 26 (start --import-realm), слушает 8080 в сети compose, на хосте — 127.0.0.1:${KEYCLOAK_HOST_PORT} (по умолчанию 18081) |
Консоль и рабочие места людей входят через Keycloak: профиль console поднимает и
сервисы idp, а профиль harness требует idp — без Keycloak launcher не может впустить
человека.
make up PROFILES="core edge console" # консоль и Keycloak
make up PROFILES="core edge console harness" # и ассистент
Параметры контейнера keycloak¶
| Переменная | Значение | Смысл |
|---|---|---|
KC_HOSTNAME |
${TAIMEN_PUBLIC_URL}/auth |
публичный адрес с префиксом; issuer realm = ${TAIMEN_PUBLIC_URL}/auth/realms/platform |
KC_HTTP_RELATIVE_PATH |
/auth |
Keycloak обслуживает всё под /auth |
KC_HOSTNAME_STRICT |
${KEYCLOAK_HOSTNAME_STRICT:-true} |
локально по http выключают (false), в промышленной инсталляции — true |
KC_PROXY_HEADERS |
xforwarded |
доверять X-Forwarded-* от Caddy |
KC_HTTP_ENABLED |
true |
TLS терминирует Caddy |
KC_HEALTH_ENABLED |
true |
healthcheck GET /auth/health/ready на management-порту 9000 |
KC_DB_URL_HOST, KC_DB_URL_DATABASE |
keycloak-db, keycloak |
своя база в keycloak-db |
KC_BOOTSTRAP_ADMIN_USERNAME / _PASSWORD |
KEYCLOAK_ADMIN / KEYCLOAK_ADMIN_PASSWORD |
администратор realm master (применяется только при первом старте) |
Лимит памяти — KEYCLOAK_MEM_LIMIT (по умолчанию 768m), у keycloak-db —
общий PG_MEM_LIMIT. Секреты KEYCLOAK_DB_PASSWORD и
KEYCLOAK_ADMIN_PASSWORD заполняет make secrets.
Шаблон realm¶
Realm описан в git шаблоном deploy/keycloak/platform-realm.json. Keycloak при
импорте не разворачивает переменные окружения, поэтому realm-render
заменяет плейсхолдер __WEB_BASE_URL__ на TAIMEN_PUBLIC_URL (redirect URI и web
origins клиентов консоли и ассистента). Клиенты консоли (runtime-console) и
ассистента (human-harness) входят в шаблон: новая инсталляция получает их при первом
импорте.
Импорт realm выполняется только при первом старте
--import-realm создаёт realm, если его ещё нет. Правка шаблона и
перезапуск Keycloak на уже импортированный realm не действуют: живой realm
хранится в keycloak-db. Изменения в существующем realm вносятся через
Admin API или админ-консоль; шаблон держите в согласии с ними, чтобы новая
инсталляция получила то же при импорте.
Realm platform¶
Основные настройки¶
| Параметр | Значение |
|---|---|
sslRequired |
external |
loginWithEmailAllowed |
true (вход по e-mail) |
duplicateEmailsAllowed |
false |
registrationAllowed |
false — самостоятельной регистрации нет |
resetPasswordAllowed |
true |
rememberMe |
true |
bruteForceProtected |
true — защита от подбора пароля на стороне IdP |
accessTokenLifespan |
300 с |
ssoSessionIdleTimeout |
1800 с |
ssoSessionMaxLifespan |
36000 с |
defaultSignatureAlgorithm |
RS256 |
Клиенты¶
| Клиент | Назначение |
|---|---|
runtime-console |
вход в консоль: confidential, Authorization Code + PKCE S256, redirect ${TAIMEN_PUBLIC_URL}/console/_auth/callback |
human-harness |
вход launcher'а личных харнессов: Authorization Code + PKCE S256, redirect ${TAIMEN_PUBLIC_URL}/harness/* |
iam-service |
bearer-only, только audience: IAM принимает upstream-токены, адресованные ему |
У клиентов консоли и ассистента есть маппер iam-service-audience
(oidc-audience-mapper): добавляет iam-service в aud токенов. Именно этот audience
IAM проверяет у провайдера keycloak.
Ролей realm, групповых мапперов и атрибутов организации в realm нет: они не нужны ни IAM, ни Control Plane.
User profile¶
Realm объявляет декларативный user profile. Для заведения людей важно:
email обязателен, а скрипт keycloak-users.py всегда заполняет firstName и
lastName — без них Keycloak может потребовать дозаполнить профиль при входе
(required action). Необъявленные атрибуты может менять только администратор
(unmanagedAttributePolicy: ADMIN_EDIT).
Служебные скрипты Admin API¶
Скрипты лежат в deploy/keycloak/, используют только стандартную библиотеку
Python и ходят в Keycloak по внутреннему адресу http://keycloak:8080/auth
(переопределяется KC_INTERNAL_URL). Запускаются одноразовым контейнером в сети
compose; строка запуска — в docstring каждого скрипта.
| Скрипт | Что делает | Окружение |
|---|---|---|
keycloak-users.py |
Создаёт или обновляет людей в realm: профиль (email, firstName, lastName, emailVerified: true), пароль. Идемпотентен. Печатает {"username", "id"} — id и есть sub пользователя |
KC_ADMIN_PASSWORD, KC_USERS (JSON-массив: username, email, password, необязательно first_name, last_name, temporary), KC_ADMIN_USERNAME, KC_REALM |
keycloak-runtime-console-client.py |
Заводит confidential-клиент консоли runtime-console (Code + PKCE S256, redirect <адрес>/console/_auth/callback, audience iam-service в id и access token) или приводит существующий к шаблону. Секрет берёт из файла или генерирует и пишет туда (0600), на экран не печатает |
KC_ADMIN_PASSWORD, WEB_BASE_URL, SECRET_FILE (по умолчанию /secrets/runtime-console-oidc-secret — смонтировать secrets/), LOCAL_PORTS |
docker run --rm --network taimen_default -v "$PWD/deploy/keycloak:/s:ro" \
-e KC_ADMIN_PASSWORD="$KEYCLOAK_ADMIN_PASSWORD" \
-e KC_USERS='[{"username":"alice","email":"alice@example.com","first_name":"Alice","last_name":"Example","password":"<пароль>"}]' \
python:3.12-alpine python /s/keycloak-users.py
# {"username": "alice", "id": "<sub>"}
Сеть — TAIMEN_NETWORK из .env (по умолчанию taimen_default). Пароли
передаются только через окружение и не печатаются.
Постоянный или временный пароль
По умолчанию скрипт ставит пароль с temporary: false. С
"temporary": true Keycloak попросит сменить пароль на своей странице при
первом входе — для входа в харнесс это допустимо, он идёт через страницу
Keycloak.
Регистрация Keycloak в IAM¶
Чтобы IAM принимал токены Keycloak в federation:exchange, realm
регистрируется в tenant IAM как identity provider. make bootstrap этого не
делает — шаг выполняется один раз bootstrap-токеном IAM по адресу на хосте
(на периметре административные пути IAM закрыты):
IAM=http://127.0.0.1:18010
curl -s -X POST "$IAM/api/v1/tenants/$IAM_TENANT_ID/identity-providers" \
-H "X-IAM-Bootstrap-Token: $IAM_BOOTSTRAP_TOKEN" -H "Content-Type: application/json" \
-d '{
"key": "keycloak",
"issuer": "https://platform.example.com/auth/realms/platform",
"audience": "iam-service",
"lifecycleProfile": "managed"
}'
| Поле | Значение | Почему |
|---|---|---|
key |
keycloak |
имя провайдера, которое передаёт launcher |
issuer |
issuer realm | ровно тот iss, что в токенах Keycloak |
audience |
iam-service |
токены несут его благодаря маппёру iam-service-audience |
subjectClaim, externalIdClaim |
по умолчанию sub |
стабильный id пользователя Keycloak |
lifecycleProfile |
managed |
позволяет заранее привязать внешнюю identity к существующему principal'у; при read_only ручная привязка отвечает 409 identity_provider_managed |
jwksUri можно не указывать: IAM возьмёт его из discovery по публичному
адресу issuer (в сети compose этот адрес ведёт на Caddy благодаря псевдониму
${TAIMEN_PUBLIC_HOST}). Подробности проверки токена и связывания — в статье
Федерация identity.
Заведение человека¶
Основной путь — консоль, раздел «Люди и роли»:
администратор людей вводит имя, e-mail, идентификатор пользователя Keycloak (sub,
ID в разделе Users), workspace, роли и профиль прав, а консоль одним идемпотентным
потоком проходит шаги 1, 3 и 4 ниже и выдаёт ссылку первого входа. Пользователя в
самом Keycloak (шаг 2) и личное рабочее место (шаг 5) консоль не заводит.
Инвайтов нет. Если консоли нет или нужен ручной путь, человека заводят пятью шагами; порядок важен — binding в Control Plane должен существовать до первого запроса человека.
flowchart LR
A["1. IAM principal<br/>kind human"] --> B["2. пользователь<br/>Keycloak"]
B --> C["3. external identity<br/>issuer + sub"]
C --> D["4. principal и binding<br/>в Control Plane"]
D --> E["5. реестр харнесса<br/>и PAT"]
-
IAM principal — bootstrap-эндпоинт IAM:
curl -s -X POST "$IAM/api/v1/tenants/$IAM_TENANT_ID/principals" \ -H "X-IAM-Bootstrap-Token: $IAM_BOOTSTRAP_TOKEN" -H 'Content-Type: application/json' \ -d '{"kind": "human", "displayName": "Alice Example"}'idответа —<iam-principal-id>. -
Пользователь Keycloak —
deploy/keycloak/keycloak-users.py(см. Служебные скрипты);idиз вывода —subпользователя. -
Связь внешней identity с principal:
curl -s -X POST \ "$IAM/api/v1/tenants/$IAM_TENANT_ID/principals/<iam-principal-id>/external-identities" \ -H "X-IAM-Bootstrap-Token: $IAM_BOOTSTRAP_TOKEN" -H 'Content-Type: application/json' \ -d '{"issuer": "https://platform.example.com/auth/realms/platform", "subject": "<sub>"}'Без этого шага первый вход создаст новый principal (JIT), а не использует заведённый в шаге 1.
-
Principal и binding в Control Plane —
POST /api/v1/principalsиPOST /api/v1/principals/{id}/iam-bindingsтокеном администратора ядра (пример запросов — в статье Identity агента; для человекаkind: human). Для работы в харнессе binding нужныtasks.claimиskills.invokeсверх базовых прав чтения и записи. -
Личный харнесс — запись
{"iamPrincipalId": "<iam-principal-id>", "name": …, "email": …}в реестр людей (deploy/harness-people.json) иmake bootstrap ARGS="--harness-people deploy/harness-people.json": шаг 8 выпускает PAT человека и обновляет реестр launcher'а (см. Рабочее место человека).
Дальше человек входит в консоль ${TAIMEN_PUBLIC_URL}/console/ e-mail'ом и паролем из
шага 2; ассистент открывается в ней панелью (${TAIMEN_PUBLIC_URL}/harness/ ведёт туда же). Работать с ядром из Claude Code он может через MCP-плагин по своему PAT
(см. MCP-плагин).
Эксплуатация¶
Изменения в живом realm¶
Все изменения после первого импорта — через Admin API
(/auth/admin/realms/platform/…), скрипты deploy/keycloak/ или админ-консоль
https://platform.example.com/auth/admin/.
Админ-консоль доступна снаружи
Раскладка Caddy проксирует весь /auth/*, включая /auth/admin/. В
промышленной инсталляции ограничьте доступ к /auth/admin/* на внешнем
контуре (allow-list адресов или отдельный внутренний вход) и используйте
стойкий KEYCLOAK_ADMIN_PASSWORD.
Отключение человека¶
Отключение учётной записи в Keycloak закрывает только новый вход в харнесс.
Чтобы закрыть доступ полностью, отключите principal в IAM
(POST …/principals/{id}:disable — отзывает и его PAT) и отзовите binding в
Control Plane (см. Аварийные процедуры).
Резервное копирование¶
Состояние Keycloak (пользователи, пароли, живой realm) — в базе keycloak
сервиса keycloak-db, том keycloak_db. Бэкап — pg_dump этой базы (см.
Резервное копирование).
Типичные проблемы¶
| Симптом | Причина | Что делать |
|---|---|---|
| Правка шаблона realm не применилась | realm импортируется только при первом старте | Admin API или скрипты deploy/keycloak/ |
Консоль получает invalid_client |
в живом realm нет клиента runtime-console |
keycloak-runtime-console-client.py |
Keycloak долго в starting, затем OOM |
JVM не укладывается в KEYCLOAK_MEM_LIMIT |
поднять лимит, проверить свободную память хоста |
| Ошибка про hostname или редиректы на внутренний адрес | KC_HOSTNAME строится из TAIMEN_PUBLIC_URL; при KEYCLOAK_HOSTNAME_STRICT=true запросы с другим именем отвергаются |
проверить TAIMEN_PUBLIC_URL |
| Нет доступа к админ-консоли | пароль администратора сменили в Keycloak, а .env устарел, или наоборот |
KEYCLOAK_ADMIN_PASSWORD применяется только при первом старте; меняйте пароль в самом Keycloak |
| Launcher отвечает «Вход запрещён» | IAM отказал в federation:exchange: провайдер не зарегистрирован, не совпадает issuer или audience, principal отключён |
Ошибки федерации |
| После входа человек работает под новым лишним principal'ом | external identity не была привязана заранее — сработало JIT-создание | привязать identity к нужному principal'у (шаг 3), лишний principal отключить |