Интеграция OAuth2/OIDC и LDAP/AD

Руководство по настройке для команд ИТ и информационной безопасности


Содержание

Обзор

CodeGraph поддерживает корпоративную единую аутентификацию (SSO) через OAuth2/OIDC и интеграцию с LDAP/Active Directory. Оба метода инициализируются при запуске приложения и генерируют события безопасности для SIEM при каждой попытке аутентификации.

Ключевые возможности

Возможность Описание
6 провайдеров OAuth2 GitHub, Google, Keycloak (OIDC), GitLab, SourceCraft (Yandex ID), GitVerse (Sber ID)
Поддержка LDAP/AD Active Directory, OpenLDAP с синхронизацией групп
Автоматическое создание Пользователи создаются при первом входе через SSO/LDAP
Сопоставление RBAC Группы LDAP сопоставляются с ролями CodeGraph (ADMIN, REVIEWER, ANALYST, VIEWER)
Защита от CSRF Параметр state с серверной валидацией
Аудит в SIEM События AUTH_SUCCESS / AUTH_FAILURE при каждом входе
Устойчивость к сбоям Приложение запускается даже без OAuth/LDAP

Архитектура

Поток аутентификации

                     ┌──────────────┐
                     │   Браузер /  │
                     │  API-клиент  │
                     └──────┬───────┘
                            │
              ┌─────────────┼─────────────┐
              │             │             │
       ┌──────▼──────┐ ┌───▼────┐ ┌──────▼──────┐
       │  OAuth2     │ │  LDAP  │ │ Локальный   │
       │  /OIDC      │ │  /AD   │ │ JWT + API   │
       └──────┬──────┘ └───┬────┘ └──────┬──────┘
              │             │             │
              └─────────────┼─────────────┘
                            │
                    ┌───────▼───────┐
                    │  User Repo    │
                    │  (найти или   │
                    │   создать)    │
                    └───────┬───────┘
                            │
                    ┌───────▼───────┐       ┌──────────────┐
                    │  JWT-токены   │       │  SIEM-событие│
                    │  (access +    │──────►│  Диспетчер   │
                    │   refresh)    │       │  (auth.*)    │
                    └───────────────┘       └──────────────┘

Регистрация провайдеров

При запуске функция lifespan() инициализирует подсистемы аутентификации:

  1. OAuthsetup_oauth_providers() читает переменные окружения OAUTH_*, регистрирует включённые провайдеры в OAuthManager
  2. IAMsetup_iam_validator() инициализирует валидацию токенов Yandex Cloud IAM (если IAM_ENABLED=true)
  3. LDAPsetup_ldap_authenticator() читает переменные окружения LDAP_*, создаёт синглтон LDAPAuthenticator

Все блоки обёрнуты в try/except — приложение нормально запускается, даже если SSO не сконфигурирован.


OAuth2/OIDC

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

CodeGraph поддерживает 6 провайдеров OAuth2/OIDC. 4 базовых провайдера перечислены в эндпоинте /oauth/providers; SourceCraft и GitVerse — платформенные интеграции с отдельной документацией.

Провайдер URL авторизации URL токена Области доступа
GitHub github.com/login/oauth/authorize github.com/login/oauth/access_token user:email
Google accounts.google.com/o/oauth2/v2/auth oauth2.googleapis.com/token openid, email, profile
Keycloak {server}/realms/{realm}/protocol/openid-connect/auth .../token openid, email, profile
GitLab gitlab.com/oauth/authorize (или локальный) gitlab.com/oauth/token read_user, openid
SourceCraft {server}/authorize (Yandex ID) {server}/token login:email, login:info
GitVerse {server}/oauth/authorize (Sber ID) {server}/oauth/token read:user, user:email

Примечание: SourceCraft и GitVerse подробно описаны в Интеграция с SourceCraft и Интеграция с GitVerse.

Настройка

Установите переменные окружения для каждого провайдера, который необходимо включить:

# GitHub
export OAUTH_GITHUB_CLIENT_ID="ваш_github_client_id"
export OAUTH_GITHUB_CLIENT_SECRET="ваш_github_client_secret"

# Google
export OAUTH_GOOGLE_CLIENT_ID="ваш_google_client_id"
export OAUTH_GOOGLE_CLIENT_SECRET="ваш_google_client_secret"

# Keycloak (OIDC)
export OAUTH_KEYCLOAK_SERVER_URL="https://keycloak.corp.example.com"
export OAUTH_KEYCLOAK_REALM="codegraph"
export OAUTH_KEYCLOAK_CLIENT_ID="codegraph-app"
export OAUTH_KEYCLOAK_CLIENT_SECRET="ваш_keycloak_client_secret"

# SourceCraft (Yandex ID)
export OAUTH_SOURCECRAFT_CLIENT_ID="ваш_sourcecraft_client_id"
export OAUTH_SOURCECRAFT_CLIENT_SECRET="ваш_sourcecraft_client_secret"
export OAUTH_SOURCECRAFT_SERVER_URL="https://oauth.yandex.ru"    # по умолчанию

# GitVerse (Sber ID)
export OAUTH_GITVERSE_CLIENT_ID="ваш_gitverse_client_id"
export OAUTH_GITVERSE_CLIENT_SECRET="ваш_gitverse_client_secret"
export OAUTH_GITVERSE_SERVER_URL="https://gitverse.ru"           # по умолчанию

Провайдеры без CLIENT_ID и CLIENT_SECRET автоматически пропускаются.

Примечание: OAuth для GitLab поддерживается в коде, но не имеет выделенных переменных окружения OAUTH_GITLAB_*. Для включения GitLab настройте его напрямую в config.yaml в секции auth.oauth.gitlab с полями client_id, client_secret и опционально authorize_url / token_url для локальных инстансов.

Поток авторизации

1. Пользователь    GET /api/v1/auth/oauth/{provider}
2. Сервер генерирует CSRF state, сохраняет в памяти
3. Редирект 302 на URL авторизации провайдера
4. Пользователь аутентифицируется на стороне провайдера
5. Провайдер перенаправляет на GET /api/v1/auth/oauth/{provider}/callback?code=...&state=...
6. Сервер проверяет state (CSRF), обменивает code на access token
7. Сервер запрашивает информацию о пользователе через API провайдера
8. Сервер находит или создаёт пользователя в базе данных
9. Сервер возвращает JWT-токены (access + refresh)

Автоматическое создание пользователей

При первом входе через OAuth автоматически создаётся пользователь:

Поле Источник
username Зависит от провайдера (GitHub: login, Google: префикс email, Keycloak: preferred_username)
email Информация от провайдера
auth_provider Per-provider: oauth_github, oauth_google, oauth_gitlab, oauth_keycloak
role По умолчанию: ANALYST
external_id Уникальный ID пользователя у провайдера

Примечание: Поле display_name из информации провайдера передаётся в метод создания пользователя, но в настоящее время не сохраняется в базе данных. Поле username служит основным отображаемым идентификатором.

Последующие входы находят пользователя по паре (auth_provider, external_id) и используют существующий аккаунт.


LDAP/Active Directory

Настройка LDAP

# Обязательные
export LDAP_SERVER="ldaps://ldap.corp.example.com"
export LDAP_BASE_DN="dc=corp,dc=example,dc=com"

# Сервисный аккаунт для поиска
export LDAP_BIND_USER="cn=codegraph-svc,ou=service-accounts,dc=corp,dc=example,dc=com"
export LDAP_BIND_PASSWORD="пароль_сервисного_аккаунта"

# Базы поиска
export LDAP_USER_SEARCH_BASE="ou=users,dc=corp,dc=example,dc=com"
export LDAP_GROUP_SEARCH_BASE="ou=groups,dc=corp,dc=example,dc=com"

Полная конфигурация LDAPConfig в config.yaml:

auth:
  ldap:
    enabled: true
    server: "ldaps://ldap.corp.example.com"
    port: 636
    use_ssl: true
    base_dn: "dc=corp,dc=example,dc=com"
    user_search_base: "ou=users,dc=corp,dc=example,dc=com"
    group_search_base: "ou=groups,dc=corp,dc=example,dc=com"
    bind_user: "cn=codegraph-svc,ou=service-accounts,dc=corp,dc=example,dc=com"
    bind_password: "${LDAP_BIND_PASSWORD}"
    user_object_class: "person"
    group_object_class: "group"
    username_attribute: "sAMAccountName"
    email_attribute: "mail"
    group_membership_attribute: "memberOf"
    role_mapping:
      "cn=codegraph-admins,ou=groups,dc=corp,dc=example,dc=com": "admin"
      "cn=codegraph-reviewers,ou=groups,dc=corp,dc=example,dc=com": "reviewer"
      "cn=codegraph-analysts,ou=groups,dc=corp,dc=example,dc=com": "analyst"

Сопоставление групп и ролей

Группы LDAP сопоставляются с ролями RBAC CodeGraph через role_mapping в LDAPConfig. LDAPAuthenticator проверяет группы пользователя в порядке приоритета (admin > reviewer > analyst > viewer). Побеждает первое совпадение.

Группа LDAP Роль CodeGraph Разрешения
codegraph-admins ADMIN Полный доступ (admin:all)
codegraph-reviewers REVIEWER Проверка кода + разрешения аналитика
codegraph-analysts ANALYST Выполнение запросов + API-ключи
(нет совпадения) ANALYST Роль по умолчанию

Известная проблема: Метод LDAPAuthenticator.map_groups_to_role() обращается к config.group_role_mapping вместо поля role_mapping, определённого в LDAPConfig. Из-за этого сопоставление молча возвращает роль analyst по умолчанию. Исправление требует выравнивания имени атрибута в ldap_auth.py с именем поля LDAPConfig.

Создание пользователей из LDAP

При первом входе через LDAP создаётся пользователь через LDAPAuthenticator:

Поле Источник
username LDAP-атрибут sAMAccountName (или настроенный атрибут)
email LDAP-атрибут mail
ldap_dn Полное различающееся имя (DN), сохраняется как external_id
auth_provider ldap
role Из сопоставления групп и ролей

Примечание: display_name из LDAP-атрибута displayName передаётся в create_ldap_user(), но в настоящее время не сохраняется в базе данных.

Последующие входы находят пользователя по ldap_dn (хранится как external_id с auth_provider=ldap).


Интеграция с SIEM

Все попытки аутентификации (успешные и неуспешные) генерируют SIEM-события, отправляемые в настроенные обработчики (Syslog, CEF, LEEF).

События аутентификации

Тип события Серьёзность Триггер
AUTH_SUCCESS 6 (Информационное) Успешный вход любым методом
AUTH_FAILURE 4 (Предупреждение) Неуспешный вход — неверные учётные данные, неизвестный пользователь, ошибка провайдера

Примеры событий

Успешный вход через OAuth:

{
  "event_type": "auth.success",
  "timestamp": "2026-02-26T10:30:00.000Z",
  "severity": 6,
  "message": "OAuth login: ghuser via github",
  "user_id": "550e8400-e29b-41d4-a716-446655440000",
  "ip_address": "10.0.0.50",
  "provider": "github",
  "request_id": "req-abc123"
}

Неуспешный вход через LDAP:

{
  "event_type": "auth.failure",
  "timestamp": "2026-02-26T10:31:00.000Z",
  "severity": 4,
  "message": "LDAP auth failed: unknown_user",
  "ip_address": "10.0.0.51",
  "request_id": "req-def456"
}

Неуспешный локальный вход (неверный пароль):

{
  "event_type": "auth.failure",
  "timestamp": "2026-02-26T10:32:00.000Z",
  "severity": 4,
  "message": "Login failed: invalid password for analyst01",
  "user_id": "550e8400-e29b-41d4-a716-446655440001",
  "ip_address": "10.0.0.52",
  "request_id": "req-ghi789"
}

Справочник API

Конечные точки OAuth

Метод Путь Описание
GET /api/v1/auth/oauth/providers Список 4 базовых провайдеров OAuth и их статус
GET /api/v1/auth/oauth/{provider} Начать OAuth-поток (перенаправление к провайдеру)
GET /api/v1/auth/oauth/{provider}/callback Обработка OAuth-колбэка, возврат JWT-токенов

Ответ списка провайдеров:

Примечание: Эндпоинт /oauth/providers перечисляет 4 базовых провайдера (github, google, gitlab, keycloak) по захардкоженному списку. SourceCraft и GitVerse зарегистрированы в OAuthManager и принимают OAuth-потоки через /oauth/{provider}, но не включены в листинг провайдеров.

[
  {"name": "github", "enabled": true, "authorize_url": "https://github.com/login/oauth/authorize?..."},
  {"name": "google", "enabled": false, "authorize_url": null},
  {"name": "gitlab", "enabled": false, "authorize_url": null},
  {"name": "keycloak", "enabled": false, "authorize_url": null}
]

Успешный ответ колбэка:

{
  "access_token": "eyJhbGciOiJIUzI1NiIs...",
  "refresh_token": "eyJhbGciOiJIUzI1NiIs...",
  "token_type": "bearer",
  "expires_in": 1800
}

Конечные точки LDAP

Метод Путь Описание
POST /api/v1/auth/ldap Аутентификация через LDAP
GET /api/v1/auth/ldap/status Проверка статуса подключения к LDAP

Запрос аутентификации LDAP:

{
  "username": "jdoe",
  "password": "corporate_password"
}

Ответ статуса LDAP (подключён):

{
  "enabled": true,
  "available": true,
  "connected": true,
  "server": "ldaps://ldap.corp.example.com"
}

Ответ статуса LDAP (не настроен):

{
  "enabled": false,
  "available": false,
  "message": "LDAP not configured"
}

Ответ статуса LDAP (библиотека ldap3 не установлена):

{
  "enabled": true,
  "available": false,
  "message": "LDAP configured but ldap3 library not installed"
}

Ответ статуса LDAP (ошибка подключения):

{
  "enabled": true,
  "available": true,
  "connected": false,
  "error": "Connection refused"
}

Запуск и остановка

Последовательность запуска

При запуске приложения (lifespan()) подсистемы аутентификации инициализируются последовательно:

# 1. Провайдеры OAuth (6 провайдеров)
auth_config = get_auth_config()
oauth_providers = setup_oauth_providers({
    "github": auth_config.oauth.github,
    "google": auth_config.oauth.google,
    "gitlab": auth_config.oauth.gitlab,
    "keycloak": auth_config.oauth.keycloak,
    "sourcecraft": auth_config.oauth.sourcecraft,
    "gitverse": auth_config.oauth.gitverse,
})
# Лог: "OAuth providers initialized: 2" или "No OAuth providers configured"

# 2. Yandex Cloud IAM (опционально)
if settings.iam_enabled:
    setup_iam_validator(enabled=True, validation_url=settings.iam_validation_url)
# Лог: "Yandex Cloud IAM validator initialized" или "IAM auth disabled"

# 3. LDAP-аутентификатор
ldap = setup_ldap_authenticator(auth_config.ldap)
# Лог: "LDAP authenticator initialized: ldaps://..." или "LDAP not configured"

Очистка при остановке

При остановке HTTP-клиенты OAuth закрываются корректно:

await get_oauth_manager().close_all()

Устойчивость к сбоям

Все подсистемы необязательны. Если инициализация не удалась (отсутствует библиотека, неверная конфигурация), приложение логирует предупреждение и продолжает работу:

WARNING  OAuth initialization skipped: <ошибка>
WARNING  IAM initialization skipped: <ошибка>
WARNING  LDAP initialization skipped: <ошибка>

Все остальные конечные точки (локальный JWT-логин, API-ключи, запросы, сценарии) остаются полностью функциональными.


Устранение неполадок

Провайдеры OAuth показывают enabled: false

Проверьте установку переменных окружения:

echo $OAUTH_GITHUB_CLIENT_ID
echo $OAUTH_GITHUB_CLIENT_SECRET

Оба значения CLIENT_ID и CLIENT_SECRET должны быть непустыми для регистрации провайдера.

OAuth-колбэк возвращает invalid_state_parameter

CSRF-токен state хранится в памяти сервера. Эта ошибка возникает если: - Токен state устарел (сервер перезапущен между редиректом и колбэком) - Токен state уже использован (обновление страницы в браузере) - Многоинстансное развёртывание без общего хранилища state (используйте Redis в рабочей среде)

LDAP возвращает 503 ldap_not_configured

Переменная окружения LDAP_SERVER не установлена или библиотека ldap3 не установлена:

pip install ldap3>=2.9.1

Тест подключения к LDAP не проходит

Проверьте сетевую доступность и учётные данные:

# Тест подключения к LDAP
ldapsearch -H ldaps://ldap.corp.example.com -x -D "cn=codegraph-svc,..." -w ПАРОЛЬ -b "dc=corp,dc=example,dc=com" "(sAMAccountName=testuser)"

Сопоставление ролей LDAP всегда возвращает analyst

Метод LDAPAuthenticator.map_groups_to_role() имеет известное несоответствие имён атрибутов: обращается к config.group_role_mapping вместо config.role_mapping. До исправления в ldap_auth.py все LDAP-пользователи получают роль analyst по умолчанию вне зависимости от конфигурации role_mapping.

SIEM-события не появляются

Проверьте, что диспетчер SIEM включён в config.yaml:

siem:
  enabled: true
  handlers:
    - type: syslog
      host: "siem.corp.example.com"
      port: 514

Подробная настройка — см. Интеграция с SIEM.

В логах запуска предупреждения об инициализации

Проверьте логи приложения на конкретные ошибки:

uvicorn src.api.main:app --log-level debug 2>&1 | grep -i "oauth\|ldap\|iam"

Связанные документы