use async_trait::async_trait;
use serde::{Deserialize, Serialize};
use thiserror::Error;
use crate::lease::Lease;
use crate::storage::StorageBackend;
#[derive(Debug, Error)]
pub enum EngineError {
#[error("not found")]
NotFound,
#[error("operation not supported by this engine")]
Unsupported,
#[error("invalid request: {0}")]
InvalidRequest(String),
#[error("storage error: {0}")]
Storage(#[from] crate::storage::StorageError),
#[error("provider rejected the request: {0}")]
Provider(String),
#[error("engine error: {0}")]
Other(String),
}
pub type EngineResult<T> = Result<T, EngineError>;
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
#[serde(rename_all = "kebab-case")]
pub enum CredentialShape {
MintAndRevoke,
MintExpiryOnly,
RefreshBroker,
StaticCustody,
Federation,
}
impl CredentialShape {
pub fn revocable(self) -> bool {
matches!(self, Self::MintAndRevoke)
}
pub fn as_str(self) -> &'static str {
match self {
Self::MintAndRevoke => "mint-and-revoke",
Self::MintExpiryOnly => "mint-expiry-only",
Self::RefreshBroker => "refresh-broker",
Self::StaticCustody => "static-custody",
Self::Federation => "federation",
}
}
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct TtlDoc {
pub min_seconds: Option<i64>,
pub max_seconds: Option<i64>,
pub fixed: bool,
pub note: String,
}
impl TtlDoc {
pub fn fixed(seconds: i64, note: impl Into<String>) -> Self {
Self {
min_seconds: Some(seconds),
max_seconds: Some(seconds),
fixed: true,
note: note.into(),
}
}
pub fn range(min: i64, max: i64, note: impl Into<String>) -> Self {
Self {
min_seconds: Some(min),
max_seconds: Some(max),
fixed: false,
note: note.into(),
}
}
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct PathDoc {
pub path: String,
pub methods: Vec<String>,
pub capability: String,
pub description: String,
}
impl PathDoc {
pub fn new(
path: impl Into<String>,
methods: &[&str],
capability: &str,
description: impl Into<String>,
) -> Self {
Self {
path: path.into(),
methods: methods.iter().map(|m| m.to_string()).collect(),
capability: capability.to_string(),
description: description.into(),
}
}
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct EngineDoc {
pub provider: String,
pub mechanism: String,
pub shape: CredentialShape,
pub revocable: bool,
pub revoke_effect: String,
pub ttl: TtlDoc,
pub scoping: String,
pub root_credential: String,
pub paths: Vec<PathDoc>,
pub docs_url: Option<String>,
#[serde(default, skip_serializing_if = "Vec::is_empty")]
pub caveats: Vec<String>,
}
#[derive(Debug)]
pub struct GeneratedCredential {
pub data: serde_json::Value,
pub lease: Lease,
pub scoped_to: Vec<String>,
pub shape: Option<CredentialShape>,
pub revoke_effect: Option<String>,
}
impl GeneratedCredential {
pub fn new(data: serde_json::Value, lease: Lease, scoped_to: Vec<String>) -> Self {
Self {
data,
lease,
scoped_to,
shape: None,
revoke_effect: None,
}
}
pub fn with_shape(mut self, shape: CredentialShape, revoke_effect: impl Into<String>) -> Self {
self.shape = Some(shape);
self.revoke_effect = Some(revoke_effect.into());
self
}
}
#[async_trait]
pub trait SecretsEngine: Send + Sync {
async fn read(&self, storage: &dyn StorageBackend, path: &str) -> EngineResult<serde_json::Value>;
async fn write(
&self,
storage: &dyn StorageBackend,
path: &str,
data: serde_json::Value,
) -> EngineResult<()>;
async fn delete(&self, storage: &dyn StorageBackend, path: &str) -> EngineResult<()>;
async fn list(&self, storage: &dyn StorageBackend, prefix: &str) -> EngineResult<Vec<String>>;
fn doc(&self) -> EngineDoc;
async fn generate(
&self,
_storage: &dyn StorageBackend,
_role: &str,
) -> EngineResult<GeneratedCredential> {
Err(EngineError::Unsupported)
}
async fn revoke(&self, _storage: &dyn StorageBackend, _lease: &Lease) -> EngineResult<()> {
Err(EngineError::Unsupported)
}
}