153 lines
5.4 KiB
Bash
Executable file
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 "════════════════════════════════════════════════════════"
|