@@ -12,6 +12,7 @@ use vector_config::configurable_component;
1212use vector_lib:: {
1313 TimeZone , compile_vrl,
1414 event:: { Event , LogEvent , VrlTarget } ,
15+ lookup:: OwnedTargetPath ,
1516 sensitive_string:: SensitiveString ,
1617} ;
1718use vector_vrl_metrics:: MetricsStorage ;
@@ -54,9 +55,11 @@ pub enum HttpServerAuthConfig {
5455
5556 /// Custom authentication using VRL code.
5657 ///
57- /// Takes in request and validates it using VRL code.
58+ /// Takes in request and validates it using VRL code. The VRL program must return a boolean.
59+ /// Metadata fields written via `%field = value` in the VRL program are extracted and injected
60+ /// into every authenticated event.
5861 Custom {
59- /// The VRL boolean expression.
62+ /// The VRL boolean expression. May write `%field = value` to enrich events.
6063 source : String ,
6164 } ,
6265}
@@ -151,7 +154,9 @@ impl HttpServerAuthConfig {
151154 let mut config = CompileConfig :: default ( ) ;
152155 config. set_custom ( enrichment_tables. clone ( ) ) ;
153156 config. set_custom ( metrics_storage. clone ( ) ) ;
154- config. set_read_only ( ) ;
157+ // Lock the event body (.field) as read-only, but leave metadata (%field) writable
158+ // so the VRL program can enrich authenticated events via %field = value.
159+ config. set_read_only_path ( OwnedTargetPath :: event_root ( ) , true ) ;
155160
156161 let CompilationResult {
157162 program,
@@ -182,26 +187,29 @@ impl HttpServerAuthConfig {
182187pub enum HttpServerAuthMatcher {
183188 /// Matcher for comparing exact value of Authorization header
184189 AuthHeader ( HeaderValue , & ' static str ) ,
185- /// Matcher for running VRL script for requests, to allow for custom validation
190+ /// Matcher for running VRL script for requests, to allow for custom validation.
191+ /// Metadata (`%field`) writes in the program are extracted and returned to the caller
192+ /// for injection into authenticated events.
186193 Vrl {
187194 /// Compiled VRL script
188195 program : Program ,
189196 } ,
190197}
191198
192199impl HttpServerAuthMatcher {
193- /// Compares passed headers to the matcher
200+ /// Validates the request. Returns `Ok(Some(enrichment))` when auth passes and the VRL program
201+ /// wrote `%field` values; returns `Ok(None)` when auth passes with no metadata enrichment.
194202 pub fn handle_auth (
195203 & self ,
196204 address : Option < & SocketAddr > ,
197205 headers : & HeaderMap < HeaderValue > ,
198206 path : & str ,
199- ) -> Result < ( ) , ErrorMessage > {
207+ ) -> Result < Option < ObjectMap > , ErrorMessage > {
200208 match self {
201209 HttpServerAuthMatcher :: AuthHeader ( expected, err_message) => {
202210 if let Some ( header) = headers. get ( AUTHORIZATION ) {
203211 if expected == header {
204- Ok ( ( ) )
212+ Ok ( None )
205213 } else {
206214 Err ( ErrorMessage :: new (
207215 StatusCode :: UNAUTHORIZED ,
@@ -227,7 +235,7 @@ impl HttpServerAuthMatcher {
227235 headers : & HeaderMap < HeaderValue > ,
228236 path : & str ,
229237 program : & Program ,
230- ) -> Result < ( ) , ErrorMessage > {
238+ ) -> Result < Option < ObjectMap > , ErrorMessage > {
231239 let mut target = VrlTarget :: new (
232240 Event :: Log ( LogEvent :: from_map (
233241 ObjectMap :: from ( [
@@ -263,16 +271,22 @@ impl HttpServerAuthMatcher {
263271 warn ! ( "Handling auth failed: {}" , e) ;
264272 ErrorMessage :: new ( StatusCode :: UNAUTHORIZED , "Auth failed" . to_owned ( ) )
265273 } ) ? {
266- vrl:: core:: Value :: Boolean ( result) => {
267- if result {
268- Ok ( ( ) )
274+ vrl:: core:: Value :: Boolean ( true ) => {
275+ let enrichment = if let VrlTarget :: LogEvent ( _, metadata) = & target {
276+ metadata
277+ . value ( )
278+ . as_object ( )
279+ . filter ( |m| !m. is_empty ( ) )
280+ . cloned ( )
269281 } else {
270- Err ( ErrorMessage :: new (
271- StatusCode :: UNAUTHORIZED ,
272- "Auth failed" . to_owned ( ) ,
273- ) )
274- }
282+ None
283+ } ;
284+ Ok ( enrichment)
275285 }
286+ vrl:: core:: Value :: Boolean ( false ) => Err ( ErrorMessage :: new (
287+ StatusCode :: UNAUTHORIZED ,
288+ "Auth failed" . to_owned ( ) ,
289+ ) ) ,
276290 _ => Err ( ErrorMessage :: new (
277291 StatusCode :: UNAUTHORIZED ,
278292 "Invalid return value" . to_owned ( ) ,
@@ -643,4 +657,75 @@ mod tests {
643657 assert_eq ! ( 401 , error. code( ) ) ;
644658 assert_eq ! ( "Auth failed" , error. message( ) ) ;
645659 }
660+
661+ // Backward-compat: existing `custom` scripts that don't write metadata still work and return
662+ // Ok(None) — no enrichment, no change in behavior.
663+ #[ test]
664+ fn custom_auth_matcher_returns_none_enrichment_when_no_metadata_written ( ) {
665+ let custom_auth = HttpServerAuthConfig :: Custom {
666+ source : r#".headers.authorization == "Bearer token""# . to_string ( ) ,
667+ } ;
668+
669+ let matcher = custom_auth
670+ . build ( & Default :: default ( ) , & Default :: default ( ) )
671+ . unwrap ( ) ;
672+
673+ let mut headers = HeaderMap :: new ( ) ;
674+ headers. insert ( AUTHORIZATION , HeaderValue :: from_static ( "Bearer token" ) ) ;
675+ let ( _guard, addr) = next_addr ( ) ;
676+ let result = matcher. handle_auth ( Some ( & addr) , & headers, "/" ) ;
677+
678+ assert ! ( result. is_ok( ) ) ;
679+ assert_eq ! (
680+ None ,
681+ result. unwrap( ) ,
682+ "no metadata written => no enrichment"
683+ ) ;
684+ }
685+
686+ // Existing `custom` scripts that write metadata via `%field = value` now enrich events.
687+ #[ test]
688+ fn custom_auth_matcher_returns_enrichment_when_metadata_written ( ) {
689+ let custom_auth = HttpServerAuthConfig :: Custom {
690+ source : indoc ! { r#"
691+ %tenant_id = "acme"
692+ true
693+ "# }
694+ . to_string ( ) ,
695+ } ;
696+
697+ let matcher = custom_auth
698+ . build ( & Default :: default ( ) , & Default :: default ( ) )
699+ . unwrap ( ) ;
700+
701+ let headers = HeaderMap :: new ( ) ;
702+ let ( _guard, addr) = next_addr ( ) ;
703+ let result = matcher. handle_auth ( Some ( & addr) , & headers, "/" ) ;
704+
705+ assert ! ( result. is_ok( ) ) ;
706+ let enrichment = result. unwrap ( ) . expect ( "expected enrichment map" ) ;
707+ assert_eq ! (
708+ enrichment. get( "tenant_id" ) . cloned( ) ,
709+ Some ( vrl:: core:: Value :: from( "acme" ) ) ,
710+ ) ;
711+ }
712+
713+ // Existing `custom` scripts still cannot mutate event body fields.
714+ #[ test]
715+ fn custom_auth_build_fails_when_event_body_write_attempted ( ) {
716+ let custom_auth = HttpServerAuthConfig :: Custom {
717+ source : indoc ! { r#"
718+ .new_field = "value"
719+ true
720+ "# }
721+ . to_string ( ) ,
722+ } ;
723+
724+ assert ! (
725+ custom_auth
726+ . build( & Default :: default ( ) , & Default :: default ( ) )
727+ . is_err( ) ,
728+ "writing to event body (.field) must be rejected at compile time"
729+ ) ;
730+ }
646731}
0 commit comments