Skip to content

Commit

Permalink
Entity and Request Validation (#399)
Browse files Browse the repository at this point in the history
Co-authored-by: Shah <[email protected]>
Co-authored-by: bhakti shah <[email protected]>
Co-authored-by: Bhakti Shah <[email protected]>
Co-authored-by: Bhakti Shah <[email protected]>
Co-authored-by: Kesha Hietala <[email protected]>
  • Loading branch information
6 people authored Aug 12, 2024
1 parent d77fe85 commit 8e2a2ae
Show file tree
Hide file tree
Showing 13 changed files with 583 additions and 4 deletions.
3 changes: 2 additions & 1 deletion cedar-drt/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,8 @@ The table below lists all available fuzz targets, including which component of t
| [`rbac`](fuzz/fuzz_targets/rbac.rs) | Authorizer | DRT | Diff test authorizer on sets of RBAC policies, including template instantiations |
| [`validation-drt-type-directed`](fuzz/fuzz_targets/validation-drt-type-directed.rs) | Validator | DRT | Diff test validation using (mostly) well-typed inputs |
| [`validation-drt`](fuzz/fuzz_targets/validation-drt.rs) | Validator | DRT | Diff test validation |
| [`entity-validation`](fuzz/fuzz_targets/entity-validation.rs) | Entity Validator | DRT | Diff test entity validation |
| [`request-validation`](fuzz/fuzz_targets/request-validation.rs) | Request Validator | DRT | Diff test request validation |
| | | | |
| [`formatter`](fuzz/fuzz_targets/formatter.rs) | Policy formatter, Pretty printer, Parser | PBT | Test round trip property: parse ∘ format ∘ pretty-print == id for ASTs |
| [`formatter-bytes`](fuzz/fuzz_targets/formatter-bytes.rs) | Policy formatter, Parser | PBT | The same as `formatter`, but we start with an arbitrary string instead of pretty-printing a policy AST |
Expand All @@ -32,7 +34,6 @@ The table below lists all available fuzz targets, including which component of t
| [`validation-pbt`](fuzz/fuzz_targets/validation-pbt.rs) | Validator | PBT | Test that validated policies do not result in type errors |
| [`validation-pbt-type-directed`](fuzz/fuzz_targets/validation-pbt-type-directed.rs) | Validator | PBT | Test that validated policies do not result in type errors using (mostly) well-typed inputs |
| [`wildcard-matching`](fuzz/fuzz_targets/wildcard-matching.rs) | String matching algorithm used for the `like` operator | PBT | Test algorithm against a regex-based implementation |

## Logging

If the fuzz targets are compiled with the `log` features, then they will log their entire corpus to the file pointed at in the `LOGFILE` environment variable.
Expand Down
12 changes: 12 additions & 0 deletions cedar-drt/fuzz/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -180,3 +180,15 @@ name = "convert-policy-json-to-cedar"
path = "fuzz_targets/convert-policy-json-to-cedar.rs"
test = false
doc = false

[[bin]]
name = "entity-validation"
path = "fuzz_targets/entity-validation.rs"
test = false
doc = false

[[bin]]
name = "request-validation"
path = "fuzz_targets/request-validation.rs"
test = false
doc = false
83 changes: 83 additions & 0 deletions cedar-drt/fuzz/fuzz_targets/entity-validation.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,83 @@
/*
* Copyright Cedar Contributors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/

#![no_main]
use cedar_drt::*;
use cedar_drt_inner::*;
use cedar_policy_core::extensions::Extensions;
use cedar_policy_generators::{
hierarchy::Hierarchy, hierarchy::HierarchyGenerator, schema::Schema, settings::ABACSettings,
};
use libfuzzer_sys::arbitrary::{self, Arbitrary, Unstructured};
use log::{debug, info};
use serde::Serialize;

/// Input expected by this fuzz target
#[derive(Debug, Clone, Serialize)]
pub struct FuzzTargetInput {
/// generated schema
#[serde(skip)]
pub schema: Schema,
/// generated hierarchy
#[serde(skip)]
pub hierarchy: Hierarchy,
}

/// settings for this fuzz target
const SETTINGS: ABACSettings = ABACSettings {
match_types: false,
enable_extensions: true,
max_depth: 7,
max_width: 7,
enable_additional_attributes: true,
enable_like: true,
enable_action_groups_and_attrs: true,
enable_arbitrary_func_call: true,
enable_unknowns: false,
enable_action_in_constraints: true,
enable_unspecified_apply_spec: true,
};

impl<'a> Arbitrary<'a> for FuzzTargetInput {
fn arbitrary(u: &mut Unstructured<'a>) -> arbitrary::Result<Self> {
let schema: Schema = Schema::arbitrary(SETTINGS.clone(), u)?;
let hierarchy = schema.arbitrary_hierarchy(u)?;
Ok(Self { schema, hierarchy })
}

fn size_hint(depth: usize) -> (usize, Option<usize>) {
arbitrary::size_hint::and_all(&[
Schema::arbitrary_size_hint(depth),
HierarchyGenerator::size_hint(depth),
])
}
}

fuzz_target!(|input: FuzzTargetInput| {
initialize_log();
let def_impl = LeanDefinitionalEngine::new();

// generate a schema
if let Ok(schema) = ValidatorSchema::try_from(input.schema) {
debug!("Schema: {:?}", schema);
if let Ok(entities) = Entities::try_from(input.hierarchy) {
let (_, total_dur) = time_function(|| {
run_ent_val_test(&def_impl, schema, entities, Extensions::all_available())
});
info!("{}{}", TOTAL_MSG, total_dur.as_nanos());
}
}
});
123 changes: 123 additions & 0 deletions cedar-drt/fuzz/fuzz_targets/request-validation.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,123 @@
/*
* Copyright Cedar Contributors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/

#![no_main]
use cedar_drt::*;
use cedar_drt_inner::*;
use cedar_policy_core::extensions::Extensions;
use cedar_policy_generators::{
abac::ABACRequest, hierarchy::Hierarchy, hierarchy::HierarchyGenerator, schema::Schema,
settings::ABACSettings,
};
use libfuzzer_sys::arbitrary::{self, Arbitrary, Unstructured};
use log::{debug, info};
use serde::Serialize;

/// Input expected by this fuzz target
#[derive(Debug, Clone, Serialize)]
pub struct FuzzTargetInput {
/// generated schema
#[serde(skip)]
pub schema: Schema,
/// generated hierarchy
#[serde(skip)]
pub hierarchy: Hierarchy,

/// the requests to try for this schema and hierarchy. We try 8 requests per
/// schema/hierarchy
#[serde(skip)]
pub requests: [ABACRequest; 8],
}

/// settings for this fuzz target
const SETTINGS: ABACSettings = ABACSettings {
match_types: false,
enable_extensions: true,
max_depth: 7,
max_width: 7,
enable_additional_attributes: true,
enable_like: true,
enable_action_groups_and_attrs: true,
enable_arbitrary_func_call: true,
enable_unknowns: false,
enable_action_in_constraints: true,
enable_unspecified_apply_spec: true,
};

impl<'a> Arbitrary<'a> for FuzzTargetInput {
fn arbitrary(u: &mut Unstructured<'a>) -> arbitrary::Result<Self> {
let schema: Schema = Schema::arbitrary(SETTINGS.clone(), u)?;
let hierarchy = schema.arbitrary_hierarchy(u)?;
let requests = [
schema.arbitrary_request(&hierarchy, u)?,
schema.arbitrary_request(&hierarchy, u)?,
schema.arbitrary_request(&hierarchy, u)?,
schema.arbitrary_request(&hierarchy, u)?,
schema.arbitrary_request(&hierarchy, u)?,
schema.arbitrary_request(&hierarchy, u)?,
schema.arbitrary_request(&hierarchy, u)?,
schema.arbitrary_request(&hierarchy, u)?,
];
Ok(Self {
schema,
hierarchy,
requests,
})
}

fn size_hint(depth: usize) -> (usize, Option<usize>) {
arbitrary::size_hint::and_all(&[
Schema::arbitrary_size_hint(depth),
HierarchyGenerator::size_hint(depth),
Schema::arbitrary_request_size_hint(depth),
Schema::arbitrary_request_size_hint(depth),
Schema::arbitrary_request_size_hint(depth),
Schema::arbitrary_request_size_hint(depth),
Schema::arbitrary_request_size_hint(depth),
Schema::arbitrary_request_size_hint(depth),
Schema::arbitrary_request_size_hint(depth),
Schema::arbitrary_request_size_hint(depth),
])
}
}

// Non-type-directed fuzzing of (strict) validation.
fuzz_target!(|input: FuzzTargetInput| {
initialize_log();
let def_impl = LeanDefinitionalEngine::new();

// generate a schema
if let Ok(schema) = ValidatorSchema::try_from(input.schema) {
debug!("Schema: {:?}", schema);
let requests = input
.requests
.into_iter()
.map(Into::into)
.collect::<Vec<_>>();
for request in requests.iter().cloned() {
debug!("Request: {request}");
let (_, total_dur) = time_function(|| {
run_req_val_test(
&def_impl,
schema.clone(),
request,
Extensions::all_available(),
)
});
info!("{}{}", TOTAL_MSG, total_dur.as_nanos());
}
}
});
5 changes: 4 additions & 1 deletion cedar-drt/fuzz/fuzz_targets/validation-drt.rs
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,9 @@
use cedar_drt::*;
use cedar_drt_inner::*;
use cedar_policy_core::ast;
use cedar_policy_generators::{abac::ABACPolicy, schema::Schema, settings::ABACSettings};
use cedar_policy_generators::{
abac::ABACPolicy, hierarchy::HierarchyGenerator, schema::Schema, settings::ABACSettings,
};
use libfuzzer_sys::arbitrary::{self, Arbitrary, Unstructured};
use log::{debug, info};
use serde::Serialize;
Expand Down Expand Up @@ -59,6 +61,7 @@ impl<'a> Arbitrary<'a> for FuzzTargetInput {
fn size_hint(depth: usize) -> (usize, Option<usize>) {
arbitrary::size_hint::and_all(&[
Schema::arbitrary_size_hint(depth),
HierarchyGenerator::size_hint(depth),
Schema::arbitrary_policy_size_hint(&SETTINGS, depth),
])
}
Expand Down
83 changes: 83 additions & 0 deletions cedar-drt/fuzz/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,8 @@ use std::collections::HashSet;
/// Times for cedar-policy authorization and validation.
pub const RUST_AUTH_MSG: &str = "rust_auth (ns) : ";
pub const RUST_VALIDATION_MSG: &str = "rust_validation (ns) : ";
pub const RUST_ENT_VALIDATION_MSG: &str = "rust_entity_validation (ns) : ";
pub const RUST_REQ_VALIDATION_MSG: &str = "rust_request_validation (ns) : ";

/// Compare the behavior of the partial evaluator in `cedar-policy` against a custom Cedar
/// implementation. Panics if the two do not agree. `expr` is the expression to
Expand Down Expand Up @@ -294,6 +296,87 @@ pub fn run_val_test(
}
}

pub fn run_req_val_test(
custom_impl: &impl CedarTestImplementation,
schema: ValidatorSchema,
request: ast::Request,
extensions: &Extensions<'_>,
) {
let (rust_res, rust_auth_dur) = time_function(|| {
ast::Request::new_with_unknowns(
request.principal().clone(),
request.action().clone(),
request.resource().clone(),
request.context().cloned(),
Some(&schema),
extensions,
)
});
info!("{}{}", RUST_REQ_VALIDATION_MSG, rust_auth_dur.as_nanos());

let definitional_res = custom_impl.validate_request(&schema, &request);
match definitional_res {
TestResult::Failure(_) => {
panic!("request validation test: failed to parse");
}
TestResult::Success(definitional_res) => {
if rust_res.is_ok() {
assert!(
definitional_res.validation_passed(),
"Definitional Errors: {:?}\n, Rust output: {:?}",
definitional_res.errors,
rust_res.unwrap()
);
} else {
assert!(
!definitional_res.validation_passed(),
"Errors: {:?}",
definitional_res.errors
);
}
}
}
}

pub fn run_ent_val_test(
custom_impl: &impl CedarTestImplementation,
schema: ValidatorSchema,
entities: Entities,
extensions: &Extensions<'_>,
) {
let (rust_res, rust_auth_dur) = time_function(|| {
Entities::from_entities(
entities.iter().cloned(),
Some(&cedar_policy_validator::CoreSchema::new(&schema)),
TCComputation::AssumeAlreadyComputed,
extensions,
)
});
info!("{}{}", RUST_ENT_VALIDATION_MSG, rust_auth_dur.as_nanos());
let definitional_res = custom_impl.validate_entities(&schema, &entities);
match definitional_res {
TestResult::Failure(_) => {
panic!("entity validation test: failed to parse");
}
TestResult::Success(definitional_res) => {
if rust_res.is_ok() {
assert!(
definitional_res.validation_passed(),
"Definitional Errors: {:?}\n, Rust output: {:?}",
definitional_res.errors,
rust_res.unwrap()
);
} else {
assert!(
!definitional_res.validation_passed(),
"Errors: {:?}",
definitional_res.errors
);
}
}
}
}

#[test]
fn test_run_auth_test() {
use cedar_drt::LeanDefinitionalEngine;
Expand Down
2 changes: 1 addition & 1 deletion cedar-drt/set_env_vars.sh
100644 → 100755
Original file line number Diff line number Diff line change
Expand Up @@ -26,4 +26,4 @@ else
if awk "BEGIN {exit !($GLIBC_VERSION < 2.27)}"; then
export LD_PRELOAD=${LD_PRELOAD+$LD_PRELOAD:}$(lean --print-prefix)/lib/glibc/libm.so
fi
fi
fi
12 changes: 12 additions & 0 deletions cedar-drt/src/definitional_request_types.rs
Original file line number Diff line number Diff line change
Expand Up @@ -56,3 +56,15 @@ pub struct ValidationRequest<'a> {
pub policies: &'a ast::PolicySet,
pub mode: ValidationMode,
}

#[derive(Debug, Serialize)]
pub struct RequestValidationRequest<'a> {
pub schema: &'a ValidatorSchema,
pub request: &'a ast::Request,
}

#[derive(Debug, Serialize)]
pub struct EntityValidationRequest<'a> {
pub schema: &'a ValidatorSchema,
pub entities: &'a Entities,
}
Loading

0 comments on commit 8e2a2ae

Please sign in to comment.