Skip to content

Commit 5364131

Browse files
lannrylev
authored andcommitted
outbound-networking: More BlockedNetworks integration
Signed-off-by: Lann Martin <[email protected]> Signed-off-by: Ryan Levick <[email protected]>
1 parent d1b931c commit 5364131

File tree

6 files changed

+85
-16
lines changed

6 files changed

+85
-16
lines changed

crates/factor-outbound-http/src/lib.rs

Lines changed: 23 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@ mod wasi;
55
pub mod wasi_2023_10_18;
66
pub mod wasi_2023_11_10;
77

8-
use std::sync::Arc;
8+
use std::{net::SocketAddr, sync::Arc};
99

1010
use anyhow::Context;
1111
use http::{
@@ -200,3 +200,25 @@ pub struct AppState {
200200
/// A semaphore to limit the number of concurrent outbound connections.
201201
concurrent_outbound_connections_semaphore: Option<Arc<Semaphore>>,
202202
}
203+
204+
/// Removes IPs in the given [`BlockedNetworks`].
205+
///
206+
/// Returns [`ErrorCode::DestinationIpProhibited`] if all IPs are removed.
207+
fn remove_blocked_addrs(
208+
blocked_networks: &BlockedNetworks,
209+
addrs: &mut Vec<SocketAddr>,
210+
) -> Result<(), ErrorCode> {
211+
if addrs.is_empty() {
212+
return Ok(());
213+
}
214+
let blocked_addrs = blocked_networks.remove_blocked(addrs);
215+
if addrs.is_empty() && !blocked_addrs.is_empty() {
216+
tracing::error!(
217+
"error.type" = "destination_ip_prohibited",
218+
?blocked_addrs,
219+
"all destination IP(s) prohibited by runtime config"
220+
);
221+
return Err(ErrorCode::DestinationIpProhibited);
222+
}
223+
Ok(())
224+
}

crates/factor-outbound-http/src/spin.rs

Lines changed: 22 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,7 @@
1+
use std::sync::Arc;
2+
13
use http_body_util::BodyExt;
4+
use spin_factor_outbound_networking::config::blocked_networks::BlockedNetworks;
25
use spin_world::v1::{
36
http as spin_http,
47
http_types::{self, HttpError, Method, Request, Response},
@@ -90,7 +93,8 @@ impl spin_http::Host for crate::InstanceState {
9093
// Allow reuse of Client's internal connection pool for multiple requests
9194
// in a single component execution
9295
let client = self.spin_http_client.get_or_insert_with(|| {
93-
let mut builder = reqwest::Client::builder();
96+
let mut builder = reqwest::Client::builder()
97+
.dns_resolver(Arc::new(SpinResolver(self.blocked_networks.clone())));
9498
if !self.connection_pooling_enabled {
9599
builder = builder.pool_max_idle_per_host(0);
96100
}
@@ -113,6 +117,23 @@ impl spin_http::Host for crate::InstanceState {
113117
}
114118
}
115119

120+
struct SpinResolver(BlockedNetworks);
121+
122+
impl reqwest::dns::Resolve for SpinResolver {
123+
fn resolve(&self, name: reqwest::dns::Name) -> reqwest::dns::Resolving {
124+
let blocked_networks = self.0.clone();
125+
Box::pin(async move {
126+
let mut addrs = tokio::net::lookup_host(name.as_str())
127+
.await
128+
.map_err(Box::new)?
129+
.collect::<Vec<_>>();
130+
// Remove blocked IPs
131+
crate::remove_blocked_addrs(&blocked_networks, &mut addrs).map_err(Box::new)?;
132+
Ok(Box::new(addrs.into_iter()) as reqwest::dns::Addrs)
133+
})
134+
}
135+
}
136+
116137
impl http_types::Host for crate::InstanceState {
117138
fn convert_http_error(&mut self, err: HttpError) -> anyhow::Result<HttpError> {
118139
Ok(err)

crates/factor-outbound-http/src/wasi.rs

Lines changed: 1 addition & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -587,15 +587,7 @@ impl ConnectOptions {
587587
};
588588

589589
// Remove blocked IPs
590-
let blocked_addrs = self.blocked_networks.remove_blocked(&mut socket_addrs);
591-
if socket_addrs.is_empty() && !blocked_addrs.is_empty() {
592-
tracing::error!(
593-
"error.type" = "destination_ip_prohibited",
594-
?blocked_addrs,
595-
"all destination IP(s) prohibited by runtime config"
596-
);
597-
return Err(ErrorCode::DestinationIpProhibited);
598-
}
590+
crate::remove_blocked_addrs(&self.blocked_networks, &mut socket_addrs)?;
599591

600592
// If we're limiting concurrent outbound requests, acquire a permit
601593
let permit = match &self.concurrent_outbound_connections_semaphore {

crates/factor-outbound-redis/Cargo.toml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@ spin-factor-outbound-networking = { path = "../factor-outbound-networking" }
1212
spin-factors = { path = "../factors" }
1313
spin-resource-table = { path = "../table" }
1414
spin-world = { path = "../world" }
15+
tokio = { workspace = true }
1516
tracing = { workspace = true }
1617

1718
[dev-dependencies]

crates/factor-outbound-redis/src/host.rs

Lines changed: 35 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,12 @@
1+
use std::net::SocketAddr;
2+
13
use anyhow::Result;
4+
use redis::io::AsyncDNSResolver;
5+
use redis::AsyncConnectionConfig;
26
use redis::{aio::MultiplexedConnection, AsyncCommands, FromRedisValue, Value};
37
use spin_core::wasmtime::component::Resource;
48
use spin_factor_outbound_networking::config::allowed_hosts::OutboundAllowedHosts;
9+
use spin_factor_outbound_networking::config::blocked_networks::BlockedNetworks;
510
use spin_world::v1::{redis as v1, redis_types};
611
use spin_world::v2::redis::{
712
self as v2, Connection as RedisConnection, Error, RedisParameter, RedisResult,
@@ -11,6 +16,7 @@ use tracing::{instrument, Level};
1116

1217
pub struct InstanceState {
1318
pub allowed_hosts: OutboundAllowedHosts,
19+
pub blocked_networks: BlockedNetworks,
1420
pub connections: spin_resource_table::Table<MultiplexedConnection>,
1521
}
1622

@@ -23,9 +29,11 @@ impl InstanceState {
2329
&mut self,
2430
address: String,
2531
) -> Result<Resource<RedisConnection>, Error> {
32+
let config = AsyncConnectionConfig::new()
33+
.set_dns_resolver(SpinResolver(self.blocked_networks.clone()));
2634
let conn = redis::Client::open(address.as_str())
2735
.map_err(|_| Error::InvalidAddress)?
28-
.get_multiplexed_async_connection()
36+
.get_multiplexed_async_connection_with_config(&config)
2937
.await
3038
.map_err(other_error)?;
3139
self.connections
@@ -365,3 +373,29 @@ impl FromRedisValue for RedisResults {
365373
Ok(RedisResults(values))
366374
}
367375
}
376+
377+
struct SpinResolver(BlockedNetworks);
378+
379+
impl AsyncDNSResolver for SpinResolver {
380+
fn resolve<'a, 'b: 'a>(
381+
&'a self,
382+
host: &'b str,
383+
port: u16,
384+
) -> redis::RedisFuture<'a, Box<dyn Iterator<Item = std::net::SocketAddr> + Send + 'a>> {
385+
Box::pin(async move {
386+
let mut addrs = tokio::net::lookup_host((host, port))
387+
.await?
388+
.collect::<Vec<_>>();
389+
// Remove blocked IPs
390+
let blocked_addrs = self.0.remove_blocked(&mut addrs);
391+
if addrs.is_empty() && !blocked_addrs.is_empty() {
392+
tracing::error!(
393+
"error.type" = "destination_ip_prohibited",
394+
?blocked_addrs,
395+
"all destination IP(s) prohibited by runtime config"
396+
);
397+
}
398+
Ok(Box::new(addrs.into_iter()) as Box<dyn Iterator<Item = SocketAddr> + Send>)
399+
})
400+
}
401+
}

crates/factor-outbound-redis/src/lib.rs

Lines changed: 3 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -41,11 +41,10 @@ impl Factor for OutboundRedisFactor {
4141
&self,
4242
mut ctx: PrepareContext<T, Self>,
4343
) -> anyhow::Result<Self::InstanceBuilder> {
44-
let allowed_hosts = ctx
45-
.instance_builder::<OutboundNetworkingFactor>()?
46-
.allowed_hosts();
44+
let outbound_networking = ctx.instance_builder::<OutboundNetworkingFactor>()?;
4745
Ok(InstanceState {
48-
allowed_hosts,
46+
allowed_hosts: outbound_networking.allowed_hosts(),
47+
blocked_networks: outbound_networking.blocked_networks(),
4948
connections: spin_resource_table::Table::new(1024),
5049
})
5150
}

0 commit comments

Comments
 (0)