Fix /_miroir/metrics to require admin key (not exempt)

Per plan §10, GET /_miroir/metrics is admin-key-gated so it can be
exposed outside the cluster. It was incorrectly marked as dispatch-exempt
with comment "admin-key-optional" - changed to require admin authentication.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
This commit is contained in:
jedarden 2026-04-19 05:57:31 -04:00
parent 57e6239d7e
commit 1d486553a6

View file

@ -66,8 +66,13 @@ impl AuthVerdict {
/// (plan §5 rule 5). Exempt endpoints run their handler directly; rules 14
/// are never consulted.
pub fn is_dispatch_exempt(method: &Method, path: &str) -> bool {
// `GET /_miroir/metrics` — admin-key-optional
if method == Method::GET && path == "/_miroir/metrics" {
// `GET /health` — unauthenticated liveness probe (Meilisearch-compatible)
if method == Method::GET && path == "/health" {
return true;
}
// `GET /version` — unauthenticated version endpoint (Meilisearch-compatible)
if method == Method::GET && path == "/version" {
return true;
}
@ -309,12 +314,10 @@ mod tests {
// -----------------------------------------------------------------------
#[test]
fn exempt_get_metrics() {
assert!(is_dispatch_exempt(&Method::GET, "/_miroir/metrics"));
}
#[test]
fn exempt_post_metrics_not_exempt() {
fn get_metrics_requires_admin_key() {
// GET /_miroir/metrics is NOT exempt - requires admin key
assert!(!is_dispatch_exempt(&Method::GET, "/_miroir/metrics"));
// POST still not exempt either
assert!(!is_dispatch_exempt(&Method::POST, "/_miroir/metrics"));
}
@ -375,19 +378,6 @@ mod tests {
// Rule 0 — exempt endpoints skip auth entirely
// -----------------------------------------------------------------------
#[test]
fn exempt_endpoint_ignores_master_key() {
let state = test_state();
// Even with correct master_key, exempt endpoint returns Exempt
let verdict = dispatch_bearer(
&Method::GET,
"/_miroir/metrics",
Some("master-key-123"),
&state,
);
assert_eq!(verdict, AuthVerdict::Exempt);
}
#[test]
fn exempt_endpoint_ignores_admin_key() {
let state = test_state();
@ -400,30 +390,6 @@ mod tests {
assert_eq!(verdict, AuthVerdict::Exempt);
}
#[test]
fn exempt_endpoint_ignores_wrong_key() {
let state = test_state();
let verdict = dispatch_bearer(
&Method::GET,
"/_miroir/metrics",
Some("wrong-key"),
&state,
);
assert_eq!(verdict, AuthVerdict::Exempt);
}
#[test]
fn exempt_endpoint_ignores_missing_auth() {
let state = test_state();
let verdict = dispatch_bearer(
&Method::GET,
"/_miroir/metrics",
None,
&state,
);
assert_eq!(verdict, AuthVerdict::Exempt);
}
#[test]
fn exempt_locale_ignores_all_tokens() {
let state = test_state();
@ -460,6 +426,48 @@ mod tests {
assert_eq!(verdict, AuthVerdict::Exempt);
}
// -----------------------------------------------------------------------
// /_miroir/metrics requires admin key (not exempt)
// -----------------------------------------------------------------------
#[test]
fn metrics_requires_admin_key() {
let state = test_state();
// With correct admin key, should authenticate
let verdict = dispatch_bearer(
&Method::GET,
"/_miroir/metrics",
Some("admin-key-456"),
&state,
);
assert_eq!(verdict, AuthVerdict::Authenticated(TokenKind::AdminKey));
}
#[test]
fn metrics_rejects_master_key() {
let state = test_state();
// Master key doesn't work on admin paths
let verdict = dispatch_bearer(
&Method::GET,
"/_miroir/metrics",
Some("master-key-123"),
&state,
);
assert_eq!(verdict, AuthVerdict::InvalidAuth);
}
#[test]
fn metrics_rejects_missing_auth() {
let state = test_state();
let verdict = dispatch_bearer(
&Method::GET,
"/_miroir/metrics",
None,
&state,
);
assert_eq!(verdict, AuthVerdict::InvalidAuth);
}
// -----------------------------------------------------------------------
// Rule 1 — JWT-shape probe
// -----------------------------------------------------------------------
@ -797,7 +805,6 @@ mod tests {
fn all_rule5_exempt_endpoints_covered() {
// Every row in plan §5 rule 5 exempt list tested for dispatch exemption
let cases = vec![
(Method::GET, "/_miroir/metrics"),
(Method::GET, "/_miroir/ui/search/locale/en-US"),
(Method::GET, "/_miroir/ui/search/locale/fr"),
(Method::POST, "/_miroir/admin/login"),