Skip to content

Commit ea3655f

Browse files
committed
Require user to add redis host to list of allowed listed hosts
Signed-off-by: Ryan Levick <[email protected]>
1 parent e2b4098 commit ea3655f

File tree

11 files changed

+68
-7
lines changed

11 files changed

+68
-7
lines changed

Cargo.lock

Lines changed: 3 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

crates/loader/src/local.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -114,6 +114,7 @@ impl LocalLoader {
114114
let metadata = ValuesMapBuilder::new()
115115
.string("description", component.description)
116116
.string_array("allowed_http_hosts", component.allowed_http_hosts)
117+
.string_array("allowed_redis_hosts", component.allowed_redis_hosts)
117118
.string_array("key_value_stores", component.key_value_stores)
118119
.string_array("databases", component.sqlite_databases)
119120
.string_array("ai_models", component.ai_models)

crates/manifest/src/compat.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -69,6 +69,7 @@ pub fn v1_to_v2_app(manifest: v1::AppManifestV1) -> Result<v2::AppManifest, Erro
6969
sqlite_databases,
7070
ai_models,
7171
build: component.build,
72+
allowed_redis_hosts: component.allowed_redis_hosts,
7273
},
7374
);
7475
triggers

crates/manifest/src/schema/v1.rs

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -78,6 +78,9 @@ pub struct ComponentV1 {
7878
/// `allowed_http_hosts = ["example.com"]`
7979
#[serde(default)]
8080
pub allowed_http_hosts: Vec<String>,
81+
/// `allowed_redis_hosts` = ["redis://redis.com:6379"]`
82+
#[serde(default)]
83+
pub allowed_redis_hosts: Vec<String>,
8184
/// `key_value_stores = ["default"]`
8285
#[serde(default)]
8386
pub key_value_stores: Vec<String>,

crates/manifest/src/schema/v2.rs

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -102,6 +102,9 @@ pub struct Component {
102102
/// `allowed_http_hosts = ["example.com"]`
103103
#[serde(default, skip_serializing_if = "Vec::is_empty")]
104104
pub allowed_http_hosts: Vec<String>,
105+
/// `allowed_redis_hosts = ["myredishost.com"]`
106+
#[serde(default, skip_serializing_if = "Vec::is_empty")]
107+
pub allowed_redis_hosts: Vec<String>,
105108
/// `key_value_stores = ["default"]`
106109
#[serde(default, skip_serializing_if = "Vec::is_empty")]
107110
pub key_value_stores: Vec<SnakeId>,

crates/outbound-redis/Cargo.toml

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,8 +10,11 @@ doctest = false
1010
[dependencies]
1111
anyhow = "1.0"
1212
redis = { version = "0.21", features = ["tokio-comp", "tokio-native-tls-comp"] }
13+
spin-app = { path = "../app" }
1314
spin-core = { path = "../core" }
15+
spin-locked-app = { path = "../locked-app" }
1416
spin-world = { path = "../world" }
1517
table = { path = "../table" }
1618
tokio = { version = "1", features = ["sync"] }
1719
tracing = { workspace = true }
20+
terminal = { path = "../terminal" }

crates/outbound-redis/src/host_component.rs

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
use spin_app::DynamicHostComponent;
12
use spin_core::HostComponent;
23

34
use crate::OutboundRedis;
@@ -18,3 +19,17 @@ impl HostComponent for OutboundRedisComponent {
1819
Default::default()
1920
}
2021
}
22+
23+
impl DynamicHostComponent for OutboundRedisComponent {
24+
fn update_data(
25+
&self,
26+
data: &mut Self::Data,
27+
component: &spin_app::AppComponent,
28+
) -> anyhow::Result<()> {
29+
let hosts = component
30+
.get_metadata(crate::ALLOWED_REDIS_HOSTS_KEY)?
31+
.unwrap_or_default();
32+
data.allowed_hosts.extend(hosts);
33+
Ok(())
34+
}
35+
}

crates/outbound-redis/src/lib.rs

Lines changed: 31 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,17 @@
11
mod host_component;
22

3+
use std::collections::HashSet;
4+
35
use anyhow::Result;
46
use redis::{aio::Connection, AsyncCommands, FromRedisValue, Value};
57
use spin_core::{async_trait, wasmtime::component::Resource};
8+
use spin_locked_app::MetadataKey;
69
use spin_world::v1::redis::{self as v1, RedisParameter, RedisResult};
710
use spin_world::v2::redis::{self as v2, Connection as RedisConnection, Error};
811

12+
pub const ALLOWED_REDIS_HOSTS_KEY: MetadataKey<Vec<String>> =
13+
MetadataKey::new("allowed_redis_hosts");
14+
915
pub use host_component::OutboundRedisComponent;
1016

1117
struct RedisResults(Vec<RedisResult>);
@@ -29,22 +35,24 @@ impl FromRedisValue for RedisResults {
2935
}
3036

3137
pub struct OutboundRedis {
38+
allowed_hosts: HashSet<String>,
3239
connections: table::Table<Connection>,
3340
}
3441

3542
impl Default for OutboundRedis {
3643
fn default() -> Self {
3744
Self {
45+
allowed_hosts: Default::default(),
3846
connections: table::Table::new(1024),
3947
}
4048
}
4149
}
4250

43-
impl v2::Host for OutboundRedis {}
44-
45-
#[async_trait]
46-
impl v2::HostConnection for OutboundRedis {
47-
async fn open(&mut self, address: String) -> Result<Result<Resource<RedisConnection>, Error>> {
51+
impl OutboundRedis {
52+
async fn establish_connection(
53+
&mut self,
54+
address: String,
55+
) -> Result<Result<Resource<RedisConnection>, Error>> {
4856
Ok(async {
4957
let conn = redis::Client::open(address.as_str())
5058
.map_err(|_| Error::InvalidAddress)?
@@ -58,6 +66,23 @@ impl v2::HostConnection for OutboundRedis {
5866
}
5967
.await)
6068
}
69+
}
70+
71+
impl v2::Host for OutboundRedis {}
72+
73+
#[async_trait]
74+
impl v2::HostConnection for OutboundRedis {
75+
async fn open(&mut self, address: String) -> Result<Result<Resource<RedisConnection>, Error>> {
76+
if !self.allowed_hosts.contains(&address) {
77+
terminal::warn!(
78+
"A component tried to make a HTTP request to non-allowed address '{address}'."
79+
);
80+
eprintln!("To allow requests, add 'allowed_redis_hosts = [\"{address}\"]' to the manifest component section.");
81+
return Ok(Err(Error::InvalidAddress));
82+
}
83+
84+
self.establish_connection(address).await
85+
}
6186

6287
async fn publish(
6388
&mut self,
@@ -214,7 +239,7 @@ fn other_error(e: impl std::fmt::Display) -> Error {
214239
/// Delegate a function call to the v2::HostConnection implementation
215240
macro_rules! delegate {
216241
($self:ident.$name:ident($address:expr, $($arg:expr),*)) => {{
217-
let connection = match <Self as v2::HostConnection>::open($self, $address).await? {
242+
let connection = match $self.establish_connection($address).await? {
218243
Ok(c) => c,
219244
Err(_) => return Ok(Err(v1::Error::Error)),
220245
};

crates/trigger/src/lib.rs

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -112,9 +112,12 @@ impl<Executor: TriggerExecutor> TriggerExecutorBuilder<Executor> {
112112

113113
if !self.disable_default_host_components {
114114
builder.link_import(|l, _| wasmtime_wasi_http::proxy::add_to_linker(l))?;
115-
builder.add_host_component(outbound_redis::OutboundRedisComponent)?;
116115
builder.add_host_component(outbound_pg::OutboundPg::default())?;
117116
builder.add_host_component(outbound_mysql::OutboundMysql::default())?;
117+
self.loader.add_dynamic_host_component(
118+
&mut builder,
119+
outbound_redis::OutboundRedisComponent,
120+
)?;
118121
self.loader.add_dynamic_host_component(
119122
&mut builder,
120123
runtime_config::llm::build_component(&runtime_config, init_data.llm.use_gpu)

examples/rust-outbound-redis/spin.toml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@ version = "0.1.0"
88
environment = { REDIS_ADDRESS = "redis://127.0.0.1:6379", REDIS_CHANNEL = "messages" }
99
id = "outbound-redis"
1010
source = "target/wasm32-wasi/release/rust_outbound_redis.wasm"
11+
allowed_redis_hosts = ["redis://127.0.0.1:6379"]
1112
[component.trigger]
1213
route = "/publish"
1314
[component.build]

0 commit comments

Comments
 (0)