diff --git a/script/server-security-check.sh b/script/server-security-check.sh index be94207..061ffe9 100755 --- a/script/server-security-check.sh +++ b/script/server-security-check.sh @@ -58,6 +58,17 @@ if [[ $NO_SUDO -eq 0 && $EUID -ne 0 ]]; then exec sudo -- "$0" fi +# 仅在输出到终端时着色;重定向到文件或设置 NO_COLOR 时保持纯文本 +if [[ -t 1 && -z "${NO_COLOR:-}" ]]; then + COLOR_WARN=$'\033[1;33m' + COLOR_HIGH=$'\033[0;31m' + COLOR_RESET=$'\033[0m' +else + COLOR_WARN="" + COLOR_HIGH="" + COLOR_RESET="" +fi + PASS_COUNT=0 WARN_COUNT=0 HIGH_COUNT=0 @@ -70,12 +81,12 @@ pass() { warn() { WARN_COUNT=$((WARN_COUNT + 1)) - printf '[警告] %s\n' "$*" + printf '%s[警告] %s%s\n' "$COLOR_WARN" "$*" "$COLOR_RESET" } high() { HIGH_COUNT=$((HIGH_COUNT + 1)) - printf '[高风险] %s\n' "$*" + printf '%s[高风险] %s%s\n' "$COLOR_HIGH" "$*" "$COLOR_RESET" } info() { @@ -103,11 +114,42 @@ find_sshd() { } check_sshd_setting() { - local setting="$1" value - value=$(printf '%s\n' "$SSHD_EFFECTIVE_CONFIG" | awk -v key="$setting" '$1 == key { print $2; exit }') + local setting value + setting=$(printf '%s' "$1" | tr '[:upper:]' '[:lower:]') + value=$(printf '%s\n' "$SSHD_EFFECTIVE_CONFIG" | awk -v key="$setting" 'tolower($1) == key { print tolower($2); exit }') printf '%s' "$value" } +SSHD_CONFIG_FILE=/etc/ssh/sshd_config +SSHD_CONFIG_DIR=/etc/ssh/sshd_config.d + +# 列出配置文件中显式设置某个参数的行,用于解释“明明改过了为何还不生效”。 +# sshd 对同一参数只采用先读到的一条:Include /etc/ssh/sshd_config.d/*.conf +# 通常位于主配置前部,因此 drop-in 会覆盖主配置后部的同名设置;Match 块内的设置 +# 也不作用于全局配置。 +check_ssh_setting_sources() { + local key file matches files + for key in "$@"; do + files=() + [[ -r "$SSHD_CONFIG_FILE" ]] && files+=("$SSHD_CONFIG_FILE") + if [[ -d "$SSHD_CONFIG_DIR" ]]; then + for file in "$SSHD_CONFIG_DIR"/*.conf; do + [[ -r "$file" ]] && files+=("$file") + done + fi + ((${#files[@]} > 0)) || continue + + matches=$(grep -inHE "^[[:space:]]*${key}([[:space:]]|=)" "${files[@]}" 2>/dev/null || true) + if [[ -z "$matches" ]]; then + info "${key} 未在配置文件中显式设置,生效值可能来自 sshd 默认值" + continue + fi + + info "${key} 的配置行(sshd 只采用先读到的一条;Match 块内的设置不作用于全局):" + printf '%s\n' "$matches" | sed 's/^/ /' + done +} + check_ssh() { local sshd_bin root_login password_login ssh_ports @@ -121,6 +163,7 @@ check_ssh() { SSHD_EFFECTIVE_CONFIG=$("$sshd_bin" -T 2>/dev/null) if [[ -z "$SSHD_EFFECTIVE_CONFIG" ]]; then warn "无法读取 sshd 生效配置;请使用 sudo 重试或检查 sshd 配置" + check_ssh_setting_sources permitrootlogin passwordauthentication return fi @@ -132,14 +175,17 @@ check_ssh() { no|forced-commands-only) pass "SSH 已禁止 root 交互式登录(PermitRootLogin: $root_login)" ;; - prohibit-password) - pass "SSH root 登录仅允许密钥认证(PermitRootLogin: prohibit-password)" + prohibit-password|without-password) + # without-password 是 prohibit-password 的旧别名,含义相同 + pass "SSH root 登录仅允许密钥认证(PermitRootLogin: $root_login)" ;; yes) high "SSH 允许 root 直接登录;建议设为 PermitRootLogin no" + check_ssh_setting_sources permitrootlogin ;; *) warn "无法确定 PermitRootLogin 的安全状态(当前: ${root_login:-未设置})" + check_ssh_setting_sources permitrootlogin ;; esac @@ -149,9 +195,11 @@ check_ssh() { ;; yes) warn "SSH 允许密码登录;公网服务器建议改用密钥认证" + check_ssh_setting_sources passwordauthentication ;; *) warn "无法确定 PasswordAuthentication 的安全状态(当前: ${password_login:-未设置})" + check_ssh_setting_sources passwordauthentication ;; esac @@ -381,11 +429,16 @@ check_privileged_accounts check_recent_logins printf '\n== 汇总 ==\n' -printf '通过: %d 警告: %d 高风险: %d 信息: %d\n' "$PASS_COUNT" "$WARN_COUNT" "$HIGH_COUNT" "$INFO_COUNT" if ((HIGH_COUNT > 0)); then - echo "请优先处理“高风险”项。" + SUMMARY_COLOR="$COLOR_HIGH" + SUMMARY_ADVICE='请优先处理“高风险”项。' elif ((WARN_COUNT > 0)); then - echo "建议逐项确认“警告”项是否符合服务器的实际部署方式。" + SUMMARY_COLOR="$COLOR_WARN" + SUMMARY_ADVICE='建议逐项确认“警告”项是否符合服务器的实际部署方式。' else - echo "未发现需要立即处理的项。" + SUMMARY_COLOR="" + SUMMARY_ADVICE='未发现需要立即处理的项。' fi +printf '%s通过: %d 警告: %d 高风险: %d 信息: %d%s\n' \ + "$SUMMARY_COLOR" "$PASS_COUNT" "$WARN_COUNT" "$HIGH_COUNT" "$INFO_COUNT" "$COLOR_RESET" +printf '%s%s%s\n' "$SUMMARY_COLOR" "$SUMMARY_ADVICE" "$COLOR_RESET"