genpolicy: Add support for envFrom

This change adds support for the `envFrom` field in the `Pod` resource

Signed-off-by: Saul Paredes <saulparedes@microsoft.com>
This commit is contained in:
Saul Paredes 2024-04-29 13:35:36 -07:00
parent fc4357f642
commit 2681fc7eb0
6 changed files with 147 additions and 1 deletions

View File

@ -64,6 +64,19 @@ impl ConfigMap {
None
}
pub fn get_key_value_pairs(&self) -> Option<Vec<String>> {
//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::<Vec<String>>()
.into()
}
}
pub fn get_value(value_from: &pod::EnvVarSource, config_maps: &Vec<ConfigMap>) -> Option<String> {
@ -76,6 +89,18 @@ pub fn get_value(value_from: &pod::EnvVarSource, config_maps: &Vec<ConfigMap>) -
None
}
pub fn get_values(config_map_name: &str, config_maps: &Vec<ConfigMap>) -> Option<Vec<String>> {
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(

View File

@ -122,6 +122,9 @@ pub struct Container {
#[serde(skip_serializing_if = "Option::is_none")]
env: Option<Vec<EnvVar>>,
#[serde(skip_serializing_if = "Option::is_none")]
envFrom: Option<Vec<EnvFromSource>>,
#[serde(skip_serializing_if = "Option::is_none")]
resources: Option<ResourceRequirements>,
@ -416,6 +419,37 @@ pub struct ConfigMapKeySelector {
optional: Option<bool>,
}
/// 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<ConfigMapEnvSource>,
#[serde(skip_serializing_if = "Option::is_none")]
pub secretRef: Option<SecretEnvSource>,
#[serde(skip_serializing_if = "Option::is_none")]
pub prefix: Option<String>,
}
/// 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<bool>,
}
/// 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<bool>,
}
/// 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<config_map::ConfigMap>,
secrets: &Vec<secret::Secret>,
) -> Vec<String> {
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,

View File

@ -58,6 +58,21 @@ impl Secret {
None
}
pub fn get_key_value_pairs(&self) -> Option<Vec<String>> {
//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::<Vec<String>>()
.into()
}
}
pub fn get_value(value_from: &pod::EnvVarSource, secrets: &Vec<Secret>) -> Option<String> {
@ -70,6 +85,18 @@ pub fn get_value(value_from: &pod::EnvVarSource, secrets: &Vec<Secret>) -> Optio
None
}
pub fn get_values(secret_name: &str, secrets: &Vec<Secret>) -> Option<Vec<String>> {
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) {

View File

@ -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}")

View File

@ -10,3 +10,4 @@ metadata:
data:
data-1: value-1
data-2: value-2
data-3: value-3

View File

@ -49,6 +49,9 @@ spec:
- echo
- startupProbe
- test
envFrom:
- configMapRef:
name: policy-configmap
topologySpreadConstraints:
- maxSkew: 2
topologyKey: kubernetes.io/hostname