Общие сведения
Есть N-ое количество динамических и статических окружений для frontend-приложения в кубере. Задача:
- Использовать один oauth2-proxy на поддомене oauth2.example.ru для SSO всех фронтенд-приложений на *.example.ru.
- Провайдер OIDC — Keycloak.
- Единая сессия во всех поддоменах за счет доменного cookie (.example.ru).
- На стороне приложения ожидается кастомный заголовок с email пользователя после авторизации, получаемый из JWT.
Как это будет работать:
oauth2-proxy — это промежуточный сервис, который проверяет, кто вы такой, используя Keycloak, и затем передает приложениям информацию о пользователе (в заголовках) или пускает/не пускает на ресурс. Сессия хранится в зашифрованных куках.
- Пользователь открывает app1.example.ru. Nginx Ingress Controller перенаправляет запрос для проверки, есть ли у пользователя активная сессия, на oauth2-прокси к oauth2.example.ru (обычно через /oauth2/auth).
- Если сессии нет, прокси редиректит пользователя в Keycloak для логина.
- Keycloak выполняет редирект на oauth2.example.ru/oauth2/callback, отдавая прокси специальнцый код.
- oauth2-proxy обменивает этот код в кейклоке на JWT-токены и создает зашифрованную сессию (cookie) и редиректит обратно на исходный адрес, т.к. app1.example.ru.
- Nginx снова проверяет авторизацию, /oauth2/auth возвращает 200 и Ingress пропускает запрос в приложение, добавляя заголовки с данными пользователя.
- Далее приложение будет смотреть передаваемые заголовки и оперировать уже ими по своей логике.
- После успешного логина oauth2-proxy устанавливает зашифрованное cookie для домена .example.ru. Это cookie будет автоматически работать и на app2.example.ru, app3.example.ru и т.д. — получается SSO.
- Заголовки с данными пользователя (X-Auth-Request-User, X-Auth-Request-Email и т.п.) доступны бэкендам.
Как это настроить
Описанное подходит для приложений, задеплоенных в kubernetes и опубликованных через Nginx Ingress Controller.
Минимальные параметры для values.yaml хельм-чарта oauth2-proxy:
global:
security:
allowInsecureImages: true
proxyVarsAsSecrets: true
extraArgs:
client-id: "your_client"
provider: "oidc"
oidc-issuer-url: "https://keycloak.example.ru/auth/realms/REALM"
# redirect-url: "" # не указываем
scope: "openid email"
oidc-groups-claim: "email"
email-domain: "*"
pass-user-headers: true
insecure-oidc-allow-unverified-email: true
code-challenge-method: "S256" # или используйте client_secret
set-xauthrequest: true
reverse-proxy: true
request-logging: true
auth-logging: true
show-debug-on-error: true
standard-logging: true
prompt: login
cookie-domain: .example.ru
whitelist-domain: .example.ru
cookie-secure: true
cookie-samesite: lax
ingress:
enabled: true
className: "nginx"
pathType: ImplementationSpecific
path: /oauth
Информация про некоторые важные параметры:
- redirect-url не нужно указывать, но на стороне кейлока должны быть разрешены любые редиректы (wildcard *) или явно прописан нужный список
- cookie domain и whitelist domain – необходимо указать с точкой домен, на котором будут публиковаться приложения, эти хосты будут передаваться в куки и редирект урлы
- scope – информация, получаемая из токена, тут всё индивидуально и как настроите, её можно передавать в заголовки
- pass-user-headers и set-xauthrequest – потребуется, если в приложении нужны конкретные заголовки, например email из токена
- reverse-proxy сообщает oauth2-proxy, что можно доверять X-Forwarded-* заголовкам от ingress. Нужно для корректных внешних URL и редиректов
- prompt=login: заставляет провайдера (кейклок в данном случае) аутентификации игнорировать существующую сессию SSO и показать форму входа — т.е. принудительная повторная аутентификация.
- code-challenge-method: “S256” – метод “подключения” в кейклок без client_secret, когда клиент является публичный и обмен кодами\токенами происходит в зашифрованном виде, не требуется secret.
Настройка на уровне аннотаций манифеста Ingress:
nginx.ingress.kubernetes.io/auth-response-headers: x-auth-request-user, x-auth-request-email
nginx.ingress.kubernetes.io/auth-signin: >-
https://oauth2.example.ru/oauth2/start?rd=$scheme://$host$escaped_request_uri
nginx.ingress.kubernetes.io/auth-url: https://oauth2.example.ru/oauth2/auth
nginx.ingress.kubernetes.io/configuration-snippet: >-
auth_request_set $email $upstream_http_x_auth_request_email;
auth_request_set $user $upstream_http_x_auth_request_user;
auth_request_set $username
$upstream_http_x_auth_request_preferred_username; proxy_set_header
X-Forwarded-Email $email; proxy_set_header X-Forwarded-User $user;
proxy_set_header X-Forwarded-Preferred-Username $username;
if ($request_uri ~* "^/oauth2/sign_out/?$") {
return 302 https://oauth2.example.ru/oauth2/sign_out?rd=$scheme://$host/;
}
Пояснения по конфигурации:
- auth-url – nginx обращается сюда, чтобы узнать, авторизован ли текущий запрос. oauth2-proxy на /oauth2/auth должен отвечать 2xx при успешной авторизации и 401/302/403 при неуспехе. Важно: этот endpoint используется как субзапрос, он не делает редирект для браузера.
- auth-signin: URL, на который nginx будет редиректить запрос пользователя на прокси, если auth-url сообщает, что пользователь не авторизован. Важно, что в параметрах указан rd=$scheme://$host$escaped_request_uri, благодаря которому прокси отправит пользователя обратно по исходному адресу, т.е. app1.example.ru
- В configuration-snippet прописываются юзерские хедеры, которые нужны приложению (тут уже индивидуальный случай)
Отдельным важным моментом стоит отметить логаут: если пользователь в приложении после того, как успешно залогинился, захочет разлогиниться и нажмёт на соотв. кнопку, ведующую на /oauth2/sign_out, то скорее всего приложение отдаст 404, т.к. пользователь зашел на app1.example.ru, а разлогин должен вести на oauth2.example.ru, который просто очищает локальную сессию (cookie).
Именно для этого и сделан 302 redirect на https://oauth2.example.ru/oauth2/sign_out?rd=$scheme://$host/, и опять же с параметрами для последующего редиректа обратно на главную страницу приложения после удаления кук.
Заключение
Nginx Ingress Controller позволяет отдать конфигурацию SSO на аутсорс от приложений с бизнес-логикой на отдельный oauth2-proxy, который можно применять как для фронта, так и для бэка, выполняя нужные настройки по передаче пользовательской информации из JWT, необходимой для корректной приложений.
Удобным и частным случаем, описанным в этой заметке, является то, что oauth2-proxy может работать в единственном экземпляре для множества приложений на одном поддомене, выполняя для них сквозную авторизацию.