use std::{env, ops::Deref, sync::Arc, time::Duration};
use argon2::{
Argon2,
password_hash::{PasswordHasher, SaltString},
};
use myotel::warn;
use sha2::{Digest, Sha224};
use tokio::{sync::RwLock, task::JoinHandle, time::sleep};
pub fn gen_secret_key(master_secret_key: &str, salt: &str) -> anyhow::Result<String> {
let mut hasher = Sha224::new();
hasher.update(salt);
let salt = hasher.finalize();
let salt = SaltString::encode_b64(salt.as_slice()).map_err(|err| anyhow::anyhow!("{err}"))?;
let argon2 = Argon2::default();
Ok(argon2
.hash_password(master_secret_key.as_bytes(), &salt)
.map_err(|err| anyhow::anyhow!("{err}"))?
.to_string())
}
#[derive(Debug)]
pub struct SecretKeyCache<P> {
args: Arc<SecretArgs>,
secret_key: Arc<RwLock<String>>,
provider: Arc<P>,
refresh_interval: Option<Duration>,
join_handle: Option<JoinHandle<()>>,
}
impl<P: SecretArgsProvide> SecretKeyCache<P> {
pub fn new(provider: P, refresh_interval: Option<Duration>) -> anyhow::Result<Self> {
let args = provider.secret_key_args()?;
let secret_key = Arc::new(RwLock::new(Self::new_secret_key(&args)?));
let mut s = Self {
args: Arc::new(args),
secret_key,
provider: Arc::new(provider),
refresh_interval,
join_handle: None,
};
if let Some(refresh_interval) = refresh_interval {
if !refresh_interval.is_zero() {
s.join_handle.replace(s.refresh());
}
}
Ok(s)
}
pub async fn obtain_secret_key(&self) -> String {
self.secret_key.read().await.clone()
}
fn new_secret_key(
SecretArgs {
master_secret_key,
salt,
}: &SecretArgs,
) -> anyhow::Result<String> {
gen_secret_key(master_secret_key, salt)
}
fn refresh(&self) -> JoinHandle<()> {
let args = self.args.clone();
let secret_key = self.secret_key.clone();
let provide = self.provider.clone();
let refresh_interval = self.refresh_interval.unwrap();
tokio::spawn(async move {
loop {
sleep(refresh_interval).await;
match provide.secret_key_args() {
Ok(new_args) if new_args == *args => {
continue;
}
Err(err) => {
warn!("Failed to generate secret key args, error={err:?}");
continue;
}
Ok(new_args) => match Self::new_secret_key(&new_args) {
Ok(new_secret_key) => *secret_key.write().await = new_secret_key,
Err(err) => {
warn!("Failed to generate secret key, error={err:?}")
}
},
}
}
})
}
}
impl<P> Drop for SecretKeyCache<P> {
fn drop(&mut self) {
if let Some(join_handle) = &self.join_handle {
join_handle.abort();
}
}
}
#[derive(Debug, Clone, PartialEq)]
pub struct SecretArgs {
pub master_secret_key: String,
pub salt: String,
}
pub trait SecretArgsProvide: Sync + Send + 'static {
fn secret_key_args(&self) -> anyhow::Result<SecretArgs>;
}
impl<T: SecretArgsProvide> SecretArgsProvide for Arc<T> {
fn secret_key_args(&self) -> anyhow::Result<SecretArgs> {
self.deref().secret_key_args()
}
}
impl SecretArgsProvide for SecretArgs {
fn secret_key_args(&self) -> anyhow::Result<SecretArgs> {
Ok(self.clone())
}
}
pub const ENV_DIPPER_SECRET_KEY: &str = "DIPPER_SECRET_KEY";
pub const ENV_DIPPER_SECRET_SATL: &str = "DIPPER_SECRET_SATL";
#[derive(Debug, Clone)]
pub struct EnvSecretArgsProvide(());
impl EnvSecretArgsProvide {
pub fn new() -> anyhow::Result<Self> {
let s = Self(());
let _ = s.secret_key_args()?;
Ok(s)
}
}
impl SecretArgsProvide for EnvSecretArgsProvide {
fn secret_key_args(&self) -> anyhow::Result<SecretArgs> {
Ok(SecretArgs {
master_secret_key: env::var(ENV_DIPPER_SECRET_KEY)?,
salt: env::var(ENV_DIPPER_SECRET_SATL)?,
})
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_gen_secret_key() {
let secret_key = gen_secret_key("123456", "x-mod").unwrap();
println!("{secret_key}");
}
}