mirror of
https://github.com/containers/youki
synced 2024-05-10 01:26:14 +02:00
implement capabilities.
This commit is contained in:
parent
a50f71fe50
commit
5976297077
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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",
|
||||
|
|
|
@ -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"] }
|
|
@ -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(())
|
||||
}
|
|
@ -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);
|
||||
};
|
||||
|
|
|
@ -1,3 +1,4 @@
|
|||
pub mod capabilities;
|
||||
pub mod cgroups;
|
||||
pub mod cond;
|
||||
pub mod container;
|
||||
|
|
12
src/main.rs
12
src/main.rs
|
@ -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)?;
|
||||
|
||||
|
|
158
src/spec.rs
158
src/spec.rs
|
@ -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,
|
||||
|
|
Loading…
Reference in New Issue