From f0b42e572e64525feb1cd14e62c10593afc93f66 Mon Sep 17 00:00:00 2001 From: ajaypj Date: Sat, 10 Aug 2024 18:12:43 +0530 Subject: [PATCH] GRPC getClientIP - Strip and keep only ip-address (#85) Fix GRPC getClientIP Issue --------- Co-authored-by: Kinshuk Bairagi Co-authored-by: Kinshuk Bairagi --- .../com/flipkart/gjex/core/filter/Filter.java | 2 - .../core/filter/http/AccessLogHttpFilter.java | 5 +- .../flipkart/gjex/core/util/NetworkUtils.java | 31 +++++++++ .../gjex/core/util/NetworkUtilsTest.java | 64 +++++++++++++++++++ .../helloworld/guice/HelloWorldModule.java | 2 - .../grpc/interceptor/FilterInterceptor.java | 20 +++++- .../interceptor/FilterInterceptorTest.java | 62 ++++++++++++++++++ 7 files changed, 180 insertions(+), 6 deletions(-) create mode 100644 core/src/main/java/com/flipkart/gjex/core/util/NetworkUtils.java create mode 100644 core/src/test/java/com/flipkart/gjex/core/util/NetworkUtilsTest.java create mode 100644 guice/src/test/java/com/flipkart/gjex/grpc/interceptor/FilterInterceptorTest.java diff --git a/core/src/main/java/com/flipkart/gjex/core/filter/Filter.java b/core/src/main/java/com/flipkart/gjex/core/filter/Filter.java index 569259cd..4e1a02f0 100644 --- a/core/src/main/java/com/flipkart/gjex/core/filter/Filter.java +++ b/core/src/main/java/com/flipkart/gjex/core/filter/Filter.java @@ -15,8 +15,6 @@ */ package com.flipkart.gjex.core.filter; -import com.flipkart.gjex.core.logging.Logging; - /** * A Filter interface for processing Request, Request-Headers, Response and Response-Headers * around gRPC and HTTP method invocation diff --git a/core/src/main/java/com/flipkart/gjex/core/filter/http/AccessLogHttpFilter.java b/core/src/main/java/com/flipkart/gjex/core/filter/http/AccessLogHttpFilter.java index 4d26fd4a..5e198ff6 100644 --- a/core/src/main/java/com/flipkart/gjex/core/filter/http/AccessLogHttpFilter.java +++ b/core/src/main/java/com/flipkart/gjex/core/filter/http/AccessLogHttpFilter.java @@ -9,6 +9,7 @@ import javax.servlet.ServletRequest; import javax.servlet.ServletResponse; import javax.servlet.http.HttpServletResponse; +import javax.ws.rs.core.HttpHeaders; import java.util.Map; import java.util.Optional; @@ -90,7 +91,9 @@ public void doProcessResponse(ServletResponse response) { HttpServletResponse httpServletResponse = (HttpServletResponse) response; if (isSuccess(httpServletResponse.getStatus())) { // 2xx response - int contentLength = Optional.ofNullable(httpServletResponse.getHeader(HttpHeaderNames.CONTENT_LENGTH.toString())) + // TODO: check case where GET response is successful by content-length is -1. + int contentLength = + Optional.ofNullable(httpServletResponse.getHeader(HttpHeaders.CONTENT_LENGTH)) .map(Integer::parseInt).orElse(0); accessLogContextBuilder.contentLength(contentLength); } else { diff --git a/core/src/main/java/com/flipkart/gjex/core/util/NetworkUtils.java b/core/src/main/java/com/flipkart/gjex/core/util/NetworkUtils.java new file mode 100644 index 00000000..5f7e9495 --- /dev/null +++ b/core/src/main/java/com/flipkart/gjex/core/util/NetworkUtils.java @@ -0,0 +1,31 @@ +package com.flipkart.gjex.core.util; + +import java.util.regex.Matcher; +import java.util.regex.Pattern; + +public class NetworkUtils { + + private static final Pattern[] ipAddressPattern = { + Pattern.compile( "(\\d{1,3}\\.\\d{1,3}\\.\\d{1,3}\\.\\d{1,3})"), + Pattern.compile("((([0-9a-fA-F]){1,4})\\:){7}([0-9a-fA-F]){1,4}") + }; + + + /** + * Extracts the IP address from a given string. + * + * @param str The input string from which the IP address is to be extracted. + * @return The IP address extracted from the input string. + */ + public static String extractIPAddress(String str) { + if (str != null) { + for (Pattern pattern : ipAddressPattern) { + Matcher matcher = pattern.matcher(str); + if (matcher.find()) { + return matcher.group(); + } + } + } + return "0.0.0.0"; + } +} diff --git a/core/src/test/java/com/flipkart/gjex/core/util/NetworkUtilsTest.java b/core/src/test/java/com/flipkart/gjex/core/util/NetworkUtilsTest.java new file mode 100644 index 00000000..539b5b42 --- /dev/null +++ b/core/src/test/java/com/flipkart/gjex/core/util/NetworkUtilsTest.java @@ -0,0 +1,64 @@ +package com.flipkart.gjex.core.util; + +import org.junit.Test; + +import static org.junit.Assert.assertEquals; + +public class NetworkUtilsTest { + + + @Test + public void extractIPAddressReturnsIPv4Address() { + String input = "User IP is 192.168.1.1 and should be extracted"; + String result = NetworkUtils.extractIPAddress(input); + assertEquals("192.168.1.1", result); + } + + @Test + public void extractIPAddressReturnsIPv6Address() { + String input = "User IP is 2001:0db8:85a3:0000:0000:8a2e:0370:7334 and should be extracted"; + String result = NetworkUtils.extractIPAddress(input); + assertEquals("2001:0db8:85a3:0000:0000:8a2e:0370:7334", result); + } + + @Test + public void extractIPAddressReturnsFirstIPv4AddressWhenMultiplePresent() { + String input = "User IPs are 192.168.1.1 and 10.0.0.1, first one should be extracted"; + String result = NetworkUtils.extractIPAddress(input); + assertEquals("192.168.1.1", result); + } + + @Test + public void extractIPAddressReturnsWithPrefix() { + String input = "/192.168.1.1:1234"; + String result = NetworkUtils.extractIPAddress(input); + assertEquals("192.168.1.1", result); + } + + @Test + public void extractIPAddressReturnsFirstIPv6AddressWhenMultiplePresent() { + String input = "User IPs are 2001:0db8:85a3:0000:0000:8a2e:0370:7334 and fe80::1ff:fe23:4567:890a, first one should be extracted"; + String result = NetworkUtils.extractIPAddress(input); + assertEquals("2001:0db8:85a3:0000:0000:8a2e:0370:7334", result); + } + + @Test + public void extractIPFromStringReturnsDefaultWhenNoIPPresent() { + String input = "No IP address in this string"; + String result = NetworkUtils.extractIPAddress(input); + assertEquals("0.0.0.0", result); + } + + @Test + public void extractIPAddress() { + String input = ""; + String result = NetworkUtils.extractIPAddress(input); + assertEquals("0.0.0.0", result); + } + + @Test + public void extractIPAddressHandlesNullInput() { + String result = NetworkUtils.extractIPAddress(null); + assertEquals("0.0.0.0", result); + } +} diff --git a/examples/src/main/java/com/flipkart/gjex/examples/helloworld/guice/HelloWorldModule.java b/examples/src/main/java/com/flipkart/gjex/examples/helloworld/guice/HelloWorldModule.java index c8ca9f2f..546818dc 100644 --- a/examples/src/main/java/com/flipkart/gjex/examples/helloworld/guice/HelloWorldModule.java +++ b/examples/src/main/java/com/flipkart/gjex/examples/helloworld/guice/HelloWorldModule.java @@ -53,11 +53,9 @@ protected void configure() { bind(BindableService.class).annotatedWith(Names.named("GreeterService")).to(GreeterService.class); bind(GrpcFilter.class).annotatedWith(Names.named("LoggingFilter")).to(LoggingFilter.class); bind(GrpcFilter.class).annotatedWith(Names.named("AuthFilter")).to(AuthFilter.class); -// bind(AccessLogGrpcFilter.class).to(AccessLogTestFilter.class); bind(TracingSampler.class).to(AllWhitelistTracingSampler.class); bind(ResourceConfig.class).annotatedWith(Names.named("HelloWorldResourceConfig")).to(HelloWorldResourceConfig.class); bind(JavaxFilterParams.class).annotatedWith(Names.named("ExampleJavaxFilter")).toInstance(JavaxFilterParams.builder().filter(new ExampleJavaxFilter()).pathSpec("/*").build()); bind(HttpFilterParams.class).annotatedWith(Names.named("CustomHeaderHttpFilter")).toInstance(HttpFilterParams.builder().filter(new CustomHeaderHttpFilter()).pathSpec("/*").build()); - } } diff --git a/guice/src/main/java/com/flipkart/gjex/grpc/interceptor/FilterInterceptor.java b/guice/src/main/java/com/flipkart/gjex/grpc/interceptor/FilterInterceptor.java index a873321c..d3877457 100644 --- a/guice/src/main/java/com/flipkart/gjex/grpc/interceptor/FilterInterceptor.java +++ b/guice/src/main/java/com/flipkart/gjex/grpc/interceptor/FilterInterceptor.java @@ -22,6 +22,7 @@ import com.flipkart.gjex.core.filter.grpc.GrpcFilterConfig; import com.flipkart.gjex.core.filter.grpc.MethodFilters; import com.flipkart.gjex.core.logging.Logging; +import com.flipkart.gjex.core.util.NetworkUtils; import com.flipkart.gjex.core.util.Pair; import com.flipkart.gjex.grpc.utils.AnnotationUtils; import io.grpc.*; @@ -34,6 +35,8 @@ import javax.inject.Singleton; import javax.validation.ConstraintViolationException; import java.lang.reflect.Method; +import java.net.InetSocketAddress; +import java.net.SocketAddress; import java.util.*; import java.util.function.Function; import java.util.stream.Collectors; @@ -130,7 +133,7 @@ public void sendHeaders(final Metadata responseHeaders) { }, headers); RequestParams requestParams = RequestParams.builder() - .clientIp(call.getAttributes().get(Grpc.TRANSPORT_ATTR_REMOTE_ADDR).toString()) + .clientIp(getClientIp(call.getAttributes().get(Grpc.TRANSPORT_ATTR_REMOTE_ADDR))) .resourcePath(call.getMethodDescriptor().getFullMethodName().toLowerCase()) .metadata(headers) .build(); @@ -215,4 +218,19 @@ private void configureAccessLog(GrpcFilterConfig grpcFilterConfig, filtersForMethod.add(accessLogGrpcFilter); } } + + protected static String getClientIp(SocketAddress socketAddress) { + if (socketAddress != null) { + if (socketAddress instanceof InetSocketAddress) { + return ((InetSocketAddress)socketAddress).getHostName(); + } else { + // handle other scenarios use regex + String socketAddressString = socketAddress.toString(); + return NetworkUtils.extractIPAddress(socketAddressString); + } + } + return "0.0.0.0"; + } + + } diff --git a/guice/src/test/java/com/flipkart/gjex/grpc/interceptor/FilterInterceptorTest.java b/guice/src/test/java/com/flipkart/gjex/grpc/interceptor/FilterInterceptorTest.java new file mode 100644 index 00000000..248a486d --- /dev/null +++ b/guice/src/test/java/com/flipkart/gjex/grpc/interceptor/FilterInterceptorTest.java @@ -0,0 +1,62 @@ +package com.flipkart.gjex.grpc.interceptor; + +import org.junit.Test; + +import java.net.InetSocketAddress; +import java.net.SocketAddress; + +import static org.junit.Assert.assertEquals; + +public class FilterInterceptorTest { + + @Test + public void getClientIpReturnsHostNameForInetSocketAddress() { + InetSocketAddress inetSocketAddress = new InetSocketAddress("localhost", 1234); + String result = FilterInterceptor.getClientIp(inetSocketAddress); + assertEquals("localhost", result); + } + + @Test + public void getClientIpReturnsExtractedIpForNonInetSocketAddress() { + SocketAddress socketAddress = new SocketAddress() { + @Override + public String toString() { + return "192.168.1.1:1234"; + } + }; + String result = FilterInterceptor.getClientIp(socketAddress); + assertEquals("192.168.1.1", result); + } + + @Test + public void getClientIpReturnsDefaultIpForNullSocketAddress() { + String result = FilterInterceptor.getClientIp(null); + assertEquals("0.0.0.0", result); + } + + @Test + public void getClientIpReturnsExtractedIpForGRPCSocketAddress() { + SocketAddress socketAddress = new SocketAddress() { + @Override + public String toString() { + return "/192.168.1.1:1234"; + } + }; + String result = FilterInterceptor.getClientIp(socketAddress); + assertEquals("192.168.1.1", result); + } + + @Test + public void getClientIpReturnsExtractedFirstIpForGRPCSocketAddress() { + SocketAddress socketAddress = new SocketAddress() { + @Override + public String toString() { + return "192.168.1.1/192.168.1.2:1234"; + } + }; + String result = FilterInterceptor.getClientIp(socketAddress); + assertEquals("192.168.1.1", result); + } + + +}