mirror of
https://github.com/containers/youki
synced 2024-11-23 01:11:58 +01:00
Implement apparmor support (#312)
* Implement apparmor support * Add explanation to apparmor test
This commit is contained in:
parent
2af52169cf
commit
19316ce3c1
@ -45,6 +45,8 @@ test_cases=(
|
|||||||
"linux_ns_nopath/linux_ns_nopath.t"
|
"linux_ns_nopath/linux_ns_nopath.t"
|
||||||
"linux_ns_path/linux_ns_path.t"
|
"linux_ns_path/linux_ns_path.t"
|
||||||
"linux_ns_path_type/linux_ns_path_type.t"
|
"linux_ns_path_type/linux_ns_path_type.t"
|
||||||
|
# This test case requires that an apparmor profile named 'acme_secure_profile' has been installed on the system. It needs to allow the capabilites
|
||||||
|
# validated by runtime-tools otherwise the test case will fail despite the profile being available.
|
||||||
# "linux_process_apparmor_profile/linux_process_apparmor_profile.t"
|
# "linux_process_apparmor_profile/linux_process_apparmor_profile.t"
|
||||||
"linux_readonly_paths/linux_readonly_paths.t"
|
"linux_readonly_paths/linux_readonly_paths.t"
|
||||||
"linux_rootfs_propagation/linux_rootfs_propagation.t"
|
"linux_rootfs_propagation/linux_rootfs_propagation.t"
|
||||||
|
37
src/apparmor.rs
Normal file
37
src/apparmor.rs
Normal file
@ -0,0 +1,37 @@
|
|||||||
|
use anyhow::{Context, Result};
|
||||||
|
use std::{
|
||||||
|
fs::{self},
|
||||||
|
path::Path,
|
||||||
|
};
|
||||||
|
|
||||||
|
use crate::utils;
|
||||||
|
|
||||||
|
const ENABLED_PARAMETER_PATH: &str = "/sys/module/apparmor/parameters/enabled";
|
||||||
|
|
||||||
|
/// Checks if AppArmor has been enabled on the system.
|
||||||
|
pub fn is_enabled() -> Result<bool> {
|
||||||
|
let aa_enabled = fs::read_to_string(ENABLED_PARAMETER_PATH)
|
||||||
|
.with_context(|| format!("could not read {}", ENABLED_PARAMETER_PATH))?;
|
||||||
|
Ok(aa_enabled.starts_with('Y'))
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Applies an AppArmor profile to the container.
|
||||||
|
pub fn apply_profile(profile: &str) -> Result<()> {
|
||||||
|
if profile.is_empty() {
|
||||||
|
return Ok(());
|
||||||
|
}
|
||||||
|
|
||||||
|
// Try the module specific subdirectory. This is the recommended way to configure
|
||||||
|
// LSMs since Linux 5.1. AppArmor has such a directory since Linux 5.8.
|
||||||
|
if activate_profile(Path::new("/proc/self/attr/apparmor/exec"), profile).is_ok() {
|
||||||
|
return Ok(());
|
||||||
|
}
|
||||||
|
|
||||||
|
// try the legacy interface
|
||||||
|
activate_profile(Path::new("/proc/self/attr/exec"), profile)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn activate_profile(path: &Path, profile: &str) -> Result<()> {
|
||||||
|
utils::ensure_procfs(path)?;
|
||||||
|
utils::write_file(path, format!("exec {}", profile))
|
||||||
|
}
|
@ -7,7 +7,7 @@ use std::{
|
|||||||
path::{Path, PathBuf},
|
path::{Path, PathBuf},
|
||||||
};
|
};
|
||||||
|
|
||||||
use crate::{notify_socket::NOTIFY_FILE, rootless, tty, utils};
|
use crate::{apparmor, notify_socket::NOTIFY_FILE, rootless, tty, utils};
|
||||||
|
|
||||||
use super::{
|
use super::{
|
||||||
builder::ContainerBuilder, builder_impl::ContainerBuilderImpl, Container, ContainerStatus,
|
builder::ContainerBuilder, builder_impl::ContainerBuilderImpl, Container, ContainerStatus,
|
||||||
@ -100,14 +100,33 @@ impl<'a> InitContainerBuilder<'a> {
|
|||||||
fn load_spec(&self) -> Result<Spec> {
|
fn load_spec(&self) -> Result<Spec> {
|
||||||
let source_spec_path = self.bundle.join("config.json");
|
let source_spec_path = self.bundle.join("config.json");
|
||||||
let mut spec = Spec::load(&source_spec_path)?;
|
let mut spec = Spec::load(&source_spec_path)?;
|
||||||
|
Self::validate_spec(&spec).context("failed to validate runtime spec")?;
|
||||||
|
|
||||||
|
spec.canonicalize_rootfs(&self.bundle)?;
|
||||||
|
Ok(spec)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn validate_spec(spec: &Spec) -> Result<()> {
|
||||||
if !spec.version.starts_with("1.0") {
|
if !spec.version.starts_with("1.0") {
|
||||||
bail!(
|
bail!(
|
||||||
"runtime spec has incompatible version '{}'. Only 1.0.X is supported",
|
"runtime spec has incompatible version '{}'. Only 1.0.X is supported",
|
||||||
spec.version
|
spec.version
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
spec.canonicalize_rootfs(&self.bundle)?;
|
|
||||||
Ok(spec)
|
if let Some(process) = &spec.process {
|
||||||
|
if let Some(profile) = &process.apparmor_profile {
|
||||||
|
if !apparmor::is_enabled()? {
|
||||||
|
bail!(
|
||||||
|
"apparmor profile {} is specified in runtime spec, \
|
||||||
|
but apparmor is not activated on this system",
|
||||||
|
profile
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
fn save_spec(&self, spec: &Spec, container_dir: &Path) -> Result<()> {
|
fn save_spec(&self, spec: &Spec, container_dir: &Path) -> Result<()> {
|
||||||
|
@ -1,3 +1,4 @@
|
|||||||
|
pub mod apparmor;
|
||||||
pub mod capabilities;
|
pub mod capabilities;
|
||||||
pub mod commands;
|
pub mod commands;
|
||||||
pub mod container;
|
pub mod container;
|
||||||
|
@ -1,4 +1,5 @@
|
|||||||
use super::args::ContainerArgs;
|
use super::args::ContainerArgs;
|
||||||
|
use crate::apparmor;
|
||||||
use crate::{
|
use crate::{
|
||||||
capabilities, hooks, namespaces::Namespaces, process::channel, rootfs, rootless::Rootless,
|
capabilities, hooks, namespaces::Namespaces, process::channel, rootfs, rootless::Rootless,
|
||||||
seccomp, tty, utils,
|
seccomp, tty, utils,
|
||||||
@ -9,34 +10,19 @@ use nix::mount::MsFlags;
|
|||||||
use nix::sched::CloneFlags;
|
use nix::sched::CloneFlags;
|
||||||
use nix::{
|
use nix::{
|
||||||
fcntl,
|
fcntl,
|
||||||
sys::statfs,
|
|
||||||
unistd::{self, Gid, Uid},
|
unistd::{self, Gid, Uid},
|
||||||
};
|
};
|
||||||
use oci_spec::runtime::{LinuxNamespaceType, User};
|
use oci_spec::runtime::{LinuxNamespaceType, User};
|
||||||
use std::collections::HashMap;
|
use std::collections::HashMap;
|
||||||
use std::{
|
use std::{
|
||||||
env, fs,
|
env, fs,
|
||||||
os::unix::io::AsRawFd,
|
|
||||||
path::{Path, PathBuf},
|
path::{Path, PathBuf},
|
||||||
};
|
};
|
||||||
|
|
||||||
// Make sure a given path is on procfs. This is to avoid the security risk that
|
|
||||||
// /proc path is mounted over. Ref: CVE-2019-16884
|
|
||||||
fn ensure_procfs(path: &Path) -> Result<()> {
|
|
||||||
let procfs_fd = fs::File::open(path)?;
|
|
||||||
let fstat_info = statfs::fstatfs(&procfs_fd.as_raw_fd())?;
|
|
||||||
|
|
||||||
if fstat_info.filesystem_type() != statfs::PROC_SUPER_MAGIC {
|
|
||||||
bail!(format!("{:?} is not on the procfs", path));
|
|
||||||
}
|
|
||||||
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
|
|
||||||
// Get a list of open fds for the calling process.
|
// Get a list of open fds for the calling process.
|
||||||
fn get_open_fds() -> Result<Vec<i32>> {
|
fn get_open_fds() -> Result<Vec<i32>> {
|
||||||
const PROCFS_FD_PATH: &str = "/proc/self/fd";
|
const PROCFS_FD_PATH: &str = "/proc/self/fd";
|
||||||
ensure_procfs(Path::new(PROCFS_FD_PATH))
|
utils::ensure_procfs(Path::new(PROCFS_FD_PATH))
|
||||||
.with_context(|| format!("{} is not the actual procfs", PROCFS_FD_PATH))?;
|
.with_context(|| format!("{} is not the actual procfs", PROCFS_FD_PATH))?;
|
||||||
|
|
||||||
let fds: Vec<i32> = fs::read_dir(PROCFS_FD_PATH)?
|
let fds: Vec<i32> = fs::read_dir(PROCFS_FD_PATH)?
|
||||||
@ -257,6 +243,11 @@ pub fn container_init(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if let Some(profile) = &proc.apparmor_profile {
|
||||||
|
apparmor::apply_profile(profile)
|
||||||
|
.with_context(|| format!("failed to apply apparmor profile {}", profile))?;
|
||||||
|
}
|
||||||
|
|
||||||
if let Some(true) = spec.root.as_ref().map(|r| r.readonly.unwrap_or(false)) {
|
if let Some(true) = spec.root.as_ref().map(|r| r.readonly.unwrap_or(false)) {
|
||||||
nix_mount(
|
nix_mount(
|
||||||
None::<&str>,
|
None::<&str>,
|
||||||
@ -470,7 +461,7 @@ mod tests {
|
|||||||
use anyhow::{bail, Result};
|
use anyhow::{bail, Result};
|
||||||
use nix::{fcntl, sys, unistd};
|
use nix::{fcntl, sys, unistd};
|
||||||
use serial_test::serial;
|
use serial_test::serial;
|
||||||
use std::fs;
|
use std::{fs, os::unix::prelude::AsRawFd};
|
||||||
|
|
||||||
// Note: We have to run these tests here as serial. The main issue is that
|
// Note: We have to run these tests here as serial. The main issue is that
|
||||||
// these tests has a dependency on the system state. The
|
// these tests has a dependency on the system state. The
|
||||||
|
15
src/utils.rs
15
src/utils.rs
@ -6,12 +6,14 @@ use std::fs::{self, DirBuilder, File};
|
|||||||
use std::ops::Deref;
|
use std::ops::Deref;
|
||||||
use std::os::linux::fs::MetadataExt;
|
use std::os::linux::fs::MetadataExt;
|
||||||
use std::os::unix::fs::DirBuilderExt;
|
use std::os::unix::fs::DirBuilderExt;
|
||||||
|
use std::os::unix::prelude::AsRawFd;
|
||||||
use std::path::{Path, PathBuf};
|
use std::path::{Path, PathBuf};
|
||||||
use std::time::Duration;
|
use std::time::Duration;
|
||||||
|
|
||||||
use anyhow::Context;
|
use anyhow::Context;
|
||||||
use anyhow::{bail, Result};
|
use anyhow::{bail, Result};
|
||||||
use nix::sys::stat::Mode;
|
use nix::sys::stat::Mode;
|
||||||
|
use nix::sys::statfs;
|
||||||
use nix::unistd;
|
use nix::unistd;
|
||||||
|
|
||||||
pub trait PathBufExt {
|
pub trait PathBufExt {
|
||||||
@ -152,6 +154,19 @@ pub fn create_dir_all_with_mode<P: AsRef<Path>>(path: P, owner: u32, mode: Mode)
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Make sure a given path is on procfs. This is to avoid the security risk that
|
||||||
|
// /proc path is mounted over. Ref: CVE-2019-16884
|
||||||
|
pub fn ensure_procfs(path: &Path) -> Result<()> {
|
||||||
|
let procfs_fd = fs::File::open(path)?;
|
||||||
|
let fstat_info = statfs::fstatfs(&procfs_fd.as_raw_fd())?;
|
||||||
|
|
||||||
|
if fstat_info.filesystem_type() != statfs::PROC_SUPER_MAGIC {
|
||||||
|
bail!(format!("{:?} is not on the procfs", path));
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
pub struct TempDir {
|
pub struct TempDir {
|
||||||
path: Option<PathBuf>,
|
path: Option<PathBuf>,
|
||||||
}
|
}
|
||||||
|
Loading…
Reference in New Issue
Block a user