Брокеры идентификации и SSO: внедрение OAuth 2.0 и OpenID Connect на практике | AdminWiki

Брокеры идентификации и SSO: внедрение OAuth 2.0 и OpenID Connect на практике

11 сентября 2026 16 мин. чтения
Содержание статьи

OAuth 2.0 - фреймворк делегированной авторизации: клиент получает access token и обращается с ним к API от имени пользователя. OpenID Connect добавляет аутентификацию: выдает ID token, JWT с информацией о пользователе. Брокер идентификации - сервис-посредник, который избавляет приложения от прямой интеграции с каждым провайдером: Azure AD, Google, Keycloak, корпоративный LDAP.

Единый вход (SSO) в микросервисах строится на этой связке. Дальше - практика: поток Authorization Code с PKCE, валидация токенов, конфигурации oauth2-proxy и Nginx, защита API на Kong и Traefik, вход в Kubernetes через Keycloak и Dex, разбор ошибок 401/403 и меры безопасности для production.

OAuth 2.0, OpenID Connect и брокер идентификации: как это работает вместе

OAuth 2.0 описывает четыре роли: resource owner (пользователь), client (приложение), authorization server (в OIDC-мире - IdP, провайдер идентификации) и resource server (API с защищенными ресурсами). Пароль пользователя клиент не видит: сервер авторизации выдает access token, resource server проверяет его и отдает запрошенные ресурсы. Scope задает границы: например, scope read:orders разрешает только чтение заказов.

OpenID Connect 1.0 - надстройка над OAuth 2.0: добавляет ID token (подписанный JWT с claims sub, email, name, groups), discovery-документ /.well-known/openid-configuration и эндпоинт /userinfo. Оба токена клиент получает за один обмен кода.

Брокер идентификации (identity broker) стоит между приложениями и провайдерами. Поток выглядит так: Client → Broker → IdP → Broker → Client. Приложение знает только адрес брокера, брокер хранит настройки всех IdP, преобразует протоколы (например, SAML в OIDC) и возвращает приложению унифицированный токен. Identity brokering встроен в Keycloak; похожие сценарии закрывают Dex и Ory Hydra.

Ключевые различия OAuth 2.0 и OpenID Connect

Путаница между протоколами приводит к ошибкам в конфигурации: инженеры ищут email в access token или пытаются валидировать ID token на API. Разделение простое.

КритерийOAuth 2.0OpenID Connect
ЦельДоступ к ресурсамИдентификация пользователя
Основной токенAccess tokenID token (JWT)
Кто использует токенResource server (API)Client (приложение)
Информация о пользователеScopeClaims в ID token: profile, email, groups
Эндпоинты/authorize, /tokenТе же плюс /userinfo, discovery, /jwks

Access token создан для API: клиент не должен читать его содержимое и строить на нем логику. ID token создан для клиента: это JWT, который клиент обязан проверить (подпись, iss, aud, exp, nonce), прежде чем пустить пользователя в приложение.

Роль брокера идентификации в микросервисной архитектуре

Сценарии, в которых брокер экономит недели работы:

  • Сотрудники входят через Azure AD или Active Directory, партнеры - через Google Workspace. Брокер подключает оба IdP, а приложения получают один OIDC-поток.
  • Legacy-приложение умеет только SAML. Брокер принимает SAML от IdP и отдает приложению OIDC-токены.
  • Нужен централизованный logout: сессия рвется на брокере, backchannel-уведомления гасят сессии в приложениях.
  • Политики MFA и ограничения по IP настраиваются один раз на брокере, а не в каждом сервисе.

Замена IdP перестает быть проектом на квартал: меняются настройки брокера, приложения продолжают работать без правок. Схема доступа к микросервисам через JWT и API-шлюзы разобрана в отдельном руководстве: Управление доступом в микросервисной архитектуре: JWT, API-шлюзы и сервис-меш.

Authorization Code Flow с PKCE: стандарт для production

Authorization Code + PKCE - рекомендованный поток для веб-приложений, SPA и мобильных клиентов. Код авторизации живет порядка минуты и обменивается на токены через backchannel. PKCE (Proof Key for Code Exchange, RFC 7636) привязывает код к клиенту: перехваченный код без code_verifier бесполезен. В SPA и мобильных приложениях client_secret не используют: он все равно окажется в бандле.

Шаги потока:

  1. Клиент генерирует code_verifier (случайная строка 43-128 символов) и code_challenge = BASE64URL(SHA256(code_verifier)).
  2. Редирект на /authorize с параметрами response_type=code, client_id, redirect_uri, scope, state, nonce, code_challenge и code_challenge_method=S256.
  3. Пользователь аутентифицируется у IdP, при необходимости проходит MFA.
  4. IdP возвращает code на redirect_uri. Клиент сверяет state с сохраненным значением.
  5. Клиент отправляет code и code_verifier на /token и получает access token, ID token и refresh token.
  6. Клиент валидирует ID token и открывает сессию.

Три параметра закрывают три атаки: state защищает от CSRF, nonce от replay ID token, точное совпадение redirect_uri от увода кода на чужой домен. State делайте одноразовым и привязанным к сессии пользователя.

Генерация code_verifier и code_challenge: пример на Python

Библиотека secrets и стандартный hashlib покрывают задачу целиком:

import base64
import hashlib
import secrets

code_verifier = secrets.token_urlsafe(64)
digest = hashlib.sha256(code_verifier.encode("ascii")).digest()
code_challenge = base64.urlsafe_b64encode(digest).rstrip(b"=").decode("ascii")

print("verifier:", code_verifier)
print("challenge:", code_challenge)

secrets.token_urlsafe(64) выдает около 86 символов URL-safe алфавита, что укладывается в диапазон 43-128 из RFC 7636. Обмен кода на токены с code_verifier:

curl -s -X POST "$ISSUER/protocol/openid-connect/token" \
  -H "Content-Type: application/x-www-form-urlencoded" \
  -d "grant_type=authorization_code" \
  -d "code=$AUTH_CODE" \
  -d "redirect_uri=$REDIRECT_URI" \
  -d "client_id=$CLIENT_ID" \
  -d "code_verifier=$CODE_VERIFIER"

ISSUER - базовый URL провайдера из discovery-документа. В ответе приходят access_token, id_token, refresh_token и expires_in:

{
  "access_token": "eyJhbGciOiJSUzI1NiIs...",
  "token_type": "Bearer",
  "expires_in": 300,
  "refresh_token": "1f2e...",
  "id_token": "eyJhbGciOiJSUzI1NiIs..."
}

Валидация ID token: подпись, iss, aud, exp, nonce

ID token без проверки - открытая дверь: поддельный JWT на callback приведет к чужой сессии. Чек-лист:

  1. Получить публичные ключи по jwks_uri из discovery-документа, кэшировать с обновлением по kid.
  2. Проверить подпись (RS256, реже ES256). Алгоритм брать из заранее заданного списка, а не из заголовка токена: поле alg контролирует отправитель.
  3. Сверить iss с issuer из discovery посимвольно, включая завершающий слэш.
  4. Сверить aud с client_id приложения.
  5. Проверить exp и iat с запасом 60 секунд на рассинхрон часов.
  6. Сверить nonce со значением, сгенерированным перед редиректом.
import jwt
from jwt import PyJWKClient

jwks_client = PyJWKClient(JWKS_URI)
signing_key = jwks_client.get_signing_key_from_jwt(id_token)

claims = jwt.decode(
    id_token,
    signing_key.key,
    algorithms=["RS256"],
    audience=CLIENT_ID,
    issuer=ISSUER,
    options={"require": ["exp", "iss", "aud", "nonce"]},
)

user_email = claims["email"]
user_groups = claims.get("groups", [])

PyJWT из примера проверяет подпись, iss, aud и exp, nonce сверяется отдельной строкой. Ошибка Signature verification failed чаще всего означает неверный jwks_uri или устаревший кэш ключей после ротации.

Implicit Flow: почему он устарел и как мигрировать

Implicit flow возвращает access token прямо в URL-фрагменте: /callback#access_token=...&token_type=Bearer&expires_in=3600. Обмена кода нет, подтверждения клиента нет, refresh token по умолчанию тоже нет, поэтому время жизни access token растягивают. OAuth 2.1 исключает Implicit из спецификации, а Security Best Current Practice (RFC 9700) рекомендует его не использовать. Для новых проектов выбор один: Authorization Code + PKCE.

Конкретные риски Implicit: token leakage и replay

  • Токен лежит в адресной строке: он остается в истории браузера, доступен расширениям, попадает в сохраненные сессии и на скриншоты. Любой скрипт на странице читает его через location.hash.
  • Токен не привязан к клиенту: получивший строку предъявит ее API. Для replay-атаки нужна только копия токена. Клиенты без проверки state и nonce открыты для CSRF и переигрывания ответа IdP.
  • Если реализация передает токен в query-параметре, значение осядет в access-логах Nginx, балансировщиков и APM-систем.
  • Silent renew через скрытый iframe опирается на third-party cookies, которые браузеры блокируют все чаще, и продление сессии в старых SPA перестает работать.
  • Нет refresh token: чтобы пользователь не перелогинивался каждый час, access token делают долгоживущим, увеличивая окно утечки.

План миграции с Implicit на Authorization Code + PKCE

  1. Добавить в настройках клиента IdP новый redirect_uri для потока с кодом. Старый адрес оставить на период миграции.
  2. Развернуть BFF (Backend for Frontend) или oauth2-proxy. Браузер хранит только httpOnly-cookie сессии, токены остаются на сервере.
  3. Перевести фронтенд на новую схему: редирект на /oauth2/start, профиль через /oauth2/userinfo.
  4. Отключить Implicit у провайдера: в Keycloak это Clients → Capability config → выключить Implicit flow.
  5. Дождаться истечения старых токенов или отозвать их на стороне IdP.

Интеграция SSO с Nginx и API-шлюзами

Есть два проверенных подхода к защите веб-приложений и API. Первый: oauth2-proxy отдельным сервисом и nginx auth_request. Второй: проверка JWT прямо на API-шлюзе (Kong, Traefik, Envoy). Первый вариант подходит приложениям без поддержки OIDC: прокси берет аутентификацию на себя и передает идентификатор пользователя заголовками. Второй подход выбирают в API-first архитектурах, где важна быстрая stateless-проверка на каждом запросе.

Плюсы схемы с oauth2-proxy: приложение не меняется, legacy-сервисы получают единый вход, сессия хранится в зашифрованной cookie. Минусы: дополнительный сетевой хоп, отдельный сервис в цепочке и контроль его доступности. Плюсы проверки на шлюзе: нет серверных сессий, проще масштабирование, минимальная задержка. Минусы: централизованный logout требует отдельного механизма, каждый шлюз нужно настроить на обновление JWKS.

Настройка oauth2-proxy с Nginx: пример конфигурации

oauth2-proxy выполняет весь OIDC-поток за приложение: редиректит на IdP, принимает callback, хранит сессию в зашифрованной cookie и отвечает на проверочный эндпоинт /oauth2/auth. Минимальный docker-compose:

services:
  oauth2-proxy:
    image: oauth2-proxy/oauth2-proxy:v7.6.0
    restart: unless-stopped
    environment:
      OAUTH2_PROXY_PROVIDER: oidc
      OAUTH2_PROXY_OIDC_ISSUER_URL: ${OIDC_ISSUER_URL}
      OAUTH2_PROXY_CLIENT_ID: ${OIDC_CLIENT_ID}
      OAUTH2_PROXY_CLIENT_SECRET: ${OIDC_CLIENT_SECRET}
      OAUTH2_PROXY_COOKIE_SECRET: ${COOKIE_SECRET}
      OAUTH2_PROXY_EMAIL_DOMAINS: "*"
      OAUTH2_PROXY_HTTP_ADDRESS: "0.0.0.0:4180"
      OAUTH2_PROXY_REDIRECT_URL: ${REDIRECT_URL}
      OAUTH2_PROXY_UPSTREAMS: "static://200"

COOKIE_SECRET - 32 случайных байта в base64, сгенерировать можно командой openssl rand -base64 32. Значение REDIRECT_URL должно посимвольно совпадать с redirect_uri, зарегистрированным у IdP: схема, хост, порт, путь. Фрагмент nginx.conf:

location /oauth2/ {
    proxy_pass http://oauth2-proxy:4180;
    proxy_set_header Host $host;
    proxy_set_header X-Real-IP $remote_addr;
    proxy_set_header X-Scheme $scheme;
    proxy_set_header X-Auth-Request-Redirect $request_uri;
}

location = /oauth2/auth {
    proxy_pass http://oauth2-proxy:4180;
    proxy_set_header Host $host;
    proxy_set_header Content-Length "";
    proxy_pass_request_body off;
}

location /app/ {
    auth_request /oauth2/auth;
    error_page 401 = /oauth2/sign_in;

    auth_request_set $user $upstream_http_x_auth_request_user;
    auth_request_set $email $upstream_http_x_auth_request_email;

    proxy_set_header X-User $user;
    proxy_set_header X-Email $email;
    proxy_pass http://app-upstream;
}

Заголовки X-User и X-Email можно доверять, только если upstream недоступен напрямую. Если сервис открыт, заголовки подделает любой: закрывайте его сетевыми политиками или проверяйте подписанный внутренний токен.

Валидация JWT на API-шлюзе: Kong и Traefik

Второй подход не создает серверных сессий: шлюз проверяет подпись access token и пропускает запрос дальше. JWKS кэшируется, ключи обновляются по kid.

В Kong для проверки токенов включают плагин jwt с публичным ключом IdP; в Enterprise-версии есть плагин openid-connect, который сам разбирает discovery и JWKS. После валидации доступны ACL по claim groups и rate limiting по пользователю. В Traefik ту же задачу решает middleware ForwardAuth: каждый запрос уходит на endpoint проверки, заголовки ответа копируются в upstream.

http:
  middlewares:
    sso:
      forwardAuth:
        address: "http://oauth2-proxy.auth.svc.cluster.local:4180/oauth2/auth"
        trustForwardHeader: true
        authResponseHeaders:
          - X-Auth-Request-User
          - X-Auth-Request-Email

Плата за stateless-проверку: отдельный access token нельзя отозвать до истечения exp. Время жизни держат коротким (5-15 минут), а отзыв выполняют через refresh-сессии.

Настройка Keycloak и Dex как IdP для Kubernetes

kube-apiserver не хранит пользователей. Он доверяет OIDC-провайдеру: получает подписанный JWT, проверяет подпись по discovery-документу, извлекает из claims имя пользователя и группы, а RBAC превращает группы в права через ClusterRoleBinding и RoleBinding. Провайдер выбирают из трех вариантов: Keycloak (максимум функций, включая федерацию LDAP), Dex (легкий, конфигурируется одним файлом) и Azure AD (для организаций на Microsoft).

Обязательные флаги kube-apiserver для OIDC

  • --oidc-issuer-url: адрес провайдера, совпадает с claim iss посимвольно.
  • --oidc-client-id: значение claim aud.
  • --oidc-username-claim: claim с логином, по умолчанию sub. На практике удобнее preferred_username или email.
  • --oidc-username-prefix: префикс (например, oidc:), защищает от коллизий с системными пользователями.
  • --oidc-groups-claim: claim со списком групп.
  • --oidc-ca-file: CA для сертификата провайдера, если он самоподписанный.
  • --oidc-signing-algs: список допустимых алгоритмов подписи, по умолчанию RS256.

Фрагмент манифеста kube-apiserver (kubeadm хранит его в /etc/kubernetes/manifests/kube-apiserver.yaml). Замените <idp-хост> на хост вашего провайдера:

spec:
  containers:
    - command:
        - kube-apiserver
        - --oidc-issuer-url=https://<idp-хост>/realms/main
        - --oidc-client-id=kubernetes
        - --oidc-username-claim=preferred_username
        - --oidc-username-prefix=oidc:
        - --oidc-groups-claim=groups
        - --oidc-groups-prefix=oidc:
        - --oidc-ca-file=/etc/kubernetes/pki/idp-ca.crt

Проверка входа: kubectl --token="$ID_TOKEN" get pods. Ответ 401 означает, что токен не прошел валидацию: чаще всего не совпал issuer или не подтянулся CA.

Настройка Keycloak с LDAP/Active Directory

Связка с каталогом настраивается через User Federation: User Federation → Add provider → LDAP. Ключевые параметры:

  • Connection URL: адрес контроллера домена, порт 389 для LDAP или 636 для LDAPS.
  • Bind DN и Bind credentials: служебная учетная запись с правом чтения каталога.
  • Users DN: база поиска пользователей, например OU=Users,DC=company,DC=local.
  • Edit Mode: READ_ONLY, если пользователи живут только в AD.
  • Sync Settings: периодическая синхронизация, например раз в 30 минут, с импортом групп.

Группы попадают в токен через mapper: Mappers → New mapper → Group Membership, Token Claim Name = groups, Full group path = Off. Без маппера claim groups окажется пустым, и RBAC-привязки не сработают. Проверка: Clients → ваш клиент → Client scopes → Evaluate: выберите пользователя и посмотрите итоговый набор claims.

Связка групп из Keycloak с ролями Kubernetes и выбор между RBAC и ABAC разобраны в статье RBAC и ABAC в IAM-системах: как выбрать модель контроля доступа и настроить её в Keycloak, LDAP и Kubernetes.

Получение доступа к кластеру через kubelogin

Вручную копировать токены неудобно, поэтому вход выполняют через kubectl-плагин kubelogin (oidc-login). Плагин открывает браузер, пользователь аутентифицируется у IdP, плагин получает токены, кэширует refresh token и отдает kubectl свежий access token.

Настройка запускается так:

kubectl oidc-login setup \
  --oidc-issuer-url="$OIDC_ISSUER_URL" \
  --oidc-client-id=kubernetes

Утилита выведет фрагмент kubeconfig с exec-блоком. После подстановки в ~/.kube/config команда kubectl get nodes откроет браузер и пустит в кластер, если RBAC-привязки для групп уже созданы.

Шаблоны kubeconfig для kubelogin, ServiceAccount для CI/CD и типовые ошибки входа собраны в руководстве Интеграция IAM с Kubernetes: настройка безопасного доступа к кластеру и управление сервисными аккаунтами.

Типичные ошибки при настройке SSO и их решение

Большинство сбоев SSO сводится к трем классам: аутентификация (401), авторизация (403) и инфраструктура (сертификаты, DNS, часы). Если вход перестал работать после обновления IdP, библиотеки или конфигурации, начните с чек-листа в статье Ошибка аутентификации после обновления: что проверить в первую очередь.

Ошибки аутентификации: 401 и issuer URL

Симптомы: 401 Unauthorized, Invalid bearer token, The token is not yet valid. Причины и проверки:

  • Issuer не совпадает: сверьте --oidc-issuer-url и claim iss посимвольно. Лишний или отсутствующий завершающий слэш ломает валидацию.
  • Подпись не проходит: JWKS не обновился после ротации ключей, kid токена отсутствует в наборе.
  • Самоподписанный IdP: в kube-apiserver укажите --oidc-ca-file, на шлюзах добавьте CA в trust store.
  • Рассинхрон часов: exp и nbf проверяются строго, запас 60 секунд. NTP или chrony должны работать на всех узлах.
  • Токен выпущен не тем клиентом: claim aud не совпадает с client_id из конфигурации.

Диагностика: декодируйте токен и сравните iss, aud, exp с конфигурацией, затем смотрите логи сервиса, который отверг запрос. Разбор ошибок 401 и 403 при обращении к API, включая Bearer-токены, API-ключи и scopes, собран в статье Ошибка аутентификации при обращении к API: токены, ключи и OAuth 2.0.

Ошибки авторизации: 403 и claims

403 Forbidden означает: токен валиден, но прав нет. Проверяйте цепочку claims → привязки:

  • Claim username пустой или не тот: если задан --oidc-username-claim=preferred_username, а IdP кладет логин в другой claim, RBAC не найдет пользователя.
  • Группы не попали в токен: маппер Group Membership отсутствует или выключен. Сравните claim groups с ожидаемым списком.
  • Привязки не созданы: группа в токене есть, а ClusterRoleBinding или RoleBinding для нее отсутствует. Проверка: kubectl auth can-i get pods --as="oidc:ivanov" --as-group="oidc:developers".
  • Сдвиг префиксов: при --oidc-groups-prefix=oidc: в привязках указывайте oidc:developers, а не developers.

Проверка привязок: kubectl get clusterrolebinding -o yaml и поиск по subjects. Если группа есть в токене, но не в привязках, добавьте ее в subjects нужной роли.

Проблемы с сертификатами и сетевой доступностью

  • CA не обновлена после ротации сертификата IdP: apiserver или шлюз отвергает TLS-соединение. Держите --oidc-ca-file и trust store в актуальном состоянии.
  • DNS: kube-apiserver должен резолвить хост провайдера, браузер клиента - хост брокера. Расхождение внутренних и внешних зон дает картину «из кластера работает, снаружи нет».
  • Firewall: блокировка /token или /jwks ломает обмен кода и обновление ключей. Проверьте доступность из пода: curl -v "$ISSUER/.well-known/openid-configuration".
  • redirect_uri mismatch: IdP сравнивает строку целиком, включая схему, порт, путь и завершающий слэш.
  • CORS: прямой запрос из браузера к /token завершится ошибкой. Токен-эндпоинт не обязан отдавать CORS-заголовки, браузерные потоки проводите через BFF или прокси.

Безопасность SSO: ротация токенов, отзыв и аудит

Работающий SSO и безопасный SSO отличаются настройками времени жизни и логирования. Базовая линия: access token живет 5-15 минут, refresh token ротируется, входы и отказы пишутся в журнал, client_secret не лежит в git.

Ротация refresh-токенов и отзыв сессий

Refresh token rotation: каждый обмен возвращает новый refresh token, старый инвалидируется. Повторное использование старого токена провайдер трактует как кражу и разрывает всю цепочку сессий пользователя. В Keycloak это включается в настройках realm: Revoke Refresh Token = On, поле Refresh Token Max Reuse задает число переиспользований до отзыва.

Backchannel logout закрывает сценарий, когда пользователь вышел на одном сервисе, а на другом сессия осталась жива: провайдер отправляет logout_token на backchannel-эндпоинт клиента, и приложение гасит локальную сессию. В Keycloak у каждого клиента указывается Backchannel logout URL, а лимиты сессии задаются в Realm settings → Sessions: SSO Session Idle (например, 30 минут простоя) и SSO Session Max (например, 10 часов).

Аудит доступа и мониторинг аномалий

Что логировать: user, client_id, IP, user_agent, timestamp, результат (success или fail). Keycloak отдает события LOGIN, LOGIN_ERROR, LOGOUT и REFRESH_TOKEN через event listener, дальше их отправляют в ELK или Loki. Метрики для алертов: доля 401, число неуспешных входов на пользователя, всплеск входов из одной подсети.

Пример правила: больше 10 неуспешных входов с одного IP за 5 минут - временная блокировка и уведомление дежурного. Разбор больших объемов журналов ускоряют LLM-модели: агрегатор AiTunnel объединяет доступ к GPT, Gemini и Claude в одном API с оплатой в рублях, на его основе удобно собрать скрипт для анализа журналов входа.

Секреты (client_secret, cookie_secret) храните в Kubernetes Secret или Vault, доступ ограничивайте RBAC, ротацию планируйте заранее. Утечка cookie_secret равносильна возможности подделывать сессии oauth2-proxy.

SSO в контейнерах: Docker и Kubernetes

В Docker oauth2-proxy ставят отдельным контейнером рядом с приложением: приложение слушает только внутреннюю сеть, наружу смотрит прокси, который проверяет сессию. Для стенда хватает пары сервисов, для прода добавляются healthcheck, резервная реплика и сбор логов.

oauth2-proxy в Kubernetes: манифест и Ingress

В кластере oauth2-proxy разворачивается как Deployment из двух и более реплик. Секреты выносятся в Secret, параметры OIDC передаются переменными окружения:

apiVersion: apps/v1
kind: Deployment
metadata:
  name: oauth2-proxy
  namespace: auth
spec:
  replicas: 2
  selector:
    matchLabels:
      app: oauth2-proxy
  template:
    metadata:
      labels:
        app: oauth2-proxy
    spec:
      containers:
        - name: oauth2-proxy
          image: oauth2-proxy/oauth2-proxy:v7.6.0
          args:
            - --provider=oidc
            - --email-domain=*
            - --http-address=0.0.0.0:4180
            - --upstream=static://200
          env:
            - name: OAUTH2_PROXY_OIDC_ISSUER_URL
              valueFrom:
                secretKeyRef:
                  name: oauth2-proxy-secrets
                  key: issuer-url
            - name: OAUTH2_PROXY_CLIENT_ID
              valueFrom:
                secretKeyRef:
                  name: oauth2-proxy-secrets
                  key: client-id
            - name: OAUTH2_PROXY_CLIENT_SECRET
              valueFrom:
                secretKeyRef:
                  name: oauth2-proxy-secrets
                  key: client-secret
            - name: OAUTH2_PROXY_COOKIE_SECRET
              valueFrom:
                secretKeyRef:
                  name: oauth2-proxy-secrets
                  key: cookie-secret

Ingress NGINX подключает проверку аннотациями:

apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
  name: app
  annotations:
    nginx.ingress.kubernetes.io/auth-url: "http://oauth2-proxy.auth.svc.cluster.local:4180/oauth2/auth"
    nginx.ingress.kubernetes.io/auth-signin: "http://oauth2-proxy.auth.svc.cluster.local/oauth2/start?rd=$escaped_request_uri"
    nginx.ingress.kubernetes.io/auth-response-headers: "x-auth-request-user, x-auth-request-email"
spec:
  rules:
    - host: app.internal
      http:
        paths:
          - path: /
            pathType: Prefix
            backend:
              service:
                name: app
                port:
                  number: 80

Проверка: kubectl -n auth port-forward deploy/oauth2-proxy 4180:4180, затем откройте localhost:4180. Для тестового кластера подойдет managed Kubernetes в облаке: Timeweb Cloud дает кластеры, VDS и объектное хранилище с почасовой оплатой, минимальной конфигурации хватит для стенда.

Хранение client_secret: Secret, Vault, External Secrets

Kubernetes Secret кодирует значения в base64 и не шифрует их: включайте encryption at rest и ограничивайте доступ через RBAC. Для хранения в git подходят Sealed Secrets или SOPS. Для централизованного управления секретами используют External Secrets Operator, который тянет значения из Vault:

apiVersion: external-secrets.io/v1beta1
kind: ExternalSecret
metadata:
  name: oauth2-proxy-secrets
  namespace: auth
spec:
  refreshInterval: 1h
  secretStoreRef:
    name: vault-backend
    kind: ClusterSecretStore
  target:
    name: oauth2-proxy-secrets
  data:
    - secretKey: client-secret
      remoteRef:
        key: sso/oauth2-proxy
        property: client_secret

Ротация: сначала обновите client_secret у IdP и в Vault, затем перезапустите поды oauth2-proxy, чтобы они подхватили новое значение. Cookie secret меняйте отдельно: его ротация завершает сессии всех пользователей, поэтому планируйте замену на окно обслуживания.

Соберите тестовый контур целиком: Keycloak с тестовым realm, oauth2-proxy и приложение за Nginx. Прогоните сценарии успешного входа, истекшего access token (401), нехватки прав (403) и logout с backchannel-уведомлением. Конфигурацию, которая прошла эти проверки, можно переносить в production.

Поделиться:
Сохранить гайд? В закладки браузера