docbox_core/secrets/
json.rs1use 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)]
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
43pub 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}