From 4bb441965f83af6fdc6f093900f1302ba0fb50e1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Aur=C3=A9lien=20Bombo?= Date: Mon, 15 Apr 2024 19:57:44 +0000 Subject: [PATCH 1/5] genpolicy: support arbitrary resources with -c MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit This allows passing config maps and secrets (as well as any other resource kinds relevant in the future) using the -c flag. Fixes: #10033 Co-authored-by: Leonard Cohnen Signed-off-by: Leonard Cohnen Signed-off-by: Aurélien Bombo --- src/tools/genpolicy/src/policy.rs | 51 +++++++++++++++++++++++++++++-- src/tools/genpolicy/src/utils.rs | 29 ++++++++++-------- src/tools/genpolicy/tests/main.rs | 2 +- 3 files changed, 66 insertions(+), 16 deletions(-) diff --git a/src/tools/genpolicy/src/policy.rs b/src/tools/genpolicy/src/policy.rs index e55ef4951..ee0a92894 100644 --- a/src/tools/genpolicy/src/policy.rs +++ b/src/tools/genpolicy/src/policy.rs @@ -438,6 +438,11 @@ pub struct SandboxData { pub storages: Vec, } +enum K8sEnvFromSource { + ConfigMap(config_map::ConfigMap), + Secret(secret::Secret), +} + impl AgentPolicy { pub async fn from_files(config: &utils::Config) -> Result { let mut config_maps = Vec::new(); @@ -488,9 +493,18 @@ impl AgentPolicy { } } - if let Some(config_map_files) = &config.config_map_files { - for file in config_map_files { - config_maps.push(config_map::ConfigMap::new(file)?); + if let Some(config_files) = &config.config_files { + for resource_file in config_files { + for config_resource in parse_config_file(resource_file.to_string(), config).await? { + match config_resource { + K8sEnvFromSource::ConfigMap(config_map) => { + config_maps.push(config_map); + } + K8sEnvFromSource::Secret(secret) => { + secrets.push(secret); + } + } + } } } @@ -822,6 +836,37 @@ fn get_image_layer_storages( storages.push(overlay_storage); } +async fn parse_config_file( + yaml_file: String, + config: &utils::Config, +) -> Result> { + let mut k8sRes = Vec::new(); + let yaml_contents = yaml::get_input_yaml(&Some(yaml_file))?; + for document in serde_yaml::Deserializer::from_str(&yaml_contents) { + let doc_mapping = Value::deserialize(document)?; + if doc_mapping != Value::Null { + 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)?; + + resource.init(config, &doc_mapping, silent).await; + + // ConfigMap and Secret documents contain additional input for policy generation. + if kind.eq("ConfigMap") { + let config_map: config_map::ConfigMap = serde_yaml::from_str(&yaml_string)?; + debug!("{:#?}", &config_map); + k8sRes.push(K8sEnvFromSource::ConfigMap(config_map)); + } else if kind.eq("Secret") { + let secret: secret::Secret = serde_yaml::from_str(&yaml_string)?; + debug!("{:#?}", &secret); + k8sRes.push(K8sEnvFromSource::Secret(secret)); + } + } + } + + Ok(k8sRes) +} + /// Converts the given name to a string representation of its sha256 hash. fn name_to_hash(name: &str) -> String { let mut hasher = Sha256::new(); diff --git a/src/tools/genpolicy/src/utils.rs b/src/tools/genpolicy/src/utils.rs index 6696338cd..26f619825 100644 --- a/src/tools/genpolicy/src/utils.rs +++ b/src/tools/genpolicy/src/utils.rs @@ -18,10 +18,16 @@ struct CommandLineOptions { #[clap( short, long, - help = "Optional Kubernetes config map YAML input file path" + help = "Optional Kubernetes config map YAML input file path. DEPRECATED: use --config-file instead" )] config_map_file: Option, + #[clap( + long, + help = "Optional Kubernetes YAML input file path containing config resources such as ConfigMaps and Secrets" + )] + config_file: Option>, + #[clap( short = 'p', long, @@ -111,7 +117,7 @@ pub struct Config { pub yaml_file: Option, pub rego_rules_path: String, pub settings: settings::Settings, - pub config_map_files: Option>, + pub config_files: Option>, pub silent_unsupported_fields: bool, pub raw_out: bool, @@ -125,16 +131,15 @@ impl Config { pub fn new() -> Self { let args = CommandLineOptions::parse(); - let mut config_map_files = Vec::new(); - if let Some(config_map_file) = &args.config_map_file { - config_map_files.push(config_map_file.clone()); - } + // Migrate all files from the old `config_map_file` to the new `config_files` field + let config_files = args + .config_file + .unwrap_or_default() + .into_iter() + .chain(args.config_map_file.iter().cloned()) + .collect::>(); - let cm_files = if !config_map_files.is_empty() { - Some(config_map_files.clone()) - } else { - None - }; + let config_files = (!config_files.is_empty()).then_some(config_files); let mut layers_cache_file_path = args.layers_cache_file_path; // preserve backwards compatibility for only using the `use_cached_files` flag @@ -151,7 +156,7 @@ impl Config { yaml_file: args.yaml_file, rego_rules_path: args.rego_rules_path, settings, - config_map_files: cm_files, + config_files, silent_unsupported_fields: args.silent_unsupported_fields, raw_out: args.raw_out, base64_out: args.base64_out, diff --git a/src/tools/genpolicy/tests/main.rs b/src/tools/genpolicy/tests/main.rs index e6a827573..04f6f32e9 100644 --- a/src/tools/genpolicy/tests/main.rs +++ b/src/tools/genpolicy/tests/main.rs @@ -85,7 +85,7 @@ mod tests { let config = genpolicy::utils::Config { base64_out: false, - config_map_files: None, + config_files: None, containerd_socket_path: None, // Some(String::from("/var/run/containerd/containerd.sock")), insecure_registries: Vec::new(), layers_cache_file_path: None, From 2ea57aefbcfe35e90c342e7a6eb976a8857c3e82 Mon Sep 17 00:00:00 2001 From: Leonard Cohnen Date: Thu, 6 Mar 2025 04:58:03 +0100 Subject: [PATCH 2/5] genpolicy: remove unused function Remove function that became unused in the last commit. Signed-off-by: Leonard Cohnen --- src/tools/genpolicy/src/config_map.rs | 10 ---------- 1 file changed, 10 deletions(-) diff --git a/src/tools/genpolicy/src/config_map.rs b/src/tools/genpolicy/src/config_map.rs index 69519bd9d..49d816688 100644 --- a/src/tools/genpolicy/src/config_map.rs +++ b/src/tools/genpolicy/src/config_map.rs @@ -13,10 +13,8 @@ use crate::utils::Config; use crate::yaml; use async_trait::async_trait; -use log::debug; use serde::{Deserialize, Serialize}; use std::collections::BTreeMap; -use std::fs::File; /// See Reference / Kubernetes API / Config and Storage Resources / ConfigMap. #[derive(Clone, Debug, Serialize, Deserialize)] @@ -41,14 +39,6 @@ pub struct ConfigMap { } impl ConfigMap { - pub fn new(file: &str) -> anyhow::Result { - debug!("Reading ConfigMap..."); - let config_map: ConfigMap = serde_yaml::from_reader(File::open(file)?)?; - debug!("\nRead ConfigMap => {:#?}", config_map); - - Ok(config_map) - } - pub fn get_value(&self, value_from: &pod::EnvVarSource) -> Option { if let Some(key_ref) = &value_from.configMapKeyRef { if let Some(name) = &key_ref.name { From 61ee330029b0ff2d0f0c77332630b1a5e277458f Mon Sep 17 00:00:00 2001 From: Leonard Cohnen Date: Thu, 24 Apr 2025 15:36:51 +0200 Subject: [PATCH 3/5] genpolicy: move policy enforcement integration test to separate folder In preparation for adding more types of integration tests, moving the policy enforcements test into a separate folder. Signed-off-by: Leonard Cohnen Signed-off-by: Markus Rudy --- src/tools/genpolicy/tests/{ => policy}/main.rs | 2 +- .../genpolicy/tests/{ => policy}/testdata/copyfile/pod.yaml | 0 .../tests/{ => policy}/testdata/copyfile/testcases.json | 0 .../testdata/createcontainer/generate_name/pod.yaml | 0 .../testdata/createcontainer/generate_name/testcases.json | 0 .../testdata/createcontainer/network_namespace/pod.yaml | 0 .../testdata/createcontainer/network_namespace/testcases.json | 0 .../testdata/createcontainer/security_context/pod.yaml | 0 .../testdata/createcontainer/security_context/testcases.json | 0 .../{ => policy}/testdata/createcontainer/sysctls/pod.yaml | 0 .../testdata/createcontainer/sysctls/testcases.json | 0 .../testdata/createcontainer/volumes/emptydir/pod.yaml | 0 .../testdata/createcontainer/volumes/emptydir/testcases.json | 0 .../tests/{ => policy}/testdata/createsandbox/pod.yaml | 0 .../tests/{ => policy}/testdata/createsandbox/testcases.json | 0 .../tests/{ => policy}/testdata/state/createcontainer/pod.yaml | 0 .../{ => policy}/testdata/state/createcontainer/testcases.json | 0 .../tests/{ => policy}/testdata/state/execprocess/pod.yaml | 0 .../{ => policy}/testdata/state/execprocess/testcases.json | 0 .../tests/{ => policy}/testdata/updateinterface/pod.yaml | 0 .../tests/{ => policy}/testdata/updateinterface/testcases.json | 0 .../genpolicy/tests/{ => policy}/testdata/updateroutes/pod.yaml | 0 .../tests/{ => policy}/testdata/updateroutes/testcases.json | 0 23 files changed, 1 insertion(+), 1 deletion(-) rename src/tools/genpolicy/tests/{ => policy}/main.rs (99%) rename src/tools/genpolicy/tests/{ => policy}/testdata/copyfile/pod.yaml (100%) rename src/tools/genpolicy/tests/{ => policy}/testdata/copyfile/testcases.json (100%) rename src/tools/genpolicy/tests/{ => policy}/testdata/createcontainer/generate_name/pod.yaml (100%) rename src/tools/genpolicy/tests/{ => policy}/testdata/createcontainer/generate_name/testcases.json (100%) rename src/tools/genpolicy/tests/{ => policy}/testdata/createcontainer/network_namespace/pod.yaml (100%) rename src/tools/genpolicy/tests/{ => policy}/testdata/createcontainer/network_namespace/testcases.json (100%) rename src/tools/genpolicy/tests/{ => policy}/testdata/createcontainer/security_context/pod.yaml (100%) rename src/tools/genpolicy/tests/{ => policy}/testdata/createcontainer/security_context/testcases.json (100%) rename src/tools/genpolicy/tests/{ => policy}/testdata/createcontainer/sysctls/pod.yaml (100%) rename src/tools/genpolicy/tests/{ => policy}/testdata/createcontainer/sysctls/testcases.json (100%) rename src/tools/genpolicy/tests/{ => policy}/testdata/createcontainer/volumes/emptydir/pod.yaml (100%) rename src/tools/genpolicy/tests/{ => policy}/testdata/createcontainer/volumes/emptydir/testcases.json (100%) rename src/tools/genpolicy/tests/{ => policy}/testdata/createsandbox/pod.yaml (100%) rename src/tools/genpolicy/tests/{ => policy}/testdata/createsandbox/testcases.json (100%) rename src/tools/genpolicy/tests/{ => policy}/testdata/state/createcontainer/pod.yaml (100%) rename src/tools/genpolicy/tests/{ => policy}/testdata/state/createcontainer/testcases.json (100%) rename src/tools/genpolicy/tests/{ => policy}/testdata/state/execprocess/pod.yaml (100%) rename src/tools/genpolicy/tests/{ => policy}/testdata/state/execprocess/testcases.json (100%) rename src/tools/genpolicy/tests/{ => policy}/testdata/updateinterface/pod.yaml (100%) rename src/tools/genpolicy/tests/{ => policy}/testdata/updateinterface/testcases.json (100%) rename src/tools/genpolicy/tests/{ => policy}/testdata/updateroutes/pod.yaml (100%) rename src/tools/genpolicy/tests/{ => policy}/testdata/updateroutes/testcases.json (100%) diff --git a/src/tools/genpolicy/tests/main.rs b/src/tools/genpolicy/tests/policy/main.rs similarity index 99% rename from src/tools/genpolicy/tests/main.rs rename to src/tools/genpolicy/tests/policy/main.rs index 04f6f32e9..8454ade8e 100644 --- a/src/tools/genpolicy/tests/main.rs +++ b/src/tools/genpolicy/tests/policy/main.rs @@ -68,7 +68,7 @@ mod tests { .expect("should be able to create directories under CARGO_TARGET_TMPDIR"); let testdata_dir = path::PathBuf::from(env!("CARGO_MANIFEST_DIR")) - .join("tests/testdata") + .join("tests/policy/testdata") .join(test_case_dir); fs::copy(testdata_dir.join("pod.yaml"), workdir.join("pod.yaml")) .expect("copying files around should not fail"); diff --git a/src/tools/genpolicy/tests/testdata/copyfile/pod.yaml b/src/tools/genpolicy/tests/policy/testdata/copyfile/pod.yaml similarity index 100% rename from src/tools/genpolicy/tests/testdata/copyfile/pod.yaml rename to src/tools/genpolicy/tests/policy/testdata/copyfile/pod.yaml diff --git a/src/tools/genpolicy/tests/testdata/copyfile/testcases.json b/src/tools/genpolicy/tests/policy/testdata/copyfile/testcases.json similarity index 100% rename from src/tools/genpolicy/tests/testdata/copyfile/testcases.json rename to src/tools/genpolicy/tests/policy/testdata/copyfile/testcases.json diff --git a/src/tools/genpolicy/tests/testdata/createcontainer/generate_name/pod.yaml b/src/tools/genpolicy/tests/policy/testdata/createcontainer/generate_name/pod.yaml similarity index 100% rename from src/tools/genpolicy/tests/testdata/createcontainer/generate_name/pod.yaml rename to src/tools/genpolicy/tests/policy/testdata/createcontainer/generate_name/pod.yaml diff --git a/src/tools/genpolicy/tests/testdata/createcontainer/generate_name/testcases.json b/src/tools/genpolicy/tests/policy/testdata/createcontainer/generate_name/testcases.json similarity index 100% rename from src/tools/genpolicy/tests/testdata/createcontainer/generate_name/testcases.json rename to src/tools/genpolicy/tests/policy/testdata/createcontainer/generate_name/testcases.json diff --git a/src/tools/genpolicy/tests/testdata/createcontainer/network_namespace/pod.yaml b/src/tools/genpolicy/tests/policy/testdata/createcontainer/network_namespace/pod.yaml similarity index 100% rename from src/tools/genpolicy/tests/testdata/createcontainer/network_namespace/pod.yaml rename to src/tools/genpolicy/tests/policy/testdata/createcontainer/network_namespace/pod.yaml diff --git a/src/tools/genpolicy/tests/testdata/createcontainer/network_namespace/testcases.json b/src/tools/genpolicy/tests/policy/testdata/createcontainer/network_namespace/testcases.json similarity index 100% rename from src/tools/genpolicy/tests/testdata/createcontainer/network_namespace/testcases.json rename to src/tools/genpolicy/tests/policy/testdata/createcontainer/network_namespace/testcases.json diff --git a/src/tools/genpolicy/tests/testdata/createcontainer/security_context/pod.yaml b/src/tools/genpolicy/tests/policy/testdata/createcontainer/security_context/pod.yaml similarity index 100% rename from src/tools/genpolicy/tests/testdata/createcontainer/security_context/pod.yaml rename to src/tools/genpolicy/tests/policy/testdata/createcontainer/security_context/pod.yaml diff --git a/src/tools/genpolicy/tests/testdata/createcontainer/security_context/testcases.json b/src/tools/genpolicy/tests/policy/testdata/createcontainer/security_context/testcases.json similarity index 100% rename from src/tools/genpolicy/tests/testdata/createcontainer/security_context/testcases.json rename to src/tools/genpolicy/tests/policy/testdata/createcontainer/security_context/testcases.json diff --git a/src/tools/genpolicy/tests/testdata/createcontainer/sysctls/pod.yaml b/src/tools/genpolicy/tests/policy/testdata/createcontainer/sysctls/pod.yaml similarity index 100% rename from src/tools/genpolicy/tests/testdata/createcontainer/sysctls/pod.yaml rename to src/tools/genpolicy/tests/policy/testdata/createcontainer/sysctls/pod.yaml diff --git a/src/tools/genpolicy/tests/testdata/createcontainer/sysctls/testcases.json b/src/tools/genpolicy/tests/policy/testdata/createcontainer/sysctls/testcases.json similarity index 100% rename from src/tools/genpolicy/tests/testdata/createcontainer/sysctls/testcases.json rename to src/tools/genpolicy/tests/policy/testdata/createcontainer/sysctls/testcases.json diff --git a/src/tools/genpolicy/tests/testdata/createcontainer/volumes/emptydir/pod.yaml b/src/tools/genpolicy/tests/policy/testdata/createcontainer/volumes/emptydir/pod.yaml similarity index 100% rename from src/tools/genpolicy/tests/testdata/createcontainer/volumes/emptydir/pod.yaml rename to src/tools/genpolicy/tests/policy/testdata/createcontainer/volumes/emptydir/pod.yaml diff --git a/src/tools/genpolicy/tests/testdata/createcontainer/volumes/emptydir/testcases.json b/src/tools/genpolicy/tests/policy/testdata/createcontainer/volumes/emptydir/testcases.json similarity index 100% rename from src/tools/genpolicy/tests/testdata/createcontainer/volumes/emptydir/testcases.json rename to src/tools/genpolicy/tests/policy/testdata/createcontainer/volumes/emptydir/testcases.json diff --git a/src/tools/genpolicy/tests/testdata/createsandbox/pod.yaml b/src/tools/genpolicy/tests/policy/testdata/createsandbox/pod.yaml similarity index 100% rename from src/tools/genpolicy/tests/testdata/createsandbox/pod.yaml rename to src/tools/genpolicy/tests/policy/testdata/createsandbox/pod.yaml diff --git a/src/tools/genpolicy/tests/testdata/createsandbox/testcases.json b/src/tools/genpolicy/tests/policy/testdata/createsandbox/testcases.json similarity index 100% rename from src/tools/genpolicy/tests/testdata/createsandbox/testcases.json rename to src/tools/genpolicy/tests/policy/testdata/createsandbox/testcases.json diff --git a/src/tools/genpolicy/tests/testdata/state/createcontainer/pod.yaml b/src/tools/genpolicy/tests/policy/testdata/state/createcontainer/pod.yaml similarity index 100% rename from src/tools/genpolicy/tests/testdata/state/createcontainer/pod.yaml rename to src/tools/genpolicy/tests/policy/testdata/state/createcontainer/pod.yaml diff --git a/src/tools/genpolicy/tests/testdata/state/createcontainer/testcases.json b/src/tools/genpolicy/tests/policy/testdata/state/createcontainer/testcases.json similarity index 100% rename from src/tools/genpolicy/tests/testdata/state/createcontainer/testcases.json rename to src/tools/genpolicy/tests/policy/testdata/state/createcontainer/testcases.json diff --git a/src/tools/genpolicy/tests/testdata/state/execprocess/pod.yaml b/src/tools/genpolicy/tests/policy/testdata/state/execprocess/pod.yaml similarity index 100% rename from src/tools/genpolicy/tests/testdata/state/execprocess/pod.yaml rename to src/tools/genpolicy/tests/policy/testdata/state/execprocess/pod.yaml diff --git a/src/tools/genpolicy/tests/testdata/state/execprocess/testcases.json b/src/tools/genpolicy/tests/policy/testdata/state/execprocess/testcases.json similarity index 100% rename from src/tools/genpolicy/tests/testdata/state/execprocess/testcases.json rename to src/tools/genpolicy/tests/policy/testdata/state/execprocess/testcases.json diff --git a/src/tools/genpolicy/tests/testdata/updateinterface/pod.yaml b/src/tools/genpolicy/tests/policy/testdata/updateinterface/pod.yaml similarity index 100% rename from src/tools/genpolicy/tests/testdata/updateinterface/pod.yaml rename to src/tools/genpolicy/tests/policy/testdata/updateinterface/pod.yaml diff --git a/src/tools/genpolicy/tests/testdata/updateinterface/testcases.json b/src/tools/genpolicy/tests/policy/testdata/updateinterface/testcases.json similarity index 100% rename from src/tools/genpolicy/tests/testdata/updateinterface/testcases.json rename to src/tools/genpolicy/tests/policy/testdata/updateinterface/testcases.json diff --git a/src/tools/genpolicy/tests/testdata/updateroutes/pod.yaml b/src/tools/genpolicy/tests/policy/testdata/updateroutes/pod.yaml similarity index 100% rename from src/tools/genpolicy/tests/testdata/updateroutes/pod.yaml rename to src/tools/genpolicy/tests/policy/testdata/updateroutes/pod.yaml diff --git a/src/tools/genpolicy/tests/testdata/updateroutes/testcases.json b/src/tools/genpolicy/tests/policy/testdata/updateroutes/testcases.json similarity index 100% rename from src/tools/genpolicy/tests/testdata/updateroutes/testcases.json rename to src/tools/genpolicy/tests/policy/testdata/updateroutes/testcases.json From bad0cd0003e7f9bb1db2649a6f05f49236ad4fea Mon Sep 17 00:00:00 2001 From: Leonard Cohnen Date: Thu, 6 Mar 2025 05:16:54 +0100 Subject: [PATCH 4/5] genpolicy: add cli integration tests Add a new type of integration test to genpolicy. Now we can test flag handling and how the CLI behaves with certain yaml inputs. The first tests cover the case when a Pod references a Kubernetes secret of config map in another file. Those need to be explicitly added via the --config-files flag. In the future we can easily add test suites that cover that all yaml fields of all resources are understood by genpolicy. Signed-off-by: Leonard Cohnen --- src/tools/genpolicy/Cargo.lock | 88 +++++++++++++++++ src/tools/genpolicy/Cargo.toml | 7 +- src/tools/genpolicy/tests/generate/main.rs | 99 +++++++++++++++++++ .../tests/generate/testdata/config_map.yaml | 75 ++++++++++++++ .../testdata/pod_with_config_map_ref.yaml | 60 +++++++++++ .../testdata/pod_with_secret_ref.yaml | 60 +++++++++++ .../tests/generate/testdata/secret.yaml | 73 ++++++++++++++ 7 files changed, 460 insertions(+), 2 deletions(-) create mode 100644 src/tools/genpolicy/tests/generate/main.rs create mode 100644 src/tools/genpolicy/tests/generate/testdata/config_map.yaml create mode 100644 src/tools/genpolicy/tests/generate/testdata/pod_with_config_map_ref.yaml create mode 100644 src/tools/genpolicy/tests/generate/testdata/pod_with_secret_ref.yaml create mode 100644 src/tools/genpolicy/tests/generate/testdata/secret.yaml diff --git a/src/tools/genpolicy/Cargo.lock b/src/tools/genpolicy/Cargo.lock index 5849bfb15..0f7540cc4 100644 --- a/src/tools/genpolicy/Cargo.lock +++ b/src/tools/genpolicy/Cargo.lock @@ -41,6 +41,12 @@ dependencies = [ "libc", ] +[[package]] +name = "anstyle" +version = "1.0.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "55cc3b69f167a1ef2e161439aa98aed94e6028e5f9a59be9a6ffb47aef1651f9" + [[package]] name = "anyhow" version = "1.0.69" @@ -53,6 +59,22 @@ version = "1.7.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "69f7f8c3906b62b754cd5326047894316021dcfe5a194c8ea52bdd94934a3457" +[[package]] +name = "assert_cmd" +version = "2.0.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2bd389a4b2970a01282ee455294913c0a43724daedcd1a24c3eb0ec1c1320b66" +dependencies = [ + "anstyle", + "bstr", + "doc-comment", + "libc", + "predicates", + "predicates-core", + "predicates-tree", + "wait-timeout", +] + [[package]] name = "async-trait" version = "0.1.68" @@ -169,6 +191,17 @@ dependencies = [ "generic-array", ] +[[package]] +name = "bstr" +version = "1.12.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "234113d19d0d7d613b40e86fb654acf958910802bcceab913a4f9e7cda03b1a4" +dependencies = [ + "memchr", + "regex-automata", + "serde", +] + [[package]] name = "bumpalo" version = "3.12.0" @@ -451,6 +484,12 @@ dependencies = [ "syn 2.0.58", ] +[[package]] +name = "difflib" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6184e33543162437515c2e2b48714794e37845ec9851711914eec9d308f6ebe8" + [[package]] name = "digest" version = "0.10.6" @@ -462,6 +501,12 @@ dependencies = [ "subtle", ] +[[package]] +name = "doc-comment" +version = "0.3.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fea41bba32d969b513997752735605054bc0dfa92b4c56bf1189f2e174be7a10" + [[package]] name = "docker_credential" version = "1.3.1" @@ -699,6 +744,7 @@ name = "genpolicy" version = "0.1.0" dependencies = [ "anyhow", + "assert_cmd", "async-trait", "base64 0.21.7", "clap", @@ -1580,6 +1626,33 @@ version = "0.2.17" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5b40af805b3121feab8a3c29f04d8ad262fa8e0561883e7653e024ae4479e6de" +[[package]] +name = "predicates" +version = "3.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a5d19ee57562043d37e82899fade9a22ebab7be9cef5026b07fda9cdd4293573" +dependencies = [ + "anstyle", + "difflib", + "predicates-core", +] + +[[package]] +name = "predicates-core" +version = "1.0.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "727e462b119fe9c93fd0eb1429a5f7647394014cf3c04ab2c0350eeb09095ffa" + +[[package]] +name = "predicates-tree" +version = "1.0.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "72dd2d6d381dfb73a193c7fca536518d7caee39fc8503f74e7dc0be0531b425c" +dependencies = [ + "predicates-core", + "termtree", +] + [[package]] name = "prettyplease" version = "0.1.25" @@ -2351,6 +2424,12 @@ dependencies = [ "winapi-util", ] +[[package]] +name = "termtree" +version = "0.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8f50febec83f5ee1df3015341d8bd429f2d1cc62bcba7ea2076759d315084683" + [[package]] name = "thiserror" version = "1.0.40" @@ -2695,6 +2774,15 @@ version = "0.9.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "49874b5167b65d7193b8aba1567f5c7d93d001cafc34600cee003eda787e483f" +[[package]] +name = "wait-timeout" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "09ac3b126d3914f9849036f826e054cbabdc8519970b8998ddaf3b5bd3c65f11" +dependencies = [ + "libc", +] + [[package]] name = "want" version = "0.3.0" diff --git a/src/tools/genpolicy/Cargo.toml b/src/tools/genpolicy/Cargo.toml index 17bfc29d9..04685b8fe 100644 --- a/src/tools/genpolicy/Cargo.toml +++ b/src/tools/genpolicy/Cargo.toml @@ -40,14 +40,16 @@ serde_yaml = "0.8" anyhow = "1.0.32" async-trait = "0.1.68" docker_credential = "1.3.1" -flate2 = { version = "1.0.26", features = ["zlib-ng"], default-features = false } +flate2 = { version = "1.0.26", features = [ + "zlib-ng", +], default-features = false } libz-ng-sys = "1.1.15" # force newer version that compiles on ppc64le oci-client = { version = "0.12.0" } openssl = { version = "0.10.72", features = ["vendored"] } serde_ignored = "0.1.7" serde_json = "1.0.39" serde-transcode = "1.1.1" -tokio = {version = "1.38.0", features = ["rt-multi-thread"]} +tokio = { version = "1.38.0", features = ["rt-multi-thread"] } # OCI container specs. oci-spec = { version = "0.6.8", features = ["runtime"] } @@ -76,3 +78,4 @@ tar = "0.4.41" [dev-dependencies] kata-agent-policy = { path = "../../agent/policy" } slog = "2.5.2" +assert_cmd = "2.0.14" diff --git a/src/tools/genpolicy/tests/generate/main.rs b/src/tools/genpolicy/tests/generate/main.rs new file mode 100644 index 000000000..f228eb9a0 --- /dev/null +++ b/src/tools/genpolicy/tests/generate/main.rs @@ -0,0 +1,99 @@ +// Copyright (c) 2025 Edgeless Systems GmbH +// +// SPDX-License-Identifier: Apache-2.0 +// + +use assert_cmd::prelude::*; +use std::fs::{self}; +use std::path; +use std::process::Command; + +#[test] +fn config_map_in_separate_file_config_map_flag() -> Result<(), Box> { + // Prepare temp dir for running genpolicy. + let test_case_dir = "config_map_separate_file_config_map_flag"; + let pod_yaml_name = "pod_with_config_map_ref.yaml"; + let config_file = "config_map.yaml"; + let workdir = prepare_workdir(test_case_dir, &[pod_yaml_name, config_file]); + + let mut cmd = Command::cargo_bin("genpolicy")?; + cmd.arg("--yaml-file").arg(workdir.join(pod_yaml_name)); + cmd.assert().failure(); + + let mut cmd = Command::cargo_bin("genpolicy")?; + cmd.arg("--yaml-file").arg(workdir.join(pod_yaml_name)); + cmd.arg("--config-map-file").arg(workdir.join(config_file)); + cmd.assert().success(); + + Ok(()) +} + +#[test] +fn config_map_in_separate_file_workdir_flag() -> Result<(), Box> { + // Prepare temp dir for running genpolicy. + let test_case_dir = "config_map_separate_file_workdir_flag"; + let pod_yaml_name = "pod_with_config_map_ref.yaml"; + let config_file = "config_map.yaml"; + let workdir = prepare_workdir(test_case_dir, &[pod_yaml_name, config_file]); + + let mut cmd = Command::cargo_bin("genpolicy")?; + cmd.arg("--yaml-file").arg(workdir.join(pod_yaml_name)); + cmd.assert().failure(); + + let mut cmd = Command::cargo_bin("genpolicy")?; + cmd.arg("--yaml-file").arg(workdir.join(pod_yaml_name)); + cmd.arg("--config-file").arg(workdir.join(config_file)); + cmd.assert().success(); + + Ok(()) +} + +#[test] +fn secret_in_separate_file() -> Result<(), Box> { + // Prepare temp dir for running genpolicy. + let test_case_dir = "secret_separate_file"; + let pod_yaml_name = "pod_with_secret_ref.yaml"; + let config_file = "secret.yaml"; + let workdir = prepare_workdir(test_case_dir, &[pod_yaml_name, config_file]); + + let mut cmd = Command::cargo_bin("genpolicy")?; + cmd.arg("--yaml-file").arg(workdir.join(pod_yaml_name)); + cmd.assert().failure(); + + let mut cmd = Command::cargo_bin("genpolicy")?; + cmd.arg("--yaml-file").arg(workdir.join(pod_yaml_name)); + cmd.arg("--config-file").arg(workdir.join(config_file)); + cmd.assert().success(); + + Ok(()) +} + +fn prepare_workdir(test_case_dir: &str, files_to_copy: &[&str]) -> path::PathBuf { + // Prepare temp dir for running genpolicy. + let workdir = path::PathBuf::from(env!("CARGO_TARGET_TMPDIR")).join(test_case_dir); + fs::create_dir_all(&workdir) + .expect("should be able to create directories under CARGO_TARGET_TMPDIR"); + + let testdata_dir = + path::PathBuf::from(env!("CARGO_MANIFEST_DIR")).join("tests/generate/testdata"); + + // Make sure that workdir is empty. + for entry in fs::read_dir(&workdir).expect("should be able to read directories") { + let entry = entry.expect("should be able to read directory entries"); + fs::remove_file(entry.path()).expect("should be able to remove files"); + } + + for file in files_to_copy { + fs::copy(testdata_dir.join(file), workdir.join(file)) + .expect("copying files around should not fail"); + } + + let genpolicy_dir = path::PathBuf::from(env!("CARGO_MANIFEST_DIR")); + + for base in ["rules.rego", "genpolicy-settings.json"] { + fs::copy(genpolicy_dir.join(base), workdir.join(base)) + .expect("copying files around should not fail"); + } + + workdir +} diff --git a/src/tools/genpolicy/tests/generate/testdata/config_map.yaml b/src/tools/genpolicy/tests/generate/testdata/config_map.yaml new file mode 100644 index 000000000..c42f67b73 --- /dev/null +++ b/src/tools/genpolicy/tests/generate/testdata/config_map.yaml @@ -0,0 +1,75 @@ +apiVersion: v1 +data: + key: YmFyCg== +kind: ConfigMap +metadata: + name: configmap-sample +--- +apiVersion: v1 +data: + key: YmFyCg== +kind: ConfigMap +metadata: + name: configmap-sample2 +--- +# Add some other yaml document to make sure that we can parse out +# ConfigMaps and Secrets from yaml files with multiple documents. +apiVersion: v1 +kind: Pod +metadata: + name: one-container-config-map + labels: + run: busybox +spec: + restartPolicy: Never + runtimeClassName: kata-cc + containers: + - name: busybox + image: "quay.io/prometheus/busybox:latest" + stdin: true + env: + - name: ENV_FROM_CONFIGMAP + valueFrom: + configMapKeyRef: + key: key + name: configmap-sample + - name: POD_NAME + valueFrom: + fieldRef: + apiVersion: v1 + fieldPath: metadata.name + - name: POD_NAMESPACE + valueFrom: + fieldRef: + apiVersion: v1 + fieldPath: metadata.namespace + - name: POD_IP + valueFrom: + fieldRef: + apiVersion: v1 + fieldPath: status.podIP + - name: SERVICE_ACCOUNT + valueFrom: + fieldRef: + apiVersion: v1 + fieldPath: spec.serviceAccountName + - name: PROXY_CONFIG + value: "{}\n" + - name: ISTIO_META_POD_PORTS + value: "[\n]" + - name: ISTIO_META_APP_CONTAINERS + value: serviceaclient + - name: ISTIO_META_CLUSTER_ID + value: Kubernetes + - name: ISTIO_META_NODE_NAME + valueFrom: + fieldRef: + apiVersion: v1 + fieldPath: spec.nodeName + securityContext: + privileged: true + command: + - /bin/sh + args: + - "-c" + - while true; do echo $(POD_NAME); sleep 10; done diff --git a/src/tools/genpolicy/tests/generate/testdata/pod_with_config_map_ref.yaml b/src/tools/genpolicy/tests/generate/testdata/pod_with_config_map_ref.yaml new file mode 100644 index 000000000..76a39f4e3 --- /dev/null +++ b/src/tools/genpolicy/tests/generate/testdata/pod_with_config_map_ref.yaml @@ -0,0 +1,60 @@ +--- +apiVersion: v1 +kind: Pod +metadata: + name: one-container + labels: + run: busybox +spec: + restartPolicy: Never + runtimeClassName: kata-cc + containers: + - name: busybox + image: "quay.io/prometheus/busybox:latest" + stdin: true + env: + - name: ENV_FROM_CONFIGMAP + valueFrom: + configMapKeyRef: + key: key + name: configmap-sample + - name: POD_NAME + valueFrom: + fieldRef: + apiVersion: v1 + fieldPath: metadata.name + - name: POD_NAMESPACE + valueFrom: + fieldRef: + apiVersion: v1 + fieldPath: metadata.namespace + - name: POD_IP + valueFrom: + fieldRef: + apiVersion: v1 + fieldPath: status.podIP + - name: SERVICE_ACCOUNT + valueFrom: + fieldRef: + apiVersion: v1 + fieldPath: spec.serviceAccountName + - name: PROXY_CONFIG + value: "{}\n" + - name: ISTIO_META_POD_PORTS + value: "[\n]" + - name: ISTIO_META_APP_CONTAINERS + value: serviceaclient + - name: ISTIO_META_CLUSTER_ID + value: Kubernetes + - name: ISTIO_META_NODE_NAME + valueFrom: + fieldRef: + apiVersion: v1 + fieldPath: spec.nodeName + securityContext: + privileged: true + command: + - /bin/sh + args: + - "-c" + - while true; do echo $(POD_NAME); sleep 10; done diff --git a/src/tools/genpolicy/tests/generate/testdata/pod_with_secret_ref.yaml b/src/tools/genpolicy/tests/generate/testdata/pod_with_secret_ref.yaml new file mode 100644 index 000000000..062da520c --- /dev/null +++ b/src/tools/genpolicy/tests/generate/testdata/pod_with_secret_ref.yaml @@ -0,0 +1,60 @@ +--- +apiVersion: v1 +kind: Pod +metadata: + name: one-container + labels: + run: busybox +spec: + restartPolicy: Never + runtimeClassName: kata-cc + containers: + - name: busybox + image: "quay.io/prometheus/busybox:latest" + stdin: true + env: + - name: ENV_FROM_SECRET + valueFrom: + secretKeyRef: + key: key + name: secret-sample + - name: POD_NAME + valueFrom: + fieldRef: + apiVersion: v1 + fieldPath: metadata.name + - name: POD_NAMESPACE + valueFrom: + fieldRef: + apiVersion: v1 + fieldPath: metadata.namespace + - name: POD_IP + valueFrom: + fieldRef: + apiVersion: v1 + fieldPath: status.podIP + - name: SERVICE_ACCOUNT + valueFrom: + fieldRef: + apiVersion: v1 + fieldPath: spec.serviceAccountName + - name: PROXY_CONFIG + value: "{}\n" + - name: ISTIO_META_POD_PORTS + value: "[\n]" + - name: ISTIO_META_APP_CONTAINERS + value: serviceaclient + - name: ISTIO_META_CLUSTER_ID + value: Kubernetes + - name: ISTIO_META_NODE_NAME + valueFrom: + fieldRef: + apiVersion: v1 + fieldPath: spec.nodeName + securityContext: + privileged: true + command: + - /bin/sh + args: + - "-c" + - while true; do echo $(POD_NAME); sleep 10; done diff --git a/src/tools/genpolicy/tests/generate/testdata/secret.yaml b/src/tools/genpolicy/tests/generate/testdata/secret.yaml new file mode 100644 index 000000000..9d8c6c696 --- /dev/null +++ b/src/tools/genpolicy/tests/generate/testdata/secret.yaml @@ -0,0 +1,73 @@ +apiVersion: v1 +data: + key: YmFyCg== +kind: Secret +metadata: + name: secret-sample +--- +apiVersion: v1 +data: + key: YmFyCg== +kind: ConfigMap +metadata: + name: configmap-sample3 +--- +apiVersion: v1 +kind: Pod +metadata: + name: one-container-secret + labels: + run: busybox +spec: + restartPolicy: Never + runtimeClassName: kata-cc + containers: + - name: busybox + image: "quay.io/prometheus/busybox:latest" + stdin: true + env: + - name: ENV_FROM_CONFIGMAP + valueFrom: + configMapKeyRef: + key: key + name: configmap-sample + - name: POD_NAME + valueFrom: + fieldRef: + apiVersion: v1 + fieldPath: metadata.name + - name: POD_NAMESPACE + valueFrom: + fieldRef: + apiVersion: v1 + fieldPath: metadata.namespace + - name: POD_IP + valueFrom: + fieldRef: + apiVersion: v1 + fieldPath: status.podIP + - name: SERVICE_ACCOUNT + valueFrom: + fieldRef: + apiVersion: v1 + fieldPath: spec.serviceAccountName + - name: PROXY_CONFIG + value: "{}\n" + - name: ISTIO_META_POD_PORTS + value: "[\n]" + - name: ISTIO_META_APP_CONTAINERS + value: serviceaclient + - name: ISTIO_META_CLUSTER_ID + value: Kubernetes + - name: ISTIO_META_NODE_NAME + valueFrom: + fieldRef: + apiVersion: v1 + fieldPath: spec.nodeName + securityContext: + privileged: true + command: + - /bin/sh + args: + - "-c" + - while true; do echo $(POD_NAME); sleep 10; done From b23ff6fc68e66881e371b50dce587753302cdca8 Mon Sep 17 00:00:00 2001 From: Leonard Cohnen Date: Wed, 12 Mar 2025 20:11:22 +0100 Subject: [PATCH 5/5] genpolicy: refactor policy test workdir setup This aligns the workdir preparation more closely with the workdir preparation for the generate integration test. Most notably, we clean up the temporary directory before we execute the tests in it. This way we better isolate different runs. Signed-off-by: Leonard Cohnen Signed-off-by: Markus Rudy --- src/tools/genpolicy/tests/policy/main.rs | 63 +++++++++++++++++------- 1 file changed, 46 insertions(+), 17 deletions(-) diff --git a/src/tools/genpolicy/tests/policy/main.rs b/src/tools/genpolicy/tests/policy/main.rs index 8454ade8e..be89899bb 100644 --- a/src/tools/genpolicy/tests/policy/main.rs +++ b/src/tools/genpolicy/tests/policy/main.rs @@ -5,6 +5,7 @@ #[cfg(test)] mod tests { + use anyhow::Context; use base64::prelude::*; use std::fmt::{self, Display}; use std::fs::{self, File}; @@ -63,23 +64,7 @@ mod tests { /// with the tag `type` listing the exact type of request. async fn runtests(test_case_dir: &str) { // Prepare temp dir for running genpolicy. - let workdir = path::PathBuf::from(env!("CARGO_TARGET_TMPDIR")).join(test_case_dir); - fs::create_dir_all(&workdir) - .expect("should be able to create directories under CARGO_TARGET_TMPDIR"); - - let testdata_dir = path::PathBuf::from(env!("CARGO_MANIFEST_DIR")) - .join("tests/policy/testdata") - .join(test_case_dir); - fs::copy(testdata_dir.join("pod.yaml"), workdir.join("pod.yaml")) - .expect("copying files around should not fail"); - - let genpolicy_dir = - path::PathBuf::from(env!("CARGO_MANIFEST_DIR")).join("../../tools/genpolicy"); - - for base in ["rules.rego", "genpolicy-settings.json"] { - fs::copy(genpolicy_dir.join(base), workdir.join(base)) - .expect("copying files around should not fail"); - } + let (workdir, testdata_dir) = prepare_workdir(test_case_dir, &["pod.yaml"]); // Run the command and return the generated policy. @@ -153,6 +138,50 @@ mod tests { } } + fn prepare_workdir( + test_case_dir: &str, + files_to_copy: &[&str], + ) -> (path::PathBuf, path::PathBuf) { + // Prepare temp dir for running genpolicy. + let workdir = path::PathBuf::from(env!("CARGO_TARGET_TMPDIR")).join(test_case_dir); + fs::create_dir_all(&workdir) + .expect("should be able to create directories under CARGO_TARGET_TMPDIR"); + + let testdata_dir = path::PathBuf::from(env!("CARGO_MANIFEST_DIR")) + .join("tests/policy/testdata") + .join(test_case_dir); + + // Make sure that workdir is empty. + for entry in fs::read_dir(&workdir).expect("should be able to read directories") { + let entry = entry.expect("should be able to read directory entries"); + fs::remove_file(entry.path()).expect("should be able to remove files"); + } + + for file in files_to_copy { + fs::copy(testdata_dir.join(file), workdir.join(file)) + .context(format!( + "{:?} --> {:?}", + testdata_dir.join(file), + workdir.join(file) + )) + .expect("copying files around should not fail"); + } + + let genpolicy_dir = path::PathBuf::from(env!("CARGO_MANIFEST_DIR")); + + for base in ["rules.rego", "genpolicy-settings.json"] { + fs::copy(genpolicy_dir.join(base), workdir.join(base)) + .context(format!( + "{:?} --> {:?}", + genpolicy_dir.join(base), + workdir.join(base) + )) + .expect("copying files around should not fail"); + } + + (workdir, testdata_dir) + } + #[tokio::test] async fn test_copyfile() { runtests("copyfile").await;