geth/crates/geth-secrets/src/lib.rs

99 lines
2.8 KiB
Rust
Raw Normal View History

2026-05-15 15:08:20 +02:00
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<u8>,
}
#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
pub struct BearerAccess {
pub secret: SecretId,
pub resource: ResourceId,
pub capabilities: Vec<Capability>,
pub expires_at: Option<UnixMillis>,
pub may_delegate: bool,
}
impl BearerAccess {
#[must_use]
pub fn resource_scoped(
secret: SecretId,
resource: ResourceId,
capabilities: Vec<Capability>,
) -> 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(_))
));
}
}