@@ -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 ;
@@ -59,6 +60,16 @@ pub enum HttpServerAuthConfig {
5960 /// The VRL boolean expression.
6061 source : String ,
6162 } ,
63+
64+ /// Custom authentication using VRL code, with event enrichment.
65+ ///
66+ /// Like `custom`, validates the request using a VRL boolean expression.
67+ /// Additionally, metadata fields written via `%field = value` in the VRL
68+ /// program are extracted and injected into every authenticated event.
69+ CustomEnriching {
70+ /// The VRL boolean expression. May write `%field = value` to enrich events.
71+ source : String ,
72+ } ,
6273}
6374
6475// Custom deserializer implementation to default `strategy` to `basic`
@@ -120,7 +131,16 @@ impl<'de> Deserialize<'de> for HttpServerAuthConfig {
120131 . ok_or_else ( || Error :: missing_field ( "source" ) ) ?;
121132 Ok ( HttpServerAuthConfig :: Custom { source } )
122133 }
123- _ => Err ( Error :: unknown_variant ( strategy, & [ "basic" , "custom" ] ) ) ,
134+ "custom_enriching" => {
135+ let source = fields
136+ . remove ( "source" )
137+ . ok_or_else ( || Error :: missing_field ( "source" ) ) ?;
138+ Ok ( HttpServerAuthConfig :: CustomEnriching { source } )
139+ }
140+ _ => Err ( Error :: unknown_variant (
141+ strategy,
142+ & [ "basic" , "custom" , "custom_enriching" ] ,
143+ ) ) ,
124144 }
125145 }
126146 }
@@ -171,6 +191,34 @@ impl HttpServerAuthConfig {
171191
172192 Ok ( HttpServerAuthMatcher :: Vrl { program } )
173193 }
194+ HttpServerAuthConfig :: CustomEnriching { source } => {
195+ let state = TypeState :: default ( ) ;
196+
197+ let mut config = CompileConfig :: default ( ) ;
198+ config. set_custom ( enrichment_tables. clone ( ) ) ;
199+ config. set_custom ( metrics_storage. clone ( ) ) ;
200+ // Lock the event body (.field) as read-only, but leave metadata (%field) writable
201+ // so the VRL program can enrich authenticated events via %field = value.
202+ config. set_read_only_path ( OwnedTargetPath :: event_root ( ) , true ) ;
203+
204+ let CompilationResult {
205+ program,
206+ warnings,
207+ config : _,
208+ } = compile_vrl ( source, & vector_vrl_functions:: all ( ) , & state, config)
209+ . map_err ( |diagnostics| format_vrl_diagnostics ( source, diagnostics) ) ?;
210+
211+ if !program. final_type_info ( ) . result . is_boolean ( ) {
212+ return Err ( "VRL conditions must return a boolean." . into ( ) ) ;
213+ }
214+
215+ if !warnings. is_empty ( ) {
216+ let warnings = format_vrl_diagnostics ( source, warnings) ;
217+ warn ! ( message = "VRL compilation warning." , %warnings) ;
218+ }
219+
220+ Ok ( HttpServerAuthMatcher :: VrlEnriching { program } )
221+ }
174222 }
175223 }
176224}
@@ -187,21 +235,28 @@ pub enum HttpServerAuthMatcher {
187235 /// Compiled VRL script
188236 program : Program ,
189237 } ,
238+ /// Like `Vrl`, but metadata (`%field`) writes in the program are extracted
239+ /// and returned to the caller for injection into authenticated events.
240+ VrlEnriching {
241+ /// Compiled VRL script
242+ program : Program ,
243+ } ,
190244}
191245
192246impl HttpServerAuthMatcher {
193- /// Compares passed headers to the matcher
247+ /// Validates the request. Returns `Ok(Some(enrichment))` for `VrlEnriching` when auth passes
248+ /// and the VRL program wrote `%field` values; returns `Ok(None)` for all other matchers.
194249 pub fn handle_auth (
195250 & self ,
196251 address : Option < & SocketAddr > ,
197252 headers : & HeaderMap < HeaderValue > ,
198253 path : & str ,
199- ) -> Result < ( ) , ErrorMessage > {
254+ ) -> Result < Option < ObjectMap > , ErrorMessage > {
200255 match self {
201256 HttpServerAuthMatcher :: AuthHeader ( expected, err_message) => {
202257 if let Some ( header) = headers. get ( AUTHORIZATION ) {
203258 if expected == header {
204- Ok ( ( ) )
259+ Ok ( None )
205260 } else {
206261 Err ( ErrorMessage :: new (
207262 StatusCode :: UNAUTHORIZED ,
@@ -216,7 +271,11 @@ impl HttpServerAuthMatcher {
216271 }
217272 }
218273 HttpServerAuthMatcher :: Vrl { program } => {
219- self . handle_vrl_auth ( address, headers, path, program)
274+ self . handle_vrl_auth ( address, headers, path, program) ?;
275+ Ok ( None )
276+ }
277+ HttpServerAuthMatcher :: VrlEnriching { program } => {
278+ self . handle_vrl_enriching_auth ( address, headers, path, program)
220279 }
221280 }
222281 }
@@ -279,6 +338,71 @@ impl HttpServerAuthMatcher {
279338 ) ) ,
280339 }
281340 }
341+
342+ fn handle_vrl_enriching_auth (
343+ & self ,
344+ address : Option < & SocketAddr > ,
345+ headers : & HeaderMap < HeaderValue > ,
346+ path : & str ,
347+ program : & Program ,
348+ ) -> Result < Option < ObjectMap > , ErrorMessage > {
349+ let mut target = VrlTarget :: new (
350+ Event :: Log ( LogEvent :: from_map (
351+ ObjectMap :: from ( [
352+ (
353+ "headers" . into ( ) ,
354+ Value :: Object (
355+ headers
356+ . iter ( )
357+ . map ( |( k, v) | {
358+ (
359+ KeyString :: from ( k. to_string ( ) ) ,
360+ Value :: Bytes ( Bytes :: copy_from_slice ( v. as_bytes ( ) ) ) ,
361+ )
362+ } )
363+ . collect :: < ObjectMap > ( ) ,
364+ ) ,
365+ ) ,
366+ (
367+ "address" . into ( ) ,
368+ address. map_or ( Value :: Null , |a| Value :: from ( a. ip ( ) . to_string ( ) ) ) ,
369+ ) ,
370+ ( "path" . into ( ) , Value :: from ( path. to_owned ( ) ) ) ,
371+ ] ) ,
372+ Default :: default ( ) ,
373+ ) ) ,
374+ program. info ( ) ,
375+ false ,
376+ ) ;
377+ let timezone = TimeZone :: default ( ) ;
378+
379+ let result = Runtime :: default ( ) . resolve ( & mut target, program, & timezone) ;
380+ match result. map_err ( |e| {
381+ warn ! ( "Handling auth failed: {}" , e) ;
382+ ErrorMessage :: new ( StatusCode :: UNAUTHORIZED , "Auth failed" . to_owned ( ) )
383+ } ) ? {
384+ vrl:: core:: Value :: Boolean ( true ) => {
385+ let enrichment = if let VrlTarget :: LogEvent ( _, metadata) = & target {
386+ metadata
387+ . value ( )
388+ . as_object ( )
389+ . filter ( |m| !m. is_empty ( ) )
390+ . cloned ( )
391+ } else {
392+ None
393+ } ;
394+ Ok ( enrichment)
395+ }
396+ vrl:: core:: Value :: Boolean ( false ) => Err ( ErrorMessage :: new (
397+ StatusCode :: UNAUTHORIZED ,
398+ "Auth failed" . to_owned ( ) ,
399+ ) ) ,
400+ _ => Err ( ErrorMessage :: new (
401+ StatusCode :: UNAUTHORIZED ,
402+ "Invalid return value" . to_owned ( ) ,
403+ ) ) ,
404+ }
405+ }
282406}
283407
284408#[ cfg( test) ]
@@ -294,7 +418,7 @@ mod tests {
294418 HttpServerAuthMatcher :: AuthHeader ( header_value, error_message) => {
295419 ( header_value, error_message)
296420 }
297- HttpServerAuthMatcher :: Vrl { .. } => {
421+ HttpServerAuthMatcher :: Vrl { .. } | HttpServerAuthMatcher :: VrlEnriching { .. } => {
298422 panic ! ( "Expected HttpServerAuthMatcher::AuthHeader" )
299423 }
300424 }
0 commit comments