#!/usr/bin/env bash
# security/audit -- one screen that answers "is this box in good shape?"
#
# Read-only. Every finding names the command that fixes it, because a report
# without a next step is just worry.
set -o pipefail
SELF=$(readlink -f "${BASH_SOURCE[0]}")
NV_BASE=$(cd "$(dirname "$SELF")/../../.." && pwd)
export NV_BASE
# shellcheck source=/dev/null
. "$NV_BASE/lib/core/bootstrap.sh"

nv_require_root || exit 1
ui_header "$(t sec_m_audit)"
issues=0
note_issue() { issues=$((issues + 1)); }

# --- ssh ----------------------------------------------------------------
ui_section "$(t audit_ssh)"
sshd_cfg=/etc/ssh/sshd_config
root_login=$(sed -n 's/^[[:space:]]*PermitRootLogin[[:space:]]\+//p' "$sshd_cfg" 2>/dev/null | tail -n 1)
pass_auth=$(sed -n 's/^[[:space:]]*PasswordAuthentication[[:space:]]\+//p' "$sshd_cfg" 2>/dev/null | tail -n 1)
ssh_port=$(sshd_port 2>/dev/null || echo 22)
ssh_port=$(grep -hoE '^[[:space:]]*Port[[:space:]]+[0-9]+' "$sshd_cfg" 2>/dev/null | awk '{print $2}' | tail -n 1)
ssh_port=${ssh_port:-22}

case ${root_login:-prohibit-password} in
    no)  ui_ok "$(t audit_ssh_root_no)" ;;
    *)   ui_warn "$(t audit_ssh_root_yes "${root_login:-prohibit-password}")"; note_issue ;;
esac
if [ "${pass_auth:-yes}" = "yes" ]; then
    ui_warn "$(t audit_ssh_password_yes)"; note_issue
else
    ui_ok "$(t audit_ssh_password_no)"
fi
ui_kv "$(t audit_ssh_port)" "$ssh_port"

# --- firewall -----------------------------------------------------------
ui_section "$(t audit_firewall)"
fw=$(fw_status 2>/dev/null)
if printf '%s' "$fw" | grep -qi 'active'; then
    ui_ok "$fw"
else
    ui_warn "$(t audit_fw_off)"; note_issue
fi

# --- fail2ban -----------------------------------------------------------
ui_section "$(t audit_fail2ban)"
if svc_is_active fail2ban; then
    ui_ok "$(t audit_f2b_on)"
    banned=$(fail2ban-client status 2>/dev/null | sed -n 's/.*Jail list:[[:space:]]*//p')
    ui_kv "$(t f2b_jails)" "${banned:--}"
else
    ui_warn "$(t audit_f2b_off)"; note_issue
fi

# --- secrets ------------------------------------------------------------
ui_section "$(t audit_secrets)"
if [ -f "$NV_SECRET_FILE" ]; then
    mode=$(stat -c '%a' "$NV_SECRET_FILE" 2>/dev/null)
    if [ "$mode" = "600" ]; then ui_ok "$(t audit_secret_mode "$NV_SECRET_FILE" "$mode")"
    else ui_warn "$(t audit_secret_mode_bad "$NV_SECRET_FILE" "$mode")"; note_issue; fi
else
    ui_info "$(t audit_secret_none)"
fi

# --- filesystem ---------------------------------------------------------
ui_section "$(t audit_worldwritable)"
ww=0
for d in /home/*/public_html; do
    [ -d "$d" ] || continue
    n=$(find "$d" -xdev -perm -o+w -type f 2>/dev/null | head -n 50 | wc -l)
    ww=$((ww + n))
done
if [ "$ww" -eq 0 ]; then
    ui_ok "$(t audit_ww_none)"
else
    ui_warn "$(t audit_ww_found "$ww")"; note_issue
fi

# --- certificates -------------------------------------------------------
ui_section "$(t col_ssl)"
soon=0
for d in /etc/letsencrypt/live/*/; do
    [ -r "$d/fullchain.pem" ] || continue
    end=$(openssl x509 -enddate -noout -in "$d/fullchain.pem" 2>/dev/null | cut -d= -f2)
    ep=$(date -d "$end" +%s 2>/dev/null || echo 0)
    days=$(( (ep - $(date +%s)) / 86400 ))
    if [ "$days" -lt 21 ]; then
        ui_warn "$(basename "${d%/}") $days"; soon=$((soon + 1)); note_issue
    fi
done
[ "$soon" -eq 0 ] && ui_ok "$(t audit_cert_ok)"

# --- updates ------------------------------------------------------------
ui_section "$(t audit_updates)"
if nv_have apt-get; then
    pending=$(apt-get -s upgrade 2>/dev/null | grep -c '^Inst ')
    if [ "${pending:-0}" -eq 0 ]; then ui_ok "$(t audit_upd_none)"
    else ui_warn "$(t audit_upd_pending "$pending")"; note_issue; fi
fi

ui_blank
if [ "$issues" -eq 0 ]; then
    ui_ok "$(t audit_clean)"
else
    ui_warn "$(t audit_issues "$issues")"
fi
exit 0
