member_center/docs/INSTALL.md
2026-07-07 13:27:49 +09:00

210 lines
11 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

# 安裝流程Installer + 管理者帳號)
此文件定義「首次安裝」與「既有環境管理」兩種情境,並以 EF Core Code First 為主。
完整環境變數、執行期 options、DB 管理設定與預設值請見 `docs/CONFIGURATION.md`
現有 OAuth scopes、resource 與 client usage mapping 請見 `docs/SCOPES.md`
## 目標
- 透過 EF Core migrations 建立/更新 DB schema
- 建立或更新 superuser 帳號
- 已設定 DB 連線時,可重複執行(不重建 schema
## 安裝模式
### A. 首次安裝Init
1) 輸入 DB 連線資訊
2) 連線檢查與版本檢查
3) 建立/更新 schemaEF Core migrations
4) 建立 superuser預設帳號 admin可自訂
5) 建立必要 seedroles 等)
### B. 既有環境(管理)
- 建立額外 superuser
- 強制重設 superuser 密碼
- 只執行 migration差異安裝不重建 schema
## CLI 指令規格(實作)
### 通用參數
- `--connection-string <string>`: DB 連線字串
- `--appsettings <path>`: 設定檔路徑(讀/寫 `ConnectionStrings:Default`
- `--no-prompt`: 不使用互動輸入CI/CD
- `--verbose`: 詳細輸出
### 環境變數(建議用於部署)
- `ConnectionStrings__Default`: 主要連線字串(優先)
- `MEMBERCENTER_CONNECTION`: 備用連線字串
- API / Web 在非 Development 環境必須提供 `ConnectionStrings__Default`缺少或空值時會拒絕啟動localhost 預設值僅供 Development。
若在開發環境(`ASPNETCORE_ENVIRONMENT=Development``DOTNET_ENVIRONMENT=Development`
可以建立 `.env` 檔案installer 與 API 會在啟動時讀取(僅限開發環境)。
建議在 `.env` 內加入:
```
ASPNETCORE_ENVIRONMENT=Development
ConnectionStrings__Default=Host=localhost;Database=member_center;Username=postgres;Password=postgres
Auth__Issuer=http://localhost:7850/
Auth__WebLoginUrl=http://localhost:5080/account/login
Auth__AllowedLoginReturnUrlPrefixes=http://localhost:7850/
Auth__AllowedLogoutReturnUrlPrefixes=http://localhost:5243/
Auth__AllowInternalHttpTokenEndpoint=false
Auth__Certificates__Signing__Path=
Auth__Certificates__Signing__Password=
Auth__Certificates__Encryption__Path=
Auth__Certificates__Encryption__Password=
DataProtection__Certificate__Path=
DataProtection__Certificate__Password=
ReverseProxy__KnownProxies=
ReverseProxy__KnownNetworks=
ReverseProxy__ForwardLimit=1
Auth__Resources__MemberCenter__Audience=member_center_api
Auth__Resources__SendEngine__Audience=send_engine_api
Auth__Resources__FileAccess__Audience=file_access_api
SendEngine__BaseUrl=http://localhost:6060
SendEngine__WebhookSecret=change-me
```
相容性說明:
- 現行程式已優先使用 resource registry 與目標型態:
- `Auth__Resources__MemberCenter__Audience`
- `Auth__Resources__SendEngine__Audience`
- `Auth__Resources__FileAccess__Audience`
- 舊 key 仍保留相容讀取:
- `Auth__MemberCenterAudience`
- `Auth__SendEngineAudience`
- 規劃上將收斂為 DB resource registry`.env` 僅作為初始 seed / 部署覆寫來源,不應再為每個新服務新增平行 hardcoded key。
- `File Access` 已直接採用 resource registry 形式,不新增第三組硬編碼 audience 判斷。
OIDC / Redirect login 設定說明:
- `Auth__WebLoginUrl`: API `/oauth/authorize` 未登入時導向的 Web login URL。
- `Auth__AllowedLoginReturnUrlPrefixes`: Web login 成功後允許 redirect 回去的 URL prefix通常填 API issuer/base URL。
- `Auth__AllowedLogoutReturnUrlPrefixes`: Web logout 後允許 redirect 的 URL prefix不會同時成為 login allowlist。
- Return URL allowlist 會結構化比對 `scheme + host + port + path segment prefix`,設定值不得含 userinfo、query 或 fragment例如 `https://example.com/app` 不會允許 `https://example.com.attacker.tld``/application`
- Identity cookie 固定使用 `SameSite=None``Secure=Always``Path=/`,因此 stage/prod 必須使用 HTTPS。
- AWS 架構由 ALB / CloudFront 終止 TLS並負責 HTTP→HTTPS redirect 與 HSTSMember Center 私有 listener 使用 HTTP不在應用程式層 redirect避免私有網域、health check 與 S2S 呼叫形成循環。
- 非 Development 的 `Auth__Issuer` 為必填且必須使用 canonical HTTPS URL缺少或使用 HTTP 時 API 拒絕啟動。
- 同 VPC 服務若需直接呼叫私有 HTTP `/oauth/token`,設定 `Auth__AllowInternalHttpTokenEndpoint=true`;此設定只放寬 OAuth endpoint transport不放寬 issuer 或外部 return URL。
- 私有 HTTP listener 必須以 Security Group 限制來源CMS 可使用內部 HTTP token endpoint但驗證 token 時仍必須接受 canonical HTTPS issuer。
- 非 Development 若 `Auth__Issuer` 不是 HTTPSAPI 會拒絕啟動Login / Logout 的外部 HTTP return URL 也會被拒絕。
- TLS 終止於 reverse proxy 時,必須正確設定 trusted proxy讓應用程式只接受 ALB / ingress 提供的 `X-Forwarded-Proto`
Reverse proxy 信任設定:
- 未設定 `ReverseProxy__KnownProxies` / `ReverseProxy__KnownNetworks`API 與 Web 完全忽略 `X-Forwarded-For``X-Forwarded-Proto`
- `ReverseProxy__KnownProxies` 使用逗號分隔 IP例如 `10.0.0.10,10.0.0.11`
- `ReverseProxy__KnownNetworks` 使用逗號分隔 CIDR例如 `10.0.0.0/24,fd00::/64`
- `ReverseProxy__ForwardLimit` 預設為 `1`,只應設為實際 proxy hop 數,允許範圍 `15`
- 不可填入 `0.0.0.0/0``::/0`;正式環境只信任 load balancer / ingress 的固定 IP 或內部網段。
Web security headers
- Web 全域送出 self-only Content Security Policy禁止 object、外部 frame 與 inline script/style。
- 同時送出 `X-Content-Type-Options: nosniff``X-Frame-Options: DENY``Referrer-Policy: no-referrer` 與受限 `Permissions-Policy`
- 新增外部資產或第三方前端服務時,必須先明確調整 CSP不可直接加入 `'unsafe-inline'` 或萬用來源。
SMTP 密碼儲存:
- `smtp_password``protected:v1:` 開頭的 Data Protection ciphertext 儲存,不需要新增 migration 或重建既有 DB。
- 舊版明碼會在第一次讀取 SMTP 設定時自動加密;開啟 `/admin/security`、寄信或送測試信都會觸發。
- `DataProtectionKeys` 是解密必要資料不可任意清空Production 應再以外部憑證保護 key ring並將憑證納入備份與輪替程序。
Production 憑證:
- 非 Development 啟動 API 時必須提供 `Auth__Certificates__Signing__Path/Password``Auth__Certificates__Encryption__Path/Password`;不可再使用 development certificates。
- API 與 Web 都必須提供相同的 `DataProtection__Certificate__Path/Password`,用來保護共用 DB key ring。
- 憑證檔必須是含 private key、在有效期內的 PFX密碼只可由 secret manager / environment 注入,不可提交到 repo。
- signing、encryption 與 Data Protection 憑證建議分離;輪替時需保留仍用於驗證舊 token解密舊資料的前一代憑證完成相容輪替後才能移除。
- 前代憑證以 `__Previous__0__Path/Password` 設定,可依序增加 `Previous__1__...`;目前憑證用於新簽發/新 key前代憑證保留舊 token 驗證與 key 解密能力。
- 應監控憑證到期日並先在 Stage 驗證;路徑錯誤、缺 private key或已過期時應用程式會拒絕啟動。
- 此處監控的是 OpenIddict signing / encryption 與 Data Protection PFX對外 TLS/SSL 憑證若由 AWS ACM 管理,續期與告警由 ACM / AWS 邊界負責。
- `Certificates__ExpirationWarningDays` 預設 `30`(允許 `1180`);進入期限後 API / Web 啟動會寫入 warning logProduction 應以 CloudWatch metric filter / alarm 監控該 warning。
安全回歸檢查:
```bash
dotnet test tests/MemberCenter.Security.Tests/MemberCenter.Security.Tests.csproj
```
此標準 xUnit test project 目前涵蓋 return URL 邊界、HTTPS 限制、trusted proxy fail-closed / CIDR、憑證必要設定與 security options 預設/驗證。
`SendEngine` 設定說明:
- `SendEngine__BaseUrl`: Send Engine API base URL
- `SendEngine__WebhookSecret`: 與 Send Engine `Webhook:Secrets:member_center` 一致
- tenant 對應 `X-Client-Id` 改由 DB 管理Tenant 設定)
- 可透過管理 UI 設定,或由 Send Engine 呼叫 `POST /integrations/send-engine/webhook-clients/upsert` 自動回填
測試旗標TEST-ONLY
- `Testing__DisableSubscriptionDryRunNoDb=true`
- 作用:`POST /subscriptions/disable` 完全不做 DB read/write只寫 log含預計執行的 DB 動作)
- 用途SES/Send Engine 串接初次測試,避免測試資料污染
- 測試結束請改回 `false`
### 1) `installer init`
用途:首次安裝(含 migrations + seed + superuser
參數:
- `--admin-email <email>`: 預設 `admin@example.com`
- `--admin-password <string>`: 預設需互動輸入
- `--admin-display-name <string>`: 可選
- `--force`: 若偵測已初始化,仍強制執行
流程:
1) 解析連線字串(參數或 appsettings
- 若提供 `--connection-string`,會寫入 appsettings
- 若 appsettings 中缺少連線字串,會互動式詢問並寫入
- 若設定環境變數,會優先使用環境變數(不寫入 appsettings
2) 執行 migrations不 Drop
3) 建立 rolessuperuser, admin, support
4) 建立使用者(不存在才建立)並加入 `superuser` 角色
5) 寫入安裝鎖定DB flag: `system_flags` / `installed=true`
### 2) `installer add-superuser`
用途:新增或提升 superuser
參數:
- `--admin-email <email>`
- `--admin-password <string>`
- `--admin-display-name <string>`
流程:
1) 解析連線字串
2) 建立使用者並指派 `superuser` 角色
相容性:
- 舊指令 `installer add-admin` 仍保留為 alias目前語意等同 `installer add-superuser`
### 3) `installer reset-superuser-password`
用途:重設指定 superuser 密碼
參數:
- `--admin-email <email>`
- `--admin-password <string>`
流程:
1) 解析連線字串
2) 更新密碼(強制)
相容性:
- 舊指令 `installer reset-admin-password` 仍保留為 alias目前語意等同 `installer reset-superuser-password`
### 4) `installer migrate`
用途:只執行 migrations
參數:
- `--target <migration>`: 指定遷移(可選)
流程:
1) 解析連線字串
2) 執行 migrations可指定 target
## 安全注意
- 密碼必須符合強度規則
- 初次安裝完成後,禁用安裝入口或限內網
- 安裝過程需紀錄 audit log
## Docker / 部署建議
- 建議用環境變數提供連線字串(避免重建 container 後設定遺失)
- 範例:
```yaml
environment:
ConnectionStrings__Default: "Host=postgres;Database=member_center;Username=postgres;Password=postgres"
```
- 若仍要用 `appsettings.json`,請用 volume 維持設定檔