diff --git a/nifi-framework-bundle/nifi-framework/nifi-web/nifi-jetty/src/main/java/org/apache/nifi/web/server/connector/FrameworkServerConnectorFactory.java b/nifi-framework-bundle/nifi-framework/nifi-web/nifi-jetty/src/main/java/org/apache/nifi/web/server/connector/FrameworkServerConnectorFactory.java index e085cc0faced..c3cd71d36931 100644 --- a/nifi-framework-bundle/nifi-framework/nifi-web/nifi-jetty/src/main/java/org/apache/nifi/web/server/connector/FrameworkServerConnectorFactory.java +++ b/nifi-framework-bundle/nifi-framework/nifi-web/nifi-jetty/src/main/java/org/apache/nifi/web/server/connector/FrameworkServerConnectorFactory.java @@ -107,7 +107,7 @@ protected HttpConfiguration getHttpConfiguration() { // Add HostHeaderCustomizer to set Host Header for HTTP/2 httpConfiguration.addCustomizer(new HostHeaderCustomizer()); - final ProxyHeaderValidatorCustomizer proxyHeaderValidatorCustomizer = new ProxyHeaderValidatorCustomizer(validProxyHosts); + final ProxyHeaderValidatorCustomizer proxyHeaderValidatorCustomizer = new ProxyHeaderValidatorCustomizer(validProxyHosts, validPorts); httpConfiguration.addCustomizer(proxyHeaderValidatorCustomizer); final HostPortValidatorCustomizer hostPortValidatorCustomizer = new HostPortValidatorCustomizer(validPorts); diff --git a/nifi-framework-bundle/nifi-framework/nifi-web/nifi-jetty/src/main/java/org/apache/nifi/web/server/connector/ProxyHeaderValidatorCustomizer.java b/nifi-framework-bundle/nifi-framework/nifi-web/nifi-jetty/src/main/java/org/apache/nifi/web/server/connector/ProxyHeaderValidatorCustomizer.java index 34c149c0877a..cf270d8da5dd 100644 --- a/nifi-framework-bundle/nifi-framework/nifi-web/nifi-jetty/src/main/java/org/apache/nifi/web/server/connector/ProxyHeaderValidatorCustomizer.java +++ b/nifi-framework-bundle/nifi-framework/nifi-web/nifi-jetty/src/main/java/org/apache/nifi/web/server/connector/ProxyHeaderValidatorCustomizer.java @@ -31,12 +31,16 @@ import java.security.cert.X509Certificate; import java.util.Objects; import java.util.Set; +import java.util.regex.Pattern; /** * Jetty Request Customizer implementing validation for supported Proxy Request Headers */ public class ProxyHeaderValidatorCustomizer implements HttpConfiguration.Customizer { private static final String MISDIRECTED_REQUEST_REASON = "Invalid Proxy Host Requested"; + private static final String MISDIRECTED_REQUEST_REASON_INVALID_PORT = "Invalid Port Requested"; + + private static final Pattern HOST_PORT_SEPARATOR = Pattern.compile(":"); private static final Set SUPPORTED_PROXY_HOST_HEADERS = Set.of( ProxyHeader.PROXY_HOST.getHeader(), @@ -44,9 +48,11 @@ public class ProxyHeaderValidatorCustomizer implements HttpConfiguration.Customi ); private final Set validProxyHosts; + private final Set validPorts; - public ProxyHeaderValidatorCustomizer(final Set validProxyHosts) { + public ProxyHeaderValidatorCustomizer(final Set validProxyHosts, final Set validPorts) { this.validProxyHosts = Objects.requireNonNull(validProxyHosts, "Valid Proxy Hosts required"); + this.validPorts = Objects.requireNonNull(validPorts, "Valid Ports required"); } /** @@ -95,20 +101,45 @@ private void processProxyHostHeaders(final Request request) { final HttpFields requestHeaders = request.getHeaders(); for (final String proxyHostHeader : SUPPORTED_PROXY_HOST_HEADERS) { - final String hostHeader = requestHeaders.get(proxyHostHeader); + String hostHeader = requestHeaders.get(proxyHostHeader); // Include empty and blank values for enforced validation of request headers if (hostHeader == null) { continue; } + + final String[] hostHeaderParts = HOST_PORT_SEPARATOR.split(hostHeader); + if (hostHeaderParts.length == 2) { + hostHeader = hostHeaderParts[0]; + } + // Allow proxy host header matching request host header based on TLS SNI and DNS SAN requirements if (requestHost.equals(hostHeader)) { + processProxyHostHeaderPortNumber(hostHeaderParts); continue; } + if (validProxyHosts.contains(hostHeader)) { + processProxyHostHeaderPortNumber(hostHeaderParts); continue; } - throw new HttpException.RuntimeException(HttpStatus.MISDIRECTED_REQUEST_421, MISDIRECTED_REQUEST_REASON); + throw new HttpException.RuntimeException(HttpStatus.MISDIRECTED_REQUEST_421, MISDIRECTED_REQUEST_REASON); + + } + } + + private void processProxyHostHeaderPortNumber(String[] hostHeaderParts) { + if (hostHeaderParts.length == 2) { + try { + final int port = Integer.parseInt(hostHeaderParts[1]); + if (validPorts.contains(port)) { + return; + } else { + throw new HttpException.RuntimeException(HttpStatus.MISDIRECTED_REQUEST_421, MISDIRECTED_REQUEST_REASON_INVALID_PORT); + } + } catch (final NumberFormatException e) { + throw new HttpException.RuntimeException(HttpStatus.MISDIRECTED_REQUEST_421, MISDIRECTED_REQUEST_REASON_INVALID_PORT); + } } } diff --git a/nifi-framework-bundle/nifi-framework/nifi-web/nifi-jetty/src/test/java/org/apache/nifi/web/server/StandardServerProviderTest.java b/nifi-framework-bundle/nifi-framework/nifi-web/nifi-jetty/src/test/java/org/apache/nifi/web/server/StandardServerProviderTest.java index e5740b3bed84..cded290641a1 100644 --- a/nifi-framework-bundle/nifi-framework/nifi-web/nifi-jetty/src/test/java/org/apache/nifi/web/server/StandardServerProviderTest.java +++ b/nifi-framework-bundle/nifi-framework/nifi-web/nifi-jetty/src/test/java/org/apache/nifi/web/server/StandardServerProviderTest.java @@ -34,6 +34,7 @@ import org.junit.jupiter.api.BeforeAll; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.Timeout; +import org.springframework.web.util.UriBuilder; import org.springframework.web.util.UriComponentsBuilder; import java.io.IOException; @@ -87,6 +88,12 @@ class StandardServerProviderTest { private static final String PUBLIC_STAGED_HOST = "nifi.staged.apache.org"; + private static final String PUBLIC_STAGED_HOST_WITH_PORT = "nifi.staged.apache.org:8443"; + + private static final String PUBLIC_STAGED_HOST_WITH_INVALID_PORT = "nifi.staged.apache.org:8444"; + + private static final String PUBLIC_STAGED_HOST_WITH_A_PORT_WHICH_NOT_NUMBER = "nifi.staged.apache.org:aaa"; + private static final String PROXY_HOST_PROPERTY = "nifi.apache.org,nifi.staged.apache.org:8443"; private static final String PUBLIC_UNKNOWN_HOST = "nifi.unknown.apache.org"; @@ -350,6 +357,12 @@ void assertRedirectRequestsCompleted(final HttpClient httpClient, final URI loca .header(ProxyHeader.FORWARDED_HOST.getHeader(), PUBLIC_STAGED_HOST) .build(); assertResponseStatusCode(httpClient, forwardedHostRequest, HttpStatus.MOVED_TEMPORARILY_302); + + final HttpRequest forwardedHostRequestWithPort = HttpRequest.newBuilder(localhostUri) + .version(HttpClient.Version.HTTP_1_1) + .header(ProxyHeader.FORWARDED_HOST.getHeader(), PUBLIC_STAGED_HOST_WITH_PORT) + .build(); + assertResponseStatusCode(httpClient, forwardedHostRequestWithPort, HttpStatus.MOVED_TEMPORARILY_302); } void assertBadRequestsCompleted(final HttpClient httpClient, final URI localhostUri) throws IOException, InterruptedException { @@ -384,6 +397,24 @@ void assertMisdirectedRequestsCompleted(final HttpClient httpClient, final URI l .header(ProxyHeader.FORWARDED_HOST.getHeader(), PUBLIC_UNKNOWN_HOST) .build(); assertResponseStatusCode(httpClient, publicUnknownForwardedHostRequest, HttpStatus.MISDIRECTED_REQUEST_421); + + final HttpRequest publicStagedHostWithInvalidPort = HttpRequest.newBuilder(localhostUri) + .version(HttpClient.Version.HTTP_1_1) + .header(ProxyHeader.FORWARDED_HOST.getHeader(), PUBLIC_STAGED_HOST_WITH_INVALID_PORT) + .build(); + assertResponseStatusCode(httpClient, publicStagedHostWithInvalidPort, HttpStatus.MISDIRECTED_REQUEST_421); + + final HttpRequest publicStagedHostWithAPortWhichNotNumber = HttpRequest.newBuilder(localhostUri) + .version(HttpClient.Version.HTTP_1_1) + .header(ProxyHeader.FORWARDED_HOST.getHeader(), PUBLIC_STAGED_HOST_WITH_A_PORT_WHICH_NOT_NUMBER) + .build(); + assertResponseStatusCode(httpClient, publicStagedHostWithAPortWhichNotNumber, HttpStatus.MISDIRECTED_REQUEST_421); + + final HttpRequest sameHostNameWithInvalidPort = HttpRequest.newBuilder(localhostUri) + .version(HttpClient.Version.HTTP_1_1) + .header(ProxyHeader.FORWARDED_HOST.getHeader(), localhostUri.getHost() + ":" + localhostUri.getPort()) + .build(); + assertResponseStatusCode(httpClient, sameHostNameWithInvalidPort, HttpStatus.MISDIRECTED_REQUEST_421); } void assertStandardResponseHeadersFound(final HttpResponse response) {