web-sign-in — Nasazené povrchy vědí, kdo se ptá, a poskytovatelé identity jsou data
Jeden kanonický druh poskytovatele identity v .ai/repo/identity/providers/ popisuje každého providera jako data: jeho vydavatele, jeho endpointy nebo discovery dokument, požadované scopes, claim nesoucí stabilní subjekt a názvy proměnných prostředí, ve kterých přicházejí jeho přihlašovací údaje. Jediný tok ve stylu OpenID Connect ve spustitelném souboru obslouží každého providera v tomto adresáři; Google a Facebook jsou dva objekty, ne dvě cesty v kódu. Relace jsou nesené cookie, podepsané, krátké a odvolatelné; identita se k požadavku připojí jednou, v routeru, a každý povrch (HTTP, Cockpit, OpenAPI, MCP) ji čte ze stejného místa. O tom, co smí anonymní čtenář, rozhoduje klasifikace efektů v registru schopností, ne seznam pro jednotlivé routy, a dokument OpenAPI i Cockpit toto rozhodnutí uvádějí, protože ho oba projektují z registru. Vyškrtnout providera znamená smazat jeden soubor a znovu vygenerovat.
PLÁNOVÁNO 0 z 8 úkolů hotovo
- připraveno
- 1
- blokováno
- 7
- aktivní
- 0
- verify
- 0
- hotovo
- 0
- zrušeno
- 0
Problém
Vše, co tento repozitář poskytuje, se poskytuje každému, kdo dosáhne na port. To bylo bezpečné, dokud server vázal loopback a jediným čtenářem byl člověk stojící před checkoutem, a přestane to být bezpečné ve chvíli, kdy milník nasazení postaví před stejný router veřejnou URL HTTPS: Cockpit, API, dokument OpenAPI a povrch MCP by byly čitelné každému, kdo hosta najde, a žádná měnící schopnost by nikdy nemohla být vystavena přes prohlížeč, protože by nebylo komu změnu přiřadit. Zřejmá náprava je špatná dvakrát. Ručně psaní klienti poskytovatelů pro Google a Facebook by byly dvě implementace jednoho toku a zkompilovaný seznam toho, kteří poskytovatelé existují, by vložil rozhodnutí o identitě do zdrojového kódu v Rustu, kde každé jiné rozhodnutí tohoto repozitáře je deklarativním objektem pod .ai/repo/. Očekává se, že sada poskytovatelů se změní — oba první dva mohou být vyškrtnuty a nahrazeny, jakmile se zná publikum — a změna takového druhu musí být úpravou jednoho objektu, nikoli releasem spustitelného souboru.
Výsledek
Jeden kanonický druh poskytovatele identity v .ai/repo/identity/providers/ popisuje každého providera jako data: jeho vydavatele, jeho endpointy nebo discovery dokument, požadované scopes, claim nesoucí stabilní subjekt a názvy proměnných prostředí, ve kterých přicházejí jeho přihlašovací údaje. Jediný tok ve stylu OpenID Connect ve spustitelném souboru obslouží každého providera v tomto adresáři; Google a Facebook jsou dva objekty, ne dvě cesty v kódu. Relace jsou nesené cookie, podepsané, krátké a odvolatelné; identita se k požadavku připojí jednou, v routeru, a každý povrch (HTTP, Cockpit, OpenAPI, MCP) ji čte ze stejného místa. O tom, co smí anonymní čtenář, rozhoduje klasifikace efektů v registru schopností, ne seznam pro jednotlivé routy, a dokument OpenAPI i Cockpit toto rozhodnutí uvádějí, protože ho oba projektují z registru. Vyškrtnout providera znamená smazat jeden soubor a znovu vygenerovat.
Aktuální stav
Neexistuje žádná autentizace jakéhokoli druhu: žádná relace, cookie, typ identity, přihlašovací route, práce s přihlašovacími údaji ani pojem volajícího nikde v apps/majordomus-cli/src/http. Soubor lease zaznamenává URL a server varuje, když se naváže na cokoli jiného než loopback. Milník nasazení (fly-deployment) uvádí jako mimo rozsah, že mutující operace nasazení nesmějí být vystaveny neautentizovanému HTTP ani prohlížeči, což je omezení, které tento milník odstraňuje.
Cílový stav
Čtenáři, který otevře veřejnou URL, se poskytují veřejné povrchy jen pro čtení tak, jak jsou dnes. Čtenář, který se přihlásí kterýmkoli poskytovatelem deklarovaným v .ai/repo/identity/providers/, dostane relaci, je jmenován v Cockpit a smí vyvolat schopnosti, které politika efektů přihlášenému čtenáři dovoluje vyvolat. Přidání třetího poskytovatele je jeden soubor YAML; odstranění prvních dvou je smazání dvou souborů; ani jedno není změna kódu a vygenerovaná dokumentace, nabídka přihlášení v Cockpit a bezpečnostní schéma OpenAPI vše vyplývá ze stejných objektů.
Rozsah
- .ai/repo/identity
- .ai/repo/adrs
- .ai/repo/rules/project
- .ai/repo/policy.yaml
- apps/majordomus-cli/src/identity
- apps/majordomus-cli/src/capability/builtin
- apps/majordomus-cli/src/http
- apps/majordomus-cli/src/cockpit
- share/kinds.yaml
- share/allow
- share/schemas
- dokumentace
- web
- test/cases
- .github/workflows
Mimo rozsah
- Databáze uživatelů, úložiště profilů nebo jakýkoli záznam o čtenáři nad rámec relace potřebné k odpovědi na další požadavek
- Hesla, obnova hesla, ověření e-mailu nebo jakékoli přihlašovací údaje, které by tento repozitář musel sám ukládat
- Kódová cesta, klientská knihovna nebo SDK specifické pro poskytovatele pro Google, Facebook nebo jakéhokoli nástupce
- Autorizační role, skupiny nebo jazyk řízení přístupu nad rámec toho, co už rozhoduje klasifikace efektů registru schopností
- Jakýkoli přihlašovací údaj, tajemství klienta nebo token commitnutý do repozitáře, zapsaný do generovaného artefaktu, vykreslený do webu nebo zalogovaný
- Vystavení mutujících schopností přihlášenému čtenáři dříve, než klasifikace efektů řekne, které je vůbec bezpečné vystavit
- Přihlášení k lokálnímu vývojovému serveru jako požadavek; loopback zůstává otevřený, takže stávající pracovní postup se nemění
Kritéria přijetí
- Každý fakt o provideru — vydavatel, endpointy, scopes, subject claim, jména proměnných s přihlašovacími údaji, zobrazované jméno, pořadí — žije v jednom objektu pod .ai/repo/identity/providers/ a nikde jinde
- Google a Facebook jsou každý jedním takovým objektem a spustitelný soubor neobsahuje žádný identifikátor, endpoint ani větev jmenující kteréhokoli z nich
- Smazání objektu poskytovatele odstraní poskytovatele z nabídky přihlášení, z generované dokumentace, ze schématu zabezpečení OpenAPI i z Cockpitu, bez další úpravy
- Tok ověřuje state a nonce, odmítá callback, jehož state nevydal, připíná relaci k vydavateli a subjektu a odmítá poskytovatele, jehož přihlašovací údaje chybí, místo aby spustil tok, který nemůže dokončit
- Cookie relace je podepsané, HttpOnly, omezené SameSite, Secure, když se povrch poskytuje přes HTTPS, a vyprší; odhlášení ho odvolá na straně serveru, nejen smaže v prohlížeči
- Identita se rozhodne jednou v routeru a každý povrch ji čte z tohoto jediného místa; žádný povrch nezpracovává cookie podruhé
- Co smí anonymní čtenář dělat, se odvozuje z klasifikace efektů registru schopností, ne ze seznamu routes
- Žádný tajný sentinel umístěný v proměnných prostředí s přihlašovacími údaji se neobjeví v žádné odpovědi, generovaném artefaktu, řádku logu, cache, výstupu webu ani příkladu OpenAPI
- Dokumentace uvádí, jak poskytovatele přidat a jak odebrat, a obě tvrzení provádí test, nejsou jen napsána
Validace
- bash test/run.sh
- bin/majordomus doctor
- cargo test --workspace --all-features
- cargo clippy --workspace --all-targets --all-features -- -D warnings
- majordomus generate --check
Požadované důkazy
- provider_objects_canonical
- provider_removal_proved
- flow_hardened
- session_hardened
- single_identity_resolution
- effect_derived_exposure
- no_secret_leakage
- documented_and_tested
Rizika
- Tok OpenID Connect napsaný dvakrát — jednou pro poskytovatele, který odpovídá standardu, a jednou pro toho, který ne — je selhání, kterému tento milník existuje zabránit. Endpointy Facebooku se od Googlových liší pravopisem, ne tvarem; cokoli, co se skutečně nedá vyjádřit jako data o jednom toku, je defekt ve schématu objektu poskytovatele a opravuje se tam.
- Přístupové údaje jsou jedinou částí, která nemůže být daty v repozitáři. Objekty jmenují proměnné prostředí; hodnoty přicházejí z úložiště tajemství nasazení a nikdy se nečtou do žádné struktury, která se serializuje, cachuje nebo vykresluje.
- Přihlášení zve k plíživému rozšiřování rozsahu směrem k účtům, profilům a rolím. Relace existuje, aby odpověděla na další požadavek, a nic víc; cokoli dalšího patří milníku, který pro to má důvod.
- Množina poskytovatelů se očekává ke škrtání. Návrh, který činí přidání Googlu a Facebooku levným, ale odebrání drahým, selhal, i když funguje.
Úkoly
Každý z nich je prováděcí kontrakt s vlastními kritérii přijetí, validačním příkazem a požadovanými důkazy. Sloupec wave je vrstva grafu závislostí, v níž úkol leží.
| issue | status | vlna | závisí na | název |
|---|---|---|---|---|
| I1201 | PŘIPRAVENO | 0 | — | Poskytovatel identity je deklarativní objekt a Google a Facebook jsou dva z nich |
| I1202 | BLOKOVÁNO | 1 | I1201 | Jeden typovaný model providerů čte objekty a žádný kód nejmenuje providera |
| I1203 | BLOKOVÁNO | 2 | I1202 | Jeden flow obslouží každého providera a odmítne vše, co nezahájil |
| I1204 | BLOKOVÁNO | 3 | I1203 | Relace je podepsaná, krátká a z pohledu serveru odvolatelná |
| I1205 | BLOKOVÁNO | 4 | I1204 | Identita se řeší jednou, v routeru, a každý povrch ji odtud čte |
| I1206 | BLOKOVÁNO | 5 | I1205 | Co smí anonymní čtenář, se odvozuje z klasifikace efektů, ne ze seznamu |
| I1207 | BLOKOVÁNO | 4 | I1204 | Žádný credential se nedostane na žádný výstup a nepřátelská metadata providera se vykreslí bezpečně |
| I1208 | BLOKOVÁNO | 6 | I1202, I1206 | Přidat providera je jeden soubor, odebrat ho je jedno smazání a obojí se provádí, nejen popisuje |
Graf
flowchart LR
I1201["I1201<br/>An identity provider is a declarative object, and Google and Facebook are two of them"]:::ready
I1202["I1202<br/>One typed provider model reads the objects, and no code names a provider"]:::blocked
I1203["I1203<br/>One flow serves every provider, and it refuses everything it did not start"]:::blocked
I1204["I1204<br/>The session is signed, short, and revocable from the server"]:::blocked
I1205["I1205<br/>Identity is resolved once, in the router, and every surface reads it from there"]:::blocked
I1206["I1206<br/>What an anonymous reader may do is derived from the effect classification, not from a list"]:::blocked
I1207["I1207<br/>No credential reaches any output, and hostile provider metadata renders safely"]:::blocked
I1208["I1208<br/>Adding a provider is one file, removing one is one deletion, and both are executed rather than described"]:::blocked
I1201 --> I1202
I1202 --> I1203
I1202 --> I1208
I1203 --> I1204
I1204 --> I1205
I1204 --> I1207
I1205 --> I1206
I1206 --> I1208
classDef done stroke:#16a34a,fill:#dcfce7,color:#14532d,stroke-width:2px
classDef active stroke:#2563eb,fill:#eff6ff,color:#1e3a8a,stroke-width:2px
classDef verify stroke:#7c3aed,fill:#f5f3ff,color:#4c1d95,stroke-width:2px
classDef ready stroke:#0891b2,fill:#ecfeff,color:#164e63,stroke-width:2px
classDef planned stroke:#0891b2,fill:#ecfeff,color:#164e63,stroke-width:2px
classDef blocked stroke:#b45309,fill:#fffbeb,color:#78350f,stroke-width:2px
classDef cancelled stroke:#6b7280,fill:#f9fafb,color:#374151,stroke-width:2px
classDef superseded stroke:#6b7280,fill:#f9fafb,color:#374151,stroke-width:2pxOdkazováno z tohoto diagramu (8)
- I1201 Poskytovatel identity je deklarativní objekt a Google a Facebook jsou dva z nich
- I1202 Jeden typovaný model providerů čte objekty a žádný kód nejmenuje providera
- I1203 Jeden tok obslouží každého providera a odmítne všechno, co nezahájil
- I1204 Relace je podepsaná, krátká a server ji může odvolat
- I1205 Identita se řeší jednou, v routeru, a každý povrch ji čte odtud
- I1206 Co smí anonymní čtenář, se odvozuje z klasifikace účinků, ne ze seznamu
- I1207 Žádný přihlašovací údaj nedosáhne žádného výstupu a nepřátelská metadata providera se vykreslují bezpečně
- I1208 Přidání providera je jeden soubor, odebrání jedno smazání a obojí se provádí, ne popisuje
Kanonický záznam: .ai/repo/project/milestones/web-sign-in.yaml. Přečtěte ho zpět pomocí majordomus plan show web-sign-in.