diff --git a/src/tools/genpolicy/src/daemon_set.rs b/src/tools/genpolicy/src/daemon_set.rs index 04c88429c4..4616551d1a 100644 --- a/src/tools/genpolicy/src/daemon_set.rs +++ b/src/tools/genpolicy/src/daemon_set.rs @@ -140,4 +140,13 @@ impl yaml::K8sResource for DaemonSet { } false } + + fn get_runtime_class_name(&self) -> Option { + self.spec + .template + .spec + .runtimeClassName + .clone() + .or_else(|| Some(String::new())) + } } diff --git a/src/tools/genpolicy/src/deployment.rs b/src/tools/genpolicy/src/deployment.rs index 45b80c83f7..2296bc9eb2 100644 --- a/src/tools/genpolicy/src/deployment.rs +++ b/src/tools/genpolicy/src/deployment.rs @@ -138,4 +138,13 @@ impl yaml::K8sResource for Deployment { } false } + + fn get_runtime_class_name(&self) -> Option { + self.spec + .template + .spec + .runtimeClassName + .clone() + .or_else(|| Some(String::new())) + } } diff --git a/src/tools/genpolicy/src/pod.rs b/src/tools/genpolicy/src/pod.rs index c897729936..c7a6ffb29b 100644 --- a/src/tools/genpolicy/src/pod.rs +++ b/src/tools/genpolicy/src/pod.rs @@ -46,7 +46,7 @@ pub struct PodSpec { restartPolicy: Option, #[serde(skip_serializing_if = "Option::is_none")] - runtimeClassName: Option, + pub runtimeClassName: Option, #[serde(skip_serializing_if = "Option::is_none")] pub initContainers: Option>, @@ -755,6 +755,13 @@ impl yaml::K8sResource for Pod { } false } + + fn get_runtime_class_name(&self) -> Option { + self.spec + .runtimeClassName + .clone() + .or_else(|| Some(String::new())) + } } impl Container { diff --git a/src/tools/genpolicy/src/policy.rs b/src/tools/genpolicy/src/policy.rs index 87b78adda6..f0834524b9 100644 --- a/src/tools/genpolicy/src/policy.rs +++ b/src/tools/genpolicy/src/policy.rs @@ -9,6 +9,7 @@ use crate::config_map; use crate::containerd; use crate::mount_and_storage; +use crate::no_policy; use crate::pod; use crate::policy; use crate::registry; @@ -380,6 +381,22 @@ impl AgentPolicy { let yaml_string = serde_yaml::to_string(&doc_mapping)?; let silent = config.silent_unsupported_fields; 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; // ConfigMap and Secret documents contain additional input for policy generation. diff --git a/src/tools/genpolicy/src/stateful_set.rs b/src/tools/genpolicy/src/stateful_set.rs index 5b078eaf5b..1c712002b7 100644 --- a/src/tools/genpolicy/src/stateful_set.rs +++ b/src/tools/genpolicy/src/stateful_set.rs @@ -185,6 +185,15 @@ impl yaml::K8sResource for StatefulSet { } false } + + fn get_runtime_class_name(&self) -> Option { + self.spec + .template + .spec + .runtimeClassName + .clone() + .or_else(|| Some(String::new())) + } } impl StatefulSet { diff --git a/src/tools/genpolicy/src/utils.rs b/src/tools/genpolicy/src/utils.rs index fc95479514..e0d4b84be5 100644 --- a/src/tools/genpolicy/src/utils.rs +++ b/src/tools/genpolicy/src/utils.rs @@ -82,6 +82,12 @@ struct CommandLineOptions { help = "Registry that uses plain HTTP. Can be passed more than once to configure multiple insecure registries." )] insecure_registry: Vec, + + #[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, } /// Application configuration, derived from on command line parameters. @@ -89,6 +95,7 @@ struct CommandLineOptions { pub struct Config { pub use_cache: bool, pub insecure_registries: Vec, + pub runtime_class_names: Vec, pub yaml_file: Option, pub rego_rules_path: String, @@ -121,6 +128,7 @@ impl Config { Self { use_cache: args.use_cached_files, insecure_registries: args.insecure_registry, + runtime_class_names: args.runtime_class_names, yaml_file: args.yaml_file, rego_rules_path: args.rego_rules_path, settings, diff --git a/src/tools/genpolicy/src/yaml.rs b/src/tools/genpolicy/src/yaml.rs index 6b7bf0065c..d2c905cde0 100644 --- a/src/tools/genpolicy/src/yaml.rs +++ b/src/tools/genpolicy/src/yaml.rs @@ -90,6 +90,10 @@ pub trait K8sResource { fn use_sandbox_pidns(&self) -> bool { panic!("Unsupported"); } + + fn get_runtime_class_name(&self) -> Option { + None + } } /// See Reference / Kubernetes API / Common Definitions / LabelSelector. diff --git a/tests/integration/kubernetes/k8s-policy-pod.bats b/tests/integration/kubernetes/k8s-policy-pod.bats index 5a6be1c1eb..5c53ae03fb 100644 --- a/tests/integration/kubernetes/k8s-policy-pod.bats +++ b/tests/integration/kubernetes/k8s-policy-pod.bats @@ -17,13 +17,22 @@ setup() { get_pod_config_dir 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" + 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" + pre_generate_pod_yaml="${pod_config_dir}/k8s-policy-pod-pre-generation.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. 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 auto_generate_policy "${pod_config_dir}" "${correct_pod_yaml}" "${correct_configmap_yaml}" fi @@ -31,6 +40,10 @@ setup() { # Start each test case with a copy of the correct yaml files. cp "${correct_configmap_yaml}" "${incorrect_configmap_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" { @@ -39,6 +52,17 @@ setup() { 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. test_pod_policy_error() { kubectl create -f "${correct_configmap_yaml}" @@ -143,6 +167,17 @@ test_pod_policy_error() { 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() { auto_generate_policy_enabled || skip "Auto-generated policy tests are disabled." @@ -154,4 +189,6 @@ teardown() { kubectl delete configmap "${configmap_name}" rm -f "${incorrect_pod_yaml}" rm -f "${incorrect_configmap_yaml}" + rm -f "${testcase_pre_generate_pod_yaml}" + rm -f "${testcase_pre_generate_configmap_yaml}" } diff --git a/tests/integration/kubernetes/tests_common.sh b/tests/integration/kubernetes/tests_common.sh index 5e631f5c3b..2d3bc01972 100644 --- a/tests/integration/kubernetes/tests_common.sh +++ b/tests/integration/kubernetes/tests_common.sh @@ -171,6 +171,7 @@ auto_generate_policy() { declare -r settings_dir="$1" declare -r yaml_file="$2" declare -r config_map_yaml_file="$3" + declare -r additional_flags="$4" auto_generate_policy_enabled || return 0 local genpolicy_command="RUST_LOG=info /opt/kata/bin/genpolicy -u -y ${yaml_file}" @@ -185,6 +186,8 @@ auto_generate_policy() { genpolicy_command+=" -d" fi + genpolicy_command+=" ${additional_flags}" + info "Executing: ${genpolicy_command}" eval "${genpolicy_command}" }