Files
buzz/crates/buzz-auth/src/nip_fi/startup/tests.rs
Brad Seiler 2d0f78c6e1 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>
2026-10-16 14:26:08 -04:00

250 lines
8.0 KiB
Rust

use super::*;
use crate::nip_fi::config::{FreshnessClass, IssuerPolicy, IssuerRegistry, TokenClass};
use crate::nip_fi::jwks::{IssuerJwksConfig, JwksSourceContract};
use jsonwebtoken::Algorithm as JwtAlgorithm;
fn test_contract(issuer: &str) -> JwksSourceContract {
// Build a canonical JWKS URI from the issuer URL. The issuer may already
// be a full HTTPS URL (e.g. "https://id.example") or a bare hostname.
let uri = if issuer.starts_with("https://") {
format!("{}/.well-known/jwks.json", issuer.trim_end_matches('/'))
} else {
format!("https://{}/.well-known/jwks.json", issuer)
};
JwksSourceContract::new(uri, 300, 3600).expect("valid test contract")
}
fn make_offline_policy(issuer: &str) -> IssuerPolicy {
IssuerPolicy::new(
issuer.to_owned(),
vec![format!("https://relay.example/api")],
TokenClass::DedicatedNipFi,
FreshnessClass::OfflineJwt,
vec![JwtAlgorithm::ES256],
0,
3600,
None,
test_contract(issuer),
)
.unwrap()
}
fn make_status_policy(issuer: &str) -> IssuerPolicy {
IssuerPolicy::new(
issuer.to_owned(),
vec![format!("https://relay.example/api")],
TokenClass::DedicatedNipFi,
FreshnessClass::CurrentStatus,
vec![JwtAlgorithm::ES256],
0,
3600,
Some(60),
test_contract(issuer),
)
.unwrap()
}
fn make_jwks_config(issuer: &str) -> IssuerJwksConfig {
IssuerJwksConfig {
issuer: issuer.to_owned(),
contract: test_contract(issuer),
}
}
#[test]
fn off_mode_accepts_empty_registry() {
let registry = IssuerRegistry::new();
assert!(validate_nip_fi_config(NipFiMode::Off, &registry, &[]).is_ok());
}
#[test]
fn deny_protected_mode_accepts_empty_registry() {
let registry = IssuerRegistry::new();
assert!(validate_nip_fi_config(NipFiMode::DenyProtected, &registry, &[]).is_ok());
}
#[test]
fn enforce_valid_config_passes() {
let issuer = "https://id.example";
let mut registry = IssuerRegistry::new();
registry.insert(make_offline_policy(issuer));
assert!(
validate_nip_fi_config(NipFiMode::Enforce, &registry, &[make_jwks_config(issuer)]).is_ok()
);
}
#[test]
fn enforce_multiple_issuers_passes() {
let issuers = [
"https://a.example",
"https://b.example",
"https://c.example",
];
let mut registry = IssuerRegistry::new();
for iss in &issuers {
registry.insert(make_offline_policy(iss));
}
let jwks: Vec<_> = issuers.iter().map(|i| make_jwks_config(i)).collect();
assert!(validate_nip_fi_config(NipFiMode::Enforce, &registry, &jwks).is_ok());
}
#[test]
fn enforce_empty_registry_rejects() {
let registry = IssuerRegistry::new();
let err = validate_nip_fi_config(NipFiMode::Enforce, &registry, &[]).unwrap_err();
assert_eq!(err, NipFiStartupError::EmptyRegistry);
}
#[test]
fn enforce_issuer_without_jwks_rejects() {
let issuer = "https://id.example";
let mut registry = IssuerRegistry::new();
registry.insert(make_offline_policy(issuer));
let err = validate_nip_fi_config(NipFiMode::Enforce, &registry, &[]).unwrap_err();
assert_eq!(err, NipFiStartupError::MissingJwksConfig);
}
#[test]
fn enforce_unmatched_jwks_config_rejects() {
let issuer = "https://id.example";
let mut registry = IssuerRegistry::new();
registry.insert(make_offline_policy(issuer));
let err = validate_nip_fi_config(
NipFiMode::Enforce,
&registry,
&[make_jwks_config("https://other.example")],
)
.unwrap_err();
assert_eq!(err, NipFiStartupError::UnmatchedJwksConfig);
}
/// A JWKS config whose contract differs from the policy contract must be
/// rejected — a mismatch means two independent copies of URI/timing have
/// drifted, violating the single-source-of-truth invariant.
#[test]
fn enforce_jwks_contract_mismatch_rejects() {
let issuer = "https://id.example";
let mut registry = IssuerRegistry::new();
registry.insert(make_offline_policy(issuer));
// Config carries a different refresh interval than the policy (300 vs 600).
let mismatched_config = IssuerJwksConfig {
issuer: issuer.to_owned(),
contract: JwksSourceContract::new(
format!("{}/.well-known/jwks.json", issuer.trim_end_matches('/')),
600, // differs from policy contract (300)
3600,
)
.unwrap(),
};
assert_eq!(
validate_nip_fi_config(NipFiMode::Enforce, &registry, &[mismatched_config]).unwrap_err(),
NipFiStartupError::JwksContractMismatch
);
}
#[test]
fn enforce_policy_and_jwks_source_contract_mismatch_rejects() {
let issuer = "https://id.example";
let mesh_contract = JwksSourceContract::new_mesh(
"http://mesh.example.test/.well-known/jwks.json".to_owned(),
300,
3600,
)
.expect("valid mesh contract");
let mesh_policy = IssuerPolicy::new(
issuer.to_owned(),
vec!["https://relay.example/api".to_owned()],
TokenClass::DedicatedNipFi,
FreshnessClass::OfflineJwt,
vec![JwtAlgorithm::ES256],
0,
3600,
None,
mesh_contract,
)
.expect("valid mesh policy");
let mut registry = IssuerRegistry::new();
registry.insert(mesh_policy);
let error = validate_nip_fi_config(NipFiMode::Enforce, &registry, &[make_jwks_config(issuer)])
.expect_err("policy and JWKS source contracts must match");
assert_eq!(error, NipFiStartupError::JwksContractMismatch);
}
/// Rejected regardless of whether a JWKS config is present — the verifier
/// has no status witness to satisfy the freshness guarantee.
#[test]
fn enforce_current_status_policy_always_rejects() {
let issuer = "https://id.example";
let mut registry = IssuerRegistry::new();
registry.insert(make_status_policy(issuer));
assert_eq!(
validate_nip_fi_config(NipFiMode::Enforce, &registry, &[]).unwrap_err(),
NipFiStartupError::UnsupportedPosture
);
assert_eq!(
validate_nip_fi_config(NipFiMode::Enforce, &registry, &[make_jwks_config(issuer)])
.unwrap_err(),
NipFiStartupError::UnsupportedPosture
);
}
/// Duplicate JWKS configs for the same issuer must not silently succeed.
#[test]
fn enforce_duplicate_jwks_issuer_in_configs_rejects() {
let issuer = "https://id.example";
let mut registry = IssuerRegistry::new();
registry.insert(make_offline_policy(issuer));
let jwks = vec![make_jwks_config(issuer), make_jwks_config(issuer)];
assert!(
validate_nip_fi_config(NipFiMode::Enforce, &registry, &jwks).is_err(),
"duplicate JWKS configs must not pass"
);
}
#[test]
fn shadow_mode_validates_like_enforce() {
let issuer = "https://id.example";
let mut registry = IssuerRegistry::new();
assert_eq!(
validate_nip_fi_config(NipFiMode::Shadow, &registry, &[]).unwrap_err(),
NipFiStartupError::EmptyRegistry
);
registry.insert(make_offline_policy(issuer));
assert_eq!(
validate_nip_fi_config(NipFiMode::Shadow, &registry, &[]).unwrap_err(),
NipFiStartupError::MissingJwksConfig
);
assert!(
validate_nip_fi_config(NipFiMode::Shadow, &registry, &[make_jwks_config(issuer)]).is_ok()
);
}
#[test]
fn mode_predicates_separate_evaluation_from_authority() {
// (mode, restricts, evaluates): Shadow is the only mode that evaluates
// evidence without the verdict affecting admission.
for (mode, restricts, evaluates) in [
(NipFiMode::Off, false, false),
(NipFiMode::Enforce, true, true),
(NipFiMode::DenyProtected, true, false),
(NipFiMode::Shadow, false, true),
] {
assert_eq!(mode.restricts(), restricts, "{mode:?} restricts");
assert_eq!(mode.evaluates(), evaluates, "{mode:?} evaluates");
assert_eq!(mode.enforces(), restricts && evaluates, "{mode:?} enforces");
assert_eq!(
mode.denies_unconditionally(),
restricts && !evaluates,
"{mode:?}"
);
assert_eq!(mode.observes_only(), evaluates && !restricts, "{mode:?}");
}
}