1use crate::{Secret, SecretManager};
14use age::secrecy::SecretString;
15use anyhow::Context;
16use serde::{Deserialize, Serialize};
17use std::{collections::HashMap, fmt::Debug, path::PathBuf, str::FromStr};
18
19#[derive(Clone, Deserialize, Serialize)]
20pub struct JsonSecretManagerConfig {
21 pub key: String,
23
24 pub path: PathBuf,
26}
27
28impl Debug for JsonSecretManagerConfig {
29 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
30 f.debug_struct("JsonSecretManagerConfig")
31 .field("path", &self.path)
32 .finish()
33 }
34}
35
36impl JsonSecretManagerConfig {
37 pub fn from_env() -> anyhow::Result<Self> {
39 let key = std::env::var("DOCBOX_SECRET_MANAGER_KEY")
40 .context("missing DOCBOX_SECRET_MANAGER_KEY secret key to access store")?;
41 let path = std::env::var("DOCBOX_SECRET_MANAGER_PATH")
42 .context("missing DOCBOX_SECRET_MANAGER_PATH file path to access store")?;
43
44 Ok(Self {
45 key,
46 path: PathBuf::from_str(&path)?,
47 })
48 }
49}
50
51pub struct JsonSecretManager {
53 path: PathBuf,
54 key: SecretString,
55}
56
57#[derive(Deserialize, Serialize)]
59struct SecretFile {
60 secrets: HashMap<String, String>,
62}
63
64impl JsonSecretManager {
65 pub fn from_config(config: JsonSecretManagerConfig) -> Self {
66 let key = SecretString::from(config.key);
67
68 Self {
69 path: config.path,
70 key,
71 }
72 }
73
74 async fn read_file(&self) -> anyhow::Result<SecretFile> {
75 let bytes = tokio::fs::read(&self.path).await?;
76 let identity = age::scrypt::Identity::new(self.key.clone());
77 let decrypted = age::decrypt(&identity, &bytes)?;
78 let file = serde_json::from_slice(&decrypted)?;
79 Ok(file)
80 }
81
82 async fn write_file(&self, file: SecretFile) -> anyhow::Result<()> {
83 let bytes = serde_json::to_string(&file)?;
84 let recipient = age::scrypt::Recipient::new(self.key.clone());
85 let encrypted = age::encrypt(&recipient, bytes.as_bytes())?;
86 tokio::fs::write(&self.path, encrypted).await?;
87 Ok(())
88 }
89}
90
91impl SecretManager for JsonSecretManager {
92 async fn get_secret(&self, name: &str) -> anyhow::Result<Option<Secret>> {
93 let file = match self.read_file().await {
94 Ok(value) => value,
95 Err(_) => return Ok(None),
96 };
97
98 let secret = file.secrets.get(name);
99 Ok(secret.map(|value| Secret::String(value.clone())))
100 }
101
102 async fn set_secret(&self, name: &str, value: &str) -> anyhow::Result<()> {
103 let mut secrets = if self.path.exists() {
104 self.read_file().await?
105 } else {
106 SecretFile {
107 secrets: Default::default(),
108 }
109 };
110
111 secrets.secrets.insert(name.to_string(), value.to_string());
112 self.write_file(secrets).await?;
113 Ok(())
114 }
115
116 async fn delete_secret(&self, name: &str) -> anyhow::Result<()> {
117 let mut secrets = if self.path.exists() {
118 self.read_file().await?
119 } else {
120 SecretFile {
121 secrets: Default::default(),
122 }
123 };
124
125 secrets.secrets.remove(name);
126 self.write_file(secrets).await?;
127 Ok(())
128 }
129}