Skip to content

Commit f0bb0c8

Browse files
committed
helper-rust: avoid spurious service recreations with remote config disabled
If remote config was disabled, client_init would inform the helper that remote config is disabled. However, subsequent config_syncs would neverthless send a remote config path (config_sync doesn't separately send whether remote config is enabled). The path being non-empty the helper-rust would deem remote config enabled and create a new service. In practice, this did not actually result in remote config being enabled, because sidecar being informed through config that it's disabled, the shared memory path would not be written to. Neverthless, this resulted in the creation of extra Service's and extra initializations of the waf, reflected in a higher number of waf.init metrics.
1 parent 4bfc3ac commit f0bb0c8

2 files changed

Lines changed: 58 additions & 12 deletions

File tree

appsec/helper-rust/src/client/protocol.rs

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -103,18 +103,19 @@ pub struct RemoteConfigSettings {
103103

104104
impl RemoteConfigSettings {
105105
pub fn new(enabled: bool, shmem_path: PathBuf) -> Self {
106+
// we allow enabled with an empty path -- a config_sync can then fix the path
106107
Self {
107108
enabled,
108-
shmem_path,
109+
shmem_path: if enabled { shmem_path } else { PathBuf::new() },
109110
}
110111
}
111112

112-
fn enabled(&self) -> bool {
113-
self.enabled && !self.shmem_path.as_os_str().is_empty()
113+
pub fn can_be_enabled(&self) -> bool {
114+
self.enabled
114115
}
115116

116117
pub fn shmem_path(&self) -> Option<&Path> {
117-
if self.enabled() {
118+
if self.enabled && !self.shmem_path.as_os_str().is_empty() {
118119
Some(&self.shmem_path)
119120
} else {
120121
None

appsec/helper-rust/src/service.rs

Lines changed: 53 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -671,14 +671,17 @@ impl ServiceFixedConfig {
671671
None
672672
};
673673

674-
if maybe_new_rem_cfg_path != self.rem_cfg_settings.shmem_path()
675-
|| args.telemetry_settings != self.telemetry_settings
676-
{
674+
// config_sync sends paths even if RC is disabled. We reject those
675+
// if we got enabled=false in client_init.
676+
let rem_cfg_path_changed = self.rem_cfg_settings.can_be_enabled()
677+
&& maybe_new_rem_cfg_path != self.rem_cfg_settings.shmem_path();
678+
679+
if rem_cfg_path_changed || args.telemetry_settings != self.telemetry_settings {
677680
let mut new_cfg = self.clone();
678-
new_cfg.rem_cfg_settings = protocol::RemoteConfigSettings::new(
679-
!new_rem_cfg_path.as_os_str().is_empty(),
680-
new_rem_cfg_path,
681-
);
681+
if rem_cfg_path_changed {
682+
new_cfg.rem_cfg_settings =
683+
protocol::RemoteConfigSettings::new(true, new_rem_cfg_path);
684+
}
682685
new_cfg.telemetry_settings = args.telemetry_settings;
683686
Some(new_cfg)
684687
} else {
@@ -807,7 +810,7 @@ mod tests {
807810
},
808811
protocol::RemoteConfigSettings::new(false, PathBuf::from(format!("/tmp/test_{}", id))),
809812
protocol::TelemetrySettings {
810-
service_name: "test".to_string(),
813+
service_name: format!("test_{}", id),
811814
env_name: "test".to_string(),
812815
},
813816
)
@@ -907,4 +910,46 @@ mod tests {
907910
drop(s2);
908911
drop(s1_new);
909912
}
913+
914+
#[test]
915+
fn config_sync_does_not_enable_rc_when_client_init_disabled_it() {
916+
// Simulates DD_REMOTE_CONFIG_ENABLED=0 in client_init: the path is
917+
// non-empty but enabled=false. A later config_sync carrying the same
918+
// (non-empty) path must NOT produce a new ServiceFixedConfig, because
919+
// doing so would flip RC to enabled and trigger a WAF reload per
920+
// PHP-FPM worker.
921+
let telemetry = protocol::TelemetrySettings {
922+
service_name: "svc".to_string(),
923+
env_name: "env".to_string(),
924+
};
925+
let cfg = ServiceFixedConfig::new(
926+
true,
927+
protocol::WafSettings {
928+
rules_file: Some(TEST_RULES_FILE.to_string()),
929+
waf_timeout_us: Some(10000),
930+
trace_rate_limit: 100,
931+
obfuscator_key_regex: None,
932+
obfuscator_value_regex: None,
933+
schema_extraction: protocol::SchemaExtraction {
934+
enabled: false,
935+
sampling_period: 1.0,
936+
},
937+
},
938+
// RC disabled by client_init, but the PHP side still has a path.
939+
protocol::RemoteConfigSettings::new(false, PathBuf::from("/ddrc-test")),
940+
telemetry.clone(),
941+
);
942+
943+
let result = cfg.new_from_config_sync(protocol::ConfigSyncArgs {
944+
rem_cfg_path: "/ddrc-test".to_string(),
945+
telemetry_settings: telemetry,
946+
});
947+
948+
assert!(
949+
result.is_none(),
950+
"config_sync with the same path must not create a new config when \
951+
client_init disabled RC, but got: {:?}",
952+
result
953+
);
954+
}
910955
}

0 commit comments

Comments
 (0)