feat: add security hardening, AppArmor, and ARM64 Raspberry Pi support (Phase 6)
Security hardening: bind kubeconfig server to localhost, mount hardening (noexec/nosuid/nodev on tmpfs), sysctl network hardening, kernel module loading lock after boot, SHA256 checksum verification for downloads, kernel AppArmor + Audit support, complain-mode AppArmor profiles for containerd and kubelet, and security integration test. ARM64 Raspberry Pi support: piCore64 base extraction, RPi kernel build from raspberrypi/linux fork, RPi firmware fetch, SD card image with 4- partition GPT and tryboot A/B mechanism, BootEnv Go interface abstracting GRUB vs RPi boot environments, architecture-aware build scripts, QEMU aarch64 dev VM and boot test. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -64,6 +64,7 @@ export KUBESOLO_CLOUDINIT=""
|
||||
export KUBESOLO_EXTRA_FLAGS=""
|
||||
export KUBESOLO_PORTAINER_EDGE_ID=""
|
||||
export KUBESOLO_PORTAINER_EDGE_KEY=""
|
||||
export KUBESOLO_NOAPPARMOR=""
|
||||
|
||||
# --- Logging ---
|
||||
log() {
|
||||
|
||||
@@ -12,10 +12,10 @@ if ! mountpoint -q /dev 2>/dev/null; then
|
||||
mount -t devtmpfs devtmpfs /dev 2>/dev/null || mount -t tmpfs tmpfs /dev
|
||||
fi
|
||||
if ! mountpoint -q /tmp 2>/dev/null; then
|
||||
mount -t tmpfs tmpfs /tmp
|
||||
mount -t tmpfs -o noexec,nosuid,nodev,size=256M tmpfs /tmp
|
||||
fi
|
||||
if ! mountpoint -q /run 2>/dev/null; then
|
||||
mount -t tmpfs tmpfs /run
|
||||
mount -t tmpfs -o nosuid,nodev,size=64M tmpfs /run
|
||||
fi
|
||||
|
||||
mkdir -p /dev/pts /dev/shm
|
||||
@@ -23,7 +23,7 @@ if ! mountpoint -q /dev/pts 2>/dev/null; then
|
||||
mount -t devpts devpts /dev/pts
|
||||
fi
|
||||
if ! mountpoint -q /dev/shm 2>/dev/null; then
|
||||
mount -t tmpfs tmpfs /dev/shm
|
||||
mount -t tmpfs -o noexec,nosuid,nodev,size=64M tmpfs /dev/shm
|
||||
fi
|
||||
|
||||
# Ensure essential device nodes exist (devtmpfs may be incomplete after switch_root)
|
||||
|
||||
@@ -11,9 +11,14 @@ for arg in $(cat /proc/cmdline); do
|
||||
kubesolo.flags=*) KUBESOLO_EXTRA_FLAGS="${arg#kubesolo.flags=}" ;;
|
||||
kubesolo.edge_id=*) KUBESOLO_PORTAINER_EDGE_ID="${arg#kubesolo.edge_id=}" ;;
|
||||
kubesolo.edge_key=*) KUBESOLO_PORTAINER_EDGE_KEY="${arg#kubesolo.edge_key=}" ;;
|
||||
kubesolo.nomodlock) KUBESOLO_NOMODLOCK=1 ;;
|
||||
kubesolo.noapparmor) KUBESOLO_NOAPPARMOR=1 ;;
|
||||
esac
|
||||
done
|
||||
|
||||
export KUBESOLO_NOMODLOCK
|
||||
export KUBESOLO_NOAPPARMOR
|
||||
|
||||
if [ -z "$KUBESOLO_DATA_DEV" ] && [ "$KUBESOLO_NOPERSIST" != "1" ]; then
|
||||
log_warn "No kubesolo.data= specified and kubesolo.nopersist not set"
|
||||
log_warn "Attempting auto-detection of data partition (label: KSOLODATA)"
|
||||
|
||||
@@ -38,13 +38,13 @@ fi
|
||||
|
||||
# Mount data partition (format on first boot if unformatted)
|
||||
mkdir -p "$DATA_MOUNT"
|
||||
if ! mount -t ext4 -o noatime "$KUBESOLO_DATA_DEV" "$DATA_MOUNT" 2>/dev/null; then
|
||||
if ! mount -t ext4 -o noatime,nosuid,nodev "$KUBESOLO_DATA_DEV" "$DATA_MOUNT" 2>/dev/null; then
|
||||
log "Formatting $KUBESOLO_DATA_DEV as ext4 (first boot)"
|
||||
mkfs.ext4 -q -L KSOLODATA "$KUBESOLO_DATA_DEV" || {
|
||||
log_err "Failed to format $KUBESOLO_DATA_DEV"
|
||||
return 1
|
||||
}
|
||||
mount -t ext4 -o noatime "$KUBESOLO_DATA_DEV" "$DATA_MOUNT" || {
|
||||
mount -t ext4 -o noatime,nosuid,nodev "$KUBESOLO_DATA_DEV" "$DATA_MOUNT" || {
|
||||
log_err "Failed to mount $KUBESOLO_DATA_DEV after format"
|
||||
return 1
|
||||
}
|
||||
|
||||
47
init/lib/35-apparmor.sh
Normal file
47
init/lib/35-apparmor.sh
Normal file
@@ -0,0 +1,47 @@
|
||||
#!/bin/sh
|
||||
# 35-apparmor.sh — Load AppArmor LSM profiles
|
||||
|
||||
# Check for opt-out boot parameter
|
||||
if [ "$KUBESOLO_NOAPPARMOR" = "1" ]; then
|
||||
log "AppArmor disabled via kubesolo.noapparmor boot parameter"
|
||||
return 0
|
||||
fi
|
||||
|
||||
# Mount securityfs if not already mounted
|
||||
if ! mountpoint -q /sys/kernel/security 2>/dev/null; then
|
||||
mount -t securityfs securityfs /sys/kernel/security 2>/dev/null || true
|
||||
fi
|
||||
|
||||
# Check if AppArmor is available in the kernel
|
||||
if [ ! -d /sys/kernel/security/apparmor ]; then
|
||||
log_warn "AppArmor not available in kernel — skipping profile loading"
|
||||
return 0
|
||||
fi
|
||||
|
||||
# Check for apparmor_parser
|
||||
if ! command -v apparmor_parser >/dev/null 2>&1; then
|
||||
log_warn "apparmor_parser not found — skipping profile loading"
|
||||
return 0
|
||||
fi
|
||||
|
||||
# Load all profiles from /etc/apparmor.d/
|
||||
PROFILE_DIR="/etc/apparmor.d"
|
||||
if [ ! -d "$PROFILE_DIR" ]; then
|
||||
log_warn "No AppArmor profiles directory ($PROFILE_DIR) — skipping"
|
||||
return 0
|
||||
fi
|
||||
|
||||
LOADED=0
|
||||
FAILED=0
|
||||
|
||||
for profile in "$PROFILE_DIR"/*; do
|
||||
[ -f "$profile" ] || continue
|
||||
if apparmor_parser -r "$profile" 2>/dev/null; then
|
||||
LOADED=$((LOADED + 1))
|
||||
else
|
||||
log_warn "Failed to load AppArmor profile: $(basename "$profile")"
|
||||
FAILED=$((FAILED + 1))
|
||||
fi
|
||||
done
|
||||
|
||||
log_ok "AppArmor: loaded $LOADED profiles ($FAILED failed)"
|
||||
20
init/lib/85-security-lockdown.sh
Executable file
20
init/lib/85-security-lockdown.sh
Executable file
@@ -0,0 +1,20 @@
|
||||
#!/bin/sh
|
||||
# 85-security-lockdown.sh — Lock down kernel after all modules loaded
|
||||
|
||||
# Allow disabling via boot parameter for debugging
|
||||
if [ "$KUBESOLO_NOMODLOCK" = "1" ]; then
|
||||
log_warn "Module lock DISABLED (kubesolo.nomodlock)"
|
||||
else
|
||||
# Permanently prevent new kernel module loading (irreversible until reboot)
|
||||
# All required modules must already be loaded by stage 30
|
||||
if [ -f /proc/sys/kernel/modules_disabled ]; then
|
||||
echo 1 > /proc/sys/kernel/modules_disabled 2>/dev/null && \
|
||||
log_ok "Kernel module loading locked" || \
|
||||
log_warn "Failed to lock kernel module loading"
|
||||
fi
|
||||
fi
|
||||
|
||||
# Safety net: enforce kernel information protection
|
||||
# (also set via sysctl.d but enforce here in case sysctl.d was bypassed)
|
||||
echo 2 > /proc/sys/kernel/kptr_restrict 2>/dev/null || true
|
||||
echo 1 > /proc/sys/kernel/dmesg_restrict 2>/dev/null || true
|
||||
@@ -87,7 +87,7 @@ if [ -f "$KUBECONFIG_PATH" ]; then
|
||||
|
||||
# Serve kubeconfig via HTTP on port 8080 using BusyBox nc
|
||||
(while true; do
|
||||
printf "HTTP/1.1 200 OK\r\nContent-Type: text/yaml\r\nConnection: close\r\n\r\n" | cat - "$EXTERNAL_KC" | nc -l -p 8080 2>/dev/null
|
||||
printf "HTTP/1.1 200 OK\r\nContent-Type: text/yaml\r\nConnection: close\r\n\r\n" | cat - "$EXTERNAL_KC" | nc -l -s 127.0.0.1 -p 8080 2>/dev/null
|
||||
done) &
|
||||
|
||||
log_ok "Kubeconfig available via HTTP"
|
||||
|
||||
Reference in New Issue
Block a user