# vNext.4 Trusted-Subject 安全链部署指南 > 解决 2026-06-08 EXECUTION_BOARD.md 中记录的线上阻塞问题 ## 问题描述 remote43 当前 nginx `/portal-admin-api/` 未注入 `X-CRM-Authenticated-Subject` / `X-CRM-Trusted-Proxy`,导致无法在现网安全完成新的 user-key 真验闭环。 ## 解决方案 实施受信代理安全链: ``` 用户浏览器 ← → Portal 前端 ← → nginx (cookie→header 转换) ← → CRM ↑ ↓ 设置 httpOnly cookie 验证并注入受信 header ``` ## 所需变更 ### 1. CRM 二进制更新 (已完成) 新增文件: - `internal/app/portal_auth.go` - Portal user session 认证模块 - `internal/app/portal_auth_test.go` - 测试用例 变更文件: - `internal/app/http_api.go` - 添加 `/api/portal/session/*` 路由 - `internal/app/bootstrap.go` - 传递 trusted proxy secret - `deploy/tksea-portal/index.html` - 添加 CRM session 登录/登出 - `deploy/tksea-portal/nginx.sub.tksea.top.conf.example` - nginx 配置模板 - `.env.example` - 环境变量模板 ### 2. remote43 部署步骤 #### 步骤 1: 生成共享密钥 在 remote43 上执行: ```bash TRUSTED_PROXY_SECRET=$(openssl rand -hex 32) echo "Generated secret: $TRUSTED_PROXY_SECRET" # 保存此密钥,需要同时配置到 nginx 和 CRM ``` #### 步骤 2: 更新 CRM 配置 编辑 `/home/ubuntu/.env.crm`(或实际运行目录): ```bash # 在文件末尾添加: # Trusted Subject Proxy Configuration SUB2API_CRM_TRUSTED_SUBJECT_HEADER=X-CRM-Authenticated-Subject SUB2API_CRM_TRUSTED_PROXY_SECRET_HEADER=X-CRM-Trusted-Proxy SUB2API_CRM_TRUSTED_PROXY_SECRET=<步骤1生成的64字符密钥> ``` #### 步骤 3: 更新 nginx 配置 编辑 `/etc/nginx/sites-enabled/sub.tksea.top.conf`: 在 `server` 块内添加: ```nginx # 从 httpOnly cookie 提取 portal subject(放在 server 块内) map $http_cookie $portal_subject { default ""; ~*crm_session=([^;]+) $1; } ``` 修改 `/portal-admin-api/` location: ```nginx location /portal-admin-api/ { proxy_pass http://127.0.0.1:18190/; proxy_set_header Host $host; proxy_set_header X-Real-IP $remote_addr; proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; proxy_set_header X-Forwarded-Proto $scheme; # 关键:从验证过的 cookie 提取并注入 subject proxy_set_header X-CRM-Authenticated-Subject $portal_subject; # 受信代理密钥(必须与 CRM 配置一致) proxy_set_header X-CRM-Trusted-Proxy "<步骤1生成的64字符密钥>"; proxy_http_version 1.1; } ``` **注意**: - 删除原来的 `proxy_set_header X-Portal-Subject "";` 行 - 确保密钥替换为实际生成的 64 字符 hex 字符串 #### 步骤 4: 重启服务 ```bash # 测试 nginx 配置 sudo nginx -t # 重载 nginx sudo systemctl reload nginx # 重启 CRM sudo systemctl restart crm # 或使用: # pkill -f "crm" && cd /home/ubuntu && ./server & ``` #### 步骤 5: 验证 浏览器测试: 1. 访问 `https://sub.tksea.top/portal/` 2. 登录(会同时设置 CRM session cookie) 3. 打开浏览器 DevTools → Application → Cookies 4. 确认看到 `crm_session` cookie(httpOnly)和 `crm_subject` cookie 5. 尝试创建/管理用户 Key,应该可以正常工作 API 测试: ```bash # 1. 登录获取 session cookie curl -c cookies.txt -X POST https://sub.tksea.top/portal-admin-api/api/portal/session/login \ -H "Content-Type: application/json" \ -d '{"email":"test@example.com"}' # 2. 使用 cookie 访问 user-key API curl -b cookies.txt https://sub.tksea.top/portal-admin-api/api/keys # 3. 创建新 key curl -b cookies.txt -X POST https://sub.tksea.top/portal-admin-api/api/keys \ -H "Content-Type: application/json" \ -d '{"key_name":"test-key","logical_group_id":"gpt-shared"}' ``` ### 3. 故障排除 #### CRM 返回 `unauthorized` / `trusted proxy authentication required` - 检查 `.env.crm` 中的 `SUB2API_CRM_TRUSTED_PROXY_SECRET` 是否正确设置 - 检查 nginx 中的 `X-CRM-Trusted-Proxy` header 值是否一致 - 检查两个密钥是否完全匹配(无多余空格) #### CRM 返回 `trusted subject header required` - 检查 nginx 是否正确添加了 `map $http_cookie $portal_subject` - 检查浏览器是否有 `crm_session` cookie(登录后应该自动设置) - 检查 cookie 是否被浏览器阻止(SameSite/Secure 设置) #### Portal 前端无法登录 CRM session - 检查浏览器 console 是否有 CORS 错误 - 检查 `/portal-admin-api/` location 是否正确配置 - 确认 CRM 服务正在监听 `127.0.0.1:18190` ## 安全配置建议 1. **密钥管理** - 使用 `openssl rand -hex 32` 生成强密钥 - 不要在任何地方记录或提交密钥 - 考虑使用 HashiCorp Vault 或 AWS Secrets Manager 2. **HTTPS** - 生产环境必须启用 HTTPS - 设置 `Secure` flag 在 cookies 上 3. **Cookie 设置** - `crm_session`: httpOnly, SameSite=Lax, Secure (HTTPS only) - `crm_subject`: SameSite=Lax, Secure (HTTPS only) ## 回滚计划 如果需要回滚: 1. 还原 nginx 配置(删除 map 和 header 设置) 2. 还原 `.env.crm`(移除 TRUSTED\_\* 配置) 3. 重载 nginx / 重启 CRM portal 会回退到之前的 bearer token 认证模式。 ## 验证清单 - [ ] 生成并记录了 64 字符 hex secret - [ ] 更新了 `.env.crm` 配置 - [ ] 更新了 nginx 配置 - [ ] 重载了 nginx 配置 - [ ] 重启了 CRM 服务 - [ ] 浏览器测试通过(可以看到 crm_session cookie) - [ ] API 测试通过(可以创建 user-key) - [ ] 完整链路测试通过(create → chat → pause → resume → delete)