secretgarden 0.5.0

tool for generating and securely storing secrets
use anyhow::{anyhow, bail, Context, Result as AHResult};
use serde::{de::DeserializeOwned, Deserialize, Serialize};
use std::collections::HashMap;
use std::path::Path;

use crate::types::{ConfigType, GenerateOpt, OptionsType, Secret, SecretMap, SecretType};

const DEFAULT_FILENAME: &str = "secretgarden.dat";

#[derive(Serialize, Deserialize)]
struct SecretFile {
    secrets: SecretMap,
}

pub trait SecretStore {
    fn get_or_generate<'a, OptsT: OptionsType<'a>, ConfigT: ConfigType<'a>>(
        &mut self,
        f: impl Fn(&OptsT, &ConfigT) -> AHResult<String>,
        secret_type: SecretType,
        opts: &OptsT,
        config: &ConfigT,
    ) -> AHResult<String>;

    fn get_secret_with_config<T: DeserializeOwned>(
        &mut self,
        secret_type: SecretType,
        name: &str,
    ) -> AHResult<(String, T)>;

    fn set_opaque(&mut self, key: String, value: String) -> AHResult<()>;

    fn get_secrets<'a>(&'a mut self) -> AHResult<&'a SecretMap>;
}

pub struct ContainedSecretStore<S: SecretContainerFile> {
    _secrets: Option<SecretMap>,
    secret_container_file: S,
}

pub trait SecretContainerFile {
    fn decrypt<P: AsRef<Path>>(&mut self, path: P) -> AHResult<Vec<u8>>;
    fn encrypt<P: AsRef<Path>>(&mut self, path: P, data: Vec<u8>) -> AHResult<()>;
}

impl<S: SecretContainerFile> ContainedSecretStore<S> {
    pub fn new(secret_container_file: S) -> ContainedSecretStore<S> {
        ContainedSecretStore {
            _secrets: None,
            secret_container_file,
        }
    }

    fn _load_secrets(&mut self) -> AHResult<&mut SecretMap> {
        if let Some(ref mut _secrets) = self._secrets {
            return Ok(_secrets);
        }

        let mut _secrets;

        let secrets_location = Path::new(DEFAULT_FILENAME);

        if secrets_location.is_file() {
            let existing_contents = self.secret_container_file.decrypt(secrets_location)?;

            let existing_secrets: SecretFile = serde_json::from_slice(&existing_contents)
                .context("Could not parse secrets JSON")?;

            _secrets = existing_secrets.secrets;
        } else {
            _secrets = HashMap::new();
        }

        self._secrets = Some(_secrets);
        Ok(self._secrets.as_mut().unwrap())
    }

    fn _store_secrets(&mut self) -> AHResult<()> {
        let serialized_secrets = serde_json::to_vec(&SecretFile {
            secrets: self._secrets.to_owned().unwrap(),
        })
        .context("Failed to serialize secrets")?;

        self.secret_container_file
            .encrypt(DEFAULT_FILENAME, serialized_secrets)
    }
}

impl<S: SecretContainerFile> SecretStore for ContainedSecretStore<S> {
    fn get_or_generate<'a, OptsT: OptionsType<'a>, ConfigT: ConfigType<'a>>(
        &mut self,
        f: impl Fn(&OptsT, &ConfigT) -> AHResult<String>,
        secret_type: SecretType,
        opts: &OptsT,
        config: &ConfigT,
    ) -> AHResult<String> {
        let secrets = self._load_secrets()?;
        let common_opts = opts.common_opts();
        let serialized_config: serde_json::Value = serde_json::from_str(
            &serde_json::to_string(config).context("Failed to serialize options")?,
        )
        .context("Failed to deserialize options")?;

        if let Some(secret) = secrets.get(&common_opts.name) {
            if !config.should_cause_secret_regeneration(secret)?
                || common_opts.generate == GenerateOpt::Once
            {
                return Ok(secret.value.to_string());
            }
        }

        if common_opts.generate == GenerateOpt::Never {
            bail!(
                "Secret {} does not exist and generation is disabled",
                common_opts.name
            );
        }

        let value = f(opts, config)?;
        secrets.insert(
            common_opts.name.to_owned(),
            Secret {
                secret_type,
                value: value.to_owned(),
                config: serialized_config.clone(),
            },
        );
        self._store_secrets()?;

        Ok(value)
    }

    fn get_secret_with_config<T: DeserializeOwned>(
        &mut self,
        secret_type: SecretType,
        name: &str,
    ) -> AHResult<(String, T)> {
        let secrets = self._load_secrets()?;

        let secret = secrets
            .get(name)
            .ok_or(anyhow!("Secret {} does not exist", name))?;

        if secret.secret_type != secret_type {
            return Err(anyhow!(
                "Expected secret {} to be of type {}, was {}",
                name,
                secret_type,
                secret.secret_type,
            ));
        }

        let secret_config: T = serde_json::from_value(secret.config.clone())
            .context("Failed to deserialize options")?;

        Ok((secret.value.clone(), secret_config))
    }

    fn set_opaque(&mut self, key: String, value: String) -> AHResult<()> {
        let secrets = self._load_secrets()?;

        secrets.insert(
            key,
            Secret {
                secret_type: SecretType::Opaque,
                value: value.clone(),
                config: serde_json::Value::Object(serde_json::Map::new()),
            },
        );
        self._store_secrets()?;

        Ok(())
    }

    fn get_secrets<'a>(&'a mut self) -> AHResult<&'a SecretMap> {
        self._load_secrets()?;

        Ok(self._secrets.as_ref().unwrap())
    }
}