diff options
| author | mo khan <mo@mokhan.ca> | 2025-07-05 13:36:41 -0600 |
|---|---|---|
| committer | mo khan <mo@mokhan.ca> | 2025-07-05 13:36:41 -0600 |
| commit | 63c5263087c9e282ced0e549b78c7ebd4353b273 (patch) | |
| tree | e408c3aed5e4edf723c72897094bdd2f49077a15 /src/authorization/cedar_authorizer.rs | |
| parent | 33083559c6d43d266ca77fcd1beb94c6feb4b547 (diff) | |
| parent | c6ec4e63d797c5e6cc01a4f09e723ad781b1034e (diff) | |
Merge branch 'sparkle-policies' into 'main'
Add Cedar policy validation and improve authorization architecture
See merge request gitlab-org/software-supply-chain-security/authorization/authzd!4
Diffstat (limited to 'src/authorization/cedar_authorizer.rs')
| -rw-r--r-- | src/authorization/cedar_authorizer.rs | 207 |
1 files changed, 118 insertions, 89 deletions
diff --git a/src/authorization/cedar_authorizer.rs b/src/authorization/cedar_authorizer.rs index 432102ef..64287414 100644 --- a/src/authorization/cedar_authorizer.rs +++ b/src/authorization/cedar_authorizer.rs @@ -1,45 +1,48 @@ use super::authorizer::Authorizer; -use cedar_policy::{ - Authorizer as CedarAuth, Context, Entities, EntityId, EntityTypeName, EntityUid, PolicySet, - Request as CedarRequest, RestrictedExpression, -}; -use envoy_types::ext_authz::v3::pb::CheckRequest; -use std::collections::HashMap; use std::fs; use std::str::FromStr; #[derive(Debug)] pub struct CedarAuthorizer { - policies: PolicySet, - authorizer: CedarAuth, + authorizer: cedar_policy::Authorizer, + entities: cedar_policy::Entities, + policies: cedar_policy::PolicySet, } impl CedarAuthorizer { - pub fn new(policies: cedar_policy::PolicySet) -> CedarAuthorizer { + pub fn new( + policies: cedar_policy::PolicySet, + entities: cedar_policy::Entities, + ) -> CedarAuthorizer { CedarAuthorizer { policies, - authorizer: CedarAuth::new(), + entities, + authorizer: cedar_policy::Authorizer::new(), } } - pub fn new_from(path: &std::path::Path) -> CedarAuthorizer { - Self::new(Self::load_from(path).unwrap_or_else(|_| PolicySet::default())) + pub fn new_from(path: &std::path::Path, entities: cedar_policy::Entities) -> CedarAuthorizer { + Self::new( + Self::load_from(path).unwrap_or_else(|_| cedar_policy::PolicySet::default()), + entities, + ) } - fn load_from(path: &std::path::Path) -> Result<PolicySet, Box<dyn std::error::Error>> { + fn load_from( + path: &std::path::Path, + ) -> Result<cedar_policy::PolicySet, Box<dyn std::error::Error>> { if !path.exists() || !path.is_dir() { - return Ok(PolicySet::default()); + return Ok(cedar_policy::PolicySet::default()); } - let mut policies = PolicySet::new(); - + let mut policies = cedar_policy::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)?; + let file_policies = cedar_policy::PolicySet::from_str(&content)?; for policy in file_policies.policies() { policies.add(policy.clone())?; @@ -50,16 +53,96 @@ impl CedarAuthorizer { Ok(policies) } + + fn map_from( + &self, + http_request: envoy_types::pb::envoy::service::auth::v3::attribute_context::HttpRequest, + ) -> Result<cedar_policy::Request, Box<dyn std::error::Error>> { + 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<cedar_policy::EntityUid, Box<dyn std::error::Error>> { + Ok(cedar_policy::EntityUid::from_type_name_and_id( + cedar_policy::EntityTypeName::from_str("User")?, + cedar_policy::EntityId::from_str("client")?, + )) + } + + fn permission_from( + &self, + _http_request: &envoy_types::pb::envoy::service::auth::v3::attribute_context::HttpRequest, + ) -> Result<cedar_policy::EntityUid, Box<dyn std::error::Error>> { + 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<cedar_policy::EntityUid, Box<dyn std::error::Error>> { + 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<cedar_policy::Context, Box<dyn std::error::Error>> { + 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::<Vec<_>>(), + )?) + } + + 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")) + Self::new_from( + std::path::Path::new("/etc/authzd"), + cedar_policy::Entities::empty(), + ) } } impl Authorizer for CedarAuthorizer { - fn authorize(&self, request: CheckRequest) -> bool { + fn authorize(&self, request: envoy_types::ext_authz::v3::pb::CheckRequest) -> bool { let http_request = match request .attributes .as_ref() @@ -70,37 +153,26 @@ impl Authorizer for CedarAuthorizer { 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; - } - - let headers = &http_request.headers; - - let bearer_token = headers - .get("authorization") - .and_then(|auth| auth.strip_prefix("Bearer ")) - .unwrap_or(""); - - match self.create_cedar_request(bearer_token, &http_request.path.to_string()) { + 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); + .is_authorized(&cedar_request, &self.policies, &self.entities); + + let decision = response.decision(); - matches!(response.decision(), cedar_policy::Decision::Allow) + 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!( @@ -113,46 +185,3 @@ impl Authorizer for CedarAuthorizer { } } } - -impl CedarAuthorizer { - fn create_cedar_request( - &self, - bearer_token: &str, - path: &str, - ) -> Result<CedarRequest, Box<dyn std::error::Error>> { - // Create principal entity - let principal_id = EntityId::from_str("client")?; - let principal_type = EntityTypeName::from_str("User")?; - let principal = EntityUid::from_type_name_and_id(principal_type, principal_id); - - // Create action entity - let action_id = EntityId::from_str("check")?; - let action_type = EntityTypeName::from_str("Action")?; - let action = EntityUid::from_type_name_and_id(action_type, action_id); - - // Create resource entity - let resource_id = EntityId::from_str("resource")?; - let resource_type = EntityTypeName::from_str("Resource")?; - let resource = EntityUid::from_type_name_and_id(resource_type, resource_id); - - // Create context with bearer token and path - let mut context_map = HashMap::new(); - if !bearer_token.is_empty() { - context_map.insert( - "bearer_token".to_string(), - RestrictedExpression::from_str(&format!("\"{bearer_token}\""))?, - ); - } - if !path.is_empty() { - context_map.insert( - "path".to_string(), - RestrictedExpression::from_str(&format!("\"{path}\""))?, - ); - } - - let context = Context::from_pairs(context_map.into_iter().collect::<Vec<_>>())?; - - CedarRequest::new(principal, action, resource, context, None) - .map_err(|e| Box::new(e) as Box<dyn std::error::Error>) - } -} |
