1use std::{
2 collections::HashMap,
3 convert::Infallible,
4 net::{Ipv4Addr, SocketAddr},
5 sync::Arc,
6 time::Duration,
7};
8
9use bytes::{Buf, Bytes, BytesMut};
10use chrono::{DateTime, TimeZone, Utc};
11use futures::FutureExt;
12use http::StatusCode;
13use hyper::{Server, service::make_service_fn};
14use serde::{Serialize, de::DeserializeOwned};
15use serde_json::{
16 Deserializer, Value as JsonValue,
17 de::{Read as JsonRead, StrRead},
18};
19use snafu::Snafu;
20use tokio::net::TcpStream;
21use tokio_util::codec::Decoder as _;
22use tower::ServiceBuilder;
23use tracing::Span;
24use vector_lib::{
25 EstimatedJsonEncodedSizeOf,
26 codecs::{
27 Decoder, StreamDecodingError,
28 decoding::{DeserializerConfig, FramingConfig},
29 },
30 config::{LegacyKey, LogNamespace},
31 configurable::configurable_component,
32 event::{BatchNotifier, BatchStatusReceiver, EventMetadata},
33 internal_event::{
34 ComponentEventsDropped, CountByteSize, InternalEventHandle as _, Registered, UNINTENTIONAL,
35 },
36 lookup::{
37 self, OwnedValuePath, event_path, lookup_v2::OptionalValuePath, metadata_path,
38 owned_value_path,
39 },
40 schema::meaning,
41 sensitive_string::SensitiveString,
42 source_sender::SendError,
43 tls::MaybeTlsIncomingStream,
44};
45use vrl::{
46 path::{OwnedTargetPath, PathPrefix, ValuePath as _},
47 value::{Kind, kind::Collection},
48};
49use warp::{
50 Filter, Reply,
51 filters::BoxedFilter,
52 http::header::{CONTENT_TYPE, HeaderValue},
53 path,
54 reject::Rejection,
55 reply::Response,
56};
57
58use self::{
59 acknowledgements::{
60 HecAckStatusRequest, HecAckStatusResponse, HecAcknowledgementsConfig,
61 IndexerAcknowledgement,
62 },
63 splunk_response::{HecResponse, HecResponseMetadata, HecStatusCode},
64};
65use crate::{
66 SourceSender,
67 codecs::DecodingConfig,
68 common::http::ErrorMessage,
69 config::{DataType, Resource, SourceConfig, SourceContext, SourceOutput, log_schema},
70 event::{Event, LogEvent, Value},
71 http::{KeepaliveConfig, MaxConnectionAgeLayer, build_http_trace_layer},
72 internal_events::{
73 EventsReceived, HttpBytesReceived, SplunkHecRequestBodyInvalidError, SplunkHecRequestError,
74 },
75 serde::bool_or_struct,
76 sources::util::{decompression::CappedDecoder, http::capped_body},
77 tls::{MaybeTlsSettings, TlsEnableableConfig},
78};
79
80mod acknowledgements;
81
82pub const CHANNEL: &str = "splunk_channel";
84pub const INDEX: &str = "splunk_index";
85pub const SOURCE: &str = "splunk_source";
86pub const SOURCETYPE: &str = "splunk_sourcetype";
87
88const X_SPLUNK_REQUEST_CHANNEL: &str = "x-splunk-request-channel";
89
90#[configurable_component(source("splunk_hec", "Receive logs from Splunk."))]
92#[derive(Clone, Debug)]
93#[serde(deny_unknown_fields, default)]
94pub struct SplunkConfig {
95 #[serde(default = "default_socket_address")]
99 pub address: SocketAddr,
100
101 #[configurable(deprecated = "This option has been deprecated, use `valid_tokens` instead.")]
108 token: Option<SensitiveString>,
109
110 #[configurable(metadata(docs::examples = "A94A8FE5CCB19BA61C4C08"))]
117 valid_tokens: Option<Vec<SensitiveString>>,
118
119 store_hec_token: bool,
124
125 #[configurable(derived)]
126 tls: Option<TlsEnableableConfig>,
127
128 #[configurable(derived)]
129 #[serde(deserialize_with = "bool_or_struct")]
130 acknowledgements: HecAcknowledgementsConfig,
131
132 #[configurable(metadata(docs::hidden))]
134 #[serde(default)]
135 log_namespace: Option<bool>,
136
137 #[configurable(derived)]
138 #[serde(default)]
139 keepalive: KeepaliveConfig,
140
141 #[configurable(derived)]
152 #[configurable(metadata(docs::advanced))]
153 #[serde(default)]
154 pub event: CodecConfig,
155
156 #[configurable(derived)]
163 #[configurable(metadata(docs::advanced))]
164 #[serde(default)]
165 pub raw: CodecConfig,
166}
167
168#[configurable_component]
170#[derive(Clone, Debug, Default)]
171#[serde(deny_unknown_fields, default)]
172pub struct CodecConfig {
173 #[configurable(derived)]
178 #[configurable(metadata(docs::advanced))]
179 #[serde(default)]
180 pub framing: Option<FramingConfig>,
181
182 #[configurable(derived)]
189 #[configurable(metadata(docs::advanced))]
190 #[serde(default)]
191 pub decoding: Option<DeserializerConfig>,
192}
193
194impl CodecConfig {
195 fn build_decoder(&self, log_namespace: LogNamespace) -> crate::Result<Option<Decoder>> {
196 match &self.decoding {
197 Some(decoding) => {
198 let framing = self
199 .framing
200 .clone()
201 .unwrap_or_else(|| decoding.default_message_based_framing());
202 Ok(Some(
203 DecodingConfig::new(framing, decoding.clone(), log_namespace).build()?,
204 ))
205 }
206 None => Ok(None),
207 }
208 }
209}
210
211impl_generate_config_from_default!(SplunkConfig);
212
213impl Default for SplunkConfig {
214 fn default() -> Self {
215 SplunkConfig {
216 address: default_socket_address(),
217 token: None,
218 valid_tokens: None,
219 tls: None,
220 acknowledgements: Default::default(),
221 store_hec_token: false,
222 log_namespace: None,
223 keepalive: Default::default(),
224 event: CodecConfig::default(),
225 raw: CodecConfig::default(),
226 }
227 }
228}
229
230fn default_socket_address() -> SocketAddr {
231 SocketAddr::new(Ipv4Addr::UNSPECIFIED.into(), 8088)
232}
233
234#[async_trait::async_trait]
235#[typetag::serde(name = "splunk_hec")]
236impl SourceConfig for SplunkConfig {
237 async fn build(&self, cx: SourceContext) -> crate::Result<super::Source> {
238 let tls = MaybeTlsSettings::from_config(self.tls.as_ref(), true)?;
239 let shutdown = cx.shutdown.clone();
240 let out = cx.out.clone();
241 let log_namespace = cx.log_namespace(self.log_namespace);
242 let event_decoder = self.event.build_decoder(log_namespace)?;
243 let raw_decoder = self.raw.build_decoder(log_namespace)?;
244 let source = SplunkSource::new(
245 self,
246 tls.http_protocol_name(),
247 event_decoder,
248 raw_decoder,
249 cx,
250 );
251
252 let event_service = source.event_service(out.clone());
253 let raw_service = source.raw_service(out);
254 let health_service = source.health_service();
255 let ack_service = source.ack_service();
256 let options = SplunkSource::options();
257
258 let services = path!("services" / "collector" / ..)
259 .and(
260 event_service
261 .or(raw_service)
262 .unify()
263 .or(health_service)
264 .unify()
265 .or(ack_service)
266 .unify()
267 .or(options)
268 .unify(),
269 )
270 .or_else(finish_err);
271
272 let listener = tls.bind(&self.address).await?;
273
274 let keepalive_settings = self.keepalive.clone();
275 Ok(Box::pin(async move {
276 let span = Span::current();
277 let make_svc = make_service_fn(move |conn: &MaybeTlsIncomingStream<TcpStream>| {
278 let svc = ServiceBuilder::new()
279 .layer(build_http_trace_layer(span.clone()))
280 .option_layer(keepalive_settings.max_connection_age_secs.map(|secs| {
281 MaxConnectionAgeLayer::new(
282 Duration::from_secs(secs),
283 keepalive_settings.max_connection_age_jitter_factor,
284 conn.peer_addr(),
285 )
286 }))
287 .service(warp::service(services.clone()));
288 futures_util::future::ok::<_, Infallible>(svc)
289 });
290
291 Server::builder(hyper::server::accept::from_stream(listener.accept_stream()))
292 .serve(make_svc)
293 .with_graceful_shutdown(shutdown.map(|_| ()))
294 .await
295 .map_err(|err| {
296 error!("An error occurred: {:?}.", err);
297 })?;
298
299 Ok(())
300 }))
301 }
302
303 fn outputs(&self, global_log_namespace: LogNamespace) -> Vec<SourceOutput> {
304 let log_namespace = global_log_namespace.merge(self.log_namespace);
305
306 let legacy_base = || match log_namespace {
311 LogNamespace::Legacy => {
312 let definition = vector_lib::schema::Definition::empty_legacy_namespace()
313 .with_event_field(
314 &owned_value_path!("line"),
315 Kind::object(Collection::empty())
316 .or_array(Collection::empty())
317 .or_undefined(),
318 None,
319 );
320
321 if let Some(message_key) = log_schema().message_key() {
322 definition.with_event_field(
323 message_key,
324 Kind::bytes().or_undefined(),
325 Some(meaning::MESSAGE),
326 )
327 } else {
328 definition
329 }
330 }
331 LogNamespace::Vector => vector_lib::schema::Definition::new_with_default_metadata(
332 Kind::bytes().or_object(Collection::empty()),
333 [log_namespace],
334 )
335 .with_meaning(OwnedTargetPath::event_root(), meaning::MESSAGE),
336 };
337
338 let endpoint_base = |decoding: &Option<DeserializerConfig>| match decoding {
339 Some(decoding) => decoding.schema_definition(log_namespace),
340 None => legacy_base(),
341 };
342
343 let splunk_legacy_key = |path: OwnedValuePath, has_decoder: bool| {
344 if has_decoder {
345 LegacyKey::InsertIfEmpty(path)
346 } else {
347 LegacyKey::Overwrite(path)
348 }
349 };
350
351 let add_common_metadata = |definition: vector_lib::schema::Definition| {
352 definition
353 .with_standard_vector_source_metadata()
354 .with_source_metadata(
355 SplunkConfig::NAME,
356 log_schema()
357 .host_key()
358 .cloned()
359 .map(LegacyKey::InsertIfEmpty),
360 &owned_value_path!("host"),
361 Kind::bytes(),
362 Some(meaning::HOST),
363 )
364 };
365
366 let add_channel_metadata = |definition: vector_lib::schema::Definition,
367 has_decoder: bool| {
368 definition.with_source_metadata(
369 SplunkConfig::NAME,
370 Some(splunk_legacy_key(owned_value_path!(CHANNEL), has_decoder)),
371 &owned_value_path!("channel"),
372 Kind::bytes(),
373 None,
374 )
375 };
376
377 let event_has_decoder = self.event.decoding.is_some();
378 let raw_has_decoder = self.raw.decoding.is_some();
379
380 let merged_base = add_common_metadata(
386 endpoint_base(&self.event.decoding).merge(endpoint_base(&self.raw.decoding)),
387 );
388
389 let channel_has_decoder = event_has_decoder && raw_has_decoder;
393 let schema_definition = add_channel_metadata(
394 merged_base
395 .with_source_metadata(
396 SplunkConfig::NAME,
397 Some(splunk_legacy_key(
398 owned_value_path!(INDEX),
399 event_has_decoder,
400 )),
401 &owned_value_path!("index"),
402 Kind::bytes(),
403 None,
404 )
405 .with_source_metadata(
406 SplunkConfig::NAME,
407 Some(splunk_legacy_key(
408 owned_value_path!(SOURCE),
409 event_has_decoder,
410 )),
411 &owned_value_path!("source"),
412 Kind::bytes(),
413 Some(meaning::SERVICE),
414 )
415 .with_source_metadata(
417 SplunkConfig::NAME,
418 Some(splunk_legacy_key(
419 owned_value_path!(SOURCETYPE),
420 event_has_decoder,
421 )),
422 &owned_value_path!("sourcetype"),
423 Kind::bytes(),
424 None,
425 ),
426 channel_has_decoder,
427 );
428
429 let output_type = match (&self.event.decoding, &self.raw.decoding) {
433 (None, None) => DataType::Log,
434 (Some(d), None) | (None, Some(d)) => d.output_type() | DataType::Log,
435 (Some(de), Some(dr)) => de.output_type() | dr.output_type(),
436 };
437 vec![SourceOutput::new_maybe_logs(output_type, schema_definition)]
438 }
439
440 fn resources(&self) -> Vec<Resource> {
441 vec![Resource::tcp(self.address)]
442 }
443
444 fn can_acknowledge(&self) -> bool {
445 true
446 }
447}
448
449struct SplunkSource {
451 valid_credentials: Vec<String>,
452 protocol: &'static str,
453 idx_ack: Option<Arc<IndexerAcknowledgement>>,
454 store_hec_token: bool,
455 log_namespace: LogNamespace,
456 events_received: Registered<EventsReceived>,
457 event_decoder: Option<Decoder>,
458 raw_decoder: Option<Decoder>,
459}
460
461impl SplunkSource {
462 fn new(
463 config: &SplunkConfig,
464 protocol: &'static str,
465 event_decoder: Option<Decoder>,
466 raw_decoder: Option<Decoder>,
467 cx: SourceContext,
468 ) -> Self {
469 let log_namespace = cx.log_namespace(config.log_namespace);
470 let acknowledgements = cx.do_acknowledgements(config.acknowledgements.enabled.into());
471 let shutdown = cx.shutdown;
472 let valid_tokens = config
473 .valid_tokens
474 .iter()
475 .flatten()
476 .chain(config.token.iter());
477
478 let idx_ack = acknowledgements.then(|| {
479 Arc::new(IndexerAcknowledgement::new(
480 config.acknowledgements.clone(),
481 shutdown,
482 ))
483 });
484
485 SplunkSource {
486 valid_credentials: valid_tokens
487 .map(|token| format!("Splunk {}", token.inner()))
488 .collect(),
489 protocol,
490 idx_ack,
491 store_hec_token: config.store_hec_token,
492 log_namespace,
493 events_received: register!(EventsReceived),
494 event_decoder,
495 raw_decoder,
496 }
497 }
498
499 fn event_service(&self, out: SourceSender) -> BoxedFilter<(Response,)> {
500 let splunk_channel_query_param = warp::query::<HashMap<String, String>>()
501 .map(|qs: HashMap<String, String>| qs.get("channel").map(|v| v.to_owned()));
502 let splunk_channel_header = warp::header::optional::<String>(X_SPLUNK_REQUEST_CHANNEL);
503
504 let splunk_channel = splunk_channel_header
505 .and(splunk_channel_query_param)
506 .map(|header: Option<String>, query_param| header.or(query_param));
507
508 let protocol = self.protocol;
509 let idx_ack = self.idx_ack.clone();
510 let store_hec_token = self.store_hec_token;
511 let log_namespace = self.log_namespace;
512 let events_received = self.events_received.clone();
513 let decoder = self.event_decoder.clone();
514
515 warp::post()
516 .and(
517 path!("event")
518 .or(path!("event" / "1.0"))
519 .or(warp::path::end()),
520 )
521 .and(self.authorization())
522 .and(splunk_channel)
523 .and(warp::addr::remote())
524 .and(warp::header::optional::<String>("X-Forwarded-For"))
525 .and(self.gzip())
526 .and(capped_body())
527 .and(warp::path::full())
528 .and_then(
529 move |_,
530 token: Option<String>,
531 channel: Option<String>,
532 remote: Option<SocketAddr>,
533 remote_addr: Option<String>,
534 gzip: bool,
535 body: Bytes,
536 path: warp::path::FullPath| {
537 let mut out = out.clone();
538 let idx_ack = idx_ack.clone();
539 let events_received = events_received.clone();
540 let decoder = decoder.clone();
541
542 async move {
543 if idx_ack.is_some() && channel.is_none() {
544 return Err(Rejection::from(ApiError::MissingChannel));
545 }
546
547 let data;
548 let (byte_size, body) = if gzip {
549 data = CappedDecoder::gzip(body.reader())
551 .decompress()
552 .map_err(|_| Rejection::from(ApiError::BadRequest))?;
553 (data.len(), String::from_utf8_lossy(data.as_slice()))
554 } else {
555 (body.len(), String::from_utf8_lossy(body.as_ref()))
556 };
557 emit!(HttpBytesReceived {
558 byte_size,
559 http_path: path.as_str(),
560 protocol,
561 });
562
563 let (batch, mut receiver) =
564 BatchNotifier::maybe_new_with_receiver(idx_ack.is_some());
565 let decoder_in_use = decoder.is_some();
566
567 let mut maybe_ack_id = None;
572 if !decoder_in_use {
573 maybe_ack_id =
574 register_ack(idx_ack.clone(), receiver.take(), channel.clone())
575 .await?;
576 }
577
578 let mut error = None;
579 let mut events = Vec::new();
580 let mut had_decode_errors = false;
581
582 let iter: EventIterator<'_, StrRead<'_>> = EventIteratorGenerator {
583 deserializer: Deserializer::from_str(&body).into_iter::<JsonValue>(),
584 channel: channel.clone(),
585 remote,
586 remote_addr,
587 batch,
588 token: token.filter(|_| store_hec_token).map(Into::into),
589 log_namespace,
590 events_received,
591 decoder,
592 }
593 .into();
594
595 for result in iter {
596 match result {
597 Ok((chunk, errored)) => {
598 events.extend(chunk);
599 had_decode_errors |= errored;
600 }
601 Err(err) => {
602 error = Some(err);
603 break;
604 }
605 }
606 }
607
608 if decoder_in_use {
615 maybe_ack_id =
616 if events.is_empty() || had_decode_errors || error.is_some() {
617 drop(receiver);
618 None
619 } else {
620 register_ack(idx_ack, receiver, channel).await?
621 };
622 }
623
624 if !events.is_empty() {
625 match out.send_batch(events).await {
626 Ok(()) => (),
627 Err(SendError::Closed) => {
628 return Err(Rejection::from(ApiError::ServerShutdown));
629 }
630 Err(SendError::Timeout) => {
631 unreachable!("No timeout is configured for this source.")
632 }
633 }
634 }
635
636 if let Some(error) = error {
637 Err(error)
638 } else {
639 Ok(maybe_ack_id)
640 }
641 }
642 },
643 )
644 .map(finish_ok)
645 .boxed()
646 }
647
648 fn raw_service(&self, out: SourceSender) -> BoxedFilter<(Response,)> {
649 let protocol = self.protocol;
650 let idx_ack = self.idx_ack.clone();
651 let store_hec_token = self.store_hec_token;
652 let events_received = self.events_received.clone();
653 let log_namespace = self.log_namespace;
654 let decoder = self.raw_decoder.clone();
655
656 warp::post()
657 .and(path!("raw" / "1.0").or(path!("raw")))
658 .and(self.authorization())
659 .and(SplunkSource::required_channel())
660 .and(warp::addr::remote())
661 .and(warp::header::optional::<String>("X-Forwarded-For"))
662 .and(self.gzip())
663 .and(capped_body())
664 .and(warp::path::full())
665 .and_then(
666 move |_,
667 token: Option<String>,
668 channel_id: String,
669 remote: Option<SocketAddr>,
670 xff: Option<String>,
671 gzip: bool,
672 body: Bytes,
673 path: warp::path::FullPath| {
674 let mut out = out.clone();
675 let idx_ack = idx_ack.clone();
676 let events_received = events_received.clone();
677 let decoder = decoder.clone();
678 emit!(HttpBytesReceived {
679 byte_size: body.len(),
680 http_path: path.as_str(),
681 protocol,
682 });
683
684 async move {
685 let (batch, receiver) =
686 BatchNotifier::maybe_new_with_receiver(idx_ack.is_some());
687
688 let Some(decoder) = decoder else {
693 let maybe_ack_id =
694 register_ack(idx_ack, receiver, Some(channel_id.clone())).await?;
695 let (mut events, _) = raw_event(
696 body,
697 gzip,
698 channel_id,
699 remote,
700 xff,
701 batch,
702 log_namespace,
703 &events_received,
704 None,
705 None,
706 )?;
707 let mut event = events.pop().expect(
710 "raw_event always produces a single event when no decoder is set",
711 );
712 if let Some(token) = token.filter(|_| store_hec_token) {
713 event.metadata_mut().set_splunk_hec_token(token.into());
714 }
715 let res = out.send_event(event).await;
716 return res
717 .map(|_| maybe_ack_id)
718 .map_err(|_| Rejection::from(ApiError::ServerShutdown));
719 };
720
721 let token: Option<Arc<str>> =
725 token.filter(|_| store_hec_token).map(Arc::from);
726 let (events, had_decode_errors) = raw_event(
727 body,
728 gzip,
729 channel_id.clone(),
730 remote,
731 xff,
732 batch,
733 log_namespace,
734 &events_received,
735 Some(decoder),
736 token,
737 )?;
738
739 if events.is_empty() || had_decode_errors {
740 drop(receiver);
745 if events.is_empty() {
746 return Ok(None);
747 }
748 let res = out.send_batch(events).await;
751 return res
752 .map(|_| None)
753 .map_err(|_| Rejection::from(ApiError::ServerShutdown));
754 }
755
756 let maybe_ack_id =
757 register_ack(idx_ack, receiver, Some(channel_id)).await?;
758
759 let res = out.send_batch(events).await;
760 res.map(|_| maybe_ack_id)
761 .map_err(|_| Rejection::from(ApiError::ServerShutdown))
762 }
763 },
764 )
765 .map(finish_ok)
766 .boxed()
767 }
768
769 fn health_service(&self) -> BoxedFilter<(Response,)> {
770 warp::get()
777 .and(path!("health" / "1.0").or(path!("health")))
778 .map(move |_| {
779 http::Response::builder()
780 .header(http::header::CONTENT_TYPE, "application/json")
781 .body(hyper::Body::from(r#"{"text":"HEC is healthy","code":17}"#))
782 .expect("static response")
783 })
784 .boxed()
785 }
786
787 fn lenient_json_content_type_check<T>() -> impl Filter<Extract = (T,), Error = Rejection> + Clone
788 where
789 T: Send + DeserializeOwned + 'static,
790 {
791 warp::header::optional::<HeaderValue>(CONTENT_TYPE.as_str())
792 .and(capped_body())
793 .and_then(
794 |ctype: Option<HeaderValue>, body: bytes::Bytes| async move {
795 let ok = ctype
796 .as_ref()
797 .and_then(|v| v.to_str().ok())
798 .map(|h| h.to_ascii_lowercase().contains("application/json"))
799 .unwrap_or(true);
800
801 if !ok {
802 return Err(warp::reject::custom(ApiError::UnsupportedContentType));
803 }
804
805 let value = serde_json::from_slice::<T>(&body)
806 .map_err(|_| warp::reject::custom(ApiError::BadRequest))?;
807
808 Ok(value)
809 },
810 )
811 }
812
813 fn ack_service(&self) -> BoxedFilter<(Response,)> {
814 let idx_ack = self.idx_ack.clone();
815
816 warp::post()
817 .and(warp::path!("ack"))
818 .and(self.authorization())
819 .and(SplunkSource::required_channel())
820 .and(Self::lenient_json_content_type_check::<HecAckStatusRequest>())
821 .and_then(move |_, channel: String, req: HecAckStatusRequest| {
822 let idx_ack = idx_ack.clone();
823 async move {
824 if let Some(idx_ack) = idx_ack {
825 let acks = idx_ack
826 .get_acks_status_from_channel(channel, &req.acks)
827 .await?;
828 Ok(warp::reply::json(&HecAckStatusResponse { acks }).into_response())
829 } else {
830 Err(warp::reject::custom(ApiError::AckIsDisabled))
831 }
832 }
833 })
834 .boxed()
835 }
836
837 fn options() -> BoxedFilter<(Response,)> {
838 let post = warp::options()
839 .and(
840 path!("event")
841 .or(path!("event" / "1.0"))
842 .or(path!("raw" / "1.0"))
843 .or(path!("raw")),
844 )
845 .map(|_| warp::reply::with_header(warp::reply(), "Allow", "POST").into_response());
846
847 let get = warp::options()
848 .and(path!("health").or(path!("health" / "1.0")))
849 .map(|_| warp::reply::with_header(warp::reply(), "Allow", "GET").into_response());
850
851 post.or(get).unify().boxed()
852 }
853
854 fn authorization(&self) -> BoxedFilter<(Option<String>,)> {
856 let valid_credentials = self.valid_credentials.clone();
857 warp::header::optional("Authorization")
858 .and_then(move |token: Option<String>| {
859 let valid_credentials = valid_credentials.clone();
860 async move {
861 match (token, valid_credentials.is_empty()) {
862 (token, true) => {
865 Ok(token
866 .map(|t| t.strip_prefix("Splunk ").map(Into::into).unwrap_or(t)))
867 }
868 (Some(token), false) if valid_credentials.contains(&token) => Ok(Some(
869 token
870 .strip_prefix("Splunk ")
871 .map(Into::into)
872 .unwrap_or(token),
873 )),
874 (Some(_), false) => Err(Rejection::from(ApiError::InvalidAuthorization)),
875 (None, false) => Err(Rejection::from(ApiError::MissingAuthorization)),
876 }
877 }
878 })
879 .boxed()
880 }
881
882 fn gzip(&self) -> BoxedFilter<(bool,)> {
884 warp::header::optional::<String>("Content-Encoding")
885 .and_then(|encoding: Option<String>| async move {
886 match encoding {
887 Some(s) if s.as_bytes() == b"gzip" => Ok(true),
888 Some(_) => Err(Rejection::from(ApiError::UnsupportedEncoding)),
889 None => Ok(false),
890 }
891 })
892 .boxed()
893 }
894
895 fn required_channel() -> BoxedFilter<(String,)> {
896 let splunk_channel_query_param = warp::query::<HashMap<String, String>>()
897 .map(|qs: HashMap<String, String>| qs.get("channel").map(|v| v.to_owned()));
898 let splunk_channel_header = warp::header::optional::<String>(X_SPLUNK_REQUEST_CHANNEL);
899
900 splunk_channel_header
901 .and(splunk_channel_query_param)
902 .and_then(|header: Option<String>, query_param| async move {
903 header
904 .or(query_param)
905 .ok_or_else(|| Rejection::from(ApiError::MissingChannel))
906 })
907 .boxed()
908 }
909}
910struct EventIterator<'de, R: JsonRead<'de>> {
913 deserializer: serde_json::StreamDeserializer<'de, R, JsonValue>,
915 envelopes_processed: usize,
920 channel: Option<Value>,
922 time: Time,
924 extractors: [DefaultExtractor; 4],
926 batch: Option<BatchNotifier>,
928 token: Option<Arc<str>>,
930 log_namespace: LogNamespace,
932 events_received: Registered<EventsReceived>,
934 decoder: Option<Decoder>,
937}
938
939struct EventIteratorGenerator<'de, R: JsonRead<'de>> {
941 deserializer: serde_json::StreamDeserializer<'de, R, JsonValue>,
942 channel: Option<String>,
943 batch: Option<BatchNotifier>,
944 token: Option<Arc<str>>,
945 log_namespace: LogNamespace,
946 events_received: Registered<EventsReceived>,
947 remote: Option<SocketAddr>,
948 remote_addr: Option<String>,
949 decoder: Option<Decoder>,
950}
951
952impl<'de, R: JsonRead<'de>> From<EventIteratorGenerator<'de, R>> for EventIterator<'de, R> {
953 fn from(f: EventIteratorGenerator<'de, R>) -> Self {
954 let extractor_strategy = if f.decoder.is_some() {
960 LegacyKeyStrategy::InsertIfEmpty
961 } else {
962 LegacyKeyStrategy::Overwrite
963 };
964 Self {
965 deserializer: f.deserializer,
966 envelopes_processed: 0,
967 channel: f.channel.map(Value::from),
968 time: Time::Now(Utc::now()),
969 extractors: [
970 DefaultExtractor::new_with(
975 "host",
976 log_schema().host_key().cloned().into(),
977 f.remote_addr
978 .or_else(|| f.remote.map(|addr| addr.to_string()))
979 .map(Value::from),
980 f.log_namespace,
981 )
982 .with_legacy_key_strategy(extractor_strategy),
983 DefaultExtractor::new("index", OptionalValuePath::new(INDEX), f.log_namespace)
984 .with_legacy_key_strategy(extractor_strategy),
985 DefaultExtractor::new("source", OptionalValuePath::new(SOURCE), f.log_namespace)
986 .with_legacy_key_strategy(extractor_strategy),
987 DefaultExtractor::new(
988 "sourcetype",
989 OptionalValuePath::new(SOURCETYPE),
990 f.log_namespace,
991 )
992 .with_legacy_key_strategy(extractor_strategy),
993 ],
994 batch: f.batch,
995 token: f.token,
996 log_namespace: f.log_namespace,
997 events_received: f.events_received,
998 decoder: f.decoder,
999 }
1000 }
1001}
1002
1003impl<'de, R: JsonRead<'de>> EventIterator<'de, R> {
1004 fn process_time(&mut self, json: &mut JsonValue) -> Result<(), Rejection> {
1007 let parsed_time = match json.get_mut("time").map(JsonValue::take) {
1008 Some(JsonValue::Number(time)) => Some(Some(time)),
1009 Some(JsonValue::String(time)) => Some(time.parse::<serde_json::Number>().ok()),
1010 _ => None,
1011 };
1012
1013 match parsed_time {
1014 None => Ok(()),
1015 Some(Some(t)) => {
1016 if let Some(t) = t.as_u64() {
1017 let time = parse_timestamp(t as i64).ok_or(ApiError::InvalidDataFormat {
1018 event: self.envelopes_processed.saturating_sub(1),
1019 })?;
1020 self.time = Time::Provided(time);
1021 Ok(())
1022 } else if let Some(t) = t.as_f64() {
1023 self.time = Time::Provided(
1024 Utc.timestamp_opt(
1025 t.floor() as i64,
1026 (t.fract() * 1000.0 * 1000.0 * 1000.0) as u32,
1027 )
1028 .single()
1029 .expect("invalid timestamp"),
1030 );
1031 Ok(())
1032 } else {
1033 Err(ApiError::InvalidDataFormat {
1034 event: self.envelopes_processed.saturating_sub(1),
1035 }
1036 .into())
1037 }
1038 }
1039 Some(None) => Err(ApiError::InvalidDataFormat {
1040 event: self.envelopes_processed.saturating_sub(1),
1041 }
1042 .into()),
1043 }
1044 }
1045
1046 fn build_event(&mut self, mut json: JsonValue) -> Result<Event, Rejection> {
1047 self.envelopes_processed += 1;
1048 let mut log = match self.log_namespace {
1050 LogNamespace::Vector => self.build_log_vector(&mut json)?,
1051 LogNamespace::Legacy => self.build_log_legacy(&mut json)?,
1052 };
1053
1054 self.log_namespace.insert_vector_metadata(
1056 &mut log,
1057 log_schema().source_type_key(),
1058 &owned_value_path!("source_type"),
1059 SplunkConfig::NAME,
1060 );
1061
1062 let channel_path = owned_value_path!(CHANNEL);
1064 if let Some(JsonValue::String(guid)) = json.get_mut("channel").map(JsonValue::take) {
1065 self.log_namespace.insert_source_metadata(
1066 SplunkConfig::NAME,
1067 &mut log,
1068 Some(LegacyKey::Overwrite(&channel_path)),
1069 lookup::path!(CHANNEL),
1070 guid,
1071 );
1072 } else if let Some(guid) = self.channel.as_ref() {
1073 self.log_namespace.insert_source_metadata(
1074 SplunkConfig::NAME,
1075 &mut log,
1076 Some(LegacyKey::Overwrite(&channel_path)),
1077 lookup::path!(CHANNEL),
1078 guid.clone(),
1079 );
1080 }
1081
1082 if let Some(JsonValue::Object(object)) = json.get_mut("fields").map(JsonValue::take) {
1084 for (key, value) in object {
1085 self.log_namespace.insert_source_metadata(
1086 SplunkConfig::NAME,
1087 &mut log,
1088 Some(LegacyKey::Overwrite(&owned_value_path!(key.as_str()))),
1089 lookup::path!(key.as_str()),
1090 value,
1091 );
1092 }
1093 }
1094
1095 self.process_time(&mut json)?;
1096
1097 let timestamp = match self.time.clone() {
1099 Time::Provided(time) => time,
1100 Time::Now(time) => time,
1101 };
1102
1103 self.log_namespace.insert_source_metadata(
1104 SplunkConfig::NAME,
1105 &mut log,
1106 log_schema().timestamp_key().map(LegacyKey::Overwrite),
1107 lookup::path!("timestamp"),
1108 timestamp,
1109 );
1110
1111 for de in self.extractors.iter_mut() {
1113 de.extract(&mut log, &mut json);
1114 }
1115
1116 if let Some(token) = &self.token {
1118 log.metadata_mut().set_splunk_hec_token(Arc::clone(token));
1119 }
1120
1121 if let Some(batch) = self.batch.clone() {
1122 log = log.with_batch_notifier(&batch);
1123 }
1124
1125 Ok(log.into())
1126 }
1127
1128 fn build_vrl_metadata(&self, json: &JsonValue) -> EventMetadata {
1136 let mut metadata = EventMetadata::default();
1137
1138 if let Some(token) = &self.token {
1140 metadata.set_splunk_hec_token(Arc::clone(token));
1141 }
1142
1143 let fields: &[(&str, &str)] = &[
1146 ("host", "splunk_hec.host"),
1147 ("source", "splunk_hec.source"),
1148 ("sourcetype", "splunk_hec.sourcetype"),
1149 ("index", "splunk_hec.index"),
1150 ];
1151 for (json_key, meta_path) in fields {
1152 let val = json
1153 .get(json_key)
1154 .and_then(|v| v.as_str())
1155 .map(|s| Value::from(s.to_string()))
1156 .or_else(|| {
1157 self.extractors
1158 .iter()
1159 .find(|e| e.field == *json_key)
1160 .and_then(|e| e.value.clone())
1161 });
1162 if let Some(v) = val {
1163 metadata.value_mut().insert(
1164 &vrl::path::parse_value_path(meta_path)
1165 .expect("hardcoded splunk_hec metadata path is a valid VRL path"),
1166 v,
1167 );
1168 }
1169 }
1170
1171 let channel = json
1173 .get("channel")
1174 .and_then(|v| v.as_str())
1175 .map(|s| Value::from(s.to_string()))
1176 .or_else(|| self.channel.clone());
1177 if let Some(ch) = channel {
1178 metadata.value_mut().insert(
1179 &vrl::path::parse_value_path("splunk_hec.channel")
1180 .expect("splunk_hec.channel is a valid VRL path"),
1181 ch,
1182 );
1183 }
1184
1185 metadata
1186 }
1187
1188 fn build_events_decoded(
1194 &mut self,
1195 mut json: JsonValue,
1196 decoder: Decoder,
1197 ) -> Result<(Vec<Event>, bool), Rejection> {
1198 self.envelopes_processed += 1;
1199 let event = self.validate_event_field(&json)?;
1200 let payload = if let Some(s) = event.as_str() {
1205 s.as_bytes().to_vec()
1206 } else {
1207 match serde_json::to_vec(event) {
1208 Ok(bytes) => bytes,
1209 Err(error) => {
1210 let error: vector_lib::Error = Box::new(error);
1211 emit!(
1212 vector_lib::codecs::internal_events::DecoderDeserializeError {
1213 error: &error
1214 }
1215 );
1216 emit!(ComponentEventsDropped::<UNINTENTIONAL> {
1217 count: 1,
1218 reason: "Failed to serialize event field to bytes.",
1219 });
1220 return Ok((vec![], true));
1221 }
1222 }
1223 };
1224
1225 self.process_time(&mut json)?;
1226
1227 let fallback_time = match self.time {
1232 Time::Provided(t) | Time::Now(t) => t,
1233 };
1234
1235 let decoder = decoder.with_metadata_template(self.build_vrl_metadata(&json));
1239
1240 let (decoded, had_decode_errors) = decode_payload(
1241 decoder,
1242 &payload,
1243 Some(fallback_time),
1244 true, DecodePayloadContext {
1246 batch: &self.batch,
1247 log_namespace: self.log_namespace,
1248 events_received: &self.events_received,
1249 splunk_hec_token: self.token.as_ref(),
1250 },
1251 );
1252
1253 let envelope_channel: Option<Value> = match json.get_mut("channel").map(JsonValue::take) {
1255 Some(JsonValue::String(guid)) => Some(guid.into()),
1256 _ => None,
1257 };
1258 let envelope_fields: Option<serde_json::Map<String, JsonValue>> =
1259 match json.get_mut("fields").map(JsonValue::take) {
1260 Some(JsonValue::Object(object)) => Some(object),
1261 _ => None,
1262 };
1263 let channel_path = owned_value_path!(CHANNEL);
1264
1265 let mut out = Vec::with_capacity(decoded.len());
1266 for mut event in decoded {
1267 if let Event::Log(log) = &mut event {
1268 if let Some(channel_val) = envelope_channel.clone().or_else(|| self.channel.clone())
1272 {
1273 match self.log_namespace {
1274 LogNamespace::Legacy => {
1275 self.log_namespace.insert_source_metadata(
1276 SplunkConfig::NAME,
1277 log,
1278 Some(LegacyKey::InsertIfEmpty(&channel_path)),
1279 lookup::path!(CHANNEL),
1280 channel_val,
1281 );
1282 }
1283 LogNamespace::Vector => {
1284 log.try_insert(
1285 metadata_path!(SplunkConfig::NAME, CHANNEL),
1286 channel_val,
1287 );
1288 }
1289 }
1290 }
1291
1292 for de in self.extractors.iter_mut() {
1298 de.extract(log, &mut json);
1299 }
1300
1301 if let Some(ref fields) = envelope_fields {
1305 for (key, value) in fields {
1306 match self.log_namespace {
1307 LogNamespace::Legacy => {
1308 self.log_namespace.insert_source_metadata(
1309 SplunkConfig::NAME,
1310 log,
1311 Some(LegacyKey::InsertIfEmpty(&owned_value_path!(
1312 key.as_str()
1313 ))),
1314 lookup::path!(key.as_str()),
1315 value.clone(),
1316 );
1317 }
1318 LogNamespace::Vector => {
1319 log.try_insert(
1320 metadata_path!(SplunkConfig::NAME, key.as_str()),
1321 value.clone(),
1322 );
1323 }
1324 }
1325 }
1326 }
1327 }
1328 out.push(event);
1332 }
1333
1334 Ok((out, had_decode_errors))
1335 }
1336
1337 fn validate_event_field<'a>(&self, json: &'a JsonValue) -> Result<&'a JsonValue, Rejection> {
1342 let event_idx = self.envelopes_processed.saturating_sub(1);
1343 match json.get("event") {
1344 None | Some(JsonValue::Null) => {
1345 Err(ApiError::MissingEventField { event: event_idx }.into())
1346 }
1347 Some(JsonValue::String(s)) if s.is_empty() => {
1348 Err(ApiError::EmptyEventField { event: event_idx }.into())
1349 }
1350 Some(event) => Ok(event),
1351 }
1352 }
1353
1354 fn build_log_vector(&mut self, json: &mut JsonValue) -> Result<LogEvent, Rejection> {
1358 let event: Value = self.validate_event_field(json)?.into();
1359 let mut log = LogEvent::from(event);
1360
1361 self.events_received
1363 .emit(CountByteSize(1, log.estimated_json_encoded_size_of()));
1364
1365 self.log_namespace.insert_vector_metadata(
1367 &mut log,
1368 log_schema().timestamp_key(),
1369 lookup::path!("ingest_timestamp"),
1370 chrono::Utc::now(),
1371 );
1372
1373 Ok(log)
1374 }
1375
1376 fn build_log_legacy(&mut self, json: &mut JsonValue) -> Result<LogEvent, Rejection> {
1381 self.validate_event_field(json)?;
1383 let mut log = LogEvent::default();
1384 match json["event"].take() {
1385 JsonValue::String(string) => {
1386 log.maybe_insert(log_schema().message_key_target_path(), string);
1387 }
1388 JsonValue::Object(mut object) => {
1389 if object.is_empty() {
1390 return Err(ApiError::EmptyEventField {
1391 event: self.envelopes_processed.saturating_sub(1),
1392 }
1393 .into());
1394 }
1395
1396 if let Some(line) = object.remove("line") {
1398 match line {
1399 JsonValue::Array(_) | JsonValue::Object(_) => {
1401 log.insert(event_path!("line"), line);
1402 }
1403 _ => {
1404 log.maybe_insert(log_schema().message_key_target_path(), line);
1405 }
1406 }
1407 }
1408
1409 for (key, value) in object {
1410 log.insert(event_path!(key.as_str()), value);
1411 }
1412 }
1413 _ => {
1414 return Err(ApiError::InvalidDataFormat {
1415 event: self.envelopes_processed.saturating_sub(1),
1416 }
1417 .into());
1418 }
1419 }
1420
1421 self.events_received
1423 .emit(CountByteSize(1, log.estimated_json_encoded_size_of()));
1424
1425 Ok(log)
1426 }
1427}
1428
1429impl<'de, R: JsonRead<'de>> Iterator for EventIterator<'de, R> {
1430 type Item = Result<(Vec<Event>, bool), Rejection>;
1434
1435 fn next(&mut self) -> Option<Self::Item> {
1436 match self.deserializer.next() {
1437 Some(Ok(json)) => {
1438 let result = if let Some(decoder) = self.decoder.clone() {
1439 self.build_events_decoded(json, decoder)
1440 } else {
1441 self.build_event(json).map(|event| (vec![event], false))
1442 };
1443 Some(result)
1444 }
1445 None => {
1446 if self.envelopes_processed == 0 {
1447 Some(Err(ApiError::NoData.into()))
1448 } else {
1449 None
1450 }
1451 }
1452 Some(Err(error)) => {
1453 emit!(SplunkHecRequestBodyInvalidError {
1454 error: error.into()
1455 });
1456 Some(Err(ApiError::InvalidDataFormat {
1460 event: self.envelopes_processed,
1461 }
1462 .into()))
1463 }
1464 }
1465 }
1466}
1467
1468struct DecodePayloadContext<'a> {
1469 batch: &'a Option<BatchNotifier>,
1470 log_namespace: LogNamespace,
1471 events_received: &'a Registered<EventsReceived>,
1472 splunk_hec_token: Option<&'a Arc<str>>,
1473}
1474
1475fn decode_payload(
1489 mut decoder: Decoder,
1490 payload: &[u8],
1491 fallback_timestamp: Option<DateTime<Utc>>,
1492 set_source_timestamp: bool,
1493 ctx: DecodePayloadContext<'_>,
1494) -> (Vec<Event>, bool) {
1495 let DecodePayloadContext {
1496 batch,
1497 log_namespace,
1498 events_received,
1499 splunk_hec_token,
1500 } = ctx;
1501 let mut buffer = BytesMut::with_capacity(payload.len());
1502 buffer.extend_from_slice(payload);
1503 let now = Utc::now();
1504 let mut events: Vec<Event> = Vec::new();
1505 let mut had_errors = false;
1506
1507 loop {
1508 match decoder.decode_eof(&mut buffer) {
1509 Ok(Some((decoded, _))) => {
1510 for mut event in decoded {
1511 if let Event::Log(log) = &mut event {
1512 log_namespace.insert_vector_metadata(
1513 log,
1514 log_schema().source_type_key(),
1515 lookup::path!("source_type"),
1516 Bytes::from_static(SplunkConfig::NAME.as_bytes()),
1517 );
1518 match log_namespace {
1519 LogNamespace::Vector => {
1520 if set_source_timestamp && let Some(timestamp) = fallback_timestamp
1525 {
1526 log.try_insert(
1527 metadata_path!(SplunkConfig::NAME, "timestamp"),
1528 timestamp,
1529 );
1530 }
1531 log.insert(metadata_path!("vector", "ingest_timestamp"), now);
1532 }
1533 LogNamespace::Legacy => {
1534 if let Some(timestamp) = fallback_timestamp
1535 && let Some(timestamp_key) = log_schema().timestamp_key()
1536 {
1537 log.try_insert((PathPrefix::Event, timestamp_key), timestamp);
1538 }
1539 }
1540 }
1541 }
1542 if let Some(token) = splunk_hec_token {
1543 event.metadata_mut().set_splunk_hec_token(Arc::clone(token));
1544 }
1545 events_received.emit(CountByteSize(1, event.estimated_json_encoded_size_of()));
1546 events.push(event.with_batch_notifier_option(batch));
1547 }
1548 }
1549 Ok(None) => break,
1550 Err(error) => {
1551 had_errors = true;
1554 if !error.can_continue() {
1555 break;
1556 }
1557 }
1558 }
1559 }
1560
1561 (events, had_errors)
1562}
1563
1564fn parse_timestamp(t: i64) -> Option<DateTime<Utc>> {
1575 const SEC_CUTOFF: i64 = 13569465600;
1577 const MILLISEC_CUTOFF: i64 = 253402300800000;
1579
1580 if t < 0 {
1582 return None;
1583 }
1584
1585 let ts = if t < SEC_CUTOFF {
1586 Utc.timestamp_opt(t, 0).single().expect("invalid timestamp")
1587 } else if t < MILLISEC_CUTOFF {
1588 Utc.timestamp_millis_opt(t)
1589 .single()
1590 .expect("invalid timestamp")
1591 } else {
1592 Utc.timestamp_nanos(t)
1593 };
1594
1595 Some(ts)
1596}
1597
1598#[derive(Clone, Copy)]
1600enum LegacyKeyStrategy {
1601 Overwrite,
1602 InsertIfEmpty,
1603}
1604
1605struct DefaultExtractor {
1607 field: &'static str,
1608 to_field: OptionalValuePath,
1609 value: Option<Value>,
1610 log_namespace: LogNamespace,
1611 legacy_key_strategy: LegacyKeyStrategy,
1612}
1613
1614impl DefaultExtractor {
1615 const fn new(
1616 field: &'static str,
1617 to_field: OptionalValuePath,
1618 log_namespace: LogNamespace,
1619 ) -> Self {
1620 DefaultExtractor {
1621 field,
1622 to_field,
1623 value: None,
1624 log_namespace,
1625 legacy_key_strategy: LegacyKeyStrategy::Overwrite,
1626 }
1627 }
1628
1629 fn new_with(
1630 field: &'static str,
1631 to_field: OptionalValuePath,
1632 value: impl Into<Option<Value>>,
1633 log_namespace: LogNamespace,
1634 ) -> Self {
1635 DefaultExtractor {
1636 field,
1637 to_field,
1638 value: value.into(),
1639 log_namespace,
1640 legacy_key_strategy: LegacyKeyStrategy::Overwrite,
1641 }
1642 }
1643
1644 const fn with_legacy_key_strategy(mut self, strategy: LegacyKeyStrategy) -> Self {
1648 self.legacy_key_strategy = strategy;
1649 self
1650 }
1651
1652 fn extract(&mut self, log: &mut LogEvent, value: &mut JsonValue) {
1653 if let Some(JsonValue::String(new_value)) = value.get_mut(self.field).map(JsonValue::take) {
1655 self.value = Some(new_value.into());
1656 }
1657
1658 if let Some(index) = self.value.as_ref()
1660 && let Some(metadata_key) = self.to_field.path.as_ref()
1661 {
1662 if matches!(self.log_namespace, LogNamespace::Vector)
1667 && matches!(self.legacy_key_strategy, LegacyKeyStrategy::InsertIfEmpty)
1668 {
1669 log.try_insert(
1670 (
1671 PathPrefix::Metadata,
1672 lookup::path!(SplunkConfig::NAME).concat(metadata_key),
1673 ),
1674 index.clone(),
1675 );
1676 } else {
1677 let legacy_key = match self.legacy_key_strategy {
1678 LegacyKeyStrategy::Overwrite => LegacyKey::Overwrite(metadata_key),
1679 LegacyKeyStrategy::InsertIfEmpty => LegacyKey::InsertIfEmpty(metadata_key),
1680 };
1681 self.log_namespace.insert_source_metadata(
1682 SplunkConfig::NAME,
1683 log,
1684 Some(legacy_key),
1685 &self.to_field.path.clone().unwrap_or(owned_value_path!("")),
1686 index.clone(),
1687 );
1688 }
1689 }
1690 }
1691}
1692
1693#[derive(Clone, Debug)]
1695enum Time {
1696 Now(DateTime<Utc>),
1698 Provided(DateTime<Utc>),
1700}
1701
1702#[allow(clippy::too_many_arguments)]
1710fn raw_event(
1711 bytes: Bytes,
1712 gzip: bool,
1713 channel: String,
1714 remote: Option<SocketAddr>,
1715 xff: Option<String>,
1716 batch: Option<BatchNotifier>,
1717 log_namespace: LogNamespace,
1718 events_received: &Registered<EventsReceived>,
1719 decoder: Option<Decoder>,
1720 splunk_hec_token: Option<Arc<str>>,
1721) -> Result<(Vec<Event>, bool), Rejection> {
1722 let body_bytes: Bytes = if gzip {
1724 match CappedDecoder::gzip(bytes.reader()).decompress() {
1726 Ok(data) if data.is_empty() => return Err(ApiError::NoData.into()),
1727 Ok(data) => Bytes::from(data),
1728 Err(error) => {
1729 emit!(SplunkHecRequestBodyInvalidError { error });
1730 return Err(ApiError::InvalidDataFormat { event: 0 }.into());
1731 }
1732 }
1733 } else {
1734 bytes
1735 };
1736
1737 let host = if let Some(remote_address) = xff {
1741 Some(remote_address)
1742 } else {
1743 remote.map(|remote| remote.to_string())
1744 };
1745
1746 let decoder_in_use = decoder.is_some();
1747 let (mut events, had_decode_errors): (Vec<Event>, bool) = if let Some(decoder) = decoder {
1748 let decoder = {
1752 let mut meta = EventMetadata::default();
1753 if let Some(token) = splunk_hec_token.as_ref() {
1754 meta.set_splunk_hec_token(Arc::clone(token));
1755 }
1756 if let Some(ref h) = host {
1757 meta.value_mut().insert(
1758 &vrl::path::parse_value_path("splunk_hec.host")
1759 .expect("splunk_hec.host is a valid VRL path"),
1760 h.clone(),
1761 );
1762 }
1763 meta.value_mut().insert(
1764 &vrl::path::parse_value_path("splunk_hec.channel")
1765 .expect("splunk_hec.channel is a valid VRL path"),
1766 channel.clone(),
1767 );
1768 decoder.with_metadata_template(meta)
1769 };
1770
1771 decode_payload(
1776 decoder,
1777 &body_bytes,
1778 Some(Utc::now()),
1779 false, DecodePayloadContext {
1781 batch: &batch,
1782 log_namespace,
1783 events_received,
1784 splunk_hec_token: splunk_hec_token.as_ref(),
1785 },
1786 )
1787 } else {
1788 let message: Value = body_bytes.into();
1789 let mut log = match log_namespace {
1790 LogNamespace::Vector => LogEvent::from(message),
1791 LogNamespace::Legacy => {
1792 let mut log = LogEvent::default();
1793 log.maybe_insert(log_schema().message_key_target_path(), message);
1794 log
1795 }
1796 };
1797 events_received.emit(CountByteSize(1, log.estimated_json_encoded_size_of()));
1799
1800 log_namespace.insert_standard_vector_source_metadata(
1801 &mut log,
1802 SplunkConfig::NAME,
1803 Utc::now(),
1804 );
1805
1806 if let Some(batch) = batch.clone() {
1807 log = log.with_batch_notifier(&batch);
1808 }
1809 (vec![Event::from(log)], false)
1810 };
1811
1812 let channel_path = owned_value_path!(CHANNEL);
1813 for event in &mut events {
1814 if let Event::Log(log) = event {
1815 if decoder_in_use && matches!(log_namespace, LogNamespace::Vector) {
1821 log.try_insert(metadata_path!(SplunkConfig::NAME, CHANNEL), channel.clone());
1822 if let Some(ref h) = host {
1823 log.try_insert(metadata_path!(SplunkConfig::NAME, "host"), h.clone());
1824 }
1825 } else {
1826 let channel_legacy_key = if decoder_in_use {
1827 LegacyKey::InsertIfEmpty(&channel_path)
1828 } else {
1829 LegacyKey::Overwrite(&channel_path)
1830 };
1831 log_namespace.insert_source_metadata(
1832 SplunkConfig::NAME,
1833 log,
1834 Some(channel_legacy_key),
1835 lookup::path!(CHANNEL),
1836 channel.clone(),
1837 );
1838 if let Some(ref host) = host {
1839 log_namespace.insert_source_metadata(
1840 SplunkConfig::NAME,
1841 log,
1842 log_schema().host_key().map(LegacyKey::InsertIfEmpty),
1843 lookup::path!("host"),
1844 host.clone(),
1845 );
1846 }
1847 }
1848 }
1849 }
1850
1851 Ok((events, had_decode_errors))
1852}
1853
1854#[derive(Clone, Copy, Debug, Snafu)]
1855pub(crate) enum ApiError {
1856 MissingAuthorization,
1857 InvalidAuthorization,
1858 UnsupportedEncoding,
1859 UnsupportedContentType,
1860 MissingChannel,
1861 NoData,
1862 InvalidDataFormat { event: usize },
1863 ServerShutdown,
1864 EmptyEventField { event: usize },
1865 MissingEventField { event: usize },
1866 BadRequest,
1867 ServiceUnavailable,
1868 AckIsDisabled,
1869}
1870
1871impl warp::reject::Reject for ApiError {}
1872
1873mod splunk_response {
1875 use serde::Serialize;
1876
1877 pub enum HecStatusCode {
1879 Success = 0,
1880 TokenIsRequired = 2,
1881 InvalidAuthorization = 3,
1882 NoData = 5,
1883 InvalidDataFormat = 6,
1884 ServerIsBusy = 9,
1885 DataChannelIsMissing = 10,
1886 EventFieldIsRequired = 12,
1887 EventFieldCannotBeBlank = 13,
1888 AckIsDisabled = 14,
1889 }
1890
1891 #[derive(Serialize)]
1892 pub enum HecResponseMetadata {
1893 #[serde(rename = "ackId")]
1894 AckId(u64),
1895 #[serde(rename = "invalid-event-number")]
1896 InvalidEventNumber(usize),
1897 }
1898
1899 #[derive(Serialize)]
1900 pub struct HecResponse {
1901 text: &'static str,
1902 code: u8,
1903 #[serde(skip_serializing_if = "Option::is_none", flatten)]
1904 pub metadata: Option<HecResponseMetadata>,
1905 }
1906
1907 impl HecResponse {
1908 pub const fn new(code: HecStatusCode) -> Self {
1909 let text = match code {
1910 HecStatusCode::Success => "Success",
1911 HecStatusCode::TokenIsRequired => "Token is required",
1912 HecStatusCode::InvalidAuthorization => "Invalid authorization",
1913 HecStatusCode::NoData => "No data",
1914 HecStatusCode::InvalidDataFormat => "Invalid data format",
1915 HecStatusCode::DataChannelIsMissing => "Data channel is missing",
1916 HecStatusCode::EventFieldIsRequired => "Event field is required",
1917 HecStatusCode::EventFieldCannotBeBlank => "Event field cannot be blank",
1918 HecStatusCode::ServerIsBusy => "Server is busy",
1919 HecStatusCode::AckIsDisabled => "Ack is disabled",
1920 };
1921
1922 Self {
1923 text,
1924 code: code as u8,
1925 metadata: None,
1926 }
1927 }
1928
1929 pub const fn with_metadata(mut self, metadata: HecResponseMetadata) -> Self {
1930 self.metadata = Some(metadata);
1931 self
1932 }
1933 }
1934
1935 pub const INVALID_AUTHORIZATION: HecResponse =
1936 HecResponse::new(HecStatusCode::InvalidAuthorization);
1937 pub const TOKEN_IS_REQUIRED: HecResponse = HecResponse::new(HecStatusCode::TokenIsRequired);
1938 pub const NO_DATA: HecResponse = HecResponse::new(HecStatusCode::NoData);
1939 pub const SUCCESS: HecResponse = HecResponse::new(HecStatusCode::Success);
1940 pub const SERVER_IS_BUSY: HecResponse = HecResponse::new(HecStatusCode::ServerIsBusy);
1941 pub const NO_CHANNEL: HecResponse = HecResponse::new(HecStatusCode::DataChannelIsMissing);
1942 pub const ACK_IS_DISABLED: HecResponse = HecResponse::new(HecStatusCode::AckIsDisabled);
1943}
1944
1945async fn register_ack(
1946 idx_ack: Option<Arc<IndexerAcknowledgement>>,
1947 receiver: Option<BatchStatusReceiver>,
1948 channel: Option<String>,
1949) -> Result<Option<u64>, Rejection> {
1950 match (idx_ack, receiver, channel) {
1951 (Some(ack), Some(rx), Some(ch)) => Ok(Some(ack.get_ack_id_from_channel(ch, rx).await?)),
1952 _ => Ok(None),
1953 }
1954}
1955
1956fn finish_ok(maybe_ack_id: Option<u64>) -> Response {
1957 let body = if let Some(ack_id) = maybe_ack_id {
1958 HecResponse::new(HecStatusCode::Success).with_metadata(HecResponseMetadata::AckId(ack_id))
1959 } else {
1960 splunk_response::SUCCESS
1961 };
1962 response_json(StatusCode::OK, body)
1963}
1964
1965fn response_plain(code: StatusCode, msg: &'static str) -> Response {
1966 warp::reply::with_status(
1967 warp::reply::with_header(msg, http::header::CONTENT_TYPE, "text/plain; charset=utf-8"),
1968 code,
1969 )
1970 .into_response()
1971}
1972
1973async fn finish_err(rejection: Rejection) -> Result<(Response,), Rejection> {
1974 if let Some(&error) = rejection.find::<ApiError>() {
1975 emit!(SplunkHecRequestError { error });
1976 Ok((match error {
1977 ApiError::MissingAuthorization => {
1978 response_json(StatusCode::UNAUTHORIZED, splunk_response::TOKEN_IS_REQUIRED)
1979 }
1980 ApiError::InvalidAuthorization => response_json(
1981 StatusCode::UNAUTHORIZED,
1982 splunk_response::INVALID_AUTHORIZATION,
1983 ),
1984 ApiError::UnsupportedEncoding => empty_response(StatusCode::UNSUPPORTED_MEDIA_TYPE),
1985 ApiError::UnsupportedContentType => response_plain(
1986 StatusCode::UNSUPPORTED_MEDIA_TYPE,
1987 "The request's content-type is not supported",
1988 ),
1989 ApiError::MissingChannel => {
1990 response_json(StatusCode::BAD_REQUEST, splunk_response::NO_CHANNEL)
1991 }
1992 ApiError::NoData => response_json(StatusCode::BAD_REQUEST, splunk_response::NO_DATA),
1993 ApiError::ServerShutdown => empty_response(StatusCode::SERVICE_UNAVAILABLE),
1994 ApiError::InvalidDataFormat { event } => response_json(
1995 StatusCode::BAD_REQUEST,
1996 HecResponse::new(HecStatusCode::InvalidDataFormat)
1997 .with_metadata(HecResponseMetadata::InvalidEventNumber(event)),
1998 ),
1999 ApiError::EmptyEventField { event } => response_json(
2000 StatusCode::BAD_REQUEST,
2001 HecResponse::new(HecStatusCode::EventFieldCannotBeBlank)
2002 .with_metadata(HecResponseMetadata::InvalidEventNumber(event)),
2003 ),
2004 ApiError::MissingEventField { event } => response_json(
2005 StatusCode::BAD_REQUEST,
2006 HecResponse::new(HecStatusCode::EventFieldIsRequired)
2007 .with_metadata(HecResponseMetadata::InvalidEventNumber(event)),
2008 ),
2009 ApiError::BadRequest => empty_response(StatusCode::BAD_REQUEST),
2010 ApiError::ServiceUnavailable => response_json(
2011 StatusCode::SERVICE_UNAVAILABLE,
2012 splunk_response::SERVER_IS_BUSY,
2013 ),
2014 ApiError::AckIsDisabled => {
2015 response_json(StatusCode::BAD_REQUEST, splunk_response::ACK_IS_DISABLED)
2016 }
2017 },))
2018 } else if let Some(error) = rejection.find::<ErrorMessage>() {
2019 Ok((response_json(error.status_code(), error),))
2020 } else {
2021 Err(rejection)
2022 }
2023}
2024
2025fn empty_response(code: StatusCode) -> Response {
2027 let mut res = Response::default();
2028 *res.status_mut() = code;
2029 res
2030}
2031
2032fn response_json(code: StatusCode, body: impl Serialize) -> Response {
2034 warp::reply::with_status(warp::reply::json(&body), code).into_response()
2035}
2036
2037#[cfg(feature = "sinks-splunk_hec")]
2038#[cfg(test)]
2039mod tests {
2040 use std::{net::SocketAddr, num::NonZeroU64};
2041
2042 use chrono::{TimeZone, Utc};
2043 use futures_util::Stream;
2044 use http::Uri;
2045 use reqwest::{RequestBuilder, Response};
2046 use serde::Deserialize;
2047 use vector_lib::{
2048 codecs::{
2049 BytesDecoderConfig, JsonSerializerConfig, TextSerializerConfig,
2050 decoding::{
2051 DeserializerConfig,
2052 format::{VrlDeserializerConfig, VrlDeserializerOptions},
2053 },
2054 },
2055 event::EventStatus,
2056 schema::Definition,
2057 sensitive_string::SensitiveString,
2058 };
2059 use vrl::path::PathPrefix;
2060
2061 use super::*;
2062 use crate::{
2063 SourceSender,
2064 codecs::{DecodingConfig, EncodingConfig},
2065 components::validation::prelude::*,
2066 config::{SinkConfig, SinkContext, SourceConfig, SourceContext, log_schema},
2067 event::{Event, LogEvent},
2068 sinks::{
2069 Healthcheck, VectorSink,
2070 splunk_hec::logs::config::HecLogsSinkConfig,
2071 util::{BatchConfig, Compression, TowerRequestConfig},
2072 },
2073 sources::splunk_hec::acknowledgements::{HecAckStatusRequest, HecAckStatusResponse},
2074 test_util::{
2075 addr::{PortGuard, next_addr},
2076 collect_n,
2077 components::{
2078 COMPONENT_ERROR_TAGS, HTTP_PUSH_SOURCE_TAGS, assert_source_compliance,
2079 assert_source_error,
2080 },
2081 wait_for_tcp,
2082 },
2083 };
2084
2085 #[test]
2086 fn generate_config() {
2087 crate::test_util::test_generate_config::<SplunkConfig>();
2088 }
2089
2090 #[tokio::test]
2091 async fn finish_err_maps_capped_body_to_client_error() {
2092 let rejection = warp::reject::custom(ErrorMessage::new(
2095 StatusCode::PAYLOAD_TOO_LARGE,
2096 "Request body exceeds limit of 1024 bytes.".to_owned(),
2097 ));
2098
2099 let (response,) = finish_err(rejection)
2100 .await
2101 .expect("capped-body rejection should be recovered");
2102
2103 assert_eq!(response.status(), StatusCode::PAYLOAD_TOO_LARGE);
2104 }
2105
2106 const TOKEN: &str = "token";
2108 const VALID_TOKENS: &[&str; 2] = &[TOKEN, "secondary-token"];
2109
2110 async fn source(
2111 acknowledgements: Option<HecAcknowledgementsConfig>,
2112 ) -> (impl Stream<Item = Event> + Unpin, SocketAddr, PortGuard) {
2113 source_with(Some(TOKEN.to_owned().into()), None, acknowledgements, false).await
2114 }
2115
2116 async fn source_with(
2117 token: Option<SensitiveString>,
2118 valid_tokens: Option<&[&str]>,
2119 acknowledgements: Option<HecAcknowledgementsConfig>,
2120 store_hec_token: bool,
2121 ) -> (
2122 impl Stream<Item = Event> + Unpin + use<>,
2123 SocketAddr,
2124 PortGuard,
2125 ) {
2126 let (sender, recv) = SourceSender::new_test_finalize(EventStatus::Delivered);
2127 let (_guard, address) = next_addr();
2128 let valid_tokens =
2129 valid_tokens.map(|tokens| tokens.iter().map(|v| v.to_string().into()).collect());
2130 let cx = SourceContext::new_test(sender, None);
2131 tokio::spawn(async move {
2132 SplunkConfig {
2133 address,
2134 token,
2135 valid_tokens,
2136 tls: None,
2137 acknowledgements: acknowledgements.unwrap_or_default(),
2138 store_hec_token,
2139 log_namespace: None,
2140 keepalive: Default::default(),
2141 event: CodecConfig::default(),
2142 raw: CodecConfig::default(),
2143 }
2144 .build(cx)
2145 .await
2146 .unwrap()
2147 .await
2148 .unwrap()
2149 });
2150 wait_for_tcp(address).await;
2151 (recv, address, _guard)
2152 }
2153
2154 async fn sink(
2155 address: SocketAddr,
2156 encoding: EncodingConfig,
2157 compression: Compression,
2158 ) -> (VectorSink, Healthcheck) {
2159 HecLogsSinkConfig {
2160 default_token: TOKEN.to_owned().into(),
2161 endpoint: format!("http://{address}"),
2162 host_key: None,
2163 indexed_fields: vec![],
2164 index: None,
2165 sourcetype: None,
2166 source: None,
2167 encoding,
2168 compression,
2169 batch: BatchConfig::default(),
2170 request: TowerRequestConfig::default(),
2171 tls: None,
2172 acknowledgements: Default::default(),
2173 timestamp_nanos_key: None,
2174 timestamp_key: None,
2175 auto_extract_timestamp: None,
2176 endpoint_target: Default::default(),
2177 confinement: Default::default(),
2178 }
2179 .build(SinkContext::default())
2180 .await
2181 .unwrap()
2182 }
2183
2184 async fn start(
2185 encoding: EncodingConfig,
2186 compression: Compression,
2187 acknowledgements: Option<HecAcknowledgementsConfig>,
2188 ) -> (VectorSink, impl Stream<Item = Event> + Unpin) {
2189 let (source, address, _guard) = source(acknowledgements).await;
2190 let (sink, health) = sink(address, encoding, compression).await;
2191 assert!(health.await.is_ok());
2192 (sink, source)
2193 }
2194
2195 async fn channel_n(
2196 messages: Vec<impl Into<String> + Send + 'static>,
2197 sink: VectorSink,
2198 source: impl Stream<Item = Event> + Unpin,
2199 ) -> Vec<Event> {
2200 let n = messages.len();
2201
2202 tokio::spawn(async move {
2203 sink.run_events(
2204 messages
2205 .into_iter()
2206 .map(|s| Event::Log(LogEvent::from(s.into()))),
2207 )
2208 .await
2209 .unwrap();
2210 });
2211
2212 let events = collect_n(source, n).await;
2213 assert_eq!(n, events.len());
2214
2215 events
2216 }
2217
2218 #[derive(Clone, Copy, Debug)]
2219 enum Channel<'a> {
2220 Header(&'a str),
2221 QueryParam(&'a str),
2222 }
2223
2224 #[derive(Default)]
2225 struct SendWithOpts<'a> {
2226 channel: Option<Channel<'a>>,
2227 forwarded_for: Option<String>,
2228 }
2229
2230 async fn post(address: SocketAddr, api: &str, message: &str) -> u16 {
2231 let channel = Channel::Header("channel");
2232 let options = SendWithOpts {
2233 channel: Some(channel),
2234 forwarded_for: None,
2235 };
2236 send_with(address, api, message, TOKEN, &options).await
2237 }
2238
2239 fn build_request(
2240 address: SocketAddr,
2241 api: &str,
2242 message: &str,
2243 token: &str,
2244 opts: &SendWithOpts<'_>,
2245 ) -> RequestBuilder {
2246 let mut b = reqwest::Client::new()
2247 .post(format!("http://{address}/{api}"))
2248 .header("Authorization", format!("Splunk {token}"));
2249
2250 b = match opts.channel {
2251 Some(c) => match c {
2252 Channel::Header(v) => b.header("x-splunk-request-channel", v),
2253 Channel::QueryParam(v) => b.query(&[("channel", v)]),
2254 },
2255 None => b,
2256 };
2257
2258 b = match &opts.forwarded_for {
2259 Some(f) => b.header("X-Forwarded-For", f),
2260 None => b,
2261 };
2262
2263 b.body(message.to_owned())
2264 }
2265
2266 async fn send_with(
2267 address: SocketAddr,
2268 api: &str,
2269 message: &str,
2270 token: &str,
2271 opts: &SendWithOpts<'_>,
2272 ) -> u16 {
2273 let b = build_request(address, api, message, token, opts);
2274 b.send().await.unwrap().status().as_u16()
2275 }
2276
2277 async fn send_with_response(
2278 address: SocketAddr,
2279 api: &str,
2280 message: &str,
2281 token: &str,
2282 opts: &SendWithOpts<'_>,
2283 ) -> Response {
2284 let b = build_request(address, api, message, token, opts);
2285 b.send().await.unwrap()
2286 }
2287
2288 #[tokio::test]
2289 async fn no_compression_text_event() {
2290 let message = "gzip_text_event";
2291 let (sink, source) = start(
2292 TextSerializerConfig::default().into(),
2293 Compression::None,
2294 None,
2295 )
2296 .await;
2297
2298 let event = channel_n(vec![message], sink, source).await.remove(0);
2299
2300 assert_eq!(
2301 event.as_log()[log_schema().message_key().unwrap().to_string()],
2302 message.into()
2303 );
2304 assert!(event.as_log().get_timestamp().is_some());
2305 assert_eq!(
2306 event.as_log()[log_schema().source_type_key().unwrap().to_string()],
2307 "splunk_hec".into()
2308 );
2309 assert!(event.metadata().splunk_hec_token().is_none());
2310 }
2311
2312 #[tokio::test]
2313 async fn one_simple_text_event() {
2314 let message = "one_simple_text_event";
2315 let (sink, source) = start(
2316 TextSerializerConfig::default().into(),
2317 Compression::gzip_default(),
2318 None,
2319 )
2320 .await;
2321
2322 let event = channel_n(vec![message], sink, source).await.remove(0);
2323
2324 assert_eq!(
2325 event.as_log()[log_schema().message_key().unwrap().to_string()],
2326 message.into()
2327 );
2328 assert!(event.as_log().get_timestamp().is_some());
2329 assert_eq!(
2330 event.as_log()[log_schema().source_type_key().unwrap().to_string()],
2331 "splunk_hec".into()
2332 );
2333 assert!(event.metadata().splunk_hec_token().is_none());
2334 }
2335
2336 #[tokio::test]
2337 async fn multiple_simple_text_event() {
2338 let n = 200;
2339 let (sink, source) = start(
2340 TextSerializerConfig::default().into(),
2341 Compression::None,
2342 None,
2343 )
2344 .await;
2345
2346 let messages = (0..n)
2347 .map(|i| format!("multiple_simple_text_event_{i}"))
2348 .collect::<Vec<_>>();
2349 let events = channel_n(messages.clone(), sink, source).await;
2350
2351 for (msg, event) in messages.into_iter().zip(events.into_iter()) {
2352 assert_eq!(
2353 event.as_log()[log_schema().message_key().unwrap().to_string()],
2354 msg.into()
2355 );
2356 assert!(event.as_log().get_timestamp().is_some());
2357 assert_eq!(
2358 event.as_log()[log_schema().source_type_key().unwrap().to_string()],
2359 "splunk_hec".into()
2360 );
2361 assert!(event.metadata().splunk_hec_token().is_none());
2362 }
2363 }
2364
2365 #[tokio::test]
2366 async fn one_simple_json_event() {
2367 let message = "one_simple_json_event";
2368 let (sink, source) = start(
2369 JsonSerializerConfig::default().into(),
2370 Compression::gzip_default(),
2371 None,
2372 )
2373 .await;
2374
2375 let event = channel_n(vec![message], sink, source).await.remove(0);
2376
2377 assert_eq!(
2378 event.as_log()[log_schema().message_key().unwrap().to_string()],
2379 message.into()
2380 );
2381 assert!(event.as_log().get_timestamp().is_some());
2382 assert_eq!(
2383 event.as_log()[log_schema().source_type_key().unwrap().to_string()],
2384 "splunk_hec".into()
2385 );
2386 assert!(event.metadata().splunk_hec_token().is_none());
2387 }
2388
2389 #[tokio::test]
2390 async fn multiple_simple_json_event() {
2391 let n = 200;
2392 let (sink, source) = start(
2393 JsonSerializerConfig::default().into(),
2394 Compression::gzip_default(),
2395 None,
2396 )
2397 .await;
2398
2399 let messages = (0..n)
2400 .map(|i| format!("multiple_simple_json_event{i}"))
2401 .collect::<Vec<_>>();
2402 let events = channel_n(messages.clone(), sink, source).await;
2403
2404 for (msg, event) in messages.into_iter().zip(events.into_iter()) {
2405 assert_eq!(
2406 event.as_log()[log_schema().message_key().unwrap().to_string()],
2407 msg.into()
2408 );
2409 assert!(event.as_log().get_timestamp().is_some());
2410 assert_eq!(
2411 event.as_log()[log_schema().source_type_key().unwrap().to_string()],
2412 "splunk_hec".into()
2413 );
2414 assert!(event.metadata().splunk_hec_token().is_none());
2415 }
2416 }
2417
2418 #[tokio::test]
2419 async fn json_event() {
2420 let (sink, source) = start(
2421 JsonSerializerConfig::default().into(),
2422 Compression::gzip_default(),
2423 None,
2424 )
2425 .await;
2426
2427 let mut log = LogEvent::default();
2428 log.insert(event_path!("greeting"), "hello");
2429 log.insert(event_path!("name"), "bob");
2430 sink.run_events(vec![log.into()]).await.unwrap();
2431
2432 let event = collect_n(source, 1).await.remove(0).into_log();
2433 assert_eq!(event["greeting"], "hello".into());
2434 assert_eq!(event["name"], "bob".into());
2435 assert!(event.get_timestamp().is_some());
2436 assert_eq!(
2437 event[log_schema().source_type_key().unwrap().to_string()],
2438 "splunk_hec".into()
2439 );
2440 assert!(event.metadata().splunk_hec_token().is_none());
2441 }
2442
2443 #[tokio::test]
2444 async fn json_invalid_path_event() {
2445 let (sink, source) = start(
2446 JsonSerializerConfig::default().into(),
2447 Compression::gzip_default(),
2448 None,
2449 )
2450 .await;
2451
2452 let mut log = LogEvent::default();
2453 log.insert(event_path!("(greeting | thing"), "hello");
2456 sink.run_events(vec![log.into()]).await.unwrap();
2457
2458 let event = collect_n(source, 1).await.remove(0).into_log();
2459 assert_eq!(
2460 event.get(event_path!("(greeting | thing")),
2461 Some(&Value::from("hello"))
2462 );
2463 }
2464
2465 #[tokio::test]
2466 async fn line_to_message() {
2467 let (sink, source) = start(
2468 JsonSerializerConfig::default().into(),
2469 Compression::gzip_default(),
2470 None,
2471 )
2472 .await;
2473
2474 let mut event = LogEvent::default();
2475 event.insert(event_path!("line"), "hello");
2476 sink.run_events(vec![event.into()]).await.unwrap();
2477
2478 let event = collect_n(source, 1).await.remove(0);
2479 assert_eq!(
2480 event.as_log()[log_schema().message_key().unwrap().to_string()],
2481 "hello".into()
2482 );
2483 assert!(event.metadata().splunk_hec_token().is_none());
2484 }
2485
2486 #[tokio::test]
2487 async fn raw() {
2488 assert_source_compliance(&HTTP_PUSH_SOURCE_TAGS, async {
2489 let message = "raw";
2490 let (source, address, _guard) = source(None).await;
2491
2492 assert_eq!(200, post(address, "services/collector/raw", message).await);
2493
2494 let event = collect_n(source, 1).await.remove(0);
2495 assert_eq!(
2496 event.as_log()[log_schema().message_key().unwrap().to_string()],
2497 message.into()
2498 );
2499 assert_eq!(event.as_log()[&super::CHANNEL], "channel".into());
2500 assert!(event.as_log().get_timestamp().is_some());
2501 assert_eq!(
2502 event.as_log()[log_schema().source_type_key().unwrap().to_string()],
2503 "splunk_hec".into()
2504 );
2505 assert!(event.metadata().splunk_hec_token().is_none());
2506 })
2507 .await;
2508 }
2509
2510 #[tokio::test]
2511 async fn root() {
2512 assert_source_compliance(&HTTP_PUSH_SOURCE_TAGS, async {
2513 let message = r#"{ "event": { "message": "root"} }"#;
2514 let (source, address, _guard) = source(None).await;
2515
2516 assert_eq!(200, post(address, "services/collector", message).await);
2517
2518 let event = collect_n(source, 1).await.remove(0);
2519 assert_eq!(
2520 event.as_log()[log_schema().message_key().unwrap().to_string()],
2521 "root".into()
2522 );
2523 assert_eq!(event.as_log()[&super::CHANNEL], "channel".into());
2524 assert!(event.as_log().get_timestamp().is_some());
2525 assert_eq!(
2526 event.as_log()[log_schema().source_type_key().unwrap().to_string()],
2527 "splunk_hec".into()
2528 );
2529 assert!(event.metadata().splunk_hec_token().is_none());
2530 })
2531 .await;
2532 }
2533
2534 #[tokio::test]
2535 async fn channel_header() {
2536 assert_source_compliance(&HTTP_PUSH_SOURCE_TAGS, async {
2537 let message = "raw";
2538 let (source, address, _guard) = source(None).await;
2539
2540 let opts = SendWithOpts {
2541 channel: Some(Channel::Header("guid")),
2542 forwarded_for: None,
2543 };
2544
2545 assert_eq!(
2546 200,
2547 send_with(address, "services/collector/raw", message, TOKEN, &opts).await
2548 );
2549
2550 let event = collect_n(source, 1).await.remove(0);
2551 assert_eq!(event.as_log()[&super::CHANNEL], "guid".into());
2552 })
2553 .await;
2554 }
2555
2556 #[tokio::test]
2557 async fn xff_header_raw() {
2558 assert_source_compliance(&HTTP_PUSH_SOURCE_TAGS, async {
2559 let message = "raw";
2560 let (source, address, _guard) = source(None).await;
2561
2562 let opts = SendWithOpts {
2563 channel: Some(Channel::Header("guid")),
2564 forwarded_for: Some(String::from("10.0.0.1")),
2565 };
2566
2567 assert_eq!(
2568 200,
2569 send_with(address, "services/collector/raw", message, TOKEN, &opts).await
2570 );
2571
2572 let event = collect_n(source, 1).await.remove(0);
2573 assert_eq!(
2574 event.as_log()[log_schema().host_key().unwrap().to_string().as_str()],
2575 "10.0.0.1".into()
2576 );
2577 })
2578 .await;
2579 }
2580
2581 #[tokio::test]
2583 async fn xff_header_event_with_host_field() {
2584 assert_source_compliance(&HTTP_PUSH_SOURCE_TAGS, async {
2585 let message = r#"{"event":"first", "host": "10.1.0.2"}"#;
2586 let (source, address, _guard) = source(None).await;
2587
2588 let opts = SendWithOpts {
2589 channel: Some(Channel::Header("guid")),
2590 forwarded_for: Some(String::from("10.0.0.1")),
2591 };
2592
2593 assert_eq!(
2594 200,
2595 send_with(address, "services/collector/event", message, TOKEN, &opts).await
2596 );
2597
2598 let event = collect_n(source, 1).await.remove(0);
2599 assert_eq!(
2600 event.as_log()[log_schema().host_key().unwrap().to_string().as_str()],
2601 "10.1.0.2".into()
2602 );
2603 })
2604 .await;
2605 }
2606
2607 #[tokio::test]
2609 async fn xff_header_event_without_host_field() {
2610 assert_source_compliance(&HTTP_PUSH_SOURCE_TAGS, async {
2611 let message = r#"{"event":"first", "color": "blue"}"#;
2612 let (source, address, _guard) = source(None).await;
2613
2614 let opts = SendWithOpts {
2615 channel: Some(Channel::Header("guid")),
2616 forwarded_for: Some(String::from("10.0.0.1")),
2617 };
2618
2619 assert_eq!(
2620 200,
2621 send_with(address, "services/collector/event", message, TOKEN, &opts).await
2622 );
2623
2624 let event = collect_n(source, 1).await.remove(0);
2625 assert_eq!(
2626 event.as_log()[log_schema().host_key().unwrap().to_string().as_str()],
2627 "10.0.0.1".into()
2628 );
2629 })
2630 .await;
2631 }
2632
2633 #[tokio::test]
2634 async fn channel_query_param() {
2635 assert_source_compliance(&HTTP_PUSH_SOURCE_TAGS, async {
2636 let message = "raw";
2637 let (source, address, _guard) = source(None).await;
2638
2639 let opts = SendWithOpts {
2640 channel: Some(Channel::QueryParam("guid")),
2641 forwarded_for: None,
2642 };
2643
2644 assert_eq!(
2645 200,
2646 send_with(address, "services/collector/raw", message, TOKEN, &opts).await
2647 );
2648
2649 let event = collect_n(source, 1).await.remove(0);
2650 assert_eq!(event.as_log()[&super::CHANNEL], "guid".into());
2651 })
2652 .await;
2653 }
2654
2655 #[tokio::test]
2656 async fn no_data() {
2657 let (_source, address, _guard) = source(None).await;
2658
2659 assert_eq!(400, post(address, "services/collector/event", "").await);
2660 }
2661
2662 #[tokio::test]
2663 async fn invalid_token() {
2664 assert_source_error(&COMPONENT_ERROR_TAGS, async {
2665 let (_source, address, _guard) = source(None).await;
2666 let opts = SendWithOpts {
2667 channel: Some(Channel::Header("channel")),
2668 forwarded_for: None,
2669 };
2670
2671 assert_eq!(
2672 401,
2673 send_with(address, "services/collector/event", "", "nope", &opts).await
2674 );
2675 })
2676 .await;
2677 }
2678
2679 #[tokio::test]
2680 async fn health_ignores_token() {
2681 let (_source, address, _guard) = source(None).await;
2682
2683 let res = reqwest::Client::new()
2684 .get(format!("http://{address}/services/collector/health"))
2685 .header("Authorization", format!("Splunk {}", "invalid token"))
2686 .send()
2687 .await
2688 .unwrap();
2689
2690 assert_eq!(200, res.status().as_u16());
2691 }
2692
2693 #[tokio::test]
2694 async fn health() {
2695 let (_source, address, _guard) = source(None).await;
2696
2697 let res = reqwest::Client::new()
2698 .get(format!("http://{address}/services/collector/health"))
2699 .send()
2700 .await
2701 .unwrap();
2702
2703 assert_eq!(200, res.status().as_u16());
2704 }
2705
2706 #[tokio::test]
2707 async fn secondary_token() {
2708 assert_source_compliance(&HTTP_PUSH_SOURCE_TAGS, async {
2709 let message = r#"{"event":"first", "color": "blue"}"#;
2710 let (_source, address, _guard) =
2711 source_with(None, Some(VALID_TOKENS), None, false).await;
2712 let options = SendWithOpts {
2713 channel: None,
2714 forwarded_for: None,
2715 };
2716
2717 assert_eq!(
2718 200,
2719 send_with(
2720 address,
2721 "services/collector/event",
2722 message,
2723 VALID_TOKENS.get(1).unwrap(),
2724 &options
2725 )
2726 .await
2727 );
2728 })
2729 .await;
2730 }
2731
2732 #[tokio::test]
2733 async fn event_service_token_passthrough_enabled() {
2734 assert_source_compliance(&HTTP_PUSH_SOURCE_TAGS, async {
2735 let message = "passthrough_token_enabled";
2736 let (source, address, _guard) = source_with(None, Some(VALID_TOKENS), None, true).await;
2737 let (sink, health) = sink(
2738 address,
2739 TextSerializerConfig::default().into(),
2740 Compression::gzip_default(),
2741 )
2742 .await;
2743 assert!(health.await.is_ok());
2744
2745 let event = channel_n(vec![message], sink, source).await.remove(0);
2746
2747 assert_eq!(
2748 event.as_log()[log_schema().message_key().unwrap().to_string()],
2749 message.into()
2750 );
2751 assert_eq!(
2752 event.metadata().splunk_hec_token().as_deref().unwrap(),
2753 TOKEN
2754 );
2755 })
2756 .await;
2757 }
2758
2759 #[tokio::test]
2760 async fn raw_service_token_passthrough_enabled() {
2761 assert_source_compliance(&HTTP_PUSH_SOURCE_TAGS, async {
2762 let message = "raw";
2763 let (source, address, _guard) = source_with(None, Some(VALID_TOKENS), None, true).await;
2764
2765 assert_eq!(200, post(address, "services/collector/raw", message).await);
2766
2767 let event = collect_n(source, 1).await.remove(0);
2768 assert_eq!(
2769 event.as_log()[log_schema().message_key().unwrap().to_string()],
2770 message.into()
2771 );
2772 assert_eq!(event.as_log()[&super::CHANNEL], "channel".into());
2773 assert!(event.as_log().get_timestamp().is_some());
2774 assert_eq!(
2775 event.as_log()[log_schema().source_type_key().unwrap().to_string()],
2776 "splunk_hec".into()
2777 );
2778 assert_eq!(
2779 event.metadata().splunk_hec_token().as_deref().unwrap(),
2780 TOKEN
2781 );
2782 })
2783 .await;
2784 }
2785
2786 #[tokio::test]
2787 async fn no_authorization() {
2788 assert_source_compliance(&HTTP_PUSH_SOURCE_TAGS, async {
2789 let message = "no_authorization";
2790 let (source, address, _guard) = source_with(None, None, None, false).await;
2791 let (sink, health) = sink(
2792 address,
2793 TextSerializerConfig::default().into(),
2794 Compression::gzip_default(),
2795 )
2796 .await;
2797 assert!(health.await.is_ok());
2798
2799 let event = channel_n(vec![message], sink, source).await.remove(0);
2800
2801 assert_eq!(
2802 event.as_log()[log_schema().message_key().unwrap().to_string()],
2803 message.into()
2804 );
2805 assert!(event.metadata().splunk_hec_token().is_none());
2806 })
2807 .await;
2808 }
2809
2810 #[tokio::test]
2811 async fn no_authorization_token_passthrough_enabled() {
2812 assert_source_compliance(&HTTP_PUSH_SOURCE_TAGS, async {
2813 let message = "no_authorization";
2814 let (source, address, _guard) = source_with(None, None, None, true).await;
2815 let (sink, health) = sink(
2816 address,
2817 TextSerializerConfig::default().into(),
2818 Compression::gzip_default(),
2819 )
2820 .await;
2821 assert!(health.await.is_ok());
2822
2823 let event = channel_n(vec![message], sink, source).await.remove(0);
2824
2825 assert_eq!(
2826 event.as_log()[log_schema().message_key().unwrap().to_string()],
2827 message.into()
2828 );
2829 assert_eq!(
2830 event.metadata().splunk_hec_token().as_deref().unwrap(),
2831 TOKEN
2832 );
2833 })
2834 .await;
2835 }
2836
2837 #[tokio::test]
2838 async fn partial() {
2839 assert_source_compliance(&HTTP_PUSH_SOURCE_TAGS, async {
2840 let message = r#"{"event":"first"}{"event":"second""#;
2841 let (source, address, _guard) = source(None).await;
2842
2843 assert_eq!(
2844 400,
2845 post(address, "services/collector/event", message).await
2846 );
2847
2848 let event = collect_n(source, 1).await.remove(0);
2849 assert_eq!(
2850 event.as_log()[log_schema().message_key().unwrap().to_string()],
2851 "first".into()
2852 );
2853 assert!(event.as_log().get_timestamp().is_some());
2854 assert_eq!(
2855 event.as_log()[log_schema().source_type_key().unwrap().to_string()],
2856 "splunk_hec".into()
2857 );
2858 })
2859 .await;
2860 }
2861
2862 #[tokio::test]
2863 async fn handles_newlines() {
2864 assert_source_compliance(&HTTP_PUSH_SOURCE_TAGS, async {
2865 let message = r#"
2866{"event":"first"}
2867 "#;
2868 let (source, address, _guard) = source(None).await;
2869
2870 assert_eq!(
2871 200,
2872 post(address, "services/collector/event", message).await
2873 );
2874
2875 let event = collect_n(source, 1).await.remove(0);
2876 assert_eq!(
2877 event.as_log()[log_schema().message_key().unwrap().to_string()],
2878 "first".into()
2879 );
2880 assert!(event.as_log().get_timestamp().is_some());
2881 assert_eq!(
2882 event.as_log()[log_schema().source_type_key().unwrap().to_string()],
2883 "splunk_hec".into()
2884 );
2885 })
2886 .await;
2887 }
2888
2889 #[tokio::test]
2890 async fn handles_spaces() {
2891 assert_source_compliance(&HTTP_PUSH_SOURCE_TAGS, async {
2892 let message = r#" {"event":"first"} "#;
2893 let (source, address, _guard) = source(None).await;
2894
2895 assert_eq!(
2896 200,
2897 post(address, "services/collector/event", message).await
2898 );
2899
2900 let event = collect_n(source, 1).await.remove(0);
2901 assert_eq!(
2902 event.as_log()[log_schema().message_key().unwrap().to_string()],
2903 "first".into()
2904 );
2905 assert!(event.as_log().get_timestamp().is_some());
2906 assert_eq!(
2907 event.as_log()[log_schema().source_type_key().unwrap().to_string()],
2908 "splunk_hec".into()
2909 );
2910 })
2911 .await;
2912 }
2913
2914 #[tokio::test]
2915 async fn handles_non_utf8() {
2916 assert_source_compliance(&HTTP_PUSH_SOURCE_TAGS, async {
2917 let message = b" {\"event\": { \"non\": \"A non UTF8 character \xE4\", \"number\": 2, \"bool\": true } } ";
2918 let (source, address, _guard) = source(None).await;
2919
2920 let b = reqwest::Client::new()
2921 .post(format!(
2922 "http://{}/{}",
2923 address, "services/collector/event"
2924 ))
2925 .header("Authorization", format!("Splunk {TOKEN}"))
2926 .body::<&[u8]>(message);
2927
2928 assert_eq!(200, b.send().await.unwrap().status().as_u16());
2929
2930 let event = collect_n(source, 1).await.remove(0);
2931 assert_eq!(event.as_log()["non"], "A non UTF8 character �".into());
2932 assert_eq!(event.as_log()["number"], 2.into());
2933 assert_eq!(event.as_log()["bool"], true.into());
2934 assert!(event.as_log().get((lookup::PathPrefix::Event, log_schema().timestamp_key().unwrap())).is_some());
2935 assert_eq!(
2936 event.as_log()[log_schema().source_type_key().unwrap().to_string()],
2937 "splunk_hec".into()
2938 );
2939 }).await;
2940 }
2941
2942 #[tokio::test]
2943 async fn default() {
2944 assert_source_compliance(&HTTP_PUSH_SOURCE_TAGS, async {
2945 let message = r#"{"event":"first","source":"main"}{"event":"second"}{"event":"third","source":"secondary"}"#;
2946 let (source, address, _guard) = source(None).await;
2947
2948 assert_eq!(
2949 200,
2950 post(address, "services/collector/event", message).await
2951 );
2952
2953 let events = collect_n(source, 3).await;
2954
2955 assert_eq!(
2956 events[0].as_log()[log_schema().message_key().unwrap().to_string()],
2957 "first".into()
2958 );
2959 assert_eq!(events[0].as_log()[&super::SOURCE], "main".into());
2960
2961 assert_eq!(
2962 events[1].as_log()[log_schema().message_key().unwrap().to_string()],
2963 "second".into()
2964 );
2965 assert_eq!(events[1].as_log()[&super::SOURCE], "main".into());
2966
2967 assert_eq!(
2968 events[2].as_log()[log_schema().message_key().unwrap().to_string()],
2969 "third".into()
2970 );
2971 assert_eq!(events[2].as_log()[&super::SOURCE], "secondary".into());
2972 }).await;
2973 }
2974
2975 #[test]
2976 fn parse_timestamps() {
2977 let cases = vec![
2978 Utc::now(),
2979 Utc.with_ymd_and_hms(1971, 11, 7, 1, 1, 1)
2980 .single()
2981 .expect("invalid timestamp"),
2982 Utc.with_ymd_and_hms(2011, 8, 5, 1, 1, 1)
2983 .single()
2984 .expect("invalid timestamp"),
2985 Utc.with_ymd_and_hms(2189, 11, 4, 2, 2, 2)
2986 .single()
2987 .expect("invalid timestamp"),
2988 ];
2989
2990 for case in cases {
2991 let sec = case.timestamp();
2992 let millis = case.timestamp_millis();
2993 let nano = case.timestamp_nanos_opt().expect("Timestamp out of range");
2994
2995 assert_eq!(parse_timestamp(sec).unwrap().timestamp(), case.timestamp());
2996 assert_eq!(
2997 parse_timestamp(millis).unwrap().timestamp_millis(),
2998 case.timestamp_millis()
2999 );
3000 assert_eq!(
3001 parse_timestamp(nano)
3002 .unwrap()
3003 .timestamp_nanos_opt()
3004 .unwrap(),
3005 case.timestamp_nanos_opt().expect("Timestamp out of range")
3006 );
3007 }
3008
3009 assert!(parse_timestamp(-1).is_none());
3010 }
3011
3012 #[tokio::test]
3019 async fn host_test() {
3020 assert_source_compliance(&HTTP_PUSH_SOURCE_TAGS, async {
3021 let message = "for the host";
3022 let (sink, source) = start(
3023 TextSerializerConfig::default().into(),
3024 Compression::gzip_default(),
3025 None,
3026 )
3027 .await;
3028
3029 let event = channel_n(vec![message], sink, source).await.remove(0);
3030
3031 assert_eq!(
3032 event.as_log()[log_schema().message_key().unwrap().to_string()],
3033 message.into()
3034 );
3035 assert!(
3036 event
3037 .as_log()
3038 .get((PathPrefix::Event, log_schema().host_key().unwrap()))
3039 .is_none()
3040 );
3041 })
3042 .await;
3043 }
3044
3045 #[derive(Deserialize)]
3046 struct HecAckEventResponse {
3047 text: String,
3048 code: u8,
3049 #[serde(rename = "ackId")]
3050 ack_id: u64,
3051 }
3052
3053 #[tokio::test]
3054 async fn ack_json_event() {
3055 let ack_config = HecAcknowledgementsConfig {
3056 enabled: Some(true),
3057 ..Default::default()
3058 };
3059 let (source, address, _guard) = source(Some(ack_config)).await;
3060 let event_message = r#"{"event":"first", "color": "blue"}{"event":"second"}"#;
3061 let opts = SendWithOpts {
3062 channel: Some(Channel::Header("guid")),
3063 forwarded_for: None,
3064 };
3065 let event_res = send_with_response(
3066 address,
3067 "services/collector/event",
3068 event_message,
3069 TOKEN,
3070 &opts,
3071 )
3072 .await
3073 .json::<HecAckEventResponse>()
3074 .await
3075 .unwrap();
3076 assert_eq!("Success", event_res.text.as_str());
3077 assert_eq!(0, event_res.code);
3078 _ = collect_n(source, 1).await;
3079
3080 let ack_message = serde_json::to_string(&HecAckStatusRequest {
3081 acks: vec![event_res.ack_id],
3082 })
3083 .unwrap();
3084 let ack_res = send_with_response(
3085 address,
3086 "services/collector/ack",
3087 ack_message.as_str(),
3088 TOKEN,
3089 &opts,
3090 )
3091 .await
3092 .json::<HecAckStatusResponse>()
3093 .await
3094 .unwrap();
3095 assert!(ack_res.acks.get(&event_res.ack_id).unwrap());
3096 }
3097
3098 #[tokio::test]
3099 async fn ack_raw_event() {
3100 let ack_config = HecAcknowledgementsConfig {
3101 enabled: Some(true),
3102 ..Default::default()
3103 };
3104 let (source, address, _guard) = source(Some(ack_config)).await;
3105 let event_message = "raw event message";
3106 let opts = SendWithOpts {
3107 channel: Some(Channel::Header("guid")),
3108 forwarded_for: None,
3109 };
3110 let event_res = send_with_response(
3111 address,
3112 "services/collector/raw",
3113 event_message,
3114 TOKEN,
3115 &opts,
3116 )
3117 .await
3118 .json::<HecAckEventResponse>()
3119 .await
3120 .unwrap();
3121 assert_eq!("Success", event_res.text.as_str());
3122 assert_eq!(0, event_res.code);
3123 _ = collect_n(source, 1).await;
3124
3125 let ack_message = serde_json::to_string(&HecAckStatusRequest {
3126 acks: vec![event_res.ack_id],
3127 })
3128 .unwrap();
3129 let ack_res = send_with_response(
3130 address,
3131 "services/collector/ack",
3132 ack_message.as_str(),
3133 TOKEN,
3134 &opts,
3135 )
3136 .await
3137 .json::<HecAckStatusResponse>()
3138 .await
3139 .unwrap();
3140 assert!(ack_res.acks.get(&event_res.ack_id).unwrap());
3141 }
3142
3143 #[tokio::test]
3144 async fn ack_repeat_ack_query() {
3145 let ack_config = HecAcknowledgementsConfig {
3146 enabled: Some(true),
3147 ..Default::default()
3148 };
3149 let (source, address, _guard) = source(Some(ack_config)).await;
3150 let event_message = "raw event message";
3151 let opts = SendWithOpts {
3152 channel: Some(Channel::Header("guid")),
3153 forwarded_for: None,
3154 };
3155 let event_res = send_with_response(
3156 address,
3157 "services/collector/raw",
3158 event_message,
3159 TOKEN,
3160 &opts,
3161 )
3162 .await
3163 .json::<HecAckEventResponse>()
3164 .await
3165 .unwrap();
3166 _ = collect_n(source, 1).await;
3167
3168 let ack_message = serde_json::to_string(&HecAckStatusRequest {
3169 acks: vec![event_res.ack_id],
3170 })
3171 .unwrap();
3172 let ack_res = send_with_response(
3173 address,
3174 "services/collector/ack",
3175 ack_message.as_str(),
3176 TOKEN,
3177 &opts,
3178 )
3179 .await
3180 .json::<HecAckStatusResponse>()
3181 .await
3182 .unwrap();
3183 assert!(ack_res.acks.get(&event_res.ack_id).unwrap());
3184
3185 let ack_res = send_with_response(
3186 address,
3187 "services/collector/ack",
3188 ack_message.as_str(),
3189 TOKEN,
3190 &opts,
3191 )
3192 .await
3193 .json::<HecAckStatusResponse>()
3194 .await
3195 .unwrap();
3196 assert!(!ack_res.acks.get(&event_res.ack_id).unwrap());
3197 }
3198
3199 #[tokio::test]
3200 async fn ack_exceed_max_number_of_ack_channels() {
3201 let ack_config = HecAcknowledgementsConfig {
3202 enabled: Some(true),
3203 max_number_of_ack_channels: NonZeroU64::new(1).unwrap(),
3204 ..Default::default()
3205 };
3206
3207 let (_source, address, _guard) = source(Some(ack_config)).await;
3208 let mut opts = SendWithOpts {
3209 channel: Some(Channel::Header("guid")),
3210 forwarded_for: None,
3211 };
3212 assert_eq!(
3213 200,
3214 send_with(address, "services/collector/raw", "message", TOKEN, &opts).await
3215 );
3216
3217 opts.channel = Some(Channel::Header("other-guid"));
3218 assert_eq!(
3219 503,
3220 send_with(address, "services/collector/raw", "message", TOKEN, &opts).await
3221 );
3222 assert_eq!(
3223 503,
3224 send_with(
3225 address,
3226 "services/collector/event",
3227 r#"{"event":"first"}"#,
3228 TOKEN,
3229 &opts
3230 )
3231 .await
3232 );
3233 }
3234
3235 #[tokio::test]
3236 async fn ack_exceed_max_pending_acks_per_channel() {
3237 let ack_config = HecAcknowledgementsConfig {
3238 enabled: Some(true),
3239 max_pending_acks_per_channel: NonZeroU64::new(1).unwrap(),
3240 ..Default::default()
3241 };
3242
3243 let (source, address, _guard) = source(Some(ack_config)).await;
3244 let opts = SendWithOpts {
3245 channel: Some(Channel::Header("guid")),
3246 forwarded_for: None,
3247 };
3248 for _ in 0..5 {
3249 send_with(
3250 address,
3251 "services/collector/event",
3252 r#"{"event":"first"}"#,
3253 TOKEN,
3254 &opts,
3255 )
3256 .await;
3257 }
3258 for _ in 0..5 {
3259 send_with(address, "services/collector/raw", "message", TOKEN, &opts).await;
3260 }
3261 let event_res = send_with_response(
3262 address,
3263 "services/collector/event",
3264 r#"{"event":"this will be acked"}"#,
3265 TOKEN,
3266 &opts,
3267 )
3268 .await
3269 .json::<HecAckEventResponse>()
3270 .await
3271 .unwrap();
3272 _ = collect_n(source, 11).await;
3273
3274 let ack_message_dropped = serde_json::to_string(&HecAckStatusRequest {
3275 acks: (0..10).collect::<Vec<u64>>(),
3276 })
3277 .unwrap();
3278 let ack_res = send_with_response(
3279 address,
3280 "services/collector/ack",
3281 ack_message_dropped.as_str(),
3282 TOKEN,
3283 &opts,
3284 )
3285 .await
3286 .json::<HecAckStatusResponse>()
3287 .await
3288 .unwrap();
3289 assert!(ack_res.acks.values().all(|ack_status| !*ack_status));
3290
3291 let ack_message_acked = serde_json::to_string(&HecAckStatusRequest {
3292 acks: vec![event_res.ack_id],
3293 })
3294 .unwrap();
3295 let ack_res = send_with_response(
3296 address,
3297 "services/collector/ack",
3298 ack_message_acked.as_str(),
3299 TOKEN,
3300 &opts,
3301 )
3302 .await
3303 .json::<HecAckStatusResponse>()
3304 .await
3305 .unwrap();
3306 assert!(ack_res.acks.get(&event_res.ack_id).unwrap());
3307 }
3308
3309 #[tokio::test]
3310 async fn ack_service_accepts_parameterized_content_type() {
3311 let ack_config = HecAcknowledgementsConfig {
3312 enabled: Some(true),
3313 ..Default::default()
3314 };
3315 let (source, address, _guard) = source(Some(ack_config)).await;
3316 let opts = SendWithOpts {
3317 channel: Some(Channel::Header("guid")),
3318 forwarded_for: None,
3319 };
3320
3321 let event_res = send_with_response(
3322 address,
3323 "services/collector/event",
3324 r#"{"event":"param-test"}"#,
3325 TOKEN,
3326 &opts,
3327 )
3328 .await
3329 .json::<HecAckEventResponse>()
3330 .await
3331 .unwrap();
3332 let _ = collect_n(source, 1).await;
3333
3334 let body = serde_json::to_string(&HecAckStatusRequest {
3335 acks: vec![event_res.ack_id],
3336 })
3337 .unwrap();
3338
3339 let res = reqwest::Client::new()
3340 .post(format!("http://{address}/services/collector/ack"))
3341 .header("Authorization", format!("Splunk {TOKEN}"))
3342 .header("x-splunk-request-channel", "guid")
3343 .header("Content-Type", "application/json; some-random-text; hello")
3344 .body(body)
3345 .send()
3346 .await
3347 .unwrap();
3348
3349 assert_eq!(200, res.status().as_u16());
3350
3351 let _parsed: HecAckStatusResponse = res.json().await.unwrap();
3352 }
3353
3354 #[tokio::test]
3355 async fn event_service_acknowledgements_enabled_channel_required() {
3356 let message = r#"{"event":"first", "color": "blue"}"#;
3357 let ack_config = HecAcknowledgementsConfig {
3358 enabled: Some(true),
3359 ..Default::default()
3360 };
3361 let (_, address, _guard) = source(Some(ack_config)).await;
3362
3363 let opts = SendWithOpts {
3364 channel: None,
3365 forwarded_for: None,
3366 };
3367
3368 assert_eq!(
3369 400,
3370 send_with(address, "services/collector/event", message, TOKEN, &opts).await
3371 );
3372 }
3373
3374 #[tokio::test]
3375 async fn ack_service_acknowledgements_disabled() {
3376 let message = r#" {"acks":[0]} "#;
3377 let (_, address, _guard) = source(None).await;
3378
3379 let opts = SendWithOpts {
3380 channel: Some(Channel::Header("guid")),
3381 forwarded_for: None,
3382 };
3383
3384 assert_eq!(
3385 400,
3386 send_with(address, "services/collector/ack", message, TOKEN, &opts).await
3387 );
3388 }
3389
3390 async fn source_with_codec(
3391 event: CodecConfig,
3392 raw: CodecConfig,
3393 ) -> (
3394 impl Stream<Item = Event> + Unpin + use<>,
3395 SocketAddr,
3396 PortGuard,
3397 ) {
3398 let (sender, recv) = SourceSender::new_test_finalize(EventStatus::Delivered);
3399 let (_guard, address) = next_addr();
3400 let cx = SourceContext::new_test(sender, None);
3401 tokio::spawn(async move {
3402 SplunkConfig {
3403 address,
3404 token: Some(TOKEN.to_owned().into()),
3405 valid_tokens: None,
3406 tls: None,
3407 acknowledgements: Default::default(),
3408 store_hec_token: false,
3409 log_namespace: None,
3410 keepalive: Default::default(),
3411 event,
3412 raw,
3413 }
3414 .build(cx)
3415 .await
3416 .unwrap()
3417 .await
3418 .unwrap()
3419 });
3420 wait_for_tcp(address).await;
3421 (recv, address, _guard)
3422 }
3423
3424 fn codec_decoding(decoding: DeserializerConfig) -> CodecConfig {
3426 CodecConfig {
3427 framing: None,
3428 decoding: Some(decoding),
3429 }
3430 }
3431
3432 fn codec_full(
3434 framing: Option<FramingConfig>,
3435 decoding: Option<DeserializerConfig>,
3436 ) -> CodecConfig {
3437 CodecConfig { framing, decoding }
3438 }
3439
3440 #[tokio::test]
3441 async fn decoder_event_endpoint_json_string() {
3442 assert_source_compliance(&HTTP_PUSH_SOURCE_TAGS, async {
3443 let (source, address, _guard) = source_with_codec(
3444 codec_decoding(vector_lib::codecs::JsonDeserializerConfig::default().into()),
3445 CodecConfig::default(),
3446 )
3447 .await;
3448 let envelope =
3449 r#"{"event":"{\"foo\":\"bar\",\"n\":42}","host":"client-host","sourcetype":"my-app"}"#;
3450 assert_eq!(
3451 200,
3452 post(address, "services/collector/event", envelope).await
3453 );
3454
3455 let event = collect_n(source, 1).await.remove(0);
3456 let log = event.as_log();
3457 assert_eq!(log["foo"], "bar".into());
3458 assert_eq!(log["n"], 42.into());
3459 assert_eq!(
3460 log[log_schema().host_key().unwrap().to_string().as_str()],
3461 "client-host".into()
3462 );
3463 assert_eq!(log[&super::SOURCETYPE], "my-app".into());
3464 })
3465 .await;
3466 }
3467
3468 #[tokio::test]
3469 async fn decoder_event_endpoint_json_object_round_trip() {
3470 assert_source_compliance(&HTTP_PUSH_SOURCE_TAGS, async {
3471 let (source, address, _guard) = source_with_codec(
3472 codec_decoding(vector_lib::codecs::JsonDeserializerConfig::default().into()),
3473 CodecConfig::default(),
3474 )
3475 .await;
3476 let envelope = r#"{"event":{"foo":"bar","nested":{"k":1}},"host":"h"}"#;
3477 assert_eq!(
3478 200,
3479 post(address, "services/collector/event", envelope).await
3480 );
3481
3482 let event = collect_n(source, 1).await.remove(0);
3483 let log = event.as_log();
3484 assert_eq!(log["foo"], "bar".into());
3485 assert_eq!(*log.get(event_path!("nested", "k")).unwrap(), 1.into());
3486 assert_eq!(
3487 log[log_schema().host_key().unwrap().to_string().as_str()],
3488 "h".into()
3489 );
3490 })
3491 .await;
3492 }
3493
3494 #[tokio::test]
3495 async fn decoder_event_endpoint_all_envelope_fields_yield_to_decoder() {
3496 assert_source_compliance(&HTTP_PUSH_SOURCE_TAGS, async {
3501 let (source, address, _guard) = source_with_codec(
3502 codec_decoding(vector_lib::codecs::JsonDeserializerConfig::default().into()),
3503 CodecConfig::default(),
3504 )
3505 .await;
3506 let envelope = r#"{
3510 "event":"{\"splunk_channel\":\"decoder-channel\",\"splunk_index\":\"decoder-index\",\"splunk_source\":\"decoder-source\",\"splunk_sourcetype\":\"decoder-sourcetype\"}",
3511 "index":"envelope-index",
3512 "source":"envelope-source",
3513 "sourcetype":"envelope-sourcetype"
3514 }"#;
3515 assert_eq!(
3516 200,
3517 post(address, "services/collector/event", envelope).await
3518 );
3519
3520 let event = collect_n(source, 1).await.remove(0);
3521 let log = event.as_log();
3522 assert_eq!(log[&super::CHANNEL], "decoder-channel".into());
3523 assert_eq!(log[&super::INDEX], "decoder-index".into());
3524 assert_eq!(log[&super::SOURCE], "decoder-source".into());
3525 assert_eq!(log[&super::SOURCETYPE], "decoder-sourcetype".into());
3526 })
3527 .await;
3528 }
3529
3530 #[tokio::test]
3531 async fn decoder_event_endpoint_decoder_field_wins_over_envelope() {
3532 assert_source_compliance(&HTTP_PUSH_SOURCE_TAGS, async {
3533 let (source, address, _guard) = source_with_codec(
3534 codec_decoding(vector_lib::codecs::JsonDeserializerConfig::default().into()),
3535 CodecConfig::default(),
3536 )
3537 .await;
3538 let envelope = r#"{"event":"{\"host\":\"decoder-host\"}","host":"envelope-host"}"#;
3541 assert_eq!(
3542 200,
3543 post(address, "services/collector/event", envelope).await
3544 );
3545
3546 let event = collect_n(source, 1).await.remove(0);
3547 let log = event.as_log();
3548 assert_eq!(
3549 log[log_schema().host_key().unwrap().to_string().as_str()],
3550 "decoder-host".into()
3551 );
3552 })
3553 .await;
3554 }
3555
3556 #[tokio::test]
3557 async fn decoder_event_endpoint_decode_failure_returns_200() {
3558 let (_source, address, _guard) = source_with_codec(
3562 codec_decoding(vector_lib::codecs::JsonDeserializerConfig::default().into()),
3563 CodecConfig::default(),
3564 )
3565 .await;
3566 let envelope = r#"{"event":"not valid json {","host":"h"}"#;
3567 assert_eq!(
3568 200,
3569 post(address, "services/collector/event", envelope).await
3570 );
3571 }
3572
3573 #[tokio::test]
3574 async fn decoder_raw_endpoint_newline_delimited() {
3575 assert_source_compliance(&HTTP_PUSH_SOURCE_TAGS, async {
3576 let (source, address, _guard) = source_with_codec(
3577 CodecConfig::default(),
3578 codec_full(
3579 Some(FramingConfig::NewlineDelimited(Default::default())),
3580 Some(DeserializerConfig::Bytes),
3581 ),
3582 )
3583 .await;
3584 let body = "line1\nline2\nline3";
3585 assert_eq!(200, post(address, "services/collector/raw", body).await);
3586
3587 let events = collect_n(source, 3).await;
3588 assert_eq!(events.len(), 3);
3589 let messages: Vec<String> = events
3590 .iter()
3591 .map(|e| {
3592 e.as_log()[log_schema().message_key().unwrap().to_string()]
3593 .to_string_lossy()
3594 .into_owned()
3595 })
3596 .collect();
3597 assert!(messages.contains(&"line1".to_string()));
3598 assert!(messages.contains(&"line2".to_string()));
3599 assert!(messages.contains(&"line3".to_string()));
3600
3601 for event in &events {
3603 assert_eq!(event.as_log()[&super::CHANNEL], "channel".into());
3604 }
3605 })
3606 .await;
3607 }
3608
3609 #[tokio::test]
3610 async fn decoder_event_endpoint_envelope_without_time_has_fallback_timestamp() {
3611 assert_source_compliance(&HTTP_PUSH_SOURCE_TAGS, async {
3614 let (source, address, _guard) = source_with_codec(
3615 codec_decoding(vector_lib::codecs::JsonDeserializerConfig::default().into()),
3616 CodecConfig::default(),
3617 )
3618 .await;
3619 let envelope = r#"{"event":"{\"foo\":\"bar\"}"}"#;
3620 assert_eq!(
3621 200,
3622 post(address, "services/collector/event", envelope).await
3623 );
3624
3625 let event = collect_n(source, 1).await.remove(0);
3626 assert!(
3627 event.as_log().get_timestamp().is_some(),
3628 "decoded event from envelope without `time` field is missing a timestamp"
3629 );
3630 })
3631 .await;
3632 }
3633
3634 #[tokio::test]
3635 async fn decoder_independent_per_endpoint_codecs() {
3636 assert_source_compliance(&HTTP_PUSH_SOURCE_TAGS, async {
3641 let (source, address, _guard) = source_with_codec(
3642 codec_decoding(vector_lib::codecs::JsonDeserializerConfig::default().into()),
3643 codec_full(
3644 Some(FramingConfig::NewlineDelimited(Default::default())),
3645 Some(DeserializerConfig::Bytes),
3646 ),
3647 )
3648 .await;
3649
3650 assert_eq!(
3652 200,
3653 post(
3654 address,
3655 "services/collector/event",
3656 r#"{"event":"{\"foo\":\"bar\"}"}"#
3657 )
3658 .await
3659 );
3660 assert_eq!(
3662 200,
3663 post(address, "services/collector/raw", "a\nb\nc").await
3664 );
3665
3666 let events = collect_n(source, 4).await;
3667 assert_eq!(events.len(), 4);
3668
3669 let event_log = events
3671 .iter()
3672 .find(|e| e.as_log().contains(event_path!("foo")))
3673 .expect("expected /event request to produce a log with `foo` set");
3674 assert_eq!(event_log.as_log()["foo"], "bar".into());
3675
3676 let raw_messages: Vec<String> = events
3678 .iter()
3679 .filter(|e| !e.as_log().contains(event_path!("foo")))
3680 .map(|e| {
3681 e.as_log()[log_schema().message_key().unwrap().to_string()]
3682 .to_string_lossy()
3683 .into_owned()
3684 })
3685 .collect();
3686 assert_eq!(raw_messages.len(), 3);
3687 assert!(raw_messages.contains(&"a".to_string()));
3688 assert!(raw_messages.contains(&"b".to_string()));
3689 assert!(raw_messages.contains(&"c".to_string()));
3690 })
3691 .await;
3692 }
3693
3694 #[tokio::test]
3700 async fn decoder_vrl_reads_envelope_metadata() {
3701 assert_source_compliance(&HTTP_PUSH_SOURCE_TAGS, async {
3702 let vrl_source = r#"
3703 # Read envelope metadata injected before this VRL program runs.
3704 .envelope_host = string!(%splunk_hec.host)
3705 .envelope_sourcetype = string!(%splunk_hec.sourcetype)
3706
3707 # Decode the inner JSON payload (the bytes of the `event` string).
3708 . = merge!(parse_json!(string!(.message)), .)
3709 "#;
3710
3711 let event_codec = codec_decoding(
3712 DeserializerConfig::Vrl(VrlDeserializerConfig {
3713 vrl: VrlDeserializerOptions {
3714 source: vrl_source.into(),
3715 timezone: None,
3716 },
3717 }),
3718 );
3719
3720 let (source, address, _guard) =
3721 source_with_codec(event_codec, CodecConfig::default()).await;
3722
3723 let payload = r#"{"event":"{\"level\":\"info\",\"msg\":\"hello\"}","host":"splunk-host","sourcetype":"my-app"}"#;
3726 assert_eq!(
3727 200,
3728 post(address, "services/collector/event", payload).await
3729 );
3730
3731 let event = collect_n(source, 1).await.remove(0);
3732 let log = event.as_log();
3733
3734 assert_eq!(log["level"], "info".into());
3736 assert_eq!(log["msg"], "hello".into());
3737
3738 assert_eq!(log["envelope_host"], "splunk-host".into());
3740 assert_eq!(log["envelope_sourcetype"], "my-app".into());
3741
3742 assert_eq!(
3744 log[log_schema().host_key().unwrap().to_string().as_str()],
3745 "splunk-host".into()
3746 );
3747 assert_eq!(log[&super::SOURCETYPE], "my-app".into());
3748 })
3749 .await;
3750 }
3751
3752 #[tokio::test]
3753 async fn decoder_raw_endpoint_event_has_fallback_timestamp() {
3754 assert_source_compliance(&HTTP_PUSH_SOURCE_TAGS, async {
3757 let (source, address, _guard) = source_with_codec(
3758 CodecConfig::default(),
3759 codec_full(None, Some(DeserializerConfig::Bytes)),
3760 )
3761 .await;
3762 let body = "hello";
3763 assert_eq!(200, post(address, "services/collector/raw", body).await);
3764
3765 let event = collect_n(source, 1).await.remove(0);
3766 assert!(
3767 event.as_log().get_timestamp().is_some(),
3768 "decoded /raw event is missing a timestamp"
3769 );
3770 })
3771 .await;
3772 }
3773
3774 #[tokio::test]
3775 async fn decoder_raw_endpoint_empty_decode_does_not_ack() {
3776 let ack_config = HecAcknowledgementsConfig {
3781 enabled: Some(true),
3782 ..Default::default()
3783 };
3784 let (sender, _recv) = SourceSender::new_test_finalize(EventStatus::Delivered);
3785 let (_guard, address) = next_addr();
3786 let cx = SourceContext::new_test(sender, None);
3787 tokio::spawn(async move {
3788 SplunkConfig {
3789 address,
3790 token: Some(TOKEN.to_owned().into()),
3791 valid_tokens: None,
3792 tls: None,
3793 acknowledgements: ack_config,
3794 store_hec_token: false,
3795 log_namespace: None,
3796 keepalive: Default::default(),
3797 event: CodecConfig::default(),
3798 raw: codec_decoding(vector_lib::codecs::JsonDeserializerConfig::default().into()),
3799 }
3800 .build(cx)
3801 .await
3802 .unwrap()
3803 .await
3804 .unwrap()
3805 });
3806 wait_for_tcp(address).await;
3807
3808 let opts = SendWithOpts {
3809 channel: Some(Channel::Header("guid")),
3810 forwarded_for: None,
3811 };
3812 let body = "not json {";
3814 let response = send_with_response(address, "services/collector/raw", body, TOKEN, &opts)
3815 .await
3816 .json::<serde_json::Value>()
3817 .await
3818 .unwrap();
3819
3820 assert_eq!(response["code"].as_u64(), Some(0), "response: {response:?}");
3821 assert!(
3822 response.get("ackId").is_none(),
3823 "expected no ackId in response when decoder produced zero events, got: {response:?}"
3824 );
3825 }
3826
3827 #[tokio::test]
3828 async fn decoder_raw_endpoint_partial_decode_does_not_ack() {
3829 let ack_config = HecAcknowledgementsConfig {
3834 enabled: Some(true),
3835 ..Default::default()
3836 };
3837 let (sender, _recv) = SourceSender::new_test_finalize(EventStatus::Delivered);
3838 let (_guard, address) = next_addr();
3839 let cx = SourceContext::new_test(sender, None);
3840 tokio::spawn(async move {
3841 SplunkConfig {
3842 address,
3843 token: Some(TOKEN.to_owned().into()),
3844 valid_tokens: None,
3845 tls: None,
3846 acknowledgements: ack_config,
3847 store_hec_token: false,
3848 log_namespace: None,
3849 keepalive: Default::default(),
3850 event: CodecConfig::default(),
3851 raw: codec_full(
3852 Some(FramingConfig::NewlineDelimited(Default::default())),
3853 Some(vector_lib::codecs::JsonDeserializerConfig::default().into()),
3854 ),
3855 }
3856 .build(cx)
3857 .await
3858 .unwrap()
3859 .await
3860 .unwrap()
3861 });
3862 wait_for_tcp(address).await;
3863
3864 let opts = SendWithOpts {
3865 channel: Some(Channel::Header("guid")),
3866 forwarded_for: None,
3867 };
3868 let body = "{\"valid\":1}\nnot json\n{\"valid\":2}";
3870 let response = send_with_response(address, "services/collector/raw", body, TOKEN, &opts)
3871 .await
3872 .json::<serde_json::Value>()
3873 .await
3874 .unwrap();
3875
3876 assert_eq!(response["code"].as_u64(), Some(0), "response: {response:?}");
3877 assert!(
3878 response.get("ackId").is_none(),
3879 "expected no ackId when the decoder dropped a frame mid-request, got: {response:?}"
3880 );
3881 }
3882
3883 #[tokio::test]
3884 async fn decoder_event_endpoint_error_index_matches_envelope_not_fanout() {
3885 let (source, address, _guard) = source_with_codec(
3889 codec_full(
3890 Some(FramingConfig::NewlineDelimited(Default::default())),
3891 Some(DeserializerConfig::Bytes),
3892 ),
3893 CodecConfig::default(),
3894 )
3895 .await;
3896 let body = "{\"event\":\"a\\nb\\nc\"}{\"foo\":\"bar\"}";
3900
3901 let opts = SendWithOpts {
3902 channel: Some(Channel::Header("guid")),
3903 forwarded_for: None,
3904 };
3905 let response =
3906 send_with_response(address, "services/collector/event", body, TOKEN, &opts).await;
3907 let status = response.status();
3908 let body: serde_json::Value = response.json().await.unwrap();
3909
3910 assert_eq!(status.as_u16(), 400, "body: {body:?}");
3911 assert_eq!(
3912 body["invalid-event-number"].as_u64(),
3913 Some(1),
3914 "expected envelope index 1 (the failing envelope), not a fan-out event index. body: {body:?}"
3915 );
3916 let _ = collect_n(source, 3).await;
3918 }
3919
3920 #[test]
3921 fn output_schema_definition_with_decoder_vector_namespace() {
3922 let config = SplunkConfig {
3923 log_namespace: Some(true),
3924 event: codec_decoding(vector_lib::codecs::JsonDeserializerConfig::default().into()),
3925 ..Default::default()
3926 };
3927 let definition = config
3928 .outputs(LogNamespace::Vector)
3929 .remove(0)
3930 .schema_definition(true);
3931
3932 let expected_definition =
3937 Definition::new_with_default_metadata(Kind::json(), [LogNamespace::Vector])
3938 .with_meaning(OwnedTargetPath::event_root(), meaning::MESSAGE)
3939 .with_metadata_field(
3940 &owned_value_path!("vector", "source_type"),
3941 Kind::bytes(),
3942 None,
3943 )
3944 .with_metadata_field(
3945 &owned_value_path!("vector", "ingest_timestamp"),
3946 Kind::timestamp(),
3947 None,
3948 )
3949 .with_metadata_field(
3950 &owned_value_path!("splunk_hec", "host"),
3951 Kind::bytes(),
3952 Some("host"),
3953 )
3954 .with_metadata_field(
3955 &owned_value_path!("splunk_hec", "index"),
3956 Kind::bytes(),
3957 None,
3958 )
3959 .with_metadata_field(
3960 &owned_value_path!("splunk_hec", "source"),
3961 Kind::bytes(),
3962 Some("service"),
3963 )
3964 .with_metadata_field(
3965 &owned_value_path!("splunk_hec", "channel"),
3966 Kind::bytes(),
3967 None,
3968 )
3969 .with_metadata_field(
3970 &owned_value_path!("splunk_hec", "sourcetype"),
3971 Kind::bytes(),
3972 None,
3973 );
3974
3975 assert_eq!(definition, Some(expected_definition));
3976 }
3977
3978 #[test]
3979 fn output_schema_definition_vector_namespace() {
3980 let config = SplunkConfig {
3981 log_namespace: Some(true),
3982 ..Default::default()
3983 };
3984
3985 let definition = config
3986 .outputs(LogNamespace::Vector)
3987 .remove(0)
3988 .schema_definition(true);
3989
3990 let expected_definition = Definition::new_with_default_metadata(
3991 Kind::object(Collection::empty()).or_bytes(),
3992 [LogNamespace::Vector],
3993 )
3994 .with_meaning(OwnedTargetPath::event_root(), meaning::MESSAGE)
3995 .with_metadata_field(
3996 &owned_value_path!("vector", "source_type"),
3997 Kind::bytes(),
3998 None,
3999 )
4000 .with_metadata_field(
4001 &owned_value_path!("vector", "ingest_timestamp"),
4002 Kind::timestamp(),
4003 None,
4004 )
4005 .with_metadata_field(
4006 &owned_value_path!("splunk_hec", "host"),
4007 Kind::bytes(),
4008 Some("host"),
4009 )
4010 .with_metadata_field(
4011 &owned_value_path!("splunk_hec", "index"),
4012 Kind::bytes(),
4013 None,
4014 )
4015 .with_metadata_field(
4016 &owned_value_path!("splunk_hec", "source"),
4017 Kind::bytes(),
4018 Some("service"),
4019 )
4020 .with_metadata_field(
4021 &owned_value_path!("splunk_hec", "channel"),
4022 Kind::bytes(),
4023 None,
4024 )
4025 .with_metadata_field(
4026 &owned_value_path!("splunk_hec", "sourcetype"),
4027 Kind::bytes(),
4028 None,
4029 );
4030
4031 assert_eq!(definition, Some(expected_definition));
4032 }
4033
4034 #[test]
4035 fn output_schema_definition_legacy_namespace() {
4036 let config = SplunkConfig::default();
4037 let definitions = config
4038 .outputs(LogNamespace::Legacy)
4039 .remove(0)
4040 .schema_definition(true);
4041
4042 let expected_definition = Definition::new_with_default_metadata(
4043 Kind::object(Collection::empty()),
4044 [LogNamespace::Legacy],
4045 )
4046 .with_event_field(&owned_value_path!("host"), Kind::bytes(), Some("host"))
4047 .with_event_field(
4048 &owned_value_path!("message"),
4049 Kind::bytes().or_undefined(),
4050 Some("message"),
4051 )
4052 .with_event_field(
4053 &owned_value_path!("line"),
4054 Kind::array(Collection::empty())
4055 .or_object(Collection::empty())
4056 .or_undefined(),
4057 None,
4058 )
4059 .with_event_field(&owned_value_path!("source_type"), Kind::bytes(), None)
4060 .with_event_field(&owned_value_path!("splunk_channel"), Kind::bytes(), None)
4061 .with_event_field(&owned_value_path!("splunk_index"), Kind::bytes(), None)
4062 .with_event_field(
4063 &owned_value_path!("splunk_source"),
4064 Kind::bytes(),
4065 Some("service"),
4066 )
4067 .with_event_field(&owned_value_path!("splunk_sourcetype"), Kind::bytes(), None)
4068 .with_event_field(&owned_value_path!("timestamp"), Kind::timestamp(), None);
4069
4070 assert_eq!(definitions, Some(expected_definition));
4071 }
4072
4073 impl ValidatableComponent for SplunkConfig {
4074 fn validation_configuration() -> ValidationConfiguration {
4075 let config = Self {
4076 address: default_socket_address(),
4077 ..Default::default()
4078 };
4079
4080 let listen_addr_http = format!("http://{}/services/collector/event", config.address);
4081 let uri = Uri::try_from(&listen_addr_http).expect("should not fail to parse URI");
4082
4083 let log_namespace: LogNamespace = config.log_namespace.unwrap_or_default().into();
4084 let framing = BytesDecoderConfig::new().into();
4085 let decoding = DeserializerConfig::Json(Default::default());
4086
4087 let external_resource = ExternalResource::new(
4088 ResourceDirection::Push,
4089 HttpResourceConfig::from_parts(uri, None).with_headers(HashMap::from([(
4090 X_SPLUNK_REQUEST_CHANNEL.to_string(),
4091 "channel".to_string(),
4092 )])),
4093 DecodingConfig::new(framing, decoding, false.into()),
4094 );
4095
4096 ValidationConfiguration::from_source(
4097 Self::NAME,
4098 log_namespace,
4099 vec![ComponentTestCaseConfig::from_source(
4100 config,
4101 None,
4102 Some(external_resource),
4103 )],
4104 )
4105 }
4106 }
4107
4108 register_validatable_component!(SplunkConfig);
4109}