Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

factors: Update outbound networking #2737

Merged
merged 3 commits into from
Aug 21, 2024

Conversation

lann
Copy link
Collaborator

@lann lann commented Aug 21, 2024

Fell down this rabbit hole while implementing OutboundHttpFactor's fermyon:spin/http interface:

  • Re-add instrumentation to wasi outbound http impl
  • Introduce OutboundNetworkingFactor "disallowed host callback" to handle Spin-CLI-specific terminal output
    • Rearrange a bunch of allowed outbound host checking code to dedupe this callback logic
  • Refactor SelfRequestOrigin to be a more straightforward InstanceState setter; the extension method didn't work well for the spin interface anyway

@lann lann requested a review from rylev August 21, 2024 15:01
@lann lann force-pushed the factors-update-outbound-net branch from 8f96df9 to 4237a81 Compare August 21, 2024 15:04
Comment on lines 90 to 124
let hosts_future = allowed_hosts_future.clone();
let allowed_hosts = OutboundAllowedHosts {
allowed_hosts_future: allowed_hosts_future.clone(),
disallowed_host_callback: self.disallowed_host_callback,
};
wasi_builder.outbound_socket_addr_check(move |addr, addr_use| {
let hosts_future = hosts_future.clone();
let allowed_hosts = allowed_hosts.clone();
async move {
match hosts_future.await {
Ok(allowed_hosts) => {
// TODO: validate against existing spin-core behavior
let scheme = match addr_use {
SocketAddrUse::TcpBind => return false,
SocketAddrUse::TcpConnect => "tcp",
SocketAddrUse::UdpBind | SocketAddrUse::UdpConnect | SocketAddrUse::UdpOutgoingDatagram => "udp",
};
spin_outbound_networking::check_url(&addr.to_string(),scheme, &allowed_hosts)
}
Err(err) => {
// TODO: should this trap (somehow)?
tracing::error!(%err, "allowed_outbound_hosts variable resolution failed");
false
}
}
// TODO: validate against existing spin-core behavior
let scheme = match addr_use {
SocketAddrUse::TcpBind => return false,
SocketAddrUse::TcpConnect => "tcp",
SocketAddrUse::UdpBind | SocketAddrUse::UdpConnect | SocketAddrUse::UdpOutgoingDatagram => "udp",
};
allowed_hosts.check_url(&addr.to_string(), scheme).await.unwrap_or_else(|err| {
// TODO: should this trap (somehow)?
tracing::error!(%err, "allowed_outbound_hosts variable resolution failed");
false
})
Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This whole diff chunk is basically just switching from spin_outbound_networking::check_url to OutboundAllowedHosts::check_url.

Comment on lines -24 to -31
terminal::warn!("A component tried to make a request to non-allowed url '{url}'.");
let (scheme, host, port) = (url.scheme, url.host, url.port);
let msg = if let Some(port) = port {
format!("`allowed_outbound_hosts = [\"{scheme}://{host}:{port}\"]`")
} else {
format!("`allowed_outbound_hosts = [\"{scheme}://{host}:$PORT\"]` (where $PORT is the correct port number)")
};
eprintln!("To allow requests, add {msg} to the manifest component section.");
Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This language is merged with the outbound HTTP warning language in disallowed_host_callback below. This is probably a bit of a regression around the "where $PORT is the correct port number" stuff, but I feel like we could probably do better there anyway.

@@ -58,7 +57,6 @@ impl spin_factor_outbound_http::OutboundHttpInterceptor for OutboundHttpIntercep
let resp = HostFutureIncomingResponse::pending(wasmtime_wasi::runtime::spawn(resp_fut));
InterceptOutcome::Complete(Ok(resp))
} else {
request.extensions_mut().insert(self.origin.clone());
Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@rylev It turns out this was a bad idea. WHO COULD HAVE GUESSED

.await
.map_err(|_| ErrorCode::HttpRequestUriInvalid)?;
if !is_allowed {
return Ok(Err(ErrorCode::HttpRequestDenied));
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think we're missing a call to outbound_allowed_hosts.report_disallowed_host here, no?

I see later on that report_disallowed_host gets called automatically in check_url. Doing it in the check_url call but not in allows_relative_url is confusing. I think we should encapsulate the call to report_disallowed_host in the same equivalent places for both relative and non-relative requests.

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I reworked this significantly; see the latest commit.

Comment on lines -118 to -121
let origin = request
.extensions()
.get::<SelfRequestOrigin>()
.cloned()
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

😍

.map_err(|_| ErrorCode::HttpRequestUriInvalid)?;
if !allowed_hosts.allows(&outbound_url) {
return Ok(handle_not_allowed(request.uri(), false));
}
}

if let Some(authority) = request.uri().authority() {
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nit: I know this was here before, but it seems strange we have two different blocks for when request.uri().authority() is Some instead of just combining them.

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hmm...authority should always be set here. I'll change it to an error.

disallowed_host_callback: Option<DisallowedHostCallback>,
}

pub type DisallowedHostCallback = fn(scheme: &str, authority: &str);
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I know it's more typing, but should we only limit this to function pointers instead of any impl Fn(&str, &str)?

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ugh but its so much more typing 😐

Signed-off-by: Lann Martin <[email protected]>
@lann lann merged commit cd64556 into fermyon:factors Aug 21, 2024
2 checks passed
@lann lann deleted the factors-update-outbound-net branch August 21, 2024 18:13
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

2 participants