diff --git a/Cargo.lock b/Cargo.lock index e909621..5990566 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2,6 +2,15 @@ # It is not intended for manual editing. version = 3 +[[package]] +name = "aho-corasick" +version = "1.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b2969dcb958b36655471fc61f7e416fa76033bdd4bfed0678d8fee1e2d07a1f0" +dependencies = [ + "memchr", +] + [[package]] name = "anyhow" version = "1.0.75" @@ -63,6 +72,107 @@ dependencies = [ "percent-encoding", ] +[[package]] +name = "futures" +version = "0.3.29" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "da0290714b38af9b4a7b094b8a37086d1b4e61f2df9122c3cad2577669145335" +dependencies = [ + "futures-channel", + "futures-core", + "futures-executor", + "futures-io", + "futures-sink", + "futures-task", + "futures-util", +] + +[[package]] +name = "futures-channel" +version = "0.3.29" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ff4dd66668b557604244583e3e1e1eada8c5c2e96a6d0d6653ede395b78bbacb" +dependencies = [ + "futures-core", + "futures-sink", +] + +[[package]] +name = "futures-core" +version = "0.3.29" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "eb1d22c66e66d9d72e1758f0bd7d4fd0bee04cad842ee34587d68c07e45d088c" + +[[package]] +name = "futures-executor" +version = "0.3.29" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0f4fb8693db0cf099eadcca0efe2a5a22e4550f98ed16aba6c48700da29597bc" +dependencies = [ + "futures-core", + "futures-task", + "futures-util", +] + +[[package]] +name = "futures-io" +version = "0.3.29" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8bf34a163b5c4c52d0478a4d757da8fb65cabef42ba90515efee0f6f9fa45aaa" + +[[package]] +name = "futures-macro" +version = "0.3.29" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "53b153fd91e4b0147f4aced87be237c98248656bb01050b96bf3ee89220a8ddb" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "futures-sink" +version = "0.3.29" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e36d3378ee38c2a36ad710c5d30c2911d752cb941c00c72dbabfb786a7970817" + +[[package]] +name = "futures-task" +version = "0.3.29" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "efd193069b0ddadc69c46389b740bbccdd97203899b48d09c5f7969591d6bae2" + +[[package]] +name = "futures-timer" +version = "3.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e64b03909df88034c26dc1547e8970b91f98bdb65165d6a4e9110d94263dbb2c" + +[[package]] +name = "futures-util" +version = "0.3.29" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a19526d624e703a3179b3d322efec918b6246ea0fa51d41124525f00f1cc8104" +dependencies = [ + "futures-channel", + "futures-core", + "futures-io", + "futures-macro", + "futures-sink", + "futures-task", + "memchr", + "pin-project-lite", + "pin-utils", + "slab", +] + +[[package]] +name = "glob" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d2fabcfbdc87f4758337ca535fb41a6d701b65693ce38287d856d1674551ec9b" + [[package]] name = "hashbrown" version = "0.14.0" @@ -152,6 +262,12 @@ dependencies = [ "scopeguard", ] +[[package]] +name = "memchr" +version = "2.6.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f665ee40bc4a3c5590afb1e9677db74a508659dfd71e126420da8274909a0167" + [[package]] name = "num" version = "0.4.1" @@ -283,6 +399,18 @@ version = "2.3.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e3148f5046208a5d56bcfc03053e3ca6334e51da8dfb19b6cdc8b306fae3283e" +[[package]] +name = "pin-project-lite" +version = "0.2.13" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8afb450f006bf6385ca15ef45d71d2288452bc3683ce2e2cacc0d18e4be60b58" + +[[package]] +name = "pin-utils" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8b870d8c151b6f2fb93e84a13146138f05d02ed11c7e7c54f8826aaaf7c9f184" + [[package]] name = "pod-privileged-policy" version = "0.2.7" @@ -291,6 +419,7 @@ dependencies = [ "k8s-openapi", "kubewarden-policy-sdk", "lazy_static", + "rstest", "serde", "serde_json", "slog", @@ -323,6 +452,79 @@ dependencies = [ "bitflags", ] +[[package]] +name = "regex" +version = "1.10.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "380b951a9c5e80ddfd6136919eef32310721aa4aacd4889a8d39124b026ab343" +dependencies = [ + "aho-corasick", + "memchr", + "regex-automata", + "regex-syntax", +] + +[[package]] +name = "regex-automata" +version = "0.4.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5f804c7828047e88b2d32e2d7fe5a105da8ee3264f01902f796c8e067dc2483f" +dependencies = [ + "aho-corasick", + "memchr", + "regex-syntax", +] + +[[package]] +name = "regex-syntax" +version = "0.8.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c08c74e62047bb2de4ff487b251e4a92e24f48745648451635cec7d591162d9f" + +[[package]] +name = "relative-path" +version = "1.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c707298afce11da2efef2f600116fa93ffa7a032b5d7b628aa17711ec81383ca" + +[[package]] +name = "rstest" +version = "0.18.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "97eeab2f3c0a199bc4be135c36c924b6590b88c377d416494288c14f2db30199" +dependencies = [ + "futures", + "futures-timer", + "rstest_macros", + "rustc_version", +] + +[[package]] +name = "rstest_macros" +version = "0.18.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d428f8247852f894ee1be110b375111b586d4fa431f6c46e64ba5a0dcccbe605" +dependencies = [ + "cfg-if", + "glob", + "proc-macro2", + "quote", + "regex", + "relative-path", + "rustc_version", + "syn", + "unicode-ident", +] + +[[package]] +name = "rustc_version" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bfa0f585226d2e68097d4f95d113b15b83a82e819ab25717ec0590d9584ef366" +dependencies = [ + "semver", +] + [[package]] name = "ryu" version = "1.0.15" @@ -335,6 +537,12 @@ version = "1.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "94143f37725109f92c262ed2cf5e59bce7498c01bcc1502d7b9afe439a4e9f49" +[[package]] +name = "semver" +version = "1.0.20" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "836fa6a3e1e547f9a2c4040802ec865b5d85f4014efe00555d7090a3dcaa1090" + [[package]] name = "serde" version = "1.0.193" @@ -389,6 +597,15 @@ dependencies = [ "unsafe-libyaml", ] +[[package]] +name = "slab" +version = "0.4.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8f92a496fb766b417c996b9c5e57daf2f7ad3b0bebe1ccfca4856390e3d3bb67" +dependencies = [ + "autocfg", +] + [[package]] name = "slog" version = "2.7.0" diff --git a/Cargo.toml b/Cargo.toml index ce6a6b3..9a3ccb5 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -19,3 +19,4 @@ lazy_static = "1.4" serde = { version = "1.0", features = ["derive"] } serde_json = "1.0" slog = "2.7" +rstest = "0.18.2" diff --git a/e2e.bats b/e2e.bats index 861dc8b..f588382 100644 --- a/e2e.bats +++ b/e2e.bats @@ -46,4 +46,18 @@ [ "$status" -eq 0 ] [ $(expr "$output" : '.*allowed.*false') -ne 0 ] [ $(expr "$output" : '.*Privileged container is not allowed*') -ne 0 ] -} \ No newline at end of file +} + +@test "accept privileged init container when required" { + run kwctl run annotated-policy.wasm -r test_data/privileged_init_container.json --settings-path test_data/settings_skip_init_and_ephemeral_containers.json + # request accepted + [ "$status" -eq 0 ] + [ $(expr "$output" : '.*allowed.*true') -ne 0 ] +} + +@test "accept privileged ephemeral container when required" { + run kwctl run annotated-policy.wasm -r test_data/privileged_ephemeral_container.json --settings-path test_data/settings_skip_init_and_ephemeral_containers.json + # request accepted + [ "$status" -eq 0 ] + [ $(expr "$output" : '.*allowed.*true') -ne 0 ] +} diff --git a/src/lib.rs b/src/lib.rs index 17f647a..8106af5 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -100,522 +100,158 @@ fn validate_container(container: &apicore::Container) -> bool { #[cfg(test)] mod tests { use super::*; - - #[test] - fn accept_pod_when_all_ephemeral_containers_are_not_privileged_test() -> Result<()> { - let result = validate_pod( - &apicore::PodSpec { - ephemeral_containers: Some(vec![ - apicore::EphemeralContainer { - security_context: Some(apicore::SecurityContext { - privileged: Some(false), - ..apicore::SecurityContext::default() - }), - ..apicore::EphemeralContainer::default() - }, - apicore::EphemeralContainer { - security_context: Some(apicore::SecurityContext { - privileged: Some(false), - ..apicore::SecurityContext::default() - }), - ..apicore::EphemeralContainer::default() - }, - apicore::EphemeralContainer { - security_context: Some(apicore::SecurityContext { - privileged: Some(false), - ..apicore::SecurityContext::default() - }), - ..apicore::EphemeralContainer::default() - }, - ]), - ..apicore::PodSpec::default() - }, - &Settings::default(), - ); - assert!( - result.is_ok(), - "Pod with no privileged ephemeral container should be accepted by the validator" - ); - Ok(()) - } - - #[test] - fn reject_pod_when_all_ephemeral_container_is_privileged_test() -> Result<()> { - let result = validate_pod( - &apicore::PodSpec { - ephemeral_containers: Some(vec![ - apicore::EphemeralContainer { - security_context: Some(apicore::SecurityContext { - privileged: Some(true), - ..apicore::SecurityContext::default() - }), - ..apicore::EphemeralContainer::default() - }, - apicore::EphemeralContainer { - security_context: Some(apicore::SecurityContext { - privileged: Some(true), - ..apicore::SecurityContext::default() - }), - ..apicore::EphemeralContainer::default() - }, - apicore::EphemeralContainer { - security_context: Some(apicore::SecurityContext { - privileged: Some(true), - ..apicore::SecurityContext::default() - }), - ..apicore::EphemeralContainer::default() - }, - ]), - ..apicore::PodSpec::default() - }, - &Settings::default(), - ); - assert!( - result.is_err(), - "Pod with all privileged ephemeral container should be rejected by the validator" - ); - Ok(()) - } - - #[test] - fn reject_pod_when_one_ephemeral_container_is_privileged_test() -> Result<()> { - let result = validate_pod( - &apicore::PodSpec { - ephemeral_containers: Some(vec![ - apicore::EphemeralContainer { - security_context: Some(apicore::SecurityContext { - privileged: Some(false), - ..apicore::SecurityContext::default() - }), - ..apicore::EphemeralContainer::default() - }, - apicore::EphemeralContainer { - security_context: Some(apicore::SecurityContext { - privileged: Some(false), - ..apicore::SecurityContext::default() - }), - ..apicore::EphemeralContainer::default() - }, - apicore::EphemeralContainer { - security_context: Some(apicore::SecurityContext { - privileged: Some(true), - ..apicore::SecurityContext::default() - }), - ..apicore::EphemeralContainer::default() - }, - ]), - ..apicore::PodSpec::default() - }, - &Settings::default(), - ); - assert!(result.is_err(), - "Pod with only a single privileged ephemeral container should be rejected by the validator" - ); - Ok(()) - } - - #[test] - fn accept_pod_when_init_containers_are_not_privileged_test() -> Result<()> { - let result = validate_pod( - &apicore::PodSpec { - init_containers: Some(vec![ - apicore::Container { - security_context: Some(apicore::SecurityContext { - privileged: Some(false), - ..apicore::SecurityContext::default() - }), - ..apicore::Container::default() - }, - apicore::Container { - security_context: Some(apicore::SecurityContext { - privileged: Some(false), - ..apicore::SecurityContext::default() - }), - ..apicore::Container::default() - }, - apicore::Container { - security_context: Some(apicore::SecurityContext { - privileged: Some(false), - ..apicore::SecurityContext::default() - }), - ..apicore::Container::default() - }, - ]), - ..apicore::PodSpec::default() - }, - &Settings::default(), - ); - assert!( - result.is_ok(), - "Pod with no privileged init container should be accepted by the validator" - ); - Ok(()) - } - - #[test] - fn reject_pod_when_one_init_container_is_privileged_test() -> Result<()> { - let result = validate_pod( - &apicore::PodSpec { - init_containers: Some(vec![ - apicore::Container { - security_context: Some(apicore::SecurityContext { - privileged: Some(false), - ..apicore::SecurityContext::default() - }), - ..apicore::Container::default() - }, - apicore::Container { - security_context: Some(apicore::SecurityContext { - privileged: Some(false), - ..apicore::SecurityContext::default() - }), - ..apicore::Container::default() - }, - apicore::Container { - security_context: Some(apicore::SecurityContext { - privileged: Some(true), - ..apicore::SecurityContext::default() - }), - ..apicore::Container::default() - }, - ]), - ..apicore::PodSpec::default() - }, - &Settings::default(), - ); - assert!( - result.is_err(), - "Pod with only a single privileged init container should be rejected by the validator" - ); - Ok(()) - } - - #[test] - fn accept_pod_when_containers_are_privileged_and_policy_should_ignore_test() { - let result = validate_pod( - &apicore::PodSpec { - containers: vec![apicore::Container { + use rstest::rstest; + + fn pod_factory( + containers_privileged: Option>, + init_container_privileged: Option>, + ephemeral_container_privileged: Option>, + ) -> apicore::PodSpec { + let mut containers: Vec = Vec::new(); + let mut init_containers: Option> = None; + let mut ephemeral_containers: Option> = None; + + if let Some(containers_values) = containers_privileged { + containers = containers_values + .into_iter() + .map(|privileged| apicore::Container { security_context: Some(apicore::SecurityContext { - privileged: Some(false), + privileged: Some(privileged), ..apicore::SecurityContext::default() }), ..apicore::Container::default() - }], - init_containers: Some(vec![ - apicore::Container { - security_context: Some(apicore::SecurityContext { - privileged: Some(false), - ..apicore::SecurityContext::default() - }), - ..apicore::Container::default() - }, - apicore::Container { - security_context: Some(apicore::SecurityContext { - privileged: Some(true), - ..apicore::SecurityContext::default() - }), - ..apicore::Container::default() - }, - ]), - ephemeral_containers: Some(vec![ - apicore::EphemeralContainer { - security_context: Some(apicore::SecurityContext { - privileged: Some(false), - ..apicore::SecurityContext::default() - }), - ..apicore::EphemeralContainer::default() - }, - apicore::EphemeralContainer { + }) + .collect(); + } + if let Some(ephemeral_container_privileged_values) = ephemeral_container_privileged { + ephemeral_containers = Some( + ephemeral_container_privileged_values + .into_iter() + .map(|privileged| apicore::EphemeralContainer { security_context: Some(apicore::SecurityContext { - privileged: Some(true), + privileged: Some(privileged), ..apicore::SecurityContext::default() }), ..apicore::EphemeralContainer::default() - }, - ]), - ..apicore::PodSpec::default() - }, - &Settings { - skip_init_containers: true, - skip_ephemeral_containers: true, - }, - ); - assert!( - result.is_ok(), - "Pod should be accepted if settings is configured to ignore init and ephemeral containers" - ); - } - - #[test] - fn reject_pod_when_all_init_containers_are_privileged_test() -> Result<()> { - let result = validate_pod( - &apicore::PodSpec { - init_containers: Some(vec![ - apicore::Container { - security_context: Some(apicore::SecurityContext { - privileged: Some(true), - ..apicore::SecurityContext::default() - }), - ..apicore::Container::default() - }, - apicore::Container { - security_context: Some(apicore::SecurityContext { - privileged: Some(true), - ..apicore::SecurityContext::default() - }), - ..apicore::Container::default() - }, - apicore::Container { - security_context: Some(apicore::SecurityContext { - privileged: Some(true), - ..apicore::SecurityContext::default() - }), - ..apicore::Container::default() - }, - ]), - ..apicore::PodSpec::default() - }, - &Settings::default(), - ); - assert!( - result.is_err(), - "Pod with all privileged init containers should be rejected by the validator" - ); - Ok(()) - } - - #[test] - fn accecpt_pod_when_containers_are_not_privileged_test() -> Result<()> { - let result = validate_pod( - &apicore::PodSpec { - containers: vec![ - apicore::Container { - security_context: Some(apicore::SecurityContext { - privileged: Some(false), - ..apicore::SecurityContext::default() - }), - ..apicore::Container::default() - }, - apicore::Container { - security_context: Some(apicore::SecurityContext { - privileged: Some(false), - ..apicore::SecurityContext::default() - }), - ..apicore::Container::default() - }, - apicore::Container { - security_context: Some(apicore::SecurityContext { - privileged: Some(false), - ..apicore::SecurityContext::default() - }), - ..apicore::Container::default() - }, - ], - ..apicore::PodSpec::default() - }, - &Settings::default(), - ); - assert!( - result.is_ok(), - "Pod with no privileged container should be accepted by the validator" - ); - Ok(()) - } - - #[test] - fn reject_pod_when_one_container_is_privileged_test() -> Result<()> { - let result = validate_pod( - &apicore::PodSpec { - containers: vec![ - apicore::Container { - security_context: Some(apicore::SecurityContext { - privileged: Some(false), - ..apicore::SecurityContext::default() - }), - ..apicore::Container::default() - }, - apicore::Container { - security_context: Some(apicore::SecurityContext { - privileged: Some(false), - ..apicore::SecurityContext::default() - }), - ..apicore::Container::default() - }, - apicore::Container { - security_context: Some(apicore::SecurityContext { - privileged: Some(true), - ..apicore::SecurityContext::default() - }), - ..apicore::Container::default() - }, - ], - ..apicore::PodSpec::default() - }, - &Settings::default(), - ); - - assert!( - result.is_err(), - "Pod with only a single privileged container should be rejected by the validator" - ); - Ok(()) - } - - #[test] - fn reject_pod_when_all_containers_are_privileged_test() -> Result<()> { - let result = validate_pod( - &apicore::PodSpec { - containers: vec![ - apicore::Container { - security_context: Some(apicore::SecurityContext { - privileged: Some(true), - ..apicore::SecurityContext::default() - }), - ..apicore::Container::default() - }, - apicore::Container { - security_context: Some(apicore::SecurityContext { - privileged: Some(true), - ..apicore::SecurityContext::default() - }), - ..apicore::Container::default() - }, - apicore::Container { + }) + .collect(), + ); + } + if let Some(init_container_privileged_values) = init_container_privileged { + init_containers = Some( + init_container_privileged_values + .into_iter() + .map(|privileged| apicore::Container { security_context: Some(apicore::SecurityContext { - privileged: Some(true), + privileged: Some(privileged), ..apicore::SecurityContext::default() }), ..apicore::Container::default() - }, - ], - ..apicore::PodSpec::default() - }, - &Settings::default(), - ); - assert!( - result.is_err(), - "Pod with all privileged containers should be rejected by the validator" - ); - Ok(()) - } - - #[test] - fn accept_container_is_not_privileged_test() -> Result<()> { - assert_eq!( - validate_container(&apicore::Container { - security_context: Some(apicore::SecurityContext { - privileged: Some(false), - ..apicore::SecurityContext::default() - }), - ..apicore::Container::default() - }), - true, - "Non privileged container should be accepted by the validator" - ); - Ok(()) - } - - #[test] - fn accept_container_with_no_security_context() -> Result<()> { - assert_eq!( - validate_container(&apicore::Container { - ..apicore::Container::default() - }), - true, - "Non privileged container should be accepted by the validator" - ); - Ok(()) - } - - #[test] - fn reject_privileged_container_test() -> Result<()> { - assert_eq!( - validate_container(&apicore::Container { - security_context: Some(apicore::SecurityContext { - privileged: Some(true), - ..apicore::SecurityContext::default() - }), - ..apicore::Container::default() - }), - false, - "Privileged container should be rejected by the validator" - ); - Ok(()) + }) + .collect(), + ); + } + apicore::PodSpec { + containers, + ephemeral_containers, + init_containers, + ..Default::default() + } } - #[test] - fn accept_privileged_container_when_privileged_is_none_test() -> Result<()> { - assert_eq!( - validate_container(&apicore::Container { - security_context: Some(apicore::SecurityContext { - privileged: None, - ..apicore::SecurityContext::default() - }), - ..apicore::Container::default() - }), - true, - "Privileged container should be accepted by the validator when there is no 'privileged' configuration. The default behaviour is disable privileged containers" - ); - Ok(()) + #[rstest] + #[case::accept_pod_when_all_ephemeral_containers_are_not_privileged_test2(pod_factory(None, None, Some(vec![false,false,false])), Settings::default(), Ok(true), "Pod with no privileged ephemeral container should be accepted by the validator")] + #[case::reject_pod_when_all_ephemeral_container_is_privileged_test(pod_factory(None, None, Some(vec![true, true, true])), Settings::default(), Err(anyhow!("")), "Pod with all privileged ephemeral container should be rejected by the validator")] + #[case::reject_pod_when_one_ephemeral_container_is_privileged_test(pod_factory(None, None, Some(vec![false, false, true])), Settings::default(), Err(anyhow!("")), "Pod with only a single privileged ephemeral container should be rejected by the validator")] + #[case::accept_pod_when_init_containers_are_not_privileged_test(pod_factory(None, Some(vec![false, false, false]), None), Settings::default(), Ok(true), "Pod with no privileged init container should be accepted by the validator")] + #[case::reject_pod_when_one_init_container_is_privileged_test(pod_factory(None, Some(vec![false, false, true]), None), Settings::default(), Err(anyhow!("")), "Pod with only a single privileged init container should be rejected by the validator")] + #[case::accept_pod_when_containers_are_privileged_and_policy_should_ignore_test(pod_factory(Some(vec![false]), Some(vec![false, true]), Some(vec![false, true])), Settings { skip_init_containers: true, skip_ephemeral_containers: true, }, Ok(true), "Pod should be accepted if settings is configured to ignore init and ephemeral containers")] + #[case::reject_pod_when_all_init_containers_are_privileged_test(pod_factory(None, Some(vec![true, true, true]), None), Settings::default(), Err(anyhow!("")), "Pod with all privileged init containers should be rejected by the validator")] + #[case::accecpt_pod_when_containers_are_not_privileged_test(pod_factory(Some(vec![false, false, false]), None, None), Settings::default(), Ok(true), "Pod with no privileged container should be accepted by the validator")] + #[case::reject_pod_when_one_container_is_privileged_test(pod_factory(Some(vec![false, false, true]), None, None), Settings::default(), Err(anyhow!("")), "Pod with only a single privileged container should be rejected by the validator")] + #[case::reject_pod_when_all_containers_are_privileged_test(pod_factory(Some(vec![true, true, true]), None, None), Settings::default(), Err(anyhow!("")), "Pod with all privileged containers should be rejected by the validator")] + fn validate_pod_test( + #[case] pod_spec: apicore::PodSpec, + #[case] settings: Settings, + #[case] expected_result: anyhow::Result, + #[case] error_msg: String, + ) { + let result = validate_pod(&pod_spec, &settings); + match expected_result { + Ok(_) => assert!(result.is_ok(), "{}", error_msg.to_owned()), + Err(_) => assert!(result.is_err(), "{}", error_msg.to_owned()), + } } - #[test] - fn accept_ephemeral_container_is_not_privileged_test() -> Result<()> { - assert_eq!( - validate_ephemeral_container(&apicore::EphemeralContainer { - security_context: Some(apicore::SecurityContext { - privileged: Some(false), - ..apicore::SecurityContext::default() - }), - ..apicore::EphemeralContainer::default() + fn container_factory(privileged: Option) -> apicore::Container { + apicore::Container { + security_context: Some(apicore::SecurityContext { + privileged, + ..apicore::SecurityContext::default() }), - true, - "Non privileged container should be accepted by the validator" - ); - Ok(()) + ..apicore::Container::default() + } } - #[test] - fn accept_ephemeral_container_with_no_security_context() -> Result<()> { + #[rstest] + #[case::accept_container_is_not_privileged_test( + container_factory(Some(false)), + true, + "Non privileged container should be accepted by the validator" + )] + #[case::accept_container_with_no_security_context(apicore::Container { ..apicore::Container::default() }, true, "Non privileged container should be accepted by the validator")] + #[case::reject_privileged_container_test( + container_factory(Some(true)), + false, + "Privileged container should be rejected by the validator" + )] + #[case::accept_privileged_container_when_privileged_is_none_test(container_factory(None), true, "Privileged container should be accepted by the validator when there is no 'privileged' configuration. The default behaviour is disable privileged containers")] + fn validate_container_test( + #[case] container: apicore::Container, + #[case] expected_result: bool, + #[case] error_msg: String, + ) { assert_eq!( - validate_ephemeral_container(&apicore::EphemeralContainer { - ..apicore::EphemeralContainer::default() - }), - true, - "Non privileged container should be accepted by the validator" - ); - Ok(()) + validate_container(&container), + expected_result, + "{}", + error_msg + ) } - #[test] - fn reject_privileged_ephemeral_container_test() -> Result<()> { - assert_eq!( - validate_ephemeral_container(&apicore::EphemeralContainer { - security_context: Some(apicore::SecurityContext { - privileged: Some(true), - ..apicore::SecurityContext::default() - }), - ..apicore::EphemeralContainer::default() + fn ephemeral_container_factory(privileged: Option) -> apicore::EphemeralContainer { + apicore::EphemeralContainer { + security_context: Some(apicore::SecurityContext { + privileged, + ..apicore::SecurityContext::default() }), - false, - "Privileged container should be rejected by the validator" - ); - Ok(()) + ..apicore::EphemeralContainer::default() + } } - #[test] - fn accept_privileged_ephemeral_container_when_privileged_is_none_test() -> Result<()> { + #[rstest] + #[case::accept_ephemeral_container_is_not_privileged_test( + ephemeral_container_factory(Some(false)), + true, + "Non privileged container should be accepted by the validator" + )] + #[case::accept_ephemeral_container_with_no_security_context(apicore::EphemeralContainer { ..apicore::EphemeralContainer::default() }, true, "Non privileged container should be accepted by the validator")] + #[case::reject_privileged_ephemeral_container_test( + ephemeral_container_factory(Some(true)), + false, + "Privileged container should be rejected by the validator" + )] + #[case::accept_privileged_ephemeral_container_when_privileged_is_none_test(ephemeral_container_factory(None), true, "Privileged container should be accepted by the validator when there is no 'privileged' configuration. The default behaviour is disable privileged containers")] + fn validate_ephemeral_container_test( + #[case] container: apicore::EphemeralContainer, + #[case] expected_result: bool, + #[case] error_msg: String, + ) { assert_eq!( - validate_ephemeral_container(&apicore::EphemeralContainer { - security_context: Some(apicore::SecurityContext { - privileged: None, - ..apicore::SecurityContext::default() - }), - ..apicore::EphemeralContainer::default() - }), - true, - "Privileged container should be accepted by the validator when there is no 'privileged' configuration. The default behaviour is disable privileged containers" + validate_ephemeral_container(&container), + expected_result, + "{}", + error_msg ); - Ok(()) } } diff --git a/test_data/privileged_ephemeral_container.json b/test_data/privileged_ephemeral_container.json new file mode 100644 index 0000000..fbd5e91 --- /dev/null +++ b/test_data/privileged_ephemeral_container.json @@ -0,0 +1,185 @@ +{ + "uid": "1299d386-525b-4032-98ae-1949f69f9cfc", + "kind": { + "group": "", + "version": "v1", + "kind": "Pod" + }, + "resource": { + "group": "", + "version": "v1", + "resource": "pods" + }, + "requestKind": { + "group": "", + "version": "v1", + "kind": "Pod" + }, + "requestResource": { + "group": "", + "version": "v1", + "resource": "pods" + }, + "name": "nginx", + "namespace": "default", + "operation": "CREATE", + "userInfo": { + "username": "kubernetes-admin", + "groups": [ + "system:masters", + "system:authenticated" + ] + }, + "object": { + "kind": "Pod", + "apiVersion": "v1", + "metadata": { + "name": "nginx", + "namespace": "default", + "uid": "04dc7a5e-e1f1-4e34-8d65-2c9337a43e64", + "creationTimestamp": "2020-11-12T15:18:36Z", + "labels": { + "env": "test" + }, + "annotations": { + "kubectl.kubernetes.io/last-applied-configuration": "{\"apiVersion\":\"v1\",\"kind\":\"Pod\",\"metadata\":{\"annotations\":{},\"labels\":{\"env\":\"test\"},\"name\":\"nginx\",\"namespace\":\"default\"},\"spec\":{\"containers\":[{\"image\":\"nginx\",\"imagePullPolicy\":\"IfNotPresent\",\"name\":\"nginx\"}],\"tolerations\":[{\"effect\":\"NoSchedule\",\"key\":\"example-key\",\"operator\":\"Exists\"}]}}\n" + }, + "managedFields": [ + { + "manager": "kubectl", + "operation": "Update", + "apiVersion": "v1", + "time": "2020-11-12T15:18:36Z", + "fieldsType": "FieldsV1", + "fieldsV1": { + "f:metadata": { + "f:annotations": { + ".": {}, + "f:kubectl.kubernetes.io/last-applied-configuration": {} + }, + "f:labels": { + ".": {}, + "f:env": {} + } + }, + "f:spec": { + "f:containers": { + "k:{\"name\":\"nginx\"}": { + ".": {}, + "f:image": {}, + "f:imagePullPolicy": {}, + "f:name": {}, + "f:resources": {}, + "f:terminationMessagePath": {}, + "f:terminationMessagePolicy": {} + } + }, + "f:dnsPolicy": {}, + "f:enableServiceLinks": {}, + "f:restartPolicy": {}, + "f:schedulerName": {}, + "f:securityContext": {}, + "f:terminationGracePeriodSeconds": {}, + "f:tolerations": {} + } + } + } + ] + }, + "spec": { + "volumes": [ + { + "name": "default-token-pvpz7", + "secret": { + "secretName": "default-token-pvpz7" + } + } + ], + "ephemeralContainers": [ + { + "name": "nginx", + "image": "nginx", + "securityContext": { + "privileged": true + } + } + ], + "containers": [ + { + "name": "sleeping-sidecar", + "image": "alpine", + "command": ["sleep", "1h"], + "resources": {}, + "volumeMounts": [ + { + "name": "default-token-pvpz7", + "readOnly": true, + "mountPath": "/var/run/secrets/kubernetes.io/serviceaccount" + } + ], + "terminationMessagePath": "/dev/termination-log", + "terminationMessagePolicy": "File", + "imagePullPolicy": "IfNotPresent" + }, + { + "name": "nginx", + "image": "nginx", + "resources": {}, + "volumeMounts": [ + { + "name": "default-token-pvpz7", + "readOnly": true, + "mountPath": "/var/run/secrets/kubernetes.io/serviceaccount" + } + ], + "securityContext": { + "privileged": false + }, + "terminationMessagePath": "/dev/termination-log", + "terminationMessagePolicy": "File", + "imagePullPolicy": "IfNotPresent" + } + ], + "restartPolicy": "Always", + "terminationGracePeriodSeconds": 30, + "dnsPolicy": "ClusterFirst", + "serviceAccountName": "default", + "serviceAccount": "default", + "securityContext": {}, + "schedulerName": "default-scheduler", + "tolerations": [ + { + "key": "node.kubernetes.io/not-ready", + "operator": "Exists", + "effect": "NoExecute", + "tolerationSeconds": 300 + }, + { + "key": "node.kubernetes.io/unreachable", + "operator": "Exists", + "effect": "NoExecute", + "tolerationSeconds": 300 + }, + { + "key": "dedicated", + "operator": "Equal", + "value": "tenantA", + "effect": "NoSchedule" + } + ], + "priority": 0, + "enableServiceLinks": true, + "preemptionPolicy": "PreemptLowerPriority" + }, + "status": { + "phase": "Pending", + "qosClass": "BestEffort" + } + }, + "oldObject": null, + "dryRun": false, + "options": { + "kind": "CreateOptions", + "apiVersion": "meta.k8s.io/v1" + } +} diff --git a/test_data/privileged_init_container.json b/test_data/privileged_init_container.json new file mode 100644 index 0000000..7c57242 --- /dev/null +++ b/test_data/privileged_init_container.json @@ -0,0 +1,186 @@ +{ + "uid": "1299d386-525b-4032-98ae-1949f69f9cfc", + "kind": { + "group": "", + "version": "v1", + "kind": "Pod" + }, + "resource": { + "group": "", + "version": "v1", + "resource": "pods" + }, + "requestKind": { + "group": "", + "version": "v1", + "kind": "Pod" + }, + "requestResource": { + "group": "", + "version": "v1", + "resource": "pods" + }, + "name": "nginx", + "namespace": "default", + "operation": "CREATE", + "userInfo": { + "username": "kubernetes-admin", + "groups": [ + "system:masters", + "system:authenticated" + ] + }, + "object": { + "kind": "Pod", + "apiVersion": "v1", + "metadata": { + "name": "nginx", + "namespace": "default", + "uid": "04dc7a5e-e1f1-4e34-8d65-2c9337a43e64", + "creationTimestamp": "2020-11-12T15:18:36Z", + "labels": { + "env": "test" + }, + "annotations": { + "kubectl.kubernetes.io/last-applied-configuration": "{\"apiVersion\":\"v1\",\"kind\":\"Pod\",\"metadata\":{\"annotations\":{},\"labels\":{\"env\":\"test\"},\"name\":\"nginx\",\"namespace\":\"default\"},\"spec\":{\"containers\":[{\"image\":\"nginx\",\"imagePullPolicy\":\"IfNotPresent\",\"name\":\"nginx\"}],\"tolerations\":[{\"effect\":\"NoSchedule\",\"key\":\"example-key\",\"operator\":\"Exists\"}]}}\n" + }, + "managedFields": [ + { + "manager": "kubectl", + "operation": "Update", + "apiVersion": "v1", + "time": "2020-11-12T15:18:36Z", + "fieldsType": "FieldsV1", + "fieldsV1": { + "f:metadata": { + "f:annotations": { + ".": {}, + "f:kubectl.kubernetes.io/last-applied-configuration": {} + }, + "f:labels": { + ".": {}, + "f:env": {} + } + }, + "f:spec": { + "f:containers": { + "k:{\"name\":\"nginx\"}": { + ".": {}, + "f:image": {}, + "f:imagePullPolicy": {}, + "f:name": {}, + "f:resources": {}, + "f:terminationMessagePath": {}, + "f:terminationMessagePolicy": {} + } + }, + "f:dnsPolicy": {}, + "f:enableServiceLinks": {}, + "f:restartPolicy": {}, + "f:schedulerName": {}, + "f:securityContext": {}, + "f:terminationGracePeriodSeconds": {}, + "f:tolerations": {} + } + } + } + ] + }, + "spec": { + "volumes": [ + { + "name": "default-token-pvpz7", + "secret": { + "secretName": "default-token-pvpz7" + } + } + ], + "initContainers": [ + { + "name": "nginx", + "image": "nginx", + "resources": {}, + "securityContext": { + "privileged": true + } + } + ], + "containers": [ + { + "name": "sleeping-sidecar", + "image": "alpine", + "command": ["sleep", "1h"], + "resources": {}, + "volumeMounts": [ + { + "name": "default-token-pvpz7", + "readOnly": true, + "mountPath": "/var/run/secrets/kubernetes.io/serviceaccount" + } + ], + "terminationMessagePath": "/dev/termination-log", + "terminationMessagePolicy": "File", + "imagePullPolicy": "IfNotPresent" + }, + { + "name": "nginx", + "image": "nginx", + "resources": {}, + "volumeMounts": [ + { + "name": "default-token-pvpz7", + "readOnly": true, + "mountPath": "/var/run/secrets/kubernetes.io/serviceaccount" + } + ], + "securityContext": { + "privileged": false + }, + "terminationMessagePath": "/dev/termination-log", + "terminationMessagePolicy": "File", + "imagePullPolicy": "IfNotPresent" + } + ], + "restartPolicy": "Always", + "terminationGracePeriodSeconds": 30, + "dnsPolicy": "ClusterFirst", + "serviceAccountName": "default", + "serviceAccount": "default", + "securityContext": {}, + "schedulerName": "default-scheduler", + "tolerations": [ + { + "key": "node.kubernetes.io/not-ready", + "operator": "Exists", + "effect": "NoExecute", + "tolerationSeconds": 300 + }, + { + "key": "node.kubernetes.io/unreachable", + "operator": "Exists", + "effect": "NoExecute", + "tolerationSeconds": 300 + }, + { + "key": "dedicated", + "operator": "Equal", + "value": "tenantA", + "effect": "NoSchedule" + } + ], + "priority": 0, + "enableServiceLinks": true, + "preemptionPolicy": "PreemptLowerPriority" + }, + "status": { + "phase": "Pending", + "qosClass": "BestEffort" + } + }, + "oldObject": null, + "dryRun": false, + "options": { + "kind": "CreateOptions", + "apiVersion": "meta.k8s.io/v1" + } +} diff --git a/test_data/settings_skip_init_and_ephemeral_containers.json b/test_data/settings_skip_init_and_ephemeral_containers.json new file mode 100644 index 0000000..83a016f --- /dev/null +++ b/test_data/settings_skip_init_and_ephemeral_containers.json @@ -0,0 +1,4 @@ +{ + "skip_init_containers": true, + "skip_ephemeral_containers": true +}