Skip to content

Commit 6dc6547

Browse files
committed
factor-outbound-http: Update override_connect_host
- Rename to override_connect_endpoint - Support optionally overriding port by making the value an 'Authority' - Fix bug in host resolution for ipv6 addresses Signed-off-by: Lann Martin <[email protected]>
1 parent d3d0903 commit 6dc6547

File tree

3 files changed

+45
-36
lines changed

3 files changed

+45
-36
lines changed

crates/factor-outbound-http/src/intercept.rs

Lines changed: 13 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,8 @@ use http_body_util::{BodyExt, Full};
33
use spin_world::async_trait;
44
use wasmtime_wasi_http::{body::HyperOutgoingBody, HttpResult};
55

6+
pub use http::uri::Authority;
7+
68
pub type HyperBody = HyperOutgoingBody;
79

810
/// An outbound HTTP request interceptor to be used with
@@ -39,7 +41,7 @@ pub enum InterceptOutcome {
3941
pub struct InterceptRequest {
4042
inner: Request<()>,
4143
body: InterceptBody,
42-
pub(crate) override_connect_host: Option<String>,
44+
pub(crate) override_connect_endpoint: Option<Authority>,
4345
}
4446

4547
enum InterceptBody {
@@ -48,16 +50,18 @@ enum InterceptBody {
4850
}
4951

5052
impl InterceptRequest {
51-
/// Overrides the host that will be connected to for this outbound request.
53+
/// Overrides the endpoint (host and optional port) that will be connected
54+
/// to for this outbound request.
5255
///
5356
/// This override does not have any effect on TLS server name checking or
5457
/// HTTP authority / host headers.
5558
///
56-
/// This host will not be checked against `allowed_outbound_hosts`; if that
57-
/// check should occur it must be performed by the interceptor. The resolved
58-
/// IP addresses from this host will be checked against blocked IP networks.
59-
pub fn override_connect_host(&mut self, host: impl Into<String>) {
60-
self.override_connect_host = Some(host.into())
59+
/// This endpoint will not be checked against `allowed_outbound_hosts`; if
60+
/// that check should occur it must be performed by the interceptor. The
61+
/// resolved IP addresses from this host will be checked against blocked IP
62+
/// networks.
63+
pub fn override_connect_endpoint(&mut self, endpoint: Authority) {
64+
self.override_connect_endpoint = Some(endpoint);
6165
}
6266

6367
pub fn into_hyper_request(self) -> Request<HyperBody> {
@@ -94,7 +98,7 @@ impl From<Request<HyperBody>> for InterceptRequest {
9498
Self {
9599
inner: Request::from_parts(parts, ()),
96100
body: InterceptBody::Hyper(body),
97-
override_connect_host: None,
101+
override_connect_endpoint: None,
98102
}
99103
}
100104
}
@@ -105,7 +109,7 @@ impl From<Request<Vec<u8>>> for InterceptRequest {
105109
Self {
106110
inner: Request::from_parts(parts, ()),
107111
body: InterceptBody::Vec(body),
108-
override_connect_host: None,
112+
override_connect_endpoint: None,
109113
}
110114
}
111115
}

crates/factor-outbound-http/src/wasi.rs

Lines changed: 23 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@ use std::{
88
time::Duration,
99
};
1010

11-
use http::{header::HOST, Uri};
11+
use http::{header::HOST, uri::Authority, Uri};
1212
use http_body_util::BodyExt;
1313
use hyper_util::{
1414
client::legacy::{
@@ -113,7 +113,7 @@ impl WasiHttpView for WasiHttpImplInner<'_> {
113113
http.response.status_code = Empty,
114114
server.address = Empty,
115115
server.port = Empty,
116-
),
116+
)
117117
)]
118118
fn send_request(
119119
&mut self,
@@ -166,12 +166,12 @@ impl RequestSender {
166166
spin_telemetry::inject_trace_context(&mut request);
167167

168168
// Run any configured request interceptor
169-
let mut override_connect_host = None;
169+
let mut override_connect_endpoint = None;
170170
if let Some(interceptor) = &self.request_interceptor {
171171
let intercept_request = std::mem::take(&mut request).into();
172172
match interceptor.intercept(intercept_request).await? {
173173
InterceptOutcome::Continue(mut req) => {
174-
override_connect_host = req.override_connect_host.take();
174+
override_connect_endpoint = req.override_connect_endpoint.take();
175175
request = req.into_hyper_request();
176176
}
177177
InterceptOutcome::Complete(resp) => {
@@ -188,15 +188,15 @@ impl RequestSender {
188188
// Backfill span fields after potentially updating the URL in the interceptor
189189
if let Some(authority) = request.uri().authority() {
190190
let span = tracing::Span::current();
191-
let host = override_connect_host.as_deref().unwrap_or(authority.host());
192-
span.record("server.address", host);
193-
if let Some(port) = authority.port() {
194-
span.record("server.port", port.as_u16());
191+
let authority = override_connect_endpoint.as_ref().unwrap_or(authority);
192+
span.record("server.address", authority.host());
193+
if let Some(port) = authority.port_u16() {
194+
span.record("server.port", port);
195195
}
196196
}
197197

198198
Ok(self
199-
.send_request(request, config, override_connect_host)
199+
.send_request(request, config, override_connect_endpoint)
200200
.await?)
201201
}
202202

@@ -275,7 +275,7 @@ impl RequestSender {
275275
self,
276276
request: OutgoingRequest,
277277
config: OutgoingRequestConfig,
278-
override_connect_host: Option<String>,
278+
override_connect_endpoint: Option<Authority>,
279279
) -> Result<IncomingResponse, ErrorCode> {
280280
let OutgoingRequestConfig {
281281
use_tls,
@@ -296,7 +296,7 @@ impl RequestSender {
296296
blocked_networks: self.blocked_networks,
297297
connect_timeout,
298298
tls_client_config,
299-
override_connect_host,
299+
override_connect_endpoint,
300300
},
301301
async move {
302302
if use_tls {
@@ -376,19 +376,24 @@ struct ConnectOptions {
376376
blocked_networks: BlockedNetworks,
377377
connect_timeout: Duration,
378378
tls_client_config: Option<TlsClientConfig>,
379-
override_connect_host: Option<String>,
379+
override_connect_endpoint: Option<Authority>,
380380
}
381381

382382
impl ConnectOptions {
383383
async fn connect_tcp(&self, uri: &Uri, default_port: u16) -> Result<TcpStream, ErrorCode> {
384-
let host = self
385-
.override_connect_host
386-
.as_deref()
387-
.or(uri.host())
384+
let authority = self
385+
.override_connect_endpoint
386+
.as_ref()
387+
.or(uri.authority())
388388
.ok_or(ErrorCode::HttpRequestUriInvalid)?;
389-
let host_and_port = (host, uri.port_u16().unwrap_or(default_port));
390389

391-
let mut socket_addrs = tokio::net::lookup_host(host_and_port)
390+
let host_and_port = if authority.port().is_some() {
391+
authority.as_str().to_string()
392+
} else {
393+
format!("{}:{}", authority.as_str(), default_port)
394+
};
395+
396+
let mut socket_addrs = tokio::net::lookup_host(&host_and_port)
392397
.await
393398
.map_err(|err| {
394399
tracing::debug!(?host_and_port, ?err, "Error resolving host");

crates/factor-outbound-http/tests/factor_test.rs

Lines changed: 9 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -22,7 +22,7 @@ struct TestFactors {
2222
http: OutboundHttpFactor,
2323
}
2424

25-
#[tokio::test]
25+
#[tokio::test(flavor = "multi_thread")]
2626
async fn allowed_host_is_allowed() -> anyhow::Result<()> {
2727
let mut state = test_instance_state("https://*", true).await?;
2828
let mut wasi_http = OutboundHttpFactor::get_wasi_http_impl(&mut state).unwrap();
@@ -36,7 +36,7 @@ async fn allowed_host_is_allowed() -> anyhow::Result<()> {
3636
Ok(())
3737
}
3838

39-
#[tokio::test]
39+
#[tokio::test(flavor = "multi_thread")]
4040
async fn self_request_smoke_test() -> anyhow::Result<()> {
4141
let mut state = test_instance_state("http://self", true).await?;
4242
// [100::] is the IPv6 "Discard Prefix", which should always fail
@@ -52,7 +52,7 @@ async fn self_request_smoke_test() -> anyhow::Result<()> {
5252
Ok(())
5353
}
5454

55-
#[tokio::test]
55+
#[tokio::test(flavor = "multi_thread")]
5656
async fn disallowed_host_fails() -> anyhow::Result<()> {
5757
let mut state = test_instance_state("https://allowed.test", true).await?;
5858
let mut wasi_http = OutboundHttpFactor::get_wasi_http_impl(&mut state).unwrap();
@@ -67,7 +67,7 @@ async fn disallowed_host_fails() -> anyhow::Result<()> {
6767
Ok(())
6868
}
6969

70-
#[tokio::test]
70+
#[tokio::test(flavor = "multi_thread")]
7171
async fn disallowed_private_ips_fails() -> anyhow::Result<()> {
7272
async fn run_test(allow_private_ips: bool) -> anyhow::Result<()> {
7373
let mut state = test_instance_state("http://*", allow_private_ips).await?;
@@ -100,8 +100,8 @@ async fn disallowed_private_ips_fails() -> anyhow::Result<()> {
100100
Ok(())
101101
}
102102

103-
#[tokio::test]
104-
async fn override_connect_host_disallowed_private_ip_fails() -> anyhow::Result<()> {
103+
#[tokio::test(flavor = "multi_thread")]
104+
async fn override_connect_endpoint_disallowed_private_ip_fails() -> anyhow::Result<()> {
105105
let mut state = test_instance_state("http://*", false).await?;
106106
state.http.set_request_interceptor({
107107
struct Interceptor;
@@ -111,7 +111,7 @@ async fn override_connect_host_disallowed_private_ip_fails() -> anyhow::Result<(
111111
&self,
112112
mut request: InterceptRequest,
113113
) -> wasmtime_wasi_http::HttpResult<InterceptOutcome> {
114-
request.override_connect_host("localhost");
114+
request.override_connect_endpoint("[::1]".try_into().unwrap());
115115
Ok(InterceptOutcome::Continue(request))
116116
}
117117
}
@@ -159,8 +159,8 @@ fn test_request_config() -> OutgoingRequestConfig {
159159
OutgoingRequestConfig {
160160
use_tls: false,
161161
connect_timeout: Duration::from_millis(10),
162-
first_byte_timeout: Duration::from_millis(10),
163-
between_bytes_timeout: Duration::from_millis(10),
162+
first_byte_timeout: Duration::from_millis(0),
163+
between_bytes_timeout: Duration::from_millis(0),
164164
}
165165
}
166166

0 commit comments

Comments
 (0)