210 lines
11 KiB
Markdown
210 lines
11 KiB
Markdown
# 安裝流程(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) 建立/更新 schema(EF Core migrations)
|
||
4) 建立 superuser(預設帳號 admin,可自訂)
|
||
5) 建立必要 seed(roles 等)
|
||
|
||
### 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 與 HSTS;Member 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` 不是 HTTPS,API 會拒絕啟動;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 數,允許範圍 `1–5`。
|
||
- 不可填入 `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`(允許 `1–180`);進入期限後 API / Web 啟動會寫入 warning log,Production 應以 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) 建立 roles(superuser, 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 維持設定檔
|