docbox_core/secrets/
json.rs

1use std::{collections::HashMap, fmt::Debug, path::PathBuf, str::FromStr};
2
3use age::secrecy::SecretString;
4use anyhow::Context;
5use serde::{Deserialize, Serialize};
6
7use crate::secrets::SecretManager;
8
9use super::Secret;
10
11#[derive(Clone, Deserialize, Serialize)]
12pub struct JsonSecretManagerConfig {
13    path: PathBuf,
14    key: String,
15}
16
17impl Debug for JsonSecretManagerConfig {
18    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
19        f.debug_struct("JsonSecretManagerConfig")
20            .field("path", &self.path)
21            .finish()
22    }
23}
24
25impl JsonSecretManagerConfig {
26    pub fn from_env() -> anyhow::Result<Self> {
27        let key = std::env::var("DOCBOX_SECRET_MANAGER_KEY")
28            .context("missing DOCBOX_SECRET_MANAGER_KEY secret key to access store")?;
29        let path = std::env::var("DOCBOX_SECRET_MANAGER_PATH")
30            .context("missing DOCBOX_SECRET_MANAGER_PATH file path to access store")?;
31        Ok(Self {
32            key,
33            path: PathBuf::from_str(&path)?,
34        })
35    }
36}
37
38#[derive(Deserialize, Serialize)]
39struct SecretFile {
40    secrets: HashMap<String, String>,
41}
42
43// Local encrypted JSON based secret manager
44pub struct JsonSecretManager {
45    path: PathBuf,
46    key: SecretString,
47}
48
49impl JsonSecretManager {
50    pub fn from_config(config: JsonSecretManagerConfig) -> Self {
51        let key = SecretString::from(config.key);
52
53        Self {
54            path: config.path,
55            key,
56        }
57    }
58
59    async fn read_file(&self) -> anyhow::Result<SecretFile> {
60        let bytes = tokio::fs::read(&self.path).await?;
61        let identity = age::scrypt::Identity::new(self.key.clone());
62        let decrypted = age::decrypt(&identity, &bytes)?;
63        let file = serde_json::from_slice(&decrypted)?;
64        Ok(file)
65    }
66
67    async fn write_file(&self, file: SecretFile) -> anyhow::Result<()> {
68        let bytes = serde_json::to_string(&file)?;
69        let recipient = age::scrypt::Recipient::new(self.key.clone());
70        let encrypted = age::encrypt(&recipient, bytes.as_bytes())?;
71        tokio::fs::write(&self.path, encrypted).await?;
72        Ok(())
73    }
74}
75
76impl SecretManager for JsonSecretManager {
77    async fn get_secret(&self, name: &str) -> anyhow::Result<Option<Secret>> {
78        let file = match self.read_file().await {
79            Ok(value) => value,
80            Err(_) => return Ok(None),
81        };
82
83        let secret = file.secrets.get(name);
84        Ok(secret.map(|value| Secret::String(value.clone())))
85    }
86
87    async fn create_secret(&self, name: &str, value: &str) -> anyhow::Result<()> {
88        let mut secrets = if self.path.exists() {
89            self.read_file().await?
90        } else {
91            SecretFile {
92                secrets: Default::default(),
93            }
94        };
95        secrets.secrets.insert(name.to_string(), value.to_string());
96        self.write_file(secrets).await?;
97        Ok(())
98    }
99}