11use std:: { error:: Error , sync:: Arc } ;
22
33use anyhow:: Context ;
4- use http:: { header:: HOST , uri :: Authority , Request , Uri } ;
4+ use http:: { header:: HOST , Request } ;
55use http_body_util:: BodyExt ;
66use rustls:: ClientConfig ;
7- use spin_factor_outbound_networking:: { OutboundAllowedHosts , OutboundUrl } ;
7+ use spin_factor_outbound_networking:: OutboundAllowedHosts ;
88use spin_factors:: { wasmtime:: component:: ResourceTable , RuntimeFactorsInstanceState } ;
99use tokio:: { net:: TcpStream , time:: timeout} ;
10- use tracing:: Instrument ;
10+ use tracing:: { field :: Empty , instrument , Instrument } ;
1111use wasmtime_wasi_http:: {
1212 bindings:: http:: types:: ErrorCode ,
1313 body:: HyperOutgoingBody ,
@@ -68,6 +68,19 @@ impl<'a> WasiHttpView for WasiHttpImplInner<'a> {
6868 self . table
6969 }
7070
71+ #[ instrument(
72+ name = "spin_outbound_http.send_request" ,
73+ skip_all,
74+ fields(
75+ otel. kind = "client" ,
76+ url. full = %request. uri( ) ,
77+ http. request. method = %request. method( ) ,
78+ otel. name = %request. method( ) ,
79+ http. response. status_code = Empty ,
80+ server. address = Empty ,
81+ server. port = Empty ,
82+ ) ,
83+ ) ]
7184 fn send_request (
7285 & mut self ,
7386 mut request : Request < wasmtime_wasi_http:: body:: HyperOutgoingBody > ,
@@ -93,6 +106,7 @@ impl<'a> WasiHttpView for WasiHttpImplInner<'a> {
93106 request,
94107 config,
95108 self . state . allowed_hosts . clone ( ) ,
109+ self . state . self_request_origin . clone ( ) ,
96110 tls_client_config,
97111 )
98112 . in_current_span ( ) ,
@@ -104,67 +118,52 @@ impl<'a> WasiHttpView for WasiHttpImplInner<'a> {
104118async fn send_request_impl (
105119 mut request : Request < wasmtime_wasi_http:: body:: HyperOutgoingBody > ,
106120 mut config : wasmtime_wasi_http:: types:: OutgoingRequestConfig ,
107- allowed_hosts : OutboundAllowedHosts ,
121+ outbound_allowed_hosts : OutboundAllowedHosts ,
122+ self_request_origin : Option < SelfRequestOrigin > ,
108123 tls_client_config : Arc < ClientConfig > ,
109124) -> anyhow:: Result < Result < IncomingResponse , ErrorCode > > {
110- let allowed_hosts = allowed_hosts. resolve ( ) . await ?;
111-
112- let is_relative_url = request. uri ( ) . authority ( ) . is_none ( ) ;
113- if is_relative_url {
114- if !allowed_hosts. allows_relative_url ( & [ "http" , "https" ] ) {
115- return Ok ( handle_not_allowed ( request. uri ( ) , true ) ) ;
125+ if request. uri ( ) . authority ( ) . is_some ( ) {
126+ // Absolute URI
127+ let is_allowed = outbound_allowed_hosts
128+ . check_url ( & request. uri ( ) . to_string ( ) , "https" )
129+ . await
130+ . unwrap_or ( false ) ;
131+ if !is_allowed {
132+ return Ok ( Err ( ErrorCode :: HttpRequestDenied ) ) ;
133+ }
134+ } else {
135+ // Relative URI ("self" request)
136+ let is_allowed = outbound_allowed_hosts
137+ . check_relative_url ( & [ "http" , "https" ] )
138+ . await
139+ . unwrap_or ( false ) ;
140+ if !is_allowed {
141+ return Ok ( Err ( ErrorCode :: HttpRequestDenied ) ) ;
116142 }
117143
118- let origin = request
119- . extensions ( )
120- . get :: < SelfRequestOrigin > ( )
121- . cloned ( )
122- . context ( "cannot send relative outbound request; no 'origin' set by host" ) ?;
144+ let Some ( origin) = self_request_origin else {
145+ tracing:: error!( "Couldn't handle outbound HTTP request to relative URI; no origin set" ) ;
146+ return Ok ( Err ( ErrorCode :: HttpRequestUriInvalid ) ) ;
147+ } ;
123148
124149 config. use_tls = origin. use_tls ( ) ;
125150
126151 request. headers_mut ( ) . insert ( HOST , origin. host_header ( ) ) ;
127152
128153 let path_and_query = request. uri ( ) . path_and_query ( ) . cloned ( ) ;
129154 * request. uri_mut ( ) = origin. into_uri ( path_and_query) ;
130- } else {
131- let outbound_url = OutboundUrl :: parse ( request. uri ( ) . to_string ( ) , "https" )
132- . map_err ( |_| ErrorCode :: HttpRequestUriInvalid ) ?;
133- if !allowed_hosts. allows ( & outbound_url) {
134- return Ok ( handle_not_allowed ( request. uri ( ) , false ) ) ;
135- }
136155 }
137156
138- if let Some ( authority) = request. uri ( ) . authority ( ) {
139- let current_span = tracing:: Span :: current ( ) ;
140- current_span. record ( "server.address" , authority. host ( ) ) ;
141- if let Some ( port) = authority. port ( ) {
142- current_span. record ( "server.port" , port. as_u16 ( ) ) ;
143- }
157+ let authority = request. uri ( ) . authority ( ) . context ( "authority not set" ) ?;
158+ let current_span = tracing:: Span :: current ( ) ;
159+ current_span. record ( "server.address" , authority. host ( ) ) ;
160+ if let Some ( port) = authority. port ( ) {
161+ current_span. record ( "server.port" , port. as_u16 ( ) ) ;
144162 }
145163
146164 Ok ( send_request_handler ( request, config, tls_client_config) . await )
147165}
148166
149- // TODO(factors): Move to some callback on spin-factor-outbound-networking (?)
150- fn handle_not_allowed ( uri : & Uri , is_relative : bool ) -> Result < IncomingResponse , ErrorCode > {
151- tracing:: error!( "Destination not allowed!: {uri}" ) ;
152- let allowed_host_example = if is_relative {
153- terminal:: warn!( "A component tried to make a HTTP request to the same component but it does not have permission." ) ;
154- "http://self" . to_string ( )
155- } else {
156- let host = format ! (
157- "{scheme}://{authority}" ,
158- scheme = uri. scheme_str( ) . unwrap_or_default( ) ,
159- authority = uri. authority( ) . map( Authority :: as_str) . unwrap_or_default( )
160- ) ;
161- terminal:: warn!( "A component tried to make a HTTP request to non-allowed host '{host}'." ) ;
162- host
163- } ;
164- eprintln ! ( "To allow requests, add 'allowed_outbound_hosts = [\" {allowed_host_example}\" ]' to the manifest component section." ) ;
165- Err ( ErrorCode :: HttpRequestDenied )
166- }
167-
168167/// This is a fork of wasmtime_wasi_http::default_send_request_handler function
169168/// forked from bytecodealliance/wasmtime commit-sha 29a76b68200fcfa69c8fb18ce6c850754279a05b
170169/// This fork provides the ability to configure client cert auth for mTLS
0 commit comments