Все проекты

Идентификация и доступ

pacod-id

Единое место, где входят люди и машины — и где записано, что им разрешено, во всех ваших продуктах.

pacod-id — провайдер OpenID Connect для собственных приложений, с аккаунтами, организациями, привязками ролей и лентой изменений, по которой продукты принимают решения о доступе локально. Написан на Rust, с клиентскими библиотеками для Rust, TypeScript и Python.

  • Apache-2.0
  • Версия 0.1.0
  • Предварительная

Возможности

  • Провайдер OpenID Connect

    Вход в ваши собственные приложения, проверенный набором тестов на соответствие от OpenID Foundation.

  • Аккаунты и организации

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

  • Один движок правил, три порта

    Манифесты продуктов, scopes, allowed, has_role и touches — на Rust, TypeScript и Python, и все проходят одни и те же тестовые векторы YAML.

  • Лента авторизации

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

  • Токены как надо

    Compact JWS с RS256 и ES256 на ring, JWKS, интроспекция и обмен токенов.

  • Брендированный вход

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

  • Готовые клиенты

    Клиентский crate для Rust, пакет для Python с зависимостями FastAPI и сценарий входа backend-for-frontend для SvelteKit.

  • Готов к запуску

    Docker-образ, стек для разработки с почтой и mock-версией Google, а также Helm-чарт.

Место в экосистеме

Люди и сервисыLLM-провайдерыAPpacod-apCMpacod-cmsKBpacod-kbGWpacod-gwIDpacod-id
Люди и сервисыLLM-провайдерыAPpacod-apCMpacod-cmsKBpacod-kbGWpacod-gwIDpacod-id
Схема: как связаны проекты pacod Вызовы LLM Нажмите на проект, чтобы увидеть, с чем он связан.

Вход для целого семейства продуктов

pacod-id — сервис идентификации, на котором работают продукты pacod. Пользователь входит один раз и получает доступ к консоли шлюза, платформе агентов, студии CMS и базе знаний с одним и тем же аккаунтом. Машины тоже входят — как сервисные аккаунты.

Он нужен для того, чтобы ни одному продукту не приходилось самому вести пароли, письма об аккаунтах и страницы администрирования. Аккаунты лежат в одном общем пуле: sub человека одинаков в каждой области (realm). От продукта к продукту различаются вид страницы входа и то, кому что там разрешено, — и pacod-id записывает и то и другое.

Это провайдер OpenID Connect для ваших собственных приложений. Каждый клиент описан в конфигурации: динамической регистрации и экрана согласия нет.

Области, клиенты и токены

Область (realm) — это один хост issuer со своим брендом, cookie входа, языками и политиками: длина пароля, длительность сессии, может ли кто угодно зарегистрироваться, какие роли управляют аккаунтами. pacod-id выбирает область по хосту запроса, поэтому два бренда могут делить один пул аккаунтов и при этом выглядеть как два разных продукта. Бренд области — один из пяти встроенных: gateway, cms, kb, а также shop и bazaar для вымышленного продукта, на котором построены документация и тесты.

Клиент — это одно приложение одного из трёх видов:

Вид Типичное применение Grants
confidential Backend-for-frontend продукта, хранящий секрет authorization_code, refresh_token
public Нативное или мобильное приложение; его коды защищает только PKCE authorization_code, refresh_token
service Сервер продукта, обращающийся к pacod-id client_credentials, обмен токенов

Поддерживается только authorization code flow, причём PKCE (S256) обязателен для каждого клиента, включая конфиденциальные.

  • Access-токены — это JWT (RFC 9068), RS256 или ES256 в зависимости от клиента, по умолчанию действуют 5 минут (от 2 до 15). Они говорят, кто, в какой сессии и с каким scope, но ролей не содержат.
  • id_token живёт 5 минут и содержит адрес и имя, если их запрашивают scopes.
  • Refresh-токены непрозрачны и меняются при каждом использовании. Если после 30-секундного льготного окна предъявить уже заменённый токен, все сессии аккаунта завершаются, а владелец получает письмо.
  • Обмен токенов (RFC 8693) позволяет сервису продукта действовать от имени человека в пределах его прав, при этом act указывает на сервис.

Вход через Google включается отдельно для каждой области. Когда сессия завершается, каждый клиент, у которого в ней был grant и задан back-channel logout URI, получает logout-токен.

Решения о доступе — локально

Роли намеренно не записываются в access-токены. При запуске каждый продукт отправляет манифест: свои типы scopes, свои роли с их правами и то, кто что может выдавать. Scopes образуют дерево — от platform, при необходимости через organisation:<id>, до собственных типов продукта, например gw.workspace:ws_1. Привязка — это роль, которой владеют в одном scope, и её права действуют вниз: роль в организации действует в каждом рабочем пространстве под ней.

product: shop
scope_types:
  profile: { parent: platform }
roles:
  admin:
    at: [platform]
    permissions: [admin.read, admin.write, profiles.assign]
    protected: true
  profile_editor:
    at: [profile]
    permissions: [profile.edit]
grant:
  default: admin.write

pacod-id хранит привязки и проверяет каждое их изменение: чтобы выдать роль, нужно право на выдачу из манифеста в этом scope, а роль с protected никогда не теряет последнего владельца.

Движок правил отвечает на allowed, has_role, touches и can_grant. Он существует на Rust, TypeScript и Python, и все три проходят одни и те же тестовые векторы YAML (policy/engine-vectors/). Векторы и есть спецификация, поэтому решение одинаково, где бы оно ни принималось.

Лента авторизации

Каждый продукт держит собственную копию привязок и следит за изменениями:

  1. При запуске он загружает снимок (snapshot): свой каталог, организации и свои scopes, привязки своих ролей, отключённые и удалённые аккаунты и недавно завершённые сессии — всё на момент одного порядкового номера, seq.
  2. Затем он запрашивает изменения после этого seq через long poll, ожидая до 30 секунд на вызов. Ответ приходит, как только что-то меняется: PostgreSQL LISTEN будит ожидающий запрос.
  3. Изменения хранятся 7 дней. На более старый курсор приходит 410, и продукт заново загружает снимок.

События охватывают привязки, аккаунты, сессии, сервисные токены, scopes и каталоги. Запись возвращает seq ленты, так что продукт, которому нужно прочитать собственную запись, ждёт, пока его копия догонит этот номер. Клиентские библиотеки для Rust и Python делают всё это в своём кэше привязок.

По замерам make measure на одной машине разработчика (Docker Desktop на Apple silicon) изменение доходило до уже ожидающего подписчика с p99 около 10 мс при цели меньше секунды.

Компоненты

Часть Что это
crates/pacod-id Сервис: провайдер OpenID Connect, аккаунты, организации, привязки ролей, лента авторизации, почта, аудит
crates/pacod-id-authz Движок правил
crates/pacod-id-jose Compact JWS (RS256, ES256) на ring и JWKS
crates/pacod-id-client Проверка токенов, кэш привязок, интроспекция, обмен токенов для продуктов на Rust
python/ Пакет pacod_id: движок правил, проверка токенов, зависимости FastAPI
packages/* @pacod/id-authz, @pacod/ui, @pacod/id-client, @pacod/id-sveltekit
ui/ Готовые страницы входа и аккаунта

Сервис — это один исполняемый файл на Rust с тремя слушателями:

Слушатель Порт Для чего
публичный 8070 Браузеры и приложения, через обратный прокси с TLS: эндпоинты OpenID Connect и готовые страницы
внутренний 8071 Только серверы продуктов: токены, лента, интроспекция, завершение сессий, API администрирования
метрики 9170 /healthz, /readyz и /metrics

Готовые страницы входа, аккаунта и администрирования — это статическая сборка SvelteKit, которую исполняемый файл отдаёт сам. @pacod/ui содержит дизайн-токены, темы брендов и компоненты входа, которые использует каждый продукт pacod, — включая этот сайт.

Модель безопасности

Основные меры защиты по модели угроз:

  • Пароли: Argon2id на минимуме OWASP (19 МиБ, 2 прохода), по умолчанию 12 символов и список запрещённых распространённых паролей. Ограничения попыток входа хранятся в PostgreSQL и общие для всех экземпляров: на аккаунт, на знакомый браузер и на IP-адрес.
  • Без перебора аккаунтов: вход, сброс пароля и регистрация отвечают одинаково, есть ли у адреса аккаунт или нет.
  • Ключи подписи: pacod-id создаёт их сам, хранит в базе запечатанными под ключом шифрования ключей (PID_KEK) и меняет каждые 90 дней, публикуя следующий ключ за сутки. Экстренная ротация — одна команда.
  • Хранимые секреты: refresh-токены и ссылки из писем — как SHA-256, секреты клиентов и сервисные токены — как HMAC с отдельным pepper. Собственные строки лога pacod-id никогда не содержат пароля, токена или ключа.
  • Доступность извне: публичный слушатель не обслуживает внутренние и административные пути, а NetworkPolicy в Helm-чарте пускает на внутренний порт только из указанных вами источников. На общем edge полного стека pacod маршрут есть только у публичного слушателя.
  • Образ: статический исполняемый файл musl на distroless, без прав root и без shell.

Эксплуатация

Области и клиенты описываются в одном файле TOML, секреты — никогда. Секрет клиента берётся из переменной, которую называет его secret_env.

[[realm]]
name = "gateway"
issuer = "https://id.example.com"
brand = "gateway"
locales = ["en", "bg", "de", "ru"]
self_signup = false

[[client]]
id = "pacod-gw-console"
realm = "gateway"
kind = "confidential"
secret_env = "PGW_UI_CLIENT_SECRET"
redirect_uris = ["https://console.example.com/auth/callback"]
audience = "pacod-gw"

pacod-id check-config проверяет файл без базы данных. Кроме него сервису нужны PID_DATABASE_URL, PID_KEK и PID_TOKEN_PEPPER — переменной, файлом или через каталог секретов.

  • База данных: PostgreSQL, с собственной ролью, которая не является суперпользователем. Миграции только добавляют и выполняются при каждом запуске под advisory lock.
  • Экземпляры: любое их число делит одну базу. Каждый отправляет назревшие письма и logout-ы и перезагружает ключи и каталоги; один, держащий lease, меняет ключи и удаляет старые строки.
  • Почта: ни один запрос не ждёт SMTP. Письма попадают в outbox, в бренде и на языках области, и повторно отправляются в течение примерно восьми часов.
  • Первый администратор: создаётся при запуске из PID_BOOTSTRAP_ADMIN_*.
  • Наблюдение: /readyz проверяет базу и ключ подписи. /metrics даёт число и длительность запросов по слушателям. Логи в JSON, без строки на каждый запрос. Входы, изменения ролей и действия администраторов пишутся в журнал аудита, который хранится 400 дней и доступен на страницах администрирования.

Шлюз, который раньше сам выполнял вход пользователей, может передать их командой pacod-id import gw: пароли, токены администраторов и ссылки-приглашения продолжают работать.

make compose-up запускает стек для разработки в pacod-net — Docker-сети, к которой подключается каждый продукт pacod. Его конфигурация Compose состоит из двух файлов. docker-compose.yml содержит только pacod-id. docker-compose.standalone.yml добавляет собственные копии нужных ему общих компонентов: PostgreSQL (pacod-postgres), pacod-db-init, который при каждом запуске создаёт роль pacod_id и её базу, если их нет, и почтовую заглушку (pacod-mailpit), а также mock-версию Google (id-mock-idp). Стек слушает только 127.0.0.1, issuer — http://127.0.0.1:26070.

Полный стек pacod, pacod-demo (работает на demo.pacod.io), загружает только первый файл. В нём один PostgreSQL, одна почтовая заглушка, один edge и один pacod-id на все продукты, с единственной областью для всех них. В platform/ лежит доля pacod-id в этой конфигурации: его скрипт базы данных, его маршрут на общем Caddy и его задание Prometheus. Для общего сервера PostgreSQL модель угроз советует давать каждому продукту собственную роль без прав суперпользователя: продукт, подключающийся как суперпользователь, может читать таблицы pacod-id.

Чего пока нет

  • pacod-id 0.1.0 ещё не выпущен. Обновления, резервное копирование и восстановление пока не опробованы.
  • Helm-чарт проходит helm lint и проверку схем, но ещё не устанавливался в кластер. Настоящий SMTP-сервер и настоящий Google не проверялись — только заглушка из среды разработки.
  • Тесты соответствия OpenID Foundation прогонялись локально: планы Config, Basic и RP-Initiated Logout — без единого невыполненного условия, а один тест Back-Channel Logout был заблокирован собственным сертификатом тестового набора. Локальные прогоны — не сертификация.
  • Пока нет MFA и passkeys, а значит, и claim acr; в документации они значатся как запланированные.
  • Нет claims адреса и телефона; scope profile даёт name и locale.
  • Внутренний слушатель работает по обычному HTTP, и, кроме ограничений при входе, общего ограничения частоты запросов нет.

Что почитать дальше

Скриншоты

Технологии

Языки
  • Rust
  • TypeScript
  • Python
Технологии
  • Rust
  • axum
  • PostgreSQL
  • OpenID Connect
  • SvelteKit
  • Docker
  • Helm

Быстрый старт

Разработка и тестирование
make test-servicesmake verifymake e2e
Сборка и запуск образа
make imagemake compose-up

Работает с

  • pacod-gw

    Консоль шлюза может выполнять вход через pacod-id и проверяет роли в рабочих пространствах с помощью его движка правил.

  • pacod-ap

    Приложение и консоль администратора платформы агентов выполняют вход через pacod-id как конфиденциальный клиент.

  • pacod-cms

    Студия выполняет вход пользователей через pacod-id и хранит там роли на уровне организации и сайта.

  • pacod-kb

    Интерфейс базы знаний — OIDC-клиент pacod-id; роли viewer, editor и admin назначаются там.

Лицензия

Apache-2.0