Skip to content

Commit f5f00ae

Browse files
authored
Merge pull request #3266 from itowlson/expand-service-chaining-wildcard
Expand wildcard service chaining during `spin up`
2 parents 7d28445 + 8a2a2d3 commit f5f00ae

File tree

2 files changed

+101
-6
lines changed

2 files changed

+101
-6
lines changed

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

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -94,14 +94,20 @@ impl Factor for OutboundNetworkingFactor {
9494
.instance_builder::<VariablesFactor>()?
9595
.expression_resolver()
9696
.clone();
97+
let component_ids = ctx
98+
.app_component()
99+
.app
100+
.components()
101+
.map(|c| c.id().to_string())
102+
.collect::<Vec<_>>();
97103
let allowed_hosts_future = async move {
98104
let prepared = resolver.prepare().await.inspect_err(|err| {
99105
tracing::error!(
100106
%err, "error.type" = "variable_resolution_failed",
101107
"Error resolving variables when checking request against allowed outbound hosts",
102108
);
103109
})?;
104-
AllowedHostsConfig::parse(&hosts, &prepared).inspect_err(|err| {
110+
AllowedHostsConfig::parse(&hosts, &prepared, &component_ids).inspect_err(|err| {
105111
tracing::error!(
106112
%err, "error.type" = "invalid_allowed_hosts",
107113
"Error parsing allowed outbound hosts",

crates/outbound-networking-config/src/allowed_hosts.rs

Lines changed: 94 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -465,12 +465,14 @@ impl AllowedHostsConfig {
465465
pub fn parse<S: AsRef<str>>(
466466
hosts: &[S],
467467
resolver: &spin_expressions::PreparedResolver,
468+
component_ids: &[String],
468469
) -> anyhow::Result<AllowedHostsConfig> {
469470
let partial = Self::parse_partial(hosts)?;
470471
let allowed = partial
471472
.into_iter()
472473
.map(|p| p.resolve(resolver))
473474
.collect::<anyhow::Result<Vec<_>>>()?;
475+
let allowed = Self::expand_wildcard_service_chaining(allowed, component_ids);
474476
Ok(Self::SpecificHosts(allowed))
475477
}
476478

@@ -502,6 +504,28 @@ impl AllowedHostsConfig {
502504
Ok(allowed)
503505
}
504506

507+
fn expand_wildcard_service_chaining(
508+
hosts: Vec<AllowedHostConfig>,
509+
component_ids: &[String],
510+
) -> Vec<AllowedHostConfig> {
511+
let expand_one = |host: AllowedHostConfig| match host.host() {
512+
HostConfig::AnySubdomain(domain) if domain == SERVICE_CHAINING_DOMAIN_SUFFIX => {
513+
let expanded_domains = component_ids
514+
.iter()
515+
.map(|c| format!("{c}{SERVICE_CHAINING_DOMAIN_SUFFIX}"));
516+
let expanded_hosts = expanded_domains.map(|d| {
517+
let mut hh = host.clone();
518+
hh.host = HostConfig::Literal(url::Host::Domain(d));
519+
hh
520+
});
521+
expanded_hosts.collect()
522+
}
523+
_ => vec![host],
524+
};
525+
526+
hosts.into_iter().flat_map(expand_one).collect()
527+
}
528+
505529
/// Returns true if the given url is allowed.
506530
pub fn allows(&self, url: &OutboundUrl) -> bool {
507531
match self {
@@ -901,10 +925,13 @@ mod test {
901925

902926
#[test]
903927
fn test_allowed_hosts_respects_allow_all() {
904-
assert!(AllowedHostsConfig::parse(&["insecure:allow-all"], &dummy_resolver()).is_err());
928+
assert!(
929+
AllowedHostsConfig::parse(&["insecure:allow-all"], &dummy_resolver(), &[]).is_err()
930+
);
905931
assert!(AllowedHostsConfig::parse(
906932
&["spin.fermyon.dev", "insecure:allow-all"],
907-
&dummy_resolver()
933+
&dummy_resolver(),
934+
&[]
908935
)
909936
.is_err());
910937
}
@@ -927,6 +954,7 @@ mod test {
927954
let allowed = AllowedHostsConfig::parse(
928955
&["*://spin.fermyon.dev:443", "http://example.com:8383"],
929956
&dummy_resolver(),
957+
&[],
930958
)
931959
.unwrap();
932960
assert!(
@@ -944,7 +972,7 @@ mod test {
944972
#[test]
945973
fn test_allowed_hosts_with_trailing_slash() {
946974
let allowed =
947-
AllowedHostsConfig::parse(&["https://my.api.com/"], &dummy_resolver()).unwrap();
975+
AllowedHostsConfig::parse(&["https://my.api.com/"], &dummy_resolver(), &[]).unwrap();
948976
assert!(allowed.allows(&OutboundUrl::parse("https://my.api.com", "https").unwrap()));
949977
assert!(allowed.allows(&OutboundUrl::parse("https://my.api.com/", "https").unwrap()));
950978
}
@@ -954,6 +982,7 @@ mod test {
954982
let allowed = AllowedHostsConfig::parse(
955983
&["http://*.example.com", "http://*.example2.com:8383"],
956984
&dummy_resolver(),
985+
&[],
957986
)
958987
.unwrap();
959988
assert!(
@@ -976,7 +1005,8 @@ mod test {
9761005

9771006
#[test]
9781007
fn test_hash_char_in_db_password() {
979-
let allowed = AllowedHostsConfig::parse(&["mysql://xyz.com"], &dummy_resolver()).unwrap();
1008+
let allowed =
1009+
AllowedHostsConfig::parse(&["mysql://xyz.com"], &dummy_resolver(), &[]).unwrap();
9801010
assert!(
9811011
allowed.allows(&OutboundUrl::parse("mysql://user:pass#[email protected]", "mysql").unwrap())
9821012
);
@@ -988,7 +1018,66 @@ mod test {
9881018
#[test]
9891019
fn test_cidr() {
9901020
let allowed =
991-
AllowedHostsConfig::parse(&["*://127.0.0.1/24:63551"], &dummy_resolver()).unwrap();
1021+
AllowedHostsConfig::parse(&["*://127.0.0.1/24:63551"], &dummy_resolver(), &[]).unwrap();
9921022
assert!(allowed.allows(&OutboundUrl::parse("tcp://127.0.0.1:63551", "tcp").unwrap()));
9931023
}
1024+
1025+
fn exact_host(ahc: &AllowedHostConfig) -> String {
1026+
match ahc.host() {
1027+
HostConfig::Literal(host) => host.to_string(),
1028+
_ => panic!("expected host {:?} to be a literal", ahc.host()),
1029+
}
1030+
}
1031+
1032+
#[test]
1033+
fn expand_wildcard_service_chaining_lists_all_components() {
1034+
let component_ids = ["first", "second", "third"]
1035+
.iter()
1036+
.map(|s| s.to_string())
1037+
.collect::<Vec<_>>();
1038+
let allowed = AllowedHostsConfig::parse(
1039+
&["http://*.spin.internal"],
1040+
&dummy_resolver(),
1041+
&component_ids,
1042+
)
1043+
.unwrap();
1044+
let AllowedHostsConfig::SpecificHosts(allowed) = allowed else {
1045+
panic!("expanded AllowedHostsConfig should be specific hosts");
1046+
};
1047+
1048+
assert_eq!(3, allowed.len());
1049+
1050+
assert_eq!("first.spin.internal", exact_host(&allowed[0]));
1051+
assert_eq!("second.spin.internal", exact_host(&allowed[1]));
1052+
assert_eq!("third.spin.internal", exact_host(&allowed[2]));
1053+
}
1054+
1055+
#[test]
1056+
fn expand_wildcard_service_chaining_leaves_others_untouched() {
1057+
let component_ids = ["first", "second", "third"]
1058+
.iter()
1059+
.map(|s| s.to_string())
1060+
.collect::<Vec<_>>();
1061+
let allowed = AllowedHostsConfig::parse(
1062+
&[
1063+
"pg://localhost:5656",
1064+
"http://*.spin.internal",
1065+
"https://spinframework.dev",
1066+
],
1067+
&dummy_resolver(),
1068+
&component_ids,
1069+
)
1070+
.unwrap();
1071+
let AllowedHostsConfig::SpecificHosts(allowed) = allowed else {
1072+
panic!("expanded AllowedHostsConfig should be specific hosts");
1073+
};
1074+
1075+
assert_eq!(5, allowed.len());
1076+
1077+
assert_eq!("localhost", exact_host(&allowed[0]));
1078+
assert_eq!("first.spin.internal", exact_host(&allowed[1]));
1079+
assert_eq!("second.spin.internal", exact_host(&allowed[2]));
1080+
assert_eq!("third.spin.internal", exact_host(&allowed[3]));
1081+
assert_eq!("spinframework.dev", exact_host(&allowed[4]));
1082+
}
9941083
}

0 commit comments

Comments
 (0)