@@ -274,6 +274,13 @@ fn apply_set_filter_state_filters<B>(
274274 use orion_configuration:: config:: network_filters:: http_connection_manager:: http_filters:: set_filter_state:: SharedWithUpstream as ConfigSharedWithUpstream ;
275275 use crate :: SharedWithUpstream as RuntimeSharedWithUpstream ;
276276
277+ tracing:: trace!(
278+ "Applying set_filter_state filters: method={}, uri={}, filters={}" ,
279+ request. method( ) ,
280+ request. uri( ) ,
281+ http_filters. len( )
282+ ) ;
283+
277284 // Build request context from HTTP request
278285 let mut ctx = RequestContext :: new ( ) ;
279286
@@ -284,9 +291,19 @@ fn apply_set_filter_state_filters<B>(
284291 }
285292 }
286293
294+ if ctx. get_header ( ":authority" ) . is_none ( ) {
295+ if let Some ( host) = ctx. get_header ( "host" ) {
296+ let host = host. clone ( ) ;
297+ ctx = ctx. with_header ( ":authority" , host) ;
298+ }
299+ }
300+
287301 // Extract method
288302 ctx = ctx. with_method ( request. method ( ) . as_str ( ) ) ;
289303
304+ // Also add :method pseudo-header for compatibility with %REQ(:method)%
305+ ctx = ctx. with_header ( ":method" , request. method ( ) . as_str ( ) ) ;
306+
290307 // Extract path
291308 ctx = ctx. with_path ( request. uri ( ) . path ( ) ) ;
292309
@@ -308,6 +325,12 @@ fn apply_set_filter_state_filters<B>(
308325
309326 // Check if this is a SetFilterState filter
310327 if let Some ( HttpFilterValue :: SetFilterState ( sfs_config) ) = & filter. filter {
328+ tracing:: debug!(
329+ "Processing SetFilterState filter '{}' with {} entries" ,
330+ filter. name,
331+ sfs_config. on_request_headers. len( )
332+ ) ;
333+
311334 // Process each on_request_headers entry
312335 for value_config in & sfs_config. on_request_headers {
313336 // 1. Evaluate format string
@@ -325,7 +348,7 @@ fn apply_set_filter_state_filters<B>(
325348 } ;
326349
327350 // 2. Check skip_if_empty
328- if value_config. skip_if_empty && evaluated_value. is_empty ( ) {
351+ if value_config. skip_if_empty && ( evaluated_value. is_empty ( ) || evaluated_value == "-" ) {
329352 tracing:: debug!(
330353 "Skipping filter state key '{}' due to empty value" ,
331354 value_config. object_key
@@ -1311,6 +1334,10 @@ fn apply_authorization_rules<B>(rbac: &HttpRbac, req: &Request<B>) -> FilterDeci
13111334#[ cfg( test) ]
13121335mod tests {
13131336 use orion_configuration:: config:: network_filters:: http_connection_manager:: MatchHost ;
1337+ use hyper:: Method ;
1338+ use std:: net:: { IpAddr , Ipv4Addr , SocketAddr } ;
1339+ use crate :: filter_state:: SharedWithUpstream as RuntimeSharedWithUpstream ;
1340+ use crate :: listeners:: filter_state:: { DownstreamConnectionMetadata , DownstreamMetadata } ;
13141341
13151342 use super :: * ;
13161343
@@ -1349,4 +1376,328 @@ mod tests {
13491376 let request = Request :: builder ( ) . header ( "host" , "domain2.com" ) . body ( ( ) ) . unwrap ( ) ;
13501377 assert_eq ! ( select_virtual_host( & request, & [ vh1. clone( ) , vh2. clone( ) , vh3. clone( ) ] ) , None ) ;
13511378 }
1379+
1380+ #[ test]
1381+ fn test_apply_set_filter_state_filters_basic ( ) {
1382+ use orion_configuration:: config:: network_filters:: http_connection_manager:: http_filters:: {
1383+ set_filter_state:: { FilterStateValue , FormatString , SharedWithUpstream as ConfigSharedWithUpstream } ,
1384+ } ;
1385+ use compact_str:: CompactString ;
1386+
1387+ let filter_state = FilterState :: new ( ) ;
1388+ let format_evaluator = FormatStringEvaluator :: new ( ) ;
1389+
1390+ let sfs_config = orion_configuration:: config:: network_filters:: http_connection_manager:: http_filters:: set_filter_state:: SetFilterState {
1391+ on_request_headers : vec ! [
1392+ FilterStateValue {
1393+ object_key: CompactString :: from( "io.istio.connect_authority" ) ,
1394+ format_string: FormatString :: Text ( CompactString :: from( "%REQ(:authority)%" ) ) ,
1395+ factory_key: None ,
1396+ read_only: true ,
1397+ shared_with_upstream: ConfigSharedWithUpstream :: Once ,
1398+ skip_if_empty: false ,
1399+ } ,
1400+ ] ,
1401+ } ;
1402+
1403+ let http_filter = Arc :: new ( HttpFilter {
1404+ name : CompactString :: from ( "test.set_filter_state" ) ,
1405+ disabled : false ,
1406+ filter : Some ( HttpFilterValue :: SetFilterState ( sfs_config) ) ,
1407+ } ) ;
1408+
1409+ let request = Request :: builder ( )
1410+ . method ( Method :: CONNECT )
1411+ . uri ( "example.com:443" )
1412+ . header ( "host" , "example.com:443" )
1413+ . body ( ( ) )
1414+ . unwrap ( ) ;
1415+
1416+ let downstream_metadata = DownstreamMetadata :: new (
1417+ DownstreamConnectionMetadata :: Socket {
1418+ peer_address : SocketAddr :: new ( IpAddr :: V4 ( Ipv4Addr :: new ( 10 , 0 , 0 , 1 ) ) , 54321 ) ,
1419+ local_address : SocketAddr :: new ( IpAddr :: V4 ( Ipv4Addr :: new ( 172 , 16 , 0 , 1 ) ) , 15008 ) ,
1420+ original_destination_address : None ,
1421+ } ,
1422+ None :: < String > ,
1423+ ) ;
1424+
1425+ apply_set_filter_state_filters (
1426+ & request,
1427+ & downstream_metadata,
1428+ & [ http_filter] ,
1429+ & filter_state,
1430+ & format_evaluator,
1431+ ) ;
1432+
1433+ let ( value, read_only, shared_with_upstream) = filter_state. get_with_metadata ( "io.istio.connect_authority" ) . unwrap ( ) ;
1434+ assert_eq ! ( value. as_str( ) , "example.com:443" ) ;
1435+ assert ! ( read_only) ;
1436+ assert ! ( matches!( shared_with_upstream, RuntimeSharedWithUpstream :: Once ) ) ;
1437+ }
1438+
1439+ #[ test]
1440+ fn test_apply_set_filter_state_filters_skip_if_empty ( ) {
1441+ use orion_configuration:: config:: network_filters:: http_connection_manager:: http_filters:: {
1442+ set_filter_state:: { FilterStateValue , FormatString , SharedWithUpstream as ConfigSharedWithUpstream } ,
1443+ } ;
1444+ use compact_str:: CompactString ;
1445+
1446+ let filter_state = FilterState :: new ( ) ;
1447+ let format_evaluator = FormatStringEvaluator :: new ( ) ;
1448+
1449+ // Create config with skip_if_empty=true for a missing header
1450+ let sfs_config = orion_configuration:: config:: network_filters:: http_connection_manager:: http_filters:: set_filter_state:: SetFilterState {
1451+ on_request_headers : vec ! [
1452+ FilterStateValue {
1453+ object_key: CompactString :: from( "test.missing_header" ) ,
1454+ format_string: FormatString :: Text ( CompactString :: from( "%REQ(x-missing-header)%" ) ) ,
1455+ factory_key: None ,
1456+ read_only: false ,
1457+ shared_with_upstream: ConfigSharedWithUpstream :: None ,
1458+ skip_if_empty: true ,
1459+ } ,
1460+ ] ,
1461+ } ;
1462+
1463+ let http_filter = Arc :: new ( HttpFilter {
1464+ name : CompactString :: from ( "test.set_filter_state" ) ,
1465+ disabled : false ,
1466+ filter : Some ( HttpFilterValue :: SetFilterState ( sfs_config) ) ,
1467+ } ) ;
1468+
1469+ let request = Request :: builder ( )
1470+ . method ( Method :: GET )
1471+ . uri ( "/test" )
1472+ . body ( ( ) )
1473+ . unwrap ( ) ;
1474+
1475+ let downstream_metadata = DownstreamMetadata :: new (
1476+ DownstreamConnectionMetadata :: Socket {
1477+ peer_address : SocketAddr :: new ( IpAddr :: V4 ( Ipv4Addr :: new ( 127 , 0 , 0 , 1 ) ) , 12345 ) ,
1478+ local_address : SocketAddr :: new ( IpAddr :: V4 ( Ipv4Addr :: new ( 127 , 0 , 0 , 1 ) ) , 8080 ) ,
1479+ original_destination_address : None ,
1480+ } ,
1481+ None :: < String > ,
1482+ ) ;
1483+
1484+ apply_set_filter_state_filters (
1485+ & request,
1486+ & downstream_metadata,
1487+ & [ http_filter] ,
1488+ & filter_state,
1489+ & format_evaluator,
1490+ ) ;
1491+
1492+ assert ! ( filter_state. get( "test.missing_header" ) . is_err( ) ) ;
1493+ }
1494+
1495+ #[ test]
1496+ fn test_apply_set_filter_state_filters_client_address ( ) {
1497+ use orion_configuration:: config:: network_filters:: http_connection_manager:: http_filters:: {
1498+ set_filter_state:: { FilterStateValue , FormatString , SharedWithUpstream as ConfigSharedWithUpstream } ,
1499+ } ;
1500+ use compact_str:: CompactString ;
1501+
1502+ let filter_state = FilterState :: new ( ) ;
1503+ let format_evaluator = FormatStringEvaluator :: new ( ) ;
1504+
1505+ // Create config for client address
1506+ let sfs_config = orion_configuration:: config:: network_filters:: http_connection_manager:: http_filters:: set_filter_state:: SetFilterState {
1507+ on_request_headers : vec ! [
1508+ FilterStateValue {
1509+ object_key: CompactString :: from( "io.istio.client_address" ) ,
1510+ format_string: FormatString :: Text ( CompactString :: from( "%DOWNSTREAM_REMOTE_ADDRESS%" ) ) ,
1511+ factory_key: None ,
1512+ read_only: false ,
1513+ shared_with_upstream: ConfigSharedWithUpstream :: Transitive ,
1514+ skip_if_empty: false ,
1515+ } ,
1516+ ] ,
1517+ } ;
1518+
1519+ let http_filter = Arc :: new ( HttpFilter {
1520+ name : CompactString :: from ( "test.set_filter_state" ) ,
1521+ disabled : false ,
1522+ filter : Some ( HttpFilterValue :: SetFilterState ( sfs_config) ) ,
1523+ } ) ;
1524+
1525+ let request = Request :: builder ( )
1526+ . method ( Method :: GET )
1527+ . uri ( "/" )
1528+ . body ( ( ) )
1529+ . unwrap ( ) ;
1530+
1531+ let downstream_metadata = DownstreamMetadata :: new (
1532+ DownstreamConnectionMetadata :: Socket {
1533+ peer_address : SocketAddr :: new ( IpAddr :: V4 ( Ipv4Addr :: new ( 192 , 168 , 1 , 100 ) ) , 45678 ) ,
1534+ local_address : SocketAddr :: new ( IpAddr :: V4 ( Ipv4Addr :: new ( 10 , 0 , 0 , 1 ) ) , 8080 ) ,
1535+ original_destination_address : None ,
1536+ } ,
1537+ None :: < String > ,
1538+ ) ;
1539+
1540+ // Apply filters
1541+ apply_set_filter_state_filters (
1542+ & request,
1543+ & downstream_metadata,
1544+ & [ http_filter] ,
1545+ & filter_state,
1546+ & format_evaluator,
1547+ ) ;
1548+
1549+ // Verify the client address was captured
1550+ let ( value, read_only, shared_with_upstream) = filter_state. get_with_metadata ( "io.istio.client_address" ) . unwrap ( ) ;
1551+ assert_eq ! ( value. as_str( ) , "192.168.1.100:45678" ) ;
1552+ assert ! ( !read_only) ;
1553+ assert ! ( matches!( shared_with_upstream, RuntimeSharedWithUpstream :: Transitive ) ) ;
1554+ }
1555+
1556+ #[ test]
1557+ fn test_apply_set_filter_state_filters_multiple_entries ( ) {
1558+ use orion_configuration:: config:: network_filters:: http_connection_manager:: http_filters:: {
1559+ set_filter_state:: { FilterStateValue , FormatString , SharedWithUpstream as ConfigSharedWithUpstream } ,
1560+ } ;
1561+ use compact_str:: CompactString ;
1562+
1563+ let filter_state = FilterState :: new ( ) ;
1564+ let format_evaluator = FormatStringEvaluator :: new ( ) ;
1565+
1566+ // Create config with multiple filter state entries
1567+ let sfs_config = orion_configuration:: config:: network_filters:: http_connection_manager:: http_filters:: set_filter_state:: SetFilterState {
1568+ on_request_headers : vec ! [
1569+ FilterStateValue {
1570+ object_key: CompactString :: from( "io.istio.connect_authority" ) ,
1571+ format_string: FormatString :: Text ( CompactString :: from( "%REQ(:authority)%" ) ) ,
1572+ factory_key: None ,
1573+ read_only: true ,
1574+ shared_with_upstream: ConfigSharedWithUpstream :: Once ,
1575+ skip_if_empty: false ,
1576+ } ,
1577+ FilterStateValue {
1578+ object_key: CompactString :: from( "io.istio.client_address" ) ,
1579+ format_string: FormatString :: Text ( CompactString :: from( "%DOWNSTREAM_REMOTE_ADDRESS%" ) ) ,
1580+ factory_key: None ,
1581+ read_only: false ,
1582+ shared_with_upstream: ConfigSharedWithUpstream :: Transitive ,
1583+ skip_if_empty: false ,
1584+ } ,
1585+ FilterStateValue {
1586+ object_key: CompactString :: from( "test.method" ) ,
1587+ format_string: FormatString :: Text ( CompactString :: from( "%REQ(:method)%" ) ) ,
1588+ factory_key: None ,
1589+ read_only: false ,
1590+ shared_with_upstream: ConfigSharedWithUpstream :: None ,
1591+ skip_if_empty: false ,
1592+ } ,
1593+ ] ,
1594+ } ;
1595+
1596+ let http_filter = Arc :: new ( HttpFilter {
1597+ name : CompactString :: from ( "test.set_filter_state" ) ,
1598+ disabled : false ,
1599+ filter : Some ( HttpFilterValue :: SetFilterState ( sfs_config) ) ,
1600+ } ) ;
1601+
1602+ let request = Request :: builder ( )
1603+ . method ( Method :: CONNECT )
1604+ . uri ( "dest.example.com:443" )
1605+ . header ( "host" , "dest.example.com:443" )
1606+ . body ( ( ) )
1607+ . unwrap ( ) ;
1608+
1609+ let downstream_metadata = DownstreamMetadata :: new (
1610+ DownstreamConnectionMetadata :: Socket {
1611+ peer_address : SocketAddr :: new ( IpAddr :: V4 ( Ipv4Addr :: new ( 10 , 1 , 2 , 3 ) ) , 12345 ) ,
1612+ local_address : SocketAddr :: new ( IpAddr :: V4 ( Ipv4Addr :: new ( 172 , 31 , 0 , 1 ) ) , 15008 ) ,
1613+ original_destination_address : None ,
1614+ } ,
1615+ None :: < String > ,
1616+ ) ;
1617+
1618+ // Apply filters
1619+ apply_set_filter_state_filters (
1620+ & request,
1621+ & downstream_metadata,
1622+ & [ http_filter] ,
1623+ & filter_state,
1624+ & format_evaluator,
1625+ ) ;
1626+
1627+ // Verify all three entries were set
1628+ assert_eq ! ( filter_state. get( "io.istio.connect_authority" ) . unwrap( ) . as_str( ) , "dest.example.com:443" ) ;
1629+ assert_eq ! ( filter_state. get( "io.istio.client_address" ) . unwrap( ) . as_str( ) , "10.1.2.3:12345" ) ;
1630+ assert_eq ! ( filter_state. get( "test.method" ) . unwrap( ) . as_str( ) , "CONNECT" ) ;
1631+
1632+ // Verify metadata
1633+ let ( _, read_only1, shared1) = filter_state. get_with_metadata ( "io.istio.connect_authority" ) . unwrap ( ) ;
1634+ assert ! ( read_only1) ;
1635+ assert ! ( matches!( shared1, RuntimeSharedWithUpstream :: Once ) ) ;
1636+
1637+ let ( _, read_only2, shared2) = filter_state. get_with_metadata ( "io.istio.client_address" ) . unwrap ( ) ;
1638+ assert ! ( !read_only2) ;
1639+ assert ! ( matches!( shared2, RuntimeSharedWithUpstream :: Transitive ) ) ;
1640+
1641+ let ( _, read_only3, shared3) = filter_state. get_with_metadata ( "test.method" ) . unwrap ( ) ;
1642+ assert ! ( !read_only3) ;
1643+ assert ! ( matches!( shared3, RuntimeSharedWithUpstream :: None ) ) ;
1644+ }
1645+
1646+ #[ test]
1647+ fn test_apply_set_filter_state_filters_disabled_filter ( ) {
1648+ use orion_configuration:: config:: network_filters:: http_connection_manager:: http_filters:: {
1649+ set_filter_state:: { FilterStateValue , FormatString , SharedWithUpstream as ConfigSharedWithUpstream } ,
1650+ } ;
1651+ use compact_str:: CompactString ;
1652+
1653+ let filter_state = FilterState :: new ( ) ;
1654+ let format_evaluator = FormatStringEvaluator :: new ( ) ;
1655+
1656+ let sfs_config = orion_configuration:: config:: network_filters:: http_connection_manager:: http_filters:: set_filter_state:: SetFilterState {
1657+ on_request_headers : vec ! [
1658+ FilterStateValue {
1659+ object_key: CompactString :: from( "test.disabled" ) ,
1660+ format_string: FormatString :: Text ( CompactString :: from( "value" ) ) ,
1661+ factory_key: None ,
1662+ read_only: false ,
1663+ shared_with_upstream: ConfigSharedWithUpstream :: None ,
1664+ skip_if_empty: false ,
1665+ } ,
1666+ ] ,
1667+ } ;
1668+
1669+ // Create a disabled filter
1670+ let http_filter = Arc :: new ( HttpFilter {
1671+ name : CompactString :: from ( "test.set_filter_state" ) ,
1672+ disabled : true , // Disabled!
1673+ filter : Some ( HttpFilterValue :: SetFilterState ( sfs_config) ) ,
1674+ } ) ;
1675+
1676+ let request = Request :: builder ( )
1677+ . method ( Method :: GET )
1678+ . uri ( "/" )
1679+ . body ( ( ) )
1680+ . unwrap ( ) ;
1681+
1682+ let downstream_metadata = DownstreamMetadata :: new (
1683+ DownstreamConnectionMetadata :: Socket {
1684+ peer_address : SocketAddr :: new ( IpAddr :: V4 ( Ipv4Addr :: new ( 127 , 0 , 0 , 1 ) ) , 12345 ) ,
1685+ local_address : SocketAddr :: new ( IpAddr :: V4 ( Ipv4Addr :: new ( 127 , 0 , 0 , 1 ) ) , 8080 ) ,
1686+ original_destination_address : None ,
1687+ } ,
1688+ None :: < String > ,
1689+ ) ;
1690+
1691+ // Apply filters
1692+ apply_set_filter_state_filters (
1693+ & request,
1694+ & downstream_metadata,
1695+ & [ http_filter] ,
1696+ & filter_state,
1697+ & format_evaluator,
1698+ ) ;
1699+
1700+ // Verify nothing was set (filter was disabled)
1701+ assert ! ( filter_state. get( "test.disabled" ) . is_err( ) ) ;
1702+ }
13521703}
0 commit comments