veilor-os/scripts/20-harden-kernel.sh

153 lines
5.4 KiB
Bash
Executable file

#!/usr/bin/env bash
# veilor-os — kernel + service hardening (sysctl, USBGuard, NTS chrony, pwquality, service prune)
# Idempotent. Run as root.
set -uo pipefail
GREEN='\033[0;32m'; YELLOW='\033[1;33m'; RED='\033[0;31m'; NC='\033[0m'
ok() { echo -e "${GREEN}[OK]${NC} $*"; }
info() { echo -e "${YELLOW}[INFO]${NC} $*"; }
err() { echo -e "${RED}[ERR]${NC} $*"; }
[[ $EUID -eq 0 ]] || { err "Must run as root"; exit 1; }
echo "════════════════════════════════════════════════════════"
echo " veilor-os :: 20-harden-kernel"
echo "════════════════════════════════════════════════════════"
# ── sysctl hardening ──
info "Writing /etc/sysctl.d/99-veilor-hardening.conf"
cat > /etc/sysctl.d/99-veilor-hardening.conf << 'EOF'
# kernel pointer hiding
kernel.kptr_restrict = 2
kernel.dmesg_restrict = 1
# BPF JIT constant blinding
net.core.bpf_jit_harden = 2
# unprivileged perf events disabled
kernel.perf_event_paranoid = 3
# ptrace restricted to parent
kernel.yama.ptrace_scope = 2
# full ASLR
kernel.randomize_va_space = 2
# modules unlocked at boot; veilor-modules-lock locks 30s after graphical
kernel.modules_disabled = 0
# reverse path filter
net.ipv4.conf.all.rp_filter = 1
net.ipv4.conf.default.rp_filter = 1
# log martians
net.ipv4.conf.all.log_martians = 1
net.ipv4.conf.default.log_martians = 1
# no SUID core dumps
fs.suid_dumpable = 0
# block unprivileged tty line discipline loading (LPE vector)
dev.tty.ldisc_autoload = 0
# /proc/sched_debug hardened
kernel.sched_schedstats = 0
# TCP SYN cookies
net.ipv4.tcp_syncookies = 1
# ignore ICMP broadcast
net.ipv4.icmp_echo_ignore_broadcasts = 1
# no source routing
net.ipv4.conf.all.accept_source_route = 0
net.ipv6.conf.all.accept_source_route = 0
# no ICMP redirects
net.ipv4.conf.all.accept_redirects = 0
net.ipv4.conf.all.send_redirects = 0
net.ipv6.conf.all.accept_redirects = 0
EOF
sysctl --system >/dev/null 2>&1 || true
ok "sysctl hardening written"
# ── kernel module lock service ──
info "Enabling veilor-modules-lock.service"
systemctl enable veilor-modules-lock.service 2>/dev/null || \
info "veilor-modules-lock.service not yet installed (overlay step)"
# ── chrony with NTS ──
info "Configuring chrony with NTS"
[[ -f /etc/chrony.conf ]] && cp /etc/chrony.conf /etc/chrony.conf.bak.veilor 2>/dev/null
cat > /etc/chrony.conf << 'EOF'
# NTS-authenticated time sources
server time.cloudflare.com iburst nts
server nts.sth1.ntp.se iburst nts
server nts.sth2.ntp.se iburst nts
# unauthenticated pool fallback
pool 2.fedora.pool.ntp.org iburst
sourcedir /run/chrony-dhcp
ntsdumpdir /var/lib/chrony
driftfile /var/lib/chrony/drift
makestep 1.0 3
rtcsync
leapseclist /usr/share/zoneinfo/leap-seconds.list
logdir /var/log/chrony
EOF
systemctl enable chronyd
ok "chrony NTS configured (Cloudflare + NETNOD)"
# ── password complexity ──
info "Writing /etc/security/pwquality.conf"
cat > /etc/security/pwquality.conf << 'EOF'
minlen = 14
dcredit = -1
ucredit = -1
lcredit = -1
ocredit = -1
minclass = 3
maxrepeat = 3
maxclassrepeat = 4
difok = 3
dictcheck = 1
usercheck = 1
enforce_for_root
retry = 3
EOF
ok "pwquality: minlen=14, 4 classes required"
# ── disable unneeded services ──
# Packages stay installed (KDE depsolve), but the daemons never start.
for svc in gssproxy atd pcscd.socket pcscd.service cups cups-browsed cups.socket \
cups.path abrtd abrt-journal-core abrt-xorg abrt-oops abrt-ccpp \
geoclue avahi-daemon avahi-daemon.socket bluetooth ModemManager \
packagekit packagekit-offline-update; do
systemctl disable --now "$svc" 2>/dev/null && ok "disabled $svc" || true
done
# Mask cups so even socket activation can't bring it up
systemctl mask cups.service cups.socket cups.path 2>/dev/null || true
systemctl mask geoclue.service 2>/dev/null || true
systemctl mask ModemManager.service 2>/dev/null || true
systemctl mask packagekit.service 2>/dev/null || true
# ── USBGuard ──
info "Setting up USBGuard"
rpm -q usbguard &>/dev/null || dnf install -y usbguard usbguard-tools
# At install time no devices are connected — ship empty allowlist.
# First boot, admin runs: usbguard generate-policy > /etc/usbguard/rules.conf
mkdir -p /etc/usbguard
[[ -f /etc/usbguard/rules.conf ]] || : > /etc/usbguard/rules.conf
chmod 600 /etc/usbguard/rules.conf
chown root:root /etc/usbguard/rules.conf
cat > /etc/usbguard/usbguard-daemon.conf << 'EOF'
ImplicitPolicyTarget=block
AuditBackend=LinuxAudit
IPCAllowedUsers=root
IPCAllowedGroups=wheel
RuleFile=/etc/usbguard/rules.conf
EOF
systemctl enable usbguard
ok "USBGuard configured (generate-policy on first boot to allowlist your devices)"
# ── firewalld drop zone ──
info "Setting firewalld default zone to drop"
systemctl enable firewalld
firewall-offline-cmd --set-default-zone=drop 2>/dev/null || true
firewall-offline-cmd --zone=drop --add-service=ssh 2>/dev/null || true
ok "firewalld: default drop, ssh allowed"
echo "════════════════════════════════════════════════════════"
echo " 20-harden-kernel complete"
echo "════════════════════════════════════════════════════════"