diff --git a/core/src/main/java/io/undertow/server/handlers/ForwardedHandler.java b/core/src/main/java/io/undertow/server/handlers/ForwardedHandler.java index 4bdfefa345..41ef4ec399 100644 --- a/core/src/main/java/io/undertow/server/handlers/ForwardedHandler.java +++ b/core/src/main/java/io/undertow/server/handlers/ForwardedHandler.java @@ -31,14 +31,20 @@ public class ForwardedHandler implements HttpHandler { public static final String HOST = "host"; public static final String PROTO = "proto"; private static final String UNKNOWN = "unknown"; - - + private static final boolean DEFAULT_CHANGE_LOCAL_ADDR_PORT = Boolean.getBoolean("io.undertow.forwarded.change-local-addr-port"); + private static final String CHANGE_LOCAL_ADDR_PORT = "change-local-addr-port"; + private final boolean isChangeLocalAddrPort; private final HttpHandler next; public ForwardedHandler(HttpHandler next) { + this(next, DEFAULT_CHANGE_LOCAL_ADDR_PORT); + } + public ForwardedHandler(HttpHandler next, boolean isChangeLocalAddrPort) { this.next = next; + this.isChangeLocalAddrPort = isChangeLocalAddrPort; } + @Override public void handleRequest(HttpServerExchange exchange) throws Exception { HeaderValues forwarded = exchange.getRequestHeaders().get(Headers.FORWARDED); @@ -54,11 +60,13 @@ public void handleRequest(HttpServerExchange exchange) throws Exception { if (host != null) { exchange.getRequestHeaders().put(Headers.HOST, host); - exchange.setDestinationAddress(InetSocketAddress.createUnresolved(exchange.getHostName(), exchange.getHostPort())); + if (isChangeLocalAddrPort) { + exchange.setDestinationAddress(InetSocketAddress.createUnresolved(exchange.getHostName(), exchange.getHostPort())); + } } else if (by != null) { //we only use 'by' if the host is null InetSocketAddress destAddress = parseAddress(by); - if (destAddress != null) { + if (destAddress != null && isChangeLocalAddrPort) { exchange.setDestinationAddress(destAddress); } } @@ -242,12 +250,6 @@ private enum SearchingFor { START_OF_NAME, EQUALS_SIGN, START_OF_VALUE, LAST_QUOTE, END_OF_VALUE; } - public static final HandlerWrapper WRAPPER = new HandlerWrapper() { - @Override - public HttpHandler wrap(HttpHandler handler) { - return new ForwardedHandler(handler); - } - }; @Override public String toString() { @@ -264,7 +266,9 @@ public String name() { @Override public Map> parameters() { - return Collections.emptyMap(); + Map> params = new HashMap<>(); + params.put(CHANGE_LOCAL_ADDR_PORT, boolean.class); + return params; } @Override @@ -274,12 +278,26 @@ public Set requiredParameters() { @Override public String defaultParameter() { - return null; + return CHANGE_LOCAL_ADDR_PORT; } @Override public HandlerWrapper build(Map config) { - return WRAPPER; + Boolean isChangeLocalAddrPort = (Boolean) config.get(CHANGE_LOCAL_ADDR_PORT); + return new Wrapper(isChangeLocalAddrPort == null ? DEFAULT_CHANGE_LOCAL_ADDR_PORT : isChangeLocalAddrPort); + } + } + private static class Wrapper implements HandlerWrapper { + + private final boolean isChangeLocalAddrPort; + + private Wrapper(boolean isChangeLocalAddrPort) { + this.isChangeLocalAddrPort = isChangeLocalAddrPort; + } + + @Override + public HttpHandler wrap(HttpHandler handler) { + return new ForwardedHandler(handler, isChangeLocalAddrPort); } } } diff --git a/core/src/main/java/io/undertow/server/handlers/ProxyPeerAddressHandler.java b/core/src/main/java/io/undertow/server/handlers/ProxyPeerAddressHandler.java index 16e0b75d8f..7ed80efcde 100644 --- a/core/src/main/java/io/undertow/server/handlers/ProxyPeerAddressHandler.java +++ b/core/src/main/java/io/undertow/server/handlers/ProxyPeerAddressHandler.java @@ -28,6 +28,7 @@ import java.net.InetSocketAddress; import java.util.Collections; +import java.util.HashMap; import java.util.Map; import java.util.Set; import java.util.regex.Pattern; @@ -48,8 +49,19 @@ public class ProxyPeerAddressHandler implements HttpHandler { private final HttpHandler next; + private static final boolean DEFAULT_CHANGE_LOCAL_ADDR_PORT = Boolean.getBoolean("io.undertow.forwarded.change-local-addr-port"); + + private static final String CHANGE_LOCAL_ADDR_PORT = "change-local-addr-port"; + + private final boolean isChangeLocalAddrPort; + public ProxyPeerAddressHandler(HttpHandler next) { + this(next, DEFAULT_CHANGE_LOCAL_ADDR_PORT); + } + + public ProxyPeerAddressHandler(HttpHandler next, boolean isChangeLocalAddrPort) { this.next = next; + this.isChangeLocalAddrPort = isChangeLocalAddrPort; } @Override @@ -110,7 +122,9 @@ public void handleRequest(HttpServerExchange exchange) throws Exception { } } exchange.getRequestHeaders().put(Headers.HOST, hostHeader); - exchange.setDestinationAddress(InetSocketAddress.createUnresolved(value, port)); + if (isChangeLocalAddrPort) { + exchange.setDestinationAddress(InetSocketAddress.createUnresolved(value, port)); + } } next.handleRequest(exchange); } @@ -142,7 +156,9 @@ public String name() { @Override public Map> parameters() { - return Collections.emptyMap(); + Map> params = new HashMap<>(); + params.put(CHANGE_LOCAL_ADDR_PORT, boolean.class); + return params; } @Override @@ -152,20 +168,27 @@ public Set requiredParameters() { @Override public String defaultParameter() { - return null; + return CHANGE_LOCAL_ADDR_PORT; } @Override public HandlerWrapper build(Map config) { - return new Wrapper(); + Boolean isChangeLocalAddrPort = (Boolean) config.get(CHANGE_LOCAL_ADDR_PORT); + return new Wrapper(isChangeLocalAddrPort == null ? DEFAULT_CHANGE_LOCAL_ADDR_PORT : isChangeLocalAddrPort); } } private static class Wrapper implements HandlerWrapper { + private final boolean isChangeLocalAddrPort; + + private Wrapper(boolean isChangeLocalAddrPort) { + this.isChangeLocalAddrPort = isChangeLocalAddrPort; + } + @Override public HttpHandler wrap(HttpHandler handler) { - return new ProxyPeerAddressHandler(handler); + return new ProxyPeerAddressHandler(handler, isChangeLocalAddrPort); } } } diff --git a/core/src/test/java/io/undertow/server/handlers/ForwardedHandlerTestCase.java b/core/src/test/java/io/undertow/server/handlers/ForwardedHandlerTestCase.java index 8df193a367..3d8d0e15af 100644 --- a/core/src/test/java/io/undertow/server/handlers/ForwardedHandlerTestCase.java +++ b/core/src/test/java/io/undertow/server/handlers/ForwardedHandlerTestCase.java @@ -35,6 +35,7 @@ public class ForwardedHandlerTestCase { @BeforeClass public static void setup() { + final boolean DEFAULT_CHANGE_LOCAL_ADDR_PORT = Boolean.TRUE; DefaultServer.setRootHandler(new ForwardedHandler(new HttpHandler() { @Override public void handleRequest(HttpServerExchange exchange) throws Exception { @@ -44,7 +45,7 @@ public void handleRequest(HttpServerExchange exchange) throws Exception { + "|" + toJreNormalizedString(exchange.getDestinationAddress()) + "|" + toJreNormalizedString(exchange.getSourceAddress())); } - })); + }, DEFAULT_CHANGE_LOCAL_ADDR_PORT)); } private static String toJreNormalizedString(InetSocketAddress address) { @@ -147,6 +148,7 @@ public void testForwardedHandler() throws IOException { Assert.assertEquals( "foo.com", res[1]); Assert.assertEquals( "foo.com:80", res[2]); Assert.assertEquals( "/9.9.9.9:2343", res[3]); + } private static String[] run(String ... headers) throws IOException { diff --git a/servlet/src/test/java/io/undertow/servlet/test/ProxyForwardedTestCase.java b/servlet/src/test/java/io/undertow/servlet/test/ProxyForwardedTestCase.java new file mode 100644 index 0000000000..a36932869e --- /dev/null +++ b/servlet/src/test/java/io/undertow/servlet/test/ProxyForwardedTestCase.java @@ -0,0 +1,124 @@ +/* + * JBoss, Home of Professional Open Source. + * Copyright 2021 Red Hat, Inc., and individual contributors + * as indicated by the @author tags. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package io.undertow.servlet.test; + +import io.undertow.server.HttpHandler; +import io.undertow.server.handlers.ForwardedHandler; +import io.undertow.server.handlers.PathHandler; +import io.undertow.servlet.api.DeploymentInfo; +import io.undertow.servlet.api.DeploymentManager; +import io.undertow.servlet.api.ServletContainer; +import io.undertow.servlet.api.ServletInfo; +import io.undertow.servlet.test.constant.GenericServletConstants; +import io.undertow.servlet.test.util.ProxyPeerXForwardedHandlerServlet; +import io.undertow.servlet.test.util.TestClassIntrospector; +import io.undertow.testutils.DefaultServer; +import io.undertow.testutils.ProxyIgnore; +import io.undertow.testutils.TestHttpClient; +import io.undertow.util.Headers; +import io.undertow.util.StatusCodes; +import org.apache.http.HttpEntity; +import org.apache.http.HttpResponse; +import org.apache.http.client.methods.HttpGet; +import org.apache.http.util.EntityUtils; +import org.junit.Assert; +import org.junit.BeforeClass; +import org.junit.Test; +import org.junit.runner.RunWith; + +import javax.servlet.ServletException; +import java.io.IOException; +import java.net.InetAddress; +import java.util.Arrays; +import java.util.HashMap; +import java.util.Map; +import java.util.stream.Collectors; + +/** + * @author Moulali Shikalwadi + */ +@RunWith(DefaultServer.class) +@ProxyIgnore +public class ProxyForwardedTestCase { + protected static int PORT; + + @BeforeClass + public static void setup() throws ServletException { + PORT = DefaultServer.getHostPort("default"); + final PathHandler root = new PathHandler(); + final ServletContainer container = ServletContainer.Factory.newInstance(); + + ServletInfo s = new ServletInfo("servlet", ProxyPeerXForwardedHandlerServlet.class) + .addMapping("/forwardedHandler"); + + DeploymentInfo builder = new DeploymentInfo() + .setClassLoader(SimpleServletTestCase.class.getClassLoader()) + .setContextPath("/servletContext") + .setClassIntrospecter(TestClassIntrospector.INSTANCE) + .setDeploymentName("servletContext.war") + .addServlet(s); + + DeploymentManager manager = container.addDeployment(builder); + manager.deploy(); + HttpHandler startHandler = manager.start(); + startHandler = new ForwardedHandler(startHandler, false); + root.addPrefixPath(builder.getContextPath(), startHandler); + + DefaultServer.setRootHandler(root); + } + + + @Test + public void testForwardedHandler() throws IOException { + TestHttpClient client = new TestHttpClient(); + try { + HttpGet getForwardedHandler = new HttpGet(DefaultServer.getDefaultServerURL() + "/servletContext/forwardedHandler"); + getForwardedHandler.addHeader(Headers.FORWARDED_STRING, "for=192.0.2.43"); + getForwardedHandler.addHeader(Headers.FORWARDED_STRING, "by=203.0.113.60"); + getForwardedHandler.addHeader(Headers.FORWARDED_STRING, "proto=http"); + getForwardedHandler.addHeader(Headers.FORWARDED_STRING, "host=192.0.2.10:8888"); + HttpResponse result = client.execute(getForwardedHandler); + HttpEntity entity = result.getEntity(); + String results = EntityUtils.toString(entity); + Map map = convertWithStream(results); + + Assert.assertEquals(StatusCodes.OK, result.getStatusLine().getStatusCode()); + Assert.assertEquals(InetAddress.getLocalHost().getHostAddress(), map.get(GenericServletConstants.LOCAL_ADDR)); + Assert.assertEquals(InetAddress.getLocalHost().getHostName(), map.get(GenericServletConstants.LOCAL_NAME)); + Assert.assertEquals(PORT, Integer.parseInt(map.get(GenericServletConstants.LOCAL_PORT))); + Assert.assertEquals("192.0.2.10", map.get(GenericServletConstants.SERVER_NAME)); + Assert.assertEquals("8888", map.get(GenericServletConstants.SERVER_PORT)); + Assert.assertEquals("192.0.2.43", map.get(GenericServletConstants.REMOTE_ADDR)); + Assert.assertEquals("0", map.get(GenericServletConstants.REMOTE_PORT)); + + } finally { + client.getConnectionManager().shutdown(); + } + } + + private Map convertWithStream(String mapAsString) { + Map map = new HashMap(); + if (mapAsString != null) { + mapAsString = mapAsString.substring(1, mapAsString.length() - 1); + map = Arrays.stream(mapAsString.split(",")) + .map(entry -> entry.split("=")) + .collect(Collectors.toMap(entry -> entry[0].trim(), entry -> entry[1].trim())); + } + return map; + } +} diff --git a/servlet/src/test/java/io/undertow/servlet/test/ProxyXForwardedTestCase.java b/servlet/src/test/java/io/undertow/servlet/test/ProxyXForwardedTestCase.java new file mode 100644 index 0000000000..e6aea0d7e3 --- /dev/null +++ b/servlet/src/test/java/io/undertow/servlet/test/ProxyXForwardedTestCase.java @@ -0,0 +1,125 @@ +/* + * JBoss, Home of Professional Open Source. + * Copyright 2021 Red Hat, Inc., and individual contributors + * as indicated by the @author tags. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package io.undertow.servlet.test; + +import io.undertow.server.HttpHandler; +import io.undertow.server.handlers.PathHandler; +import io.undertow.server.handlers.ProxyPeerAddressHandler; +import io.undertow.servlet.api.DeploymentInfo; +import io.undertow.servlet.api.DeploymentManager; +import io.undertow.servlet.api.ServletContainer; +import io.undertow.servlet.api.ServletInfo; +import io.undertow.servlet.test.constant.GenericServletConstants; +import io.undertow.servlet.test.util.ProxyPeerXForwardedHandlerServlet; +import io.undertow.servlet.test.util.TestClassIntrospector; +import io.undertow.testutils.DefaultServer; +import io.undertow.testutils.ProxyIgnore; +import io.undertow.testutils.TestHttpClient; +import io.undertow.util.Headers; +import io.undertow.util.StatusCodes; +import org.apache.http.HttpEntity; +import org.apache.http.HttpResponse; +import org.apache.http.client.methods.HttpGet; +import org.apache.http.util.EntityUtils; +import org.junit.Assert; +import org.junit.BeforeClass; +import org.junit.Test; +import org.junit.runner.RunWith; + +import javax.servlet.ServletException; +import java.io.IOException; +import java.net.InetAddress; +import java.util.Arrays; +import java.util.HashMap; +import java.util.Map; +import java.util.stream.Collectors; + +/** + * @author Moulali Shikalwadi + */ +@RunWith(DefaultServer.class) +@ProxyIgnore +public class ProxyXForwardedTestCase { + protected static int PORT; + + @BeforeClass + public static void setup() throws ServletException { + PORT = DefaultServer.getHostPort("default"); + final PathHandler root = new PathHandler(); + final ServletContainer container = ServletContainer.Factory.newInstance(); + + ServletInfo s = new ServletInfo("servlet", ProxyPeerXForwardedHandlerServlet.class) + .addMapping("/proxyPeerHandler"); + + DeploymentInfo builder = new DeploymentInfo() + .setClassLoader(SimpleServletTestCase.class.getClassLoader()) + .setContextPath("/servletContext") + .setClassIntrospecter(TestClassIntrospector.INSTANCE) + .setDeploymentName("servletContext.war") + .addServlet(s); + + DeploymentManager manager = container.addDeployment(builder); + manager.deploy(); + HttpHandler startHandler = manager.start(); + startHandler = new ProxyPeerAddressHandler(startHandler, false); + root.addPrefixPath(builder.getContextPath(), startHandler); + DefaultServer.setRootHandler(root); + } + + @Test + public void testProxyPeerHandler() throws IOException, ServletException { + TestHttpClient client = new TestHttpClient(); + try { + + HttpGet getProxyPeerHandler = new HttpGet(DefaultServer.getDefaultServerURL() + "/servletContext/proxyPeerHandler"); + getProxyPeerHandler.addHeader(Headers.X_FORWARDED_FOR_STRING, "192.0.2.43"); + getProxyPeerHandler.addHeader(Headers.X_FORWARDED_PROTO_STRING, "http"); + getProxyPeerHandler.addHeader(Headers.X_FORWARDED_HOST_STRING, "192.0.2.10"); + getProxyPeerHandler.addHeader(Headers.X_FORWARDED_PORT_STRING, "8888"); + HttpResponse result = client.execute(getProxyPeerHandler); + HttpEntity entity = result.getEntity(); + String results = EntityUtils.toString(entity); + Map map = convertWithStream(results); + + Assert.assertEquals(StatusCodes.OK, result.getStatusLine().getStatusCode()); + Assert.assertEquals(StatusCodes.OK, result.getStatusLine().getStatusCode()); + Assert.assertEquals(InetAddress.getLocalHost().getHostAddress(), map.get(GenericServletConstants.LOCAL_ADDR)); + Assert.assertEquals(InetAddress.getLocalHost().getHostName(), map.get(GenericServletConstants.LOCAL_NAME)); + Assert.assertEquals(PORT, Integer.parseInt(map.get(GenericServletConstants.LOCAL_PORT))); + Assert.assertEquals("192.0.2.10", map.get(GenericServletConstants.SERVER_NAME)); + Assert.assertEquals("8888", map.get(GenericServletConstants.SERVER_PORT)); + Assert.assertEquals("192.0.2.43", map.get(GenericServletConstants.REMOTE_ADDR)); + Assert.assertEquals("0", map.get(GenericServletConstants.REMOTE_PORT)); + + } finally { + client.getConnectionManager().shutdown(); + } + } + + private Map convertWithStream(String mapAsString) { + Map map = new HashMap(); + if(mapAsString != null){ + mapAsString = mapAsString.substring(1, mapAsString.length() - 1); + map = Arrays.stream(mapAsString.split(",")) + .map(entry -> entry.split("=")) + .collect(Collectors.toMap(entry -> entry[0].trim(), entry -> entry[1].trim())); + } + return map; + } + +} diff --git a/servlet/src/test/java/io/undertow/servlet/test/constant/GenericServletConstants.java b/servlet/src/test/java/io/undertow/servlet/test/constant/GenericServletConstants.java new file mode 100644 index 0000000000..627ea61520 --- /dev/null +++ b/servlet/src/test/java/io/undertow/servlet/test/constant/GenericServletConstants.java @@ -0,0 +1,32 @@ +/* + * JBoss, Home of Professional Open Source. + * Copyright 2021 Red Hat, Inc., and individual contributors + * as indicated by the @author tags. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package io.undertow.servlet.test.constant; + +/** + * @author Moulali Shikalwadi + */ + +public class GenericServletConstants { + public static final String LOCAL_ADDR = "localAddr"; + public static final String LOCAL_NAME = "localName"; + public static final String LOCAL_PORT = "localPort"; + public static final String SERVER_NAME = "serverName"; + public static final String SERVER_PORT = "serverPort"; + public static final String REMOTE_ADDR = "remoteAddr"; + public static final String REMOTE_PORT = "remotePort"; +} diff --git a/servlet/src/test/java/io/undertow/servlet/test/util/ProxyPeerXForwardedHandlerServlet.java b/servlet/src/test/java/io/undertow/servlet/test/util/ProxyPeerXForwardedHandlerServlet.java new file mode 100644 index 0000000000..4c428e5562 --- /dev/null +++ b/servlet/src/test/java/io/undertow/servlet/test/util/ProxyPeerXForwardedHandlerServlet.java @@ -0,0 +1,71 @@ +/* + * JBoss, Home of Professional Open Source. + * Copyright 2021 Red Hat, Inc., and individual contributors + * as indicated by the @author tags. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package io.undertow.servlet.test.util; + +import io.undertow.servlet.test.constant.GenericServletConstants; + +import javax.servlet.ServletConfig; +import javax.servlet.ServletException; +import javax.servlet.http.HttpServlet; +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; +import java.io.IOException; +import java.io.PrintWriter; +import java.util.HashMap; +import java.util.Map; +import java.util.stream.Collectors; + +/** + * @author Moulali Shikalwadi + */ +public class ProxyPeerXForwardedHandlerServlet extends HttpServlet { + + + @Override + public void init(final ServletConfig config) throws ServletException { + super.init(config); + } + + @Override + protected void doGet(final HttpServletRequest req, final HttpServletResponse resp) throws ServletException, IOException { + Map resMap = new HashMap(); + resMap.put(GenericServletConstants.SERVER_NAME, req.getServerName()); + resMap.put(GenericServletConstants.SERVER_PORT, String.valueOf(req.getServerPort())); + resMap.put(GenericServletConstants.LOCAL_NAME, req.getLocalName()); + resMap.put(GenericServletConstants.LOCAL_ADDR, req.getLocalAddr()); + resMap.put(GenericServletConstants.LOCAL_PORT, String.valueOf(req.getLocalPort())); + resMap.put(GenericServletConstants.REMOTE_ADDR, req.getRemoteAddr()); + resMap.put(GenericServletConstants.REMOTE_PORT, String.valueOf(req.getRemotePort())); + + PrintWriter writer = resp.getWriter(); + writer.write(convertWithStream(resMap)); + writer.close(); + } + + @Override + protected void doPost(final HttpServletRequest req, final HttpServletResponse resp) throws ServletException, IOException { + doGet(req, resp); + } + + public String convertWithStream(Map map) { + String mapAsString = map.keySet().stream() + .map(key -> key + "=" + map.get(key)) + .collect(Collectors.joining(", ", "{", "}")); + return mapAsString; + } +}