From 49e2b4d68e3c0d68f4fb7f85493db9d56464f99b Mon Sep 17 00:00:00 2001 From: Flavio Castelli Date: Tue, 30 Nov 2021 16:53:49 +0100 Subject: [PATCH] Initial implementation of the policy --- .../{release.yml.template => release.yml} | 0 Cargo.lock | 428 ++++++++++++++++++ Cargo.toml | 4 +- README.md | 52 ++- hub.yml | 7 +- metadata.yml | 26 +- src/lib.rs | 246 ++++++---- src/settings.rs | 8 - test_data/ingress_creation.json | 38 -- test_data/pod_creation.json | 32 -- test_data/pod_creation_invalid_name.json | 32 -- 11 files changed, 642 insertions(+), 231 deletions(-) rename .github/workflows/{release.yml.template => release.yml} (100%) create mode 100644 Cargo.lock delete mode 100644 test_data/ingress_creation.json delete mode 100644 test_data/pod_creation.json delete mode 100644 test_data/pod_creation_invalid_name.json diff --git a/.github/workflows/release.yml.template b/.github/workflows/release.yml similarity index 100% rename from .github/workflows/release.yml.template rename to .github/workflows/release.yml diff --git a/Cargo.lock b/Cargo.lock new file mode 100644 index 0000000..ac7b13e --- /dev/null +++ b/Cargo.lock @@ -0,0 +1,428 @@ +# This file is automatically @generated by Cargo. +# It is not intended for manual editing. +version = 3 + +[[package]] +name = "anyhow" +version = "1.0.51" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8b26702f315f53b6071259e15dd9d64528213b44d61de1ec926eca7715d62203" + +[[package]] +name = "autocfg" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cdb031dd78e28731d87d56cc8ffef4a8f36ca26c38fe2de700543e627f8a464a" + +[[package]] +name = "base64" +version = "0.13.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "904dfeac50f3cdaba28fc6f57fdcddb75f49ed61346676a78c4ffe55877802fd" + +[[package]] +name = "bytes" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c4872d67bab6358e59559027aa3b9157c53d9358c51423c17554809a8858e0f8" + +[[package]] +name = "chrono" +version = "0.4.19" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "670ad68c9088c2a963aaa298cb369688cf3f9465ce5e2d4ca10e6e0098a1ce73" +dependencies = [ + "libc", + "num-integer", + "num-traits", + "serde", + "time", + "winapi", +] + +[[package]] +name = "fnv" +version = "1.0.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3f9eec918d3f24069decb9af1554cad7c880e2da24a9afd88aca000531ab82c1" + +[[package]] +name = "form_urlencoded" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5fc25a87fa4fd2094bffb06925852034d90a17f0d1e05197d4956d3555752191" +dependencies = [ + "matches", + "percent-encoding", +] + +[[package]] +name = "http" +version = "0.2.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1323096b05d41827dadeaee54c9981958c0f94e670bc94ed80037d1a7b8b186b" +dependencies = [ + "bytes", + "fnv", + "itoa", +] + +[[package]] +name = "idna" +version = "0.2.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "418a0a6fab821475f634efe3ccc45c013f742efe03d853e8d3355d5cb850ecf8" +dependencies = [ + "matches", + "unicode-bidi", + "unicode-normalization", +] + +[[package]] +name = "itoa" +version = "0.4.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b71991ff56294aa922b450139ee08b3bfc70982c6b2c7562771375cf73542dd4" + +[[package]] +name = "k8s-openapi" +version = "0.11.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bcc1f973542059e6d5a6d63de6a9539d0ec784f82b2327f3c1915d33200bc6a4" +dependencies = [ + "base64", + "bytes", + "chrono", + "http", + "percent-encoding", + "serde", + "serde-value", + "serde_json", + "url", +] + +[[package]] +name = "kubewarden-policy-sdk" +version = "0.2.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "61a46677c118ebeff46e1d3b09450715270366482a5b0cbeb8ef04152f7b4605" +dependencies = [ + "anyhow", + "k8s-openapi", + "num", + "num-derive", + "num-traits", + "serde", + "serde_json", + "slog", + "wapc-guest", +] + +[[package]] +name = "lazy_static" +version = "1.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e2abad23fbc42b3700f2f279844dc832adb2b2eb069b2df918f455c4e18cc646" + +[[package]] +name = "libc" +version = "0.2.108" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8521a1b57e76b1ec69af7599e75e38e7b7fad6610f037db8c79b127201b5d119" + +[[package]] +name = "matches" +version = "0.1.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a3e378b66a060d48947b590737b30a1be76706c8dd7b8ba0f2fe3989c68a853f" + +[[package]] +name = "num" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "43db66d1170d347f9a065114077f7dccb00c1b9478c89384490a3425279a4606" +dependencies = [ + "num-bigint", + "num-complex", + "num-integer", + "num-iter", + "num-rational", + "num-traits", +] + +[[package]] +name = "num-bigint" +version = "0.4.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f93ab6289c7b344a8a9f60f88d80aa20032336fe78da341afc91c8a2341fc75f" +dependencies = [ + "autocfg", + "num-integer", + "num-traits", +] + +[[package]] +name = "num-complex" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "26873667bbbb7c5182d4a37c1add32cdf09f841af72da53318fdb81543c15085" +dependencies = [ + "num-traits", +] + +[[package]] +name = "num-derive" +version = "0.3.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "876a53fff98e03a936a674b29568b0e605f06b29372c2489ff4de23f1949743d" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "num-integer" +version = "0.1.44" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d2cc698a63b549a70bc047073d2949cce27cd1c7b0a4a862d08a8031bc2801db" +dependencies = [ + "autocfg", + "num-traits", +] + +[[package]] +name = "num-iter" +version = "0.1.42" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b2021c8337a54d21aca0d59a92577a029af9431cb59b909b03252b9c164fad59" +dependencies = [ + "autocfg", + "num-integer", + "num-traits", +] + +[[package]] +name = "num-rational" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d41702bd167c2df5520b384281bc111a4b5efcf7fbc4c9c222c815b07e0a6a6a" +dependencies = [ + "autocfg", + "num-bigint", + "num-integer", + "num-traits", +] + +[[package]] +name = "num-traits" +version = "0.2.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9a64b1ec5cda2586e284722486d802acf1f7dbdc623e2bfc57e65ca1cd099290" +dependencies = [ + "autocfg", +] + +[[package]] +name = "ordered-float" +version = "2.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "97c9d06878b3a851e8026ef94bf7fef9ba93062cd412601da4d9cf369b1cc62d" +dependencies = [ + "num-traits", +] + +[[package]] +name = "percent-encoding" +version = "2.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d4fd5641d01c8f18a23da7b6fe29298ff4b55afcccdf78973b24cf3175fee32e" + +[[package]] +name = "proc-macro2" +version = "1.0.32" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ba508cc11742c0dc5c1659771673afbab7a0efab23aa17e854cbab0837ed0b43" +dependencies = [ + "unicode-xid", +] + +[[package]] +name = "quote" +version = "1.0.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "38bc8cc6a5f2e3655e0899c1b848643b2562f853f114bfec7be120678e3ace05" +dependencies = [ + "proc-macro2", +] + +[[package]] +name = "readonly-root-filesystem-psp-policy" +version = "0.1.0" +dependencies = [ + "k8s-openapi", + "kubewarden-policy-sdk", + "serde", + "serde_json", + "wapc-guest", +] + +[[package]] +name = "ryu" +version = "1.0.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3c9613b5a66ab9ba26415184cfc41156594925a9cf3a2057e57f31ff145f6568" + +[[package]] +name = "serde" +version = "1.0.130" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f12d06de37cf59146fbdecab66aa99f9fe4f78722e3607577a5375d66bd0c913" +dependencies = [ + "serde_derive", +] + +[[package]] +name = "serde-value" +version = "0.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f3a1a3341211875ef120e117ea7fd5228530ae7e7036a779fdc9117be6b3282c" +dependencies = [ + "ordered-float", + "serde", +] + +[[package]] +name = "serde_derive" +version = "1.0.130" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d7bc1a1ab1961464eae040d96713baa5a724a8152c1222492465b54322ec508b" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "serde_json" +version = "1.0.72" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d0ffa0837f2dfa6fb90868c2b5468cad482e175f7dad97e7421951e663f2b527" +dependencies = [ + "itoa", + "ryu", + "serde", +] + +[[package]] +name = "slog" +version = "2.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8347046d4ebd943127157b94d63abb990fcf729dc4e9978927fdf4ac3c998d06" + +[[package]] +name = "syn" +version = "1.0.82" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8daf5dd0bb60cbd4137b1b587d2fc0ae729bc07cf01cd70b36a1ed5ade3b9d59" +dependencies = [ + "proc-macro2", + "quote", + "unicode-xid", +] + +[[package]] +name = "time" +version = "0.1.44" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6db9e6914ab8b1ae1c260a4ae7a49b6c5611b40328a735b21862567685e73255" +dependencies = [ + "libc", + "wasi", + "winapi", +] + +[[package]] +name = "tinyvec" +version = "1.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2c1c1d5a42b6245520c249549ec267180beaffcc0615401ac8e31853d4b6d8d2" +dependencies = [ + "tinyvec_macros", +] + +[[package]] +name = "tinyvec_macros" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cda74da7e1a664f795bb1f8a87ec406fb89a02522cf6e50620d016add6dbbf5c" + +[[package]] +name = "unicode-bidi" +version = "0.3.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1a01404663e3db436ed2746d9fefef640d868edae3cceb81c3b8d5732fda678f" + +[[package]] +name = "unicode-normalization" +version = "0.1.19" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d54590932941a9e9266f0832deed84ebe1bf2e4c9e4a3554d393d18f5e854bf9" +dependencies = [ + "tinyvec", +] + +[[package]] +name = "unicode-xid" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8ccb82d61f80a663efe1f787a51b16b5a51e3314d6ac365b08639f52387b33f3" + +[[package]] +name = "url" +version = "2.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a507c383b2d33b5fc35d1861e77e6b383d158b2da5e14fe51b83dfedf6fd578c" +dependencies = [ + "form_urlencoded", + "idna", + "matches", + "percent-encoding", +] + +[[package]] +name = "wapc-guest" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "47cbd9d778b9718eda797278936f93f25ce81064fe26f0bb6a710cd51315f00b" +dependencies = [ + "lazy_static", +] + +[[package]] +name = "wasi" +version = "0.10.0+wasi-snapshot-preview1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1a143597ca7c7793eff794def352d41792a93c481eb1042423ff7ff72ba2c31f" + +[[package]] +name = "winapi" +version = "0.3.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5c839a674fcd7a98952e593242ea400abe93992746761e38641405d28b00f419" +dependencies = [ + "winapi-i686-pc-windows-gnu", + "winapi-x86_64-pc-windows-gnu", +] + +[[package]] +name = "winapi-i686-pc-windows-gnu" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6" + +[[package]] +name = "winapi-x86_64-pc-windows-gnu" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f" diff --git a/Cargo.toml b/Cargo.toml index d6fd7ce..96bf457 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -12,8 +12,6 @@ crate-type = ["cdylib"] [dependencies] k8s-openapi = { version = "0.11.0", features = ["v1_20"] } kubewarden-policy-sdk = "0.2.3" -lazy_static = "1.4" serde = { version = "1.0", features = ["derive"] } serde_json = "1.0" -slog = "2.7" -wapc-guest = "0.4.0" \ No newline at end of file +wapc-guest = "0.4.0" diff --git a/README.md b/README.md index d8316bc..9ea5cc5 100644 --- a/README.md +++ b/README.md @@ -1,35 +1,37 @@ -Please, note well: this file and the scaffold were generated from [a -template](https://github.com/kubewarden/policy-rust-template). Make -this project yours! +Continuous integration | License + -----------------------|-------- +![Continuous integration](https://github.com/kubewarden/readonly-root-filesystem-psp-policy/workflows/Continuous%20integration/badge.svg) | [![License: Apache 2.0](https://img.shields.io/badge/License-Apache2.0-brightgreen.svg)](https://opensource.org/licenses/Apache-2.0) -# Kubewarden policy readonly-root-filesystem-psp-policy -## Description +This Kubewarden Policy is a replacement for the Kubernetes Pod Security Policy +that enforces the usage of [`ReadOnlyRootFilesystems`](https://kubernetes.io/docs/concepts/policy/pod-security-policy/#volumes-and-file-systems). -This policy will reject pods that have a name `invalid-pod-name`. If -the pod to be validated has a different name, or if a different type -of resource is evaluated, it will be accepted. +# How the policy works -## Settings +The policy inspects the `securityContext` of each container defined inside of +a Pod and ensures all the containers have the `readOnlyRootFilesystem` attribute +set to `true`. -This policy has no configurable settings. This would be a good place -to document if yours does, and what behaviors can be configured by -tweaking them. +The policy checks the both the `pod.spec.containers` and the init containers +too. -## License +Containers that do not have a `securityContext` defined are rejected too. +That happens because, by default, the root filesystem of a container is +considered to be writable. -``` -Copyright (C) 2021 Flavio Castelli +Ephemeral containers are not checked because, by Kubernetes definition, they +cannot have a `securityContext`. -Licensed under the Apache License, Version 2.0 (the "License"); -you may not use this file except in compliance with the License. -You may obtain a copy of the License at +# Configuration - http://www.apache.org/licenses/LICENSE-2.0 +The policy doesn't have any configuration. -Unless required by applicable law or agreed to in writing, software -distributed under the License is distributed on an "AS IS" BASIS, -WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -See the License for the specific language governing permissions and -limitations under the License. -``` +# Obtain policy + +The policy is automatically published as an OCI artifact inside of +[this](https://github.com/orgs/kubewarden/packages/container/package/policies%2Freadonly-root-filesystem-psp-policy) +container registry. + +# Using the policy + +The easiest way to use this policy is through the [kubewarden-controller](https://github.com/kubewarden/kubewarden-controller). diff --git a/hub.yml b/hub.yml index f925b35..2c8601d 100644 --- a/hub.yml +++ b/hub.yml @@ -8,9 +8,12 @@ download: # Important: leave the __TAG__ around: this is automatically replaced with the value of the git tag registry: ghcr.io/kubewarden/policies/readonly-root-filesystem-psp-policy:__TAG__ # url is optional - url: https://github.com/yourorg/readonly-root-filesystem-psp-policy/releases/download/__TAG__/policy.wasm + url: https://github.com/kubewarden/readonly-root-filesystem-psp-policy/releases/download/__TAG__/policy.wasm keywords: - - this is freeform + - PSP + - Container + - filesystem + - Volume resources: - Pod mutation: false diff --git a/metadata.yml b/metadata.yml index 2e9d9da..384ea8a 100644 --- a/metadata.yml +++ b/metadata.yml @@ -2,18 +2,32 @@ rules: - apiGroups: [""] apiVersions: ["v1"] resources: ["pods"] - operations: ["CREATE"] + operations: ["CREATE", "UPDATE"] mutating: false contextAware: false executionMode: kubewarden-wapc annotations: io.kubewarden.policy.title: readonly-root-filesystem-psp-policy - io.kubewarden.policy.description: Short description + io.kubewarden.policy.description: Enforce all the containers to have a readonly root filesystem io.kubewarden.policy.author: Flavio Castelli - io.kubewarden.policy.url: https://github.com/yourorg/readonly-root-filesystem-psp-policy - io.kubewarden.policy.source: https://github.com/yourorg/readonly-root-filesystem-psp-policy + io.kubewarden.policy.url: https://github.com/kubewarden/readonly-root-filesystem-psp-policy + io.kubewarden.policy.source: https://github.com/kubewarden/readonly-root-filesystem-psp-policy io.kubewarden.policy.license: Apache-2.0 io.kubewarden.policy.usage: | - Long explaination. + The policy inspects the `securityContext` of each container defined inside of + a Pod and ensures all the containers have the `readOnlyRootFilesystem` attribute + set to `true`. - **Note well:** this can be Markdown text + The policy checks the both the `pod.spec.containers` and the init containers + too. + + Containers that do not have a `securityContext` defined are rejected too. + That happens because, by default, the root filesystem of a container is + considered to be writable. + + Ephemeral containers are not checked because, by Kubernetes definition, they + cannot have a `securityContext`. + + ## Configuration + + The policy doesn't have any configuration. diff --git a/src/lib.rs b/src/lib.rs index 4c7c6be..dcb43d5 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -1,25 +1,14 @@ -use lazy_static::lazy_static; - extern crate wapc_guest as guest; use guest::prelude::*; use k8s_openapi::api::core::v1 as apicore; extern crate kubewarden_policy_sdk as kubewarden; -use kubewarden::{logging, protocol_version_guest, request::ValidationRequest, validate_settings}; +use kubewarden::{protocol_version_guest, request::ValidationRequest, validate_settings}; mod settings; use settings::Settings; -use slog::{info, o, warn, Logger}; - -lazy_static! { - static ref LOG_DRAIN: Logger = Logger::root( - logging::KubewardenDrain::new(), - o!("policy" => "sample-policy") - ); -} - #[no_mangle] pub extern "C" fn wapc_init() { register_function("validate", validate); @@ -27,104 +16,191 @@ pub extern "C" fn wapc_init() { register_function("protocol_version", protocol_version_guest); } +#[derive(Debug, PartialEq)] +enum PolicyResponse { + Accept, + Reject(String), +} + fn validate(payload: &[u8]) -> CallResult { let validation_request: ValidationRequest = ValidationRequest::new(payload)?; - info!(LOG_DRAIN, "starting validation"); - - // TODO: you can unmarshal any Kubernetes API type you are interested in - match serde_json::from_value::(validation_request.request.object) { - Ok(pod) => { - // TODO: your logic goes here - if pod.metadata.name == Some("invalid-pod-name".to_string()) { - let pod_name = pod.metadata.name.unwrap(); - info!( - LOG_DRAIN, - "rejecting pod"; - "pod_name" => &pod_name - ); - kubewarden::reject_request( - Some(format!("pod name {} is not accepted", &pod_name)), - None, - ) - } else { - info!(LOG_DRAIN, "accepting resource"); - kubewarden::accept_request() - } - } - Err(_) => { - // TODO: handle as you wish - // We were forwarded a request we cannot unmarshal or - // understand, just accept it - warn!(LOG_DRAIN, "cannot unmarshal resource: this policy does not know how to evaluate this resource; accept it"); - kubewarden::accept_request() - } + let pod = match serde_json::from_value::(validation_request.request.object) { + Ok(pod) => pod, + Err(_) => return kubewarden::accept_request(), + }; + + match do_validate(&pod) { + PolicyResponse::Accept => kubewarden::accept_request(), + PolicyResponse::Reject(msg) => kubewarden::reject_request(Some(msg), None), + } +} + +fn do_validate(pod: &apicore::Pod) -> PolicyResponse { + if pod.spec.is_none() { + return PolicyResponse::Accept; + } + + let pod_spec = pod.spec.clone().unwrap(); + + let init_containers_do_not_have_readonly_filesystem = match pod_spec.init_containers { + Some(ic) => does_not_have_readonly_root_filesystem(&ic), + None => false, + }; + + let containers_have_readonly_disabled = + does_not_have_readonly_root_filesystem(&pod_spec.containers); + + let mut errors = vec![]; + if init_containers_do_not_have_readonly_filesystem { + errors.push("One of the init containers does not have readOnlyRootFilesystem enabled"); + } + if containers_have_readonly_disabled { + errors.push("One of the containers does not have readOnlyRootFilesystem enabled"); + } + + if errors.is_empty() { + PolicyResponse::Accept + } else { + PolicyResponse::Reject(errors.join(", ")) } } +fn does_not_have_readonly_root_filesystem(containers: &[apicore::Container]) -> bool { + containers + .iter() + .any(|c| match c.security_context.as_ref() { + Some(sc) => !sc.read_only_root_filesystem.unwrap_or_default(), + None => true, + }) +} + #[cfg(test)] mod tests { use super::*; - use kubewarden_policy_sdk::test::Testcase; - #[test] - fn accept_pod_with_valid_name() -> Result<(), ()> { - let request_file = "test_data/pod_creation.json"; - let tc = Testcase { - name: String::from("Valid name"), - fixture_file: String::from(request_file), - expected_validation_result: true, - settings: Settings {}, + fn accept_pod_with_container_with_readonly_root() { + let pod = apicore::Pod { + spec: Some(apicore::PodSpec { + containers: vec![apicore::Container { + name: "nginx".to_string(), + image: Some("nginx".to_string()), + security_context: Some(apicore::SecurityContext { + read_only_root_filesystem: Some(true), + ..apicore::SecurityContext::default() + }), + ..apicore::Container::default() + }], + ..apicore::PodSpec::default() + }), + ..apicore::Pod::default() }; - let res = tc.eval(validate).unwrap(); - assert!( - res.mutated_object.is_none(), - "Something mutated with test case: {}", - tc.name, - ); + let actual = do_validate(&pod); + assert_eq!(PolicyResponse::Accept, actual); + } - Ok(()) + #[test] + fn accept_pod_with_init_container_with_readonly_root() { + let pod = apicore::Pod { + spec: Some(apicore::PodSpec { + init_containers: Some(vec![apicore::Container { + name: "init".to_string(), + image: Some("alpine".to_string()), + security_context: Some(apicore::SecurityContext { + read_only_root_filesystem: Some(true), + ..apicore::SecurityContext::default() + }), + ..apicore::Container::default() + }]), + containers: vec![apicore::Container { + name: "nginx".to_string(), + image: Some("nginx".to_string()), + security_context: Some(apicore::SecurityContext { + read_only_root_filesystem: Some(true), + ..apicore::SecurityContext::default() + }), + ..apicore::Container::default() + }], + ..apicore::PodSpec::default() + }), + ..apicore::Pod::default() + }; + + let actual = do_validate(&pod); + assert_eq!(PolicyResponse::Accept, actual); } #[test] - fn reject_pod_with_invalid_name() -> Result<(), ()> { - let request_file = "test_data/pod_creation_invalid_name.json"; - let tc = Testcase { - name: String::from("Bad name"), - fixture_file: String::from(request_file), - expected_validation_result: false, - settings: Settings {}, + fn reject_pod_with_container_with_writable_root() { + let pod = apicore::Pod { + spec: Some(apicore::PodSpec { + containers: vec![ + apicore::Container { + name: "nginx".to_string(), + image: Some("nginx".to_string()), + security_context: Some(apicore::SecurityContext { + read_only_root_filesystem: Some(true), + ..apicore::SecurityContext::default() + }), + ..apicore::Container::default() + }, + apicore::Container { + name: "db".to_string(), + image: Some("mariadb".to_string()), + // no security_context means root fs is writable + ..apicore::Container::default() + }, + ], + ..apicore::PodSpec::default() + }), + ..apicore::Pod::default() }; - let res = tc.eval(validate).unwrap(); - assert!( - res.mutated_object.is_none(), - "Something mutated with test case: {}", - tc.name, + let actual = do_validate(&pod); + assert_eq!( + PolicyResponse::Reject( + "One of the containers does not have readOnlyRootFilesystem enabled".to_string() + ), + actual ); - - Ok(()) } #[test] - fn accept_request_with_non_pod_resource() -> Result<(), ()> { - let request_file = "test_data/ingress_creation.json"; - let tc = Testcase { - name: String::from("Ingress creation"), - fixture_file: String::from(request_file), - expected_validation_result: true, - settings: Settings {}, + fn reject_pod_with_init_container_with_writable_root() { + let pod = apicore::Pod { + spec: Some(apicore::PodSpec { + init_containers: Some(vec![apicore::Container { + name: "init".to_string(), + image: Some("alpine".to_string()), + security_context: Some(apicore::SecurityContext { + read_only_root_filesystem: Some(false), + ..apicore::SecurityContext::default() + }), + ..apicore::Container::default() + }]), + containers: vec![apicore::Container { + name: "nginx".to_string(), + image: Some("nginx".to_string()), + security_context: Some(apicore::SecurityContext { + read_only_root_filesystem: Some(true), + ..apicore::SecurityContext::default() + }), + ..apicore::Container::default() + }], + ..apicore::PodSpec::default() + }), + ..apicore::Pod::default() }; - let res = tc.eval(validate).unwrap(); - assert!( - res.mutated_object.is_none(), - "Something mutated with test case: {}", - tc.name, + let actual = do_validate(&pod); + assert_eq!( + PolicyResponse::Reject( + "One of the init containers does not have readOnlyRootFilesystem enabled" + .to_string() + ), + actual ); - - Ok(()) } } diff --git a/src/settings.rs b/src/settings.rs index 8aeb9cd..25edc09 100644 --- a/src/settings.rs +++ b/src/settings.rs @@ -1,19 +1,11 @@ -use crate::LOG_DRAIN; - use serde::{Deserialize, Serialize}; -use slog::info; -// Describe the settings your policy expects when -// loaded by the policy server. #[derive(Serialize, Deserialize, Default, Debug)] #[serde(default)] pub(crate) struct Settings {} impl kubewarden::settings::Validatable for Settings { fn validate(&self) -> Result<(), String> { - info!(LOG_DRAIN, "starting settings validation"); - - // TODO: perform settings validation if applies Ok(()) } } diff --git a/test_data/ingress_creation.json b/test_data/ingress_creation.json deleted file mode 100644 index 312639c..0000000 --- a/test_data/ingress_creation.json +++ /dev/null @@ -1,38 +0,0 @@ -{ - "kind": { - "group": "extensions", - "kind": "Ingress", - "version": "v1beta1" - }, - "object": { - "metadata": { - "name": "prod" - }, - "spec": { - "tls": [ - { - "hosts": ["some-host.com"], - "secretName": "secret-name-tls" - } - ], - "rules": [ - { - "host": "some-host.com", - "http": { - "paths": [ - { - "path": "/", - "backend": { - "service": { - "name": "service", - "port": 443 - } - } - } - ] - } - } - ] - } - } -} diff --git a/test_data/pod_creation.json b/test_data/pod_creation.json deleted file mode 100644 index b90992a..0000000 --- a/test_data/pod_creation.json +++ /dev/null @@ -1,32 +0,0 @@ -{ - "uid": "1299d386-525b-4032-98ae-1949f69f9cfc", - "kind": { - "kind": "Pod", - "version": "v1" - }, - "object": { - "metadata": { - "name": "nginx" - }, - "spec": { - "containers": [ - { - "image": "nginx", - "name": "nginx" - } - ] - } - }, - "operation": "CREATE", - "requestKind": { - "version": "v1", - "kind": "Pod" - }, - "userInfo": { - "username": "alice", - "uid": "alice-uid", - "groups": [ - "system:authenticated" - ] - } -} \ No newline at end of file diff --git a/test_data/pod_creation_invalid_name.json b/test_data/pod_creation_invalid_name.json deleted file mode 100644 index 005ea95..0000000 --- a/test_data/pod_creation_invalid_name.json +++ /dev/null @@ -1,32 +0,0 @@ -{ - "uid": "1299d386-525b-4032-98ae-1949f69f9cfc", - "kind": { - "kind": "Pod", - "version": "v1" - }, - "object": { - "metadata": { - "name": "invalid-pod-name" - }, - "spec": { - "containers": [ - { - "image": "nginx", - "name": "nginx" - } - ] - } - }, - "operation": "CREATE", - "requestKind": { - "version": "v1", - "kind": "Pod" - }, - "userInfo": { - "username": "alice", - "uid": "alice-uid", - "groups": [ - "system:authenticated" - ] - } -} \ No newline at end of file