Skip to content

Commit c1ed5f0

Browse files
committed
Strip measurements headers to avoid them being spoofed
1 parent a047837 commit c1ed5f0

1 file changed

Lines changed: 176 additions & 0 deletions

File tree

src/lib.rs

Lines changed: 176 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -509,6 +509,10 @@ impl ProxyServer {
509509

510510
update_header(headers, &X_FORWARDED_FOR, &new_x_forwarded_for);
511511

512+
// Strip any caller-provided attestation metadata before injecting authenticated values.
513+
headers.remove(ATTESTATION_TYPE_HEADER);
514+
headers.remove(MEASUREMENT_HEADER);
515+
512516
// If we have measurements, from the remote peer, add them to the request header
513517
let measurements = measurements.clone();
514518

@@ -782,6 +786,8 @@ impl ProxyClient {
782786
Ok(mut resp) => {
783787
debug!("[proxy-client] Read response from proxy-server: {resp:?}");
784788
let headers = resp.headers_mut();
789+
headers.remove(MEASUREMENT_HEADER);
790+
785791
if let Some(measurements) = measurements.clone() {
786792
match measurements.to_header_format() {
787793
Ok(header_value) => {
@@ -1225,6 +1231,56 @@ mod tests {
12251231
assert!(headers.get(MEASUREMENT_HEADER).is_none());
12261232
}
12271233

1234+
/// Test service that echoes attestation-related request headers as JSON.
1235+
async fn request_header_echo_service() -> SocketAddr {
1236+
let listener = TcpListener::bind("127.0.0.1:0").await.unwrap();
1237+
let addr = listener.local_addr().unwrap();
1238+
1239+
let app = axum::Router::new().route(
1240+
"/",
1241+
axum::routing::get(|headers: http::HeaderMap| async move {
1242+
axum::Json(serde_json::json!({
1243+
"measurement": headers
1244+
.get(MEASUREMENT_HEADER)
1245+
.and_then(|v| v.to_str().ok()),
1246+
"attestation_type": headers
1247+
.get(ATTESTATION_TYPE_HEADER)
1248+
.and_then(|v| v.to_str().ok()),
1249+
}))
1250+
}),
1251+
);
1252+
1253+
tokio::spawn(async move {
1254+
axum::serve(listener, app).await.unwrap();
1255+
});
1256+
1257+
addr
1258+
}
1259+
1260+
/// Test service that deliberately returns a spoofed measurement header.
1261+
async fn spoofed_response_measurement_service() -> SocketAddr {
1262+
let listener = TcpListener::bind("127.0.0.1:0").await.unwrap();
1263+
let addr = listener.local_addr().unwrap();
1264+
1265+
let app = axum::Router::new().route(
1266+
"/",
1267+
axum::routing::get(|| async move {
1268+
let mut response = http::Response::new("ok".to_string());
1269+
response.headers_mut().insert(
1270+
MEASUREMENT_HEADER,
1271+
HeaderValue::from_static("{\"spoofed\":\"value\"}"),
1272+
);
1273+
response
1274+
}),
1275+
);
1276+
1277+
tokio::spawn(async move {
1278+
axum::serve(listener, app).await.unwrap();
1279+
});
1280+
1281+
addr
1282+
}
1283+
12281284
#[test]
12291285
fn proxy_alpn_protocols_prefer_http2() {
12301286
let mut protocols = Vec::new();
@@ -1597,6 +1653,68 @@ mod tests {
15971653
assert_eq!(res_body, "No measurements");
15981654
}
15991655

1656+
#[tokio::test(flavor = "multi_thread")]
1657+
async fn http_proxy_strips_spoofed_request_attestation_headers() {
1658+
let target_addr = request_header_echo_service().await;
1659+
1660+
let (server_cert_chain, server_private_key) =
1661+
generate_certificate_chain_for_host("localhost");
1662+
let (server_config, client_config) =
1663+
generate_tls_config(server_cert_chain.clone(), server_private_key);
1664+
1665+
let proxy_server = ProxyServer::new(
1666+
Some(OuterTlsConfig {
1667+
listen_addr: "127.0.0.1:0",
1668+
tls: OuterTlsMode::Preconfigured {
1669+
server_config,
1670+
certificate_name: certificate_identity_from_chain(&server_cert_chain).unwrap(),
1671+
},
1672+
}),
1673+
Some("127.0.0.1:0"),
1674+
target_addr.to_string(),
1675+
AttestationGenerator::with_no_attestation(),
1676+
AttestationVerifier::mock(),
1677+
false,
1678+
)
1679+
.await
1680+
.unwrap();
1681+
1682+
let proxy_addr = proxy_server.local_addr().unwrap();
1683+
1684+
tokio::spawn(async move {
1685+
proxy_server.accept().await.unwrap();
1686+
});
1687+
1688+
let proxy_client = ProxyClient::new_with_tls_config(
1689+
client_config,
1690+
"127.0.0.1:0",
1691+
format!("localhost:{}", proxy_addr.port()),
1692+
AttestationGenerator::with_no_attestation(),
1693+
AttestationVerifier::expect_none(),
1694+
None,
1695+
)
1696+
.await
1697+
.unwrap();
1698+
1699+
let proxy_client_addr = proxy_client.local_addr().unwrap();
1700+
1701+
tokio::spawn(async move {
1702+
proxy_client.accept().await.unwrap();
1703+
});
1704+
1705+
let res = reqwest::Client::new()
1706+
.get(format!("http://{}", proxy_client_addr))
1707+
.header(MEASUREMENT_HEADER, "{\"spoofed\":\"request\"}")
1708+
.header(ATTESTATION_TYPE_HEADER, "dcap-tdx")
1709+
.send()
1710+
.await
1711+
.unwrap();
1712+
1713+
let echoed: serde_json::Value = res.json().await.unwrap();
1714+
assert!(echoed["measurement"].is_null());
1715+
assert!(echoed["attestation_type"].is_null());
1716+
}
1717+
16001718
// Server has mock DCAP, client has mock DCAP and client auth
16011719
#[tokio::test(flavor = "multi_thread")]
16021720
async fn http_proxy_mutual_attestation() {
@@ -2044,6 +2162,64 @@ mod tests {
20442162
assert_eq!(request_count.load(Ordering::SeqCst), 2);
20452163
}
20462164

2165+
#[tokio::test(flavor = "multi_thread")]
2166+
async fn http_proxy_strips_spoofed_response_measurement_header() {
2167+
let target_addr = spoofed_response_measurement_service().await;
2168+
2169+
let (server_cert_chain, server_private_key) =
2170+
generate_certificate_chain_for_host("localhost");
2171+
let (server_config, client_config) =
2172+
generate_tls_config(server_cert_chain.clone(), server_private_key);
2173+
2174+
let proxy_server = ProxyServer::new(
2175+
Some(OuterTlsConfig {
2176+
listen_addr: "127.0.0.1:0",
2177+
tls: OuterTlsMode::Preconfigured {
2178+
server_config,
2179+
certificate_name: certificate_identity_from_chain(&server_cert_chain).unwrap(),
2180+
},
2181+
}),
2182+
Some("127.0.0.1:0"),
2183+
target_addr.to_string(),
2184+
AttestationGenerator::with_no_attestation(),
2185+
AttestationVerifier::expect_none(),
2186+
false,
2187+
)
2188+
.await
2189+
.unwrap();
2190+
2191+
let proxy_addr = proxy_server.local_addr().unwrap();
2192+
2193+
tokio::spawn(async move {
2194+
proxy_server.accept().await.unwrap();
2195+
});
2196+
2197+
let proxy_client = ProxyClient::new_with_tls_config(
2198+
client_config,
2199+
"127.0.0.1:0",
2200+
format!("localhost:{}", proxy_addr.port()),
2201+
AttestationGenerator::with_no_attestation(),
2202+
AttestationVerifier::expect_none(),
2203+
None,
2204+
)
2205+
.await
2206+
.unwrap();
2207+
2208+
let proxy_client_addr = proxy_client.local_addr().unwrap();
2209+
2210+
tokio::spawn(async move {
2211+
proxy_client.accept().await.unwrap();
2212+
});
2213+
2214+
let res = reqwest::get(format!("http://{}", proxy_client_addr))
2215+
.await
2216+
.unwrap();
2217+
2218+
assert_attestation_type_header(res.headers(), "none");
2219+
assert_no_measurements_header(res.headers());
2220+
assert_eq!(res.text().await.unwrap(), "ok");
2221+
}
2222+
20472223
// Use HTTP 1.1
20482224
#[tokio::test(flavor = "multi_thread")]
20492225
async fn http_proxy_with_http1() {

0 commit comments

Comments
 (0)