diff --git a/.circleci/config.yml b/.circleci/config.yml index 46fd70013..6049ac0ab 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -4,7 +4,7 @@ job-defaults: &job-defaults test_results_directory: &test_results_directory /tmp/test-results environment: - - RELEASE_BRANCH: vgs-edition + - RELEASE_BRANCH: trace_header_support - AWS_DEFAULT_REGION: us-west-2 - AWS_REGION: us-west-2 - AWS_PROFILE: vgs-dev @@ -86,7 +86,7 @@ jobs: - <<: *setup-env - add_ssh_keys: fingerprints: - - "5c:bb:63:02:38:b1:b5:ff:a8:6e:0f:fd:d3:20:d9:1c" + - "b9:76:24:a1:6e:4c:fb:04:b3:0c:05:d9:b2:22:c1:a4" - run: name: Release command: | diff --git a/pom.xml b/pom.xml index acb1d7d47..e3d05c716 100644 --- a/pom.xml +++ b/pom.xml @@ -3,7 +3,7 @@ org.littleshoot littleproxy jar - 1.1.15-VGS-SNAPSHOT + 1.2.0-VGS-SNAPSHOT LittleProxy LittleProxy is a high performance HTTP proxy written in Java and using the Netty networking framework. @@ -606,8 +606,8 @@ true true - vgs-edition - vgs-edition + trace_header_support + trace_header_support release- diff --git a/scripts/env.sh b/scripts/env.sh index 796034044..0da2566c5 100755 --- a/scripts/env.sh +++ b/scripts/env.sh @@ -9,6 +9,6 @@ aws_access_key_id=$AWS_ACCESS_KEY_ID aws_secret_access_key=$AWS_SECRET_ACCESS_KEY [vgs-dev] region = us-west-2 -role_arn = arn:aws:iam::883127560329:role/StageDeploy +role_arn = arn:aws:iam::883127560329:role/VGSStageDeploy source_profile = default " > ~/.aws/credentials \ No newline at end of file diff --git a/src/main/java/org/littleshoot/proxy/impl/ClientToProxyConnection.java b/src/main/java/org/littleshoot/proxy/impl/ClientToProxyConnection.java index b78a1ff90..d492980c6 100644 --- a/src/main/java/org/littleshoot/proxy/impl/ClientToProxyConnection.java +++ b/src/main/java/org/littleshoot/proxy/impl/ClientToProxyConnection.java @@ -803,7 +803,7 @@ private void initChannelPipeline(ChannelPipeline pipeline) { pipeline.addLast("bytesReadMonitor", bytesReadMonitor); pipeline.addLast("bytesWrittenMonitor", bytesWrittenMonitor); - pipeline.addLast("proxyProtocolReader", new HttpProxyProtocolRequestDecoder()); + pipeline.addLast("proxyProtocolReader", new ProtocolHeadersRequestDecoder()); pipeline.addLast("encoder", new HttpResponseEncoder()); // We want to allow longer request lines, headers, and chunks diff --git a/src/main/java/org/littleshoot/proxy/impl/HttpProxyProtocolRequestDecoder.java b/src/main/java/org/littleshoot/proxy/impl/ProtocolHeadersRequestDecoder.java similarity index 61% rename from src/main/java/org/littleshoot/proxy/impl/HttpProxyProtocolRequestDecoder.java rename to src/main/java/org/littleshoot/proxy/impl/ProtocolHeadersRequestDecoder.java index 1f02fe795..4ddcbf4f0 100644 --- a/src/main/java/org/littleshoot/proxy/impl/HttpProxyProtocolRequestDecoder.java +++ b/src/main/java/org/littleshoot/proxy/impl/ProtocolHeadersRequestDecoder.java @@ -1,16 +1,16 @@ package org.littleshoot.proxy.impl; -import java.util.regex.Matcher; -import java.util.regex.Pattern; - import io.netty.buffer.ByteBuf; import io.netty.channel.ChannelHandlerContext; import io.netty.channel.ChannelInboundHandlerAdapter; import io.netty.util.AttributeKey; +import java.util.regex.Matcher; +import java.util.regex.Pattern; -public class HttpProxyProtocolRequestDecoder extends ChannelInboundHandlerAdapter { +public class ProtocolHeadersRequestDecoder extends ChannelInboundHandlerAdapter { public static final AttributeKey SOURCE_IP_ATTRIBUTE = AttributeKey.valueOf("sourceIp"); + public static final AttributeKey PROTOCOL_TRACE_ID_ATTRIBUTE = AttributeKey.valueOf("protocolTraceId"); // Pattern: // PROXY_STRING + single space + INET_PROTOCOL + single space + CLIENT_IP + single space + PROXY_IP + single space + CLIENT_PORT + single space + PROXY_PORT + "\r\n" @@ -21,10 +21,19 @@ public class HttpProxyProtocolRequestDecoder extends ChannelInboundHandlerAdapte // Source: // https://docs.aws.amazon.com/elasticloadbalancing/latest/classic/enable-proxy-protocol.html private static final Pattern TCP4_PROXY_PROTOCOL_HEADER_PATTERN = - Pattern.compile("PROXY TCP4 (((25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)(\\.)?){4}) ((25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)(\\.)?){4} \\d+ \\d+\\r\\n"); + Pattern.compile("^PROXY TCP4 (((25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)(\\.)?){4}) ((25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)(\\.)?){4} \\d+ \\d+\\r\\n"); private static final Pattern TCP6_PROXY_PROTOCOL_HEADER_PATTERN = - Pattern.compile("PROXY TCP6 (([0-9a-f]{1,4}:){7}([0-9a-f]){1,4}) ([0-9a-f]{1,4}:){7}([0-9a-f]){1,4} \\d+ \\d+\\r\\n", Pattern.CASE_INSENSITIVE); + Pattern.compile("^PROXY TCP6 (([0-9a-f]{1,4}:){7}([0-9a-f]){1,4}) ([0-9a-f]{1,4}:){7}([0-9a-f]){1,4} \\d+ \\d+\\r\\n", Pattern.CASE_INSENSITIVE); + + // Pattern: + // TRACE <32HEX>\r\n + // Example: + // TRACE 01234567890abcdef01234567890abcdef\r\n + // Source: + // https://github.com/verygoodsecurity/nginx/pull/1 + // https://github.com/opentracing/specification/issues/150 + private static final Pattern TRACE_HEADER_PATTERN = Pattern.compile("^TRACE ([0-9a-f]{32})\\r\\n"); @Override public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception { @@ -51,6 +60,20 @@ public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception int proxyProtocolHeaderIndex = body.indexOf("\r\n"); String stripped = body.substring(proxyProtocolHeaderIndex + 2); // +2 for \r\n + String traceId; + + Matcher traceHeaderMatcher = TRACE_HEADER_PATTERN.matcher(stripped); + if (traceHeaderMatcher.find()) { + traceId = traceHeaderMatcher.group(1); + } else { + buf.clear().writeBytes(stripped.getBytes()); + ctx.fireChannelRead(buf); + return; + } + + ctx.channel().attr(PROTOCOL_TRACE_ID_ATTRIBUTE).set(traceId); + + stripped = stripped.substring(6 + 32 + 2); // TRACE 32HEX\r\n buf.clear().writeBytes(stripped.getBytes()); ctx.fireChannelRead(buf); diff --git a/src/test/java/org/littleshoot/proxy/ProxyProtocolRequestDecoderTest.java b/src/test/java/org/littleshoot/proxy/ProtocolHeadersRequestDecoderTest.java similarity index 66% rename from src/test/java/org/littleshoot/proxy/ProxyProtocolRequestDecoderTest.java rename to src/test/java/org/littleshoot/proxy/ProtocolHeadersRequestDecoderTest.java index 56c95c7bb..7e9d20ada 100644 --- a/src/test/java/org/littleshoot/proxy/ProxyProtocolRequestDecoderTest.java +++ b/src/test/java/org/littleshoot/proxy/ProtocolHeadersRequestDecoderTest.java @@ -1,9 +1,25 @@ package org.littleshoot.proxy; +import static org.hamcrest.CoreMatchers.nullValue; +import static org.hamcrest.MatcherAssert.assertThat; +import static org.hamcrest.Matchers.equalTo; +import static org.littleshoot.proxy.impl.ProtocolHeadersRequestDecoder.SOURCE_IP_ATTRIBUTE; +import static org.littleshoot.proxy.impl.ProtocolHeadersRequestDecoder.PROTOCOL_TRACE_ID_ATTRIBUTE; + import com.tngtech.java.junit.dataprovider.DataProvider; import com.tngtech.java.junit.dataprovider.DataProviderRunner; import com.tngtech.java.junit.dataprovider.UseDataProvider; - +import io.netty.channel.ChannelHandlerContext; +import io.netty.handler.codec.http.HttpObject; +import io.netty.handler.codec.http.HttpRequest; +import java.io.IOException; +import java.io.InputStream; +import java.net.InetSocketAddress; +import java.net.Socket; +import java.util.concurrent.atomic.AtomicReference; +import javax.servlet.ServletException; +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; import org.apache.commons.io.IOUtils; import org.eclipse.jetty.server.Request; import org.eclipse.jetty.server.Server; @@ -16,27 +32,8 @@ import org.littleshoot.proxy.impl.DefaultHttpProxyServer; import org.littleshoot.proxy.impl.ThreadPoolConfiguration; -import java.io.IOException; -import java.io.InputStream; -import java.net.InetSocketAddress; -import java.net.Socket; -import java.util.concurrent.atomic.AtomicReference; - -import javax.servlet.ServletException; -import javax.servlet.http.HttpServletRequest; -import javax.servlet.http.HttpServletResponse; - -import io.netty.channel.ChannelHandlerContext; -import io.netty.handler.codec.http.HttpObject; -import io.netty.handler.codec.http.HttpRequest; - -import static org.hamcrest.CoreMatchers.nullValue; -import static org.hamcrest.MatcherAssert.assertThat; -import static org.hamcrest.Matchers.equalTo; -import static org.littleshoot.proxy.impl.HttpProxyProtocolRequestDecoder.SOURCE_IP_ATTRIBUTE; - @RunWith(DataProviderRunner.class) -public class ProxyProtocolRequestDecoderTest { +public class ProtocolHeadersRequestDecoderTest { private Server webServer; private HttpProxyServer proxyServer; @@ -87,7 +84,7 @@ public static Object[][] validCases() { return new Object[][]{ {"", nullValue()}, // No Proxy Header, typical case {"PROXY TCP4 11.22.33.44 99.88.77.66 5555 6666\r\n", equalTo("11.22.33.44")}, // Valid TCP4 - {"PROXY TCP6 FE80:0000:0000:0000:0202:B3FF:FE1E:8329 1200:0000:AB00:1234:0000:2552:7777:1313 5555 6666\r\n", equalTo("FE80:0000:0000:0000:0202:B3FF:FE1E:8329")}, // Valid TCP6 + {"PROXY TCP6 FE80:0000:0000:0000:0202:B3FF:FE1E:8329 1200:0000:AB00:1234:0000:2552:7777:1313 5555 6666\r\n", equalTo("FE80:0000:0000:0000:0202:B3FF:FE1E:8329")} // Valid TCP6 }; } @@ -98,6 +95,7 @@ public static Object[][] invalidCases() { {"PROXY TCP6 FE80::0202:B3FF:FE1E:8329 1200::AB00:1234::2552:7777:1313 5555 6666\r\n"}, // Invalid collapsed TCP6, should be 39 characters {"PROXY TCP4 555.22.33.44 99.88.77.66 5555 6666"}, // Missing \r\n {"PROXY UNKNOWN 11.22.33.44 99.88.77.66 5555 6666\n"}, // We do not support UNKNOWN + {"testPROXY TCP4 555.22.33.44 99.88.77.66 5555 6666\r\n"}, // PROXY is not the first string in request }; } @@ -116,6 +114,44 @@ public void testInvalid(String proxyProtocolHeader) throws Exception { assertThat(context, nullValue()); } + @DataProvider + public static Object[][] validCasesTraceHeader() { + return new Object[][]{ + {"PROXY TCP4 11.22.33.44 99.88.77.66 5555 6666\r\nTRACE 0123456789abcdef0123456789abcdef\r\n", equalTo("0123456789abcdef0123456789abcdef")}, // PROXY_TCP4 + TRACE + {"PROXY TCP6 FE80:0000:0000:0000:0202:B3FF:FE1E:8329 1200:0000:AB00:1234:0000:2552:7777:1313 5555 6666\r\nTRACE 0123456789abcdef0123456789abcdef\r\n", equalTo("0123456789abcdef0123456789abcdef")} // PROXY_TCP6 + TRACE + }; + } + + @DataProvider + public static Object[][] invalidCasesTraceHeader() { + return new Object[][]{ + {"TRACE 0123456789abcdef0123456789abcdef\r\n"}, // NO PROXY header in front of trace + {"PROXY TCP4 55.22.33.44 99.88.77.66 5555 6666\r\nTRACE 0123456789abcdef0123456789abcdef\n\n"}, // Invalid ending + {"PROXY TCP4 55.22.33.44 99.88.77.66 5555 6666\r\nTRACE 01234567890abcdef01234567890abcde\r\n"}, // Too short + {"PROXY TCP4 55.22.33.44 99.88.77.66 5555 6666\r\nTRACE 0123456789abcdef0123456789abcdeff\r\n"}, // Too long + {"PROXY TCP4 55.22.33.44 99.88.77.66 5555 6666\r\nTRACE 0123456789ABCDEF0123456789ABCDEF\r\n"}, // Uppercase characters + {"PROXY TCP4 55.22.33.44 99.88.77.66 5555 6666\r\nTRACE asdfasdfasdfasdfasdfasdfasdfasdfa\r\n"}, // Invalid characters + {"PROXY TCP4 555.22.33.44 99.88.77.66 5555 6666\r\nTRACE 0123456789abcdef0123456789abcdef\r\n"}, // Invalid IP in PROXY header in front + {"PROXY TCP4 55.22.33.44 99.88.77.66 5555 6666\rTRACE 0123456789abcdef0123456789abcdef\r\n"}, // Invalid delimiter in between PROXY and TRACE headers + {"PROXY TCP4 55.22.33.44 99.88.77.66 5555 6666\r\ntestTRACE 0123456789abcdef0123456789abcdef\r\n"}, // Invalid characters in front of TRACE header + }; + } + + @Test + @UseDataProvider("validCasesTraceHeader") + public void testValidTraceHeader(String traceHeader, Matcher traceMatcher) throws Exception { + ChannelHandlerContext context = runTest(traceHeader); + + assertThat(context.channel().attr(PROTOCOL_TRACE_ID_ATTRIBUTE).get(), traceMatcher); + } + + @Test + @UseDataProvider("invalidCasesTraceHeader") + public void testInvalidTraceHeader(String traceHeader) throws Exception { + ChannelHandlerContext context = runTest(traceHeader); + assertThat(context, nullValue()); + } + private ChannelHandlerContext runTest(String proxyProtocolHeader) throws Exception { final AtomicReference serverCtxReference = new AtomicReference<>();