summaryrefslogtreecommitdiff
path: root/src/authorization/cedar_authorizer.rs
blob: fb85012edfe106d7f8d2c9fffc156e8b5c47b9b2 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
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::str::FromStr;

#[derive(Debug)]
pub struct CedarAuthorizer {
    policies: PolicySet,
    authorizer: CedarAuth,
}

impl CedarAuthorizer {
    pub fn new() -> CedarAuthorizer {
        let policy_src = include_str!("../../policies/auth_policy.cedar");
        let policies = policy_src.parse().expect("Failed to parse Cedar policies");
        let authorizer = CedarAuth::new();

        CedarAuthorizer {
            policies,
            authorizer,
        }
    }
}

impl Default for CedarAuthorizer {
    fn default() -> Self {
        Self::new()
    }
}

impl Authorizer for CedarAuthorizer {
    fn authorize(&self, request: CheckRequest) -> bool {
        let headers = match request
            .attributes
            .as_ref()
            .and_then(|attr| attr.request.as_ref())
            .and_then(|req| req.http.as_ref())
            .map(|http| &http.headers)
        {
            Some(headers) => headers,
            None => return false,
        };

        // Extract authorization token
        let bearer_token = headers
            .get("authorization")
            .and_then(|auth| auth.strip_prefix("Bearer "))
            .unwrap_or("");

        // Extract request path for static asset checking
        let path = headers
            .get(":path")
            .or_else(|| headers.get("path"))
            .map_or("", |v| v.as_str());

        // Create Cedar entities and request
        match self.create_cedar_request(bearer_token, path) {
            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(_) => false,
        }
    }
}

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>)
    }
}