Add TS admin endpoint to look up EC by id - #928
Conversation
Adds GET /_ts/admin/ec/{id} (explicit EC ID) and GET /_ts/admin/ec
(EC ID from the caller's ts-ec cookie) so operators can inspect EC
identity graph entries and debug KV-to-auction EID propagation.
The core handler returns the stored KvEntry verbatim (including raw
consent strings and partner UIDs), the KV metadata mirror, the store
generation marker, and a derived auction view showing exactly which
EIDs the auction would attach and why each stored partner ID was
skipped (empty_uid, not_in_registry, bidstream_disabled). Corrupt
entries are returned with the parse error and raw body via the new
KvIdentityGraph::lookup_raw instead of failing closed.
The routes join Settings::ADMIN_ENDPOINTS so startup validation
rejects configs whose basic-auth handler regex does not cover them.
The EC identity graph is Fastly KV backed, so the Axum, Cloudflare,
and Spin adapters register the routes to local 501 responses, keeping
them off the publisher fallback that would forward the Authorization
header to the origin.
Closes #921
The dispatch arm reused EcRequestState::kv_graph, which is deliberately None for clients that fail the browser gate. Operators hit this auth-gated endpoint with curl, so every lookup returned 501 as if no EC store were configured. Build the identity graph directly from settings instead, and document why the bot-gated copy must not be used. Also point the bare-route no-cookie 404 at the explicit-id route, since the ts-ec cookie (Domain-scoped, Secure) cannot exist on localhost.
Adds GET /_ts/admin/eids, complementing the EC lookup endpoint with the client-side half of EID propagation: it decodes the request's ts-eids and sharedId cookies and previews what cookie ingestion would write into the EC entry's ids map — matched partner UIDs (deduplicated exactly like the ingestion path) and unmatched sources that would be dropped. The endpoint always responds 200; missing or malformed cookies are reported in the payload rather than as errors. It is pure request inspection with no KV access, so every adapter serves the real handler. The path joins Settings::ADMIN_ENDPOINTS for basic-auth coverage validation.
Review feedback on the admin EC lookup asked for readable dates. The echoed entry now carries derived created_iso and consent.updated_iso fields (yyyy-MM-ddTHH:mm:ss.SSSZ) next to the stored unix-seconds values, which stay untouched so the echo remains faithful to KV.
Resolve route-registration conflicts in the axum, spin, and fastly adapters.
Main's `/__ts/page-bids` legacy alias landed next to the admin EC lookup
routes, so the named-route array lengths and a shared `#[test]` attribute
collided:
- axum `named_routes` and spin `named_fallback_paths` now declare 16 entries
(13 from main plus `/_ts/admin/ec`, `/_ts/admin/ec/{id}`, `/_ts/admin/eids`).
- fastly keeps both colliding tests: `admin_ec_lookup_routes_are_registered`
and `page_bids_serves_canonical_path_and_deprecated_alias`.
ChristianPavilonis
left a comment
There was a problem hiding this comment.
Summary
Request changes. This adds sensitive admin diagnostics, but the current route/auth wiring permits an authentication bypass and can forward admin credentials to the publisher origin. The inline comments also cover Fastly-only write side effects, loss of raw KV data, and the missing operator API contract.
ChristianPavilonis
left a comment
There was a problem hiding this comment.
Automated Review:
Summary
Reviewed the admin EC/EID lookup routes, cross-adapter registration, KV handling, response behavior, and authorization validation. I found one high-severity authorization-coverage gap for the parameterized EC lookup route.
Findings by severity
P0 / Blockers
None.
P1 / High
One inline finding.
P2 / Medium
None.
P3 / Low
None.
CI / Existing Reviews
The displayed CI check runs for head 866bcd4c24be67ca5434fff82727375fbdfc5a45 are successful. Immediately before submission, the PR had no existing reviews or inline review comments. Local tests were not run during this review.
Summary
GET /_ts/admin/ec/{id}(explicit EC ID) andGET /_ts/admin/ec(EC ID from the caller'sts-eccookie) so operators can inspect EC identity graph entries without KV console access.empty_uid,not_in_registry,bidstream_disabled) — making KV-to-auction EID propagation debuggable in one call. Corrupt or legacy-schema entries stay inspectable via a new raw lookup that reportsentry_error+raw_bodyinstead of failing closed.GET /_ts/admin/eids, the client-side half of the picture: it echoes the request'sts-eidsandsharedIdcookies and previews what cookie ingestion would write into the EC entry'sidsmap (matched partner UIDs, deduplicated exactly like the ingestion path, plus dropped sources). Pure request inspection with no KV access, so every adapter serves the real handler; always responds 200.Changes
crates/trusted-server-core/src/ec/admin.rshandle_admin_ec_lookup+handle_admin_eids_lookuphandlers, response payload types, 14 unit testscrates/trusted-server-core/src/ec/kv.rsKvIdentityGraph::lookup_rawreturning raw body/metadata/generation without validationcrates/trusted-server-core/src/ec/prebid_eids.rspub(crate)so the EIDs preview reuses them instead of reimplementingcrates/trusted-server-core/src/ec/mod.rsadminmodulecrates/trusted-server-core/src/settings.rsSettings::ADMIN_ENDPOINTS; update coverage testscrates/trusted-server-adapter-fastly/src/app.rsec.kv_graph, so operator curl requests workcrates/trusted-server-adapter-axum/src/app.rsAdminEcNotSupported); EIDs route → real handlercrates/trusted-server-adapter-axum/tests/routes.rscrates/trusted-server-adapter-cloudflare/src/app.rscrates/trusted-server-adapter-cloudflare/tests/routes.rscrates/trusted-server-adapter-spin/src/app.rscrates/trusted-server-adapter-spin/tests/routes.rsCloses
Closes #921
Test plan
cargo test-fastly && cargo test-axum(alsocargo test-cloudflare && cargo test-spin)cargo clippy-fastly && cargo clippy-axum(alsoclippy-cloudflare,clippy-cloudflare-wasm,clippy-spin-native,clippy-spin-wasm)cargo fmt --all -- --checkcd crates/trusted-server-js/lib && npx vitest run(JS untouched — left to CI)cd crates/trusted-server-js/lib && npm run format(JS untouched — left to CI)cd docs && npm run format(docs untouched — left to CI)cargo check -p trusted-server-adapter-fastly --target wasm32-wasip1fastly compute serve: verified auth gating, the EC lookup against the seeded Viceroy entry, and the EIDs echo against real browser cookies; this surfaced (and the branch fixes) an initial bug where the bot-gated KV graph made curl requests return 501cargo test --manifest-path crates/trusted-server-integration-tests/Cargo.toml --test parity)Hardening note
All three paths were added to
Settings::ADMIN_ENDPOINTS, so startup validation fails closed (aConfigurationerror viaReport, nopanic!/unwrap()/expect()) for any config whose basic-auth handler regex does not cover them; prefix-style regexes like the example config's^/_ts/adminremain valid. Covered by the updateduncovered_admin_endpoints_*andadmin_endpoints_match_fastly_routertests. Routes are registered explicitly on every adapter so they never fall through to the publisher fallback, which would forward the caller'sAuthorizationheader to the origin.Checklist
unwrap()in production code — useexpect("should ...")logmacros (notprintln!)