From 34dce308dc6aeb62536296509ec802533b611397 Mon Sep 17 00:00:00 2001 From: Rinjae Date: Thu, 6 Aug 2026 09:59:34 +0900 Subject: [PATCH] =?UTF-8?q?=EB=A9=94=EB=89=B4=20=EB=82=B4=EB=B6=80=20API?= =?UTF-8?q?=20=ED=97=88=EC=9A=A9=20IP=20=EA=B2=80=EC=A6=9D=20=EB=A1=9C?= =?UTF-8?q?=EC=A7=81=20=EA=B0=9C=EC=84=A0:=20-=20IpAddressMatcher=EB=A1=9C?= =?UTF-8?q?=20CIDR/=EC=99=80=EC=9D=BC=EB=93=9C=EC=B9=B4=EB=93=9C=20?= =?UTF-8?q?=EC=A7=80=EC=9B=90=20-=20=ED=97=88=EC=9A=A9=20IP=20=EB=AA=A9?= =?UTF-8?q?=EB=A1=9D=20=EC=84=A4=EB=AA=85=20=ED=95=84=EB=93=9C=20=EC=B6=94?= =?UTF-8?q?=EA=B0=80=20-=20RemoteAddr=20=EC=A0=95=EA=B7=9C=ED=99=94=20?= =?UTF-8?q?=EB=A1=9C=EC=A7=81=20=EC=A0=9C=EA=B1=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../djb/menu/MenuInternalController.java | 26 +++++++------------ 1 file changed, 9 insertions(+), 17 deletions(-) diff --git a/src/main/java/com/eactive/apim/portal/djb/menu/MenuInternalController.java b/src/main/java/com/eactive/apim/portal/djb/menu/MenuInternalController.java index 1663d58..75ea49c 100644 --- a/src/main/java/com/eactive/apim/portal/djb/menu/MenuInternalController.java +++ b/src/main/java/com/eactive/apim/portal/djb/menu/MenuInternalController.java @@ -1,5 +1,6 @@ package com.eactive.apim.portal.djb.menu; +import com.eactive.apim.portal.common.util.IpAddressMatcher; import com.eactive.apim.portal.portalproperty.service.PortalPropertyService; import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; @@ -12,11 +13,8 @@ import org.springframework.web.bind.annotation.RestController; import javax.servlet.http.HttpServletRequest; import java.time.LocalDateTime; import java.time.format.DateTimeFormatter; -import java.util.Arrays; import java.util.LinkedHashMap; import java.util.Map; -import java.util.Set; -import java.util.stream.Collectors; /** * 메뉴 캐시 내부 API — eapim-admin 의 reload 명령 수신용. @@ -24,6 +22,8 @@ import java.util.stream.Collectors; *

가드: PTL_PROPERTY {@code Portal / menu.internal.allow-ips} 허용 IP 목록 * (기본 loopback) + X-Forwarded-For 동반 요청 거부 * ({@code LegacyEncryptionMigrationController.assertLocalOnly} 모델). + * 허용 목록은 {@link IpAddressMatcher} 규칙을 따라 정확 일치 외에 + * IPv4 CIDR({@code 172.30.1.0/24}) 과 옥텟 와일드카드({@code 172.30.*.*}) 를 지원한다. * CSRF 는 PortalConfigSecurity 에서 {@code /internal/menu/**} 예외 처리.

* *
curl -X POST http://127.0.0.1:39130/internal/menu/reload
@@ -37,6 +37,9 @@ public class MenuInternalController { static final String PROP_GROUP = "Portal"; static final String PROP_ALLOW_IPS = "menu.internal.allow-ips"; static final String DEFAULT_ALLOW_IPS = "127.0.0.1,::1"; + static final String PROP_ALLOW_IPS_DESCRIPTION = + "메뉴 내부 API(리로드) 허용 IP 목록. 콤마(,)/세미콜론(;)/줄바꿈 구분, " + + "정확일치·IPv4 CIDR(172.30.1.0/24)·와일드카드(172.30.*.*) 지원"; private final MenuService menuService; private final PortalPropertyService portalPropertyService; @@ -66,16 +69,10 @@ public class MenuInternalController { * 그 경우에도 allowlist 검사로 차단된다. WebLogic WAR 배포에서는 원 소켓 IP 로 검사된다.) */ private boolean isAllowed(HttpServletRequest request) { - String remote = canonicalize(request.getRemoteAddr()); + String remote = IpAddressMatcher.canonicalize(request.getRemoteAddr()); boolean viaProxy = request.getHeader("X-Forwarded-For") != null; - Set allowed = Arrays.stream(resolveAllowIps().split(",")) - .map(String::trim) - .filter(ip -> !ip.isEmpty()) - .map(MenuInternalController::canonicalize) - .collect(Collectors.toSet()); - - if (viaProxy || !allowed.contains(remote)) { + if (viaProxy || !IpAddressMatcher.matches(resolveAllowIps(), remote)) { log.warn("메뉴 내부 API 차단 - remote: {}, viaProxy: {}", remote, viaProxy); return false; } @@ -85,15 +82,10 @@ public class MenuInternalController { private String resolveAllowIps() { try { return portalPropertyService.getOrCreateProperty(PROP_GROUP, PROP_ALLOW_IPS, - DEFAULT_ALLOW_IPS, "메뉴 내부 API(리로드) 허용 IP 목록(콤마 구분)"); + DEFAULT_ALLOW_IPS, PROP_ALLOW_IPS_DESCRIPTION); } catch (Exception e) { log.warn("허용 IP 목록 조회 실패 - 기본값({}) 사용", DEFAULT_ALLOW_IPS, e); return DEFAULT_ALLOW_IPS; } } - - /** IPv6 loopback 표기 통일 */ - private static String canonicalize(String ip) { - return "0:0:0:0:0:0:0:1".equals(ip) ? "::1" : ip; - } }