- Add deployment-name branding to titles, mastheads, and OG tags - Share one grant delivery-state query with lineage across grants surfaces - Show pool status/usage, org owners, and config readiness - Make billing views projection-aware with recency and sync vocabulary - Guard FedWiki creation without domains and render route-aware 404s
6.5 KiB
domain-authorization Specification
Purpose
Decide, per exact fully-qualified domain name, whether the deployment may serve (and mint a TLS certificate for) that name — the console-side answer to a TLS proxy's on-demand ask contract, backed by registered placements rather than filesystem state.
Requirements
Requirement: Serving-authorization endpoint
The system SHALL expose an unauthenticated endpoint GET /domains/ask accepting a
domain query parameter, implementing the on-demand-TLS ask contract: it SHALL respond
200 with an empty body if and only if a servable placement exists at exactly that
fully-qualified domain name, and a non-200 status otherwise — except that when
domains-ask-fallback-url is configured, a name entirely unknown to the registry MAY
be authorized by the fallback answerer (see "Registry misses may be forwarded to a
configured legacy answerer"). Before lookup the system
SHALL normalize the queried name by lowercasing and stripping a single trailing dot. The
check SHALL be an exact-match indexed lookup against domains.placements; the endpoint
SHALL NOT perform hierarchical (ancestor or wildcard) matching — a claim without a
placement at the queried name SHALL NOT authorize serving. A request with a missing or
empty domain parameter SHALL receive a non-200 response.
Scenario: Servable placement is authorized
- WHEN
/domains/ask?domain=alice.example.testis requested and a placement exists atalice.example.testwithservable = true - THEN the response status SHALL be 200
Scenario: Read-only site is still served
- WHEN the placement at the queried domain backs a read-only site (the provider
keeps it
servable = true) - THEN the response status SHALL be 200
Scenario: Archived site is refused
- WHEN the placement at the queried domain has
servable = false(e.g. its site is archived) - THEN the response status SHALL be non-200
Scenario: Unknown domain is refused
- WHEN no placement exists at the queried domain and no fallback is configured
- THEN the response status SHALL be non-200
Scenario: Claimed but unplaced name is not authorized
- WHEN a live claim exists at
alice.example.testwith no placement atblog.alice.example.test, and the query is forblog.alice.example.test - THEN the response status SHALL be non-200
Scenario: Query is normalized before lookup
- WHEN the query is
Alice.Example.Test.and a servable placement exists atalice.example.test - THEN the response status SHALL be 200
Scenario: Missing domain parameter
- WHEN
/domains/askis requested without adomainparameter - THEN the response status SHALL be non-200
Requirement: Authorization is delegated through a provider-neutral seam
The /domains/ask handler SHALL delegate to a narrow authorizer interface owned by the
core server, and SHALL NOT query provider storage directly. The domains registry SHALL
supply the authorizer implementation backed by domains.placements, and the route (with
its authentication and CSRF exemptions) SHALL be registered by core, not by a provider
integration. Replacing the authorizer implementation SHALL NOT change the endpoint's
HTTP contract.
Scenario: Registry-backed authorizer answers the contract
- WHEN the server boots
- THEN
/domains/askSHALL answer according todomains.placementsservability as specified above, with no provider integration involved in the lookup
Scenario: Contract is implementation-independent
- WHEN the authorizer implementation is substituted
- THEN the request/response contract of
/domains/askSHALL remain unchanged
Requirement: Registry misses may be forwarded to a configured legacy answerer
The endpoint SHALL forward registry misses to a configured legacy answerer when the
configuration key domains-ask-fallback-url is set (default empty = disabled). A
"miss" SHALL mean no placement row exists at the queried name: a placement with
servable = false SHALL be refused locally and SHALL NOT consult the fallback (an
archived name must not be resurrected by the legacy answerer). The forwarded request
SHALL be built with URL-library query encoding (never string concatenation of the
attacker-controllable parameter), setting domain to the normalized name, sent with
a short timeout; the endpoint SHALL respond 200 if and only if the fallback answers
200. Names failing FQDN shape validation SHALL be refused without any outbound
request. Registry hits SHALL NOT consult the fallback. Fallback errors or timeouts
SHALL be treated as refusal (fail-closed). This is a migration-window seam (strangler
pattern for deployments moving off filesystem-based answerers); the general
availability posture remains that a console outage stops new certificate issuance
while existing certificates keep serving.
Scenario: Miss forwarded to the fallback
- WHEN the fallback URL is configured, no placement exists at the queried name, and the legacy answerer responds 200 for it
- THEN
/domains/askSHALL respond 200
Scenario: Fallback refusal and errors fail closed
- WHEN the fallback answers non-200, times out, or is unreachable for a registry-miss query
- THEN
/domains/askSHALL respond non-200
Scenario: Unservable placement never falls through
- WHEN the fallback URL is configured and a placement exists at the queried name
with
servable = false - THEN
/domains/askSHALL respond non-200 without consulting the fallback
Scenario: Disabled by default
- WHEN
domains-ask-fallback-urlis unset and no placement exists at the queried name - THEN
/domains/askSHALL respond non-200 without attempting any outbound request
Requirement: Malformed public ask requests render a styled error page
The public domain-ask endpoint SHALL answer malformed browser requests (such as a missing domain parameter) with a styled, human-readable error page — a short plain-language statement of what was missing and what the visitor can do — instead of an unstyled plaintext string. The HTTP status and the machine-facing contract for programmatic callers are unchanged; only the browser-facing rendering is specified here.
Scenario: Missing parameter renders a styled page
- WHEN a visitor's browser requests the public ask endpoint without the required domain parameter
- THEN the response renders a styled page stating in plain language that the link is missing its domain and what to do next
- AND the response does not consist of a bare internal parameter name on an unstyled page