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_path/linux_ns_path.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_readonly_paths/linux_readonly_paths.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},
|
||||
};
|
||||
|
||||
use crate::{notify_socket::NOTIFY_FILE, rootless, tty, utils};
|
||||
use crate::{apparmor, notify_socket::NOTIFY_FILE, rootless, tty, utils};
|
||||
|
||||
use super::{
|
||||
builder::ContainerBuilder, builder_impl::ContainerBuilderImpl, Container, ContainerStatus,
|
||||
@ -100,14 +100,33 @@ impl<'a> InitContainerBuilder<'a> {
|
||||
fn load_spec(&self) -> Result<Spec> {
|
||||
let source_spec_path = self.bundle.join("config.json");
|
||||
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") {
|
||||
bail!(
|
||||
"runtime spec has incompatible version '{}'. Only 1.0.X is supported",
|
||||
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<()> {
|
||||
|
@ -1,3 +1,4 @@
|
||||
pub mod apparmor;
|
||||
pub mod capabilities;
|
||||
pub mod commands;
|
||||
pub mod container;
|
||||
|
@ -1,4 +1,5 @@
|
||||
use super::args::ContainerArgs;
|
||||
use crate::apparmor;
|
||||
use crate::{
|
||||
capabilities, hooks, namespaces::Namespaces, process::channel, rootfs, rootless::Rootless,
|
||||
seccomp, tty, utils,
|
||||
@ -9,34 +10,19 @@ use nix::mount::MsFlags;
|
||||
use nix::sched::CloneFlags;
|
||||
use nix::{
|
||||
fcntl,
|
||||
sys::statfs,
|
||||
unistd::{self, Gid, Uid},
|
||||
};
|
||||
use oci_spec::runtime::{LinuxNamespaceType, User};
|
||||
use std::collections::HashMap;
|
||||
use std::{
|
||||
env, fs,
|
||||
os::unix::io::AsRawFd,
|
||||
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.
|
||||
fn get_open_fds() -> Result<Vec<i32>> {
|
||||
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))?;
|
||||
|
||||
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)) {
|
||||
nix_mount(
|
||||
None::<&str>,
|
||||
@ -470,7 +461,7 @@ mod tests {
|
||||
use anyhow::{bail, Result};
|
||||
use nix::{fcntl, sys, unistd};
|
||||
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
|
||||
// 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::os::linux::fs::MetadataExt;
|
||||
use std::os::unix::fs::DirBuilderExt;
|
||||
use std::os::unix::prelude::AsRawFd;
|
||||
use std::path::{Path, PathBuf};
|
||||
use std::time::Duration;
|
||||
|
||||
use anyhow::Context;
|
||||
use anyhow::{bail, Result};
|
||||
use nix::sys::stat::Mode;
|
||||
use nix::sys::statfs;
|
||||
use nix::unistd;
|
||||
|
||||
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 {
|
||||
path: Option<PathBuf>,
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user