OIDC авторизация в Kubernetes: oauth2-proxy для нескольких приложений

Общие сведения

Есть 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/, и опять же с параметрами для последующего редиректа обратно на главную страницу приложения после удаления кук.

Для полного выхода из Keycloak используйте end_session, иначе активная SSO-сессия может “впустить” обратно, т.е. после нажатия на кнопку выхода ничего не произойдет. Параметр prompt=login в oauth2-proxy управляет тем, требовать ли повторный ввод пароля после выхода и удаления куки при следующем входе.

Заключение

Nginx Ingress Controller позволяет отдать конфигурацию SSO на аутсорс от приложений с бизнес-логикой на отдельный oauth2-proxy, который можно применять как для фронта, так и для бэка, выполняя нужные настройки по передаче пользовательской информации из JWT, необходимой для корректной приложений.

Удобным и частным случаем, описанным в этой заметке, является то, что oauth2-proxy может работать в единственном экземпляре для множества приложений на одном поддомене, выполняя для них сквозную авторизацию.

Понравилась статья? Поделиться с друзьями:
Добавить комментарий

;-) :| :x :twisted: :smile: :shock: :sad: :roll: :razz: :oops: :o :mrgreen: :lol: :idea: :grin: :evil: :cry: :cool: :arrow: :???: :?: :!: