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:
Jeff Storck 2018-10-29 13:29:28 -04:00 committed by Kevin Doran
parent 7c9617a0ec
commit cc47a8c0e1
No known key found for this signature in database
GPG Key ID: 5621A6244B77AC02
9 changed files with 195 additions and 34 deletions

View File

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

View File

@ -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 ->

View File

@ -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_.

View File

@ -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"

View File

@ -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"

View File

@ -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.
|====

View File

@ -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.
*

View File

@ -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"
}
}

View File

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