mirror of
https://github.com/wahyd4/home-docker.git
synced 2026-08-08 20:15:03 +10:00
Add security script
This commit is contained in:
@@ -34,6 +34,7 @@ terraform.rc
|
||||
|
||||
# Ignore generated scripts
|
||||
scripts/*.sh
|
||||
!scripts/security-audit-192.168.1.2.sh
|
||||
|
||||
# Ignore temporary files
|
||||
*.tmp
|
||||
|
||||
Executable
+194
@@ -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
|
||||
Reference in New Issue
Block a user