mirror of https://github.com/apache/nifi.git
NIFI-5748 Improved Proxy Header Support
- Fixed proxy header support to use X-Forwarded-Host instead of X-ForwardedServer - Added support for the context path header used by Traefik when proxying a service (X-Forwarded-Prefix) - Added tests to ApplicationResourceTest for X-Forwarded-Context and X-Forwarded-Prefix - Updated administration doc to include X-Forwarded-Prefix - Added NIFI_WEB_PROXY_CONTEXT_PATH env var to dockerhub and dockermaven start.sh scripts - Added documentation for NIFI_WEB_PROXY_CONTEXT_PATH to dockerhub README.md - Updated ApplicationResource to handle a port specified in X-ProxyPort and X-Forwarded-Port headers This closes #3129. Signed-off-by: Kevin Doran <kdoran@apache.org>
This commit is contained in:
parent
7c9617a0ec
commit
cc47a8c0e1
|
@ -21,6 +21,7 @@ import java.util.Arrays;
|
|||
import java.util.List;
|
||||
import java.util.concurrent.locks.ReadWriteLock;
|
||||
import java.util.concurrent.locks.ReentrantReadWriteLock;
|
||||
import java.util.stream.Stream;
|
||||
import javax.net.ssl.SSLContext;
|
||||
import javax.servlet.ServletRequest;
|
||||
import javax.servlet.http.HttpServletRequest;
|
||||
|
@ -45,6 +46,7 @@ public final class WebUtils {
|
|||
|
||||
private static final String PROXY_CONTEXT_PATH_HTTP_HEADER = "X-ProxyContextPath";
|
||||
private static final String FORWARDED_CONTEXT_HTTP_HEADER = "X-Forwarded-Context";
|
||||
private static final String FORWARDED_PREFIX_HTTP_HEADER = "X-Forwarded-Prefix";
|
||||
|
||||
private WebUtils() {
|
||||
}
|
||||
|
@ -199,7 +201,8 @@ public final class WebUtils {
|
|||
}
|
||||
|
||||
/**
|
||||
* Determines the context path if populated in {@code X-ProxyContextPath} or {@code X-ForwardContext} headers. If not populated, returns an empty string.
|
||||
* Determines the context path if populated in {@code X-ProxyContextPath}, {@code X-ForwardContext},
|
||||
* or {@code X-Forwarded-Prefix} headers. If not populated, returns an empty string.
|
||||
*
|
||||
* @param request the HTTP request
|
||||
* @return the provided context path or an empty string
|
||||
|
@ -208,18 +211,20 @@ public final class WebUtils {
|
|||
String contextPath = request.getContextPath();
|
||||
String proxyContextPath = request.getHeader(PROXY_CONTEXT_PATH_HTTP_HEADER);
|
||||
String forwardedContext = request.getHeader(FORWARDED_CONTEXT_HTTP_HEADER);
|
||||
String prefix = request.getHeader(FORWARDED_PREFIX_HTTP_HEADER);
|
||||
|
||||
logger.debug("Context path: " + contextPath);
|
||||
String determinedContextPath = "";
|
||||
|
||||
// If either header is set, log both
|
||||
if (anyNotBlank(proxyContextPath, forwardedContext)) {
|
||||
// If a context path header is set, log each
|
||||
if (anyNotBlank(proxyContextPath, forwardedContext, prefix)) {
|
||||
logger.debug(String.format("On the request, the following context paths were parsed" +
|
||||
" from headers:\n\t X-ProxyContextPath: %s\n\tX-Forwarded-Context: %s",
|
||||
proxyContextPath, forwardedContext));
|
||||
" from headers:\n\t X-ProxyContextPath: %s\n\tX-Forwarded-Context: %s\n\tX-Forwarded-Prefix: %s",
|
||||
proxyContextPath, forwardedContext, prefix));
|
||||
|
||||
// Implementing preferred order here: PCP, FCP
|
||||
determinedContextPath = StringUtils.isNotBlank(proxyContextPath) ? proxyContextPath : forwardedContext;
|
||||
// Implementing preferred order here: PCP, FC, FP
|
||||
determinedContextPath = Stream.of(proxyContextPath, forwardedContext, prefix)
|
||||
.filter(StringUtils::isNotBlank).findFirst().orElse("");
|
||||
}
|
||||
|
||||
logger.debug("Determined context path: " + determinedContextPath);
|
||||
|
|
|
@ -32,10 +32,10 @@ import sun.security.x509.X500Name
|
|||
import javax.net.ssl.SSLPeerUnverifiedException
|
||||
import javax.servlet.http.HttpServletRequest
|
||||
import javax.ws.rs.core.UriBuilderException
|
||||
import javax.ws.rs.client.Client;
|
||||
import javax.ws.rs.client.Client
|
||||
import javax.net.ssl.SSLContext
|
||||
import javax.net.ssl.HostnameVerifier;
|
||||
import java.security.cert.X509Certificate;
|
||||
import javax.net.ssl.HostnameVerifier
|
||||
import java.security.cert.X509Certificate
|
||||
|
||||
|
||||
@RunWith(JUnit4.class)
|
||||
|
@ -44,9 +44,10 @@ class WebUtilsTest extends GroovyTestCase {
|
|||
|
||||
static final String PCP_HEADER = "X-ProxyContextPath"
|
||||
static final String FC_HEADER = "X-Forwarded-Context"
|
||||
static final String FP_HEADER = "X-Forwarded-Prefix"
|
||||
|
||||
static final String WHITELISTED_PATH = "/some/context/path"
|
||||
private static final String OCSP_REQUEST_CONTENT_TYPE = "application/ocsp-request";
|
||||
private static final String OCSP_REQUEST_CONTENT_TYPE = "application/ocsp-request"
|
||||
|
||||
@BeforeClass
|
||||
static void setUpOnce() throws Exception {
|
||||
|
@ -78,6 +79,9 @@ class WebUtilsTest extends GroovyTestCase {
|
|||
case FC_HEADER:
|
||||
return keys["forward"]
|
||||
break
|
||||
case FP_HEADER:
|
||||
return keys["prefix"]
|
||||
break
|
||||
default:
|
||||
return ""
|
||||
}
|
||||
|
@ -94,8 +98,12 @@ class WebUtilsTest extends GroovyTestCase {
|
|||
// Variety of requests with different ordering of context paths (the correct one is always "some/context/path"
|
||||
HttpServletRequest proxyRequest = mockRequest([proxy: CORRECT_CONTEXT_PATH])
|
||||
HttpServletRequest forwardedRequest = mockRequest([forward: CORRECT_CONTEXT_PATH])
|
||||
HttpServletRequest prefixRequest = mockRequest([prefix: CORRECT_CONTEXT_PATH])
|
||||
HttpServletRequest proxyBeforeForwardedRequest = mockRequest([proxy: CORRECT_CONTEXT_PATH, forward: WRONG_CONTEXT_PATH])
|
||||
List<HttpServletRequest> requests = [proxyRequest, forwardedRequest, proxyBeforeForwardedRequest]
|
||||
HttpServletRequest proxyBeforePrefixRequest = mockRequest([proxy: CORRECT_CONTEXT_PATH, prefix: WRONG_CONTEXT_PATH])
|
||||
HttpServletRequest forwardBeforePrefixRequest = mockRequest([forward: CORRECT_CONTEXT_PATH, prefix: WRONG_CONTEXT_PATH])
|
||||
List<HttpServletRequest> requests = [proxyRequest, forwardedRequest, prefixRequest, proxyBeforeForwardedRequest,
|
||||
proxyBeforePrefixRequest, forwardBeforePrefixRequest]
|
||||
|
||||
// Act
|
||||
requests.each { HttpServletRequest request ->
|
||||
|
@ -117,8 +125,12 @@ class WebUtilsTest extends GroovyTestCase {
|
|||
HttpServletRequest proxySpacesRequest = mockRequest([proxy: " "])
|
||||
HttpServletRequest forwardedRequest = mockRequest([forward: ""])
|
||||
HttpServletRequest forwardedSpacesRequest = mockRequest([forward: " "])
|
||||
HttpServletRequest proxyBeforeForwardedRequest = mockRequest([proxy: "", forward: ""])
|
||||
List<HttpServletRequest> requests = [proxyRequest, proxySpacesRequest, forwardedRequest, forwardedSpacesRequest, proxyBeforeForwardedRequest]
|
||||
HttpServletRequest prefixRequest = mockRequest([prefix: ""])
|
||||
HttpServletRequest prefixSpacesRequest = mockRequest([prefix: " "])
|
||||
HttpServletRequest proxyBeforeForwardedOrPrefixRequest = mockRequest([proxy: "", forward: "", prefix: ""])
|
||||
HttpServletRequest proxyBeforeForwardedOrPrefixSpacesRequest = mockRequest([proxy: " ", forward: " ", prefix: " "])
|
||||
List<HttpServletRequest> requests = [proxyRequest, proxySpacesRequest, forwardedRequest, forwardedSpacesRequest, prefixRequest, prefixSpacesRequest,
|
||||
proxyBeforeForwardedOrPrefixRequest, proxyBeforeForwardedOrPrefixSpacesRequest]
|
||||
|
||||
// Act
|
||||
requests.each { HttpServletRequest request ->
|
||||
|
@ -156,7 +168,9 @@ class WebUtilsTest extends GroovyTestCase {
|
|||
|
||||
HttpServletRequest requestWithProxyHeader = mockRequest([proxy: "any/context/path"])
|
||||
HttpServletRequest requestWithProxyAndForwardHeader = mockRequest([proxy: "any/context/path", forward: "any/other/context/path"])
|
||||
List<HttpServletRequest> requests = [requestWithProxyHeader, requestWithProxyAndForwardHeader]
|
||||
HttpServletRequest requestWithProxyAndForwardAndPrefixHeader = mockRequest([proxy : "any/context/path", forward: "any/other/context/path",
|
||||
prefix: "any/other/prefix/path"])
|
||||
List<HttpServletRequest> requests = [requestWithProxyHeader, requestWithProxyAndForwardHeader, requestWithProxyAndForwardAndPrefixHeader]
|
||||
|
||||
// Act
|
||||
requests.each { HttpServletRequest request ->
|
||||
|
@ -179,7 +193,10 @@ class WebUtilsTest extends GroovyTestCase {
|
|||
HttpServletRequest requestWithProxyHeader = mockRequest([proxy: "some/context/path"])
|
||||
HttpServletRequest requestWithForwardHeader = mockRequest([forward: "some/context/path"])
|
||||
HttpServletRequest requestWithProxyAndForwardHeader = mockRequest([proxy: "some/context/path", forward: "any/other/context/path"])
|
||||
List<HttpServletRequest> requests = [requestWithProxyHeader, requestWithForwardHeader, requestWithProxyAndForwardHeader]
|
||||
HttpServletRequest requestWithProxyAndForwardAndPrefixHeader = mockRequest([proxy: "some/context/path", forward: "any/other/context/path",
|
||||
prefix: "any/other/prefix/path"])
|
||||
List<HttpServletRequest> requests = [requestWithProxyHeader, requestWithForwardHeader, requestWithProxyAndForwardHeader,
|
||||
requestWithProxyAndForwardAndPrefixHeader]
|
||||
|
||||
// Act
|
||||
requests.each { HttpServletRequest request ->
|
||||
|
@ -194,15 +211,19 @@ class WebUtilsTest extends GroovyTestCase {
|
|||
@Test
|
||||
void testGetResourcePathShouldAllowContextPathHeaderIfElementInMultipleWhitelist() throws Exception {
|
||||
// Arrange
|
||||
String multipleWhitelistedPaths = [WHITELISTED_PATH, "/another/path", "/a/third/path"].join(",")
|
||||
String multipleWhitelistedPaths = [WHITELISTED_PATH, "/another/path", "/a/third/path", "/a/prefix/path"].join(",")
|
||||
logger.info("Whitelisted path(s): ${multipleWhitelistedPaths}")
|
||||
|
||||
final List<String> VALID_RESOURCE_PATHS = multipleWhitelistedPaths.split(",").collect { "$it/actualResource" }
|
||||
|
||||
HttpServletRequest requestWithProxyHeader = mockRequest([proxy: "some/context/path"])
|
||||
HttpServletRequest requestWithForwardHeader = mockRequest([forward: "another/path"])
|
||||
HttpServletRequest requestWithPrefixHeader = mockRequest([prefix: "a/prefix/path"])
|
||||
HttpServletRequest requestWithProxyAndForwardHeader = mockRequest([proxy: "a/third/path", forward: "any/other/context/path"])
|
||||
List<HttpServletRequest> requests = [requestWithProxyHeader, requestWithForwardHeader, requestWithProxyAndForwardHeader]
|
||||
HttpServletRequest requestWithProxyAndForwardAndPrefixHeader = mockRequest([proxy : "a/third/path", forward: "any/other/context/path",
|
||||
prefix: "any/other/prefix/path"])
|
||||
List<HttpServletRequest> requests = [requestWithProxyHeader, requestWithForwardHeader, requestWithProxyAndForwardHeader,
|
||||
requestWithPrefixHeader, requestWithProxyAndForwardAndPrefixHeader]
|
||||
|
||||
// Act
|
||||
requests.each { HttpServletRequest request ->
|
||||
|
|
|
@ -191,6 +191,9 @@ can be published to the host.
|
|||
|
||||
The Variable Registry can be configured for the docker image using the `NIFI_VARIABLE_REGISTRY_PROPERTIES` environment variable.
|
||||
|
||||
=======
|
||||
=======
|
||||
**NOTE**: If NiFi is proxied at context paths other than the root path of the proxy, the paths need to be set in the
|
||||
_nifi.web.proxy.context.path_ property, which can be assigned via the environment variable _NIFI\_WEB\_PROXY\_CONTEXT\_PATH_.
|
||||
|
||||
**NOTE**: If mapping the HTTPS port specifying trusted hosts should be provided for the property _nifi.web.proxy.host_. This property can be specified to running instances
|
||||
via specifying an environment variable at container instantiation of _NIFI\_WEB\_PROXY\_HOST_.
|
||||
|
|
|
@ -40,6 +40,7 @@ prop_replace 'nifi.zookeeper.connect.string' "${NIFI_ZK_CONNECT_S
|
|||
prop_replace 'nifi.zookeeper.root.node' "${NIFI_ZK_ROOT_NODE:-/nifi}"
|
||||
prop_replace 'nifi.cluster.flow.election.max.wait.time' "${NIFI_ELECTION_MAX_WAIT:-5 mins}"
|
||||
prop_replace 'nifi.cluster.flow.election.max.candidates' "${NIFI_ELECTION_MAX_CANDIDATES:-}"
|
||||
prop_replace 'nifi.web.proxy.context.path' "${NIFI_WEB_PROXY_CONTEXT_PATH:-}"
|
||||
|
||||
. "${scripts_dir}/update_cluster_state_management.sh"
|
||||
|
||||
|
|
|
@ -40,6 +40,7 @@ prop_replace 'nifi.zookeeper.connect.string' "${NIFI_ZK_CONNECT_S
|
|||
prop_replace 'nifi.zookeeper.root.node' "${NIFI_ZK_ROOT_NODE:-/nifi}"
|
||||
prop_replace 'nifi.cluster.flow.election.max.wait.time' "${NIFI_ELECTION_MAX_WAIT:-5 mins}"
|
||||
prop_replace 'nifi.cluster.flow.election.max.candidates' "${NIFI_ELECTION_MAX_CANDIDATES:-}"
|
||||
prop_replace 'nifi.web.proxy.context.path' "${NIFI_WEB_PROXY_CONTEXT_PATH:-}"
|
||||
|
||||
. "${scripts_dir}/update_cluster_state_management.sh"
|
||||
|
||||
|
|
|
@ -2292,8 +2292,8 @@ host[:port] the expected values need to be configured. This may be required when
|
|||
separated list in _nifi.properties_ using the `nifi.web.proxy.host` property (e.g. `localhost:18443, proxyhost:443`). IPv6 addresses are accepted. Please refer to
|
||||
RFC 5952 Sections link:https://tools.ietf.org/html/rfc5952#section-4[4] and link:https://tools.ietf.org/html/rfc5952#section-6[6] for additional details.
|
||||
|
||||
** NiFi will only accept HTTP requests with a X-ProxyContextPath or X-Forwarded-Context header if the value is whitelisted in the `nifi.web.proxy.context.path` property in
|
||||
_nifi.properties_. This property accepts a comma separated list of expected values. In the event an incoming request has an X-ProxyContextPath or X-Forwarded-Context header value that is not
|
||||
** NiFi will only accept HTTP requests with a X-ProxyContextPath, X-Forwarded-Context, or X-Forwarded-Prefix header if the value is whitelisted in the `nifi.web.proxy.context.path` property in
|
||||
_nifi.properties_. This property accepts a comma separated list of expected values. In the event an incoming request has an X-ProxyContextPath, X-Forwarded-Context, or X-Forwarded-Prefix header value that is not
|
||||
present in the whitelist, the "An unexpected error has occurred" page will be shown and an error will be written to the _nifi-app.log_.
|
||||
|
||||
* Additional configurations at both proxy server and NiFi cluster are required to make NiFi Site-to-Site work behind reverse proxies. See <<site_to_site_reverse_proxy_properties>> for details.
|
||||
|
@ -3009,7 +3009,7 @@ Providing three total network interfaces, including `nifi.web.https.network.int
|
|||
|`nifi.web.proxy.host`|A comma separated list of allowed HTTP Host header values to consider when NiFi is running securely and will be receiving requests to a different host[:port] than it is bound to.
|
||||
For example, when running in a Docker container or behind a proxy (e.g. localhost:18443, proxyhost:443). By default, this value is blank meaning NiFi should only allow requests sent to the
|
||||
host[:port] that NiFi is bound to.
|
||||
|`nifi.web.proxy.context.path`|A comma separated list of allowed HTTP X-ProxyContextPath or X-Forwarded-Context header values to consider. By default, this value is
|
||||
|`nifi.web.proxy.context.path`|A comma separated list of allowed HTTP X-ProxyContextPath, X-Forwarded-Context, or X-Forwarded-Prefix header values to consider. By default, this value is
|
||||
blank meaning all requests containing a proxy context path are rejected. Configuring this property would allow requests where the proxy path is contained in this listing.
|
||||
|====
|
||||
|
||||
|
|
|
@ -107,10 +107,13 @@ public abstract class ApplicationResource {
|
|||
public static final String PROXY_CONTEXT_PATH_HTTP_HEADER = "X-ProxyContextPath";
|
||||
|
||||
public static final String FORWARDED_PROTO_HTTP_HEADER = "X-Forwarded-Proto";
|
||||
public static final String FORWARDED_HOST_HTTP_HEADER = "X-Forwarded-Server";
|
||||
public static final String FORWARDED_HOST_HTTP_HEADER = "X-Forwarded-Host";
|
||||
public static final String FORWARDED_PORT_HTTP_HEADER = "X-Forwarded-Port";
|
||||
public static final String FORWARDED_CONTEXT_HTTP_HEADER = "X-Forwarded-Context";
|
||||
|
||||
// Traefik-specific headers
|
||||
public static final String FORWARDED_PREFIX_HTTP_HEADER = "X-Forwarded-Prefix";
|
||||
|
||||
protected static final String NON_GUARANTEED_ENDPOINT = "Note: This endpoint is subject to change as NiFi and it's REST API evolve.";
|
||||
|
||||
private static final Logger logger = LoggerFactory.getLogger(ApplicationResource.class);
|
||||
|
@ -151,8 +154,11 @@ public abstract class ApplicationResource {
|
|||
// check for proxy settings
|
||||
|
||||
final String scheme = getFirstHeaderValue(PROXY_SCHEME_HTTP_HEADER, FORWARDED_PROTO_HTTP_HEADER);
|
||||
final String host = getFirstHeaderValue(PROXY_HOST_HTTP_HEADER, FORWARDED_HOST_HTTP_HEADER);
|
||||
final String port = getFirstHeaderValue(PROXY_PORT_HTTP_HEADER, FORWARDED_PORT_HTTP_HEADER);
|
||||
final String hostHeaderValue = getFirstHeaderValue(PROXY_HOST_HTTP_HEADER, FORWARDED_HOST_HTTP_HEADER);
|
||||
final String portHeaderValue = getFirstHeaderValue(PROXY_PORT_HTTP_HEADER, FORWARDED_PORT_HTTP_HEADER);
|
||||
|
||||
final String host = determineProxiedHost(hostHeaderValue);
|
||||
final String port = determineProxiedPort(hostHeaderValue, portHeaderValue);
|
||||
|
||||
// Catch header poisoning
|
||||
String whitelistedContextPaths = properties.getWhitelistedContextPaths();
|
||||
|
@ -188,6 +194,44 @@ public abstract class ApplicationResource {
|
|||
return uri;
|
||||
}
|
||||
|
||||
private String determineProxiedHost(String hostHeaderValue) {
|
||||
final String host;
|
||||
// check for a port in the proxied host header
|
||||
String[] hostSplits = hostHeaderValue == null ? new String[] {} : hostHeaderValue.split(":");
|
||||
if (hostSplits.length >= 1 && hostSplits.length <= 2) {
|
||||
// zero or one occurrence of ':', this is an IPv4 address
|
||||
// strip off the port by reassigning host the 0th split
|
||||
host = hostSplits[0];
|
||||
} else if (hostSplits.length == 0) {
|
||||
// hostHeaderValue passed in was null, no splits
|
||||
host = null;
|
||||
} else {
|
||||
// hostHeaderValue has more than one occurrence of ":", IPv6 address
|
||||
host = hostHeaderValue;
|
||||
}
|
||||
return host;
|
||||
}
|
||||
|
||||
private String determineProxiedPort(String hostHeaderValue, String portHeaderValue) {
|
||||
final String port;
|
||||
// check for a port in the proxied host header
|
||||
String[] hostSplits = hostHeaderValue == null ? new String[] {} : hostHeaderValue.split(":");
|
||||
// determine the proxied port
|
||||
final String portFromHostHeader;
|
||||
if (hostSplits.length == 2) {
|
||||
// if the port is specified in the proxied host header, it will be overridden by the
|
||||
// port specified in X-ProxyPort or X-Forwarded-Port
|
||||
portFromHostHeader = hostSplits[1];
|
||||
} else {
|
||||
portFromHostHeader = null;
|
||||
}
|
||||
if (StringUtils.isNotBlank(portFromHostHeader) && StringUtils.isNotBlank(portHeaderValue)) {
|
||||
logger.warn(String.format("The proxied host header contained a port, but was overridden by the proxied port header"));
|
||||
}
|
||||
port = StringUtils.isNotBlank(portHeaderValue) ? portHeaderValue : (StringUtils.isNotBlank(portFromHostHeader) ? portFromHostHeader : null);
|
||||
return port;
|
||||
}
|
||||
|
||||
/**
|
||||
* Edit the response headers to indicating no caching.
|
||||
*
|
||||
|
|
|
@ -36,6 +36,9 @@ import javax.ws.rs.core.UriInfo
|
|||
class ApplicationResourceTest extends GroovyTestCase {
|
||||
private static final Logger logger = LoggerFactory.getLogger(ApplicationResourceTest.class)
|
||||
|
||||
public static final String PROXY_HOST_HTTP_HEADER = "X-ProxyHost"
|
||||
public static final String FORWARDED_HOST_HTTP_HEADER = "X-Forwarded-Host"
|
||||
|
||||
static final String PROXY_SCHEME_HTTP_HEADER = "X-ProxyScheme"
|
||||
static final String PROXY_PORT_HTTP_HEADER = "X-ProxyPort"
|
||||
static final String PROXY_CONTEXT_PATH_HTTP_HEADER = "X-ProxyContextPath"
|
||||
|
@ -43,6 +46,7 @@ class ApplicationResourceTest extends GroovyTestCase {
|
|||
static final String FORWARDED_PROTO_HTTP_HEADER = "X-Forwarded-Proto"
|
||||
static final String FORWARDED_PORT_HTTP_HEADER = "X-Forwarded-Port"
|
||||
static final String FORWARDED_CONTEXT_HTTP_HEADER = "X-Forwarded-Context"
|
||||
static final String FORWARDED_PREFIX_HTTP_HEADER = "X-Forwarded-Prefix"
|
||||
|
||||
static final String PROXY_CONTEXT_PATH_PROP = NiFiProperties.WEB_PROXY_CONTEXT_PATH
|
||||
static final String WHITELISTED_PATH = "/some/context/path"
|
||||
|
@ -73,21 +77,29 @@ class ApplicationResourceTest extends GroovyTestCase {
|
|||
}
|
||||
|
||||
private ApplicationResource buildApplicationResource() {
|
||||
buildApplicationResource([FORWARDED_PREFIX_HTTP_HEADER, FORWARDED_CONTEXT_HTTP_HEADER, PROXY_CONTEXT_PATH_HTTP_HEADER])
|
||||
}
|
||||
|
||||
private ApplicationResource buildApplicationResource(List proxyHeaders) {
|
||||
ApplicationResource resource = new MockApplicationResource()
|
||||
String headerValue = ""
|
||||
HttpServletRequest mockRequest = [getHeader: { String k ->
|
||||
logger.mock("Request.getHeader($k)")
|
||||
if ([FORWARDED_CONTEXT_HTTP_HEADER, PROXY_CONTEXT_PATH_HTTP_HEADER].contains(k)) {
|
||||
WHITELISTED_PATH
|
||||
if (proxyHeaders.contains(k)) {
|
||||
headerValue = WHITELISTED_PATH
|
||||
} else if ([FORWARDED_PORT_HTTP_HEADER, PROXY_PORT_HTTP_HEADER].contains(k)) {
|
||||
"8081"
|
||||
headerValue = "8081"
|
||||
} else if ([FORWARDED_PROTO_HTTP_HEADER, PROXY_SCHEME_HTTP_HEADER].contains(k)) {
|
||||
"https"
|
||||
headerValue = "https"
|
||||
} else if ([PROXY_HOST_HTTP_HEADER, FORWARDED_HOST_HTTP_HEADER].contains(k)) {
|
||||
headerValue = "nifi.apache.org:8081"
|
||||
} else {
|
||||
"nifi.apache.org"
|
||||
headerValue = ""
|
||||
}
|
||||
logger.mock("Request.getHeader($k) -> \"$headerValue\"")
|
||||
headerValue
|
||||
}, getContextPath: { ->
|
||||
logger.mock("Request.getContextPath()")
|
||||
""
|
||||
logger.mock("Request.getContextPath() -> \"$headerValue\"")
|
||||
headerValue
|
||||
}] as HttpServletRequest
|
||||
|
||||
UriInfo mockUriInfo = [getBaseUriBuilder: { ->
|
||||
|
@ -155,27 +167,100 @@ class ApplicationResourceTest extends GroovyTestCase {
|
|||
@Test
|
||||
void testGenerateUriShouldBlockForwardedContextHeaderIfNotInWhitelist() throws Exception {
|
||||
// Arrange
|
||||
ApplicationResource resource = buildApplicationResource([FORWARDED_CONTEXT_HTTP_HEADER])
|
||||
logger.info("Whitelisted path(s): ")
|
||||
|
||||
// Act
|
||||
def msg = shouldFail(UriBuilderException) {
|
||||
String generatedUri = resource.generateResourceUri('actualResource')
|
||||
logger.unexpected("Generated URI: ${generatedUri}")
|
||||
}
|
||||
|
||||
// Assert
|
||||
logger.expected(msg)
|
||||
assert msg =~ "The provided context path \\[.*\\] was not whitelisted \\[\\]"
|
||||
}
|
||||
|
||||
@Test
|
||||
void testGenerateUriShouldBlockForwardedPrefixHeaderIfNotInWhitelist() throws Exception {
|
||||
// Arrange
|
||||
ApplicationResource resource = buildApplicationResource([FORWARDED_PREFIX_HTTP_HEADER])
|
||||
logger.info("Whitelisted path(s): ")
|
||||
|
||||
// Act
|
||||
def msg = shouldFail(UriBuilderException) {
|
||||
String generatedUri = resource.generateResourceUri('actualResource')
|
||||
logger.unexpected("Generated URI: ${generatedUri}")
|
||||
}
|
||||
|
||||
// Assert
|
||||
logger.expected(msg)
|
||||
assert msg =~ "The provided context path \\[.*\\] was not whitelisted \\[\\]"
|
||||
}
|
||||
|
||||
@Test
|
||||
void testGenerateUriShouldAllowForwardedContextHeaderIfInWhitelist() throws Exception {
|
||||
// Arrange
|
||||
ApplicationResource resource = buildApplicationResource([FORWARDED_CONTEXT_HTTP_HEADER])
|
||||
logger.info("Whitelisted path(s): ${WHITELISTED_PATH}")
|
||||
NiFiProperties niFiProperties = new StandardNiFiProperties([(PROXY_CONTEXT_PATH_PROP): WHITELISTED_PATH] as Properties)
|
||||
resource.properties = niFiProperties
|
||||
|
||||
// Act
|
||||
String generatedUri = resource.generateResourceUri('actualResource')
|
||||
logger.info("Generated URI: ${generatedUri}")
|
||||
|
||||
// Assert
|
||||
assert generatedUri == "https://nifi.apache.org:8081${WHITELISTED_PATH}/actualResource"
|
||||
}
|
||||
|
||||
@Test
|
||||
void testGenerateUriShouldAllowForwardedPrefixHeaderIfInWhitelist() throws Exception {
|
||||
// Arrange
|
||||
ApplicationResource resource = buildApplicationResource([FORWARDED_PREFIX_HTTP_HEADER])
|
||||
logger.info("Whitelisted path(s): ${WHITELISTED_PATH}")
|
||||
NiFiProperties niFiProperties = new StandardNiFiProperties([(PROXY_CONTEXT_PATH_PROP): WHITELISTED_PATH] as Properties)
|
||||
resource.properties = niFiProperties
|
||||
|
||||
// Act
|
||||
String generatedUri = resource.generateResourceUri('actualResource')
|
||||
logger.info("Generated URI: ${generatedUri}")
|
||||
|
||||
// Assert
|
||||
assert generatedUri == "https://nifi.apache.org:8081${WHITELISTED_PATH}/actualResource"
|
||||
}
|
||||
|
||||
@Test
|
||||
void testGenerateUriShouldAllowForwardedContextHeaderIfElementInMultipleWhitelist() throws Exception {
|
||||
// Arrange
|
||||
ApplicationResource resource = buildApplicationResource([FORWARDED_CONTEXT_HTTP_HEADER])
|
||||
String multipleWhitelistedPaths = [WHITELISTED_PATH, "another/path", "a/third/path"].join(",")
|
||||
logger.info("Whitelisted path(s): ${multipleWhitelistedPaths}")
|
||||
NiFiProperties niFiProperties = new StandardNiFiProperties([(PROXY_CONTEXT_PATH_PROP): multipleWhitelistedPaths] as Properties)
|
||||
resource.properties = niFiProperties
|
||||
|
||||
// Act
|
||||
String generatedUri = resource.generateResourceUri('actualResource')
|
||||
logger.info("Generated URI: ${generatedUri}")
|
||||
|
||||
// Assert
|
||||
assert generatedUri == "https://nifi.apache.org:8081${WHITELISTED_PATH}/actualResource"
|
||||
}
|
||||
|
||||
@Test
|
||||
void testGenerateUriShouldAllowForwardedPrefixHeaderIfElementInMultipleWhitelist() throws Exception {
|
||||
// Arrange
|
||||
ApplicationResource resource = buildApplicationResource([FORWARDED_PREFIX_HTTP_HEADER])
|
||||
String multipleWhitelistedPaths = [WHITELISTED_PATH, "another/path", "a/third/path"].join(",")
|
||||
logger.info("Whitelisted path(s): ${multipleWhitelistedPaths}")
|
||||
NiFiProperties niFiProperties = new StandardNiFiProperties([(PROXY_CONTEXT_PATH_PROP): multipleWhitelistedPaths] as Properties)
|
||||
resource.properties = niFiProperties
|
||||
|
||||
// Act
|
||||
String generatedUri = resource.generateResourceUri('actualResource')
|
||||
logger.info("Generated URI: ${generatedUri}")
|
||||
|
||||
// Assert
|
||||
assert generatedUri == "https://nifi.apache.org:8081${WHITELISTED_PATH}/actualResource"
|
||||
}
|
||||
}
|
||||
|
|
|
@ -57,6 +57,7 @@ public class ContentViewerController extends HttpServlet {
|
|||
|
||||
private static final String PROXY_CONTEXT_PATH_HTTP_HEADER = "X-ProxyContextPath";
|
||||
private static final String FORWARDED_CONTEXT_HTTP_HEADER = "X-Forwarded-Context";
|
||||
private static final String FORWARDED_PREFIX_HTTP_HEADER = "X-Forwarded-Prefix";
|
||||
|
||||
/**
|
||||
* Gets the content and defers to registered viewers to generate the markup.
|
||||
|
@ -311,7 +312,7 @@ public class ContentViewerController extends HttpServlet {
|
|||
refUriBuilder.scheme(request.getScheme());
|
||||
|
||||
// If there is path context from a proxy, remove it since this request will be used inside the cluster
|
||||
final String proxyContextPath = getFirstHeaderValue(request, PROXY_CONTEXT_PATH_HTTP_HEADER, FORWARDED_CONTEXT_HTTP_HEADER);
|
||||
final String proxyContextPath = getFirstHeaderValue(request, PROXY_CONTEXT_PATH_HTTP_HEADER, FORWARDED_CONTEXT_HTTP_HEADER, FORWARDED_PREFIX_HTTP_HEADER);
|
||||
if (StringUtils.isNotBlank(proxyContextPath)) {
|
||||
refUriBuilder.replacePath(StringUtils.substringAfter(UriBuilder.fromUri(ref).build().getPath(), proxyContextPath));
|
||||
}
|
||||
|
|
Loading…
Reference in New Issue