diff --git a/crates/test-programs/tests/wasi-http.rs b/crates/test-programs/tests/wasi-http.rs index 0b1a4fd70567..3a4d67f75fc9 100644 --- a/crates/test-programs/tests/wasi-http.rs +++ b/crates/test-programs/tests/wasi-http.rs @@ -77,12 +77,15 @@ pub fn run(name: &str) -> anyhow::Result<()> { // Create our wasi context. let builder = WasiCtxBuilder::new().inherit_stdio().arg(name)?; + let mut wasi_http = WasiHttp::new(); + wasi_http.allowed_methods = vec!["GET".to_string(), "POST".to_string(), "PUT".to_string()]; + wasi_http.allowed_authorities = vec!["localhost:3000".to_string()]; let mut store = Store::new( &ENGINE, Ctx { wasi: builder.build(), - http: WasiHttp::new(), + http: wasi_http, }, ); diff --git a/crates/test-programs/wasi-http-tests/src/bin/outbound_request.rs b/crates/test-programs/wasi-http-tests/src/bin/outbound_request.rs index 4062d370a122..3a6363685b01 100644 --- a/crates/test-programs/wasi-http-tests/src/bin/outbound_request.rs +++ b/crates/test-programs/wasi-http-tests/src/bin/outbound_request.rs @@ -184,5 +184,35 @@ fn main() -> Result<()> { "Error::UnexpectedError(\"unsupported scheme WS\")" ); + // Delete is not an allowed method in this test. + let r6 = request( + wasi::http::types::Method::Delete, + wasi::http::types::Scheme::Http, + "localhost:3000", + "/", + &[], + ); + + let error = r6.unwrap_err(); + assert_eq!( + error.to_string(), + "Error::UnexpectedError(\"Method DELETE is not allowed.\")" + ); + + // localhost:8080 is not an allowed authority in this test. + let r7 = request( + wasi::http::types::Method::Get, + wasi::http::types::Scheme::Http, + "localhost:8080", + "/", + &[], + ); + + let error = r7.unwrap_err(); + assert_eq!( + error.to_string(), + "Error::UnexpectedError(\"Authority localhost:8080 is not allowed.\")" + ); + Ok(()) } diff --git a/crates/wasi-http/src/http_impl.rs b/crates/wasi-http/src/http_impl.rs index 239e54a68771..09d22a770b93 100644 --- a/crates/wasi-http/src/http_impl.rs +++ b/crates/wasi-http/src/http_impl.rs @@ -43,6 +43,18 @@ fn port_for_scheme(scheme: &Option) -> &str { } } +fn is_allowed(allow_list: &Vec, value: String) -> bool { + for allowed in allow_list.iter() { + if allowed == "*" { + return true; + } + if value == *allowed { + return true; + } + } + false +} + impl WasiHttp { pub(crate) async fn handle_async( &mut self, @@ -82,17 +94,42 @@ impl WasiHttp { crate::wasi::http::types::Method::Other(s) => bail!("unknown method {}", s), }; + if !is_allowed(&self.allowed_methods, method.to_string()) { + bail!("Method {} is not allowed.", method.to_string()); + } + let scheme = match request.scheme.as_ref().unwrap_or(&Scheme::Https) { Scheme::Http => "http://", Scheme::Https => "https://", Scheme::Other(s) => bail!("unsupported scheme {}", s), }; + if !is_allowed( + &self.allowed_schemes, + request + .scheme + .as_ref() + .unwrap_or(&Scheme::Https) + .to_string(), + ) { + bail!( + "Scheme {} is not allowed.", + request + .scheme + .as_ref() + .unwrap_or(&Scheme::Https) + .to_string() + ); + } + // Largely adapted from https://hyper.rs/guides/1/client/basic/ let authority = match request.authority.find(":") { Some(_) => request.authority.clone(), None => request.authority.clone() + port_for_scheme(&request.scheme), }; + if !is_allowed(&self.allowed_authorities, authority.clone()) { + bail!("Authority {} is not allowed.", authority); + } let mut sender = if scheme == "https://" { #[cfg(not(any(target_arch = "riscv64", target_arch = "s390x")))] { diff --git a/crates/wasi-http/src/struct.rs b/crates/wasi-http/src/struct.rs index d73fd7c6d562..e1a25ce3a758 100644 --- a/crates/wasi-http/src/struct.rs +++ b/crates/wasi-http/src/struct.rs @@ -1,6 +1,36 @@ use crate::wasi::http::types::{Method, RequestOptions, Scheme}; use bytes::{BufMut, Bytes, BytesMut}; use std::collections::HashMap; +use std::fmt; + +impl fmt::Display for Scheme { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + let scheme_str = match self { + Scheme::Http => "http", + Scheme::Https => "https", + Scheme::Other(s) => s, + }; + write!(f, "{}", scheme_str) + } +} + +impl fmt::Display for Method { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + let method_str = match self { + Method::Get => "GET", + Method::Put => "PUT", + Method::Post => "POST", + Method::Options => "OPTIONS", + Method::Head => "HEAD", + Method::Patch => "PATCH", + Method::Connect => "CONNECT", + Method::Delete => "DELETE", + Method::Trace => "TRACE", + Method::Other(s) => s, + }; + write!(f, "{}", method_str) + } +} #[derive(Clone, Default)] pub struct Stream { @@ -20,6 +50,10 @@ pub struct WasiHttp { pub fields: HashMap>>>, pub streams: HashMap, pub futures: HashMap, + + pub allowed_methods: Vec, + pub allowed_schemes: Vec, + pub allowed_authorities: Vec, } #[derive(Clone)] @@ -119,6 +153,9 @@ impl WasiHttp { fields: HashMap::new(), streams: HashMap::new(), futures: HashMap::new(), + allowed_methods: vec!["*".to_string()], + allowed_schemes: vec!["*".to_string()], + allowed_authorities: vec!["*".to_string()], } } }