mirror of
https://github.com/wahyd4/home-docker.git
synced 2026-08-09 04:15:52 +10:00
195 lines
7.7 KiB
Bash
Executable File
195 lines
7.7 KiB
Bash
Executable File
#!/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
|