-
Notifications
You must be signed in to change notification settings - Fork 16
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Merge pull request #68 from kubewarden/opa
Enable evaluation of Rego-based policies
- Loading branch information
Showing
10 changed files
with
1,094 additions
and
368 deletions.
There are no files selected for viewing
Large diffs are not rendered by default.
Oops, something went wrong.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,7 +1,7 @@ | ||
[package] | ||
name = "kwctl" | ||
description = "Tool to manage Kubewarden policies" | ||
version = "0.1.10" | ||
version = "0.2.0" | ||
authors = [ | ||
"Flavio Castelli <[email protected]>", | ||
"Rafael Fernández López <[email protected]>", | ||
|
@@ -14,11 +14,13 @@ edition = "2018" | |
anyhow = "1.0" | ||
clap = "2.33.3" | ||
directories = "3.0.2" | ||
itertools = "0.10.1" | ||
k8s-openapi = { version = "0.11.0", default-features = false, features = ["v1_20"] } | ||
kube = "0.51.0" | ||
kubewarden-policy-sdk = "0.2.3" | ||
lazy_static = "1.4.0" | ||
mdcat = "0.22" | ||
policy-evaluator = { git = "https://github.com/kubewarden/policy-evaluator", tag = "v0.1.19" } | ||
policy-evaluator = { git = "https://github.com/kubewarden/policy-evaluator", tag = "v0.2.0" } | ||
policy-fetcher = { git = "https://github.com/kubewarden/policy-fetcher", tag = "v0.1.13" } | ||
pretty-bytes = "0.2.2" | ||
prettytable-rs = "^0.8" | ||
|
@@ -37,6 +39,7 @@ tracing-subscriber = { version= "0.2", features = ["fmt"] } | |
url = "2.2.0" | ||
validator = { version = "0.13", features = ["derive"] } | ||
walrus = "0.19.0" | ||
wasmparser = "0.80.0" | ||
|
||
[dev-dependencies] | ||
tempfile = "3.2.0" |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,172 @@ | ||
use anyhow::{anyhow, Result}; | ||
use kubewarden_policy_sdk::metadata::ProtocolVersion; | ||
use policy_evaluator::{ | ||
policy_evaluator::{PolicyEvaluator, PolicyExecutionMode}, | ||
policy_metadata::Metadata, | ||
}; | ||
use std::path::{Path, PathBuf}; | ||
|
||
pub(crate) enum Backend { | ||
Opa, | ||
OpaGatekeeper, | ||
KubewardenWapc(ProtocolVersion), | ||
} | ||
|
||
type KubewardenProtocolDetectorFn = fn(PathBuf) -> Result<ProtocolVersion>; | ||
type RegoDetectorFn = fn(PathBuf) -> Result<bool>; | ||
|
||
// Looks at the Wasm module pointed by `wasm_path` and return whether it was generaed by a Rego | ||
// policy | ||
// | ||
// The code looks at the export symbols offered by the Wasm module. | ||
// Having at least one symbol that starts with the `opa_` prefix leads | ||
// the policy to be considered a Rego-based one. | ||
fn rego_policy_detector(wasm_path: PathBuf) -> Result<bool> { | ||
let data: Vec<u8> = std::fs::read(wasm_path)?; | ||
for payload in wasmparser::Parser::new(0).parse_all(&data) { | ||
if let wasmparser::Payload::ExportSection(s) = payload? { | ||
for export in s { | ||
if export?.field.starts_with("opa_") { | ||
return Ok(true); | ||
} | ||
} | ||
} | ||
} | ||
|
||
Ok(false) | ||
} | ||
|
||
fn kubewarden_protocol_detector(wasm_path: PathBuf) -> Result<ProtocolVersion> { | ||
let mut policy_evaluator = PolicyEvaluator::from_file( | ||
String::from(wasm_path.to_string_lossy()), | ||
wasm_path.as_path(), | ||
PolicyExecutionMode::KubewardenWapc, | ||
None, | ||
)?; | ||
policy_evaluator | ||
.protocol_version() | ||
.map_err(|e| anyhow!("Cannot compute ProtocolVersion used by the policy: {:?}", e)) | ||
} | ||
|
||
pub(crate) struct BackendDetector { | ||
kubewarden_protocol_detector_func: KubewardenProtocolDetectorFn, | ||
rego_detector_func: RegoDetectorFn, | ||
} | ||
|
||
impl Default for BackendDetector { | ||
fn default() -> Self { | ||
BackendDetector { | ||
kubewarden_protocol_detector_func: kubewarden_protocol_detector, | ||
rego_detector_func: rego_policy_detector, | ||
} | ||
} | ||
} | ||
|
||
impl BackendDetector { | ||
#[allow(dead_code)] | ||
/// This method is intended to be used by unit tests | ||
pub(crate) fn new( | ||
rego_detector_func: RegoDetectorFn, | ||
kubewarden_protocol_detector_func: KubewardenProtocolDetectorFn, | ||
) -> Self { | ||
BackendDetector { | ||
kubewarden_protocol_detector_func, | ||
rego_detector_func, | ||
} | ||
} | ||
|
||
pub(crate) fn is_rego_policy(&self, wasm_path: &Path) -> Result<bool> { | ||
(self.rego_detector_func)(wasm_path.to_path_buf()).map_err(|e| { | ||
anyhow!( | ||
"Error while checking if the policy has been created using Opa/Gatekeeper: {}", | ||
e | ||
) | ||
}) | ||
} | ||
|
||
pub(crate) fn detect(&self, wasm_path: PathBuf, metadata: &Metadata) -> Result<Backend> { | ||
let is_rego_policy = self.is_rego_policy(&wasm_path)?; | ||
match metadata.execution_mode { | ||
PolicyExecutionMode::Opa => { | ||
if is_rego_policy { | ||
Ok(Backend::Opa) | ||
} else { | ||
Err(anyhow!( | ||
"Wrong value inside of policy's metatada for 'executionMode'. The policy has not been created using Rego" | ||
)) | ||
} | ||
} | ||
PolicyExecutionMode::OpaGatekeeper => { | ||
if is_rego_policy { | ||
Ok(Backend::OpaGatekeeper) | ||
} else { | ||
Err(anyhow!( | ||
"Wrong value inside of policy's metatada for 'executionMode'. The policy has not been created using Rego" | ||
)) | ||
} | ||
} | ||
PolicyExecutionMode::KubewardenWapc => { | ||
if is_rego_policy { | ||
Err(anyhow!( | ||
"Wrong value inside of policy's metatada for 'executionMode'. This policy has been created using Rego" | ||
)) | ||
} else { | ||
let protocol_version = (self.kubewarden_protocol_detector_func)(wasm_path) | ||
.map_err(|e| { | ||
anyhow!("Error while detecting Kubewarden protocol version: {:?}", e) | ||
})?; | ||
Ok(Backend::KubewardenWapc(protocol_version)) | ||
} | ||
} | ||
} | ||
} | ||
} | ||
|
||
#[cfg(test)] | ||
mod tests { | ||
use super::*; | ||
|
||
fn mock_protocol_version_detector_v1(_wasm_path: PathBuf) -> Result<ProtocolVersion> { | ||
Ok(ProtocolVersion::V1) | ||
} | ||
|
||
fn mock_rego_policy_detector_true(_wasm_path: PathBuf) -> Result<bool> { | ||
Ok(true) | ||
} | ||
|
||
fn mock_rego_policy_detector_false(_wasm_path: PathBuf) -> Result<bool> { | ||
Ok(false) | ||
} | ||
|
||
#[test] | ||
fn test_execution_mode_cannot_be_kubewarden_for_a_rego_policy() { | ||
let metadata = Metadata { | ||
execution_mode: PolicyExecutionMode::KubewardenWapc, | ||
..Default::default() | ||
}; | ||
|
||
let backend_detector = BackendDetector::new( | ||
mock_rego_policy_detector_true, | ||
mock_protocol_version_detector_v1, | ||
); | ||
let backend = backend_detector.detect(PathBuf::from("irrelevant.wasm"), &metadata); | ||
assert!(backend.is_err()); | ||
} | ||
|
||
#[test] | ||
fn test_execution_mode_cannot_be_opa_or_gatekeeper_for_a_kubewarden_policy() { | ||
for execution_mode in vec![PolicyExecutionMode::Opa, PolicyExecutionMode::OpaGatekeeper] { | ||
let metadata = Metadata { | ||
execution_mode, | ||
..Default::default() | ||
}; | ||
|
||
let backend_detector = BackendDetector::new( | ||
mock_rego_policy_detector_false, | ||
mock_protocol_version_detector_v1, | ||
); | ||
let backend = backend_detector.detect(PathBuf::from("irrelevant.wasm"), &metadata); | ||
assert!(backend.is_err()); | ||
} | ||
} | ||
} |
Oops, something went wrong.