#!/bin/bash 
 
# 全宇宙最强SSH加固脚本 
# 功能：检测操作系统、依赖项，加固SSH服务器，下载公钥并输出详细日志 
 
# 日志文件 
LOG_FILE="/var/log/ssh_hardening_$(date +%Y%m%d_%H%M%S).log"
 
# 颜色定义 
RED='\e[0;31m'
GREEN='\e[0;32m'
YELLOW='\e[0;33m'
NC='\e[0m' # No Color 
 
# 日志函数 
log() {
    local level="$1"
    local message="$2"
    local timestamp=$(date "+%Y-%m-%d %H:%M:%S")
    
    echo -e "[$timestamp] [$level] $message" | tee -a "$LOG_FILE"
}
 
# 错误处理函数 
handle_error() {
    local step="$1"
    local message="$2"
    log "ERROR" "步骤 $step 失败: $message"
    echo -e "${RED}错误: $message${NC}"
    exit 1 
}
 
# 检测命令是否存在 
check_command() {
    if ! command -v "$1" >/dev/null 2>&1; then 
        return 1 
    fi 
    log "INFO" "找到 $1 命令: $(command -v "$1")"
    return 0 
}
 
# 获取完整操作系统名称 
get_full_os_name() {
    case "$OS" in 
        "rhel")
            echo "Red Hat Enterprise Linux $OS_VERSION"
            ;;
        "centos")
            echo "CentOS Linux $OS_VERSION"
            ;;
        "fedora")
            echo "Fedora Linux $OS_VERSION"
            ;;
        "almalinux")
            echo "AlmaLinux $OS_VERSION"
            ;;
        "rocky")
            echo "Rocky Linux $OS_VERSION"
            ;;
        "debian")
            echo "Debian GNU/Linux $OS_VERSION"
            ;;
        "ubuntu")
            echo "Ubuntu $OS_VERSION"
            ;;
        "linuxmint")
            echo "Linux Mint $OS_VERSION"
            ;;
        "arch")
            echo "Arch Linux"
            ;;
        "manjaro")
            echo "Manjaro Linux"
            ;;
        "alpine")
            echo "Alpine Linux $OS_VERSION"
            ;;
        "gentoo")
            echo "Gentoo Linux"
            ;;
        "sles")
            echo "SUSE Linux Enterprise Server $OS_VERSION"
            ;;
        "opensuse-leap")
            echo "openSUSE Leap $OS_VERSION"
            ;;
        "amazon")
            echo "Amazon Linux $OS_VERSION"
            ;;
        *)
            echo "$OS $OS_VERSION"
            ;;
    esac 
}
 
# 检测操作系统 
detect_os() {
    log "INFO" "开始检测操作系统..."
    
    if [ -f "/etc/os-release" ]; then 
        . /etc/os-release 
        OS="$ID"
        OS_VERSION="$VERSION_ID"
        
        # 特殊处理 
        if [ "$OS" = "ol" ]; then 
            OS="oracle"
        elif [ "$OS" = "amzn" ]; then 
            OS="amazon"
        fi 
        
        OS_FULL=$(get_full_os_name)
    elif [ -f "/etc/redhat-release" ]; then 
        OS="rhel"
        OS_VERSION=$(grep -oE '[0-9]+' /etc/redhat-release | head -1)
        OS_FULL="Red Hat Enterprise Linux $OS_VERSION"
    elif [ -f "/etc/debian_version" ]; then 
        OS="debian"
        OS_VERSION=$(cat /etc/debian_version)
        OS_FULL="Debian GNU/Linux $OS_VERSION"
    elif [ -f "/etc/arch-release" ]; then 
        OS="arch"
        OS_FULL="Arch Linux"
    elif [ -f "/etc/alpine-release" ]; then 
        OS="alpine"
        OS_VERSION=$(cat /etc/alpine-release)
        OS_FULL="Alpine Linux $OS_VERSION"
    else 
        handle_error "1" "无法检测操作系统类型"
    fi 
    
    log "INFO" "检测到操作系统: $OS_FULL"
    echo -e "${GREEN}检测到操作系统: $OS_FULL${NC}"
    
    # 确定服务管理系统类型 
    if [ "$OS" = "alpine" ]; then 
        SERVICE_MANAGER="openrc"
    elif check_command "systemctl"; then 
        SERVICE_MANAGER="systemd"
    elif check_command "service"; then 
        SERVICE_MANAGER="sysvinit"
    else 
        log "WARNING" "无法自动确定服务管理系统类型，尝试默认使用systemd"
        SERVICE_MANAGER="systemd"
    fi 
    
    log "INFO" "服务管理系统: $SERVICE_MANAGER"
}
 
# 安装依赖 
install_dependencies() {
    log "INFO" "开始安装依赖..."
    
    # 检测并安装curl 
    if ! check_command "curl"; then 
        log "INFO" "未找到curl命令，准备安装..."
        
        case "$OS" in 
            "debian" | "ubuntu" | "linuxmint" | "oracle")
                apt-get update || handle_error "2" "更新包索引失败"
                apt-get install -y curl || handle_error "2" "安装curl失败"
                ;;
            "rhel" | "centos" | "fedora" | "almalinux" | "rocky" | "amazon")
                dnf install -y curl || yum install -y curl || handle_error "2" "安装curl失败"
                ;;
            "arch" | "manjaro")
                pacman -Syu --noconfirm curl || handle_error "2" "安装curl失败"
                ;;
            "alpine")
                apk update || handle_error "2" "更新包索引失败"
                apk add curl || handle_error "2" "安装curl失败"
                ;;
            "gentoo")
                emerge -q net-misc/curl || handle_error "2" "安装curl失败"
                ;;
            "sles" | "opensuse-leap")
                zypper -n install curl || handle_error "2" "安装curl失败"
                ;;
            *)
                handle_error "2" "不支持的操作系统: $OS，无法安装curl"
                ;;
        esac 
        
        log "INFO" "curl安装成功"
    else 
        log "INFO" "curl已安装"
    fi 
    
    # 检测并安装OpenSSH服务器 
    SSHD_CONFIG="/etc/ssh/sshd_config"
    if [ ! -f "$SSHD_CONFIG" ]; then 
        log "INFO" "未找到sshd_config文件，开始安装OpenSSH服务器..."
        
        case "$OS" in 
            "debian" | "ubuntu" | "linuxmint" | "oracle")
                apt-get update || handle_error "2" "更新包索引失败"
                apt-get install -y openssh-server || handle_error "2" "安装OpenSSH服务器失败"
                ;;
            "rhel" | "centos" | "fedora" | "almalinux" | "rocky" | "amazon")
                dnf install -y openssh-server || yum install -y openssh-server || handle_error "2" "安装OpenSSH服务器失败"
                ;;
            "arch" | "manjaro")
                pacman -Syu --noconfirm openssh || handle_error "2" "安装OpenSSH服务器失败"
                ;;
            "alpine")
                apk update || handle_error "2" "更新包索引失败"
                apk add openssh-server || handle_error "2" "安装OpenSSH服务器失败"
                
                # 在Alpine上创建默认配置 
                if [ ! -f "$SSHD_CONFIG" ]; then 
                    cp /etc/ssh/sshd_config.sample   "$SSHD_CONFIG" || handle_error "2" "创建SSH配置文件失败"
                fi 
                ;;
            "gentoo")
                emerge -q net-misc/openssh || handle_error "2" "安装OpenSSH服务器失败"
                ;;
            "sles" | "opensuse-leap")
                zypper -n install openssh || handle_error "2" "安装OpenSSH服务器失败"
                ;;
            *)
                handle_error "2" "不支持的操作系统: $OS"
                ;;
        esac 
        
        # 再次检查配置文件是否存在 
        if [ ! -f "$SSHD_CONFIG" ]; then 
            handle_error "2" "安装OpenSSH服务器后仍未找到sshd_config文件"
        fi 
        
        log "INFO" "OpenSSH服务器安装成功"
    else 
        log "INFO" "找到sshd_config文件，OpenSSH服务器已安装"
    fi 
    
    log "INFO" "依赖检查完成"
    echo -e "${GREEN}依赖检查完成${NC}"
}
 
# 备份SSH配置 
backup_ssh_config() {
    local config_file="/etc/ssh/sshd_config"
    local backup_file="${config_file}.bak_$(date +%Y%m%d_%H%M%S)"
    
    log "INFO" "备份SSH配置文件到: $backup_file"
    
    if [ -f "$config_file" ]; then 
        cp "$config_file" "$backup_file" || handle_error "3" "备份SSH配置文件失败"
        log "INFO" "SSH配置文件备份成功"
    else 
        handle_error "3" "SSH配置文件不存在: $config_file"
    fi 
}
 
# 下载公钥 
download_public_key() {
    local key_url="https://xiwaer.com/onekey/cnfug.keys"  
    local authorized_keys="/root/.ssh/authorized_keys"
    local ssh_dir="/root/.ssh"
    
    log "INFO" "开始下载公钥文件: $key_url"
    
    # 创建.ssh目录（如果不存在）
    if [ ! -d "$ssh_dir" ]; then 
        mkdir -p "$ssh_dir"
        chmod 700 "$ssh_dir"
        chown root:root "$ssh_dir"
        log "INFO" "创建.ssh目录，权限设置为700"
    else 
        # 确保.ssh目录权限正确 
        if [ "$(stat -c %a "$ssh_dir")" != "700" ]; then 
            chmod 700 "$ssh_dir"
            log "INFO" "修正.ssh目录权限为700"
        fi 
        log "INFO" ".ssh目录已存在，权限正确"
    fi 
    
    # 下载公钥 
    if ! curl -sSL -o "$authorized_keys" "$key_url"; then 
        handle_error "4" "下载公钥文件失败，可能是网络问题或URL无效"
    fi 
    
    # 确保公钥文件权限正确 
    chmod 600 "$authorized_keys"
    chown root:root "$authorized_keys"
    log "INFO" "设置authorized_keys权限为600"
    
    # 显示公钥文件信息 
    log "INFO" "公钥文件内容:"
    while read -r line; do 
        log "INFO" "  $line"
    done < "$authorized_keys"
    
    log "INFO" "公钥文件下载并配置成功"
    echo -e "${GREEN}公钥文件下载并配置成功${NC}"
}
# 强制配置PAM安全设置 
disable_pam_password_auth() {
    local sshd_config="/etc/ssh/sshd_config"
    local pam_dir="/etc/pam.d"
    local pam_sshd_file="$pam_dir/sshd"
    
    log "INFO" "开始PAM配置安全检查..."
    
    # 1. 检查sshd_config中是否启用了PAM 
    if ! grep -q "^UsePAM yes" "$sshd_config"; then
        log "INFO" "检测到sshd_config未启用PAM(UsePAM no)，跳过PAM配置"
        return 0 
    fi
    
    # 2. 检查PAM目录是否存在
    if [ ! -d "$pam_dir" ]; then
        log "WARNING" "未找到PAM配置目录 $pam_dir，跳过PAM配置"
        return 0
    fi 
    
    # 3. 排除Alpine系统
    if grep -qi "alpine" /etc/os-release; then
        log "INFO" "检测到Alpine系统，按约定跳过PAM配置"
        return 0 
    fi
    
    log "INFO" "开始写入严格PAM策略..."
    cat > "$pam_sshd_file" <<-'EOF'
# SSH 公钥认证专用PAM配置 
# 认证阶段
auth       required     pam_env.so 
auth       sufficient   pam_publickey.so 
auth       required     pam_deny.so 
 
# 账户阶段 
account    required     pam_nologin.so  
account    required     pam_unix.so 
 
# 会话阶段 
session    required     pam_unix.so 
session    required     pam_loginuid.so 
EOF
    
    # 设置文件权限 
    chmod 644 "$pam_sshd_file"
    chown root:root "$pam_sshd_file"
    
    log "INFO" "PAM安全策略配置完成"
    echo -e "${GREEN}PAM安全策略已配置完成${NC}"
    
    # 返回SSH配置检查结果
    if grep -q "^UsePAM yes" "$sshd_config"; then
        log "INFO" "验证通过：sshd_config中UsePAM=yes"
    else
        log "WARNING" "注意：sshd_config中UsePAM未启用，PAM配置可能不生效"
    fi 
}
# 加固SSH配置 
harden_ssh_config() {
    local config_file="/etc/ssh/sshd_config"
    
    log "INFO" "开始加固SSH配置..."
    
    # 禁用root密码登录，允许 root 使用公钥登录 
    sed -i 's/^#\?PermitRootLogin.*/PermitRootLogin prohibit-password/' "$config_file"
    # 禁用密码登录 
    sed -i 's/^[# ]*UsePAM.*/UsePAM yes/' "$config_file"
    ## 禁用密码认证 
    sed -i 's/^#\?PasswordAuthentication.*/PasswordAuthentication no/' "$config_file"
    log "INFO" "已禁用密码认证，仅允许密钥认证"
    
    # 禁用空密码 
    sed -i 's/^#\?PermitEmptyPasswords.*/PermitEmptyPasswords no/' "$config_file"
    log "INFO" "已禁用空密码"
    
    # 禁用X11转发 
    sed -i 's/^#\?X11Forwarding.*/X11Forwarding no/' "$config_file"
    log "INFO" "已禁用X11转发"
    
    # 确保启用了公钥认证 
    if ! grep -q "^PubkeyAuthentication" "$config_file"; then 
        echo "PubkeyAuthentication yes" >> "$config_file"
        log "INFO" "已启用公钥认证"
    else 
        sed -i 's/^#\?PubkeyAuthentication.*/PubkeyAuthentication yes/' "$config_file"
        log "INFO" "已确保公钥认证已启用"
    fi 
    
    # 指定公钥文件位置 
    if ! grep -q "^AuthorizedKeysFile" "$config_file"; then 
        echo "AuthorizedKeysFile .ssh/authorized_keys" >> "$config_file"
        log "INFO" "已设置公钥文件位置"
    fi 
    
    # 检测并禁用PAM中的密码认证 
    disable_pam_password_auth 
    
    log "INFO" "SSH配置加固完成"
    echo -e "${GREEN}SSH配置加固完成${NC}"
}
 
# 管理服务函数 
manage_service() {
    local action="$1"
    local service_name="sshd"
    
    case "$SERVICE_MANAGER" in 
        "systemd")
            log "INFO" "使用systemctl $action $service_name"
            systemctl "$action" "$service_name" || return 1 
            ;;
        "openrc")
            log "INFO" "使用rc-service $action $service_name"
            rc-service "$service_name" "$action" || return 1 
            
            # 对于enable操作，使用rc-update 
            if [ "$action" = "enable" ]; then 
                log "INFO" "使用rc-update add $service_name"
                rc-update add "$service_name" || return 1 
            fi 
            ;;
        "sysvinit")
            log "INFO" "使用service $service_name $action"
            service "$service_name" "$action" || return 1 
            
            # 对于enable操作，尝试使用chkconfig或update-rc.d 
            if [ "$action" = "enable" ]; then 
                if check_command "chkconfig"; then 
                    log "INFO" "使用chkconfig启用$service_name"
                    chkconfig "$service_name" on || return 1 
                elif check_command "update-rc.d"; then 
                    log "INFO" "使用update-rc.d启用$service_name"
                    update-rc.d "$service_name" enable || return 1 
                else 
                    log "WARNING" "无法确定如何启用$service_name服务，可能需要手动配置"
                fi 
            fi 
            ;;
        *)
            log "ERROR" "不支持的服务管理系统: $SERVICE_MANAGER"
            return 1 
            ;;
    esac 
    
    return 0 
}
 
# 重启SSH服务 
restart_ssh_service() {
    log "INFO" "准备重启SSH服务..."
    
    # 停止服务 
    if ! manage_service "stop"; then 
        handle_error "6" "停止SSH服务失败"
    fi 
    
    # 启动服务 
    if ! manage_service "start"; then 
        handle_error "6" "启动SSH服务失败"
    fi 
    
    # 启用服务开机自启 
    if ! manage_service "enable"; then 
        log "WARNING" "启用SSH服务开机自启失败，可能需要手动配置"
    else 
        log "INFO" "SSH服务已设置为开机自启"
    fi 
    
    # 检查服务状态 
    log "INFO" "检查SSH服务状态..."
    if [ "$SERVICE_MANAGER" = "systemd" ]; then 
        if ! systemctl is-active --quiet sshd; then 
            handle_error "6" "SSH服务未成功启动"
        fi 
        log "INFO" "SSH服务状态: $(systemctl is-active sshd)"
    elif [ "$SERVICE_MANAGER" = "openrc" ]; then 
        if ! rc-service sshd status | grep -q "started"; then 
            handle_error "6" "SSH服务未成功启动"
        fi 
        log "INFO" "SSH服务状态: running"
    else 
        log "INFO" "无法自动验证服务状态，请手动检查"
    fi 
    
    log "INFO" "SSH服务重启完成"
    echo -e "${GREEN}SSH服务重启完成${NC}"
    
    # 显示SSH配置验证信息 
    log "INFO" "验证SSH配置:"
    if check_command "sshd"; then 
        sshd -t 2>&1 | while read -r line; do 
            log "INFO" "  $line"
        done 
        if [ $? -eq 0 ]; then 
            log "INFO" "SSH配置验证通过"
        else 
            log "WARNING" "SSH配置验证失败，可能存在配置错误"
        fi 
    else 
        log "WARNING" "无法验证SSH配置，未找到sshd命令"
    fi 
}
 
# 显示最终配置 
display_final_config() {
    local config_file="/etc/ssh/sshd_config"
    local pam_sshd_file="/etc/pam.d/sshd"
    
    log "INFO" "显示最终SSH配置..."
    
    echo -e "${GREEN}\n===== 最终SSH配置 (/etc/ssh/sshd_config) ====${NC}"
    
    # 显示配置文件内容并添加中文注释 
    grep -v '^#\|^$' "$config_file" | while read -r line; do 
        case "$line" in 
            "PermitRootLogin no")
                echo -e "${GREEN}$line${NC}    # 禁用root用户直接登录"
                ;;
            "PermitRootLogin yes")
                echo -e "${YELLOW}$line${NC}    # 在Alpine上允许root登录"
                ;;
            "PasswordAuthentication no")
                echo -e "${GREEN}$line${NC}    # 禁用密码认证，仅使用密钥认证"
                ;;
            "PermitEmptyPasswords no")
                echo -e "${GREEN}$line${NC}    # 禁用空密码"
                ;;
            "X11Forwarding no")
                echo -e "${GREEN}$line${NC}    # 禁用X11转发"
                ;;
            "PubkeyAuthentication yes")
                echo -e "${GREEN}$line${NC}    # 启用公钥认证"
                ;;
            "AuthorizedKeysFile .ssh/authorized_keys")
                echo -e "${GREEN}$line${NC}    # 指定公钥文件位置"
                ;;
            "UsePAM no")
                echo -e "${GREEN}$line${NC}    # 禁用PAM认证"
                ;;
            *)
                echo -e "${GREEN}$line${NC}"
                ;;
        esac 
    done 
    
    echo -e "${GREEN}=========================================${NC}"
    
    # 如果存在PAM配置，显示相关配置 
    if [ -f "$pam_sshd_file" ]; then 
        echo -e "${GREEN}\n===== PAM SSH配置 (/etc/pam.d/sshd) ====${NC}"
        grep -v '^#\|^$' "$pam_sshd_file" | while read -r line; do 
            case "$line" in 
                *"pam_unix.so"*) 
                    echo -e "${GREEN}$line${NC}    # PAM认证模块配置"
                    ;;
                *)
                    echo -e "${GREEN}$line${NC}"
                    ;;
            esac 
        done 
        echo -e "${GREEN}=========================================${NC}"
    fi 
    
    # 显示关键目录和文件权限 
    echo -e "${GREEN}\n===== SSH相关目录和文件权限 ====${NC}"
    echo -e "${GREEN}/root/.ssh 目录权限: $(stat -c %a /root/.ssh)${NC}"
    if [ -f "/root/.ssh/authorized_keys" ]; then 
        echo -e "${GREEN}/root/.ssh/authorized_keys 文件权限: $(stat -c %a /root/.ssh/authorized_keys)${NC}"
    else 
        echo -e "${RED}/root/.ssh/authorized_keys 文件不存在${NC}"
    fi 
    echo -e "${GREEN}/etc/ssh/sshd_config 文件权限: $(stat -c %a /etc/ssh/sshd_config)${NC}"
    if [ -f "/etc/pam.d/sshd" ]; then 
        echo -e "${GREEN}/etc/pam.d/sshd 文件权限: $(stat -c %a /etc/pam.d/sshd)${NC}"
    fi 
    echo -e "${GREEN}=========================================${NC}"
}
 
# 主函数 
main() {
    echo -e "${GREEN}===== SSH加固脚本开始执行 ====${NC}"
    log "INFO" "===== SSH加固脚本开始执行 ====" 
    
    # 检查是否为root用户 
    if [ "$(id -u)" -ne 0 ]; then 
        handle_error "0" "请使用root用户运行此脚本"
    fi 
    
    # 步骤1: 检测操作系统 
    detect_os 
    
    # 步骤2: 安装依赖 
    install_dependencies 
    
    # 步骤3: 备份SSH配置 
    backup_ssh_config 
    
    # 步骤4: 下载公钥 
    download_public_key 
    
    # 步骤5: 加固SSH配置 
    harden_ssh_config 
    
    # 步骤6: 重启SSH服务 
    restart_ssh_service 
    
    # 步骤7: 显示最终配置 
    display_final_config 
    
    log "INFO" "===== SSH加固脚本执行完成 ====" 
    echo -e "${GREEN}===== SSH加固脚本执行完成 ====${NC}"
    echo -e "${GREEN}详细日志已保存到: $LOG_FILE${NC}"
}
 
# 执行主函数 
main 