diff --git a/proxy_agent/src/common/config.rs b/proxy_agent/src/common/config.rs index a9dae135..021ef065 100644 --- a/proxy_agent/src/common/config.rs +++ b/proxy_agent/src/common/config.rs @@ -122,39 +122,44 @@ impl Default for Config { config_file_full_path = misc_helpers::get_current_exe_dir(); config_file_full_path.push(CONFIG_FILE_NAME); } + Config::from_json_file(config_file_full_path) } } impl Config { + /// Load config from a specific JSON file path pub fn from_json_file(file_path: PathBuf) -> Self { - misc_helpers::json_read_from_file::(&file_path).unwrap_or_else(|_| { - panic!( - "Error in reading Config from Json file: {}", - misc_helpers::path_to_string(&file_path) - ) - }) + let mut config = + misc_helpers::json_read_from_file::(&file_path).unwrap_or_else(|_| { + panic!( + "Error in reading Config from Json file: {}", + misc_helpers::path_to_string(&file_path) + ) + }); + config.resolve_env_variables(); + config + } + + /// Resolve environment variables in path fields once during construction + /// This allows us to keep the rest of the code simple without worrying about env vars, + /// and also ensures that we only pay the cost of resolving env vars once at startup rather than on every access. + fn resolve_env_variables(&mut self) { + self.logFolder = misc_helpers::resolve_env_variables(&self.logFolder); + self.eventFolder = misc_helpers::resolve_env_variables(&self.eventFolder); + self.latchKeyFolder = misc_helpers::resolve_env_variables(&self.latchKeyFolder); } pub fn get_log_folder(&self) -> String { - match misc_helpers::resolve_env_variables(&self.logFolder) { - Ok(val) => val, - Err(_) => self.logFolder.clone(), - } + self.logFolder.clone() } pub fn get_event_folder(&self) -> String { - match misc_helpers::resolve_env_variables(&self.eventFolder) { - Ok(val) => val, - Err(_) => self.eventFolder.clone(), - } + self.eventFolder.clone() } pub fn get_latch_key_folder(&self) -> String { - match misc_helpers::resolve_env_variables(&self.latchKeyFolder) { - Ok(val) => val, - Err(_) => self.latchKeyFolder.clone(), - } + self.latchKeyFolder.clone() } pub fn get_monitor_interval(&self) -> u64 { @@ -231,19 +236,19 @@ mod tests { let config = create_config_file(config_file_path); assert_eq!( - r#"C:\logFolderName"#.to_string(), + r#"C:\logFolderName"#, config.get_log_folder(), "Log Folder mismatch" ); assert_eq!( - r#"C:\eventFolderName"#.to_string(), + r#"C:\eventFolderName"#, config.get_event_folder(), "Event Folder mismatch" ); assert_eq!( - r#"C:\latchKeyFolderName"#.to_string(), + r#"C:\latchKeyFolderName"#, config.get_latch_key_folder(), "Latch Key Folder mismatch" ); @@ -267,7 +272,7 @@ mod tests { ); assert_eq!( - "ebpfProgramName".to_string(), + "ebpfProgramName", config.get_ebpf_program_name(), "get_ebpf_program_name mismatch" ); diff --git a/proxy_agent/src/proxy.rs b/proxy_agent/src/proxy.rs index dcc50b1a..5b1d79d0 100644 --- a/proxy_agent/src/proxy.rs +++ b/proxy_agent/src/proxy.rs @@ -200,7 +200,7 @@ impl Process { } // redact the secrets in the command line - let cmd = proxy_agent_shared::secrets_redactor::redact_secrets(cmd); + let cmd = proxy_agent_shared::secrets_redactor::redact_secrets_string(cmd); let process_name = process_full_path .file_name() diff --git a/proxy_agent/src/proxy/authorization_rules.rs b/proxy_agent/src/proxy/authorization_rules.rs index 59261293..746efee0 100644 --- a/proxy_agent/src/proxy/authorization_rules.rs +++ b/proxy_agent/src/proxy/authorization_rules.rs @@ -263,6 +263,10 @@ pub struct AuthorizationRulesForLogging { pub computedRules: ComputedAuthorizationRules, } +/// Remark: Regex::new is performance-sensitive, so we use LazyLock to compile it only once and reuse it for subsequent calls +static AUTHORIZATION_RULES_FILE_SEARCH_REGEX: std::sync::LazyLock = + std::sync::LazyLock::new(|| regex::Regex::new(r"^AuthorizationRules_.*\.json$").unwrap()); + impl AuthorizationRulesForLogging { pub fn new( input_rules: Option, @@ -280,7 +284,10 @@ impl AuthorizationRulesForLogging { /// The file is written to the path_dir specified by the input parameter pub fn write_all(&self, path_dir: &Path, max_file_count: usize) { // remove the old files - let files = match misc_helpers::search_files(path_dir, r"^AuthorizationRules_.*\.json$") { + let files = match misc_helpers::search_files( + path_dir, + &AUTHORIZATION_RULES_FILE_SEARCH_REGEX, + ) { Ok(files) => files, Err(e) => { // This should not happen, log the error and skip write the file @@ -343,7 +350,9 @@ mod tests { AccessControlRules, AuthorizationItem, AuthorizationRules, Identity, Privilege, Role, RoleAssignment, }; - use crate::proxy::authorization_rules::{AuthorizationMode, ComputedAuthorizationItem}; + use crate::proxy::authorization_rules::{ + AuthorizationMode, ComputedAuthorizationItem, AUTHORIZATION_RULES_FILE_SEARCH_REGEX, + }; use crate::proxy::{proxy_connection::ConnectionLogger, Claims}; use proxy_agent_shared::misc_helpers; use std::ffi::OsString; @@ -600,7 +609,8 @@ mod tests { } let files = - misc_helpers::search_files(&temp_test_path, r"^AuthorizationRules_.*\.json$").unwrap(); + misc_helpers::search_files(&temp_test_path, &AUTHORIZATION_RULES_FILE_SEARCH_REGEX) + .unwrap(); assert_eq!(files.len(), max_file_count); // clean up and ignore the clean up errors diff --git a/proxy_agent_setup/src/running.rs b/proxy_agent_setup/src/running.rs index 93905cb8..f6048e88 100644 --- a/proxy_agent_setup/src/running.rs +++ b/proxy_agent_setup/src/running.rs @@ -26,8 +26,7 @@ pub fn proxy_agent_running_folder(_service_name: &str) -> PathBuf { pub fn proxy_agent_parent_folder() -> PathBuf { #[cfg(windows)] { - let path = misc_helpers::resolve_env_variables("%SYSTEMDRIVE%\\WindowsAzure\\ProxyAgent") - .unwrap_or("C:\\WindowsAzure\\ProxyAgent".to_string()); + let path = misc_helpers::resolve_env_variables("%SYSTEMDRIVE%\\WindowsAzure\\ProxyAgent"); PathBuf::from(path) } #[cfg(not(windows))] diff --git a/proxy_agent_shared/src/etw/etw_writter.rs b/proxy_agent_shared/src/etw/etw_writter.rs index bc71d821..c2ff3798 100644 --- a/proxy_agent_shared/src/etw/etw_writter.rs +++ b/proxy_agent_shared/src/etw/etw_writter.rs @@ -45,7 +45,7 @@ impl WindowsEventWritter { ); let value = crate::misc_helpers::resolve_env_variables( r"%SystemRoot%\Microsoft.NET\Framework64\v4.0.30319\EventLogMessages.dll", - )?; + ); crate::windows::set_reg_string(&key_name, "EventMessageFile", value)?; let source_name_wide = super::to_wide(source_name); diff --git a/proxy_agent_shared/src/misc_helpers.rs b/proxy_agent_shared/src/misc_helpers.rs index 57781523..ffe96fac 100644 --- a/proxy_agent_shared/src/misc_helpers.rs +++ b/proxy_agent_shared/src/misc_helpers.rs @@ -261,28 +261,18 @@ pub fn empty_path() -> PathBuf { PathBuf::new() } -/// Search files in a directory with a regex pattern +/// Search files in a directory with a regex /// # Arguments /// * `dir` - The directory to search -/// * `search_regex_pattern` - The regex pattern to search +/// * `search_regex` - The regex to search /// # Returns /// A vector of PathBufs that match the search pattern in ascending order /// # Errors /// Returns an error if the regex pattern is invalid or if there is an IO error -/// # Example -/// ```rust -/// use std::path::PathBuf; -/// use proxy_agent_shared::misc_helpers; -/// let dir = PathBuf::from("."); -/// let search_regex_pattern = r"^(.*\.log)$"; // search for files with .log extension -/// let files = misc_helpers::search_files(&dir, search_regex_pattern).unwrap(); -/// -/// let search_regex_pattern = r"^MyFile.*\.json$"; // Regex pattern to match "MyFile*.json" -/// let files = misc_helpers::search_files(&dir, search_regex_pattern).unwrap(); -/// ``` -pub fn search_files(dir: &Path, search_regex_pattern: &str) -> Result> { +/// Remarks: The Regex::new is expensive, so the caller should cache the regex if it is used frequently, +/// for example, by using once_cell::sync::Lazy or std::sync::LazyLock to create a static regex instance. +pub fn search_files(dir: &Path, search_regex: &Regex) -> Result> { let mut files = Vec::new(); - let regex = Regex::new(search_regex_pattern)?; for entry in fs::read_dir(dir)? { let entry = entry?; @@ -292,7 +282,7 @@ pub fn search_files(dir: &Path, search_regex_pattern: &str) -> Result Result { } } +/// Static regex for matching environment variables like %VAR% +/// expect() only panics on failure to compile the regex, which should not happen since the pattern is constant and valid +/// This is the idiomatic Rust pattern for creating a static regex that is compiled once and reused, ensuring thread safety and performance +/// Remark: Regex::new is performance-sensitive, so we use LazyLock to compile it only once and reuse it for subsequent calls to resolve_env_variables, which can be called frequently. +/// This avoids the overhead of compiling the regex on every call, improving performance while ensuring thread safety. +static ENV_VAR_REGEX: std::sync::LazyLock = + std::sync::LazyLock::new(|| Regex::new(r"%(\w+)%").expect("Invalid env var regex pattern")); + /// This function replaces all occurrences of %VAR% in the input string with the value of the environment variable VAR /// If the environment variable is not set, it returns the original string with VAR unchanged. /// # Arguments /// * `input` - The input string to resolve environment variables in /// # Returns /// A Result containing the resolved string or an error if the regex pattern is invalid -pub fn resolve_env_variables(input: &str) -> Result { - let re = Regex::new(r"%(\w+)%")?; - let ret = re +/// The resolved string with environment variables expanded +pub fn resolve_env_variables(input: &str) -> String { + if input.is_empty() || !input.contains('%') { + // If the input string is empty or does not contain '%', return the original string + return input.to_string(); + } + + ENV_VAR_REGEX .replace_all(input, |caps: ®ex::Captures| { std::env::var(&caps[1]).unwrap_or_else(|_| caps[1].to_string()) }) - .to_string(); - - Ok(ret) + .to_string() } /// Compute HMAC-SHA256 signature for the input using the provided hex-encoded key @@ -424,6 +425,7 @@ pub fn xml_escape(s: String) -> String { #[cfg(test)] mod tests { + use regex::Regex; use serde_derive::{Deserialize, Serialize}; use std::env; use std::fs; @@ -578,14 +580,16 @@ mod tests { let json_file = json_file.join("test_1.json"); super::json_write_to_file(&test, &json_file).unwrap(); - let files = super::search_files(&temp_test_path, "test.json").unwrap(); + let regex = Regex::new(r"test.json").unwrap(); + let files = super::search_files(&temp_test_path, ®ex).unwrap(); assert_eq!( 1, files.len(), "file count mismatch with 'test.json' search" ); - let files = super::search_files(&temp_test_path, r"^test.*\.json$").unwrap(); + let regex = Regex::new(r"^test.*\.json$").unwrap(); + let files = super::search_files(&temp_test_path, ®ex).unwrap(); assert_eq!( 2, files.len(), @@ -647,12 +651,12 @@ mod tests { "{}\\WindowsAzure\\ProxyAgent\\Package_1.0.0", env::var("SYSTEMDRIVE").unwrap_or("SYSTEMDRIVE".to_string()) ); - let resolved = super::resolve_env_variables(input).unwrap(); + let resolved = super::resolve_env_variables(input); assert_eq!(expected, resolved, "resolved string mismatch"); let input = "/var/log/azure-proxy-agent/"; let expected = "/var/log/azure-proxy-agent/".to_string(); - let resolved = super::resolve_env_variables(input).unwrap(); + let resolved = super::resolve_env_variables(input); assert_eq!(expected, resolved, "resolved string mismatch"); } diff --git a/proxy_agent_shared/src/proxy_agent_aggregate_status.rs b/proxy_agent_shared/src/proxy_agent_aggregate_status.rs index 0911a1fa..9d862db7 100644 --- a/proxy_agent_shared/src/proxy_agent_aggregate_status.rs +++ b/proxy_agent_shared/src/proxy_agent_aggregate_status.rs @@ -12,8 +12,7 @@ const PROXY_AGENT_AGGREGATE_STATUS_FOLDER: &str = "/var/log/azure-proxy-agent/"; pub const PROXY_AGENT_AGGREGATE_STATUS_FILE_NAME: &str = "status.json"; pub fn get_proxy_agent_aggregate_status_folder() -> std::path::PathBuf { - let path = misc_helpers::resolve_env_variables(PROXY_AGENT_AGGREGATE_STATUS_FOLDER) - .unwrap_or(PROXY_AGENT_AGGREGATE_STATUS_FOLDER.to_string()); + let path = misc_helpers::resolve_env_variables(PROXY_AGENT_AGGREGATE_STATUS_FOLDER); PathBuf::from(path) } diff --git a/proxy_agent_shared/src/secrets_redactor.rs b/proxy_agent_shared/src/secrets_redactor.rs index bf983664..7cb070f6 100644 --- a/proxy_agent_shared/src/secrets_redactor.rs +++ b/proxy_agent_shared/src/secrets_redactor.rs @@ -1,7 +1,31 @@ // Copyright (c) Microsoft Corporation // SPDX-License-Identifier: MIT +use std::borrow::Cow; + const REDACTED_TEXT: &str = "[REDACTED]"; +/// Common substrings that indicate a secret might be present - for quick pre-filtering +/// These are not regex patterns, just simple substrings to check for before running the more expensive regexes. +const SECRET_INDICATORS: [&str; 15] = [ + "pwd=", + "password=", + "AccountKey=", + "PrimaryKey=", + "SecondaryKey=", + "sig=", + "AzCa", + "PRIVATE KEY", + "token", + "ado", + "vsts", + "key", + "secret", + "authorization", + "eyJ", +]; +/// Regular expression patterns to identify secrets. These are more expensive to run, so we first check for indicators. +/// Remarks: when add more patterns, please also add corresponding indicators in SECRET_INDICATORS for better performance. +/// And try to make the pattern as specific as possible to avoid false positives and unnecessary redaction. const CRED_PATTERNS: [&str; 17] = [ // SQL Connection String Password "pwd=[^;]*", @@ -45,20 +69,46 @@ fn init_regex_patterns() -> Vec { patterns } -pub fn redact_secrets(text: String) -> String { - if text.is_empty() { - return text; +/// Quick check if text might contain secrets (case-insensitive for most indicators) +#[inline] +fn might_contain_secrets(text: &str) -> bool { + let lower = text.to_ascii_lowercase(); + SECRET_INDICATORS.iter().any(|indicator| { + if *indicator == "AzCa" || *indicator == "PRIVATE KEY" || *indicator == "eyJ" { + // Case-sensitive check for these + text.contains(indicator) + } else { + lower.contains(&indicator.to_ascii_lowercase()) + } + }) +} + +/// Redacts secrets from text. Returns the original text unchanged if no secrets found. +/// Takes `&str` to avoid unnecessary ownership transfer. +fn redact_secrets(text: &str) -> Cow<'_, str> { + if text.is_empty() || !might_contain_secrets(text) { + return Cow::Borrowed(text); } - let mut redacted_text = text.clone(); + let mut redacted_text = Cow::Borrowed(text); for pattern in REGEX_PATTERNS.iter() { - redacted_text = pattern - .replace_all(&redacted_text, REDACTED_TEXT) - .to_string(); + if let Cow::Owned(s) = pattern.replace_all(&redacted_text, REDACTED_TEXT) { + redacted_text = Cow::Owned(s); + } } redacted_text } +/// Convenience function that takes ownership and returns String +/// Use this when you already have a String and need a String back +#[inline] +pub fn redact_secrets_string(text: String) -> String { + match redact_secrets(&text) { + Cow::Borrowed(_) => text, // No changes, return original + Cow::Owned(s) => s, // Changed, return new string + } +} + #[cfg(test)] mod tests { use super::*; @@ -121,7 +171,23 @@ authorization: aws4-hmac-sha256"#, ), ]; for (input, expected) in test_strings { - assert_eq!(redact_secrets(input.to_string()), expected.to_string()); + assert_eq!(redact_secrets(input), expected); } } + + #[test] + fn test_no_secrets_no_allocation() { + let text = "This is a normal log message without any secrets"; + let result = redact_secrets(text); + // Should return Borrowed (no allocation) when no secrets found + assert!(matches!(result, std::borrow::Cow::Borrowed(_))); + assert_eq!(result, text); + } + + #[test] + fn test_redact_secrets_string() { + let text = "pwd=secret123;".to_string(); + let result = redact_secrets_string(text); + assert_eq!(result, "[REDACTED];"); + } } diff --git a/proxy_agent_shared/src/telemetry.rs b/proxy_agent_shared/src/telemetry.rs index 684e6b1a..c831acc4 100644 --- a/proxy_agent_shared/src/telemetry.rs +++ b/proxy_agent_shared/src/telemetry.rs @@ -10,10 +10,15 @@ use crate::{current_info, misc_helpers}; use serde_derive::{Deserialize, Serialize}; pub const GENERIC_EVENT_FILE_SEARCH_PATTERN: &str = r"^[0-9]+\.json$"; +pub static GENERIC_EVENT_FILE_SEARCH_REGEX: std::sync::LazyLock = + std::sync::LazyLock::new(|| regex::Regex::new(GENERIC_EVENT_FILE_SEARCH_PATTERN).unwrap()); pub fn new_generic_event_file_name() -> String { format!("{}.json", misc_helpers::get_date_time_unix_nano()) } pub const EXTENSION_EVENT_FILE_SEARCH_PATTERN: &str = r"^extension_[0-9]+\.json$"; +pub static EXTENSION_EVENT_FILE_SEARCH_REGEX: std::sync::LazyLock = + std::sync::LazyLock::new(|| regex::Regex::new(EXTENSION_EVENT_FILE_SEARCH_PATTERN).unwrap()); + pub fn new_extension_event_file_name() -> String { format!("extension_{}.json", misc_helpers::get_date_time_unix_nano()) } diff --git a/proxy_agent_shared/src/telemetry/event_logger.rs b/proxy_agent_shared/src/telemetry/event_logger.rs index 96bda630..d790b645 100644 --- a/proxy_agent_shared/src/telemetry/event_logger.rs +++ b/proxy_agent_shared/src/telemetry/event_logger.rs @@ -82,7 +82,7 @@ pub async fn start( // if it exceeds the max file number, drop the new events match misc_helpers::search_files( &event_dir, - crate::telemetry::GENERIC_EVENT_FILE_SEARCH_PATTERN, + &crate::telemetry::GENERIC_EVENT_FILE_SEARCH_REGEX, ) { Ok(files) => { if files.len() >= max_event_file_count { @@ -188,7 +188,7 @@ pub fn report_extension_status_event( // if it exceeds the max file number, drop the new events match misc_helpers::search_files( &event_dir, - crate::telemetry::EXTENSION_EVENT_FILE_SEARCH_PATTERN, + &crate::telemetry::EXTENSION_EVENT_FILE_SEARCH_REGEX, ) { Ok(files) => { if files.len() >= MAX_EXTENSION_EVENT_FILE_COUNT { @@ -334,7 +334,7 @@ mod tests { // Verify extension event file was created let files = misc_helpers::search_files( &events_dir, - crate::telemetry::EXTENSION_EVENT_FILE_SEARCH_PATTERN, + &crate::telemetry::EXTENSION_EVENT_FILE_SEARCH_REGEX, ) .unwrap(); assert!( diff --git a/proxy_agent_shared/src/telemetry/event_reader.rs b/proxy_agent_shared/src/telemetry/event_reader.rs index 8002dfae..c8b6b22a 100644 --- a/proxy_agent_shared/src/telemetry/event_reader.rs +++ b/proxy_agent_shared/src/telemetry/event_reader.rs @@ -123,7 +123,7 @@ impl EventReader { // get all [0-9]+.json event filenames with numbers in the directory match misc_helpers::search_files( &self.dir_path, - crate::telemetry::GENERIC_EVENT_FILE_SEARCH_PATTERN, + &crate::telemetry::GENERIC_EVENT_FILE_SEARCH_REGEX, ) { Ok(files) => { let file_count = files.len(); @@ -282,7 +282,7 @@ impl EventReader { // get all extension status event filenames in the directory match misc_helpers::search_files( &self.dir_path, - crate::telemetry::EXTENSION_EVENT_FILE_SEARCH_PATTERN, + &crate::telemetry::EXTENSION_EVENT_FILE_SEARCH_REGEX, ) { Ok(files) => { let file_count = files.len(); @@ -503,7 +503,7 @@ mod tests { // Verify files were created let files = misc_helpers::search_files( &events_dir, - crate::telemetry::EXTENSION_EVENT_FILE_SEARCH_PATTERN, + &crate::telemetry::EXTENSION_EVENT_FILE_SEARCH_REGEX, ) .unwrap(); assert_eq!(2, files.len(), "Should have 2 extension event files"); @@ -518,7 +518,7 @@ mod tests { // Verify files were cleaned up after processing let files = misc_helpers::search_files( &events_dir, - crate::telemetry::EXTENSION_EVENT_FILE_SEARCH_PATTERN, + &crate::telemetry::EXTENSION_EVENT_FILE_SEARCH_REGEX, ) .unwrap(); assert!( @@ -578,7 +578,7 @@ mod tests { // Verify the file was processed let files = misc_helpers::search_files( &events_dir, - crate::telemetry::EXTENSION_EVENT_FILE_SEARCH_PATTERN, + &crate::telemetry::EXTENSION_EVENT_FILE_SEARCH_REGEX, ) .unwrap(); assert!( @@ -661,14 +661,14 @@ mod tests { // Verify all files were created let generic_files = misc_helpers::search_files( &events_dir, - crate::telemetry::GENERIC_EVENT_FILE_SEARCH_PATTERN, + &crate::telemetry::GENERIC_EVENT_FILE_SEARCH_REGEX, ) .unwrap(); assert_eq!(2, generic_files.len(), "Should have 2 generic event files"); let extension_files = misc_helpers::search_files( &events_dir, - crate::telemetry::EXTENSION_EVENT_FILE_SEARCH_PATTERN, + &crate::telemetry::EXTENSION_EVENT_FILE_SEARCH_REGEX, ) .unwrap(); assert_eq!( @@ -687,7 +687,7 @@ mod tests { // Verify only generic files were cleaned up let generic_files = misc_helpers::search_files( &events_dir, - crate::telemetry::GENERIC_EVENT_FILE_SEARCH_PATTERN, + &crate::telemetry::GENERIC_EVENT_FILE_SEARCH_REGEX, ) .unwrap(); assert!( @@ -697,7 +697,7 @@ mod tests { let extension_files = misc_helpers::search_files( &events_dir, - crate::telemetry::EXTENSION_EVENT_FILE_SEARCH_PATTERN, + &crate::telemetry::EXTENSION_EVENT_FILE_SEARCH_REGEX, ) .unwrap(); assert_eq!( @@ -716,7 +716,7 @@ mod tests { // Verify extension files were cleaned up let extension_files = misc_helpers::search_files( &events_dir, - crate::telemetry::EXTENSION_EVENT_FILE_SEARCH_PATTERN, + &crate::telemetry::EXTENSION_EVENT_FILE_SEARCH_REGEX, ) .unwrap(); assert!( @@ -744,7 +744,7 @@ mod tests { // Generic file should still exist let generic_files = misc_helpers::search_files( &events_dir, - crate::telemetry::GENERIC_EVENT_FILE_SEARCH_PATTERN, + &crate::telemetry::GENERIC_EVENT_FILE_SEARCH_REGEX, ) .unwrap(); assert_eq!( diff --git a/proxy_agent_shared/src/telemetry/telemetry_event.rs b/proxy_agent_shared/src/telemetry/telemetry_event.rs index c8ed89ef..a8a532ba 100644 --- a/proxy_agent_shared/src/telemetry/telemetry_event.rs +++ b/proxy_agent_shared/src/telemetry/telemetry_event.rs @@ -309,20 +309,23 @@ impl TelemetryGenericLogsEvent { // if ga_version is None, use event_log.Version as ga_version and keep event_name unchanged let (ga_version, event_name) = match ga_version { Some(version) => (version, format!("{}-{}", event_name, event_log.Version)), - None => (event_log.Version.to_string(), event_name), + None => (event_log.Version.clone(), event_name), }; + // redact secrets in the message before sending to telemetry + let message = event_log.Message.clone(); + let message = crate::secrets_redactor::redact_secrets_string(message); TelemetryGenericLogsEvent { event_name, ga_version, execution_mode, event_pid: event_log.EventPid.parse::().unwrap_or(0), event_tid: event_log.EventTid.parse::().unwrap_or(0), - task_name: event_log.TaskName.to_string(), - opcode_name: event_log.TimeStamp.to_string(), - capability_used: event_log.EventLevel.to_string(), - context1: event_log.Message.to_string(), - context2: event_log.TimeStamp.to_string(), - context3: event_log.OperationId.to_string(), + task_name: event_log.TaskName.clone(), + opcode_name: event_log.TimeStamp.clone(), + capability_used: event_log.EventLevel.clone(), + context1: message, + context2: event_log.TimeStamp.clone(), + context3: event_log.OperationId.clone(), } } @@ -413,20 +416,23 @@ impl TelemetryExtensionEventsEvent { execution_mode: String, ga_version: String, ) -> Self { + // redact secrets in the message before sending to telemetry + let message = event.operation_status.message.clone(); + let message = crate::secrets_redactor::redact_secrets_string(message); TelemetryExtensionEventsEvent { ga_version, execution_mode, event_pid: event.event_pid.parse::().unwrap_or(0), event_tid: event.event_tid.parse::().unwrap_or(0), - opcode_name: event.time_stamp.to_string(), - extension_type: event.extension.extension_type.to_string(), + opcode_name: event.time_stamp.clone(), + extension_type: event.extension.extension_type.clone(), is_internal: event.extension.is_internal, - name: event.extension.name.to_string(), - version: event.extension.version.to_string(), - operation: event.operation_status.operation.to_string(), - task_name: event.operation_status.task_name.to_string(), + name: event.extension.name.clone(), + version: event.extension.version.clone(), + operation: event.operation_status.operation.clone(), + task_name: event.operation_status.task_name.clone(), operation_success: event.operation_status.operation_success, - message: event.operation_status.message.to_string(), + message, duration: event.operation_status.duration as u64, } }