use super::authorizer::Authorizer; use std::fs; use std::str::FromStr; #[derive(Debug)] pub struct CedarAuthorizer { authorizer: cedar_policy::Authorizer, entities: cedar_policy::Entities, policies: cedar_policy::PolicySet, } impl CedarAuthorizer { pub fn new( policies: cedar_policy::PolicySet, entities: cedar_policy::Entities, ) -> CedarAuthorizer { CedarAuthorizer { policies, entities, authorizer: cedar_policy::Authorizer::new(), } } pub fn new_from(path: &std::path::Path, entities: cedar_policy::Entities) -> CedarAuthorizer { Self::new( Self::load_from(path).unwrap_or_else(|e| { tracing::error!( path = ?path, error = %e, "Failed to load Cedar policies, using empty policy set" ); cedar_policy::PolicySet::default() }), entities, ) } fn load_from( path: &std::path::Path, ) -> Result> { if !path.exists() { return Ok(cedar_policy::PolicySet::default()); } if path.is_file() && path.extension().map_or(false, |ext| ext == "cedar") { let content = fs::read_to_string(&path)?; return Ok(cedar_policy::PolicySet::from_str(&content)?); } if !path.is_dir() { return Ok(cedar_policy::PolicySet::default()); } let mut policies = cedar_policy::PolicySet::new(); for entry in fs::read_dir(path)? { policies.merge(&Self::load_from(&entry?.path())?, true)?; } return Ok(policies); } fn map_from( &self, http_request: envoy_types::pb::envoy::service::auth::v3::attribute_context::HttpRequest, ) -> Result> { let principal = self.principal_from(&http_request)?; let permission = self.permission_from(&http_request)?; let resource = self.resource_from(&http_request)?; let context = self.context_from(http_request)?; Ok(cedar_policy::Request::new( principal, permission, resource, context, None, )?) } fn principal_from( &self, http_request: &envoy_types::pb::envoy::service::auth::v3::attribute_context::HttpRequest, ) -> Result> { let subject = http_request .headers .get("x-jwt-claim-sub") .map_or("", |v| v); Ok(cedar_policy::EntityUid::from_type_name_and_id( cedar_policy::EntityTypeName::from_str("User")?, cedar_policy::EntityId::from_str(subject)?, )) } fn permission_from( &self, _http_request: &envoy_types::pb::envoy::service::auth::v3::attribute_context::HttpRequest, ) -> Result> { Ok(cedar_policy::EntityUid::from_type_name_and_id( cedar_policy::EntityTypeName::from_str("Action")?, cedar_policy::EntityId::from_str("check")?, )) } fn resource_from( &self, _http_request: &envoy_types::pb::envoy::service::auth::v3::attribute_context::HttpRequest, ) -> Result> { Ok(cedar_policy::EntityUid::from_type_name_and_id( cedar_policy::EntityTypeName::from_str("Resource")?, cedar_policy::EntityId::from_str("resource")?, )) } fn context_from( &self, http_request: envoy_types::pb::envoy::service::auth::v3::attribute_context::HttpRequest, ) -> Result> { let mut items = std::collections::HashMap::new(); items.insert("bearer_token".to_string(), self.token_from(&http_request)); items.insert("host".to_string(), self.safe_string(&http_request.host)); items.insert("method".to_string(), self.safe_string(&http_request.method)); items.insert("path".to_string(), self.safe_string(&http_request.path)); Ok(cedar_policy::Context::from_pairs( items.into_iter().collect::>(), )?) } fn token_from( &self, http_request: &envoy_types::pb::envoy::service::auth::v3::attribute_context::HttpRequest, ) -> cedar_policy::RestrictedExpression { let bearer_token = &http_request .headers .get("authorization") .and_then(|auth| auth.strip_prefix("Bearer ")) .unwrap_or(""); self.safe_string(bearer_token) } fn safe_string(&self, item: &str) -> cedar_policy::RestrictedExpression { cedar_policy::RestrictedExpression::new_string(item.to_string()) } } impl Default for CedarAuthorizer { fn default() -> Self { Self::new_from( std::path::Path::new("/etc/authzd"), cedar_policy::Entities::empty(), ) } } impl Authorizer for CedarAuthorizer { fn authorize(&self, request: envoy_types::ext_authz::v3::pb::CheckRequest) -> bool { let http_request = match request .attributes .as_ref() .and_then(|attr| attr.request.as_ref()) .and_then(|req| req.http.as_ref()) { Some(http) => http, None => return false, }; match self.map_from(http_request.clone()) { Ok(cedar_request) => { let response = self.authorizer .is_authorized(&cedar_request, &self.policies, &self.entities); let decision = response.decision(); tracing::info!( method = %http_request.method, host = %http_request.host, path = %http_request.path, scheme = %http_request.scheme, protocol = %http_request.protocol, decision = ?decision, diagnostics = ?response.diagnostics(), "Processing HTTP request" ); matches!(decision, cedar_policy::Decision::Allow) } Err(e) => { tracing::error!( error = %e, path = %http_request.path, "Failed to create Cedar request" ); false } } } }