Skip to content

Commit 4127fa3

Browse files
committed
allow metadata enrichment for metrics and traces
1 parent 7fb22d0 commit 4127fa3

8 files changed

Lines changed: 159 additions & 21 deletions

File tree

src/common/http/server_auth.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -57,7 +57,7 @@ pub enum HttpServerAuthConfig {
5757
///
5858
/// Takes in request and validates it using VRL code. The VRL program must return a boolean.
5959
/// Metadata fields written via `%field = value` in the VRL program are extracted and injected
60-
/// into every authenticated event.
60+
/// into the log's body if log namespacing is enabled.
6161
Custom {
6262
/// The VRL boolean expression. May write `%field = value` to enrich events.
6363
source: String,

src/sources/http_server.rs

Lines changed: 152 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -220,9 +220,16 @@ impl SimpleHttpConfig {
220220
)
221221
.with_standard_vector_source_metadata();
222222

223-
// for metadata that is added to the events dynamically from config options
223+
// For metadata that is added to the events dynamically from config options.
224224
if log_namespace == LogNamespace::Legacy {
225-
schema_definition = schema_definition.unknown_fields(Kind::bytes());
225+
// Custom auth programs can inject any VRL value, not just bytes; widen the unknown
226+
// field kind accordingly so schema-aware downstream components don't reject events.
227+
let unknown_kind = if matches!(self.auth, Some(HttpServerAuthConfig::Custom { .. })) {
228+
Kind::any()
229+
} else {
230+
Kind::bytes()
231+
};
232+
schema_definition = schema_definition.unknown_fields(unknown_kind);
226233
}
227234

228235
schema_definition
@@ -426,6 +433,12 @@ struct SimpleHttpSource {
426433
log_namespace: LogNamespace,
427434
}
428435

436+
/// Built-in `http_server` metadata keys written by `enrich_events` with a trusted value.
437+
/// Auth enrichment must not overwrite these; all other keys are auth-derived and may override
438+
/// client-supplied metadata from decoded events (e.g. a native-codec request).
439+
const RESERVED_HTTP_SERVER_METADATA_KEYS: &[&str] =
440+
&["path", "host", "headers", "query_parameters"];
441+
429442
impl HttpSource for SimpleHttpSource {
430443
/// Enriches the log events with metadata for the `request_path` and for each of the headers.
431444
/// Non-log events are skipped.
@@ -529,23 +542,43 @@ impl HttpSource for SimpleHttpSource {
529542
/// Both namespaces use insert-if-empty semantics so auth enrichment never
530543
/// overwrites built-in source metadata (`path`, `host`, `headers`, …) that
531544
/// `enrich_events` already populated.
532-
/// Legacy namespace: inserted into the event body.
533-
/// Vector namespace: inserted into event metadata under `http_server.<field>`.
545+
/// Vector namespace: inserted into event metadata under `http_server.<field>` for
546+
/// all event types (Log, Metric, Trace).
547+
/// Legacy namespace: inserted into the Log event body only (Metric/Trace are skipped).
534548
fn inject_auth_enrichment(&self, events: &mut [Event], enrichment: ObjectMap) {
535549
for event in events.iter_mut() {
536-
if let Event::Log(log) = event {
537-
for (key, value) in &enrichment {
538-
match self.log_namespace {
539-
LogNamespace::Vector => {
540-
log.try_insert(
541-
(
542-
PathPrefix::Metadata,
543-
path!(SimpleHttpConfig::NAME).concat(path!(key.as_str())),
544-
),
550+
match self.log_namespace {
551+
LogNamespace::Vector => {
552+
// metadata_mut() dispatches to Log, Metric, and Trace so every
553+
// decoded event type receives the auth enrichment fields.
554+
let meta = event.metadata_mut().value_mut();
555+
for (key, value) in &enrichment {
556+
let key_str = key.as_str();
557+
if RESERVED_HTTP_SERVER_METADATA_KEYS.contains(&key_str) {
558+
// Built-in source key: skip if already written by enrich_events.
559+
if meta
560+
.get(path!(SimpleHttpConfig::NAME).concat(path!(key_str)))
561+
.is_none()
562+
{
563+
meta.insert(
564+
path!(SimpleHttpConfig::NAME).concat(path!(key_str)),
565+
value.clone(),
566+
);
567+
}
568+
} else {
569+
// Auth-derived key: overwrite any client-supplied value so
570+
// clients cannot spoof fields the auth program sets.
571+
meta.insert(
572+
path!(SimpleHttpConfig::NAME).concat(path!(key_str)),
545573
value.clone(),
546574
);
547575
}
548-
LogNamespace::Legacy => {
576+
}
577+
}
578+
LogNamespace::Legacy => {
579+
// Legacy enrichment targets the event body; only Log events have one.
580+
if let Event::Log(log) = event {
581+
for (key, value) in &enrichment {
549582
log.try_insert((PathPrefix::Event, path!(key.as_str())), value.clone());
550583
}
551584
}
@@ -1824,6 +1857,111 @@ mod tests {
18241857
);
18251858
}
18261859

1860+
#[test]
1861+
fn inject_auth_enrichment_overwrites_client_supplied_metadata_in_vector_namespace() {
1862+
use crate::{codecs::DecodingConfig, sources::util::HttpSource as _};
1863+
use vector_lib::codecs::BytesDeserializerConfig;
1864+
use vrl::value::KeyString;
1865+
1866+
let decoder = DecodingConfig::new(
1867+
BytesDecoderConfig::new().into(),
1868+
BytesDeserializerConfig::new().into(),
1869+
LogNamespace::Vector,
1870+
)
1871+
.build()
1872+
.unwrap()
1873+
.with_log_namespace(LogNamespace::Vector);
1874+
1875+
let source = super::SimpleHttpSource {
1876+
headers: vec![],
1877+
query_parameters: vec![],
1878+
path_key: OptionalValuePath::none(),
1879+
host_key: OptionalValuePath::none(),
1880+
decoder,
1881+
log_namespace: LogNamespace::Vector,
1882+
};
1883+
1884+
let mut log = LogEvent::default();
1885+
// Simulate a client pre-populating an auth-derived key (e.g. via native codec).
1886+
log.insert(
1887+
(
1888+
PathPrefix::Metadata,
1889+
path!(SimpleHttpConfig::NAME).concat(path!("tenant_id")),
1890+
),
1891+
"attacker",
1892+
);
1893+
1894+
let mut events = vec![Event::Log(log)];
1895+
let mut enrichment = ObjectMap::new();
1896+
enrichment.insert(KeyString::from("tenant_id"), Value::from("trusted"));
1897+
1898+
source.inject_auth_enrichment(&mut events, enrichment);
1899+
1900+
let Event::Log(log) = &events[0] else {
1901+
panic!("expected log event");
1902+
};
1903+
assert_eq!(
1904+
log.get((
1905+
PathPrefix::Metadata,
1906+
path!(SimpleHttpConfig::NAME).concat(path!("tenant_id")),
1907+
)),
1908+
Some(&Value::from("trusted")),
1909+
"auth enrichment must overwrite client-supplied metadata for auth-derived keys"
1910+
);
1911+
}
1912+
1913+
#[test]
1914+
fn inject_auth_enrichment_applies_to_non_log_events_in_vector_namespace() {
1915+
use crate::{codecs::DecodingConfig, sources::util::HttpSource as _};
1916+
use vector_lib::{
1917+
codecs::BytesDeserializerConfig,
1918+
event::{Metric, MetricKind, MetricValue},
1919+
};
1920+
use vrl::value::KeyString;
1921+
1922+
let decoder = DecodingConfig::new(
1923+
BytesDecoderConfig::new().into(),
1924+
BytesDeserializerConfig::new().into(),
1925+
LogNamespace::Vector,
1926+
)
1927+
.build()
1928+
.unwrap()
1929+
.with_log_namespace(LogNamespace::Vector);
1930+
1931+
let source = super::SimpleHttpSource {
1932+
headers: vec![],
1933+
query_parameters: vec![],
1934+
path_key: OptionalValuePath::none(),
1935+
host_key: OptionalValuePath::none(),
1936+
decoder,
1937+
log_namespace: LogNamespace::Vector,
1938+
};
1939+
1940+
let metric = Metric::new(
1941+
"requests",
1942+
MetricKind::Incremental,
1943+
MetricValue::Counter { value: 1.0 },
1944+
);
1945+
let mut events = vec![Event::Metric(metric)];
1946+
1947+
let mut enrichment = ObjectMap::new();
1948+
enrichment.insert(KeyString::from("tenant_id"), Value::from("t-456"));
1949+
1950+
source.inject_auth_enrichment(&mut events, enrichment);
1951+
1952+
let Event::Metric(metric) = &events[0] else {
1953+
panic!("expected metric event");
1954+
};
1955+
assert_eq!(
1956+
metric
1957+
.metadata()
1958+
.value()
1959+
.get(path!(SimpleHttpConfig::NAME).concat(path!("tenant_id")),),
1960+
Some(&Value::from("t-456")),
1961+
"auth enrichment must be written to non-log event metadata"
1962+
);
1963+
}
1964+
18271965
impl ValidatableComponent for SimpleHttpConfig {
18281966
fn validation_configuration() -> ValidationConfiguration {
18291967
let config = Self {

website/cue/reference/components/sinks/generated/websocket_server.cue

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -73,7 +73,7 @@ generated: components: sinks: websocket_server: configuration: {
7373
7474
Takes in request and validates it using VRL code. The VRL program must return a boolean.
7575
Metadata fields written via `%field = value` in the VRL program are extracted and injected
76-
into every authenticated event.
76+
into the log's body if log namespacing is enabled.
7777
"""
7878
}
7979
}

website/cue/reference/components/sources/generated/heroku_logs.cue

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -64,7 +64,7 @@ generated: components: sources: heroku_logs: configuration: {
6464
6565
Takes in request and validates it using VRL code. The VRL program must return a boolean.
6666
Metadata fields written via `%field = value` in the VRL program are extracted and injected
67-
into every authenticated event.
67+
into the log's body if log namespacing is enabled.
6868
"""
6969
}
7070
}

website/cue/reference/components/sources/generated/http.cue

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -68,7 +68,7 @@ generated: components: sources: http: configuration: {
6868
6969
Takes in request and validates it using VRL code. The VRL program must return a boolean.
7070
Metadata fields written via `%field = value` in the VRL program are extracted and injected
71-
into every authenticated event.
71+
into the log's body if log namespacing is enabled.
7272
"""
7373
}
7474
}

website/cue/reference/components/sources/generated/http_server.cue

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -68,7 +68,7 @@ generated: components: sources: http_server: configuration: {
6868
6969
Takes in request and validates it using VRL code. The VRL program must return a boolean.
7070
Metadata fields written via `%field = value` in the VRL program are extracted and injected
71-
into every authenticated event.
71+
into the log's body if log namespacing is enabled.
7272
"""
7373
}
7474
}

website/cue/reference/components/sources/generated/prometheus_pushgateway.cue

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -78,7 +78,7 @@ generated: components: sources: prometheus_pushgateway: configuration: {
7878
7979
Takes in request and validates it using VRL code. The VRL program must return a boolean.
8080
Metadata fields written via `%field = value` in the VRL program are extracted and injected
81-
into every authenticated event.
81+
into the log's body if log namespacing is enabled.
8282
"""
8383
}
8484
}

website/cue/reference/components/sources/generated/prometheus_remote_write.cue

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -68,7 +68,7 @@ generated: components: sources: prometheus_remote_write: configuration: {
6868
6969
Takes in request and validates it using VRL code. The VRL program must return a boolean.
7070
Metadata fields written via `%field = value` in the VRL program are extracted and injected
71-
into every authenticated event.
71+
into the log's body if log namespacing is enabled.
7272
"""
7373
}
7474
}

0 commit comments

Comments
 (0)