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() {
147 if !openai.api_key_from_env {
148 let api_key = openai.api_key.trim();
149 if !api_key.is_empty() {
156 openai.api_key_encrypted = Some(
157 crate::encryption::encrypt(api_key)
158 .context("Failed to encrypt OpenAI api_key")?,
159 );
160 }
161 }
162 }
163
164 if let Some(anthropic) = self.providers.anthropic.as_mut() {
165 if !anthropic.api_key_from_env {
166 let api_key = anthropic.api_key.trim();
167 if !api_key.is_empty() {
169 anthropic.api_key_encrypted = Some(
170 crate::encryption::encrypt(api_key)
171 .context("Failed to encrypt Anthropic api_key")?,
172 );
173 }
174 }
175 }
176
177 if let Some(gemini) = self.providers.gemini.as_mut() {
178 if !gemini.api_key_from_env {
179 let api_key = gemini.api_key.trim();
180 if !api_key.is_empty() {
182 gemini.api_key_encrypted = Some(
183 crate::encryption::encrypt(api_key)
184 .context("Failed to encrypt Gemini api_key")?,
185 );
186 }
187 }
188 }
189
190 if let Some(bodhi) = self.providers.bodhi.as_mut() {
191 let api_key = bodhi.api_key.trim();
192 if !api_key.is_empty() {
194 bodhi.api_key_encrypted = Some(
195 crate::encryption::encrypt(api_key)
196 .context("Failed to encrypt Bodhi api_key")?,
197 );
198 }
199 }
200
201 Ok(())
202 }
203
204 pub fn hydrate_provider_instance_api_keys_from_encrypted(&mut self) {
209 for (id, instance) in self.provider_instances.iter_mut() {
210 if instance.api_key.trim().is_empty() {
211 if let Some(encrypted) = instance.api_key_encrypted.as_deref() {
212 match crate::encryption::decrypt(encrypted) {
213 Ok(value) => instance.api_key = value,
214 Err(e) => {
215 tracing::warn!(instance_id = id, "Failed to decrypt api_key: {}", e)
216 }
217 }
218 }
219 }
220 }
221 }
222
223 pub fn refresh_provider_instance_api_keys_encrypted(&mut self) -> Result<()> {
226 for (id, instance) in self.provider_instances.iter_mut() {
227 let api_key = instance.api_key.trim();
228 if !api_key.is_empty() {
231 instance.api_key_encrypted = Some(crate::encryption::encrypt(api_key).context(
232 format!("Failed to encrypt api_key for provider instance '{}'", id),
233 )?);
234 }
235 }
236 Ok(())
237 }
238
239 pub fn hydrate_mcp_secrets_from_encrypted(&mut self) {
242 for server in self.mcp.servers.iter_mut() {
243 match &mut server.transport {
244 bamboo_domain::mcp_config::TransportConfig::Stdio(stdio) => {
245 if stdio.env_encrypted.is_empty() {
246 continue;
247 }
248
249 for (key, encrypted) in stdio.env_encrypted.clone() {
251 let should_hydrate = stdio
252 .env
253 .get(&key)
254 .map(|v| v.trim().is_empty())
255 .unwrap_or(true);
256 if !should_hydrate {
257 continue;
258 }
259
260 match crate::encryption::decrypt(&encrypted) {
261 Ok(value) => {
262 stdio.env.insert(key, value);
263 }
264 Err(e) => tracing::warn!("Failed to decrypt MCP stdio env var: {}", e),
265 }
266 }
267 }
268 bamboo_domain::mcp_config::TransportConfig::Sse(sse) => {
269 for header in sse.headers.iter_mut() {
270 if !header.value.trim().is_empty() {
271 continue;
272 }
273 let Some(encrypted) = header.value_encrypted.as_deref() else {
274 continue;
275 };
276 match crate::encryption::decrypt(encrypted) {
277 Ok(value) => header.value = value,
278 Err(e) => {
279 tracing::warn!("Failed to decrypt MCP SSE header value: {}", e)
280 }
281 }
282 }
283 }
284 bamboo_domain::mcp_config::TransportConfig::StreamableHttp(sh) => {
285 for header in sh.headers.iter_mut() {
286 if !header.value.trim().is_empty() {
287 continue;
288 }
289 let Some(encrypted) = header.value_encrypted.as_deref() else {
290 continue;
291 };
292 match crate::encryption::decrypt(encrypted) {
293 Ok(value) => header.value = value,
294 Err(e) => {
295 tracing::warn!(
296 "Failed to decrypt MCP StreamableHTTP header value: {}",
297 e
298 )
299 }
300 }
301 }
302 }
303 }
304 }
305 }
306
307 pub fn refresh_mcp_secrets_encrypted(&mut self) -> Result<()> {
308 for server in self.mcp.servers.iter_mut() {
309 match &mut server.transport {
310 bamboo_domain::mcp_config::TransportConfig::Stdio(stdio) => {
311 stdio.env_encrypted.clear();
312 for (key, value) in &stdio.env {
313 let encrypted = crate::encryption::encrypt(value).with_context(|| {
314 format!("Failed to encrypt MCP stdio env var '{key}'")
315 })?;
316 stdio.env_encrypted.insert(key.clone(), encrypted);
317 }
318 }
319 bamboo_domain::mcp_config::TransportConfig::Sse(sse) => {
320 for header in sse.headers.iter_mut() {
321 let configured = !header.value.trim().is_empty();
322 header.value_encrypted = if !configured {
323 None
324 } else {
325 Some(crate::encryption::encrypt(&header.value).with_context(|| {
326 format!("Failed to encrypt MCP SSE header '{}'", header.name)
327 })?)
328 };
329 }
330 }
331 bamboo_domain::mcp_config::TransportConfig::StreamableHttp(sh) => {
332 for header in sh.headers.iter_mut() {
333 let configured = !header.value.trim().is_empty();
334 header.value_encrypted = if !configured {
335 None
336 } else {
337 Some(crate::encryption::encrypt(&header.value).with_context(|| {
338 format!(
339 "Failed to encrypt MCP StreamableHTTP header '{}'",
340 header.name
341 )
342 })?)
343 };
344 }
345 }
346 }
347 }
348
349 Ok(())
350 }
351
352 pub fn hydrate_env_vars_from_encrypted(&mut self) {
356 for entry in &mut self.env_vars {
357 if !entry.secret {
358 continue;
359 }
360 if !entry.value.trim().is_empty() {
361 continue;
363 }
364 let Some(encrypted) = &entry.value_encrypted else {
365 continue;
366 };
367 match crate::encryption::decrypt(encrypted) {
368 Ok(value) => entry.value = value,
369 Err(e) => tracing::warn!("Failed to decrypt env var '{}': {}", entry.name, e),
370 }
371 }
372 }
373
374 pub fn refresh_env_vars_encrypted(&mut self) -> Result<()> {
376 for entry in &mut self.env_vars {
377 if entry.secret && !entry.value.trim().is_empty() {
378 entry.value_encrypted = Some(
379 crate::encryption::encrypt(&entry.value)
380 .with_context(|| format!("Failed to encrypt env var '{}'", entry.name))?,
381 );
382 } else if !entry.secret {
383 entry.value_encrypted = None;
384 }
385 }
386 Ok(())
387 }
388
389 pub fn sanitize_env_vars_for_disk(&mut self) {
391 for entry in &mut self.env_vars {
392 if entry.secret {
393 entry.value = String::new();
394 }
395 }
396 }
397
398 pub fn hydrate_broker_token_from_encrypted(&mut self) {
402 let Some(broker) = self.subagents.broker.as_mut() else {
403 return;
404 };
405 if !broker.token.trim().is_empty() {
406 return; }
408 if let Some(encrypted) = &broker.token_encrypted {
409 match crate::encryption::decrypt(encrypted) {
410 Ok(value) => broker.token = value,
411 Err(e) => tracing::warn!("Failed to decrypt broker token: {}", e),
412 }
413 }
414 }
415
416 pub fn refresh_broker_token_encrypted(&mut self) -> Result<()> {
418 let Some(broker) = self.subagents.broker.as_mut() else {
419 return Ok(());
420 };
421 if broker.token.trim().is_empty() {
422 return Ok(());
424 }
425 broker.token_encrypted = Some(
426 crate::encryption::encrypt(&broker.token).context("Failed to encrypt broker token")?,
427 );
428 Ok(())
429 }
430
431 pub fn sanitize_broker_token_for_disk(&mut self) {
433 if let Some(broker) = self.subagents.broker.as_mut() {
434 broker.token = String::new();
435 }
436 }
437
438 pub fn hydrate_notifications_from_encrypted(&mut self) {
445 let ntfy = &mut self.notifications.ntfy;
446 if ntfy
447 .token
448 .as_deref()
449 .map(str::trim)
450 .unwrap_or("")
451 .is_empty()
452 {
453 if let Some(encrypted) = ntfy.token_encrypted.as_deref() {
454 match crate::encryption::decrypt(encrypted) {
455 Ok(value) => ntfy.token = Some(value),
456 Err(e) => tracing::warn!("Failed to decrypt ntfy token: {}", e),
457 }
458 }
459 }
460
461 let bark = &mut self.notifications.bark;
462 if bark
463 .device_key
464 .as_deref()
465 .map(str::trim)
466 .unwrap_or("")
467 .is_empty()
468 {
469 if let Some(encrypted) = bark.device_key_encrypted.as_deref() {
470 match crate::encryption::decrypt(encrypted) {
471 Ok(value) => bark.device_key = Some(value),
472 Err(e) => tracing::warn!("Failed to decrypt Bark device key: {}", e),
473 }
474 }
475 }
476 }
477
478 pub fn refresh_notifications_encrypted(&mut self) -> Result<()> {
484 let ntfy = &mut self.notifications.ntfy;
485 let token = ntfy.token.as_deref().unwrap_or("").trim();
486 if !token.is_empty() {
487 ntfy.token_encrypted =
488 Some(crate::encryption::encrypt(token).context("Failed to encrypt ntfy token")?);
489 }
490
491 let bark = &mut self.notifications.bark;
492 let device_key = bark.device_key.as_deref().unwrap_or("").trim();
493 if !device_key.is_empty() {
494 bark.device_key_encrypted = Some(
495 crate::encryption::encrypt(device_key)
496 .context("Failed to encrypt Bark device key")?,
497 );
498 }
499
500 Ok(())
501 }
502
503 pub fn hydrate_connect_platform_tokens_from_encrypted(&mut self) {
511 for platform in &mut self.connect.platforms {
512 let has_plaintext = platform
513 .token
514 .as_deref()
515 .map(str::trim)
516 .map(|value| !value.is_empty())
517 .unwrap_or(false);
518 if !has_plaintext {
519 if let Some(encrypted) = platform.token_encrypted.as_deref() {
520 match crate::encryption::decrypt(encrypted) {
521 Ok(value) => platform.token = Some(value),
522 Err(e) => tracing::warn!(
523 "Failed to decrypt connect platform '{}' token: {}",
524 platform.platform_type,
525 e
526 ),
527 }
528 }
529 }
530
531 let has_app_secret_plaintext = platform
532 .app_secret
533 .as_deref()
534 .map(str::trim)
535 .map(|value| !value.is_empty())
536 .unwrap_or(false);
537 if !has_app_secret_plaintext {
538 if let Some(encrypted) = platform.app_secret_encrypted.as_deref() {
539 match crate::encryption::decrypt(encrypted) {
540 Ok(value) => platform.app_secret = Some(value),
541 Err(e) => tracing::warn!(
542 "Failed to decrypt connect platform '{}' app_secret: {}",
543 platform.platform_type,
544 e
545 ),
546 }
547 }
548 }
549 }
550 }
551
552 pub fn refresh_connect_platform_tokens_encrypted(&mut self) -> Result<()> {
558 for platform in &mut self.connect.platforms {
559 let token = platform.token.as_deref().unwrap_or("").trim();
560 if !token.is_empty() {
561 platform.token_encrypted =
562 Some(crate::encryption::encrypt(token).with_context(|| {
563 format!(
564 "Failed to encrypt connect platform '{}' token",
565 platform.platform_type
566 )
567 })?);
568 }
569
570 let app_secret = platform.app_secret.as_deref().unwrap_or("").trim();
571 if !app_secret.is_empty() {
572 platform.app_secret_encrypted =
573 Some(crate::encryption::encrypt(app_secret).with_context(|| {
574 format!(
575 "Failed to encrypt connect platform '{}' app_secret",
576 platform.platform_type
577 )
578 })?);
579 }
580 }
581 Ok(())
582 }
583
584 pub fn preserve_env_sourced_provider_keys(&mut self, previous: &Config) {
600 macro_rules! restore_env_key {
601 ($field:ident) => {
602 if let (Some(current), Some(prev)) = (
603 self.providers.$field.as_mut(),
604 previous.providers.$field.as_ref(),
605 ) {
606 if prev.api_key_from_env && current.api_key.trim().is_empty() {
607 current.api_key = prev.api_key.clone();
608 current.api_key_from_env = true;
609 }
610 }
611 };
612 }
613 restore_env_key!(openai);
614 restore_env_key!(anthropic);
615 restore_env_key!(gemini);
616 }
617
618 pub fn refresh_encrypted_secrets(&mut self) -> Result<()> {
631 self.refresh_proxy_auth_encrypted()?;
632 self.refresh_provider_api_keys_encrypted()?;
633 self.refresh_provider_instance_api_keys_encrypted()?;
634 self.refresh_env_vars_encrypted()?;
635 self.refresh_cluster_fabric_encrypted()?;
636 self.refresh_notifications_encrypted()?;
637 self.refresh_connect_platform_tokens_encrypted()?;
638 Ok(())
639 }
640}