use async_trait::async_trait;
mod manifest_gate;
#[cfg(test)]
mod test_gates;
pub(crate) use manifest_gate::ManifestSecurityGate;
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum IdentityOperation {
Resolve,
Link,
Unlink,
ListLinks,
CreateUser,
}
impl IdentityOperation {
#[must_use]
pub fn required_capability(self) -> &'static str {
match self {
Self::Resolve => "resolve",
Self::Link | Self::Unlink | Self::ListLinks => "link",
Self::CreateUser => "admin",
}
}
}
pub(super) fn identity_capability_satisfies(declared: &[String], required: &str) -> bool {
if declared.iter().any(|d| d == required) {
return true;
}
match required {
"resolve" => declared.iter().any(|d| d == "link" || d == "admin"),
"link" => declared.iter().any(|d| d == "admin"),
_ => false,
}
}
#[async_trait]
pub trait CapsuleSecurityGate: Send + Sync {
async fn check_http_request(
&self,
capsule_id: &str,
method: &str,
url: &str,
) -> Result<(), String>;
async fn check_file_read(
&self,
capsule_id: &str,
path: &str,
principal_home: Option<&std::path::Path>,
) -> Result<(), String>;
async fn check_file_write(
&self,
capsule_id: &str,
path: &str,
principal_home: Option<&std::path::Path>,
) -> Result<(), String>;
async fn check_host_process(&self, capsule_id: &str, command: &str) -> Result<(), String>;
async fn check_net_bind(&self, capsule_id: &str) -> Result<(), String> {
Err(format!(
"capsule '{capsule_id}' denied: net_bind not permitted (default)"
))
}
async fn check_net_connect(
&self,
capsule_id: &str,
_host: &str,
_port: u16,
) -> Result<(), String> {
Err(format!(
"capsule '{capsule_id}' denied: net_connect not permitted (default)"
))
}
async fn check_uplink_register(
&self,
_capsule_id: &str,
_uplink_name: &str,
_platform: &str,
) -> Result<(), String> {
Ok(())
}
async fn check_identity(
&self,
capsule_id: &str,
operation: IdentityOperation,
) -> Result<(), String> {
Err(format!(
"capsule '{capsule_id}' denied: identity operation '{:?}' not permitted (default)",
operation
))
}
}