@@ -3,6 +3,7 @@ use std::sync::Arc;
33
44use anyhow:: { bail, ensure, Context as _} ;
55use futures_util:: future:: { BoxFuture , Shared } ;
6+ use url:: Host ;
67
78/// The domain used for service chaining.
89pub const SERVICE_CHAINING_DOMAIN : & str = "spin.internal" ;
@@ -131,10 +132,17 @@ impl AllowedHostConfig {
131132 None => rest,
132133 } ;
133134
135+ let port = PortConfig :: parse ( port, scheme)
136+ . with_context ( || format ! ( "Invalid allowed host port {port:?}" ) ) ?;
137+ let scheme = SchemeConfig :: parse ( scheme)
138+ . with_context ( || format ! ( "Invalid allowed host scheme {scheme:?}" ) ) ?;
139+ let host =
140+ HostConfig :: parse ( host) . with_context ( || format ! ( "Invalid allowed host {host:?}" ) ) ?;
141+
134142 Ok ( Self {
135- scheme : SchemeConfig :: parse ( scheme ) ? ,
136- host : HostConfig :: parse ( host ) ? ,
137- port : PortConfig :: parse ( port , scheme ) ? ,
143+ scheme,
144+ host,
145+ port,
138146 original,
139147 } )
140148 }
@@ -151,6 +159,11 @@ impl AllowedHostConfig {
151159 & self . port
152160 }
153161
162+ /// Returns true if this config is for service chaining requests.
163+ pub fn is_for_service_chaining ( & self ) -> bool {
164+ self . host . is_for_service_chaining ( )
165+ }
166+
154167 /// Returns true if the given URL is allowed.
155168 fn allows ( & self , url : & OutboundUrl ) -> bool {
156169 self . scheme . allows ( & url. scheme )
@@ -198,7 +211,7 @@ impl SchemeConfig {
198211 }
199212
200213 if scheme. chars ( ) . any ( |c| !c. is_alphabetic ( ) ) {
201- anyhow:: bail!( "scheme {scheme:?} contains non alphabetic character " ) ;
214+ anyhow:: bail!( "only alphabetic characters are allowed " ) ;
202215 }
203216
204217 Ok ( Self :: List ( vec ! [ scheme. into( ) ] ) )
@@ -224,7 +237,7 @@ pub enum HostConfig {
224237 Any ,
225238 AnySubdomain ( String ) ,
226239 ToSelf ,
227- List ( Vec < String > ) ,
240+ Literal ( Host ) ,
228241 Cidr ( ip_network:: IpNetwork ) ,
229242}
230243
@@ -249,47 +262,67 @@ impl HostConfig {
249262 return Ok ( Self :: Cidr ( net) ) ;
250263 }
251264
252- if matches ! ( host. split( '/' ) . nth( 1 ) , Some ( path) if !path. is_empty( ) ) {
253- bail ! ( "hosts must not contain paths" ) ;
265+ host = host. trim_end_matches ( '/' ) ;
266+ if host. contains ( '/' ) {
267+ bail ! ( "must not include a path" ) ;
254268 }
255269
256270 if let Some ( domain) = host. strip_prefix ( "*." ) {
257271 if domain. contains ( '*' ) {
258- bail ! ( "Invalid allowed host {host}: wildcards are allowed only as prefixes" ) ;
272+ bail ! ( "wildcards are allowed only as prefixes" ) ;
259273 }
260274 return Ok ( Self :: AnySubdomain ( format ! ( ".{domain}" ) ) ) ;
261275 }
262276
263277 if host. contains ( '*' ) {
264- bail ! ( "Invalid allowed host {host}: wildcards are allowed only as subdomains" ) ;
278+ bail ! ( "wildcards are allowed only as subdomains" ) ;
265279 }
266280
267- // Remove trailing slashes
268- host = host . trim_end_matches ( '/' ) ;
281+ Self :: literal ( host )
282+ }
269283
270- Ok ( Self :: List ( vec ! [ host. into( ) ] ) )
284+ /// Returns a HostConfig from the given literal host name.
285+ fn literal ( host : & str ) -> anyhow:: Result < Self > {
286+ Ok ( Self :: Literal ( Host :: parse ( host) ?) )
271287 }
272288
273289 /// Returns true if the given host is allowed.
274290 fn allows ( & self , host : & str ) -> bool {
275- match self {
276- HostConfig :: Any => true ,
277- HostConfig :: AnySubdomain ( suffix) => host. ends_with ( suffix) ,
278- HostConfig :: List ( l) => l. iter ( ) . any ( |h| h. as_str ( ) == host) ,
279- HostConfig :: ToSelf => false ,
280- HostConfig :: Cidr ( c) => {
281- let Ok ( ip) = host. parse :: < std:: net:: IpAddr > ( ) else {
282- return false ;
283- } ;
284- c. contains ( ip)
291+ let host: Host = match Host :: parse ( host) {
292+ Ok ( host) => host,
293+ Err ( err) => {
294+ tracing:: warn!( ?err, "invalid host in HostConfig::allows" ) ;
295+ return false ;
285296 }
297+ } ;
298+ match ( self , host) {
299+ ( HostConfig :: Any , _) => true ,
300+ ( HostConfig :: AnySubdomain ( suffix) , Host :: Domain ( domain) ) => domain. ends_with ( suffix) ,
301+ ( HostConfig :: Literal ( literal) , host) => host == * literal,
302+ ( HostConfig :: Cidr ( c) , Host :: Ipv4 ( ip) ) => c. contains ( ip) ,
303+ ( HostConfig :: Cidr ( c) , Host :: Ipv6 ( ip) ) => c. contains ( ip) ,
304+ // AnySubdomain only matches domains
305+ ( HostConfig :: AnySubdomain ( _) , _) => false ,
306+ // Cidr doesn't match domains
307+ ( HostConfig :: Cidr ( _) , Host :: Domain ( _) ) => false ,
308+ // ToSelf is checked separately with allow_relative
309+ ( HostConfig :: ToSelf , _) => false ,
286310 }
287311 }
288312
289313 /// Returns true if relative ("self") requests are allowed.
290314 fn allows_relative ( & self ) -> bool {
291315 matches ! ( self , Self :: Any | Self :: ToSelf )
292316 }
317+
318+ /// Returns true if this config is for service chaining requests.
319+ fn is_for_service_chaining ( & self ) -> bool {
320+ match self {
321+ Self :: Literal ( Host :: Domain ( domain) ) => domain. ends_with ( SERVICE_CHAINING_DOMAIN_SUFFIX ) ,
322+ Self :: AnySubdomain ( suffix) => suffix == SERVICE_CHAINING_DOMAIN_SUFFIX ,
323+ _ => false ,
324+ }
325+ }
293326}
294327
295328/// Represents the port part of an allowed_outbound_hosts item.
@@ -601,9 +634,6 @@ mod test {
601634 }
602635
603636 impl HostConfig {
604- fn new ( host : & str ) -> Self {
605- Self :: List ( vec ! [ host. into( ) ] )
606- }
607637 fn subdomain ( domain : & str ) -> Self {
608638 Self :: AnySubdomain ( format ! ( ".{domain}" ) )
609639 }
@@ -652,7 +682,7 @@ mod test {
652682 assert_eq ! (
653683 AllowedHostConfig :: new(
654684 SchemeConfig :: new( "http" ) ,
655- HostConfig :: new ( "spin.fermyon.dev" ) ,
685+ HostConfig :: literal ( "spin.fermyon.dev" ) . unwrap ( ) ,
656686 PortConfig :: new( 80 )
657687 ) ,
658688 AllowedHostConfig :: parse( "http://spin.fermyon.dev" ) . unwrap( )
@@ -662,7 +692,7 @@ mod test {
662692 AllowedHostConfig :: new(
663693 SchemeConfig :: new( "http" ) ,
664694 // Trailing slash is removed
665- HostConfig :: new ( "spin.fermyon.dev" ) ,
695+ HostConfig :: literal ( "spin.fermyon.dev" ) . unwrap ( ) ,
666696 PortConfig :: new( 80 )
667697 ) ,
668698 AllowedHostConfig :: parse( "http://spin.fermyon.dev/" ) . unwrap( )
@@ -671,7 +701,7 @@ mod test {
671701 assert_eq ! (
672702 AllowedHostConfig :: new(
673703 SchemeConfig :: new( "https" ) ,
674- HostConfig :: new ( "spin.fermyon.dev" ) ,
704+ HostConfig :: literal ( "spin.fermyon.dev" ) . unwrap ( ) ,
675705 PortConfig :: new( 443 )
676706 ) ,
677707 AllowedHostConfig :: parse( "https://spin.fermyon.dev" ) . unwrap( )
@@ -683,23 +713,23 @@ mod test {
683713 assert_eq ! (
684714 AllowedHostConfig :: new(
685715 SchemeConfig :: new( "http" ) ,
686- HostConfig :: new ( "spin.fermyon.dev" ) ,
716+ HostConfig :: literal ( "spin.fermyon.dev" ) . unwrap ( ) ,
687717 PortConfig :: new( 4444 )
688718 ) ,
689719 AllowedHostConfig :: parse( "http://spin.fermyon.dev:4444" ) . unwrap( )
690720 ) ;
691721 assert_eq ! (
692722 AllowedHostConfig :: new(
693723 SchemeConfig :: new( "http" ) ,
694- HostConfig :: new ( "spin.fermyon.dev" ) ,
724+ HostConfig :: literal ( "spin.fermyon.dev" ) . unwrap ( ) ,
695725 PortConfig :: new( 4444 )
696726 ) ,
697727 AllowedHostConfig :: parse( "http://spin.fermyon.dev:4444/" ) . unwrap( )
698728 ) ;
699729 assert_eq ! (
700730 AllowedHostConfig :: new(
701731 SchemeConfig :: new( "https" ) ,
702- HostConfig :: new ( "spin.fermyon.dev" ) ,
732+ HostConfig :: literal ( "spin.fermyon.dev" ) . unwrap ( ) ,
703733 PortConfig :: new( 5555 )
704734 ) ,
705735 AllowedHostConfig :: parse( "https://spin.fermyon.dev:5555" ) . unwrap( )
@@ -711,7 +741,7 @@ mod test {
711741 assert_eq ! (
712742 AllowedHostConfig :: new(
713743 SchemeConfig :: new( "http" ) ,
714- HostConfig :: new ( "spin.fermyon.dev" ) ,
744+ HostConfig :: literal ( "spin.fermyon.dev" ) . unwrap ( ) ,
715745 PortConfig :: range( 4444 ..5555 )
716746 ) ,
717747 AllowedHostConfig :: parse( "http://spin.fermyon.dev:4444..5555" ) . unwrap( )
@@ -733,7 +763,7 @@ mod test {
733763 assert_eq ! (
734764 AllowedHostConfig :: new(
735765 SchemeConfig :: Any ,
736- HostConfig :: new ( "spin.fermyon.dev" ) ,
766+ HostConfig :: literal ( "spin.fermyon.dev" ) . unwrap ( ) ,
737767 PortConfig :: new( 7777 )
738768 ) ,
739769 AllowedHostConfig :: parse( "*://spin.fermyon.dev:7777" ) . unwrap( )
@@ -758,7 +788,7 @@ mod test {
758788 assert_eq ! (
759789 AllowedHostConfig :: new(
760790 SchemeConfig :: new( "http" ) ,
761- HostConfig :: new ( "localhost" ) ,
791+ HostConfig :: literal ( "localhost" ) . unwrap ( ) ,
762792 PortConfig :: new( 80 )
763793 ) ,
764794 AllowedHostConfig :: parse( "http://localhost" ) . unwrap( )
@@ -767,7 +797,7 @@ mod test {
767797 assert_eq ! (
768798 AllowedHostConfig :: new(
769799 SchemeConfig :: new( "http" ) ,
770- HostConfig :: new ( "localhost" ) ,
800+ HostConfig :: literal ( "localhost" ) . unwrap ( ) ,
771801 PortConfig :: new( 3001 )
772802 ) ,
773803 AllowedHostConfig :: parse( "http://localhost:3001" ) . unwrap( )
@@ -791,23 +821,23 @@ mod test {
791821 assert_eq ! (
792822 AllowedHostConfig :: new(
793823 SchemeConfig :: new( "http" ) ,
794- HostConfig :: new ( "192.168.1.1" ) ,
824+ HostConfig :: literal ( "192.168.1.1" ) . unwrap ( ) ,
795825 PortConfig :: new( 80 )
796826 ) ,
797827 AllowedHostConfig :: parse( "http://192.168.1.1" ) . unwrap( )
798828 ) ;
799829 assert_eq ! (
800830 AllowedHostConfig :: new(
801831 SchemeConfig :: new( "http" ) ,
802- HostConfig :: new ( "192.168.1.1" ) ,
832+ HostConfig :: literal ( "192.168.1.1" ) . unwrap ( ) ,
803833 PortConfig :: new( 3002 )
804834 ) ,
805835 AllowedHostConfig :: parse( "http://192.168.1.1:3002" ) . unwrap( )
806836 ) ;
807837 assert_eq ! (
808838 AllowedHostConfig :: new(
809839 SchemeConfig :: new( "http" ) ,
810- HostConfig :: new ( "[::1]" ) ,
840+ HostConfig :: literal ( "[::1]" ) . unwrap ( ) ,
811841 PortConfig :: new( 8001 )
812842 ) ,
813843 AllowedHostConfig :: parse( "http://[::1]:8001" ) . unwrap( )
0 commit comments