use anyhow::bail;
use bytes::Bytes;
use chacha20poly1305::aead::{Aead, OsRng};
use chacha20poly1305::{AeadCore, KeyInit, XChaCha20Poly1305, XNonce};
use saferlmdb::{
self as lmdb, Database, DatabaseOptions, Environment, ReadTransaction, WriteTransaction, put,
};
use std::sync::Arc;
use tracing::instrument;
pub struct SecretsStore {
env: Arc<Environment>,
secrets_db: Arc<Database<'static>>,
encryption_key: [u8; 32],
}
impl SecretsStore {
pub fn new(env: &Arc<Environment>, encryption_key: [u8; 32]) -> anyhow::Result<Self> {
let secrets_db = Arc::new(Database::open(
env.clone(),
Some("secrets"),
&DatabaseOptions::new(lmdb::db::Flags::CREATE),
)?);
Ok(Self {
env: env.clone(),
secrets_db,
encryption_key,
})
}
#[instrument(skip_all, err)]
pub fn put(&self, name: &str, value: &[u8]) -> anyhow::Result<()> {
tracing::info!(name);
let cipher = XChaCha20Poly1305::new(&self.encryption_key.into());
let nonce = XChaCha20Poly1305::generate_nonce(&mut OsRng);
let txn = WriteTransaction::new(self.env.clone())?;
{
let mut access = txn.access();
match cipher.encrypt(&nonce, value) {
Ok(mut encrypted) => {
encrypted.extend_from_slice(&nonce);
access.put(
&self.secrets_db,
name.as_bytes(),
&encrypted,
&put::Flags::empty(),
)?;
}
Err(err) => bail!("{err}"),
}
}
txn.commit()?;
Ok(())
}
#[instrument(skip_all, err)]
pub fn get(&self, name: &str) -> anyhow::Result<Bytes> {
tracing::info!(name);
let cipher = XChaCha20Poly1305::new(&self.encryption_key.into());
let txn = ReadTransaction::new(self.env.clone())?;
let access = txn.access();
let result = access.get::<[u8], [u8]>(&self.secrets_db, name.as_bytes())?;
let ciphertext_len = result.len() - 24;
match cipher.decrypt(
XNonce::from_slice(&result[ciphertext_len..]),
&result[..ciphertext_len],
) {
Ok(plaintext) => Ok(Bytes::copy_from_slice(plaintext.as_ref())),
Err(err) => bail!("{err}"),
}
}
}