1use std::{collections::HashMap, fmt, net::SocketAddr};
3
4use bytes::Bytes;
5use headers::{Authorization, authorization::Credentials};
6use http::{HeaderMap, HeaderValue, StatusCode, header::AUTHORIZATION};
7use serde::{
8 Deserialize,
9 de::{Error, MapAccess, Visitor},
10};
11use vector_config::configurable_component;
12use vector_lib::{
13 TimeZone, compile_vrl,
14 event::{Event, LogEvent, VrlTarget},
15 lookup::OwnedTargetPath,
16 sensitive_string::SensitiveString,
17};
18use vector_vrl_metrics::MetricsStorage;
19use vrl::{
20 compiler::{CompilationResult, CompileConfig, Program, runtime::Runtime},
21 core::Value,
22 prelude::TypeState,
23 value::{KeyString, ObjectMap},
24};
25
26use crate::format_vrl_diagnostics;
27
28use super::ErrorMessage;
29
30#[configurable_component(no_deser)]
35#[derive(Clone, Debug, Eq, PartialEq)]
36#[configurable(metadata(docs::enum_tag_description = "The authentication strategy to use."))]
37#[serde(tag = "strategy", rename_all = "snake_case")]
38pub enum HttpServerAuthConfig {
39 Basic {
45 #[configurable(metadata(docs::examples = "${USERNAME}"))]
47 #[configurable(metadata(docs::examples = "username"))]
48 username: String,
49
50 #[configurable(metadata(docs::examples = "${PASSWORD}"))]
52 #[configurable(metadata(docs::examples = "password"))]
53 password: SensitiveString,
54 },
55
56 Custom {
60 source: String,
62 },
63}
64
65impl<'de> Deserialize<'de> for HttpServerAuthConfig {
67 fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
68 where
69 D: serde::Deserializer<'de>,
70 {
71 struct HttpServerAuthConfigVisitor;
72
73 const FIELD_KEYS: [&str; 4] = ["strategy", "username", "password", "source"];
74
75 impl<'de> Visitor<'de> for HttpServerAuthConfigVisitor {
76 type Value = HttpServerAuthConfig;
77
78 fn expecting(&self, formatter: &mut fmt::Formatter) -> fmt::Result {
79 formatter.write_str("a valid authentication strategy (basic or custom)")
80 }
81
82 fn visit_map<A>(self, mut map: A) -> Result<HttpServerAuthConfig, A::Error>
83 where
84 A: MapAccess<'de>,
85 {
86 let mut fields: HashMap<&str, String> = HashMap::default();
87
88 while let Some(key) = map.next_key::<String>()? {
89 if let Some(field_index) = FIELD_KEYS.iter().position(|k| *k == key.as_str()) {
90 if fields.contains_key(FIELD_KEYS[field_index]) {
91 return Err(Error::duplicate_field(FIELD_KEYS[field_index]));
92 }
93 fields.insert(FIELD_KEYS[field_index], map.next_value()?);
94 } else {
95 return Err(Error::unknown_field(&key, &FIELD_KEYS));
96 }
97 }
98
99 let strategy = fields
101 .get("strategy")
102 .map(String::as_str)
103 .unwrap_or_else(|| "basic");
104
105 match strategy {
106 "basic" => {
107 let username = fields
108 .remove("username")
109 .ok_or_else(|| Error::missing_field("username"))?;
110 let password = fields
111 .remove("password")
112 .ok_or_else(|| Error::missing_field("password"))?;
113 Ok(HttpServerAuthConfig::Basic {
114 username,
115 password: SensitiveString::from(password),
116 })
117 }
118 "custom" => {
119 let source = fields
120 .remove("source")
121 .ok_or_else(|| Error::missing_field("source"))?;
122 Ok(HttpServerAuthConfig::Custom { source })
123 }
124 _ => Err(Error::unknown_variant(strategy, &["basic", "custom"])),
125 }
126 }
127 }
128
129 deserializer.deserialize_map(HttpServerAuthConfigVisitor)
130 }
131}
132
133impl HttpServerAuthConfig {
134 pub fn build(
138 &self,
139 enrichment_tables: &vector_lib::enrichment::TableRegistry,
140 metrics_storage: &MetricsStorage,
141 ) -> crate::Result<HttpServerAuthMatcher> {
142 match self {
143 HttpServerAuthConfig::Basic { username, password } => {
144 Ok(HttpServerAuthMatcher::AuthHeader(
145 Authorization::basic(username, password.inner()).0.encode(),
146 "Invalid username/password",
147 ))
148 }
149 HttpServerAuthConfig::Custom { source } => {
150 let state = TypeState::default();
151
152 let mut config = CompileConfig::default();
153 config.set_custom(enrichment_tables.clone());
154 config.set_custom(metrics_storage.clone());
155 config.set_read_only_path(OwnedTargetPath::event_root(), true);
158
159 let CompilationResult {
160 program,
161 warnings,
162 config: _,
163 } = compile_vrl(source, &vector_vrl_functions::all(), &state, config)
164 .map_err(|diagnostics| format_vrl_diagnostics(source, diagnostics))?;
165
166 if !program.final_type_info().result.is_boolean() {
167 return Err("VRL conditions must return a boolean.".into());
168 }
169
170 if !warnings.is_empty() {
171 let warnings = format_vrl_diagnostics(source, warnings);
172 warn!(message = "VRL compilation warning.", %warnings);
173 }
174
175 Ok(HttpServerAuthMatcher::Vrl { program })
176 }
177 }
178 }
179}
180
181#[allow(clippy::large_enum_variant)]
184#[derive(Clone, Debug)]
185pub enum HttpServerAuthMatcher {
186 AuthHeader(HeaderValue, &'static str),
188 Vrl {
192 program: Program,
194 },
195}
196
197impl HttpServerAuthMatcher {
198 pub fn handle_auth(
201 &self,
202 address: Option<&SocketAddr>,
203 headers: &HeaderMap<HeaderValue>,
204 path: &str,
205 ) -> Result<Option<ObjectMap>, ErrorMessage> {
206 match self {
207 HttpServerAuthMatcher::AuthHeader(expected, err_message) => {
208 if let Some(header) = headers.get(AUTHORIZATION) {
209 if expected == header {
210 Ok(None)
211 } else {
212 Err(ErrorMessage::new(
213 StatusCode::UNAUTHORIZED,
214 err_message.to_string(),
215 ))
216 }
217 } else {
218 Err(ErrorMessage::new(
219 StatusCode::UNAUTHORIZED,
220 "No authorization header".to_owned(),
221 ))
222 }
223 }
224 HttpServerAuthMatcher::Vrl { program } => {
225 self.handle_vrl_auth(address, headers, path, program)
226 }
227 }
228 }
229
230 fn handle_vrl_auth(
231 &self,
232 address: Option<&SocketAddr>,
233 headers: &HeaderMap<HeaderValue>,
234 path: &str,
235 program: &Program,
236 ) -> Result<Option<ObjectMap>, ErrorMessage> {
237 let mut target = VrlTarget::new(
238 Event::Log(LogEvent::from_map(
239 ObjectMap::from([
240 (
241 "headers".into(),
242 Value::Object(
243 headers
244 .iter()
245 .map(|(k, v)| {
246 (
247 KeyString::from(k.to_string()),
248 Value::Bytes(Bytes::copy_from_slice(v.as_bytes())),
249 )
250 })
251 .collect::<ObjectMap>(),
252 ),
253 ),
254 (
255 "address".into(),
256 address.map_or(Value::Null, |a| Value::from(a.ip().to_string())),
257 ),
258 ("path".into(), Value::from(path.to_owned())),
259 ]),
260 Default::default(),
261 )),
262 program.info(),
263 false,
264 );
265 let timezone = TimeZone::default();
266
267 let result = Runtime::default().resolve(&mut target, program, &timezone);
268 match result.map_err(|e| {
269 warn!("Handling auth failed: {}", e);
270 ErrorMessage::new(StatusCode::UNAUTHORIZED, "Auth failed".to_owned())
271 })? {
272 vrl::core::Value::Boolean(true) => {
273 let enrichment = if let VrlTarget::LogEvent(_, metadata) = &target {
274 metadata
275 .value()
276 .as_object()
277 .filter(|m| !m.is_empty())
278 .cloned()
279 } else {
280 None
281 };
282 Ok(enrichment)
283 }
284 vrl::core::Value::Boolean(false) => Err(ErrorMessage::new(
285 StatusCode::UNAUTHORIZED,
286 "Auth failed".to_owned(),
287 )),
288 _ => Err(ErrorMessage::new(
289 StatusCode::UNAUTHORIZED,
290 "Invalid return value".to_owned(),
291 )),
292 }
293 }
294}
295
296#[cfg(test)]
297mod tests {
298 use indoc::indoc;
299
300 use super::*;
301 use crate::test_util::{addr::next_addr, random_string};
302
303 impl HttpServerAuthMatcher {
304 fn auth_header(self) -> (HeaderValue, &'static str) {
305 match self {
306 HttpServerAuthMatcher::AuthHeader(header_value, error_message) => {
307 (header_value, error_message)
308 }
309 HttpServerAuthMatcher::Vrl { .. } => {
310 panic!("Expected HttpServerAuthMatcher::AuthHeader")
311 }
312 }
313 }
314 }
315
316 #[test]
317 fn config_should_default_to_basic() {
318 let config: HttpServerAuthConfig = serde_yaml::from_str(indoc! { r#"
319 username: foo
320 password: bar
321 "#
322 })
323 .unwrap();
324
325 if let HttpServerAuthConfig::Basic { username, password } = config {
326 assert_eq!(username, "foo");
327 assert_eq!(password.inner(), "bar");
328 } else {
329 panic!("Expected HttpServerAuthConfig::Basic");
330 }
331 }
332
333 #[test]
334 fn config_should_support_explicit_basic_strategy() {
335 let config: HttpServerAuthConfig = serde_yaml::from_str(indoc! { r#"
336 strategy: basic
337 username: foo
338 password: bar
339 "#
340 })
341 .unwrap();
342
343 if let HttpServerAuthConfig::Basic { username, password } = config {
344 assert_eq!(username, "foo");
345 assert_eq!(password.inner(), "bar");
346 } else {
347 panic!("Expected HttpServerAuthConfig::Basic");
348 }
349 }
350
351 #[test]
352 fn config_should_support_custom_strategy() {
353 let config: HttpServerAuthConfig = serde_yaml::from_str(indoc! { r#"
354 strategy: custom
355 source: "true"
356 "#
357 })
358 .unwrap();
359
360 assert!(matches!(config, HttpServerAuthConfig::Custom { .. }));
361 if let HttpServerAuthConfig::Custom { source } = config {
362 assert_eq!(source, "true");
363 } else {
364 panic!("Expected HttpServerAuthConfig::Custom");
365 }
366 }
367
368 #[test]
369 fn build_basic_auth_should_always_work() {
370 let basic_auth = HttpServerAuthConfig::Basic {
371 username: random_string(16),
372 password: random_string(16).into(),
373 };
374
375 let matcher = basic_auth.build(&Default::default(), &Default::default());
376
377 assert!(matcher.is_ok());
378 assert!(matches!(
379 matcher.unwrap(),
380 HttpServerAuthMatcher::AuthHeader { .. }
381 ));
382 }
383
384 #[test]
385 fn build_basic_auth_should_use_username_password_related_message() {
386 let basic_auth = HttpServerAuthConfig::Basic {
387 username: random_string(16),
388 password: random_string(16).into(),
389 };
390
391 let (_, error_message) = basic_auth
392 .build(&Default::default(), &Default::default())
393 .unwrap()
394 .auth_header();
395 assert_eq!("Invalid username/password", error_message);
396 }
397
398 #[test]
399 fn build_basic_auth_should_use_encode_basic_header() {
400 let username = random_string(16);
401 let password = random_string(16);
402 let basic_auth = HttpServerAuthConfig::Basic {
403 username: username.clone(),
404 password: password.clone().into(),
405 };
406
407 let (header, _) = basic_auth
408 .build(&Default::default(), &Default::default())
409 .unwrap()
410 .auth_header();
411 assert_eq!(
412 Authorization::basic(&username, &password).0.encode(),
413 header
414 );
415 }
416
417 #[test]
418 fn build_custom_should_fail_on_invalid_source() {
419 let custom_auth = HttpServerAuthConfig::Custom {
420 source: "invalid VRL source".to_string(),
421 };
422
423 assert!(
424 custom_auth
425 .build(&Default::default(), &Default::default())
426 .is_err()
427 );
428 }
429
430 #[test]
431 fn build_custom_should_fail_on_non_boolean_return_type() {
432 let custom_auth = HttpServerAuthConfig::Custom {
433 source: indoc! {r#"
434 .success = true
435 .
436 "#}
437 .to_string(),
438 };
439
440 assert!(
441 custom_auth
442 .build(&Default::default(), &Default::default())
443 .is_err()
444 );
445 }
446
447 #[test]
448 fn build_custom_should_success_on_proper_source_with_boolean_return_type() {
449 let custom_auth = HttpServerAuthConfig::Custom {
450 source: indoc! {r#"
451 .headers.authorization == "Basic test"
452 "#}
453 .to_string(),
454 };
455
456 assert!(
457 custom_auth
458 .build(&Default::default(), &Default::default())
459 .is_ok()
460 );
461 }
462
463 #[test]
464 fn basic_auth_matcher_should_return_401_when_missing_auth_header() {
465 let basic_auth = HttpServerAuthConfig::Basic {
466 username: random_string(16),
467 password: random_string(16).into(),
468 };
469
470 let matcher = basic_auth
471 .build(&Default::default(), &Default::default())
472 .unwrap();
473
474 let (_guard, addr) = next_addr();
475 let result = matcher.handle_auth(Some(&addr), &HeaderMap::new(), "/");
476
477 assert!(result.is_err());
478 let error = result.unwrap_err();
479 assert_eq!(401, error.code());
480 assert_eq!("No authorization header", error.message());
481 }
482
483 #[test]
484 fn basic_auth_matcher_should_return_401_and_with_wrong_credentials() {
485 let basic_auth = HttpServerAuthConfig::Basic {
486 username: random_string(16),
487 password: random_string(16).into(),
488 };
489
490 let matcher = basic_auth
491 .build(&Default::default(), &Default::default())
492 .unwrap();
493
494 let mut headers = HeaderMap::new();
495 headers.insert(AUTHORIZATION, HeaderValue::from_static("Basic wrong"));
496 let (_guard, addr) = next_addr();
497 let result = matcher.handle_auth(Some(&addr), &headers, "/");
498
499 assert!(result.is_err());
500 let error = result.unwrap_err();
501 assert_eq!(401, error.code());
502 assert_eq!("Invalid username/password", error.message());
503 }
504
505 #[test]
506 fn basic_auth_matcher_should_return_ok_for_correct_credentials() {
507 let username = random_string(16);
508 let password = random_string(16);
509 let basic_auth = HttpServerAuthConfig::Basic {
510 username: username.clone(),
511 password: password.clone().into(),
512 };
513
514 let matcher = basic_auth
515 .build(&Default::default(), &Default::default())
516 .unwrap();
517
518 let mut headers = HeaderMap::new();
519 headers.insert(
520 AUTHORIZATION,
521 Authorization::basic(&username, &password).0.encode(),
522 );
523 let (_guard, addr) = next_addr();
524 let result = matcher.handle_auth(Some(&addr), &headers, "/");
525
526 assert!(result.is_ok());
527 }
528
529 #[test]
530 fn custom_auth_matcher_should_return_ok_for_true_vrl_script_result() {
531 let custom_auth = HttpServerAuthConfig::Custom {
532 source: r#".headers.authorization == "test""#.to_string(),
533 };
534
535 let matcher = custom_auth
536 .build(&Default::default(), &Default::default())
537 .unwrap();
538
539 let mut headers = HeaderMap::new();
540 headers.insert(AUTHORIZATION, HeaderValue::from_static("test"));
541 let (_guard, addr) = next_addr();
542 let result = matcher.handle_auth(Some(&addr), &headers, "/");
543
544 assert!(result.is_ok());
545 }
546
547 #[test]
548 fn custom_auth_matcher_should_be_able_to_check_address() {
549 let (_guard, addr) = next_addr();
550 let addr_string = addr.ip().to_string();
551 let custom_auth = HttpServerAuthConfig::Custom {
552 source: format!(".address == \"{addr_string}\""),
553 };
554
555 let matcher = custom_auth
556 .build(&Default::default(), &Default::default())
557 .unwrap();
558
559 let headers = HeaderMap::new();
560 let result = matcher.handle_auth(Some(&addr), &headers, "/");
561
562 assert!(result.is_ok());
563 }
564
565 #[test]
566 fn custom_auth_matcher_should_work_with_missing_address_too() {
567 let (_guard, addr) = next_addr();
568 let addr_string = addr.ip().to_string();
569 let custom_auth = HttpServerAuthConfig::Custom {
570 source: format!(".address == \"{addr_string}\""),
571 };
572
573 let matcher = custom_auth
574 .build(&Default::default(), &Default::default())
575 .unwrap();
576
577 let headers = HeaderMap::new();
578 let result = matcher.handle_auth(None, &headers, "/");
579
580 assert!(result.is_err());
581 }
582
583 #[test]
584 fn custom_auth_matcher_should_be_able_to_check_path() {
585 let custom_auth = HttpServerAuthConfig::Custom {
586 source: r#".path == "/ok""#.to_string(),
587 };
588
589 let matcher = custom_auth
590 .build(&Default::default(), &Default::default())
591 .unwrap();
592
593 let headers = HeaderMap::new();
594 let (_guard, addr) = next_addr();
595 let result = matcher.handle_auth(Some(&addr), &headers, "/ok");
596
597 assert!(result.is_ok());
598 }
599
600 #[test]
601 fn custom_auth_matcher_should_return_401_with_wrong_path() {
602 let custom_auth = HttpServerAuthConfig::Custom {
603 source: r#".path == "/ok""#.to_string(),
604 };
605
606 let matcher = custom_auth
607 .build(&Default::default(), &Default::default())
608 .unwrap();
609
610 let headers = HeaderMap::new();
611 let (_guard, addr) = next_addr();
612 let result = matcher.handle_auth(Some(&addr), &headers, "/bad");
613
614 assert!(result.is_err());
615 }
616
617 #[test]
618 fn custom_auth_matcher_should_return_401_for_false_vrl_script_result() {
619 let custom_auth = HttpServerAuthConfig::Custom {
620 source: r#".headers.authorization == "test""#.to_string(),
621 };
622
623 let matcher = custom_auth
624 .build(&Default::default(), &Default::default())
625 .unwrap();
626
627 let mut headers = HeaderMap::new();
628 headers.insert(AUTHORIZATION, HeaderValue::from_static("wrong value"));
629 let (_guard, addr) = next_addr();
630 let result = matcher.handle_auth(Some(&addr), &headers, "/");
631
632 assert!(result.is_err());
633 let error = result.unwrap_err();
634 assert_eq!(401, error.code());
635 assert_eq!("Auth failed", error.message());
636 }
637
638 #[test]
639 fn custom_auth_matcher_should_return_401_for_failed_script_execution() {
640 let custom_auth = HttpServerAuthConfig::Custom {
641 source: "abort".to_string(),
642 };
643
644 let matcher = custom_auth
645 .build(&Default::default(), &Default::default())
646 .unwrap();
647
648 let mut headers = HeaderMap::new();
649 headers.insert(AUTHORIZATION, HeaderValue::from_static("test"));
650 let (_guard, addr) = next_addr();
651 let result = matcher.handle_auth(Some(&addr), &headers, "/");
652
653 assert!(result.is_err());
654 let error = result.unwrap_err();
655 assert_eq!(401, error.code());
656 assert_eq!("Auth failed", error.message());
657 }
658
659 #[test]
662 fn custom_auth_matcher_returns_none_enrichment_when_no_metadata_written() {
663 let custom_auth = HttpServerAuthConfig::Custom {
664 source: r#".headers.authorization == "Bearer token""#.to_string(),
665 };
666
667 let matcher = custom_auth
668 .build(&Default::default(), &Default::default())
669 .unwrap();
670
671 let mut headers = HeaderMap::new();
672 headers.insert(AUTHORIZATION, HeaderValue::from_static("Bearer token"));
673 let (_guard, addr) = next_addr();
674 let result = matcher.handle_auth(Some(&addr), &headers, "/");
675
676 assert!(result.is_ok());
677 assert_eq!(
678 None,
679 result.unwrap(),
680 "no metadata written => no enrichment"
681 );
682 }
683
684 #[test]
686 fn custom_auth_matcher_returns_enrichment_when_metadata_written() {
687 let custom_auth = HttpServerAuthConfig::Custom {
688 source: indoc! {r#"
689 %tenant_id = "acme"
690 true
691 "#}
692 .to_string(),
693 };
694
695 let matcher = custom_auth
696 .build(&Default::default(), &Default::default())
697 .unwrap();
698
699 let headers = HeaderMap::new();
700 let (_guard, addr) = next_addr();
701 let result = matcher.handle_auth(Some(&addr), &headers, "/");
702
703 assert!(result.is_ok());
704 let enrichment = result.unwrap().expect("expected enrichment map");
705 assert_eq!(
706 enrichment.get("tenant_id").cloned(),
707 Some(vrl::core::Value::from("acme")),
708 );
709 }
710
711 #[test]
713 fn custom_auth_build_fails_when_event_body_write_attempted() {
714 let custom_auth = HttpServerAuthConfig::Custom {
715 source: indoc! {r#"
716 .new_field = "value"
717 true
718 "#}
719 .to_string(),
720 };
721
722 assert!(
723 custom_auth
724 .build(&Default::default(), &Default::default())
725 .is_err(),
726 "writing to event body (.field) must be rejected at compile time"
727 );
728 }
729}