diff --git a/Cargo.lock b/Cargo.lock index 4310374..987878d 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -184,9 +184,9 @@ checksum = "7ff69b9dd49fd426c69a0db9fc04dd934cdb6645ff000864d98f7e2af8830eaa" [[package]] name = "bytemuck" -version = "1.15.0" +version = "1.20.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5d6d68c57235a3a081186990eca2867354726650f42f7516ca50c28d6281fd15" +checksum = "8b37c88a63ffd85d15b406896cc343916d7cf57838a847b3a6f2ca5d39a5695a" [[package]] name = "bytes" @@ -211,6 +211,7 @@ name = "cedar-local-agent" version = "3.0.0" dependencies = [ "async-trait", + "bytemuck", "cedar-policy", "cedar-policy-core", "cedar-policy-formatter", @@ -228,6 +229,7 @@ dependencies = [ "serde_json", "serde_repr", "sha256", + "smol_str", "tempfile", "thiserror", "tokio", @@ -717,9 +719,9 @@ dependencies = [ [[package]] name = "futures" -version = "0.3.30" +version = "0.3.31" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "645c6916888f6cb6350d2550b80fb63e734897a8498abe35cfb732b6487804b0" +checksum = "65bc07b1a8bc7c85c5f2e110c476c7389b4554ba72af57d8445ea63a576b0876" dependencies = [ "futures-channel", "futures-core", @@ -732,9 +734,9 @@ dependencies = [ [[package]] name = "futures-channel" -version = "0.3.30" +version = "0.3.31" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "eac8f7d7865dcb88bd4373ab671c8cf4508703796caa2b1985a9ca867b3fcb78" +checksum = "2dff15bf788c671c1934e366d07e30c1814a8ef514e1af724a602e8a2fbe1b10" dependencies = [ "futures-core", "futures-sink", @@ -742,15 +744,15 @@ dependencies = [ [[package]] name = "futures-core" -version = "0.3.30" +version = "0.3.31" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dfc6580bb841c5a68e9ef15c77ccc837b40a7504914d52e47b8b0e9bbda25a1d" +checksum = "05f29059c0c2090612e8d742178b0580d2dc940c837851ad723096f87af6663e" [[package]] name = "futures-executor" -version = "0.3.30" +version = "0.3.31" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a576fc72ae164fca6b9db127eaa9a9dda0d61316034f33a0a0d4eda41f02b01d" +checksum = "1e28d1d997f585e54aebc3f97d39e72338912123a67330d723fdbb564d646c9f" dependencies = [ "futures-core", "futures-task", @@ -759,15 +761,15 @@ dependencies = [ [[package]] name = "futures-io" -version = "0.3.30" +version = "0.3.31" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a44623e20b9681a318efdd71c299b6b222ed6f231972bfe2f224ebad6311f0c1" +checksum = "9e5c1b78ca4aae1ac06c48a526a655760685149f0d465d21f37abfe57ce075c6" [[package]] name = "futures-macro" -version = "0.3.30" +version = "0.3.31" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "87750cf4b7a4c0625b1529e4c543c2182106e4dedc60a2a6455e00d212c489ac" +checksum = "162ee34ebcb7c64a8abebc059ce0fee27c2262618d7b60ed8faf72fef13c3650" dependencies = [ "proc-macro2", "quote", @@ -776,21 +778,21 @@ dependencies = [ [[package]] name = "futures-sink" -version = "0.3.30" +version = "0.3.31" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9fb8e00e87438d937621c1c6269e53f536c14d3fbd6a042bb24879e57d474fb5" +checksum = "e575fab7d1e0dcb8d0c7bcf9a63ee213816ab51902e6d244a95819acacf1d4f7" [[package]] name = "futures-task" -version = "0.3.30" +version = "0.3.31" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "38d84fa142264698cdce1a9f9172cf383a0c82de1bddcf3092901442c4097004" +checksum = "f90f7dce0722e95104fcb095585910c0977252f286e354b5e3bd38902cd99988" [[package]] name = "futures-util" -version = "0.3.30" +version = "0.3.31" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3d6401deb83407ab3da39eba7e33987a73c3df0c82b4bb5813ee871c19c41d48" +checksum = "9fa08315bb612088cc391249efdc3bc77536f16c91f6cf495e6fbe85b20a4a81" dependencies = [ "futures-channel", "futures-core", @@ -1755,9 +1757,9 @@ checksum = "e6ecd384b10a64542d77071bd64bd7b231f4ed5940fba55e98c3de13824cf3d7" [[package]] name = "smol_str" -version = "0.3.1" +version = "0.3.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "66eaf762c5af19db3108300515c8aa7a50efc90ff745f4c62288052ebf9fdd25" +checksum = "9676b89cd56310a87b93dec47b11af744f34d5fc9f367b829474eec0a891350d" dependencies = [ "borsh", "serde", diff --git a/Cargo.toml b/Cargo.toml index 7ce6406..240be49 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -13,9 +13,10 @@ bench = false [dependencies] # Utilities async-trait = "0.1.71" +bytemuck = "1.20.0" chrono = "0.4.26" derive_builder = "0.12.0" -futures = { version = "0.3.28", features = ["std"] } +futures = { version = "0.3.31", features = ["std"] } fs2 = "0.4.3" once_cell = "1.18.0" rand = "0.8.5" @@ -23,6 +24,7 @@ serde = { version = "1.0.166", features = ["derive"] } serde_json = "1.0.100" serde_repr = "0.1.16" sha256 = "1.3.0" +smol_str = "0.3.2" tokio = { version = "1.0", features = ["full", "signal", "sync", "parking_lot"] } uuid = { version = "1.4.1", features = ["v4"] } diff --git a/benches/data_gen/utils.rs b/benches/data_gen/utils.rs index 361b69a..4f314ee 100644 --- a/benches/data_gen/utils.rs +++ b/benches/data_gen/utils.rs @@ -2,13 +2,14 @@ use rand::Rng; /// Alphabet as an &str pub const ALPHA: &str = "abcdefghijklmnopqrstuvwxyz"; +#[allow(clippy::single_char_add_str)] pub fn random_string(n: u32, charset: &str) -> String { let mut rng = rand::thread_rng(); let mut res = "".to_string(); for _i in 0..n as usize { let random_index: usize = rng.gen_range(0..charset.len()); - res.push_str(&charset.chars().nth(random_index).unwrap().to_string()); + res.push(charset.chars().nth(random_index).unwrap()); } res } diff --git a/deny.toml b/deny.toml index 22eecd5..183ebc2 100644 --- a/deny.toml +++ b/deny.toml @@ -1,12 +1,7 @@ # This file defines configuration for the cargo deny command # Ref: https://github.com/EmbarkStudios/cargo-deny -targets = [] [advisories] -vulnerability = "deny" -unmaintained = "deny" -notice = "deny" -unsound = "deny" ignore = [] [bans] @@ -37,9 +32,6 @@ unknown-registry = "deny" unknown-git = "deny" [licenses] -unlicensed = "deny" -allow-osi-fsf-free = "neither" -copyleft = "deny" confidence-threshold = 0.93 allow = [ "Apache-2.0", diff --git a/src/public/log/mod.rs b/src/public/log/mod.rs index 7a37bbb..b82ead0 100644 --- a/src/public/log/mod.rs +++ b/src/public/log/mod.rs @@ -16,7 +16,7 @@ pub const DEFAULT_REQUESTER_NAME: &str = "cedar::simple::authorizer"; #[builder(setter(into))] pub struct Config { /// `format` is used to specify the log rotation format. - /// By default the log rotation format is OpenCyberSecurityFramework (OCSF). + /// By default the log rotation format is `OpenCyberSecurityFramework` (OCSF). #[builder(default)] pub format: Format, diff --git a/src/public/log/schema.rs b/src/public/log/schema.rs index 4f013b9..9af6143 100644 --- a/src/public/log/schema.rs +++ b/src/public/log/schema.rs @@ -31,8 +31,10 @@ const VENDOR_NAME: &str = "cedar::simple::authorizer"; const SECRET_STRING: &str = "Sensitive"; /// A basic Open Cyber Security Framework structure +/// /// Entity Management events report activity. The activity can be a /// create, read, update, and delete operation on a managed entity. +/// /// #[derive(Default, Builder, Serialize, Deserialize, Eq, PartialEq, Debug, Clone)] #[builder( @@ -53,7 +55,7 @@ pub struct OpenCyberSecurityFramework { /// The category unique identifier of the event. The authorization log will always be 3 #[builder(default = "3u8")] pub category_uid: u8, - /// The event class name, as defined by class_uid value: `Entity Management` + /// The event class name, as defined by `class_uid` value: `Entity Management` #[builder(default = "Some(\"Entity Management\".to_string())")] #[serde(skip_serializing_if = "Option::is_none")] pub class_name: Option, @@ -69,7 +71,7 @@ pub struct OpenCyberSecurityFramework { #[builder(default)] #[serde(skip_serializing_if = "Option::is_none")] pub count: Option, - /// The event duration or aggregate time, the amount of time the event covers from start_time to end_time in milliseconds + /// The event duration or aggregate time, the amount of time the event covers from `start_time` to `end_time` in milliseconds #[builder(default)] #[serde(skip_serializing_if = "Option::is_none")] pub duration: Option, @@ -105,7 +107,7 @@ pub struct OpenCyberSecurityFramework { #[builder(default)] #[serde(skip_serializing_if = "Option::is_none")] pub raw_data: Option, - /// The event severity, normalized to the caption of the severity_id value + /// The event severity, normalized to the caption of the `severity_id` value #[builder(default)] #[serde(skip_serializing_if = "Option::is_none")] pub severity: Option, @@ -115,7 +117,7 @@ pub struct OpenCyberSecurityFramework { #[builder(default)] #[serde(skip_serializing_if = "Option::is_none")] pub start_time: Option, - /// The event status, normalized to the caption of the status_id value + /// The event status, normalized to the caption of the `status_id` value #[builder(default)] #[serde(skip_serializing_if = "Option::is_none")] pub status: Option, @@ -137,9 +139,9 @@ pub struct OpenCyberSecurityFramework { #[serde(skip_serializing_if = "Option::is_none")] pub timezone_offset: Option, /// The event type ID. It identifies the event's semantics and structure. - /// the value is calculated by the logging system as: class_uid * 100 + activity_id + /// the value is calculated by the logging system as: `class_uid` * 100 + `activity_id` pub type_uid: TypeUid, - /// The event type name, as defined by the type_uid + /// The event type name, as defined by the `type_uid` #[builder(default)] #[serde(skip_serializing_if = "Option::is_none")] pub type_name: Option, @@ -548,9 +550,9 @@ pub enum ObservableTypeId { IPAddress = 2, /// Media Access Control (MAC) address. For example: 18:36:F3:98:4F:9A MACAddress = 3, - /// User name. For example: john_doe + /// User name. For example: `john_doe` UserName = 4, - /// Email address. For example: john_doe@example.com + /// Email address. For example: `john_doe@example.com` EmailAddress = 5, /// Uniform Resource Locator (URL) string URLString = 6, @@ -564,7 +566,7 @@ pub enum ObservableTypeId { ResourceUID = 10, /// Endpoints, whether physical or virtual, connect to and interact with computer networks. /// Examples include mobile devices, computers, virtual machines, embedded devices, servers, - /// and IoT devices like cameras and smart speakers + /// and `IoT` devices like cameras and smart speakers Endpoint = 20, /// The User object describes the characteristics of a user/person or a security principal. /// Defined by D3FEND [d3f:UserAccount](https://d3fend.mitre.org/dao/artifact/d3f:UserAccount/) @@ -691,7 +693,7 @@ pub struct Reputation { #[builder(default)] #[serde(skip_serializing_if = "Option::is_none")] pub provider: Option, - /// The reputation score, normalized to the caption of the score_id value. In the case of 'Other', + /// The reputation score, normalized to the caption of the `score_id` value. In the case of 'Other', /// it is defined by the event source #[builder(default)] #[serde(skip_serializing_if = "Option::is_none")] @@ -835,6 +837,7 @@ pub struct Product { /// Encompasses details related to the capabilities, components, user interface (UI) design, /// and performance upgrades associated with the feature. +/// /// #[derive(Default, Serialize, Deserialize, Builder, Eq, PartialEq, Debug, Clone)] #[builder(setter(into))] @@ -882,12 +885,12 @@ struct FilteredRequest { /// authorization decision. #[derive(Default, Debug, Clone, PartialEq, Eq)] pub(crate) enum EntityComponent { - /// A concrete EntityUID + /// A concrete `EntityUID` Concrete(EntityUid), /// An entity that is not specified / concrete. Unspecified, #[default] - /// No EntityUID because it was filtered out. + /// No `EntityUID` because it was filtered out. None, } @@ -935,15 +938,12 @@ impl From> for EntityComponent { #[cfg(test)] mod test { - use core::num; use std::collections::{HashMap, HashSet}; use std::str::FromStr; use cedar_policy::{ - AuthorizationError, Authorizer, Context, Entities, EntityId, EntityTypeName, EntityUid, - EvaluationError, PolicyId, PolicySet, Request, Response, + Context, Entities, EntityId, EntityTypeName, EntityUid, PolicyId, Request, Response, }; - use cedar_policy_core::ast::{PolicyID, RestrictedExpr, Value}; use cedar_policy_core::authorizer::Decision; use serde_json::{from_str, to_string, to_value, Map}; @@ -1088,7 +1088,7 @@ mod test { // }), // }) // .collect(); - + println!("Number of errors needed: {}", { num_of_error }); // Uses a empty vector now instead of giving num_of_error errors. Tests have been changed to reflect this // Leads to problems in test coverage Response::new(decision, policy_ids, vec![]) diff --git a/src/public/simple.rs b/src/public/simple.rs index 4952cd6..7952d9e 100644 --- a/src/public/simple.rs +++ b/src/public/simple.rs @@ -2,7 +2,7 @@ use std::sync::Arc; #[cfg(feature = "partial-eval")] -use cedar_policy::PartialResponse; +use cedar_policy::{Diagnostics, PartialResponse}; use cedar_policy::{Entities, Request, Response}; use derive_builder::Builder; use thiserror::Error; @@ -214,26 +214,31 @@ where ); info!("Fetched Authorization data from Policy Set Provider and Entity Provider"); + let concrete_response = partial_response.clone().concretize(); + // Skip logging for now info!("Generated OCSF log record."); - match &partial_response { - Concrete(response) => self.log(request, response, entities), - Residual(residual_response) => self.log_residual(request, residual_response, entities), - }; + // match &partial_response { + // Concrete(response) => self.log(request, response, entities), + // Residual(residual_response) => self.log_residual(request, residual_response, entities), + // }; + match partial_response.decision() { + Some(_) => self.log(request, &concrete_response, entities), + None => self.log_residual( + request, + concrete_response.diagnostics(), + &partial_response, + entities, + ), + } info!( - "Is_authorized_partial completed: response_decision={}", - match &partial_response { - Concrete(response) => format!("{:?}", response.decision()), - Residual(residual_response) => format!("{:?}", residual_response.residuals()), - } + "Is_authorized_partial completed: response_decision={:?}", + partial_response.decision() ); debug!( "This decision was reached because: response_diagnostics={:?}", - match &partial_response { - Concrete(response) => response.diagnostics(), - Residual(residual_response) => residual_response.diagnostics(), - } + partial_response.clone().concretize().diagnostics() ); Ok(partial_response) @@ -244,19 +249,24 @@ where fn log_residual( &self, request: &Request, - residual_response: &ResidualResponse, + // residual_response: &ResidualResponse, + diagnostics: &Diagnostics, + policies: &PartialResponse, entities: &Entities, ) { event!(target: "cedar::simple::authorizer", Level::INFO, "{}", serde_json::to_string( &OpenCyberSecurityFramework::create_generic( request, - residual_response.diagnostics(), - residual_response.residuals().policies() + //residual_response.diagnostics(), + diagnostics, + // residual_response.residuals().policies() + policies.all_residuals() .map(|policy| format!("{}", policy.id())) .collect::>() .join(", ") .as_str(), + String::from("Residuals"), entities, &self.log_config.field_set, @@ -423,7 +433,7 @@ mod test_partial { use std::sync::Arc; use async_trait::async_trait; - use cedar_policy::{Context, Entities, PartialResponse, Policy, PolicySet, Request}; + use cedar_policy::{Context, Entities, Policy, PolicyId, PolicySet, Request}; use cedar_policy_core::authorizer::Decision; use cool_asserts::assert_matches; @@ -445,8 +455,8 @@ mod test_partial { let result = authorizer .is_authorized_partial( &Request::builder() - .principal(Some(r#"User::"Mike""#.parse().unwrap())) - .action(Some(r#"Action::"View""#.parse().unwrap())) + .principal(r#"User::"Mike""#.parse().unwrap()) + .action(r#"Action::"View""#.parse().unwrap()) .context(Context::empty()) .build(), &Entities::empty(), @@ -454,9 +464,7 @@ mod test_partial { .await; assert_matches!(result, Ok(partial_response) => - assert_matches!(partial_response, PartialResponse::Concrete(response) => - assert_eq!(response.decision(), Decision::Deny) - ) + assert_eq!(partial_response.decision(), Some(Decision::Deny)) ); assert_eq!(authorizer.log_config.requester, DEFAULT_REQUESTER_NAME); assert!(!authorizer.log_config.field_set.principal); @@ -475,8 +483,8 @@ mod test_partial { let result = authorizer .is_authorized_partial( &Request::builder() - .action(Some(r#"Action::"View""#.parse().unwrap())) - .resource(Some(r#"Box::"10""#.parse().unwrap())) + .action(r#"Action::"View""#.parse().unwrap()) + .resource(r#"Box::"10""#.parse().unwrap()) .context(Context::empty()) .build(), &Entities::empty(), @@ -484,9 +492,7 @@ mod test_partial { .await; assert_matches!(result, Ok(partial_response) => - assert_matches!(partial_response, PartialResponse::Concrete(response) => - assert_eq!(response.decision(), Decision::Deny) - ) + assert_eq!(partial_response.decision(), Some(Decision::Deny)) ); assert_eq!(authorizer.log_config.requester, DEFAULT_REQUESTER_NAME); assert!(!authorizer.log_config.field_set.principal); @@ -502,7 +508,7 @@ mod test_partial { _: &Request, ) -> Result, PolicySetProviderError> { let policy = Policy::parse( - Some("test".into()), + Some(PolicyId::new("test")), r#"permit(principal == User::"Mike", action, resource == Box::"10");"#, ) .expect("Failed to parse"); @@ -526,8 +532,8 @@ mod test_partial { let result = authorizer .is_authorized_partial( &Request::builder() - .action(Some(r#"Action::"View""#.parse().unwrap())) - .resource(Some(r#"Box::"10""#.parse().unwrap())) + .action(r#"Action::"View""#.parse().unwrap()) + .resource(r#"Box::"10""#.parse().unwrap()) .context(Context::empty()) .build(), &Entities::empty(), @@ -535,9 +541,7 @@ mod test_partial { .await; assert_matches!(result, Ok(partial_response) => - assert_matches!(partial_response, PartialResponse::Residual(residual_response) => { - assert_eq!(residual_response.residuals().policies().count(), 1); - }) + assert_eq!(partial_response.all_residuals().count(), 1) ); assert_eq!(authorizer.log_config.requester, DEFAULT_REQUESTER_NAME); assert!(!authorizer.log_config.field_set.principal);