diff --git a/tools/cc-policy/src/kubernetes.rs b/tools/cc-policy/src/kubernetes.rs index 3b65e656dce2..5be604f231c3 100644 --- a/tools/cc-policy/src/kubernetes.rs +++ b/tools/cc-policy/src/kubernetes.rs @@ -1,8 +1,12 @@ // Copyright (c) Microsoft Corporation. // Licensed under the Apache 2.0 license. -use anyhow::Result; +use anyhow::{bail, Result}; +use checked_command::{CheckedCommand, Error}; use oci_spec::runtime::{Process, Spec}; +use std::path::PathBuf; + +const KUBECTL: &str = "kubectl"; // The default image version of the pause container is based // on https://github.com/kubernetes/kubernetes/blob/release-1.23/cmd/kubeadm/app/constants/constants.go#L415 @@ -12,6 +16,73 @@ pub const KUBERNETES_PAUSE_VERSION: &str = "3.6"; pub const KUBERNETES_PAUSE_NAME: &str = "pause"; pub const KUBERNETES_REGISTRY: &str = "registry.k8s.io"; +pub struct KubeCtl; + +impl KubeCtl { + pub fn get_config_map(name: &str) -> Result { + let output = match CheckedCommand::new(KUBECTL) + .arg("get") + .arg("configmap") + .arg(name) + .arg("-o") + .arg("yaml") + .output() + { + Ok(result) => String::from_utf8(result.stdout)?, + Err(Error::Failure(ex, output)) => { + println!("failed with exit code: {:?}", ex.code()); + if let Some(output) = output { + bail!( + "{}: kubectl failed: {}", + loc!(), + String::from_utf8_lossy(&*output.stderr) + ); + } + bail!("{}", loc!()); + } + Err(Error::Io(io_err)) => { + bail!("{}: unexpected I/O error: {:?}", loc!(), io_err); + } + }; + + let config_map: serde_yaml::Value = serde_yaml::from_str(&output)?; + + Ok(config_map) + } + + pub fn get_yaml_with_dry_run_server(file: &PathBuf) -> Result { + let output = match CheckedCommand::new(KUBECTL) + .arg("apply") + .arg("-f") + .arg(file) + .arg("--dry-run=server") + .arg("-o") + .arg("yaml") + .output() + { + Ok(result) => String::from_utf8(result.stdout)?, + Err(Error::Failure(ex, output)) => { + println!("failed with exit code: {:?}", ex.code()); + if let Some(output) = output { + bail!( + "{}: kubectl failed: {}", + loc!(), + String::from_utf8_lossy(&*output.stderr) + ); + } + bail!("{}", loc!()); + } + Err(Error::Io(io_err)) => { + bail!("{}: unexpected I/O error: {:?}", loc!(), io_err); + } + }; + + let pod_yaml: serde_yaml::Value = serde_yaml::from_str(&output)?; + + Ok(pod_yaml) + } +} + fn get_container_rules() -> Result { let mut spec: Spec = serde_json::from_str("{}")?; @@ -43,36 +114,6 @@ fn get_container_rules() -> Result { spec.set_process(Some(process)); - // TODO: Add reference - let mounts = serde_json::from_str( - r#" - [ - { - "destination": "/dev/termination-log", - "source": "^/run/kata-containers/shared/containers/[a-z0-9]+-[a-z0-9]+-termination-log$", - "type": "bind", - "options": [ - "rbind", - "rprivate", - "rw" - ] - }, - { - "destination": "/var/run/secrets/kubernetes.io/serviceaccount", - "source": "^/run/kata-containers/shared/containers/[a-z0-9]+-[a-z0-9]+-serviceaccount$", - "type": "bind", - "options": [ - "rbind", - "rprivate", - "ro" - ] - } - ] - "#, - )?; - - spec.set_mounts(Some(mounts)); - Ok(spec) } diff --git a/tools/cc-policy/src/main.rs b/tools/cc-policy/src/main.rs index 16239520d1f3..3f2ca779390b 100644 --- a/tools/cc-policy/src/main.rs +++ b/tools/cc-policy/src/main.rs @@ -10,6 +10,7 @@ mod oci; mod pod_yaml; mod policy; +use kubernetes::KubeCtl; use pod_yaml::*; use policy::*; @@ -24,14 +25,14 @@ use serde::{Deserialize, Serialize}; #[derive(Parser)] struct Cli { - #[clap(short = 'i', long = "input", default_value = "")] - input_yaml: PathBuf, - #[clap(long = "image_ref", default_value = "")] - image_ref: String, - #[clap(short = 'o', long = "output", default_value = "")] - output_yaml: PathBuf, - #[clap(short = 'p', long = "policy", default_value = "")] - output_policy: PathBuf, + #[clap(short = 'i', long = "input")] + input_yaml: Option, + #[clap(long = "image_ref")] + image_ref: Option, + #[clap(short = 'o', long = "output")] + output_yaml: Option, + #[clap(short = 'p', long = "policy")] + output_policy: Option, #[clap(long = "with_default_rules")] with_default_rules: bool, #[clap(short = 'v', long = "verbose")] @@ -63,10 +64,14 @@ fn create_and_inject_policy( let mut policy_list = Vec::new(); let mut policy_base64_list = Vec::new(); + let yaml_from_dry_run = KubeCtl::get_yaml_with_dry_run_server(path)?; + for doc in serde_yaml::Deserializer::from_str(yaml.as_str()) { let mut yaml = serde_yaml::Value::deserialize(doc)?; - if let Ok((kind, policy, policy_base64)) = get_policy_from_yaml(&yaml, with_default_rules) { + if let Ok((kind, policy, policy_base64)) = + get_policy_from_yaml(&yaml_from_dry_run, with_default_rules) + { patch_yaml(&mut yaml, &kind, &policy_base64)?; policy_list.push(policy.clone()); policy_base64_list.push(policy_base64.clone()); @@ -104,11 +109,11 @@ fn write_to_file(data: &str, path: &PathBuf) -> Result<()> { fn main() -> Result<()> { let args = Cli::parse(); - if args.input_yaml.as_os_str().is_empty() && args.image_ref.is_empty() { + if args.input_yaml == None && args.image_ref == None { bail!("Please specify either input_yaml or image_ref"); } - if !args.input_yaml.as_os_str().is_empty() && !args.image_ref.is_empty() { + if args.input_yaml != None && args.image_ref != None { bail!("Cannot specify input_yaml and image_ref at the same time"); } @@ -116,12 +121,13 @@ fn main() -> Result<()> { let policy_encoded; let mut patched_yaml = String::new(); - if !args.input_yaml.as_os_str().is_empty() { + if let Some(input_yaml) = args.input_yaml { (policy, policy_encoded, patched_yaml) = - create_and_inject_policy(&args.input_yaml, args.with_default_rules)?; + create_and_inject_policy(&input_yaml, args.with_default_rules)?; + } else if let Some(image_ref) = args.image_ref { + (policy, policy_encoded) = create_policy_by_image_ref(&image_ref, args.with_default_rules)?; } else { - (policy, policy_encoded) = - create_policy_by_image_ref(&args.image_ref, args.with_default_rules)?; + unreachable!(); } if args.verbose { @@ -130,12 +136,12 @@ fn main() -> Result<()> { println!("Encoding size: {}", policy_encoded.len()); } - if !args.output_policy.as_os_str().is_empty() { - write_to_file(&policy, &args.output_policy)?; + if let Some(output_policy) = args.output_policy { + write_to_file(&policy, &output_policy)?; } - if !args.output_yaml.as_os_str().is_empty() { - write_to_file(&patched_yaml, &args.output_yaml)?; + if let Some(output_yaml) = args.output_yaml { + write_to_file(&patched_yaml, &output_yaml)?; } Ok(()) diff --git a/tools/cc-policy/src/pod_yaml.rs b/tools/cc-policy/src/pod_yaml.rs index 7428ecd00651..54465b331e1b 100644 --- a/tools/cc-policy/src/pod_yaml.rs +++ b/tools/cc-policy/src/pod_yaml.rs @@ -1,15 +1,14 @@ // Copyright (c) Microsoft Corporation. // Licensed under the Apache 2.0 license. +use crate::kubernetes::KubeCtl; + use anyhow::{anyhow, bail, Result}; -use checked_command::{CheckedCommand, Error}; use oci_spec::runtime::Mount; use serde::{Deserialize, Serialize}; use std::collections::HashMap; use std::path::PathBuf; -const KUBECTL: &str = "kubectl"; - const CC_POLICY_KEY: &str = "io.katacontainers.cc_policy"; // Supported keys used by valueFrom and EnvFrom @@ -159,32 +158,7 @@ impl<'input> PodYaml<'input> { .as_str() .ok_or_else(|| anyhow!("failed to parse key into str"))?; - let output = match CheckedCommand::new(KUBECTL) - .arg("get") - .arg("configmap") - .arg(name) - .arg("-o") - .arg("yaml") - .output() - { - Ok(result) => String::from_utf8(result.stdout)?, - Err(Error::Failure(ex, output)) => { - println!("failed with exit code: {:?}", ex.code()); - if let Some(output) = output { - bail!( - "{}: kubectl failed: {}", - loc!(), - String::from_utf8_lossy(&*output.stderr) - ); - } - bail!("{}", loc!()); - } - Err(Error::Io(io_err)) => { - bail!("{}: unexpected I/O error: {:?}", loc!(), io_err); - } - }; - - let config_map: serde_yaml::Value = serde_yaml::from_str(&output)?; + let config_map: serde_yaml::Value = KubeCtl::get_config_map(name)?; let data = config_map["data"] .as_mapping() @@ -465,6 +439,49 @@ impl<'input> PodYaml<'input> { } } + // Add the termination message mount based on + // https://github.com/kubernetes/kubernetes/blob/release-1.23/pkg/kubelet/kuberuntime/kuberuntime_container.go#L400 + if let Some(path) = container.get("terminationMessagePath") { + let path = path + .as_str() + .ok_or_else(|| anyhow!("failed to parse terminationMessagePath into string"))?; + + if let Some(policy) = container.get("terminationMessagePolicy") { + let policy = policy.as_str().ok_or_else(|| { + anyhow!("failed to parse terminationMessagePolicy into string") + })?; + + // TODO: Check this + // Ensure the terminationMessagePath is File + if policy == "File" { + let path = PathBuf::from(path); + let file_name = path.as_path().file_name().unwrap(); + let file_name = file_name.to_str().unwrap(); + + let mut mount = Mount::default(); + + mount.set_destination(path.clone()); + mount.set_typ(Some("bind".to_string())); + mount.set_source(Some(PathBuf::from( + [ + "^/run/kata-containers/shared/containers/[a-z0-9]+-[a-z0-9]+-", + file_name, + "$", + ] + .concat(), + ))); + mount.set_options(Some( + vec!["rbind", "rprivate", "rw"] + .into_iter() + .map(String::from) + .collect(), + )); + + results.push(mount); + } + } + } + Ok(results) } } diff --git a/tools/cc-policy/src/policy.rs b/tools/cc-policy/src/policy.rs index 56a997fdfcff..0274c138b236 100644 --- a/tools/cc-policy/src/policy.rs +++ b/tools/cc-policy/src/policy.rs @@ -133,7 +133,7 @@ impl ContainerPolicy { } else { empty_spec()? }; - let kube_rules = kubernetes::get_rules(false)?; + let k8s_rules = kubernetes::get_rules(false)?; let image_name = container["image"] .as_str() .ok_or_else(|| anyhow!("failed to parse image into string"))?; @@ -141,14 +141,14 @@ impl ContainerPolicy { let image_config = pull_image_config(image_name)?; //let allow_elevated = security_context.allow_elevated; - Self::get_process(&mut oci_spec, container, &image_config, &kube_rules)?; + Self::get_process(&mut oci_spec, container, &image_config, &k8s_rules)?; Self::get_mounts( &mut oci_spec, Some(pod_yaml), container, &image_config, - &kube_rules, + &k8s_rules, )?; let custom = Some(Custom { layers }); @@ -205,7 +205,7 @@ impl ContainerPolicy { spec: &Spec, container: &serde_yaml::Value, image_config: &ImageConfiguration, - kube_rules: &Spec, + k8s_rules: &Spec, ) -> Result> { // Override rule: the latter variables will override the former ones with the same name // Order based on the CRI: @@ -222,15 +222,15 @@ impl ContainerPolicy { } } - let mut kube_envs = Vec::new(); + let mut k8s_envs = Vec::new(); - if let Some(process) = kube_rules.process() { + if let Some(process) = k8s_rules.process() { if let Some(envs) = process.env() { - kube_envs = envs.clone(); + k8s_envs = envs.clone(); } } - merge_process_env(&mut results, &kube_envs)?; + merge_process_env(&mut results, &k8s_envs)?; let image_envs = image::get_env(image_config)?; @@ -247,7 +247,7 @@ impl ContainerPolicy { spec: &mut Spec, container: &serde_yaml::Value, image_config: &ImageConfiguration, - kube_rules: &Spec, + k8s_rules: &Spec, ) -> Result<()> { let (working_dir, command, args) = PodYaml::get_entry_point(container)?; @@ -270,7 +270,7 @@ impl ContainerPolicy { process.set_cwd(cwd); } - let env = Self::get_env(spec, container, image_config, kube_rules)?; + let env = Self::get_env(spec, container, image_config, k8s_rules)?; process.set_args(Some(args)); process.set_env(Some(env)); @@ -285,7 +285,7 @@ impl ContainerPolicy { pod_yaml: Option<&PodYaml>, container: &serde_yaml::Value, image_config: &ImageConfiguration, - kube_rules: &Spec, + k8s_rules: &Spec, ) -> Result<()> { let pod_mounts = if let Some(pod_yaml) = pod_yaml { pod_yaml.get_mounts(container)? @@ -300,8 +300,8 @@ impl ContainerPolicy { // - Default mounts let image_volumes = get_image_volume_mounts(image_config)?; - let results = if let Some(kube_mounts) = kube_rules.mounts() { - merge_mounts(&pod_mounts, kube_mounts)? + let results = if let Some(k8s_mounts) = k8s_rules.mounts() { + merge_mounts(&pod_mounts, k8s_mounts)? } else { pod_mounts };