#!/usr/bin/env bash
set -euo pipefail

# RHEL SSH Hardening Validation Script
#
# Purpose:
#   Review common SSH daemon hardening settings on Red Hat Enterprise Linux
#   systems before making changes. This script is read-only by default and does
#   not modify configuration files or restart services.
#
# What it checks:
#   - Whether sshd_config exists and is readable
#   - Effective sshd configuration via sshd -T when available
#   - Common hardening controls:
#       PasswordAuthentication
#       KbdInteractiveAuthentication / ChallengeResponseAuthentication
#       PermitRootLogin
#       PubkeyAuthentication
#       AllowUsers / AllowGroups / DenyUsers / DenyGroups
#       X11Forwarding
#       MaxAuthTries
#       LoginGraceTime
#   - Whether SSH service appears active
#   - Whether port 22 is listening locally
#
# Usage:
#   ./rhel-ssh-hardening-check.sh
#   ./rhel-ssh-hardening-check.sh /etc/ssh/sshd_config
#
# Notes:
#   - Run from a second session or with console access before changing SSH.
#   - Review the output before applying any hardening changes.

CONFIG_FILE="${1:-/etc/ssh/sshd_config}"

info() {
  printf '[INFO] %s\n' "$*"
}

warn() {
  printf '[WARN] %s\n' "$*"
}

ok() {
  printf '[ OK ] %s\n' "$*"
}

fail() {
  printf '[FAIL] %s\n' "$*"
}

section() {
  printf '\n== %s ==\n' "$*"
}

command_exists() {
  command -v "$1" >/dev/null 2>&1
}

check_config_file() {
  section "Configuration file"
  if [[ -r "$CONFIG_FILE" ]]; then
    ok "Readable SSH config found: $CONFIG_FILE"
  else
    fail "SSH config not readable or not found: $CONFIG_FILE"
    return 1
  fi
}

check_sshd_service() {
  section "Service status"
  if command_exists systemctl; then
    if systemctl is-active --quiet sshd; then
      ok "sshd service is active"
    elif systemctl is-active --quiet ssh; then
      ok "ssh service is active"
    else
      warn "sshd/ssh service does not appear active via systemctl"
    fi
  else
    warn "systemctl not available; skipping service status check"
  fi
}

check_listening_port() {
  section "Local listener"
  if command_exists ss; then
    if ss -ltn '( sport = :22 )' | grep -q ':22'; then
      ok "Port 22 is listening locally"
    else
      warn "Port 22 not detected as listening locally"
    fi
  else
    warn "ss command not available; skipping port check"
  fi
}

print_effective_settings() {
  section "Effective SSH settings"
  if command_exists sshd; then
    if sshd -T -f "$CONFIG_FILE" >/tmp/sshd-effective.$$ 2>/dev/null; then
      ok "Loaded effective configuration from $CONFIG_FILE"
      grep -E '^(passwordauthentication|kbdinteractiveauthentication|challengeresponseauthentication|permitrootlogin|pubkeyauthentication|x11forwarding|maxauthtries|logingracetime|allowusers|allowgroups|denyusers|denygroups) ' /tmp/sshd-effective.$$ || true
      rm -f /tmp/sshd-effective.$$
    else
      warn "Could not query effective sshd settings with sshd -T"
      warn "The configuration may contain syntax issues or unsupported options"
    fi
  else
    warn "sshd binary not available; skipping effective settings check"
  fi
}

check_hardening_hints() {
  section "Hardening hints from config text"

  local findings=0

  if grep -Eq '^[[:space:]]*PasswordAuthentication[[:space:]]+no([[:space:]]|$)' "$CONFIG_FILE"; then
    ok "PasswordAuthentication is disabled"
  else
    warn "PasswordAuthentication is not explicitly disabled"
    findings=$((findings + 1))
  fi

  if grep -Eq '^[[:space:]]*(KbdInteractiveAuthentication|ChallengeResponseAuthentication)[[:space:]]+no([[:space:]]|$)' "$CONFIG_FILE"; then
    ok "Keyboard-interactive / challenge-response authentication is restricted"
  else
    warn "Keyboard-interactive / challenge-response authentication is not explicitly restricted"
    findings=$((findings + 1))
  fi

  if grep -Eq '^[[:space:]]*PermitRootLogin[[:space:]]+no([[:space:]]|$)' "$CONFIG_FILE"; then
    ok "Direct root SSH login is disabled"
  else
    warn "Direct root SSH login is not explicitly disabled"
    findings=$((findings + 1))
  fi

  if grep -Eq '^[[:space:]]*PubkeyAuthentication[[:space:]]+yes([[:space:]]|$)' "$CONFIG_FILE"; then
    ok "Public key authentication is enabled"
  else
    warn "Public key authentication is not explicitly enabled"
    findings=$((findings + 1))
  fi

  if grep -Eq '^[[:space:]]*(AllowUsers|AllowGroups|DenyUsers|DenyGroups)[[:space:]]+' "$CONFIG_FILE"; then
    ok "User or group access restrictions are present"
  else
    warn "No explicit AllowUsers / AllowGroups / DenyUsers / DenyGroups rules found"
    findings=$((findings + 1))
  fi

  if grep -Eq '^[[:space:]]*X11Forwarding[[:space:]]+no([[:space:]]|$)' "$CONFIG_FILE"; then
    ok "X11 forwarding is disabled"
  else
    warn "X11 forwarding is not explicitly disabled"
    findings=$((findings + 1))
  fi

  if grep -Eq '^[[:space:]]*MaxAuthTries[[:space:]]+[0-9]+' "$CONFIG_FILE"; then
    ok "MaxAuthTries is set"
  else
    warn "MaxAuthTries is not explicitly set"
    findings=$((findings + 1))
  fi

  if grep -Eq '^[[:space:]]*LoginGraceTime[[:space:]]+' "$CONFIG_FILE"; then
    ok "LoginGraceTime is set"
  else
    warn "LoginGraceTime is not explicitly set"
    findings=$((findings + 1))
  fi

  if [[ "$findings" -eq 0 ]]; then
    ok "No obvious gaps detected in the basic text scan"
  else
    warn "Basic text scan found $findings potential hardening gaps"
  fi
}

print_next_steps() {
  section "Recommended next steps"
  cat <<'EOF'
- Confirm a second session or console path before changing SSH settings.
- Back up the current sshd_config before any edits.
- Prefer key-based authentication for approved administrators.
- Restrict root SSH login and use named accounts with privilege elevation.
- Limit access by user, group, and source network where practical.
- Validate changes with 'sshd -t' before reloading the service.
- Test login from an authorized host before closing the maintenance window.
EOF
}

main() {
  info "Starting RHEL SSH hardening validation"
  info "Using config file: $CONFIG_FILE"

  check_config_file
  check_sshd_service
  check_listening_port
  print_effective_settings
  check_hardening_hints
  print_next_steps

  info "Validation complete"
}

main "$@"