fix: 修正 server-security-check 的 SSH 误报并标注高风险

- PermitRootLogin: 接受 without-password 别名(OpenSSH -T 会输出该旧别名),
  避免把仅允许密钥的 root 登录误报为“无法确定安全状态”
- 检查未通过时列出配置文件中的相关设置行:sshd 只采用先读到的一条,
  主配置 Include 之前的 drop-in 以及 Match 块内的设置都会造成“改过但不生效”
- sshd -T 读取失败时同样给出配置文件设置,便于定位
- 参数匹配改为大小写不敏感
- 高风险整行红色、警告黄色,汇总同步着色;非 TTY 或 NO_COLOR 时禁用
This commit is contained in:
2026-09-17 12:08:24 +08:00
parent 2060bbd23a
commit 8c80e5b630
+63 -10
View File
@@ -58,6 +58,17 @@ if [[ $NO_SUDO -eq 0 && $EUID -ne 0 ]]; then
exec sudo -- "$0" exec sudo -- "$0"
fi 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 PASS_COUNT=0
WARN_COUNT=0 WARN_COUNT=0
HIGH_COUNT=0 HIGH_COUNT=0
@@ -70,12 +81,12 @@ pass() {
warn() { warn() {
WARN_COUNT=$((WARN_COUNT + 1)) WARN_COUNT=$((WARN_COUNT + 1))
printf '[警告] %s\n' "$*" printf '%s[警告] %s%s\n' "$COLOR_WARN" "$*" "$COLOR_RESET"
} }
high() { high() {
HIGH_COUNT=$((HIGH_COUNT + 1)) HIGH_COUNT=$((HIGH_COUNT + 1))
printf '[高风险] %s\n' "$*" printf '%s[高风险] %s%s\n' "$COLOR_HIGH" "$*" "$COLOR_RESET"
} }
info() { info() {
@@ -103,11 +114,42 @@ find_sshd() {
} }
check_sshd_setting() { check_sshd_setting() {
local setting="$1" value local setting value
value=$(printf '%s\n' "$SSHD_EFFECTIVE_CONFIG" | awk -v key="$setting" '$1 == key { print $2; exit }') 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" 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() { check_ssh() {
local sshd_bin root_login password_login ssh_ports local sshd_bin root_login password_login ssh_ports
@@ -121,6 +163,7 @@ check_ssh() {
SSHD_EFFECTIVE_CONFIG=$("$sshd_bin" -T 2>/dev/null) SSHD_EFFECTIVE_CONFIG=$("$sshd_bin" -T 2>/dev/null)
if [[ -z "$SSHD_EFFECTIVE_CONFIG" ]]; then if [[ -z "$SSHD_EFFECTIVE_CONFIG" ]]; then
warn "无法读取 sshd 生效配置;请使用 sudo 重试或检查 sshd 配置" warn "无法读取 sshd 生效配置;请使用 sudo 重试或检查 sshd 配置"
check_ssh_setting_sources permitrootlogin passwordauthentication
return return
fi fi
@@ -132,14 +175,17 @@ check_ssh() {
no|forced-commands-only) no|forced-commands-only)
pass "SSH 已禁止 root 交互式登录(PermitRootLogin: $root_login)" pass "SSH 已禁止 root 交互式登录(PermitRootLogin: $root_login)"
;; ;;
prohibit-password) prohibit-password|without-password)
pass "SSH root 登录仅允许密钥认证(PermitRootLogin: prohibit-password)" # without-password 是 prohibit-password 的旧别名,含义相同
pass "SSH root 登录仅允许密钥认证(PermitRootLogin: $root_login)"
;; ;;
yes) yes)
high "SSH 允许 root 直接登录;建议设为 PermitRootLogin no" high "SSH 允许 root 直接登录;建议设为 PermitRootLogin no"
check_ssh_setting_sources permitrootlogin
;; ;;
*) *)
warn "无法确定 PermitRootLogin 的安全状态(当前: ${root_login:-未设置})" warn "无法确定 PermitRootLogin 的安全状态(当前: ${root_login:-未设置})"
check_ssh_setting_sources permitrootlogin
;; ;;
esac esac
@@ -149,9 +195,11 @@ check_ssh() {
;; ;;
yes) yes)
warn "SSH 允许密码登录;公网服务器建议改用密钥认证" warn "SSH 允许密码登录;公网服务器建议改用密钥认证"
check_ssh_setting_sources passwordauthentication
;; ;;
*) *)
warn "无法确定 PasswordAuthentication 的安全状态(当前: ${password_login:-未设置})" warn "无法确定 PasswordAuthentication 的安全状态(当前: ${password_login:-未设置})"
check_ssh_setting_sources passwordauthentication
;; ;;
esac esac
@@ -381,11 +429,16 @@ check_privileged_accounts
check_recent_logins check_recent_logins
printf '\n== 汇总 ==\n' printf '\n== 汇总 ==\n'
printf '通过: %d 警告: %d 高风险: %d 信息: %d\n' "$PASS_COUNT" "$WARN_COUNT" "$HIGH_COUNT" "$INFO_COUNT"
if ((HIGH_COUNT > 0)); then if ((HIGH_COUNT > 0)); then
echo "请优先处理“高风险”项。" SUMMARY_COLOR="$COLOR_HIGH"
SUMMARY_ADVICE='请优先处理“高风险”项。'
elif ((WARN_COUNT > 0)); then elif ((WARN_COUNT > 0)); then
echo "建议逐项确认“警告”项是否符合服务器的实际部署方式。" SUMMARY_COLOR="$COLOR_WARN"
SUMMARY_ADVICE='建议逐项确认“警告”项是否符合服务器的实际部署方式。'
else else
echo "未发现需要立即处理的项。" SUMMARY_COLOR=""
SUMMARY_ADVICE='未发现需要立即处理的项。'
fi 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"