Конфигурация OmniAuth

Deckhouse Code поддерживает вход через внешних провайдеров аутентификации (OmniAuth), в том числе через OpenID Connect (OIDC) и SAML. Ниже описаны общие параметры OmniAuth, параметры провайдеров и дополнительные возможности Deckhouse Code.

Поддерживаемые провайдеры

В параметре name записи провайдера указывается одно из следующих значений:

  • openid_connect — OpenID Connect (описан ниже);
  • saml — SAML (описан ниже);
  • oauth2_generic — произвольный провайдер OAuth 2.0;
  • jwt — аутентификация по JWT;
  • github — GitHub;
  • gitlab — GitLab.com;
  • google_oauth2 — Google;
  • azure_activedirectory_v2 — Microsoft Entra ID (Azure AD);
  • atlassian_oauth2 — Atlassian;
  • crowd — Atlassian Crowd;
  • auth0 — Auth0;
  • alicloud — AliCloud;
  • salesforce — Salesforce;
  • shibboleth — Shibboleth.

Вход через LDAP настраивается отдельно, в секции spec.appConfig.ldap. (подробнее — в разделе «Синхронизация с LDAP»).

Общие параметры OmniAuth

Параметры задаются в секции spec.appConfig.omniauth.:

  • enabled — разрешает вход через внешних провайдеров. По умолчанию — true.
  • providers — список провайдеров, через которых разрешён вход. По умолчанию — [].
  • allow_single_sign_on — список провайдеров, для которых при первом входе автоматически создаётся учётная запись (например, ['openid_connect']). Также принимает значения true (все провайдеры) и false. Если автоматическое создание отключено, пользователь должен сначала получить учётную запись в Deckhouse Code, а затем связать её с провайдером. По умолчанию — false.
  • block_auto_created_users — если true, автоматически созданные учётные записи блокируются до одобрения администратором. По умолчанию — true.
  • auto_link_ldap_user — связывает учётную запись с учётной записью LDAP при первом входе (подробнее — в разделе «Связывание учётных записей OIDC с LDAP»). По умолчанию — false.
  • auto_link_user — связывает вход через провайдера с существующей учётной записью Deckhouse Code по адресу электронной почты. Принимает список провайдеров или значения true и false. По умолчанию — false.
  • auto_sign_in_with_provider — имя провайдера, на страницу входа которого пользователь перенаправляется автоматически, минуя страницу входа Deckhouse Code. По умолчанию — false.
  • external_providers — список провайдеров, учётные записи которых создаются как внешние. По умолчанию — [].
  • allow_bypass_two_factor — список провайдеров, вход через которых не требует двухфакторной аутентификации. Также принимает значения true и false. По умолчанию — false.
  • sync_profile_from_provider — список провайдеров, из данных которых обновляется профиль пользователя при каждом входе. Также принимает значения true и false. По умолчанию — false.
  • sync_profile_attributes — список атрибутов профиля, которые обновляются при синхронизации: name, email, location. Синхронизируемые атрибуты становятся доступными только для чтения. По умолчанию — ['email'].

OpenID Connect (OIDC)

Провайдеры перечисляются в параметре providers секции spec.appConfig.omniauth.. Для провайдера OIDC доступны следующие параметры:

  • name — тип провайдера. Для OIDC — всегда 'openid_connect'.
  • label — подпись кнопки входа. По умолчанию — 'Openid Connect'.
  • icon — адрес изображения, которое будет показано на кнопке входа.
  • args — параметры подключения к провайдеру:
    • name — имя стратегии OmniAuth, совпадает со значением параметра name провайдера;
    • scope — список запрашиваемых областей доступа, например ['openid', 'profile', 'email'];
    • response_type — тип ответа OAuth 2.0. Для потока Authorization Code — 'code';
    • issuer — адрес OIDC-провайдера;
    • discovery — если true, настройки провайдера запрашиваются автоматически по адресу <issuer>/.well-known/openid-configuration;
    • client_auth_method — способ аутентификации клиента на token endpoint: 'basic' или 'query';
    • uid_field — поле из данных пользователя, которое используется как uid учётной записи (например, preferred_username). Если параметр не задан или поле отсутствует, используется поле sub;
    • send_scope_to_token_endpoint — передавать ли параметр scope в запросах к token endpoint. Задайте false, если провайдер не принимает этот параметр. По умолчанию — true;
    • pkce — включает Proof Key for Code Exchange (PKCE);
    • client_options:
      • identifier — идентификатор клиента, зарегистрированного у провайдера;
      • secret — секрет клиента;
      • redirect_uri — адрес установки Deckhouse Code с путём /users/auth/openid_connect/callback. Тот же адрес должен быть указан в настройках клиента на стороне провайдера.

Дополнительно Deckhouse Code поддерживает следующие параметры. Они указываются на верхнем уровне записи провайдера, рядом с параметром name:

  • allowed_groups — список групп, пользователям которых разрешён вход. Пользователи вне этих групп будут заблокированы. По умолчанию — null (разрешены все группы).

  • admin_groups — список групп, пользователи которых получают административные права. По умолчанию — null (права администратора не выдаются ни одной группе).

  • auditor_groups — список групп, пользователи которых получают роль аудитора: доступ только на чтение ко всем группам и проектам, без доступа к административному разделу. По умолчанию — null (роль аудитора не выдаётся ни одной группе).

  • groups_attribute — имя атрибута, из которого извлекаются группы пользователя. По умолчанию — 'groups'.

Параметры admin_groups и auditor_groups учитываются, только если задан параметр allowed_groups. Если пользователь входит и в admin_groups, и в auditor_groups, ему назначаются права администратора.

Пример конфигурации OIDC

Настройка выполняется в секции spec.appConfig.omniauth.:

providers:
  - name: 'openid_connect'   # Не изменяйте это значение.
    label: 'Keycloak'        # Подпись кнопки входа.
    allowed_groups:
      - 'gitlab'
    admin_groups:
      - 'admin'
    auditor_groups:
      - 'audit'
    groups_attribute: 'gitlab_group'
    args:
      name: 'openid_connect'
      scope:
        - 'openid'
        - 'profile'
        - 'email'
      response_type: 'code'
      issuer: 'https://keycloak.example.com/realms/example'
      discovery: true
      client_auth_method: 'query'
      uid_field: 'preferred_username'
      send_scope_to_token_endpoint: false
      pkce: true
      client_options:
        identifier: '<client_id>'
        secret: '<client_secret>'
        redirect_uri: 'https://code.example.com/users/auth/openid_connect/callback'

SAML

Для провайдеров SAML доступны аналогичные параметры:

  • allowed_groups — список групп с разрешённым входом.
    По умолчанию — null (разрешены все группы).

  • admin_groups — группы с административными правами.
    По умолчанию — null (права администратора не выдаются ни одной группе).

  • auditor_groups — группы, пользователи которых получают роль аудитора: доступ только на чтение ко всем группам и проектам.
    По умолчанию — null (роль аудитора не выдаётся ни одной группе).

  • groups_attribute — имя атрибута, содержащего группы. По умолчанию — 'Groups'.

Пример конфигурации SAML

Настройка выполняется в секции spec.appConfig.omniauth.:

providers:
  - name: 'saml'
    allowed_groups:
      - 'gitlab'
    admin_groups:
      - 'admin'
    groups_attribute: 'gitlab_group'

Если пользователь входит в admin_groups, но не указан в allowed_groups, доступ будет запрещён. В этом случае административные права также не будут назначены.

Синхронизация с LDAP

Deckhouse Code поддерживает синхронизацию пользователей, групп и прав доступа с LDAP-сервером. Синхронизация выполняется автоматически раз в час, либо с заданной периодичностью.

Вы можете настроить периодичность синхронизации через параметр cronJobs в секции spec.appConfig.:

cron_jobs:
  ldap_sync_worker:
    cron: "0 * * * *"

Ограничения на стороне LDAP-сервера

Во время синхронизации выполняются LDAP-запросы ко всем пользователям и группам, указанным в конфигурации. При необходимости используется постраничная загрузка (pagination). Если на стороне LDAP установлены ограничения на число возвращаемых объектов, это может привести к ошибкам синхронизации или удалению прав доступа у пользователей.

Пример конфигурации LDAP-провайдера

Конфигурация размещается в spec.appConfig.ldap.:

main:
  label: ldap
  host: 127.0.0.1
  port: 3389
  bind_dn: 'uid=viewer,ou=People,dc=example,dc=com'
  base: 'ou=People,dc=example,dc=com'
  uid: 'cn'
  password: 'viewer123'
  sync_name: true
  group_sync: {
    create_groups: true,
    base: 'ou=Groups,dc=example,dc=org',
    filter: '(objectClass=groupOfNames)',
    prefix: {
      attribute: 'businessCategory',
      default: 'default-program',
    },
    top_level_group: "LdapGroups",
    name_mask: "(?<=-)[A-z0-9А-я]*$",
    owner: "root",
    role_mapping: [
      { by_name: '.*-project_manager-.*', gitlab_role: 'maintainer' },
      { by_name: '.*-developer-.*', gitlab_role: 'developer' },
      { by_name: '.*-participant-.*', gitlab_role: 'reporter' }
    ]
  }

Группы и права доступа

LDAP-группы сопоставляются с группами GitLab. При этом можно назначать роли пользователям на основе имени группы.

Обязательные параметры:

  • group_sync.base — DN, с которого начинается поиск LDAP-групп.

Опциональные параметры:

  • group_sync.create_groups — если true, группы будут создаваться в Deckhouse Code.
  • group_sync.filter — LDAP-фильтр для поиска групп.
  • group_sync.scope — область поиска групп (0 — Base, 1 — SingleLevel, 2 — WholeSubtree).
  • group_sync.prefix — определяет, из какого атрибута брать имя родительской группы. Если атрибут отсутствует — используется значение по умолчанию.
  • group_sync.top_level_group — имя группы верхнего уровня, в которую будут добавлены все синхронизированные группы.
  • group_sync.name_mask — регулярное выражение для извлечения имени группы из атрибута CN (Common Name).
  • group_sync.owner — имя пользователя, который будет добавлен как владелец группы (по умолчанию — root).

Секция role_mapping

Назначает права пользователям на основе имени группы (cn):

  • role_mapping.by_name — регулярное выражение; если имя группы совпадает, пользователю назначается соответствующая роль.
  • role_mapping.gitlab_role — название роли в Deckhouse Code (например: guest, reporter, developer, maintainer, owner).

Определение членов группы

LDAP Sync не поддерживает транзитивность для вложенных групп. Подробная информация в разделе «Вложенные группы и транзитивность».

Deckhouse Code поддерживает следующие атрибуты для определения членов группы (все значения — массив DN):

  • member;
  • uniquemember;
  • memberof;
  • memberuid;
  • submember.

Синхронизация пользователей

Во время синхронизации обновляются имена и email-адреса пользователей, а также статус блокировки.

Опциональные параметры:

sync_name — если true, имя пользователя будет обновлено по данным LDAP.

Блокировка пользователей по данным LDAP

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

Блокировка выполняется всегда и не требует дополнительных настроек: источником истины для статуса учётной записи остаётся LDAP. Вход заблокированного пользователя через OIDC-провайдера завершится отказом, даже если в самом провайдере пользователь активен.

Если в вашем каталоге пользователей не удаляют, а блокируют через атрибут, исключите заблокированных пользователей из выдачи LDAP с помощью параметра user_filter. Укажите один фильтр, соответствующий вашему каталогу:

user_filter: '(!(pwdAccountLockedTime=*))'                        # OpenLDAP ppolicy
user_filter: '(!(nsAccountLock=TRUE))'                            # 389-DS
user_filter: '(!(userAccountControl:1.2.840.113556.1.4.803:=2))'  # Active Directory
user_filter: '(!(employeeType=blocked))'                          # собственный атрибут

Связывание учётных записей OIDC с LDAP

Если пользователи входят через OIDC-провайдера (например, Keycloak), а права выдаются по LDAP-группам, включите автоматическое связывание OIDC-учётной записи с учётной записью LDAP. Настройка выполняется в секции spec.appConfig.omniauth.:

auto_link_ldap_user: true

Поиск учётной запись LDAP

При первом входе через OIDC Deckhouse Code ищет пользователя в LDAP. Для поиска используются два значения из данных OIDC-провайдера:

  • uid — значение поля, указанного в параметре uid_field провайдера (например, preferred_username);
  • email — адрес электронной почты пользователя.

Настроенные LDAP-серверы опрашиваются по очереди. На каждом сервере выполняется до четырёх попыток поиска, до первого совпадения:

Искомое значение Атрибут для поиска
uid Атрибут, указанный в параметре uid LDAP-сервера (например, cn)
uid Почтовые атрибуты: mail, email, userPrincipalName
email Те же почтовые атрибуты
uid DN — если значение uid само является DN

Если пользователь найден, к его учётной записи добавляется LDAP-идентификатор с найденным DN. Если ни одна попытка не дала результата, учётная запись создаётся без привязки к LDAP.

Таким образом, связывание сработает, только если uid или email из OIDC-провайдера совпадает со значением соответствующего атрибута в LDAP.

Первый и последующие входы

Первый успешный вход связывает учётную запись с LDAP. При последующих входах:

  • LDAP не опрашивается — используется ранее установленная связь;
  • доступ и административные права переоцениваются по группам из данных OIDC-провайдера (параметры allowed_groups и admin_groups). Если пользователя убрали из разрешённой группы, учётная запись будет заблокирована;
  • членства в группах и проектах, а также роли в них при входе через OIDC не меняются — их обновляет фоновая синхронизация с LDAP по расписанию.

Поэтому сразу после первого входа пользователь может войти, но членств в группах и проектах у него ещё нет: они появятся после ближайшей синхронизации. Чтобы не ждать её, запустите синхронизацию вручную (подробнее — в разделе «Ручной запуск синхронизации»).

Синхронизация групп и членств пользователя при входе выполняется только при входе через LDAP-провайдера (имя провайдера начинается с ldap). Вход через OIDC-провайдера её не запускает, даже если учётная запись уже связана с LDAP.

Вход пользователей, найденных в LDAP

Чтобы пользователи, найденные в LDAP, могли входить сразу, а остальные отправлялись на одобрение администратору, задайте следующие параметры в секции spec.appConfig.:

omniauth:
  auto_link_ldap_user: true
  # Пользователь не найден в LDAP — учётная запись создаётся заблокированной,
  # до одобрения администратором.
  block_auto_created_users: true
ldap:
  main:
    # Пользователь найден в LDAP — вход разрешён сразу.
    block_auto_created_users: false

Особенности связывания

  • Если пользователя блокируют переименованием cn в каталоге, при первом входе он всё равно может быть найден по email и связан с учётной записью LDAP. Для блокировки используйте удаление пользователя из каталога или параметр user_filter (подробнее — в разделе «Блокировка пользователей по данным LDAP»).
  • Если пользователь не был связан с LDAP и его заблокировала задача Ldap::BlockNonLdapUsersWorker, автоматическая разблокировка не сработает. Такого пользователя нужно разблокировать вручную и связать с учётной записью LDAP.

Устранение проблем с синхронизацией

Если предыдущее задание синхронизации завершилось некорректно, Redis может сохранить блокировку на его выполнение (по умолчанию параметр concurrency = 1). Это помешает запуску нового задания.

Чтобы снять блокировку:

  1. Подключитесь к Redis, используя базы, указанные в config/redis.shared_state.yml и config/redis.queues.yml.
  2. Удалите ключ sidekiq:concurrency_limit:throttled_jobs:{ldap/sync_worker} следующими командами:

    keys *ldap*
    del "sidekiq:concurrency_limit:throttled_jobs:{ldap/sync_worker}"
    

Ручной запуск синхронизации

Чтобы синхронизировать группы сразу после их изменения на стороне LDAP, выполните следующие шаги:

  1. Зайдите на страницу worker’а синхронизации LDAP /admin/sidekiq/cron/namespaces/default/jobs/ldap_sync_worker.
  2. В верхнем правом углу нажмите кнопку «Запустить» и подтвердите в диалоговом окне. Ldap sync worker UI

Чтобы посмотреть, как завершилась запущенная синхронизация, откройте страницу метрик задачи синхронизации LDAP: /admin/sidekiq/metrics?substr=SyncWorker&period=8h. На графике отображается статистика вызовов, в таблице ниже — количество успешно и аварийно завершённых вызовов синхронизации LDAP.

Ldap sync worker metrics

Чтобы посмотреть полные логи процесса синхронизации:

  1. На странице worker’а /admin/sidekiq/cron/namespaces/default/jobs/ldap_sync_worker найдите таблицу событий запусков «История». Первая строка соответствует последнему запуску. Скопируйте значение в колонке JID (Job ID) — оно понадобится для поиска по логам.

    Ldap sync history table

  2. Подключитесь к кластеру и определите имя пода Sidekiq командой: d8 k -n d8-code -l app.kubernetes.io/component=sidekiq get pod -o NAME

  3. Выполните сбор логов, подставив скопированный JID и имя пода (POD_NAME): d8 k -n d8-code logs POD_NAME | jq 'select(.jid=="JID")'

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

Особенности работы LDAP Sync

Алгоритм синхронизации

LDAP Sync использует плоский алгоритм синхронизации:

  1. Получение групп. Выполняется LDAP-запрос, который получает все группы по параметрам base, filter и scope.
  2. Извлечение участников. Для каждой найденной группы читаются атрибуты членства: member, uniquemember, memberof, memberuid, submember.
  3. Сопоставление пользователей. Каждый DN из атрибутов членства сопоставляется со значением Identity.extern_uid в базе данных.
  4. Игнорирование неизвестных DN. Если DN не соответствует известному пользователю, он пропускается. Например, это может быть DN вложенной группы.

Циклические зависимости между группами

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

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

Вложенные группы и транзитивность

LDAP Sync не поддерживает транзитивность для вложенных групп.

Во время синхронизации LDAP Sync обрабатывает только прямые значения атрибутов членства группы и не выполняет рекурсивный обход вложенных групп.

Если одна LDAP-группа содержит другую как участника, пользователи вложенной группы не будут автоматически добавлены в родительскую группу.

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

Если требуется учитывать вложенные группы, это нужно реализовать на стороне LDAP-сервера. Например, можно заполнять атрибут submember полным списком транзитивных участников.

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

Создание локальной учётной записи при включённой синхронизации с LDAP

Локальные учётные записи можно создавать и использовать даже при включённой синхронизации с LDAP.

Чтобы такие пользователи могли входить через веб-интерфейс, в административном разделе на странице «Настройки» → «Общие» → «Ограничения входа» должна быть включена настройка «Разрешить аутентификацию по паролю и passkey для веб-интерфейса» (password_authentication_enabled_for_web).

Пример настройки: вход через OIDC и права из LDAP

Ниже приведена последовательность настройки, при которой пользователи входят через OIDC-провайдера (например, Keycloak), а группы, членства в них и роли приходят из LDAP.

Что потребуется

  • OIDC-провайдер.
  • LDAP-каталог с теми же пользователями и с группами, имена которых позволяют определить роль.
  • Сервисная учётная запись LDAP с правами на чтение каталога (параметры bind_dn и password).

Значение uid или email в OIDC-провайдере должно совпадать со значением соответствующего атрибута в LDAP, иначе связывание не сработает (подробнее — в разделе «Как ищется учётная запись LDAP»).

Шаг 1. Настройте LDAP-провайдера

Конфигурация размещается в spec.appConfig.ldap.:

main:
  label: ldap
  host: ldap.example.com
  port: 3389
  bind_dn: 'uid=viewer,ou=People,dc=example,dc=com'
  password: 'viewer123'
  base: 'ou=People,dc=example,dc=com'
  uid: 'cn'
  sync_name: true
  # Не учитывать пользователей, заблокированных в каталоге (опционально).
  user_filter: '(!(nsAccountLock=TRUE))'
  # Пользователь найден в LDAP — вход разрешён сразу.
  block_auto_created_users: false
  group_sync: {
    create_groups: true,
    base: 'ou=Groups,dc=example,dc=com',
    filter: '(objectClass=groupOfNames)',
    top_level_group: "LdapGroups",
    name_mask: "(?<=-)[A-z0-9]*$",
    owner: "root",
    role_mapping: [
      { by_name: '.*-maintainer-.*', gitlab_role: 'maintainer' },
      { by_name: '.*-developer-.*', gitlab_role: 'developer' },
      { by_name: '.*-participant-.*', gitlab_role: 'reporter' }
    ]
  }

Шаг 2. Настройте OIDC-провайдера и включите связывание с LDAP

Конфигурация размещается в секции spec.appConfig.omniauth.:

auto_link_ldap_user: true
providers:
  - name: 'openid_connect'   # Не изменяйте это значение.
    label: 'Keycloak'        # Подпись кнопки входа.
    groups_attribute: 'gitlab_group'
    args:
      name: 'openid_connect'
      scope:
        - 'openid'
        - 'profile'
        - 'email'
      response_type: 'code'
      issuer: 'https://keycloak.example.com/realms/example'
      discovery: true
      client_auth_method: 'query'
      uid_field: 'preferred_username'
      send_scope_to_token_endpoint: false
      pkce: true
      client_options:
        identifier: '<client_id>'
        secret: '<client_secret>'
        redirect_uri: 'https://code.example.com/users/auth/openid_connect/callback'

Значение uid_field используется при поиске пользователя в LDAP. Оно должно совпадать со значением атрибута, указанного в параметре uid LDAP-сервера, или с адресом электронной почты — подробнее в разделе «Как ищется учётная запись LDAP».

Остальные параметры провайдера описаны в разделе «OpenID Connect (OIDC)».

Шаг 3. Проверьте связывание при первом входе

  1. Войдите тестовым пользователем через OIDC-провайдера.
  2. Откройте страницу пользователя в административном разделе (/admin/users/<username>/identities). У связанной учётной записи должно быть два идентификатора: openid_connect и ldapmain (имя LDAP-идентификатора складывается из префикса ldap и имени LDAP-сервера, в примере — main).

Если LDAP-идентификатора нет, проверьте следующее:

  • значения uid или email в OIDC-провайдере и в LDAP совпадают;
  • пользователь попадает в область поиска base и не отсекается фильтром user_filter;
  • параметр auto_link_ldap_user включён.

Шаг 4. Дождитесь синхронизации прав

Дождитесь ближайшей синхронизации или запустите её вручную (подробнее — в разделе «Ручной запуск синхронизации»), после чего проверьте, что:

  • группы созданы внутри группы, указанной в group_sync.top_level_group;
  • пользователь добавлен в них с ролью, соответствующей role_mapping.