use std::{io::Read, str::FromStr, sync::Arc};
use oso::{Oso, PolarClass, PolarValue, ToPolar};
use crate::{
commons::{
actor::Actor,
error::{Error, KrillIoError},
KrillResult,
},
constants::{ACTOR_DEF_ADMIN_TOKEN, ACTOR_DEF_ANON, ACTOR_DEF_KRILL, ACTOR_DEF_TESTBED},
daemon::{
auth::{
common::{permissions::Permission, NoResourceType},
Handle,
},
config::Config,
},
};
#[derive(Clone)]
pub struct AuthPolicy {
oso: Arc<Oso>,
}
impl std::ops::Deref for AuthPolicy {
type Target = Arc<Oso>;
fn deref(&self) -> &Self::Target {
&self.oso
}
}
impl AuthPolicy {
pub fn new(config: Arc<Config>) -> KrillResult<Self> {
let mut oso = Oso::new();
oso.register_class(Actor::get_polar_class()).unwrap();
oso.register_class(Handle::get_polar_class()).unwrap();
oso.register_class(Permission::get_polar_class()).unwrap();
for permission in Permission::iter() {
let name = format!("{}", permission);
oso.register_constant(permission, &name).unwrap();
}
Self::load_internal_policy(&mut oso, include_bytes!("../../../defaults/roles.polar"), "roles")?;
Self::load_internal_policy(&mut oso, include_bytes!("../../../defaults/rules.polar"), "rules")?;
Self::load_internal_policy(&mut oso, include_bytes!("../../../defaults/aliases.polar"), "aliases")?;
Self::load_internal_policy(&mut oso, include_bytes!("../../../defaults/rbac.polar"), "rbac")?;
Self::load_internal_policy(&mut oso, include_bytes!("../../../defaults/abac.polar"), "abac")?;
Self::load_user_policy(config, &mut oso)?;
debug!("Running Polar self checks");
Self::exec_query(&mut oso, r#"actor_has_role(Actor.builtin("krill"), "admin")"#)?;
Self::exec_query(&mut oso, r#"actor_has_role(Actor.builtin("admin-token"), "admin")"#)?;
Self::exec_query(&mut oso, r#"not actor_has_role(Actor.builtin("anon"), _)"#)?;
Self::exec_query(&mut oso, r#"actor_has_role(Actor.builtin("testbed"), "testbed")"#)?;
Ok(AuthPolicy { oso: Arc::new(oso) })
}
pub fn is_allowed<U, A, R>(&self, actor: U, action: A, resource: R) -> Result<bool, Error>
where
U: ToPolar,
A: ToPolar,
R: ToPolar,
{
self.oso
.is_allowed(actor, action, resource)
.map_err(|err| Error::custom(format!("Internal error while checking access against policy: {}", err)))
}
fn load_internal_policy(oso: &mut Oso, bytes: &[u8], fname: &str) -> KrillResult<()> {
trace!("Loading Polar policy '{}'", fname);
oso.load_str(
std::str::from_utf8(bytes).map_err(|err| {
Error::custom(format!("Internal Polar policy '{}' is not valid UTF-8: {}", fname, err))
})?,
)
.map_err(|err| {
Error::custom(format!(
"Internal Polar policy '{}' is not valid Polar syntax: {}",
fname, err
))
})
}
fn exec_query(oso: &mut Oso, query: &str) -> KrillResult<()> {
oso.query(query)
.map_err(|err| Error::custom(format!("The Polar self check query '{}' failed: {}", query, err)))?;
Ok(())
}
fn load_user_policy(config: Arc<Config>, oso: &mut Oso) -> KrillResult<()> {
for policy in config.auth_policies.iter() {
info!("Loading user-defined authorization policy file {:?}", policy);
let fname = policy.file_name().unwrap().to_str().unwrap();
let mut buffer = Vec::new();
std::fs::File::open(policy.as_path())
.map_err(|e| {
KrillIoError::new(format!("Could not open policy file '{}'", policy.to_string_lossy()), e)
})?
.read_to_end(&mut buffer)
.map_err(|e| {
KrillIoError::new(format!("Could not read policy file '{}'", policy.to_string_lossy()), e)
})?;
AuthPolicy::load_internal_policy(oso, &buffer, fname)?;
}
Ok(())
}
}
impl ToPolar for NoResourceType {
#[allow(clippy::wrong_self_convention)]
fn to_polar(self) -> oso::PolarValue {
Option::<PolarValue>::None.to_polar()
}
}
impl PolarClass for Actor {
fn get_polar_class() -> oso::Class {
Self::get_polar_class_builder()
.set_constructor(Actor::test_from_details)
.set_equality_check(|left: &Actor, right: &Actor| left.name() == right.name())
.add_attribute_getter("name", |instance| instance.name().to_string())
.add_class_method("builtin", |name: String| -> Actor {
match name.as_str() {
"anon" => Actor::test_from_def(ACTOR_DEF_ANON),
"krill" => Actor::test_from_def(ACTOR_DEF_KRILL),
"admin-token" => Actor::test_from_def(ACTOR_DEF_ADMIN_TOKEN),
"testbed" => Actor::test_from_def(ACTOR_DEF_TESTBED),
_ => panic!("Unknown built-in actor name '{}'", name),
}
})
.add_class_method("is_in", |name: String, names: Vec<String>| -> bool {
names.contains(&name)
})
.add_method("attr", Actor::attribute)
.add_method("attrs", Actor::attributes)
.build()
}
fn get_polar_class_builder() -> oso::ClassBuilder<Self> {
oso::Class::builder()
}
}
impl PolarClass for Permission {
fn get_polar_class() -> oso::Class {
Self::get_polar_class_builder()
.set_constructor(|perm_name: String| -> Permission { Permission::from_str(&perm_name).unwrap() })
.set_equality_check(|left: &Permission, right: &Permission| *left == *right)
.build()
}
fn get_polar_class_builder() -> oso::ClassBuilder<Self> {
oso::Class::builder()
}
}