Skip to content

Commit cd45525

Browse files
committed
Respect 'allowed_redis_hosts' even in v1 components if manifest key set
Signed-off-by: Ryan Levick <[email protected]>
1 parent ea3655f commit cd45525

File tree

6 files changed

+48
-12
lines changed

6 files changed

+48
-12
lines changed

crates/loader/src/local.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -114,7 +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)
117+
.string_array_option("allowed_redis_hosts", component.allowed_redis_hosts)
118118
.string_array("key_value_stores", component.key_value_stores)
119119
.string_array("databases", component.sqlite_databases)
120120
.string_array("ai_models", component.ai_models)

crates/locked-app/src/values.rs

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -58,6 +58,20 @@ impl ValuesMapBuilder {
5858
self.entry(key, entries)
5959
}
6060

61+
/// Inserts an optional list of strings
62+
pub fn string_array_option(
63+
&mut self,
64+
key: impl Into<String>,
65+
value: Option<impl IntoIterator<Item = impl Into<String>>>,
66+
) -> &mut Self {
67+
if let Some(value) = value {
68+
let entries = value.into_iter().map(|s| s.into()).collect::<Vec<_>>();
69+
self.entry(key, entries)
70+
} else {
71+
self.entry(key, Value::Null)
72+
}
73+
}
74+
6175
/// Inserts an entry into the map using the value's `impl Into<Value>`.
6276
pub fn entry(&mut self, key: impl Into<String>, value: impl Into<Value>) -> &mut Self {
6377
self.0.insert(key.into(), value.into());

crates/manifest/src/schema/v1.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -80,7 +80,7 @@ pub struct ComponentV1 {
8080
pub allowed_http_hosts: Vec<String>,
8181
/// `allowed_redis_hosts` = ["redis://redis.com:6379"]`
8282
#[serde(default)]
83-
pub allowed_redis_hosts: Vec<String>,
83+
pub allowed_redis_hosts: Option<Vec<String>>,
8484
/// `key_value_stores = ["default"]`
8585
#[serde(default)]
8686
pub key_value_stores: Vec<String>,

crates/manifest/src/schema/v2.rs

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -103,8 +103,8 @@ pub struct Component {
103103
#[serde(default, skip_serializing_if = "Vec::is_empty")]
104104
pub allowed_http_hosts: Vec<String>,
105105
/// `allowed_redis_hosts = ["myredishost.com"]`
106-
#[serde(default, skip_serializing_if = "Vec::is_empty")]
107-
pub allowed_redis_hosts: Vec<String>,
106+
#[serde(default, skip_serializing_if = "is_none_or_empty")]
107+
pub allowed_redis_hosts: Option<Vec<String>>,
108108
/// `key_value_stores = ["default"]`
109109
#[serde(default, skip_serializing_if = "Vec::is_empty")]
110110
pub key_value_stores: Vec<SnakeId>,
@@ -119,6 +119,11 @@ pub struct Component {
119119
pub build: Option<ComponentBuildConfig>,
120120
}
121121

122+
/// Used to skip serializing if the value is either `None` or `Some(v)` where `v` is empty
123+
fn is_none_or_empty<T>(value: &Option<Vec<T>>) -> bool {
124+
value.as_ref().map(|s| !s.is_empty()).unwrap_or_default()
125+
}
126+
122127
mod one_or_many {
123128
use serde::{Deserialize, Deserializer, Serialize, Serializer};
124129

crates/outbound-redis/src/host_component.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -29,7 +29,7 @@ impl DynamicHostComponent for OutboundRedisComponent {
2929
let hosts = component
3030
.get_metadata(crate::ALLOWED_REDIS_HOSTS_KEY)?
3131
.unwrap_or_default();
32-
data.allowed_hosts.extend(hosts);
32+
data.allowed_hosts = hosts;
3333
Ok(())
3434
}
3535
}

crates/outbound-redis/src/lib.rs

Lines changed: 24 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@ use spin_locked_app::MetadataKey;
99
use spin_world::v1::redis::{self as v1, RedisParameter, RedisResult};
1010
use spin_world::v2::redis::{self as v2, Connection as RedisConnection, Error};
1111

12-
pub const ALLOWED_REDIS_HOSTS_KEY: MetadataKey<Vec<String>> =
12+
pub const ALLOWED_REDIS_HOSTS_KEY: MetadataKey<Option<HashSet<String>>> =
1313
MetadataKey::new("allowed_redis_hosts");
1414

1515
pub use host_component::OutboundRedisComponent;
@@ -35,7 +35,7 @@ impl FromRedisValue for RedisResults {
3535
}
3636

3737
pub struct OutboundRedis {
38-
allowed_hosts: HashSet<String>,
38+
allowed_hosts: Option<HashSet<String>>,
3939
connections: table::Table<Connection>,
4040
}
4141

@@ -49,6 +49,24 @@ impl Default for OutboundRedis {
4949
}
5050

5151
impl OutboundRedis {
52+
fn is_address_allowed(&self, address: &str, default: bool) -> bool {
53+
fn do_check(allowed_hosts: Option<&HashSet<String>>, address: &str, default: bool) -> bool {
54+
let Some(allowed_hosts) = allowed_hosts else {
55+
return default;
56+
};
57+
allowed_hosts.contains(address)
58+
}
59+
60+
let response = do_check(self.allowed_hosts.as_ref(), address, default);
61+
if !response {
62+
terminal::warn!(
63+
"A component tried to make a HTTP request to non-allowed address {address:?}."
64+
);
65+
eprintln!("To allow requests, add 'allowed_redis_hosts = [{address:?}]' to the manifest component section.");
66+
}
67+
response
68+
}
69+
5270
async fn establish_connection(
5371
&mut self,
5472
address: String,
@@ -73,11 +91,7 @@ impl v2::Host for OutboundRedis {}
7391
#[async_trait]
7492
impl v2::HostConnection for OutboundRedis {
7593
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.");
94+
if !self.is_address_allowed(&address, false) {
8195
return Ok(Err(Error::InvalidAddress));
8296
}
8397

@@ -239,6 +253,9 @@ fn other_error(e: impl std::fmt::Display) -> Error {
239253
/// Delegate a function call to the v2::HostConnection implementation
240254
macro_rules! delegate {
241255
($self:ident.$name:ident($address:expr, $($arg:expr),*)) => {{
256+
if !$self.is_address_allowed(&$address, true) {
257+
return Ok(Err(v1::Error::Error));
258+
}
242259
let connection = match $self.establish_connection($address).await? {
243260
Ok(c) => c,
244261
Err(_) => return Ok(Err(v1::Error::Error)),

0 commit comments

Comments
 (0)