#!/bin/bash
################################################################################
# 46_security_deep.sh — 安全合规深度检测
# 功能: 检测弱密码、安全基线、可疑文件、异常配置
# 参考: SecurityCheck.psm1（PowerShell 移植）
# 日期: 2026-07-22
################################################################################

# 获取脚本所在目录并加载依赖
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
LIB_DIR="${LIB_DIR:-/tmp/check_modules}"

# 加载配置文件和通用函数库
if [ -f "$LIB_DIR/lib/config.sh" ]; then
    source "$LIB_DIR/lib/config.sh"
else
    echo "ERROR: 配置文件不存在: $LIB_DIR/lib/config.sh"
    exit 1
fi

if [ -f "$LIB_DIR/lib/common.sh" ]; then
    source "$LIB_DIR/lib/common.sh"
else
    echo "ERROR: 通用函数库不存在: $LIB_DIR/lib/common.sh"
    exit 1
fi

# 从配置获取容器匹配模式
MYSQL_CONTAINER="${CONTAINERS[mysql]:-mysql}"
REDIS_CONTAINER="${CONTAINERS[redis]:-redis}"
EMQX_CONTAINER="${CONTAINERS[emqx]:-emqx}"

# 安全白名单（从 config.sh 读取或使用默认值）
SUID_WHITELIST="${SUID_WHITELIST:-/usr/bin/sudo,/usr/bin/passwd,/usr/bin/su,/usr/bin/ping,/usr/bin/mount,/usr/bin/umount,/usr/bin/newgrp,/usr/bin/chsh,/usr/bin/chfn,/usr/bin/gpasswd,/usr/lib/openssh/ssh-keysign,/usr/libexec/openssh/ssh-keysign}"
PORT_WHITELIST="${PORT_WHITELIST_NEW:-22,80,443,1883,3306,6379,8083,8084,8888,18083}"
EMQX_DASHBOARD_PORT="${EMQX_DASHBOARD_PORT:-18083}"

# ==================== MySQL 安全检测 ====================

# 检测 MySQL Root 访问范围
check_mysql_root_access() {
    local actual_container
    actual_container=$(docker ps --format '{{.Names}}' 2>/dev/null | grep -iE "$MYSQL_CONTAINER" | head -1)

    if [ -z "$actual_container" ]; then
        output_result "SECURITY_DEEP_MYSQL_ROOT_HOST" "未检测到容器"
        output_result "SECURITY_DEEP_MYSQL_ROOT_HOST_LEVEL" "跳过"
        return
    fi

    # 查询 root 用户的 host 字段
    local hosts
    hosts=$(docker exec "$actual_container" mysql -uroot -p"$MYSQL_PASSWORD" -N -e \
        "SELECT host FROM mysql.user WHERE user='root'" 2>/dev/null | tr '\n' ',' | sed 's/,$//')

    if [ -z "$hosts" ]; then
        output_result "SECURITY_DEEP_MYSQL_ROOT_HOST" "查询失败"
        output_result "SECURITY_DEEP_MYSQL_ROOT_HOST_LEVEL" "跳过"
        return
    fi

    # 判断是否存在 % 通配符（允许任意主机连接）
    if echo "$hosts" | grep -q '%'; then
        output_result "SECURITY_DEEP_MYSQL_ROOT_HOST" "$hosts"
        output_result "SECURITY_DEEP_MYSQL_ROOT_HOST_LEVEL" "严重"
    else
        output_result "SECURITY_DEEP_MYSQL_ROOT_HOST" "$hosts"
        output_result "SECURITY_DEEP_MYSQL_ROOT_HOST_LEVEL" "正常"
    fi
}

# 检测 MySQL 空密码用户
check_mysql_empty_password() {
    local actual_container
    actual_container=$(docker ps --format '{{.Names}}' 2>/dev/null | grep -iE "$MYSQL_CONTAINER" | head -1)

    if [ -z "$actual_container" ]; then
        output_result "SECURITY_DEEP_MYSQL_EMPTY_PWD_USERS" "未检测到容器"
        output_result "SECURITY_DEEP_MYSQL_EMPTY_PWD_USERS_LEVEL" "跳过"
        return
    fi

    # 查询 authentication_string 为空的用户
    local empty_count
    empty_count=$(docker exec "$actual_container" mysql -uroot -p"$MYSQL_PASSWORD" -N -e \
        "SELECT COUNT(*) FROM mysql.user WHERE authentication_string='' OR (plugin='mysql_native_password' AND authentication_string='')" \
        2>/dev/null | tr -d '\r\n')

    if [ -z "$empty_count" ]; then
        output_result "SECURITY_DEEP_MYSQL_EMPTY_PWD_USERS" "查询失败"
        output_result "SECURITY_DEEP_MYSQL_EMPTY_PWD_USERS_LEVEL" "跳过"
        return
    fi

    output_result "SECURITY_DEEP_MYSQL_EMPTY_PWD_USERS" "$empty_count"

    if [ "$empty_count" -gt 0 ]; then
        output_result "SECURITY_DEEP_MYSQL_EMPTY_PWD_USERS_LEVEL" "严重"
    else
        output_result "SECURITY_DEEP_MYSQL_EMPTY_PWD_USERS_LEVEL" "正常"
    fi
}

# ==================== Redis 密码检测 ====================

check_redis_password() {
    local actual_container
    actual_container=$(docker ps --format '{{.Names}}' 2>/dev/null | grep -iE "$REDIS_CONTAINER" | head -1)

    if [ -z "$actual_container" ]; then
        output_result "SECURITY_DEEP_REDIS_PASSWORD" "未检测到容器"
        output_result "SECURITY_DEEP_REDIS_PASSWORD_LEVEL" "跳过"
        return
    fi

    # 查找 requirepass 配置
    local requirepass
    requirepass=$(docker exec "$actual_container" sh -c \
        "grep -E '^requirepass' /etc/redis/redis.conf 2>/dev/null || \
         grep -E '^requirepass' /usr/local/etc/redis/redis.conf 2>/dev/null || \
         echo 'NO_CONFIG'" | awk '{print $2}')

    if [ "$requirepass" = "NO_CONFIG" ] || [ -z "$requirepass" ]; then
        output_result "SECURITY_DEEP_REDIS_PASSWORD" "无密码"
        output_result "SECURITY_DEEP_REDIS_PASSWORD_LEVEL" "严重"
    else
        # 弱密码字典检测
        local weak_passwords="123456 password root admin redis test guest"
        local is_weak="no"
        for wp in $weak_passwords; do
            if [ "$requirepass" = "$wp" ]; then
                is_weak="yes"
                break
            fi
        done

        if [ "$is_weak" = "yes" ]; then
            output_result "SECURITY_DEEP_REDIS_PASSWORD" "弱密码"
            output_result "SECURITY_DEEP_REDIS_PASSWORD_LEVEL" "严重"
        else
            output_result "SECURITY_DEEP_REDIS_PASSWORD" "已设置"
            output_result "SECURITY_DEEP_REDIS_PASSWORD_LEVEL" "正常"
        fi
    fi
}

# ==================== EMQX 默认密码检测 ====================

check_emqx_default_password() {
    local actual_container
    actual_container=$(docker ps --format '{{.Names}}' 2>/dev/null | grep -iE "$EMQX_CONTAINER" | head -1)

    if [ -z "$actual_container" ]; then
        output_result "SECURITY_DEEP_EMQX_DEFAULT_PWD" "未检测到容器"
        output_result "SECURITY_DEEP_EMQX_DEFAULT_PWD_LEVEL" "跳过"
        return
    fi

    # 使用默认密码 admin:public 测试 Dashboard API
    local http_code
    http_code=$(docker exec "$actual_container" curl -s -o /dev/null -w '%{http_code}' \
        -u admin:public "http://localhost:$EMQX_DASHBOARD_PORT/api/v5/status" 2>/dev/null)

    if [ "$http_code" = "200" ] || [ "$http_code" = "204" ]; then
        output_result "SECURITY_DEEP_EMQX_DEFAULT_PWD" "使用默认密码"
        output_result "SECURITY_DEEP_EMQX_DEFAULT_PWD_LEVEL" "严重"
    else
        output_result "SECURITY_DEEP_EMQX_DEFAULT_PWD" "非默认密码"
        output_result "SECURITY_DEEP_EMQX_DEFAULT_PWD_LEVEL" "正常"
    fi
}

# ==================== Linux 空密码账户检测 ====================

check_linux_empty_password() {
    # 需要 root 权限读取 /etc/shadow
    if [ ! -r /etc/shadow ]; then
        output_result "SECURITY_DEEP_LINUX_EMPTY_PWD_COUNT" "无法读取"
        output_result "SECURITY_DEEP_LINUX_EMPTY_PWD_COUNT_LEVEL" "跳过"
        return
    fi

    # 第 2 字段为空表示空密码
    local empty_count
    empty_count=$(awk -F: 'length($2)==0 {print $1}' /etc/shadow 2>/dev/null | wc -l)

    output_result "SECURITY_DEEP_LINUX_EMPTY_PWD_COUNT" "$empty_count"

    if [ "$empty_count" -gt 0 ]; then
        output_result "SECURITY_DEEP_LINUX_EMPTY_PWD_COUNT_LEVEL" "严重"
    else
        output_result "SECURITY_DEEP_LINUX_EMPTY_PWD_COUNT_LEVEL" "正常"
    fi
}

# ==================== SUID 文件检测 ====================

check_suid_files() {
    # 扫描 SUID 文件，排除 Docker/proc/sys 路径
    local all_suid
    all_suid=$(find / -perm -4000 -type f \
        -not -path '/data/dockers/*' \
        -not -path '/var/lib/docker/*' \
        -not -path '/proc/*' \
        -not -path '/sys/*' \
        2>/dev/null | sort)

    local all_count
    all_count=$(echo "$all_suid" | grep -c '/' 2>/dev/null || echo "0")

    # 与白名单对比
    local suspicious=0
    while IFS= read -r file; do
        [ -z "$file" ] && continue
        if ! echo "$SUID_WHITELIST" | grep -qF "$file"; then
            suspicious=$((suspicious + 1))
        fi
    done <<< "$all_suid"

    output_result "SECURITY_DEEP_SUID_COUNT" "$all_count"
    output_result "SECURITY_DEEP_SUSPICIOUS_SUID_COUNT" "$suspicious"

    if [ "$suspicious" -gt 0 ]; then
        output_result "SECURITY_DEEP_SUSPICIOUS_SUID_COUNT_LEVEL" "警告"
    else
        output_result "SECURITY_DEEP_SUSPICIOUS_SUID_COUNT_LEVEL" "正常"
    fi
}

# ==================== Crontab 检测 ====================

check_crontab() {
    # 收集所有用户和系统级 crontab
    local cron_entries=""

    # 用户 crontab
    for user in $(cut -d: -f1 /etc/passwd 2>/dev/null); do
        local user_cron
        user_cron=$(crontab -u "$user" -l 2>/dev/null | grep -vE '^\s*#|^\s*$' || true)
        [ -n "$user_cron" ] && cron_entries+="$user_cron"$'\n'
    done

    # 系统 crontab
    local sys_cron
    sys_cron=$(cat /etc/crontab 2>/dev/null | grep -vE '^\s*#|^\s*$' || true)
    [ -n "$sys_cron" ] && cron_entries+="$sys_cron"$'\n'

    # cron.d 目录
    if [ -d /etc/cron.d ]; then
        local cron_d
        cron_d=$(cat /etc/cron.d/* 2>/dev/null | grep -vE '^\s*#|^\s*$' || true)
        [ -n "$cron_d" ] && cron_entries+="$cron_d"$'\n'
    fi

    # 已知安全模式（白名单关键词）
    local safe_patterns="health_check|check_server|backup|logrotate|certbot|docker|container|ntpdate|sysstat"

    local suspicious=0
    while IFS= read -r line; do
        [ -z "$line" ] && continue
        if ! echo "$line" | grep -qiE "$safe_patterns"; then
            suspicious=$((suspicious + 1))
        fi
    done <<< "$cron_entries"

    output_result "SECURITY_DEEP_SUSPICIOUS_CRON_COUNT" "$suspicious"

    if [ "$suspicious" -gt 0 ]; then
        output_result "SECURITY_DEEP_SUSPICIOUS_CRON_COUNT_LEVEL" "警告"
    else
        output_result "SECURITY_DEEP_SUSPICIOUS_CRON_COUNT_LEVEL" "正常"
    fi
}

# ==================== SSH 暴力破解检测 ====================

check_ssh_bruteforce() {
    local logfile=""

    if [ -f /var/log/secure ]; then
        logfile=/var/log/secure
    elif [ -f /var/log/auth.log ]; then
        logfile=/var/log/auth.log
    fi

    if [ -z "$logfile" ]; then
        output_result "SECURITY_DEEP_SSH_BRUTEFORCE_IPS" "无日志"
        output_result "SECURITY_DEEP_SSH_BRUTEFORCE_IPS_LEVEL" "跳过"
        return
    fi

    # 统计失败登录 IP（>=50 次视为暴力破解）
    local bruteforce_ips
    bruteforce_ips=$(grep -i "failed\|failure\|invalid" "$logfile" 2>/dev/null | \
        grep -oE 'from [0-9]+\.[0-9]+\.[0-9]+\.[0-9]+' | \
        awk '{print $2}' | sort | uniq -c | \
        awk '$1 >= 50 {print $2}')

    local bruteforce_count
    bruteforce_count=$(echo "$bruteforce_ips" | grep -cE '[0-9]+\.' 2>/dev/null || echo "0")

    output_result "SECURITY_DEEP_SSH_BRUTEFORCE_IPS" "$bruteforce_count"

    if [ "$bruteforce_count" -gt 0 ]; then
        output_result "SECURITY_DEEP_SSH_BRUTEFORCE_IPS_LEVEL" "严重"
    else
        output_result "SECURITY_DEEP_SSH_BRUTEFORCE_IPS_LEVEL" "正常"
    fi
}

# ==================== 异常端口检测 ====================

check_unexpected_ports() {
    # 获取监听端口列表
    local listening_ports
    listening_ports=$(ss -tlnp 2>/dev/null | grep LISTEN | \
        awk '{print $4}' | grep -oE '[0-9]+$' | sort -nu)

    # 与白名单对比
    local unexpected=""
    local unexpected_count=0

    for port in $listening_ports; do
        if ! echo ",$PORT_WHITELIST," | grep -qF ",$port,"; then
            if [ -n "$unexpected" ]; then
                unexpected="$unexpected,$port"
            else
                unexpected="$port"
            fi
            unexpected_count=$((unexpected_count + 1))
        fi
    done

    output_result "SECURITY_DEEP_UNEXPECTED_PORTS" "$unexpected"
    output_result "SECURITY_DEEP_UNEXPECTED_PORTS_COUNT" "$unexpected_count"

    if [ "$unexpected_count" -gt 0 ]; then
        output_result "SECURITY_DEEP_UNEXPECTED_PORTS_COUNT_LEVEL" "警告"
    else
        output_result "SECURITY_DEEP_UNEXPECTED_PORTS_COUNT_LEVEL" "正常"
    fi
}

# ==================== 主检测流程 ====================

main() {
    log_info "开始安全合规深度检测..."

    set +e

    # 弱密码检测
    check_mysql_root_access 2>/dev/null || true
    check_mysql_empty_password 2>/dev/null || true
    check_redis_password 2>/dev/null || true
    check_emqx_default_password 2>/dev/null || true
    check_linux_empty_password 2>/dev/null || true

    # 安全基线扫描
    check_suid_files 2>/dev/null || true
    check_crontab 2>/dev/null || true
    check_ssh_bruteforce 2>/dev/null || true
    check_unexpected_ports 2>/dev/null || true

    log_info "安全合规深度检测完成"
}

main
