12 KiB
12 KiB
安裝流程(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)
- 輸入 DB 連線資訊
- 連線檢查與版本檢查
- 建立/更新 schema(EF Core migrations)
- 建立 superuser(預設帳號 admin,可自訂)
- 建立必要 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
# Optional certificate file overrides. Normally installer provisions app certificates into DB.
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__AudienceAuth__Resources__SendEngine__AudienceAuth__Resources__FileAccess__Audience
- 舊 key 仍保留相容讀取:
Auth__MemberCenterAudienceAuth__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 時,必須讓應用程式接受 proxy 提供的 forwarded headers,否則登入與 callback redirect 可能從
https退回http。
Reverse proxy 信任設定:
- AWS ALB / managed reverse proxy 的 private IP 會變動時,建議由 Security Group 限制 Member Center 只接受該 proxy 連入,並設定:
ReverseProxy__TrustForwardedHeaders=trueReverseProxy__ForwardLimit=1
ReverseProxy__TrustForwardedHeaders=true會接受X-Forwarded-For、X-Forwarded-Proto、X-Forwarded-Host;只有在 app inbound 已由 Security Group / 私有網路限制為可信 proxy 時才可使用。- 接受
X-Forwarded-Host是為了讓 AWS ALB 後方的登入、OAuth callback 與外部 URL 產生使用原始 public host;ALB / router 必須覆寫外部傳入的 forwarded headers,不可直接沿用任意 client 值。 - Production 的
/admin/securityPublic base URL必須設定為 canonical HTTPS URL,讓驗證信與密碼重設信不依賴 request host。 - 部署後必須以偽造
Host/X-Forwarded-Host實測登入 redirect、OAuth callback 與 email link,確認不會導向非預期網域。 - 若不使用
TrustForwardedHeaders,未設定ReverseProxy__KnownProxies/ReverseProxy__KnownNetworks時,API 與 Web 完全忽略 forwarded headers。 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。- 不使用
TrustForwardedHeaders而改用KnownNetworks時,不可填入0.0.0.0/0或::/0;正式環境只信任 load balancer / ingress 的固定 IP 或內部網段。
Web security headers:
- Web 全域送出受限 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'或萬用來源。 - Google login 啟用時,
form-action會額外允許https://accounts.google.com,因為/Account/ExternalLogin的 form submit 會被 ASP.NET Core external auth challenge 導向 Google OAuth endpoint。
SMTP 密碼儲存:
smtp_password以protected:v1:開頭的 Data Protection ciphertext 儲存,不需要新增 migration 或重建既有 DB。- 舊版明碼會在第一次讀取 SMTP 設定時自動加密;開啟
/admin/security、寄信或送測試信都會觸發。 DataProtectionKeys是解密必要資料,不可任意清空;Production 應再以外部憑證保護 key ring,並將憑證納入備份與輪替程序。
Production 憑證:
- Installer
init/migrate會在 DBsystem_flags自動產生三張 app-managed PFX:OpenIddict signing、OpenIddict encryption、Data Protection。 - API 非 Development 啟動時會優先使用
Auth__Certificates__Signing__Path/Auth__Certificates__Encryption__Path,未設定時改讀 DB。 - Web 非 Development 啟動時會優先使用
DataProtection__Certificate__Path,未設定時改讀 DB。 - API 與 Web 必須使用同一份 DB 與同一份 Data Protection certificate;因此正式部署順序是先跑 installer migration,再啟動 API/Web。
- 檔案型 PFX 設定只作為覆寫或手動輪替用途;若設定了 path,檔案必須存在、含 private key 且在有效期內。
- signing、encryption 與 Data Protection 憑證分離;輪替時需保留仍用於驗證舊 token/解密舊資料的前一代憑證,完成相容輪替後才能移除。
- 前代檔案憑證以
__Previous__0__Path/Password設定,可依序增加Previous__1__...;DB-managed 前代輪替管理介面尚未建立。 - 應監控憑證到期日並先在 Stage 驗證;DB certificate 缺失、缺 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。
安全回歸檢查:
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 URLSendEngine__WebhookSecret: 與 Send EngineWebhook: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: 若偵測已初始化,仍強制執行
流程:
- 解析連線字串(參數或 appsettings)
- 若提供
--connection-string,會寫入 appsettings - 若 appsettings 中缺少連線字串,會互動式詢問並寫入
- 若設定環境變數,會優先使用環境變數(不寫入 appsettings)
- 若提供
- 執行 migrations(不 Drop)
- 建立 roles(superuser, admin, support)
- 建立使用者(不存在才建立)並加入
superuser角色 - 寫入安裝鎖定(DB flag:
system_flags/installed=true)
2) installer add-superuser
用途:新增或提升 superuser
參數:
--admin-email <email>--admin-password <string>--admin-display-name <string>
流程:
- 解析連線字串
- 建立使用者並指派
superuser角色
相容性:
- 舊指令
installer add-admin仍保留為 alias,目前語意等同installer add-superuser
3) installer reset-superuser-password
用途:重設指定 superuser 密碼
參數:
--admin-email <email>--admin-password <string>
流程:
- 解析連線字串
- 更新密碼(強制)
相容性:
- 舊指令
installer reset-admin-password仍保留為 alias,目前語意等同installer reset-superuser-password
4) installer migrate
用途:只執行 migrations
參數:
--target <migration>: 指定遷移(可選)
流程:
- 解析連線字串
- 執行 migrations(可指定 target)
安全注意
- 密碼必須符合強度規則
- 初次安裝完成後,禁用安裝入口或限內網
- 安裝過程需紀錄 audit log
Docker / 部署建議
- 建議用環境變數提供連線字串(避免重建 container 後設定遺失)
- 範例:
environment:
ConnectionStrings__Default: "Host=postgres;Database=member_center;Username=postgres;Password=postgres"
- 若仍要用
appsettings.json,請用 volume 維持設定檔