diff --git a/.gitignore b/.gitignore index 26cb3d1..8d2db51 100644 --- a/.gitignore +++ b/.gitignore @@ -34,6 +34,7 @@ terraform.rc # Ignore generated scripts scripts/*.sh +!scripts/security-audit-192.168.1.2.sh # Ignore temporary files *.tmp diff --git a/scripts/security-audit-192.168.1.2.sh b/scripts/security-audit-192.168.1.2.sh new file mode 100755 index 0000000..e44a492 --- /dev/null +++ b/scripts/security-audit-192.168.1.2.sh @@ -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