Corp/docs/guides/OIDC.md

8.7 KiB
Raw Blame History

Корпоративный OpenID Connect

Портал выступает доверенным OpenID Connect Provider для внутренних приложений. Первое подключение — Gitea; тот же контракт можно использовать для других клиентов с поддержкой OIDC Authorization Code Flow.

Контракт

  • issuer: https://corp.example.com/o
  • discovery: https://corp.example.com/o/.well-known/openid-configuration
  • flow: Authorization Code
  • подпись ID token: RS256
  • client authentication: client_secret_basic или client_secret_post
  • scopes: openid, profile, email, groups
  • идентификатор пользователя sub: неизменяемый Django PK сотрудника
  • рекомендуемый логин: claim preferred_username из LDAP username; fallback employee-<pk>

Пароль портала стороннему приложению не передаётся. Обычный JWT-вход портала создаёт короткую browser session только перед возвратом на /o/authorize/. Redirect из bridge ограничен собственным authorization endpoint и не может увести пользователя на произвольный сайт.

Неактивный, уволенный или не подтвердивший email сотрудник не может обменять authorization code, использовать access token либо обновить токен. Внешний клиент получает только claims из согласованных scopes.

Production-настройка портала

OIDC и callback клиентов должны работать по HTTPS. Ключ подписи хранится вне Git и резервируется как production secret.

Создать отдельный RSA-ключ:

install -d -m 700 /srv/eusrr/secrets
openssl genpkey -algorithm RSA -pkeyopt rsa_keygen_bits:3072 \
  -out /srv/eusrr/secrets/oidc-private.pem
chmod 600 /srv/eusrr/secrets/oidc-private.pem

Передать файл backend-контейнеру read-only и задать окружение:

SITE_URL=https://corp.example.com
PORTAL_FRONTEND_URL=https://corp.example.com
OIDC_PROVIDER_ENABLED=true
OIDC_RSA_PRIVATE_KEY_PATH=/run/secrets/eusrr_oidc_private_key.pem
# При ротации: OIDC_RSA_PRIVATE_KEY_PATHS_INACTIVE=/run/secrets/oidc-private-old.pem
OIDC_ISSUER_URL=https://corp.example.com/o
OIDC_PORTAL_LOGIN_URL=https://corp.example.com/login
OIDC_BROWSER_SESSION_SECONDS=28800
OIDC_ACCESS_TOKEN_SECONDS=3600
OIDC_ID_TOKEN_SECONDS=600
OIDC_REFRESH_TOKEN_SECONDS=43200
SESSION_COOKIE_SECURE=true

OIDC_ISSUER_URL — постоянный публичный идентификатор. Его нельзя менять после подключения клиентов без их перенастройки. Reverse proxy должен сохранять исходный HTTPS scheme и host.

Публичный reverse proxy должен отправлять весь prefix /o/ в Django так же, как /api/. Пример для Nginx:

location ^~ /o/ {
    proxy_set_header Host $host;
    proxy_set_header X-Forwarded-Proto $scheme;
    proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
    proxy_pass http://django_backend;
}

После установки зависимостей выполнить миграции:

python manage.py migrate
python manage.py check --deploy

Регистрация Gitea в портале

Сначала выберите неизменяемое имя authentication source в Gitea, например corp. Оно входит в callback URL:

https://git.example.com/user/oauth2/corp/callback

Создать confidential-клиент и сохранить показанный secret в менеджер секретов:

python manage.py provision_oidc_client \
  --name Gitea \
  --client-id gitea \
  --redirect-uri https://git.example.com/user/oauth2/corp/callback

Повторный запуск обновляет параметры, не меняя secret. Для плановой ротации используйте --rotate-secret и сразу обновите Gitea. Клиент также доступен администраторам в Django Admin в разделе OAuth2 Provider, но management command безопаснее для первичного получения секрета.

Настройка Gitea

В app.ini:

[oauth2_client]
ENABLE_AUTO_REGISTRATION = true
USERNAME = preferred_username
ACCOUNT_LINKING = login
UPDATE_AVATAR = true
OPENID_CONNECT_SCOPES = profile email groups
REGISTER_EMAIL_CONFIRM = false

Затем в Site Administration → Identity & Access → Authentication Sources добавить источник:

Поле Значение
Authentication Type OAuth2
Authentication Name corp
OAuth2 Provider OpenID Connect
Client ID значение из provision_oidc_client
Client Secret одноразово показанный secret
Auto Discovery URL https://corp.example.com/o/.well-known/openid-configuration
Additional Scopes profile email groups
Group Claim Name groups

До переключения пользователей оставить локальный вход Gitea включённым и сохранить отдельную аварийную локальную учётную запись администратора. ACCOUNT_LINKING = login не удаляет и не разлогинивает существующие локальные учётки: при первом конфликте username/email Gitea попросит один раз подтвердить привязку существующим локальным входом.

Gitea 1.23.x не отправляет PKCE challenge для confidential OIDC-клиента, поэтому портал временно допускает Authorization Code без PKCE для совместимости. После обновления Gitea и проверки поддержки PKCE следует включить PKCE_REQUIRED в настройках провайдера.

Claims и группы

userinfo и ID token могут содержать:

Scope Claims
openid sub
profile name, given_name, family_name, preferred_username, nickname, picture, locale, zoneinfo, updated_at
email email, email_verified
groups Django groups, portal-users, а также portal-staff/portal-admins для служебных ролей

Не назначайте Gitea administrator по общей группе portal-users. Для автоматического администрирования заведите отдельную узкую Django group и явно сопоставьте её в Gitea.

Проверка

До включения кнопки входа проверить с хоста Gitea:

curl -fsS https://corp.example.com/o/.well-known/openid-configuration
curl -fsS https://corp.example.com/o/.well-known/jwks.json

Discovery должен публиковать один и тот же issuer, HTTPS endpoints и RS256; JWKS содержит только публичную часть ключа. После этого пройти вход тестовой активной учёткой, проверить создание/привязку пользователя, имя, email и avatar, затем проверить отказ для деактивированной тестовой учётки.

Отзыв доступа и ротация

  • отключение сотрудника начинает блокировать access/refresh tokens при следующем обращении;
  • OAuth grants и токены можно отозвать через Django Admin;
  • client secret ротируется provision_oidc_client --rotate-secret;
  • при RSA rotation укажите старые ключи через OIDC_RSA_PRIVATE_KEY_PATHS_INACTIVE на переходный период; не удаляйте их до истечения ранее выданных ID token;
  • периодическая очистка выполняется командой python manage.py cleartokens.

Dynamic Client Registration и пользовательское создание OAuth applications публично не маршрутизируются. Новый клиент регистрирует только администратор.