diff --git a/bottlecap/Cargo.lock b/bottlecap/Cargo.lock index 3eacd5c28..285e4b0c5 100644 --- a/bottlecap/Cargo.lock +++ b/bottlecap/Cargo.lock @@ -782,7 +782,7 @@ dependencies = [ [[package]] name = "datadog-agent-config" version = "0.1.0" -source = "git+https://github.com/DataDog/serverless-components?rev=bb4dedeee20b949db3143c05e5a779b843a8a484#bb4dedeee20b949db3143c05e5a779b843a8a484" +source = "git+https://github.com/DataDog/serverless-components?rev=8ce37eb029410b7cf30847376772e3af6baa5f5c#8ce37eb029410b7cf30847376772e3af6baa5f5c" dependencies = [ "datadog-opentelemetry", "dogstatsd", @@ -800,7 +800,7 @@ dependencies = [ [[package]] name = "datadog-fips" version = "0.1.0" -source = "git+https://github.com/DataDog/serverless-components?rev=bb4dedeee20b949db3143c05e5a779b843a8a484#bb4dedeee20b949db3143c05e5a779b843a8a484" +source = "git+https://github.com/DataDog/serverless-components?rev=8ce37eb029410b7cf30847376772e3af6baa5f5c#8ce37eb029410b7cf30847376772e3af6baa5f5c" dependencies = [ "reqwest", "rustls", @@ -942,7 +942,7 @@ dependencies = [ [[package]] name = "dogstatsd" version = "0.1.0" -source = "git+https://github.com/DataDog/serverless-components?rev=bb4dedeee20b949db3143c05e5a779b843a8a484#bb4dedeee20b949db3143c05e5a779b843a8a484" +source = "git+https://github.com/DataDog/serverless-components?rev=8ce37eb029410b7cf30847376772e3af6baa5f5c#8ce37eb029410b7cf30847376772e3af6baa5f5c" dependencies = [ "datadog-protos", "ddsketch-agent", @@ -1072,12 +1072,6 @@ version = "0.4.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0ce7134b9999ecaf8bcd65542e436736ef32ddca1b3e06094cb6ec5755203b80" -[[package]] -name = "fixedbitset" -version = "0.5.7" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1d674e81391d1e1ab681a28d99df07927c6d4aa5b027d7da16ba32d1d21ecd99" - [[package]] name = "flate2" version = "1.1.9" @@ -1841,7 +1835,7 @@ dependencies = [ "ena", "itertools 0.11.0", "lalrpop-util", - "petgraph 0.6.5", + "petgraph", "pico-args", "regex", "regex-syntax", @@ -2654,17 +2648,7 @@ version = "0.6.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b4c5cc86750666a3ed20bdaf5ca2a0344f9c67674cae0515bec2da16fbaa47db" dependencies = [ - "fixedbitset 0.4.2", - "indexmap", -] - -[[package]] -name = "petgraph" -version = "0.7.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3672b37090dbd86368a4145bc067582552b29c27377cad4e0a306c97f9bd7772" -dependencies = [ - "fixedbitset 0.5.7", + "fixedbitset", "indexmap", ] @@ -2864,7 +2848,7 @@ dependencies = [ "log", "multimap", "once_cell", - "petgraph 0.7.1", + "petgraph", "prettyplease", "prost 0.13.5", "prost-types 0.13.5", diff --git a/bottlecap/Cargo.toml b/bottlecap/Cargo.toml index b3ef5c58d..714afa77f 100644 --- a/bottlecap/Cargo.toml +++ b/bottlecap/Cargo.toml @@ -82,9 +82,9 @@ libdd-trace-normalization = { git = "https://github.com/DataDog/libdatadog", rev libdd-trace-obfuscation = { git = "https://github.com/DataDog/libdatadog", rev = "48da0d82cb32b43d4cdece35b794c9bcbc275a03", default-features = false } libdd-trace-stats = { git = "https://github.com/DataDog/libdatadog", rev = "48da0d82cb32b43d4cdece35b794c9bcbc275a03", default-features = false } datadog-opentelemetry = { git = "https://github.com/DataDog/dd-trace-rs", rev = "f51cefc4ad24bec81b38fb2f36b1ed93f21ae913", default-features = false } -dogstatsd = { git = "https://github.com/DataDog/serverless-components", rev = "bb4dedeee20b949db3143c05e5a779b843a8a484", default-features = false } -datadog-fips = { git = "https://github.com/DataDog/serverless-components", rev = "bb4dedeee20b949db3143c05e5a779b843a8a484", default-features = false } -datadog-agent-config = { git = "https://github.com/DataDog/serverless-components", rev = "bb4dedeee20b949db3143c05e5a779b843a8a484", default-features = false } +dogstatsd = { git = "https://github.com/DataDog/serverless-components", rev = "8ce37eb029410b7cf30847376772e3af6baa5f5c", default-features = false } +datadog-fips = { git = "https://github.com/DataDog/serverless-components", rev = "8ce37eb029410b7cf30847376772e3af6baa5f5c", default-features = false } +datadog-agent-config = { git = "https://github.com/DataDog/serverless-components", rev = "8ce37eb029410b7cf30847376772e3af6baa5f5c", default-features = false } libddwaf = { version = "1.28.1", git = "https://github.com/DataDog/libddwaf-rust", rev = "d1534a158d976bd4f747bf9fcc58e0712d2d17fc", default-features = false, features = ["serde"] } [dev-dependencies] diff --git a/bottlecap/src/config/mod.rs b/bottlecap/src/config/mod.rs index e9ce7f331..db0a1f192 100644 --- a/bottlecap/src/config/mod.rs +++ b/bottlecap/src/config/mod.rs @@ -45,7 +45,7 @@ use datadog_agent_config::{ deserialize_optional_duration_from_microseconds as deser_dur_micros, deserialize_optional_duration_from_seconds as deser_dur_secs, deserialize_optional_duration_from_seconds_ignore_zero as deser_dur_secs_ignore_zero, - deserialize_optional_string as deser_opt_str, deserialize_string_or_int as deser_str_or_int, + deserialize_optional_string as deser_opt_str, flush_strategy::FlushStrategy as UpstreamFlushStrategy, }; @@ -68,7 +68,6 @@ pub struct LambdaConfig { pub lambda_extension_compute_stats: bool, pub span_dedup_timeout: Option, pub api_key_secret_reload_interval: Option, - pub dd_org_uuid: String, pub serverless_appsec_enabled: bool, pub appsec_rules: Option, pub appsec_waf_timeout: Duration, @@ -98,7 +97,6 @@ impl Default for LambdaConfig { lambda_extension_compute_stats: false, span_dedup_timeout: None, api_key_secret_reload_interval: None, - dd_org_uuid: String::new(), serverless_appsec_enabled: false, appsec_rules: None, appsec_waf_timeout: Duration::from_millis(5), @@ -126,11 +124,17 @@ pub struct LambdaConfigSource { #[serde(deserialize_with = "deser_opt_str")] pub api_key_ssm_arn: Option, - /// `DD_SERVERLESS_LOGS_ENABLED` — primary toggle for Lambda log shipping. + /// `DD_SERVERLESS_LOGS_ENABLED` — Lambda-specific log toggle, kept for + /// backwards compatibility. Defaults to `true` (the Lambda extension's + /// historical behavior). #[serde(deserialize_with = "deser_opt_bool")] pub serverless_logs_enabled: Option, - /// `DD_LOGS_ENABLED` — alias for `serverless_logs_enabled`; OR-merged so - /// either being `true` turns logs on. See `merge_from` below. + /// `DD_LOGS_ENABLED` — deserialized here a second time (the canonical + /// `Config::logs_enabled` upstream field is also populated by the upstream + /// env/yaml parsing) because lambda's default for logs is `true` while + /// upstream's is `false`. Keeping the alias as `Option` lets + /// `merge_from` detect "was it explicitly set?" and OR-merge it into + /// `serverless_logs_enabled` — that is the field lambda call sites read. #[serde(deserialize_with = "deser_opt_bool")] pub logs_enabled: Option, @@ -154,12 +158,6 @@ pub struct LambdaConfigSource { #[serde(deserialize_with = "deser_dur_secs_ignore_zero")] pub api_key_secret_reload_interval: Option, - /// `DD_ORG_UUID` — when set, delegated auth is auto-enabled. The source - /// field is `org_uuid` (matching the env var) and merges into the - /// `dd_org_uuid` config field. - #[serde(deserialize_with = "deser_str_or_int")] - pub org_uuid: Option, - #[serde(deserialize_with = "deser_opt_bool")] pub serverless_appsec_enabled: Option, #[serde(deserialize_with = "deser_opt_str")] @@ -210,17 +208,20 @@ impl DatadogConfigExtension for LambdaConfig { option: [span_dedup_timeout, api_key_secret_reload_interval, appsec_rules], ); - // OR-merge serverless_logs_enabled with the logs_enabled alias. Either - // env var set to `true` enables logs; if both are absent the default - // (true) is preserved. + // Preserve legacy OR-merge semantics: when either env var is + // explicitly set, the resolved value is the OR of the two (unset + // counts as false for the OR). When neither is set, the default + // (true) is preserved. This invariant — in particular that setting + // only DD_LOGS_ENABLED=false disables logs — predates upstream + // owning `logs_enabled` and must be kept. The duplicate parse of + // DD_LOGS_ENABLED (once upstream, once here via the alias) is + // intentional: upstream populates `config.logs_enabled` for any + // non-lambda consumer, while this branch keeps the lambda contract. if source.serverless_logs_enabled.is_some() || source.logs_enabled.is_some() { self.serverless_logs_enabled = source.serverless_logs_enabled.unwrap_or(false) || source.logs_enabled.unwrap_or(false); } - // org_uuid (source) → dd_org_uuid (config) - datadog_agent_config::merge_string!(self, dd_org_uuid, source, org_uuid); - // lambda_customer_metrics_exclude_tags (source) → custom_metrics_exclude_tags (config) if !source.lambda_customer_metrics_exclude_tags.is_empty() { self.custom_metrics_exclude_tags @@ -316,6 +317,14 @@ mod lambda_config_tests { } // ---- serverless_logs_enabled with OR-merge alias ---- + // + // The legacy contract: DD_SERVERLESS_LOGS_ENABLED and DD_LOGS_ENABLED are + // OR-merged into config.ext.serverless_logs_enabled. The default (true) + // is preserved iff neither env var was explicitly set; otherwise the + // resolved value is the OR of the two (unset counts as false). The + // upstream `Config::logs_enabled` field is also populated independently + // (sourced by the upstream env/yaml parsing), but lambda call sites that + // gate on log shipping continue to use serverless_logs_enabled. #[test] fn serverless_logs_enabled_defaults_true() { @@ -343,7 +352,7 @@ mod lambda_config_tests { } #[test] - fn logs_enabled_alias_only() { + fn logs_enabled_alias_only_true() { let config = load(|jail| { jail.set_env("DD_LOGS_ENABLED", "true"); Ok(()) @@ -351,6 +360,18 @@ mod lambda_config_tests { assert!(config.ext.serverless_logs_enabled); } + #[test] + fn logs_enabled_alias_only_false_overrides_default() { + // Setting only DD_LOGS_ENABLED=false must disable logs, overriding + // the default-true. This is the legacy behavior that the alias-only + // entry into the OR-merge guards. + let config = load(|jail| { + jail.set_env("DD_LOGS_ENABLED", "false"); + Ok(()) + }); + assert!(!config.ext.serverless_logs_enabled); + } + #[test] fn serverless_logs_disabled_when_both_false() { let config = load(|jail| { @@ -370,6 +391,18 @@ mod lambda_config_tests { assert!(!config.ext.serverless_logs_enabled); } + #[test] + fn dd_logs_enabled_also_populates_upstream_field() { + // The upstream Config::logs_enabled field is wired through the + // upstream env parsing independently of the lambda alias. Lambda + // doesn't read this field, but other consumers of the same crate do. + let config = load(|jail| { + jail.set_env("DD_LOGS_ENABLED", "true"); + Ok(()) + }); + assert!(config.logs_enabled); + } + // ---- FlushStrategy ---- #[test] @@ -598,10 +631,7 @@ mod lambda_config_tests { jail.set_env("DD_ORG_UUID", "00000000-1111-2222-3333-444444444444"); Ok(()) }); - assert_eq!( - config.ext.dd_org_uuid, - "00000000-1111-2222-3333-444444444444" - ); + assert_eq!(config.dd_org_uuid, "00000000-1111-2222-3333-444444444444"); } #[test] @@ -615,10 +645,7 @@ mod lambda_config_tests { )?; Ok(()) }); - assert_eq!( - config.ext.dd_org_uuid, - "00000000-1111-2222-3333-444444444444" - ); + assert_eq!(config.dd_org_uuid, "00000000-1111-2222-3333-444444444444"); } #[test] diff --git a/bottlecap/src/secrets/decrypt.rs b/bottlecap/src/secrets/decrypt.rs index cde54a56a..23e4be81c 100644 --- a/bottlecap/src/secrets/decrypt.rs +++ b/bottlecap/src/secrets/decrypt.rs @@ -26,7 +26,7 @@ pub async fn resolve_secrets( let api_key_candidate = if !config.ext.api_key_secret_arn.is_empty() || !config.ext.kms_api_key.is_empty() || !config.ext.api_key_ssm_arn.is_empty() - || !config.ext.dd_org_uuid.is_empty() + || !config.dd_org_uuid.is_empty() { let before_decrypt = Instant::now(); @@ -48,7 +48,7 @@ pub async fn resolve_secrets( let aws_credentials = get_aws_credentials(&client).await?; - let decrypted_key = if !config.ext.dd_org_uuid.is_empty() { + let decrypted_key = if !config.dd_org_uuid.is_empty() { delegated_auth::get_delegated_api_key( &config, &aws_config, diff --git a/bottlecap/src/secrets/delegated_auth/client.rs b/bottlecap/src/secrets/delegated_auth/client.rs index 29ea70a54..fa56ba1fd 100644 --- a/bottlecap/src/secrets/delegated_auth/client.rs +++ b/bottlecap/src/secrets/delegated_auth/client.rs @@ -16,7 +16,7 @@ const INTAKE_KEY_ENDPOINT: &str = "/api/v2/intake-key"; /// 3. Returns the managed API key /// /// # Arguments -/// * `config` - The extension configuration containing site and `org_uuid` +/// * `config` - The agent configuration containing `site` and `dd_org_uuid` /// * `aws_config` - The AWS configuration containing region /// * `client` - A pre-built `reqwest::Client` to use for the request. The client is built /// with `create_reqwest_client_builder()` which respects proxy configuration via @@ -35,7 +35,7 @@ pub async fn get_delegated_api_key( ) -> Result> { debug!("Attempting to get API key via delegated auth"); - let proof = generate_auth_proof(aws_credentials, &aws_config.region, &config.ext.dd_org_uuid)?; + let proof = generate_auth_proof(aws_credentials, &aws_config.region, &config.dd_org_uuid)?; let url = get_api_endpoint(&config.site); debug!("Requesting delegated API key from: {}", url);