1
0
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:
Furisto 2021-09-21 01:13:13 +02:00 committed by GitHub
parent 2af52169cf
commit 19316ce3c1
Signed by: GitHub
GPG Key ID: 4AEE18F83AFDEB23
6 changed files with 85 additions and 20 deletions

@ -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

@ -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

@ -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>,
}