bamboo_config/
config_crypto.rs1use anyhow::{Context, Result};
8
9use super::{Config, ProxyAuth};
10
11impl Config {
12 pub fn hydrate_proxy_auth_from_encrypted(&mut self) {
20 if self.proxy_auth.is_some() {
21 return;
22 }
23
24 if self
31 .proxy_auth_encrypted
32 .as_deref()
33 .map(|s| s.trim().is_empty())
34 .unwrap_or(true)
35 {
36 let legacy = self
37 .extra
38 .get("https_proxy_auth_encrypted")
39 .and_then(|v| v.as_str())
40 .or_else(|| {
41 self.extra
42 .get("http_proxy_auth_encrypted")
43 .and_then(|v| v.as_str())
44 })
45 .map(|s| s.trim())
46 .filter(|s| !s.is_empty())
47 .map(|s| s.to_string());
48
49 if let Some(legacy) = legacy {
50 self.proxy_auth_encrypted = Some(legacy);
51 }
52 }
53
54 let Some(encrypted) = self.proxy_auth_encrypted.as_deref() else {
55 return;
56 };
57
58 match crate::encryption::decrypt(encrypted) {
59 Ok(decrypted) => match serde_json::from_str::<ProxyAuth>(&decrypted) {
60 Ok(auth) => {
61 self.proxy_auth = Some(auth);
62 self.extra.remove("http_proxy_auth_encrypted");
65 self.extra.remove("https_proxy_auth_encrypted");
66 }
67 Err(e) => tracing::warn!("Failed to parse decrypted proxy auth JSON: {}", e),
68 },
69 Err(e) => tracing::warn!("Failed to decrypt proxy auth: {}", e),
70 }
71 }
72
73 pub fn refresh_proxy_auth_encrypted(&mut self) -> Result<()> {
78 let Some(auth) = self.proxy_auth.as_ref() else {
82 self.proxy_auth_encrypted = None;
83 return Ok(());
84 };
85
86 let auth_str = serde_json::to_string(auth).context("Failed to serialize proxy auth")?;
87 let encrypted =
88 crate::encryption::encrypt(&auth_str).context("Failed to encrypt proxy auth")?;
89 self.proxy_auth_encrypted = Some(encrypted);
90 Ok(())
91 }
92
93 pub fn hydrate_provider_api_keys_from_encrypted(&mut self) {
96 if let Some(openai) = self.providers.openai.as_mut() {
97 if openai.api_key.trim().is_empty() {
98 if let Some(encrypted) = openai.api_key_encrypted.as_deref() {
99 match crate::encryption::decrypt(encrypted) {
100 Ok(value) => openai.api_key = value,
101 Err(e) => tracing::warn!("Failed to decrypt OpenAI api_key: {}", e),
102 }
103 }
104 }
105 }
106
107 if let Some(anthropic) = self.providers.anthropic.as_mut() {
108 if anthropic.api_key.trim().is_empty() {
109 if let Some(encrypted) = anthropic.api_key_encrypted.as_deref() {
110 match crate::encryption::decrypt(encrypted) {
111 Ok(value) => anthropic.api_key = value,
112 Err(e) => tracing::warn!("Failed to decrypt Anthropic api_key: {}", e),
113 }
114 }
115 }
116 }
117
118 if let Some(gemini) = self.providers.gemini.as_mut() {
119 if gemini.api_key.trim().is_empty() {
120 if let Some(encrypted) = gemini.api_key_encrypted.as_deref() {
121 match crate::encryption::decrypt(encrypted) {
122 Ok(value) => gemini.api_key = value,
123 Err(e) => tracing::warn!("Failed to decrypt Gemini api_key: {}", e),
124 }
125 }
126 }
127 }
128
129 if let Some(bodhi) = self.providers.bodhi.as_mut() {
130 if bodhi.api_key.trim().is_empty() {
131 if let Some(encrypted) = bodhi.api_key_encrypted.as_deref() {
132 match crate::encryption::decrypt(encrypted) {
133 Ok(value) => bodhi.api_key = value,
134 Err(e) => tracing::warn!("Failed to decrypt Bodhi api_key: {}", e),
135 }
136 }
137 }
138 }
139 }
140
141 pub fn refresh_provider_api_keys_encrypted(&mut self) -> Result<()> {
142 if let Some(openai) = self.providers.openai.as_mut() {
143 let api_key = openai.api_key.trim();
144 openai.api_key_encrypted = if api_key.is_empty() {
145 None
146 } else {
147 Some(
148 crate::encryption::encrypt(api_key)
149 .context("Failed to encrypt OpenAI api_key")?,
150 )
151 };
152 }
153
154 if let Some(anthropic) = self.providers.anthropic.as_mut() {
155 let api_key = anthropic.api_key.trim();
156 anthropic.api_key_encrypted = if api_key.is_empty() {
157 None
158 } else {
159 Some(
160 crate::encryption::encrypt(api_key)
161 .context("Failed to encrypt Anthropic api_key")?,
162 )
163 };
164 }
165
166 if let Some(gemini) = self.providers.gemini.as_mut() {
167 let api_key = gemini.api_key.trim();
168 gemini.api_key_encrypted = if api_key.is_empty() {
169 None
170 } else {
171 Some(
172 crate::encryption::encrypt(api_key)
173 .context("Failed to encrypt Gemini api_key")?,
174 )
175 };
176 }
177
178 if let Some(bodhi) = self.providers.bodhi.as_mut() {
179 let api_key = bodhi.api_key.trim();
180 bodhi.api_key_encrypted = if api_key.is_empty() {
181 None
182 } else {
183 Some(
184 crate::encryption::encrypt(api_key)
185 .context("Failed to encrypt Bodhi api_key")?,
186 )
187 };
188 }
189
190 Ok(())
191 }
192
193 pub fn hydrate_provider_instance_api_keys_from_encrypted(&mut self) {
198 for (id, instance) in self.provider_instances.iter_mut() {
199 if instance.api_key.trim().is_empty() {
200 if let Some(encrypted) = instance.api_key_encrypted.as_deref() {
201 match crate::encryption::decrypt(encrypted) {
202 Ok(value) => instance.api_key = value,
203 Err(e) => {
204 tracing::warn!(instance_id = id, "Failed to decrypt api_key: {}", e)
205 }
206 }
207 }
208 }
209 }
210 }
211
212 pub fn refresh_provider_instance_api_keys_encrypted(&mut self) -> Result<()> {
215 for (id, instance) in self.provider_instances.iter_mut() {
216 let api_key = instance.api_key.trim();
217 instance.api_key_encrypted = if api_key.is_empty() {
218 None
219 } else {
220 Some(crate::encryption::encrypt(api_key).context(format!(
221 "Failed to encrypt api_key for provider instance '{}'",
222 id
223 ))?)
224 };
225 }
226 Ok(())
227 }
228
229 pub fn hydrate_mcp_secrets_from_encrypted(&mut self) {
232 for server in self.mcp.servers.iter_mut() {
233 match &mut server.transport {
234 bamboo_domain::mcp_config::TransportConfig::Stdio(stdio) => {
235 if stdio.env_encrypted.is_empty() {
236 continue;
237 }
238
239 for (key, encrypted) in stdio.env_encrypted.clone() {
241 let should_hydrate = stdio
242 .env
243 .get(&key)
244 .map(|v| v.trim().is_empty())
245 .unwrap_or(true);
246 if !should_hydrate {
247 continue;
248 }
249
250 match crate::encryption::decrypt(&encrypted) {
251 Ok(value) => {
252 stdio.env.insert(key, value);
253 }
254 Err(e) => tracing::warn!("Failed to decrypt MCP stdio env var: {}", e),
255 }
256 }
257 }
258 bamboo_domain::mcp_config::TransportConfig::Sse(sse) => {
259 for header in sse.headers.iter_mut() {
260 if !header.value.trim().is_empty() {
261 continue;
262 }
263 let Some(encrypted) = header.value_encrypted.as_deref() else {
264 continue;
265 };
266 match crate::encryption::decrypt(encrypted) {
267 Ok(value) => header.value = value,
268 Err(e) => {
269 tracing::warn!("Failed to decrypt MCP SSE header value: {}", e)
270 }
271 }
272 }
273 }
274 bamboo_domain::mcp_config::TransportConfig::StreamableHttp(sh) => {
275 for header in sh.headers.iter_mut() {
276 if !header.value.trim().is_empty() {
277 continue;
278 }
279 let Some(encrypted) = header.value_encrypted.as_deref() else {
280 continue;
281 };
282 match crate::encryption::decrypt(encrypted) {
283 Ok(value) => header.value = value,
284 Err(e) => {
285 tracing::warn!(
286 "Failed to decrypt MCP StreamableHTTP header value: {}",
287 e
288 )
289 }
290 }
291 }
292 }
293 }
294 }
295 }
296
297 pub fn refresh_mcp_secrets_encrypted(&mut self) -> Result<()> {
298 for server in self.mcp.servers.iter_mut() {
299 match &mut server.transport {
300 bamboo_domain::mcp_config::TransportConfig::Stdio(stdio) => {
301 stdio.env_encrypted.clear();
302 for (key, value) in &stdio.env {
303 let encrypted = crate::encryption::encrypt(value).with_context(|| {
304 format!("Failed to encrypt MCP stdio env var '{key}'")
305 })?;
306 stdio.env_encrypted.insert(key.clone(), encrypted);
307 }
308 }
309 bamboo_domain::mcp_config::TransportConfig::Sse(sse) => {
310 for header in sse.headers.iter_mut() {
311 let configured = !header.value.trim().is_empty();
312 header.value_encrypted = if !configured {
313 None
314 } else {
315 Some(crate::encryption::encrypt(&header.value).with_context(|| {
316 format!("Failed to encrypt MCP SSE header '{}'", header.name)
317 })?)
318 };
319 }
320 }
321 bamboo_domain::mcp_config::TransportConfig::StreamableHttp(sh) => {
322 for header in sh.headers.iter_mut() {
323 let configured = !header.value.trim().is_empty();
324 header.value_encrypted = if !configured {
325 None
326 } else {
327 Some(crate::encryption::encrypt(&header.value).with_context(|| {
328 format!(
329 "Failed to encrypt MCP StreamableHTTP header '{}'",
330 header.name
331 )
332 })?)
333 };
334 }
335 }
336 }
337 }
338
339 Ok(())
340 }
341
342 pub fn hydrate_env_vars_from_encrypted(&mut self) {
346 for entry in &mut self.env_vars {
347 if !entry.secret {
348 continue;
349 }
350 if !entry.value.trim().is_empty() {
351 continue;
353 }
354 let Some(encrypted) = &entry.value_encrypted else {
355 continue;
356 };
357 match crate::encryption::decrypt(encrypted) {
358 Ok(value) => entry.value = value,
359 Err(e) => tracing::warn!("Failed to decrypt env var '{}': {}", entry.name, e),
360 }
361 }
362 }
363
364 pub fn refresh_env_vars_encrypted(&mut self) -> Result<()> {
366 for entry in &mut self.env_vars {
367 if entry.secret && !entry.value.trim().is_empty() {
368 entry.value_encrypted = Some(
369 crate::encryption::encrypt(&entry.value)
370 .with_context(|| format!("Failed to encrypt env var '{}'", entry.name))?,
371 );
372 } else if !entry.secret {
373 entry.value_encrypted = None;
374 }
375 }
376 Ok(())
377 }
378
379 pub fn sanitize_env_vars_for_disk(&mut self) {
381 for entry in &mut self.env_vars {
382 if entry.secret {
383 entry.value = String::new();
384 }
385 }
386 }
387}