use std::collections::HashSet;
use std::path::Path;
use chrono::{Duration, Utc};
use ed25519_dalek::SigningKey;
use uuid::Uuid;
use crate::{
agent::{backend::AgentBackend, meta::MetadataStore},
audit::{AuditEvent, AuditLog},
error::{GlovesError, Result},
human::{backend::HumanBackend, pending::PendingRequestStore},
types::{AgentId, Owner, PendingRequest, SecretId, SecretMeta, SecretValue},
};
#[derive(Debug, Clone, serde::Serialize, serde::Deserialize)]
#[serde(tag = "kind", rename_all = "snake_case")]
pub enum ListItem {
Secret(SecretMeta),
Pending(PendingRequest),
}
pub struct SecretsManager {
pub agent_backend: AgentBackend,
pub human_backend: HumanBackend,
pub metadata_store: MetadataStore,
pub pending_store: PendingRequestStore,
pub audit_log: AuditLog,
}
pub struct SetSecretOptions {
pub owner: Owner,
pub ttl: Option<Duration>,
pub created_by: AgentId,
pub recipients: HashSet<AgentId>,
pub recipient_keys: Vec<String>,
}
impl SecretsManager {
pub fn new(
agent_backend: AgentBackend,
human_backend: HumanBackend,
metadata_store: MetadataStore,
pending_store: PendingRequestStore,
audit_log: AuditLog,
) -> Self {
Self {
agent_backend,
human_backend,
metadata_store,
pending_store,
audit_log,
}
}
pub fn set(
&self,
secret_id: SecretId,
secret_value: SecretValue,
options: SetSecretOptions,
) -> Result<SecretId> {
if options.owner != Owner::Agent {
return Err(GlovesError::Forbidden);
}
self.agent_backend
.encrypt(&secret_id, &secret_value, options.recipient_keys)?;
let checksum = self.agent_backend.ciphertext_checksum(&secret_id)?;
let now = Utc::now();
let meta = SecretMeta {
id: secret_id.clone(),
owner: options.owner,
created_at: now,
expires_at: options.ttl.map(|ttl| now + ttl),
recipients: options.recipients,
created_by: options.created_by.clone(),
last_accessed: None,
access_count: 0,
checksum,
};
if let Err(error) = self.metadata_store.save(&meta) {
let _ = self.agent_backend.delete(&secret_id);
return Err(error);
}
if let Err(error) = self.audit_log.log(AuditEvent::SecretCreated {
secret_id: secret_id.clone(),
by: options.created_by,
}) {
let _ = self.metadata_store.delete(&secret_id);
let _ = self.agent_backend.delete(&secret_id);
return Err(error);
}
Ok(secret_id)
}
pub fn get(
&self,
secret_id: &SecretId,
caller: &AgentId,
caller_identity_file: Option<&Path>,
) -> Result<SecretValue> {
let mut meta = self.metadata_store.load(secret_id)?;
if matches!(meta.expires_at, Some(expires_at) if expires_at <= Utc::now()) {
return Err(GlovesError::Expired);
}
let secret = match meta.owner {
Owner::Agent => {
if !meta.recipients.contains(caller) {
return Err(GlovesError::Unauthorized);
}
if !meta.checksum.is_empty() {
let actual_checksum = self.agent_backend.ciphertext_checksum(secret_id)?;
if actual_checksum != meta.checksum {
return Err(GlovesError::IntegrityViolation);
}
}
let identity_file = caller_identity_file.ok_or(GlovesError::Unauthorized)?;
self.agent_backend.decrypt(secret_id, identity_file)?
}
Owner::Human => {
if !self.pending_store.is_fulfilled(secret_id, caller)? {
return Err(GlovesError::Forbidden);
}
self.human_backend.get(secret_id.as_str())?
}
};
meta.last_accessed = Some(Utc::now());
meta.access_count += 1;
self.metadata_store.save(&meta)?;
self.audit_log.log(AuditEvent::SecretAccessed {
secret_id: secret_id.clone(),
by: caller.clone(),
})?;
Ok(secret)
}
pub fn request(
&self,
secret_id: SecretId,
requested_by: AgentId,
reason: String,
ttl: Duration,
signing_key: &SigningKey,
) -> Result<PendingRequest> {
let request =
self.pending_store
.create(secret_id, requested_by, reason, ttl, signing_key)?;
let _ = self.audit_log.log(AuditEvent::RequestCreated {
request_id: request.id,
secret_id: request.secret_name.clone(),
requested_by: request.requested_by.clone(),
reason: request.reason.clone(),
expires_at: request.expires_at,
});
Ok(request)
}
pub fn grant(
&self,
secret_id: &SecretId,
granter: &AgentId,
granter_identity_file: &Path,
new_recipient: AgentId,
all_recipient_keys: &[String],
) -> Result<()> {
let mut meta = self.metadata_store.load(secret_id)?;
if meta.owner != Owner::Agent {
return Err(GlovesError::Forbidden);
}
if meta.created_by != *granter {
return Err(GlovesError::Forbidden);
}
meta.recipients.insert(new_recipient);
self.agent_backend.grant(
secret_id,
granter_identity_file,
all_recipient_keys.to_vec(),
)?;
meta.checksum = self.agent_backend.ciphertext_checksum(secret_id)?;
self.metadata_store.save(&meta)
}
pub fn revoke(&self, secret_id: &SecretId, caller: &AgentId) -> Result<()> {
let meta = self.metadata_store.load(secret_id)?;
if meta.created_by != *caller {
return Err(GlovesError::Forbidden);
}
self.agent_backend.delete(secret_id)?;
self.metadata_store.delete(secret_id)?;
self.audit_log.log(AuditEvent::SecretRevoked {
secret_id: secret_id.clone(),
by: caller.clone(),
})?;
Ok(())
}
pub fn list_all(&self) -> Result<Vec<ListItem>> {
let mut entries = self
.metadata_store
.list()?
.into_iter()
.map(ListItem::Secret)
.collect::<Vec<_>>();
entries.extend(
self.pending_store
.load_all()?
.into_iter()
.map(ListItem::Pending),
);
Ok(entries)
}
pub fn approve_request(
&self,
request_id: Uuid,
approved_by: AgentId,
) -> Result<PendingRequest> {
let request = self
.pending_store
.approve(request_id, approved_by.clone())?;
let _ = self.audit_log.log(AuditEvent::RequestApproved {
request_id: request.id,
secret_id: request.secret_name.clone(),
requested_by: request.requested_by.clone(),
approved_by,
});
Ok(request)
}
pub fn deny_request(&self, request_id: Uuid, denied_by: AgentId) -> Result<PendingRequest> {
let request = self.pending_store.deny(request_id, denied_by.clone())?;
let _ = self.audit_log.log(AuditEvent::RequestDenied {
request_id: request.id,
secret_id: request.secret_name.clone(),
requested_by: request.requested_by.clone(),
denied_by,
});
Ok(request)
}
}