1use lazy_static::lazy_static;
2
3use guest::prelude::*;
4use kubewarden_policy_sdk::wapc_guest as guest;
5
6use k8s_openapi::api::core::v1 as apicore;
7use k8s_openapi::Resource;
8
9extern crate kubewarden_policy_sdk as kubewarden;
10use kubewarden::{logging, protocol_version_guest, request::ValidationRequest, validate_settings};
11
12mod settings;
13use settings::Settings;
14
15use slog::{info, o, warn, Logger};
16
17lazy_static! {
18 static ref LOG_DRAIN: Logger = Logger::root(
19 logging::KubewardenDrain::new(),
20 o!("policy" => "sample-policy")
21 );
22}
23
24#[no_mangle]
25pub extern "C" fn wapc_init() {
26 register_function("validate", validate);
27 register_function("validate_settings", validate_settings::<Settings>);
28 register_function("protocol_version", protocol_version_guest);
29}
30
31fn validate(payload: &[u8]) -> CallResult {
32 let validation_request: ValidationRequest<Settings> = ValidationRequest::new(payload)?;
33
34 info!(LOG_DRAIN, "starting validation");
35 if validation_request.request.kind.kind != apicore::Pod::KIND {
36 warn!(LOG_DRAIN, "Policy validates Pods only. Accepting resource"; "kind" => &validation_request.request.kind.kind);
37 return kubewarden::accept_request();
38 }
39
40 match serde_json::from_value::<apicore::Pod>(validation_request.request.object) {
41 Ok(mut pod) => {
43 let pod_name = pod.metadata.name.clone().unwrap_or_default();
44 if validation_request
45 .settings
46 .invalid_names
47 .contains(&pod_name)
48 {
49 kubewarden::reject_request(
50 Some(format!("pod name {:?} is not accepted", pod_name)),
51 None,
52 None,
53 None,
54 )
55 } else {
56 let mut new_annotations = pod.metadata.annotations.clone().unwrap_or_default();
58 new_annotations.insert(
59 String::from("kubewarden.policy.demo/inspected"),
60 String::from("true"),
61 );
62 pod.metadata.annotations = Some(new_annotations);
63
64 let mutated_object = serde_json::to_value(pod)?;
66 kubewarden::mutate_request(mutated_object)
67 }
68 }
69 Err(_) => {
70 kubewarden::accept_request()
73 }
74 }
75}
76#[cfg(test)]
77mod tests {
78 use super::*;
79
80 use kubewarden_policy_sdk::test::Testcase;
81 use std::collections::HashSet;
82
83 #[test]
84 fn accept_pod_with_valid_name() -> Result<(), ()> {
85 let mut invalid_names = HashSet::new();
86 invalid_names.insert(String::from("bad_name1"));
87 let settings = Settings { invalid_names };
88
89 let request_file = "test_data/pod_creation.json";
90 let tc = Testcase {
91 name: String::from("Pod creation with valid name"),
92 fixture_file: String::from(request_file),
93 expected_validation_result: true,
94 settings,
95 };
96
97 let res = tc.eval(validate).unwrap();
98 assert!(
100 res.mutated_object.is_some(),
101 "Expected accepted object to be mutated",
102 );
103
104 let final_pod =
106 serde_json::from_value::<apicore::Pod>(res.mutated_object.unwrap()).unwrap();
107 let final_annotations = final_pod.metadata.annotations.unwrap();
108 assert_eq!(
109 final_annotations.get_key_value("kubewarden.policy.demo/inspected"),
110 Some((
111 &String::from("kubewarden.policy.demo/inspected"),
112 &String::from("true")
113 )),
114 );
115
116 Ok(())
117 }
118
119 #[test]
120 fn reject_pod_with_invalid_name() -> Result<(), ()> {
121 let mut invalid_names = HashSet::new();
122 invalid_names.insert(String::from("nginx"));
123 let settings = Settings { invalid_names };
124
125 let request_file = "test_data/pod_creation.json";
126 let tc = Testcase {
127 name: String::from("Pod creation with invalid name"),
128 fixture_file: String::from(request_file),
129 expected_validation_result: false,
130 settings,
131 };
132
133 let res = tc.eval(validate).unwrap();
134 assert!(
135 res.mutated_object.is_none(),
136 "Something mutated with test case: {}",
137 tc.name,
138 );
139
140 Ok(())
141 }
142
143 #[test]
144 fn accept_request_with_non_pod_resource() -> Result<(), ()> {
145 let mut invalid_names = HashSet::new();
146 invalid_names.insert(String::from("prod"));
147 let settings = Settings { invalid_names };
148
149 let request_file = "test_data/ingress_creation.json";
150 let tc = Testcase {
151 name: String::from("Ingress creation"),
152 fixture_file: String::from(request_file),
153 expected_validation_result: true,
154 settings,
155 };
156
157 let res = tc.eval(validate).unwrap();
158 assert!(
159 res.mutated_object.is_none(),
160 "Something mutated with test case: {}",
161 tc.name,
162 );
163
164 Ok(())
165 }
166}