Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add rootless option for spec #350

Merged
merged 6 commits into from Oct 3, 2021
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
7 changes: 7 additions & 0 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions Cargo.toml
Expand Up @@ -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
Expand Down
116 changes: 111 additions & 5 deletions src/commands/spec_json.rs
@@ -1,20 +1,126 @@
use anyhow::Result;
use clap::Clap;
use oci_spec::runtime::Spec;
use nix;
use oci_spec::runtime::{
Linux, LinuxBuilder, LinuxIdMappingBuilder, LinuxNamespace, LinuxNamespaceBuilder,
LinuxNamespaceType, MountBuilder, Spec, SpecBuilder,
};
use path_clean;
use serde_json::to_writer_pretty;
use std::fs::File;

/// Create a new runtime specification
use std::path::PathBuf;
/// 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<Spec> {
let uid = nix::unistd::geteuid().as_raw();
let gid = nix::unistd::getegid().as_raw();

// Remove network from the default spec
let mut namespaces: Vec<LinuxNamespace> = spec
.linux()
.as_ref()
.unwrap_or(&Linux::default())
.namespaces()
.as_ref()
.unwrap_or(&vec![])
.iter()
.filter(|&ns| {
ns.typ() != LinuxNamespaceType::Network && ns.typ() != LinuxNamespaceType::User
})
.cloned()
.collect();

// 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();
// 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" {
chenyukang marked this conversation as resolved.
Show resolved Hide resolved
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<String> = 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(())
}
}

#[cfg(test)]
// 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 = 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(())
}
}