Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -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);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -31,22 +31,28 @@
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<String> SUPPORTED_PROXY_HOST_HEADERS = Set.of(
ProxyHeader.PROXY_HOST.getHeader(),
ProxyHeader.FORWARDED_HOST.getHeader()
);

private final Set<String> validProxyHosts;
private final Set<Integer> validPorts;

public ProxyHeaderValidatorCustomizer(final Set<String> validProxyHosts) {
public ProxyHeaderValidatorCustomizer(final Set<String> validProxyHosts, final Set<Integer> validPorts) {
this.validProxyHosts = Objects.requireNonNull(validProxyHosts, "Valid Proxy Hosts required");
this.validPorts = Objects.requireNonNull(validPorts, "Valid Ports required");
}

/**
Expand Down Expand Up @@ -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;
}
Comment thread
taz1988 marked this conversation as resolved.

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);
}
}
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -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";
Expand Down Expand Up @@ -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 {
Expand Down Expand Up @@ -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<Void> response) {
Expand Down
Loading