use geth_types::{Capability, PrincipalId, ResourceId, SecretId, UnixMillis}; use serde::{Deserialize, Serialize}; pub const RESOURCE_SECRET_SIGNATURE_NAMESPACE: &str = "geth.resource-secret.v1@geth.local"; #[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)] pub struct ResourceMasterSecret { pub id: SecretId, pub resource: ResourceId, pub epoch: u64, pub created_at: UnixMillis, } #[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)] pub struct ResourceKeyEnvelope { pub secret: SecretId, pub recipient: PrincipalId, pub epoch: u64, pub algorithm: String, pub ciphertext: Vec, } #[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)] pub struct BearerAccess { pub secret: SecretId, pub resource: ResourceId, pub capabilities: Vec, pub expires_at: Option, pub may_delegate: bool, } impl BearerAccess { #[must_use] pub fn resource_scoped( secret: SecretId, resource: ResourceId, capabilities: Vec, ) -> Self { Self { secret, resource, capabilities, expires_at: None, may_delegate: false, } } } #[derive(Debug, thiserror::Error)] pub enum SecretsError { #[error("bearer access must grant at least one capability")] EmptyBearerCapabilities, #[error("capability is not allowed for bearer access: {0}")] ForbiddenBearerCapability(String), } pub fn validate_bearer_capabilities(capabilities: &[Capability]) -> Result<(), SecretsError> { if capabilities.is_empty() { return Err(SecretsError::EmptyBearerCapabilities); } for capability in capabilities { let capability = capability.as_str(); if matches!( capability, "auth.delegate" | "auth.revoke" | "trust.modify" | "ssh_proxy.admin_shell" | "node.enroll" ) { return Err(SecretsError::ForbiddenBearerCapability( capability.to_owned(), )); } } Ok(()) } #[cfg(test)] mod tests { use super::*; #[test] fn bearer_capabilities_reject_trust_mutation() { assert!(validate_bearer_capabilities(&["kv.read".into(), "kv.write".into()]).is_ok()); assert!(matches!( validate_bearer_capabilities(&[]), Err(SecretsError::EmptyBearerCapabilities) )); assert!(matches!( validate_bearer_capabilities(&["auth.delegate".into()]), Err(SecretsError::ForbiddenBearerCapability(_)) )); assert!(matches!( validate_bearer_capabilities(&["node.enroll".into()]), Err(SecretsError::ForbiddenBearerCapability(_)) )); } }