From 5714615b80198fb915930dec91601141e4e7c518 Mon Sep 17 00:00:00 2001 From: yukang Date: Fri, 1 Oct 2021 20:44:10 +0800 Subject: [PATCH 1/6] add rootless option for spec --- Cargo.lock | 7 +++ Cargo.toml | 1 + src/commands/spec_json.rs | 98 +++++++++++++++++++++++++++++++++++++-- 3 files changed, 101 insertions(+), 5 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index e04c26b2a..e6ba87053 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -948,6 +948,12 @@ dependencies = [ "winapi 0.3.9", ] +[[package]] +name = "path-clean" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ecba01bf2678719532c5e3059e0b5f0811273d94b397088b82e3bd0a78c78fdd" + [[package]] name = "pentacle" version = "1.0.0" @@ -1529,6 +1535,7 @@ dependencies = [ "nix", "oci-spec", "once_cell", + "path-clean", "pentacle", "prctl", "procfs", diff --git a/Cargo.toml b/Cargo.toml index 05803330f..2ddd571f1 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -51,6 +51,7 @@ fastrand = "1.4.1" crossbeam-channel = "0.5" seccomp = { version = "0.1.0", path = "./seccomp" } pentacle = "1.0.0" +path-clean = "0.1.0" [dev-dependencies] # TODO: Fetch from crate.io instead of git when next release oci-spec-rs diff --git a/src/commands/spec_json.rs b/src/commands/spec_json.rs index a9c124306..26e5c03f8 100644 --- a/src/commands/spec_json.rs +++ b/src/commands/spec_json.rs @@ -1,18 +1,106 @@ use anyhow::Result; use clap::Clap; -use oci_spec::runtime::Spec; +use std::path::PathBuf; + +use nix; +use oci_spec::runtime::{ + LinuxBuilder, LinuxIdMappingBuilder, LinuxNamespaceBuilder, LinuxNamespaceType, MountBuilder, + Spec, SpecBuilder, +}; +use path_clean; use serde_json::to_writer_pretty; use std::fs::File; - -/// Create a new runtime specification +/// Command generates a config.json #[derive(Clap, Debug)] -pub struct SpecJson; +pub struct SpecJson { + /// Generate a configuration for a rootless container + #[clap(long)] + pub rootless: bool, +} + +pub fn set_for_rootless(spec: &Spec) -> Result { + let uid = nix::unistd::geteuid().as_raw(); + let gid = nix::unistd::getegid().as_raw(); + + // Remove network from the default spec + let mut namespaces = vec![]; + for ns in spec + .linux() + .as_ref() + .unwrap() + .namespaces() + .as_ref() + .unwrap() + .iter() + { + if ns.typ() != LinuxNamespaceType::Network && ns.typ() != LinuxNamespaceType::User { + namespaces.push(ns.clone()); + } + } + // Add user namespace + namespaces.push( + LinuxNamespaceBuilder::default() + .typ(LinuxNamespaceType::User) + .build()?, + ); + let linux_builder = LinuxBuilder::default() + .namespaces(namespaces) + .uid_mappings(vec![LinuxIdMappingBuilder::default() + .host_id(uid) + .container_id(0_u32) + .size(1_u32) + .build()?]) + .gid_mappings(vec![LinuxIdMappingBuilder::default() + .host_id(gid) + .container_id(0_u32) + .size(1_u32) + .build()?]); + + // Fix the mounts + let mut mounts = vec![]; + for mount in spec.mounts().as_ref().unwrap().iter() { + let dest = mount.destination().clone(); + if path_clean::clean(dest.as_path().to_str().unwrap()) == "/sys" { + let mount = MountBuilder::default() + .destination(PathBuf::from("/sys")) + .source(PathBuf::from("/sys")) + .typ("none".to_string()) + .options(vec![ + "rbind".to_string(), + "nosuid".to_string(), + "noexec".to_string(), + "nodev".to_string(), + "ro".to_string(), + ]) + .build()?; + mounts.push(mount); + } else { + let options: Vec = mount + .options() + .as_ref() + .unwrap_or(&vec![]) + .iter() + .filter(|&o| !o.starts_with("gid=") && !o.starts_with("uid=")) + .map(|o| o.to_string()) + .collect(); + let mount_builder = MountBuilder::default().options(options); + mounts.push(mount_builder.build()?); + } + } + let spec_builder = SpecBuilder::default() + .linux(linux_builder.build()?) + .mounts(mounts); + Ok(spec_builder.build()?) +} /// spec Cli command impl SpecJson { pub fn exec(&self) -> Result<()> { // get default values for Spec - let default_json: Spec = Default::default(); + let mut default_json: Spec = Default::default(); + if self.rootless { + default_json = set_for_rootless(&default_json)? + }; // write data to config.json to_writer_pretty(&File::create("config.json")?, &default_json)?; Ok(()) From c9c102f0bfc6b7efbd663a6129b3bb6433266849 Mon Sep 17 00:00:00 2001 From: yukang Date: Sat, 2 Oct 2021 11:21:49 +0800 Subject: [PATCH 2/6] code refactor --- src/commands/spec_json.rs | 27 ++++++++++++++------------- 1 file changed, 14 insertions(+), 13 deletions(-) diff --git a/src/commands/spec_json.rs b/src/commands/spec_json.rs index 26e5c03f8..6630cf498 100644 --- a/src/commands/spec_json.rs +++ b/src/commands/spec_json.rs @@ -1,15 +1,14 @@ use anyhow::Result; use clap::Clap; -use std::path::PathBuf; - use nix; use oci_spec::runtime::{ - LinuxBuilder, LinuxIdMappingBuilder, LinuxNamespaceBuilder, LinuxNamespaceType, MountBuilder, - Spec, SpecBuilder, + Linux, LinuxBuilder, LinuxIdMappingBuilder, LinuxNamespace, LinuxNamespaceBuilder, + LinuxNamespaceType, MountBuilder, Spec, SpecBuilder, }; use path_clean; use serde_json::to_writer_pretty; use std::fs::File; +use std::path::PathBuf; /// Command generates a config.json #[derive(Clap, Debug)] pub struct SpecJson { @@ -23,20 +22,20 @@ pub fn set_for_rootless(spec: &Spec) -> Result { let gid = nix::unistd::getegid().as_raw(); // Remove network from the default spec - let mut namespaces = vec![]; - for ns in spec + let mut namespaces: Vec = spec .linux() .as_ref() - .unwrap() + .unwrap_or(&Linux::default()) .namespaces() .as_ref() - .unwrap() + .unwrap_or(&vec![]) .iter() - { - if ns.typ() != LinuxNamespaceType::Network && ns.typ() != LinuxNamespaceType::User { - namespaces.push(ns.clone()); - } - } + .filter(|&ns| { + ns.typ() != LinuxNamespaceType::Network && ns.typ() != LinuxNamespaceType::User + }) + .map(|ns| ns.clone()) + .collect(); + // Add user namespace namespaces.push( LinuxNamespaceBuilder::default() @@ -60,6 +59,8 @@ pub fn set_for_rootless(spec: &Spec) -> Result { let mut mounts = vec![]; for mount in spec.mounts().as_ref().unwrap().iter() { let dest = mount.destination().clone(); + // Use path_clean to reduce multiple slashes to a single slash + // and take care of '..' and '.' in dest path. if path_clean::clean(dest.as_path().to_str().unwrap()) == "/sys" { let mount = MountBuilder::default() .destination(PathBuf::from("/sys")) From 0847d1711e0a64434af94dadb38ca3b53dddcca2 Mon Sep 17 00:00:00 2001 From: yukang Date: Sat, 2 Oct 2021 11:36:26 +0800 Subject: [PATCH 3/6] add testcase --- src/commands/spec_json.rs | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/src/commands/spec_json.rs b/src/commands/spec_json.rs index 6630cf498..ddebbb18b 100644 --- a/src/commands/spec_json.rs +++ b/src/commands/spec_json.rs @@ -107,3 +107,17 @@ impl SpecJson { Ok(()) } } + +#[cfg(test)] +// Tests become unstable if not serial. The cause is not known. +mod tests { + use super::*; + + #[test] + fn test_spec_json() -> Result<()> { + let mut spec = Default::default(); + spec = set_for_rootless(&spec)?; + to_writer_pretty(&File::create("config.json")?, &spec)?; + Ok(()) + } +} From 2d0c6b0202d7856aa9a2d8d325076796ecce10e1 Mon Sep 17 00:00:00 2001 From: yukang Date: Sat, 2 Oct 2021 11:39:04 +0800 Subject: [PATCH 4/6] fix clippy --- src/commands/spec_json.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/commands/spec_json.rs b/src/commands/spec_json.rs index ddebbb18b..d9d9b1f9a 100644 --- a/src/commands/spec_json.rs +++ b/src/commands/spec_json.rs @@ -33,7 +33,7 @@ pub fn set_for_rootless(spec: &Spec) -> Result { .filter(|&ns| { ns.typ() != LinuxNamespaceType::Network && ns.typ() != LinuxNamespaceType::User }) - .map(|ns| ns.clone()) + .cloned() .collect(); // Add user namespace From 26e345d190e911ed7ff859822ae5773f29049b69 Mon Sep 17 00:00:00 2001 From: yukang Date: Sat, 2 Oct 2021 12:32:50 +0800 Subject: [PATCH 5/6] use tempfile for testcase --- Cargo.lock | 1 + Cargo.toml | 1 + src/commands/spec_json.rs | 4 +++- 3 files changed, 5 insertions(+), 1 deletion(-) diff --git a/Cargo.lock b/Cargo.lock index e6ba87053..9fdaf720d 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1546,4 +1546,5 @@ dependencies = [ "serial_test", "systemd", "tabwriter", + "tempfile", ] diff --git a/Cargo.toml b/Cargo.toml index 2ddd571f1..7fd9706ce 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -61,6 +61,7 @@ oci-spec = { git = "https://github.com/containers/oci-spec-rs", rev = "5018f8e5 quickcheck = "1" serial_test = "0.5.1" ipc-channel = "0.15.0" +tempfile = "3" [profile.release] lto = true diff --git a/src/commands/spec_json.rs b/src/commands/spec_json.rs index d9d9b1f9a..8bbc2f0e4 100644 --- a/src/commands/spec_json.rs +++ b/src/commands/spec_json.rs @@ -117,7 +117,9 @@ mod tests { fn test_spec_json() -> Result<()> { let mut spec = Default::default(); spec = set_for_rootless(&spec)?; - to_writer_pretty(&File::create("config.json")?, &spec)?; + let tmpdir = tempfile::tempdir().unwrap(); + let path = tmpdir.path().join("config.json"); + to_writer_pretty(&File::create(path)?, &spec)?; Ok(()) } } From c4a61d72c6de8372564595f11350a7d5431006c5 Mon Sep 17 00:00:00 2001 From: yukang Date: Sat, 2 Oct 2021 19:54:44 +0800 Subject: [PATCH 6/6] use existing create_temp_dir --- Cargo.lock | 1 - Cargo.toml | 1 - src/commands/spec_json.rs | 3 ++- 3 files changed, 2 insertions(+), 3 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 9fdaf720d..e6ba87053 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1546,5 +1546,4 @@ dependencies = [ "serial_test", "systemd", "tabwriter", - "tempfile", ] diff --git a/Cargo.toml b/Cargo.toml index 7fd9706ce..2ddd571f1 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -61,7 +61,6 @@ oci-spec = { git = "https://github.com/containers/oci-spec-rs", rev = "5018f8e5 quickcheck = "1" serial_test = "0.5.1" ipc-channel = "0.15.0" -tempfile = "3" [profile.release] lto = true diff --git a/src/commands/spec_json.rs b/src/commands/spec_json.rs index 8bbc2f0e4..f9301fb85 100644 --- a/src/commands/spec_json.rs +++ b/src/commands/spec_json.rs @@ -112,12 +112,13 @@ impl SpecJson { // Tests become unstable if not serial. The cause is not known. mod tests { use super::*; + use crate::utils::create_temp_dir; #[test] fn test_spec_json() -> Result<()> { let mut spec = Default::default(); spec = set_for_rootless(&spec)?; - let tmpdir = tempfile::tempdir().unwrap(); + let tmpdir = create_temp_dir("test_spec_json").expect("failed to create temp dir"); let path = tmpdir.path().join("config.json"); to_writer_pretty(&File::create(path)?, &spec)?; Ok(())