From c557e81d3301a3bc7ab5b13d220b44f5fb45be4b Mon Sep 17 00:00:00 2001 From: Brendan Burns Date: Tue, 16 May 2023 22:36:38 +0000 Subject: [PATCH 1/3] Add restrictions for method/scheme/host --- crates/wasi-http/src/http_impl.rs | 32 ++++++++++++++++++++++++++++ crates/wasi-http/src/struct.rs | 35 +++++++++++++++++++++++++++++++ 2 files changed, 67 insertions(+) diff --git a/crates/wasi-http/src/http_impl.rs b/crates/wasi-http/src/http_impl.rs index 239e54a68771..d31ce730644c 100644 --- a/crates/wasi-http/src/http_impl.rs +++ b/crates/wasi-http/src/http_impl.rs @@ -82,12 +82,44 @@ impl WasiHttp { crate::wasi::http::types::Method::Other(s) => bail!("unknown method {}", s), }; + let mut allowed = false; + let method_str = request.method.to_string(); + for allowed_method in self.allowed_methods.iter() { + if allowed_method == "*" { + allowed = true; + break; + } + if method_str == *allowed_method { + allowed = true; + break; + } + } + if !allowed { + 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), }; + let mut allowed = false; + let scheme_str = request.scheme.to_string(); + for allowed_scheme in self.allowed_schemes.iter() { + if allowed_scheme == "*" { + allowed = true; + break; + } + if scheme_str == *allowed_scheme { + allowed = true; + break; + } + } + if !allowed { + bail!("Scheme {} is not allowed.", scheme_str); + } + // Largely adapted from https://hyper.rs/guides/1/client/basic/ let authority = match request.authority.find(":") { Some(_) => request.authority.clone(), diff --git a/crates/wasi-http/src/struct.rs b/crates/wasi-http/src/struct.rs index d73fd7c6d562..3509a0e0e5d6 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,9 @@ pub struct WasiHttp { pub fields: HashMap>>>, pub streams: HashMap, pub futures: HashMap, + + pub allowed_methods: Vec, + pub allowed_schemes: Vec, } #[derive(Clone)] @@ -119,6 +152,8 @@ impl WasiHttp { fields: HashMap::new(), streams: HashMap::new(), futures: HashMap::new(), + allowed_methods: vec!("*".to_string()), + allowed_schemes: vec!("*".to_string()), } } } From d3be496c972aef24cc81131f4477a0cde7f425ed Mon Sep 17 00:00:00 2001 From: Brendan Burns Date: Wed, 17 May 2023 19:37:06 +0000 Subject: [PATCH 2/3] Add allow-lists for HTTP requests. --- crates/test-programs/tests/wasi-http.rs | 5 +- .../src/bin/outbound_request.rs | 32 ++++++++++ crates/wasi-http/src/http_impl.rs | 59 ++++++++++--------- crates/wasi-http/src/struct.rs | 6 +- 4 files changed, 72 insertions(+), 30 deletions(-) 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..6f75eb1c21a5 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,37 @@ fn main() -> Result<()> { "Error::UnexpectedError(\"unsupported scheme WS\")" ); + // Delete is not an allowed method in this test. + let r6 = request( + types::MethodParam::Delete, + types::SchemeParam::Http, + "localhost:3000", + "/", + "", + &[], + ); + + let error = r6.unwrap_err(); + assert_eq!( + error.to_string(), + "ErrorResult::UnexpectedError(\"Method DELETE is not allowed.\")" + ); + + // localhost:8080 is not an allowed authority in this test. + let r7 = request( + types::MethodParam::Get, + types::SchemeParam::Http, + "localhost:8080", + "/", + "", + &[], + ); + + let error = r7.unwrap_err(); + assert_eq!( + error.to_string(), + "ErrorResult::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 d31ce730644c..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,19 +94,7 @@ impl WasiHttp { crate::wasi::http::types::Method::Other(s) => bail!("unknown method {}", s), }; - let mut allowed = false; - let method_str = request.method.to_string(); - for allowed_method in self.allowed_methods.iter() { - if allowed_method == "*" { - allowed = true; - break; - } - if method_str == *allowed_method { - allowed = true; - break; - } - } - if !allowed { + if !is_allowed(&self.allowed_methods, method.to_string()) { bail!("Method {} is not allowed.", method.to_string()); } @@ -104,20 +104,22 @@ impl WasiHttp { Scheme::Other(s) => bail!("unsupported scheme {}", s), }; - let mut allowed = false; - let scheme_str = request.scheme.to_string(); - for allowed_scheme in self.allowed_schemes.iter() { - if allowed_scheme == "*" { - allowed = true; - break; - } - if scheme_str == *allowed_scheme { - allowed = true; - break; - } - } - if !allowed { - bail!("Scheme {} is not allowed.", scheme_str); + 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/ @@ -125,6 +127,9 @@ impl WasiHttp { 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 3509a0e0e5d6..e1a25ce3a758 100644 --- a/crates/wasi-http/src/struct.rs +++ b/crates/wasi-http/src/struct.rs @@ -53,6 +53,7 @@ pub struct WasiHttp { pub allowed_methods: Vec, pub allowed_schemes: Vec, + pub allowed_authorities: Vec, } #[derive(Clone)] @@ -152,8 +153,9 @@ impl WasiHttp { fields: HashMap::new(), streams: HashMap::new(), futures: HashMap::new(), - allowed_methods: vec!("*".to_string()), - allowed_schemes: vec!("*".to_string()), + allowed_methods: vec!["*".to_string()], + allowed_schemes: vec!["*".to_string()], + allowed_authorities: vec!["*".to_string()], } } } From 2f752de1d28b42e534c58bf4cf6305544e30430e Mon Sep 17 00:00:00 2001 From: Brendan Burns Date: Wed, 17 May 2023 23:28:55 +0000 Subject: [PATCH 3/3] Fix compile errors. --- .../wasi-http-tests/src/bin/outbound_request.rs | 14 ++++++-------- 1 file changed, 6 insertions(+), 8 deletions(-) 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 6f75eb1c21a5..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 @@ -186,34 +186,32 @@ fn main() -> Result<()> { // Delete is not an allowed method in this test. let r6 = request( - types::MethodParam::Delete, - types::SchemeParam::Http, + wasi::http::types::Method::Delete, + wasi::http::types::Scheme::Http, "localhost:3000", "/", - "", &[], ); let error = r6.unwrap_err(); assert_eq!( error.to_string(), - "ErrorResult::UnexpectedError(\"Method DELETE is not allowed.\")" + "Error::UnexpectedError(\"Method DELETE is not allowed.\")" ); // localhost:8080 is not an allowed authority in this test. let r7 = request( - types::MethodParam::Get, - types::SchemeParam::Http, + wasi::http::types::Method::Get, + wasi::http::types::Scheme::Http, "localhost:8080", "/", - "", &[], ); let error = r7.unwrap_err(); assert_eq!( error.to_string(), - "ErrorResult::UnexpectedError(\"Authority localhost:8080 is not allowed.\")" + "Error::UnexpectedError(\"Authority localhost:8080 is not allowed.\")" ); Ok(())