|
22 | 22 | //! | POST | `/verify-signature` | [`handle_verify_signature`] | |
23 | 23 | //! | POST | `/_ts/admin/keys/rotate` | [`handle_rotate_key`] | |
24 | 24 | //! | POST | `/_ts/admin/keys/deactivate` | [`handle_deactivate_key`] | |
| 25 | +//! | GET | `/_ts/admin/ec` | [`handle_admin_ec_lookup`] | |
| 26 | +//! | GET | `/_ts/admin/ec/{id}` | [`handle_admin_ec_lookup`] | |
25 | 27 | //! | POST | `/_ts/api/v1/batch-sync` | [`handle_batch_sync`] | |
26 | 28 | //! | GET | `/_ts/api/v1/identify` | [`handle_identify`] | |
27 | 29 | //! | GET | `/_ts/set-tester` | [`handle_set_tester`] | |
@@ -98,6 +100,7 @@ use trusted_server_core::auction::endpoints::handle_auction; |
98 | 100 | use trusted_server_core::auction::{AuctionOrchestrator, build_orchestrator}; |
99 | 101 | use trusted_server_core::constants::{COOKIE_SHAREDID, COOKIE_TS_EIDS}; |
100 | 102 | use trusted_server_core::ec::EcContext; |
| 103 | +use trusted_server_core::ec::admin::handle_admin_ec_lookup; |
101 | 104 | use trusted_server_core::ec::batch_sync::handle_batch_sync; |
102 | 105 | use trusted_server_core::ec::consent::ec_consent_withdrawn; |
103 | 106 | use trusted_server_core::ec::device::DeviceSignals; |
@@ -565,6 +568,10 @@ async fn run_named_route( |
565 | 568 | } |
566 | 569 | NamedRouteHandler::RotateKey => handle_rotate_key(&state.settings, services, req), |
567 | 570 | NamedRouteHandler::DeactivateKey => handle_deactivate_key(&state.settings, services, req), |
| 571 | + NamedRouteHandler::AdminEcLookup => { |
| 572 | + let partner_registry = PartnerRegistry::from_config(&state.settings.ec.partners)?; |
| 573 | + handle_admin_ec_lookup(ec.kv_graph.as_ref(), &partner_registry, &req) |
| 574 | + } |
568 | 575 | NamedRouteHandler::LegacyAdminDenied => Ok(legacy_admin_alias_denied()), |
569 | 576 | NamedRouteHandler::BatchSync => { |
570 | 577 | // Dispatched by execute_named before EC state is built. |
@@ -987,6 +994,7 @@ enum NamedRouteHandler { |
987 | 994 | VerifySignature, |
988 | 995 | RotateKey, |
989 | 996 | DeactivateKey, |
| 997 | + AdminEcLookup, |
990 | 998 | /// Legacy `/admin/keys/*` aliases — denied locally with 404 so they never |
991 | 999 | /// reach the publisher fallback (which would leak admin credentials). |
992 | 1000 | LegacyAdminDenied, |
@@ -1039,6 +1047,18 @@ const NAMED_ROUTES: &[NamedRoute] = &[ |
1039 | 1047 | primary_methods: &[Method::POST], |
1040 | 1048 | handler: NamedRouteHandler::DeactivateKey, |
1041 | 1049 | }, |
| 1050 | + // Admin EC lookup: the bare route reads the EC ID from the caller's |
| 1051 | + // `ts-ec` cookie; the parameterized route takes an explicit EC ID. |
| 1052 | + NamedRoute { |
| 1053 | + path: "/_ts/admin/ec", |
| 1054 | + primary_methods: &[Method::GET], |
| 1055 | + handler: NamedRouteHandler::AdminEcLookup, |
| 1056 | + }, |
| 1057 | + NamedRoute { |
| 1058 | + path: "/_ts/admin/ec/{id}", |
| 1059 | + primary_methods: &[Method::GET], |
| 1060 | + handler: NamedRouteHandler::AdminEcLookup, |
| 1061 | + }, |
1042 | 1062 | // The legacy non-`/_ts` aliases (`/admin/keys/*`) are denied locally with a |
1043 | 1063 | // 404 instead of executing key operations: the production basic-auth handler |
1044 | 1064 | // regex `^/_ts/admin` does not match them, and letting them fall through to |
@@ -1624,6 +1644,30 @@ mod tests { |
1624 | 1644 | } |
1625 | 1645 | } |
1626 | 1646 |
|
| 1647 | + #[test] |
| 1648 | + fn admin_ec_lookup_routes_are_registered() { |
| 1649 | + // Both lookup shapes must be explicitly routed to the admin EC |
| 1650 | + // handler: the bare cookie-based route and the parameterized route. |
| 1651 | + // Leaving either unrouted would fall through to the publisher |
| 1652 | + // fallback, forwarding the caller's `Authorization` header to the |
| 1653 | + // origin. |
| 1654 | + for path in ["/_ts/admin/ec", "/_ts/admin/ec/{id}"] { |
| 1655 | + let route = NAMED_ROUTES |
| 1656 | + .iter() |
| 1657 | + .find(|route| route.path == path) |
| 1658 | + .unwrap_or_else(|| panic!("{path} must be a named route")); |
| 1659 | + assert!( |
| 1660 | + matches!(route.handler, NamedRouteHandler::AdminEcLookup), |
| 1661 | + "{path} must map to the admin EC lookup handler" |
| 1662 | + ); |
| 1663 | + assert_eq!( |
| 1664 | + route.primary_methods, |
| 1665 | + &[Method::GET], |
| 1666 | + "{path} must have GET as its only primary method" |
| 1667 | + ); |
| 1668 | + } |
| 1669 | + } |
| 1670 | + |
1627 | 1671 | #[test] |
1628 | 1672 | fn legacy_admin_aliases_denied_locally_not_proxied_to_publisher() { |
1629 | 1673 | // Regression for the credential-leak finding: with a production-shaped |
|
0 commit comments