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.0 | OpenID Connect |
|---|---|---|
| Цель | Доступ к ресурсам | Идентификация пользователя |
| Основной токен | Access token | ID token (JWT) |
| Кто использует токен | Resource server (API) | Client (приложение) |
| Информация о пользователе | Scope | Claims в 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 не используют: он все равно окажется в бандле.
Шаги потока:
- Клиент генерирует code_verifier (случайная строка 43-128 символов) и code_challenge = BASE64URL(SHA256(code_verifier)).
- Редирект на /authorize с параметрами response_type=code, client_id, redirect_uri, scope, state, nonce, code_challenge и code_challenge_method=S256.
- Пользователь аутентифицируется у IdP, при необходимости проходит MFA.
- IdP возвращает code на redirect_uri. Клиент сверяет state с сохраненным значением.
- Клиент отправляет code и code_verifier на /token и получает access token, ID token и refresh token.
- Клиент валидирует 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 приведет к чужой сессии. Чек-лист:
- Получить публичные ключи по jwks_uri из discovery-документа, кэшировать с обновлением по kid.
- Проверить подпись (RS256, реже ES256). Алгоритм брать из заранее заданного списка, а не из заголовка токена: поле alg контролирует отправитель.
- Сверить iss с issuer из discovery посимвольно, включая завершающий слэш.
- Сверить aud с client_id приложения.
- Проверить exp и iat с запасом 60 секунд на рассинхрон часов.
- Сверить 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
- Добавить в настройках клиента IdP новый redirect_uri для потока с кодом. Старый адрес оставить на период миграции.
- Развернуть BFF (Backend for Frontend) или oauth2-proxy. Браузер хранит только httpOnly-cookie сессии, токены остаются на сервере.
- Перевести фронтенд на новую схему: редирект на /oauth2/start, профиль через /oauth2/userinfo.
- Отключить Implicit у провайдера: в Keycloak это Clients → Capability config → выключить Implicit flow.
- Дождаться истечения старых токенов или отозвать их на стороне 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.