mirror of
https://github.com/block/buzz.git
synced 2026-10-27 12:40:30 +00:00
feat(auth): support explicit mesh JWKS transport (#8382)
## Summary Allow an issuer to opt into mesh HTTP for its JSON Web Key Set (JWKS). Public HTTPS remains the default. This supports deployments whose service mesh authenticates and protects the key endpoint. The sealed source contract selects transport and contributes to policy identity. Mesh mode requires a DNS hostname and checks every resolved address against an explicit allow-list before pinning one address. Both modes disable proxies, reject redirects, cap responses, and enforce a whole-fetch deadline. The relay cannot verify mesh security: operators must enforce mutual TLS, endpoint authorization, and egress restrictions. ### Related issue No duplicate mesh-JWKS issue or PR found. Existing NIP-FI relay enforcement: #7224. No deployment change is included. ### Testing - At `eccb8cb19420abda44cf6e5e62b1361e2843bd1d`: full auth package and auth/relay all-target clippy passed. Full relay package passed: 1,457 library tests, 37 binary tests, and 9 boot tests; 446 tests were ignored. - At prior head `113939838cd81d5e8d0b0f8783c42ffaae44a4d3`: the full relay library suite failed the demo echo test (1,455 passed, 1 failed, 440 ignored). Independent checks reproduced that timeout at base `d61fe2071d2b3d8e56dd931947f16bfe0d6173fe`; the receive-cancellation defect is outside this change. A later pass does not establish that defect is fixed. - Three falsifying mutations at `eccb8cb194` were caught: mixed DNS answers, proxy bypass, and whole-fetch deadline. The full locked auth package passed after restoring the exact source. - At `eccb8cb194`, `cargo build --locked --release -p buzz-relay -p buzz-cli -p buzz-admin` passed with command-local `CARGO_PROFILE_RELEASE_STRIP=none`. This avoids a macOS loader error without a source, lockfile, or project-profile change. - Earlier hooks failed the ACP signal test and Tauri login-shell PATH test. The ACP failure also reproduced on unmodified main `93ffc37c`. Main merged without conflict. The full Tauri package then passed with `RUST_TEST_THREADS=1`: 3,422 library tests and 3 integration tests, 19 ignored. All pre-push hooks passed with that same command-local setting; no hook or test was skipped. - At `eccb8cb194`, isolated `just test` passed all 107 runner steps. The monolithic `just ci` run timed out after 1,120 seconds; all recipes passed across that run and separate `just desktop-tauri-test`, `just web-build`, and `just mobile-test` invocations. No monolithic pass is claimed. - Exact-head release live-local checks passed: loopback HTTP mesh JWKS warm; valid assertion 101; expired/wrong issuer/wrong audience 403; valid signed disconnect 200; wrong command audience 403. Redirect-only JWKS returned assertion 503 without a request to its target. Both omitted/default and explicit public transport rejected HTTP at startup. - A separate NIP-FI-off release CLI smoke test created an open channel, sent an accepted message, read it back, and fetched its thread. The assertion probe closes after upgrade; it does not prove a complete NIP-42 session. Local loopback tests do not prove deployment mesh authentication, endpoint authorization, egress controls, or real `240/4` routing. Generated with Codex ## Human Testing valid assertion / configured community audience: HTTP 101 (expected 101) expired assertion: HTTP 403 (expected 403) wrong issuer: HTTP 403 (expected 403) wrong community audience: HTTP 403 (expected 403) valid command JWT / configured command audience: HTTP 200, body=b'{"disconnected": true}' (expected 200) valid command signature / wrong audience: HTTP 403 (expected 403) --------- Signed-off-by: seiler <seiler@squareup.com> Signed-off-by: Local Builder <1c476cb1127ea270cb900bdc3891060140bdd1173f91498030ee9b4db01c2f20@buzz.block.builderlab.xyz> Co-authored-by: Codex <noreply@openai.com> Co-authored-by: Local Builder <1c476cb1127ea270cb900bdc3891060140bdd1173f91498030ee9b4db01c2f20@buzz.block.builderlab.xyz>
This commit is contained in:
@@ -170,6 +170,9 @@ TransportContractId = H(canonical transport contract)
|
||||
association, audience, token class, allowed algorithms, key-source contract,
|
||||
identity/key/claim mapping, time and size rules, and compiled verifier
|
||||
behavior. JWKS key rotation changes the snapshot, not the policy ID.
|
||||
The key-source contract includes the JWKS transport. A mesh transport tag is
|
||||
encoded only when `jwks_transport` is `mesh`; `public_https` is represented by
|
||||
the absence of that tag.
|
||||
`TransportContractId` covers the client-attached field, parsing, attachment,
|
||||
and no-fallback semantics.
|
||||
|
||||
@@ -228,9 +231,28 @@ regardless of issuer policy.
|
||||
|
||||
Each issuer policy configures:
|
||||
|
||||
- `jwks_uri`: HTTPS URI selecting the authenticated key source. SSRF-protected
|
||||
at both URI validation and DNS-resolution time; no credentials, fragments,
|
||||
or private-IP endpoints accepted.
|
||||
- `jwks_uri`: selects the authenticated key source. With the default
|
||||
`public_https` transport, it MUST use HTTPS and contain no credentials or
|
||||
fragment. DNS address checks and pinning are specified under
|
||||
[Security considerations](#security-considerations).
|
||||
- `jwks_transport`: optional per-issuer transport, defaulting to
|
||||
`public_https`. The explicit value `mesh` permits an HTTP URI only when it
|
||||
uses a DNS hostname rather than an IP literal and has no userinfo, query,
|
||||
fragment, or backslash. The only accepted IPv4 ranges are RFC 1918 (`10/8`,
|
||||
`172.16/12`, and `192.168/16`), CGNAT (`100.64/10`), reserved `240/4` except
|
||||
`255.255.255.255`, and loopback `127/8`. The only accepted IPv6 ranges or
|
||||
addresses are ULA (`fc00::/7`) and `::1`. Every other IPv4 or IPv6 class is
|
||||
rejected. IPv4-mapped, IPv4-compatible, NAT64, 6to4, and SIIT-translated
|
||||
IPv6 addresses are also rejected; embedded IPv4 does not inherit eligibility.
|
||||
Every DNS answer is checked and the first allowed address is pinned. Empty or
|
||||
mixed answers deny the fetch. Loopback is accepted for isolated local use;
|
||||
in production it can reach services on the relay host, so operators MUST
|
||||
constrain endpoint selection and egress accordingly. Mesh transport does not
|
||||
authenticate itself: selecting it is an operator assertion that the
|
||||
deployment enforces mutual TLS peer authentication and integrity protection,
|
||||
endpoint authorization, and egress restrictions. The relay cannot verify
|
||||
those properties. Unauthenticated HTTP permits JWKS key substitution and
|
||||
issuer impersonation. There is no public-network fallback.
|
||||
- `refresh_interval_seconds`: positive, ≤ 1 year, strictly less than
|
||||
`key_snapshot_hard_deadline_seconds`.
|
||||
- `key_snapshot_hard_deadline_seconds`: the outer time bound after which no
|
||||
@@ -974,10 +996,15 @@ gap by requiring assertion verification on every protected HTTP request.
|
||||
The per-request re-verification model means there is no cached admission
|
||||
window; a deny-set entry takes effect on the very next request.
|
||||
|
||||
**SSRF.** The JWKS fetcher MUST enforce SSRF protection: HTTPS-only URI
|
||||
validation, DNS resolution with IP deny-list enforcement, address pinning to
|
||||
prevent DNS rebinding TOCTOU, and redirect denial. The IP deny-list covers
|
||||
the complete IANA Special-Purpose address registry.
|
||||
**JWKS SSRF.** `public_https` MUST validate an HTTPS URI, reject every
|
||||
resolved address on its IP deny-list, pin the checked address to prevent DNS
|
||||
rebinding, and deny redirects. The IP deny-list covers the complete IANA
|
||||
Special-Purpose address registry. The `mesh` address allow-list and operator
|
||||
security boundary are defined in the [JWKS snapshot contract](#jwks-snapshot).
|
||||
Both modes MUST resolve once, validate every answer before pinning the first
|
||||
permitted address, disable proxies, deny redirects, enforce a 10-second
|
||||
whole-fetch deadline, cap the streamed response at 512 KiB, reject non-2xx
|
||||
responses, and provide no fallback network path.
|
||||
|
||||
**Issuer compromise.** A compromised assertion issuer can impersonate any
|
||||
identity but cannot prove possession of the assertion-named Nostr key. The NIP-42
|
||||
|
||||
Reference in New Issue
Block a user