add HttpConfiguration.Customizer implementation that sets request attributes containing the real local and remote address:port pairs (#4849)

Signed-off-by: Ludovic Orban <lorban@bitronix.be>
This commit is contained in:
Ludovic Orban 2020-05-07 19:53:15 +02:00 committed by GitHub
parent c50a52a392
commit 072cc978fb
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
3 changed files with 291 additions and 0 deletions

View File

@ -768,6 +768,11 @@ public class ProxyConnectionFactory extends DetectorConnectionFactory
_local = local;
}
public EndPoint unwrap()
{
return _endp;
}
@Override
public void close()
{

View File

@ -0,0 +1,108 @@
//
// ========================================================================
// Copyright (c) 1995-2020 Mort Bay Consulting Pty Ltd and others.
// ------------------------------------------------------------------------
// All rights reserved. This program and the accompanying materials
// are made available under the terms of the Eclipse Public License v1.0
// and Apache License v2.0 which accompanies this distribution.
//
// The Eclipse Public License is available at
// http://www.eclipse.org/legal/epl-v10.html
//
// The Apache License v2.0 is available at
// http://www.opensource.org/licenses/apache2.0.php
//
// You may elect to redistribute this code under either of these licenses.
// ========================================================================
//
package org.eclipse.jetty.server;
import java.net.InetSocketAddress;
import java.util.HashSet;
import java.util.Set;
import javax.servlet.ServletRequest;
import org.eclipse.jetty.io.EndPoint;
import org.eclipse.jetty.util.Attributes;
/**
* <p>Customizer that extracts the real local and remote address:port pairs from a {@link ProxyConnectionFactory}
* and sets them on the request with {@link ServletRequest#setAttribute(String, Object)}.
*/
public class ProxyCustomizer implements HttpConfiguration.Customizer
{
/**
* The remote address attribute name.
*/
public static final String REMOTE_ADDRESS_ATTRIBUTE_NAME = "org.eclipse.jetty.proxy.remote.address";
/**
* The remote port attribute name.
*/
public static final String REMOTE_PORT_ATTRIBUTE_NAME = "org.eclipse.jetty.proxy.remote.port";
/**
* The local address attribute name.
*/
public static final String LOCAL_ADDRESS_ATTRIBUTE_NAME = "org.eclipse.jetty.proxy.local.address";
/**
* The local port attribute name.
*/
public static final String LOCAL_PORT_ATTRIBUTE_NAME = "org.eclipse.jetty.proxy.local.port";
@Override
public void customize(Connector connector, HttpConfiguration channelConfig, Request request)
{
EndPoint endPoint = request.getHttpChannel().getEndPoint();
if (endPoint instanceof ProxyConnectionFactory.ProxyEndPoint)
{
EndPoint underlyingEndpoint = ((ProxyConnectionFactory.ProxyEndPoint)endPoint).unwrap();
request.setAttributes(new ProxyAttributes(underlyingEndpoint.getRemoteAddress(), underlyingEndpoint.getLocalAddress(), request.getAttributes()));
}
}
private static class ProxyAttributes extends Attributes.Wrapper
{
private final InetSocketAddress remoteAddress;
private final InetSocketAddress localAddress;
private ProxyAttributes(InetSocketAddress remoteAddress, InetSocketAddress localAddress, Attributes attributes)
{
super(attributes);
this.remoteAddress = remoteAddress;
this.localAddress = localAddress;
}
@Override
public Object getAttribute(String name)
{
switch (name)
{
case REMOTE_ADDRESS_ATTRIBUTE_NAME:
return remoteAddress.getAddress().getHostAddress();
case REMOTE_PORT_ATTRIBUTE_NAME:
return remoteAddress.getPort();
case LOCAL_ADDRESS_ATTRIBUTE_NAME:
return localAddress.getAddress().getHostAddress();
case LOCAL_PORT_ATTRIBUTE_NAME:
return localAddress.getPort();
default:
return super.getAttribute(name);
}
}
@Override
public Set<String> getAttributeNameSet()
{
Set<String> names = new HashSet<>(_attributes.getAttributeNameSet());
names.add(REMOTE_ADDRESS_ATTRIBUTE_NAME);
names.add(REMOTE_PORT_ATTRIBUTE_NAME);
names.add(LOCAL_ADDRESS_ATTRIBUTE_NAME);
names.add(LOCAL_PORT_ATTRIBUTE_NAME);
return names;
}
}
}

View File

@ -0,0 +1,178 @@
//
// ========================================================================
// Copyright (c) 1995-2020 Mort Bay Consulting Pty Ltd and others.
// ------------------------------------------------------------------------
// All rights reserved. This program and the accompanying materials
// are made available under the terms of the Eclipse Public License v1.0
// and Apache License v2.0 which accompanies this distribution.
//
// The Eclipse Public License is available at
// http://www.eclipse.org/legal/epl-v10.html
//
// The Apache License v2.0 is available at
// http://www.opensource.org/licenses/apache2.0.php
//
// You may elect to redistribute this code under either of these licenses.
// ========================================================================
//
package org.eclipse.jetty.server;
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.io.OutputStream;
import java.net.InetSocketAddress;
import java.net.Socket;
import java.nio.charset.StandardCharsets;
import java.util.ArrayList;
import java.util.Collections;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import org.eclipse.jetty.server.handler.AbstractHandler;
import org.eclipse.jetty.util.TypeUtil;
import org.hamcrest.Matchers;
import org.junit.jupiter.api.AfterEach;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import static org.hamcrest.MatcherAssert.assertThat;
public class ProxyCustomizerTest
{
private Server server;
private ProxyResponse sendProxyRequest(String proxyAsHexString, String rawHttp) throws IOException
{
try (Socket socket = new Socket(server.getURI().getHost(), server.getURI().getPort()))
{
OutputStream output = socket.getOutputStream();
output.write(TypeUtil.fromHexString(proxyAsHexString));
output.write(rawHttp.getBytes(StandardCharsets.UTF_8));
output.flush();
socket.shutdownOutput();
StringBuilder sb = new StringBuilder();
InputStream input = socket.getInputStream();
BufferedReader reader = new BufferedReader(new InputStreamReader(input, StandardCharsets.UTF_8));
while (true)
{
String line = reader.readLine();
if (line == null)
break;
sb.append(line).append("\r\n");
}
return new ProxyResponse((InetSocketAddress)socket.getLocalSocketAddress(), (InetSocketAddress)socket.getRemoteSocketAddress(), sb.toString());
}
}
private static class ProxyResponse
{
private final InetSocketAddress localSocketAddress;
private final InetSocketAddress remoteSocketAddress;
private final String httpResponse;
public ProxyResponse(InetSocketAddress localSocketAddress, InetSocketAddress remoteSocketAddress, String httpResponse)
{
this.localSocketAddress = localSocketAddress;
this.remoteSocketAddress = remoteSocketAddress;
this.httpResponse = httpResponse;
}
}
@BeforeEach
void setUp() throws Exception
{
Handler handler = new AbstractHandler()
{
@Override
public void handle(String target, Request baseRequest, HttpServletRequest request, HttpServletResponse response)
{
response.addHeader("preexisting.attribute", request.getAttribute("some.attribute").toString());
ArrayList<String> attributeNames = Collections.list(request.getAttributeNames());
Collections.sort(attributeNames);
response.addHeader("attributeNames", String.join(",", attributeNames));
response.addHeader("localAddress", request.getLocalAddr() + ":" + request.getLocalPort());
response.addHeader("remoteAddress", request.getRemoteAddr() + ":" + request.getRemotePort());
Object localAddress = request.getAttribute(ProxyCustomizer.LOCAL_ADDRESS_ATTRIBUTE_NAME);
if (localAddress != null)
response.addHeader("proxyLocalAddress", localAddress.toString() + ":" + request.getAttribute(ProxyCustomizer.LOCAL_PORT_ATTRIBUTE_NAME));
Object remoteAddress = request.getAttribute(ProxyCustomizer.REMOTE_ADDRESS_ATTRIBUTE_NAME);
if (remoteAddress != null)
response.addHeader("proxyRemoteAddress", remoteAddress.toString() + ":" + request.getAttribute(ProxyCustomizer.REMOTE_PORT_ATTRIBUTE_NAME));
baseRequest.setHandled(true);
}
};
server = new Server();
HttpConfiguration httpConfiguration = new HttpConfiguration();
httpConfiguration.addCustomizer((connector, channelConfig, request) -> request.setAttribute("some.attribute", "some value"));
httpConfiguration.addCustomizer(new ProxyCustomizer());
ServerConnector connector = new ServerConnector(server, new ProxyConnectionFactory(), new HttpConnectionFactory(httpConfiguration));
server.addConnector(connector);
server.setHandler(handler);
server.start();
}
@AfterEach
void tearDown() throws Exception
{
server.stop();
server = null;
}
@Test
void testProxyCustomizerWithProxyData() throws Exception
{
String proxy =
// Preamble
"0D0A0D0A000D0A515549540A" +
// V2, PROXY
"21" +
// 0x1 : AF_INET 0x1 : STREAM. Address length is 2*4 + 2*2 = 12 bytes.
"11" +
// length of remaining header (4+4+2+2 = 12)
"000C" +
// uint32_t src_addr; uint32_t dst_addr; uint16_t src_port; uint16_t dst_port;
"01010001" +
"010100FE" +
"3039" +
"1F90";
String http = "GET /1 HTTP/1.1\r\n" +
"Host: localhost\r\n" +
"\r\n";
ProxyResponse response = sendProxyRequest(proxy, http);
assertThat(response.httpResponse, Matchers.containsString("localAddress: 1.1.0.254:8080"));
assertThat(response.httpResponse, Matchers.containsString("remoteAddress: 1.1.0.1:12345"));
assertThat(response.httpResponse, Matchers.containsString("proxyLocalAddress: " + response.remoteSocketAddress.getAddress().getHostAddress() + ":" + response.remoteSocketAddress.getPort()));
assertThat(response.httpResponse, Matchers.containsString("proxyRemoteAddress: " + response.localSocketAddress.getAddress().getHostAddress() + ":" + response.localSocketAddress.getPort()));
assertThat(response.httpResponse, Matchers.containsString("preexisting.attribute: some value"));
assertThat(response.httpResponse, Matchers.containsString("attributeNames: org.eclipse.jetty.proxy.local.address,org.eclipse.jetty.proxy.local.port,org.eclipse.jetty.proxy.remote.address,org.eclipse.jetty.proxy.remote.port,some.attribute"));
}
@Test
void testProxyCustomizerWithoutProxyData() throws Exception
{
String proxy = "";
String http = "GET /1 HTTP/1.1\r\n" +
"Host: localhost\r\n" +
"\r\n";
ProxyResponse response = sendProxyRequest(proxy, http);
assertThat(response.httpResponse, Matchers.containsString("localAddress: " + response.remoteSocketAddress.getAddress().getHostAddress() + ":" + response.remoteSocketAddress.getPort()));
assertThat(response.httpResponse, Matchers.containsString("remoteAddress: " + response.localSocketAddress.getAddress().getHostAddress() + ":" + response.localSocketAddress.getPort()));
assertThat(response.httpResponse, Matchers.not(Matchers.containsString("proxyLocalAddress: ")));
assertThat(response.httpResponse, Matchers.not(Matchers.containsString("proxyRemoteAddress: ")));
assertThat(response.httpResponse, Matchers.containsString("preexisting.attribute: some value"));
assertThat(response.httpResponse, Matchers.containsString("attributeNames: some.attribute"));
}
}