
pacod-id е OpenID Connect доставчикът зад продуктите на PACOD: един общ пул от акаунти, готови страници за вход и за акаунта, организации и присвоявания на роли. Токените му казват кой е човекът, а не какво може: всеки продукт следи поток от присвояванията на роли и сам решава за достъпа, с един механизъм за правила на Rust, TypeScript и Python.
Идентичност и достъп
- Лиценз: Apache-2.0
- Версия: 0.1.0 · Предварителна
- Написан на: Rust, TypeScript, Python
- Технологии: Rust, axum, PostgreSQL, OpenID Connect, SvelteKit, Docker, Helm
- Вижте в GitHub · Докладвайте проблем
Възможности
OpenID Connect доставчик
Authorization code flow с PKCE за всеки клиент, клиенти от конфигурацията и без екран за съгласие, проверен локално с тестовия пакет на OpenID Foundation.
Един пул от акаунти
Хора, служебни акаунти, организации, покани и присвоявания на роли, общи за всички продукти; sub на човека е един и същ навсякъде.
Поток за оторизация
Продуктите зареждат моментна снимка на присвояванията и чакат промените с long poll, така че отнета роля или прекратена сесия стига до тях без извикване при всяка заявка.
Един механизъм за правила, три порта
Манифести, scopes, allowed, has_role, touches и can_grant на Rust, TypeScript и Python, като и трите минават едни и същи YAML тестови вектори.
Ключове и токени
RS256 или ES256 върху ring, ключове за подписване, запечатани с ключ за шифроване на ключове и сменяни на всеки 90 дни, refresh токени, които се сменят и разпознават повторна употреба.
Готови страници
Страници за вход, регистрация, акаунт и администрация в бранда на областта, на четири езика, в светла и тъмна тема, под строга Content-Security-Policy.
Готови клиенти
Crate за Rust, Python пакет с FastAPI зависимости и вход тип backend-for-frontend за SvelteKit 2 и 3.
Готов за работа
Статичен образ върху distroless, среда за разработка с пощенски приемник и симулиран Google, както и Helm чарт.

Екранни снимки




Защо съществува
Всеки продукт, който пуска хора вътре, накрая има свои пароли, писма за акаунти, административни страници и таблици с роли, а всеки човек — по един акаунт за всеки продукт. pacod-id поема всичко това от продуктите. Човек влиза веднъж и с един и същ акаунт стига до конзолата на шлюза, платформата за агенти, CMS студиото, базата знания и бенчмарковете. Машините също влизат — като служебни акаунти.
Това е OpenID Connect доставчик за вашите собствени приложения. Всеки клиент е описан в
конфигурацията му: няма динамична регистрация и няма екран за съгласие. Пулът от акаунти е един и
sub на човека е един и същ във всяка област. Между продуктите се различават видът на входа и кой
какво може да прави там — и pacod-id записва и двете.
Области, брандове и клиенти
Областта (realm) е един issuer хост със собствен бранд, бисквитка за вход, езици и правила:
минимална дължина на паролата, продължителност на сесията, дали всеки може да се регистрира и кои
роли управляват акаунтите. pacod-id избира областта по Host на заявката, така че две области
могат да споделят един пул от акаунти и въпреки това да изглеждат като два продукта.
Брандът на областта е един от петте вградени: gateway, cms и kb за продуктите на PACOD,
както и shop и bazaar за измисления продукт, който използват документацията и тестовете. Той
определя знака, надписа и цветовете на страниците и оформлението на писмата. Брандът принадлежи на
областта, а не на клиента: всички приложения в една област изглеждат еднакво.
pacod-demo пуска една област, pacod, за всички продукти
и ѝ дава бранда на шлюза. Затова страниците му за вход и за акаунта носят знака на pacod-gw, от
който и продукт да е дошъл човекът.
Клиентът е едно приложение от един от три вида:
| Вид | Типична употреба | Grants |
|---|---|---|
confidential |
Backend-for-frontend на продукт, който пази тайна | authorization_code, refresh_token |
public |
Нативно или мобилно приложение; кодовете му пази само PKCE | authorization_code, refresh_token |
service |
Сървърът на продукт, който говори с pacod-id | client_credentials, token exchange |
Един вход, стъпка по стъпка
- Продуктът го започва. Сървърът му изпраща браузъра към
/oidc/authorizeсъсstate,nonceи PKCE challenge. Предлага се само authorization code flow, а PKCE (S256) се изисква от всеки клиент, включително от confidential клиентите. - pacod-id проверява заявката. Областта идва от
Host; клиентът трябва да принадлежи на нея, а redirect URI трябва да съвпада точно с регистриран. Докато това не е сигурно, грешката показва страница и никога не пренасочва. - Единен вход или сценарий. Жива сесия на областта получава код веднага. Иначе pacod-id
записва заявката като сценарий за вход за 15 минути, обвързва го с браузъра чрез бисквитка и
изпраща браузъра към своята страница
/login. - Човекът влиза с парола или, където областта го предлага, с Google. Всеки опит заема мястото си в ограниченията в PostgreSQL, преди паролата да бъде хеширана, а непознат адрес получава същия отговор като грешна парола. Ако адресът трябва първо да се потвърди или паролата е за смяна, сценарият минава оттам.
- Сесия и код. Входът създава нова SSO сесия (12 часа или 7 дни с „запомни ме“) и код, който
живее 60 секунди, важи веднъж и е обвързан с клиента, redirect URI и PKCE challenge. Браузърът
се връща в продукта с
code,stateиiss. - Продуктът го осребрява на
/oidc/token, обикновено на вътрешния слушател, и получава access токен, id_token и refresh токен. Техният issuer остава публичният URL на областта.
Токени
- Access токените са JWT (RFC 9068), RS256 или ES256 според клиента, валидни по подразбиране 5 минути (от 2 до 15). Те казват кой, в коя сесия и с какъв scope, но не носят роли.
- id_token живее 5 минути и носи адреса и името, когато scopes ги поискат.
- Refresh токените са непрозрачни и се сменят при всяко използване. Токен, представен отново до 30 секунди, получава същия наследник, така че два раздела могат да се обновят едновременно. Представянето на по-стар прекратява всички сесии на акаунта, съобщава го на всички продукти и изпраща писмо на собственика.
- Служебните токени (
pidt_…) принадлежат на служебни акаунти, показват се веднъж и се пазят като HMAC. - Token exchange (RFC 8693) позволява на услугата на продукт да действа от името на човек, в
рамките на неговите права, като
actпосочва услугата. Делегираният достъп свършва заедно със сесията на човека.
Когато сесия приключи, всеки клиент, който е имал достъп в нея и има back-channel logout URI, получава logout токен, чиято доставка се опитва в продължение на десет минути.
Ролите остават извън токените
Ролите нарочно не влизат в access токените: отнета роля трябва да спре да важи веднага, а не
когато токенът изтече. При всяко стартиране всеки продукт изпраща манифест: своите типове
scopes, своите роли и техните права, и кой какво може да присвоява. Scopes образуват дърво — от
platform, по избор през organisation:<id>, до собствените типове на продукта. Ето манифеста на
шлюза, съкратен:
product: gw
scope_types:
workspace: { parent: organisation }
roles:
platform_admin:
at: [platform]
permissions: ["*"]
protected: true
org_admin:
at: [organisation]
permissions: [org.manage, org.read, workspace.manage, workspace.read,
audit_records.read, audit_log.read, members.manage]
protected: { bypass: platform.manage }
viewer:
at: [platform, organisation, workspace]
permissions: [org.read, workspace.read]
grant:
default: members.manage
platform_admin: platform.manage
Присвояването е роля в един scope, например gw.viewer в gw.workspace:ws_1, и правата ѝ
важат надолу: роля в организация важи във всяко работно пространство под нея. pacod-id пази
присвояванията и проверява всяка промяна. За да се даде роля, е нужно правото за присвояване от
манифеста в този scope (тук администраторът на организация дава роли в своята организация, а само
администратор на платформата прави други администратори на платформата), а роля с protected
никога не губи последния си притежател. Където манифестът го позволява, администраторите на
продукта могат да създават свои роли, които сами не дават права, но се броят за has_role.
Собственият манифест на pacod-id е вграден: id.admin може всичко в pacod-id и е защитена, а
id.auditor чете акаунтите и одитния дневник. Една област може също да посочи роли на продукти,
чиито притежатели управляват регистрираните в нея акаунти; демото посочва администраторите на
продуктите. Такъв администратор управлява само акаунти, всичките роли на които би могъл да даде и
сам, така че администратор на продукт не може да завладее администратор на идентичността.
Механизмът за правила отговаря, наред с други, на allowed, has_role, touches и can_grant.
Той съществува на Rust, TypeScript и Python и и трите минават едни и същи YAML тестови
вектори (policy/engine-vectors/). Векторите са спецификацията, затова едно решение е еднакво,
където и да се взема.
Потокът за оторизация
Всеки продукт пази собствено копие на присвояванията и следи промените:
- При стартиране зарежда моментна снимка: своя каталог, организациите и собствените си scopes,
присвояванията на своите роли и на ролите
id.*, изключените и изтритите акаунти и сесиите, отнети през последните 15 минути — всичко към един пореден номер,seq. - След това чака промените след този
seqс long poll, до 30 секунди на извикване. Отговорът идва веднага щом нещо се промени: PostgreSQLLISTENсъбужда чакащата заявка. - Промените се пазят 7 дни. По-стар курсор получава 410 и продуктът зарежда снимката отново.
{
"seq": 1843,
"events": [
{ "seq": 1843, "kind": "binding.removed", "principal": "usr_0191…", "role": "gw.viewer",
"path": ["platform", "organisation:org_1", "gw.workspace:ws_1"] }
]
}
Събитията засягат присвоявания, акаунти (изключени, включени, изтрити, променени), сесии,
служебни токени, scopes и каталози. Записите отговарят с seq на потока, така че продукт, който
трябва да прочете собствения си запис, изчаква копието му да стигне до този номер. Клиентските
библиотеки за Rust и Python правят всичко това в своя кеш на присвояванията. Access токените носят
sid на сесията, който продуктът отказва, щом потокът съобщи, че е отнета.
Измерено с make measure на една машина за разработка (Docker Desktop на Apple silicon), промяна
стигаше до чакащ последовател с p99 около 10 ms при цел под една секунда. Това е делът на самия
pacod-id: целта на продуктите — две секунди от излизането до отказа — зависи от последователя на
всеки продукт.
Частите
| Част | Какво е |
|---|---|
crates/pacod-id |
Услугата: OpenID Connect доставчик, акаунти, организации, присвояване на роли, потокът за оторизация, поща, одит |
crates/pacod-id-authz |
Механизмът за правила |
crates/pacod-id-jose |
Compact JWS (RS256, ES256) върху ring и JWKS |
crates/pacod-id-client |
Проверка на токени, кеш на присвояванията, introspection и token exchange за продукти на Rust |
python/ |
Пакетът pacod_id: механизмът за правила, проверка на токени, кеш на присвояванията, FastAPI зависимости |
packages/* |
@pacod/id-authz, @pacod/ui, @pacod/id-client, @pacod/id-sveltekit |
ui/ |
Хостваните страници за вход, акаунт и администрация |
Услугата е един изпълним файл на Rust върху axum с три слушателя:
| Слушател | Порт | За |
|---|---|---|
| public | 8070 | Браузъри и приложения, през обратен прокси с TLS: OpenID Connect крайните точки и хостваните страници |
| internal | 8071 | Само сървърите на продуктите: токени, потокът, introspection, прекратяване на сесии, admin API |
| metrics | 9170 | /healthz, /readyz и /metrics |
Хостваните страници са статичен SvelteKit build, който изпълнимият файл сервира сам, с бранда,
езика и темата на областта още от първото изрисуване. Там хората намират профила си, паролата,
устройствата и сесиите, начините за вход, организациите и ролите си; администраторите — акаунтите,
служебните акаунти и одитния дневник. @pacod/ui съдържа дизайн токените, темите на брандовете и
компонентите за вход, които продуктите, ползващи pacod-id, могат да споделят.
Модел на сигурност
Основните мерки от модела на заплахите:
- Пароли: Argon2id на минимума на OWASP (19 MiB, 2 преминавания), по подразбиране 12 знака и списък от 46 528 често срещани пароли, които се отказват. Ограниченията за вход са в PostgreSQL и важат за всички инстанции: за акаунт, за познат браузър и за IP адрес.
- Без проверка дали има акаунт: входът, възстановяването на парола и регистрацията отговарят еднакво, независимо дали адресът има акаунт.
- Ключове за подписване: създадени от pacod-id, запечатани в базата данни с ключ за шифроване
на ключове (
PID_KEK), сменяни на всеки 90 дни, като следващият ключ се публикува ден по-рано. Аварийната смяна е една команда. - Пазени тайни: refresh токените и връзките в писмата като SHA-256, тайните на клиентите и служебните токени като HMAC с отделен pepper. Собствените редове в дневника на pacod-id никога не съдържат парола, токен или ключ и той не пише ред за всяка заявка.
- Сценарии в браузъра: PKCE,
state,nonceиissпри всяко пренасочване; точни redirect URI; Content-Security-Policy от хешовете на скриптовете на страниците; записи само от собствения origin на областта. - Достъп отвън: публичният слушател не обслужва вътрешни и административни маршрути. В пълния
стек на pacod вътрешният слушател е вързан към
pacod-id-internal— псевдоним, който pacod-id има само във вътрешната мрежаpacod-admin-net, в която влизат само сървърите на продуктите. NetworkPolicy на Helm чарта пуска до вътрешния порт само от източниците, които посочите. - Образ: статичен musl изпълним файл върху distroless, без root права и без shell.
Моделът на заплахите изброява и какво остава, всяко отбелязано като отворено или прието: вътрешният слушател говори обикновен HTTP, услугата на всеки продукт може да прекрати всяка сесия, а организациите са общи за продуктите.
Работа с него
Областите и клиентите са в един TOML файл, тайните — никога. Тайната на клиента идва от
променливата, която посочва неговото secret_env, а ${NAME} взема стойност от средата.
[[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.
- Инстанции: колкото и да е, върху една база данни. Всяка изпраща дължимите писма и излизания и презарежда ключове и каталози; една, която държи lease, сменя ключовете и чисти старите редове.
- Поща: никоя заявка не чака SMTP. Писмата влизат в опашка, в бранда и на езиците на областта, и се опитват отново около осем часа.
- Първи администратор: създава се при стартиране от
PID_BOOTSTRAP_ADMIN_*, по подразбиране сid.admin. - Наблюдение:
/readyzпроверява базата данни и ключа за подписване;/metricsдава брой и продължителност на заявките по слушател. Дневниците следват общия формат на pacod — един JSON или текстов ред на събитие. Входове, промени на роли и действия на администратори отиват в одитен дневник, който се пази 400 дни и се чете от административните страници.
Шлюз, който сам е вписвал хората, може да ги предаде с pacod-id import gw: паролите,
административните токени и връзките от поканите продължават да работят, а докато не приключи
прехода, шлюзът може да се върне към собствения си вход, ако тук междувременно нищо не се е
променило.
В стека на pacod
make compose-up стартира среда за разработка: област shop на http://127.0.0.1:26070 с
отворена регистрация, пощенски приемник и заместител на Google, всичко публикувано само на
127.0.0.1. Като всеки проект на pacod, pacod-id влиза в общата Docker мрежа pacod-net, а
настройката му за compose има два файла. docker-compose.yml съдържа само pacod-id;
docker-compose.standalone.yml добавя собствени копия на общите компоненти: PostgreSQL
(pacod-postgres), pacod-db-init, който създава ролята pacod_id и нейната база данни, ако
липсват, пощенски приемник (pacod-mailpit) и симулирания Google (id-mock-idp).
Пълният стек на pacod, pacod-demo, зарежда само първия
файл. Той пуска един PostgreSQL, един пощенски приемник, един edge и един pacod-id за всички
продукти, с една-единствена област и собствените клиенти на продуктите: по един клиент за вход и
един служебен клиент за приложението, шлюза, студиото, базата знания и бенчмарковете. В
platform/ е делът на pacod-id в тази конфигурация: скриптът му за базата данни, маршрутът му в
общия Caddy и задачата му в Prometheus.
Какво е проверено и какво още липсва
make verifyпуска форматиране, линтери, проверки на типовете и всички тестове на Rust, TypeScript и Python. CI освен това проверява зависимостите, изгражда образа, стартира средата за разработка, проверява целите за закъснение и линтва Helm чарта.make e2eпуска хостваните страници и три тестови приложения в истински браузър, с три области, пощенския приемник и симулирания Google, и обхожда всеки бранд в светла и тъмна тема. Пуска се локално, не в CI.- Тестовият пакет за съответствие на OpenID Foundation (v5.3.2) е пускан локално: плановете Config, Basic и RP-Initiated Logout без нито едно неизпълнено условие от 2 370 проверени, а един тест за Back-Channel Logout беше блокиран от самоподписания сертификат на самия пакет. Локалните пускания не са сертифициране.
- pacod-id 0.1.0 още не е издаден. Надграждането, резервното копие и възстановяването не са изпробвани.
- Helm чартът минава
helm lintи проверки на схемите, но още не е инсталиран в клъстер. Истински SMTP сървър и истинският Google не са пробвани, само симулацията от средата за разработка. - Още няма MFA и passkeys, а оттам и claim
acr. Докато ги няма, административните страници действат само по бисквитката на сесията, без да искат скорошен вход. - Няма claims за адрес и телефон; scope profile дава
nameиlocale. - Няма общо ограничение на честотата извън ограниченията за вход, а образите още не са подписани и нямат SBOM.
За още четене
- Runbook: инсталиране и работа.
- HTTP API: всяка крайна точка и събитията на потока.
- Модел на заплахите: мерки и оставащи рискове.
- Измервания: закъснение на крайната точка за токени и на потока.
- Резултати от проверката за съответствие и тестовите вектори на механизма за правила.
Бърз старт
Разработка и тестване
make test-services
make verify
make e2e
Изграждане и стартиране на образа
make image
make compose-up
Работи с
- pacod-gw: С
PGW_AUTH=oidcконзолата вписва хората през pacod-id; ядрото изпраща ролите наgw, регистрира организации и работни пространства и следи потока. - pacod-ap: Приложението и неговата административна конзола влизат през pacod-id; API-то изпраща ролите на
pacodи следи потока, така че приложението и конзолата на шлюза споделят един акаунт. - pacod-cms: Студиото вписва хората през pacod-id, където са ролите им за всяка организация и за всеки сайт.
- pacod-kb: Интерфейсът вписва хората през pacod-id; ролите viewer, editor и admin се предоставят там.
- pacod-benchmark: Интерфейсът вписва хората през pacod-id, а API-то проверява всеки извикващ спрямо своите роли viewer, operator и admin, които изпраща при всяко стартиране.