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

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

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


Содержание

Обзор

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

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

Возможность Описание
4 поставщика OAuth2 GitHub, GitLab, Google, Keycloak (OIDC)
Поддержка 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. LDAPsetup_ldap_authenticator() читает переменные окружения LDAP_*, создаёт синглтон LDAPAuthenticator

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


OAuth2/OIDC

Поддерживаемые поставщики

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

Настройка

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

# 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"

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

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

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 Информация от поставщика
display_name Поле name поставщика
auth_provider oauth
role По умолчанию: ANALYST
external_id Уникальный ID пользователя у поставщика

Последующие входы находят пользователя по паре (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"

Полная настройка в 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. Аутентификатор проверяет группы пользователя в порядке приоритета (admin > reviewer > analyst > viewer). Побеждает первое совпадение.

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

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

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

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

Последующие входы находят пользователя по ldap_dn.


Интеграция с 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 Список всех поставщиков OAuth и их статус
GET /api/v1/auth/oauth/{provider} Начать OAuth-поток (перенаправление к поставщику)
GET /api/v1/auth/oauth/{provider}/callback Обработка OAuth-колбэка, возврат JWT-токенов

Ответ списка поставщиков:

[
  {"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": "корпоративный_пароль"
}

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

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

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

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

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

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

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

# 1. поставщики OAuth
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,
})
# Лог: "OAuth providers initialized: 2" или "No OAuth providers configured"

# 2. 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  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)"

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"

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