use serde::{Deserialize, Serialize};
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct RbacPolicy {
pub roles: Vec<RoleDefinition>,
#[serde(default)]
pub assignments: Vec<Assignment>,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct RoleDefinition {
pub name: String,
#[serde(default)]
pub permissions: Vec<String>,
#[serde(default)]
pub resources: Vec<String>,
#[serde(default)]
pub inherits: Vec<String>,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct Assignment {
pub subject: String,
pub roles: Vec<String>,
}
impl RbacPolicy {
pub fn from_yaml(yaml: &str) -> Result<Self, serde_yaml::Error> {
serde_yaml::from_str(yaml)
}
pub fn validate(&self) -> Result<(), String> {
let names: std::collections::HashSet<&str> =
self.roles.iter().map(|r| r.name.as_str()).collect();
for role in &self.roles {
for parent in &role.inherits {
if !names.contains(parent.as_str()) {
return Err(format!(
"role '{}' inherits from unknown role '{}'",
role.name, parent
));
}
}
}
for role in &self.roles {
walk_inheritance(&role.name, self, &mut |_| {})?;
}
Ok(())
}
}
pub(crate) fn walk_inheritance(
start: &str,
policy: &RbacPolicy,
visit: &mut impl FnMut(&RoleDefinition),
) -> Result<(), String> {
let mut visited = std::collections::HashSet::new();
let mut path = std::collections::HashSet::new();
walk_inheritance_inner(start, policy, &mut visited, &mut path, visit)
}
fn walk_inheritance_inner(
role_name: &str,
policy: &RbacPolicy,
visited: &mut std::collections::HashSet<String>,
path: &mut std::collections::HashSet<String>,
visit: &mut impl FnMut(&RoleDefinition),
) -> Result<(), String> {
if path.contains(role_name) {
return Err(format!(
"circular inheritance detected for role '{role_name}'"
));
}
if !visited.insert(role_name.to_owned()) {
return Ok(());
}
let Some(role) = policy.roles.iter().find(|r| r.name == role_name) else {
return Ok(());
};
path.insert(role_name.to_owned());
visit(role);
for parent in &role.inherits {
walk_inheritance_inner(parent, policy, visited, path, visit)?;
}
path.remove(role_name);
Ok(())
}
#[cfg(test)]
mod tests;