init: llm-wiki-haness 하네스 설계

This commit is contained in:
DongHyeonka
2026-07-24 14:21:35 +09:00
parent 42bf3db4fd
commit 6c53ded9cb
2436 changed files with 194486 additions and 1 deletions
@@ -0,0 +1,158 @@
---
title: ca-tmpl - Security Baseline 결정 (JWT + Actuator + Secrets)
source_type: project
status: verified
confidence: high
tags: [ca-tmpl, security, jwt, oauth2, secrets, actually-implemented, locally-verified]
related_projects: [ca-tmpl]
last_reviewed: 2026-07-02
---
# ca-tmpl - Security Baseline 결정 (JWT + Actuator + Secrets)
> Layer: `wiki/projects/` — ca-tmpl skeleton 내 보안 baseline 결정 사실 문서. 일반 개념/표준 정의는 [[wiki/concepts/security-baseline-jwt-actuator-secrets]] 참고.
## 프로젝트 컨텍스트
`ca-tmpl`은 Clean Architecture 기반 Spring Boot **skeleton/template** 저장소다. 이 문서가 다루는 범위는 운영 계약([[raw/project-notes/ca-skeleton-operational-contract]] §18 Control Plane Contract, §29 Group G-B 외부 근거 인덱스) 중 **보안 baseline 세 축**의 설계 결정이다.
세 축:
1. **데이터면 인증/인가**: JWT Resource Server + AuthN/AuthZ matrix 12행 + JWKS 10분 refresh + clock skew tolerance 60s + key rotation overlap 24h + public path snapshot diff.
2. **제어면 (Actuator)**: management port **9001** 분리 + prod allowlist (`health` / `prometheus` / `info`) + `heapdump`/`threaddump`/`env`/`configprops`/`shutdown` prod forbidden + `loggers` prod read-only + metrics network ACL default.
3. **Secrets / Config**: prod = secret manager OR mounted env, local만 `.env` 허용. `no-runtime-reload` default, `@RefreshScope` 금지. JWT signing key 24h overlap / DB credential dual-bind 60s / API key restart-reload / HMAC salt 90d rotation.
**진행 상태: C2 부분 구현 + 로컬 검증 완료.** JWT Resource Server filter chain, lazy JWT decoder, security error classifier/envelope entry point, actuator management policy, secret source/reload guard는 코드화되어 있다. secret manager 연동과 실제 rotation automation은 아직 없다.
## 실제 구현 내용 (`actually-implemented`)
- `adapter-web``SecurityConfig``SecurityFilterChain``oauth2ResourceServer`를 구성한다.
- `JwtDecoderConfig``SupplierJwtDecoder`로 JWKS discovery를 lazy 처리하고 `JwtTimestampValidator(Duration.ofSeconds(60))`, issuer, audience validator를 명시한다.
- `SecurityErrorClassifier`와 envelope entry point/denied handler 테스트가 filter-layer 보안 실패를 API error envelope으로 분류한다.
- `MethodSecurityConfig`, `RequiresPermission`, `AuthorizationPort`, `AuthorizationContractTest`가 framework-free method authorization path를 구성한다.
- `app-bootstrap``ManagementSecurityConfig`, sample management config, `SecretSource*`, `SecretReloadContractTest`가 actuator/secret baseline 일부를 코드화한다.
## 로컬/dev 검증 (`locally-verified`)
- `./gradlew check` 통과(2026-07-02, `BUILD SUCCESSFUL`, 114 tasks).
- `SecurityErrorClassifierTest`, `JwtDecoderConfigTest`, `EnvelopeAuthenticationEntryPointTest` 등 web security/error path 테스트가 통과한다.
- `ManagementActuatorSecurityContractTest`, `ActuatorSecurityHttpTest`가 management port/exposure/loggers read-only 정책을 검증한다.
- `SecuritySettingsTest`, `SecretSourceTest`, `SecretSourceValidatorTest`, `SecretReloadContractTest`가 설정/secret source/reload guard를 검증한다.
## 운영 검증 (`prod-verified`)
**없음.** ca-tmpl은 skeleton/template이며 운영 배포 대상이 아니다. prod 환경에서 JWT 검증 latency·JWKS rotation·secret rotation·actuator endpoint 노출을 측정한 적이 없다.
## 문서/계획만 존재 (`documented-only` / `planned`)
다음 항목은 구현된 baseline과 아직 `documented-only` / `planned`로 남은 영역을 분리한다. 면접/블로그에서 구현 범위와 혼동하면 안 된다.
### D1. JWT Resource Server 채택 (`actually-implemented` / `locally-verified`)
- **결정**: 데이터면 인증을 OAuth2 Resource Server + JWT (`spring-boot-starter-oauth2-resource-server`) 로 표준화.
- **검토한 대안**:
- Session + Cookie — 분산 session store 비용, stateless 확장성 손실.
- OAuth2 Authorization Code (issuance flow) — 본 baseline은 **검증 side**이므로 직교. issuance 자체는 별도 IdP.
- mTLS (RFC 8705 sender-constrained token) — PKI 운영 비용 + public client(SPA/mobile) 운영 어려움.
- API key + HMAC (AWS SigV4 류) — webhook/외부 호출 인증에는 적합하나 일반 사용자 인증 모델이 아님.
- OPA (Open Policy Agent) — 외부 호출 latency + sidecar 운영. 인가 정책 2~3종에는 과한 인프라.
- **채택 이유**: framework-neutral skeleton 가정과 정합 (Spring Security 6 표준 경로), revocation 한계는 short expiry + JWKS rotation overlap으로 완화.
- **설계만 동결한 파라미터**: JWKS refresh 10분 + unknown `kid` 시 on-demand refresh, clock skew 60s, key rotation overlap 24h, AuthN/AuthZ matrix 12행, public path snapshot diff.
### D2. Actuator management port 9001 분리 + prod allowlist (`actually-implemented` / `locally-verified`)
- **결정**: `management.server.port=9001` 별도 포트 + prod allowlist=`health,prometheus,info` + 그 외 prod forbidden.
- **검토한 대안**:
- Single port (8080) + path ACL — cloud ingress의 path 매칭 신뢰도, filter ordering / regex 우회 risk.
- mTLS for management — 강하지만 cert 운영 부담.
- Network ACL only (VPC SG / NetworkPolicy) — port가 같으면 비즈니스 트래픽과 분리 정책이 복잡.
- Istio sidecar AuthorizationPolicy — mesh 도입 전제, skeleton의 framework-neutral 가정 위배.
- **채택 이유**: 외부 노출 차단을 **네트워크 경계 단순화**(다른 포트 = 다른 ingress 정책)로 풀어 single-port + path ACL의 우회 위험을 피함.
- **설계만 동결한 파라미터**: `heapdump`/`threaddump`/`env`/`configprops`/`shutdown` prod 차단, `loggers` prod read-only, metrics scrape는 internal network ACL default.
### D3. Secrets: secret manager OR mounted env + restart-only rotation + HMAC salt 90d (`partially-implemented`)
- **결정**: prod source = (secret manager) OR (mounted env), `.env`는 local 전용. `__LOCAL_DEV_` sentinel로 prod 오탑재 차단. `@RefreshScope` 금지 / `no-runtime-reload` default. JWT signing key 24h overlap, DB credential dual-bind 60s, API key restart-reload, HMAC salt 90d rotation.
- **검토한 대안**:
- Vault dynamic secrets (short lease) — `@RefreshScope` + bean 재생성을 전제 → connection pool/캐시 lifecycle과 충돌, 본 계약(`@RefreshScope` 금지)과 정면 충돌.
- External Secrets Operator (ESO) — K8s native, 단 etcd 평문 저장은 cluster operator 책임 (이중 신뢰 경계).
- Doppler / 1Password SDK — dev 머신 보호에 강점이나 SaaS 외부 의존.
- **채택 이유**: runtime reload를 거부하면 bean lifecycle / connection pool 충돌이 사라지고, rotation은 **명시적 dual-bind window**로만 처리. HMAC salt 90d 주기는 NIST SP 800-57 cryptoperiod 권고 범위 내에서 누적 노출/downstream re-hash 비용을 절충한 값.
Secret source abstraction과 local/prod guard는 구현되어 있으나, 외부 secret manager/Vault/KMS 통합 및 실제 rotation automation은 미구현이다.
### D4. 한국 보안 사례 reference 추가 (2026-05-22) (`documented-only`)
- **추가된 reference** (raw 출처만, 구현 변경 없음):
- [[raw/company-tech-blogs/security-woowahan-actuator-safe-usage]] — 우아한형제들 SOC팀 "Security Actuator 안전하게 사용하기" (별도 포트 + endpoint allowlist + shutdown/heapdump forbidden 권고). ca-tmpl D2 결정과 정합.
- [[raw/company-tech-blogs/security-toss-actuator-healthcheck]] — 토스 "Spring Boot Actuator의 헬스체크 살펴보기" (health detail 민감성 분류). ca-tmpl D2 + public path misconfiguration 정책과 정합.
- **영향**: Group G-B Actuator 결정의 한국 도메인 사례 근거 보강. 현재 ca-tmpl의 actuator exposure/management security contract와 함께 보조 근거로만 사용한다.
- **여전히 미확보**: 한국 기업의 JWT Resource Server 구현 사례, secret manager / Vault 운영 사례 직접 source는 미발견 — follow-up 후보로 유지.
## 면접에서 말할 수 있는 범위
### 자신 있게 답할 수 있는 질문
- **JWT vs Session 선택 기준** — stateless 확장성, revocation trade-off, cookie 운영 비용, 클라이언트 타입에 따른 결정 근거.
- **JWKS rotation 주기 설계** — 10분 refresh + unknown `kid` 시 on-demand refresh + 24h overlap window의 근거.
- **Management port 분리 이유** — single-port + path ACL의 filter ordering / regex 우회 risk 대비 별도 포트의 네트워크 경계 단순화.
- **Secret rotation 방식 (dual-bind)** — JWT key 24h overlap / DB credential dual-bind 60s / API key restart-reload가 왜 다른지.
- **HMAC salt 90d rotation 근거** — NIST SP 800-57 cryptoperiod 권고 + 누적 노출량 한도 + downstream re-hash 비용 절충.
### 적당히 답할 수 있는 질문
- **OPA vs in-process AUTHZ trade-off** — 외부 호출 latency / sidecar 운영 / 정책 코드 분리 가치 / 정책 종수 임계.
- **Vault dynamic secrets vs static lease** — `@RefreshScope` 강제와 bean lifecycle 충돌, dynamic secret이 본 계약과 왜 충돌하는지.
- **clock skew tolerance 30s vs 60s** — NTP drift 가정, 발급자/검증자 분산도, expired vs replay 창 trade-off.
### 답하면 안 되는 질문 (모른다고 해야 함)
- "**JWT Resource Server baseline을 구현했다**" — 가능. 단 IdP 운영/JWKS rotation 실측은 없음.
- "**Secret rotation을 운영에서 돌려봤다**" — prod 적용 사례 없음. dual-bind window는 설계 값.
- "**Actuator endpoint 보안 침투 테스트 결과**" — pentest 수행 안 함.
- "**JWKS rotation 시 latency가 얼마였다**" — 측정 안 함.
- "**Vault/Secrets Manager를 ca-tmpl에 연결해서 돌려봤다**" — 어떤 secret manager와도 통합하지 않음.
## 과장 금지 지점
- **"JWT는 안전하다"는 단정 금지.** token theft 시 stateless 검증은 즉시 revocation이 어렵다. JWKS rotation overlap + short expiry는 완화책일 뿐 근본 해결책이 아니다.
- **"Vault가 secret 관리의 표준"이라는 표현 금지.** dynamic secrets는 `@RefreshScope` 흐름을 전제하며, ca-tmpl의 `@RefreshScope` 금지 계약과 정면 충돌. 채택 가능한 표준이 단일하지 않다.
- **한국 보안 기술블로그 사례 참조 범위 한정.** 2026-05-22 기준 ca-tmpl이 직접 참조하는 한국 사례는 **Actuator 노출 정책 영역에 한정**된다 ([[raw/company-tech-blogs/security-woowahan-actuator-safe-usage]] / [[raw/company-tech-blogs/security-toss-actuator-healthcheck]]). JWT Resource Server 운영, secret manager 통합, JWKS rotation 같은 영역의 한국 도메인 직접 사례는 부재 — 인용 시 영역을 actuator로 명시할 것.
- **"Actuator를 닫아두면 안전하다"는 단정 금지.** allowlist + 네트워크 경계 + 인증의 다층 방어가 필요하다. `info`만 열어도 build/commit 메타데이터가 attack surface가 될 수 있다.
- **"AuthN/AuthZ matrix 12행 전체가 E2E로 검증됐다"고 말하면 안 됨.** 주요 security/error path와 method authorization contract는 테스트되지만, 모든 matrix row의 외부 IdP 통합 검증은 없다.
### Blog-topic ingest: secret-source-port-restart-only-rotation (2026-07-02)
[[raw/blog-topics/secret-source-port-restart-only-rotation-2026-07-02]] 는 secret source를 문자열 규칙이 아니라 `SecretSource` port, restart-only rotation, `@RefreshScope` 금지 계약으로 닫은 이유를 블로그로 풀기 위한 raw seed다.
- **canonical 반영 범위**: secrets/source/rotation 정책 글감을 security baseline canonical에 연결했다.
- **blogify 전 조건**: 충족. 이 문서는 2026-07-02 기준 코드와 `./gradlew check`로 검증됨.
- **블로그 전 과장 방지**: Vault/KMS dynamic secret 운영이나 secret manager 통합을 구현한 것처럼 쓰지 않고, restart-only contract 범위로 제한한다.
- [[raw/blog-topics/framework-free-method-authorization-clean-architecture-2026-06-08]]: Spring Security annotation을 application layer에 직접 붙이지 않고 plain annotation + authorization port + adapter method-security로 분리하는 글감. Spring Security 자체를 부정하지 않고 ca-tmpl layer boundary 선택으로 제한한다.
- [[raw/blog-topics/spring-security-filter-layer-error-envelope-2026-06-08]]: Spring Security 인증/인가 실패가 filter layer에서 entry point / denied handler로 처리되어 ControllerAdvice에 도달하지 않는다는 점을 envelope 통일과 연결하는 글감. heuristic 분류의 한계를 유지한다.
## 관련 개념
- [[wiki/concepts/security-baseline-jwt-actuator-secrets]]
## Sources
### Canonical project SSOT
- [[raw/project-notes/ca-skeleton-operational-contract]] — §18 Control Plane Contract, §29 Group G-B 외부 근거 인덱스
### Branch-notes (결정 동결 위치)
- [[raw/branch-notes/feature-security-operational-baseline]] — JWT Resource Server + AuthN/AuthZ Matrix 12행 + JWKS 10min refresh + clock skew 60s + rotation overlap 24h + public path snapshot diff
- [[raw/branch-notes/feature-secrets-config-source-contract]] — secret source port + restart-only rotation + `@RefreshScope` 금지 결정
- [[raw/blog-topics/secret-source-port-restart-only-rotation-2026-07-02]] — secret source/restart-only rotation 블로그 글감 raw seed
- [[raw/blog-topics/framework-free-method-authorization-clean-architecture-2026-06-08]] — framework-free method authorization 블로그 글감 raw seed
- [[raw/blog-topics/spring-security-filter-layer-error-envelope-2026-06-08]] — Spring Security filter-layer envelope 블로그 글감 raw seed
- [[raw/branch-notes/feature-management-actuator-security-contract]] — management port 9001 + prod allowlist + heapdump/threaddump prod forbidden + loggers prod read-only + metrics network ACL default
- [[raw/branch-notes/feature-secrets-config-source-contract]] — prod = secret manager OR mounted env + no-runtime-reload default + `__LOCAL_DEV_` sentinel + JWT key 24h overlap / DB credential dual-bind 60s / API key restart-reload + HMAC salt 90d
## Cluster / 묶음
<!-- GENERATED: derived-blogs:start -->
- [[wiki/blog/ca-tmpl-security-baseline-jwt-actuator-secrets-2026-07-02]]
<!-- GENERATED: derived-blogs:end -->