Skip to content

Commit

Permalink
allow outbound calls to components in same spin app
Browse files Browse the repository at this point in the history
+ use special value `self` in allowed_http_hosts
to allow components in the same spin app to make outbound
http requests to each other
+ make requests with relative routes in app once self is set
+ update example for rust outbound http to demonstrate capability
+ adds e2e
+ resolves spinframework#957
+ resolves spinframework#1533

Signed-off-by: Michelle Dhanani <michelle@fermyon.com>
  • Loading branch information
michelleN committed Aug 31, 2023
1 parent 16dd133 commit 5b9c013
Show file tree
Hide file tree
Showing 26 changed files with 337 additions and 518 deletions.
1 change: 1 addition & 0 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

24 changes: 24 additions & 0 deletions crates/outbound-http/src/allowed_http_hosts.rs
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,13 @@ impl AllowedHttpHosts {
Self::AllowSpecific(hosts) => hosts.iter().any(|h| h.allow(url)),
}
}

pub fn includes(&self, host: AllowedHttpHost) -> bool {
match self {
Self::AllowAll => true,
Self::AllowSpecific(hosts) => hosts.contains(&host),
}
}
}

/// An HTTP host allow-list entry.
Expand Down Expand Up @@ -214,6 +221,14 @@ mod test {
);
}

#[test]
fn test_allowed_hosts_accepts_self() {
assert_eq!(
AllowedHttpHost::host("self"),
parse_allowed_http_host("self").unwrap()
);
}

#[test]
fn test_allowed_hosts_accepts_localhost_addresses() {
assert_eq!(
Expand Down Expand Up @@ -303,4 +318,13 @@ mod test {
assert!(!allowed.allow(&Url::parse("http://example.com/").unwrap()));
assert!(!allowed.allow(&Url::parse("http://google.com/").unwrap()));
}

#[test]
fn test_allowed_hosts_includes() {
let allowed =
parse_allowed_http_hosts(&to_vec_owned(&["self", "http://example.com:8383"])).unwrap();
assert!(allowed.includes(AllowedHttpHost::host("self")));
assert!(allowed.includes(AllowedHttpHost::host_and_port("example.com", 8383)));
assert!(!allowed.includes(AllowedHttpHost::host("google.com")));
}
}
29 changes: 24 additions & 5 deletions crates/outbound-http/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ use spin_world::{
http_types::{Headers, HttpError, Method, Request, Response},
};

use allowed_http_hosts::AllowedHttpHosts;
use allowed_http_hosts::{AllowedHttpHost, AllowedHttpHosts};
pub use host_component::OutboundHttpComponent;

pub const ALLOWED_HTTP_HOSTS_KEY: MetadataKey<Vec<String>> = MetadataKey::new("allowed_http_hosts");
Expand All @@ -21,15 +21,27 @@ pub const ALLOWED_HTTP_HOSTS_KEY: MetadataKey<Vec<String>> = MetadataKey::new("a
pub struct OutboundHttp {
/// List of hosts guest modules are allowed to make requests to.
pub allowed_hosts: AllowedHttpHosts,
/// During an incoming HTTP request, origin is set to the host of that incoming HTTP request.
/// This is used to direct outbound requests to the same host when allowed.
pub origin: String,
client: Option<Client>,
}

impl OutboundHttp {
/// Check if guest module is allowed to send request to URL, based on the list of
/// allowed hosts defined by the runtime. If the list of allowed hosts contains
/// allowed hosts defined by the runtime. If the url passed in is a relative path,
/// only allow if allowed_hosts contains `self`. If the list of allowed hosts contains
/// `insecure:allow-all`, then all hosts are allowed.
/// If `None` is passed, the guest module is not allowed to send the request.
fn is_allowed(&self, url: &str) -> Result<bool, HttpError> {
fn is_allowed(&mut self, url: &str) -> Result<bool, HttpError> {
if url.starts_with('/') {
if self.allowed_hosts.includes(AllowedHttpHost::host("self")) {
return Ok(true);
} else {
return Ok(false);
}
}

let url = Url::parse(url).map_err(|_| HttpError::InvalidUrl)?;
Ok(self.allowed_hosts.allow(&url))
}
Expand All @@ -49,7 +61,14 @@ impl outbound_http::Host for OutboundHttp {
}

let method = method_from(req.method);
let url = Url::parse(&req.uri).map_err(|_| HttpError::InvalidUrl)?;

let req_url: Url = if req.uri.starts_with('/') {
Url::parse(&format!("{}{}", self.origin, req.uri))
.map_err(|_| HttpError::InvalidUrl)?
} else {
Url::parse(&req.uri).map_err(|_| HttpError::InvalidUrl)?
};

let headers = request_headers(req.headers).map_err(|_| HttpError::RuntimeError)?;
let body = req.body.unwrap_or_default().to_vec();

Expand All @@ -62,7 +81,7 @@ impl outbound_http::Host for OutboundHttp {
let client = self.client.get_or_insert_with(Default::default);

let resp = client
.request(method, url)
.request(method, req_url)
.headers(headers)
.body(body)
.send()
Expand Down
1 change: 1 addition & 0 deletions crates/trigger-http/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ futures-util = "0.3.8"
http = "0.2"
hyper = { version = "0.14", features = ["full"] }
indexmap = "1"
outbound-http = { path = "../outbound-http" }
percent-encoding = "2"
rustls-pemfile = "0.3.0"
serde = { version = "1.0", features = ["derive"] }
Expand Down
19 changes: 18 additions & 1 deletion crates/trigger-http/src/spin.rs
Original file line number Diff line number Diff line change
Expand Up @@ -27,11 +27,28 @@ impl HttpExecutor for SpinHttpExecutor {
component_id
);

let (instance, store) = engine.prepare_instance(component_id).await?;
let (instance, mut store) = engine.prepare_instance(component_id).await?;
let EitherInstance::Component(instance) = instance else {
unreachable!()
};

if let Some(authority) = req.uri().authority() {
if let Some(scheme) = req.uri().scheme_str() {
let origin_info = format!("{}://{}", scheme, authority);
if let Some(outbound_http_handle) = engine
.engine
.find_host_component_handle::<std::sync::Arc<outbound_http::OutboundHttpComponent>>(
)
{
let outbound_http_data = store
.host_components_data()
.get_or_insert(outbound_http_handle);

outbound_http_data.origin = origin_info
}
}
}

let resp = Self::execute_impl(store, instance, base, raw_route, req, client_addr)
.await
.map_err(contextualise_err)?;
Expand Down
Loading

0 comments on commit 5b9c013

Please sign in to comment.