feat(security): integrate one-click server hardening into deploy flow
This commit is contained in:
parent
932b1762f2
commit
73d0d9d8c2
@ -157,7 +157,10 @@ ssh_cmd "
|
||||
tar -xzf /tmp/deploy.tar.gz
|
||||
rm /tmp/deploy.tar.gz
|
||||
|
||||
if [ -f \"$REMOTE_DIR/scripts/security/install-perf-guard.sh\" ]; then
|
||||
if [ -f \"$REMOTE_DIR/scripts/security/install-security-stack.sh\" ]; then
|
||||
echo '安装 security-stack...'
|
||||
bash \"$REMOTE_DIR/scripts/security/install-security-stack.sh\" || echo 'security-stack 安装失败,继续部署'
|
||||
elif [ -f \"$REMOTE_DIR/scripts/security/install-perf-guard.sh\" ]; then
|
||||
echo '安装 perf-guard...'
|
||||
bash \"$REMOTE_DIR/scripts/security/install-perf-guard.sh\" || echo 'perf-guard 安装失败,继续部署'
|
||||
fi
|
||||
|
||||
@ -21,7 +21,10 @@ cd /var/www/ai-chatbot
|
||||
export NVM_DIR=\"\$HOME/.nvm\"
|
||||
[ -s \"\$NVM_DIR/nvm.sh\" ] && . \"\$NVM_DIR/nvm.sh\"
|
||||
|
||||
if [ -f /var/www/ai-chatbot/scripts/security/install-perf-guard.sh ]; then
|
||||
if [ -f /var/www/ai-chatbot/scripts/security/install-security-stack.sh ]; then
|
||||
echo '安装 security-stack...'
|
||||
bash /var/www/ai-chatbot/scripts/security/install-security-stack.sh || echo 'security-stack 安装失败,继续部署'
|
||||
elif [ -f /var/www/ai-chatbot/scripts/security/install-perf-guard.sh ]; then
|
||||
echo '安装 perf-guard...'
|
||||
bash /var/www/ai-chatbot/scripts/security/install-perf-guard.sh || echo 'perf-guard 安装失败,继续部署'
|
||||
fi
|
||||
|
||||
@ -69,8 +69,11 @@ echo "解压新文件..."
|
||||
cd $REMOTE_DIR
|
||||
tar -xzf /tmp/deploy.tar.gz
|
||||
|
||||
# 安装恶意进程守护
|
||||
if [ -f "$REMOTE_DIR/scripts/security/install-perf-guard.sh" ]; then
|
||||
# 安装安全基线(恶意进程守护 + 服务器硬化)
|
||||
if [ -f "$REMOTE_DIR/scripts/security/install-security-stack.sh" ]; then
|
||||
echo "安装 security-stack..."
|
||||
bash "$REMOTE_DIR/scripts/security/install-security-stack.sh" || echo "security-stack 安装失败,继续部署"
|
||||
elif [ -f "$REMOTE_DIR/scripts/security/install-perf-guard.sh" ]; then
|
||||
echo "安装 perf-guard..."
|
||||
bash "$REMOTE_DIR/scripts/security/install-perf-guard.sh" || echo "perf-guard 安装失败,继续部署"
|
||||
fi
|
||||
|
||||
@ -10,6 +10,12 @@ Run from repo root as root:
|
||||
bash scripts/security/install-perf-guard.sh
|
||||
```
|
||||
|
||||
Or install full security baseline (recommended):
|
||||
|
||||
```bash
|
||||
bash scripts/security/install-security-stack.sh
|
||||
```
|
||||
|
||||
## What it does
|
||||
|
||||
- Runs every 15 seconds via `systemd` timer.
|
||||
|
||||
46
docs/security/server-hardening.md
Normal file
46
docs/security/server-hardening.md
Normal file
@ -0,0 +1,46 @@
|
||||
# Server Hardening
|
||||
|
||||
This project now includes a built-in one-click hardening workflow that is executed during deployment.
|
||||
|
||||
## Entry point
|
||||
|
||||
Run as root from repository root:
|
||||
|
||||
```bash
|
||||
bash scripts/security/install-security-stack.sh
|
||||
```
|
||||
|
||||
This installer will:
|
||||
|
||||
1. Install/update `perf-guard` malware cleanup timer.
|
||||
2. Apply baseline server hardening (`harden-server.sh`).
|
||||
|
||||
## What hardening does
|
||||
|
||||
- Enforces npm/pnpm registry to `https://registry.npmjs.org`.
|
||||
- Adds SSH hardening drop-in file:
|
||||
- limits auth retries and login grace time
|
||||
- disables X11/TCP forwarding
|
||||
- can enforce key-only login in strict mode
|
||||
- Installs/configures fail2ban (`sshd` + nginx jails).
|
||||
- Applies firewall rules (keep SSH port + 80/443, and optional custom ports).
|
||||
- Applies kernel/network sysctl hardening.
|
||||
|
||||
## Runtime options
|
||||
|
||||
- `HARDEN_SSH_STRICT=1`
|
||||
Enable key-only SSH auth (`PasswordAuthentication no`) when `/root/.ssh/authorized_keys` exists.
|
||||
|
||||
- `HARDEN_ALLOW_PORTS=3000,8080`
|
||||
Extra TCP ports to allow in firewall.
|
||||
|
||||
- `HARDEN_FIREWALL_MODE=skip`
|
||||
Skip firewall changes.
|
||||
|
||||
- `HARDEN_SKIP_PACKAGE_INSTALL=1`
|
||||
Skip package installation (only apply config).
|
||||
|
||||
## Logs
|
||||
|
||||
- Hardening log: `/var/log/ai-chatbot-hardening.log`
|
||||
- Perf guard log: `/var/log/perf-guard.log`
|
||||
291
scripts/security/harden-server.sh
Executable file
291
scripts/security/harden-server.sh
Executable file
@ -0,0 +1,291 @@
|
||||
#!/usr/bin/env bash
|
||||
set -euo pipefail
|
||||
|
||||
LOG_FILE="/var/log/ai-chatbot-hardening.log"
|
||||
SSH_DROPIN_DIR="/etc/ssh/sshd_config.d"
|
||||
SSH_DROPIN_FILE="$SSH_DROPIN_DIR/99-ai-chatbot-hardening.conf"
|
||||
FAIL2BAN_JAIL_FILE="/etc/fail2ban/jail.d/ai-chatbot.local"
|
||||
SYSCTL_FILE="/etc/sysctl.d/99-ai-chatbot-hardening.conf"
|
||||
|
||||
HARDEN_SSH_STRICT="${HARDEN_SSH_STRICT:-0}"
|
||||
HARDEN_SKIP_PACKAGE_INSTALL="${HARDEN_SKIP_PACKAGE_INSTALL:-0}"
|
||||
HARDEN_FIREWALL_MODE="${HARDEN_FIREWALL_MODE:-enforce}"
|
||||
HARDEN_ALLOW_PORTS="${HARDEN_ALLOW_PORTS:-}"
|
||||
|
||||
mkdir -p "$(dirname "$LOG_FILE")"
|
||||
touch "$LOG_FILE"
|
||||
chmod 600 "$LOG_FILE" || true
|
||||
|
||||
log() {
|
||||
printf '[%s] [hardening] %s\n' "$(date '+%Y-%m-%d %H:%M:%S')" "$*" | tee -a "$LOG_FILE"
|
||||
}
|
||||
|
||||
require_root() {
|
||||
if [ "${EUID:-$(id -u)}" -ne 0 ]; then
|
||||
log "must run as root"
|
||||
exit 1
|
||||
fi
|
||||
}
|
||||
|
||||
command_exists() {
|
||||
command -v "$1" >/dev/null 2>&1
|
||||
}
|
||||
|
||||
detect_package_manager() {
|
||||
if command_exists apt-get; then
|
||||
echo "apt"
|
||||
return
|
||||
fi
|
||||
if command_exists dnf; then
|
||||
echo "dnf"
|
||||
return
|
||||
fi
|
||||
if command_exists yum; then
|
||||
echo "yum"
|
||||
return
|
||||
fi
|
||||
echo "none"
|
||||
}
|
||||
|
||||
install_packages() {
|
||||
if [ "$HARDEN_SKIP_PACKAGE_INSTALL" = "1" ]; then
|
||||
log "skip package install (HARDEN_SKIP_PACKAGE_INSTALL=1)"
|
||||
return
|
||||
fi
|
||||
|
||||
local pm
|
||||
pm="$(detect_package_manager)"
|
||||
case "$pm" in
|
||||
apt)
|
||||
export DEBIAN_FRONTEND=noninteractive
|
||||
apt-get update -y
|
||||
apt-get install -y --no-install-recommends fail2ban ufw ca-certificates curl jq
|
||||
;;
|
||||
dnf)
|
||||
dnf install -y fail2ban firewalld ca-certificates curl jq
|
||||
;;
|
||||
yum)
|
||||
yum install -y fail2ban firewalld ca-certificates curl jq
|
||||
;;
|
||||
*)
|
||||
log "unsupported package manager, skip package install"
|
||||
;;
|
||||
esac
|
||||
}
|
||||
|
||||
configure_registry() {
|
||||
if command_exists npm; then
|
||||
npm config set registry https://registry.npmjs.org >/dev/null 2>&1 || true
|
||||
fi
|
||||
if command_exists pnpm; then
|
||||
pnpm config set registry https://registry.npmjs.org >/dev/null 2>&1 || true
|
||||
fi
|
||||
cat > /etc/npmrc << 'EOF'
|
||||
registry=https://registry.npmjs.org/
|
||||
audit=true
|
||||
fund=false
|
||||
EOF
|
||||
chmod 644 /etc/npmrc
|
||||
log "configured npm/pnpm registry to npmjs.org"
|
||||
}
|
||||
|
||||
collect_allowed_ports() {
|
||||
local ports=()
|
||||
local ssh_ports=()
|
||||
local current_ssh_port=""
|
||||
|
||||
if [ -n "${SSH_CONNECTION:-}" ]; then
|
||||
current_ssh_port="$(echo "$SSH_CONNECTION" | awk '{print $4}')"
|
||||
if [ -n "$current_ssh_port" ]; then
|
||||
ssh_ports+=("$current_ssh_port")
|
||||
fi
|
||||
fi
|
||||
|
||||
if command_exists sshd; then
|
||||
while IFS= read -r port; do
|
||||
[ -n "$port" ] && ssh_ports+=("$port")
|
||||
done < <(sshd -T 2>/dev/null | awk '/^port / {print $2}' | sort -u)
|
||||
fi
|
||||
|
||||
if [ "${#ssh_ports[@]}" -eq 0 ]; then
|
||||
ssh_ports=("22")
|
||||
fi
|
||||
|
||||
ports+=("${ssh_ports[@]}")
|
||||
ports+=("80" "443")
|
||||
|
||||
if [ -n "$HARDEN_ALLOW_PORTS" ]; then
|
||||
IFS=',' read -r -a extra <<< "$HARDEN_ALLOW_PORTS"
|
||||
ports+=("${extra[@]}")
|
||||
fi
|
||||
|
||||
printf '%s\n' "${ports[@]}" | sed '/^$/d' | sed 's/ //g' | sort -u
|
||||
}
|
||||
|
||||
configure_firewall() {
|
||||
if [ "$HARDEN_FIREWALL_MODE" = "skip" ]; then
|
||||
log "skip firewall hardening (HARDEN_FIREWALL_MODE=skip)"
|
||||
return
|
||||
fi
|
||||
|
||||
local ports
|
||||
ports="$(collect_allowed_ports)"
|
||||
|
||||
if command_exists ufw; then
|
||||
ufw default deny incoming || true
|
||||
ufw default allow outgoing || true
|
||||
while IFS= read -r port; do
|
||||
[ -n "$port" ] && ufw allow "${port}/tcp" || true
|
||||
done <<< "$ports"
|
||||
ufw --force enable || true
|
||||
log "ufw enabled with allowed TCP ports: $(echo "$ports" | tr '\n' ',' | sed 's/,$//')"
|
||||
return
|
||||
fi
|
||||
|
||||
if command_exists firewall-cmd; then
|
||||
systemctl enable --now firewalld >/dev/null 2>&1 || true
|
||||
firewall-cmd --permanent --set-default-zone=public >/dev/null 2>&1 || true
|
||||
firewall-cmd --permanent --add-service=ssh >/dev/null 2>&1 || true
|
||||
firewall-cmd --permanent --add-service=http >/dev/null 2>&1 || true
|
||||
firewall-cmd --permanent --add-service=https >/dev/null 2>&1 || true
|
||||
while IFS= read -r port; do
|
||||
[ -n "$port" ] && firewall-cmd --permanent --add-port="${port}/tcp" >/dev/null 2>&1 || true
|
||||
done <<< "$ports"
|
||||
firewall-cmd --reload >/dev/null 2>&1 || true
|
||||
log "firewalld enabled with allowed TCP ports: $(echo "$ports" | tr '\n' ',' | sed 's/,$//')"
|
||||
return
|
||||
fi
|
||||
|
||||
log "no supported firewall command found"
|
||||
}
|
||||
|
||||
has_root_authorized_keys() {
|
||||
[ -s /root/.ssh/authorized_keys ]
|
||||
}
|
||||
|
||||
configure_sshd() {
|
||||
mkdir -p "$SSH_DROPIN_DIR"
|
||||
|
||||
local permit_root="yes"
|
||||
local password_auth="yes"
|
||||
|
||||
if [ "$HARDEN_SSH_STRICT" = "1" ]; then
|
||||
if has_root_authorized_keys; then
|
||||
permit_root="prohibit-password"
|
||||
password_auth="no"
|
||||
log "strict SSH mode enabled (key login required)"
|
||||
else
|
||||
log "strict SSH mode requested but no /root/.ssh/authorized_keys, fallback to password auth"
|
||||
fi
|
||||
fi
|
||||
|
||||
local tmp_file
|
||||
tmp_file="$(mktemp)"
|
||||
cat > "$tmp_file" << EOF
|
||||
# Managed by ai-chatbot scripts/security/harden-server.sh
|
||||
Protocol 2
|
||||
PubkeyAuthentication yes
|
||||
PasswordAuthentication $password_auth
|
||||
PermitRootLogin $permit_root
|
||||
PermitEmptyPasswords no
|
||||
ChallengeResponseAuthentication no
|
||||
UsePAM yes
|
||||
MaxAuthTries 3
|
||||
LoginGraceTime 30
|
||||
ClientAliveInterval 300
|
||||
ClientAliveCountMax 2
|
||||
X11Forwarding no
|
||||
AllowTcpForwarding no
|
||||
AllowAgentForwarding no
|
||||
EOF
|
||||
|
||||
local rollback_file=""
|
||||
if [ -f "$SSH_DROPIN_FILE" ]; then
|
||||
cp -a "$SSH_DROPIN_FILE" "${SSH_DROPIN_FILE}.bak.$(date '+%Y%m%d%H%M%S')" || true
|
||||
rollback_file="$(mktemp)"
|
||||
cp -a "$SSH_DROPIN_FILE" "$rollback_file"
|
||||
fi
|
||||
install -m 644 "$tmp_file" "$SSH_DROPIN_FILE"
|
||||
rm -f "$tmp_file"
|
||||
|
||||
if command_exists sshd && sshd -t; then
|
||||
systemctl reload sshd >/dev/null 2>&1 || systemctl reload ssh >/dev/null 2>&1 || systemctl restart sshd >/dev/null 2>&1 || systemctl restart ssh >/dev/null 2>&1 || true
|
||||
log "sshd hardened and reloaded"
|
||||
else
|
||||
if [ -n "$rollback_file" ] && [ -f "$rollback_file" ]; then
|
||||
install -m 644 "$rollback_file" "$SSH_DROPIN_FILE"
|
||||
else
|
||||
rm -f "$SSH_DROPIN_FILE"
|
||||
fi
|
||||
log "sshd config test failed, rollback applied"
|
||||
fi
|
||||
[ -n "$rollback_file" ] && rm -f "$rollback_file" || true
|
||||
}
|
||||
|
||||
configure_fail2ban() {
|
||||
if ! command_exists fail2ban-client; then
|
||||
log "fail2ban not installed, skip"
|
||||
return
|
||||
fi
|
||||
|
||||
mkdir -p "$(dirname "$FAIL2BAN_JAIL_FILE")"
|
||||
cat > "$FAIL2BAN_JAIL_FILE" << 'EOF'
|
||||
[DEFAULT]
|
||||
bantime = 1h
|
||||
findtime = 10m
|
||||
maxretry = 5
|
||||
backend = auto
|
||||
bantime.increment = true
|
||||
bantime.factor = 2
|
||||
bantime.maxtime = 24h
|
||||
ignoreip = 127.0.0.1/8 ::1
|
||||
|
||||
[sshd]
|
||||
enabled = true
|
||||
mode = aggressive
|
||||
|
||||
[nginx-http-auth]
|
||||
enabled = true
|
||||
|
||||
[nginx-botsearch]
|
||||
enabled = true
|
||||
EOF
|
||||
|
||||
systemctl enable fail2ban >/dev/null 2>&1 || true
|
||||
systemctl restart fail2ban >/dev/null 2>&1 || true
|
||||
log "fail2ban configured"
|
||||
}
|
||||
|
||||
configure_sysctl() {
|
||||
cat > "$SYSCTL_FILE" << 'EOF'
|
||||
net.ipv4.conf.all.accept_redirects = 0
|
||||
net.ipv4.conf.default.accept_redirects = 0
|
||||
net.ipv4.conf.all.send_redirects = 0
|
||||
net.ipv4.conf.default.send_redirects = 0
|
||||
net.ipv4.conf.all.rp_filter = 1
|
||||
net.ipv4.conf.default.rp_filter = 1
|
||||
net.ipv4.tcp_syncookies = 1
|
||||
net.ipv4.icmp_echo_ignore_broadcasts = 1
|
||||
net.ipv4.icmp_ignore_bogus_error_responses = 1
|
||||
kernel.kptr_restrict = 2
|
||||
kernel.dmesg_restrict = 1
|
||||
fs.protected_hardlinks = 1
|
||||
fs.protected_symlinks = 1
|
||||
EOF
|
||||
sysctl --system >/dev/null 2>&1 || true
|
||||
log "sysctl hardening applied"
|
||||
}
|
||||
|
||||
main() {
|
||||
require_root
|
||||
log "start server hardening"
|
||||
install_packages
|
||||
configure_registry
|
||||
configure_sshd
|
||||
configure_fail2ban
|
||||
configure_firewall
|
||||
configure_sysctl
|
||||
log "server hardening done"
|
||||
}
|
||||
|
||||
main "$@"
|
||||
21
scripts/security/install-security-stack.sh
Executable file
21
scripts/security/install-security-stack.sh
Executable file
@ -0,0 +1,21 @@
|
||||
#!/usr/bin/env bash
|
||||
set -euo pipefail
|
||||
|
||||
if [ "${EUID:-$(id -u)}" -ne 0 ]; then
|
||||
echo "[security-stack] must run as root"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
||||
|
||||
echo "[security-stack] installing security stack..."
|
||||
|
||||
if [ -f "$SCRIPT_DIR/install-perf-guard.sh" ]; then
|
||||
bash "$SCRIPT_DIR/install-perf-guard.sh" || echo "[security-stack] perf-guard install failed"
|
||||
fi
|
||||
|
||||
if [ -f "$SCRIPT_DIR/harden-server.sh" ]; then
|
||||
bash "$SCRIPT_DIR/harden-server.sh" || echo "[security-stack] harden-server failed"
|
||||
fi
|
||||
|
||||
echo "[security-stack] completed"
|
||||
@ -92,8 +92,11 @@ ufw allow 80/tcp
|
||||
ufw allow 443/tcp
|
||||
ufw allow 5204/tcp
|
||||
|
||||
# 安装恶意进程守护(如果脚本存在)
|
||||
if [ -f "$(dirname "$0")/scripts/security/install-perf-guard.sh" ]; then
|
||||
# 安装安全基线(如果脚本存在)
|
||||
if [ -f "$(dirname "$0")/scripts/security/install-security-stack.sh" ]; then
|
||||
echo "安装 security-stack..."
|
||||
bash "$(dirname "$0")/scripts/security/install-security-stack.sh" || echo "security-stack 安装失败,继续执行"
|
||||
elif [ -f "$(dirname "$0")/scripts/security/install-perf-guard.sh" ]; then
|
||||
echo "安装 perf-guard..."
|
||||
bash "$(dirname "$0")/scripts/security/install-perf-guard.sh" || echo "perf-guard 安装失败,继续执行"
|
||||
fi
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user