feat: add hardened base and per-edition image overlays

Add the files that land in the image, organised as layered rootfs trees
(base first, then the edition layer wins on conflict).

- base: hardened kernel cmdline + sysctl, default-deny nftables,
  key-only ssh, persistent journald, module blacklist, no core dumps,
  snapshot timer units, motd.
- server: Prometheus + auto-provisioned Grafana + Loki + promtail.
- workstation: dev profile and desktop sysctl relaxations (perf,
  rootless containers).
- cloud: cloud-init provisioning config.
This commit is contained in:
Blake Ridgway
2026-08-21 13:15:43 -05:00
parent 729f191950
commit 94ab6043e7
19 changed files with 516 additions and 0 deletions

View File

@@ -0,0 +1,18 @@
# Arcline OS — disabled kernel modules
# Modules that add attack surface without being needed on the base system.
# The workstation edition keeps most of these disabled too; hardware you
# actually use can be re-enabled per-module.
# exotic / historically-risky network protocols
blacklist sctp
blacklist dccp
blacklist rds
blacklist tipc
# unneeded legacy filesystems (mountable by a user with local access)
install cramfs /bin/false
install freevxfs /bin/false
install jffs2 /bin/false
install hfs /bin/false
install hfsplus /bin/false
install udf /bin/false

8
overlays/base/etc/motd Normal file
View File

@@ -0,0 +1,8 @@
Welcome to Arcline OS
Secure by default. Zero telemetry. Self-hosted by principle.
* hardening guide: man arcline-hardening (or docs/hardening.md)
* manage snapshots: arcline-snapshot snapshot|list|prune
* check firewall: nft list ruleset

View File

@@ -0,0 +1,44 @@
#!/usr/sbin/nft -f
# Arcline OS — default-deny firewall (nftables)
# Every edition ships with this policy: drop inbound by default, allow only
# what you opt in to. Services in `accept` rules are the baseline; add yours
# with `nft add rule inet filter input tcp dport <port> accept` (or edit this
# file and reload with `systemctl reload nftables`).
flush ruleset
table inet filter {
chain input {
type filter hook input priority filter; policy drop;
# established traffic is fine
ct state established,related accept
# loopback always
iif "lo" accept
# drop invalid packets early
ct state invalid drop
# ICMP (needed for PMTU discovery; rate-limited by kernel)
ip protocol icmp accept
ip6 nexthdr icmpv6 accept
# baseline services
# ssh (key-based auth only — see sshd_config.d)
tcp dport 22 accept
# web (commented out by default; enable when hosting)
# tcp dport 80 accept
# tcp dport 443 accept
}
chain forward {
type filter hook forward priority filter; policy drop;
# containers route via their own bridge tables (docker/podman);
# this chain stays drop for anything else.
}
chain output {
type filter hook output priority filter; policy accept;
# zero telemetry means we never call out — but we don't block
# outgoing by default. Arcline never phones home on its own.
}
}

View File

@@ -0,0 +1,24 @@
# Arcline OS — ssh hardening
# Secure by default: key-based auth only, root login via key only.
# To allow password auth, comment the PasswordAuthentication line — you opt
# in to exposure, never out.
#
# Matches Debian's drop-in semantics (Read drop-ins from sshd_config.d).
# authentication
PermitRootLogin prohibit-password
PubkeyAuthentication yes
PasswordAuthentication no
KbdInteractiveAuthentication no
PermitEmptyPasswords no
MaxAuthTries 3
LoginGraceTime 30
# session hardening
X11Forwarding no
AllowTcpForwarding yes
AllowAgentForwarding yes
ClientAliveInterval 300
ClientAliveCountMax 2
TCPKeepAlive no
UseDNS no

View File

@@ -0,0 +1,52 @@
# Arcline OS — kernel hardening (sysctl)
# Applies to every edition via the base overlay. Conservative defaults;
# the workstation edition relaxes a couple of entries (see overlays/workstation).
# ── memory ──────────────────────────────────────────────────────────────────
vm.swappiness=10
vm.overcommit_memory=2
vm.dirty_ratio=5
vm.dirty_background_ratio=2
# ── networking ──────────────────────────────────────────────────────────────
# strict reverse-path filtering
net.ipv4.conf.all.rp_filter=1
net.ipv4.conf.default.rp_filter=1
# no ICMP redirects
net.ipv4.conf.all.accept_redirects=0
net.ipv4.conf.default.accept_redirects=0
net.ipv4.conf.all.secure_redirects=0
net.ipv4.conf.default.secure_redirects=0
net.ipv4.conf.all.send_redirects=0
net.ipv4.conf.default.send_redirects=0
net.ipv6.conf.all.accept_redirects=0
net.ipv6.conf.default.accept_redirects=0
# ICMP hardening
net.ipv4.icmp_echo_ignore_broadcasts=1
net.ipv4.icmp_ignore_bogus_error_responses=1
# TCP hardening
net.ipv4.tcp_syncookies=1
net.ipv4.tcp_rfc1337=1
net.ipv4.tcp_timestamps=0
net.ipv4.conf.all.log_martians=1
net.ipv4.conf.default.log_martians=1
# IPv6: don't auto-accept router advertisements
net.ipv6.conf.all.accept_ra=0
net.ipv6.conf.default.accept_ra=0
# ── kernel / visibility ─────────────────────────────────────────────────────
kernel.dmesg_restrict=1
kernel.kptr_restrict=2
kernel.perf_event_paranoid=3
kernel.yama.ptrace_scope=1
kernel.core_uses_pid=1
kernel.unprivileged_bpf_disabled=1
kernel.randomize_va_space=2
kernel.sysrq=4
# ── filesystem hardening ────────────────────────────────────────────────────
fs.protected_hardlinks=1
fs.protected_symlinks=1
fs.protected_fifos=2
fs.protected_regular=2
fs.suid_dumpable=0

View File

@@ -0,0 +1,12 @@
# Arcline OS — journald
# Persistent, bounded, compressed logs. Logs live on @log so they survive
# system rollbacks (see btrfs/README.md).
[Journal]
Storage=persistent
Compress=yes
SystemMaxUse=500M
SystemMaxFileSize=100M
MaxRetentionSec=14day
ForwardToSyslog=no
ForwardToConsole=no

View File

@@ -0,0 +1,9 @@
# Arcline OS — systemd manager hardening
# Global defaults applied to every unit. No core dumps on disk, sane limits.
[Manager]
DefaultLimitNOFILE=65535
DefaultLimitNPROC=4096
DefaultLimitCORE=0
DumpCore=no
CoreDumpSize=0