genpolicy: add ability to filter for runtimeClassName

Add the CLI flag --runtime-class-names, which is used during
policy generation. For resources that can define a
runtimeClassName (e.g., Pods, Deployments, ReplicaSets,...)
the value must have any of the --runtime-class-names as
prefix, otherwise the resource is ignored.

This allows to run genpolicy on larger yaml
files defining many different resources and only generating
a policy for resources which will be deployed in a
confidential context.

Signed-off-by: Leonard Cohnen <lc@edgeless.systems>
This commit is contained in:
Leonard Cohnen 2024-05-27 23:29:18 +02:00
parent 2f686b1179
commit 1d1690e2a4
9 changed files with 104 additions and 1 deletions

View File

@ -140,4 +140,13 @@ impl yaml::K8sResource for DaemonSet {
} }
false false
} }
fn get_runtime_class_name(&self) -> Option<String> {
self.spec
.template
.spec
.runtimeClassName
.clone()
.or_else(|| Some(String::new()))
}
} }

View File

@ -138,4 +138,13 @@ impl yaml::K8sResource for Deployment {
} }
false false
} }
fn get_runtime_class_name(&self) -> Option<String> {
self.spec
.template
.spec
.runtimeClassName
.clone()
.or_else(|| Some(String::new()))
}
} }

View File

@ -46,7 +46,7 @@ pub struct PodSpec {
restartPolicy: Option<String>, restartPolicy: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")] #[serde(skip_serializing_if = "Option::is_none")]
runtimeClassName: Option<String>, pub runtimeClassName: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")] #[serde(skip_serializing_if = "Option::is_none")]
pub initContainers: Option<Vec<Container>>, pub initContainers: Option<Vec<Container>>,
@ -755,6 +755,13 @@ impl yaml::K8sResource for Pod {
} }
false false
} }
fn get_runtime_class_name(&self) -> Option<String> {
self.spec
.runtimeClassName
.clone()
.or_else(|| Some(String::new()))
}
} }
impl Container { impl Container {

View File

@ -9,6 +9,7 @@
use crate::config_map; use crate::config_map;
use crate::containerd; use crate::containerd;
use crate::mount_and_storage; use crate::mount_and_storage;
use crate::no_policy;
use crate::pod; use crate::pod;
use crate::policy; use crate::policy;
use crate::registry; use crate::registry;
@ -380,6 +381,22 @@ impl AgentPolicy {
let yaml_string = serde_yaml::to_string(&doc_mapping)?; let yaml_string = serde_yaml::to_string(&doc_mapping)?;
let silent = config.silent_unsupported_fields; let silent = config.silent_unsupported_fields;
let (mut resource, kind) = yaml::new_k8s_resource(&yaml_string, silent)?; let (mut resource, kind) = yaml::new_k8s_resource(&yaml_string, silent)?;
// Filter out resources that don't match the runtime class name.
if let Some(resource_runtime_name) = resource.get_runtime_class_name() {
if !config.runtime_class_names.is_empty()
&& !config
.runtime_class_names
.iter()
.any(|prefix| resource_runtime_name.starts_with(prefix))
{
resource =
boxed::Box::new(no_policy::NoPolicyResource { yaml: yaml_string });
resources.push(resource);
continue;
}
}
resource.init(config, &doc_mapping, silent).await; resource.init(config, &doc_mapping, silent).await;
// ConfigMap and Secret documents contain additional input for policy generation. // ConfigMap and Secret documents contain additional input for policy generation.

View File

@ -185,6 +185,15 @@ impl yaml::K8sResource for StatefulSet {
} }
false false
} }
fn get_runtime_class_name(&self) -> Option<String> {
self.spec
.template
.spec
.runtimeClassName
.clone()
.or_else(|| Some(String::new()))
}
} }
impl StatefulSet { impl StatefulSet {

View File

@ -82,6 +82,12 @@ struct CommandLineOptions {
help = "Registry that uses plain HTTP. Can be passed more than once to configure multiple insecure registries." help = "Registry that uses plain HTTP. Can be passed more than once to configure multiple insecure registries."
)] )]
insecure_registry: Vec<String>, insecure_registry: Vec<String>,
#[clap(
long,
help = "If specified, resources that have a runtimeClassName field defined will only receive a policy if the parameter is a prefix one of the given runtime class names."
)]
runtime_class_names: Vec<String>,
} }
/// Application configuration, derived from on command line parameters. /// Application configuration, derived from on command line parameters.
@ -89,6 +95,7 @@ struct CommandLineOptions {
pub struct Config { pub struct Config {
pub use_cache: bool, pub use_cache: bool,
pub insecure_registries: Vec<String>, pub insecure_registries: Vec<String>,
pub runtime_class_names: Vec<String>,
pub yaml_file: Option<String>, pub yaml_file: Option<String>,
pub rego_rules_path: String, pub rego_rules_path: String,
@ -121,6 +128,7 @@ impl Config {
Self { Self {
use_cache: args.use_cached_files, use_cache: args.use_cached_files,
insecure_registries: args.insecure_registry, insecure_registries: args.insecure_registry,
runtime_class_names: args.runtime_class_names,
yaml_file: args.yaml_file, yaml_file: args.yaml_file,
rego_rules_path: args.rego_rules_path, rego_rules_path: args.rego_rules_path,
settings, settings,

View File

@ -90,6 +90,10 @@ pub trait K8sResource {
fn use_sandbox_pidns(&self) -> bool { fn use_sandbox_pidns(&self) -> bool {
panic!("Unsupported"); panic!("Unsupported");
} }
fn get_runtime_class_name(&self) -> Option<String> {
None
}
} }
/// See Reference / Kubernetes API / Common Definitions / LabelSelector. /// See Reference / Kubernetes API / Common Definitions / LabelSelector.

View File

@ -17,13 +17,22 @@ setup() {
get_pod_config_dir get_pod_config_dir
correct_configmap_yaml="${pod_config_dir}/k8s-policy-configmap.yaml" correct_configmap_yaml="${pod_config_dir}/k8s-policy-configmap.yaml"
pre_generate_configmap_yaml="${pod_config_dir}/k8s-policy-configmap-pre-generation.yaml"
incorrect_configmap_yaml="${pod_config_dir}/k8s-policy-configmap-incorrect.yaml" incorrect_configmap_yaml="${pod_config_dir}/k8s-policy-configmap-incorrect.yaml"
testcase_pre_generate_configmap_yaml="${pod_config_dir}/k8s-policy-configmap-testcase-pre-generation.yaml"
correct_pod_yaml="${pod_config_dir}/k8s-policy-pod.yaml" correct_pod_yaml="${pod_config_dir}/k8s-policy-pod.yaml"
pre_generate_pod_yaml="${pod_config_dir}/k8s-policy-pod-pre-generation.yaml"
incorrect_pod_yaml="${pod_config_dir}/k8s-policy-pod-incorrect.yaml" incorrect_pod_yaml="${pod_config_dir}/k8s-policy-pod-incorrect.yaml"
testcase_pre_generate_pod_yaml="${pod_config_dir}/k8s-policy-pod-testcase-pre-generation.yaml"
# Save some time by executing genpolicy a single time. # Save some time by executing genpolicy a single time.
if [ "${BATS_TEST_NUMBER}" == "1" ]; then if [ "${BATS_TEST_NUMBER}" == "1" ]; then
# Save pre-generated yaml files
cp "${correct_configmap_yaml}" "${pre_generate_configmap_yaml}"
cp "${correct_pod_yaml}" "${pre_generate_pod_yaml}"
# Add policy to the correct pod yaml file # Add policy to the correct pod yaml file
auto_generate_policy "${pod_config_dir}" "${correct_pod_yaml}" "${correct_configmap_yaml}" auto_generate_policy "${pod_config_dir}" "${correct_pod_yaml}" "${correct_configmap_yaml}"
fi fi
@ -31,6 +40,10 @@ setup() {
# Start each test case with a copy of the correct yaml files. # Start each test case with a copy of the correct yaml files.
cp "${correct_configmap_yaml}" "${incorrect_configmap_yaml}" cp "${correct_configmap_yaml}" "${incorrect_configmap_yaml}"
cp "${correct_pod_yaml}" "${incorrect_pod_yaml}" cp "${correct_pod_yaml}" "${incorrect_pod_yaml}"
# Also give each testcase a copy of the pre-generated yaml files.
cp "${pre_generate_configmap_yaml}" "${testcase_pre_generate_configmap_yaml}"
cp "${pre_generate_pod_yaml}" "${testcase_pre_generate_pod_yaml}"
} }
@test "Successful pod with auto-generated policy" { @test "Successful pod with auto-generated policy" {
@ -39,6 +52,17 @@ setup() {
kubectl wait --for=condition=Ready "--timeout=${timeout}" pod "${pod_name}" kubectl wait --for=condition=Ready "--timeout=${timeout}" pod "${pod_name}"
} }
@test "Successful pod with auto-generated policy and runtimeClassName filter" {
runtime_class_name=$(yq read "${testcase_pre_generate_pod_yaml}" "spec.runtimeClassName")
auto_generate_policy "${pod_config_dir}" "${testcase_pre_generate_pod_yaml}" "${testcase_pre_generate_configmap_yaml}" \
"--runtime-class-names=other-runtime-class-name --runtime-class-names=${runtime_class_name}"
kubectl create -f "${testcase_pre_generate_configmap_yaml}"
kubectl create -f "${testcase_pre_generate_pod_yaml}"
kubectl wait --for=condition=Ready "--timeout=${timeout}" pod "${pod_name}"
}
# Common function for several test cases from this bats script. # Common function for several test cases from this bats script.
test_pod_policy_error() { test_pod_policy_error() {
kubectl create -f "${correct_configmap_yaml}" kubectl create -f "${correct_configmap_yaml}"
@ -143,6 +167,17 @@ test_pod_policy_error() {
waitForProcess "${wait_time}" "$sleep_time" "${command}" | grep -v "Message:" waitForProcess "${wait_time}" "$sleep_time" "${command}" | grep -v "Message:"
} }
@test "RuntimeClassName filter: no policy" {
# The policy should not be generated because the pod spec does not have a runtimeClassName.
runtime_class_name=$(yq read "${testcase_pre_generate_pod_yaml}" "spec.runtimeClassName")
auto_generate_policy "${pod_config_dir}" "${testcase_pre_generate_pod_yaml}" "${testcase_pre_generate_configmap_yaml}" \
"--runtime-class-names=other-${runtime_class_name}"
# Check that the pod yaml does not contain a policy annotation.
run ! grep -q "io.katacontainers.config.agent.policy" "${testcase_pre_generate_pod_yaml}"
}
teardown() { teardown() {
auto_generate_policy_enabled || skip "Auto-generated policy tests are disabled." auto_generate_policy_enabled || skip "Auto-generated policy tests are disabled."
@ -154,4 +189,6 @@ teardown() {
kubectl delete configmap "${configmap_name}" kubectl delete configmap "${configmap_name}"
rm -f "${incorrect_pod_yaml}" rm -f "${incorrect_pod_yaml}"
rm -f "${incorrect_configmap_yaml}" rm -f "${incorrect_configmap_yaml}"
rm -f "${testcase_pre_generate_pod_yaml}"
rm -f "${testcase_pre_generate_configmap_yaml}"
} }

View File

@ -171,6 +171,7 @@ auto_generate_policy() {
declare -r settings_dir="$1" declare -r settings_dir="$1"
declare -r yaml_file="$2" declare -r yaml_file="$2"
declare -r config_map_yaml_file="$3" declare -r config_map_yaml_file="$3"
declare -r additional_flags="$4"
auto_generate_policy_enabled || return 0 auto_generate_policy_enabled || return 0
local genpolicy_command="RUST_LOG=info /opt/kata/bin/genpolicy -u -y ${yaml_file}" local genpolicy_command="RUST_LOG=info /opt/kata/bin/genpolicy -u -y ${yaml_file}"
@ -185,6 +186,8 @@ auto_generate_policy() {
genpolicy_command+=" -d" genpolicy_command+=" -d"
fi fi
genpolicy_command+=" ${additional_flags}"
info "Executing: ${genpolicy_command}" info "Executing: ${genpolicy_command}"
eval "${genpolicy_command}" eval "${genpolicy_command}"
} }