You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@knox.apache.org by mo...@apache.org on 2019/08/06 18:28:35 UTC

[knox] branch master updated: KNOX-1694 - Prevent port mapped topologies from being exposed to gateway port (#126)

This is an automated email from the ASF dual-hosted git repository.

more pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/knox.git


The following commit(s) were added to refs/heads/master by this push:
     new 55adc6a  KNOX-1694 - Prevent port mapped topologies from being exposed to gateway port (#126)
55adc6a is described below

commit 55adc6a9cd59525f41159c5499ab214749887be9
Author: Sandeep Moré <mo...@apache.org>
AuthorDate: Tue Aug 6 14:28:30 2019 -0400

    KNOX-1694 - Prevent port mapped topologies from being exposed to gateway port (#126)
---
 .../org/apache/knox/gateway/GatewayMessages.java   |  12 ++
 .../org/apache/knox/gateway/GatewayServer.java     |  63 ++++---
 .../gateway/filter/PortMappingHelperHandler.java   |  79 ++++-----
 .../knox/gateway/GatewayDefaultTopologyTest.java   |  75 ++++++++
 .../GatewayPortMappingDisableFeatureTest.java      | 163 +-----------------
 .../knox/gateway/GatewayPortMappingFailTest.java   |  50 +-----
 .../knox/gateway/GatewayPortMappingFuncTest.java   | 179 +------------------
 .../org/apache/knox/gateway/PortMappingHelper.java | 189 +++++++++++++++++++++
 8 files changed, 365 insertions(+), 445 deletions(-)

diff --git a/gateway-server/src/main/java/org/apache/knox/gateway/GatewayMessages.java b/gateway-server/src/main/java/org/apache/knox/gateway/GatewayMessages.java
index 1c52d56..aa019da 100644
--- a/gateway-server/src/main/java/org/apache/knox/gateway/GatewayMessages.java
+++ b/gateway-server/src/main/java/org/apache/knox/gateway/GatewayMessages.java
@@ -449,6 +449,14 @@ public interface GatewayMessages {
            text = "Topology port mapping feature enabled: {0}")
   void gatewayTopologyPortMappingEnabled(boolean enabled);
 
+  @Message(level = MessageLevel.ERROR,
+           text = "No topology mapped to port: {0}")
+  void noTopologyMappedToPort(int port);
+
+  @Message(level = MessageLevel.ERROR,
+           text = "Could not find topology {0} specified in port mapping config")
+  void noMappedTopologyFound(String topology);
+
   @Message(level = MessageLevel.DEBUG,
            text = "Creating a connector for topology {0} listening on port {1}.")
   void createJettyConnector(String topology, int port);
@@ -490,6 +498,10 @@ public interface GatewayMessages {
                    + "Gateway restart will be required if in the future \"{0}\" topology is added.")
   void topologyPortMappingCannotFindTopology(String topology, int port);
 
+  @Message(level = MessageLevel.ERROR,
+           text = "Port mapped topology {0} cannot be configured as default topology")
+  void defaultTopologyInPortmappedTopology(String topology);
+
 
   @Message( level = MessageLevel.WARN, text = "There is no registry client defined for remote configuration monitoring." )
   void missingClientConfigurationForRemoteMonitoring();
diff --git a/gateway-server/src/main/java/org/apache/knox/gateway/GatewayServer.java b/gateway-server/src/main/java/org/apache/knox/gateway/GatewayServer.java
index 5462aa1..6e8777e 100644
--- a/gateway-server/src/main/java/org/apache/knox/gateway/GatewayServer.java
+++ b/gateway-server/src/main/java/org/apache/knox/gateway/GatewayServer.java
@@ -34,11 +34,10 @@ import org.apache.knox.gateway.deploy.DeploymentException;
 import org.apache.knox.gateway.deploy.DeploymentFactory;
 import org.apache.knox.gateway.filter.CorrelationHandler;
 import org.apache.knox.gateway.filter.PortMappingHelperHandler;
-import org.apache.knox.gateway.filter.RequestUpdateHandler;
 import org.apache.knox.gateway.i18n.messages.MessagesFactory;
 import org.apache.knox.gateway.i18n.resources.ResourcesFactory;
-import org.apache.knox.gateway.services.ServiceType;
 import org.apache.knox.gateway.services.GatewayServices;
+import org.apache.knox.gateway.services.ServiceType;
 import org.apache.knox.gateway.services.registry.ServiceRegistry;
 import org.apache.knox.gateway.services.security.AliasServiceException;
 import org.apache.knox.gateway.services.security.SSLService;
@@ -56,13 +55,13 @@ import org.apache.knox.gateway.websockets.GatewayWebsocketHandler;
 import org.apache.log4j.PropertyConfigurator;
 import org.eclipse.jetty.server.ConnectionFactory;
 import org.eclipse.jetty.server.Connector;
+import org.eclipse.jetty.server.Handler;
 import org.eclipse.jetty.server.HttpConfiguration;
 import org.eclipse.jetty.server.HttpConnectionFactory;
 import org.eclipse.jetty.server.NetworkConnector;
 import org.eclipse.jetty.server.SecureRequestCustomizer;
 import org.eclipse.jetty.server.Server;
 import org.eclipse.jetty.server.ServerConnector;
-import org.eclipse.jetty.server.handler.ContextHandler;
 import org.eclipse.jetty.server.handler.ContextHandlerCollection;
 import org.eclipse.jetty.server.handler.HandlerCollection;
 import org.eclipse.jetty.server.handler.RequestLogHandler;
@@ -424,7 +423,6 @@ public class GatewayServer {
 
     HttpConfiguration httpConfig = new HttpConfiguration();
     httpConfig.setRequestHeaderSize( config.getHttpServerRequestHeaderBuffer() );
-    //httpConfig.setRequestBufferSize( config.getHttpServerRequestBuffer() );
     httpConfig.setResponseHeaderSize( config.getHttpServerResponseHeaderBuffer() );
     httpConfig.setOutputBufferSize( config.getHttpServerResponseBuffer() );
 
@@ -459,6 +457,15 @@ public class GatewayServer {
       final GatewayServices services,
       final ContextHandlerCollection contexts,
       final Map<String, Integer> topologyPortMap) {
+
+    final Map<String, Handler> contextToHandlerMap = new HashMap<>();
+    if(contexts.getHandlers() != null) {
+      Arrays.asList(contexts.getHandlers()).stream()
+          .filter(h -> h instanceof WebAppContext)
+          .forEach(h -> contextToHandlerMap
+              .put(((WebAppContext) h).getContextPath(), h));
+    }
+
     HandlerCollection handlers = new HandlerCollection();
     RequestLogHandler logHandler = new RequestLogHandler();
 
@@ -485,21 +492,26 @@ public class GatewayServer {
 
     if (config.isGatewayPortMappingEnabled()) {
 
-      for (final Map.Entry<String, Integer> entry : topologyPortMap
-          .entrySet()) {
-        log.createJettyHandler(entry.getKey());
-        final ContextHandler topologyContextHandler = new ContextHandler();
-
-        final RequestUpdateHandler updateHandler = new RequestUpdateHandler(
-            config, entry.getKey(), services);
-
-        topologyContextHandler.setHandler(updateHandler);
-        topologyContextHandler.setVirtualHosts(
-            new String[] { "@" + entry.getKey().toLowerCase(Locale.ROOT) });
-
-        handlers.addHandler(topologyContextHandler);
-      }
-
+      /* Do the virtual host bindings for all the defined topology port mapped
+      *  contexts except for the one that has gateway port to prevent issues
+      *  with context deployment */
+      topologyPortMap
+          .entrySet()
+          .stream()
+          .filter(e -> !e.getValue().equals(config.getGatewayPort()))
+          .forEach( entry ->  {
+            log.createJettyHandler(entry.getKey());
+            final Handler context = contextToHandlerMap
+                .get("/" + config.getGatewayPath() + "/" + entry.getKey());
+
+            if(context !=  null) {
+              ((WebAppContext) context).setVirtualHosts(
+                  new String[] { "@" + entry.getKey().toLowerCase(Locale.ROOT) });
+            } else {
+              // no topology found for mapping entry.getKey()
+              log.noMappedTopologyFound(entry.getKey());
+            }
+          });
     }
 
     handlers.addHandler(logHandler);
@@ -565,7 +577,20 @@ public class GatewayServer {
               port, topologyName));
         }
       }
+    }
 
+    /*
+     * Check for a case where default topology is also in port mapping list.
+     * This is not a valid scenario, you cannot have same topology listening on
+     * multiple ports.
+     */
+    if (config.getDefaultTopologyName() != null && config
+        .getGatewayPortMappings()
+        .containsKey(config.getDefaultTopologyName())) {
+      log.defaultTopologyInPortmappedTopology(config.getDefaultTopologyName());
+      throw new IOException(String.format(Locale.ROOT,
+          "Default topology cannot be in port mapping list, please remove %s from port mapping list or don't make it a default topology.",
+          config.getDefaultTopologyName()));
     }
 
   }
diff --git a/gateway-server/src/main/java/org/apache/knox/gateway/filter/PortMappingHelperHandler.java b/gateway-server/src/main/java/org/apache/knox/gateway/filter/PortMappingHelperHandler.java
index e776596..f917f8f 100644
--- a/gateway-server/src/main/java/org/apache/knox/gateway/filter/PortMappingHelperHandler.java
+++ b/gateway-server/src/main/java/org/apache/knox/gateway/filter/PortMappingHelperHandler.java
@@ -16,7 +16,6 @@
  */
 package org.apache.knox.gateway.filter;
 
-import org.apache.commons.lang3.StringUtils;
 import org.apache.knox.gateway.GatewayMessages;
 import org.apache.knox.gateway.config.GatewayConfig;
 import org.apache.knox.gateway.i18n.messages.MessagesFactory;
@@ -47,31 +46,28 @@ public class PortMappingHelperHandler extends HandlerWrapper {
   private static final GatewayMessages LOG = MessagesFactory
       .get(GatewayMessages.class);
 
-  final GatewayConfig config;
+  private final GatewayConfig config;
+  private final Map<String, Integer> topologyPortMap;
 
   private String defaultTopologyRedirectContext;
 
   public PortMappingHelperHandler(final GatewayConfig config) {
 
     this.config = config;
+    this.topologyPortMap = config.getGatewayPortMappings();
+
     //Set up context for default topology feature.
     String defaultTopologyName = config.getDefaultTopologyName();
 
     // default topology feature can also be enabled using port mapping feature
     // config e.g. gateway.port.mapping.{defaultTopologyName}
-
     if(defaultTopologyName == null && config.getGatewayPortMappings().values().contains(config.getGatewayPort())) {
-
       for(final Map.Entry<String, Integer> entry: config.getGatewayPortMappings().entrySet()) {
-
-        if(entry.getValue() == config.getGatewayPort()) {
+        if(entry.getValue().intValue() == config.getGatewayPort()) {
           defaultTopologyRedirectContext = "/" + config.getGatewayPath() + "/" + entry.getKey();
           break;
         }
-
       }
-
-
     }
 
     if (defaultTopologyName != null) {
@@ -85,7 +81,6 @@ public class PortMappingHelperHandler extends HandlerWrapper {
       LOG.defaultTopologySetup(defaultTopologyName,
           defaultTopologyRedirectContext);
     }
-
   }
 
   @Override
@@ -95,60 +90,54 @@ public class PortMappingHelperHandler extends HandlerWrapper {
 
     String newTarget = target;
     String baseURI = baseRequest.getRequestURI();
+    final int port = baseRequest.getLocalPort();
+    RequestUpdateHandler.ForwardedRequest newRequest;
 
     // If Port Mapping feature enabled
-    if (config.isGatewayPortMappingEnabled()) {
-      int targetIndex;
-      String context = "";
-
-      // extract the gateway specific part i.e. {/gatewayName/}
-      String originalContextPath = "";
-      targetIndex = StringUtils.ordinalIndexOf(target, "/", 2);
-
-      // Match found e.g. /{string}/
-      if (targetIndex > 0) {
-        originalContextPath = target.substring(0, targetIndex + 1);
-      } else if (targetIndex == -1) {
-        targetIndex = StringUtils.ordinalIndexOf(target, "/", 1);
-        // For cases "/" and "/hive"
-        if(targetIndex == 0) {
-          originalContextPath = target;
-        }
+    if (config.isGatewayPortMappingEnabled() && topologyPortMap.containsValue(port)) {
+
+      final String topologyName = topologyPortMap.entrySet()
+          .stream()
+          .filter(e -> e.getValue().equals(port))
+          .map(Map.Entry::getKey)
+          .findFirst()
+          .orElse(null);
+      final String gatewayTopologyContext =
+          "/" + config.getGatewayPath() + "/" + topologyName;
+
+      if(!target.contains(gatewayTopologyContext)) {
+        newTarget = gatewayTopologyContext + target;
       }
 
-      // Match "/{gatewayName}/{topologyName/foo" or "/".
-      // There could be a case where content is served from the root
-      // i.e. https://host:port/
+      // if the request does not contain /{gatewayName}/{topologyName}
+      if(!baseRequest.getRequestURI().contains(gatewayTopologyContext)) {
+        newRequest = new RequestUpdateHandler.ForwardedRequest(
+            request, gatewayTopologyContext, newTarget);
 
-      if (!baseURI.startsWith(originalContextPath)) {
-        final int index = StringUtils.ordinalIndexOf(baseURI, "/", 3);
-        if (index > 0) {
-          context = baseURI.substring(0, index);
-        }
-      }
+        baseRequest.setPathInfo(gatewayTopologyContext + baseRequest.getPathInfo());
+        baseRequest.setURIPathQuery(gatewayTopologyContext + baseRequest.getRequestURI());
 
-      if(!StringUtils.isBlank(context)) {
-        LOG.topologyPortMappingAddContext(target, context + target);
+        LOG.topologyPortMappingUpdateRequest(target, newTarget);
+        super.handle(newTarget, baseRequest, newRequest, response);
+      }
+      else {
+        super.handle(newTarget, baseRequest, request, response);
       }
-      // Move on to the next handler in chain with updated path
-      newTarget = context + target;
     }
-
     //Backwards compatibility for default topology feature
-    if (defaultTopologyRedirectContext != null && !baseURI
+    else if (defaultTopologyRedirectContext != null && !baseURI
         .startsWith("/" + config.getGatewayPath())) {
       newTarget = defaultTopologyRedirectContext + target;
 
-      final RequestUpdateHandler.ForwardedRequest newRequest = new RequestUpdateHandler.ForwardedRequest(
+      newRequest = new RequestUpdateHandler.ForwardedRequest(
           request, defaultTopologyRedirectContext, newTarget);
 
       LOG.defaultTopologyForward(target, newTarget);
       super.handle(newTarget, baseRequest, newRequest, response);
 
     } else {
-
+      /* case where topology port mapping is not enabled (or improperly configured) and no default topology is configured  */
       super.handle(newTarget, baseRequest, request, response);
     }
-
   }
 }
diff --git a/gateway-test/src/test/java/org/apache/knox/gateway/GatewayDefaultTopologyTest.java b/gateway-test/src/test/java/org/apache/knox/gateway/GatewayDefaultTopologyTest.java
new file mode 100644
index 0000000..be43dd2
--- /dev/null
+++ b/gateway-test/src/test/java/org/apache/knox/gateway/GatewayDefaultTopologyTest.java
@@ -0,0 +1,75 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with this
+ * work for additional information regarding copyright ownership. The ASF
+ * licenses this file to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+ * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+ * License for the specific language governing permissions and limitations under
+ * the License.
+ */
+package org.apache.knox.gateway;
+
+import org.apache.knox.test.TestUtils;
+import org.apache.knox.test.category.ReleaseTest;
+import org.junit.AfterClass;
+import org.junit.BeforeClass;
+import org.junit.Test;
+import org.junit.experimental.categories.Category;
+
+import java.io.IOException;
+
+import static org.apache.knox.test.TestUtils.LOG_ENTER;
+import static org.apache.knox.test.TestUtils.LOG_EXIT;
+
+/**
+ * Test default topology feature
+ */
+@Category(ReleaseTest.class)
+public class GatewayDefaultTopologyTest extends PortMappingHelper {
+
+  public GatewayDefaultTopologyTest() {
+    super();
+  }
+
+  @BeforeClass
+  public static void setup() throws Exception {
+    /* setup test with eerie as a default topology */
+    init("eerie",false);
+  }
+
+  @AfterClass
+  public static void cleanup() throws Exception {
+    LOG_ENTER();
+    driver.cleanup();
+    driver.reset();
+    masterServer.reset();
+    LOG_EXIT();
+  }
+
+  /*
+   * Test the standard case:
+   * http://localhost:{topologyPort}/gateway/eerie/webhdfs/v1
+   */
+  @Test(timeout = TestUtils.MEDIUM_TIMEOUT )
+  public void testBasicListOperation() throws IOException {
+    test("http://localhost:" + driver.getGatewayPort() + "/gateway/eerie" + "/webhdfs" );
+  }
+
+  /*
+   * Test the Default Topology Feature, activated by property
+   * "default.app.topology.name"
+   *
+   * http://localhost:{eeriePort}/gateway/eerie/webhdfs/v1
+   */
+  @Test(timeout = TestUtils.MEDIUM_TIMEOUT )
+  public void testDefaultTopologyFeature() throws IOException {
+    test("http://localhost:" + driver.getGatewayPort() + "/webhdfs" );
+  }
+}
diff --git a/gateway-test/src/test/java/org/apache/knox/gateway/GatewayPortMappingDisableFeatureTest.java b/gateway-test/src/test/java/org/apache/knox/gateway/GatewayPortMappingDisableFeatureTest.java
index 4d9e767..5d50700 100644
--- a/gateway-test/src/test/java/org/apache/knox/gateway/GatewayPortMappingDisableFeatureTest.java
+++ b/gateway-test/src/test/java/org/apache/knox/gateway/GatewayPortMappingDisableFeatureTest.java
@@ -16,12 +16,8 @@
  */
 package org.apache.knox.gateway;
 
-import com.mycila.xmltool.XMLDoc;
-import com.mycila.xmltool.XMLTag;
 import org.apache.knox.test.TestUtils;
 import org.apache.knox.test.category.ReleaseTest;
-import org.apache.knox.test.mock.MockServer;
-import org.apache.http.HttpStatus;
 import org.junit.After;
 import org.junit.Before;
 import org.junit.Rule;
@@ -33,31 +29,15 @@ import java.io.IOException;
 import java.net.ConnectException;
 import java.util.concurrent.ConcurrentHashMap;
 
-import static io.restassured.RestAssured.given;
 import static org.apache.knox.test.TestUtils.LOG_ENTER;
 import static org.apache.knox.test.TestUtils.LOG_EXIT;
-import static org.hamcrest.CoreMatchers.is;
 
 /**
  * Test that the Gateway Topology Port Mapping feature is disabled properly.
  *
  */
 @Category(ReleaseTest.class)
-public class GatewayPortMappingDisableFeatureTest {
-
-  // Specifies if the test requests should go through the gateway or directly to the services.
-  // This is frequently used to verify the behavior of the test both with and without the gateway.
-  private static final boolean USE_GATEWAY = true;
-
-  // Specifies if the test requests should be sent to mock services or the real services.
-  // This is frequently used to verify the behavior of the test both with and without mock services.
-  private static final boolean USE_MOCK_SERVICES = true;
-
-  private static GatewayTestDriver driver = new GatewayTestDriver();
-
-  private static MockServer masterServer;
-
-  private int eeriePort;
+public class GatewayPortMappingDisableFeatureTest extends PortMappingHelper {
 
   @Rule
   public ExpectedException exception = ExpectedException.none();
@@ -66,41 +46,13 @@ public class GatewayPortMappingDisableFeatureTest {
     super();
   }
 
-  /**
-   * Creates a deployment of a gateway instance that all test methods will share.  This method also creates a
-   * registry of sorts for all of the services that will be used by the test methods.
-   * The createTopology method is used to create the topology file that would normally be read from disk.
-   * The driver.setupGateway invocation is where the creation of GATEWAY_HOME occurs.
-   * <p>
-   * This would normally be done once for this suite but the failure tests start affecting each other depending
-   * on the state the last 'active' url
-   *
-   * @throws Exception Thrown if any failure occurs.
-   */
   @Before
   public void setup() throws Exception {
-    LOG_ENTER();
-
     eeriePort = getAvailablePort(1240, 49151);
-
     ConcurrentHashMap<String, Integer> topologyPortMapping = new ConcurrentHashMap<>();
     topologyPortMapping.put("eerie", eeriePort);
-
-    masterServer = new MockServer("master", true);
-    GatewayTestConfig config = new GatewayTestConfig();
-    config.setGatewayPath("gateway");
-    config.setTopologyPortMapping(topologyPortMapping);
-    // disable the feature
-    config.setGatewayPortMappingEnabled(false);
-
-    driver.setResourceBase(WebHdfsHaFuncTest.class);
-    driver.setupLdap(0);
-
-    driver.setupService("WEBHDFS", "http://vm.local:50070/webhdfs", "/eerie/webhdfs", USE_MOCK_SERVICES);
-
-    driver.setupGateway(config, "eerie", createTopology("WEBHDFS"), USE_GATEWAY);
-
-    LOG_EXIT();
+    /* define port mappings but feature disabled */
+    init(null, topologyPortMapping, false);
   }
 
   @After
@@ -117,9 +69,7 @@ public class GatewayPortMappingDisableFeatureTest {
    */
   @Test(timeout = TestUtils.MEDIUM_TIMEOUT )
   public void testBasicListOperation() throws IOException {
-    LOG_ENTER();
     test(driver.getUrl("WEBHDFS") );
-    LOG_EXIT();
   }
 
   /*
@@ -127,115 +77,8 @@ public class GatewayPortMappingDisableFeatureTest {
    */
   @Test(timeout = TestUtils.MEDIUM_TIMEOUT )
   public void testMultiPortFailOperation() throws IOException {
-    LOG_ENTER();
     exception.expect(ConnectException.class);
     exception.expectMessage("Connection refused");
-
     test("http://localhost:" + eeriePort + "/webhdfs" );
-    LOG_EXIT();
-  }
-
-  private void test (final String url) throws IOException {
-    String password = "hdfs-password";
-    String username = "hdfs";
-
-    masterServer.expect()
-        .method("GET")
-        .pathInfo("/webhdfs/v1/")
-        .queryParam("op", "LISTSTATUS")
-        .queryParam("user.name", username)
-        .respond()
-        .status(HttpStatus.SC_OK)
-        .content(driver.getResourceBytes("webhdfs-liststatus-success.json"))
-        .contentType("application/json");
-
-    given()
-        .auth().preemptive().basic(username, password)
-        .header("X-XSRF-Header", "jksdhfkhdsf")
-        .queryParam("op", "LISTSTATUS")
-        .then()
-        .log().ifError()
-        .statusCode(HttpStatus.SC_OK)
-        .body("FileStatuses.FileStatus[0].pathSuffix", is("app-logs"))
-        .when().get(url + "/v1/");
-    masterServer.isEmpty();
-  }
-
-  /**
-   * Creates a topology that is deployed to the gateway instance for the test suite.
-   * Note that this topology is shared by all of the test methods in this suite.
-   *
-   * @return A populated XML structure for a topology file.
-   */
-  private static XMLTag createTopology(final String role) {
-    return XMLDoc.newDocument(true)
-        .addRoot("topology")
-        .addTag("gateway")
-        .addTag("provider")
-        .addTag("role").addText("webappsec")
-        .addTag("name").addText("WebAppSec")
-        .addTag("enabled").addText("true")
-        .addTag("param")
-        .addTag("name").addText("csrf.enabled")
-        .addTag("value").addText("true").gotoParent().gotoParent()
-        .addTag("provider")
-        .addTag("role").addText("authentication")
-        .addTag("name").addText("ShiroProvider")
-        .addTag("enabled").addText("true")
-        .addTag("param")
-        .addTag("name").addText("main.ldapRealm")
-        .addTag("value").addText("org.apache.knox.gateway.shirorealm.KnoxLdapRealm").gotoParent()
-        .addTag("param")
-        .addTag("name").addText("main.ldapRealm.userDnTemplate")
-        .addTag("value").addText("uid={0},ou=people,dc=hadoop,dc=apache,dc=org").gotoParent()
-        .addTag("param")
-        .addTag("name").addText("main.ldapRealm.contextFactory.url")
-        .addTag("value").addText(driver.getLdapUrl()).gotoParent()
-        .addTag("param")
-        .addTag("name").addText("main.ldapRealm.contextFactory.authenticationMechanism")
-        .addTag("value").addText("simple").gotoParent()
-        .addTag("param")
-        .addTag("name").addText("urls./**")
-        .addTag("value").addText("authcBasic").gotoParent().gotoParent()
-        .addTag("provider")
-        .addTag("role").addText("identity-assertion")
-        .addTag("enabled").addText("true")
-        .addTag("name").addText("Default").gotoParent()
-        .addTag("provider")
-        .addTag("role").addText("authorization")
-        .addTag("enabled").addText("true")
-        .addTag("name").addText("AclsAuthz").gotoParent()
-        .addTag("param")
-        .addTag("name").addText("webhdfs-acl")
-        .addTag("value").addText("hdfs;*;*").gotoParent()
-        .addTag("provider")
-        .addTag("role").addText("ha")
-        .addTag("enabled").addText("true")
-        .addTag("name").addText("HaProvider")
-        .addTag("param")
-        .addTag("name").addText("WEBHDFS")
-        .addTag("value").addText("maxFailoverAttempts=3;failoverSleep=15;maxRetryAttempts=3;retrySleep=10;enabled=true").gotoParent()
-        .gotoRoot()
-        .addTag("service")
-        .addTag("role").addText(role)
-        .addTag("url").addText("http://localhost:" + masterServer.getPort() + "/webhdfs")
-        .gotoRoot();
-  }
-
-  /**
-   * This utility method will return the next available port
-   * that can be used.
-   * @param min min port to check
-   * @param max max port to check
-   * @return Port that is available.
-   */
-  public static int getAvailablePort(final int min, final int max) {
-    for (int i = min; i <= max; i++) {
-      if (!GatewayServer.isPortInUse(i)) {
-        return i;
-      }
-    }
-    // too bad
-    return -1;
   }
 }
diff --git a/gateway-test/src/test/java/org/apache/knox/gateway/GatewayPortMappingFailTest.java b/gateway-test/src/test/java/org/apache/knox/gateway/GatewayPortMappingFailTest.java
index 7daed0f..d6edf21 100644
--- a/gateway-test/src/test/java/org/apache/knox/gateway/GatewayPortMappingFailTest.java
+++ b/gateway-test/src/test/java/org/apache/knox/gateway/GatewayPortMappingFailTest.java
@@ -16,10 +16,9 @@
  */
 package org.apache.knox.gateway;
 
+import org.apache.http.HttpStatus;
 import org.apache.knox.test.TestUtils;
 import org.apache.knox.test.category.ReleaseTest;
-import org.apache.knox.test.mock.MockServer;
-import org.apache.http.HttpStatus;
 import org.junit.AfterClass;
 import org.junit.BeforeClass;
 import org.junit.Test;
@@ -36,21 +35,7 @@ import static org.apache.knox.test.TestUtils.LOG_EXIT;
  * Test the fail cases for the Port Mapping Feature
  */
 @Category(ReleaseTest.class)
-public class GatewayPortMappingFailTest {
-
-  // Specifies if the test requests should go through the gateway or directly to the services.
-  // This is frequently used to verify the behavior of the test both with and without the gateway.
-  private static final boolean USE_GATEWAY = true;
-
-  // Specifies if the test requests should be sent to mock services or the real services.
-  // This is frequently used to verify the behavior of the test both with and without mock services.
-  private static final boolean USE_MOCK_SERVICES = true;
-
-  private static GatewayTestDriver driver = new GatewayTestDriver();
-
-  private static MockServer masterServer;
-
-  private static int eeriePort;
+public class GatewayPortMappingFailTest extends PortMappingHelper {
 
   /**
    * Create an instance
@@ -59,39 +44,12 @@ public class GatewayPortMappingFailTest {
     super();
   }
 
-  /**
-   * Creates a deployment of a gateway instance that all test methods will share.  This method also creates a
-   * registry of sorts for all of the services that will be used by the test methods.
-   * The createTopology method is used to create the topology file that would normally be read from disk.
-   * The driver.setupGateway invocation is where the creation of GATEWAY_HOME occurs.
-   * <p>
-   * This would normally be done once for this suite but the failure tests start affecting each other depending
-   * on the state the last 'active' url
-   *
-   * @throws Exception Thrown if any failure occurs.
-   */
   @BeforeClass
   public static void setup() throws Exception {
-    LOG_ENTER();
-
-    eeriePort = GatewayPortMappingFuncTest.getAvailablePort(1240, 49151);
-
+    eeriePort = getAvailablePort(1240, 49151);
     ConcurrentHashMap<String, Integer> topologyPortMapping = new ConcurrentHashMap<>();
     topologyPortMapping.put("eerie", eeriePort);
-
-    masterServer = new MockServer("master", true);
-    GatewayTestConfig config = new GatewayTestConfig();
-    config.setGatewayPath("gateway");
-    config.setTopologyPortMapping(topologyPortMapping);
-
-    driver.setResourceBase(WebHdfsHaFuncTest.class);
-    driver.setupLdap(0);
-
-    driver.setupService("WEBHDFS", "http://vm.local:50070/webhdfs", "/eerie/webhdfs", USE_MOCK_SERVICES);
-
-    driver.setupGateway(config, "eerie", GatewayPortMappingFuncTest.createTopology("WEBHDFS", driver.getLdapUrl(), masterServer.getPort()), USE_GATEWAY);
-
-    LOG_EXIT();
+    init(null, topologyPortMapping);
   }
 
   @AfterClass
diff --git a/gateway-test/src/test/java/org/apache/knox/gateway/GatewayPortMappingFuncTest.java b/gateway-test/src/test/java/org/apache/knox/gateway/GatewayPortMappingFuncTest.java
index afd3e73..81ff7a2 100644
--- a/gateway-test/src/test/java/org/apache/knox/gateway/GatewayPortMappingFuncTest.java
+++ b/gateway-test/src/test/java/org/apache/knox/gateway/GatewayPortMappingFuncTest.java
@@ -16,12 +16,8 @@
  */
 package org.apache.knox.gateway;
 
-import com.mycila.xmltool.XMLDoc;
-import com.mycila.xmltool.XMLTag;
 import org.apache.knox.test.TestUtils;
 import org.apache.knox.test.category.ReleaseTest;
-import org.apache.knox.test.mock.MockServer;
-import org.apache.http.HttpStatus;
 import org.junit.AfterClass;
 import org.junit.BeforeClass;
 import org.junit.Test;
@@ -30,72 +26,26 @@ import org.junit.experimental.categories.Category;
 import java.io.IOException;
 import java.util.concurrent.ConcurrentHashMap;
 
-import static io.restassured.RestAssured.given;
 import static org.apache.knox.test.TestUtils.LOG_ENTER;
 import static org.apache.knox.test.TestUtils.LOG_EXIT;
-import static org.hamcrest.CoreMatchers.is;
 
 /**
  * Test the Gateway Topology Port Mapping functionality
  *
  */
 @Category(ReleaseTest.class)
-public class GatewayPortMappingFuncTest {
-
-  // Specifies if the test requests should go through the gateway or directly to the services.
-  // This is frequently used to verify the behavior of the test both with and without the gateway.
-  private static final boolean USE_GATEWAY = true;
-
-  // Specifies if the test requests should be sent to mock services or the real services.
-  // This is frequently used to verify the behavior of the test both with and without mock services.
-  private static final boolean USE_MOCK_SERVICES = true;
-
-  private static GatewayTestDriver driver = new GatewayTestDriver();
-
-  private static MockServer masterServer;
-
-  private static int eeriePort;
+public class GatewayPortMappingFuncTest extends PortMappingHelper {
 
   public GatewayPortMappingFuncTest() {
     super();
   }
 
-  /**
-   * Creates a deployment of a gateway instance that all test methods will share.  This method also creates a
-   * registry of sorts for all of the services that will be used by the test methods.
-   * The createTopology method is used to create the topology file that would normally be read from disk.
-   * The driver.setupGateway invocation is where the creation of GATEWAY_HOME occurs.
-   * <p>
-   * This would normally be done once for this suite but the failure tests start affecting each other depending
-   * on the state the last 'active' url
-   *
-   * @throws Exception Thrown if any failure occurs.
-   */
   @BeforeClass
   public static void setup() throws Exception {
-    LOG_ENTER();
-
     eeriePort = getAvailablePort(1240, 49151);
-
     ConcurrentHashMap<String, Integer> topologyPortMapping = new ConcurrentHashMap<>();
     topologyPortMapping.put("eerie", eeriePort);
-
-    masterServer = new MockServer("master", true);
-    GatewayTestConfig config = new GatewayTestConfig();
-    config.setGatewayPath("gateway");
-    config.setTopologyPortMapping(topologyPortMapping);
-
-    // Enable default topology
-    config.setDefaultTopologyName("eerie");
-
-    driver.setResourceBase(WebHdfsHaFuncTest.class);
-    driver.setupLdap(0);
-
-    driver.setupService("WEBHDFS", "http://vm.local:50070/webhdfs", "/eerie/webhdfs", USE_MOCK_SERVICES);
-
-    driver.setupGateway(config, "eerie", createTopology("WEBHDFS", driver.getLdapUrl(), masterServer.getPort()), USE_GATEWAY);
-
-    LOG_EXIT();
+    init(null, topologyPortMapping);
   }
 
   @AfterClass
@@ -109,25 +59,12 @@ public class GatewayPortMappingFuncTest {
 
   /*
    * Test the standard case:
-   * http://localhost:{gatewayPort}/gateway/eerie/webhdfs/v1
+   * http://localhost:{topologyPort}/gateway/eerie/webhdfs/v1
    */
   @Test(timeout = TestUtils.MEDIUM_TIMEOUT )
   public void testBasicListOperation() throws IOException {
     LOG_ENTER();
-    test("http://localhost:" + driver.getGatewayPort() + "/gateway/eerie" + "/webhdfs" );
-    LOG_EXIT();
-  }
-
-  /*
-   * Test the Default Topology Feature, activated by property
-   * "default.app.topology.name"
-   *
-   * http://localhost:{eeriePort}/gateway/eerie/webhdfs/v1
-   */
-  @Test(timeout = TestUtils.MEDIUM_TIMEOUT )
-  public void testDefaultTopologyFeature() throws IOException {
-    LOG_ENTER();
-    test("http://localhost:" + driver.getGatewayPort() + "/webhdfs" );
+    test("http://localhost:" + eeriePort + "/gateway/eerie" + "/webhdfs" );
     LOG_EXIT();
   }
 
@@ -155,112 +92,4 @@ public class GatewayPortMappingFuncTest {
     LOG_EXIT();
   }
 
-  private void test (final String url) throws IOException {
-    String password = "hdfs-password";
-    String username = "hdfs";
-
-    masterServer.expect()
-        .method("GET")
-        .pathInfo("/webhdfs/v1/")
-        .queryParam("op", "LISTSTATUS")
-        .queryParam("user.name", username)
-        .respond()
-        .status(HttpStatus.SC_OK)
-        .content(driver.getResourceBytes("webhdfs-liststatus-success.json"))
-        .contentType("application/json");
-
-    given()
-        .auth().preemptive().basic(username, password)
-        .header("X-XSRF-Header", "jksdhfkhdsf")
-        .queryParam("op", "LISTSTATUS")
-        .then()
-        .log().ifError()
-        .statusCode(HttpStatus.SC_OK)
-        .body("FileStatuses.FileStatus[0].pathSuffix", is("app-logs"))
-        .when().get(url + "/v1/");
-    masterServer.isEmpty();
-  }
-
-  /**
-   * Creates a topology that is deployed to the gateway instance for the test suite.
-   * Note that this topology is shared by all of the test methods in this suite.
-   * @param role role name
-   * @param ldapURL ldap url
-   * @param gatewayPort port for the gateway
-   * @return A populated XML structure for a topology file.
-   */
-  public static XMLTag createTopology(final String role, final String ldapURL, final int gatewayPort ) {
-    return XMLDoc.newDocument(true)
-        .addRoot("topology")
-        .addTag("gateway")
-        .addTag("provider")
-        .addTag("role").addText("webappsec")
-        .addTag("name").addText("WebAppSec")
-        .addTag("enabled").addText("true")
-        .addTag("param")
-        .addTag("name").addText("csrf.enabled")
-        .addTag("value").addText("true").gotoParent().gotoParent()
-        .addTag("provider")
-        .addTag("role").addText("authentication")
-        .addTag("name").addText("ShiroProvider")
-        .addTag("enabled").addText("true")
-        .addTag("param")
-        .addTag("name").addText("main.ldapRealm")
-        .addTag("value").addText("org.apache.knox.gateway.shirorealm.KnoxLdapRealm").gotoParent()
-        .addTag("param")
-        .addTag("name").addText("main.ldapRealm.userDnTemplate")
-        .addTag("value").addText("uid={0},ou=people,dc=hadoop,dc=apache,dc=org").gotoParent()
-        .addTag("param")
-        .addTag("name").addText("main.ldapRealm.contextFactory.url")
-        .addTag("value").addText(ldapURL).gotoParent()
-        .addTag("param")
-        .addTag("name").addText("main.ldapRealm.contextFactory.authenticationMechanism")
-        .addTag("value").addText("simple").gotoParent()
-        .addTag("param")
-        .addTag("name").addText("urls./**")
-        .addTag("value").addText("authcBasic").gotoParent().gotoParent()
-        .addTag("provider")
-        .addTag("role").addText("identity-assertion")
-        .addTag("enabled").addText("true")
-        .addTag("name").addText("Default").gotoParent()
-        .addTag("provider")
-        .addTag("role").addText("authorization")
-        .addTag("enabled").addText("true")
-        .addTag("name").addText("AclsAuthz").gotoParent()
-        .addTag("param")
-        .addTag("name").addText("webhdfs-acl")
-        .addTag("value").addText("hdfs;*;*").gotoParent()
-        .addTag("provider")
-        .addTag("role").addText("ha")
-        .addTag("enabled").addText("true")
-        .addTag("name").addText("HaProvider")
-        .addTag("param")
-        .addTag("name").addText("WEBHDFS")
-        .addTag("value").addText("maxFailoverAttempts=3;failoverSleep=15;maxRetryAttempts=3;retrySleep=10;enabled=true").gotoParent()
-        .gotoRoot()
-        .addTag("service")
-        .addTag("role").addText(role)
-        .addTag("url").addText("http://localhost:" + gatewayPort + "/webhdfs")
-        .gotoRoot();
-  }
-
-  /**
-   * This utility method will return the next available port
-   * that can be used.
-   * @param min min port to check
-   * @param max max port to check
-   * @return Port that is available.
-   */
-  public static int getAvailablePort(final int min, final int max) {
-
-    for (int i = min; i <= max; i++) {
-
-      if (!GatewayServer.isPortInUse(i)) {
-        return i;
-      }
-    }
-    // too bad
-    return -1;
-  }
-
 }
diff --git a/gateway-test/src/test/java/org/apache/knox/gateway/PortMappingHelper.java b/gateway-test/src/test/java/org/apache/knox/gateway/PortMappingHelper.java
new file mode 100644
index 0000000..cede7aa
--- /dev/null
+++ b/gateway-test/src/test/java/org/apache/knox/gateway/PortMappingHelper.java
@@ -0,0 +1,189 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.knox.gateway;
+
+import com.mycila.xmltool.XMLDoc;
+import com.mycila.xmltool.XMLTag;
+import org.apache.http.HttpStatus;
+import org.apache.knox.test.mock.MockServer;
+
+import java.io.IOException;
+import java.util.concurrent.ConcurrentHashMap;
+
+import static io.restassured.RestAssured.given;
+import static org.apache.knox.test.TestUtils.LOG_ENTER;
+import static org.apache.knox.test.TestUtils.LOG_EXIT;
+import static org.hamcrest.CoreMatchers.is;
+
+/**
+ * Helper class that contains common code used by port mapping tests
+ */
+public abstract class PortMappingHelper {
+  // Specifies if the test requests should go through the gateway or directly to the services.
+  // This is frequently used to verify the behavior of the test both with and without the gateway.
+  private static final boolean USE_GATEWAY = true;
+  // Specifies if the test requests should be sent to mock services or the real services.
+  // This is frequently used to verify the behavior of the test both with and without mock services.
+  private static final boolean USE_MOCK_SERVICES = true;
+  static GatewayTestDriver driver = new GatewayTestDriver();
+  static MockServer masterServer;
+  static int eeriePort;
+
+  public PortMappingHelper() {
+    super();
+  }
+
+  /**
+   * Creates a deployment of a gateway instance that all test methods will
+   * share.  This method also creates a registry of sorts for all of the
+   * services that will be used by the test methods. The createTopology method
+   * is used to create the topology file that would normally be read from disk.
+   * The driver.setupGateway invocation is where the creation of GATEWAY_HOME
+   * occurs.
+   * <p>
+   * This would normally be done once for this suite but the failure tests start
+   * affecting each other depending on the state the last 'active' url
+   *
+   * @throws Exception Thrown if any failure occurs.
+   */
+  public static void init(final String defaultTopologyName,
+      final ConcurrentHashMap<String, Integer> topologyPortMapping, final boolean isPortMappingEnabled)
+      throws Exception {
+    LOG_ENTER();
+
+    masterServer = new MockServer("master", true);
+    GatewayTestConfig config = new GatewayTestConfig();
+    config.setGatewayPath("gateway");
+
+    /* define default topology to be used */
+    if (defaultTopologyName != null) {
+      config.setDefaultTopologyName(defaultTopologyName);
+    }
+
+    if (topologyPortMapping != null) {
+      config.setTopologyPortMapping(topologyPortMapping);
+    }
+
+    config.setGatewayPortMappingEnabled(isPortMappingEnabled);
+
+    driver.setResourceBase(WebHdfsHaFuncTest.class);
+    driver.setupLdap(0);
+
+    driver.setupService("WEBHDFS", "http://vm.local:50070/webhdfs",
+        "/eerie/webhdfs", USE_MOCK_SERVICES);
+
+    driver.setupGateway(config, "eerie",
+        createTopology("WEBHDFS", driver.getLdapUrl(), masterServer.getPort()),
+        USE_GATEWAY);
+
+    LOG_EXIT();
+  }
+
+  public static void init(final String defaultTopologyName, final boolean isPortMappingEnabled)
+      throws Exception {
+    init(defaultTopologyName, null, isPortMappingEnabled);
+  }
+
+  public static void init(final String defaultTopologyName, final ConcurrentHashMap<String, Integer> topologyPortMapping)
+      throws Exception {
+    init(defaultTopologyName, topologyPortMapping, true);
+  }
+
+  /**
+   * Creates a topology that is deployed to the gateway instance for the test
+   * suite. Note that this topology is shared by all of the test methods in this
+   * suite.
+   *
+   * @param role        role name
+   * @param ldapURL     ldap url
+   * @param gatewayPort port for the gateway
+   * @return A populated XML structure for a topology file.
+   */
+  static XMLTag createTopology(final String role, final String ldapURL,
+      final int gatewayPort) {
+    return XMLDoc.newDocument(true).addRoot("topology").addTag("gateway")
+        .addTag("provider").addTag("role").addText("webappsec").addTag("name")
+        .addText("WebAppSec").addTag("enabled").addText("true").addTag("param")
+        .addTag("name").addText("csrf.enabled").addTag("value").addText("true")
+        .gotoParent().gotoParent().addTag("provider").addTag("role")
+        .addText("authentication").addTag("name").addText("ShiroProvider")
+        .addTag("enabled").addText("true").addTag("param").addTag("name")
+        .addText("main.ldapRealm").addTag("value")
+        .addText("org.apache.knox.gateway.shirorealm.KnoxLdapRealm")
+        .gotoParent().addTag("param").addTag("name")
+        .addText("main.ldapRealm.userDnTemplate").addTag("value")
+        .addText("uid={0},ou=people,dc=hadoop,dc=apache,dc=org").gotoParent()
+        .addTag("param").addTag("name")
+        .addText("main.ldapRealm.contextFactory.url").addTag("value")
+        .addText(ldapURL).gotoParent().addTag("param").addTag("name")
+        .addText("main.ldapRealm.contextFactory.authenticationMechanism")
+        .addTag("value").addText("simple").gotoParent().addTag("param")
+        .addTag("name").addText("urls./**").addTag("value")
+        .addText("authcBasic").gotoParent().gotoParent().addTag("provider")
+        .addTag("role").addText("identity-assertion").addTag("enabled")
+        .addText("true").addTag("name").addText("Default").gotoParent()
+        .addTag("provider").addTag("role").addText("authorization")
+        .addTag("enabled").addText("true").addTag("name").addText("AclsAuthz")
+        .gotoParent().addTag("param").addTag("name").addText("webhdfs-acl")
+        .addTag("value").addText("hdfs;*;*").gotoParent().addTag("provider")
+        .addTag("role").addText("ha").addTag("enabled").addText("true")
+        .addTag("name").addText("HaProvider").addTag("param").addTag("name")
+        .addText("WEBHDFS").addTag("value").addText(
+            "maxFailoverAttempts=3;failoverSleep=15;maxRetryAttempts=3;retrySleep=10;enabled=true")
+        .gotoParent().gotoRoot().addTag("service").addTag("role").addText(role)
+        .addTag("url").addText("http://localhost:" + gatewayPort + "/webhdfs")
+        .gotoRoot();
+  }
+
+  /**
+   * This utility method will return the next available port that can be used.
+   *
+   * @param min min port to check
+   * @param max max port to check
+   * @return Port that is available.
+   */
+  static int getAvailablePort(final int min, final int max) {
+
+    for (int i = min; i <= max; i++) {
+
+      if (!GatewayServer.isPortInUse(i)) {
+        return i;
+      }
+    }
+    // too bad
+    return -1;
+  }
+
+  void test(final String url) throws IOException {
+    String password = "hdfs-password";
+    String username = "hdfs";
+
+    masterServer.expect().method("GET").pathInfo("/webhdfs/v1/")
+        .queryParam("op", "LISTSTATUS").queryParam("user.name", username)
+        .respond().status(HttpStatus.SC_OK)
+        .content(driver.getResourceBytes("webhdfs-liststatus-success.json"))
+        .contentType("application/json");
+
+    given().auth().preemptive().basic(username, password)
+        .header("X-XSRF-Header", "jksdhfkhdsf").queryParam("op", "LISTSTATUS")
+        .then().log().ifError().statusCode(HttpStatus.SC_OK)
+        .body("FileStatuses.FileStatus[0].pathSuffix", is("app-logs")).when()
+        .get(url + "/v1/");
+    masterServer.isEmpty();
+  }
+}