#!/bin/bash 
 
# SH加固脚本 
# 功能：检测操作系统、依赖项，加固SSH服务器，下载公钥并输出详细日志,支持全操作系统 
# 版本：1.0
 
# ========== 全局变量声明 ==========
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'
 
# 系统检测相关 
OS=""
OS_VERSION=""
OS_FULL=""
SERVICE_MANAGER=""
 
# 文件路径 
SSHD_CONFIG="/etc/ssh/sshd_config"
PAM_SSHD_FILE="/etc/pam.d/sshd"
AUTHORIZED_KEYS="/root/.ssh/authorized_keys"
SSH_DIR="/root/.ssh"
 
# 其他配置 
KEY_URL="https://github.com/xxxxxxxxx.keys" 
 
# ========== 函数定义 ==========
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 
}
 
# Alpine专用服务管理 
alpine_service() {
    local action="$1"
    local service="$2"
    
    case "$action" in 
        "start"|"stop"|"restart")
            /etc/init.d/$service $action >/dev/null 2>&1 
            ;;
        "enable")
            rc-update add $service default >/dev/null 2>&1 
            ;;
        "status")
            /etc/init.d/$service status >/dev/null 2>&1 
            ;;
        *)
            return 1 
            ;;
    esac 
}
 
get_full_os_name() {
    case "$OS" in 
        "alpine") echo "Alpine Linux $OS_VERSION" ;;
        *) echo "$OS $OS_VERSION" ;;
    esac 
}
 
detect_os() {
    log "INFO" "开始检测操作系统..."
    
    if [ -f "/etc/alpine-release" ]; then 
        OS="alpine"
        OS_VERSION=$(cat /etc/alpine-release)
        OS_FULL=$(get_full_os_name)
        SERVICE_MANAGER="openrc"
    elif [ -f "/etc/os-release" ]; then 
        . /etc/os-release 
        OS="$ID"
        OS_VERSION="$VERSION_ID"
        OS_FULL=$(get_full_os_name)
        
        if [ "$OS" = "alpine" ]; then 
            SERVICE_MANAGER="openrc"
        elif check_command "systemctl"; then 
            SERVICE_MANAGER="systemd"
        else 
            SERVICE_MANAGER="sysvinit"
        fi 
    else 
        handle_error "1" "无法检测操作系统类型"
    fi 
    
    log "INFO" "检测到操作系统: $OS_FULL"
    echo -e "${GREEN}检测到操作系统: $OS_FULL${NC}"
    log "INFO" "服务管理系统: $SERVICE_MANAGER"
}
 
install_dependencies() {
    log "INFO" "开始安装依赖..."
    
    if [ "$OS" = "alpine" ]; then 
        if ! check_command "curl"; then 
            log "INFO" "安装curl..."
            apk add --no-cache curl || handle_error "2" "安装curl失败"
        fi 
        
        if [ ! -f "$SSHD_CONFIG" ]; then 
            log "INFO" "安装OpenSSH服务器..."
            apk add --no-cache openssh-server || handle_error "2" "安装OpenSSH服务器失败"
            cp /etc/ssh/sshd_config.sample  "$SSHD_CONFIG"
        fi 
    else 
        if ! check_command "curl"; then 
            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失败"
                    ;;
                "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 
        fi 
        
        if [ ! -f "$SSHD_CONFIG" ]; then 
            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服务器失败"
                    ;;
                "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 
        fi 
    fi 
    
    log "INFO" "依赖检查完成"
    echo -e "${GREEN}依赖检查完成${NC}"
}
 
backup_ssh_config() {
    local backup_file="${SSHD_CONFIG}.bak_$(date +%Y%m%d_%H%M%S)"
    log "INFO" "备份SSH配置文件到: $backup_file"
    cp "$SSHD_CONFIG" "$backup_file" || handle_error "3" "备份SSH配置文件失败"
}
 
download_public_key() {
    log "INFO" "开始下载公钥文件: $KEY_URL"
    
    mkdir -p "$SSH_DIR"
    chmod 700 "$SSH_DIR"
    chown root:root "$SSH_DIR"
    
    curl -sSL -o "$AUTHORIZED_KEYS" "$KEY_URL" || handle_error "4" "下载公钥文件失败"
    chmod 600 "$AUTHORIZED_KEYS"
    chown root:root "$AUTHORIZED_KEYS"
    
    log "INFO" "公钥文件下载并配置成功"
    echo -e "${GREEN}公钥文件下载并配置成功${NC}"
}
 
# 修复后的PAM配置函数 
disable_pam_password_auth() {
    # 如果是Alpine系统则跳过 
    if [ "$OS" = "alpine" ]; then 
        log "INFO" "检测到Alpine系统，跳过PAM配置"
        return 0 
    fi 
    
    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 "/etc/pam.d" ]; then 
        log "WARNING" "未找到PAM配置目录/etc/pam.d，跳过PAM配置"
        return 0 
    fi 
    
    log "INFO" "开始写入严格PAM策略..."
    
    # 使用printf替代heredoc，避免语法问题 
    printf '%s\n' \
        "# 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"   \
        > "$PAM_SSHD_FILE"
    
    # 设置文件权限 
    if ! chmod 644 "$PAM_SSHD_FILE"; then 
        log "ERROR" "无法设置PAM文件权限"
        return 1 
    fi 
    
    if ! chown root:root "$PAM_SSHD_FILE"; then 
        log "ERROR" "无法设置PAM文件所有者"
        return 1 
    fi 
    
    log "INFO" "PAM安全策略配置完成"
    echo -e "${GREEN}PAM安全策略已配置完成${NC}"
    
    # 验证配置 
    if grep -q "^UsePAM yes" "$SSHD_CONFIG"; then 
        log "INFO" "验证通过：sshd_config中UsePAM=yes"
    else 
        log "WARNING" "注意：sshd_config中UsePAM未启用，PAM配置可能不生效"
    fi 
    
    return 0 
}
 
harden_ssh_config() {
    log "INFO" "开始加固SSH配置..."
    
    if [ "$OS" = "alpine" ]; then 
        # Alpine专用配置 
        sed -i -e 's/^#\?PermitRootLogin.*/PermitRootLogin yes/' \
               -e 's/^#\?PasswordAuthentication.*/PasswordAuthentication no/' \
               -e 's/^#\?PermitEmptyPasswords.*/PermitEmptyPasswords no/' \
               -e 's/^#\?X11Forwarding.*/X11Forwarding no/' \
               -e '/^#\?UsePAM/d' \
               -e 's/^#\?PubkeyAuthentication.*/PubkeyAuthentication yes/' \
               "$SSHD_CONFIG"
        
        # 添加Alpine推荐的安全配置 
        echo -e "\n# Alpine安全增强配置" >> "$SSHD_CONFIG"
        echo "Protocol 2" >> "$SSHD_CONFIG"
        echo "AllowTcpForwarding no" >> "$SSHD_CONFIG"
        echo "AllowAgentForwarding no" >> "$SSHD_CONFIG"
        echo "Compression no" >> "$SSHD_CONFIG"
        
        log "INFO" "Alpine系统专用配置已应用"
    else 
        # 标准加固配置 
        sed -i -e 's/^#\?PermitRootLogin.*/PermitRootLogin prohibit-password/' \
               -e 's/^#\?PasswordAuthentication.*/PasswordAuthentication no/' \
               -e 's/^#\?PermitEmptyPasswords.*/PermitEmptyPasswords no/' \
               -e 's/^#\?X11Forwarding.*/X11Forwarding no/' \
               -e 's/^[# ]*UsePAM.*/UsePAM yes/' \
               -e 's/^#\?PubkeyAuthentication.*/PubkeyAuthentication yes/' \
               "$SSHD_CONFIG"
        
        # 确保AuthorizedKeysFile设置 
        if ! grep -q "^AuthorizedKeysFile" "$SSHD_CONFIG"; then 
            echo "AuthorizedKeysFile .ssh/authorized_keys" >> "$SSHD_CONFIG"
        fi 
        
        # 只在非Alpine系统配置PAM 
        disable_pam_password_auth 
    fi 
    
    log "INFO" "SSH配置加固完成"
    echo -e "${GREEN}SSH配置加固完成${NC}"
}
 
restart_ssh_service() {
    log "INFO" "准备重启SSH服务..."
    
    if [ "$OS" = "alpine" ]; then 
        # Alpine专用服务管理 
        alpine_service stop sshd || log "WARNING" "停止SSH服务失败（可能未运行）"
        alpine_service start sshd || handle_error "6" "启动SSH服务失败"
        alpine_service enable sshd || log "WARNING" "启用开机自启失败，请手动执行: rc-update add sshd"
        
        if alpine_service status sshd; then 
            log "INFO" "SSH服务已成功启动"
        else 
            handle_error "6" "SSH服务未成功启动"
        fi 
    else 
        # 其他系统服务管理 
        local ssh_service_name="sshd"
        if [ "$OS" = "ubuntu" ] || [ "$OS" = "debian" ] || [ "$OS" = "linuxmint" ]; then 
            ssh_service_name="ssh"
        fi 
        
        if ! systemctl stop "$ssh_service_name" 2>/dev/null && ! service "$ssh_service_name" stop 2>/dev/null; then 
            log "WARNING" "停止SSH服务失败（可能未运行）"
        fi 
        
        if ! systemctl start "$ssh_service_name" 2>/dev/null && ! service "$ssh_service_name" start 2>/dev/null; then 
            handle_error "6" "启动SSH服务失败"
        fi 
        
        if ! systemctl enable "$ssh_service_name" 2>/dev/null && ! update-rc.d "$ssh_service_name" enable 2>/dev/null; then 
            log "WARNING" "启用开机自启失败"
        else 
            log "INFO" "SSH服务已设置为开机自启"
        fi 
        
        if systemctl is-active --quiet "$ssh_service_name" 2>/dev/null || service "$ssh_service_name" status >/dev/null 2>&1; then 
            log "INFO" "SSH服务已成功启动"
        else 
            handle_error "6" "SSH服务未成功启动"
        fi 
    fi 
    
    log "INFO" "SSH服务重启完成"
    echo -e "${GREEN}SSH服务重启完成${NC}"
}
 
display_final_config() {
    log "INFO" "显示最终SSH配置..."
    
    echo -e "${GREEN}\n===== 最终SSH配置 ($SSHD_CONFIG) ====${NC}"
    grep -v '^#\|^$' "$SSHD_CONFIG" | while read -r line; do 
        case "$line" in 
            "PermitRootLogin yes") echo -e "${GREEN}$line${NC}    # Alpine允许root登录" ;;
            "PasswordAuthentication no") echo -e "${GREEN}$line${NC}    # 禁用密码认证" ;;
            *) echo -e "${GREEN}$line${NC}" ;;
        esac 
    done 
    
    echo -e "${GREEN}\n===== 关键文件权限 ====${NC}"
    echo -e "${GREEN}$SSH_DIR 权限: $(stat -c %a "$SSH_DIR")${NC}"
    echo -e "${GREEN}$AUTHORIZED_KEYS 权限: $(stat -c %a "$AUTHORIZED_KEYS")${NC}"
    echo -e "${GREEN}$SSHD_CONFIG 权限: $(stat -c %a "$SSHD_CONFIG")${NC}"
}
 
main() {
    echo -e "${GREEN}===== SSH加固脚本开始执行 ====${NC}"
    log "INFO" "===== SSH加固脚本开始执行 ====" 
    
    [ "$(id -u)" -ne 0 ] && handle_error "0" "请使用root用户运行此脚本"
    
    detect_os 
    install_dependencies 
    backup_ssh_config 
    download_public_key 
    harden_ssh_config 
    restart_ssh_service 
    display_final_config 
    
    log "INFO" "===== SSH加固脚本执行完成 ====" 
    echo -e "${GREEN}===== SSH加固脚本执行完成 ====${NC}"
    echo -e "${GREEN}详细日志已保存到: $LOG_FILE${NC}"
    
    # Alpine特殊提示 
    if [ "$OS" = "alpine" ]; then 
        echo -e "${YELLOW}\n注意：Alpine系统需手动确保sshd在default运行级别"
        echo -e "如需验证，请执行: rc-update show default | grep sshd${NC}"
    fi 
}
 
main "$@"