ordinary_storage/stores/
secrets.rs1use anyhow::bail;
6use bytes::Bytes;
7use chacha20poly1305::aead::{Aead, OsRng};
8use chacha20poly1305::{AeadCore, KeyInit, XChaCha20Poly1305, XNonce};
9use saferlmdb::{
10 self as lmdb, Database, DatabaseOptions, Environment, ReadTransaction, WriteTransaction, put,
11};
12use std::sync::Arc;
13use tracing::instrument;
14
15pub struct SecretsStore {
16 env: Arc<Environment>,
17
18 secrets_db: Arc<Database<'static>>,
20
21 encryption_key: [u8; 32],
22}
23
24impl SecretsStore {
25 pub fn new(env: &Arc<Environment>, encryption_key: [u8; 32]) -> anyhow::Result<Self> {
26 let secrets_db = Arc::new(Database::open(
27 env.clone(),
28 Some("secrets"),
29 &DatabaseOptions::new(lmdb::db::Flags::CREATE),
30 )?);
31
32 Ok(Self {
33 env: env.clone(),
34 secrets_db,
35 encryption_key,
36 })
37 }
38
39 #[instrument(skip_all, err)]
41 pub fn put(&self, name: &str, value: &[u8]) -> anyhow::Result<()> {
42 tracing::info!(name);
43
44 let cipher = XChaCha20Poly1305::new(&self.encryption_key.into());
45 let nonce = XChaCha20Poly1305::generate_nonce(&mut OsRng);
46
47 let txn = WriteTransaction::new(self.env.clone())?;
48
49 {
50 let mut access = txn.access();
51
52 match cipher.encrypt(&nonce, value) {
53 Ok(mut encrypted) => {
54 encrypted.extend_from_slice(&nonce);
55 access.put(
56 &self.secrets_db,
57 name.as_bytes(),
58 &encrypted,
59 &put::Flags::empty(),
60 )?;
61 }
62 Err(err) => bail!("{err}"),
63 }
64 }
65
66 txn.commit()?;
67
68 Ok(())
69 }
70
71 #[instrument(skip_all, err)]
73 pub fn get(&self, name: &str) -> anyhow::Result<Bytes> {
74 tracing::info!(name);
75
76 let cipher = XChaCha20Poly1305::new(&self.encryption_key.into());
77
78 let txn = ReadTransaction::new(self.env.clone())?;
79 let access = txn.access();
80
81 let result = access.get::<[u8], [u8]>(&self.secrets_db, name.as_bytes())?;
82
83 let ciphertext_len = result.len() - 24;
84
85 match cipher.decrypt(
86 XNonce::from_slice(&result[ciphertext_len..]),
87 &result[..ciphertext_len],
88 ) {
89 Ok(plaintext) => Ok(Bytes::copy_from_slice(plaintext.as_ref())),
90 Err(err) => bail!("{err}"),
91 }
92 }
93}