use std::sync::Arc;
use crate::proto::ids::{TokenId, VaultId};
use crate::proto::path::EnvPath;
use zeroize::Zeroizing;
#[derive(Debug, Clone)]
pub struct StoreError {
message: String,
source: Option<Arc<dyn std::error::Error + Send + Sync>>,
}
impl StoreError {
pub fn new(message: impl Into<String>) -> StoreError {
StoreError {
message: message.into(),
source: None,
}
}
pub fn with_source(
message: impl Into<String>,
source: impl std::error::Error + Send + Sync + 'static,
) -> StoreError {
StoreError {
message: message.into(),
source: Some(Arc::new(source)),
}
}
pub fn message(&self) -> &str {
&self.message
}
}
impl std::fmt::Display for StoreError {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.write_str(&self.message)
}
}
impl std::error::Error for StoreError {
fn source(&self) -> Option<&(dyn std::error::Error + 'static)> {
self.source
.as_deref()
.map(|e| e as &(dyn std::error::Error + 'static))
}
}
#[derive(Debug, Clone, PartialEq, Eq)]
#[non_exhaustive]
pub enum KeyName {
Node(EnvPath),
Token {
vault: VaultId,
id: TokenId,
},
RekeyOld(EnvPath),
RekeyNew(EnvPath),
}
impl KeyName {
pub fn entry(&self) -> String {
match self {
KeyName::Node(p) => format!("node:{p}"),
KeyName::Token { vault, id } => format!("token:{}:{}", vault.to_hex(), id.to_hex()),
KeyName::RekeyOld(p) => format!("rekey-old:{p}"),
KeyName::RekeyNew(p) => format!("rekey-new:{p}"),
}
}
}
impl std::fmt::Display for KeyName {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.write_str(&self.entry())
}
}
pub trait KeyStore: Send + Sync {
fn get(&self, key: &KeyName) -> Result<Option<Zeroizing<String>>, StoreError>;
fn set(&self, key: &KeyName, value: &str) -> Result<(), StoreError>;
fn delete(&self, key: &KeyName) -> Result<(), StoreError>;
}
impl<K: KeyStore + ?Sized> KeyStore for Arc<K> {
fn get(&self, key: &KeyName) -> Result<Option<Zeroizing<String>>, StoreError> {
(**self).get(key)
}
fn set(&self, key: &KeyName, value: &str) -> Result<(), StoreError> {
(**self).set(key, value)
}
fn delete(&self, key: &KeyName) -> Result<(), StoreError> {
(**self).delete(key)
}
}
#[cfg(feature = "test-util")]
#[derive(Default)]
pub struct MemoryKeyStore {
entries: std::sync::Mutex<std::collections::BTreeMap<String, Zeroizing<String>>>,
}
#[cfg(feature = "test-util")]
impl std::fmt::Debug for MemoryKeyStore {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.debug_struct("MemoryKeyStore")
.field("entries", &self.names())
.finish()
}
}
#[cfg(feature = "test-util")]
impl MemoryKeyStore {
pub fn new() -> MemoryKeyStore {
MemoryKeyStore::default()
}
pub fn names(&self) -> Vec<String> {
self.lock().keys().cloned().collect()
}
fn lock(
&self,
) -> std::sync::MutexGuard<'_, std::collections::BTreeMap<String, Zeroizing<String>>> {
self.entries.lock().unwrap_or_else(|p| p.into_inner())
}
}
#[cfg(feature = "test-util")]
impl KeyStore for MemoryKeyStore {
fn get(&self, key: &KeyName) -> Result<Option<Zeroizing<String>>, StoreError> {
Ok(self.lock().get(&key.entry()).cloned())
}
fn set(&self, key: &KeyName, value: &str) -> Result<(), StoreError> {
self.lock()
.insert(key.entry(), Zeroizing::new(value.to_owned()));
Ok(())
}
fn delete(&self, key: &KeyName) -> Result<(), StoreError> {
self.lock().remove(&key.entry());
Ok(())
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn entry_names_are_the_ones_gv_has_always_used() {
let p: EnvPath = "acme/dev".parse().unwrap();
assert_eq!(KeyName::Node(p.clone()).entry(), "node:acme/dev");
assert_eq!(KeyName::RekeyOld(p.clone()).entry(), "rekey-old:acme/dev");
assert_eq!(KeyName::RekeyNew(p).entry(), "rekey-new:acme/dev");
}
}