41 KiB
title, source_type, status, id, kind, project, work_item, inherits, refines, overrides, depends_on, contract_packet, branch, parent_branch, governing_docs, related_projects, tags, created, target_merge, status_label, contract_packet_sha256
| title | source_type | status | id | kind | project | work_item | inherits | refines | overrides | depends_on | contract_packet | branch | parent_branch | governing_docs | related_projects | tags | created | target_merge | status_label | contract_packet_sha256 | ||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| branch / feature-management-actuator-security-contract | branch-note | raw | BR-CA-SKELETON-OPERATIONAL-CONTRACT-021 | project-work-item | ca-skeleton-operational-contract | WI-CA-SKELETON-OPERATIONAL-CONTRACT-021 |
|
1 | feature-management-actuator-security-contract |
|
|
|
2026-05-22 | in-progress | 8efcccc3f1adafbda15730eb0506dc851a4177a83e02825fb0928f61536bf939 |
branch: feature-management-actuator-security-contract
Layer:
raw/branch-notes/— actuator/management endpoint 노출 보안 기준을 정의합니다.
부모 (필수)
raw/project-notes/ca-skeleton-operational-contract
브랜치 계약 패킷
- 생성 시 프로젝트 개정:
1 - 패킷 스키마:
contract_packet: 1 - 완료 조건: management endpoint exposure·authorization test가 통과한다
상속한 프로젝트 결정
| Decision Ref | Project Summary | Branch Application | Source |
|---|---|---|---|
DEC-CA-SKELETON-OPERATIONAL-CONTRACT-STACK-FRAMEWORK-001@1 |
framework는 Spring Boot 3.5.14다 | Spring Boot Actuator endpoint exposure와 authorization contract에 적용한다 | raw/project-notes/ca-skeleton-operational-contract |
브랜치 지역 결정
기존 branch-local 결정은 아래
## Decision Evidence Map / 결정-근거 매핑의 D-row가 소유하며 이 packet에서 복제하지 않는다.
| Decision ID | Decision | Relation | Supporting Claims | Status |
|---|
선언한 예외
| Override ID | Overrides | Reason | Approval | Status |
|---|
없음.
목표
actuator는 운영에 필수지만 잘못 노출되면 env, config, metric, health detail이 공격 표면이 됩니다. skeleton은 management endpoint allowlist와 profile별 노출 정책을 가져야 합니다.
- 이슈:
- PR:
범위
포함 범위
- actuator endpoint allowlist.
- health detail exposure 기준.
- metrics endpoint 인증 기준.
- management port 분리 여부.
- prod env/configprops 노출 금지.
- management endpoint security log 기준.
제외 범위
- Kubernetes ingress rule.
- cloud load balancer health check 설정.
- enterprise admin portal 구현.
근거 (필수, 최소 1개+)
본 branch의 결정 근거. 상세 비교는 §외부 근거 / 대안 조사 (있다면) 참조.
| Source | 정당화하는 결정 |
|---|---|
| raw/official-docs/actuator-endpoint-exposure-spring-official | Spring 공식 default + exposure 정책 |
| raw/official-docs/actuator-management-port-spring-official | Spring 공식 separate port 권고 |
| raw/official-docs/security-mtls-rfc-8705 | zero-trust 권장이나 cert 운영 부담 |
| raw/official-docs/actuator-istio-sidecar-management-alt | mesh 가정이 강함, skeleton 중립성 손실 |
| raw/company-tech-blogs/security-woowahan-actuator-safe-usage | 우아한형제들 SOC팀 — 별도 포트 + endpoint allowlist + shutdown/heapdump forbidden 권고 |
| raw/company-tech-blogs/security-toss-actuator-healthcheck | 토스 — health endpoint 자체도 민감 정보 포함 가능, public 접근 금지 |
| raw/official-docs/archunit-conditional-on-property-3-layer-pattern | ArchUnit custom rule — actuator-security 코드의 shape-ownership 정적 경계 강제 (D6 부분 근거) |
외부 근거 / 대안 조사 (2026-05-22 — Group G-B: Management / Actuator Security)
본 branch의 management port 9001 분리 + prod allowlist (health/prometheus/info) + heapdump/threaddump prod forbidden + loggers prod read-only 결정에 대한 외부 source.
- 채택 결정 (separate management port + prod allowlist):
- raw/official-docs/actuator-endpoint-exposure-spring-official — Spring 공식 default + exposure 정책
- raw/official-docs/actuator-management-port-spring-official — Spring 공식 separate port 권고
- 검토한 대안:
- 대안 1: Single port + path ACL — cloud ingress 환경에서 합리적; ca-tmpl이 "platform ingress 보호 문서화 시" 허용으로 포섭
- 대안 2: mTLS for management endpoints — raw/official-docs/security-mtls-rfc-8705 (zero-trust 권장이나 cert 운영 부담)
- 대안 3: Network ACL only — ca-tmpl baseline 선택 (단순 + 충분)
- 대안 4: Service mesh sidecar auth (Istio) — raw/official-docs/actuator-istio-sidecar-management-alt (mesh 가정이 강함, skeleton 중립성 손실)
- 비교 핵심: separate port 9001은 cloud-native + skeleton 중립성 우선. mTLS는 cert 부담, Istio는 mesh 종속. Single port는 platform ingress 보호 시 명시적으로 허용 — escape hatch 보유.
후속 보강 (2026-05-22): 한국 보안 사례 source 추가. raw/company-tech-blogs/security-woowahan-actuator-safe-usage (우아한형제들 SOC팀 — 별도 포트 + endpoint allowlist + shutdown/heapdump forbidden 권고) 및 raw/company-tech-blogs/security-toss-actuator-healthcheck (토스 — health endpoint 자체도 민감 정보 포함 가능, public 접근 금지) 참조.
후속 보강 (2026-06-14 — /branch-spec): D6 ownership 강제 메커니즘 근거로 raw/official-docs/archunit-conditional-on-property-3-layer-pattern (AUCP-C1~C4) 편입. ArchUnit 은 package/type/annotation 의 정적 경계만 강제 가능 (AUCP-C1) — "PR diff 의 shape 변경 감지" 는 ArchUnit 범위 밖이므로 그 부분은 CODEOWNERS/CI gate 로 위임 (§구현 가이드 §5). D7(Prometheus rate-limit 면제)은 자동조사 후에도 외부 normative 근거 없음 + rate-limit owner 미정의 → UNSUPPORTED_DECISION 유지 (cross-branch gap, §엣지·실패·의존).
TODO
TODO drained 2026-05-22 — 결정은 아래 "결정 사항" / "Exposure Policy" 참조. actuator allowlist / health detail exposure / metrics auth / management port / prod env·configprops forbidden / security log 기준 모두 결정 라인 또는 표로 반영됨. 잔존 TODO 없음.
진행 중 메모
- readiness/liveness와 management endpoint 보안은 연결되지만 별도 기준입니다.
결정 사항 (decisions)
- 2026-05-22: actuator exposure security를 runtime lifecycle에서 분리.
- 2026-05-22: actuator/health endpoint shape owner는
feature-runtime-health-lifecycle-contract, 이 branch는 endpoint exposure/auth/security log만 소유. - 2026-05-22: prod/staging management port는 분리 권장, 단일 port는 platform ingress 보호가 문서화될 때만 허용.
- 2026-05-22: management port default = 9001 (separate from app 8080). single-port는 platform ingress 보호 + 문서화 시만 허용.
- 2026-05-22: metrics endpoint 인증 = network ACL (cluster-internal scrape only) default. basic auth는 cluster 외부 노출 시 의무. mTLS는 zero-trust 환경에서 권장.
- 2026-05-22: heapdump/threaddump endpoint = prod forbidden, non-prod에서만 admin role.
- 2026-05-22: prod allowlist endpoint final =
health/liveness,health/readiness,health/startup,prometheus,info(build info only, no secret).env/configprops/heapdump/threaddump는 prod forbidden.loggers는 prod read-only. - 2026-05-22: Prometheus scrape는 rate-limit 면제 (network ACL로 보호).
Exposure Policy
| endpoint | prod default |
|---|---|
| liveness/readiness | exposed with minimal detail |
| metrics/prometheus | authenticated or management network only |
| env/configprops | forbidden |
| heapdump/threaddump | forbidden unless break-glass runbook |
| shutdown | forbidden |
Work Item Contract
각 TODO는 아래 판정 단위로 재작성되어야 canonical 승급 가능합니다. TODO가 단순히 기준 작성으로 남아 있으면 이 branch는 완료로 보지 않습니다.
| field | required | rule |
|---|---|---|
| Decision | yes | 구현자가 선택해야 하는 기본값 |
| Allowed | yes | 허용되는 예외와 조건 |
| Forbidden | yes | 절대 금지되는 구현/문서 상태 |
| Required registry update | conditional | error/env/header/log/metric/capability 변경 시 필수 |
| Required contract test | yes | 계약 위반 시 실패해야 하는 테스트 |
| Failure condition | yes | review/build에서 실패로 판정할 상태 |
| Canonical extraction target | yes | wiki/projects 승급 위치 |
결정-근거 매핑
각 결정이 어떤 raw source claim 으로 뒷받침되는지 명시. company-tech-blog 증거는
company-case-study로 표기하며 공식 best practice 로 승격하지 않음.
| Decision ID | Decision | Supporting Claims | Evidence Strength | Open Risk |
|---|---|---|---|---|
| D1 | management port = 9001 (separate from app 8080), single-port 는 platform ingress 보호 + 문서화 시만 허용 | raw/official-docs/actuator-management-port-spring-official.md#SB-ACT-PORT-C1, raw/official-docs/actuator-management-port-spring-official.md#SB-ACT-PORT-C2, raw/official-docs/actuator-management-port-spring-official.md#SB-ACT-PORT-C3, raw/company-tech-blogs/security-woowahan-actuator-safe-usage.md#WW-ACT-C4 · registry FACT: ca-tmpl/docs/registries/env-keys.yaml#MANAGEMENT_SERVER_PORT (default 9001, owner_branch 본 branch, required_test actuator-contract:management-port-separated) |
official-vendor-doc + company-case-study (Spring 이 두 옵션 모두 sensible 로 명시 — 어느 쪽이 absolute 최선 아님) |
port 번호 9001 자체는 Spring 권장 default 아님 (사용자 선택 — registry 에 고정됨). LoadBalancer/NodePort 실수 노출 방지 위한 network policy 검증 필요 |
| D2 | prod allowlist = health/liveness,health/readiness,health/startup,prometheus,info (build info only), env/configprops/heapdump/threaddump 는 prod forbidden, loggers 는 prod read-only |
raw/official-docs/actuator-endpoint-exposure-spring-official.md#SB-ACT-EXP-C1, raw/official-docs/actuator-endpoint-exposure-spring-official.md#SB-ACT-EXP-C2, raw/official-docs/actuator-endpoint-exposure-spring-official.md#SB-ACT-EXP-C4, raw/company-tech-blogs/security-woowahan-actuator-safe-usage.md#WW-ACT-C1, raw/company-tech-blogs/security-woowahan-actuator-safe-usage.md#WW-ACT-C2, raw/company-tech-blogs/security-woowahan-actuator-safe-usage.md#WW-ACT-C3 · registry FACT: ca-tmpl/docs/registries/error-codes.yaml#ACTUATOR_FORBIDDEN (AUTHZ/403, owner_branch 본 branch, required_test contract-verification:management-actuator) |
official-vendor-doc + company-case-study (Spring default sanitize env/configprops → ca-tmpl 은 한 단계 더 strict 한 자체 결정. heapdump/threaddump prod 금지는 Spring 공식 의무 아님) |
info 의 contributor 가 추가 정보로 secret 노출 가능 — review 통제 필요. env/configprops 부분 노출 시 secret masking 은 feature-secrets-config-source-contract 위임 (§엣지·실패·의존) |
| D3 | metrics endpoint 인증 = network ACL (cluster-internal scrape only) default, external 노출 시 basic auth 의무, zero-trust 에서 mTLS 권장 | raw/official-docs/actuator-endpoint-exposure-spring-official.md#SB-ACT-EXP-C2, raw/official-docs/actuator-endpoint-exposure-spring-official.md#SB-ACT-EXP-C3, raw/company-tech-blogs/security-woowahan-actuator-safe-usage.md#WW-ACT-C8 |
official-vendor-doc + company-case-study (Spring 권고 옵션 3개 중 firewall/Spring Security 선택 — 어느 쪽이 absolute 최선 아님) |
custom SecurityFilterChain 정의 시 Spring auto-secured 가 비활성 (SB-ACT-EXP-C3 의 흔한 함정) — actuator path 보호 룰을 명시적으로 검증 필요 |
| D4 | shutdown endpoint forbidden (모든 환경) | raw/company-tech-blogs/security-woowahan-actuator-safe-usage.md#WW-ACT-C6 |
company-case-study (Spring 공식 default 는 disabled 지만 절대 금지는 우아한형제들 운영 권고 — 공식 표준 아님) |
dev/staging 에서도 항상 금지인지 결정 — WW-ACT-C6 는 prod 강조로 해석. local 단축키 필요 시 별도 escape hatch 필요 |
| D5 | heapdump/threaddump endpoint = prod forbidden, non-prod 에서만 admin role 로 허용 | raw/company-tech-blogs/security-woowahan-actuator-safe-usage.md#WW-ACT-C3 |
company-case-study (Spring 공식 의무 아님 — 회사 운영 권고) |
non-prod 에서 admin role 발급/회수 절차 미정의 — IAM branch 와 cross-link 필요 |
| D6 | shape-ownership 경계 강제 — actuator-security 코드는 HealthEndpoint/HealthIndicator/HealthComponent 를 선언·구현하지 않음 (shape owner 는 feature-runtime-health-lifecycle-contract, 본 branch 는 exposure/auth 만 소유) |
raw/official-docs/archunit-conditional-on-property-3-layer-pattern.md#AUCP-C1 (ArchUnit custom rule = classes that ${PREDICATE} should ${CONDITION} — package/type/annotation 정적 boundary 강제 가능), raw/official-docs/archunit-conditional-on-property-3-layer-pattern.md#AUCP-C2 (classpath 有 시 type/annotation 접근) · 정합: raw/branch-notes/feature-runtime-health-lifecycle-contract D2 (shape owner 분할 SSOT) |
official-vendor-doc (partial — ArchUnit static package/type boundary 한정) |
PR diff 기반 shape-change 감지는 ArchUnit 범위 밖 (bytecode static ≠ git diff — AUCP Usage Boundary §"증명하지 않는 것"). 그 부분은 CODEOWNERS / CI diff gate 로 위임 = UNSUPPORTED_IMPL_DECISION (§구현 가이드 §5). HealthEndpoint 등 Spring type 의존 rule 이므로 classpath 필요 |
| D7 | Prometheus scrape 는 rate-limit 면제 (network ACL 로 보호) | UNSUPPORTED_DECISION (rate-limit 면제는 cited official-doc 직접 인용 없음 + rate-limit owner raw/branch-notes/feature-rate-limit-idempotency-contract 도 metrics/scrape 예외를 결정하지 않음 — 그 branch D4 rate-limit-key 자체가 UNSUPPORTED) |
n/a | cross-branch gap: scrape 예외 메커니즘을 rate-limit owner 가 SSOT 로 정의해야 함. 미정의 시 prometheus scrape 가 rate-limit 에 걸려 metrics gap (§엣지·실패·의존). raw/branch-notes/feature-rate-limit-idempotency-contract 와 동시 결정 필요 |
| D8 | (보조 정합) 토스 — health endpoint 자체도 보안 민감 정보 포함 가능, public 접근 금지 | raw/company-tech-blogs/security-toss-actuator-healthcheck.md#TOSS-HEALTH-C1, raw/company-tech-blogs/security-toss-actuator-healthcheck.md#TOSS-HEALTH-C5, raw/company-tech-blogs/security-toss-actuator-healthcheck.md#TOSS-HEALTH-C6 |
company-case-study (best practice 승격 금지 — 토스 한국 사례) |
show-details: always 가 prod 에서 우회로 활성되지 않도록 ArchUnit 또는 config 검증 필요 |
구현 가이드
결정 (Decisions) 이 "무엇 을 할 것인가" 라면, 본 §는 "어디에 어떻게 구현될 것인가" 의 사전 명세 — 다음 구현자가 되묻지 않아도 코드를 작성할 수 있는 수준.
구현 상태 (ca-tmpl ground truth, updated 2026-06-15): Phase C2 구현 완료.
application.yml에management:block 추가됨,ManagementActuatorSecurityContractTest4개 계약 테스트 통과,ManagementSecurityConfig@Order(0)SecurityFilterChain 구현,management_security_does_not_depend_on_health_internalsArchUnit rule 통과.ACTUATOR_FORBIDDENenum 추가됨. 상태:actually-implemented(D1/D2/D3/D4/D6/D8) +locally-verified. D5(non-prod admin role) + D7(rate-limit carve-out)는 여전히planned.Post-review 경화 (2026-06-15, /branch-spec 검증 follow-up): 검토에서 드러난 3개 테스트/동작 gap 보강 — (F1)
ManagementActuatorSecurityContractTest가 실제application.yml의 include/exclude/port/show-details/shutdown 을 파싱·고정(주입값 검증의 tautology 제거, 9개 테스트로 확장), (F2) 신규ActuatorSecurityHttpTest(7개) 가 MockMvc 로 SecurityFilterChain 을 HTTP 레벨로 구동 — health/info/prometheus 200, loggers 비인증 401, env 404(excluded). 이를 위해ManagementSecurityConfig에HttpStatusEntryPoint(401)명시(프레임워크 default 403 → 의미상 올바른 401), (F3) loggers prod read-only 를 실제 강제 —POST /actuator/loggers/**denyAll()추가(이전엔 authenticated 면 log level 변경 가능했음 = 계약 위반). app-bootstrap 전체 410 테스트 green, 회귀 없음.
1. Management port separation (D1)
Trace: D1 +
SB-ACT-PORT-C1~C3+WW-ACT-C4. registry FACT:ca-tmpl/docs/registries/env-keys.yaml#MANAGEMENT_SERVER_PORT(default 9001, owner 본 branch, required_testactuator-contract:management-port-separated).
- UNSUPPORTED_IMPL_DECISION: port 번호 9001 은 Spring 권장 default 아님 (사용자 선택) — registry 에 고정. trade-off: 8080(app)과 충돌만 피하면 임의값 가능, 9001 은 관례적 선택.
| 항목 | 명세 (planned) | anchor |
|---|---|---|
| config key | management.server.port ← ${MANAGEMENT_SERVER_PORT:9001} (application.yml 에 management.server block 신규 추가) |
env-keys.yaml#MANAGEMENT_SERVER_PORT |
| app port (consume only) | server.port ← ${APP_SERVER_PORT:8080} — owner feature-env-driven-runtime-configuration, 본 branch 는 분리 대상으로만 참조 |
env-keys.yaml#APP_SERVER_PORT |
| contract test | actuator-contract:management-port-separated — app port 와 management port 가 다른 listener 인지 검증 (planned) |
required_test |
| single-port escape hatch | management.server.port 미설정 = app port 공유 허용, 단 platform ingress path ACL 보호가 문서화될 때만 (D1 조건) |
— |
2. Prod exposure allowlist (D2)
Trace: D2 +
SB-ACT-EXP-C1/C2/C4+WW-ACT-C1~C3. registry FACT: forbidden endpoint 접근 →error-codes.yaml#ACTUATOR_FORBIDDEN(AUTHZ/403, client_safe "Permission denied", log WARN, required_testcontract-verification:management-actuator).
- UNSUPPORTED_IMPL_DECISION: non-prod 의 정확한 노출 집합은 cited doc 이 권고하지 않음 — prod 만 strict allowlist, non-prod 는 더 넓게(운영 편의) = 사용자 trade-off.
exclude명시 vs include-only 의 선택도 운영 trade-off (여기선 defense-in-depth 위해 forbidden 을 explicitexclude).
| profile | management.endpoints.web.exposure.include |
...exposure.exclude |
비고 |
|---|---|---|---|
| prod | health,prometheus,info,loggers |
env,configprops,heapdump,threaddump,shutdown |
loggers 는 read-only(write 차단은 SecurityFilterChain §3). info = build info only, no secret |
| non-prod | 더 넓게 허용 (UNSUPPORTED_IMPL — 정확 집합 미정) | shutdown (항상, D4) |
heapdump/threaddump 는 admin role 게이트(§4) |
- forbidden endpoint 접근 시
ACTUATOR_FORBIDDEN(403, WARN log) — 보안 이벤트 로그 필수(§테스트 계약). runbookrunbook://management/actuator-forbidden는 planned(아직docs/runbooks/부재). - DELEGATED (R3): env/configprops 가 부분 노출되는 경로의 secret masking 은 본 branch 범위 밖 →
feature-secrets-config-source-contract(secrets-contract:db-password-no-leak-in-actuator,datasource-username/url-masked-in-actuator).
3. Metrics / actuator auth (D3)
Trace: D3 +
SB-ACT-EXP-C2/C3+WW-ACT-C8.
- UNSUPPORTED_IMPL_DECISION: custom
SecurityFilterChain정의 시 Spring 의 actuator auto-secure 가 비활성(SB-ACT-EXP-C3함정) → actuator path 보호를 명시적 rule 로 작성해야 함. matcher 표현(EndpointRequest.toAnyEndpoint()등)은 Spring Security 관용이나 정확한 bean 모양은 본 branch 결정 아닌 구현 detail.
| 노출 위치 | 기본 (planned) | 강화 옵션 |
|---|---|---|
| cluster-internal scrape | network ACL only (app-level auth 없음) — baseline | — |
| cluster 외부 노출 | basic auth 의무 (D3) | zero-trust 환경 mTLS — security-mtls-rfc-8705, cert 운영 부담으로 baseline 아님 |
- SecurityFilterChain bean (
ManagementSecurityConfig,actually-implemented):securityMatcher(EndpointRequest.toAnyEndpoint())로 actuator path 만 가로채고, health/info/prometheuspermitAll(), 나머지authenticated(). 비인증 접근은HttpStatusEntryPoint(HttpStatus.UNAUTHORIZED)로 401 응답(default 403 아님 —ActuatorSecurityHttpTest.loggers_endpoint_challenges_unauthenticated_caller_with_401검증). loggers write 차단은 아래 §4 가 아닌 본 체인의POST /actuator/loggers/** denyAll()+DELETE /actuator/loggers/** denyAll()두 라인으로 구현(D2 read-only). DELETE 는 logger-level reset mutation 으로 POST 와 동일한 write 위험 — 함께 막아야 일관성 보장.
4. Dangerous endpoints (D4, D5)
Trace: D4(shutdown forbidden 전 환경,
WW-ACT-C6) + D5(heapdump/threaddump prod forbidden·non-prod admin role,WW-ACT-C3).
- UNSUPPORTED_IMPL_DECISION: non-prod admin role 의 발급/회수 절차 는 본 branch 결정 근거 없음 → IAM/security branch 위임(D5 Open Risk). "절대 금지(전 환경)" vs "non-prod escape hatch" 는 D4 의 운영 trade-off(local 단축키 필요 시 별도 hatch).
shutdown: 모든 profileexclude(Spring default disabled 와 정합, D4 는 한 단계 더 — explicit 금지).heapdump/threaddump: prodexclude; non-prod 는 admin role gate(절차 미정 = planned).
5. Shape-ownership boundary enforcement (D6)
Trace: D6 +
AUCP-C1(ArchUnit custom rule PREDICATE/CONDITION) +AUCP-C2(classpath type 접근) + 정합 raw/branch-notes/feature-runtime-health-lifecycle-contract#D2`(shape owner 분할).
- SUPPORTED (정적 경계): ArchUnit rule — actuator-security package 의 class 가
HealthEndpoint/HealthIndicator/HealthComponent를 선언·구현·의존하지 않는다.noClasses().that().resideInAPackage("..management.security..").should().dependOnClassesThat().areAssignableTo(HealthIndicator.class)형태 (AUCP-C1 표준 형식, classpath 필요 → AUCP-C2).- UNSUPPORTED_IMPL_DECISION: "PR diff 에서 health response field 변경 감지"(현 §테스트 계약 표현)는 ArchUnit 범위 밖(bytecode static ≠ git diff — AUCP Usage Boundary). → CODEOWNERS / CI diff gate 로 위임. trade-off: ArchUnit 은 구조 경계만, 변경 출처는 CI 책임.
6. Prometheus rate-limit exemption (D7)
Trace: D7 —
UNSUPPORTED_DECISION(자동조사 후에도 외부 normative 근거 없음).
- UNSUPPORTED_IMPL_DECISION (cross-branch gap): prometheus scrape path 의 rate-limit carve-out 메커니즘은 rate-limit owner(raw/branch-notes/feature-rate-limit-idempotency-contract)가 정의해야 SSOT 정합. 현재 그 branch 는 metrics 예외를 결정하지 않음(D4 rate-limit-key 자체 UNSUPPORTED). 본 branch 는 network ACL 보호를 가정만 함 — filter 예외 코드는 rate-limit branch 와 동시 결정 전까지
planned.
- 임시 운영선 (interim, rate-limit owner 결정 전까지): prometheus scrape 는 network ACL (cluster-internal scrape only) 단독 의존으로 운영 — rate-limit filter 를 적용하지 않는 별도 management network 에 둠(D1 의 management port 9001 분리가 이 격리를 제공). 즉 carve-out 코드를 짜지 않고도 "scrape 가 rate-limit 에 걸려 metrics 가 비는" 실패 경로가 발생하지 않음(scrape 트래픽이 rate-limited app port 를 통과하지 않으므로). 본격 filter carve-out 은 management endpoint 가 app port 와 단일 포트로 합쳐지는(single-port escape hatch, D1) 경우에만 필요해지며, 그 때 rate-limit owner 와 동시 PR.
엣지·실패·의존
R4(깊이 게이트) 캡처용. 정상 경로 외에 구현 중 부딪힐 실패/엣지/다른 계약 의존을 미리 열거.
- 실패·엣지 경로:
- forbidden endpoint 접근 → 403
ACTUATOR_FORBIDDEN+ WARN 보안 로그. 접근 실패가 security event log 에 안 남으면 테스트 fail(§테스트 계약). - custom
SecurityFilterChain가 actuator auto-secure 를 비활성화 → actuator path 가permitAll로 누수(SB-ACT-EXP-C3). 기대: 통합 테스트로/actuator/env비인증 접근 시 401/403. - single-port mode 에서 ingress path ACL 누락 → management endpoint 가 public LB 로 노출. 기대: network policy 검증(D1 Open Risk).
- prometheus scrape 가 rate-limit 에 걸림 → metrics gap. 기대: scrape carve-out(D7) — 현재 미구현.
info/show-details: always가 prod profile 에 실수로 override → 민감정보 노출(D8). 기대: prod profile config 검증.
- forbidden endpoint 접근 → 403
- 다른 계약 의존:
- raw/branch-notes/feature-runtime-health-lifecycle-contract
D2에 의존 — health endpoint shape owner. 본 branch 는 exposure/auth 만. shape(/actuator/health/{liveness,readiness,startup}sub-path)가 바뀌면 allowlist 의 health 항목 영향. - raw/branch-notes/feature-secrets-config-source-contract 에 의존 — actuator 출력 내 secret masking(
db-password-no-leak-in-actuator,datasource-username/url-masked-in-actuator). env/configprops 부분 노출 시 masking 은 이 owner. - raw/branch-notes/feature-env-driven-runtime-configuration 에 의존 —
APP_SERVER_PORT(8080) owner. 본 branch 의MANAGEMENT_SERVER_PORT(9001) 와의 분리 전제. consume only. - raw/branch-notes/feature-rate-limit-idempotency-contract 에 의존(미해소 gap) — prometheus scrape rate-limit 예외. 현재 그 branch 가 정의 안 함(D7).
- raw/branch-notes/feature-runtime-health-lifecycle-contract
테스트 계약
- prod에서 env/configprops endpoint가 노출되면 실패.
- health detail이 prod에서 과노출되면 실패.
- metrics endpoint가 인증 없이 열리면 실패.
- management endpoint 접근 실패가 security event log에 남지 않으면 실패.
- shape ownership 위반 검사: 본 branch의 PR diff에서
org.springframework.boot.actuate.health.HealthEndpoint,HealthIndicator,/actuator/health/*endpoint response field 변경 시 fail. 측정 방법: PR diff filter — actuator security branch가 owner인 영역(exposure, port, auth)이 아닌 response shape 영역(HealthEndpoint,HealthIndicator,HealthComponent) 변경이 포함되면 review reject. ArchUnit으로 이 branch가 자칭 owner인 file 외 변경 금지. (⚠️ ArchUnit 은 정적 구조 경계만 — PR diff 변경 감지는 CODEOWNERS/CI gate 책임. §구현 가이드 §5 의 SUPPORTED/UNSUPPORTED 분리 참조.)
검증해야 할 주장
공식 문서나 사례는 근거지만, 내 프로젝트에서의 동작을 자동으로 보장하지 않는다.
| Claim | Why uncertain | How to verify | Status |
|---|---|---|---|
prod 에서 management.endpoints.web.exposure.include=health,prometheus,info 설정 시 실제 노출되는 sub-endpoint 집합 (특히 /actuator/health/liveness group sub-path 포함 여부) |
SB-ACT-EXP-C1/C2 는 default 만 다룸 — group sub-path 노출 동작은 별도 페이지 |
local 통합 테스트로 /actuator/health/liveness curl + status 200 확인 + /actuator/env 403/404 확인 |
planned |
custom SecurityFilterChain 정의된 ca-tmpl 환경에서 actuator path 가 permitAll() vs authenticated() 어디로 떨어지는지 |
SB-ACT-EXP-C3 가 명시한 함정 — auto-config 비활성 시 명시적 설정 필요 |
SecurityFilterChain bean 정의 검증 + 통합 테스트로 /actuator/env 비인증 접근 시 401 확인 |
needs-confirmation |
| prometheus endpoint 의 prod 노출 시 scrape 인증 (network ACL 만으로 충분한지) | D3 의 network ACL 가정은 클러스터 외부 노출 차단 의존 — 별도 검증 | k8s NetworkPolicy 적용 + 외부 IP 에서 /actuator/prometheus curl 시 차단 확인 |
planned |
| ArchUnit 기반 shape ownership 검사 (D6) 의 실 구현 가능 여부 | D6 — ArchUnit 은 정적 boundary(AUCP-C1)만, diff 감지는 범위 밖. fitness function 도입 결정 코드 단계 보류 | raw/official-docs/archunit-conditional-on-property-3-layer-pattern 의 AUCP-C1~C4 검토 후 noClasses().should().dependOnClassesThat().areAssignableTo(HealthIndicator) rule 작성 가능성 평가 + CODEOWNERS gate 분리 |
needs-confirmation |
| heapdump/threaddump non-prod admin role 발급/회수 절차 (D5) | non-prod IAM 정책이 정의되지 않음 | IAM branch 와 cross-link, admin role 발급 runbook 작성 | planned |
show-details: always 가 prod profile 에서 차단되는지 (D8 관련) |
Spring profile 별 config override 가 실수로 prod 에 적용 가능 | ArchUnit 또는 @Value("${management.endpoint.health.show-details}") 확인 + prod profile 통합 테스트 |
planned |
| prometheus scrape 의 rate-limit 예외 (D7) 가 어느 owner 의 어느 메커니즘으로 구현되는지 | rate-limit owner(feature-rate-limit-idempotency-contract)가 metrics 예외를 미정의 — cross-branch gap |
rate-limit branch 와 동시 결정: scrape path carve-out 을 rate-limit filter SSOT 에 추가할지 vs network ACL 단독 의존할지 | needs-confirmation |
관심사 커버리지 (coverage-auditor 자동 생성 — 2026-06-14)
/coverage(coverage-auditor) 생성물 — governing docwiki/projects/ca-tmpl/security-baseline-jwt-actuator-secrets.md의 Actuator axis(§D2)가 요구하는 관심사를 이 branch 가 빠짐없이 덮는지의 결과. 판정: Covered (missing 0 / Blocking 0). 기준:rules/coverage-gate.md.
| 관심사 | 상태 | owner | 심각도 | 근거 |
|---|---|---|---|---|
| Management port 9001 분리 | covered-here | — | — | D1 · env-keys.yaml#MANAGEMENT_SERVER_PORT (owner_branch 본 branch) |
Prod exposure allowlist (health/prometheus/info) |
covered-here | — | — | D2 · §구현 가이드 §2 · governing doc §D2 |
env/configprops prod forbidden |
covered-here | — | — | D2 · error-codes.yaml#ACTUATOR_FORBIDDEN (owner_branch 본 branch) |
heapdump/threaddump prod forbidden |
covered-here | — | — | D2 + D5 · §구현 가이드 §4 |
shutdown endpoint forbidden (전 환경) |
covered-here | — | — | D4 · §구현 가이드 §4 |
loggers prod read-only |
covered-here | — | — | D2 · §구현 가이드 §2 표 |
| Metrics network ACL default (metrics auth) | covered-here | — | — | D3 · §구현 가이드 §3 |
| Health detail exposure 기준 (show-details policy) | covered-here | — | — | D8 · Claims To Verify (show-details prod 차단 검증) |
| Health endpoint shape | delegated | raw/branch-notes/feature-runtime-health-lifecycle-contract | OK | 위임: raw/branch-notes/feature-runtime-health-lifecycle-contract D2 (양방향 owner 합의) · §엣지·실패·의존 |
| Secret masking inside actuator output (env/configprops 부분 노출) | delegated | raw/branch-notes/feature-secrets-config-source-contract | OK | 위임: raw/branch-notes/feature-secrets-config-source-contract (secrets-contract:datasource-username/url-masked-in-actuator registry test) · 역방향 위임 링크 존재 |
| Prometheus rate-limit 면제 (scrape carve-out) | delegated | raw/branch-notes/feature-rate-limit-idempotency-contract | Should-fix | 위임: raw/branch-notes/feature-rate-limit-idempotency-contract (D7, cross-branch gap — rate-limit owner 미정의). 임시 운영선 = network ACL 단독(§구현 가이드 §6). owner 가 carve-out 을 결정하면 동시 PR |
| Security event logging (forbidden 접근 시 WARN) | covered-here | — | — | §테스트 계약 · §구현 가이드 §2 · error-codes.yaml#ACTUATOR_FORBIDDEN (log_level: WARN) |
| Ownership boundary (shape vs exposure 분리) | covered-here | — | — | D6 · §구현 가이드 §5 · runtime-health D2 양방향 포인터 |
마주친 문제
-
2026-06-15: app-bootstrap compile classpath에 Spring Security 없음
- 원인:
adapter-web이spring-boot-starter-security를implementation(notapi) 으로 선언 →app-bootstrap의 compile classpath 에 security 타입 없음. - 시도:
ManagementSecurityConfig가HttpSecurity,SecurityFilterChain을 import → compileJava 실패 (6 errors). - 해결:
app-bootstrap/build.gradle에implementation 'org.springframework.boot:spring-boot-starter-security'추가. composition root 가 cross-cutting security wiring 을 소유하는 것은 정상 (AGENTS.md §app-bootstrap). - 별도 에러 노트로 분리됨: 불필요 (원인·해결이 1-liner, 재발 가능성 낮음)
- 원인:
-
2026-06-15: @SpringBootTest 에서 dual-port 충돌 방지
- 원인:
management.server.port=9001설정 시@SpringBootTestfull-context 가 두 번째 포트를 바인드하려 해 기존 smoke 테스트와 충돌 가능. - 해결:
application-test.yml에management.server.port=0오버라이드 추가 (random port). 계약 테스트는ApplicationContextRunner(no live server) 로 properties 검증 — 포트 충돌 없음.
- 원인:
묶음
- raw/company-tech-blogs/security-toss-actuator-healthcheck
- raw/company-tech-blogs/security-woowahan-actuator-safe-usage
- raw/official-docs/actuator-endpoint-exposure-spring-official
- raw/official-docs/actuator-istio-sidecar-management-alt
- raw/official-docs/actuator-management-port-spring-official
- raw/official-docs/runtime-health-spring-actuator-groups
- raw/official-docs/security-authorization-cheatsheet-owasp
- raw/official-docs/security-jwt-rfc-7519-validation
- raw/official-docs/security-mtls-rfc-8705
Phase C2 실 코드 작성 완료 (2026-06-15). 아래 항목 실제 구현됨.
Implemented (2026-06-15) — actually-implemented + locally-verified
src/shared-contract/.../error/OperationalError.java—ACTUATOR_FORBIDDEN(Category.AUTHZ, 403, false)상수 추가 (D2 registry 정합)src/app-bootstrap/build.gradle—spring-boot-starter-actuator,micrometer-registry-prometheus,spring-boot-starter-security추가src/app-bootstrap/src/main/resources/application.yml—management:block 신규 추가: port 9001, exposure allowlist, exclude list, show-details: when-authorized, shutdown.enabled: false, info.build.enabled: truesrc/app-bootstrap/src/test/resources/application-test.yml—management.server.port: 0오버라이드 (test dual-port 방지)src/.env—MANAGEMENT_SERVER_PORT=9001추가src/app-bootstrap/.../management/security/ManagementSecurityConfig.java—@Order(0)actuatorSecurityFilterChain: health/info/prometheus permitAll, 나머지 authenticatedsrc/app-bootstrap/src/test/.../architecture/CleanArchitectureTest.java—management_security_does_not_depend_on_health_internalsArchUnit rule 추가 (D6 정적 경계)src/app-bootstrap/src/test/.../contract/ManagementActuatorSecurityContractTest.java— 4개 계약 테스트 신규 작성 (error-code/management-port-separated/exposure-policy/show-details-when-authorized)
Post-review hardening (2026-06-15, ca-quality-reviewer fixes) — actually-implemented + locally-verified
ManagementSecurityConfig.java—DELETE /actuator/loggers/**denyAll() 추가 (POST 와 나란히). logger-level reset 도 write mutation — POST 단독 차단은 불완전했음.ActuatorSecurityHttpTest.java—loggers_reset_via_delete_is_denied테스트 추가 (DELETE /actuator/loggers/dev.caskeleton → 403).deleteMockMvcRequestBuilders import 추가.ManagementActuatorSecurityContractTest.java—health_show_details_is_when_authorized_not_always메서드 삭제. 이 메서드는.withPropertyValues("management.endpoint.health.show-details=when-authorized")로 값을 직접 주입하고 같은 값을 assert 하는 tautology — 실제application.ymlregression 을 감지할 수 없었음. 진짜 regression guard 는application_yml_pins_show_details_when_authorized_and_shutdown_disabled(main application.yml artifact 파싱) 이며, 이 테스트는 그대로 유지됨.
Verification (locally-verified)
| Command | Result |
|---|---|
./gradlew :shared-contract:test |
BUILD SUCCESSFUL |
./gradlew :app-bootstrap:test --tests '*ManagementActuatorSecurityContractTest' |
BUILD SUCCESSFUL |
./gradlew :app-bootstrap:test --tests '*CleanArchitectureTest' |
BUILD SUCCESSFUL |
./gradlew :app-bootstrap:test |
BUILD SUCCESSFUL (full suite) |
./gradlew verifyCleanArchitectureDependencies |
BUILD SUCCESSFUL |
./gradlew verifyEnvKeys |
BUILD SUCCESSFUL — 98 env keys, 74 required placeholders |
./gradlew verifyPublicPathSnapshot |
BUILD SUCCESSFUL — 1 public path unchanged |
Post-review hardening verification (2026-06-15)
| Command | Result |
|---|---|
./gradlew :app-bootstrap:compileTestJava |
BUILD SUCCESSFUL |
./gradlew :app-bootstrap:test --tests '*ManagementActuatorSecurityContractTest' --tests '*ActuatorSecurityHttpTest' |
BUILD SUCCESSFUL |
./gradlew :app-bootstrap:test |
BUILD SUCCESSFUL (full module, no regression) |
Claims To Verify — 상태 업데이트 (2026-06-15)
| Claim | Status 변경 |
|---|---|
| ArchUnit shape-ownership rule (D6) 실 구현 가능 여부 | actually-implemented — management_security_does_not_depend_on_health_internals rule 작성됨, CleanArchitectureTest 통과 확인 |
show-details: always prod 차단 (D8) |
locally-verified — contract test health_show_details_is_when_authorized_not_always 통과 |
| management port 분리 (D1) | locally-verified — contract test management_server_port_defaults_to_9001_and_differs_from_app_port 통과 |
| exposure allowlist (D2) | locally-verified — contract test forbidden_endpoints_are_not_in_exposure_include_allowlist 통과 |
custom SecurityFilterChain 에서 actuator path 가 permitAll vs authenticated 어디로 떨어지는지 (SB-ACT-EXP-C3 함정) |
locally-verified — ActuatorSecurityHttpTest HTTP 구동: health/info/prometheus 200, loggers 비인증 401, env 404 |
| application.yml 의 실제 include/exclude/port/show-details/shutdown 값 (주입값이 아닌 artifact 고정) | locally-verified — application_yml_* 4개 테스트가 main application.yml 파싱·단언(teeth-check 로 regression 감지 확인) |
| loggers prod read-only (D2) — 인증된 caller 도 log level 변경 불가 | actually-implemented + locally-verified — POST /actuator/loggers/** denyAll() + DELETE /actuator/loggers/** denyAll(), loggers_write_is_denied_even_for_authenticated_caller (POST 403) + loggers_reset_via_delete_is_denied (DELETE 403) + loggers_read_is_allowed_for_authenticated_caller (200) |
show-details: always prod 차단 (D8) — tautology 제거, real pin test 만 유지 |
locally-verified — application_yml_pins_show_details_when_authorized_and_shutdown_disabled 가 main application.yml 파싱·단언(진짜 regression guard). tautological health_show_details_is_when_authorized_not_always 삭제됨 (2026-06-15 post-review). |
Non-goals (this task) — OUT_OF_BRANCH_SCOPE
- Health endpoint GROUPS (
management.endpoint.health.group.*) — runtime-health branch owns - Runbook stub bodies (
docs/runbooks/management-actuator-forbidden.md) — operational-runbook branch owns adapter-web변경 없음 (existingSecurityConfiguntouched — actuator chain is additive)
오류 기록 (본 feature 작업 중 발생)
- Spring Security compile classpath 문제 (해결됨 — §마주친 문제 참조)
- test dual-port 방지 (해결됨 — §마주친 문제 참조)
면접 준비 (이 작업에서 나올 수 있는 면접 질문)
[[raw/interviews/actuator-security-management-port-interview]]— "Spring Boot actuator를 별도 포트로 분리하는 이유와 SecurityFilterChain 순서 제어(Order) 방법"
블로그·채용공고 연계 글감
[[raw/blog-topics/spring-actuator-security-separate-port-archunit]]— "Spring Boot actuator 별도 포트 + ArchUnit으로 health shape-ownership 경계 강제하기"
관련 일일 노트
[[raw/daily-notes/2026-06-15]]
완료 후 정리
- PR 링크:
- 리뷰 메모:
- 머지 결과 / 배포 환경: locally-verified (worktree — rebase 후 통합 예정)
- wiki 추출 대상 (verified만,
wiki/projects/로만 추출):actually-implemented항목: ACTUATOR_FORBIDDEN enum, ManagementSecurityConfig (@Order(0)+ 401 entry point + loggersPOST denyAll+DELETE denyAll), ArchUnit rule, contract test (8, tautology 1개 삭제 후) + HTTP 통합 테스트 (ActuatorSecurityHttpTest, 8,loggers_reset_via_delete_is_denied추가)locally-verified항목: management port separation, exposure policy(application.yml artifact 고정), show-details (real pin test only — tautology removed), env-key gate, HTTP 보안 posture(probe 200 / 비인증 401 / excluded 404), loggers read-only(POST 403 + DELETE 403)prod-verified항목: (없음 — local worktree only)
- 추출하지 않을 항목 (planned / documented-only / abandoned):
- heapdump/threaddump non-prod admin role 절차 (D5 — planned, IAM branch 위임)
- prometheus rate-limit carve-out (D7 — cross-branch gap, rate-limit branch 위임)