Два прозореца със страницата Accounts на pacod-id на id.demo.pacod.io, светъл и тъмен, един върху друг, всеки със заглавие pacod-gw · AI Gateway, с връзки за акаунта и администрацията вляво, търсене във всички акаунти, ред за admin@pacod.demo с бутон Send reset link и форма Create an account.
Екранът с акаунти в администрацията на pacod-id през деня и през нощта: намерете всеки акаунт, вижте кога е влизал за последно, изпратете връзка за нова парола или създайте акаунт. Страниците носят бранда на своята област, а единствената област на демото за всички продукти е с бранда на шлюза, затова и знакът на pacod-gw.

pacod-id е OpenID Connect доставчикът зад продуктите на PACOD: един общ пул от акаунти, готови страници за вход и за акаунта, организации и присвоявания на роли. Токените му казват кой е човекът, а не какво може: всеки продукт следи поток от присвояванията на роли и сам решава за достъпа, с един механизъм за правила на Rust, TypeScript и Python.

Идентичност и достъп

Възможности

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 и приглушени останали проекти; златисти пунктирни линии го свързват с pacod-ap, pacod-cms, pacod-kb и pacod-gw.
Мястото на pacod-id. pacod-ap, pacod-cms, pacod-kb и конзолата на pacod-gw вписват хората чрез него и вземат ролите им от него (златисти точки). Приглушено е останалото от платформата: обръщенията към LLM през pacod-gw (зелено), хората, които използват продуктите (сиво), и pacod-benchmark, който измерва шлюза (коралово).

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

Светла карта за вход на id.demo.pacod.io със знака GW, думите Sign in и Welcome back, полета за имейл и парола, отметка Keep me signed in, зелен бутон Sign in и връзка Forgot your password; под нея изборът на език и тема и думите PACOD-GW · AI GATEWAY.
Хостваната страница за вход, към която всеки продукт изпраща хората си, на четири езика и в светла, тъмна или системната тема. Тя носи бранда на своята област: демото има една област за всички продукти, с бранда на шлюза, и няма връзка за регистрация, защото акаунтите му идват с покана.
Светлата страница Organisations and roles с таблица Your roles от седем реда, всеки Everywhere и с дата Oct 27, 2026; ролите са admin, superuser, admin, platform admin, platform admin, admin и admin за продуктите id, pacod, pacod, gw, cms, kb и bench.
Организации и роли, както човек вижда своите. Администраторът на демото има собствената администраторска роля на pacod-id и администраторска роля във всеки продукт (pacod е на pacod-ap, bench на pacod-benchmark), всяка на ниво платформа, показано като Everywhere. Всеки продукт чете тези присвоявания от потока.
Тъмната страница Audit log със заглавие pacod-gw · AI Gateway, с филтри Action, By и Concerning над списък от записи login_succeeded, всеки с часа си, идентификатор на акаунт, адрес на клиента и засегнатия акаунт.
Одитният дневник в тъмната тема: входове и неуспешни опити, изходи, промени по акаунти, роли, покани, токени и ключове, всяка с това кой, за кого и от кой адрес. Пази се 400 дни, филтрира се по действие, извършител и засегнат и се чете от притежателите на id.admin и id.auditor.
Светлата страница Password and security със заглавие pacod-gw · AI Gateway, с форма Change your password за текущата и новата парола и бутон Save, и панел Delete your account, който иска да се въведе адресът admin@pacod.demo за потвърждение.
Парола и сигурност, една от страниците на акаунта, които има всеки. Нова парола излиза от другите устройства; изтриването на акаунта, след вход в последните десет минути, го премахва от pacod-id и казва на всеки продукт да изтрие каквото пази за човека.

Защо съществува

Всеки продукт, който пуска хора вътре, накрая има свои пароли, писма за акаунти, административни страници и таблици с роли, а всеки човек — по един акаунт за всеки продукт. 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

Един вход, стъпка по стъпка

  1. Продуктът го започва. Сървърът му изпраща браузъра към /oidc/authorize със state, nonce и PKCE challenge. Предлага се само authorization code flow, а PKCE (S256) се изисква от всеки клиент, включително от confidential клиентите.
  2. pacod-id проверява заявката. Областта идва от Host; клиентът трябва да принадлежи на нея, а redirect URI трябва да съвпада точно с регистриран. Докато това не е сигурно, грешката показва страница и никога не пренасочва.
  3. Единен вход или сценарий. Жива сесия на областта получава код веднага. Иначе pacod-id записва заявката като сценарий за вход за 15 минути, обвързва го с браузъра чрез бисквитка и изпраща браузъра към своята страница /login.
  4. Човекът влиза с парола или, където областта го предлага, с Google. Всеки опит заема мястото си в ограниченията в PostgreSQL, преди паролата да бъде хеширана, а непознат адрес получава същия отговор като грешна парола. Ако адресът трябва първо да се потвърди или паролата е за смяна, сценарият минава оттам.
  5. Сесия и код. Входът създава нова SSO сесия (12 часа или 7 дни с „запомни ме“) и код, който живее 60 секунди, важи веднъж и е обвързан с клиента, redirect URI и PKCE challenge. Браузърът се връща в продукта с code, state и iss.
  6. Продуктът го осребрява на /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/). Векторите са спецификацията, затова едно решение е еднакво, където и да се взема.

Потокът за оторизация

Всеки продукт пази собствено копие на присвояванията и следи промените:

  1. При стартиране зарежда моментна снимка: своя каталог, организациите и собствените си scopes, присвояванията на своите роли и на ролите id.*, изключените и изтритите акаунти и сесиите, отнети през последните 15 минути — всичко към един пореден номер, seq.
  2. След това чака промените след този seq с long poll, до 30 секунди на извикване. Отговорът идва веднага щом нещо се промени: PostgreSQL LISTEN събужда чакащата заявка.
  3. Промените се пазят 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.

За още четене

Бърз старт

Разработка и тестване

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, които изпраща при всяко стартиране.