1
1
use policy_evaluator:: {
2
- admission_response:: { AdmissionResponse , AdmissionResponseStatus } ,
2
+ admission_response:: { self , AdmissionResponse , AdmissionResponseStatus } ,
3
3
callback_requests:: CallbackRequest ,
4
4
evaluation_context:: EvaluationContext ,
5
5
kubewarden_policy_sdk:: settings:: SettingsValidationResponse ,
@@ -659,28 +659,67 @@ impl EvaluationEnvironment {
659
659
// to define inside of the expression
660
660
let allowed = rhai_engine. eval_expression :: < bool > ( expression. as_str ( ) ) ?;
661
661
662
+ // The API Server puts some limitations on the warnings:
663
+ // - they cannot exceed 256 characters
664
+ // - the size of all the warnings cannot exceed 4096 characters
665
+ // - they are returned as HTTP headers, hence not all characters are allowed
666
+ //
667
+ // Because of these reasons, we use the warning struct only to
668
+ // tell the user whether a member policy was evaluated or not. When it was
669
+ // evaluated we just tell the outcome (allow/reject).
670
+ let mut warnings = vec ! [ ] ;
671
+
672
+ // The details of each policy evaluation are returned as part of the
673
+ // AdmissionResponse.status.details.causes
674
+ let mut status_causes = vec ! [ ] ;
675
+
676
+ let evaluation_results = policies_evaluation_results. lock ( ) . unwrap ( ) ;
677
+
678
+ for policy_id in & policy_ids {
679
+ if let Some ( result) = evaluation_results. get ( policy_id) {
680
+ let outcome = if result. allowed {
681
+ "allowed"
682
+ } else {
683
+ "rejected"
684
+ } ;
685
+ warnings. push ( format ! ( "{policy_id}: {outcome}" , ) ) ;
686
+
687
+ if !result. allowed {
688
+ let cause = admission_response:: StatusCause {
689
+ field : Some ( format ! ( "spec.policies.{}" , policy_id) ) ,
690
+ message : result. message . clone ( ) ,
691
+ ..Default :: default ( )
692
+ } ;
693
+ status_causes. push ( cause) ;
694
+ }
695
+ } else {
696
+ warnings. push ( format ! ( "{}: not evaluated" , policy_id) ) ;
697
+ }
698
+ }
699
+ debug ! (
700
+ ?policy_id,
701
+ ?allowed,
702
+ ?warnings,
703
+ ?status_causes,
704
+ "policy group evaluation result"
705
+ ) ;
706
+
662
707
let status = if allowed {
708
+ // The status field is discarded by the Kubernetes API server when the
709
+ // request is allowed.
663
710
None
664
711
} else {
665
712
Some ( AdmissionResponseStatus {
666
713
message : Some ( message) ,
667
714
code : None ,
715
+ details : Some ( admission_response:: StatusDetails {
716
+ causes : status_causes,
717
+ ..Default :: default ( )
718
+ } ) ,
719
+ ..Default :: default ( )
668
720
} )
669
721
} ;
670
722
671
- // Provide some feedback to the end user about the single policy evaluation results
672
- let evaluation_results = policies_evaluation_results. lock ( ) . unwrap ( ) ;
673
- let warnings: Vec < String > = policy_ids
674
- . iter ( )
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
- } )
682
- . collect ( ) ;
683
-
684
723
Ok ( AdmissionResponse {
685
724
uid : req. uid ( ) . to_string ( ) ,
686
725
allowed,
@@ -1022,24 +1061,38 @@ mod tests {
1022
1061
"group_policy_with_unhappy_or_bracket_happy_and_unhappy_bracket" ,
1023
1062
false ,
1024
1063
vec![
1025
- "unhappy_policy_2: [DENIED] - failing as expected " ,
1026
- "unhappy_policy_1: [DENIED] - failing as expected " ,
1027
- "happy_policy_1: [ALLOWED] " ,
1064
+ "unhappy_policy_2: rejected " ,
1065
+ "unhappy_policy_1: rejected " ,
1066
+ "happy_policy_1: allowed " ,
1028
1067
] ,
1068
+ vec![
1069
+ admission_response:: StatusCause {
1070
+ field: Some ( "spec.policies.unhappy_policy_1" . to_string( ) ) ,
1071
+ message: Some ( "failing as expected" . to_string( ) ) ,
1072
+ ..Default :: default ( )
1073
+ } ,
1074
+ admission_response:: StatusCause {
1075
+ field: Some ( "spec.policies.unhappy_policy_2" . to_string( ) ) ,
1076
+ message: Some ( "failing as expected" . to_string( ) ) ,
1077
+ ..Default :: default ( )
1078
+ } ,
1079
+ ]
1029
1080
) ]
1030
1081
#[ case:: not_all_policies_are_evaluated(
1031
1082
"group_policy_with_unhappy_or_happy_or_unhappy" ,
1032
1083
true ,
1033
1084
vec![
1034
- "unhappy_policy_1: [DENIED] - failing as expected " ,
1035
- "unhappy_policy_2: [NOT EVALUATED] " ,
1036
- "happy_policy_1: [ALLOWED] " ,
1085
+ "unhappy_policy_1: rejected " ,
1086
+ "unhappy_policy_2: not evaluated " ,
1087
+ "happy_policy_1: allowed " ,
1037
1088
] ,
1089
+ Vec :: new( ) , // no expected causes, since the request is accepted
1038
1090
) ]
1039
1091
fn group_policy_warning_assignments (
1040
1092
#[ case] policy_id : & str ,
1041
1093
#[ case] admission_accepted : bool ,
1042
1094
#[ case] expected_warnings : Vec < & str > ,
1095
+ #[ case] expected_status_causes : Vec < admission_response:: StatusCause > ,
1043
1096
) {
1044
1097
let policy_id = PolicyID :: Policy ( policy_id. to_string ( ) ) ;
1045
1098
let evaluation_environment = Arc :: new ( build_evaluation_environment ( ) ) ;
@@ -1063,10 +1116,28 @@ mod tests {
1063
1116
for expected in expected_warnings {
1064
1117
assert ! (
1065
1118
warnings. iter( ) . any( |w| w. contains( expected) ) ,
1066
- "could not find {}" ,
1119
+ "could not find warning {}" ,
1067
1120
expected
1068
1121
) ;
1069
1122
}
1123
+
1124
+ if admission_accepted {
1125
+ assert ! ( response. status. is_none( ) ) ;
1126
+ } else {
1127
+ let causes = response
1128
+ . status
1129
+ . expect ( "should have status" )
1130
+ . details
1131
+ . expect ( "should have details" )
1132
+ . causes ;
1133
+ for expected in expected_status_causes {
1134
+ assert ! (
1135
+ causes. iter( ) . any( |c| * c == expected) ,
1136
+ "could not find cause {:?}" ,
1137
+ expected
1138
+ ) ;
1139
+ }
1140
+ }
1070
1141
}
1071
1142
1072
1143
/// Given to identical wasm modules, only one instance of PolicyEvaluator is going to be
0 commit comments