diff --git a/crates/didbot-dns/src/lib.rs b/crates/didbot-dns/src/lib.rs index 4fdf5c10..10b1c691 100644 --- a/crates/didbot-dns/src/lib.rs +++ b/crates/didbot-dns/src/lib.rs @@ -729,11 +729,12 @@ impl DnsProvider for WildcardDns { /// Why a set of per-zone providers could not be assembled. /// -/// Mirrors `didbot_identity::ZoneRegistryError`: a server managing several -/// zones needs the DNS side to refuse the same overlap the identity side -/// does, for the same reason. Its own type rather than a re-export, because -/// this crate deliberately does not depend on how a zone was validated — -/// only on the hostname string it was validated to. +/// Refuses a nested pair of zones, which is a stricter rule than +/// `didbot_identity::ZoneRegistry` applies today — see that type's own docs +/// for why nesting there is not the namespace collision it looks like. This +/// crate has not been revisited to match; its own type rather than a +/// re-export, because this crate deliberately does not depend on how a zone +/// was validated — only on the hostname string it was validated to. #[derive(Debug, Clone, PartialEq, Eq, thiserror::Error)] pub enum MultiZoneDnsError { /// No providers were offered at all. diff --git a/docs/deployment.md b/docs/deployment.md index a49fe616..d01439d0 100644 --- a/docs/deployment.md +++ b/docs/deployment.md @@ -9,10 +9,11 @@ One zone, used for everything: the server, the agent handles, the authority serving every `did.json`, and the zone named in the owner's ownership record. HTTPS throughout. -The server sits at the apex and agents in a subzone beneath it. Two rules force -that: an agent's hostname must sit at or below the server's own, so the -relationship is checkable from the identifier alone; and agents must be off the -apex, so a wildcard certificate cannot shadow the server's own name. +The server sits at the apex and agents in a subzone beneath it. Agents are off +the apex so a wildcard certificate cannot shadow the server's own name. The +zone does not have to sit at or below the server's own hostname — which server +serves a given agent is answered by resolving the agent's DID document, not by +comparing hostnames — but the recommended layout keeps them together. Two hostnames per account — one for the DID, one for the handle — and both must resolve before the account exists. @@ -23,6 +24,10 @@ Control of the zone is control of every DID under it. Whoever holds it can serve their own documents and be any agent, and can pass the DNS-01 challenge for a certificate that looks correct. +Whatever DNS zone this server is given access to, it has full ownership of +that zone; and if that zone contains the server's own hostname, it can also +re-point that name and obtain certificates for it. + It is as powerful as the signing keys, and it lives beside them. The server writes its own records — per-agent hostnames, and the `_acme-challenge` TXT records its certificate depends on — so the credential has to be reachable from diff --git a/plan/adversarial.md b/plan/adversarial.md index 7adebf17..36e11cae 100644 --- a/plan/adversarial.md +++ b/plan/adversarial.md @@ -18,11 +18,11 @@ invariants are the most valuable kind. Three targets already have unit tests, and unit tests are not what is missing: what is missing is a test at the seam between the components each one assumes will hold up its end. -- **Zone containment** has unit coverage in - `crates/didbot-identity/tests/identity.rs` - (`containment_respects_label_boundaries`, - `containment_is_case_insensitive`, - `containment_holds_for_the_development_shape`) and in vibescrobble.com's +- **Zone containment** — narrower than it once was, now that a zone need not + sit at or below the PDS's own hostname (see `didbot_identity::Zone`'s + docs) — has unit coverage in `crates/didbot-identity/tests/identity.rs` + (`hostname_is_at_or_below_respects_label_boundaries`, + `hostname_is_at_or_below_is_case_insensitive`) and in vibescrobble.com's index crate (`a_suffix_is_not_containment`, `a_handle_suffix_is_not_containment_either`). Each tests containment as a standalone function against a string. Neither drives a request through a @@ -49,9 +49,16 @@ the seam between the components each one assumes will hold up its end. ## Zone containment: the seam between resolution, serving and vouching -A sibling zone (`evilfoo.bar` against `foo.bar`) and a suffix sharing no -label boundary are the two shapes `containment_respects_label_boundaries` and -`a_suffix_is_not_containment` already refuse in isolation. +The containment left to test is not "is this zone under the PDS's own +hostname" — that rule is gone — but two narrower ones: a requested handle +must sit at or below the zone the deployment mints under, and the index +crate must not credit a server with an agent whose DID sits outside what a +vouch actually covers. A suffix sharing no label boundary +(`agents.localhost.attacker.com` against `agents.localhost`) is the shape +`hostname_is_at_or_below_respects_label_boundaries` and +`a_suffix_is_not_containment` already refuse in isolation, and is what the +handle-forgery tests in `crates/didbot/tests/zone_containment.rs` drive +through the real HTTP surface. - [ ] **The two containment checks agree.** `didbot-identity` and vibescrobble.com's index each implement containment against the rule diff --git a/plan/agent-sites.md b/plan/agent-sites.md index 3de35d77..99ab87dd 100644 --- a/plan/agent-sites.md +++ b/plan/agent-sites.md @@ -33,11 +33,10 @@ on. ## The origin problem -[deployment](../docs/deployment.md) fixes the layout: the server at the apex, -agents in a subzone beneath it, because an agent's hostname must be checkable -against the server's from the identifier alone. That makes every agent host a -sibling of the server's own under one registrable domain, which is fine while -the only thing served at an agent host is a document a resolver fetches. +[deployment](../docs/deployment.md) recommends the layout: the server at the +apex, agents in a subzone beneath it. That makes every agent host a sibling of +the server's own under one registrable domain, which is fine while the only +thing served at an agent host is a document a resolver fetches. It stops being fine when a browser is involved. Cookies are scoped by domain rather than by origin, and any host may set one for a parent domain that is not @@ -46,8 +45,9 @@ origin is handed back. [oauth](oauth.md), [ops-dashboard](ops-dashboard.md) and [policy-dashboard](policy-dashboard.md) are all browser sessions in that zone. The fix is a separate registrable domain for agent-authored content, and it -contradicts the containment rule that put agents inside the server's zone. One -of the two gives, and this is the epic that has to say which. +contradicts the recommended layout that put agents inside the server's own +registrable domain. One of the two gives, and this is the epic that has to say +which. [site](site.md) sits next to this problem without answering it: did.bot is the marketing origin, safe on the apex domain precisely because it has no diff --git a/plan/deploy.md b/plan/deploy.md index dc927b0d..a4d920e8 100644 --- a/plan/deploy.md +++ b/plan/deploy.md @@ -151,9 +151,9 @@ that had not landed as this revision was written. fail quietly" above. - [ ] **The zone layout, in the server rather than in Terraform.** The server at the apex, agents at `*.` for every zone the deployment - is configured with, following [agent-accounts](agent-accounts.md)'s - containment rule — unchanged in shape, but no longer Terraform's to - enforce. An earlier revision of this stack had `acm.tf`/`route53.tf`/ + is configured with — the recommended shape + [agent-accounts](agent-accounts.md) describes, unenforced but unchanged + by this move, and no longer Terraform's to enforce. An earlier revision of this stack had `acm.tf`/`route53.tf`/ `alb.tf` create a certificate and a wildcard `ALIAS` record per zone, `for_each` over `var.zones`; `acm.tf` and `alb.tf` are gone and `route53.tf` now creates only the hosted zone. Which zones exist and