1
0
Fork 0
mirror of https://github.com/containers/youki synced 2024-05-10 01:26:14 +02:00

implement capabilities.

This commit is contained in:
utam0k 2021-04-18 19:39:09 +09:00
parent a50f71fe50
commit 5976297077
9 changed files with 282 additions and 12 deletions

View File

@ -9,7 +9,7 @@ GREEN='\033[0;32m'
NC='\033[0m' # No Color
COLUMNS=$(tput cols)
expect_err_num=116
expect_err_num=8
act_err_num=0
for case in "${test_cases[@]}"; do

View File

@ -42,7 +42,7 @@ jobs:
make runtimetest validation-executables
- name: Run intetgration test
run: |
expect_err_num=107
expect_err_num=8
act_err_num=0
cd $(go env GOPATH)/src/github.com/opencontainers/runtime-tools
if [ 0 -ne $(sudo RUNTIME=$GITHUB_WORKSPACE/target/x86_64-unknown-linux-gnu/debug/youki ./validation/linux_cgroups_devices/linux_cgroups_devices.t | grep "not ok" | wc -l) ]; then

59
Cargo.lock generated
View File

@ -41,6 +41,17 @@ version = "1.4.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "14c189c53d098945499cdfa7ecc63567cf3886b3332b312a5b4585d8d3a6a610"
[[package]]
name = "caps"
version = "0.5.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d092fbb6657fb1f98a7da70c14335ac97e5a9477e1a8156d4bbf19a3a7aece51"
dependencies = [
"errno",
"libc",
"thiserror",
]
[[package]]
name = "cc"
version = "1.0.66"
@ -107,6 +118,27 @@ dependencies = [
"cfg-if",
]
[[package]]
name = "errno"
version = "0.2.7"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "fa68f2fb9cae9d37c9b2b3584aba698a2e97f72d7aef7b9f7aa71d8b54ce46fe"
dependencies = [
"errno-dragonfly",
"libc",
"winapi",
]
[[package]]
name = "errno-dragonfly"
version = "0.1.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "14ca354e36190500e1e1fb267c647932382b54053c50b14970856c0b00a35067"
dependencies = [
"gcc",
"libc",
]
[[package]]
name = "flate2"
version = "1.0.20"
@ -212,6 +244,12 @@ dependencies = [
"slab",
]
[[package]]
name = "gcc"
version = "0.3.55"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8f5f3913fa0bfe7ee1fd8248b6b9f42a5af4b9d65ec2dd2c3c26132b950ecfc2"
[[package]]
name = "hashbrown"
version = "0.9.1"
@ -560,6 +598,26 @@ dependencies = [
"unicode-width",
]
[[package]]
name = "thiserror"
version = "1.0.24"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e0f4a65597094d4483ddaed134f409b2cb7c1beccf25201a9f73c719254fa98e"
dependencies = [
"thiserror-impl",
]
[[package]]
name = "thiserror-impl"
version = "1.0.24"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7765189610d8241a44529806d6fd1f2e0a08734313a35d5b3a556f92b381f3c0"
dependencies = [
"proc-macro2",
"quote",
"syn",
]
[[package]]
name = "time"
version = "0.1.44"
@ -643,6 +701,7 @@ name = "youki"
version = "0.0.1"
dependencies = [
"anyhow",
"caps",
"chrono",
"clap",
"futures",

View File

@ -7,6 +7,8 @@ edition = "2018"
[dependencies]
clap = "3.0.0-beta.2"
nix = "0.19.1"
procfs = "0.9.1"
caps = "0.5.1"
serde = { version = "1.0", features = ["derive"] }
serde_json = "1.0"
prctl = "1.0.0"
@ -16,5 +18,4 @@ anyhow = "1.0"
mio = { version = "0.7", features = ["os-ext", "os-poll"] }
chrono = "0.4"
once_cell = "1.6.0"
procfs = "0.9.1"
futures = { version = "0.3", features = ["thread-pool"] }

40
src/capabilities.rs Normal file
View File

@ -0,0 +1,40 @@
use crate::spec::{LinuxCapabilities, LinuxCapabilityType};
use caps::*;
use anyhow::Result;
fn to_set(caps: &[LinuxCapabilityType]) -> CapsHashSet {
let mut capabilities = CapsHashSet::new();
for c in caps {
capabilities.insert(c.cap);
}
capabilities
}
pub fn reset_effective() -> Result<()> {
log::debug!("reset all caps");
set(None, CapSet::Effective, &caps::all())?;
Ok(())
}
pub fn drop_privileges(cs: &LinuxCapabilities) -> Result<()> {
let all = caps::all();
log::debug!("dropping bounding capabilities to {:?}", cs.bounding);
for c in all.difference(&to_set(&cs.bounding)) {
match c {
Capability::CAP_PERFMON | Capability::CAP_CHECKPOINT_RESTORE | Capability::CAP_BPF => {
continue
}
_ => caps::drop(None, CapSet::Bounding, *c)?,
}
}
set(None, CapSet::Effective, &to_set(&cs.effective))?;
set(None, CapSet::Permitted, &to_set(&cs.permitted))?;
set(None, CapSet::Inheritable, &to_set(&cs.inheritable))?;
if let Err(e) = set(None, CapSet::Ambient, &to_set(&cs.ambient)) {
log::error!("failed to set ambient capabilities: {}", e);
}
Ok(())
}

View File

@ -10,6 +10,7 @@ use nix::sys::stat;
use nix::unistd;
use nix::unistd::{Gid, Uid};
use crate::capabilities;
use crate::cgroups;
use crate::container::{Container, ContainerStatus};
use crate::notify_socket::NotifyListener;
@ -44,6 +45,7 @@ impl Create {
let spec = spec::Spec::load("config.json")?;
fs::copy("config.json", container_dir.join("config.json"))?;
log::debug!("spec: {:?}", spec);
let container_dir = fs::canonicalize(container_dir)?;
unistd::chdir(&*container_dir)?;
@ -121,6 +123,8 @@ fn run_container<P: AsRef<Path>>(
)? {
Process::Parent(parent) => Ok(Process::Parent(parent)),
Process::Child(child) => {
setid(Uid::from_raw(0), Gid::from_raw(0))?;
sched::unshare(cf & !sched::CloneFlags::CLONE_NEWUSER)?;
if let Some(csocketfd) = csocketfd {
@ -140,6 +144,7 @@ fn run_container<P: AsRef<Path>>(
Process::Init(mut init) => {
let spec_args: &Vec<String> = &spec.process.args.clone();
let proc = spec.process.clone();
let clone_spec = std::sync::Arc::new(spec);
let clone_rootfs = std::sync::Arc::new(rootfs.clone());
@ -155,7 +160,13 @@ fn run_container<P: AsRef<Path>>(
notify_socket.wait_for_container_start()?;
// utils::do_exec(&spec.process.args[0], &spec.process.args)?;
setid(Uid::from_raw(proc.user.uid), Gid::from_raw(proc.user.gid))?;
capabilities::reset_effective()?;
if let Some(caps) = &proc.capabilities {
let _ = prctl::set_no_new_privileges(true);
capabilities::drop_privileges(&caps)?;
}
utils::do_exec(&spec_args[0], spec_args)?;
container.update_status(ContainerStatus::Stopped)?.save()?;
@ -174,6 +185,10 @@ fn setid(uid: Uid, gid: Gid) -> Result<()> {
};
unistd::setresgid(gid, gid, gid)?;
unistd::setresuid(uid, uid, uid)?;
if uid != Uid::from_raw(0) {
capabilities::reset_effective()?;
}
if let Err(e) = prctl::set_keep_capabilities(false) {
bail!("set keep capabilities returned {}", e);
};

View File

@ -1,3 +1,4 @@
pub mod capabilities;
pub mod cgroups;
pub mod cond;
pub mod container;

View File

@ -70,12 +70,12 @@ fn main() -> Result<()> {
let opts = Opts::parse();
// debug mode for developer
if matches!(opts.subcmd, SubCommand::Create(_)) {
// #[cfg(debug_assertions)]
// std::env::set_var("YOUKI_MODE", "/var/lib/docker/containers/");
// #[cfg(debug_assertions)]
// std::env::set_var("YOUKI_LOG_LEVEL", "debug");
}
// if matches!(opts.subcmd, SubCommand::Create(_)) {
// #[cfg(debug_assertions)]
// std::env::set_var("YOUKI_MODE", "/var/lib/docker/containers/");
// #[cfg(debug_assertions)]
// std::env::set_var("YOUKI_LOG_LEVEL", "debug");
// }
youki::logger::init(opts.subcmd.get_container_id().as_str(), opts.log)?;

View File

@ -35,7 +35,7 @@ pub struct User {
pub username: String,
}
#[derive(Serialize, Deserialize, Debug, Clone)]
#[derive(Deserialize, Debug, Clone)]
#[serde(rename_all = "camelCase")]
pub struct Process {
#[serde(default)]
@ -54,6 +54,160 @@ pub struct Process {
pub apparmor_profile: String,
#[serde(default)]
pub selinux_label: String,
#[serde(default, deserialize_with = "deserialize_caps")]
pub capabilities: Option<LinuxCapabilities>,
}
use caps::Capability;
#[derive(Debug, Clone)]
pub struct LinuxCapabilityType {
pub cap: Capability,
}
impl<'de> Deserialize<'de> for LinuxCapabilityType {
fn deserialize<D>(desirializer: D) -> Result<Self, D::Error>
where
D: serde::de::Deserializer<'de>,
{
let r: serde_json::Value = serde::Deserialize::deserialize(desirializer)?;
match r {
serde_json::Value::String(type_string) => {
let cap = match type_string.as_str() {
"CAP_CHOWN" => Capability::CAP_CHOWN,
"CAP_DAC_OVERRIDE" => Capability::CAP_DAC_OVERRIDE,
"CAP_DAC_READ_SEARCH" => Capability::CAP_DAC_READ_SEARCH,
"CAP_FOWNER" => Capability::CAP_FOWNER,
"CAP_FSETID" => Capability::CAP_FSETID,
"CAP_KILL" => Capability::CAP_KILL,
"CAP_SETGID" => Capability::CAP_SETGID,
"CAP_SETUID" => Capability::CAP_SETUID,
"CAP_SETPCAP" => Capability::CAP_SETPCAP,
"CAP_LINUX_IMMUTABLE" => Capability::CAP_LINUX_IMMUTABLE,
"CAP_NET_BIND_SERVICE" => Capability::CAP_NET_BIND_SERVICE,
"CAP_NET_BROADCAST" => Capability::CAP_NET_BROADCAST,
"CAP_NET_ADMIN" => Capability::CAP_NET_ADMIN,
"CAP_NET_RAW" => Capability::CAP_NET_RAW,
"CAP_IPC_LOCK" => Capability::CAP_IPC_LOCK,
"CAP_IPC_OWNER" => Capability::CAP_IPC_OWNER,
"CAP_SYS_MODULE" => Capability::CAP_SYS_MODULE,
"CAP_SYS_RAWIO" => Capability::CAP_SYS_RAWIO,
"CAP_SYS_CHROOT" => Capability::CAP_SYS_CHROOT,
"CAP_SYS_PTRACE" => Capability::CAP_SYS_PTRACE,
"CAP_SYS_PACCT" => Capability::CAP_SYS_PACCT,
"CAP_SYS_ADMIN" => Capability::CAP_SYS_ADMIN,
"CAP_SYS_BOOT" => Capability::CAP_SYS_BOOT,
"CAP_SYS_NICE" => Capability::CAP_SYS_NICE,
"CAP_SYS_RESOURCE" => Capability::CAP_SYS_RESOURCE,
"CAP_SYS_TIME" => Capability::CAP_SYS_TIME,
"CAP_SYS_TTYCONFIG" => Capability::CAP_SYS_TTY_CONFIG,
"CAP_SYSLOG" => Capability::CAP_SYSLOG,
"CAP_MKNOD" => Capability::CAP_MKNOD,
"CAP_LEASE" => Capability::CAP_LEASE,
"CAP_AUDIT_WRITE" => Capability::CAP_AUDIT_WRITE,
"CAP_AUDIT_CONTROL" => Capability::CAP_AUDIT_CONTROL,
"CAP_AUDIT_READ" => Capability::CAP_AUDIT_READ,
"CAP_SETFCAP" => Capability::CAP_SETFCAP,
"CAP_MAC_OVERRIDE" => Capability::CAP_MAC_OVERRIDE,
"CAP_MAC_ADMIN" => Capability::CAP_MAC_ADMIN,
"CAP_WAKE_ALARM" => Capability::CAP_WAKE_ALARM,
"CAP_BLOCK_SUSPEND" => Capability::CAP_BLOCK_SUSPEND,
unknown_cap => {
return Err(serde::de::Error::custom(format!(
"{:?} is unexpected type in capabilites",
unknown_cap
)))
}
};
Ok(LinuxCapabilityType { cap })
}
_ => Err(serde::de::Error::custom("Unexpected type in capabilites")),
}
}
}
#[derive(Deserialize, Debug, Clone)]
pub struct LinuxCapabilities {
#[serde(skip_serializing_if = "Vec::is_empty")]
pub bounding: Vec<LinuxCapabilityType>,
#[serde(skip_serializing_if = "Vec::is_empty")]
pub effective: Vec<LinuxCapabilityType>,
#[serde(skip_serializing_if = "Vec::is_empty")]
pub inheritable: Vec<LinuxCapabilityType>,
#[serde(skip_serializing_if = "Vec::is_empty")]
pub permitted: Vec<LinuxCapabilityType>,
#[serde(skip_serializing_if = "Vec::is_empty")]
pub ambient: Vec<LinuxCapabilityType>,
}
fn deserialize_caps<'de, D>(desirializer: D) -> Result<Option<LinuxCapabilities>, D::Error>
where
D: serde::de::Deserializer<'de>,
{
let r: serde_json::Value = serde::Deserialize::deserialize(desirializer)?;
match r {
serde_json::Value::Null => Ok(None),
serde_json::Value::Array(a) => {
let caps = cap_from_array::<D>(&a)?;
let capabilities = LinuxCapabilities {
bounding: caps.clone(),
effective: caps.clone(),
inheritable: caps.clone(),
permitted: caps.clone(),
ambient: caps,
};
Ok(Some(capabilities))
}
serde_json::Value::Object(o) => {
let capabilities = LinuxCapabilities {
bounding: cap_from_object::<D>(&o, "bounding")?,
effective: cap_from_object::<D>(&o, "effective")?,
inheritable: cap_from_object::<D>(&o, "inheritable")?,
permitted: cap_from_object::<D>(&o, "permitted")?,
ambient: cap_from_object::<D>(&o, "ambient")?,
};
Ok(Some(capabilities))
}
_ => Err(serde::de::Error::custom("Unexpected value in capabilites")),
}
}
fn cap_from_object<'de, D>(
o: &serde_json::Map<String, serde_json::Value>,
key: &str,
) -> Result<Vec<LinuxCapabilityType>, D::Error>
where
D: serde::de::Deserializer<'de>,
{
if let Some(v) = o.get(key) {
match *v {
serde_json::Value::Null => Ok(Vec::new()),
serde_json::Value::Array(ref a) => cap_from_array::<D>(a),
_ => Err(serde::de::Error::custom(
"Unexpected value in capability set",
)),
}
} else {
Ok(Vec::new())
}
}
fn cap_from_array<'de, D>(a: &[serde_json::Value]) -> Result<Vec<LinuxCapabilityType>, D::Error>
where
D: serde::de::Deserializer<'de>,
{
let mut caps = Vec::new();
for c in a {
match LinuxCapabilityType::deserialize(c) {
Ok(val) => caps.push(val),
Err(_) => {
let msg = format!("Capability '{}' is not valid", c);
return Err(serde::de::Error::custom(msg));
}
}
}
Ok(caps)
}
#[derive(Serialize, Deserialize, Debug, Clone)]
@ -372,7 +526,7 @@ pub struct Linux {
pub mount_label: String,
}
#[derive(Serialize, Deserialize, Debug, Clone)]
#[derive(Deserialize, Debug, Clone)]
pub struct Spec {
#[serde(default, rename = "ociVersion")]
pub version: String,