From ab48d049a0054b1c6d187e5338a13b6205c5052d Mon Sep 17 00:00:00 2001 From: Stuart Douglas Date: Mon, 26 Mar 2018 17:23:51 +1100 Subject: [PATCH] WFLY-10084 Add support for the proxy protocol --- .../undertow/UndertowConnectorTestCase.java | 4 +- .../undertow/UndertowEngineTestCase.java | 2 +- .../management/api/web/ListenerTestCase.java | 62 +++++++++++++++---- .../management/api/web/RemoteIpServlet.java | 39 ++++++++++++ .../test/http/util/TestHttpClientUtils.java | 39 +++++++----- .../test/integration/management/Listener.java | 19 ++---- .../undertow/AjpListenerService.java | 2 +- .../wildfly/extension/undertow/Constants.java | 2 + .../extension/undertow/HttpListenerAdd.java | 3 +- .../HttpListenerResourceDefinition.java | 8 +++ .../undertow/HttpListenerService.java | 4 +- .../extension/undertow/HttpsListenerAdd.java | 3 +- .../HttpsListenerResourceDefinition.java | 1 + .../undertow/HttpsListenerService.java | 50 ++++++++++----- .../extension/undertow/ListenerService.java | 27 +++++++- .../undertow/UndertowSubsystemParser_6_0.java | 6 +- .../undertow/UndertowTransformers.java | 22 ++++--- .../undertow/LocalDescriptions.properties | 1 + .../resources/schema/wildfly-undertow_6_0.xsd | 2 + .../UndertowTransformersTestCase.java | 7 ++- .../extension/undertow/undertow-6.0.xml | 4 +- 21 files changed, 224 insertions(+), 83 deletions(-) create mode 100644 testsuite/integration/basic/src/test/java/org/jboss/as/test/integration/management/api/web/RemoteIpServlet.java diff --git a/mod_cluster/undertow/src/test/java/org/wildfly/mod_cluster/undertow/UndertowConnectorTestCase.java b/mod_cluster/undertow/src/test/java/org/wildfly/mod_cluster/undertow/UndertowConnectorTestCase.java index fd90cc6f9180..79a722091975 100644 --- a/mod_cluster/undertow/src/test/java/org/wildfly/mod_cluster/undertow/UndertowConnectorTestCase.java +++ b/mod_cluster/undertow/src/test/java/org/wildfly/mod_cluster/undertow/UndertowConnectorTestCase.java @@ -51,8 +51,8 @@ public class UndertowConnectorTestCase { public void getType() { OptionMap options = OptionMap.builder().getMap(); assertSame(Connector.Type.AJP, new UndertowConnector(new AjpListenerService("", "", options, OptionMap.EMPTY)).getType()); - assertSame(Connector.Type.HTTP, new UndertowConnector(new HttpListenerService("", "", options, OptionMap.EMPTY, false, false)).getType()); - assertSame(Connector.Type.HTTPS, new UndertowConnector(new HttpsListenerService("", "", options, null, OptionMap.EMPTY)).getType()); + assertSame(Connector.Type.HTTP, new UndertowConnector(new HttpListenerService("", "", options, OptionMap.EMPTY, false, false, false)).getType()); + assertSame(Connector.Type.HTTPS, new UndertowConnector(new HttpsListenerService("", "", options, null, OptionMap.EMPTY, false)).getType()); } @Test diff --git a/mod_cluster/undertow/src/test/java/org/wildfly/mod_cluster/undertow/UndertowEngineTestCase.java b/mod_cluster/undertow/src/test/java/org/wildfly/mod_cluster/undertow/UndertowEngineTestCase.java index 4fd1949aba52..f2799ad48b45 100644 --- a/mod_cluster/undertow/src/test/java/org/wildfly/mod_cluster/undertow/UndertowEngineTestCase.java +++ b/mod_cluster/undertow/src/test/java/org/wildfly/mod_cluster/undertow/UndertowEngineTestCase.java @@ -44,7 +44,7 @@ public class UndertowEngineTestCase { private final String hostName = "default-host"; private final String route = "route"; private final Host host = new Host(this.hostName, Collections.emptyList(), "ROOT.war"); - private final HttpsListenerService listener = new HttpsListenerService("default", "https", OptionMap.EMPTY, null, OptionMap.EMPTY); + private final HttpsListenerService listener = new HttpsListenerService("default", "https", OptionMap.EMPTY, null, OptionMap.EMPTY, false); private final UndertowService service = new TestUndertowService("default-container", this.serverName, this.hostName, this.route, this.server); private final Server server = new TestServer(this.serverName, this.hostName, this.service, this.host, this.listener); private final Connector connector = mock(Connector.class); diff --git a/testsuite/integration/basic/src/test/java/org/jboss/as/test/integration/management/api/web/ListenerTestCase.java b/testsuite/integration/basic/src/test/java/org/jboss/as/test/integration/management/api/web/ListenerTestCase.java index ffa9466d2898..4113bcdd0feb 100644 --- a/testsuite/integration/basic/src/test/java/org/jboss/as/test/integration/management/api/web/ListenerTestCase.java +++ b/testsuite/integration/basic/src/test/java/org/jboss/as/test/integration/management/api/web/ListenerTestCase.java @@ -26,11 +26,14 @@ import static org.jboss.as.controller.descriptions.ModelDescriptionConstants.OPERATION_HEADERS; import static org.jboss.as.controller.descriptions.ModelDescriptionConstants.STEPS; import static org.jboss.as.test.integration.management.util.ModelUtil.createOpNode; +import static org.jboss.as.test.integration.security.common.SSLTruststoreUtil.HTTPS_PORT; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertFalse; import static org.junit.Assert.assertTrue; +import java.net.Socket; import java.net.URL; +import java.nio.charset.StandardCharsets; import java.util.HashMap; import java.util.HashSet; import java.util.List; @@ -61,10 +64,13 @@ import org.jboss.dmr.ModelNode; import org.jboss.shrinkwrap.api.Archive; import org.jboss.shrinkwrap.api.ShrinkWrap; -import org.jboss.shrinkwrap.api.spec.JavaArchive; +import org.jboss.shrinkwrap.api.spec.WebArchive; +import org.junit.Assert; import org.junit.Test; import org.junit.runner.RunWith; +import io.undertow.util.FileUtils; + /** * @author Dominik Pospisil */ @@ -83,8 +89,8 @@ public class ListenerTestCase extends ContainerResourceMgmtTestBase { @Deployment public static Archive getDeployment() { - JavaArchive ja = ShrinkWrap.create(JavaArchive.class, "dummy.jar"); - ja.addClass(ListenerTestCase.class); + WebArchive ja = ShrinkWrap.create(WebArchive.class, "proxy.war"); + ja.addClasses(ListenerTestCase.class, RemoteIpServlet.class); return ja; } @@ -127,8 +133,6 @@ public void testHttpsConnector() throws Exception { } finally { removeListener(Listener.HTTPS); } - - } @Test @@ -141,7 +145,7 @@ public void testAjpConnector() throws Exception { public void testAddAndRemoveRollbacks() throws Exception { // execute and rollback add socket - ModelNode addSocketOp = getAddSocketBindingOp(Listener.HTTPJIO); + ModelNode addSocketOp = getAddSocketBindingOp(Listener.HTTP); ModelNode ret = executeAndRollbackOperation(addSocketOp); assertTrue("failed".equals(ret.get("outcome").asString())); @@ -149,7 +153,7 @@ public void testAddAndRemoveRollbacks() throws Exception { executeOperation(addSocketOp); // execute and rollback add connector - ModelNode addConnectorOp = getAddListenerOp(Listener.HTTPJIO); + ModelNode addConnectorOp = getAddListenerOp(Listener.HTTP, false); ret = executeAndRollbackOperation(addConnectorOp); assertTrue("failed".equals(ret.get("outcome").asString())); @@ -157,10 +161,10 @@ public void testAddAndRemoveRollbacks() throws Exception { executeOperation(addConnectorOp); // check it is listed - assertTrue(getListenerList().get("http").contains("test-" + Listener.HTTPJIO.getName() + "-listener")); + assertTrue(getListenerList().get("http").contains("test-" + Listener.HTTP.getName() + "-listener")); // execute and rollback remove connector - ModelNode removeConnOp = getRemoveConnectorOp(Listener.HTTPJIO); + ModelNode removeConnOp = getRemoveConnectorOp(Listener.HTTP); ret = executeAndRollbackOperation(removeConnOp); assertEquals("failed", ret.get("outcome").asString()); @@ -174,7 +178,7 @@ public void testAddAndRemoveRollbacks() throws Exception { assertFalse("Connector not removed.", WebUtil.testHttpURL(cURL)); // execute and rollback remove socket binding - ModelNode removeSocketOp = getRemoveSocketBindingOp(Listener.HTTPJIO); + ModelNode removeSocketOp = getRemoveSocketBindingOp(Listener.HTTP); ret = executeAndRollbackOperation(removeSocketOp); assertEquals("failed", ret.get("outcome").asString()); @@ -182,14 +186,45 @@ public void testAddAndRemoveRollbacks() throws Exception { executeOperation(removeSocketOp); } + @Test + public void testProxyProtocolOverHTTP() throws Exception { + addListener(Listener.HTTP, true); + try (Socket s = new Socket(url.getHost(), 8181)) { + s.getOutputStream().write("PROXY TCP4 1.2.3.4 5.6.7.8 444 555\r\nGET /proxy/addr HTTP/1.0\r\n\r\n".getBytes(StandardCharsets.US_ASCII)); + String result = FileUtils.readFile(s.getInputStream()); + Assert.assertTrue(result, result.contains("result:1.2.3.4:444 5.6.7.8:555")); + } finally { + removeListener(Listener.HTTP); + } + } + + + @Test + public void testProxyProtocolOverHTTPS() throws Exception { + addListener(Listener.HTTPS, true); + try (Socket s = new Socket(url.getHost(), 8181)) { + s.getOutputStream().write("PROXY TCP4 1.2.3.4 5.6.7.8 444 555\r\n".getBytes(StandardCharsets.US_ASCII)); + Socket ssl = TestHttpClientUtils.getSslContext().getSocketFactory().createSocket(s, url.getHost(), HTTPS_PORT, true); + ssl.getOutputStream().write("GET /proxy/addr HTTP/1.0\r\n\r\n".getBytes(StandardCharsets.US_ASCII)); + String result = FileUtils.readFile(ssl.getInputStream()); + Assert.assertTrue(result, result.contains("result:1.2.3.4:444 5.6.7.8:555")); + } finally { + removeListener(Listener.HTTPS); + } + } + private void addListener(Listener conn) throws Exception { + addListener(conn, false); + } + + private void addListener(Listener conn, boolean proxyProtocol) throws Exception { // add socket binding ModelNode op = getAddSocketBindingOp(conn); executeOperation(op); // add connector - op = getAddListenerOp(conn); + op = getAddListenerOp(conn, proxyProtocol); executeOperation(op); // check it is listed @@ -202,11 +237,14 @@ private ModelNode getAddSocketBindingOp(Listener conn) { return op; } - private ModelNode getAddListenerOp(Listener conn) { + private ModelNode getAddListenerOp(Listener conn, boolean proxyProtocol) { final ModelNode composite = Util.getEmptyOperation(COMPOSITE, new ModelNode()); final ModelNode steps = composite.get(STEPS); ModelNode op = createOpNode("subsystem=undertow/server=default-server/" + conn.getScheme() + "-listener=test-" + conn.getName() + "-listener", "add"); op.get("socket-binding").set("test-" + conn.getName() + socketBindingCount); + if(proxyProtocol) { + op.get("proxy-protocol").set(true); + } if (conn.isSecure()) { op.get("security-realm").set("ssl-realm"); } diff --git a/testsuite/integration/basic/src/test/java/org/jboss/as/test/integration/management/api/web/RemoteIpServlet.java b/testsuite/integration/basic/src/test/java/org/jboss/as/test/integration/management/api/web/RemoteIpServlet.java new file mode 100644 index 000000000000..bf9ea6bb8fe5 --- /dev/null +++ b/testsuite/integration/basic/src/test/java/org/jboss/as/test/integration/management/api/web/RemoteIpServlet.java @@ -0,0 +1,39 @@ +/* + * JBoss, Home of Professional Open Source. + * Copyright 2018, Red Hat, Inc., and individual contributors + * as indicated by the @author tags. See the copyright.txt file in the + * distribution for a full listing of individual contributors. + * + * This is free software; you can redistribute it and/or modify it + * under the terms of the GNU Lesser General Public License as + * published by the Free Software Foundation; either version 2.1 of + * the License, or (at your option) any later version. + * + * This software is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this software; if not, write to the Free + * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA + * 02110-1301 USA, or see the FSF site: http://www.fsf.org. + */ +package org.jboss.as.test.integration.management.api.web; + +import java.io.IOException; + +import javax.servlet.ServletException; +import javax.servlet.annotation.WebServlet; +import javax.servlet.http.HttpServlet; +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; + +@WebServlet(urlPatterns = "/addr") +public class RemoteIpServlet extends HttpServlet { + + @Override + protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException { + resp.getWriter().write("result:" + req.getRemoteAddr() + ":" + req.getRemotePort() + " " + req.getLocalAddr() + ":" + req.getLocalPort()); + } +} diff --git a/testsuite/shared/src/main/java/org/jboss/as/test/http/util/TestHttpClientUtils.java b/testsuite/shared/src/main/java/org/jboss/as/test/http/util/TestHttpClientUtils.java index 29d8f1874256..8cc53196a2d1 100644 --- a/testsuite/shared/src/main/java/org/jboss/as/test/http/util/TestHttpClientUtils.java +++ b/testsuite/shared/src/main/java/org/jboss/as/test/http/util/TestHttpClientUtils.java @@ -22,6 +22,8 @@ package org.jboss.as.test.http.util; +import java.security.KeyManagementException; +import java.security.NoSuchAlgorithmException; import java.security.cert.CertificateException; import java.security.cert.X509Certificate; import javax.net.ssl.SSLContext; @@ -75,22 +77,7 @@ public class TestHttpClientUtils { */ public static CloseableHttpClient getHttpsClient(CredentialsProvider credentialsProvider) { try { - SSLContext ctx = SSLContext.getInstance("TLS"); - X509TrustManager tm = new X509TrustManager() { - - public void checkClientTrusted(X509Certificate[] xcs, String string) throws CertificateException { - } - - public void checkServerTrusted(X509Certificate[] xcs, String string) throws CertificateException { - } - - public X509Certificate[] getAcceptedIssuers() { - return null; - } - }; - ctx.init(null, new TrustManager[]{tm}, null); - - ctx.init(null, new TrustManager[]{tm}, null); + SSLContext ctx = getSslContext(); SSLConnectionSocketFactory sslConnectionFactory = new SSLConnectionSocketFactory(ctx, new NoopHostnameVerifier()); @@ -113,6 +100,26 @@ public X509Certificate[] getAcceptedIssuers() { } } + public static SSLContext getSslContext() throws NoSuchAlgorithmException, KeyManagementException { + SSLContext ctx = SSLContext.getInstance("TLS"); + X509TrustManager tm = new X509TrustManager() { + + public void checkClientTrusted(X509Certificate[] xcs, String string) throws CertificateException { + } + + public void checkServerTrusted(X509Certificate[] xcs, String string) throws CertificateException { + } + + public X509Certificate[] getAcceptedIssuers() { + return null; + } + }; + ctx.init(null, new TrustManager[]{tm}, null); + + ctx.init(null, new TrustManager[]{tm}, null); + return ctx; + } + /** * Creates a http client that sends cookies to every domain, not just the originating domain. * As we don't actually have a load balancer for the clustering tests, we use this instead. diff --git a/testsuite/shared/src/main/java/org/jboss/as/test/integration/management/Listener.java b/testsuite/shared/src/main/java/org/jboss/as/test/integration/management/Listener.java index add521957966..83a52938072a 100644 --- a/testsuite/shared/src/main/java/org/jboss/as/test/integration/management/Listener.java +++ b/testsuite/shared/src/main/java/org/jboss/as/test/integration/management/Listener.java @@ -8,24 +8,17 @@ */ public enum Listener { - HTTP("http", "http", "HTTP/1.1", false), - HTTPS("http", "https", "HTTP/1.1", true), - AJP("ajp", "http", "AJP/1.3", false), - HTTPJIO("http", "http", "org.apache.coyote.http11.Http11Protocol", false), - HTTPSJIO("http", "https", "org.apache.coyote.http11.Http11Protocol", true), - AJPJIO("ajp", "http", "org.apache.coyote.ajp.AjpProtocol", false), - HTTPNATIVE("http", "http", "org.apache.coyote.http11.Http11AprProtocol", false), - HTTPSNATIVE("http","https", "org.apache.coyote.http11.Http11AprProtocol", true); + HTTP("http", "http", false), + HTTPS("http", "https", true), + AJP("ajp", "http", false); private final String name; private final String scheme; - private final String protocol; private final boolean secure; - private Listener(String name, String scheme, String protocol, boolean secure) { + private Listener(String name, String scheme, boolean secure) { this.name = name; this.scheme = scheme; - this.protocol = protocol; this.secure = secure; } @@ -37,10 +30,6 @@ public final String getScheme() { return scheme; } - public final String getProtocol() { - return protocol; - } - public final boolean isSecure() { return secure; } diff --git a/undertow/src/main/java/org/wildfly/extension/undertow/AjpListenerService.java b/undertow/src/main/java/org/wildfly/extension/undertow/AjpListenerService.java index ea9204f3d954..9b3d3a858ff1 100644 --- a/undertow/src/main/java/org/wildfly/extension/undertow/AjpListenerService.java +++ b/undertow/src/main/java/org/wildfly/extension/undertow/AjpListenerService.java @@ -48,7 +48,7 @@ public class AjpListenerService extends ListenerService { private final String scheme; public AjpListenerService(String name, final String scheme, OptionMap listenerOptions, OptionMap socketOptions) { - super(name, listenerOptions, socketOptions); + super(name, listenerOptions, socketOptions, false); this.scheme = scheme; } diff --git a/undertow/src/main/java/org/wildfly/extension/undertow/Constants.java b/undertow/src/main/java/org/wildfly/extension/undertow/Constants.java index 9ba3c255f355..6f1e0591e944 100644 --- a/undertow/src/main/java/org/wildfly/extension/undertow/Constants.java +++ b/undertow/src/main/java/org/wildfly/extension/undertow/Constants.java @@ -254,4 +254,6 @@ public interface Constants { String GET_SESSION_CREATION_TIME = "get-session-creation-time"; String GET_SESSION_CREATION_TIME_MILLIS = "get-session-creation-time-millis"; String DEFAULT_COOKIE_VERSION = "default-cookie-version"; + + String PROXY_PROTOCOL = "proxy-protocol"; } diff --git a/undertow/src/main/java/org/wildfly/extension/undertow/HttpListenerAdd.java b/undertow/src/main/java/org/wildfly/extension/undertow/HttpListenerAdd.java index 4f811ac93d38..8273160f65cc 100644 --- a/undertow/src/main/java/org/wildfly/extension/undertow/HttpListenerAdd.java +++ b/undertow/src/main/java/org/wildfly/extension/undertow/HttpListenerAdd.java @@ -46,6 +46,7 @@ public class HttpListenerAdd extends ListenerAdd { @Override ListenerService createService(String name, final String serverName, final OperationContext context, ModelNode model, OptionMap listenerOptions, OptionMap socketOptions) throws OperationFailedException { + final boolean proxyProtocol = HttpListenerResourceDefinition.PROXY_PROTOCOL.resolveModelAttribute(context, model).asBoolean(); final boolean certificateForwarding = HttpListenerResourceDefinition.CERTIFICATE_FORWARDING.resolveModelAttribute(context, model).asBoolean(); final boolean proxyAddressForwarding = HttpListenerResourceDefinition.PROXY_ADDRESS_FORWARDING.resolveModelAttribute(context, model).asBoolean(); OptionMap.Builder listenerBuilder = OptionMap.builder().addAll(listenerOptions); @@ -55,7 +56,7 @@ ListenerService createService(String name, final String serverName, final Operat handleHttp2Options(context, model, listenerBuilder); - return new HttpListenerService(name, serverName, listenerBuilder.getMap(), socketOptions, certificateForwarding, proxyAddressForwarding); + return new HttpListenerService(name, serverName, listenerBuilder.getMap(), socketOptions, certificateForwarding, proxyAddressForwarding, proxyProtocol); } static void handleHttp2Options(OperationContext context, ModelNode model, OptionMap.Builder listenerBuilder) throws OperationFailedException { diff --git a/undertow/src/main/java/org/wildfly/extension/undertow/HttpListenerResourceDefinition.java b/undertow/src/main/java/org/wildfly/extension/undertow/HttpListenerResourceDefinition.java index a77f40b61990..eb3308abc91f 100644 --- a/undertow/src/main/java/org/wildfly/extension/undertow/HttpListenerResourceDefinition.java +++ b/undertow/src/main/java/org/wildfly/extension/undertow/HttpListenerResourceDefinition.java @@ -124,6 +124,13 @@ public class HttpListenerResourceDefinition extends ListenerResourceDefinition { .setDefaultValue(new ModelNode(false)) .build(); + protected static final SimpleAttributeDefinition PROXY_PROTOCOL = new SimpleAttributeDefinitionBuilder(Constants.PROXY_PROTOCOL, ModelType.BOOLEAN) + .setDefaultValue(new ModelNode(false)) + .setRequired(false) + .setFlags(AttributeAccess.Flag.RESTART_RESOURCE_SERVICES) + .setAllowExpression(true) + .build(); + private HttpListenerResourceDefinition() { super(UndertowExtension.HTTP_LISTENER_PATH); } @@ -146,6 +153,7 @@ public Collection getAttributes() { attrs.add(HTTP2_MAX_HEADER_LIST_SIZE); attrs.add(HTTP2_MAX_FRAME_SIZE); attrs.add(REQUIRE_HOST_HTTP11); + attrs.add(PROXY_PROTOCOL); return attrs; } diff --git a/undertow/src/main/java/org/wildfly/extension/undertow/HttpListenerService.java b/undertow/src/main/java/org/wildfly/extension/undertow/HttpListenerService.java index 58151e7264a2..957ec011d717 100644 --- a/undertow/src/main/java/org/wildfly/extension/undertow/HttpListenerService.java +++ b/undertow/src/main/java/org/wildfly/extension/undertow/HttpListenerService.java @@ -62,8 +62,8 @@ public class HttpListenerService extends ListenerService { private final String serverName; - public HttpListenerService(String name, final String serverName, OptionMap listenerOptions, OptionMap socketOptions, boolean certificateForwarding, boolean proxyAddressForwarding) { - super(name, listenerOptions, socketOptions); + public HttpListenerService(String name, final String serverName, OptionMap listenerOptions, OptionMap socketOptions, boolean certificateForwarding, boolean proxyAddressForwarding, boolean proxyProtocol) { + super(name, listenerOptions, socketOptions, proxyProtocol); this.serverName = serverName; addWrapperHandler(handler -> { httpUpgradeHandler.setNonUpgradeHandler(handler); diff --git a/undertow/src/main/java/org/wildfly/extension/undertow/HttpsListenerAdd.java b/undertow/src/main/java/org/wildfly/extension/undertow/HttpsListenerAdd.java index b64bfcdaecd4..0a528531c3ce 100644 --- a/undertow/src/main/java/org/wildfly/extension/undertow/HttpsListenerAdd.java +++ b/undertow/src/main/java/org/wildfly/extension/undertow/HttpsListenerAdd.java @@ -52,6 +52,7 @@ ListenerService createService(String name, final String serverName, final Operat OptionMap.Builder builder = OptionMap.builder().addAll(socketOptions); ModelNode securityRealmModel = HttpsListenerResourceDefinition.SECURITY_REALM.resolveModelAttribute(context, model); + final boolean proxyProtocol = HttpListenerResourceDefinition.PROXY_PROTOCOL.resolveModelAttribute(context, model).asBoolean(); String cipherSuites = null; if(securityRealmModel.isDefined()) { //we only support setting these options for security realms @@ -73,7 +74,7 @@ ListenerService createService(String name, final String serverName, final Operat final boolean certificateForwarding = HttpListenerResourceDefinition.CERTIFICATE_FORWARDING.resolveModelAttribute(context, model).asBoolean(); final boolean proxyAddressForwarding = HttpListenerResourceDefinition.PROXY_ADDRESS_FORWARDING.resolveModelAttribute(context, model).asBoolean(); - return new HttpsListenerService(name, serverName, listenerBuilder.getMap(), cipherSuites, builder.getMap(), certificateForwarding, proxyAddressForwarding); + return new HttpsListenerService(name, serverName, listenerBuilder.getMap(), cipherSuites, builder.getMap(), certificateForwarding, proxyAddressForwarding, proxyProtocol); } @Override diff --git a/undertow/src/main/java/org/wildfly/extension/undertow/HttpsListenerResourceDefinition.java b/undertow/src/main/java/org/wildfly/extension/undertow/HttpsListenerResourceDefinition.java index fc081cbf3bdc..fb455c7fd760 100644 --- a/undertow/src/main/java/org/wildfly/extension/undertow/HttpsListenerResourceDefinition.java +++ b/undertow/src/main/java/org/wildfly/extension/undertow/HttpsListenerResourceDefinition.java @@ -143,6 +143,7 @@ public Collection getAttributes() { res.add(HttpListenerResourceDefinition.CERTIFICATE_FORWARDING); res.add(HttpListenerResourceDefinition.PROXY_ADDRESS_FORWARDING); res.add(HttpListenerResourceDefinition.REQUIRE_HOST_HTTP11); + res.add(HttpListenerResourceDefinition.PROXY_PROTOCOL); return res; } diff --git a/undertow/src/main/java/org/wildfly/extension/undertow/HttpsListenerService.java b/undertow/src/main/java/org/wildfly/extension/undertow/HttpsListenerService.java index 1f0f9f4fe6b0..93678b0b0ec2 100644 --- a/undertow/src/main/java/org/wildfly/extension/undertow/HttpsListenerService.java +++ b/undertow/src/main/java/org/wildfly/extension/undertow/HttpsListenerService.java @@ -65,20 +65,43 @@ public class HttpsListenerService extends HttpListenerService { private volatile AcceptingChannel sslServer; static final String PROTOCOL = "https"; private final String cipherSuites; + private final boolean proxyProtocol; - public HttpsListenerService(final String name, String serverName, OptionMap listenerOptions, String cipherSuites, OptionMap socketOptions) { - this(name, serverName, listenerOptions, cipherSuites, socketOptions, false, false); + public HttpsListenerService(final String name, String serverName, OptionMap listenerOptions, String cipherSuites, OptionMap socketOptions, boolean proxyProtocol) { + this(name, serverName, listenerOptions, cipherSuites, socketOptions, false, false, proxyProtocol); } - HttpsListenerService(final String name, String serverName, OptionMap listenerOptions, String cipherSuites, OptionMap socketOptions, boolean certificateForwarding, boolean proxyAddressForwarding) { - super(name, serverName, listenerOptions, socketOptions, certificateForwarding, proxyAddressForwarding); + HttpsListenerService(final String name, String serverName, OptionMap listenerOptions, String cipherSuites, OptionMap socketOptions, boolean certificateForwarding, boolean proxyAddressForwarding, boolean proxyProtocol) { + super(name, serverName, listenerOptions, socketOptions, certificateForwarding, proxyAddressForwarding, proxyProtocol); this.cipherSuites = cipherSuites; + this.proxyProtocol = proxyProtocol; } void setSSLContextSupplier(Supplier sslContextSupplier) { this.sslContextSupplier = sslContextSupplier; } + @Override + protected UndertowXnioSsl getSsl() { + SSLContext sslContext = sslContextSupplier.get(); + OptionMap combined = getSSLOptions(sslContext); + + return new UndertowXnioSsl(worker.getValue().getXnio(), combined, sslContext); + } + + protected OptionMap getSSLOptions(SSLContext sslContext) { + Builder builder = OptionMap.builder().addAll(commonOptions); + builder.addAll(socketOptions); + builder.set(Options.USE_DIRECT_BUFFERS, true); + + if (cipherSuites != null) { + String[] cipherList = CipherSuiteSelector.fromString(cipherSuites).evaluate(sslContext.getSupportedSSLParameters().getCipherSuites()); + builder.setSequence((Option>) HttpsListenerResourceDefinition.ENABLED_CIPHER_SUITES.getOption(), cipherList); + } + + return builder.getMap(); + } + @Override protected OpenListener createOpenListener() { if(listenerOptions.get(UndertowOptions.ENABLE_HTTP2, false)) { @@ -109,22 +132,17 @@ private OpenListener createAlpnOpenListener() { return alpn; } + + @Override protected void startListening(XnioWorker worker, InetSocketAddress socketAddress, ChannelListener> acceptListener) throws IOException { - SSLContext sslContext = sslContextSupplier.get(); - Builder builder = OptionMap.builder().addAll(commonOptions); - builder.addAll(socketOptions); - builder.set(Options.USE_DIRECT_BUFFERS, true); - if (cipherSuites != null) { - String[] cipherList = CipherSuiteSelector.fromString(cipherSuites).evaluate(sslContext.getSupportedSSLParameters().getCipherSuites()); - builder.setSequence((Option>) HttpsListenerResourceDefinition.ENABLED_CIPHER_SUITES.getOption(), cipherList); + if(proxyProtocol) { + sslServer = worker.createStreamConnectionServer(socketAddress, (ChannelListener) acceptListener, getSSLOptions(sslContextSupplier.get())); + } else { + XnioSsl ssl = getSsl(); + sslServer = ssl.createSslConnectionServer(worker, socketAddress, (ChannelListener) acceptListener, getSSLOptions(sslContextSupplier.get())); } - - OptionMap combined = builder.getMap(); - - XnioSsl xnioSsl = new UndertowXnioSsl(worker.getXnio(), combined, sslContext); - sslServer = xnioSsl.createSslConnectionServer(worker, socketAddress, (ChannelListener) acceptListener, combined); sslServer.resumeAccepts(); final InetSocketAddress boundAddress = sslServer.getLocalAddress(InetSocketAddress.class); diff --git a/undertow/src/main/java/org/wildfly/extension/undertow/ListenerService.java b/undertow/src/main/java/org/wildfly/extension/undertow/ListenerService.java index 6118b00cf42c..2fc32b4ca859 100644 --- a/undertow/src/main/java/org/wildfly/extension/undertow/ListenerService.java +++ b/undertow/src/main/java/org/wildfly/extension/undertow/ListenerService.java @@ -30,10 +30,15 @@ import java.util.List; import java.util.function.Consumer; +import javax.net.ssl.SSLContext; + import io.undertow.UndertowOptions; +import io.undertow.protocols.ssl.UndertowXnioSsl; import io.undertow.server.HandlerWrapper; import io.undertow.server.HttpHandler; import io.undertow.server.OpenListener; +import io.undertow.server.XnioByteBufferPool; +import io.undertow.server.protocol.proxy.ProxyProtocolOpenListener; import org.jboss.as.network.ManagedBinding; import org.jboss.as.network.SocketBinding; @@ -81,11 +86,13 @@ public abstract class ListenerService implements Service, Unde private volatile boolean enabled; private volatile boolean started; private Consumer statisticsChangeListener; + private final boolean proxyProtocol; - protected ListenerService(String name, OptionMap listenerOptions, OptionMap socketOptions) { + protected ListenerService(String name, OptionMap listenerOptions, OptionMap socketOptions, boolean proxyProtocol) { this.name = name; this.listenerOptions = listenerOptions; this.socketOptions = socketOptions; + this.proxyProtocol = proxyProtocol; } public InjectedValue getWorker() { @@ -121,6 +128,14 @@ public boolean isEnabled() { return enabled; } + protected UndertowXnioSsl getSsl() { + return null; + } + + protected OptionMap getSSLOptions(SSLContext sslContext) { + return OptionMap.EMPTY; + } + public synchronized void setEnabled(boolean enabled) { if(started && enabled != this.enabled) { if(enabled) { @@ -165,7 +180,15 @@ public void start(StartContext context) throws StartException { openListener.setRootHandler(handler); if(enabled) { final InetSocketAddress socketAddress = binding.getValue().getSocketAddress(); - final ChannelListener> acceptListener = ChannelListeners.openListenerAdapter(openListener); + + + final ChannelListener> acceptListener; + if(proxyProtocol) { + UndertowXnioSsl xnioSsl = getSsl(); + acceptListener = ChannelListeners.openListenerAdapter(new ProxyProtocolOpenListener(openListener, xnioSsl, new XnioByteBufferPool(bufferPool.getValue()), xnioSsl != null ? getSSLOptions(xnioSsl.getSslContext()) : null)); + } else { + acceptListener = ChannelListeners.openListenerAdapter(openListener); + } startListening(worker.getValue(), socketAddress, acceptListener); } registerBinding(); diff --git a/undertow/src/main/java/org/wildfly/extension/undertow/UndertowSubsystemParser_6_0.java b/undertow/src/main/java/org/wildfly/extension/undertow/UndertowSubsystemParser_6_0.java index dff06c02dd80..4d58a2853a7c 100644 --- a/undertow/src/main/java/org/wildfly/extension/undertow/UndertowSubsystemParser_6_0.java +++ b/undertow/src/main/java/org/wildfly/extension/undertow/UndertowSubsystemParser_6_0.java @@ -87,7 +87,8 @@ public class UndertowSubsystemParser_6_0 extends PersistentResourceXMLParser { HttpListenerResourceDefinition.HTTP2_MAX_CONCURRENT_STREAMS, HttpListenerResourceDefinition.HTTP2_MAX_FRAME_SIZE, HttpListenerResourceDefinition.HTTP2_MAX_HEADER_LIST_SIZE, - HttpListenerResourceDefinition.REQUIRE_HOST_HTTP11) + HttpListenerResourceDefinition.REQUIRE_HOST_HTTP11, + HttpListenerResourceDefinition.PROXY_PROTOCOL) ).addChild( listenerBuilder(HttpsListenerResourceDefinition.INSTANCE) // xsd https-listener-type @@ -110,7 +111,8 @@ public class UndertowSubsystemParser_6_0 extends PersistentResourceXMLParser { HttpListenerResourceDefinition.HTTP2_MAX_CONCURRENT_STREAMS, HttpListenerResourceDefinition.HTTP2_MAX_FRAME_SIZE, HttpListenerResourceDefinition.HTTP2_MAX_HEADER_LIST_SIZE, - HttpListenerResourceDefinition.REQUIRE_HOST_HTTP11) + HttpListenerResourceDefinition.REQUIRE_HOST_HTTP11, + HttpListenerResourceDefinition.PROXY_PROTOCOL) ).addChild( builder(HostDefinition.INSTANCE.getPathElement()) .addAttributes(HostDefinition.ALIAS, HostDefinition.DEFAULT_WEB_MODULE, HostDefinition.DEFAULT_RESPONSE_CODE, HostDefinition.DISABLE_CONSOLE_REDIRECT) diff --git a/undertow/src/main/java/org/wildfly/extension/undertow/UndertowTransformers.java b/undertow/src/main/java/org/wildfly/extension/undertow/UndertowTransformers.java index dfa5031e2943..3cade6745668 100644 --- a/undertow/src/main/java/org/wildfly/extension/undertow/UndertowTransformers.java +++ b/undertow/src/main/java/org/wildfly/extension/undertow/UndertowTransformers.java @@ -80,14 +80,16 @@ private static void registerTransformers_EAP_7_1_0(SubsystemTransformerRegistrat .end(); final ResourceTransformationDescriptionBuilder serverBuilder = subsystemBuilder.addChildResource(UndertowExtension.SERVER_PATH); - final AttributeTransformationDescriptionBuilder https = serverBuilder.addChildResource(UndertowExtension.HTTPS_LISTENER_PATH).getAttributeBuilder(); - addCommonListenerRules_EAP_7_1_0(https); - https.end(); - - final AttributeTransformationDescriptionBuilder http = serverBuilder.addChildResource(UndertowExtension.HTTP_LISTENER_PATH).getAttributeBuilder(); + final AttributeTransformationDescriptionBuilder http = serverBuilder.addChildResource(UndertowExtension.HTTP_LISTENER_PATH).getAttributeBuilder() + .addRejectCheck(new RejectAttributeChecker.SimpleRejectAttributeChecker(new ModelNode(true)), HttpListenerResourceDefinition.PROXY_PROTOCOL.getName()) + .setDiscard(new DiscardAttributeChecker.DiscardAttributeValueChecker(new ModelNode(false)), HttpListenerResourceDefinition.PROXY_PROTOCOL); addCommonListenerRules_EAP_7_1_0(http); http.end(); - + final AttributeTransformationDescriptionBuilder https = serverBuilder.addChildResource(UndertowExtension.HTTPS_LISTENER_PATH).getAttributeBuilder() + .addRejectCheck(new RejectAttributeChecker.SimpleRejectAttributeChecker(new ModelNode(true)), HttpListenerResourceDefinition.PROXY_PROTOCOL.getName()) + .setDiscard(new DiscardAttributeChecker.DiscardAttributeValueChecker(new ModelNode(false)), HttpListenerResourceDefinition.PROXY_PROTOCOL); + addCommonListenerRules_EAP_7_1_0(https); + https.end(); final AttributeTransformationDescriptionBuilder ajp = serverBuilder.addChildResource(UndertowExtension.AJP_LISTENER_PATH).getAttributeBuilder(); addCommonListenerRules_EAP_7_1_0(ajp); ajp.end(); @@ -205,11 +207,13 @@ private static AttributeTransformationDescriptionBuilder addCommonListenerRules_ .setDiscard(FALSE_DISCARD_CHECKER, HttpListenerResourceDefinition.REQUIRE_HOST_HTTP11) .setValueConverter(new AttributeConverter.DefaultValueAttributeConverter(HttpListenerResourceDefinition.HTTP2_HEADER_TABLE_SIZE), HttpListenerResourceDefinition.HTTP2_HEADER_TABLE_SIZE) .setValueConverter(new AttributeConverter.DefaultValueAttributeConverter(HttpListenerResourceDefinition.HTTP2_INITIAL_WINDOW_SIZE), HttpListenerResourceDefinition.HTTP2_INITIAL_WINDOW_SIZE) - .setValueConverter(new AttributeConverter.DefaultValueAttributeConverter(HttpListenerResourceDefinition.HTTP2_MAX_FRAME_SIZE), HttpListenerResourceDefinition.HTTP2_MAX_FRAME_SIZE); + .setValueConverter(new AttributeConverter.DefaultValueAttributeConverter(HttpListenerResourceDefinition.HTTP2_MAX_FRAME_SIZE), HttpListenerResourceDefinition.HTTP2_MAX_FRAME_SIZE) + .addRejectCheck(new RejectAttributeChecker.SimpleRejectAttributeChecker(new ModelNode(true)), HttpListenerResourceDefinition.PROXY_PROTOCOL.getName()) + .setDiscard(new DiscardAttributeChecker.DiscardAttributeValueChecker(new ModelNode(false)), HttpListenerResourceDefinition.PROXY_PROTOCOL); } private static AttributeTransformationDescriptionBuilder convertCommonListenerAttributes(AttributeTransformationDescriptionBuilder builder) { - return builder.setValueConverter(new AttributeConverter.DefaultAttributeConverter() { + builder.setValueConverter(new AttributeConverter.DefaultAttributeConverter() { @Override protected void convertAttribute(PathAddress address, String attributeName, ModelNode attributeValue, TransformationContext context) { if (attributeValue.isDefined() && attributeValue.asLong() == 0L) { @@ -217,5 +221,7 @@ protected void convertAttribute(PathAddress address, String attributeName, Model } } }, ListenerResourceDefinition.MAX_ENTITY_SIZE); + + return builder; } } diff --git a/undertow/src/main/resources/org/wildfly/extension/undertow/LocalDescriptions.properties b/undertow/src/main/resources/org/wildfly/extension/undertow/LocalDescriptions.properties index 352037d024c2..b734849d62cc 100644 --- a/undertow/src/main/resources/org/wildfly/extension/undertow/LocalDescriptions.properties +++ b/undertow/src/main/resources/org/wildfly/extension/undertow/LocalDescriptions.properties @@ -195,6 +195,7 @@ undertow.listener.ssl-session-cache-size.deprecated=This can now be configured o undertow.listener.ssl-session-timeout=The timeout for SSL sessions, in seconds undertow.listener.ssl-session-timeout.deprecated=This can now be configured on the Elytron security context undertow.listener.secure=If this is true then requests that originate from this listener are marked as secure, even if the request is not using HTTPS. +undertow.listener.proxy-protocol=If this is true then the listener will use the proxy protocol v1, as defined by https://www.haproxy.org/download/1.8/doc/proxy-protocol.txt. This option MUST only be enabled for listeners that are behind a load balancer that supports the same protocol. undertow.listener.max-ajp-packet-size=The maximum supported size of AJP packets. If this is modified it has to be increased on the load balancer and the backend server. undertow.listener.http2-enable-push=If server push is enabled for this connection undertow.listener.http2-max-frame-size=The max HTTP/2 frame size diff --git a/undertow/src/main/resources/schema/wildfly-undertow_6_0.xsd b/undertow/src/main/resources/schema/wildfly-undertow_6_0.xsd index 15701a410a46..c5acd7bbcc11 100644 --- a/undertow/src/main/resources/schema/wildfly-undertow_6_0.xsd +++ b/undertow/src/main/resources/schema/wildfly-undertow_6_0.xsd @@ -160,6 +160,7 @@ + @@ -221,6 +222,7 @@ + diff --git a/undertow/src/test/java/org/wildfly/extension/undertow/UndertowTransformersTestCase.java b/undertow/src/test/java/org/wildfly/extension/undertow/UndertowTransformersTestCase.java index 2179a7afb4e7..373f6d7fbd22 100644 --- a/undertow/src/test/java/org/wildfly/extension/undertow/UndertowTransformersTestCase.java +++ b/undertow/src/test/java/org/wildfly/extension/undertow/UndertowTransformersTestCase.java @@ -100,6 +100,7 @@ private void doRejectTest(ModelTestControllerVersion controllerVersion, ModelVer PathAddress serverAddress = subsystemAddress.append(UndertowExtension.SERVER_PATH); PathAddress hostAddress = serverAddress.append(UndertowExtension.HOST_PATH); PathAddress httpsAddress = serverAddress.append(UndertowExtension.HTTPS_LISTENER_PATH); + PathAddress ajpAddress = serverAddress.append(UndertowExtension.AJP_LISTENER_PATH); PathAddress httpAddress = serverAddress.append(UndertowExtension.HTTP_LISTENER_PATH); PathAddress reverseProxy = subsystemAddress.append(UndertowExtension.PATH_HANDLERS).append(Constants.REVERSE_PROXY); PathAddress reverseProxyServerAddress = reverseProxy.append(Constants.HOST); @@ -108,7 +109,8 @@ private void doRejectTest(ModelTestControllerVersion controllerVersion, ModelVer ModelTestUtils.checkFailedTransformedBootOperations(mainServices, targetVersion, ops, new FailedOperationTransformationConfig() .addFailedAttribute(httpAddress, new FailedOperationTransformationConfig.NewAttributesConfig( - HttpListenerResourceDefinition.REQUIRE_HOST_HTTP11 + HttpListenerResourceDefinition.REQUIRE_HOST_HTTP11, + HttpListenerResourceDefinition.PROXY_PROTOCOL ) ).addFailedAttribute(httpsAddress, new FailedOperationTransformationConfig.NewAttributesConfig( @@ -116,7 +118,8 @@ private void doRejectTest(ModelTestControllerVersion controllerVersion, ModelVer HttpListenerResourceDefinition.PROXY_ADDRESS_FORWARDING.getName(), HttpListenerResourceDefinition.CERTIFICATE_FORWARDING.getName(), HttpsListenerResourceDefinition.SSL_CONTEXT.getName(), - HttpsListenerResourceDefinition.ALLOW_UNESCAPED_CHARACTERS_IN_URL.getName() + HttpsListenerResourceDefinition.ALLOW_UNESCAPED_CHARACTERS_IN_URL.getName(), + HttpListenerResourceDefinition.PROXY_PROTOCOL.getName() ) ) .addFailedAttribute(reverseProxy, new FailedOperationTransformationConfig.NewAttributesConfig(ReverseProxyHandler.MAX_RETRIES)) diff --git a/undertow/src/test/resources/org/wildfly/extension/undertow/undertow-6.0.xml b/undertow/src/test/resources/org/wildfly/extension/undertow/undertow-6.0.xml index 08a755444f39..ba5e441ecdbb 100644 --- a/undertow/src/test/resources/org/wildfly/extension/undertow/undertow-6.0.xml +++ b/undertow/src/test/resources/org/wildfly/extension/undertow/undertow-6.0.xml @@ -25,12 +25,12 @@ - + - +