Skip to content
This repository has been archived by the owner on Oct 30, 2023. It is now read-only.

Commit

Permalink
TRACE header support (for vgs-edition branch) (#73)
Browse files Browse the repository at this point in the history
* TRACE header support

* Fixed github key

* Fixed release branch

* Tuned release branch

* Fixed role for artificats

* fixed key

* Introduced new channel attribute for traceProtocolHeader

* update

* update
  • Loading branch information
Zinovii Dmytriv authored Apr 10, 2020
1 parent b00c901 commit 39496cd
Show file tree
Hide file tree
Showing 5 changed files with 92 additions and 31 deletions.
2 changes: 1 addition & 1 deletion .circleci/config.yml
Original file line number Diff line number Diff line change
Expand Up @@ -120,7 +120,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: |
Expand Down
2 changes: 1 addition & 1 deletion scripts/env.sh
Original file line number Diff line number Diff line change
Expand Up @@ -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
Original file line number Diff line number Diff line change
Expand Up @@ -873,7 +873,7 @@ private void initChannelPipeline(ChannelPipeline pipeline) {
pipeline.addLast(globalStateWrapperEvenLoop, "bytesReadMonitor", bytesReadMonitor);
pipeline.addLast(globalStateWrapperEvenLoop, "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
Expand Down
Original file line number Diff line number Diff line change
@@ -1,16 +1,17 @@
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<String> SOURCE_IP_ATTRIBUTE = AttributeKey.valueOf("sourceIp");
public static final AttributeKey<String> TRACE_ID_ATTRIBUTE = AttributeKey.valueOf("traceId");
public static final AttributeKey<String> 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"
Expand All @@ -21,10 +22,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 {
Expand All @@ -51,6 +61,21 @@ 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);
ctx.channel().attr(TRACE_ID_ATTRIBUTE).set(traceId);

stripped = stripped.substring(6 + 32 + 2); // TRACE 32HEX\r\n
buf.clear().writeBytes(stripped.getBytes());

ctx.fireChannelRead(buf);
Expand Down
Original file line number Diff line number Diff line change
@@ -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;
Expand All @@ -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;
Expand Down Expand Up @@ -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
};
}

Expand All @@ -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
};
}

Expand All @@ -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<ChannelHandlerContext> serverCtxReference = new AtomicReference<>();

Expand Down

0 comments on commit 39496cd

Please sign in to comment.