Files
elink-online-common/src/main/java/com/eactive/eai/manage/hsm/HsmStatusController.java
T
curry772 2240a0d604 HSM 설정 이중화(PRIMARY/SECONDARY) 및 상태 조회 API 추가
HsmManager
- PKCS11_CONFIG_SECONDARY / PIN_SECONDARY 프로퍼티 추가.
  PIN_SECONDARY 미설정 시 PIN 재사용, PKCS11_CONFIG_SECONDARY 미설정 시
  후보가 1건이므로 기존과 동일하게 동작한다.
- 기동/재연결 모두 PRIMARY -> SECONDARY 순으로 시도해 최초 성공분을 사용한다.
  연결 성공 판정은 Provider 생성 + KeyStore.load + alias/getKey probe까지 포함하며,
  Security 등록 전에 검증하므로 실패해도 사용 중인 Provider/keyStore는 영향이 없다.
- 주기적 재로드에서 SECONDARY로 동작 중이면 PRIMARY 재연결을 먼저 시도해
  PRIMARY 복구 시 자동 복귀한다. PRIMARY 정상 동작 중에는 기존 keyStore 인스턴스에
  다시 load() 하여 PKCS11 세션 누적 방지 로직을 유지한다.
- PropertyChangeListener로 등록하여 HSM 프로퍼티 변경 시 다음 스케줄을 기다리지 않고
  즉시 재적용한다. HSM 통신이 호출 스레드를 막지 않도록 스케줄러 스레드에 위임하며,
  RELOAD_INTERVAL_MINUTES 변경 시 스케줄을 재등록한다.
- activeConfigName, failoverCount, lastReloadResult 등 모니터링 상태값을 노출한다.

HsmCryptoService
- 캐시 진단용 getCachedKeyInfos() / getCacheTtlMs() 추가. 키 원본은 노출하지 않는다.

/manage/hsm
- status(연결 설정/프로퍼티/alias/캐시 키), properties, keystore/aliases, cache/keys 조회와
  reload, cache/clear 실행 API 추가. PIN 류 프로퍼티는 마스킹한다.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_013ZMr6y9kc6Y6TWN3x4gpYg
2026-09-01 10:08:02 +09:00

83 lines
3.2 KiB
Java

package com.eactive.eai.manage.hsm;
import java.nio.charset.StandardCharsets;
import java.util.HashMap;
import java.util.Map;
import java.util.concurrent.Callable;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.MediaType;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;
/**
* HSM 연동 현황 조회 API. (ToolsController 와 동일한 응답 형태)
*
* PIN 등 비밀값과 키 원본은 응답에 포함하지 않는다.
*
* GET /manage/hsm/status → 연결 설정(PRIMARY/SECONDARY), 적용 프로퍼티,
* KeyStore alias, 캐싱된 키 목록 등 전체 현황
* ?healthCheck=true → 실제 HSM 통신으로 세션 생존까지 확인
* GET /manage/hsm/properties → HSM 프로퍼티 그룹의 현재 값 (PIN 류 마스킹)
* GET /manage/hsm/keystore/aliases → 현재 KeyStore 의 alias 목록 (HSM 통신 발생)
* GET /manage/hsm/cache/keys → HsmCryptoService 에 캐싱된 키 목록
* POST /manage/hsm/reload → 즉시 재로드 (PRIMARY → SECONDARY 순 연결 시도)
* POST /manage/hsm/cache/clear → 키 캐시 초기화
*/
@RestController
@RequestMapping("/manage/hsm")
public class HsmStatusController {
private static final MediaType APPLICATION_JSON_UTF8 = new MediaType("application", "json", StandardCharsets.UTF_8);
@Autowired
private HsmStatusService hsmStatusService;
@GetMapping("/status")
public ResponseEntity<?> status(
@RequestParam(name = "healthCheck", required = false, defaultValue = "false") boolean healthCheck) {
return respond(() -> hsmStatusService.getStatus(healthCheck));
}
@GetMapping("/properties")
public ResponseEntity<?> properties() {
return respond(() -> hsmStatusService.getMaskedProperties());
}
@GetMapping("/keystore/aliases")
public ResponseEntity<?> keyStoreAliases() {
return respond(() -> hsmStatusService.getKeyAliases());
}
@GetMapping("/cache/keys")
public ResponseEntity<?> cachedKeys() {
return respond(() -> hsmStatusService.getCachedKeys());
}
@PostMapping("/reload")
public ResponseEntity<?> reload() {
return respond(() -> hsmStatusService.reloadNow());
}
@PostMapping("/cache/clear")
public ResponseEntity<?> clearCache() {
return respond(() -> hsmStatusService.clearKeyCache());
}
private ResponseEntity<?> respond(Callable<Object> action) {
Map<String, Object> result = new HashMap<>();
try {
result.put("success", true);
result.put("data", action.call());
} catch (Exception e) {
result.put("success", false);
result.put("message", e.getMessage());
}
return ResponseEntity.ok().contentType(APPLICATION_JSON_UTF8).body(result);
}
}