Implement mandatory access controls for Podman containers using SELinux on Red Hat-based systems and AppArmor on Debian-based systems. Learn to configure security policies, monitor violations, and harden container security with production-grade controls.
Prerequisites
- Podman installed and configured
- Root or sudo access
- Basic understanding of Linux security concepts
What this solves
Container security relies on proper isolation between containers and the host system. While Podman provides better security defaults than Docker through rootless containers, mandatory access controls (MAC) like SELinux and AppArmor add critical defense-in-depth protection. These systems enforce fine-grained security policies that prevent containers from accessing unauthorized resources, even if an attacker compromises the container runtime.
Prerequisites
Before starting, ensure you have Podman installed and configured. If you need to install Podman, follow our Podman installation guide.
Step-by-step configuration
Update system packages
Start by updating your package manager to ensure you have the latest security updates.
sudo apt update && sudo apt upgrade -y
Install mandatory access control systems
Install SELinux on Red Hat-based systems or AppArmor on Debian-based systems along with required utilities.
sudo apt install -y apparmor apparmor-utils apparmor-profiles apparmor-profiles-extra
Verify mandatory access control status
Check that your mandatory access control system is active and enforcing policies.
sudo systemctl status apparmor
sudo aa-status
Configure SELinux for Podman containers (Red Hat-based systems)
Enable SELinux container separation and configure proper contexts for Podman containers.
sudo setsebool -P container_manage_cgroup true
sudo setsebool -P virt_use_nfs true
sudo setsebool -P virt_sandbox_use_all_caps true
Create custom SELinux policy for containers
Create a custom SELinux policy module for enhanced container security controls.
policy_module(container_custom, 1.0)
require {
type container_t;
type user_home_t;
type http_port_t;
class tcp_socket name_bind;
class dir { read getattr open search };
}
Allow containers to bind to HTTP ports
allow container_t http_port_t:tcp_socket name_bind;
Restrict access to user home directories
neverallow container_t user_home_t:dir { read getattr open search };
sudo mkdir -p /etc/selinux/local
sudo checkmodule -M -m -o /etc/selinux/local/container_custom.mod /etc/selinux/local/container_custom.te
sudo semodule_package -o /etc/selinux/local/container_custom.pp -m /etc/selinux/local/container_custom.mod
sudo semodule -i /etc/selinux/local/container_custom.pp
Create custom AppArmor profile for Podman containers (Debian-based systems)
Create a custom AppArmor profile that restricts container capabilities and file system access.
#include
profile podman-custom flags=(attach_disconnected,mediate_deleted) {
#include
# Network access
network inet tcp,
network inet udp,
network inet6 tcp,
network inet6 udp,
network netlink raw,
# File system access (restricted)
/usr/bin/** rix,
/bin/** rix,
/sbin/** rix,
/lib/** rm,
/lib64/** rm,
# Container-specific paths
/var/lib/containers/** rw,
/tmp/** rw,
/dev/null rw,
/dev/zero rw,
/dev/random r,
/dev/urandom r,
# Deny access to sensitive system areas
deny /etc/shadow r,
deny /etc/passwd w,
deny /boot/** rwklx,
deny /sys/kernel/security/** rwklx,
# Capabilities (minimal set)
capability net_bind_service,
capability setuid,
capability setgid,
capability dac_override,
}
sudo apparmor_parser -r /etc/apparmor.d/podman-custom
sudo systemctl reload apparmor
Configure Podman to use security profiles
Create a Podman configuration that automatically applies security profiles to containers.
[containers]
seccomp_profile = "/usr/share/containers/seccomp.json"
apparmor_profile = "podman-custom"
selinux = true
label = true
[engine]
runtime = "runc"
events_logger = "journald"
namespace = "containers"
[machine]
cpus = 2
memory = 2048
mkdir -p ~/.config/containers
Test container security with a sample application
Deploy a test container to verify that security policies are properly applied and enforced.
podman run -d --name security-test \
--security-opt label=type:container_t \
--read-only \
--tmpfs /tmp \
-p 8080:80 \
docker.io/nginx:alpine
Create container security monitoring script
Create a monitoring script to track security violations and policy enforcement.
#!/bin/bash
Container Security Monitoring Script
Monitors SELinux/AppArmor violations for containers
LOG_FILE="/var/log/container-security.log"
DATE=$(date '+%Y-%m-%d %H:%M:%S')
log_message() {
echo "[$DATE] $1" >> "$LOG_FILE"
echo "[$DATE] $1"
}
Check for SELinux violations (Red Hat-based systems)
if command -v ausearch &> /dev/null; then
log_message "Checking SELinux violations..."
VIOLATIONS=$(ausearch -m AVC -ts recent 2>/dev/null | grep container | wc -l)
if [ "$VIOLATIONS" -gt 0 ]; then
log_message "WARNING: Found $VIOLATIONS SELinux violations for containers"
ausearch -m AVC -ts recent 2>/dev/null | grep container >> "$LOG_FILE"
else
log_message "No SELinux violations detected"
fi
fi
Check for AppArmor violations (Debian-based systems)
if command -v aa-status &> /dev/null; then
log_message "Checking AppArmor violations..."
VIOLATIONS=$(dmesg | grep -i apparmor | grep -i denied | grep podman | wc -l)
if [ "$VIOLATIONS" -gt 0 ]; then
log_message "WARNING: Found $VIOLATIONS AppArmor violations for containers"
dmesg | grep -i apparmor | grep -i denied | grep podman >> "$LOG_FILE"
else
log_message "No AppArmor violations detected"
fi
fi
Check running containers security context
log_message "Checking container security contexts..."
podman ps --format "table {{.Names}}\t{{.Status}}\t{{.SecurityOpt}}" >> "$LOG_FILE"
log_message "Security monitoring complete"
sudo chmod 755 /usr/local/bin/monitor-container-security.sh
Set up automated security monitoring
Create a systemd timer to run security monitoring every hour and log violations.
[Unit]
Description=Container Security Monitor
After=network.target
[Service]
Type=oneshot
User=root
ExecStart=/usr/local/bin/monitor-container-security.sh
StandardOutput=journal
StandardError=journal
[Unit]
Description=Run container security monitoring hourly
Requires=container-security-monitor.service
[Timer]
OnCalendar=hourly
Persistent=true
[Install]
WantedBy=timers.target
sudo systemctl daemon-reload
sudo systemctl enable --now container-security-monitor.timer
sudo systemctl status container-security-monitor.timer
Verify your setup
Test that your mandatory access controls are properly protecting containers.
# Check container is running with security context
podman ps --format "table {{.Names}}\t{{.Status}}\t{{.SecurityOpt}}"
Test security policy enforcement
podman exec security-test ls /etc/shadow 2>&1 || echo "Access denied - security working"
Check security monitoring
sudo /usr/local/bin/monitor-container-security.sh
cat /var/log/container-security.log
Verify MAC system status
sudo systemctl status container-security-monitor.timer
# Check AppArmor profile status
sudo aa-status | grep podman
dmesg | grep -i apparmor | tail -5
Advanced security hardening
Configure resource limits and capabilities
Add additional security controls through resource limits and capability restrictions.
[containers]
default_capabilities = [
"CHOWN",
"DAC_OVERRIDE",
"FOWNER",
"FSETID",
"KILL",
"SETGID",
"SETUID",
"SETPCAP",
"NET_BIND_SERVICE"
]
default_ulimits = [
"nofile=65536:65536",
"nproc=4096:4096"
]
Enable user namespace for additional isolation
userns_size = 65536
Create secure container startup template
Create a script template for launching containers with enhanced security settings.
#!/bin/bash
Secure Podman Container Launcher
Usage: secure-podman-run.sh [port]
CONTAINER_NAME="$1"
IMAGE="$2"
PORT="${3:-8080}"
if [ -z "$CONTAINER_NAME" ] || [ -z "$IMAGE" ]; then
echo "Usage: $0 [port]"
exit 1
fi
echo "Launching secure container: $CONTAINER_NAME"
podman run -d \
--name "$CONTAINER_NAME" \
--security-opt no-new-privileges:true \
--security-opt label=type:container_t \
--cap-drop ALL \
--cap-add NET_BIND_SERVICE \
--cap-add SETUID \
--cap-add SETGID \
--read-only \
--tmpfs /tmp:rw,noexec,nosuid,size=100m \
--tmpfs /var/run:rw,noexec,nosuid,size=100m \
--pids-limit 1000 \
--memory 512m \
--cpus 1.0 \
--user 1001:1001 \
-p "$PORT:8080" \
"$IMAGE"
echo "Container $CONTAINER_NAME started with enhanced security"
podman ps --filter name="$CONTAINER_NAME"
sudo chmod 755 /usr/local/bin/secure-podman-run.sh
Monitor and troubleshoot security violations
Set up centralized logging for security events
Configure centralized logging to capture and analyze security violations across containers.
# Container security logging
:msg, contains, "apparmor" /var/log/container-security.log
:msg, contains, "selinux" /var/log/container-security.log
:msg, contains, "container" /var/log/container-security.log
& stop
sudo systemctl restart rsyslog
Common issues
| Symptom | Cause | Fix |
|---|---|---|
| Container fails to start with permission denied | SELinux/AppArmor blocking container access | Check ausearch -m AVC or dmesg | grep apparmor for denials |
| Container cannot bind to privileged ports | Missing NET_BIND_SERVICE capability | Add --cap-add NET_BIND_SERVICE to podman run |
| File access denied inside container | Incorrect SELinux context on host files | Run sudo chcon -Rt container_file_t /path/to/files |
| AppArmor profile not loading | Syntax error in profile | Run sudo apparmor_parser -Q /etc/apparmor.d/profile to check |
| High security violation count | Overly restrictive policies | Review logs and adjust policies incrementally |
| Container performance issues | Excessive security checks | Monitor with our performance monitoring guide |
Production considerations
For production deployments, integrate container security monitoring with your existing observability stack. Consider implementing Cilium Tetragon runtime security for advanced eBPF-based monitoring and threat detection.
Set up automated alerting when security violations exceed acceptable thresholds. Create separate security profiles for different container workload types (web servers, databases, batch jobs) to balance security with functionality.
Regularly audit your security policies and update them as application requirements change. Keep SELinux and AppArmor policies in version control and test changes in staging environments before production deployment.
Next steps
Automated install script
Run this to automate the entire setup
#!/usr/bin/env bash
set -euo pipefail
# Colors for output
RED='\033[0;31m'
GREEN='\033[0;32m'
YELLOW='\033[1;33m'
NC='\033[0m'
# Logging functions
log_info() { echo -e "${GREEN}[INFO]${NC} $1"; }
log_warn() { echo -e "${YELLOW}[WARN]${NC} $1"; }
log_error() { echo -e "${RED}[ERROR]${NC} $1"; }
# Cleanup function
cleanup() {
if [ $? -ne 0 ]; then
log_error "Installation failed. Check the logs above for details."
fi
}
trap cleanup ERR
# Usage message
usage() {
echo "Usage: $0 [--skip-updates]"
echo " --skip-updates Skip system package updates"
exit 1
}
# Parse arguments
SKIP_UPDATES=false
while [[ $# -gt 0 ]]; do
case $1 in
--skip-updates)
SKIP_UPDATES=true
shift
;;
-h|--help)
usage
;;
*)
log_error "Unknown option: $1"
usage
;;
esac
done
# Check if running as root or with sudo
if [[ $EUID -ne 0 ]]; then
log_error "This script must be run as root or with sudo"
exit 1
fi
# Detect distribution
if [ ! -f /etc/os-release ]; then
log_error "/etc/os-release not found. Cannot detect distribution."
exit 1
fi
. /etc/os-release
case "$ID" in
ubuntu|debian)
PKG_MGR="apt"
PKG_UPDATE="apt update"
PKG_UPGRADE="apt upgrade -y"
PKG_INSTALL="apt install -y"
MAC_SYSTEM="apparmor"
;;
almalinux|rocky|centos|rhel|ol|fedora)
PKG_MGR="dnf"
PKG_UPDATE="dnf check-update || true"
PKG_UPGRADE="dnf update -y"
PKG_INSTALL="dnf install -y"
MAC_SYSTEM="selinux"
;;
amzn)
PKG_MGR="yum"
PKG_UPDATE="yum check-update || true"
PKG_UPGRADE="yum update -y"
PKG_INSTALL="yum install -y"
MAC_SYSTEM="selinux"
;;
*)
log_error "Unsupported distribution: $ID"
exit 1
;;
esac
log_info "Detected distribution: $ID using $MAC_SYSTEM"
# Update system packages
if [ "$SKIP_UPDATES" = false ]; then
echo "[1/8] Updating system packages..."
$PKG_UPDATE
$PKG_UPGRADE
else
echo "[1/8] Skipping system updates..."
fi
# Install mandatory access control systems
echo "[2/8] Installing mandatory access control system ($MAC_SYSTEM)..."
if [ "$MAC_SYSTEM" = "apparmor" ]; then
$PKG_INSTALL apparmor apparmor-utils apparmor-profiles apparmor-profiles-extra
systemctl enable apparmor
systemctl start apparmor
else
$PKG_INSTALL selinux-policy-targeted policycoreutils-python-utils setroubleshoot-server
fi
# Verify MAC status
echo "[3/8] Verifying mandatory access control status..."
if [ "$MAC_SYSTEM" = "apparmor" ]; then
if ! systemctl is-active --quiet apparmor; then
log_warn "AppArmor is not active. Starting it now..."
systemctl start apparmor
fi
aa-status | head -5
else
if [ "$(getenforce)" = "Disabled" ]; then
log_warn "SELinux is disabled. Enabling enforcing mode..."
setenforce 1
sed -i 's/SELINUX=disabled/SELINUX=enforcing/' /etc/selinux/config
fi
sestatus
fi
# Configure MAC for Podman containers
echo "[4/8] Configuring MAC system for Podman containers..."
if [ "$MAC_SYSTEM" = "selinux" ]; then
# Configure SELinux booleans for containers
setsebool -P container_manage_cgroup true
setsebool -P virt_use_nfs true
setsebool -P virt_sandbox_use_all_caps true
log_info "SELinux container booleans configured"
fi
# Create custom security policies
echo "[5/8] Creating custom security policies..."
if [ "$MAC_SYSTEM" = "selinux" ]; then
# Create SELinux policy directory
mkdir -p /etc/selinux/local
# Create custom SELinux policy
cat > /etc/selinux/local/container_custom.te << 'EOF'
policy_module(container_custom, 1.0)
require {
type container_t;
type user_home_t;
type http_port_t;
class tcp_socket name_bind;
class dir { read getattr open search };
}
# Allow containers to bind to HTTP ports
allow container_t http_port_t:tcp_socket name_bind;
# Restrict access to user home directories
neverallow container_t user_home_t:dir { read getattr open search };
EOF
# Compile and install SELinux policy
checkmodule -M -m -o /etc/selinux/local/container_custom.mod /etc/selinux/local/container_custom.te
semodule_package -o /etc/selinux/local/container_custom.pp -m /etc/selinux/local/container_custom.mod
semodule -i /etc/selinux/local/container_custom.pp
log_info "Custom SELinux policy installed"
else
# Create custom AppArmor profile
cat > /etc/apparmor.d/podman-custom << 'EOF'
#include <tunables/global>
profile podman-custom flags=(attach_disconnected,mediate_deleted) {
#include <abstractions/base>
# Network access
network inet tcp,
network inet udp,
network inet6 tcp,
network inet6 udp,
network netlink raw,
# File system access (restricted)
/usr/bin/** rix,
/bin/** rix,
/sbin/** rix,
/lib/** rm,
/lib64/** rm,
# Container-specific paths
/var/lib/containers/** rw,
/tmp/** rw,
/dev/null rw,
/dev/zero rw,
/dev/random r,
/dev/urandom r,
# Deny access to sensitive system areas
deny /etc/shadow r,
deny /etc/passwd w,
deny /boot/** rwklx,
deny /sys/kernel/security/** rwklx,
# Capabilities (minimal set)
capability net_bind_service,
capability setuid,
capability setgid,
capability dac_override,
}
EOF
# Load AppArmor profile
apparmor_parser -r /etc/apparmor.d/podman-custom
systemctl reload apparmor
log_info "Custom AppArmor profile loaded"
fi
# Configure Podman security settings
echo "[6/8] Configuring Podman security settings..."
mkdir -p /etc/containers
cat > /etc/containers/containers.conf << EOF
[containers]
default_capabilities = [
"CHOWN",
"DAC_OVERRIDE",
"FOWNER",
"FSETID",
"KILL",
"NET_BIND_SERVICE",
"SETFCAP",
"SETGID",
"SETPCAP",
"SETUID",
"SYS_CHROOT"
]
seccomp_profile = "/usr/share/containers/seccomp.json"
apparmor_profile = "podman-custom"
selinux = true
[network]
default_network = "podman"
EOF
chown root:root /etc/containers/containers.conf
chmod 644 /etc/containers/containers.conf
# Create Podman security wrapper script
echo "[7/8] Creating Podman security wrapper..."
cat > /usr/local/bin/podman-secure << 'EOF'
#!/bin/bash
# Secure Podman wrapper with mandatory access controls
if [ "$MAC_SYSTEM" = "selinux" ]; then
exec podman --security-opt label=type:container_t "$@"
else
exec podman --security-opt apparmor:podman-custom "$@"
fi
EOF
chmod 755 /usr/local/bin/podman-secure
chown root:root /usr/local/bin/podman-secure
# Verification
echo "[8/8] Verifying installation..."
# Check MAC system status
if [ "$MAC_SYSTEM" = "apparmor" ]; then
if systemctl is-active --quiet apparmor && aa-status | grep -q "podman-custom"; then
log_info "✓ AppArmor is active with custom Podman profile"
else
log_error "✗ AppArmor verification failed"
exit 1
fi
else
if [ "$(getenforce)" = "Enforcing" ] && semodule -l | grep -q "container_custom"; then
log_info "✓ SELinux is enforcing with custom container policy"
else
log_error "✗ SELinux verification failed"
exit 1
fi
fi
# Check Podman configuration
if [ -f /etc/containers/containers.conf ]; then
log_info "✓ Podman security configuration created"
else
log_error "✗ Podman configuration not found"
exit 1
fi
# Check security wrapper
if [ -x /usr/local/bin/podman-secure ]; then
log_info "✓ Secure Podman wrapper created"
else
log_error "✗ Secure Podman wrapper not found"
exit 1
fi
echo
log_info "Podman MAC security configuration completed successfully!"
log_info "Use 'podman-secure' command for containers with enhanced security policies"
log_info "Regular 'podman' command will also use the new security defaults"
if [ "$MAC_SYSTEM" = "selinux" ]; then
log_warn "Note: If SELinux was previously disabled, a system reboot is recommended"
fi
Review the script before running. Execute with: bash install.sh