From 8ac5b5273d929baccb7d91fee747d2c37bcb0645 Mon Sep 17 00:00:00 2001 From: Nabeel Nauman Date: Tue, 24 Feb 2026 13:22:37 +0000 Subject: [PATCH 1/3] added HC skip logic + tests --- .../io/confluent/rest/TenantDosFilter.java | 14 +++++++ .../io/confluent/rest/TenantDryRunFilter.java | 6 ++- .../java/io/confluent/rest/TenantUtils.java | 20 ++++++++++ .../io/confluent/rest/TenantUtilsTest.java | 38 ++++++++++++++++++- 4 files changed, 76 insertions(+), 2 deletions(-) diff --git a/core/src/main/java/io/confluent/rest/TenantDosFilter.java b/core/src/main/java/io/confluent/rest/TenantDosFilter.java index cbd603d15f..4a0e3daccb 100644 --- a/core/src/main/java/io/confluent/rest/TenantDosFilter.java +++ b/core/src/main/java/io/confluent/rest/TenantDosFilter.java @@ -19,8 +19,12 @@ import static io.confluent.rest.TenantUtils.UNKNOWN_TENANT; import io.confluent.rest.jetty.DoSFilter; +import jakarta.servlet.FilterChain; +import jakarta.servlet.ServletException; import jakarta.servlet.ServletRequest; import jakarta.servlet.http.HttpServletRequest; +import jakarta.servlet.http.HttpServletResponse; +import java.io.IOException; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -36,6 +40,16 @@ public TenantDosFilter() { super(); } + @Override + protected void doFilter(HttpServletRequest request, HttpServletResponse response, + FilterChain filterChain) throws IOException, ServletException { + if (TenantUtils.isHealthCheckRequest(request)) { + filterChain.doFilter(request, response); + return; + } + super.doFilter(request, response, filterChain); + } + @Override protected String extractUserId(ServletRequest request) { // IMPORTANT: If we can't identify the tenant (or get a bad request), return null to skip diff --git a/core/src/main/java/io/confluent/rest/TenantDryRunFilter.java b/core/src/main/java/io/confluent/rest/TenantDryRunFilter.java index 86a5914067..ed7957e96a 100644 --- a/core/src/main/java/io/confluent/rest/TenantDryRunFilter.java +++ b/core/src/main/java/io/confluent/rest/TenantDryRunFilter.java @@ -55,9 +55,13 @@ protected void doFilter( HttpServletRequest request, HttpServletResponse response, FilterChain filterChain ) throws IOException, ServletException { + if (TenantUtils.isHealthCheckRequest(request)) { + filterChain.doFilter(request, response); + return; + } // Log tenant classification for all requests (successful and violations) logTenantClassification(request); - + // Let the parent class handle rate tracking and potential violations super.doFilter(request, response, filterChain); } diff --git a/core/src/main/java/io/confluent/rest/TenantUtils.java b/core/src/main/java/io/confluent/rest/TenantUtils.java index 3ebe6b38b3..af3f8ff9cf 100644 --- a/core/src/main/java/io/confluent/rest/TenantUtils.java +++ b/core/src/main/java/io/confluent/rest/TenantUtils.java @@ -34,6 +34,26 @@ public final class TenantUtils { private TenantUtils() {} + /** + * Checks if the request is a health check request that should bypass tenant rate limiting. + * Matches the simple health probe endpoint and health check produce requests. + */ + public static boolean isHealthCheckRequest(HttpServletRequest request) { + String path = request.getRequestURI(); + if (path == null) { + return false; + } + // Simple health probe endpoint + if (path.equals("/kafka/health")) { + return true; + } + // Health check produce to _confluent-healthcheck topics + if (path.contains("_confluent-healthcheck")) { + return true; + } + return false; + } + /** * Extracts tenant ID for request * Attempts hostname extraction first (applies to V4 networking - majority case), diff --git a/core/src/test/java/io/confluent/rest/TenantUtilsTest.java b/core/src/test/java/io/confluent/rest/TenantUtilsTest.java index 96e6382a21..ac50007d09 100644 --- a/core/src/test/java/io/confluent/rest/TenantUtilsTest.java +++ b/core/src/test/java/io/confluent/rest/TenantUtilsTest.java @@ -17,6 +17,8 @@ package io.confluent.rest; import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertFalse; +import static org.junit.jupiter.api.Assertions.assertTrue; import static org.mockito.Mockito.mock; import static org.mockito.Mockito.when; @@ -188,7 +190,41 @@ public void testTenantIdExtractionWithFallback() { "api.confluent.cloud", TenantUtils.UNKNOWN_TENANT); } - private void assertTenantExtraction(HttpServletRequest request, String requestURI, + @Test + public void testIsHealthCheckRequest_HealthEndpoint() { + HttpServletRequest request = mock(HttpServletRequest.class); + when(request.getRequestURI()).thenReturn("/kafka/health"); + assertTrue(TenantUtils.isHealthCheckRequest(request)); + } + + @Test + public void testIsHealthCheckRequest_HealthCheckRestTopicProduce() { + HttpServletRequest request = mock(HttpServletRequest.class); + when(request.getRequestURI()).thenReturn( + "/kafka/v3/clusters/lkc-3kv9m/topics/_confluent-healthcheck-rest_12/records"); + assertTrue(TenantUtils.isHealthCheckRequest(request)); + } + + @Test + public void testIsHealthCheckRequest_RegularEndpoints() { + HttpServletRequest request = mock(HttpServletRequest.class); + + when(request.getRequestURI()).thenReturn( + "/kafka/v3/clusters/lkc-abc123/topics/my-topic/records"); + assertFalse(TenantUtils.isHealthCheckRequest(request)); + + when(request.getRequestURI()).thenReturn("/kafka/v3/clusters/lkc-abc123"); + assertFalse(TenantUtils.isHealthCheckRequest(request)); + } + + @Test + public void testIsHealthCheckRequest_NullUri() { + HttpServletRequest request = mock(HttpServletRequest.class); + when(request.getRequestURI()).thenReturn(null); + assertFalse(TenantUtils.isHealthCheckRequest(request)); + } + + private void assertTenantExtraction(HttpServletRequest request, String requestURI, String serverName, String expectedTenantId) { when(request.getRequestURI()).thenReturn(requestURI); when(request.getServerName()).thenReturn(serverName); From 20d11c84ea1df45d067a1d5c7bc9ebfabe39a667 Mon Sep 17 00:00:00 2001 From: Nabeel Nauman Date: Tue, 24 Feb 2026 13:42:41 +0000 Subject: [PATCH 2/3] add null check --- core/src/main/java/io/confluent/rest/TenantUtils.java | 3 +++ core/src/test/java/io/confluent/rest/TenantUtilsTest.java | 5 +++++ 2 files changed, 8 insertions(+) diff --git a/core/src/main/java/io/confluent/rest/TenantUtils.java b/core/src/main/java/io/confluent/rest/TenantUtils.java index af3f8ff9cf..f0767fddbc 100644 --- a/core/src/main/java/io/confluent/rest/TenantUtils.java +++ b/core/src/main/java/io/confluent/rest/TenantUtils.java @@ -39,6 +39,9 @@ private TenantUtils() {} * Matches the simple health probe endpoint and health check produce requests. */ public static boolean isHealthCheckRequest(HttpServletRequest request) { + if (request == null) { + return false; + } String path = request.getRequestURI(); if (path == null) { return false; diff --git a/core/src/test/java/io/confluent/rest/TenantUtilsTest.java b/core/src/test/java/io/confluent/rest/TenantUtilsTest.java index ac50007d09..bd68a8c8f2 100644 --- a/core/src/test/java/io/confluent/rest/TenantUtilsTest.java +++ b/core/src/test/java/io/confluent/rest/TenantUtilsTest.java @@ -224,6 +224,11 @@ public void testIsHealthCheckRequest_NullUri() { assertFalse(TenantUtils.isHealthCheckRequest(request)); } + @Test + public void testIsHealthCheckRequest_NullRequest() { + assertFalse(TenantUtils.isHealthCheckRequest(null)); + } + private void assertTenantExtraction(HttpServletRequest request, String requestURI, String serverName, String expectedTenantId) { when(request.getRequestURI()).thenReturn(requestURI); From 9be522547b7aecc2b04971e51913bfb69a605fcd Mon Sep 17 00:00:00 2001 From: Nabeel Nauman Date: Tue, 24 Feb 2026 14:20:01 +0000 Subject: [PATCH 3/3] tighten string matching --- core/src/main/java/io/confluent/rest/TenantUtils.java | 5 +++-- core/src/test/java/io/confluent/rest/TenantUtilsTest.java | 5 +++++ 2 files changed, 8 insertions(+), 2 deletions(-) diff --git a/core/src/main/java/io/confluent/rest/TenantUtils.java b/core/src/main/java/io/confluent/rest/TenantUtils.java index f0767fddbc..2bf0ff561c 100644 --- a/core/src/main/java/io/confluent/rest/TenantUtils.java +++ b/core/src/main/java/io/confluent/rest/TenantUtils.java @@ -50,8 +50,9 @@ public static boolean isHealthCheckRequest(HttpServletRequest request) { if (path.equals("/kafka/health")) { return true; } - // Health check produce to _confluent-healthcheck topics - if (path.contains("_confluent-healthcheck")) { + // Health check produce to _confluent-healthcheck-rest topics + // Matches paths like /kafka/v3/clusters/lkc-xxx/topics/_confluent-healthcheck-rest_12/records + if (path.contains("/topics/_confluent-healthcheck")) { return true; } return false; diff --git a/core/src/test/java/io/confluent/rest/TenantUtilsTest.java b/core/src/test/java/io/confluent/rest/TenantUtilsTest.java index bd68a8c8f2..bbfd71bd8f 100644 --- a/core/src/test/java/io/confluent/rest/TenantUtilsTest.java +++ b/core/src/test/java/io/confluent/rest/TenantUtilsTest.java @@ -215,6 +215,11 @@ public void testIsHealthCheckRequest_RegularEndpoints() { when(request.getRequestURI()).thenReturn("/kafka/v3/clusters/lkc-abc123"); assertFalse(TenantUtils.isHealthCheckRequest(request)); + + // Topic name containing the healthcheck substring should not match + when(request.getRequestURI()).thenReturn( + "/kafka/v3/clusters/lkc-abc123/topics/malicioususer_confluent-healthcheck/records"); + assertFalse(TenantUtils.isHealthCheckRequest(request)); } @Test