Actuator 보안
엔드포인트 접근을 네트워크·관리 포트·Spring 보안·역할·응답 마스킹으로 제한하고 메트릭·Prometheus 수집·Grafana 조회 경로의 자격 증명과 카디널리티까지 종단 간 위협 모델로 검증합니다.
Actuator는 내부 구조를 많이 알고 있고 일부 엔드포인트는 애플리케이션 상태를 바꿉니다. 공개 API와 같은 인그레스에 전부 노출하면 빈 그래프, 구성 키, 힙, 요청 경로, 로그 수준 제어가 공격 표면이 됩니다. 보안은 기반 경로를 바꾸는 한 줄이 아니라 도달 경로, 인증, 권한, 데이터 최소화, 변경 감사를 겹친 구조여야 합니다.
엔드포인트 보안 목록
상태는 로드 밸런서가 익명으로 읽어야 할 수 있지만 상태 상세는 내부 운영자만 봐야 합니다.
메트릭 이름 목록은 비교적 낮은 위험이어도 태그 값이 테넌트와 경로를 드러낼 수 있습니다.
env, configprops, 빈, 매핑은 구조 정보, heapdump와 threaddump는 큰 페이로드와 민감 메모리, loggers와 종료는 변경 권한을 가집니다.
허용 목록은 엔드포인트 ID뿐 아니라 작업 메서드와 응답 필드를 포함합니다. 버전 업그레이드 때 새 엔드포인트가 생겨도 와일드카드 노출로 자동 공개되지 않게 하고, 탐색 결과 차이를 릴리스 통과 기준으로 둡니다.
package board.security;
public record EndpointRisk(
String id,
boolean mutating,
boolean containsConfiguration,
long maximumResponseBytes,
String requiredRole
) {
public EndpointRisk {
if (id.isBlank() || maximumResponseBytes < 1 || requiredRole.isBlank()) {
throw new IllegalArgumentException("invalid endpoint risk entry");
}
if (mutating && requiredRole.equals("PROBE")) {
throw new IllegalArgumentException("probe role cannot mutate");
}
}
}허용 목록 담당자와 검토 주기를 정합니다.
heapdump처럼 비상시에만 필요한 엔드포인트는 평소 비활성화로 두고 장애 승인 뒤 잠시 여는 절차가 더 안전합니다.
네트워크·관리 포트 경계
별도 management.server.port를 비공개 서비스나 localhost에 바인딩하고 공개 인그레스 경로에서 제외합니다.
보안 그룹, Kubernetes NetworkPolicy, 서비스 메시 정책으로 수집기와 운영자 네트워크만 허용합니다.
같은 파드 안의 SSRF나 침해된 사이드카는 네트워크 경계를 넘을 수 있으므로 이것만으로 충분하지 않습니다.
관리 TLS와 클라이언트 인증서를 별도 신뢰 도메인으로 운영할 수 있습니다. 리버스 프록시가 인증을 종료하면 위조 가능한 전달된 헤더를 애플리케이션이 그대로 신뢰하지 않게 신뢰할 프록시 범위를 제한합니다. 상태 점검이 인증 없이 필요하다면 상태-전용 경로 하나만 예외로 둡니다.
Actuator 보안 체인
애플리케이션 API 보안과 관리 엔드포인트 정책이 다르면 별도 SecurityFilterChain을 만들고 Actuator 체인에 높은 우선순위를 줍니다.
경로 문자열을 수동 조합하기보다 EndpointRequest 매처를 사용하면 기반 경로 변경과 엔드포인트 매핑을 따라갑니다.
package board.security;
import org.springframework.boot.health.actuate.endpoint.HealthEndpoint;
import org.springframework.boot.security.autoconfigure.actuate.web.servlet.EndpointRequest;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.core.annotation.Order;
import org.springframework.security.config.Customizer;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.web.SecurityFilterChain;
@Configuration(proxyBeanMethods = false)
public class ManagementSecurityConfiguration {
@Bean
@Order(1)
SecurityFilterChain managementSecurity(HttpSecurity http) throws Exception {
http.securityMatcher(EndpointRequest.toAnyEndpoint())
.authorizeHttpRequests(authorize -> authorize
.requestMatchers(EndpointRequest.to(HealthEndpoint.class))
.permitAll()
.anyRequest().hasRole("OPS"))
.httpBasic(Customizer.withDefaults());
return http.build();
}
}실제 인증은 수명이 짧은 기본 인증 정보보다 워크로드 신원, mTLS, 주기적으로 교체하는 비밀 정보를 선호합니다. 변경 엔드포인트에 CSRF를 무조건 끄지 말고 호출 주체가 브라우저인지 기계인지 위협 모델로 결정합니다. 상태 경로의 전체 허용은 상세 공개 설정과 함께 테스트합니다.
응답 정제·크기 제한
env와 configprops가 값을 마스킹해도 키 이름과 객체 구조가 아키텍처를 드러냅니다.
토큰, 비밀번호, 인증 정보, 개인 키뿐 아니라 조직 고유 비밀 정보 키도 정제 목록에 추가합니다.
사용자 정의 엔드포인트는 허용 목록 기반 DTO만 반환하고 엔티티·예외·환경 맵을 그대로 직렬화하지 않습니다.
heapdump, logfile, threaddump 같은 큰 응답은 타임아웃·동시 다운로드 수·최대 크기·감사 기록을 둡니다.
운영자가 필요하다는 이유로 브라우저에서 바로 공개하지 않고 비상 승인 절차로 제한합니다.
anonymous health status -> 200, details hidden
anonymous metrics -> 401
viewer logger write -> 403
operator logger write -> 204, audit event recorded
public ingress env -> 404 at proxy
response contains apiToken raw value -> SANITIZATION_REGRESSION메트릭 테넌트 경계
Micrometer 레지스트리가 애플리케이션 안에서 미터를 만들고 Actuator의 Prometheus 엔드포인트가 수집 텍스트로 변환하며, Prometheus가 가져와 시계열 데이터베이스에 저장하고 Grafana가 쿼리합니다. 각 단계마다 별도의 인증 주체와 최소 권한을 둡니다. 수집기 토큰을 대시보드 브라우저에 전달하지 않고 Grafana 데이터 소스가 서버 측에서 조회합니다.
메트릭 레이블에 사용자 ID, 이메일, 전체 URL, 예외 메시지를 넣으면 시계열 카디널리티와 개인정보 노출이 동시에 발생합니다.
method, 템플릿화한 경로, 상태 클래스, 제한된 결과처럼 값 집합이 제한된 차원만 허용합니다.
수집 엔드포인트 보호가 완벽해도 대시보드 조회자가 민감 레이블을 볼 수 있으므로 수집 전 제거합니다.
수집 계층 실패
애플리케이션이 UP이어도 수집 인증 실패, 서비스 탐색 대상 누락, 타임아웃, 중복 시계열, 원격 저장소 지연으로 그래프가 비게 됩니다.
대시보드의 “데이터 없음”을 트래픽 0으로 해석하지 않습니다.
up 시계열과 수집 지속 시간, 대상의 마지막 오류, 규칙 평가 상태를 함께 보여 줍니다.
package board.security;
import java.time.Duration;
public record ScrapeDecision(
boolean applicationReady,
boolean targetUp,
Duration sampleAge,
String display
) {
public static ScrapeDecision decide(
boolean applicationReady,
boolean targetUp,
Duration sampleAge,
Duration staleAfter
) {
if (!targetUp || sampleAge.compareTo(staleAfter) > 0) {
return new ScrapeDecision(
applicationReady, targetUp, sampleAge, "OBSERVABILITY_STALE");
}
return new ScrapeDecision(applicationReady, true, sampleAge, "CURRENT");
}
}수집 간격보다 짧은 순간 변화는 놓칠 수 있고, 카운터 초기화와 오래된 시계열도 쿼리에서 처리해야 합니다.
보안 변경 검증
익명 점검, 인증된 수집기, 읽기 전용 조회자, 운영자, 비상 승인 관리자 역할별로 엔드포인트와 메서드의 기대 상태를 표로 만듭니다. 보안 필터 체인 단위 테스트만으로 프록시와 관리 포트 라우팅을 확인할 수 없으므로 배포 환경 통합 검증을 추가합니다.
인증 실패를 상태 DOWN으로 연결하지 않습니다. 수집기 인증 정보 교체 중 401이 나면 모니터링 경보로 처리하고 애플리케이션 트래픽을 제거하지 않습니다. 감사 로그 수집기가 실패할 때 변경을 거부할지 별도 정책을 둡니다.
연습 문제
상태는 익명, Prometheus 수집은 수집기, 메트릭·정보는 조회자, loggers 쓰기는 운영자, heapdump는 비상 승인 사용자만 허용하는 행렬을 작성하세요.
별도 관리 포트와 공개 프록시 차단을 구성하고 원시 토큰·이메일 레이블·무제한 페이로드가 발견되면 릴리스를 실패시키세요.
해설 보기
엔드포인트 이름보다 작업 위험을 먼저 분류합니다. 동일 엔드포인트도 읽기와 쓰기 권한이 다를 수 있고 상태와 상세도 분리할 수 있습니다.
package board.security;
public enum OperationsRole {
PROBE,
SCRAPER,
VIEWER,
OPERATOR,
BREAK_GLASS;
public boolean canMutateLoggers() {
return this == OPERATOR || this == BREAK_GLASS;
}
public boolean canDownloadHeap() {
return this == BREAK_GLASS;
}
}역할 이름만 두지 말고 신원 발급·회수 절차와 인증 정보 교체 담당자까지 운영 문서에 연결합니다.