use super::*;
use crate::operation::{Operation, XattrNamespace};
use crate::state::mac::AppArmorState;
use crate::state::Probe;
use crate::test_helpers::StateBuilder;
#[test]
fn chmod_in_enforce_mode_returns_degraded_with_capability_hint() {
let state = StateBuilder::new()
.operation(Operation::Chmod)
.with_apparmor_state(AppArmorState {
profile_label: "nginx (enforce)".into(),
})
.component("/", 0, 0, 0o755)
.component_file("/var/www/html/index.html", 0, 0, 0o644)
.build();
let result = check_apparmor(&state);
assert!(
result.is_degraded(),
"expected Degraded for Chmod in enforce mode, got {result:?}"
);
if let LayerResult::Degraded { reason } = &result {
assert!(
reason.contains("capability rules"),
"expected 'capability rules' in reason, got: {reason}"
);
}
}
#[test]
fn chown_uid_in_enforce_mode_returns_degraded_with_capability_hint() {
let state = StateBuilder::new()
.operation(Operation::ChownUid)
.with_apparmor_state(AppArmorState {
profile_label: "/usr/sbin/sshd (enforce)".into(),
})
.component("/", 0, 0, 0o755)
.component_file("/etc/passwd", 0, 0, 0o644)
.build();
let result = check_apparmor(&state);
assert!(
result.is_degraded(),
"expected Degraded for ChownUid in enforce mode, got {result:?}"
);
if let LayerResult::Degraded { reason } = &result {
assert!(
reason.contains("capability rules"),
"expected 'capability rules' in reason, got: {reason}"
);
}
}
#[test]
fn setxattr_security_in_enforce_mode_returns_degraded_with_capability_hint() {
let state = StateBuilder::new()
.operation(Operation::SetXattr {
namespace: XattrNamespace::Security,
})
.with_apparmor_state(AppArmorState {
profile_label: "nginx (enforce)".into(),
})
.component("/", 0, 0, 0o755)
.component_file("/var/www/html/index.html", 0, 0, 0o644)
.build();
let result = check_apparmor(&state);
assert!(
result.is_degraded(),
"expected Degraded for SetXattr in enforce mode, got {result:?}"
);
if let LayerResult::Degraded { reason } = &result {
assert!(
reason.contains("capability rules"),
"expected 'capability rules' in reason, got: {reason}"
);
}
}
#[test]
fn read_in_enforce_mode_uses_existing_path() {
let state = StateBuilder::new()
.operation(Operation::Read)
.with_apparmor_state(AppArmorState {
profile_label: "nginx (enforce)".into(),
})
.component("/", 0, 0, 0o755)
.component_file("/var/www/html/index.html", 0, 0, 0o644)
.build();
let result = check_apparmor(&state);
assert!(
result.is_degraded(),
"expected Degraded for Read in enforce mode, got {result:?}"
);
if let LayerResult::Degraded { reason } = &result {
assert!(
!reason.contains("capability rules"),
"Read should not mention 'capability rules', got: {reason}"
);
assert!(
reason.contains("libapparmor"),
"Read should mention 'libapparmor', got: {reason}"
);
}
}
#[test]
fn parse_profile_label_enforce() {
let (name, mode) = parse_profile_label("nginx (enforce)");
assert_eq!(name, "nginx");
assert!(matches!(mode, ProfileMode::Enforce));
}
#[test]
fn parse_profile_label_complain() {
let (name, mode) = parse_profile_label("/usr/sbin/sshd (complain)");
assert_eq!(name, "/usr/sbin/sshd");
assert!(matches!(mode, ProfileMode::Complain));
}
#[test]
fn parse_profile_label_no_suffix() {
let (name, mode) = parse_profile_label("unconfined");
assert_eq!(name, "unconfined");
assert!(matches!(mode, ProfileMode::Unknown));
}
#[test]
fn parse_profile_label_unknown_mode() {
let (name, mode) = parse_profile_label("nginx (audit)");
assert_eq!(name, "nginx");
assert!(matches!(mode, ProfileMode::Unknown));
}
#[test]
fn parse_profile_label_full_path_enforce() {
let (name, mode) = parse_profile_label("/usr/bin/man (enforce)");
assert_eq!(name, "/usr/bin/man");
assert!(matches!(mode, ProfileMode::Enforce));
}
#[test]
fn apparmor_unconfined_returns_pass() {
let state = StateBuilder::new()
.operation(Operation::Read)
.with_apparmor_state(AppArmorState {
profile_label: "unconfined".into(),
})
.component("/", 0, 0, 0o755)
.component_file("/etc/hosts", 0, 0, 0o644)
.build();
let result = check_apparmor(&state);
assert!(
result.is_pass(),
"expected Pass for unconfined profile, got {result:?}"
);
if let LayerResult::Pass { warnings, .. } = &result {
assert!(warnings.is_empty(), "unconfined should carry no warnings");
}
}
#[test]
fn apparmor_complain_mode_returns_pass_with_warnings() {
let state = StateBuilder::new()
.operation(Operation::Read)
.with_apparmor_state(AppArmorState {
profile_label: "nginx (complain)".into(),
})
.component("/", 0, 0, 0o755)
.component_file("/var/www/html/index.html", 0, 0, 0o644)
.build();
let result = check_apparmor(&state);
assert!(
result.is_pass(),
"expected Pass for complain-mode profile, got {result:?}"
);
if let LayerResult::Pass { warnings, .. } = &result {
assert!(
!warnings.is_empty(),
"complain mode must carry at least one warning"
);
}
}
#[test]
fn apparmor_enforce_mode_returns_degraded() {
let state = StateBuilder::new()
.operation(Operation::Write)
.with_apparmor_state(AppArmorState {
profile_label: "nginx (enforce)".into(),
})
.component("/", 0, 0, 0o755)
.component_file("/var/www/html/index.html", 0, 0, 0o644)
.build();
let result = check_apparmor(&state);
assert!(
result.is_degraded(),
"expected Degraded for enforce-mode without libapparmor, got {result:?}"
);
}
#[test]
fn apparmor_unknown_mode_returns_degraded() {
let state = StateBuilder::new()
.operation(Operation::Read)
.with_apparmor_state(AppArmorState {
profile_label: "nginx (audit)".into(),
})
.component("/", 0, 0, 0o755)
.component_file("/var/log/nginx/access.log", 0, 0, 0o640)
.build();
let result = check_apparmor(&state);
assert!(
result.is_degraded(),
"expected Degraded for unknown mode label, got {result:?}"
);
}
#[test]
fn apparmor_probe_unknown_returns_degraded() {
let state = StateBuilder::new()
.operation(Operation::Read)
.component("/", 0, 0, 0o755)
.component_file("/etc/shadow", 0, 0, 0o640)
.build();
assert_eq!(state.mac_state.apparmor, Probe::Unknown);
let result = check_apparmor(&state);
assert!(
result.is_degraded(),
"expected Degraded for unknown probe, got {result:?}"
);
}
#[test]
fn apparmor_probe_inaccessible_returns_degraded() {
let mut state = StateBuilder::new()
.operation(Operation::Read)
.component("/", 0, 0, 0o755)
.component_file("/etc/shadow", 0, 0, 0o640)
.build();
state.mac_state.apparmor = Probe::Inaccessible;
let result = check_apparmor(&state);
assert!(
result.is_degraded(),
"expected Degraded for inaccessible probe, got {result:?}"
);
}