laminar_connectors/storage/
masking.rs1#![allow(clippy::disallowed_types)] use std::collections::HashMap;
9
10const SECRET_PATTERNS: &[&str] = &[
14 "secret",
15 "password",
16 "account_key",
17 "private_key",
18 "sas_token",
19 "session_token",
20 "client_secret",
21 "service_account_key",
22];
23
24pub struct SecretMasker;
26
27impl SecretMasker {
28 #[must_use]
45 pub fn is_secret_key(key: &str) -> bool {
46 let lower = key.to_lowercase();
47 SECRET_PATTERNS.iter().any(|p| lower.contains(p))
48 }
49
50 #[must_use]
52 pub fn redact_map(map: &HashMap<String, String>) -> HashMap<String, String> {
53 map.iter()
54 .map(|(k, v)| {
55 if Self::is_secret_key(k) {
56 (k.clone(), "***".to_string())
57 } else {
58 (k.clone(), v.clone())
59 }
60 })
61 .collect()
62 }
63
64 #[must_use]
66 pub fn display_map(map: &HashMap<String, String>) -> String {
67 if map.is_empty() {
68 return String::new();
69 }
70
71 let mut pairs: Vec<_> = map.iter().collect();
72 pairs.sort_by_key(|(k, _)| k.as_str());
73 pairs
74 .iter()
75 .map(|(k, v)| {
76 if Self::is_secret_key(k) {
77 format!("{k}=***")
78 } else {
79 format!("{k}={v}")
80 }
81 })
82 .collect::<Vec<_>>()
83 .join(", ")
84 }
85}
86
87#[cfg(test)]
88mod tests {
89 use super::*;
90
91 #[test]
94 fn test_secret_key_aws_secret() {
95 assert!(SecretMasker::is_secret_key("aws_secret_access_key"));
96 }
97
98 #[test]
99 fn test_secret_key_password() {
100 assert!(SecretMasker::is_secret_key("password"));
101 }
102
103 #[test]
104 fn test_secret_key_azure_account_key() {
105 assert!(SecretMasker::is_secret_key("azure_storage_account_key"));
106 }
107
108 #[test]
109 fn test_secret_key_sas_token() {
110 assert!(SecretMasker::is_secret_key("azure_storage_sas_token"));
111 }
112
113 #[test]
114 fn test_secret_key_session_token() {
115 assert!(SecretMasker::is_secret_key("aws_session_token"));
116 }
117
118 #[test]
119 fn test_secret_key_private_key() {
120 assert!(SecretMasker::is_secret_key("google_private_key"));
121 }
122
123 #[test]
124 fn test_secret_key_service_account_key() {
125 assert!(SecretMasker::is_secret_key("google_service_account_key"));
126 }
127
128 #[test]
129 fn test_secret_key_client_secret() {
130 assert!(SecretMasker::is_secret_key("azure_storage_client_secret"));
131 }
132
133 #[test]
134 fn test_secret_key_case_insensitive() {
135 assert!(SecretMasker::is_secret_key("AWS_SECRET_ACCESS_KEY"));
136 assert!(SecretMasker::is_secret_key("Password"));
137 }
138
139 #[test]
140 fn test_not_secret_region() {
141 assert!(!SecretMasker::is_secret_key("aws_region"));
142 }
143
144 #[test]
145 fn test_not_secret_access_key_id() {
146 assert!(!SecretMasker::is_secret_key("aws_access_key_id"));
147 }
148
149 #[test]
150 fn test_not_secret_table_path() {
151 assert!(!SecretMasker::is_secret_key("table.path"));
152 }
153
154 #[test]
155 fn test_not_secret_account_name() {
156 assert!(!SecretMasker::is_secret_key("azure_storage_account_name"));
157 }
158
159 #[test]
160 fn test_not_secret_endpoint() {
161 assert!(!SecretMasker::is_secret_key("aws_endpoint"));
162 }
163
164 #[test]
165 fn test_not_secret_service_account_path() {
166 assert!(!SecretMasker::is_secret_key("google_service_account_path"));
168 }
169
170 #[test]
173 fn test_redact_map_replaces_secrets() {
174 let mut map = HashMap::new();
175 map.insert("aws_region".to_string(), "us-east-1".to_string());
176 map.insert(
177 "aws_secret_access_key".to_string(),
178 "REAL_SECRET".to_string(),
179 );
180 map.insert("aws_access_key_id".to_string(), "AKID123".to_string());
181
182 let redacted = SecretMasker::redact_map(&map);
183 assert_eq!(redacted["aws_region"], "us-east-1");
184 assert_eq!(redacted["aws_secret_access_key"], "***");
185 assert_eq!(redacted["aws_access_key_id"], "AKID123");
186 }
187
188 #[test]
189 fn test_redact_map_empty() {
190 let map = HashMap::new();
191 let redacted = SecretMasker::redact_map(&map);
192 assert!(redacted.is_empty());
193 }
194
195 #[test]
198 fn test_display_map_sorted() {
199 let mut map = HashMap::new();
200 map.insert("z_key".to_string(), "z_val".to_string());
201 map.insert("a_key".to_string(), "a_val".to_string());
202
203 let display = SecretMasker::display_map(&map);
204 assert!(display.starts_with("a_key="));
205 assert!(display.contains("z_key="));
206 }
207
208 #[test]
209 fn test_display_map_redacts_secrets() {
210 let mut map = HashMap::new();
211 map.insert("aws_region".to_string(), "us-east-1".to_string());
212 map.insert(
213 "aws_secret_access_key".to_string(),
214 "TOP_SECRET".to_string(),
215 );
216
217 let display = SecretMasker::display_map(&map);
218 assert!(display.contains("aws_region=us-east-1"));
219 assert!(display.contains("aws_secret_access_key=***"));
220 assert!(!display.contains("TOP_SECRET"));
221 }
222
223 #[test]
224 fn test_display_map_empty() {
225 let map = HashMap::new();
226 let display = SecretMasker::display_map(&map);
227 assert!(display.is_empty());
228 }
229}