Skip to content

Commit e76a38e

Browse files
s3 debug
1 parent 5f5cb9e commit e76a38e

5 files changed

Lines changed: 768 additions & 9 deletions

File tree

crates/trusted-server-adapter-fastly/src/main.rs

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,7 @@ use trusted_server_core::integrations::IntegrationRegistry;
1616
use trusted_server_core::platform::RuntimeServices;
1717
use trusted_server_core::proxy::{
1818
handle_asset_proxy_request, handle_first_party_click, handle_first_party_proxy,
19-
handle_first_party_proxy_rebuild, handle_first_party_proxy_sign,
19+
handle_first_party_proxy_rebuild, handle_first_party_proxy_sign, handle_s3_list_objects_debug,
2020
};
2121
use trusted_server_core::publisher::{
2222
handle_publisher_request, handle_tsjs_dynamic, stream_publisher_body, PublisherResponse,
@@ -230,6 +230,9 @@ async fn route_request(
230230
(Method::POST, "/admin/keys/deactivate") => {
231231
handle_deactivate_key(settings, runtime_services, req)
232232
}
233+
(Method::GET, "/admin/debug/s3-objects") => {
234+
handle_s3_list_objects_debug(settings, runtime_services, req).await
235+
}
233236

234237
// Unified auction endpoint (returns creative HTML inline)
235238
(Method::POST, "/auction") => {
@@ -341,6 +344,11 @@ async fn route_request(
341344
let mut response = result.unwrap_or_else(|e| to_error_response(&e));
342345

343346
finalize_response(settings, geo_info.as_ref(), &mut response);
347+
// Keep object-listing diagnostics non-cacheable even when operators set a
348+
// global Cache-Control response header.
349+
if path == "/admin/debug/s3-objects" {
350+
response.set_header(header::CACHE_CONTROL, "no-store, private");
351+
}
344352

345353
Some(response)
346354
}

crates/trusted-server-adapter-fastly/src/route_tests.rs

Lines changed: 65 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
use std::net::IpAddr;
22
use std::sync::{Arc, Mutex};
33

4+
use base64::{engine::general_purpose::STANDARD, Engine as _};
45
use edgezero_core::body::Body as EdgeBody;
56
use edgezero_core::http::response_builder as edge_response_builder;
67
use edgezero_core::key_value_store::NoopKvStore;
@@ -258,6 +259,10 @@ fn test_runtime_services(req: &Request) -> RuntimeServices {
258259
)
259260
}
260261

262+
fn admin_authorization_header() -> String {
263+
format!("Basic {}", STANDARD.encode("admin:admin-pass"))
264+
}
265+
261266
fn test_runtime_services_with_http_client(
262267
req: &Request,
263268
backend: Arc<dyn PlatformBackend>,
@@ -350,6 +355,66 @@ fn configured_missing_consent_store_only_breaks_consent_routes() {
350355
);
351356
}
352357

358+
#[test]
359+
fn admin_s3_debug_requires_auth_even_when_disabled() {
360+
let settings = create_test_settings();
361+
let orchestrator = build_orchestrator(&settings).expect("should build auction orchestrator");
362+
let integration_registry =
363+
IntegrationRegistry::new(&settings).expect("should create integration registry");
364+
365+
let req = Request::get("https://test.com/admin/debug/s3-objects");
366+
let services = test_runtime_services(&req);
367+
let resp = futures::executor::block_on(route_request(
368+
&settings,
369+
&orchestrator,
370+
&integration_registry,
371+
&services,
372+
req,
373+
))
374+
.expect("should route admin S3 debug request");
375+
376+
assert_eq!(
377+
resp.get_status(),
378+
StatusCode::UNAUTHORIZED,
379+
"should challenge unauthenticated S3 debug requests"
380+
);
381+
}
382+
383+
#[test]
384+
fn admin_s3_debug_disabled_returns_404_after_auth() {
385+
let mut settings = create_test_settings();
386+
settings.response_headers.insert(
387+
header::CACHE_CONTROL.as_str().to_string(),
388+
"public, max-age=3600".to_string(),
389+
);
390+
let orchestrator = build_orchestrator(&settings).expect("should build auction orchestrator");
391+
let integration_registry =
392+
IntegrationRegistry::new(&settings).expect("should create integration registry");
393+
394+
let req = Request::get("https://test.com/admin/debug/s3-objects")
395+
.with_header(header::AUTHORIZATION, admin_authorization_header());
396+
let services = test_runtime_services(&req);
397+
let resp = futures::executor::block_on(route_request(
398+
&settings,
399+
&orchestrator,
400+
&integration_registry,
401+
&services,
402+
req,
403+
))
404+
.expect("should route admin S3 debug request");
405+
406+
assert_eq!(
407+
resp.get_status(),
408+
StatusCode::NOT_FOUND,
409+
"should hide the S3 debug endpoint when the config flag is disabled"
410+
);
411+
assert_eq!(
412+
resp.get_header_str(header::CACHE_CONTROL),
413+
Some("no-store, private"),
414+
"should prevent configured response headers from making S3 debug responses cacheable"
415+
);
416+
}
417+
353418
#[test]
354419
fn asset_routes_bypass_publisher_consent_dependencies() {
355420
let mut settings = create_test_settings();

0 commit comments

Comments
 (0)