Стадия жизненного цикла модуля: General Availability

Как защитить мое приложение?

Чтобы включить аутентификацию через Dex для приложения, выполните следующие шаги:

  1. Создайте ресурс DexAuthenticator.

    При создании DexAuthenticator в кластере создаётся экземпляр oauth2-proxy, подключённый к Dex. В указанном неймспейсе будут созданы объекты Deployment, Service, Ingress, Secret.

    Пример ресурса DexAuthenticator:

    apiVersion: deckhouse.io/v1
    kind: DexAuthenticator
    metadata:
      # Префикс имени подов Dex authenticator.
      # Например, если префикс имени `app-name`, то поды Dex authenticator будут вида `app-name-dex-authenticator-7f698684c8-c5cjg`.
      name: app-name
      # Неймспейс, в котором будет развернут Dex authenticator.
      namespace: app-ns
    spec:
      # Домен вашего приложения. Запросы на него будут перенаправляться для прохождения аутентификации в Dex.
      applicationDomain: "app-name.kube.my-domain.com"
      # Отправлять ли заголовок `Authorization: Bearer` приложению. Полезно в связке с auth_request в NGINX.
      # При значении sendAuthorizationHeader: true добавьте заголовок Authorization в аннотацию nginx.ingress.kubernetes.io/auth-response-headers Ingress приложения или в аннотацию alb.network.deckhouse.io/auth-response-headers ресурса HTTPRoute.
      sendAuthorizationHeader: false
      # Имя секрета с SSL-сертификатом.
      applicationIngressCertificateSecretName: "ingress-tls"
      # Название Ingress-класса, которое будет использоваться в создаваемом для Dex authenticator Ingress-ресурсе.
      applicationIngressClassName: "nginx"
      # Время, на протяжении которого пользовательская сессия будет считаться активной.
      keepUsersLoggedInFor: "720h"
      # Список групп, пользователям которых разрешено проходить аутентификацию.
      allowedGroups:
      - everyone
      - admins
      # Список адресов и сетей, с которых разрешено проходить аутентификацию.
      whitelistSourceRanges:
      - 1.1.1.1/32
      - 192.168.0.0/24
    
  2. Подключите приложение к Dex.

    Для этого добавьте в ресурс, через который публикуется приложение аннотации. Набор аннотаций зависит от того, каким способом публикуется приложение. Выберите подходящий вариант:

  • Через Ingress-ресурс
  • Через ALBInstance или ClusterALBInstance

Добавьте в Ingress-ресурс приложения следующие аннотации:

  • nginx.ingress.kubernetes.io/auth-signin: https://$host/dex-authenticator/sign_in
  • nginx.ingress.kubernetes.io/auth-response-headers: X-Auth-Request-User,X-Auth-Request-Email
  • nginx.ingress.kubernetes.io/auth-url: https://<SERVICE_NAME>.<NS>.svc.{{ C_DOMAIN }}/dex-authenticator/auth, где:
    • SERVICE_NAME — имя сервиса (Service) аутентификатора. Как правило, оно соответствует формату <NAME>-dex-authenticator (<NAME> — это metadata.name ресурса DexAuthenticator);
    • NS — значение параметра metadata.namespace ресурса DexAuthenticator;
    • C_DOMAIN — домен кластера (параметр clusterDomain ресурса ClusterConfiguration).

Если имя DexAuthenticator (<NAME>) слишком длинное, имя сервиса (Service) может быть сокращено. Чтобы найти корректное имя сервиса, воспользуйтесь следующей командой (укажите имя неймспейса и аутентификатора):

d8 k get service -n <NS> -l "deckhouse.io/dex-authenticator-for=<NAME>" -o jsonpath='{.items[0].metadata.name}'

Пример аннотаций Ingress-ресурса приложения для подключения к Dex:

annotations:
  nginx.ingress.kubernetes.io/auth-signin: https://$host/dex-authenticator/sign_in
  nginx.ingress.kubernetes.io/auth-url: https://app-name-dex-authenticator.app-ns.svc.cluster.local/dex-authenticator/auth
  nginx.ingress.kubernetes.io/auth-response-headers: X-Auth-Request-User,X-Auth-Request-Email

Для приложения, в котором указан только applicationIngressClassName, HTTPRoute не создаётся. Если публикация через Ingress отключена параметром global.modules.ingress.enabled, не создаётся и Ingress. Само приложение остаётся доступным через тот маршрут, которым вы его публикуете, а вот его эндпоинты /dex-authenticator — нет: редирект на вход попадает в само приложение, и войти невозможно. На остальные приложения это не влияет.

Если приложение публикуется через ресурс ALBInstance или ClusterALBInstance (подробнее — в документации модуля alb), добавьте в HTTPRoute-ресурс приложения следующие аннотации:

  • alb.network.deckhouse.io/auth-signin: https://<домен-приложения>/dex-authenticator/sign_in — в отличие от nginx, контроллер alb не поддерживает переменную $host, поэтому домен приложения нужно указать явно;
  • alb.network.deckhouse.io/auth-response-headers: X-Auth-Request-User,X-Auth-Request-Email;
  • alb.network.deckhouse.io/auth-url: https://<SERVICE_NAME>.<NS>.svc.<C_DOMAIN>/dex-authenticator/auth, где SERVICE_NAME, NS и C_DOMAIN определяются так же, как для Ingress-ресурса.

Пример аннотаций ресурса HTTPRoute для подключения приложения к Dex:

annotations:
  alb.network.deckhouse.io/auth-signin: https://app-name.kube.my-domain.com/dex-authenticator/sign_in
  alb.network.deckhouse.io/auth-url: https://app-name-dex-authenticator.app-ns.svc.cluster.local/dex-authenticator/auth
  alb.network.deckhouse.io/auth-response-headers: X-Auth-Request-User,X-Auth-Request-Email

Также укажите в ресурсе DexAuthenticator тот же ListenerSet, через который опубликован домен приложения:

spec:
  gatewayAPI:
    applicationHTTPRouteListenerSetName: my-listenerset

Создаваемый для DexAuthenticator HTTPRoute подключается к этому же ListenerSet, поэтому ListenerSet должен принимать маршруты из неймспейса, в котором находится DexAuthenticator.

Для приложения, в котором указан только gatewayAPI, Ingress не создаётся. Если Gateway API отключён параметром global.modules.gatewayAPI.enabled либо в кластере нет API HTTPRoute, не создаётся и HTTPRoute — с тем же результатом: приложение остаётся доступным, его эндпоинты /dex-authenticator — нет, войти невозможно. На остальные приложения это не влияет.

При включении sendAuthorizationHeader: true в Ingress (или в HTTPRoute, если используется модуль alb) укажите все необходимые заголовки в соответствующей аннотации, поскольку заголовок Authorization по умолчанию не передаётся:

Подробнее о том, что передаётся в заголовке Authorization и как указать его в аннотации, читайте в разделе «Как передать приложению логин и группы пользователя».

Ingress приложения должен иметь настроенный TLS. DexAuthenticator не поддерживает Ingress-ресурсы, работающие только по HTTP.

Настройка ограничений на основе CIDR

В DexAuthenticator нет встроенной системы управления разрешением аутентификации на основе IP-адреса пользователя. Вместо этого вы можете воспользоваться аннотациями для Ingress-ресурсов:

  • Если нужно ограничить доступ по IP и оставить прохождение аутентификации в Dex, добавьте аннотацию с указанием разрешенных CIDR через запятую:

    nginx.ingress.kubernetes.io/whitelist-source-range: 192.168.0.0/32,1.1.1.1
    
  • Чтобы разрешить доступ без аутентификации в Dex для пользователей из указанных сетей, а для остальных оставить обязательную аутентификацию, добавьте аннотацию:

    nginx.ingress.kubernetes.io/satisfy: "any"
    

Как передать приложению логин и группы пользователя?

По умолчанию DexAuthenticator передаёт приложению только два заголовка: X-Auth-Request-User (значение основано на непрозрачном claim’е sub) и X-Auth-Request-Email. Заголовок с группами пользователя не передаётся. Он неограниченно растёт при большом количестве групп, поэтому в DP отключён, и включить его нельзя.

Чтобы приложение получило полную информацию о пользователе, включая группы, включите параметр sendAuthorizationHeader:

apiVersion: deckhouse.io/v1
kind: DexAuthenticator
metadata:
  name: app-name
  namespace: app-ns
spec:
  applicationDomain: "app-name.kube.my-domain.com"
  applicationIngressClassName: "nginx"
  applicationIngressCertificateSecretName: "ingress-tls"
  sendAuthorizationHeader: true

В этом случае приложению передаётся заголовок Authorization: Bearer <id_token>, где <id_token> — подписанный Dex JWT (подробнее о содержимом токена и его обработке в приложении — в разделе «Содержимое токена JWT и особенности его обработки»).

Независимо от того, каким способом публикуется приложение, этот заголовок не передается в приложение автоматически. Его нужно явно указать в списке передаваемых с помощью аннотации ресурса, через который публикуется приложение. Выберите подходящий вариант, в зависимости от того, каким способом публикуется приложение:

  • Через Ingress-ресурс
  • Через ALBInstance или ClusterALBInstance

Если приложение публикуется через Ingress-ресурс (подробнее — в документации модуля ingress-nginx), при включении sendAuthorizationHeader: true необходимо:

  • указать заголовки, которые нужно передавать в приложение, в аннотации nginx.ingress.kubernetes.io/auth-response-headers;
  • увеличить размер буфера с помощью аннотации nginx.ingress.kubernetes.io/proxy-buffer-size, поскольку JWT с большим числом групп не помещается в буфер по умолчанию.

Пример указания заголовков и размера буфера с помощью соответствующих аннотаций:

annotations:
  nginx.ingress.kubernetes.io/auth-signin: https://$host/dex-authenticator/sign_in
  nginx.ingress.kubernetes.io/auth-url: https://app-name-dex-authenticator.app-ns.svc.cluster.local/dex-authenticator/auth
  nginx.ingress.kubernetes.io/auth-response-headers: X-Auth-Request-User,X-Auth-Request-Email,Authorization
  nginx.ingress.kubernetes.io/proxy-buffer-size: 32k

Если заголовок Authorization не указан в auth-response-headers, приложение его не получит. Если не увеличить proxy-buffer-size, запросы будут завершаться ошибкой 500, а в логах контроллера Ingress появится сообщение upstream sent too big header while reading response header from upstream.

Если приложение публикуется через ресурс ALBInstance или ClusterALBInstance (подробнее — в документации модуля alb), при включении sendAuthorizationHeader: true укажите заголовки, которые нужно передавать в приложение, в аннотации alb.network.deckhouse.io/auth-response-headers ресурса HTTPRoute:

annotations:
  alb.network.deckhouse.io/auth-signin: https://app-name.kube.my-domain.com/dex-authenticator/sign_in
  alb.network.deckhouse.io/auth-url: https://app-name-dex-authenticator.app-ns.svc.cluster.local/dex-authenticator/auth
  alb.network.deckhouse.io/auth-response-headers: Authorization

В аннотации alb.network.deckhouse.io/auth-response-headers, достаточно указать только Authorization, так как она уже передаваемый по умолчанию базовый набор заголовков.

Также в ресурсе DexAuthenticator укажите тот же ListenerSet, через который опубликован домен приложения:

spec:
  gatewayAPI:
    applicationHTTPRouteListenerSetName: my-listenerset

Содержимое токена JWT и особенности его обработки

Пример полезной нагрузки JWT для статического пользователя (ресурсы User и Group):

{
  "iss": "https://dex.kube.my-domain.com/",
  "sub": "Cg1qb2huLmRvZUBleGFtcGxlEgVsb2NhbA",
  "aud": "app-name-app-ns-dex-authenticator",
  "exp": 1757000600,
  "iat": 1757000000,
  "email": "john.doe@example.com",
  "email_verified": true,
  "name": "john-doe",
  "preferred_username": "",
  "groups": ["everyone", "developers"]
}

При использовании данных из токена в приложении обращайте внимание на следующее:

  • Для идентификации пользователя используйте поле email. Поле sub непрозрачно (не является предсказуемым и не может быть использовано как осмысленный идентификатор пользователя вне контекста конкретной системы), а preferred_username для статических пользователей пуст (внешние провайдеры аутентификации могут его заполнять).
  • Поле name содержит имя объекта (из поля metadata.name объекта User), а не отображаемое имя пользователя.
  • Поле aud содержит идентификатор клиента аутентификатора (<name>-<namespace>-dex-authenticator), а не идентификатор OIDC-клиента вашего приложения. Приложение, проверяющее aud по собственному client_id, отклонит такой токен.
  • Подпись проверяйте по JWKS https://dex.<modules.publicDomainTemplate>/keys.
  • Время жизни токена определяется параметром idTokenTTL (по умолчанию 10 минут). DexAuthenticator обновляет токен самостоятельно, поэтому приложение всегда получает актуальный токен.

Если приложение поддерживает OIDC самостоятельно, вместо DexAuthenticator используйте ресурс DexClient: приложение само запросит необходимый объём полномочий и получит refresh_token в дополнение к id_token.

Как работает аутентификация с помощью DexAuthenticator

Как работает аутентификация с помощью DexAuthenticator

DexAuthenticator работает только по HTTPS. Ingress-ресурсы, настроенные только на HTTP, не поддерживаются.

Аутентификационные cookie устанавливаются с атрибутом Secure, что означает их передачу только через зашифрованные HTTPS-соединения.

Убедитесь, что для Ingress вашего приложения настроен TLS, прежде чем интегрировать его с DexAuthenticator.

  1. Dex в большинстве случаев перенаправляет пользователя на страницу входа провайдера и ожидает, что пользователь будет перенаправлен на его /callback URL. Однако такие провайдеры, как LDAP или Atlassian Crowd, не поддерживают этот вариант. Вместо этого пользователь должен ввести логин и пароль в форму входа в Dex, и Dex сам проверит учётные данные, выполнив запрос к API провайдера.

  2. DexAuthenticator устанавливает cookie с полным токеном обновления (вместо выдачи тикета, как для ID-токена), потому что Redis не сохраняет данные на диск. Если по тикету в Redis не найден ID-токен, пользователь сможет запросить новый ID-токен, предоставив токен обновления из cookie.

  3. DexAuthenticator выставляет HTTP-заголовок Authorization, равный значению ID-токена из Redis. Это необязательно для сервисов вроде upmeter, так как права доступа к upmeter менее детальные. Для Kubernetes Dashboard это критичная функциональность: Dashboard передаёт ID-токен дальше для доступа к API Kubernetes.

Как сгенерировать kubeconfig для доступа к Kubernetes API?

Для генерации kubeconfig воспользуйтесь разделом «Как сгенерировать kubeconfig для доступа к Kubernetes API?» документации модуля control-plane-manager.

Настройка kube-apiserver

С помощью функций модуля control-plane-manager DP автоматически настраивает kube-apiserver, выставляя следующие флаги так, чтобы в кластере работала аутентификация через OIDC.

Аргументы kube-apiserver, которые будут настроены

  • --oidc-client-id=kubernetes
  • --oidc-groups-claim=groups
  • --oidc-issuer-url=https://dex.%addonsPublicDomainTemplate%/
  • --oidc-username-claim=email

При использовании самоподписанных сертификатов для Dex добавляется ещё один аргумент, а в под apiserver монтируется файл CA:

  • --oidc-ca-file=/etc/kubernetes/oidc-ca.crt

Как работает подключение к Kubernetes API с помощью сгенерированного kubeconfig

Схема взаимодействия при подключении к Kubernetes API с помощью сгенерированного kubeconfig

  1. До начала работы kube-apiserver необходимо запросить конфигурационный эндпоинт OIDC провайдера (в нашем случае — Dex), чтобы получить issuer и настройки JWKS-эндпоинта.

  2. Kubeconfig generator сохраняет ID-токен и Refresh-токен в файл kubeconfig.

  3. После получения запроса с ID-токеном kube-apiserver проверяет, что токен подписан провайдером, настроенным на первом шаге, с помощью ключей, полученных с JWKS-эндпоинта. Затем сравнивает значения claim iss и aud из токена со значениями из конфигурации.

Как сменить секрет OAuth2-клиента kubernetes?

Секрет привилегированного OAuth2-клиента kubernetes хранится в Secret kubernetes-dex-client-app-secret в неймспейсе d8-user-authn. То же значение используют OAuth2-клиенты kubeconfig-generator, kubeconfig-publish-api и kubeconfig-<slug>, а также компонент basic-auth-proxy, которому секрет передаётся с помощью параметра --ldap-client-secret.

Удаление Secret не приводит к смене секрета: пока значение сохраняется во внутренних параметрах модуля, хук восстановит Secret с прежним значением.

Чтобы сменить секрет, выполните следующие действия:

  1. Если неймспейс d8-user-authn управляется с помощью GitOps-инструмента, исключите Secret kubernetes-dex-client-app-secret из синхронизации. Иначе GitOps-инструмент восстановит прежнее значение.

  2. Очистите поле secret:

    d8 k -n d8-user-authn patch secret kubernetes-dex-client-app-secret --type merge -p '{"data":{"secret":""}}'
    
  3. Перезапустите DP, чтобы хук зарегистрировал пустое поле и сгенерировал новый секрет:

    d8 k -n d8-system rollout restart deployment/deckhouse
    
  4. Убедитесь, что значение секрета изменилось:

    d8 k -n d8-user-authn get secret kubernetes-dex-client-app-secret -o jsonpath='{.data.secret}'
    

    Если значение не изменилось, повторите шаги 2 и 3. Модуль мог восстановить прежнее значение до перезапуска DP.

После смены секрета конфигурация использующих его компонентов в кластере обновится автоматически, а их поды будут перезапущены.

Ранее загруженные файлы kubeconfig содержат прежний клиентский секрет и больше не смогут обновлять токены. Скачайте такие файлы заново. Уже выданные ID-токены продолжат действовать до истечения их срока действия, определяемого параметром settings.idTokenTTL (по умолчанию — 10 минут).

Как включить SSO по Kerberos (SPNEGO) для LDAP?

Если на стороне клиента настроено доменное SSO (браузер доверяет домену Dex), Dex может принимать Kerberos‑билеты по заголовку Authorization: Negotiate и выполнять аутентификацию без отображения формы ввода логина/пароля.

Включение SSO по Kerberos (SPNEGO) для LDAP:

  1. В инфраструктуре клиента должен быть задан SPN HTTP/<fqdn-dex> для сервисного аккаунта и сгенерирован keytab.
  2. В кластере создайте секрет в неймспейсе d8-user-authn с ключом krb5.keytab.
  3. В ресурсе DexProvider (тип LDAP) включите блок spec.ldap.kerberos и настройте в нём параметры:
    • enabled: true;
    • keytabSecretName: <имя секрета>;
    • опционально: expectedRealm, usernameFromPrincipal, fallbackToPassword.

Dex автоматически смонтирует keytab и начнёт принимать SPNEGO. krb5.conf на сервере не обязателен — билеты проверяются по keytab.

Как настроить базовую аутентификацию для доступа к Kubernetes API через LDAP?

Используйте настройки модуля control-plane-manager:

  1. Включите параметр apiserver.publishAPI в конфигурации модуля control-plane-manager.
  2. Создайте ресурс DexProvider типа LDAP и установите параметр enableBasicAuth: true.
  3. Настройте RBAC для групп, получаемых из LDAP.
  4. Передайте пользователям kubeconfig с настроенными параметрами базовой аутентификации (логин и пароль LDAP).

В кластере может быть только один провайдер аутентификации со включенным параметром enableBasicAuth.

Подробный пример описан в разделе Примеры конфигурации.

Как Dex защищен от подбора логина и пароля?

Каждому пользователю разрешено не более 20 попыток входа. После исчерпания лимита одна дополнительная попытка добавляется каждые 6 секунд.

UserOperation в статусе Failed — что делать?

Проверьте поле status.message ресурса UserOperation, чтобы узнать описание ошибки:

d8 k get useroperation <имя> -o jsonpath='{.status.message}'

Устраните причину (например, неверный хеш пароля или несуществующий пользователь), затем создайте новый UserOperation. UserOperation неизменяем — его спецификацию нельзя изменить после создания.

Как разблокировать пользователя?

Используйте команду:

d8 iam user unlock <имя>

Либо создайте новый ресурс UserOperation с type: Unlock. Учтите, что операции ResetPassword, Reset2FA и Lock завершают все активные сессии пользователя.

Пользователь заблокирован автоматически — почему?

Количество неудачных попыток входа превысило passwordPolicy.lockout.maxAttempts. Пользователь блокируется на время, указанное в passwordPolicy.lockout.lockDuration, после чего разблокируется автоматически. Администратор может также разблокировать пользователя вручную командой d8 iam user unlock <имя> или создав UserOperation с type: Unlock.

Можно ли отменить операцию UserOperation?

Нет. UserOperation — одноразовый неизменяемый объект. Чтобы отменить эффект операции, нужно создать обратную — например, создать UserOperation с type: Unlock после операции Lock.