#!/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 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 — 仅允许 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 , 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@" echo " 4. 测试: ssh deployer@ deploy-status"