Skip to content

Commit 8f6e4aa

Browse files
Merge pull request #712 from fabriziosestito/fix/do-not-exit-on-policy-error
feat: do not exit on policy error
2 parents 744d47f + af0129f commit 8f6e4aa

8 files changed

+250
-162
lines changed

src/api/service.rs

+14-3
Original file line numberDiff line numberDiff line change
@@ -36,12 +36,23 @@ pub(crate) fn evaluate(
3636
let start_time = Instant::now();
3737

3838
let policy_name = policy_id.to_owned();
39+
let vanilla_validation_response =
40+
match evaluation_environment.validate(policy_id, validate_request) {
41+
Ok(validation_response) => validation_response,
42+
Err(EvaluationError::PolicyInitialization(error)) => {
43+
return Ok(AdmissionResponse::reject(
44+
validate_request.uid().to_owned(),
45+
error.to_string(),
46+
500,
47+
))
48+
}
49+
50+
Err(error) => return Err(error),
51+
};
52+
3953
let policy_mode = evaluation_environment.get_policy_mode(policy_id)?;
4054
let allowed_to_mutate = evaluation_environment.get_policy_allowed_to_mutate(policy_id)?;
4155

42-
let vanilla_validation_response =
43-
evaluation_environment.validate(policy_id, validate_request)?;
44-
4556
let policy_evaluation_duration = start_time.elapsed();
4657
let accepted = vanilla_validation_response.allowed;
4758
let mutated = vanilla_validation_response.patch.is_some();

src/evaluation/errors.rs

+3
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,9 @@ pub type Result<T> = std::result::Result<T, EvaluationError>;
44

55
#[derive(Debug, Error)]
66
pub enum EvaluationError {
7+
#[error("{0}")]
8+
PolicyInitialization(String),
9+
710
#[error("unknown policy: {0}")]
811
PolicyNotFound(String),
912

src/evaluation/evaluation_environment.rs

+59-3
Original file line numberDiff line numberDiff line change
@@ -61,6 +61,11 @@ pub(crate) struct EvaluationEnvironment {
6161
/// Map a `policy_id` to the `PolicyEvaluationSettings` instance. This allows us to obtain
6262
/// the list of settings to be used when evaluating a given policy.
6363
policy_id_to_settings: HashMap<String, PolicyEvaluationSettings>,
64+
65+
/// A map with the policy ID as key, and the error message as value.
66+
/// This is used to store the errors that occurred during policies initialization.
67+
/// The errors can occur in the fetching of the policy, or in the validation of the settings.
68+
policy_initialization_errors: HashMap<String, String>,
6469
}
6570

6671
#[cfg_attr(test, automock)]
@@ -88,6 +93,16 @@ impl EvaluationEnvironment {
8893
))
8994
})?;
9095

96+
let precompiled_policy = match precompiled_policy.as_ref() {
97+
Ok(precompiled_policy) => precompiled_policy,
98+
Err(e) => {
99+
eval_env
100+
.policy_initialization_errors
101+
.insert(policy_id.clone(), e.to_string());
102+
continue;
103+
}
104+
};
105+
91106
eval_env
92107
.register(
93108
engine,
@@ -98,6 +113,8 @@ impl EvaluationEnvironment {
98113
policy_evaluation_limit_seconds,
99114
)
100115
.map_err(|e| EvaluationError::BootstrapFailure(e.to_string()))?;
116+
117+
eval_env.validate_settings(policy_id)?;
101118
}
102119

103120
Ok(eval_env)
@@ -208,18 +225,41 @@ impl EvaluationEnvironment {
208225

209226
/// Perform a request validation
210227
pub fn validate(&self, policy_id: &str, req: &ValidateRequest) -> Result<AdmissionResponse> {
228+
if let Some(error) = self.policy_initialization_errors.get(policy_id) {
229+
return Err(EvaluationError::PolicyInitialization(error.to_string()));
230+
}
231+
211232
let settings = self.get_policy_settings(policy_id)?;
212233
let mut evaluator = self.rehydrate(policy_id)?;
213234

214235
Ok(evaluator.validate(req.clone(), &settings.settings))
215236
}
216237

217238
/// Validate the settings the user provided for the given policy
218-
pub fn validate_settings(&self, policy_id: &str) -> Result<SettingsValidationResponse> {
239+
fn validate_settings(&mut self, policy_id: &str) -> Result<()> {
219240
let settings = self.get_policy_settings(policy_id)?;
220241
let mut evaluator = self.rehydrate(policy_id)?;
221242

222-
Ok(evaluator.validate_settings(&settings.settings))
243+
match evaluator.validate_settings(&settings.settings) {
244+
SettingsValidationResponse {
245+
valid: true,
246+
message: _,
247+
} => return Ok(()),
248+
SettingsValidationResponse {
249+
valid: false,
250+
message,
251+
} => {
252+
self.policy_initialization_errors.insert(
253+
policy_id.to_string(),
254+
format!(
255+
"Policy settings are invalid: {}",
256+
message.unwrap_or("no message".to_owned())
257+
),
258+
);
259+
}
260+
};
261+
262+
Ok(())
223263
}
224264

225265
/// Internal method, create a `PolicyEvaluator` by using a pre-initialized instance
@@ -321,7 +361,7 @@ mod tests {
321361
context_aware_resources: BTreeSet::new(),
322362
},
323363
);
324-
precompiled_policies.insert(policy_url, precompiled_policy.clone());
364+
precompiled_policies.insert(policy_url, Ok(precompiled_policy.clone()));
325365
}
326366

327367
EvaluationEnvironment::new(
@@ -386,4 +426,20 @@ mod tests {
386426
1
387427
);
388428
}
429+
430+
#[test]
431+
fn validate_policy_with_initialization_error() {
432+
let mut evaluation_environment = build_evaluation_environment().unwrap();
433+
let policy_id = "policy_3";
434+
evaluation_environment
435+
.policy_initialization_errors
436+
.insert(policy_id.to_string(), "error".to_string());
437+
438+
let validate_request =
439+
ValidateRequest::AdmissionRequest(build_admission_review_request().request);
440+
assert!(matches!(
441+
evaluation_environment.validate(policy_id, &validate_request).unwrap_err(),
442+
EvaluationError::PolicyInitialization(error) if error == "error"
443+
));
444+
}
389445
}

src/evaluation/precompiled_policy.rs

+3-2
Original file line numberDiff line numberDiff line change
@@ -66,8 +66,9 @@ impl PrecompiledPolicy {
6666

6767
/// A dictionary with:
6868
/// * Key: the URL of the WebAssembly module
69-
/// * value: the PrecompiledPolicy
70-
pub(crate) type PrecompiledPolicies = HashMap<String, PrecompiledPolicy>;
69+
/// * Value: a Result containing the precompiled policy or an error.
70+
/// Errors are stored and will be reported to the user in the API response.
71+
pub(crate) type PrecompiledPolicies = HashMap<String, Result<PrecompiledPolicy>>;
7172

7273
/// Check if policy server version is compatible with minimum kubewarden
7374
/// version required by the policy

src/lib.rs

+13-61
Original file line numberDiff line numberDiff line change
@@ -24,7 +24,6 @@ use policy_evaluator::policy_fetcher::verify::FulcioAndRekorData;
2424
use policy_evaluator::wasmtime;
2525
use policy_evaluator::{callback_handler::CallbackHandlerBuilder, kube};
2626
use rayon::prelude::*;
27-
use std::collections::HashMap;
2827
use std::net::SocketAddr;
2928
use std::sync::Arc;
3029
use tokio::sync::oneshot;
@@ -41,7 +40,7 @@ use crate::evaluation::{
4140
EvaluationEnvironment,
4241
};
4342
use crate::policy_downloader::{Downloader, FetchedPolicies};
44-
use config::{Config, Policy};
43+
use config::Config;
4544

4645
pub struct PolicyServer {
4746
router: Router,
@@ -129,14 +128,14 @@ impl PolicyServer {
129128
&config.policies_download_dir,
130129
config.verification_config.as_ref(),
131130
)
132-
.await?;
131+
.await;
133132

134133
let mut wasmtime_config = wasmtime::Config::new();
135134
if config.policy_evaluation_limit_seconds.is_some() {
136135
wasmtime_config.epoch_interruption(true);
137136
}
138137
let engine = wasmtime::Engine::new(&wasmtime_config)?;
139-
let precompiled_policies = precompile_policies(&engine, &fetched_policies)?;
138+
let precompiled_policies = precompile_policies(&engine, &fetched_policies);
140139

141140
let evaluation_environment = EvaluationEnvironment::new(
142141
&engine,
@@ -165,8 +164,6 @@ impl PolicyServer {
165164
info!("policy timeout protection is disabled");
166165
}
167166

168-
verify_policy_settings(&config.policies, &evaluation_environment).await?;
169-
170167
let state = Arc::new(ApiServerState {
171168
semaphore: Semaphore::new(config.pool_size),
172169
evaluation_environment,
@@ -245,66 +242,21 @@ impl PolicyServer {
245242
fn precompile_policies(
246243
engine: &wasmtime::Engine,
247244
fetched_policies: &FetchedPolicies,
248-
) -> Result<PrecompiledPolicies> {
245+
) -> PrecompiledPolicies {
249246
debug!(
250247
wasm_modules_count = fetched_policies.len(),
251248
"instantiating wasmtime::Module objects"
252249
);
253250

254-
let precompiled_policies: HashMap<String, Result<PrecompiledPolicy>> = fetched_policies
251+
fetched_policies
255252
.par_iter()
256-
.map(|(policy_url, wasm_module_path)| {
257-
let precompiled_policy = PrecompiledPolicy::new(engine, wasm_module_path);
258-
debug!(?policy_url, "module compiled");
259-
(policy_url.clone(), precompiled_policy)
260-
})
261-
.collect();
262-
263-
let errors: Vec<String> = precompiled_policies
264-
.iter()
265-
.filter_map(|(url, result)| match result {
266-
Ok(_) => None,
267-
Err(e) => Some(format!(
268-
"[{url}] policy cannot be compiled to WebAssembly module: {e:?}"
269-
)),
270-
})
271-
.collect();
272-
if !errors.is_empty() {
273-
return Err(anyhow!(
274-
"workers pool bootstrap: cannot instantiate `wasmtime::Module` objects: {:?}",
275-
errors.join(", ")
276-
));
277-
}
278-
279-
Ok(precompiled_policies
280-
.iter()
281-
.filter_map(|(url, result)| match result {
282-
Ok(p) => Some((url.clone(), p.clone())),
283-
Err(_) => None,
253+
.map(|(policy_url, fetched_policy)| match fetched_policy {
254+
Ok(policy) => {
255+
let precompiled_policy = PrecompiledPolicy::new(engine, policy);
256+
debug!(?policy_url, "module compiled");
257+
(policy_url.clone(), precompiled_policy)
258+
}
259+
Err(error) => (policy_url.clone(), Err(anyhow!(error.to_string()))),
284260
})
285-
.collect())
286-
}
287-
288-
/// Ensure the user provided valid settings for all the policies
289-
async fn verify_policy_settings(
290-
policies: &HashMap<String, Policy>,
291-
evaluation_environment: &EvaluationEnvironment,
292-
) -> Result<()> {
293-
let mut errors = vec![];
294-
for (policy_id, _policy) in policies.iter() {
295-
let set_val_rep = evaluation_environment.validate_settings(policy_id)?;
296-
if !set_val_rep.valid {
297-
errors.push(format!(
298-
"[{}] settings are not valid: {:?}",
299-
policy_id, set_val_rep.message
300-
));
301-
continue;
302-
}
303-
}
304-
305-
if errors.is_empty() {
306-
Ok(())
307-
} else {
308-
Err(anyhow!("{}", errors.join(", ")))
309-
}
261+
.collect()
310262
}

0 commit comments

Comments
 (0)