2021-07-15 22:12:21 +02:00
|
|
|
use anyhow::{bail, Context, Result};
|
2021-07-15 01:09:04 +02:00
|
|
|
use caps::Capability;
|
2021-07-21 00:41:12 +02:00
|
|
|
use nix::unistd;
|
2021-09-06 11:08:17 +02:00
|
|
|
use oci_spec::runtime::{
|
2021-09-08 13:24:37 +02:00
|
|
|
Capabilities as SpecCapabilities, Capability as SpecCapability, LinuxCapabilities,
|
|
|
|
LinuxNamespace, LinuxNamespaceType, Process, Spec,
|
2021-09-06 11:08:17 +02:00
|
|
|
};
|
2021-09-06 11:25:17 +02:00
|
|
|
use procfs::process::Namespace;
|
2021-07-15 22:12:21 +02:00
|
|
|
|
2021-07-04 22:44:07 +02:00
|
|
|
use std::{
|
|
|
|
collections::HashMap,
|
2021-07-15 01:09:04 +02:00
|
|
|
convert::TryFrom,
|
2021-07-15 22:12:21 +02:00
|
|
|
fs,
|
2021-09-07 04:32:41 +02:00
|
|
|
os::unix::prelude::RawFd,
|
2021-07-04 22:44:07 +02:00
|
|
|
path::{Path, PathBuf},
|
2021-07-15 01:09:04 +02:00
|
|
|
str::FromStr,
|
|
|
|
};
|
|
|
|
|
2021-09-08 13:24:37 +02:00
|
|
|
use crate::capabilities::CapabilityExt;
|
2021-08-28 19:37:52 +02:00
|
|
|
use crate::{notify_socket::NotifySocket, rootless::Rootless, tty, utils};
|
2021-07-04 22:44:07 +02:00
|
|
|
|
2021-07-15 01:09:04 +02:00
|
|
|
use super::{builder::ContainerBuilder, builder_impl::ContainerBuilderImpl, Container};
|
2021-07-04 22:44:07 +02:00
|
|
|
|
2021-07-15 01:09:04 +02:00
|
|
|
const NAMESPACE_TYPES: &[&str] = &["ipc", "uts", "net", "pid", "mnt", "cgroup"];
|
2021-07-15 22:12:21 +02:00
|
|
|
const TENANT_NOTIFY: &str = "tenant-notify-";
|
|
|
|
const TENANT_TTY: &str = "tenant-tty-";
|
2021-07-04 22:44:07 +02:00
|
|
|
|
2021-07-05 19:40:35 +02:00
|
|
|
/// Builder that can be used to configure the properties of a process
|
|
|
|
/// that will join an existing container sandbox
|
2021-07-04 22:44:07 +02:00
|
|
|
pub struct TenantContainerBuilder {
|
|
|
|
base: ContainerBuilder,
|
|
|
|
env: HashMap<String, String>,
|
|
|
|
cwd: Option<PathBuf>,
|
2021-07-15 01:09:04 +02:00
|
|
|
args: Vec<String>,
|
|
|
|
no_new_privs: Option<bool>,
|
|
|
|
capabilities: Vec<String>,
|
|
|
|
process: Option<PathBuf>,
|
2021-07-04 22:44:07 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
impl TenantContainerBuilder {
|
2021-07-05 19:40:35 +02:00
|
|
|
/// Generates the base configuration for a process that will join
|
|
|
|
/// an existing container sandbox from which configuration methods
|
|
|
|
/// can be chained
|
2021-07-04 22:44:07 +02:00
|
|
|
pub(super) fn new(builder: ContainerBuilder) -> Self {
|
|
|
|
Self {
|
|
|
|
base: builder,
|
|
|
|
env: HashMap::new(),
|
|
|
|
cwd: None,
|
2021-07-15 01:09:04 +02:00
|
|
|
args: Vec::new(),
|
|
|
|
no_new_privs: None,
|
|
|
|
capabilities: Vec::new(),
|
|
|
|
process: None,
|
2021-07-04 22:44:07 +02:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2021-07-05 19:40:35 +02:00
|
|
|
/// Sets environment variables for the container
|
2021-07-04 22:44:07 +02:00
|
|
|
pub fn with_env(mut self, env: HashMap<String, String>) -> Self {
|
|
|
|
self.env = env;
|
|
|
|
self
|
|
|
|
}
|
|
|
|
|
2021-07-05 19:40:35 +02:00
|
|
|
/// Sets the working directory of the container
|
2021-07-29 15:26:14 +02:00
|
|
|
pub fn with_cwd<P: Into<PathBuf>>(mut self, path: Option<P>) -> Self {
|
|
|
|
self.cwd = path.map(|p| p.into());
|
2021-07-04 22:44:07 +02:00
|
|
|
self
|
|
|
|
}
|
|
|
|
|
2021-07-05 19:40:35 +02:00
|
|
|
/// Sets the command the container will be started with
|
2021-07-15 01:09:04 +02:00
|
|
|
pub fn with_container_args(mut self, args: Vec<String>) -> Self {
|
|
|
|
self.args = args;
|
|
|
|
self
|
|
|
|
}
|
|
|
|
|
|
|
|
pub fn with_no_new_privs(mut self, no_new_privs: bool) -> Self {
|
|
|
|
self.no_new_privs = Some(no_new_privs);
|
|
|
|
self
|
|
|
|
}
|
|
|
|
|
|
|
|
pub fn with_capabilities(mut self, capabilities: Vec<String>) -> Self {
|
|
|
|
self.capabilities = capabilities;
|
|
|
|
self
|
|
|
|
}
|
|
|
|
|
2021-07-29 15:26:14 +02:00
|
|
|
pub fn with_process<P: Into<PathBuf>>(mut self, path: Option<P>) -> Self {
|
|
|
|
self.process = path.map(|p| p.into());
|
2021-07-04 22:44:07 +02:00
|
|
|
self
|
|
|
|
}
|
|
|
|
|
2021-07-05 19:40:35 +02:00
|
|
|
/// Joins an existing container
|
2021-07-04 22:44:07 +02:00
|
|
|
pub fn build(self) -> Result<()> {
|
|
|
|
let container_dir = self.lookup_container_dir()?;
|
2021-07-15 01:09:04 +02:00
|
|
|
let container = self.load_container_state(container_dir.clone())?;
|
|
|
|
let mut spec = self.load_init_spec(&container_dir)?;
|
|
|
|
self.adapt_spec_for_tenant(&mut spec, &container)?;
|
|
|
|
log::debug!("{:#?}", spec);
|
2021-07-04 22:44:07 +02:00
|
|
|
|
2021-07-21 00:41:12 +02:00
|
|
|
unistd::chdir(&*container_dir)?;
|
2021-07-19 08:22:47 +02:00
|
|
|
let notify_path = Self::setup_notify_listener(&container_dir)?;
|
2021-07-04 22:44:07 +02:00
|
|
|
// convert path of root file system of the container to absolute path
|
2021-07-30 12:40:45 +02:00
|
|
|
let rootfs = fs::canonicalize(&spec.root.as_ref().context("no root in spec")?.path)?;
|
2021-07-04 22:44:07 +02:00
|
|
|
|
|
|
|
// if socket file path is given in commandline options,
|
|
|
|
// get file descriptors of console socket
|
2021-07-15 22:12:21 +02:00
|
|
|
let csocketfd = self.setup_tty_socket(&container_dir)?;
|
2021-07-04 22:44:07 +02:00
|
|
|
|
2021-07-15 01:09:04 +02:00
|
|
|
let use_systemd = self.should_use_systemd(&container);
|
2021-08-28 19:37:52 +02:00
|
|
|
let rootless = Rootless::new(&spec)?;
|
2021-07-04 22:44:07 +02:00
|
|
|
|
|
|
|
let mut builder_impl = ContainerBuilderImpl {
|
|
|
|
init: false,
|
|
|
|
syscall: self.base.syscall,
|
|
|
|
container_id: self.base.container_id,
|
|
|
|
pid_file: self.base.pid_file,
|
|
|
|
console_socket: csocketfd,
|
2021-07-15 22:12:21 +02:00
|
|
|
use_systemd,
|
2021-08-08 10:37:13 +02:00
|
|
|
spec: &spec,
|
2021-07-04 22:44:07 +02:00
|
|
|
rootfs,
|
|
|
|
rootless,
|
2021-07-19 08:22:47 +02:00
|
|
|
notify_path: notify_path.clone(),
|
2021-07-04 22:44:07 +02:00
|
|
|
container: None,
|
2021-08-02 02:23:56 +02:00
|
|
|
preserve_fds: self.base.preserve_fds,
|
2021-07-04 22:44:07 +02:00
|
|
|
};
|
|
|
|
|
|
|
|
builder_impl.create()?;
|
2021-07-15 01:09:04 +02:00
|
|
|
|
2021-07-15 22:12:21 +02:00
|
|
|
let mut notify_socket = NotifySocket::new(notify_path);
|
2021-07-15 01:09:04 +02:00
|
|
|
notify_socket.notify_container_start()?;
|
2021-07-04 22:44:07 +02:00
|
|
|
Ok(())
|
|
|
|
}
|
|
|
|
|
|
|
|
fn lookup_container_dir(&self) -> Result<PathBuf> {
|
|
|
|
let container_dir = self.base.root_path.join(&self.base.container_id);
|
|
|
|
if !container_dir.exists() {
|
|
|
|
bail!("container {} does not exist", self.base.container_id);
|
|
|
|
}
|
|
|
|
|
|
|
|
Ok(container_dir)
|
|
|
|
}
|
|
|
|
|
|
|
|
fn load_init_spec(&self, container_dir: &Path) -> Result<Spec> {
|
|
|
|
let spec_path = container_dir.join("config.json");
|
|
|
|
|
2021-09-06 11:08:17 +02:00
|
|
|
let spec = Spec::load(spec_path).context("failed to load spec")?;
|
2021-07-04 22:44:07 +02:00
|
|
|
Ok(spec)
|
|
|
|
}
|
2021-07-15 01:09:04 +02:00
|
|
|
|
|
|
|
fn load_container_state(&self, container_dir: PathBuf) -> Result<Container> {
|
|
|
|
let container = Container::load(container_dir)?.refresh_status()?;
|
|
|
|
if !container.can_exec() {
|
|
|
|
bail!(
|
|
|
|
"Cannot exec as container is in state {}",
|
|
|
|
container.status()
|
|
|
|
);
|
|
|
|
}
|
|
|
|
|
|
|
|
Ok(container)
|
|
|
|
}
|
|
|
|
|
|
|
|
fn adapt_spec_for_tenant(&self, spec: &mut Spec, container: &Container) -> Result<()> {
|
|
|
|
if let Some(ref process) = self.process {
|
|
|
|
self.set_process(spec, process)?;
|
|
|
|
} else {
|
|
|
|
self.set_working_dir(spec)?;
|
|
|
|
self.set_args(spec)?;
|
|
|
|
self.set_environment(spec)?;
|
2021-07-30 12:40:45 +02:00
|
|
|
self.set_no_new_privileges(spec)?;
|
2021-07-15 01:09:04 +02:00
|
|
|
self.set_capabilities(spec)?;
|
|
|
|
}
|
|
|
|
|
|
|
|
if container.pid().is_none() {
|
|
|
|
bail!("Could not retrieve container init pid");
|
|
|
|
}
|
|
|
|
|
|
|
|
let init_process = procfs::process::Process::new(container.pid().unwrap().as_raw())?;
|
|
|
|
self.set_namespaces(spec, init_process.namespaces()?)?;
|
|
|
|
|
|
|
|
Ok(())
|
|
|
|
}
|
|
|
|
|
|
|
|
fn set_process(&self, spec: &mut Spec, process: &Path) -> Result<()> {
|
|
|
|
if !process.exists() {
|
|
|
|
bail!(
|
|
|
|
"Process.json file does not exist at specified path {}",
|
|
|
|
process.display()
|
|
|
|
)
|
|
|
|
}
|
|
|
|
|
2021-07-15 22:12:21 +02:00
|
|
|
let process = utils::open(process)?;
|
2021-07-15 01:09:04 +02:00
|
|
|
let process_spec: Process = serde_json::from_reader(process)?;
|
2021-07-30 12:40:45 +02:00
|
|
|
spec.process = Some(process_spec);
|
2021-07-15 22:12:21 +02:00
|
|
|
Ok(())
|
2021-07-15 01:09:04 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
fn set_working_dir(&self, spec: &mut Spec) -> Result<()> {
|
|
|
|
if let Some(ref cwd) = self.cwd {
|
|
|
|
if cwd.is_relative() {
|
|
|
|
bail!(
|
|
|
|
"Current working directory must be an absolute path, but is {}",
|
|
|
|
cwd.display()
|
|
|
|
);
|
|
|
|
}
|
|
|
|
|
2021-09-06 11:08:17 +02:00
|
|
|
spec.process.as_mut().context("no process in spec")?.cwd = cwd.to_path_buf();
|
2021-07-15 01:09:04 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
Ok(())
|
|
|
|
}
|
|
|
|
|
|
|
|
fn set_args(&self, spec: &mut Spec) -> Result<()> {
|
|
|
|
if self.args.is_empty() {
|
|
|
|
bail!("Container command was not specified")
|
|
|
|
}
|
|
|
|
|
2021-08-04 13:41:00 +02:00
|
|
|
spec.process.as_mut().context("no process in spec")?.args = self.args.clone().into();
|
2021-07-15 01:09:04 +02:00
|
|
|
Ok(())
|
|
|
|
}
|
|
|
|
|
|
|
|
fn set_environment(&self, spec: &mut Spec) -> Result<()> {
|
2021-07-30 12:40:45 +02:00
|
|
|
spec.process
|
|
|
|
.as_mut()
|
|
|
|
.context("no process in spec")?
|
|
|
|
.env
|
2021-08-04 13:41:00 +02:00
|
|
|
.as_mut()
|
|
|
|
.context("no env in process spec")?
|
2021-07-30 12:40:45 +02:00
|
|
|
.append(
|
|
|
|
&mut self
|
|
|
|
.env
|
|
|
|
.iter()
|
|
|
|
.map(|(k, v)| format!("{}={}", k, v))
|
|
|
|
.collect(),
|
|
|
|
);
|
2021-07-15 01:09:04 +02:00
|
|
|
|
|
|
|
Ok(())
|
|
|
|
}
|
|
|
|
|
2021-07-30 12:40:45 +02:00
|
|
|
fn set_no_new_privileges(&self, spec: &mut Spec) -> Result<()> {
|
2021-07-15 01:09:04 +02:00
|
|
|
if let Some(no_new_privs) = self.no_new_privs {
|
2021-07-30 12:40:45 +02:00
|
|
|
spec.process
|
|
|
|
.as_mut()
|
|
|
|
.context("no process in spec")?
|
2021-08-04 13:41:00 +02:00
|
|
|
.no_new_privileges = no_new_privs.into();
|
2021-07-15 01:09:04 +02:00
|
|
|
}
|
2021-07-30 12:40:45 +02:00
|
|
|
Ok(())
|
2021-07-15 01:09:04 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
fn set_capabilities(&self, spec: &mut Spec) -> Result<()> {
|
|
|
|
if !self.capabilities.is_empty() {
|
2021-07-20 20:59:34 +02:00
|
|
|
let mut caps: Vec<Capability> = Vec::with_capacity(self.capabilities.len());
|
2021-07-15 01:09:04 +02:00
|
|
|
for cap in &self.capabilities {
|
2021-07-20 20:59:34 +02:00
|
|
|
caps.push(Capability::from_str(cap)?);
|
2021-07-15 01:09:04 +02:00
|
|
|
}
|
|
|
|
|
2021-09-08 13:24:37 +02:00
|
|
|
let caps: SpecCapabilities =
|
|
|
|
caps.iter().map(|c| SpecCapability::from_cap(*c)).collect();
|
2021-09-06 11:08:17 +02:00
|
|
|
|
2021-07-30 12:40:45 +02:00
|
|
|
if let Some(ref mut spec_caps) = spec
|
|
|
|
.process
|
|
|
|
.as_mut()
|
|
|
|
.context("no process in spec")?
|
|
|
|
.capabilities
|
|
|
|
{
|
2021-08-04 13:41:00 +02:00
|
|
|
spec_caps
|
|
|
|
.ambient
|
|
|
|
.as_mut()
|
|
|
|
.context("no ambient caps in process spec")?
|
2021-09-06 11:08:17 +02:00
|
|
|
.extend(&caps);
|
2021-08-04 13:41:00 +02:00
|
|
|
spec_caps
|
|
|
|
.bounding
|
|
|
|
.as_mut()
|
|
|
|
.context("no bounding caps in process spec")?
|
2021-09-06 11:08:17 +02:00
|
|
|
.extend(&caps);
|
2021-08-04 13:41:00 +02:00
|
|
|
spec_caps
|
|
|
|
.effective
|
|
|
|
.as_mut()
|
|
|
|
.context("no effective caps in process spec")?
|
2021-09-06 11:08:17 +02:00
|
|
|
.extend(&caps);
|
2021-08-04 13:41:00 +02:00
|
|
|
spec_caps
|
|
|
|
.inheritable
|
|
|
|
.as_mut()
|
|
|
|
.context("no inheritable caps in process spec")?
|
2021-09-06 11:08:17 +02:00
|
|
|
.extend(&caps);
|
2021-08-04 13:41:00 +02:00
|
|
|
spec_caps
|
|
|
|
.permitted
|
|
|
|
.as_mut()
|
|
|
|
.context("no permitted caps in process spec")?
|
2021-09-06 11:08:17 +02:00
|
|
|
.extend(&caps);
|
2021-07-15 01:09:04 +02:00
|
|
|
} else {
|
2021-07-30 12:40:45 +02:00
|
|
|
spec.process
|
|
|
|
.as_mut()
|
|
|
|
.context("no process in spec")?
|
|
|
|
.capabilities = Some(LinuxCapabilities {
|
2021-08-04 13:41:00 +02:00
|
|
|
ambient: caps.clone().into(),
|
|
|
|
bounding: caps.clone().into(),
|
|
|
|
effective: caps.clone().into(),
|
|
|
|
inheritable: caps.clone().into(),
|
|
|
|
permitted: caps.into(),
|
2021-07-15 01:09:04 +02:00
|
|
|
})
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
Ok(())
|
|
|
|
}
|
|
|
|
|
|
|
|
fn set_namespaces(&self, spec: &mut Spec, init_namespaces: Vec<Namespace>) -> Result<()> {
|
|
|
|
let mut tenant_namespaces = Vec::with_capacity(init_namespaces.len());
|
|
|
|
|
|
|
|
for ns_type in NAMESPACE_TYPES.iter().copied() {
|
|
|
|
if let Some(init_ns) = init_namespaces.iter().find(|n| n.ns_type.eq(ns_type)) {
|
|
|
|
let tenant_ns = LinuxNamespaceType::try_from(ns_type)?;
|
|
|
|
tenant_namespaces.push(LinuxNamespace {
|
|
|
|
typ: tenant_ns,
|
2021-08-09 07:54:24 +02:00
|
|
|
path: Some(init_ns.path.clone()),
|
2021-07-15 01:09:04 +02:00
|
|
|
})
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2021-07-30 12:40:45 +02:00
|
|
|
let mut linux = &mut spec.linux.as_mut().context("no linux in spec")?;
|
2021-08-04 13:41:00 +02:00
|
|
|
linux.namespaces = tenant_namespaces.into();
|
2021-07-15 01:09:04 +02:00
|
|
|
Ok(())
|
|
|
|
}
|
|
|
|
|
|
|
|
fn should_use_systemd(&self, container: &Container) -> bool {
|
|
|
|
if let Some(use_systemd) = container.systemd() {
|
|
|
|
return use_systemd;
|
|
|
|
}
|
|
|
|
|
|
|
|
false
|
|
|
|
}
|
2021-07-15 22:12:21 +02:00
|
|
|
|
2021-07-19 08:22:47 +02:00
|
|
|
fn setup_notify_listener(container_dir: &Path) -> Result<PathBuf> {
|
2021-08-01 12:12:49 +02:00
|
|
|
let notify_name = Self::generate_name(container_dir, TENANT_NOTIFY);
|
2021-07-15 22:12:21 +02:00
|
|
|
let socket_path = container_dir.join(¬ify_name);
|
|
|
|
|
2021-07-19 08:22:47 +02:00
|
|
|
Ok(socket_path)
|
2021-07-15 22:12:21 +02:00
|
|
|
}
|
|
|
|
|
2021-08-09 08:05:31 +02:00
|
|
|
fn setup_tty_socket(&self, container_dir: &Path) -> Result<Option<RawFd>> {
|
2021-08-01 12:12:49 +02:00
|
|
|
let tty_name = Self::generate_name(container_dir, TENANT_TTY);
|
2021-07-15 22:12:21 +02:00
|
|
|
let csocketfd = if let Some(console_socket) = &self.base.console_socket {
|
|
|
|
Some(tty::setup_console_socket(
|
|
|
|
container_dir,
|
|
|
|
console_socket,
|
|
|
|
&tty_name,
|
|
|
|
)?)
|
|
|
|
} else {
|
|
|
|
None
|
|
|
|
};
|
|
|
|
|
|
|
|
Ok(csocketfd)
|
|
|
|
}
|
|
|
|
|
|
|
|
fn generate_name(dir: &Path, prefix: &str) -> String {
|
|
|
|
loop {
|
|
|
|
let rand = fastrand::i32(..);
|
|
|
|
let name = format!("{}{:x}.sock", prefix, rand);
|
|
|
|
if !dir.join(&name).exists() {
|
|
|
|
return name;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
2021-07-15 01:09:04 +02:00
|
|
|
}
|