Add security script

This commit is contained in:
2026-06-13 15:16:10 +10:00
parent fccba836df
commit e6f3892b02
2 changed files with 195 additions and 0 deletions
+1
View File
@@ -34,6 +34,7 @@ terraform.rc
# Ignore generated scripts # Ignore generated scripts
scripts/*.sh scripts/*.sh
!scripts/security-audit-192.168.1.2.sh
# Ignore temporary files # Ignore temporary files
*.tmp *.tmp
+194
View File
@@ -0,0 +1,194 @@
#!/usr/bin/env bash
set -euo pipefail
# Read-only weekly security audit for the K3s host.
# Override defaults:
# SSH_HOST=192.168.1.2 SSH_PORT=22422 SSH_USER=junv SINCE="14 days ago" ./scripts/security-audit-192.168.1.2.sh
SSH_HOST="${SSH_HOST:-192.168.1.2}"
SSH_PORT="${SSH_PORT:-22422}"
SSH_USER="${SSH_USER:-junv}"
SINCE="${SINCE:-14 days ago}"
GREEN='\033[0;32m'
YELLOW='\033[1;33m'
RED='\033[0;31m'
NC='\033[0m'
section() {
printf "\n${GREEN}=== %s ===${NC}\n" "$1"
}
warn() {
printf "${YELLOW}WARN:${NC} %s\n" "$1"
}
fail() {
printf "${RED}FAIL:${NC} %s\n" "$1"
}
echo "Target: ${SSH_USER}@${SSH_HOST}:${SSH_PORT}"
echo "Window: ${SINCE}"
echo "Mode: read-only"
section "Remote SSH And Login Audit"
ssh -p "${SSH_PORT}" "${SSH_USER}@${SSH_HOST}" "AUDIT_SINCE='${SINCE}' bash -s" <<'REMOTE_AUDIT'
set -euo pipefail
since="${AUDIT_SINCE:-14 days ago}"
headline() {
printf "\n--- %s ---\n" "$1"
}
run_optional() {
local description="$1"
shift
headline "$description"
"$@" 2>/dev/null || true
}
echo "Host: $(hostname)"
echo "Now: $(date -Is)"
echo "User: $(id)"
echo "Since: ${since}"
headline "Current interactive users"
w || true
headline "Current SSH TCP sessions"
sudo ss -tnp 2>/dev/null | grep ':22422' || echo "No active SSH TCP sessions found on port 22422."
headline "Recent successful logins from last(1)"
last -aiw | head -60 || true
headline "Recent failed logins from lastb(1)"
sudo lastb -aiw 2>/dev/null | head -40 || echo "No btmp records or permission denied."
headline "SSH event summary"
ssh_events="$(sudo journalctl -u ssh -u sshd --since "${since}" --no-pager 2>/dev/null || true)"
if [ -z "${ssh_events}" ]; then
echo "No ssh/sshd journal entries found for this window."
else
printf "Accepted publickey: %s\n" "$(printf '%s\n' "${ssh_events}" | grep -c 'Accepted publickey' || true)"
printf "Accepted password: %s\n" "$(printf '%s\n' "${ssh_events}" | grep -c 'Accepted password' || true)"
printf "Failed password: %s\n" "$(printf '%s\n' "${ssh_events}" | grep -c 'Failed password' || true)"
printf "Invalid user: %s\n" "$(printf '%s\n' "${ssh_events}" | grep -c 'Invalid user' || true)"
printf "Auth failures: %s\n" "$(printf '%s\n' "${ssh_events}" | grep -ci 'authentication failure' || true)"
printf "Disconnect/noise: %s\n" "$(printf '%s\n' "${ssh_events}" | grep -Eci 'Connection closed|Disconnected|Did not receive identification|Unable to negotiate' || true)"
fi
headline "Successful password logins - inspect every line"
printf '%s\n' "${ssh_events:-}" | grep 'Accepted password' || echo "No successful password SSH logins in this window."
headline "Failed or suspicious SSH events"
printf '%s\n' "${ssh_events:-}" \
| grep -Ei 'Failed password|Invalid user|authentication failure|maximum authentication|POSSIBLE BREAK-IN|Unable to negotiate|refused|error:' \
| tail -120 || echo "No failed/suspicious SSH events in this window."
headline "Accepted SSH logins, latest 120"
printf '%s\n' "${ssh_events:-}" | grep -E 'Accepted publickey|Accepted password' | tail -120 || true
headline "Map Kubernetes pod IPs seen in successful password SSH logins"
pod_ips="$(printf '%s\n' "${ssh_events:-}" | awk '/Accepted password/ {for (i=1; i<=NF; i++) if ($i == "from") print $(i+1)}' | grep '^10\.42\.' | sort -u || true)"
if [ -z "${pod_ips}" ]; then
echo "No Kubernetes pod IPs found in successful password SSH logins."
elif command -v k3s >/dev/null 2>&1; then
echo "${pod_ips}" | while read -r ip; do
echo "Pod source IP: ${ip}"
sudo k3s kubectl get pods -A -o wide 2>/dev/null | grep -E "(^NAMESPACE|[[:space:]]${ip}[[:space:]])" || true
done
else
echo "Found pod IPs but k3s command is not available:"
echo "${pod_ips}"
fi
headline "fail2ban status"
sudo fail2ban-client status 2>/dev/null || echo "fail2ban not available."
sudo fail2ban-client status sshd 2>/dev/null || true
sudo fail2ban-client status recidive 2>/dev/null || true
headline "Recent fail2ban ban/unban activity"
sudo journalctl -u fail2ban --since "${since}" --no-pager 2>/dev/null | grep -Ei 'Ban|Unban|Found' | tail -120 || echo "No fail2ban ban/unban activity in this window."
headline "Effective sshd security settings"
if sudo sshd -T >/tmp/sshd-effective.$$ 2>/dev/null; then
grep -E '^(port|permitrootlogin|passwordauthentication|kbdinteractiveauthentication|pubkeyauthentication|authenticationmethods|maxauthtries|permitemptypasswords|x11forwarding|allowusers|allowgroups|listenaddress) ' /tmp/sshd-effective.$$ || true
rm -f /tmp/sshd-effective.$$
else
echo "Could not read effective sshd config."
fi
headline "Local authorized_keys files"
sudo find /home /root -maxdepth 3 -path '*/.ssh/authorized_keys' -type f -print 2>/dev/null \
| while read -r keyfile; do
echo "${keyfile}"
sudo stat -c ' owner=%U group=%G mode=%a size=%s modified=%y' "${keyfile}" 2>/dev/null || true
sudo awk '{print " key " NR ": " $1 " " $2}' "${keyfile}" 2>/dev/null | sha256sum | awk '{print " sha256=" $1}' || true
done
headline "Recent sudo authentication and command activity"
sudo journalctl --since "${since}" --no-pager 2>/dev/null \
| grep -Ei 'sudo:|COMMAND=|authentication failure|session opened for user root|session closed for user root' \
| tail -120 || echo "No sudo activity found in this window."
headline "Listening TCP/UDP services"
sudo ss -tulpn 2>/dev/null | sed -n '1,120p' || true
headline "High-risk listener quick checks"
if sudo ss -tulpn 2>/dev/null | grep -qE '(:6379[[:space:]]|:5432[[:space:]]|:2049[[:space:]]|:9100[[:space:]])'; then
sudo ss -tulpn 2>/dev/null | grep -E '(:6379[[:space:]]|:5432[[:space:]]|:2049[[:space:]]|:9100[[:space:]])' || true
else
echo "No Redis/Postgres/NFS/node-exporter listeners detected by quick check."
fi
headline "PASS/WARN hints"
password_count="$(printf '%s\n' "${ssh_events:-}" | grep -c 'Accepted password' || true)"
failed_count="$(printf '%s\n' "${ssh_events:-}" | grep -c 'Failed password' || true)"
invalid_count="$(printf '%s\n' "${ssh_events:-}" | grep -c 'Invalid user' || true)"
if [ "${password_count}" -gt 0 ]; then
echo "WARN: Successful password SSH logins were found. Prefer key-only SSH and investigate each source."
else
echo "OK: No successful password SSH login found in this window."
fi
if [ "${failed_count}" -gt 20 ] || [ "${invalid_count}" -gt 20 ]; then
echo "WARN: SSH failure volume is elevated. Check source IPs and fail2ban coverage."
else
echo "OK: SSH failure volume is low in this window."
fi
if sudo sshd -T 2>/dev/null | grep -q '^passwordauthentication yes$'; then
echo "WARN: passwordauthentication is enabled."
else
echo "OK: passwordauthentication is disabled."
fi
if sudo sshd -T 2>/dev/null | grep -q '^permitrootlogin no$'; then
echo "OK: root SSH login is disabled."
else
echo "WARN: root SSH login is not clearly disabled."
fi
REMOTE_AUDIT
section "Local Kubernetes Pod IP Cross-check"
if command -v kubectl >/dev/null 2>&1; then
echo "Current n8n pods:"
kubectl -n home-apps get pods -o wide 2>/dev/null | grep -E '(^NAME|n8n)' || true
echo
echo "All pods with SSH-relevant names:"
kubectl get pods -A -o wide 2>/dev/null | grep -Ei '(^NAMESPACE|n8n|backup|qbit|ssh|vpn)' || true
else
warn "kubectl not found locally; skipping local pod cross-check."
fi
section "Manual Review Checklist"
cat <<'CHECKLIST'
Review these lines every week:
1. "Successful password logins" should normally be empty. If not, identify every source.
2. Pod IP sources like 10.42.x.x mean a Kubernetes workload can SSH into the host.
3. fail2ban "Currently banned" and "Total failed" should be low and explainable.
4. sshd should ideally show: permitrootlogin no, passwordauthentication no, pubkeyauthentication yes.
5. High-risk listeners such as Redis 6379, Postgres 5432, NFS 2049, and metrics 9100 should be restricted.
CHECKLIST