Skip to content

Commit fd8b097

Browse files
authored
Merge pull request #908 from flavio/group-policy-improve-response-message
group policy: improve evaluation response
2 parents d23bfaf + 779c513 commit fd8b097

File tree

2 files changed

+198
-27
lines changed

2 files changed

+198
-27
lines changed

src/evaluation/evaluation_environment.rs

+198-27
Original file line numberDiff line numberDiff line change
@@ -602,6 +602,8 @@ impl EvaluationEnvironment {
602602
Mutex<HashMap<String, PolicyGroupMemberEvaluationResult>>,
603603
> = Arc::new(Mutex::new(HashMap::new()));
604604

605+
let policy_ids = policies.clone();
606+
605607
for sub_policy_name in policies {
606608
let sub_policy_id = PolicyID::PolicyGroupPolicy {
607609
group: policy_id.to_string(),
@@ -668,9 +670,15 @@ impl EvaluationEnvironment {
668670

669671
// Provide some feedback to the end user about the single policy evaluation results
670672
let evaluation_results = policies_evaluation_results.lock().unwrap();
671-
let warnings: Vec<String> = evaluation_results
673+
let warnings: Vec<String> = policy_ids
672674
.iter()
673-
.map(|(policy, result)| format!("{}: {}", policy, result))
675+
.map(|policy_id| {
676+
let result = evaluation_results
677+
.get(policy_id)
678+
.map(|result| result.to_string())
679+
.unwrap_or_else(|| "[NOT EVALUATED]".to_string());
680+
format!("{}: {}", policy_id, result)
681+
})
674682
.collect();
675683

676684
Ok(AdmissionResponse {
@@ -728,31 +736,67 @@ fn create_policy_evaluator_pre(
728736
mod tests {
729737
use policy_evaluator::policy_evaluator::ValidateRequest;
730738
use rstest::*;
739+
use sha2::{Digest, Sha256};
731740
use std::collections::BTreeSet;
732741

733742
use super::*;
734743
use crate::config::{PolicyGroupMember, PolicyOrPolicyGroup};
735744
use crate::test_utils::build_admission_review_request;
736745

746+
/// build a precompiled policy of the given wasm module. Assumes this is a OPA Gatekeeper policy
747+
fn build_precompiled_policy(
748+
engine: &wasmtime::Engine,
749+
module_bytes: &[u8],
750+
) -> PrecompiledPolicy {
751+
let module = wasmtime::Module::new(engine, module_bytes)
752+
.expect("should be able to build the smallest wasm module ever");
753+
754+
// calculate the digest of the module using sha256
755+
let mut hasher = Sha256::new();
756+
hasher.update(module_bytes);
757+
let digest = hasher.finalize();
758+
759+
PrecompiledPolicy {
760+
precompiled_module: module.serialize().unwrap(),
761+
execution_mode: policy_evaluator::policy_evaluator::PolicyExecutionMode::OpaGatekeeper,
762+
digest: format!("{digest:x}"),
763+
}
764+
}
765+
737766
fn build_evaluation_environment() -> EvaluationEnvironment {
738767
let engine = wasmtime::Engine::default();
739-
let policy_ids = vec!["policy_1", "policy_2"];
740-
let module_bytes = include_bytes!("../../tests/data/gatekeeper_always_happy_policy.wasm");
768+
let module_bytes_always_happy =
769+
include_bytes!("../../tests/data/gatekeeper_always_happy_policy.wasm");
770+
let module_bytes_always_unhappy =
771+
include_bytes!("../../tests/data/gatekeeper_always_unhappy_policy.wasm");
741772

742-
let module = wasmtime::Module::new(&engine, module_bytes)
743-
.expect("should be able to build the smallest wasm module ever");
744773
let (callback_handler_tx, _) = mpsc::channel(10);
745774

746-
let precompiled_policy = PrecompiledPolicy {
747-
precompiled_module: module.serialize().unwrap(),
748-
execution_mode: policy_evaluator::policy_evaluator::PolicyExecutionMode::OpaGatekeeper,
749-
digest: "unique-digest".to_string(),
750-
};
775+
let precompiled_policy_happy = build_precompiled_policy(&engine, module_bytes_always_happy);
776+
let precompiled_policy_unhappy =
777+
build_precompiled_policy(&engine, module_bytes_always_unhappy);
778+
779+
let test_policies: HashMap<String, PrecompiledPolicy> = vec![
780+
(
781+
"happy_policy_1".to_string(),
782+
precompiled_policy_happy.clone(),
783+
),
784+
(
785+
"happy_policy_2".to_string(),
786+
precompiled_policy_happy.clone(),
787+
),
788+
(
789+
"unhappy_policy_1".to_string(),
790+
precompiled_policy_unhappy.clone(),
791+
),
792+
]
793+
.into_iter()
794+
.collect();
751795

752796
let mut policies: HashMap<String, crate::config::PolicyOrPolicyGroup> = HashMap::new();
753797
let mut precompiled_policies: PrecompiledPolicies = PrecompiledPolicies::new();
754798

755-
for policy_id in &policy_ids {
799+
for (policy_id, precompiled_policy) in &test_policies {
756800
let policy_url = format!("file:///tmp/{policy_id}.wasm");
757801
policies.insert(
758802
policy_id.to_string(),
@@ -773,16 +817,16 @@ mod tests {
773817
PolicyOrPolicyGroup::PolicyGroup {
774818
policy_mode: PolicyMode::Protect,
775819
policies: vec![(
776-
"policy_1".to_string(),
820+
"happy_policy_1".to_string(),
777821
PolicyGroupMember {
778-
url: "file:///tmp/policy_1.wasm".to_string(),
822+
url: "file:///tmp/happy_policy_1.wasm".to_string(),
779823
settings: None,
780824
context_aware_resources: BTreeSet::new(),
781825
},
782826
)]
783827
.into_iter()
784828
.collect(),
785-
expression: "true || policy_1()".to_string(),
829+
expression: "true || happy_policy_1()".to_string(),
786830
message: "something went wrong".to_string(),
787831
},
788832
);
@@ -800,16 +844,16 @@ mod tests {
800844
PolicyOrPolicyGroup::PolicyGroup {
801845
policy_mode: PolicyMode::Protect,
802846
policies: vec![(
803-
"policy_1".to_string(),
847+
"happy_policy_1".to_string(),
804848
PolicyGroupMember {
805-
url: "file:///tmp/policy_1.wasm".to_string(),
849+
url: "file:///tmp/happy_policy_1.wasm".to_string(),
806850
settings: None,
807851
context_aware_resources: BTreeSet::new(),
808852
},
809853
)]
810854
.into_iter()
811855
.collect(),
812-
expression: "not_a_known_policy() || policy_1()".to_string(),
856+
expression: "unknown_policy() || happy_policy_1()".to_string(),
813857
message: "something went wrong".to_string(),
814858
},
815859
);
@@ -837,16 +881,91 @@ mod tests {
837881
PolicyOrPolicyGroup::PolicyGroup {
838882
policy_mode: PolicyMode::Protect,
839883
policies: vec![(
840-
"policy_1".to_string(),
884+
"happy_policy_1".to_string(),
841885
PolicyGroupMember {
842-
url: "file:///tmp/policy_1.wasm".to_string(),
886+
url: "file:///tmp/happy_policy_1.wasm".to_string(),
843887
settings: None,
844888
context_aware_resources: BTreeSet::new(),
845889
},
846890
)]
847891
.into_iter()
848892
.collect(),
849-
expression: "policy_1() + 1".to_string(),
893+
expression: "happy_policy_1() + 1".to_string(),
894+
message: "something went wrong".to_string(),
895+
},
896+
);
897+
policies.insert(
898+
"group_policy_with_unhappy_or_bracket_happy_and_unhappy_bracket".to_string(),
899+
PolicyOrPolicyGroup::PolicyGroup {
900+
policy_mode: PolicyMode::Protect,
901+
policies: vec![
902+
(
903+
"happy_policy_1".to_string(),
904+
PolicyGroupMember {
905+
url: "file:///tmp/happy_policy_1.wasm".to_string(),
906+
settings: None,
907+
context_aware_resources: BTreeSet::new(),
908+
},
909+
),
910+
(
911+
"unhappy_policy_1".to_string(),
912+
PolicyGroupMember {
913+
url: "file:///tmp/unhappy_policy_1.wasm".to_string(),
914+
settings: None,
915+
context_aware_resources: BTreeSet::new(),
916+
},
917+
),
918+
(
919+
"unhappy_policy_2".to_string(),
920+
PolicyGroupMember {
921+
url: "file:///tmp/unhappy_policy_1.wasm".to_string(),
922+
settings: None,
923+
context_aware_resources: BTreeSet::new(),
924+
},
925+
),
926+
]
927+
.into_iter()
928+
.collect(),
929+
expression: "unhappy_policy_1() || (happy_policy_1() && unhappy_policy_2())"
930+
.to_string(),
931+
message: "something went wrong".to_string(),
932+
},
933+
);
934+
935+
policies.insert(
936+
"group_policy_with_unhappy_or_happy_or_unhappy".to_string(),
937+
PolicyOrPolicyGroup::PolicyGroup {
938+
policy_mode: PolicyMode::Protect,
939+
policies: vec![
940+
(
941+
"happy_policy_1".to_string(),
942+
PolicyGroupMember {
943+
url: "file:///tmp/happy_policy_1.wasm".to_string(),
944+
settings: None,
945+
context_aware_resources: BTreeSet::new(),
946+
},
947+
),
948+
(
949+
"unhappy_policy_1".to_string(),
950+
PolicyGroupMember {
951+
url: "file:///tmp/unhappy_policy_1.wasm".to_string(),
952+
settings: None,
953+
context_aware_resources: BTreeSet::new(),
954+
},
955+
),
956+
(
957+
"unhappy_policy_2".to_string(),
958+
PolicyGroupMember {
959+
url: "file:///tmp/unhappy_policy_1.wasm".to_string(),
960+
settings: None,
961+
context_aware_resources: BTreeSet::new(),
962+
},
963+
),
964+
]
965+
.into_iter()
966+
.collect(),
967+
expression: "unhappy_policy_1() || happy_policy_1() || unhappy_policy_2()"
968+
.to_string(),
850969
message: "something went wrong".to_string(),
851970
},
852971
);
@@ -860,7 +979,7 @@ mod tests {
860979

861980
#[rstest]
862981
#[case::policy_not_defined("policy_not_defined", true)]
863-
#[case::policy_known("policy_1", false)]
982+
#[case::policy_known("happy_policy_1", false)]
864983
fn lookup_policy(#[case] policy_id: &str, #[case] expect_error: bool) {
865984
let policy_id = PolicyID::Policy(policy_id.to_string());
866985
let evaluation_environment = Arc::new(build_evaluation_environment());
@@ -892,23 +1011,75 @@ mod tests {
8921011
assert!(evaluation_environment
8931012
.get_policy_settings(&policy_id)
8941013
.is_ok());
895-
// note: we do not test `validate` with a known policy because this would
896-
// cause another error. The test policy we're using is just an empty Wasm
897-
// module
1014+
assert!(evaluation_environment
1015+
.validate(&policy_id, &validate_request)
1016+
.is_ok());
1017+
}
1018+
}
1019+
1020+
#[rstest]
1021+
#[case::all_policies_are_evaluated(
1022+
"group_policy_with_unhappy_or_bracket_happy_and_unhappy_bracket",
1023+
false,
1024+
vec![
1025+
"unhappy_policy_2: [DENIED] - failing as expected",
1026+
"unhappy_policy_1: [DENIED] - failing as expected",
1027+
"happy_policy_1: [ALLOWED]",
1028+
],
1029+
)]
1030+
#[case::not_all_policies_are_evaluated(
1031+
"group_policy_with_unhappy_or_happy_or_unhappy",
1032+
true,
1033+
vec![
1034+
"unhappy_policy_1: [DENIED] - failing as expected",
1035+
"unhappy_policy_2: [NOT EVALUATED]",
1036+
"happy_policy_1: [ALLOWED]",
1037+
],
1038+
)]
1039+
fn group_policy_warning_assignments(
1040+
#[case] policy_id: &str,
1041+
#[case] admission_accepted: bool,
1042+
#[case] expected_warnings: Vec<&str>,
1043+
) {
1044+
let policy_id = PolicyID::Policy(policy_id.to_string());
1045+
let evaluation_environment = Arc::new(build_evaluation_environment());
1046+
let validate_request =
1047+
ValidateRequest::AdmissionRequest(build_admission_review_request().request);
1048+
1049+
assert!(evaluation_environment.get_policy_mode(&policy_id).is_ok());
1050+
assert!(evaluation_environment
1051+
.get_policy_allowed_to_mutate(&policy_id)
1052+
.is_ok());
1053+
assert!(evaluation_environment
1054+
.get_policy_settings(&policy_id)
1055+
.is_ok());
1056+
1057+
let response = evaluation_environment
1058+
.validate(&policy_id, &validate_request)
1059+
.expect("should not have errored");
1060+
assert_eq!(response.allowed, admission_accepted);
1061+
1062+
let warnings = response.warnings.expect("should have warnings");
1063+
for expected in expected_warnings {
1064+
assert!(
1065+
warnings.iter().any(|w| w.contains(expected)),
1066+
"could not find {}",
1067+
expected
1068+
);
8981069
}
8991070
}
9001071

9011072
/// Given to identical wasm modules, only one instance of PolicyEvaluator is going to be
9021073
/// created
9031074
#[test]
904-
fn avoid_duplicated_instaces_of_policy_evaluator() {
1075+
fn avoid_duplicated_instances_of_policy_evaluator() {
9051076
let evaluation_environment = build_evaluation_environment();
9061077

9071078
assert_eq!(
9081079
evaluation_environment
9091080
.module_digest_to_policy_evaluator_pre
9101081
.len(),
911-
1
1082+
2
9121083
);
9131084
}
9141085

Binary file not shown.

0 commit comments

Comments
 (0)