feat(vNext.4): implement trusted-subject security chain for portal user key self-service

- Add portal_auth.go: Portal user session auth with HMAC-signed cookies
- Add /api/portal/session/{login,logout,state} endpoints
- Update nginx config template: cookie-to-header trusted proxy pattern
- Update frontend: sync CRM session on login/logout
- Add TRUSTED_SUBJECT_DEPLOY_GUIDE.md with remote43 deployment steps
- Update EXECUTION_BOARD.md: mark trusted-subject blocking issue as resolved

This implements the secure chain:
  Browser → Portal → nginx (cookie→header) → CRM (verify proxy secret)

Required remote43 actions:
1. Generate 64-char hex secret
2. Update .env.crm with TRUSTED_* config
3. Update nginx with cookie map and header injection
4. Restart services

Fixes EXECUTION_BOARD.md 2026-06-08 blocking issue
This commit is contained in:
phamnazage-jpg
2026-06-09 07:48:03 +08:00
parent dd6f332b53
commit 4e2ee087fd
25 changed files with 1861 additions and 177 deletions

View File

@@ -97,11 +97,19 @@ block = textwrap.dedent("""\
}
location /portal-admin-api/ {
# 必须由受信登录/鉴权层把用户 subject 放进 \$portal_subject不能信任浏览器自带 header。
# 同时 CRM 需配置:
# SUB2API_CRM_TRUSTED_SUBJECT_HEADER=X-CRM-Authenticated-Subject
# SUB2API_CRM_TRUSTED_PROXY_SECRET_HEADER=X-CRM-Trusted-Proxy
# SUB2API_CRM_TRUSTED_PROXY_SECRET=<same-secret-as-nginx>
proxy_pass http://127.0.0.1:${REMOTE_CRM_PORT}/;
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;
proxy_set_header X-Portal-Subject "";
proxy_set_header X-CRM-Authenticated-Subject \$portal_subject;
proxy_set_header X-CRM-Trusted-Proxy "REPLACE_WITH_SUB2API_CRM_TRUSTED_PROXY_SECRET";
proxy_http_version 1.1;
}

View File

@@ -75,12 +75,19 @@ render_remote43_crm_env() {
local repo_root="${4:-}"
local admin_username="${5:-admin}"
local admin_password="${6:-$admin_token}"
local trusted_subject_header="${SUB2API_CRM_TRUSTED_SUBJECT_HEADER:-}"
local trusted_proxy_secret_header="${SUB2API_CRM_TRUSTED_PROXY_SECRET_HEADER:-X-CRM-Trusted-Proxy}"
local trusted_proxy_secret="${SUB2API_CRM_TRUSTED_PROXY_SECRET:-}"
local sqlite_dsn_q admin_token_q repo_root_q admin_username_q admin_password_q
local trusted_subject_header_q trusted_proxy_secret_header_q trusted_proxy_secret_q
printf -v sqlite_dsn_q '%q' "$sqlite_dsn"
printf -v admin_token_q '%q' "$admin_token"
printf -v repo_root_q '%q' "$repo_root"
printf -v admin_username_q '%q' "$admin_username"
printf -v admin_password_q '%q' "$admin_password"
printf -v trusted_subject_header_q '%q' "$trusted_subject_header"
printf -v trusted_proxy_secret_header_q '%q' "$trusted_proxy_secret_header"
printf -v trusted_proxy_secret_q '%q' "$trusted_proxy_secret"
cat <<EOF
SUB2API_CRM_LISTEN_ADDR=127.0.0.1:$crm_port
@@ -91,6 +98,9 @@ SUB2API_CRM_ADMIN_PASSWORD=$admin_password_q
SUB2API_CRM_ADMIN_SESSION_TTL=12h
SUB2API_CRM_REPO_ROOT=$repo_root_q
SUB2API_CRM_RECONCILE_WORKER_ENABLED=false
SUB2API_CRM_TRUSTED_SUBJECT_HEADER=$trusted_subject_header_q
SUB2API_CRM_TRUSTED_PROXY_SECRET_HEADER=$trusted_proxy_secret_header_q
SUB2API_CRM_TRUSTED_PROXY_SECRET=$trusted_proxy_secret_q
EOF
}