From bad0cd0003e7f9bb1db2649a6f05f49236ad4fea Mon Sep 17 00:00:00 2001 From: Leonard Cohnen Date: Thu, 6 Mar 2025 05:16:54 +0100 Subject: [PATCH] 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 5849bfb15e..0f7540cc42 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 17bfc29d94..04685b8fe6 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 0000000000..f228eb9a0d --- /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 0000000000..c42f67b736 --- /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 0000000000..76a39f4e3d --- /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 0000000000..062da520c0 --- /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 0000000000..9d8c6c696f --- /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