bookworm-smart-assistant/scripts/setup-deploy-user.sh

151 lines
5.2 KiB
Bash

#!/usr/bin/env bash
# ═══════════════════════════════════════════════════════════════════
# B3: ECS 部署用户隔离配置脚本
# 用途: 在阿里云 ECS 上创建受限的 deploy 用户,替代 root 直接部署
#
# 在 ECS 上以 root 身份执行:
# bash setup-deploy-user.sh
#
# 安全设计:
# - 专用 deployer 用户,无交互登录 shell
# - authorized_keys command= 限制只能执行白名单脚本
# - 应用目录权限隔离
# - sudo 仅允许 pm2 和指定部署操作
# ═══════════════════════════════════════════════════════════════════
set -euo pipefail
DEPLOY_USER="deployer"
APP_DIR="/var/www/app"
DEPLOY_SCRIPT="${APP_DIR}/scripts/deploy.sh"
DEPLOY_KEY_PUB="" # 需要填入部署公钥
echo "=== Bookworm ECS 部署用户配置 ==="
# 1. 创建 deployer 用户
if id "${DEPLOY_USER}" &>/dev/null; then
echo "[OK] 用户 ${DEPLOY_USER} 已存在"
else
useradd -r -m -s /usr/sbin/nologin -d /home/${DEPLOY_USER} ${DEPLOY_USER}
echo "[OK] 已创建用户 ${DEPLOY_USER}"
fi
# 2. 配置 SSH 目录
mkdir -p /home/${DEPLOY_USER}/.ssh
chmod 700 /home/${DEPLOY_USER}/.ssh
# 3. 生成部署专用密钥对 (如果本地还没有)
if [[ ! -f /home/${DEPLOY_USER}/.ssh/authorized_keys ]]; then
# 生成限制性 authorized_keys
# command= 限制: 只允许执行 deploy-wrapper.sh
cat > /home/${DEPLOY_USER}/.ssh/authorized_keys <<'AKEOF'
# Bookworm 自动部署密钥 — 仅允许执行 deploy-wrapper.sh
# 如需添加密钥,请保持 command= 前缀
# command="/home/deployer/deploy-wrapper.sh",no-port-forwarding,no-X11-forwarding,no-agent-forwarding <PUBLIC_KEY_HERE>
AKEOF
echo "[WARN] 请编辑 authorized_keys 添加部署公钥并启用 command= 限制"
fi
chmod 600 /home/${DEPLOY_USER}/.ssh/authorized_keys
chown -R ${DEPLOY_USER}:${DEPLOY_USER} /home/${DEPLOY_USER}/.ssh
# 4. 创建 deploy-wrapper.sh (白名单脚本)
cat > /home/${DEPLOY_USER}/deploy-wrapper.sh <<'WEOF'
#!/usr/bin/env bash
# 部署白名单包装器 — 仅允许预定义操作
set -euo pipefail
# 从 SSH_ORIGINAL_COMMAND 获取请求的命令
CMD="${SSH_ORIGINAL_COMMAND:-}"
LOG="/var/log/deploy-audit.log"
log_audit() {
echo "$(date -Iseconds) | deployer | $1 | $2" >> "${LOG}"
}
case "${CMD}" in
"deploy")
log_audit "ALLOW" "deploy"
cd /var/www/app && bash scripts/deploy.sh 2>&1
;;
"deploy-status")
log_audit "ALLOW" "deploy-status"
pm2 status 2>&1
;;
"health-check")
log_audit "ALLOW" "health-check"
curl -sf http://localhost:3000/api/health 2>&1 || echo "HEALTH_CHECK_FAILED"
;;
"rollback "*)
# rollback <commit-hash> — 仅允许 40 字符 hex commit hash
COMMIT="${CMD#rollback }"
if [[ "${COMMIT}" =~ ^[0-9a-f]{40}$ ]]; then
log_audit "ALLOW" "rollback to ${COMMIT}"
cd /var/www/app && git checkout "${COMMIT}" -- . && pm2 reload all 2>&1
else
log_audit "DENY" "invalid commit hash: ${COMMIT}"
echo "ERROR: 无效的 commit hash" >&2
exit 1
fi
;;
"pm2-reload")
log_audit "ALLOW" "pm2-reload"
pm2 reload all 2>&1
;;
"logs")
log_audit "ALLOW" "logs"
pm2 logs --lines 50 --nostream 2>&1
;;
*)
log_audit "DENY" "blocked command: ${CMD}"
echo "ERROR: 不允许的操作。允许的命令: deploy, deploy-status, health-check, rollback <hash>, pm2-reload, logs" >&2
exit 1
;;
esac
WEOF
chmod 755 /home/${DEPLOY_USER}/deploy-wrapper.sh
# 5. 配置 sudoers (仅 pm2 操作)
cat > /etc/sudoers.d/deployer <<'SEOF'
# Bookworm deployer — 最小权限 pm2 操作 (git 由 deployer 直接执行,无需 sudo)
deployer ALL=(ALL) NOPASSWD: /usr/bin/pm2 reload *, /usr/bin/pm2 status, /usr/bin/pm2 logs *
SEOF
chmod 440 /etc/sudoers.d/deployer
# 6. 应用目录权限
if [[ -d "${APP_DIR}" ]]; then
chown -R ${DEPLOY_USER}:${DEPLOY_USER} "${APP_DIR}"
echo "[OK] 已将 ${APP_DIR} 所有权转移到 ${DEPLOY_USER}"
fi
# 7. 创建审计日志
touch /var/log/deploy-audit.log
chown ${DEPLOY_USER}:${DEPLOY_USER} /var/log/deploy-audit.log
# 8. 创建部署前签名校验脚本
cat > /home/${DEPLOY_USER}/verify-deploy.sh <<'VEOF'
#!/usr/bin/env bash
# 部署前 Git commit 签名校验
set -euo pipefail
cd /var/www/app
CURRENT_COMMIT=$(git rev-parse HEAD)
REMOTE_COMMIT=$(git ls-remote origin HEAD | cut -f1)
# 确保本地和远程 commit 一致
if [[ "${CURRENT_COMMIT}" != "${REMOTE_COMMIT}" ]]; then
echo "VERIFY_FAIL: 本地 commit (${CURRENT_COMMIT:0:8}) != 远程 (${REMOTE_COMMIT:0:8})"
exit 1
fi
echo "VERIFY_OK: commit ${CURRENT_COMMIT:0:8}"
VEOF
chmod 755 /home/${DEPLOY_USER}/verify-deploy.sh
echo ""
echo "=== 配置完成 ==="
echo "下一步:"
echo " 1. 在本地生成部署专用密钥对: ssh-keygen -t ed25519 -f ~/.ssh/id_deploy -N ''"
echo " 2. 将公钥添加到 /home/${DEPLOY_USER}/.ssh/authorized_keys (保持 command= 前缀)"
echo " 3. 更新 loop-controller.sh 中的 DEPLOY_HOST 为 deployer@<DEPLOY_SERVER_IP>"
echo " 4. 测试: ssh deployer@<DEPLOY_SERVER_IP> deploy-status"