cedar_local_agent/public/
simple.rs

1//! Provides a simple authorizer that takes a `SimplePolicySetProvider` and a `SimpleEntityProvider`.
2use std::sync::Arc;
3
4#[cfg(feature = "partial-eval")]
5use cedar_policy::{Diagnostics, PartialResponse};
6use cedar_policy::{Entities, Request, Response};
7use derive_builder::Builder;
8use thiserror::Error;
9use tokio::join;
10use tracing::{debug, error, event, info, instrument};
11use tracing_core::Level;
12use uuid::Uuid;
13
14use crate::public::log::schema::OpenCyberSecurityFramework;
15use crate::public::{log, EntityProviderError, SimpleEntityProvider};
16use crate::public::{PolicySetProviderError, SimplePolicySetProvider};
17
18/// The `AuthorizerConfig` provides customers the ability to build their own
19/// simple authorizer.
20#[derive(Default, Builder, Debug)]
21#[builder(pattern = "owned")]
22pub struct AuthorizerConfig<P, E> {
23    /// An atomic reference counter to a policy set provider
24    pub policy_set_provider: Arc<P>,
25    /// An atomic reference counter to an entity provider
26    pub entity_provider: Arc<E>,
27    /// An optional Logging Configuration
28    #[builder(setter(into, strip_option), default)]
29    pub log_config: Option<log::Config>,
30}
31
32/// `AuthorizerError` can be thrown when a provider fails to gather data.
33#[derive(Error, Debug)]
34pub enum AuthorizerError {
35    /// Thrown when the `SimplePolicySetProvider` fails to get a `PolicySet`.
36    #[error("The Policy Set Provider failed to get a policy set: {0}")]
37    PolicySetProviderError(#[source] PolicySetProviderError),
38    /// Thrown when the `SimpleEntityProvider` fails to get `Entities`.
39    #[error("The Entity provider failed to get the entities: {0}")]
40    EntityProviderError(#[source] EntityProviderError),
41    /// Handles catching generic errors within the `Authorizer`.
42    #[error("General error that can occur within the Authorizer: {0}")]
43    General(#[source] Box<dyn std::error::Error + Send + Sync + 'static>),
44}
45
46impl From<PolicySetProviderError> for AuthorizerError {
47    fn from(value: PolicySetProviderError) -> Self {
48        Self::PolicySetProviderError(value)
49    }
50}
51
52impl From<EntityProviderError> for AuthorizerError {
53    fn from(value: EntityProviderError) -> Self {
54        Self::EntityProviderError(value)
55    }
56}
57
58/// `Authorizer` that provides an `is_authorized` implementation
59#[derive(Debug)]
60pub struct Authorizer<P, E>
61where
62    P: SimplePolicySetProvider + 'static,
63    E: SimpleEntityProvider + 'static,
64{
65    /// the policy set provider -- provides the policies from a source location
66    policy_set_provider: Arc<P>,
67    /// the entity provider -- provides entities from on disk, a database look up etc.
68    entity_provider: Arc<E>,
69    /// Log config
70    log_config: log::Config,
71}
72
73impl<P, E> Authorizer<P, E>
74where
75    P: SimplePolicySetProvider,
76    E: SimpleEntityProvider,
77{
78    /// Constructor to create a `simple::Authorizer`
79    ///
80    /// # Examples:
81    ///
82    /// ```
83    /// use std::sync::Arc;
84    /// use cedar_local_agent::public::file::entity_provider::EntityProvider;
85    /// use cedar_local_agent::public::file::policy_set_provider::{ConfigBuilder, PolicySetProvider};
86    /// use cedar_local_agent::public::simple::{Authorizer, AuthorizerConfigBuilder};
87    ///
88    /// let authorizer: Authorizer<PolicySetProvider, EntityProvider> = Authorizer::new(AuthorizerConfigBuilder::default()
89    ///     .policy_set_provider(Arc::new(PolicySetProvider::new(
90    ///             ConfigBuilder::default()
91    ///                 .policy_set_path("tests/data/sweets.cedar")
92    ///                 .build()
93    ///                 .unwrap())
94    ///         .unwrap()))
95    ///     .entity_provider(Arc::new(EntityProvider::default()))
96    ///     .build()
97    ///     .unwrap());
98    /// ```
99    #[instrument(skip(configuration))]
100    pub fn new(configuration: AuthorizerConfig<P, E>) -> Self {
101        let log_config = configuration.log_config.unwrap_or_default();
102        let entity_provider = configuration.entity_provider;
103        info!("Initialized Entity Provider");
104        let policy_set_provider = configuration.policy_set_provider;
105        info!("Initialized Policy Set Provider");
106        info!(
107            "Initialize Simple Authorizer: authorizer_id= {:?}",
108            log_config.requester
109        );
110        Self {
111            policy_set_provider,
112            entity_provider,
113            log_config,
114        }
115    }
116
117    /// Authorize
118    ///
119    /// # Errors
120    ///
121    /// This function can error if either the entity or policy set providers fails.
122    #[instrument(fields(request_id = %Uuid::new_v4(), authorizer_id = %self.log_config.requester), skip_all, err(Debug))]
123    pub async fn is_authorized(
124        &self,
125        request: &Request,
126        entities: &Entities,
127    ) -> Result<Response, AuthorizerError> {
128        info!("Received request, running is_authorized...");
129
130        let entities_future = self.entity_provider.get_entities(request);
131        let policy_set_future = self.policy_set_provider.get_policy_set(request);
132        let (fetched_entities, policy_set) = join!(entities_future, policy_set_future);
133        let merged_entities = Entities::from_entities(
134            fetched_entities?
135                .as_ref()
136                .iter()
137                .chain(entities.iter())
138                .cloned(),
139            None,
140        )
141        .map_err(|e| AuthorizerError::General(Box::new(e)))?;
142
143        let response = cedar_policy::Authorizer::new().is_authorized(
144            request,
145            policy_set?.as_ref(),
146            &merged_entities,
147        );
148        info!("Fetched Authorization data from Policy Set Provider and Entity Provider");
149
150        info!("Generated OCSF log record.");
151        self.log(request, &response, entities);
152
153        info!(
154            "Is_authorized completed: response_decision={:?}",
155            response.decision()
156        );
157        debug!(
158            "This decision was reached because: response_diagnostics={:?}",
159            response.diagnostics()
160        );
161
162        Ok(response)
163    }
164
165    #[instrument(skip_all)]
166    fn log(&self, request: &Request, response: &Response, entities: &Entities) {
167        event!(target: "cedar::simple::authorizer", Level::INFO, "{}",
168            serde_json::to_string(
169                &OpenCyberSecurityFramework::create(
170                    request,
171                    response,
172                    entities,
173                    &self.log_config.field_set,
174                    self.log_config.requester.as_str(),
175                )
176                .unwrap_or_else(|e| {
177                    OpenCyberSecurityFramework::error(e.to_string(), self.log_config.requester.clone())
178                })
179            ).unwrap_or_else(|_| "Failed to deserialize a known Open Cyber Security Framework string.".to_string()),
180        );
181    }
182
183    /// Authorize Partial
184    ///
185    /// # Errors
186    ///
187    /// This function can error if either the entity or policy set providers fails.
188    #[cfg(feature = "partial-eval")]
189    #[instrument(fields(request_id = %Uuid::new_v4(), authorizer_id = %self.log_config.requester), skip_all, err(Debug))]
190    pub async fn is_authorized_partial(
191        &self,
192        request: &Request,
193        entities: &Entities,
194    ) -> Result<PartialResponse, AuthorizerError> {
195        info!("Received request, running is_authorized_partial...");
196
197        let entities_future = self.entity_provider.get_entities(request);
198        let policy_set_future = self.policy_set_provider.get_policy_set(request);
199        let (fetched_entities, policy_set) = join!(entities_future, policy_set_future);
200        let merged_entities = Entities::from_entities(
201            fetched_entities?
202                .as_ref()
203                .iter()
204                .chain(entities.iter())
205                .cloned(),
206            None,
207        )
208        .map_err(|e| AuthorizerError::General(Box::new(e)))?;
209
210        let partial_response = cedar_policy::Authorizer::new().is_authorized_partial(
211            request,
212            policy_set?.as_ref(),
213            &merged_entities.partial(),
214        );
215        info!("Fetched Authorization data from Policy Set Provider and Entity Provider");
216
217        let concrete_response = partial_response.clone().concretize();
218
219        // Skip logging for now
220        info!("Generated OCSF log record.");
221
222        match partial_response.decision() {
223            Some(_) => self.log(request, &concrete_response, entities),
224            None => self.log_residual(
225                request,
226                concrete_response.diagnostics(),
227                &partial_response,
228                entities,
229            ),
230        }
231
232        info!(
233            "Is_authorized_partial completed: response_decision={:?}",
234            partial_response.decision()
235        );
236        debug!(
237            "This decision was reached because: response_diagnostics={:?}",
238            partial_response.clone().concretize().diagnostics()
239        );
240
241        Ok(partial_response)
242    }
243
244    #[cfg(feature = "partial-eval")]
245    #[instrument(skip_all)]
246    fn log_residual(
247        &self,
248        request: &Request,
249        diagnostics: &Diagnostics,
250        policies: &PartialResponse,
251        entities: &Entities,
252    ) {
253        event!(target: "cedar::simple::authorizer", Level::INFO, "{}",
254            serde_json::to_string(
255                &OpenCyberSecurityFramework::create_generic(
256                    request,
257                    diagnostics,
258                    policies.all_residuals()
259                        .map(|policy| format!("{}", policy.id()))
260                        .collect::<Vec<String>>()
261                        .join(", ")
262                        .as_str(),
263
264                    String::from("Residuals"),
265                    entities,
266                    &self.log_config.field_set,
267                    self.log_config.requester.as_str()
268                ).unwrap_or_else(|e| {
269                    OpenCyberSecurityFramework::error(e.to_string(), self.log_config.requester.clone())
270                })
271            ).unwrap_or_else(|_| "Failed to deserialize a known Open Cyber Security Framework string.".to_string()),
272        );
273    }
274}
275
276#[cfg(test)]
277mod test {
278    use std::fmt::Error;
279    use std::sync::Arc;
280
281    use async_trait::async_trait;
282    use cedar_policy::{Context, Decision, Entities, PolicySet, Request};
283
284    use crate::public::log::DEFAULT_REQUESTER_NAME;
285    use crate::public::simple::{Authorizer, AuthorizerConfigBuilder};
286    use crate::public::{
287        EntityProviderError, PolicySetProviderError, SimpleEntityProvider, SimplePolicySetProvider,
288    };
289
290    #[derive(Debug, Default)]
291    pub struct MockPolicySetProvider;
292
293    #[async_trait]
294    impl SimplePolicySetProvider for MockPolicySetProvider {
295        async fn get_policy_set(
296            &self,
297            _: &Request,
298        ) -> Result<Arc<PolicySet>, PolicySetProviderError> {
299            Ok(Arc::new(PolicySet::new()))
300        }
301    }
302
303    #[derive(Debug, Default)]
304    pub struct MockEntityProvider;
305
306    #[async_trait]
307    impl SimpleEntityProvider for MockEntityProvider {
308        async fn get_entities(&self, _: &Request) -> Result<Arc<Entities>, EntityProviderError> {
309            Ok(Arc::new(Entities::empty()))
310        }
311    }
312
313    #[tokio::test]
314    async fn simple_authorizer_is_ok() {
315        let authorizer: Authorizer<MockPolicySetProvider, MockEntityProvider> = Authorizer::new(
316            AuthorizerConfigBuilder::default()
317                .policy_set_provider(Arc::new(MockPolicySetProvider))
318                .entity_provider(Arc::new(MockEntityProvider))
319                .build()
320                .unwrap(),
321        );
322
323        let result = authorizer
324            .is_authorized(
325                &Request::new(
326                    r#"User::"Mike""#.parse().unwrap(),
327                    r#"Action::"View""#.parse().unwrap(),
328                    r#"Box::"10""#.parse().unwrap(),
329                    Context::empty(),
330                    None,
331                )
332                .unwrap(),
333                &Entities::empty(),
334            )
335            .await;
336
337        assert!(result.is_ok());
338        assert_eq!(result.unwrap().decision(), Decision::Deny);
339        assert_eq!(authorizer.log_config.requester, DEFAULT_REQUESTER_NAME);
340        assert!(!authorizer.log_config.field_set.principal);
341    }
342
343    #[derive(Debug, Default)]
344    pub struct MockEntityProviderError;
345
346    #[async_trait]
347    impl SimpleEntityProvider for MockEntityProviderError {
348        async fn get_entities(&self, _: &Request) -> Result<Arc<Entities>, EntityProviderError> {
349            Err(EntityProviderError::General(Box::<Error>::default()))
350        }
351    }
352
353    #[tokio::test]
354    async fn simple_authorizer_bad_entity_provider() {
355        let authorizer: Authorizer<MockPolicySetProvider, MockEntityProviderError> =
356            Authorizer::new(
357                AuthorizerConfigBuilder::default()
358                    .policy_set_provider(Arc::new(MockPolicySetProvider))
359                    .entity_provider(Arc::new(MockEntityProviderError))
360                    .build()
361                    .unwrap(),
362            );
363
364        let result = authorizer
365            .is_authorized(
366                &Request::new(
367                    r#"User::"Mike""#.parse().unwrap(),
368                    r#"Action::"View""#.parse().unwrap(),
369                    r#"Box::"2""#.parse().unwrap(),
370                    Context::empty(),
371                    None,
372                )
373                .unwrap(),
374                &Entities::empty(),
375            )
376            .await;
377
378        assert!(result.is_err());
379    }
380
381    #[derive(Debug, Default)]
382    pub struct MockPolicySetProviderError;
383
384    #[async_trait]
385    impl SimplePolicySetProvider for MockPolicySetProviderError {
386        async fn get_policy_set(
387            &self,
388            _: &Request,
389        ) -> Result<Arc<PolicySet>, PolicySetProviderError> {
390            Err(PolicySetProviderError::General(Box::<Error>::default()))
391        }
392    }
393
394    #[tokio::test]
395    async fn simple_authorizer_bad_policy_set_provider() {
396        let authorizer: Authorizer<MockPolicySetProviderError, MockEntityProvider> =
397            Authorizer::new(
398                AuthorizerConfigBuilder::default()
399                    .policy_set_provider(Arc::new(MockPolicySetProviderError))
400                    .entity_provider(Arc::new(MockEntityProvider))
401                    .build()
402                    .unwrap(),
403            );
404
405        let result = authorizer
406            .is_authorized(
407                &Request::new(
408                    r#"User::"Mike""#.parse().unwrap(),
409                    r#"Action::"View""#.parse().unwrap(),
410                    r#"Box::"3""#.parse().unwrap(),
411                    Context::empty(),
412                    None,
413                )
414                .unwrap(),
415                &Entities::empty(),
416            )
417            .await;
418
419        assert!(result.is_err());
420    }
421}
422
423#[cfg(test)]
424#[cfg(feature = "partial-eval")]
425mod test_partial {
426    use std::sync::Arc;
427
428    use async_trait::async_trait;
429    use cedar_policy::{Context, Decision, Entities, Policy, PolicyId, PolicySet, Request};
430    use cool_asserts::assert_matches;
431
432    use crate::public::log::DEFAULT_REQUESTER_NAME;
433    use crate::public::simple::test::{MockEntityProvider, MockPolicySetProvider};
434    use crate::public::simple::{Authorizer, AuthorizerConfigBuilder};
435    use crate::public::{PolicySetProviderError, SimplePolicySetProvider};
436
437    #[tokio::test]
438    async fn simple_authorizer_partial_no_resource_is_ok() {
439        let authorizer: Authorizer<MockPolicySetProvider, MockEntityProvider> = Authorizer::new(
440            AuthorizerConfigBuilder::default()
441                .policy_set_provider(Arc::new(MockPolicySetProvider))
442                .entity_provider(Arc::new(MockEntityProvider))
443                .build()
444                .unwrap(),
445        );
446
447        let result = authorizer
448            .is_authorized_partial(
449                &Request::builder()
450                    .principal(r#"User::"Mike""#.parse().unwrap())
451                    .action(r#"Action::"View""#.parse().unwrap())
452                    .context(Context::empty())
453                    .build(),
454                &Entities::empty(),
455            )
456            .await;
457
458        assert_matches!(result, Ok(partial_response) =>
459            assert_eq!(partial_response.decision(), Some(Decision::Deny))
460        );
461        assert_eq!(authorizer.log_config.requester, DEFAULT_REQUESTER_NAME);
462        assert!(!authorizer.log_config.field_set.principal);
463    }
464
465    #[tokio::test]
466    async fn simple_authorizer_partial_no_principal_is_ok() {
467        let authorizer: Authorizer<MockPolicySetProvider, MockEntityProvider> = Authorizer::new(
468            AuthorizerConfigBuilder::default()
469                .policy_set_provider(Arc::new(MockPolicySetProvider))
470                .entity_provider(Arc::new(MockEntityProvider))
471                .build()
472                .unwrap(),
473        );
474
475        let result = authorizer
476            .is_authorized_partial(
477                &Request::builder()
478                    .action(r#"Action::"View""#.parse().unwrap())
479                    .resource(r#"Box::"10""#.parse().unwrap())
480                    .context(Context::empty())
481                    .build(),
482                &Entities::empty(),
483            )
484            .await;
485
486        assert_matches!(result, Ok(partial_response) =>
487            assert_eq!(partial_response.decision(), Some(Decision::Deny))
488        );
489        assert_eq!(authorizer.log_config.requester, DEFAULT_REQUESTER_NAME);
490        assert!(!authorizer.log_config.field_set.principal);
491    }
492
493    #[derive(Debug, Default)]
494    pub struct MockPolicySetProviderPartial;
495
496    #[async_trait]
497    impl SimplePolicySetProvider for MockPolicySetProviderPartial {
498        async fn get_policy_set(
499            &self,
500            _: &Request,
501        ) -> Result<Arc<PolicySet>, PolicySetProviderError> {
502            let policy = Policy::parse(
503                Some(PolicyId::new("test")),
504                r#"permit(principal == User::"Mike", action, resource == Box::"10");"#,
505            )
506            .expect("Failed to parse");
507            let mut policy_set = PolicySet::new();
508            policy_set.add(policy).expect("Failed to add");
509            Ok(Arc::new(policy_set))
510        }
511    }
512
513    #[tokio::test]
514    async fn simple_authorizer_partial_result() {
515        let authorizer: Authorizer<MockPolicySetProviderPartial, MockEntityProvider> =
516            Authorizer::new(
517                AuthorizerConfigBuilder::default()
518                    .policy_set_provider(Arc::new(MockPolicySetProviderPartial))
519                    .entity_provider(Arc::new(MockEntityProvider))
520                    .build()
521                    .unwrap(),
522            );
523
524        let result = authorizer
525            .is_authorized_partial(
526                &Request::builder()
527                    .action(r#"Action::"View""#.parse().unwrap())
528                    .resource(r#"Box::"10""#.parse().unwrap())
529                    .context(Context::empty())
530                    .build(),
531                &Entities::empty(),
532            )
533            .await;
534
535        assert_matches!(result, Ok(partial_response) =>
536            assert_eq!(partial_response.all_residuals().count(), 1)
537        );
538        assert_eq!(authorizer.log_config.requester, DEFAULT_REQUESTER_NAME);
539        assert!(!authorizer.log_config.field_set.principal);
540    }
541}