# 安裝流程(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 `: DB 連線字串 - `--appsettings `: 設定檔路徑(讀/寫 `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 `: 預設 `admin@example.com` - `--admin-password `: 預設需互動輸入 - `--admin-display-name `: 可選 - `--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 ` - `--admin-password ` - `--admin-display-name ` 流程: 1) 解析連線字串 2) 建立使用者並指派 `superuser` 角色 相容性: - 舊指令 `installer add-admin` 仍保留為 alias,目前語意等同 `installer add-superuser` ### 3) `installer reset-superuser-password` 用途:重設指定 superuser 密碼 參數: - `--admin-email ` - `--admin-password ` 流程: 1) 解析連線字串 2) 更新密碼(強制) 相容性: - 舊指令 `installer reset-admin-password` 仍保留為 alias,目前語意等同 `installer reset-superuser-password` ### 4) `installer migrate` 用途:只執行 migrations 參數: - `--target `: 指定遷移(可選) 流程: 1) 解析連線字串 2) 執行 migrations(可指定 target) ## 安全注意 - 密碼必須符合強度規則 - 初次安裝完成後,禁用安裝入口或限內網 - 安裝過程需紀錄 audit log ## Docker / 部署建議 - 建議用環境變數提供連線字串(避免重建 container 後設定遺失) - 範例: ```yaml environment: ConnectionStrings__Default: "Host=postgres;Database=member_center;Username=postgres;Password=postgres" ``` - 若仍要用 `appsettings.json`,請用 volume 維持設定檔