arch_hardening_script.sh
· 37 KiB · Bash
Surowy
#!/bin/bash
################################################################################
# Arch Linux 服务器安全加固脚本
# 版本: v2.0
# 日期: 2025-01-19
#
# 针对Arch Linux优化的工具选择:
# - 防火墙: nftables (现代化,替代iptables) 或 firewalld (更易用)
# - 入侵防护: sshguard (轻量级,原生nftables支持) 或 crowdsec (社区驱动)
# - 包管理: pacman (Arch原生)
# - 安全工具: 优先使用官方仓库和AUR
################################################################################
export TERM=xterm-color
set +H
set +e
# 颜色定义
if command -v tput &> /dev/null; then
RED=$(tput setaf 1 2>/dev/null || echo '\033[0;31m')
GREEN=$(tput setaf 2 2>/dev/null || echo '\033[0;32m')
YELLOW=$(tput setaf 3 2>/dev/null || echo '\033[1;33m')
BLUE=$(tput setaf 4 2>/dev/null || echo '\033[0;34m')
CYAN=$(tput setaf 6 2>/dev/null || echo '\033[0;36m')
MAGENTA=$(tput setaf 5 2>/dev/null || echo '\033[0;35m')
NC=$(tput sgr0 2>/dev/null || echo '\033[0m')
else
RED='\033[0;31m'
GREEN='\033[0;32m'
YELLOW='\033[1;33m'
BLUE='\033[0;34m'
CYAN='\033[0;36m'
MAGENTA='\033[0;35m'
NC='\033[0m'
fi
# 日志和备份目录
LOG_FILE="/var/log/arch_hardening_$(date +%Y%m%d_%H%M%S).log"
BACKUP_DIR="/root/security_backup_$(date +%Y%m%d_%H%M%S)"
ROLLBACK_SCRIPT="$BACKUP_DIR/rollback.sh"
# 全局变量
FIREWALL_CHOICE=""
IPS_CHOICE=""
################################################################################
# 工具函数
################################################################################
log() {
echo -e "${GREEN}[$(date +'%Y-%m-%d %H:%M:%S')]${NC} $1" | tee -a "$LOG_FILE"
}
log_info() {
echo -e "${BLUE}[INFO]${NC} $1" | tee -a "$LOG_FILE"
}
log_warning() {
echo -e "${YELLOW}[WARNING]${NC} $1" | tee -a "$LOG_FILE"
}
log_error() {
echo -e "${RED}[ERROR]${NC} $1" | tee -a "$LOG_FILE"
}
log_success() {
echo -e "${GREEN}[SUCCESS]${NC} $1" | tee -a "$LOG_FILE"
}
log_critical() {
echo -e "${RED}[CRITICAL]${NC} $1" | tee -a "$LOG_FILE"
}
log_section() {
echo -e "\n${CYAN}========================================${NC}" | tee -a "$LOG_FILE"
echo -e "${CYAN}$1${NC}" | tee -a "$LOG_FILE"
echo -e "${CYAN}========================================${NC}\n" | tee -a "$LOG_FILE"
}
check_root() {
if [[ $EUID -ne 0 ]]; then
log_error "此脚本必须以root权限运行"
exit 1
fi
}
backup_file() {
local file=$1
if [[ ! -f "$file" ]]; then
log_warning "文件不存在,跳过备份: $file"
return 1
fi
mkdir -p "$BACKUP_DIR"
local backup_name="$(basename $file).$(date +%Y%m%d_%H%M%S).bak"
local backup_path="$BACKUP_DIR/$backup_name"
if cp -p "$file" "$backup_path" 2>/dev/null; then
if cmp -s "$file" "$backup_path"; then
log_success "✓ 备份成功: $file -> $backup_name"
echo "cp -p '$backup_path' '$file'" >> "$ROLLBACK_SCRIPT"
return 0
else
log_error "✗ 备份验证失败: $file"
rm -f "$backup_path"
return 1
fi
else
log_error "✗ 备份失败: $file"
return 1
fi
}
confirm_action() {
local message=$1
if [[ -t 0 ]]; then
echo -e "${YELLOW}${message} [y/N]: ${NC}\c"
read -n 1 -r
echo
if [[ ! $REPLY =~ ^[Yy]$ ]]; then
return 1
fi
return 0
else
log_info "非交互模式: $message - 跳过"
return 1
fi
}
safe_execute() {
local cmd="$1"
local description="$2"
local critical="${3:-false}"
log_info "执行: $description"
if eval "$cmd" >> "$LOG_FILE" 2>&1; then
log_success "✓ 成功: $description"
return 0
else
local exit_code=$?
if [[ "$critical" == "true" ]]; then
log_critical "✗ 关键操作失败: $description (退出码: $exit_code)"
log_critical "脚本终止,可使用回滚: bash $ROLLBACK_SCRIPT"
exit $exit_code
else
log_warning "⚠ 警告: $description 失败 (退出码: $exit_code)"
return $exit_code
fi
fi
}
check_port_available() {
local port=$1
log_info "检查端口 $port 可用性..."
if ss -tuln 2>/dev/null | grep -q ":$port " || netstat -tuln 2>/dev/null | grep -q ":$port "; then
log_error "✗ 端口 $port 已被占用:"
ss -tlnp 2>/dev/null | grep ":$port " || netstat -tlnp 2>/dev/null | grep ":$port "
return 1
fi
log_success "✓ 端口 $port 可用"
return 0
}
verify_ssh_keys_exist() {
log_info "验证SSH密钥配置..."
local has_keys=false
local key_locations=()
for user_home in /home/* /root; do
local authorized_keys="$user_home/.ssh/authorized_keys"
if [[ -f "$authorized_keys" ]]; then
local key_count=$(grep -c "^ssh-" "$authorized_keys" 2>/dev/null || echo 0)
if [[ $key_count -gt 0 ]]; then
has_keys=true
key_locations+=("$authorized_keys ($key_count 个密钥)")
log_success "✓ 找到 $key_count 个SSH密钥: $authorized_keys"
fi
fi
done
if [[ "$has_keys" == true ]]; then
log_success "✓ SSH密钥验证通过"
printf '%s\n' "${key_locations[@]}"
return 0
else
log_error "✗ 未找到任何SSH密钥配置"
return 1
fi
}
verify_ssh_config() {
local ssh_config="/etc/ssh/sshd_config"
log_info "验证SSH配置语法..."
if sshd -t -f "$ssh_config" 2>&1 | tee -a "$LOG_FILE"; then
log_success "✓ SSH配置语法正确"
return 0
else
log_error "✗ SSH配置语法错误"
return 1
fi
}
################################################################################
# Arch Linux 特有函数
################################################################################
# 检查是否为Arch Linux
check_arch_linux() {
if [[ ! -f /etc/arch-release ]]; then
log_error "此脚本仅支持Arch Linux系统"
exit 1
fi
log_success "✓ 检测到Arch Linux系统"
}
# 安装AUR助手(如果需要)
install_aur_helper() {
if command -v yay &> /dev/null; then
log_info "AUR助手已安装: yay"
return 0
fi
if command -v paru &> /dev/null; then
log_info "AUR助手已安装: paru"
return 0
fi
if confirm_action "是否安装AUR助手 yay?(用于安装AUR包)"; then
log_info "安装yay..."
# 需要非root用户安装yay
local build_user="nobody"
if id "nobody" &>/dev/null; then
cd /tmp
safe_execute "pacman -S --needed --noconfirm git base-devel" "安装构建依赖" false
safe_execute "git clone https://aur.archlinux.org/yay.git" "克隆yay仓库" false
safe_execute "chown -R nobody:nobody /tmp/yay" "设置权限" false
safe_execute "cd yay && sudo -u nobody makepkg -si --noconfirm" "构建yay" false
cd -
fi
fi
}
################################################################################
# 主要加固功能
################################################################################
collect_system_info() {
log_section "收集系统信息"
log_info "操作系统: Arch Linux $(uname -r)"
log_info "内核版本: $(uname -r)"
log_info "主机名: $(hostname)"
log_info "IP地址: $(hostname -I 2>/dev/null | awk '{print $1}' || echo "无法获取")"
log_info "当前用户: $(whoami)"
log_info "包管理器: pacman $(pacman --version | head -1)"
echo "系统信息收集完成" >> "$LOG_FILE"
}
update_system() {
log_section "系统更新"
if confirm_action "是否执行系统更新?"; then
# 更新pacman数据库
safe_execute "pacman -Sy" "同步包数据库" false
# 升级系统
safe_execute "pacman -Su --noconfirm" "升级系统" false
# 清理孤立包
safe_execute "pacman -Rns \$(pacman -Qtdq) --noconfirm" "清理孤立包" false 2>/dev/null || true
# 清理缓存
safe_execute "pacman -Sc --noconfirm" "清理包缓存" false
log_success "系统更新完成"
else
log_warning "跳过系统更新"
fi
}
manage_users() {
log_section "用户管理和SSH密钥配置"
if ! confirm_action "是否创建专用管理用户?"; then
log_warning "跳过用户创建"
return 0
fi
local username=""
if [[ -t 0 ]]; then
echo -e "${YELLOW}请输入新用户名 (建议:admin、operator、sysadmin): ${NC}\c"
read -p "" username
else
username="admin"
fi
if [[ -z "$username" ]] || [[ ! "$username" =~ ^[a-z_][a-z0-9_-]*$ ]]; then
log_error "无效的用户名: $username"
return 1
fi
if id "$username" &>/dev/null 2>&1; then
log_info "用户 $username 已存在"
if ! confirm_action "用户已存在,是否继续配置?"; then
return 0
fi
else
log_info "创建用户: $username"
safe_execute "useradd -m -G wheel -s /bin/bash '$username'" "创建用户并添加到wheel组" false
if [[ -t 0 ]]; then
echo -e "${YELLOW}为用户 $username 设置密码: ${NC}"
passwd "$username"
else
local temp_password=$(openssl rand -base64 32)
echo "$username:$temp_password" | chpasswd
log_warning "临时密码: $temp_password"
fi
fi
# 配置SSH密钥
local user_home=$(eval echo "~$username")
local ssh_dir="$user_home/.ssh"
local authorized_keys="$ssh_dir/authorized_keys"
if [[ -t 0 ]]; then
echo -e "${CYAN}选择SSH密钥配置方式:${NC}"
echo "1) 粘贴现有公钥(推荐)"
echo "2) 自动生成新密钥对"
echo "3) 跳过"
echo -e "${YELLOW}选择 [1-3]: ${NC}\c"
read -p "" key_option
else
key_option="3"
fi
case $key_option in
1)
echo -e "${YELLOW}粘贴SSH公钥: ${NC}"
read -p "" public_key
if [[ -n "$public_key" ]] && [[ "$public_key" =~ ^ssh- ]]; then
mkdir -p "$ssh_dir"
echo "$public_key" >> "$authorized_keys"
chown -R "$username:$username" "$ssh_dir"
chmod 700 "$ssh_dir"
chmod 600 "$authorized_keys"
log_success "✓ SSH公钥已添加"
fi
;;
2)
mkdir -p "$ssh_dir"
chown "$username:$username" "$ssh_dir"
local key_file="$ssh_dir/id_ed25519"
if [[ ! -f "$key_file" ]]; then
log_info "生成ED25519密钥对(更安全)..."
su - "$username" -c "ssh-keygen -t ed25519 -f '$key_file' -N '' -C '$username@$(hostname)'"
if [[ -f "$key_file" ]]; then
log_success "✓ SSH密钥对生成成功"
echo -e "${CYAN}━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━${NC}"
echo -e "${YELLOW}公钥内容:${NC}"
cat "$key_file.pub"
echo -e "${CYAN}━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━${NC}"
fi
fi
;;
esac
# 配置sudo权限
configure_sudo_access "$username"
if id "$username" &>/dev/null; then
log_success "✓ 用户 $username 配置完成"
fi
}
configure_sudo_access() {
local username=$1
log_info "配置sudo权限..."
# 确保wheel组可以使用sudo
if ! grep -q "^%wheel ALL=(ALL:ALL) ALL" /etc/sudoers; then
backup_file "/etc/sudoers"
echo "%wheel ALL=(ALL:ALL) ALL" >> /etc/sudoers
fi
if [[ -t 0 ]]; then
echo -e "${CYAN}选择sudo配置:${NC}"
echo "1) 需要密码(推荐)"
echo "2) 无需密码"
echo "3) 部分命令无需密码"
echo -e "${YELLOW}选择 [1-3]: ${NC}\c"
read -p "" sudo_option
else
sudo_option="1"
fi
local sudoers_file="/etc/sudoers.d/$username"
case $sudo_option in
1)
echo "$username ALL=(ALL:ALL) ALL" > "$sudoers_file"
log_success "✓ sudo需要密码"
;;
2)
echo "$username ALL=(ALL) NOPASSWD:ALL" > "$sudoers_file"
log_warning "⚠ sudo无需密码"
;;
3)
cat > "$sudoers_file" << EOF
$username ALL=(ALL) NOPASSWD: /usr/bin/systemctl restart *, /usr/bin/systemctl status *
$username ALL=(ALL) NOPASSWD: /usr/bin/pacman -Syu, /usr/bin/pacman -Sy
$username ALL=(ALL) ALL
EOF
log_success "✓ 部分命令无需密码"
;;
esac
chmod 440 "$sudoers_file"
if visudo -c -f "$sudoers_file" >/dev/null 2>&1; then
log_success "✓ sudoers配置验证通过"
else
log_error "✗ sudoers配置错误,已删除"
rm -f "$sudoers_file"
fi
}
harden_ssh() {
log_section "SSH安全加固"
local ssh_config="/etc/ssh/sshd_config"
if [[ ! -f "$ssh_config" ]]; then
log_error "SSH配置文件不存在"
return 1
fi
backup_file "$ssh_config"
# 禁用root登录
if confirm_action "是否禁用SSH root登录?"; then
sed -i 's/^#*PermitRootLogin.*/PermitRootLogin no/' "$ssh_config"
log_success "✓ 已禁用root登录"
fi
# 禁用密码认证
if confirm_action "是否禁用SSH密码认证?"; then
echo -e "${YELLOW}━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━${NC}"
echo -e "${RED}⚠ 警告:禁用密码认证后只能通过SSH密钥登录${NC}"
echo -e "${YELLOW}━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━${NC}"
if ! verify_ssh_keys_exist; then
log_critical "✗ 未找到SSH密钥!"
if confirm_action "是否现在配置SSH密钥?"; then
manage_users
if ! verify_ssh_keys_exist; then
log_error "密钥配置失败,跳过禁用密码认证"
return 1
fi
else
log_warning "跳过禁用密码认证"
return 0
fi
fi
if confirm_action "确认禁用密码认证?"; then
sed -i 's/^#*PasswordAuthentication.*/PasswordAuthentication no/' "$ssh_config"
log_success "✓ 已禁用密码认证"
fi
fi
# 修改SSH端口
local new_port=""
if [[ -t 0 ]]; then
echo -e "${YELLOW}当前SSH端口: $(grep "^Port" "$ssh_config" 2>/dev/null | awk '{print $2}' || echo 22)${NC}"
echo -e "${YELLOW}输入新端口号(1024-65535,留空跳过): ${NC}\c"
read -p "" new_port
if [[ -n "$new_port" ]]; then
if [[ "$new_port" =~ ^[0-9]+$ ]] && [[ "$new_port" -gt 1024 ]] && [[ "$new_port" -lt 65536 ]]; then
if check_port_available "$new_port"; then
sed -i "s/^#*Port.*/Port $new_port/" "$ssh_config"
log_success "✓ SSH端口已修改为: $new_port"
else
new_port=""
fi
fi
fi
fi
# 其他安全配置
cat >> "$ssh_config" << 'EOF'
# Arch Linux 安全加固配置
Protocol 2
MaxAuthTries 3
MaxSessions 2
LoginGraceTime 60
ClientAliveInterval 300
ClientAliveCountMax 2
PermitEmptyPasswords no
X11Forwarding no
UseDNS no
EOF
if ! verify_ssh_config; then
log_critical "✗ SSH配置验证失败!正在回滚..."
local backup_file=$(ls -t "$BACKUP_DIR"/sshd_config*.bak 2>/dev/null | head -1)
if [[ -n "$backup_file" ]]; then
cp -p "$backup_file" "$ssh_config"
log_success "✓ 已回滚"
fi
return 1
fi
log_success "✓ SSH配置验证通过"
if confirm_action "是否立即重启SSH服务?"; then
if systemctl restart sshd; then
log_success "✓ SSH服务重启成功"
fi
fi
}
# 选择防火墙
choose_firewall() {
log_section "选择防火墙方案"
echo -e "${CYAN}Arch Linux 防火墙选项:${NC}"
echo "1) nftables (现代化,推荐,原生内核支持)"
echo "2) firewalld (易用,动态管理)"
echo "3) ufw (简单,兼容iptables-nft)"
echo "4) 跳过防火墙配置"
if [[ -t 0 ]]; then
echo -e "${YELLOW}选择 [1-4]: ${NC}\c"
read -p "" fw_choice
else
fw_choice="1"
fi
case $fw_choice in
1) FIREWALL_CHOICE="nftables" ;;
2) FIREWALL_CHOICE="firewalld" ;;
3) FIREWALL_CHOICE="ufw" ;;
4) FIREWALL_CHOICE="none" ;;
*) FIREWALL_CHOICE="nftables" ;;
esac
log_info "已选择防火墙: $FIREWALL_CHOICE"
}
# 配置nftables
configure_nftables() {
log_section "配置nftables防火墙"
# 安装nftables
if ! command -v nft &> /dev/null; then
safe_execute "pacman -S --noconfirm nftables" "安装nftables" true
fi
# 获取SSH端口
local ssh_port=$(grep "^Port" /etc/ssh/sshd_config 2>/dev/null | awk '{print $2}')
ssh_port=${ssh_port:-22}
log_info "检测到SSH端口: $ssh_port"
# 创建nftables配置
local nft_conf="/etc/nftables.conf"
backup_file "$nft_conf" 2>/dev/null || true
cat > "$nft_conf" << EOF
#!/usr/bin/nft -f
# Arch Linux 防火墙配置
# 清空现有规则
flush ruleset
# 定义表
table inet filter {
# 输入链
chain input {
type filter hook input priority 0; policy drop;
# 允许环回接口
iif lo accept
# 允许已建立的连接
ct state established,related accept
# 允许SSH
tcp dport $ssh_port accept
# 允许HTTP/HTTPS (如果需要)
# tcp dport { 80, 443 } accept
# 允许ICMP (ping)
icmp type echo-request limit rate 1/second accept
icmpv6 type { echo-request, nd-neighbor-solicit, nd-router-advert, nd-neighbor-advert } accept
# 记录并拒绝其他流量
counter drop
}
# 转发链
chain forward {
type filter hook forward priority 0; policy drop;
}
# 输出链
chain output {
type filter hook output priority 0; policy accept;
}
}
EOF
# 询问是否开放其他端口
if confirm_action "是否开放HTTP(80)?"; then
safe_execute "ufw allow 80/tcp comment 'HTTP'" "开放HTTP" false
fi
if confirm_action "是否开放HTTPS(443)?"; then
safe_execute "ufw allow 443/tcp comment 'HTTPS'" "开放HTTPS" false
fi
echo "y" | ufw enable >> "$LOG_FILE" 2>&1
ufw status verbose
# 启用UFW服务
safe_execute "systemctl enable --now ufw.service" "启用UFW服务" false
log_success "✓ UFW配置完成"
}
# 防火墙配置入口
configure_firewall() {
choose_firewall
case $FIREWALL_CHOICE in
"nftables")
configure_nftables
;;
"firewalld")
configure_firewalld
;;
"ufw")
configure_ufw
;;
"none")
log_warning "跳过防火墙配置"
;;
esac
}
# 选择入侵防护系统
choose_ips() {
log_section "选择入侵防护系统"
echo -e "${CYAN}Arch Linux IPS选项:${NC}"
echo "1) sshguard (轻量级,原生nftables/firewalld支持,推荐)"
echo "2) crowdsec (现代化,社区驱动,支持多种防火墙)"
echo "3) fail2ban (传统,功能全面)"
echo "4) 跳过"
if [[ -t 0 ]]; then
echo -e "${YELLOW}选择 [1-4]: ${NC}\c"
read -p "" ips_choice
else
ips_choice="1"
fi
case $ips_choice in
1) IPS_CHOICE="sshguard" ;;
2) IPS_CHOICE="crowdsec" ;;
3) IPS_CHOICE="fail2ban" ;;
4) IPS_CHOICE="none" ;;
*) IPS_CHOICE="sshguard" ;;
esac
log_info "已选择IPS: $IPS_CHOICE"
}
# 配置sshguard
configure_sshguard() {
log_section "配置SSHGuard"
if ! command -v sshguard &> /dev/null; then
safe_execute "pacman -S --noconfirm sshguard" "安装sshguard" false
fi
# 根据防火墙选择后端
local backend=""
case $FIREWALL_CHOICE in
"nftables")
backend="nftables"
;;
"firewalld")
backend="firewalld"
;;
"ufw")
backend="iptables"
;;
*)
backend="nftables"
;;
esac
log_info "使用防火墙后端: $backend"
# 创建配置
local sshguard_conf="/etc/sshguard.conf"
backup_file "$sshguard_conf" 2>/dev/null || true
cat > "$sshguard_conf" << EOF
# SSHGuard 配置
BACKEND="$backend"
LOGREADER="LANG=C /usr/bin/journalctl -afb -p info -n1 -t sshd -o cat"
THRESHOLD=30
BLOCK_TIME=3600
DETECTION_TIME=600
EOF
# 启用服务
safe_execute "systemctl enable --now sshguard.service" "启用sshguard" false
# 检查状态
systemctl status sshguard.service --no-pager || true
log_success "✓ SSHGuard配置完成"
}
# 配置CrowdSec
configure_crowdsec() {
log_section "配置CrowdSec"
log_info "CrowdSec需要从AUR安装..."
if command -v yay &> /dev/null; then
safe_execute "yay -S --noconfirm crowdsec crowdsec-firewall-bouncer-nftables" "安装CrowdSec" false
elif command -v paru &> /dev/null; then
safe_execute "paru -S --noconfirm crowdsec crowdsec-firewall-bouncer-nftables" "安装CrowdSec" false
else
log_warning "未安装AUR助手,跳过CrowdSec安装"
return 1
fi
# 启用服务
safe_execute "systemctl enable --now crowdsec.service" "启用CrowdSec" false
# 配置bouncer
safe_execute "cscli bouncers add firewall-bouncer" "添加防火墙bouncer" false
log_success "✓ CrowdSec配置完成"
}
# 配置Fail2ban
configure_fail2ban() {
log_section "配置Fail2ban"
if ! command -v fail2ban-client &> /dev/null; then
safe_execute "pacman -S --noconfirm fail2ban" "安装fail2ban" false
fi
local ssh_port=$(grep "^Port" /etc/ssh/sshd_config 2>/dev/null | awk '{print $2}')
ssh_port=${ssh_port:-ssh}
local jail_local="/etc/fail2ban/jail.local"
backup_file "$jail_local" 2>/dev/null || true
cat > "$jail_local" << EOF
[DEFAULT]
bantime = 3600
findtime = 600
maxretry = 5
[sshd]
enabled = true
port = $ssh_port
filter = sshd
backend = systemd
maxretry = 3
bantime = 7200
EOF
safe_execute "systemctl enable --now fail2ban.service" "启用fail2ban" false
sleep 2
fail2ban-client status 2>/dev/null || true
log_success "✓ Fail2ban配置完成"
}
# IPS配置入口
configure_ips() {
choose_ips
case $IPS_CHOICE in
"sshguard")
configure_sshguard
;;
"crowdsec")
configure_crowdsec
;;
"fail2ban")
configure_fail2ban
;;
"none")
log_warning "跳过IPS配置"
;;
esac
}
# 密码策略
harden_users() {
log_section "密码策略加固"
# 安装pam_pwquality
if ! pacman -Q libpwquality &> /dev/null; then
safe_execute "pacman -S --noconfirm libpwquality" "安装密码质量检查" false
fi
local pwquality_conf="/etc/security/pwquality.conf"
backup_file "$pwquality_conf"
cat >> "$pwquality_conf" << 'EOF'
# Arch Linux 密码策略
minlen = 12
dcredit = -1
ucredit = -1
lcredit = -1
ocredit = -1
maxrepeat = 3
EOF
# 配置PAM
local common_password="/etc/pam.d/passwd"
if [[ -f "$common_password" ]]; then
backup_file "$common_password"
if ! grep -q "pam_pwquality.so" "$common_password"; then
sed -i '/password.*required/a password required pam_pwquality.so retry=3' "$common_password"
fi
fi
log_success "✓ 密码策略配置完成"
}
# 内核参数加固
harden_kernel() {
log_section "内核参数加固"
local sysctl_conf="/etc/sysctl.d/99-security.conf"
backup_file "$sysctl_conf" 2>/dev/null || true
cat > "$sysctl_conf" << 'EOF'
# Arch Linux 内核安全配置
net.ipv4.ip_forward = 0
net.ipv6.conf.all.forwarding = 0
net.ipv4.tcp_syncookies = 1
net.ipv4.conf.all.accept_redirects = 0
net.ipv6.conf.all.accept_redirects = 0
net.ipv4.conf.default.accept_redirects = 0
net.ipv6.conf.default.accept_redirects = 0
net.ipv4.conf.all.secure_redirects = 0
net.ipv4.conf.default.secure_redirects = 0
net.ipv4.conf.all.accept_source_route = 0
net.ipv6.conf.all.accept_source_route = 0
net.ipv4.conf.default.accept_source_route = 0
net.ipv6.conf.default.accept_source_route = 0
net.ipv4.conf.all.log_martians = 1
net.ipv4.conf.default.log_martians = 1
net.ipv4.icmp_echo_ignore_broadcasts = 1
net.ipv4.conf.all.rp_filter = 1
net.ipv4.conf.default.rp_filter = 1
net.ipv4.tcp_max_syn_backlog = 2048
net.ipv4.tcp_synack_retries = 2
net.ipv4.tcp_syn_retries = 5
kernel.dmesg_restrict = 1
kernel.kptr_restrict = 2
EOF
safe_execute "sysctl -p '$sysctl_conf'" "应用内核参数" false
log_success "✓ 内核参数配置完成"
}
# 安装安全工具
install_security_tools() {
log_section "安装安全工具"
local tools=(
"rkhunter:Rootkit检测"
"lynis:安全审计"
"aide:文件完整性检查"
"logwatch:日志分析"
)
for tool_desc in "${tools[@]}"; do
local tool="${tool_desc%:*}"
local desc="${tool_desc#*:}"
if confirm_action "是否安装 $tool ($desc)?"; then
if pacman -Q "$tool" &> /dev/null; then
log_info "$tool 已安装"
continue
fi
safe_execute "pacman -S --noconfirm '$tool'" "安装 $tool" false
# 特殊配置
case "$tool" in
"rkhunter")
safe_execute "rkhunter --update" "更新rkhunter" false
safe_execute "rkhunter --propupd" "更新属性" false
;;
esac
fi
done
}
# 配置自动更新
configure_auto_updates() {
log_section "配置自动更新"
echo -e "${CYAN}Arch Linux 自动更新选项:${NC}"
echo "1) 使用systemd timer (推荐)"
echo "2) 跳过"
if [[ -t 0 ]]; then
echo -e "${YELLOW}选择 [1-2]: ${NC}\c"
read -p "" update_choice
else
update_choice="2"
fi
if [[ "$update_choice" == "1" ]]; then
# 创建更新脚本
cat > /usr/local/bin/arch-auto-update.sh << 'EOF'
#!/bin/bash
/usr/bin/pacman -Syu --noconfirm >> /var/log/arch-auto-update.log 2>&1
EOF
chmod +x /usr/local/bin/arch-auto-update.sh
# 创建systemd service
cat > /etc/systemd/system/arch-auto-update.service << 'EOF'
[Unit]
Description=Arch Linux Auto Update
After=network-online.target
[Service]
Type=oneshot
ExecStart=/usr/local/bin/arch-auto-update.sh
EOF
# 创建systemd timer
cat > /etc/systemd/system/arch-auto-update.timer << 'EOF'
[Unit]
Description=Arch Linux Auto Update Timer
[Timer]
OnCalendar=daily
Persistent=true
[Install]
WantedBy=timers.target
EOF
safe_execute "systemctl enable --now arch-auto-update.timer" "启用自动更新" false
log_success "✓ 自动更新配置完成(每天执行)"
else
log_warning "跳过自动更新配置"
fi
}
# 生成报告
generate_report() {
log_section "生成安全报告"
local report_file="/root/arch_hardening_report_$(date +%Y%m%d_%H%M%S).txt"
cat > "$report_file" << EOF
================================================================================
Arch Linux 服务器安全加固报告 v2.0
================================================================================
生成时间: $(date)
主机名: $(hostname)
内核版本: $(uname -r)
包管理器: $(pacman --version | head -1)
--------------------------------------------------------------------------------
1. SSH配置
--------------------------------------------------------------------------------
$(grep -E "^(Port|PermitRootLogin|PasswordAuthentication)" /etc/ssh/sshd_config 2>/dev/null)
--------------------------------------------------------------------------------
2. 防火墙: $FIREWALL_CHOICE
--------------------------------------------------------------------------------
$(case $FIREWALL_CHOICE in
"nftables") nft list ruleset 2>/dev/null ;;
"firewalld") firewall-cmd --list-all 2>/dev/null ;;
"ufw") ufw status verbose 2>/dev/null ;;
*) echo "未配置" ;;
esac)
--------------------------------------------------------------------------------
3. 入侵防护: $IPS_CHOICE
--------------------------------------------------------------------------------
$(case $IPS_CHOICE in
"sshguard") systemctl status sshguard --no-pager 2>/dev/null || echo "未运行" ;;
"crowdsec") cscli metrics 2>/dev/null || echo "未运行" ;;
"fail2ban") fail2ban-client status 2>/dev/null || echo "未运行" ;;
*) echo "未配置" ;;
esac)
--------------------------------------------------------------------------------
4. SSH密钥配置
--------------------------------------------------------------------------------
$(for home in /home/* /root; do
if [[ -f "$home/.ssh/authorized_keys" ]]; then
echo "$(basename $home): $(grep -c '^ssh-' "$home/.ssh/authorized_keys" 2>/dev/null || echo 0) 个密钥"
fi
done)
--------------------------------------------------------------------------------
备份目录: $BACKUP_DIR
日志文件: $LOG_FILE
回滚脚本: $ROLLBACK_SCRIPT
--------------------------------------------------------------------------------
EOF
log_success "✓ 报告已生成: $report_file"
echo -e "${CYAN}━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━${NC}"
cat "$report_file"
echo -e "${CYAN}━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━${NC}"
}
################################################################################
# 主程序
################################################################################
main() {
clear
echo -e "${BLUE}"
cat << "EOF"
╔══════════════════════════════════════════════════════════════╗
║ Arch Linux 服务器安全加固脚本 v2.0 ║
║ 针对Arch Linux优化的现代化工具选择 ║
╚══════════════════════════════════════════════════════════════╝
防火墙选项:
• nftables (现代化,推荐)
• firewalld (易用)
• ufw (简单)
入侵防护:
• sshguard (轻量,原生支持nftables/firewalld)
• crowdsec (社区驱动,现代化)
• fail2ban (传统,功能全面)
EOF
echo -e "${NC}"
check_root
check_arch_linux
# 初始化
mkdir -p "$BACKUP_DIR"
echo "#!/bin/bash" > "$ROLLBACK_SCRIPT"
echo "# Arch Linux 回滚脚本 - $(date)" >> "$ROLLBACK_SCRIPT"
chmod +x "$ROLLBACK_SCRIPT"
log "开始Arch Linux安全加固..."
log_info "日志: $LOG_FILE"
log_info "备份: $BACKUP_DIR"
if ! confirm_action "确认继续?"; then
exit 0
fi
# 执行加固
collect_system_info
install_aur_helper
update_system
manage_users
harden_ssh
configure_firewall
configure_ips
harden_users
harden_kernel
install_security_tools
configure_auto_updates
generate_report
log_section "加固完成"
echo -e "${GREEN}"
cat << EOF
╔══════════════════════════════════════════════════════════════╗
║ Arch Linux 加固完成! ║
╚══════════════════════════════════════════════════════════════╝
配置信息:
防火墙: $FIREWALL_CHOICE
入侵防护: $IPS_CHOICE
备份: $BACKUP_DIR
日志: $LOG_FILE
下一步:
1. 测试SSH登录(不要断开当前连接)
2. 检查防火墙: nft list ruleset / firewall-cmd --list-all / ufw status
3. 检查服务: systemctl status sshd $FIREWALL_CHOICE $IPS_CHOICE
4. 查看日志: journalctl -xe
EOF
echo -e "${NC}"
if confirm_action "是否现在重启?"; then
log "5秒后重启..."
sleep 5
reboot
fi
}
main "$@")端口?"; then
sed -i 's/# tcp dport { 80, 443 } accept/tcp dport 80 accept/' "$nft_conf"
fi
if confirm_action "是否开放HTTPS(443)端口?"; then
sed -i '/tcp dport 80 accept/a\ tcp dport 443 accept' "$nft_conf"
fi
# 测试配置
if nft -c -f "$nft_conf" 2>&1 | tee -a "$LOG_FILE"; then
log_success "✓ nftables配置验证通过"
# 应用规则
safe_execute "nft -f '$nft_conf'" "应用nftables规则" true
# 启用服务
safe_execute "systemctl enable nftables.service" "启用nftables服务" false
safe_execute "systemctl start nftables.service" "启动nftables服务" false
# 显示当前规则
echo -e "${CYAN}━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━${NC}"
nft list ruleset
echo -e "${CYAN}━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━${NC}"
log_success "✓ nftables配置完成"
else
log_error "✗ nftables配置验证失败"
fi
}
# 配置firewalld
configure_firewalld() {
log_section "配置firewalld防火墙"
if ! command -v firewall-cmd &> /dev/null; then
safe_execute "pacman -S --noconfirm firewalld" "安装firewalld" true
fi
# 启动服务
safe_execute "systemctl enable --now firewalld" "启用firewalld" true
# 获取SSH端口
local ssh_port=$(grep "^Port" /etc/ssh/sshd_config 2>/dev/null | awk '{print $2}')
ssh_port=${ssh_port:-22}
# 配置规则
safe_execute "firewall-cmd --permanent --add-port=$ssh_port/tcp" "开放SSH端口" true
if confirm_action "是否开放HTTP(80)?"; then
safe_execute "firewall-cmd --permanent --add-service=http" "开放HTTP" false
fi
if confirm_action "是否开放HTTPS(443)?"; then
safe_execute "firewall-cmd --permanent --add-service=https" "开放HTTPS" false
fi
# 重载配置
safe_execute "firewall-cmd --reload" "重载firewall配置" false
# 显示状态
firewall-cmd --list-all
log_success "✓ firewalld配置完成"
}
# 配置UFW
configure_ufw() {
log_section "配置UFW防火墙"
# 安装UFW和iptables-nft
if ! command -v ufw &> /dev/null; then
safe_execute "pacman -S --noconfirm ufw iptables-nft" "安装UFW" true
fi
local ssh_port=$(grep "^Port" /etc/ssh/sshd_config 2>/dev/null | awk '{print $2}')
ssh_port=${ssh_port:-22}
safe_execute "ufw default deny incoming" "设置默认策略" false
safe_execute "ufw default allow outgoing" "设置默认策略" false
safe_execute "ufw allow $ssh_port/tcp comment 'SSH'" "开放SSH" true
if confirm_action "是否开放HTTP(80
| 1 | #!/bin/bash |
| 2 | |
| 3 | ################################################################################ |
| 4 | # Arch Linux 服务器安全加固脚本 |
| 5 | # 版本: v2.0 |
| 6 | # 日期: 2025-01-19 |
| 7 | # |
| 8 | # 针对Arch Linux优化的工具选择: |
| 9 | # - 防火墙: nftables (现代化,替代iptables) 或 firewalld (更易用) |
| 10 | # - 入侵防护: sshguard (轻量级,原生nftables支持) 或 crowdsec (社区驱动) |
| 11 | # - 包管理: pacman (Arch原生) |
| 12 | # - 安全工具: 优先使用官方仓库和AUR |
| 13 | ################################################################################ |
| 14 | |
| 15 | export TERM=xterm-color |
| 16 | set +H |
| 17 | set +e |
| 18 | |
| 19 | # 颜色定义 |
| 20 | if command -v tput &> /dev/null; then |
| 21 | RED=$(tput setaf 1 2>/dev/null || echo '\033[0;31m') |
| 22 | GREEN=$(tput setaf 2 2>/dev/null || echo '\033[0;32m') |
| 23 | YELLOW=$(tput setaf 3 2>/dev/null || echo '\033[1;33m') |
| 24 | BLUE=$(tput setaf 4 2>/dev/null || echo '\033[0;34m') |
| 25 | CYAN=$(tput setaf 6 2>/dev/null || echo '\033[0;36m') |
| 26 | MAGENTA=$(tput setaf 5 2>/dev/null || echo '\033[0;35m') |
| 27 | NC=$(tput sgr0 2>/dev/null || echo '\033[0m') |
| 28 | else |
| 29 | RED='\033[0;31m' |
| 30 | GREEN='\033[0;32m' |
| 31 | YELLOW='\033[1;33m' |
| 32 | BLUE='\033[0;34m' |
| 33 | CYAN='\033[0;36m' |
| 34 | MAGENTA='\033[0;35m' |
| 35 | NC='\033[0m' |
| 36 | fi |
| 37 | |
| 38 | # 日志和备份目录 |
| 39 | LOG_FILE="/var/log/arch_hardening_$(date +%Y%m%d_%H%M%S).log" |
| 40 | BACKUP_DIR="/root/security_backup_$(date +%Y%m%d_%H%M%S)" |
| 41 | ROLLBACK_SCRIPT="$BACKUP_DIR/rollback.sh" |
| 42 | |
| 43 | # 全局变量 |
| 44 | FIREWALL_CHOICE="" |
| 45 | IPS_CHOICE="" |
| 46 | |
| 47 | ################################################################################ |
| 48 | # 工具函数 |
| 49 | ################################################################################ |
| 50 | |
| 51 | log() { |
| 52 | echo -e "${GREEN}[$(date +'%Y-%m-%d %H:%M:%S')]${NC} $1" | tee -a "$LOG_FILE" |
| 53 | } |
| 54 | |
| 55 | log_info() { |
| 56 | echo -e "${BLUE}[INFO]${NC} $1" | tee -a "$LOG_FILE" |
| 57 | } |
| 58 | |
| 59 | log_warning() { |
| 60 | echo -e "${YELLOW}[WARNING]${NC} $1" | tee -a "$LOG_FILE" |
| 61 | } |
| 62 | |
| 63 | log_error() { |
| 64 | echo -e "${RED}[ERROR]${NC} $1" | tee -a "$LOG_FILE" |
| 65 | } |
| 66 | |
| 67 | log_success() { |
| 68 | echo -e "${GREEN}[SUCCESS]${NC} $1" | tee -a "$LOG_FILE" |
| 69 | } |
| 70 | |
| 71 | log_critical() { |
| 72 | echo -e "${RED}[CRITICAL]${NC} $1" | tee -a "$LOG_FILE" |
| 73 | } |
| 74 | |
| 75 | log_section() { |
| 76 | echo -e "\n${CYAN}========================================${NC}" | tee -a "$LOG_FILE" |
| 77 | echo -e "${CYAN}$1${NC}" | tee -a "$LOG_FILE" |
| 78 | echo -e "${CYAN}========================================${NC}\n" | tee -a "$LOG_FILE" |
| 79 | } |
| 80 | |
| 81 | check_root() { |
| 82 | if [[ $EUID -ne 0 ]]; then |
| 83 | log_error "此脚本必须以root权限运行" |
| 84 | exit 1 |
| 85 | fi |
| 86 | } |
| 87 | |
| 88 | backup_file() { |
| 89 | local file=$1 |
| 90 | |
| 91 | if [[ ! -f "$file" ]]; then |
| 92 | log_warning "文件不存在,跳过备份: $file" |
| 93 | return 1 |
| 94 | fi |
| 95 | |
| 96 | mkdir -p "$BACKUP_DIR" |
| 97 | |
| 98 | local backup_name="$(basename $file).$(date +%Y%m%d_%H%M%S).bak" |
| 99 | local backup_path="$BACKUP_DIR/$backup_name" |
| 100 | |
| 101 | if cp -p "$file" "$backup_path" 2>/dev/null; then |
| 102 | if cmp -s "$file" "$backup_path"; then |
| 103 | log_success "✓ 备份成功: $file -> $backup_name" |
| 104 | echo "cp -p '$backup_path' '$file'" >> "$ROLLBACK_SCRIPT" |
| 105 | return 0 |
| 106 | else |
| 107 | log_error "✗ 备份验证失败: $file" |
| 108 | rm -f "$backup_path" |
| 109 | return 1 |
| 110 | fi |
| 111 | else |
| 112 | log_error "✗ 备份失败: $file" |
| 113 | return 1 |
| 114 | fi |
| 115 | } |
| 116 | |
| 117 | confirm_action() { |
| 118 | local message=$1 |
| 119 | if [[ -t 0 ]]; then |
| 120 | echo -e "${YELLOW}${message} [y/N]: ${NC}\c" |
| 121 | read -n 1 -r |
| 122 | echo |
| 123 | if [[ ! $REPLY =~ ^[Yy]$ ]]; then |
| 124 | return 1 |
| 125 | fi |
| 126 | return 0 |
| 127 | else |
| 128 | log_info "非交互模式: $message - 跳过" |
| 129 | return 1 |
| 130 | fi |
| 131 | } |
| 132 | |
| 133 | safe_execute() { |
| 134 | local cmd="$1" |
| 135 | local description="$2" |
| 136 | local critical="${3:-false}" |
| 137 | |
| 138 | log_info "执行: $description" |
| 139 | |
| 140 | if eval "$cmd" >> "$LOG_FILE" 2>&1; then |
| 141 | log_success "✓ 成功: $description" |
| 142 | return 0 |
| 143 | else |
| 144 | local exit_code=$? |
| 145 | |
| 146 | if [[ "$critical" == "true" ]]; then |
| 147 | log_critical "✗ 关键操作失败: $description (退出码: $exit_code)" |
| 148 | log_critical "脚本终止,可使用回滚: bash $ROLLBACK_SCRIPT" |
| 149 | exit $exit_code |
| 150 | else |
| 151 | log_warning "⚠ 警告: $description 失败 (退出码: $exit_code)" |
| 152 | return $exit_code |
| 153 | fi |
| 154 | fi |
| 155 | } |
| 156 | |
| 157 | check_port_available() { |
| 158 | local port=$1 |
| 159 | |
| 160 | log_info "检查端口 $port 可用性..." |
| 161 | |
| 162 | if ss -tuln 2>/dev/null | grep -q ":$port " || netstat -tuln 2>/dev/null | grep -q ":$port "; then |
| 163 | log_error "✗ 端口 $port 已被占用:" |
| 164 | ss -tlnp 2>/dev/null | grep ":$port " || netstat -tlnp 2>/dev/null | grep ":$port " |
| 165 | return 1 |
| 166 | fi |
| 167 | |
| 168 | log_success "✓ 端口 $port 可用" |
| 169 | return 0 |
| 170 | } |
| 171 | |
| 172 | verify_ssh_keys_exist() { |
| 173 | log_info "验证SSH密钥配置..." |
| 174 | |
| 175 | local has_keys=false |
| 176 | local key_locations=() |
| 177 | |
| 178 | for user_home in /home/* /root; do |
| 179 | local authorized_keys="$user_home/.ssh/authorized_keys" |
| 180 | |
| 181 | if [[ -f "$authorized_keys" ]]; then |
| 182 | local key_count=$(grep -c "^ssh-" "$authorized_keys" 2>/dev/null || echo 0) |
| 183 | |
| 184 | if [[ $key_count -gt 0 ]]; then |
| 185 | has_keys=true |
| 186 | key_locations+=("$authorized_keys ($key_count 个密钥)") |
| 187 | log_success "✓ 找到 $key_count 个SSH密钥: $authorized_keys" |
| 188 | fi |
| 189 | fi |
| 190 | done |
| 191 | |
| 192 | if [[ "$has_keys" == true ]]; then |
| 193 | log_success "✓ SSH密钥验证通过" |
| 194 | printf '%s\n' "${key_locations[@]}" |
| 195 | return 0 |
| 196 | else |
| 197 | log_error "✗ 未找到任何SSH密钥配置" |
| 198 | return 1 |
| 199 | fi |
| 200 | } |
| 201 | |
| 202 | verify_ssh_config() { |
| 203 | local ssh_config="/etc/ssh/sshd_config" |
| 204 | |
| 205 | log_info "验证SSH配置语法..." |
| 206 | |
| 207 | if sshd -t -f "$ssh_config" 2>&1 | tee -a "$LOG_FILE"; then |
| 208 | log_success "✓ SSH配置语法正确" |
| 209 | return 0 |
| 210 | else |
| 211 | log_error "✗ SSH配置语法错误" |
| 212 | return 1 |
| 213 | fi |
| 214 | } |
| 215 | |
| 216 | ################################################################################ |
| 217 | # Arch Linux 特有函数 |
| 218 | ################################################################################ |
| 219 | |
| 220 | # 检查是否为Arch Linux |
| 221 | check_arch_linux() { |
| 222 | if [[ ! -f /etc/arch-release ]]; then |
| 223 | log_error "此脚本仅支持Arch Linux系统" |
| 224 | exit 1 |
| 225 | fi |
| 226 | |
| 227 | log_success "✓ 检测到Arch Linux系统" |
| 228 | } |
| 229 | |
| 230 | # 安装AUR助手(如果需要) |
| 231 | install_aur_helper() { |
| 232 | if command -v yay &> /dev/null; then |
| 233 | log_info "AUR助手已安装: yay" |
| 234 | return 0 |
| 235 | fi |
| 236 | |
| 237 | if command -v paru &> /dev/null; then |
| 238 | log_info "AUR助手已安装: paru" |
| 239 | return 0 |
| 240 | fi |
| 241 | |
| 242 | if confirm_action "是否安装AUR助手 yay?(用于安装AUR包)"; then |
| 243 | log_info "安装yay..." |
| 244 | |
| 245 | # 需要非root用户安装yay |
| 246 | local build_user="nobody" |
| 247 | if id "nobody" &>/dev/null; then |
| 248 | cd /tmp |
| 249 | safe_execute "pacman -S --needed --noconfirm git base-devel" "安装构建依赖" false |
| 250 | safe_execute "git clone https://aur.archlinux.org/yay.git" "克隆yay仓库" false |
| 251 | safe_execute "chown -R nobody:nobody /tmp/yay" "设置权限" false |
| 252 | safe_execute "cd yay && sudo -u nobody makepkg -si --noconfirm" "构建yay" false |
| 253 | cd - |
| 254 | fi |
| 255 | fi |
| 256 | } |
| 257 | |
| 258 | ################################################################################ |
| 259 | # 主要加固功能 |
| 260 | ################################################################################ |
| 261 | |
| 262 | collect_system_info() { |
| 263 | log_section "收集系统信息" |
| 264 | |
| 265 | log_info "操作系统: Arch Linux $(uname -r)" |
| 266 | log_info "内核版本: $(uname -r)" |
| 267 | log_info "主机名: $(hostname)" |
| 268 | log_info "IP地址: $(hostname -I 2>/dev/null | awk '{print $1}' || echo "无法获取")" |
| 269 | log_info "当前用户: $(whoami)" |
| 270 | log_info "包管理器: pacman $(pacman --version | head -1)" |
| 271 | |
| 272 | echo "系统信息收集完成" >> "$LOG_FILE" |
| 273 | } |
| 274 | |
| 275 | update_system() { |
| 276 | log_section "系统更新" |
| 277 | |
| 278 | if confirm_action "是否执行系统更新?"; then |
| 279 | # 更新pacman数据库 |
| 280 | safe_execute "pacman -Sy" "同步包数据库" false |
| 281 | |
| 282 | # 升级系统 |
| 283 | safe_execute "pacman -Su --noconfirm" "升级系统" false |
| 284 | |
| 285 | # 清理孤立包 |
| 286 | safe_execute "pacman -Rns \$(pacman -Qtdq) --noconfirm" "清理孤立包" false 2>/dev/null || true |
| 287 | |
| 288 | # 清理缓存 |
| 289 | safe_execute "pacman -Sc --noconfirm" "清理包缓存" false |
| 290 | |
| 291 | log_success "系统更新完成" |
| 292 | else |
| 293 | log_warning "跳过系统更新" |
| 294 | fi |
| 295 | } |
| 296 | |
| 297 | manage_users() { |
| 298 | log_section "用户管理和SSH密钥配置" |
| 299 | |
| 300 | if ! confirm_action "是否创建专用管理用户?"; then |
| 301 | log_warning "跳过用户创建" |
| 302 | return 0 |
| 303 | fi |
| 304 | |
| 305 | local username="" |
| 306 | |
| 307 | if [[ -t 0 ]]; then |
| 308 | echo -e "${YELLOW}请输入新用户名 (建议:admin、operator、sysadmin): ${NC}\c" |
| 309 | read -p "" username |
| 310 | else |
| 311 | username="admin" |
| 312 | fi |
| 313 | |
| 314 | if [[ -z "$username" ]] || [[ ! "$username" =~ ^[a-z_][a-z0-9_-]*$ ]]; then |
| 315 | log_error "无效的用户名: $username" |
| 316 | return 1 |
| 317 | fi |
| 318 | |
| 319 | if id "$username" &>/dev/null 2>&1; then |
| 320 | log_info "用户 $username 已存在" |
| 321 | if ! confirm_action "用户已存在,是否继续配置?"; then |
| 322 | return 0 |
| 323 | fi |
| 324 | else |
| 325 | log_info "创建用户: $username" |
| 326 | safe_execute "useradd -m -G wheel -s /bin/bash '$username'" "创建用户并添加到wheel组" false |
| 327 | |
| 328 | if [[ -t 0 ]]; then |
| 329 | echo -e "${YELLOW}为用户 $username 设置密码: ${NC}" |
| 330 | passwd "$username" |
| 331 | else |
| 332 | local temp_password=$(openssl rand -base64 32) |
| 333 | echo "$username:$temp_password" | chpasswd |
| 334 | log_warning "临时密码: $temp_password" |
| 335 | fi |
| 336 | fi |
| 337 | |
| 338 | # 配置SSH密钥 |
| 339 | local user_home=$(eval echo "~$username") |
| 340 | local ssh_dir="$user_home/.ssh" |
| 341 | local authorized_keys="$ssh_dir/authorized_keys" |
| 342 | |
| 343 | if [[ -t 0 ]]; then |
| 344 | echo -e "${CYAN}选择SSH密钥配置方式:${NC}" |
| 345 | echo "1) 粘贴现有公钥(推荐)" |
| 346 | echo "2) 自动生成新密钥对" |
| 347 | echo "3) 跳过" |
| 348 | echo -e "${YELLOW}选择 [1-3]: ${NC}\c" |
| 349 | read -p "" key_option |
| 350 | else |
| 351 | key_option="3" |
| 352 | fi |
| 353 | |
| 354 | case $key_option in |
| 355 | 1) |
| 356 | echo -e "${YELLOW}粘贴SSH公钥: ${NC}" |
| 357 | read -p "" public_key |
| 358 | |
| 359 | if [[ -n "$public_key" ]] && [[ "$public_key" =~ ^ssh- ]]; then |
| 360 | mkdir -p "$ssh_dir" |
| 361 | echo "$public_key" >> "$authorized_keys" |
| 362 | chown -R "$username:$username" "$ssh_dir" |
| 363 | chmod 700 "$ssh_dir" |
| 364 | chmod 600 "$authorized_keys" |
| 365 | log_success "✓ SSH公钥已添加" |
| 366 | fi |
| 367 | ;; |
| 368 | 2) |
| 369 | mkdir -p "$ssh_dir" |
| 370 | chown "$username:$username" "$ssh_dir" |
| 371 | |
| 372 | local key_file="$ssh_dir/id_ed25519" |
| 373 | |
| 374 | if [[ ! -f "$key_file" ]]; then |
| 375 | log_info "生成ED25519密钥对(更安全)..." |
| 376 | su - "$username" -c "ssh-keygen -t ed25519 -f '$key_file' -N '' -C '$username@$(hostname)'" |
| 377 | |
| 378 | if [[ -f "$key_file" ]]; then |
| 379 | log_success "✓ SSH密钥对生成成功" |
| 380 | echo -e "${CYAN}━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━${NC}" |
| 381 | echo -e "${YELLOW}公钥内容:${NC}" |
| 382 | cat "$key_file.pub" |
| 383 | echo -e "${CYAN}━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━${NC}" |
| 384 | fi |
| 385 | fi |
| 386 | ;; |
| 387 | esac |
| 388 | |
| 389 | # 配置sudo权限 |
| 390 | configure_sudo_access "$username" |
| 391 | |
| 392 | if id "$username" &>/dev/null; then |
| 393 | log_success "✓ 用户 $username 配置完成" |
| 394 | fi |
| 395 | } |
| 396 | |
| 397 | configure_sudo_access() { |
| 398 | local username=$1 |
| 399 | |
| 400 | log_info "配置sudo权限..." |
| 401 | |
| 402 | # 确保wheel组可以使用sudo |
| 403 | if ! grep -q "^%wheel ALL=(ALL:ALL) ALL" /etc/sudoers; then |
| 404 | backup_file "/etc/sudoers" |
| 405 | echo "%wheel ALL=(ALL:ALL) ALL" >> /etc/sudoers |
| 406 | fi |
| 407 | |
| 408 | if [[ -t 0 ]]; then |
| 409 | echo -e "${CYAN}选择sudo配置:${NC}" |
| 410 | echo "1) 需要密码(推荐)" |
| 411 | echo "2) 无需密码" |
| 412 | echo "3) 部分命令无需密码" |
| 413 | echo -e "${YELLOW}选择 [1-3]: ${NC}\c" |
| 414 | read -p "" sudo_option |
| 415 | else |
| 416 | sudo_option="1" |
| 417 | fi |
| 418 | |
| 419 | local sudoers_file="/etc/sudoers.d/$username" |
| 420 | |
| 421 | case $sudo_option in |
| 422 | 1) |
| 423 | echo "$username ALL=(ALL:ALL) ALL" > "$sudoers_file" |
| 424 | log_success "✓ sudo需要密码" |
| 425 | ;; |
| 426 | 2) |
| 427 | echo "$username ALL=(ALL) NOPASSWD:ALL" > "$sudoers_file" |
| 428 | log_warning "⚠ sudo无需密码" |
| 429 | ;; |
| 430 | 3) |
| 431 | cat > "$sudoers_file" << EOF |
| 432 | $username ALL=(ALL) NOPASSWD: /usr/bin/systemctl restart *, /usr/bin/systemctl status * |
| 433 | $username ALL=(ALL) NOPASSWD: /usr/bin/pacman -Syu, /usr/bin/pacman -Sy |
| 434 | $username ALL=(ALL) ALL |
| 435 | EOF |
| 436 | log_success "✓ 部分命令无需密码" |
| 437 | ;; |
| 438 | esac |
| 439 | |
| 440 | chmod 440 "$sudoers_file" |
| 441 | |
| 442 | if visudo -c -f "$sudoers_file" >/dev/null 2>&1; then |
| 443 | log_success "✓ sudoers配置验证通过" |
| 444 | else |
| 445 | log_error "✗ sudoers配置错误,已删除" |
| 446 | rm -f "$sudoers_file" |
| 447 | fi |
| 448 | } |
| 449 | |
| 450 | harden_ssh() { |
| 451 | log_section "SSH安全加固" |
| 452 | |
| 453 | local ssh_config="/etc/ssh/sshd_config" |
| 454 | |
| 455 | if [[ ! -f "$ssh_config" ]]; then |
| 456 | log_error "SSH配置文件不存在" |
| 457 | return 1 |
| 458 | fi |
| 459 | |
| 460 | backup_file "$ssh_config" |
| 461 | |
| 462 | # 禁用root登录 |
| 463 | if confirm_action "是否禁用SSH root登录?"; then |
| 464 | sed -i 's/^#*PermitRootLogin.*/PermitRootLogin no/' "$ssh_config" |
| 465 | log_success "✓ 已禁用root登录" |
| 466 | fi |
| 467 | |
| 468 | # 禁用密码认证 |
| 469 | if confirm_action "是否禁用SSH密码认证?"; then |
| 470 | echo -e "${YELLOW}━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━${NC}" |
| 471 | echo -e "${RED}⚠ 警告:禁用密码认证后只能通过SSH密钥登录${NC}" |
| 472 | echo -e "${YELLOW}━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━${NC}" |
| 473 | |
| 474 | if ! verify_ssh_keys_exist; then |
| 475 | log_critical "✗ 未找到SSH密钥!" |
| 476 | if confirm_action "是否现在配置SSH密钥?"; then |
| 477 | manage_users |
| 478 | if ! verify_ssh_keys_exist; then |
| 479 | log_error "密钥配置失败,跳过禁用密码认证" |
| 480 | return 1 |
| 481 | fi |
| 482 | else |
| 483 | log_warning "跳过禁用密码认证" |
| 484 | return 0 |
| 485 | fi |
| 486 | fi |
| 487 | |
| 488 | if confirm_action "确认禁用密码认证?"; then |
| 489 | sed -i 's/^#*PasswordAuthentication.*/PasswordAuthentication no/' "$ssh_config" |
| 490 | log_success "✓ 已禁用密码认证" |
| 491 | fi |
| 492 | fi |
| 493 | |
| 494 | # 修改SSH端口 |
| 495 | local new_port="" |
| 496 | if [[ -t 0 ]]; then |
| 497 | echo -e "${YELLOW}当前SSH端口: $(grep "^Port" "$ssh_config" 2>/dev/null | awk '{print $2}' || echo 22)${NC}" |
| 498 | echo -e "${YELLOW}输入新端口号(1024-65535,留空跳过): ${NC}\c" |
| 499 | read -p "" new_port |
| 500 | |
| 501 | if [[ -n "$new_port" ]]; then |
| 502 | if [[ "$new_port" =~ ^[0-9]+$ ]] && [[ "$new_port" -gt 1024 ]] && [[ "$new_port" -lt 65536 ]]; then |
| 503 | if check_port_available "$new_port"; then |
| 504 | sed -i "s/^#*Port.*/Port $new_port/" "$ssh_config" |
| 505 | log_success "✓ SSH端口已修改为: $new_port" |
| 506 | else |
| 507 | new_port="" |
| 508 | fi |
| 509 | fi |
| 510 | fi |
| 511 | fi |
| 512 | |
| 513 | # 其他安全配置 |
| 514 | cat >> "$ssh_config" << 'EOF' |
| 515 | |
| 516 | # Arch Linux 安全加固配置 |
| 517 | Protocol 2 |
| 518 | MaxAuthTries 3 |
| 519 | MaxSessions 2 |
| 520 | LoginGraceTime 60 |
| 521 | ClientAliveInterval 300 |
| 522 | ClientAliveCountMax 2 |
| 523 | PermitEmptyPasswords no |
| 524 | X11Forwarding no |
| 525 | UseDNS no |
| 526 | EOF |
| 527 | |
| 528 | if ! verify_ssh_config; then |
| 529 | log_critical "✗ SSH配置验证失败!正在回滚..." |
| 530 | local backup_file=$(ls -t "$BACKUP_DIR"/sshd_config*.bak 2>/dev/null | head -1) |
| 531 | if [[ -n "$backup_file" ]]; then |
| 532 | cp -p "$backup_file" "$ssh_config" |
| 533 | log_success "✓ 已回滚" |
| 534 | fi |
| 535 | return 1 |
| 536 | fi |
| 537 | |
| 538 | log_success "✓ SSH配置验证通过" |
| 539 | |
| 540 | if confirm_action "是否立即重启SSH服务?"; then |
| 541 | if systemctl restart sshd; then |
| 542 | log_success "✓ SSH服务重启成功" |
| 543 | fi |
| 544 | fi |
| 545 | } |
| 546 | |
| 547 | # 选择防火墙 |
| 548 | choose_firewall() { |
| 549 | log_section "选择防火墙方案" |
| 550 | |
| 551 | echo -e "${CYAN}Arch Linux 防火墙选项:${NC}" |
| 552 | echo "1) nftables (现代化,推荐,原生内核支持)" |
| 553 | echo "2) firewalld (易用,动态管理)" |
| 554 | echo "3) ufw (简单,兼容iptables-nft)" |
| 555 | echo "4) 跳过防火墙配置" |
| 556 | |
| 557 | if [[ -t 0 ]]; then |
| 558 | echo -e "${YELLOW}选择 [1-4]: ${NC}\c" |
| 559 | read -p "" fw_choice |
| 560 | else |
| 561 | fw_choice="1" |
| 562 | fi |
| 563 | |
| 564 | case $fw_choice in |
| 565 | 1) FIREWALL_CHOICE="nftables" ;; |
| 566 | 2) FIREWALL_CHOICE="firewalld" ;; |
| 567 | 3) FIREWALL_CHOICE="ufw" ;; |
| 568 | 4) FIREWALL_CHOICE="none" ;; |
| 569 | *) FIREWALL_CHOICE="nftables" ;; |
| 570 | esac |
| 571 | |
| 572 | log_info "已选择防火墙: $FIREWALL_CHOICE" |
| 573 | } |
| 574 | |
| 575 | # 配置nftables |
| 576 | configure_nftables() { |
| 577 | log_section "配置nftables防火墙" |
| 578 | |
| 579 | # 安装nftables |
| 580 | if ! command -v nft &> /dev/null; then |
| 581 | safe_execute "pacman -S --noconfirm nftables" "安装nftables" true |
| 582 | fi |
| 583 | |
| 584 | # 获取SSH端口 |
| 585 | local ssh_port=$(grep "^Port" /etc/ssh/sshd_config 2>/dev/null | awk '{print $2}') |
| 586 | ssh_port=${ssh_port:-22} |
| 587 | |
| 588 | log_info "检测到SSH端口: $ssh_port" |
| 589 | |
| 590 | # 创建nftables配置 |
| 591 | local nft_conf="/etc/nftables.conf" |
| 592 | backup_file "$nft_conf" 2>/dev/null || true |
| 593 | |
| 594 | cat > "$nft_conf" << EOF |
| 595 | #!/usr/bin/nft -f |
| 596 | # Arch Linux 防火墙配置 |
| 597 | |
| 598 | # 清空现有规则 |
| 599 | flush ruleset |
| 600 | |
| 601 | # 定义表 |
| 602 | table inet filter { |
| 603 | # 输入链 |
| 604 | chain input { |
| 605 | type filter hook input priority 0; policy drop; |
| 606 | |
| 607 | # 允许环回接口 |
| 608 | iif lo accept |
| 609 | |
| 610 | # 允许已建立的连接 |
| 611 | ct state established,related accept |
| 612 | |
| 613 | # 允许SSH |
| 614 | tcp dport $ssh_port accept |
| 615 | |
| 616 | # 允许HTTP/HTTPS (如果需要) |
| 617 | # tcp dport { 80, 443 } accept |
| 618 | |
| 619 | # 允许ICMP (ping) |
| 620 | icmp type echo-request limit rate 1/second accept |
| 621 | icmpv6 type { echo-request, nd-neighbor-solicit, nd-router-advert, nd-neighbor-advert } accept |
| 622 | |
| 623 | # 记录并拒绝其他流量 |
| 624 | counter drop |
| 625 | } |
| 626 | |
| 627 | # 转发链 |
| 628 | chain forward { |
| 629 | type filter hook forward priority 0; policy drop; |
| 630 | } |
| 631 | |
| 632 | # 输出链 |
| 633 | chain output { |
| 634 | type filter hook output priority 0; policy accept; |
| 635 | } |
| 636 | } |
| 637 | EOF |
| 638 | |
| 639 | # 询问是否开放其他端口 |
| 640 | if confirm_action "是否开放HTTP(80)?"; then |
| 641 | safe_execute "ufw allow 80/tcp comment 'HTTP'" "开放HTTP" false |
| 642 | fi |
| 643 | |
| 644 | if confirm_action "是否开放HTTPS(443)?"; then |
| 645 | safe_execute "ufw allow 443/tcp comment 'HTTPS'" "开放HTTPS" false |
| 646 | fi |
| 647 | |
| 648 | echo "y" | ufw enable >> "$LOG_FILE" 2>&1 |
| 649 | |
| 650 | ufw status verbose |
| 651 | |
| 652 | # 启用UFW服务 |
| 653 | safe_execute "systemctl enable --now ufw.service" "启用UFW服务" false |
| 654 | |
| 655 | log_success "✓ UFW配置完成" |
| 656 | } |
| 657 | |
| 658 | # 防火墙配置入口 |
| 659 | configure_firewall() { |
| 660 | choose_firewall |
| 661 | |
| 662 | case $FIREWALL_CHOICE in |
| 663 | "nftables") |
| 664 | configure_nftables |
| 665 | ;; |
| 666 | "firewalld") |
| 667 | configure_firewalld |
| 668 | ;; |
| 669 | "ufw") |
| 670 | configure_ufw |
| 671 | ;; |
| 672 | "none") |
| 673 | log_warning "跳过防火墙配置" |
| 674 | ;; |
| 675 | esac |
| 676 | } |
| 677 | |
| 678 | # 选择入侵防护系统 |
| 679 | choose_ips() { |
| 680 | log_section "选择入侵防护系统" |
| 681 | |
| 682 | echo -e "${CYAN}Arch Linux IPS选项:${NC}" |
| 683 | echo "1) sshguard (轻量级,原生nftables/firewalld支持,推荐)" |
| 684 | echo "2) crowdsec (现代化,社区驱动,支持多种防火墙)" |
| 685 | echo "3) fail2ban (传统,功能全面)" |
| 686 | echo "4) 跳过" |
| 687 | |
| 688 | if [[ -t 0 ]]; then |
| 689 | echo -e "${YELLOW}选择 [1-4]: ${NC}\c" |
| 690 | read -p "" ips_choice |
| 691 | else |
| 692 | ips_choice="1" |
| 693 | fi |
| 694 | |
| 695 | case $ips_choice in |
| 696 | 1) IPS_CHOICE="sshguard" ;; |
| 697 | 2) IPS_CHOICE="crowdsec" ;; |
| 698 | 3) IPS_CHOICE="fail2ban" ;; |
| 699 | 4) IPS_CHOICE="none" ;; |
| 700 | *) IPS_CHOICE="sshguard" ;; |
| 701 | esac |
| 702 | |
| 703 | log_info "已选择IPS: $IPS_CHOICE" |
| 704 | } |
| 705 | |
| 706 | # 配置sshguard |
| 707 | configure_sshguard() { |
| 708 | log_section "配置SSHGuard" |
| 709 | |
| 710 | if ! command -v sshguard &> /dev/null; then |
| 711 | safe_execute "pacman -S --noconfirm sshguard" "安装sshguard" false |
| 712 | fi |
| 713 | |
| 714 | # 根据防火墙选择后端 |
| 715 | local backend="" |
| 716 | case $FIREWALL_CHOICE in |
| 717 | "nftables") |
| 718 | backend="nftables" |
| 719 | ;; |
| 720 | "firewalld") |
| 721 | backend="firewalld" |
| 722 | ;; |
| 723 | "ufw") |
| 724 | backend="iptables" |
| 725 | ;; |
| 726 | *) |
| 727 | backend="nftables" |
| 728 | ;; |
| 729 | esac |
| 730 | |
| 731 | log_info "使用防火墙后端: $backend" |
| 732 | |
| 733 | # 创建配置 |
| 734 | local sshguard_conf="/etc/sshguard.conf" |
| 735 | backup_file "$sshguard_conf" 2>/dev/null || true |
| 736 | |
| 737 | cat > "$sshguard_conf" << EOF |
| 738 | # SSHGuard 配置 |
| 739 | BACKEND="$backend" |
| 740 | LOGREADER="LANG=C /usr/bin/journalctl -afb -p info -n1 -t sshd -o cat" |
| 741 | THRESHOLD=30 |
| 742 | BLOCK_TIME=3600 |
| 743 | DETECTION_TIME=600 |
| 744 | EOF |
| 745 | |
| 746 | # 启用服务 |
| 747 | safe_execute "systemctl enable --now sshguard.service" "启用sshguard" false |
| 748 | |
| 749 | # 检查状态 |
| 750 | systemctl status sshguard.service --no-pager || true |
| 751 | |
| 752 | log_success "✓ SSHGuard配置完成" |
| 753 | } |
| 754 | |
| 755 | # 配置CrowdSec |
| 756 | configure_crowdsec() { |
| 757 | log_section "配置CrowdSec" |
| 758 | |
| 759 | log_info "CrowdSec需要从AUR安装..." |
| 760 | |
| 761 | if command -v yay &> /dev/null; then |
| 762 | safe_execute "yay -S --noconfirm crowdsec crowdsec-firewall-bouncer-nftables" "安装CrowdSec" false |
| 763 | elif command -v paru &> /dev/null; then |
| 764 | safe_execute "paru -S --noconfirm crowdsec crowdsec-firewall-bouncer-nftables" "安装CrowdSec" false |
| 765 | else |
| 766 | log_warning "未安装AUR助手,跳过CrowdSec安装" |
| 767 | return 1 |
| 768 | fi |
| 769 | |
| 770 | # 启用服务 |
| 771 | safe_execute "systemctl enable --now crowdsec.service" "启用CrowdSec" false |
| 772 | |
| 773 | # 配置bouncer |
| 774 | safe_execute "cscli bouncers add firewall-bouncer" "添加防火墙bouncer" false |
| 775 | |
| 776 | log_success "✓ CrowdSec配置完成" |
| 777 | } |
| 778 | |
| 779 | # 配置Fail2ban |
| 780 | configure_fail2ban() { |
| 781 | log_section "配置Fail2ban" |
| 782 | |
| 783 | if ! command -v fail2ban-client &> /dev/null; then |
| 784 | safe_execute "pacman -S --noconfirm fail2ban" "安装fail2ban" false |
| 785 | fi |
| 786 | |
| 787 | local ssh_port=$(grep "^Port" /etc/ssh/sshd_config 2>/dev/null | awk '{print $2}') |
| 788 | ssh_port=${ssh_port:-ssh} |
| 789 | |
| 790 | local jail_local="/etc/fail2ban/jail.local" |
| 791 | backup_file "$jail_local" 2>/dev/null || true |
| 792 | |
| 793 | cat > "$jail_local" << EOF |
| 794 | [DEFAULT] |
| 795 | bantime = 3600 |
| 796 | findtime = 600 |
| 797 | maxretry = 5 |
| 798 | |
| 799 | [sshd] |
| 800 | enabled = true |
| 801 | port = $ssh_port |
| 802 | filter = sshd |
| 803 | backend = systemd |
| 804 | maxretry = 3 |
| 805 | bantime = 7200 |
| 806 | EOF |
| 807 | |
| 808 | safe_execute "systemctl enable --now fail2ban.service" "启用fail2ban" false |
| 809 | |
| 810 | sleep 2 |
| 811 | fail2ban-client status 2>/dev/null || true |
| 812 | |
| 813 | log_success "✓ Fail2ban配置完成" |
| 814 | } |
| 815 | |
| 816 | # IPS配置入口 |
| 817 | configure_ips() { |
| 818 | choose_ips |
| 819 | |
| 820 | case $IPS_CHOICE in |
| 821 | "sshguard") |
| 822 | configure_sshguard |
| 823 | ;; |
| 824 | "crowdsec") |
| 825 | configure_crowdsec |
| 826 | ;; |
| 827 | "fail2ban") |
| 828 | configure_fail2ban |
| 829 | ;; |
| 830 | "none") |
| 831 | log_warning "跳过IPS配置" |
| 832 | ;; |
| 833 | esac |
| 834 | } |
| 835 | |
| 836 | # 密码策略 |
| 837 | harden_users() { |
| 838 | log_section "密码策略加固" |
| 839 | |
| 840 | # 安装pam_pwquality |
| 841 | if ! pacman -Q libpwquality &> /dev/null; then |
| 842 | safe_execute "pacman -S --noconfirm libpwquality" "安装密码质量检查" false |
| 843 | fi |
| 844 | |
| 845 | local pwquality_conf="/etc/security/pwquality.conf" |
| 846 | backup_file "$pwquality_conf" |
| 847 | |
| 848 | cat >> "$pwquality_conf" << 'EOF' |
| 849 | |
| 850 | # Arch Linux 密码策略 |
| 851 | minlen = 12 |
| 852 | dcredit = -1 |
| 853 | ucredit = -1 |
| 854 | lcredit = -1 |
| 855 | ocredit = -1 |
| 856 | maxrepeat = 3 |
| 857 | EOF |
| 858 | |
| 859 | # 配置PAM |
| 860 | local common_password="/etc/pam.d/passwd" |
| 861 | if [[ -f "$common_password" ]]; then |
| 862 | backup_file "$common_password" |
| 863 | |
| 864 | if ! grep -q "pam_pwquality.so" "$common_password"; then |
| 865 | sed -i '/password.*required/a password required pam_pwquality.so retry=3' "$common_password" |
| 866 | fi |
| 867 | fi |
| 868 | |
| 869 | log_success "✓ 密码策略配置完成" |
| 870 | } |
| 871 | |
| 872 | # 内核参数加固 |
| 873 | harden_kernel() { |
| 874 | log_section "内核参数加固" |
| 875 | |
| 876 | local sysctl_conf="/etc/sysctl.d/99-security.conf" |
| 877 | backup_file "$sysctl_conf" 2>/dev/null || true |
| 878 | |
| 879 | cat > "$sysctl_conf" << 'EOF' |
| 880 | # Arch Linux 内核安全配置 |
| 881 | net.ipv4.ip_forward = 0 |
| 882 | net.ipv6.conf.all.forwarding = 0 |
| 883 | net.ipv4.tcp_syncookies = 1 |
| 884 | net.ipv4.conf.all.accept_redirects = 0 |
| 885 | net.ipv6.conf.all.accept_redirects = 0 |
| 886 | net.ipv4.conf.default.accept_redirects = 0 |
| 887 | net.ipv6.conf.default.accept_redirects = 0 |
| 888 | net.ipv4.conf.all.secure_redirects = 0 |
| 889 | net.ipv4.conf.default.secure_redirects = 0 |
| 890 | net.ipv4.conf.all.accept_source_route = 0 |
| 891 | net.ipv6.conf.all.accept_source_route = 0 |
| 892 | net.ipv4.conf.default.accept_source_route = 0 |
| 893 | net.ipv6.conf.default.accept_source_route = 0 |
| 894 | net.ipv4.conf.all.log_martians = 1 |
| 895 | net.ipv4.conf.default.log_martians = 1 |
| 896 | net.ipv4.icmp_echo_ignore_broadcasts = 1 |
| 897 | net.ipv4.conf.all.rp_filter = 1 |
| 898 | net.ipv4.conf.default.rp_filter = 1 |
| 899 | net.ipv4.tcp_max_syn_backlog = 2048 |
| 900 | net.ipv4.tcp_synack_retries = 2 |
| 901 | net.ipv4.tcp_syn_retries = 5 |
| 902 | kernel.dmesg_restrict = 1 |
| 903 | kernel.kptr_restrict = 2 |
| 904 | EOF |
| 905 | |
| 906 | safe_execute "sysctl -p '$sysctl_conf'" "应用内核参数" false |
| 907 | |
| 908 | log_success "✓ 内核参数配置完成" |
| 909 | } |
| 910 | |
| 911 | # 安装安全工具 |
| 912 | install_security_tools() { |
| 913 | log_section "安装安全工具" |
| 914 | |
| 915 | local tools=( |
| 916 | "rkhunter:Rootkit检测" |
| 917 | "lynis:安全审计" |
| 918 | "aide:文件完整性检查" |
| 919 | "logwatch:日志分析" |
| 920 | ) |
| 921 | |
| 922 | for tool_desc in "${tools[@]}"; do |
| 923 | local tool="${tool_desc%:*}" |
| 924 | local desc="${tool_desc#*:}" |
| 925 | |
| 926 | if confirm_action "是否安装 $tool ($desc)?"; then |
| 927 | if pacman -Q "$tool" &> /dev/null; then |
| 928 | log_info "$tool 已安装" |
| 929 | continue |
| 930 | fi |
| 931 | |
| 932 | safe_execute "pacman -S --noconfirm '$tool'" "安装 $tool" false |
| 933 | |
| 934 | # 特殊配置 |
| 935 | case "$tool" in |
| 936 | "rkhunter") |
| 937 | safe_execute "rkhunter --update" "更新rkhunter" false |
| 938 | safe_execute "rkhunter --propupd" "更新属性" false |
| 939 | ;; |
| 940 | esac |
| 941 | fi |
| 942 | done |
| 943 | } |
| 944 | |
| 945 | # 配置自动更新 |
| 946 | configure_auto_updates() { |
| 947 | log_section "配置自动更新" |
| 948 | |
| 949 | echo -e "${CYAN}Arch Linux 自动更新选项:${NC}" |
| 950 | echo "1) 使用systemd timer (推荐)" |
| 951 | echo "2) 跳过" |
| 952 | |
| 953 | if [[ -t 0 ]]; then |
| 954 | echo -e "${YELLOW}选择 [1-2]: ${NC}\c" |
| 955 | read -p "" update_choice |
| 956 | else |
| 957 | update_choice="2" |
| 958 | fi |
| 959 | |
| 960 | if [[ "$update_choice" == "1" ]]; then |
| 961 | # 创建更新脚本 |
| 962 | cat > /usr/local/bin/arch-auto-update.sh << 'EOF' |
| 963 | #!/bin/bash |
| 964 | /usr/bin/pacman -Syu --noconfirm >> /var/log/arch-auto-update.log 2>&1 |
| 965 | EOF |
| 966 | chmod +x /usr/local/bin/arch-auto-update.sh |
| 967 | |
| 968 | # 创建systemd service |
| 969 | cat > /etc/systemd/system/arch-auto-update.service << 'EOF' |
| 970 | [Unit] |
| 971 | Description=Arch Linux Auto Update |
| 972 | After=network-online.target |
| 973 | |
| 974 | [Service] |
| 975 | Type=oneshot |
| 976 | ExecStart=/usr/local/bin/arch-auto-update.sh |
| 977 | EOF |
| 978 | |
| 979 | # 创建systemd timer |
| 980 | cat > /etc/systemd/system/arch-auto-update.timer << 'EOF' |
| 981 | [Unit] |
| 982 | Description=Arch Linux Auto Update Timer |
| 983 | |
| 984 | [Timer] |
| 985 | OnCalendar=daily |
| 986 | Persistent=true |
| 987 | |
| 988 | [Install] |
| 989 | WantedBy=timers.target |
| 990 | EOF |
| 991 | |
| 992 | safe_execute "systemctl enable --now arch-auto-update.timer" "启用自动更新" false |
| 993 | |
| 994 | log_success "✓ 自动更新配置完成(每天执行)" |
| 995 | else |
| 996 | log_warning "跳过自动更新配置" |
| 997 | fi |
| 998 | } |
| 999 | |
| 1000 | # 生成报告 |
| 1001 | generate_report() { |
| 1002 | log_section "生成安全报告" |
| 1003 | |
| 1004 | local report_file="/root/arch_hardening_report_$(date +%Y%m%d_%H%M%S).txt" |
| 1005 | |
| 1006 | cat > "$report_file" << EOF |
| 1007 | ================================================================================ |
| 1008 | Arch Linux 服务器安全加固报告 v2.0 |
| 1009 | ================================================================================ |
| 1010 | 生成时间: $(date) |
| 1011 | 主机名: $(hostname) |
| 1012 | 内核版本: $(uname -r) |
| 1013 | 包管理器: $(pacman --version | head -1) |
| 1014 | |
| 1015 | -------------------------------------------------------------------------------- |
| 1016 | 1. SSH配置 |
| 1017 | -------------------------------------------------------------------------------- |
| 1018 | $(grep -E "^(Port|PermitRootLogin|PasswordAuthentication)" /etc/ssh/sshd_config 2>/dev/null) |
| 1019 | |
| 1020 | -------------------------------------------------------------------------------- |
| 1021 | 2. 防火墙: $FIREWALL_CHOICE |
| 1022 | -------------------------------------------------------------------------------- |
| 1023 | $(case $FIREWALL_CHOICE in |
| 1024 | "nftables") nft list ruleset 2>/dev/null ;; |
| 1025 | "firewalld") firewall-cmd --list-all 2>/dev/null ;; |
| 1026 | "ufw") ufw status verbose 2>/dev/null ;; |
| 1027 | *) echo "未配置" ;; |
| 1028 | esac) |
| 1029 | |
| 1030 | -------------------------------------------------------------------------------- |
| 1031 | 3. 入侵防护: $IPS_CHOICE |
| 1032 | -------------------------------------------------------------------------------- |
| 1033 | $(case $IPS_CHOICE in |
| 1034 | "sshguard") systemctl status sshguard --no-pager 2>/dev/null || echo "未运行" ;; |
| 1035 | "crowdsec") cscli metrics 2>/dev/null || echo "未运行" ;; |
| 1036 | "fail2ban") fail2ban-client status 2>/dev/null || echo "未运行" ;; |
| 1037 | *) echo "未配置" ;; |
| 1038 | esac) |
| 1039 | |
| 1040 | -------------------------------------------------------------------------------- |
| 1041 | 4. SSH密钥配置 |
| 1042 | -------------------------------------------------------------------------------- |
| 1043 | $(for home in /home/* /root; do |
| 1044 | if [[ -f "$home/.ssh/authorized_keys" ]]; then |
| 1045 | echo "$(basename $home): $(grep -c '^ssh-' "$home/.ssh/authorized_keys" 2>/dev/null || echo 0) 个密钥" |
| 1046 | fi |
| 1047 | done) |
| 1048 | |
| 1049 | -------------------------------------------------------------------------------- |
| 1050 | 备份目录: $BACKUP_DIR |
| 1051 | 日志文件: $LOG_FILE |
| 1052 | 回滚脚本: $ROLLBACK_SCRIPT |
| 1053 | -------------------------------------------------------------------------------- |
| 1054 | EOF |
| 1055 | |
| 1056 | log_success "✓ 报告已生成: $report_file" |
| 1057 | |
| 1058 | echo -e "${CYAN}━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━${NC}" |
| 1059 | cat "$report_file" |
| 1060 | echo -e "${CYAN}━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━${NC}" |
| 1061 | } |
| 1062 | |
| 1063 | ################################################################################ |
| 1064 | # 主程序 |
| 1065 | ################################################################################ |
| 1066 | |
| 1067 | main() { |
| 1068 | clear |
| 1069 | |
| 1070 | echo -e "${BLUE}" |
| 1071 | cat << "EOF" |
| 1072 | ╔══════════════════════════════════════════════════════════════╗ |
| 1073 | ║ Arch Linux 服务器安全加固脚本 v2.0 ║ |
| 1074 | ║ 针对Arch Linux优化的现代化工具选择 ║ |
| 1075 | ╚══════════════════════════════════════════════════════════════╝ |
| 1076 | |
| 1077 | 防火墙选项: |
| 1078 | • nftables (现代化,推荐) |
| 1079 | • firewalld (易用) |
| 1080 | • ufw (简单) |
| 1081 | |
| 1082 | 入侵防护: |
| 1083 | • sshguard (轻量,原生支持nftables/firewalld) |
| 1084 | • crowdsec (社区驱动,现代化) |
| 1085 | • fail2ban (传统,功能全面) |
| 1086 | |
| 1087 | EOF |
| 1088 | echo -e "${NC}" |
| 1089 | |
| 1090 | check_root |
| 1091 | check_arch_linux |
| 1092 | |
| 1093 | # 初始化 |
| 1094 | mkdir -p "$BACKUP_DIR" |
| 1095 | echo "#!/bin/bash" > "$ROLLBACK_SCRIPT" |
| 1096 | echo "# Arch Linux 回滚脚本 - $(date)" >> "$ROLLBACK_SCRIPT" |
| 1097 | chmod +x "$ROLLBACK_SCRIPT" |
| 1098 | |
| 1099 | log "开始Arch Linux安全加固..." |
| 1100 | log_info "日志: $LOG_FILE" |
| 1101 | log_info "备份: $BACKUP_DIR" |
| 1102 | |
| 1103 | if ! confirm_action "确认继续?"; then |
| 1104 | exit 0 |
| 1105 | fi |
| 1106 | |
| 1107 | # 执行加固 |
| 1108 | collect_system_info |
| 1109 | install_aur_helper |
| 1110 | update_system |
| 1111 | manage_users |
| 1112 | harden_ssh |
| 1113 | configure_firewall |
| 1114 | configure_ips |
| 1115 | harden_users |
| 1116 | harden_kernel |
| 1117 | install_security_tools |
| 1118 | configure_auto_updates |
| 1119 | generate_report |
| 1120 | |
| 1121 | log_section "加固完成" |
| 1122 | |
| 1123 | echo -e "${GREEN}" |
| 1124 | cat << EOF |
| 1125 | ╔══════════════════════════════════════════════════════════════╗ |
| 1126 | ║ Arch Linux 加固完成! ║ |
| 1127 | ╚══════════════════════════════════════════════════════════════╝ |
| 1128 | |
| 1129 | 配置信息: |
| 1130 | 防火墙: $FIREWALL_CHOICE |
| 1131 | 入侵防护: $IPS_CHOICE |
| 1132 | 备份: $BACKUP_DIR |
| 1133 | 日志: $LOG_FILE |
| 1134 | |
| 1135 | 下一步: |
| 1136 | 1. 测试SSH登录(不要断开当前连接) |
| 1137 | 2. 检查防火墙: nft list ruleset / firewall-cmd --list-all / ufw status |
| 1138 | 3. 检查服务: systemctl status sshd $FIREWALL_CHOICE $IPS_CHOICE |
| 1139 | 4. 查看日志: journalctl -xe |
| 1140 | |
| 1141 | EOF |
| 1142 | echo -e "${NC}" |
| 1143 | |
| 1144 | if confirm_action "是否现在重启?"; then |
| 1145 | log "5秒后重启..." |
| 1146 | sleep 5 |
| 1147 | reboot |
| 1148 | fi |
| 1149 | } |
| 1150 | |
| 1151 | main "$@")端口?"; then |
| 1152 | sed -i 's/# tcp dport { 80, 443 } accept/tcp dport 80 accept/' "$nft_conf" |
| 1153 | fi |
| 1154 | |
| 1155 | if confirm_action "是否开放HTTPS(443)端口?"; then |
| 1156 | sed -i '/tcp dport 80 accept/a\ tcp dport 443 accept' "$nft_conf" |
| 1157 | fi |
| 1158 | |
| 1159 | # 测试配置 |
| 1160 | if nft -c -f "$nft_conf" 2>&1 | tee -a "$LOG_FILE"; then |
| 1161 | log_success "✓ nftables配置验证通过" |
| 1162 | |
| 1163 | # 应用规则 |
| 1164 | safe_execute "nft -f '$nft_conf'" "应用nftables规则" true |
| 1165 | |
| 1166 | # 启用服务 |
| 1167 | safe_execute "systemctl enable nftables.service" "启用nftables服务" false |
| 1168 | safe_execute "systemctl start nftables.service" "启动nftables服务" false |
| 1169 | |
| 1170 | # 显示当前规则 |
| 1171 | echo -e "${CYAN}━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━${NC}" |
| 1172 | nft list ruleset |
| 1173 | echo -e "${CYAN}━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━${NC}" |
| 1174 | |
| 1175 | log_success "✓ nftables配置完成" |
| 1176 | else |
| 1177 | log_error "✗ nftables配置验证失败" |
| 1178 | fi |
| 1179 | } |
| 1180 | |
| 1181 | # 配置firewalld |
| 1182 | configure_firewalld() { |
| 1183 | log_section "配置firewalld防火墙" |
| 1184 | |
| 1185 | if ! command -v firewall-cmd &> /dev/null; then |
| 1186 | safe_execute "pacman -S --noconfirm firewalld" "安装firewalld" true |
| 1187 | fi |
| 1188 | |
| 1189 | # 启动服务 |
| 1190 | safe_execute "systemctl enable --now firewalld" "启用firewalld" true |
| 1191 | |
| 1192 | # 获取SSH端口 |
| 1193 | local ssh_port=$(grep "^Port" /etc/ssh/sshd_config 2>/dev/null | awk '{print $2}') |
| 1194 | ssh_port=${ssh_port:-22} |
| 1195 | |
| 1196 | # 配置规则 |
| 1197 | safe_execute "firewall-cmd --permanent --add-port=$ssh_port/tcp" "开放SSH端口" true |
| 1198 | |
| 1199 | if confirm_action "是否开放HTTP(80)?"; then |
| 1200 | safe_execute "firewall-cmd --permanent --add-service=http" "开放HTTP" false |
| 1201 | fi |
| 1202 | |
| 1203 | if confirm_action "是否开放HTTPS(443)?"; then |
| 1204 | safe_execute "firewall-cmd --permanent --add-service=https" "开放HTTPS" false |
| 1205 | fi |
| 1206 | |
| 1207 | # 重载配置 |
| 1208 | safe_execute "firewall-cmd --reload" "重载firewall配置" false |
| 1209 | |
| 1210 | # 显示状态 |
| 1211 | firewall-cmd --list-all |
| 1212 | |
| 1213 | log_success "✓ firewalld配置完成" |
| 1214 | } |
| 1215 | |
| 1216 | # 配置UFW |
| 1217 | configure_ufw() { |
| 1218 | log_section "配置UFW防火墙" |
| 1219 | |
| 1220 | # 安装UFW和iptables-nft |
| 1221 | if ! command -v ufw &> /dev/null; then |
| 1222 | safe_execute "pacman -S --noconfirm ufw iptables-nft" "安装UFW" true |
| 1223 | fi |
| 1224 | |
| 1225 | local ssh_port=$(grep "^Port" /etc/ssh/sshd_config 2>/dev/null | awk '{print $2}') |
| 1226 | ssh_port=${ssh_port:-22} |
| 1227 | |
| 1228 | safe_execute "ufw default deny incoming" "设置默认策略" false |
| 1229 | safe_execute "ufw default allow outgoing" "设置默认策略" false |
| 1230 | safe_execute "ufw allow $ssh_port/tcp comment 'SSH'" "开放SSH" true |
| 1231 | |
| 1232 | if confirm_action "是否开放HTTP(80 |