Skip to content

Commit

Permalink
Merge pull request kata-containers#11 from mingweishih/kubctl_dryrun
Browse files Browse the repository at this point in the history
Get kubernetes rules via dry_run
  • Loading branch information
danmihai1 committed Feb 1, 2023
2 parents 3878fab + 00d2e05 commit c83f164
Show file tree
Hide file tree
Showing 4 changed files with 156 additions and 92 deletions.
103 changes: 72 additions & 31 deletions tools/cc-policy/src/kubernetes.rs
Original file line number Diff line number Diff line change
@@ -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
Expand All @@ -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<serde_yaml::Value> {
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<serde_yaml::Value> {
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<Spec> {
let mut spec: Spec = serde_json::from_str("{}")?;

Expand Down Expand Up @@ -43,36 +114,6 @@ fn get_container_rules() -> Result<Spec> {

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

Expand Down
44 changes: 25 additions & 19 deletions tools/cc-policy/src/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ mod oci;
mod pod_yaml;
mod policy;

use kubernetes::KubeCtl;
use pod_yaml::*;
use policy::*;

Expand All @@ -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<PathBuf>,
#[clap(long = "image_ref")]
image_ref: Option<String>,
#[clap(short = 'o', long = "output")]
output_yaml: Option<PathBuf>,
#[clap(short = 'p', long = "policy")]
output_policy: Option<PathBuf>,
#[clap(long = "with_default_rules")]
with_default_rules: bool,
#[clap(short = 'v', long = "verbose")]
Expand Down Expand Up @@ -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());
Expand Down Expand Up @@ -104,24 +109,25 @@ 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");
}

let policy;
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 {
Expand All @@ -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(())
Expand Down
75 changes: 46 additions & 29 deletions tools/cc-policy/src/pod_yaml.rs
Original file line number Diff line number Diff line change
@@ -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
Expand Down Expand Up @@ -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()
Expand Down Expand Up @@ -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)
}
}
Expand Down
Loading

0 comments on commit c83f164

Please sign in to comment.