From 2681fc7eb0890cd3ed4ad1a72143c037f0470c83 Mon Sep 17 00:00:00 2001 From: Saul Paredes Date: Mon, 29 Apr 2024 13:35:36 -0700 Subject: [PATCH] genpolicy: Add support for envFrom This change adds support for the `envFrom` field in the `Pod` resource Signed-off-by: Saul Paredes --- src/tools/genpolicy/src/config_map.rs | 25 ++++++ src/tools/genpolicy/src/pod.rs | 77 +++++++++++++++++++ src/tools/genpolicy/src/secret.rs | 27 +++++++ .../kubernetes/k8s-policy-pod.bats | 15 +++- .../k8s-policy-configmap.yaml | 1 + .../k8s-policy-pod.yaml | 3 + 6 files changed, 147 insertions(+), 1 deletion(-) diff --git a/src/tools/genpolicy/src/config_map.rs b/src/tools/genpolicy/src/config_map.rs index ac84c623b7..d4468a46b2 100644 --- a/src/tools/genpolicy/src/config_map.rs +++ b/src/tools/genpolicy/src/config_map.rs @@ -64,6 +64,19 @@ impl ConfigMap { None } + + pub fn get_key_value_pairs(&self) -> Option> { + //eg ["key1=value1", "key2=value2"] + self.data + .as_ref()? + .keys() + .map(|key| { + let value = self.data.as_ref().unwrap().get(key).unwrap(); + format!("{key}={value}") + }) + .collect::>() + .into() + } } pub fn get_value(value_from: &pod::EnvVarSource, config_maps: &Vec) -> Option { @@ -76,6 +89,18 @@ pub fn get_value(value_from: &pod::EnvVarSource, config_maps: &Vec) - None } +pub fn get_values(config_map_name: &str, config_maps: &Vec) -> Option> { + for config_map in config_maps { + if let Some(existing_configmap_name) = &config_map.metadata.name { + if config_map_name == existing_configmap_name { + return config_map.get_key_value_pairs(); + } + } + } + + None +} + #[async_trait] impl yaml::K8sResource for ConfigMap { async fn init( diff --git a/src/tools/genpolicy/src/pod.rs b/src/tools/genpolicy/src/pod.rs index 16cd9c517a..f91787e348 100644 --- a/src/tools/genpolicy/src/pod.rs +++ b/src/tools/genpolicy/src/pod.rs @@ -122,6 +122,9 @@ pub struct Container { #[serde(skip_serializing_if = "Option::is_none")] env: Option>, + #[serde(skip_serializing_if = "Option::is_none")] + envFrom: Option>, + #[serde(skip_serializing_if = "Option::is_none")] resources: Option, @@ -416,6 +419,37 @@ pub struct ConfigMapKeySelector { optional: Option, } +/// See Reference / Kubernetes API / Workload Resources / Pod. +#[derive(Clone, Debug, Serialize, Deserialize)] +pub struct EnvFromSource { + #[serde(skip_serializing_if = "Option::is_none")] + pub configMapRef: Option, + + #[serde(skip_serializing_if = "Option::is_none")] + pub secretRef: Option, + + #[serde(skip_serializing_if = "Option::is_none")] + pub prefix: Option, +} + +/// See Reference / Kubernetes API / Workload Resources / Pod. +#[derive(Clone, Debug, Serialize, Deserialize)] +pub struct SecretEnvSource { + pub name: String, + + #[serde(skip_serializing_if = "Option::is_none")] + optional: Option, +} + +/// See Reference / Kubernetes API / Workload Resources / Pod. +#[derive(Clone, Debug, Serialize, Deserialize)] +pub struct ConfigMapEnvSource { + pub name: String, + + #[serde(skip_serializing_if = "Option::is_none")] + optional: Option, +} + /// See Reference / Kubernetes API / Common Definitions / ResourceFieldSelector. #[derive(Clone, Debug, Serialize, Deserialize)] struct ResourceFieldSelector { @@ -571,6 +605,18 @@ impl Container { } } } + + if let Some(env_from_sources) = &self.envFrom { + for env_from_source in env_from_sources { + let env_from_source_values = env_from_source.get_values(config_maps, secrets); + + for value in env_from_source_values { + if !dest_env.contains(&value) { + dest_env.push(value.clone()); + } + } + } + } } pub fn is_privileged(&self) -> bool { @@ -652,6 +698,37 @@ impl Container { } } +impl EnvFromSource { + pub fn get_values( + &self, + config_maps: &Vec, + secrets: &Vec, + ) -> Vec { + if let Some(config_map_env_source) = &self.configMapRef { + if let Some(value) = config_map::get_values(&config_map_env_source.name, config_maps) { + return value.clone(); + } else { + panic!( + "Couldn't get values from configmap ref: {}", + &config_map_env_source.name + ); + } + } + + if let Some(secret_env_source) = &self.secretRef { + if let Some(value) = secret::get_values(&secret_env_source.name, secrets) { + return value.clone(); + } else { + panic!( + "Couldn't get values from secret ref: {}", + &secret_env_source.name + ); + } + } + panic!("envFrom: no configmap or secret source found!"); + } +} + impl EnvVar { pub fn get_value( &self, diff --git a/src/tools/genpolicy/src/secret.rs b/src/tools/genpolicy/src/secret.rs index 7b870886d7..17afdc253c 100644 --- a/src/tools/genpolicy/src/secret.rs +++ b/src/tools/genpolicy/src/secret.rs @@ -58,6 +58,21 @@ impl Secret { None } + + pub fn get_key_value_pairs(&self) -> Option> { + //eg ["key1=secret1", "key2=secret2"] + self.data + .as_ref()? + .keys() + .map(|key| { + let value = self.data.as_ref().unwrap().get(key).unwrap(); + let value_bytes = general_purpose::STANDARD.decode(value).unwrap(); + let value_string = std::str::from_utf8(&value_bytes).unwrap(); + format!("{key}={value_string}") + }) + .collect::>() + .into() + } } pub fn get_value(value_from: &pod::EnvVarSource, secrets: &Vec) -> Option { @@ -70,6 +85,18 @@ pub fn get_value(value_from: &pod::EnvVarSource, secrets: &Vec) -> Optio None } +pub fn get_values(secret_name: &str, secrets: &Vec) -> Option> { + for secret in secrets { + if let Some(existing_secret_name) = &secret.metadata.name { + if existing_secret_name == secret_name { + return secret.get_key_value_pairs(); + } + } + } + + None +} + #[async_trait] impl yaml::K8sResource for Secret { async fn init(&mut self, _config: &Config, doc_mapping: &serde_yaml::Value, _silent: bool) { diff --git a/tests/integration/kubernetes/k8s-policy-pod.bats b/tests/integration/kubernetes/k8s-policy-pod.bats index 0e78a23d22..0ab4eda083 100644 --- a/tests/integration/kubernetes/k8s-policy-pod.bats +++ b/tests/integration/kubernetes/k8s-policy-pod.bats @@ -15,6 +15,11 @@ setup() { pod_name="policy-pod" get_pod_config_dir + policy_settings_dir="$(create_tmp_policy_settings_dir "${pod_config_dir}")" + + exec_command="printenv data-3" + add_exec_to_policy_settings "${policy_settings_dir}" "${exec_command}" + add_requests_to_policy_settings "${policy_settings_dir}" "ReadStreamRequest" correct_configmap_yaml="${pod_config_dir}/k8s-policy-configmap.yaml" pre_generate_configmap_yaml="${pod_config_dir}/k8s-policy-configmap-pre-generation.yaml" @@ -34,7 +39,7 @@ setup() { cp "${correct_pod_yaml}" "${pre_generate_pod_yaml}" # Add policy to the correct pod yaml file - auto_generate_policy "${pod_config_dir}" "${correct_pod_yaml}" "${correct_configmap_yaml}" + auto_generate_policy "${policy_settings_dir}" "${correct_pod_yaml}" "${correct_configmap_yaml}" fi # Start each test case with a copy of the correct yaml files. @@ -57,6 +62,14 @@ wait_for_pod_ready() { wait_for_pod_ready } +@test "Able to read env variables sourced from configmap using envFrom" { + kubectl create -f "${correct_configmap_yaml}" + kubectl create -f "${correct_pod_yaml}" + kubectl wait --for=condition=Ready "--timeout=${timeout}" pod "${pod_name}" + expected_env_var=$(kubectl exec "${pod_name}" -- printenv data-3) + [ "$expected_env_var" = "value-3" ] || fail "expected_env_var is not equal to value-3" +} + @test "Successful pod with auto-generated policy and runtimeClassName filter" { runtime_class_name=$(yq ".spec.runtimeClassName" < "${testcase_pre_generate_pod_yaml}") diff --git a/tests/integration/kubernetes/runtimeclass_workloads/k8s-policy-configmap.yaml b/tests/integration/kubernetes/runtimeclass_workloads/k8s-policy-configmap.yaml index 25f2fe0ecd..62818439f1 100644 --- a/tests/integration/kubernetes/runtimeclass_workloads/k8s-policy-configmap.yaml +++ b/tests/integration/kubernetes/runtimeclass_workloads/k8s-policy-configmap.yaml @@ -10,3 +10,4 @@ metadata: data: data-1: value-1 data-2: value-2 + data-3: value-3 diff --git a/tests/integration/kubernetes/runtimeclass_workloads/k8s-policy-pod.yaml b/tests/integration/kubernetes/runtimeclass_workloads/k8s-policy-pod.yaml index c220731b82..a25660e740 100644 --- a/tests/integration/kubernetes/runtimeclass_workloads/k8s-policy-pod.yaml +++ b/tests/integration/kubernetes/runtimeclass_workloads/k8s-policy-pod.yaml @@ -49,6 +49,9 @@ spec: - echo - startupProbe - test + envFrom: + - configMapRef: + name: policy-configmap topologySpreadConstraints: - maxSkew: 2 topologyKey: kubernetes.io/hostname