use super::authorizer::Authorizer; use cedar_policy::{ Authorizer as CedarAuth, Context, Entities, EntityId, EntityTypeName, EntityUid, PolicySet, Request as CedarRequest, }; use envoy_types::ext_authz::v3::pb::CheckRequest; use std::fs; use std::str::FromStr; #[derive(Debug)] pub struct CedarAuthorizer { policies: PolicySet, authorizer: CedarAuth, } impl CedarAuthorizer { pub fn new(policies: cedar_policy::PolicySet) -> CedarAuthorizer { CedarAuthorizer { policies, authorizer: CedarAuth::new(), } } pub fn new_from(path: &std::path::Path) -> CedarAuthorizer { Self::new(Self::load_from(path).unwrap_or_else(|_| PolicySet::default())) } fn load_from(path: &std::path::Path) -> Result> { if !path.exists() || !path.is_dir() { return Ok(PolicySet::default()); } let mut policies = PolicySet::new(); for entry in fs::read_dir(path)? { let file_path = entry?.path(); if let Some(extension) = file_path.extension() { if extension == "cedar" { let content = fs::read_to_string(&file_path)?; let file_policies = PolicySet::from_str(&content)?; for policy in file_policies.policies() { policies.add(policy.clone())?; } } } } Ok(policies) } } impl Default for CedarAuthorizer { fn default() -> Self { Self::new_from(std::path::Path::new("/etc/authzd")) } } impl Authorizer for CedarAuthorizer { fn authorize(&self, request: 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, }; tracing::info!( method = %http_request.method, host = %http_request.host, path = %http_request.path, scheme = %http_request.scheme, protocol = %http_request.protocol, "Processing HTTP request" ); if http_request.host == "sparkle.staging.runway.gitlab.net" && http_request.method == "GET" && http_request.path == "/" { return true; } if http_request.host == "sparkle.staging.runway.gitlab.net" && http_request.method == "GET" && http_request.path == "/application.js" { return true; } if http_request.host == "sparkle.staging.runway.gitlab.net" && http_request.method == "GET" && http_request.path == "/callback" { return true; } match self.map_from(http_request.clone()) { Ok(cedar_request) => { let entities = Entities::empty(); let response = self.authorizer .is_authorized(&cedar_request, &self.policies, &entities); matches!(response.decision(), cedar_policy::Decision::Allow) } Err(e) => { tracing::error!( error = %e, path = %http_request.path, "Failed to create Cedar request" ); false } } } } impl CedarAuthorizer { 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); CedarRequest::new(principal, permission, resource, context?, None) .map_err(|e| Box::new(e) as Box) } fn principal_from( &self, _http_request: &envoy_types::pb::envoy::service::auth::v3::attribute_context::HttpRequest, ) -> Result> { Ok(EntityUid::from_type_name_and_id( EntityTypeName::from_str("User")?, EntityId::from_str("client")?, )) } fn permission_from( &self, _http_request: &envoy_types::pb::envoy::service::auth::v3::attribute_context::HttpRequest, ) -> Result> { Ok(EntityUid::from_type_name_and_id( EntityTypeName::from_str("Action")?, EntityId::from_str("check")?, )) } fn resource_from( &self, _http_request: &envoy_types::pb::envoy::service::auth::v3::attribute_context::HttpRequest, ) -> Result> { Ok(EntityUid::from_type_name_and_id( EntityTypeName::from_str("Resource")?, 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("path".to_string(), self.safe_string(&http_request.path)); 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()) } }