feat(freeipa): add AppArmor deny profiles to binary blocking policy
Binary blocking now applies two layers: 1. PATH-priority wrapper in /usr/local/bin/ (existing) 2. Empty AppArmor profile in /etc/apparmor.d/ loaded in enforce mode An empty AppArmor profile denies all access — the blocked binary cannot load shared libraries and exits immediately with a permission error, covering callers that use absolute paths and bypassed the wrapper. AppArmor layer is skipped silently when apparmor_parser is not present, and deferred with a warning if the real binary is not yet installed. Profiles are unloaded and deleted when the host leaves the policy group. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>main
parent
45fd7e5d36
commit
fb8ca498ef
|
|
@ -5,13 +5,14 @@
|
|||
# leaving the group removes it on the next run (every 30 min via systemd timer).
|
||||
#
|
||||
# Host-group naming conventions:
|
||||
# policy-block-binary-<name> Block execution of <name> via a wrapper in /usr/local/bin/
|
||||
# policy-block-binary-<name> Block execution of <name> via two layers:
|
||||
# 1. PATH-priority wrapper in /usr/local/bin/ (catches $PATH calls)
|
||||
# 2. AppArmor deny profile in /etc/apparmor.d/ (catches absolute paths)
|
||||
# AppArmor layer is skipped silently if apparmor_parser is not present.
|
||||
# policy-timeshift-backup Enforce a daily Timeshift snapshot (requires timeshift installed)
|
||||
# policy-security-scan Enforce daily ClamAV + rkhunter + chkrootkit scans
|
||||
#
|
||||
# Notes:
|
||||
# - Binary blocking uses a PATH-priority wrapper in /usr/local/bin/; callers using
|
||||
# the full absolute path bypass it. For hard enforcement add AppArmor/SELinux rules.
|
||||
# - Install scan tools first: add the host to ansipa-module-anti-malware.
|
||||
# - Configure Timeshift (type + target device) before enabling policy-timeshift-backup.
|
||||
|
||||
|
|
@ -20,6 +21,7 @@ set -euo pipefail
|
|||
LOG_TAG="ansipa-policies"
|
||||
STATE_DIR="/var/lib/ansipa-policies"
|
||||
BLOCK_DIR="/usr/local/bin"
|
||||
APPARMOR_DIR="/etc/apparmor.d"
|
||||
CRON_DIR="/etc/cron.d"
|
||||
|
||||
log() { echo "[$LOG_TAG] $*"; logger -t "$LOG_TAG" "$*" 2>/dev/null || true; }
|
||||
|
|
@ -60,7 +62,7 @@ fi
|
|||
log "Active policies — block-binary: ${ACTIVE_BLOCK_BINARIES[*]:-none}" \
|
||||
"| timeshift-backup: $WANT_TIMESHIFT_BACKUP | security-scan: $WANT_SECURITY_SCAN"
|
||||
|
||||
# ── Helper ────────────────────────────────────────────────────────────────────
|
||||
# ── Helpers ───────────────────────────────────────────────────────────────────
|
||||
in_active_list() {
|
||||
local needle="$1"
|
||||
for b in "${ACTIVE_BLOCK_BINARIES[@]}"; do
|
||||
|
|
@ -69,9 +71,73 @@ in_active_list() {
|
|||
return 1
|
||||
}
|
||||
|
||||
# Find the real installed binary, skipping /usr/local/bin where our wrapper lives.
|
||||
find_real_binary() {
|
||||
local name="$1"
|
||||
for dir in /usr/bin /usr/sbin /bin /sbin /usr/local/sbin /opt/bin; do
|
||||
[[ -x "$dir/$name" ]] && echo "$dir/$name" && return 0
|
||||
done
|
||||
return 1
|
||||
}
|
||||
|
||||
aa_profile_file() { echo "$APPARMOR_DIR/ansipa-block-${1}"; }
|
||||
|
||||
# Load an AppArmor deny profile for a binary path.
|
||||
# An empty AppArmor profile denies all access: the binary cannot load shared
|
||||
# libraries or open any files, so it exits immediately with a permission error.
|
||||
apply_apparmor_block() {
|
||||
local bin="$1"
|
||||
command -v apparmor_parser &>/dev/null || return 0
|
||||
|
||||
local bin_path
|
||||
bin_path=$(find_real_binary "$bin") || {
|
||||
warn "AppArmor block: real binary '$bin' not found on disk — profile skipped until it is installed."
|
||||
return 0
|
||||
}
|
||||
|
||||
local profile_file
|
||||
profile_file=$(aa_profile_file "$bin")
|
||||
|
||||
# Write the profile only if it doesn't exist or points to a different path.
|
||||
if [[ ! -f "$profile_file" ]] || ! grep -qF "$bin_path" "$profile_file" 2>/dev/null; then
|
||||
log "Writing AppArmor block profile: $profile_file ($bin_path)"
|
||||
cat > "$profile_file" <<PROFILE
|
||||
#include <tunables/global>
|
||||
|
||||
# ansipa-block-policy: managed by ansipa-enforce-policies — do not edit manually.
|
||||
# Deny all access so the binary cannot load libraries or run.
|
||||
# To unblock manually: apparmor_parser -R $profile_file && rm $profile_file
|
||||
$bin_path {
|
||||
}
|
||||
PROFILE
|
||||
fi
|
||||
|
||||
# Load (or reload) the profile in enforce mode.
|
||||
if ! apparmor_parser -r "$profile_file" 2>/dev/null; then
|
||||
warn "apparmor_parser failed to load $profile_file — AppArmor block not active"
|
||||
fi
|
||||
}
|
||||
|
||||
# Remove the AppArmor deny profile for a binary.
|
||||
remove_apparmor_block() {
|
||||
local bin="$1"
|
||||
command -v apparmor_parser &>/dev/null || return 0
|
||||
|
||||
local profile_file
|
||||
profile_file=$(aa_profile_file "$bin")
|
||||
[[ -f "$profile_file" ]] || return 0
|
||||
|
||||
if grep -q "ansipa-block-policy" "$profile_file" 2>/dev/null; then
|
||||
apparmor_parser -R "$profile_file" 2>/dev/null || true
|
||||
rm -f "$profile_file"
|
||||
log "Removed AppArmor block profile: $bin"
|
||||
fi
|
||||
}
|
||||
|
||||
# ── Binary blocking ───────────────────────────────────────────────────────────
|
||||
# Wrapper scripts are placed in /usr/local/bin/ (higher PATH priority than /usr/bin/).
|
||||
# The "blocked by ansipa policy" sentinel line lets us identify managed wrappers.
|
||||
# Layer 1: PATH-priority wrapper in /usr/local/bin/ — blocks $PATH-based calls.
|
||||
# Layer 2: AppArmor deny profile — blocks absolute-path calls and direct exec().
|
||||
# Both layers use the "ansipa policy" sentinel to identify managed files.
|
||||
|
||||
BLOCK_STATE="$STATE_DIR/blocked-binaries"
|
||||
[[ -f "$BLOCK_STATE" ]] || touch "$BLOCK_STATE"
|
||||
|
|
@ -79,7 +145,7 @@ BLOCK_STATE="$STATE_DIR/blocked-binaries"
|
|||
for BIN in "${ACTIVE_BLOCK_BINARIES[@]}"; do
|
||||
WRAPPER="$BLOCK_DIR/$BIN"
|
||||
if [[ ! -f "$WRAPPER" ]] || ! grep -q "blocked by ansipa policy" "$WRAPPER" 2>/dev/null; then
|
||||
log "Applying block: $BIN"
|
||||
log "Applying PATH wrapper block: $BIN"
|
||||
cat > "$WRAPPER" <<WRAPPER
|
||||
#!/bin/bash
|
||||
# blocked by ansipa policy
|
||||
|
|
@ -88,17 +154,19 @@ exit 1
|
|||
WRAPPER
|
||||
chmod 755 "$WRAPPER"
|
||||
fi
|
||||
apply_apparmor_block "$BIN"
|
||||
done
|
||||
|
||||
# Remove wrappers for binaries no longer in any active policy group.
|
||||
# Remove blocks for binaries no longer in any active policy group.
|
||||
while IFS= read -r OLD_BIN; do
|
||||
[[ -z "$OLD_BIN" ]] && continue
|
||||
if ! in_active_list "$OLD_BIN"; then
|
||||
WRAPPER="$BLOCK_DIR/$OLD_BIN"
|
||||
if [[ -f "$WRAPPER" ]] && grep -q "blocked by ansipa policy" "$WRAPPER" 2>/dev/null; then
|
||||
rm -f "$WRAPPER"
|
||||
log "Removed block: $OLD_BIN"
|
||||
log "Removed PATH wrapper block: $OLD_BIN"
|
||||
fi
|
||||
remove_apparmor_block "$OLD_BIN"
|
||||
fi
|
||||
done < "$BLOCK_STATE"
|
||||
|
||||
|
|
|
|||
Loading…
Reference in New Issue