@@ -3,6 +3,7 @@ use std::sync::Arc;
3
3
4
4
use anyhow:: { bail, ensure, Context as _} ;
5
5
use futures_util:: future:: { BoxFuture , Shared } ;
6
+ use url:: Host ;
6
7
7
8
/// The domain used for service chaining.
8
9
pub const SERVICE_CHAINING_DOMAIN : & str = "spin.internal" ;
@@ -131,10 +132,17 @@ impl AllowedHostConfig {
131
132
None => rest,
132
133
} ;
133
134
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
+
134
142
Ok ( Self {
135
- scheme : SchemeConfig :: parse ( scheme ) ? ,
136
- host : HostConfig :: parse ( host ) ? ,
137
- port : PortConfig :: parse ( port , scheme ) ? ,
143
+ scheme,
144
+ host,
145
+ port,
138
146
original,
139
147
} )
140
148
}
@@ -151,6 +159,11 @@ impl AllowedHostConfig {
151
159
& self . port
152
160
}
153
161
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
+
154
167
/// Returns true if the given URL is allowed.
155
168
fn allows ( & self , url : & OutboundUrl ) -> bool {
156
169
self . scheme . allows ( & url. scheme )
@@ -198,7 +211,7 @@ impl SchemeConfig {
198
211
}
199
212
200
213
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 " ) ;
202
215
}
203
216
204
217
Ok ( Self :: List ( vec ! [ scheme. into( ) ] ) )
@@ -224,7 +237,7 @@ pub enum HostConfig {
224
237
Any ,
225
238
AnySubdomain ( String ) ,
226
239
ToSelf ,
227
- List ( Vec < String > ) ,
240
+ Literal ( Host ) ,
228
241
Cidr ( ip_network:: IpNetwork ) ,
229
242
}
230
243
@@ -249,47 +262,67 @@ impl HostConfig {
249
262
return Ok ( Self :: Cidr ( net) ) ;
250
263
}
251
264
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" ) ;
254
268
}
255
269
256
270
if let Some ( domain) = host. strip_prefix ( "*." ) {
257
271
if domain. contains ( '*' ) {
258
- bail ! ( "Invalid allowed host {host}: wildcards are allowed only as prefixes" ) ;
272
+ bail ! ( "wildcards are allowed only as prefixes" ) ;
259
273
}
260
274
return Ok ( Self :: AnySubdomain ( format ! ( ".{domain}" ) ) ) ;
261
275
}
262
276
263
277
if host. contains ( '*' ) {
264
- bail ! ( "Invalid allowed host {host}: wildcards are allowed only as subdomains" ) ;
278
+ bail ! ( "wildcards are allowed only as subdomains" ) ;
265
279
}
266
280
267
- // Remove trailing slashes
268
- host = host . trim_end_matches ( '/' ) ;
281
+ Self :: literal ( host )
282
+ }
269
283
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) ?) )
271
287
}
272
288
273
289
/// Returns true if the given host is allowed.
274
290
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 ;
285
296
}
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 ,
286
310
}
287
311
}
288
312
289
313
/// Returns true if relative ("self") requests are allowed.
290
314
fn allows_relative ( & self ) -> bool {
291
315
matches ! ( self , Self :: Any | Self :: ToSelf )
292
316
}
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
+ }
293
326
}
294
327
295
328
/// Represents the port part of an allowed_outbound_hosts item.
@@ -601,9 +634,6 @@ mod test {
601
634
}
602
635
603
636
impl HostConfig {
604
- fn new ( host : & str ) -> Self {
605
- Self :: List ( vec ! [ host. into( ) ] )
606
- }
607
637
fn subdomain ( domain : & str ) -> Self {
608
638
Self :: AnySubdomain ( format ! ( ".{domain}" ) )
609
639
}
@@ -652,7 +682,7 @@ mod test {
652
682
assert_eq ! (
653
683
AllowedHostConfig :: new(
654
684
SchemeConfig :: new( "http" ) ,
655
- HostConfig :: new ( "spin.fermyon.dev" ) ,
685
+ HostConfig :: literal ( "spin.fermyon.dev" ) . unwrap ( ) ,
656
686
PortConfig :: new( 80 )
657
687
) ,
658
688
AllowedHostConfig :: parse( "http://spin.fermyon.dev" ) . unwrap( )
@@ -662,7 +692,7 @@ mod test {
662
692
AllowedHostConfig :: new(
663
693
SchemeConfig :: new( "http" ) ,
664
694
// Trailing slash is removed
665
- HostConfig :: new ( "spin.fermyon.dev" ) ,
695
+ HostConfig :: literal ( "spin.fermyon.dev" ) . unwrap ( ) ,
666
696
PortConfig :: new( 80 )
667
697
) ,
668
698
AllowedHostConfig :: parse( "http://spin.fermyon.dev/" ) . unwrap( )
@@ -671,7 +701,7 @@ mod test {
671
701
assert_eq ! (
672
702
AllowedHostConfig :: new(
673
703
SchemeConfig :: new( "https" ) ,
674
- HostConfig :: new ( "spin.fermyon.dev" ) ,
704
+ HostConfig :: literal ( "spin.fermyon.dev" ) . unwrap ( ) ,
675
705
PortConfig :: new( 443 )
676
706
) ,
677
707
AllowedHostConfig :: parse( "https://spin.fermyon.dev" ) . unwrap( )
@@ -683,23 +713,23 @@ mod test {
683
713
assert_eq ! (
684
714
AllowedHostConfig :: new(
685
715
SchemeConfig :: new( "http" ) ,
686
- HostConfig :: new ( "spin.fermyon.dev" ) ,
716
+ HostConfig :: literal ( "spin.fermyon.dev" ) . unwrap ( ) ,
687
717
PortConfig :: new( 4444 )
688
718
) ,
689
719
AllowedHostConfig :: parse( "http://spin.fermyon.dev:4444" ) . unwrap( )
690
720
) ;
691
721
assert_eq ! (
692
722
AllowedHostConfig :: new(
693
723
SchemeConfig :: new( "http" ) ,
694
- HostConfig :: new ( "spin.fermyon.dev" ) ,
724
+ HostConfig :: literal ( "spin.fermyon.dev" ) . unwrap ( ) ,
695
725
PortConfig :: new( 4444 )
696
726
) ,
697
727
AllowedHostConfig :: parse( "http://spin.fermyon.dev:4444/" ) . unwrap( )
698
728
) ;
699
729
assert_eq ! (
700
730
AllowedHostConfig :: new(
701
731
SchemeConfig :: new( "https" ) ,
702
- HostConfig :: new ( "spin.fermyon.dev" ) ,
732
+ HostConfig :: literal ( "spin.fermyon.dev" ) . unwrap ( ) ,
703
733
PortConfig :: new( 5555 )
704
734
) ,
705
735
AllowedHostConfig :: parse( "https://spin.fermyon.dev:5555" ) . unwrap( )
@@ -711,7 +741,7 @@ mod test {
711
741
assert_eq ! (
712
742
AllowedHostConfig :: new(
713
743
SchemeConfig :: new( "http" ) ,
714
- HostConfig :: new ( "spin.fermyon.dev" ) ,
744
+ HostConfig :: literal ( "spin.fermyon.dev" ) . unwrap ( ) ,
715
745
PortConfig :: range( 4444 ..5555 )
716
746
) ,
717
747
AllowedHostConfig :: parse( "http://spin.fermyon.dev:4444..5555" ) . unwrap( )
@@ -733,7 +763,7 @@ mod test {
733
763
assert_eq ! (
734
764
AllowedHostConfig :: new(
735
765
SchemeConfig :: Any ,
736
- HostConfig :: new ( "spin.fermyon.dev" ) ,
766
+ HostConfig :: literal ( "spin.fermyon.dev" ) . unwrap ( ) ,
737
767
PortConfig :: new( 7777 )
738
768
) ,
739
769
AllowedHostConfig :: parse( "*://spin.fermyon.dev:7777" ) . unwrap( )
@@ -758,7 +788,7 @@ mod test {
758
788
assert_eq ! (
759
789
AllowedHostConfig :: new(
760
790
SchemeConfig :: new( "http" ) ,
761
- HostConfig :: new ( "localhost" ) ,
791
+ HostConfig :: literal ( "localhost" ) . unwrap ( ) ,
762
792
PortConfig :: new( 80 )
763
793
) ,
764
794
AllowedHostConfig :: parse( "http://localhost" ) . unwrap( )
@@ -767,7 +797,7 @@ mod test {
767
797
assert_eq ! (
768
798
AllowedHostConfig :: new(
769
799
SchemeConfig :: new( "http" ) ,
770
- HostConfig :: new ( "localhost" ) ,
800
+ HostConfig :: literal ( "localhost" ) . unwrap ( ) ,
771
801
PortConfig :: new( 3001 )
772
802
) ,
773
803
AllowedHostConfig :: parse( "http://localhost:3001" ) . unwrap( )
@@ -791,23 +821,23 @@ mod test {
791
821
assert_eq ! (
792
822
AllowedHostConfig :: new(
793
823
SchemeConfig :: new( "http" ) ,
794
- HostConfig :: new ( "192.168.1.1" ) ,
824
+ HostConfig :: literal ( "192.168.1.1" ) . unwrap ( ) ,
795
825
PortConfig :: new( 80 )
796
826
) ,
797
827
AllowedHostConfig :: parse( "http://192.168.1.1" ) . unwrap( )
798
828
) ;
799
829
assert_eq ! (
800
830
AllowedHostConfig :: new(
801
831
SchemeConfig :: new( "http" ) ,
802
- HostConfig :: new ( "192.168.1.1" ) ,
832
+ HostConfig :: literal ( "192.168.1.1" ) . unwrap ( ) ,
803
833
PortConfig :: new( 3002 )
804
834
) ,
805
835
AllowedHostConfig :: parse( "http://192.168.1.1:3002" ) . unwrap( )
806
836
) ;
807
837
assert_eq ! (
808
838
AllowedHostConfig :: new(
809
839
SchemeConfig :: new( "http" ) ,
810
- HostConfig :: new ( "[::1]" ) ,
840
+ HostConfig :: literal ( "[::1]" ) . unwrap ( ) ,
811
841
PortConfig :: new( 8001 )
812
842
) ,
813
843
AllowedHostConfig :: parse( "http://[::1]:8001" ) . unwrap( )
0 commit comments