#!/usr/bin/env bash
# audit.sh — auditoria de segurança, SOMENTE LEITURA.
#
# Não instala pacote, não altera configuração, não reinicia serviço. E seguro
# rodar em produção durante um incidente.
#
# 12 verificações, pesos declarados na saída, nota de 0 a 100 e comando de
# correção para cada risco encontrado.
#
# Uso: audit.sh --host <ip> --user <usuário> [--port 22] [--key <chave>]

set -uo pipefail

HOST=""; USER_NAME=""; PORT=22; KEY_PATH=""

while [ $# -gt 0 ]; do
  case "$1" in
    --host) HOST="$2"; shift 2 ;;
    --user) USER_NAME="$2"; shift 2 ;;
    --port) PORT="$2"; shift 2 ;;
    --key)  KEY_PATH="$2"; shift 2 ;;
    *) echo "argumento desconhecido: $1" >&2; exit 2 ;;
  esac
done

[ -n "$HOST" ] && [ -n "$USER_NAME" ] || { echo "ERRO: --host e --user são obrigatórios" >&2; exit 2; }

SSH_OPTS=(-o BatchMode=yes -o StrictHostKeyChecking=accept-new -o ForwardAgent=no -o ConnectTimeout=10 -p "$PORT")
[ -n "$KEY_PATH" ] && SSH_OPTS+=(-i "${KEY_PATH/#\~/$HOME}" -o IdentitiesOnly=yes)

# Todas as verificações em uma única ida ao servidor: é o que mantem abaixo de 30s.
REMOTE='
  sshd_eff() { sudo -n sshd -T 2>/dev/null || sshd -T 2>/dev/null; }
  EFF=$(sshd_eff)

  echo "CHK|root_login|$(printf "%s" "$EFF" | grep -i "^permitrootlogin" | awk "{print \$2}" | head -n1)"
  echo "CHK|password_auth|$(printf "%s" "$EFF" | grep -i "^passwordauthentication" | awk "{print \$2}" | head -n1)"
  echo "CHK|pubkey_auth|$(printf "%s" "$EFF" | grep -i "^pubkeyauthentication" | awk "{print \$2}" | head -n1)"
  echo "CHK|max_auth_tries|$(printf "%s" "$EFF" | grep -i "^maxauthtries" | awk "{print \$2}" | head -n1)"
  echo "CHK|ssh_port|$(printf "%s" "$EFF" | grep -i "^port" | awk "{print \$2}" | head -n1)"

  if command -v ufw >/dev/null 2>&1; then
    S=$(sudo -n ufw status verbose 2>/dev/null || ufw status verbose 2>/dev/null)
    if printf "%s" "$S" | grep -qi "Status: active"; then
      if printf "%s" "$S" | grep -qi "deny (incoming)"; then echo "CHK|firewall|ativo_deny"; else echo "CHK|firewall|ativo_permissivo"; fi
    else echo "CHK|firewall|inativo"; fi
    echo "CHK|firewall_regras|$(printf "%s" "$S" | grep -cE "ALLOW")"
  else echo "CHK|firewall|ausente"; echo "CHK|firewall_regras|0"; fi

  if systemctl is-active fail2ban >/dev/null 2>&1; then echo "CHK|fail2ban|ativo"
  elif command -v fail2ban-server >/dev/null 2>&1; then echo "CHK|fail2ban|instalado_inativo"
  else echo "CHK|fail2ban|ausente"; fi

  if [ -f /etc/apt/apt.conf.d/20auto-upgrades ] && grep -q "Unattended-Upgrade \"1\"" /etc/apt/apt.conf.d/20auto-upgrades 2>/dev/null; then
    echo "CHK|auto_updates|ativo"
  else echo "CHK|auto_updates|inativo"; fi

  PEND=$(apt-get -s upgrade 2>/dev/null | grep -ci "^Inst.*security" || echo 0)
  echo "CHK|security_pending|${PEND}"

  echo "CHK|reboot_required|$([ -f /var/run/reboot-required ] && echo sim || echo não)"

  ADMINS=$(getent group sudo 2>/dev/null | cut -d: -f4; getent group wheel 2>/dev/null | cut -d: -f4)
  echo "CHK|admin_users|$(printf "%s" "$ADMINS" | tr "," "\n" | grep -v "^$" | sort -u | tr "\n" " ")"

  KEYS=0
  for H in /root /home/*; do
    A="$H/.ssh/authorized_keys"
    if [ -r "$A" ]; then
      N=$(grep -cE "^(ssh|ecdsa)" "$A" 2>/dev/null || echo 0)
      KEYS=$((KEYS + N))
      echo "CHK|keys_detail|$(basename "$H"):${N}"
    fi
  done
  echo "CHK|authorized_keys|${KEYS}"
  echo "CHK|os|$( . /etc/os-release 2>/dev/null && echo "${ID} ${VERSION_ID}")"
'

RAW=$(ssh "${SSH_OPTS[@]}" "${USER_NAME}@${HOST}" "$REMOTE" 2>&1)
if [ $? -ne 0 ]; then
  echo "FALHA ao auditar ${HOST}." >&2
  printf '%s\n' "$RAW" >&2
  exit 13
fi

get() { printf '%s\n' "$RAW" | grep "^CHK|$1|" | head -n1 | cut -d'|' -f3-; }

ROOT_LOGIN=$(get root_login)
PASS_AUTH=$(get password_auth)
PUBKEY=$(get pubkey_auth)
MAXTRIES=$(get max_auth_tries)
SSH_PORT=$(get ssh_port)
FIREWALL=$(get firewall)
FAIL2BAN=$(get fail2ban)
AUTOUPD=$(get auto_updates)
PENDING=$(get security_pending)
REBOOT=$(get reboot_required)
ADMINS=$(get admin_users)
KEYS=$(get authorized_keys)
OSINFO=$(get os)

SCORE=0
declare -a RISCOS=()

check() { # nome peso condicao_ok valor severidade explicacao correção
  local nome="$1" peso="$2" ok="$3" valor="$4" sev="$5" expl="$6" fix="$7"
  if [ "$ok" -eq 1 ]; then
    SCORE=$((SCORE + peso))
    printf '  [ok]   %-22s %-18s (peso %s)\n' "$nome" "$valor" "$peso"
  else
    printf '  [RISCO] %-21s %-18s (peso %s)\n' "$nome" "$valor" "$peso"
    RISCOS+=("${sev}|${nome}|${expl}|${fix}")
  fi
}

echo ""
echo "============================================"
echo " AUDITORIA DE SEGURANCA — ${HOST}"
echo " Sistema: ${OSINFO:-desconhecido}"
echo " Modo: somente leitura, nada foi alterado"
echo "============================================"
echo ""

check "entrada do root" 20 "$([ "$ROOT_LOGIN" = "no" ] && echo 1 || echo 0)" "${ROOT_LOGIN:-?}" alta \
  "Root é o dono absoluto da máquina. É a primeira conta que os robôs tentam, o dia inteiro, em todo servidor da internet." \
  "harden.sh --host ${HOST} --validated"

check "entrada por senha" 20 "$([ "$PASS_AUTH" = "no" ] && echo 1 || echo 0)" "${PASS_AUTH:-?}" alta \
  "Senha da para ficar tentando até acertar. Chave não: ou a pessoa tem o arquivo, ou não entra." \
  "harden.sh --host ${HOST} --validated"

check "entrada por chave" 10 "$([ "$PUBKEY" = "yes" ] && echo 1 || echo 0)" "${PUBKEY:-?}" alta \
  "A chave é o seu cartão de acesso. Se ela estiver desligada, não sobra caminho seguro de entrar." \
  "no servidor, em /etc/ssh/sshd_config, a linha PubkeyAuthentication precisa estar yes"

check "firewall" 15 "$([ "$FIREWALL" = "ativo_deny" ] && echo 1 || echo 0)" "${FIREWALL:-?}" alta \
  "O firewall é o porteiro: barra tudo que você não liberou de propósito. Sem ele, qualquer porta aberta por engano fica exposta na hora." \
  "ufw default deny incoming && ufw enable (o SSHepherd libera o SSH antes, para você não ficar trancado fora)"

check "bloqueio de quem insiste" 10 "$([ "$FAIL2BAN" = "ativo" ] && echo 1 || echo 0)" "${FAIL2BAN:-?}" media \
  "Sem isso, quem está tentando adivinhar sua entrada pode tentar para sempre, sem nunca ser barrado." \
  "apt-get install -y fail2ban && systemctl enable --now fail2ban"

check "atualizações automáticas" 10 "$([ "$AUTOUPD" = "ativo" ] && echo 1 || echo 0)" "${AUTOUPD:-?}" media \
  "Quando descobrem uma falha, ela vira notícia e o conserto sai junto. Servidor sem atualizar fica com o buraco que todo mundo já sabe onde fica." \
  "apt-get install -y unattended-upgrades"

check "correções esperando" 5 "$([ "${PENDING:-0}" -eq 0 ] && echo 1 || echo 0)" "${PENDING:-?} esperando" media \
  "Cada uma é um buraco já publicado e ainda aberto na sua máquina." \
  "apt-get update && apt-get -y upgrade"

check "tentativas por conexão" 5 "$([ -n "$MAXTRIES" ] && [ "$MAXTRIES" -le 4 ] 2>/dev/null && echo 1 || echo 0)" "${MAXTRIES:-?}" baixa \
  "Quanto mais tentativas você permite por conexão, mais chances tem quem está tentando adivinhar." \
  "no servidor, em /etc/ssh/sshd_config, deixe MaxAuthTries 3"

check "reinício pendente" 5 "$([ "$REBOOT" = "não" ] && echo 1 || echo 0)" "${REBOOT:-?}" media \
  "Tem atualização da parte mais funda do sistema esperando. Ela só passa a valer quando a máquina reinicia." \
  "reinicie o servidor numa hora de pouco movimento, combinada"

echo ""
echo "  Para você conferir (não entra na nota):"
echo "    chaves que abrem este servidor  : ${KEYS:-?}  — cada uma é alguém que entra"
echo "    contas que mandam na máquina    : ${ADMINS:-nenhuma}"
echo "    porta por onde o SSH atende     : ${SSH_PORT:-22}"
printf '%s\n' "$RAW" | grep "^CHK|keys_detail|" | cut -d'|' -f3- | sed 's/^/    chaves por conta: /'

BAND="crítico"
[ "$SCORE" -ge 50 ] && BAND="fraco"
[ "$SCORE" -ge 70 ] && BAND="aceitavel"
[ "$SCORE" -ge 85 ] && BAND="bom"
[ "$SCORE" -ge 95 ] && BAND="excelente"

echo ""
echo "============================================"
echo " NOTA: ${SCORE}/100  —  ${BAND}"
echo " Alvo do SSHepherd após o Dia Zero: 85"
echo "============================================"

if [ ${#RISCOS[@]} -gt 0 ]; then
  echo ""
  echo "RISCOS ENCONTRADOS (do mais grave para o menos):"
  for sev in alta media baixa; do
    for r in "${RISCOS[@]}"; do
      IFS='|' read -r s nome expl fix <<< "$r"
      [ "$s" = "$sev" ] || continue
      echo ""
      echo "  [${s^^}] ${nome}"
      echo "     por que importa: ${expl}"
      echo "     como corrigir  : ${fix}"
    done
  done
else
  echo ""
  echo "Nenhum risco encontrado nas verificações cobertas."
fi

echo ""
echo "SCORE=${SCORE}"
echo "BAND=${BAND}"
exit 0
