@@ -102,6 +102,11 @@ impl L7EvalContext {
102102 pub ( crate ) fn websocket_scheme ( & self ) -> & ' static str {
103103 if self . is_plaintext ( ) { "ws" } else { "wss" }
104104 }
105+
106+ /// OCSF URL for `target` on the inspected transport.
107+ pub ( crate ) fn ocsf_url ( & self , target : & str ) -> OcsfUrl {
108+ OcsfUrl :: new ( self . request_scheme ( ) , & self . host , target, self . port )
109+ }
105110}
106111
107112fn request_default_port ( ctx : & L7EvalContext ) -> Option < u16 > {
@@ -1696,10 +1701,7 @@ fn build_l7_request_event(
16961701 . action ( action_id)
16971702 . disposition ( disposition_id)
16981703 . severity ( severity)
1699- . http_request ( HttpRequest :: new (
1700- action,
1701- OcsfUrl :: new ( "http" , & ctx. host , redacted_target, ctx. port ) ,
1702- ) )
1704+ . http_request ( HttpRequest :: new ( action, ctx. ocsf_url ( redacted_target) ) )
17031705 . dst_endpoint ( Endpoint :: from_domain ( & ctx. host , ctx. port ) )
17041706 . firewall_rule ( & ctx. policy_name , engine_type)
17051707 . message ( message)
@@ -2158,7 +2160,7 @@ where
21582160 . severity ( severity)
21592161 . http_request ( HttpRequest :: new (
21602162 & request_info. action ,
2161- OcsfUrl :: new ( "http" , & ctx. host , & redacted_target, ctx . port ) ,
2163+ ctx. ocsf_url ( & redacted_target) ,
21622164 ) )
21632165 . dst_endpoint ( Endpoint :: from_domain ( & ctx. host , ctx. port ) )
21642166 . firewall_rule ( & ctx. policy_name , "l7" )
@@ -2582,7 +2584,7 @@ where
25822584 . severity ( severity)
25832585 . http_request ( HttpRequest :: new (
25842586 & request_info. action ,
2585- OcsfUrl :: new ( "http" , & ctx. host , & redacted_target, ctx . port ) ,
2587+ ctx. ocsf_url ( & redacted_target) ,
25862588 ) )
25872589 . dst_endpoint ( Endpoint :: from_domain ( & ctx. host , ctx. port ) )
25882590 . firewall_rule ( & ctx. policy_name , jsonrpc_engine_type ( config. protocol ) )
@@ -2868,7 +2870,7 @@ where
28682870 . severity ( severity)
28692871 . http_request ( HttpRequest :: new (
28702872 & request_info. action ,
2871- OcsfUrl :: new ( "http" , & ctx. host , & redacted_target, ctx . port ) ,
2873+ ctx. ocsf_url ( & redacted_target) ,
28722874 ) )
28732875 . dst_endpoint ( Endpoint :: from_domain ( & ctx. host , ctx. port ) )
28742876 . firewall_rule ( & ctx. policy_name , "l7-graphql" )
@@ -3339,7 +3341,7 @@ fn emit_transformed_body_decision(
33393341 . severity ( severity)
33403342 . http_request ( HttpRequest :: new (
33413343 & request_info. action ,
3342- OcsfUrl :: new ( "http" , & ctx. host , & request_info. target , ctx . port ) ,
3344+ ctx. ocsf_url ( & request_info. target ) ,
33433345 ) )
33443346 . dst_endpoint ( Endpoint :: from_domain ( & ctx. host , ctx. port ) )
33453347 . firewall_rule ( & ctx. policy_name , engine_type)
@@ -3573,7 +3575,7 @@ where
35733575 . severity ( SeverityId :: Informational )
35743576 . http_request ( HttpRequest :: new (
35753577 & req. action ,
3576- OcsfUrl :: new ( "http" , & ctx. host , & redacted_target, ctx . port ) ,
3578+ ctx. ocsf_url ( & redacted_target) ,
35773579 ) )
35783580 . dst_endpoint ( Endpoint :: from_domain ( & ctx. host , ctx. port ) )
35793581 . message ( format ! (
@@ -3606,7 +3608,7 @@ where
36063608 req,
36073609 client,
36083610 ctx,
3609- "http" ,
3611+ ctx . request_scheme ( ) ,
36103612 openshell_supervisor_middleware:: TransformedBodyPolicy :: NotPolicyRelevant ,
36113613 )
36123614 . await ?;
@@ -3680,7 +3682,7 @@ where
36803682 let resolver = ctx. secret_resolver . as_deref ( ) ;
36813683 let response_middleware = response_selection
36823684 . as_ref ( )
3683- . map ( |exchange| exchange. response_relay ( & req_with_auth, ctx, "http" ) ) ;
3685+ . map ( |exchange| exchange. response_relay ( & req_with_auth, ctx, ctx . request_scheme ( ) ) ) ;
36843686
36853687 // Forward request with credential rewriting and relay the response.
36863688 // relay_http_request_with_resolver handles both directions: it sends
@@ -7811,7 +7813,7 @@ network_policies:
78117813 assert_eq ! ( denied_json[ "unmapped" ] [ "failed" ] , false ) ;
78127814 assert_eq ! (
78137815 denied_http. format_shorthand( ) ,
7814- "HTTP:POST [MED] DENIED POST http ://api.example.test:443 /v1/messages \
7816+ "HTTP:POST [MED] DENIED POST https ://api.example.test/v1/messages \
78157817 [policy:rest_api engine:middleware] \
78167818 [failed:false transformed:false \
78177819 reason:middleware_denied:content-guard:content_match]"
@@ -7927,6 +7929,88 @@ network_policies:
79277929 . unwrap ( ) ;
79287930 }
79297931
7932+ #[ tokio:: test]
7933+ async fn passthrough_middleware_scheme_follows_transport ( ) {
7934+ use openshell_supervisor_middleware:: { ChainRunner , MiddlewareRegistry } ;
7935+
7936+ let seen = Arc :: new ( std:: sync:: Mutex :: new ( Vec :: new ( ) ) ) ;
7937+ for ( port, transport, scheme) in [ ( 8443 , 443 , "https" ) , ( 443 , 80 , "http" ) ] {
7938+ seen. lock ( ) . unwrap ( ) . clear ( ) ;
7939+ let data = format ! (
7940+ r#"
7941+ network_middlewares:
7942+ scheme:
7943+ middleware: test/scheme
7944+ on_error: fail_closed
7945+ endpoints:
7946+ include: ["api.example.test"]
7947+ network_policies:
7948+ passthrough_api:
7949+ name: passthrough_api
7950+ endpoints:
7951+ - host: api.example.test
7952+ port: {port}
7953+ binaries:
7954+ - {{ path: /usr/bin/curl }}
7955+ "#
7956+ ) ;
7957+ let engine = Arc :: new ( OpaEngine :: from_strings ( TEST_POLICY , & data) . unwrap ( ) ) ;
7958+ let registry = MiddlewareRegistry :: connect_services (
7959+ vec ! [ Arc :: new( SchemeRecorder ( seen. clone( ) ) ) ] ,
7960+ Vec :: new ( ) ,
7961+ )
7962+ . await
7963+ . expect ( "connect named middleware services" ) ;
7964+ engine. set_middleware_runner_for_tests ( ChainRunner :: from_registry ( registry) ) ;
7965+ let generation_guard = engine
7966+ . generation_guard ( engine. current_generation ( ) )
7967+ . unwrap ( ) ;
7968+ let ctx = L7EvalContext {
7969+ host : "api.example.test" . into ( ) ,
7970+ port,
7971+ request_default_port : Some ( transport) ,
7972+ policy_name : "passthrough_api" . into ( ) ,
7973+ binary_path : "/usr/bin/curl" . into ( ) ,
7974+ ..Default :: default ( )
7975+ } ;
7976+
7977+ let ( mut app, mut relay_client) = tokio:: io:: duplex ( 8192 ) ;
7978+ let ( mut relay_upstream, mut upstream) = tokio:: io:: duplex ( 8192 ) ;
7979+ let relay = tokio:: spawn ( async move {
7980+ relay_passthrough_with_credentials (
7981+ & mut relay_client,
7982+ & mut relay_upstream,
7983+ & ctx,
7984+ & generation_guard,
7985+ Some ( engine. as_ref ( ) ) ,
7986+ )
7987+ . await
7988+ } ) ;
7989+
7990+ let request = format ! (
7991+ "GET /v1/messages HTTP/1.1\r \n Host: api.example.test:{port}\r \n Connection: close\r \n \r \n "
7992+ ) ;
7993+ app. write_all ( request. as_bytes ( ) ) . await . unwrap ( ) ;
7994+ let mut upstream_request = [ 0u8 ; 1024 ] ;
7995+ tokio:: time:: timeout (
7996+ std:: time:: Duration :: from_secs ( 1 ) ,
7997+ upstream. read ( & mut upstream_request) ,
7998+ )
7999+ . await
8000+ . expect ( "request should reach upstream" )
8001+ . unwrap ( ) ;
8002+ assert_eq ! ( * seen. lock( ) . unwrap( ) , [ scheme] ) ;
8003+
8004+ drop ( app) ;
8005+ drop ( upstream) ;
8006+ tokio:: time:: timeout ( std:: time:: Duration :: from_secs ( 1 ) , relay)
8007+ . await
8008+ . expect ( "relay should finish" )
8009+ . unwrap ( )
8010+ . ok ( ) ;
8011+ }
8012+ }
8013+
79308014 #[ tokio:: test]
79318015 async fn websocket_upgrade_request_is_inspected_and_denied ( ) {
79328016 // The WebSocket upgrade handshake is an HTTP request the hook can inspect
@@ -9020,6 +9104,7 @@ network_policies:
90209104 let ctx = L7EvalContext {
90219105 host : "gateway.example.test" . into ( ) ,
90229106 port : 443 ,
9107+ request_default_port : Some ( 443 ) ,
90239108 policy_name : "route_api" . into ( ) ,
90249109 ..Default :: default ( )
90259110 } ;
@@ -9038,10 +9123,34 @@ network_policies:
90389123 assert_eq ! ( event. base( ) . severity, SeverityId :: Medium ) ;
90399124 assert_eq ! (
90409125 event. format_shorthand( ) ,
9041- "HTTP:GET [MED] DENIED GET http ://gateway.example.test:443 /other [policy:route_api engine:l7] [reason:L7_REQUEST deny GET gateway.example.test:443/other reason=no L7 endpoint path matched request]"
9126+ "HTTP:GET [MED] DENIED GET https ://gateway.example.test/other [policy:route_api engine:l7] [reason:L7_REQUEST deny GET gateway.example.test:443/other reason=no L7 endpoint path matched request]"
90429127 ) ;
90439128 }
90449129
9130+ #[ test]
9131+ fn request_event_url_scheme_follows_transport ( ) {
9132+ for ( port, transport, scheme, url) in [
9133+ ( 80 , 80 , "http" , "http://api.example.test/v1" ) ,
9134+ ( 443 , 443 , "https" , "https://api.example.test/v1" ) ,
9135+ ( 8443 , 443 , "https" , "https://api.example.test:8443/v1" ) ,
9136+ ( 443 , 80 , "http" , "http://api.example.test:443/v1" ) ,
9137+ ] {
9138+ let ctx = L7EvalContext {
9139+ host : "api.example.test" . into ( ) ,
9140+ port,
9141+ request_default_port : Some ( transport) ,
9142+ policy_name : "api" . into ( ) ,
9143+ ..Default :: default ( )
9144+ } ;
9145+
9146+ let event = build_l7_request_event ( & ctx, "GET" , "/v1" , "allow" , "l7" , "ok" , None ) ;
9147+
9148+ let json = event. to_json ( ) . unwrap ( ) ;
9149+ assert_eq ! ( json[ "http_request" ] [ "url" ] [ "scheme" ] , scheme) ;
9150+ assert ! ( event. format_shorthand( ) . contains( url) , "{url}" ) ;
9151+ }
9152+ }
9153+
90459154 #[ tokio:: test]
90469155 async fn route_selected_unmatched_path_emits_denied_policy_activity ( ) {
90479156 let engine = OpaEngine :: from_strings ( TEST_POLICY , ENCODED_SLASH_SCOPING_POLICY ) . unwrap ( ) ;
0 commit comments