@@ -22,7 +22,24 @@ pub use runtime_config::ComponentTlsConfigs;
2222
2323pub type SharedFutureResult < T > = Shared < BoxFuture < ' static , Result < Arc < T > , Arc < anyhow:: Error > > > > ;
2424
25- pub struct OutboundNetworkingFactor ;
25+ #[ derive( Default ) ]
26+ pub struct OutboundNetworkingFactor {
27+ disallowed_host_callback : Option < DisallowedHostCallback > ,
28+ }
29+
30+ pub type DisallowedHostCallback = fn ( scheme : & str , authority : & str ) ;
31+
32+ impl OutboundNetworkingFactor {
33+ pub fn new ( ) -> Self {
34+ Self :: default ( )
35+ }
36+
37+ /// Sets a function to be called when a request is disallowed by an
38+ /// instance's configured `allowed_outbound_hosts`.
39+ pub fn set_disallowed_host_callback ( & mut self , callback : DisallowedHostCallback ) {
40+ self . disallowed_host_callback = Some ( callback) ;
41+ }
42+ }
2643
2744impl Factor for OutboundNetworkingFactor {
2845 type RuntimeConfig = RuntimeConfig ;
@@ -87,26 +104,24 @@ impl Factor for OutboundNetworkingFactor {
87104 match builders. get_mut :: < WasiFactor > ( ) {
88105 Ok ( wasi_builder) => {
89106 // Update Wasi socket allowed ports
90- let hosts_future = allowed_hosts_future. clone ( ) ;
107+ let allowed_hosts = OutboundAllowedHosts {
108+ allowed_hosts_future : allowed_hosts_future. clone ( ) ,
109+ disallowed_host_callback : self . disallowed_host_callback ,
110+ } ;
91111 wasi_builder. outbound_socket_addr_check ( move |addr, addr_use| {
92- let hosts_future = hosts_future . clone ( ) ;
112+ let allowed_hosts = allowed_hosts . clone ( ) ;
93113 async move {
94- match hosts_future. await {
95- Ok ( allowed_hosts) => {
96- // TODO: validate against existing spin-core behavior
97- let scheme = match addr_use {
98- SocketAddrUse :: TcpBind => return false ,
99- SocketAddrUse :: TcpConnect => "tcp" ,
100- SocketAddrUse :: UdpBind | SocketAddrUse :: UdpConnect | SocketAddrUse :: UdpOutgoingDatagram => "udp" ,
101- } ;
102- spin_outbound_networking:: check_url ( & addr. to_string ( ) , scheme, & allowed_hosts)
103- }
104- Err ( err) => {
105- // TODO: should this trap (somehow)?
106- tracing:: error!( %err, "allowed_outbound_hosts variable resolution failed" ) ;
107- false
108- }
109- }
114+ // TODO: validate against existing spin-core behavior
115+ let scheme = match addr_use {
116+ SocketAddrUse :: TcpBind => return false ,
117+ SocketAddrUse :: TcpConnect => "tcp" ,
118+ SocketAddrUse :: UdpBind | SocketAddrUse :: UdpConnect | SocketAddrUse :: UdpOutgoingDatagram => "udp" ,
119+ } ;
120+ allowed_hosts. check_url ( & addr. to_string ( ) , scheme) . await . unwrap_or_else ( |err| {
121+ // TODO: should this trap (somehow)?
122+ tracing:: error!( %err, "allowed_outbound_hosts variable resolution failed" ) ;
123+ false
124+ } )
110125 }
111126 } ) ;
112127 }
@@ -122,6 +137,7 @@ impl Factor for OutboundNetworkingFactor {
122137 Ok ( InstanceBuilder {
123138 allowed_hosts_future,
124139 component_tls_configs,
140+ disallowed_host_callback : self . disallowed_host_callback ,
125141 } )
126142 }
127143}
@@ -134,12 +150,14 @@ pub struct AppState {
134150pub struct InstanceBuilder {
135151 allowed_hosts_future : SharedFutureResult < AllowedHostsConfig > ,
136152 component_tls_configs : ComponentTlsConfigs ,
153+ disallowed_host_callback : Option < DisallowedHostCallback > ,
137154}
138155
139156impl InstanceBuilder {
140157 pub fn allowed_hosts ( & self ) -> OutboundAllowedHosts {
141158 OutboundAllowedHosts {
142159 allowed_hosts_future : self . allowed_hosts_future . clone ( ) ,
160+ disallowed_host_callback : self . disallowed_host_callback ,
143161 }
144162 }
145163
@@ -160,6 +178,7 @@ impl FactorInstanceBuilder for InstanceBuilder {
160178#[ derive( Clone ) ]
161179pub struct OutboundAllowedHosts {
162180 allowed_hosts_future : SharedFutureResult < AllowedHostsConfig > ,
181+ disallowed_host_callback : Option < DisallowedHostCallback > ,
163182}
164183
165184impl OutboundAllowedHosts {
@@ -170,16 +189,39 @@ impl OutboundAllowedHosts {
170189 } )
171190 }
172191
192+ /// Checks if the given URL is allowed by this component's
193+ /// `allowed_outbound_hosts`.
173194 pub async fn allows ( & self , url : & OutboundUrl ) -> anyhow:: Result < bool > {
174195 Ok ( self . resolve ( ) . await ?. allows ( url) )
175196 }
176197
198+ /// Report that an outbound connection has been disallowed by e.g.
199+ /// [`OutboundAllowedHosts::allows`] returning `false`.
200+ ///
201+ /// Calls the [`DisallowedHostCallback`] if set.
202+ pub fn report_disallowed_host ( & self , scheme : & str , authority : & str ) {
203+ if let Some ( disallowed_host_callback) = self . disallowed_host_callback {
204+ disallowed_host_callback ( scheme, authority) ;
205+ }
206+ }
207+
208+ /// Checks address against allowed hosts
209+ ///
210+ /// Calls the [`DisallowedHostCallback`] if set and URL is disallowed.
177211 pub async fn check_url ( & self , url : & str , scheme : & str ) -> anyhow:: Result < bool > {
212+ let Ok ( url) = OutboundUrl :: parse ( url, scheme) else {
213+ tracing:: warn!(
214+ "A component tried to make a request to a url that could not be parsed: {url}" ,
215+ ) ;
216+ return Ok ( false ) ;
217+ } ;
218+
178219 let allowed_hosts = self . resolve ( ) . await ?;
179- Ok ( spin_outbound_networking:: check_url (
180- url,
181- scheme,
182- & allowed_hosts,
183- ) )
220+
221+ let is_allowed = allowed_hosts. allows ( & url) ;
222+ if !is_allowed {
223+ self . report_disallowed_host ( url. scheme ( ) , & url. authority ( ) ) ;
224+ }
225+ Ok ( is_allowed)
184226 }
185227}
0 commit comments