Идентификация и доступ
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-чарт.
Место в экосистеме
Вход для целого семейства продуктов
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/). Векторы и есть спецификация, поэтому решение одинаково, где бы оно ни
принималось.
Лента авторизации
Каждый продукт держит собственную копию привязок и следит за изменениями:
- При запуске он загружает снимок (snapshot): свой каталог, организации и свои scopes,
привязки своих ролей, отключённые и удалённые аккаунты и недавно завершённые сессии — всё на
момент одного порядкового номера,
seq. - Затем он запрашивает изменения после этого
seqчерез long poll, ожидая до 30 секунд на вызов. Ответ приходит, как только что-то меняется: PostgreSQLLISTENбудит ожидающий запрос. - Изменения хранятся 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, и, кроме ограничений при входе, общего ограничения частоты запросов нет.
Что почитать дальше
- Runbook: установка и эксплуатация.
- HTTP API: все эндпоинты и события ленты.
- Модель угроз: меры защиты и остаточные риски.
- Замеры: задержки token endpoint и ленты.
- Результаты тестов соответствия и тестовые векторы движка правил.
Скриншоты
Технологии
- Языки
- Rust
- TypeScript
- Python
- Технологии
- Rust
- axum
- PostgreSQL
- OpenID Connect
- SvelteKit
- Docker
- Helm
Быстрый старт
make test-servicesmake verifymake e2emake 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