ADR-032: Actuator 維運面由應用集中分類授權
ADR 編號: 032 狀態: 已接受 (Accepted) 決策日期: 2026-08-29 決策者: Development Team 取代: ADR-026 中 Actuator authority、 AuthorityContributor 與 SecurityContributor 跟隨 endpoint owner 的決策,以及 platform-info public system handshake matcher 跟隨 Feature contributor 的決策 被取代: 無
摘要
Feature 繼續擁有固定的 @WebEndpoint、HealthIndicator、InfoContributor 與 system
handshake adapter;但 Actuator 的認證與授權 policy(預設路徑為 /actuator/**),以及固定
GET /api/v1/system/info public handshake 的 path classification,改由應用唯一的 security
chain 集中擁有。通用 Actuator 讀取使用固定 actuator:read,異動使用固定
actuator:write;Public 與單純 Authenticated 端點必須由應用明確列出。Feature 不再為每個
維運端點建立自己的 Authority/AuthorityContributor/SecurityContributor。
本 ADR 取代 ADR-026 的 Actuator 授權 ownership,並涵蓋刪除
PlatformInfoSecurityContributor 時一併上收的 system handshake public matcher。固定 adapter
仍屬 Feature core,產品資源 API 仍屬 reference implementation。
背景 (Context)
ADR-026 正確把不依賴產品模型、契約固定的 Actuator/health/info adapter 留在 Feature core,
使其能透過 Feature 同步管道維護。但它同時要求每個 endpoint 攜帶 feature-specific authority
與兩個 contributor,形成 mail:ops、audit:ops、platform_info:ops 等維運詞彙。
這個模型帶來三個問題:
- 每新增一個維運 endpoint,Authority catalog、Role seed、授權類別與測試矩陣都要成長。
- 這些 authority 的真正 consumer 都是同一類平台維運者,feature 粒度沒有形成有效的職責隔離。
- Spring Boot 已提供可解析實際 Actuator mapping 的完整安全邊界;把共同 policy 分散回各 Feature,反而增加 漏掉 Spring Boot 內建 endpoint 的風險。
同時,應用仍可能因風險、法遵或部署拓樸,需要把個別端點改成 Public、Authenticated,或使用
專屬 {resource}:{operation}。集中預設不得剝奪這個覆寫能力。
考量的方案 (Options Considered)
方案 A:維持每個 Feature 的 {feature}:ops
endpoint 與授權元件同進退,但 Authority catalog 與樣板程式碼隨 Feature 數量成長,且內建 Actuator endpoint 容易因沒有明確 owner 而漏掉授權。
方案 B:所有 Actuator endpoint 只要求 authenticated
規則最少,但任一登入者都能讀取稽核事件、組態、metrics 或執行維運異動,無法作為安全預設。
方案 C:應用集中分類,提供穩定 read/write capability(採用)
由 application security chain 依 Spring Boot 的實際 endpoint mapping 完整覆蓋 Actuator,使用 固定的讀寫 capability 作為 fail-safe 預設;應用可在通用 matcher 前對特定端點明確覆寫。
決策 (Decision)
1. Adapter ownership 與 authorization ownership 分離
| Concern | Owner |
|---|---|
@WebEndpoint、HealthIndicator、InfoContributor、固定 system handshake | 提供該整合面的 Feature core |
| Actuator 實際 endpoint mapping 的認證與授權分類 | application security chain |
固定 GET /api/v1/system/info public path classification | application security chain |
| 誰取得維運 capability | application Role seed/外部身分映射 |
產品 /api/** 與其領域 capability | structural/reference domain |
Feature 移除後 endpoint 會消失;應用的通用 matcher 留存並不產生 dead surface,也不需要跟著 每個 Feature 增減。
2. Actuator 統一分成三類
所有 matcher 必須在 anyRequest() 前完成,順序由具體到通用:
- Public:應用以精確 method+path 明列
permitAll()。 - Authenticated:應用以精確 matcher 明列
authenticated(),並完成風險與 Audit 決策。 - Capability protected:其餘 Actuator
GET/HEAD要求actuator:read;其他 method 要求actuator:write。
Actuator links endpoint(預設 /actuator 與 /actuator/)亦遵守同一 method 分類。Spring Boot
toAnyEndpoint().withHttpMethod(...) 不會把 method 限制套到 links matcher,因此實作必須先對
links 明確分流,其他 endpoint 則使用 excludingLinks().withHttpMethod(...);呼叫順序不得反轉,
因為 excludingLinks() 會清除先前的 method 限制。
actuator:read 與 actuator:write 是應用維運面的固定詞彙,不因 Feature 增減而改變。寫入權限
不自動蘊含讀取權限;需要讀寫的維運角色應同時取得兩者。
3. 應用保留端點級覆寫權,但 Feature 不得宣告 Actuator matcher
參考 security chain 先依 getOrder() 排序並套用全部 SecurityContributor,之後才註冊
application 自己的 public 與通用 Actuator matcher。因此:
getOrder()只決定 contributors 彼此的順序,不能把 contributor 排到 application fallback 之後;- 任何 contributor 若與 Actuator mapping 重疊,都會因 first-match-wins 先於集中政策命中;
- 所有非 application-owned
SecurityContributor都禁止用/actuator字面或EndpointRequest認領 Actuator,並由掃描整個非config/application source 的架構測試阻擋; - 個別端點確需不同政策時,只能由 application-owned contributor 或 application chain 的等價
matcher 明確覆寫為 Public、Authenticated 或專屬
{resource}:{operation}。
覆寫必須同步測試、Role/身分映射,以及適用的 Audit 決策。
4. 固定 system handshake 的 resource-plane 例外
SystemInfoController 與其 DTO 仍由 platform-info Feature core 擁有,但刪除
PlatformInfoSecurityContributor 後,精確的 GET /api/v1/system/info permitAll() matcher 改由
application chain 宣告。這是固定啟動握手的 application-wide path classification,不是產品領域
authority,也不屬 Actuator capability fallback。
actuator:read/actuator:write 僅匹配 Spring Boot 實際發布的 Actuator endpoints(預設位於
/actuator);持有它們不會授予任何 /api/** 權限。
5. Exposure 與 authorization 分離
management.endpoints.web.exposure.include 只決定 endpoint 是否發布,不構成認證或授權。新增
endpoint 即使已加入 exposure,也會自動落入通用 actuator:read/actuator:write policy。
兩者是不同 concern,但實作 matcher 集合會刻意從 Spring Boot PathMappedEndpoints 取得實際已
發布的 endpoints。因此未 exposure 的路徑(例如 /actuator/env)不會進入 capability matcher:
匿名請求由一般 fallback 回 401,已認證請求到達不存在的 mapping 後回 404,而非先回 403。
這不會曝露 endpoint 內容;測試與監控不得把「未發布路徑一定先回 403」當成契約。
management.endpoints.web.base-path 與 endpoint path mapping 是合法部署旋鈕。集中 policy 必須以
Spring Boot EndpointRequest 解析實際 mappings,不得硬編 /actuator/**;變更 base path 後仍須
保持相同授權分類。
參考實作只支援 Actuator 與 application 共用同一個 server port。若
management.server.port 形成不同 port,主 application chain 的 EndpointRequest 依 Spring Boot
設計不會匹配 MANAGEMENT namespace;因此參考實作會在啟動時 fail-fast。需要分離 management port
的應用必須先提供 management context 專用的完整 security chain,再明確替換此 same-port guard。
6. Health 公開範圍採精確路徑
預設只公開 GET/HEAD /actuator/health 與 /actuator/info。/actuator/health/** 的 component
或 group 子路徑仍落入 actuator:read,避免以 wildcard 公開額外診斷細節。
management.endpoint.health.show-details 維持 when_authorized,並明確指定
management.endpoint.health.roles=actuator:read。因此匿名或一般已認證使用者在 public health
root 只看到整體狀態;只有具 actuator:read 的呼叫者可看到 components/details。Spring Boot
4.0 會把這個設定值直接和 GrantedAuthority 比對,不會要求 ROLE_ 前綴。
目前參考部署、Docker health check 與 Kubernetes probe 都使用精確的 /actuator/health。下游若
使用 /actuator/health/liveness 或 /actuator/health/readiness,必須在升級時二選一:
- 讓探針攜帶具
actuator:read的身分;或 - 經風險確認後,在應用明確公開所需的精確 group 路徑。
不得只為探針方便而直接公開 /actuator/health/**。
7. Authority 與既有 Role 的遷移
應用以單一 AuthorityContributor 宣告 actuator:read、actuator:write,並由 Role seed 或外部
身分映射授予維運角色。既有 audit:ops、mail:ops、platform_info:ops role link 以
revokedAuthorities 顯式撤回;孤兒 authority 依 ADR-031 保留並告警,不在 seed 階段刪除。
revokedAuthorities 只治理 seed 擁有的 SYSTEM role,不會靜默修改人工建立的 CUSTOM role。
升級時必須人工盤點 CUSTOM role 是否仍持有 audit:ops、mail:ops 或 platform_info:ops,移除
這些失效授權,並依實際維運職責改授 actuator:read、actuator:write 或兩者。
對 ADR-026 的取代範圍
保留 ADR-026 的下列決策:
- 固定 Actuator/health/info/system handshake adapter 屬 Feature core。
- Feature core 與 reference implementation 仍以「是否預期由消費端重塑」區分。
MailEndpoint、MailHealthIndicator、ConfigCheckEndpoint、SystemInfoController繼續留在 各自 Feature core。
取代 ADR-026 的下列決策:
- 「授權跟隨 surface owner」不再適用於 Actuator 維運面。
MailAuthority、AuditAuthority、PlatformInfoAuthority及其 AuthorityContributor/ SecurityContributor 不再屬 Feature core。PlatformInfoSecurityContributor原有的GET /api/v1/system/infopublic matcher 一併移到 application chain;controller ownership 不變。- ADR-026 遷移章節中把
mail:ops與 platform-info 授權三件套移回 Feature 的部分失效。
除上述固定 system handshake 的 path classification 外,產品 /api/** 的領域 authority 不受
影響,例如 mail_setting:* 仍隨 mail reference domain 進退。
權衡分析 (Trade-offs)
我們獲得什麼
- 新增 Feature endpoint 不再增加 Authority catalog、Role seed 與授權樣板類別。
- Spring Boot 內建與自訂 endpoint 都有同一個 fail-safe policy。
- application 仍可依實際風險做端點級覆寫。
我們放棄什麼
- Actuator endpoint 與其預設 authority 不再具有完全相同的原始碼生命週期。
- 只持有舊
{feature}:ops的外部身分必須遷移到actuator:read/actuator:write。 - read/write 是較粗粒度的維運能力;真正需要職責隔離的端點必須由應用明確覆寫。
實作與驗證
- Security chain 必須以
EndpointRequest覆蓋 Spring Boot 實際 Actuator mappings,且位於一般 authenticated fallback 前;不得假設 base path 永遠是/actuator。 - 測試至少涵蓋 Public、匿名 401、已認證但無 capability 403、read 可讀不可寫、write 可異動、
health root 明細只對 read authority 顯示、health component 子路徑仍受保護,以及舊
{feature}:ops不再授權。 - 架構測試必須掃描非
config/的 application source,阻擋非 application-ownedSecurityContributor使用/actuator字面或EndpointRequest。 - 授權整合測試必須以非預設 base path 驗證 Public、401、403 與 capability success 仍成立。
- links endpoint 必須測試 GET/HEAD 走 read、其他 method 走 write,避免 Spring Boot links matcher
未繼承
withHttpMethod的行為放寬 policy。 - same-port guard 必須驗證不同
management.server.port會 fail-fast;分離 port 不得靜默改由 Spring Boot 預設 management security 承接。 - Role seed 必須驗證 grants 與
revokedAuthorities無重疊,並涵蓋既有資料庫的冪等 reconcile。 - exposure allowlist、Bruno/API 測試與部署探針文件必須和實際 endpoint 分類一致。
相關文檔 (References)
變更歷史 (Change Log)
| 日期 | 變更內容 | 變更者 |
|---|---|---|
| 2026-08-29 | 接受集中式 Actuator 授權與固定 read/write capability | Development Team + AI Assistant |
文檔維護者: Development Team + AI Assistant 最後審閱: 2026-08-29