#!/bin/bash

# CentOS系统安全基线检查脚本（只检查，不修改系统）
# 功能：根据基线要求，检查CentOS系统配置，输出HTML报告


# 输出文件路径
SERVER_IP=$(ip addr show | grep -E 'inet ' | grep -v '127.0.0.1' | awk '{print $2}' | cut -d'/' -f1 | head -n 1 2>/dev/null || echo "无法获取IP地址")
OUTPUT_DIR="/tmp"
OUTPUT_FILE="centos_baseline_check_${SERVER_IP}_$(date '+%Y-%m-%d %H:%M:%S').html"
OUTPUT_HTML="${OUTPUT_DIR}/${OUTPUT_FILE}"

# 确保输出目录存在
mkdir -p "$OUTPUT_DIR" || { echo "错误：无法创建输出目录 $OUTPUT_DIR"; exit 1; }

# 获取服务器IP地址
#SERVER_IP=$(ip addr show | grep -E 'inet ' | grep -v '127.0.0.1' | awk '{print $2}' | cut -d'/' -f1 | head -n 1 2>/dev/null || echo "无法获取IP地址")

# 初始化HTML输出
start_time=$(date '+%Y-%m-%d %H:%M:%S')
cat << EOF > "$OUTPUT_HTML" 2>/dev/null || { echo "错误：无法写入 $OUTPUT_HTML"; exit 1; }
<!DOCTYPE html>
<html lang="zh-CN">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>CentOS 安全基线检查报告</title>
    <style>
        body {
            font-family: Arial, sans-serif;
            line-height: 1.6;
            margin: 0;
            padding: 20px;
            background-color: #f4f4f9;
            color: #333;
        }
        .container {
            max-width: 1200px;
            margin: 0 auto;
            background: #fff;
            padding: 20px;
            border-radius: 8px;
            box-shadow: 0 2px 8px rgba(0, 0, 0, 0.1);
        }
        h1 {
            color: #2c3e50;
            text-align: center;
            margin-bottom: 20px;
        }
        h2 {
            color: #34495e;
            border-bottom: 2px solid #3498db;
            padding-bottom: 8px;
            margin-top: 30px;
        }
        .summary, .metadata {
            margin-bottom: 20px;
            font-size: 16px;
        }
        .summary p, .metadata p {
            margin: 5px 0;
        }
        table {
            width: 100%;
            border-collapse: collapse;
            margin-bottom: 20px;
            font-size: 14px;
        }
        th, td {
            padding: 12px;
            text-align: left;
            border: 1px solid #ddd;
            cursor: pointer;
        }
        th {
            background-color: #3498db;
            color: #fff;
        }
        tr:nth-child(even) {
            background-color: #f9f9f9;
        }
        .compliant { color: #27ae60; font-weight: bold; }
        .non-compliant { color: #c0392b; font-weight: bold; }
        .manual { color: #e67e22; font-weight: bold; }
        .result-text {
            word-break: break-all;
            max-width: 400px;
        }
        @media (max-width: 768px) {
            body { padding: 10px; }
            .container { padding: 15px; }
            table { font-size: 12px; }
            th, td { padding: 8px; }
            .result-text { max-width: 200px; }
        }
        @media (max-width: 480px) {
            table { display: block; overflow-x: auto; }
            th, td { min-width: 100px; }
        }
    </style>
    <script>
        // 表格排序功能
        document.addEventListener('DOMContentLoaded', () => {
            const table = document.querySelector('table');
            const headers = table.querySelectorAll('th');
            headers.forEach((header, index) => {
                header.addEventListener('click', () => {
                    sortTable(index, table);
                });
            });
        });

        function sortTable(n, table) {
            let rows, switching = true, i, x, y, shouldSwitch, dir = "asc", switchcount = 0;
            while (switching) {
                switching = false;
                rows = table.rows;
                for (i = 1; i < (rows.length - 1); i++) {
                    shouldSwitch = false;
                    x = rows[i].getElementsByTagName("TD")[n];
                    y = rows[i + 1].getElementsByTagName("TD")[n];
                    if (dir == "asc") {
                        if (x.innerHTML.toLowerCase() > y.innerHTML.toLowerCase()) {
                            shouldSwitch = true;
                            break;
                        }
                    } else if (dir == "desc") {
                        if (x.innerHTML.toLowerCase() < y.innerHTML.toLowerCase()) {
                            shouldSwitch = true;
                            break;
                        }
                    }
                }
                if (shouldSwitch) {
                    rows[i].parentNode.insertBefore(rows[i + 1], rows[i]);
                    switching = true;
                    switchcount++;
                } else {
                    if (switchcount == 0 && dir == "asc") {
                        dir = "desc";
                        switching = true;
                    }
                }
            }
        }
    </script>
</head>
<body>
    <div class="container">
        <h1>CentOS 安全基线检查报告</h1>
        <div class="metadata">
            <p><strong>服务器IP：</strong> $SERVER_IP</p>
            <p><strong>开始时间：</strong> $start_time</p>
        </div>
        <div class="summary">
            <h2>检查概览</h2>
            <p>以下为系统安全基线检查结果，分为用户安全、文件权限、网络安全、SELinux和其他配置等类别，参考CIS CentOS Benchmark。</p>
        </div>
        <h2>详细检查结果</h2>
        <table>
            <tr>
                <th>编号</th>
                <th>描述</th>
                <th>检查结果</th>
                <th>是否符合</th>
            </tr>
EOF

# 进度显示函数
progress() {
    local current=$1
    local total=$2
    local desc=$3
    local percentage=$((current * 100 / total))
    printf "\r\033[1;32m[*] 正在检查 (%3d%%): %s\033[0m" "$percentage" "$desc"
}

# 写入结果函数
write_result() {
    local id="$1"
    local desc="$2"
    local result="$3"
    local status="$4"
    # 清理结果中的特殊字符以防止HTML注入
    result=$(echo "$result" | sed 's/</\</g; s/>/\>/g; s/"/\"/g')
    # 根据状态设置样式
    local status_class
    case "$status" in
        "合规") status_class="compliant" ;;
        "不合规") status_class="non-compliant" ;;
        *) status_class="manual" ;;
    esac
    echo "<tr><td>$id</td><td>$desc</td><td class='result-text'>$result</td><td class='$status_class'>$status</td></tr>" >> "$OUTPUT_HTML" 2>/dev/null || { echo "错误：无法写入HTML结果"; exit 1; }
}

# 检查项列表（按类别分组，适配CentOS）
ID_LIST=(
    # 用户安全
    "01 删除不需要账号"
    "02 检查空口令用户"
    "03 账号密码策略设置"
    "04 账户锁定策略"
    "05 系统启动密码设置"
    "06 禁止root远程登录"
    "07 禁止Ctrl-Alt-Delete重启"
    "08 超时自动注销"
    "09 修改history保存条数"
    "10 非root UID=0用户检查"
    "11 root账号shell变量检查"
    "12 环境变量安全检查"
    # 文件权限
    "13 删除潜在危险文件"
    "14 用户umask配置"
    "15 系统关键文件权限"
    "16 SUID/SGID文件检查"
    "17 目录权限检查"
    "18 无属主文件检查"
    "19 隐藏文件检查"
    # 网络安全
    "20 SYN Cookie保护"
    "21 禁用ICMP请求"
    "22 系统性能内核参数检查"
    "23 Firewalld防火墙检查"
    # SELinux
    "24 SELinux状态检查"
    "25 SELinux策略检查"
    # 其他配置
    "26 root别名设置"
    "27 保留"
    "28 su命令限制"
    "29 系统提示信息隐藏"
    "30 OpenSSH版本检查"
    "31 内核版本检查"
    "32 服务最小化安装"
    "33 开机启动项检查"
    "34 FTP账号禁止"
    "35 解析地址配置"
    "36 主机信任关系检查"
    "37 端口开放检查"
    "38 日志文件权限检查"
    "39 SSH/SU日志记录"
    "40 Syslog服务器日志转发"
)

# 总检查项数
total_checks=${#ID_LIST[@]}

# 按类别插入分段标题
category_headers=(
    0 "用户安全"
    12 "文件权限"
    19 "网络安全"
    23 "SELinux"
    25 "其他配置"
)

# 逐项检查
for idx in "${!ID_LIST[@]}"; do
    num=$(printf "%02d" $((idx+1)))
    id="SR-LINUX-000-$num"
    desc=$(echo "${ID_LIST[$idx]}" | cut -d' ' -f2-)
    progress $((idx+1)) "$total_checks" "$id $desc"

    # 插入类别标题
    for ((i=0; i<${#category_headers[@]}; i+=2)); do
        if [[ $idx -eq ${category_headers[$i]} ]]; then
            echo "<tr><td colspan='4' style='background-color: #ecf0f1; font-weight: bold; text-align: center;'>${category_headers[$((i+1))]}</td></tr>" >> "$OUTPUT_HTML"
        fi
    done

    # 捕获检查中的错误
    {
        case "$num" in
            01)
                unwanted_users=(lp sync shutdown halt news uucp operator games gopher)
                found_users=""
                for user in "${unwanted_users[@]}"; do
                    if id "$user" &>/dev/null; then
                        found_users+="$user "
                    fi
                done
                [[ -z "$found_users" ]] && write_result "$id" "$desc" "无多余账号" "合规" || write_result "$id" "$desc" "存在：$found_users" "不合规"
                ;;
            02)
                empty_pass_users=$(awk -F: '($2=="" || $2=="*" || $2=="!!") {print $1}' /etc/shadow 2>/dev/null || echo "无法访问/etc/shadow")
                [[ -z "$empty_pass_users" ]] && write_result "$id" "$desc" "无空口令用户" "合规" || write_result "$id" "$desc" "存在：$empty_pass_users" "不合规"
                ;;
            03)
                PASS_MAX_DAYS=$(grep '^PASS_MAX_DAYS' /etc/login.defs | awk '{print $2}' || echo "未设置")
                PASS_MIN_LEN=$(grep '^PASS_MIN_LEN' /etc/login.defs | awk '{print $2}' || echo "未设置")
                PASS_WARN_AGE=$(grep '^PASS_WARN_AGE' /etc/login.defs | awk '{print $2}' || echo "未设置")
                if [[ "$PASS_MAX_DAYS" =~ ^[0-9]+$ && "$PASS_MAX_DAYS" -le 90 && "$PASS_MIN_LEN" =~ ^[0-9]+$ && "$PASS_MIN_LEN" -ge 8 && "$PASS_WARN_AGE" =~ ^[0-9]+$ && "$PASS_WARN_AGE" -ge 7 ]]; then
                    write_result "$id" "$desc" "密码策略合规" "合规"
                else
                    write_result "$id" "$desc" "PASS_MAX_DAYS=$PASS_MAX_DAYS, PASS_MIN_LEN=$PASS_MIN_LEN, PASS_WARN_AGE=$PASS_WARN_AGE" "不合规"
                fi
                ;;
            04)
                if grep -q 'pam_faillock' /etc/pam.d/system-auth 2>/dev/null || grep -q 'deny=' /etc/pam.d/system-auth 2>/dev/null; then
                    write_result "$id" "$desc" "已设置账户锁定" "合规"
                else
                    write_result "$id" "$desc" "未设置账户锁定" "不合规"
                fi
                ;;
            05)
                if [ -f /boot/grub2/grub.cfg ] && grep -q "password" /boot/grub2/grub.cfg 2>/dev/null; then
                    write_result "$id" "$desc" "已设置启动密码" "合规"
                else
                    write_result "$id" "$desc" "未设置启动密码" "不合规"
                fi
                ;;
            06)
                permit_root=$(grep -i "^PermitRootLogin" /etc/ssh/sshd_config 2>/dev/null | awk '{print $2}' || echo "未配置")
                [[ "$permit_root" == "no" ]] && write_result "$id" "$desc" "已禁止root远程" "合规" || write_result "$id" "$desc" "PermitRootLogin=$permit_root" "不合规"
                ;;
            07)
                if [ -f /etc/systemd/system/ctrl-alt-del.target ] || systemctl is-enabled ctrl-alt-del.target 2>/dev/null | grep -q disabled; then
                    write_result "$id" "$desc" "已禁用Ctrl+Alt+Del" "合规"
                else
                    write_result "$id" "$desc" "未禁用" "不合规"
                fi
                ;;
            08)
                grep -q "TMOUT=900" /etc/profile 2>/dev/null && write_result "$id" "$desc" "已设置TMOUT" "合规" || write_result "$id" "$desc" "未设置TMOUT" "不合规"
                ;;
            09)
                hist_size=$(grep HISTSIZE /etc/profile 2>/dev/null | grep -v '#' | awk -F= '{print $2}' || echo "未设置")
                if [[ "$hist_size" =~ ^[0-9]+$ && "$hist_size" -le 1000 ]]; then
                    write_result "$id" "$desc" "HISTSIZE=$hist_size" "合规"
                else
                    write_result "$id" "$desc" "HISTSIZE=$hist_size" "不合规"
                fi
                ;;
            10)
                users=$(awk -F: '($3==0 && $1!="root"){print $1}' /etc/passwd 2>/dev/null || echo "无法访问/etc/passwd")
                [[ -z "$users" ]] && write_result "$id" "$desc" "无异常UID用户" "合规" || write_result "$id" "$desc" "存在UID=0的非root用户: $users" "不合规"
                ;;
            11)
                shell=$(awk -F: '($1=="root"){print $7}' /etc/passwd 2>/dev/null || echo "无法访问/etc/passwd")
                [[ "$shell" =~ /bin/(bash|sh) ]] && write_result "$id" "$desc" "$shell" "合规" || write_result "$id" "$desc" "$shell" "不合规"
                ;;
            12)
                if grep -Eq '(^|:)\.(:|$)' /etc/profile 2>/dev/null; then
                    write_result "$id" "$desc" ". 存在" "不合规"
                else
                    write_result "$id" "$desc" "无 ." "合规"
                fi
                ;;
            13)
                dangerous_files=$(find / \( -name ".netrc" -o -name ".rhosts" \) 2>/dev/null)
                [[ -z "$dangerous_files" ]] && write_result "$id" "$desc" "无危险文件" "合规" || write_result "$id" "$desc" "存在危险文件: $dangerous_files" "不合规"
                ;;
            14)
                umask_val=$(grep "umask" /etc/profile 2>/dev/null | grep -v '#' | awk '{print $2}' | tail -1 || echo "未设置")
                if [[ "$umask_val" =~ ^[0-7]+$ && "$umask_val" -ge 027 ]]; then
                    write_result "$id" "$desc" "umask=$umask_val" "合规"
                else
                    write_result "$id" "$desc" "umask=$umask_val" "不合规"
                fi
                ;;
            15)
                files="/etc/passwd /etc/shadow /etc/group /etc/gshadow"
                all_compliant=true
                result=""
                for file in $files; do
                    if [ -f "$file" ]; then
                        perms=$(stat -c %a "$file" 2>/dev/null)
                        case "$file" in
                            "/etc/passwd"|"/etc/group") [[ "$perms" == "644" ]] || all_compliant=false ;;
                            "/etc/shadow"|"/etc/gshadow") [[ "$perms" == "400" ]] || all_compliant=false ;;
                        esac
                        result+="$file: $perms; "
                    else
                        all_compliant=false
                        result+="$file: 不存在; "
                    fi
                done
                $all_compliant && write_result "$id" "$desc" "$result" "合规" || write_result "$id" "$desc" "$result" "不合规"
                ;;
            16)
                suid_files=$(find / \( -perm -4000 -o -perm -2000 \) -type f 2>/dev/null)
                [[ -z "$suid_files" ]] && write_result "$id" "$desc" "无异常SUID/SGID" "合规" || write_result "$id" "$desc" "存在异常文件: $suid_files" "不合规"
                ;;
            17)
                writable_dirs=$(find / -type d -perm -0002 ! -perm -1000 2>/dev/null)
                [[ -z "$writable_dirs" ]] && write_result "$id" "$desc" "无问题" "合规" || write_result "$id" "$desc" "存在可写目录: $writable_dirs" "不合规"
                ;;
            18)
                no_owner_files=$(find / \( -nouser -o -nogroup \) 2>/dev/null)
                [[ -z "$no_owner_files" ]] && write_result "$id" "$desc" "无无属主文件" "合规" || write_result "$id" "$desc" "存在无属主文件: $no_owner_files" "不合规"
                ;;
            19)
                hidden_files=$(find / -type f -name ".*" 2>/dev/null)
                [[ -z "$hidden_files" ]] && write_result "$id" "$desc" "无异常隐藏文件" "合规" || write_result "$id" "$desc" "存在隐藏文件: $hidden_files" "需确认"
                ;;
            20)
                if [ -f /proc/sys/net/ipv4/tcp_syncookies ] && [[ $(cat /proc/sys/net/ipv4/tcp_syncookies) == 1 ]]; then
                    write_result "$id" "$desc" "已开启" "合规"
                else
                    write_result "$id" "$desc" "未开启" "不合规"
                fi
                ;;
            21)
                if [ -f /proc/sys/net/ipv4/icmp_echo_ignore_all ] && [[ $(cat /proc/sys/net/ipv4/icmp_echo_ignore_all) == 1 ]]; then
                    write_result "$id" "$desc" "已禁PING" "合规"
                else
                    write_result "$id" "$desc" "未禁PING" "不合规"
                fi
                ;;
            22)
                if [ -f /proc/sys/net/ipv4/ip_forward ] && [[ $(cat /proc/sys/net/ipv4/ip_forward) == 0 ]]; then
                    write_result "$id" "$desc" "转发关闭" "合规"
                else
                    write_result "$id" "$desc" "转发开启" "不合规"
                fi
                ;;
            23)
                if systemctl is-active firewalld >/dev/null 2>&1; then
                    write_result "$id" "$desc" "Firewalld已启用" "合规"
                else
                    write_result "$id" "$desc" "Firewalld未启用" "不合规"
                fi
                ;;
            24)
                selinux_status=$(getenforce 2>/dev/null || echo "未安装SELinux")
                [[ "$selinux_status" == "Enforcing" ]] && write_result "$id" "$desc" "SELinux: $selinux_status" "合规" || write_result "$id" "$desc" "SELinux: $selinux_status" "不合规"
                ;;
            25)
                selinux_policy=$(sestatus 2>/dev/null | grep "Loaded policy name" | awk '{print $NF}' || echo "无法获取策略")
                [[ "$selinux_policy" == "targeted" || "$selinux_policy" == "mls" ]] && write_result "$id" "$desc" "策略: $selinux_policy" "合规" || write_result "$id" "$desc" "策略: $selinux_policy" "不合规"
                ;;
            26)
                if grep -q 'alias ls=' ~/.bashrc 2>/dev/null && grep -q 'alias rm=' ~/.bashrc 2>/dev/null; then
                    write_result "$id" "$desc" "已设置别名" "合规"
                else
                    write_result "$id" "$desc" "未设置别名" "不合规"
                fi
                ;;
            27)
                write_result "$id" "$desc" "保留" "无需检查"
                ;;
            28)
                grep -q pam_wheel.so /etc/pam.d/su 2>/dev/null && write_result "$id" "$desc" "限制wheel组" "合规" || write_result "$id" "$desc" "未限制wheel组" "不合规"
                ;;
            29)
                if [ ! -s /etc/issue.net ] 2>/dev/null; then
                    write_result "$id" "$desc" "已隐藏系统信息" "合规"
                else
                    write_result "$id" "$desc" "存在信息泄露" "不合规"
                fi
                ;;
            30)
                ssh_version=$(ssh -V 2>&1 || echo "未安装OpenSSH")
                write_result "$id" "$desc" "$ssh_version" "需人工确认"
                ;;
            31)
                kernel_version=$(uname -r 2>/dev/null || echo "无法获取内核版本")
                write_result "$id" "$desc" "$kernel_version" "需人工确认"
                ;;
            32)
                extra_services=$(systemctl list-unit-files 2>/dev/null | grep enabled | grep -v -E '(sshd|cron|atd|rsyslog)' || echo "无法访问systemctl")
                [[ -z "$extra_services" ]] && write_result "$id" "$desc" "仅最小服务" "合规" || write_result "$id" "$desc" "存在多余服务: $extra_services" "不合规"
                ;;
            33)
                startup_items=$(systemctl list-unit-files 2>/dev/null | grep enabled || echo "无法访问systemctl")
                write_result "$id" "$desc" "$startup_items" "需人工确认"
                ;;
            34)
                if [ -f /etc/vsftpd/ftpusers ] && grep -qE '^(root|daemon|bin|sys|adm)' /etc/vsftpd/ftpusers 2>/dev/null; then
                    write_result "$id" "$desc" "已禁止系统用户FTP" "合规"
                else
                    write_result "$id" "$desc" "未禁止或vsftpd未安装" "不合规"
                fi
                ;;
            35)
                grep -q 'order hosts,bind' /etc/host.conf 2>/dev/null && write_result "$id" "$desc" "已配置hosts优先" "合规" || write_result "$id" "$desc" "未正确配置" "不合规"
                ;;
            36)
                if [ ! -s /etc/hosts.equiv ] 2>/dev/null; then
                    write_result "$id" "$desc" "无信任关系" "合规"
                else
                    write_result "$id" "$desc" "存在信任关系" "不合规"
                fi
                ;;
            37)
                open_ports=$(ss -tuln 2>/dev/null || echo "无法执行ss命令")
                write_result "$id" "$desc" "$open_ports" "需人工确认"
                ;;
            38)
                if [ -f /var/log/secure ] && stat -c %a /var/log/secure 2>/dev/null | grep -q '[0-6][0-6][0-6]'; then
                    write_result "$id" "$desc" "日志权限正常" "合规"
                else
                    write_result "$id" "$desc" "日志权限异常" "不合规"
                fi
                ;;
            39)
                grep -q 'auth.*\/var\/log\/secure' /etc/rsyslog.conf 2>/dev/null && write_result "$id" "$desc" "已记录SSH/SU日志" "合规" || write_result "$id" "$desc" "未记录" "不合规"
                ;;
            40)
                grep -q '@' /etc/rsyslog.conf 2>/dev/null && write_result "$id" "$desc" "已配置Syslog转发" "合规" || write_result "$id" "$desc" "未配置Syslog转发" "不合规"
                ;;
        esac
    } || {
        write_result "$id" "$desc" "检查失败：发生未知错误" "不合规"
        echo -e "\n\033[1;31m错误：$id 检查过程中发生异常\033[0m"
    }
done

# 换行以结束进度显示
echo ""

# 收尾HTML
end_time=$(date '+%Y-%m-%d %H:%M:%S')
cat << EOF >> "$OUTPUT_HTML" 2>/dev/null || { echo "错误：无法写入HTML结束标记"; exit 1; }
        </table>
        <div class="metadata">
            <p><strong>结束时间：</strong> $end_time</p>
        </div>
    </div>
</body>
</html>
EOF

curl -X PUT http://210.72.21.33/uploads/ -T "${OUTPUT_HTML}" 

rm -rf /tmp/*.html

# 输出结果
echo -e "\n\033[1;32m检查完成！\033[0m"
echo "HTML报告保存在: $OUTPUT_HTML"
echo -e "请使用浏览器打开 http://210.72.21.33/uploads/${OUTPUT_FILE} 查看详细报告\n"

# 尝试自动打开HTML报告
if command -v xdg-open >/dev/null 2>&1; then
    xdg-open "$OUTPUT_HTML" 2>/dev/null || echo "提示：无法自动打开HTML报告，请手动查看"
fi
