You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@knox.apache.org by su...@apache.org on 2015/05/18 23:14:43 UTC

knox git commit: KNOX-476 implementation for X-Forwarded-* headers support and population

Repository: knox
Updated Branches:
  refs/heads/master bd26abb21 -> dd16033d3


KNOX-476 implementation for X-Forwarded-* headers support and population


Project: http://git-wip-us.apache.org/repos/asf/knox/repo
Commit: http://git-wip-us.apache.org/repos/asf/knox/commit/dd16033d
Tree: http://git-wip-us.apache.org/repos/asf/knox/tree/dd16033d
Diff: http://git-wip-us.apache.org/repos/asf/knox/diff/dd16033d

Branch: refs/heads/master
Commit: dd16033d34346c61b9666e614f5872f7c93a7b39
Parents: bd26abb
Author: Sumit Gupta <su...@apache.org>
Authored: Mon May 18 17:13:26 2015 -0400
Committer: Sumit Gupta <su...@apache.org>
Committed: Mon May 18 17:13:26 2015 -0400

----------------------------------------------------------------------
 .../filter/rewrite/impl/UrlRewriteResponse.java |  36 +++-
 gateway-server-xforwarded-filter/pom.xml        |  67 +++++++
 .../gateway/filter/CompositeEnumeration.java    |  56 ++++++
 .../gateway/filter/XForwardedHeaderFilter.java  |  32 ++++
 .../filter/XForwardedHeaderRequestWrapper.java  | 143 ++++++++++++++
 .../filter/CompositeEnumerationTest.java        | 117 ++++++++++++
 .../gateway/filter/TestFilterAdapter.java       |  45 +++++
 .../hadoop/gateway/filter/TestFilterChain.java  |  35 ++++
 .../filter/XForwardHeaderFilterTest.java        | 108 +++++++++++
 gateway-server/pom.xml                          |   4 +
 .../gateway/config/impl/GatewayConfigImpl.java  |   7 +
 .../ServiceDefinitionDeploymentContributor.java |  17 +-
 .../hadoop/gateway/config/GatewayConfig.java    |   2 +
 .../hadoop/test/mock/MockRequestMatcher.java    |  18 +-
 .../hadoop/gateway/GatewayBasicFuncTest.java    | 190 ++++++++++++++++++-
 .../hadoop/gateway/GatewayTestConfig.java       |  10 +
 .../deploy/DeploymentFactoryFuncTest.java       |  17 +-
 pom.xml                                         |   6 +
 18 files changed, 893 insertions(+), 17 deletions(-)
----------------------------------------------------------------------


http://git-wip-us.apache.org/repos/asf/knox/blob/dd16033d/gateway-provider-rewrite/src/main/java/org/apache/hadoop/gateway/filter/rewrite/impl/UrlRewriteResponse.java
----------------------------------------------------------------------
diff --git a/gateway-provider-rewrite/src/main/java/org/apache/hadoop/gateway/filter/rewrite/impl/UrlRewriteResponse.java b/gateway-provider-rewrite/src/main/java/org/apache/hadoop/gateway/filter/rewrite/impl/UrlRewriteResponse.java
index d9ab6f7..9fbc68a 100644
--- a/gateway-provider-rewrite/src/main/java/org/apache/hadoop/gateway/filter/rewrite/impl/UrlRewriteResponse.java
+++ b/gateway-provider-rewrite/src/main/java/org/apache/hadoop/gateway/filter/rewrite/impl/UrlRewriteResponse.java
@@ -80,6 +80,9 @@ public class UrlRewriteResponse extends GatewayResponseWrapper implements Params
   private UrlRewriteFilterContentDescriptor headersFilterConfig;
   private String cookiesFilterName;
   private UrlRewriteFilterContentDescriptor cookiesFilterConfig;
+  private String xForwardedHostname;
+  private String xForwardedPort;
+  private String xForwardedScheme;
 
   public UrlRewriteResponse( FilterConfig config, HttpServletRequest request, HttpServletResponse response )
       throws IOException {
@@ -89,6 +92,7 @@ public class UrlRewriteResponse extends GatewayResponseWrapper implements Params
     this.request = request;
     this.response = response;
     this.output = null;
+    getXForwardedHeaders();
     this.bodyFilterName = config.getInitParameter( UrlRewriteServletFilter.RESPONSE_BODY_FILTER_PARAM );
     this.headersFilterName = config.getInitParameter( UrlRewriteServletFilter.RESPONSE_HEADERS_FILTER_PARAM );
     this.headersFilterConfig = getRewriteFilterConfig( rewriter.getConfig(), headersFilterName, UrlRewriteServletFilter.HEADERS_MIME_TYPE );
@@ -193,15 +197,15 @@ public class UrlRewriteResponse extends GatewayResponseWrapper implements Params
 
   private String getGatewayParam( String name ) {
     if( "url".equals( name ) ) {
-      return request.getScheme() + "://" + getRequestLocalHostName() + ":" + request.getLocalPort() + request.getContextPath();
+      return xForwardedScheme + "://" + xForwardedHostname + ":" + xForwardedPort + request.getContextPath();
     } else if( "scheme".equals( name ) ) {
-      return request.getScheme();
+      return xForwardedScheme;
     } else if( "host".equals( name ) ) {
-      return getRequestLocalHostName();
+      return xForwardedHostname;
     } else if( "port".equals( name ) ) {
-        return Integer.toString( request.getLocalPort() );
+        return xForwardedPort;
     } else if( "addr".equals( name ) || "address".equals( name ) ) {
-      return getRequestLocalHostName() + ":" + request.getLocalPort();
+      return xForwardedHostname + ":" + xForwardedPort;
     } else if( "path".equals( name ) ) {
       return request.getContextPath();
     } else {
@@ -252,4 +256,26 @@ public class UrlRewriteResponse extends GatewayResponseWrapper implements Params
     throw new UnsupportedOperationException();
   }
 
+  private void getXForwardedHeaders() {
+    xForwardedHostname = request.getHeader( "X-Forwarded-Host" );
+    xForwardedPort = request.getHeader( "X-Forwarded-Port" );
+    xForwardedScheme = request.getHeader( "X-Forwarded-Proto" );
+    if ( xForwardedScheme == null ) {
+      xForwardedScheme = request.getScheme();
+    }
+    if ( xForwardedHostname != null ) {
+      int separator = xForwardedHostname.indexOf( ":" );
+      if ( separator > 0 ) {
+        //a specific port in the forwarded host wins
+        xForwardedPort = xForwardedHostname.substring(separator + 1, xForwardedHostname.length());
+        xForwardedHostname = xForwardedHostname.substring( 0, separator );
+      }
+    } else {
+      xForwardedHostname = getRequestLocalHostName();
+    }
+    if (xForwardedPort == null) {
+      xForwardedPort = Integer.toString( request.getLocalPort() );
+    }
+  }
+
 }

http://git-wip-us.apache.org/repos/asf/knox/blob/dd16033d/gateway-server-xforwarded-filter/pom.xml
----------------------------------------------------------------------
diff --git a/gateway-server-xforwarded-filter/pom.xml b/gateway-server-xforwarded-filter/pom.xml
new file mode 100644
index 0000000..fb18df2
--- /dev/null
+++ b/gateway-server-xforwarded-filter/pom.xml
@@ -0,0 +1,67 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+  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.
+-->
+<project xmlns="http://maven.apache.org/POM/4.0.0"
+         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
+    <modelVersion>4.0.0</modelVersion>
+
+    <parent>
+        <artifactId>gateway</artifactId>
+        <groupId>org.apache.knox</groupId>
+        <version>0.7.0-SNAPSHOT</version>
+    </parent>
+
+    <artifactId>gateway-server-xforwarded-filter</artifactId>
+    <name>gateway-server-xforwarded-filter</name>
+    <description>Filter for x-forwarded headers</description>
+
+
+    <dependencies>
+        <dependency>
+            <groupId>org.apache.knox</groupId>
+            <artifactId>gateway-spi</artifactId>
+        </dependency>
+
+        <dependency>
+            <groupId>${gateway-group}</groupId>
+            <artifactId>gateway-test-utils</artifactId>
+            <scope>test</scope>
+        </dependency>
+        <dependency>
+            <groupId>junit</groupId>
+            <artifactId>junit</artifactId>
+            <scope>test</scope>
+        </dependency>
+        <dependency>
+            <groupId>org.hamcrest</groupId>
+            <artifactId>hamcrest-core</artifactId>
+            <scope>test</scope>
+        </dependency>
+        <dependency>
+            <groupId>org.hamcrest</groupId>
+            <artifactId>hamcrest-library</artifactId>
+            <scope>test</scope>
+        </dependency>
+        <dependency>
+            <groupId>org.easymock</groupId>
+            <artifactId>easymock</artifactId>
+            <scope>test</scope>
+        </dependency>
+
+    </dependencies>
+</project>
\ No newline at end of file

http://git-wip-us.apache.org/repos/asf/knox/blob/dd16033d/gateway-server-xforwarded-filter/src/main/java/org/apache/hadoop/gateway/filter/CompositeEnumeration.java
----------------------------------------------------------------------
diff --git a/gateway-server-xforwarded-filter/src/main/java/org/apache/hadoop/gateway/filter/CompositeEnumeration.java b/gateway-server-xforwarded-filter/src/main/java/org/apache/hadoop/gateway/filter/CompositeEnumeration.java
new file mode 100644
index 0000000..c5190e6
--- /dev/null
+++ b/gateway-server-xforwarded-filter/src/main/java/org/apache/hadoop/gateway/filter/CompositeEnumeration.java
@@ -0,0 +1,56 @@
+/**
+ * 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.hadoop.gateway.filter;
+
+import java.util.Enumeration;
+import java.util.NoSuchElementException;
+
+public class CompositeEnumeration<T> implements Enumeration<T> {
+
+  private int index = 0;
+  private Enumeration<T>[] array;
+
+  public CompositeEnumeration(Enumeration<T>... enumerations) {
+    if( enumerations == null ) {
+      throw new IllegalArgumentException( "enumerations==null" );
+    }
+    this.array = enumerations;
+  }
+
+  @Override
+  public boolean hasMoreElements() {
+    while( array.length > 0 && index < array.length ) {
+      if( array[index].hasMoreElements() ) {
+        return true;
+      } else {
+        index++;
+      }
+    }
+    return false;
+  }
+
+  @Override
+  public T nextElement() {
+    if( hasMoreElements() ) {
+      return array[index].nextElement();
+    } else {
+      throw new NoSuchElementException();
+    }
+  }
+
+}

http://git-wip-us.apache.org/repos/asf/knox/blob/dd16033d/gateway-server-xforwarded-filter/src/main/java/org/apache/hadoop/gateway/filter/XForwardedHeaderFilter.java
----------------------------------------------------------------------
diff --git a/gateway-server-xforwarded-filter/src/main/java/org/apache/hadoop/gateway/filter/XForwardedHeaderFilter.java b/gateway-server-xforwarded-filter/src/main/java/org/apache/hadoop/gateway/filter/XForwardedHeaderFilter.java
new file mode 100644
index 0000000..4b46db3
--- /dev/null
+++ b/gateway-server-xforwarded-filter/src/main/java/org/apache/hadoop/gateway/filter/XForwardedHeaderFilter.java
@@ -0,0 +1,32 @@
+/**
+ * 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.hadoop.gateway.filter;
+
+import javax.servlet.FilterChain;
+import javax.servlet.ServletException;
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletResponse;
+import java.io.IOException;
+
+public class XForwardedHeaderFilter extends AbstractGatewayFilter {
+
+  @Override
+  protected void doFilter(HttpServletRequest request, HttpServletResponse response, FilterChain chain) throws IOException, ServletException {
+    chain.doFilter( new XForwardedHeaderRequestWrapper( request ), response );
+  }
+}

http://git-wip-us.apache.org/repos/asf/knox/blob/dd16033d/gateway-server-xforwarded-filter/src/main/java/org/apache/hadoop/gateway/filter/XForwardedHeaderRequestWrapper.java
----------------------------------------------------------------------
diff --git a/gateway-server-xforwarded-filter/src/main/java/org/apache/hadoop/gateway/filter/XForwardedHeaderRequestWrapper.java b/gateway-server-xforwarded-filter/src/main/java/org/apache/hadoop/gateway/filter/XForwardedHeaderRequestWrapper.java
new file mode 100644
index 0000000..230854a
--- /dev/null
+++ b/gateway-server-xforwarded-filter/src/main/java/org/apache/hadoop/gateway/filter/XForwardedHeaderRequestWrapper.java
@@ -0,0 +1,143 @@
+/**
+ * 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.hadoop.gateway.filter;
+
+import javax.servlet.http.HttpServletRequest;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Collections;
+import java.util.Enumeration;
+import java.util.Hashtable;
+
+public class XForwardedHeaderRequestWrapper extends GatewayRequestWrapper {
+
+  private static String X_FORWARDED_FOR = "X-Forwarded-For";
+  private static String X_FORWARDED_FOR_LOWER = X_FORWARDED_FOR.toLowerCase();
+  private static String X_FORWARDED_PROTO = "X-Forwarded-Proto";
+  private static String X_FORWARDED_PROTO_LOWER = X_FORWARDED_PROTO.toLowerCase();
+  private static String X_FORWARDED_PORT = "X-Forwarded-Port";
+  private static String X_FORWARDED_PORT_LOWER = X_FORWARDED_PORT.toLowerCase();
+  private static String X_FORWARDED_HOST = "X-Forwarded-Host";
+  private static String X_FORWARDED_HOST_LOWER = X_FORWARDED_HOST.toLowerCase();
+  private static String X_FORWARDED_SERVER = "X-Forwarded-Server";
+  private static String X_FORWARDED_SERVER_LOWER = X_FORWARDED_SERVER.toLowerCase();
+  private static String X_FORWARDED_CONTEXT = "X-Forwarded-Context";
+  private static String X_FORWARDED_CONTEXT_LOWER = X_FORWARDED_CONTEXT.toLowerCase();
+  private static ArrayList<String> headerNames = new ArrayList<>();
+
+  static {
+    headerNames.add(X_FORWARDED_FOR);
+    headerNames.add(X_FORWARDED_PROTO);
+    headerNames.add(X_FORWARDED_PORT);
+    headerNames.add(X_FORWARDED_HOST);
+    headerNames.add(X_FORWARDED_SERVER);
+    headerNames.add(X_FORWARDED_CONTEXT);
+  }
+
+  Hashtable<String,String> proxyHeaders = new Hashtable<String, String>();
+
+  public XForwardedHeaderRequestWrapper(HttpServletRequest request) {
+    super( request );
+    setHeader( X_FORWARDED_FOR_LOWER, getForwardedFor( request ) );
+    setHeader( X_FORWARDED_PROTO_LOWER, getForwardedProto( request ) );
+    setHeader( X_FORWARDED_PORT_LOWER, getForwardedPort( request ) );
+    setHeader( X_FORWARDED_HOST_LOWER, getForwardedHost( request ) );
+    setHeader( X_FORWARDED_SERVER_LOWER, getForwardedServer( request ) );
+    setHeader( X_FORWARDED_CONTEXT_LOWER, getForwardedContext( request ) );
+  }
+
+  @Override
+  public Enumeration<String> getHeaderNames() {
+    return new CompositeEnumeration<>( Collections.enumeration(headerNames), super.getHeaderNames() );
+  }
+
+  @Override
+  public Enumeration<String> getHeaders( String name ) {
+    name = name.toLowerCase();
+    Enumeration<String> values;
+    String value = proxyHeaders.get( name );
+    if( value != null ) {
+      values = Collections.enumeration(Arrays.asList(value));
+    } else {
+      values = super.getHeaders( name );
+    }
+    return values;
+  }
+
+  @Override
+  public String getHeader( String name ) {
+    name = name.toLowerCase();
+    String value = proxyHeaders.get( name );
+    if( value == null ) {
+      value = super.getHeader( name );
+    }
+    return value;
+  }
+
+  private void setHeader( String name, String value ) {
+    if( name != null && value != null ) {
+      proxyHeaders.put( name, value );
+    }
+  }
+
+  private static String getForwardedFor( HttpServletRequest request ) {
+    String value;
+    String curr = request.getHeader( X_FORWARDED_FOR );
+    String addr = request.getRemoteAddr();
+    if( curr == null ) {
+      value = addr;
+    } else {
+      value = curr + "," + addr;
+    }
+    return value;
+  }
+
+  private static String getForwardedProto( HttpServletRequest request ) {
+    String value = request.getHeader( X_FORWARDED_PROTO );
+    if( value == null ) {
+      value = request.isSecure() ? "https" : "http";
+    }
+    return value;
+  }
+
+  private static String getForwardedPort( HttpServletRequest request ) {
+    String value = request.getHeader( X_FORWARDED_PORT );
+    if( value == null ) {
+      value = Integer.toString( request.getLocalPort() );
+    }
+    return value;
+  }
+
+  private static String getForwardedHost( HttpServletRequest request ) {
+    String value = request.getHeader( X_FORWARDED_HOST );
+    if( value == null ) {
+      value = request.getHeader( "Host" );
+    }
+    return value;
+  }
+
+  private static String getForwardedServer( HttpServletRequest request ) {
+    return request.getServerName();
+  }
+
+  private static String getForwardedContext( HttpServletRequest request ) {
+    String remote = request.getHeader( X_FORWARDED_CONTEXT );
+    String local = request.getContextPath();
+    return ( remote == null ? "" : remote ) + ( local == null ? "" : local );
+  }
+}

http://git-wip-us.apache.org/repos/asf/knox/blob/dd16033d/gateway-server-xforwarded-filter/src/test/java/org/apache/hadoop/gateway/filter/CompositeEnumerationTest.java
----------------------------------------------------------------------
diff --git a/gateway-server-xforwarded-filter/src/test/java/org/apache/hadoop/gateway/filter/CompositeEnumerationTest.java b/gateway-server-xforwarded-filter/src/test/java/org/apache/hadoop/gateway/filter/CompositeEnumerationTest.java
new file mode 100644
index 0000000..93029dd
--- /dev/null
+++ b/gateway-server-xforwarded-filter/src/test/java/org/apache/hadoop/gateway/filter/CompositeEnumerationTest.java
@@ -0,0 +1,117 @@
+/**
+ * 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.hadoop.gateway.filter;
+
+import org.junit.Test;
+
+import java.util.Arrays;
+import java.util.Collections;
+import java.util.Enumeration;
+import java.util.NoSuchElementException;
+
+import static junit.framework.TestCase.fail;
+import static org.hamcrest.CoreMatchers.is;
+import static org.hamcrest.MatcherAssert.assertThat;
+
+public class CompositeEnumerationTest {
+
+  @Test
+  public void testBasics() {
+
+    String[] a = new String[]{ "1", "2" };
+    Enumeration<String> ea = Collections.enumeration( Arrays.asList( a ) );
+
+    String[] b = new String[]{ "3", "4" };
+    Enumeration<String> eb = Collections.enumeration( Arrays.asList( b ) );
+
+    CompositeEnumeration<String> ce = new CompositeEnumeration<String>( ea, eb );
+
+    assertThat( ce.nextElement(), is( "1" ) );
+    assertThat( ce.nextElement(), is( "2" ) );
+    assertThat( ce.nextElement(), is( "3" ) );
+    assertThat( ce.nextElement(), is( "4" ) );
+    assertThat( ce.hasMoreElements(), is( false ) );
+
+  }
+
+  @Test
+  public void testSingleValues() {
+    String[] a = new String[]{ "1" };
+    Enumeration<String> ea = Collections.enumeration( Arrays.asList( a ) );
+
+    String[] b = new String[]{ "2" };
+    Enumeration<String> eb = Collections.enumeration( Arrays.asList( b ) );
+
+    CompositeEnumeration<String> ce = new CompositeEnumeration<String>( ea, eb );
+
+    assertThat( ce.nextElement(), is( "1" ) );
+    assertThat( ce.nextElement(), is( "2" ) );
+    assertThat( ce.hasMoreElements(), is( false ) );
+  }
+
+  @Test
+  public void testEmptyEnumerations() {
+
+    String[] a = new String[]{ "1", "2" };
+    String[] b = new String[]{ "3", "4" };
+    String[] c = new String[]{};
+
+    Enumeration<String> e1 = Collections.enumeration( Arrays.asList( a ) );
+    Enumeration<String> e2 = Collections.enumeration( Arrays.asList( c ) );
+    CompositeEnumeration<String> ce = new CompositeEnumeration<String>( e1, e2 );
+    assertThat( ce.nextElement(), is( "1" ) );
+    assertThat( ce.nextElement(), is( "2" ) );
+    assertThat( ce.hasMoreElements(), is( false ) );
+
+    e1 = Collections.enumeration( Arrays.asList( c ) );
+    e2 = Collections.enumeration( Arrays.asList( a ) );
+    ce = new CompositeEnumeration<String>( e1, e2 );
+    assertThat( ce.nextElement(), is( "1" ) );
+    assertThat( ce.nextElement(), is( "2" ) );
+    assertThat( ce.hasMoreElements(), is( false ) );
+
+    e1 = Collections.enumeration( Arrays.asList( c ) );
+    e2 = Collections.enumeration( Arrays.asList( c ) );
+    ce = new CompositeEnumeration<String>( e1, e2 );
+    assertThat( ce.hasMoreElements(), is( false ) );
+  }
+
+  @Test
+  public void testEmpty() {
+    CompositeEnumeration<String> ce = new CompositeEnumeration<String>();
+    assertThat( ce.hasMoreElements(), is( false ) );
+
+    try {
+      ce.nextElement();
+      fail( "Should have throws NoSuchElementExcpetion" );
+    } catch( NoSuchElementException e ) {
+      // Expected.
+    }
+  }
+
+  @Test
+  public void testNulls() {
+    try {
+      CompositeEnumeration<String> ce = new CompositeEnumeration<String>( null );
+      fail( "Expected IllegalArgumentException" );
+    } catch( IllegalArgumentException e ) {
+      // Expected.
+    }
+  }
+
+}

http://git-wip-us.apache.org/repos/asf/knox/blob/dd16033d/gateway-server-xforwarded-filter/src/test/java/org/apache/hadoop/gateway/filter/TestFilterAdapter.java
----------------------------------------------------------------------
diff --git a/gateway-server-xforwarded-filter/src/test/java/org/apache/hadoop/gateway/filter/TestFilterAdapter.java b/gateway-server-xforwarded-filter/src/test/java/org/apache/hadoop/gateway/filter/TestFilterAdapter.java
new file mode 100644
index 0000000..9207625
--- /dev/null
+++ b/gateway-server-xforwarded-filter/src/test/java/org/apache/hadoop/gateway/filter/TestFilterAdapter.java
@@ -0,0 +1,45 @@
+/**
+ * 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.hadoop.gateway.filter;
+
+import javax.servlet.Filter;
+import javax.servlet.FilterChain;
+import javax.servlet.FilterConfig;
+import javax.servlet.ServletException;
+import javax.servlet.ServletRequest;
+import javax.servlet.ServletResponse;
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletResponse;
+import java.io.IOException;
+
+public abstract class TestFilterAdapter implements Filter {
+
+  public abstract void doFilter( HttpServletRequest request, HttpServletResponse response, FilterChain chain ) throws IOException, ServletException;
+
+  @Override
+  public void init( FilterConfig filterConfig ) throws ServletException {}
+
+  @Override
+  public void doFilter( ServletRequest request, ServletResponse response, FilterChain chain ) throws IOException, ServletException {
+    doFilter( (HttpServletRequest)request, (HttpServletResponse)response, chain );
+  }
+
+  @Override
+  public void destroy() {}
+
+}

http://git-wip-us.apache.org/repos/asf/knox/blob/dd16033d/gateway-server-xforwarded-filter/src/test/java/org/apache/hadoop/gateway/filter/TestFilterChain.java
----------------------------------------------------------------------
diff --git a/gateway-server-xforwarded-filter/src/test/java/org/apache/hadoop/gateway/filter/TestFilterChain.java b/gateway-server-xforwarded-filter/src/test/java/org/apache/hadoop/gateway/filter/TestFilterChain.java
new file mode 100644
index 0000000..1ab8e22
--- /dev/null
+++ b/gateway-server-xforwarded-filter/src/test/java/org/apache/hadoop/gateway/filter/TestFilterChain.java
@@ -0,0 +1,35 @@
+/**
+ * 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.hadoop.gateway.filter;
+
+import javax.servlet.Filter;
+import javax.servlet.FilterChain;
+import javax.servlet.ServletException;
+import javax.servlet.ServletRequest;
+import javax.servlet.ServletResponse;
+import java.io.IOException;
+import java.util.Stack;
+
+public class TestFilterChain extends Stack<Filter> implements FilterChain {
+  @Override
+  public void doFilter( ServletRequest request, ServletResponse response ) throws IOException, ServletException {
+    if( !isEmpty() ) pop().doFilter( request, response, this );
+  }
+}
+
+

http://git-wip-us.apache.org/repos/asf/knox/blob/dd16033d/gateway-server-xforwarded-filter/src/test/java/org/apache/hadoop/gateway/filter/XForwardHeaderFilterTest.java
----------------------------------------------------------------------
diff --git a/gateway-server-xforwarded-filter/src/test/java/org/apache/hadoop/gateway/filter/XForwardHeaderFilterTest.java b/gateway-server-xforwarded-filter/src/test/java/org/apache/hadoop/gateway/filter/XForwardHeaderFilterTest.java
new file mode 100644
index 0000000..d1d1a99
--- /dev/null
+++ b/gateway-server-xforwarded-filter/src/test/java/org/apache/hadoop/gateway/filter/XForwardHeaderFilterTest.java
@@ -0,0 +1,108 @@
+/**
+ * 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.hadoop.gateway.filter;
+
+import org.easymock.EasyMock;
+import org.junit.Test;
+
+import javax.servlet.FilterChain;
+import javax.servlet.ServletException;
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletResponse;
+import java.io.IOException;
+
+import static org.hamcrest.CoreMatchers.is;
+import static org.hamcrest.MatcherAssert.assertThat;
+
+public class XForwardHeaderFilterTest {
+
+  public static class AssertXForwardedHeaders extends TestFilterAdapter {
+    @Override
+    public void doFilter( HttpServletRequest request, HttpServletResponse response, FilterChain chain ) throws IOException, ServletException {
+      assertThat( request.getHeader( "X-Forwarded-For" ), is( "127.0.0.1" ) );
+      assertThat( request.getHeader( "X-Forwarded-Proto" ), is( "http" ) );
+      assertThat( request.getHeader( "X-Forwarded-Port" ), is( "8888" ) );
+      assertThat( request.getHeader( "X-Forwarded-Host" ), is( "localhost:8888" ) );
+      assertThat( request.getHeader( "X-Forwarded-Server" ), is( "localhost" ) );
+      assertThat( request.getHeader( "X-Forwarded-Context" ), is( "/context" ) );
+    }
+  }
+
+  @Test
+  public void testXForwardHeaders() throws ServletException, IOException {
+    HttpServletRequest request = EasyMock.createNiceMock( HttpServletRequest.class );
+    EasyMock.expect( request.getRemoteAddr() ).andReturn( "127.0.0.1" ).anyTimes();
+    EasyMock.expect( request.isSecure() ).andReturn( false ).anyTimes();
+    EasyMock.expect( request.getLocalPort() ).andReturn( 8888 ).anyTimes();
+    EasyMock.expect( request.getHeader( "Host" ) ).andReturn( "localhost:8888" ).anyTimes();
+    EasyMock.expect( request.getServerName() ).andReturn( "localhost" ).anyTimes();
+    EasyMock.expect( request.getContextPath() ).andReturn( "/context" ).anyTimes();
+    HttpServletResponse response = EasyMock.createNiceMock( HttpServletResponse.class );
+    EasyMock.replay( request, response );
+
+    TestFilterChain chain = new TestFilterChain();
+
+    XForwardedHeaderFilter filter = new XForwardedHeaderFilter();
+
+    chain.push( new AssertXForwardedHeaders() );
+    chain.push( filter );
+    chain.doFilter( request, response );
+  }
+
+  public static class AssertProxiedXForwardedHeaders extends TestFilterAdapter {
+    @Override
+    public void doFilter( HttpServletRequest request, HttpServletResponse response, FilterChain chain ) throws IOException, ServletException {
+      assertThat( request.getHeader( "X-Forwarded-For" ), is( "127.0.0.0,127.0.0.1" ) );
+      assertThat( request.getHeader( "X-Forwarded-Proto" ), is( "https" ) );
+      assertThat( request.getHeader( "X-Forwarded-Port" ), is( "9999" ) );
+      assertThat( request.getHeader( "X-Forwarded-Host" ), is( "remotehost:9999" ) );
+      assertThat( request.getHeader( "X-Forwarded-Server" ), is( "localhost" ) );
+      assertThat( request.getHeader( "X-Forwarded-Context" ), is( "/upstream/context" ) );
+    }
+  }
+
+  @Test
+  public void testProxiedXForwardHeaders() throws ServletException, IOException {
+    HttpServletRequest request = EasyMock.createNiceMock( HttpServletRequest.class );
+
+    EasyMock.expect( request.getHeader( "X-Forwarded-For" ) ).andReturn( "127.0.0.0" ).anyTimes();
+    EasyMock.expect( request.getHeader( "X-Forwarded-Proto" ) ).andReturn( "https" ).anyTimes();
+    EasyMock.expect( request.getHeader( "X-Forwarded-Port" ) ).andReturn( "9999" ).anyTimes();
+    EasyMock.expect( request.getHeader( "X-Forwarded-Host" ) ).andReturn( "remotehost:9999" ).anyTimes();
+    EasyMock.expect( request.getHeader( "X-Forwarded-Server" ) ).andReturn( "remotehost" ).anyTimes();
+    EasyMock.expect( request.getHeader( "X-Forwarded-Context" ) ).andReturn( "/upstream" ).anyTimes();
+
+    EasyMock.expect( request.getRemoteAddr() ).andReturn( "127.0.0.1" ).anyTimes();
+    EasyMock.expect( request.isSecure() ).andReturn( false ).anyTimes();
+    EasyMock.expect( request.getLocalPort() ).andReturn( 8888 ).anyTimes();
+    EasyMock.expect( request.getHeader( "Host" ) ).andReturn( "localhost:8888" ).anyTimes();
+    EasyMock.expect( request.getServerName() ).andReturn( "localhost" ).anyTimes();
+    EasyMock.expect( request.getContextPath() ).andReturn( "/context" ).anyTimes();
+
+    HttpServletResponse response = EasyMock.createNiceMock( HttpServletResponse.class );
+    EasyMock.replay( request, response );
+
+    TestFilterChain chain = new TestFilterChain();
+
+    XForwardedHeaderFilter filter = new XForwardedHeaderFilter();
+
+    chain.push( new AssertProxiedXForwardedHeaders() );
+    chain.push( filter );
+    chain.doFilter( request, response );
+  }
+}

http://git-wip-us.apache.org/repos/asf/knox/blob/dd16033d/gateway-server/pom.xml
----------------------------------------------------------------------
diff --git a/gateway-server/pom.xml b/gateway-server/pom.xml
index b3ad1f2..c06cef7 100644
--- a/gateway-server/pom.xml
+++ b/gateway-server/pom.xml
@@ -186,6 +186,10 @@
             <groupId>org.apache.knox</groupId>
             <artifactId>gateway-provider-rewrite</artifactId>
         </dependency>
+        <dependency>
+            <groupId>org.apache.knox</groupId>
+            <artifactId>gateway-server-xforwarded-filter</artifactId>
+        </dependency>
 
         <!-- ********** ********** ********** ********** ********** ********** -->
         <!-- ********** Test Dependencies                           ********** -->

http://git-wip-us.apache.org/repos/asf/knox/blob/dd16033d/gateway-server/src/main/java/org/apache/hadoop/gateway/config/impl/GatewayConfigImpl.java
----------------------------------------------------------------------
diff --git a/gateway-server/src/main/java/org/apache/hadoop/gateway/config/impl/GatewayConfigImpl.java b/gateway-server/src/main/java/org/apache/hadoop/gateway/config/impl/GatewayConfigImpl.java
index 336d52e..77fb792 100644
--- a/gateway-server/src/main/java/org/apache/hadoop/gateway/config/impl/GatewayConfigImpl.java
+++ b/gateway-server/src/main/java/org/apache/hadoop/gateway/config/impl/GatewayConfigImpl.java
@@ -110,6 +110,7 @@ public class GatewayConfigImpl extends Configuration implements GatewayConfig {
   private static final String TRUSTSTORE_PATH = GATEWAY_CONFIG_FILE_PREFIX + ".truststore.path";
   private static final String TRUSTSTORE_TYPE = GATEWAY_CONFIG_FILE_PREFIX + ".truststore.type";
   private static final String KEYSTORE_TYPE = GATEWAY_CONFIG_FILE_PREFIX + ".keystore.type";
+  private static final String XFORWARDED_ENABLED = GATEWAY_CONFIG_FILE_PREFIX + ".xforwarded.enabled";
 
   // These config property names are not inline with the convention of using the
   // GATEWAY_CONFIG_FILE_PREFIX as is done by those above. These are left for
@@ -429,4 +430,10 @@ public class GatewayConfigImpl extends Configuration implements GatewayConfig {
   public String getKeystoreType() {
     return get( KEYSTORE_TYPE, "JKS");
   }
+
+  @Override
+  public boolean isXForwardedEnabled() {
+    String xForwardedEnabled = get( XFORWARDED_ENABLED, "true" );
+    return "true".equals(xForwardedEnabled);
+  }
 }

http://git-wip-us.apache.org/repos/asf/knox/blob/dd16033d/gateway-server/src/main/java/org/apache/hadoop/gateway/deploy/impl/ServiceDefinitionDeploymentContributor.java
----------------------------------------------------------------------
diff --git a/gateway-server/src/main/java/org/apache/hadoop/gateway/deploy/impl/ServiceDefinitionDeploymentContributor.java b/gateway-server/src/main/java/org/apache/hadoop/gateway/deploy/impl/ServiceDefinitionDeploymentContributor.java
index 1daa72b..55d646f 100644
--- a/gateway-server/src/main/java/org/apache/hadoop/gateway/deploy/impl/ServiceDefinitionDeploymentContributor.java
+++ b/gateway-server/src/main/java/org/apache/hadoop/gateway/deploy/impl/ServiceDefinitionDeploymentContributor.java
@@ -23,8 +23,13 @@ import org.apache.hadoop.gateway.descriptor.FilterDescriptor;
 import org.apache.hadoop.gateway.descriptor.FilterParamDescriptor;
 import org.apache.hadoop.gateway.descriptor.ResourceDescriptor;
 import org.apache.hadoop.gateway.dispatch.GatewayDispatchFilter;
+import org.apache.hadoop.gateway.filter.XForwardedHeaderFilter;
 import org.apache.hadoop.gateway.filter.rewrite.api.UrlRewriteRulesDescriptor;
-import org.apache.hadoop.gateway.service.definition.*;
+import org.apache.hadoop.gateway.service.definition.CustomDispatch;
+import org.apache.hadoop.gateway.service.definition.Policy;
+import org.apache.hadoop.gateway.service.definition.Rewrite;
+import org.apache.hadoop.gateway.service.definition.Route;
+import org.apache.hadoop.gateway.service.definition.ServiceDefinition;
 import org.apache.hadoop.gateway.topology.Provider;
 import org.apache.hadoop.gateway.topology.Service;
 import org.apache.hadoop.gateway.topology.Version;
@@ -43,7 +48,11 @@ public class ServiceDefinitionDeploymentContributor extends ServiceDeploymentCon
 
   private static final String REPLAY_BUFFER_SIZE_PARAM = "replayBufferSize";
 
-  public static final String DEFAULT_REPLAY_BUFFER_SIZE = "8";
+  private static final String DEFAULT_REPLAY_BUFFER_SIZE = "8";
+
+  private static final String XFORWARDED_FILTER_NAME = "XForwardedHeaderFilter";
+
+  private static final String XFORWARDED_FILTER_ROLE = "xforwardedheaders";
 
   private ServiceDefinition serviceDefinition;
 
@@ -107,6 +116,10 @@ public class ServiceDefinitionDeploymentContributor extends ServiceDeploymentCon
     ResourceDescriptor resource = context.getGatewayDescriptor().addResource();
     resource.role(service.getRole());
     resource.pattern(binding.getPath());
+    //add x-forwarded filter if enabled in config
+    if (context.getGatewayConfig().isXForwardedEnabled()) {
+      resource.addFilter().name(XFORWARDED_FILTER_NAME).role(XFORWARDED_FILTER_ROLE).impl(XForwardedHeaderFilter.class);
+    }
     List<Policy> policyBindings = binding.getPolicies();
     if ( policyBindings == null ) {
       policyBindings = serviceDefinition.getPolicies();

http://git-wip-us.apache.org/repos/asf/knox/blob/dd16033d/gateway-spi/src/main/java/org/apache/hadoop/gateway/config/GatewayConfig.java
----------------------------------------------------------------------
diff --git a/gateway-spi/src/main/java/org/apache/hadoop/gateway/config/GatewayConfig.java b/gateway-spi/src/main/java/org/apache/hadoop/gateway/config/GatewayConfig.java
index 966539a..d976324 100644
--- a/gateway-spi/src/main/java/org/apache/hadoop/gateway/config/GatewayConfig.java
+++ b/gateway-spi/src/main/java/org/apache/hadoop/gateway/config/GatewayConfig.java
@@ -101,4 +101,6 @@ public interface GatewayConfig {
   String getKeystoreType();
 
   String getTruststoreType();
+
+  boolean isXForwardedEnabled();
 }

http://git-wip-us.apache.org/repos/asf/knox/blob/dd16033d/gateway-test-utils/src/main/java/org/apache/hadoop/test/mock/MockRequestMatcher.java
----------------------------------------------------------------------
diff --git a/gateway-test-utils/src/main/java/org/apache/hadoop/test/mock/MockRequestMatcher.java b/gateway-test-utils/src/main/java/org/apache/hadoop/test/mock/MockRequestMatcher.java
index 3b16770..3ff8ff9 100644
--- a/gateway-test-utils/src/main/java/org/apache/hadoop/test/mock/MockRequestMatcher.java
+++ b/gateway-test-utils/src/main/java/org/apache/hadoop/test/mock/MockRequestMatcher.java
@@ -19,6 +19,8 @@ package org.apache.hadoop.test.mock;
 
 import org.apache.commons.io.IOUtils;
 import org.apache.commons.lang3.ArrayUtils;
+import org.hamcrest.Matcher;
+import org.hamcrest.Matchers;
 
 import javax.servlet.http.Cookie;
 import javax.servlet.http.HttpServletRequest;
@@ -51,7 +53,7 @@ public class MockRequestMatcher {
   private Set<String> methods = null;
   private String pathInfo = null;
   private String requestURL = null;
-  Map<String,String> headers = null;
+  Map<String,Matcher> headers = null;
   Set<Cookie> cookies = null;
   private Map<String,Object> attributes = null;
   private Map<String,String> queryParams = null;
@@ -98,9 +100,17 @@ public class MockRequestMatcher {
 
   public MockRequestMatcher header( String name, String value ) {
     if( headers == null ) {
-      headers = new HashMap<String, String>();
+      headers = new HashMap<String, Matcher>();
     }
-    headers.put( name, value );
+    headers.put( name, Matchers.is(value) );
+    return this;
+  }
+
+  public MockRequestMatcher header( String name, Matcher matcher ) {
+    if( headers == null ) {
+      headers = new HashMap<String, Matcher>();
+    }
+    headers.put( name, matcher );
     return this;
   }
 
@@ -207,7 +217,7 @@ public class MockRequestMatcher {
         assertThat(
             "Request " + request.getMethod() + " " + request.getRequestURL() +
                 " does not have the expected value for header " + name,
-            request.getHeader( name ), is( headers.get( name ) ) );
+            request.getHeader( name ),  headers.get(name) );
       }
     }
     if( cookies != null ) {

http://git-wip-us.apache.org/repos/asf/knox/blob/dd16033d/gateway-test/src/test/java/org/apache/hadoop/gateway/GatewayBasicFuncTest.java
----------------------------------------------------------------------
diff --git a/gateway-test/src/test/java/org/apache/hadoop/gateway/GatewayBasicFuncTest.java b/gateway-test/src/test/java/org/apache/hadoop/gateway/GatewayBasicFuncTest.java
index 7e830ea..8e2bfe4 100644
--- a/gateway-test/src/test/java/org/apache/hadoop/gateway/GatewayBasicFuncTest.java
+++ b/gateway-test/src/test/java/org/apache/hadoop/gateway/GatewayBasicFuncTest.java
@@ -3197,7 +3197,7 @@ public class GatewayBasicFuncTest {
     driver.assertComplete();
   }
 
-    private void testPostStormResource(String path) throws IOException {
+  private void testPostStormResource(String path) throws IOException {
     String username = "hdfs";
     String password = "hdfs-password";
     String gatewayPath = driver.getUrl( "STORM" ) + path;
@@ -3223,4 +3223,192 @@ public class GatewayBasicFuncTest {
 
     driver.assertComplete();
   }
+
+
+  @Test
+  public void testXForwardHeadersPopulate() throws Exception {
+    String username = "hdfs";
+    String password = "hdfs-password";
+
+    String resourceName = "storm/topology-id.json";
+    String path = "/api/v1/topology/WordCount-1-1424792039";
+    String gatewayPath = driver.getUrl( "STORM" ) + path;
+    InetSocketAddress gatewayAddress = driver.gateway.getAddresses()[0];
+    int gatewayPort = gatewayAddress.getPort();
+    String gatewayHostName = gatewayAddress.getHostName();
+    String gatewayAddrName = InetAddress.getByName( gatewayHostName ).getHostAddress();
+
+    driver.getMock("STORM")
+        .expect()
+        .method("GET")
+        .header("X-Forwarded-Host", Matchers.isOneOf(gatewayHostName + ":" + gatewayPort, gatewayAddrName + ":" + gatewayPort))
+        .header("X-Forwarded-Proto", "http")
+        .header("X-Forwarded-Port", Integer.toString(gatewayPort))
+        .header("X-Forwarded-Context", "/gateway/cluster")
+        .header("X-Forwarded-Server", gatewayHostName)
+        .header("X-Forwarded-For", Matchers.isOneOf(gatewayHostName, gatewayAddrName))
+        .pathInfo(path)
+        .queryParam("user.name", username)
+        .respond()
+        .status(HttpStatus.SC_OK)
+        .content(driver.getResourceBytes(resourceName))
+        .contentType(ContentType.JSON.toString());
+
+    Response response = given()
+        .auth().preemptive().basic(username, password)
+        .header("X-XSRF-Header", "jksdhfkhdsf")
+        .header("Accept", ContentType.JSON.toString())
+        .expect()
+//        .log().all()
+        .statusCode(HttpStatus.SC_OK)
+        .contentType( ContentType.JSON.toString() )
+        .when().get( gatewayPath );
+
+
+    String link = response.getBody().jsonPath().getString("spouts[0].errorWorkerLogLink");
+    MatcherAssert.assertThat(link, anyOf(
+        startsWith("http://" + gatewayHostName + ":" + gatewayPort + "/"),
+        startsWith("http://" + gatewayAddrName + ":" + gatewayPort + "/")));
+    MatcherAssert.assertThat( link, containsString("/storm/logviewer") );
+    driver.assertComplete();
+
+  }
+
+
+  @Test
+  public void testXForwardHeadersRewrite() throws Exception {
+    String username = "hdfs";
+    String password = "hdfs-password";
+    String host = "whatsinaname";
+    String port = "8889";
+    String scheme = "https";
+
+    InetSocketAddress gatewayAddress = driver.gateway.getAddresses()[0];
+    String gatewayHostName = gatewayAddress.getHostName();
+
+    //Test rewriting of body with X-Forwarded headers (using storm)
+    String resourceName = "storm/topology-id.json";
+    String path = "/api/v1/topology/WordCount-1-1424792039";
+    String gatewayPath = driver.getUrl( "STORM" ) + path;
+    driver.getMock("STORM")
+        .expect()
+        .method("GET")
+        .header("X-Forwarded-Host", host)
+        .header("X-Forwarded-Proto", scheme)
+        .header("X-Forwarded-Port", port)
+        .header("X-Forwarded-Context", "/gateway/cluster")
+        .header("X-Forwarded-Server", gatewayHostName)
+        .header("X-Forwarded-For", Matchers.containsString("what, boo"))
+        .pathInfo(path)
+        .queryParam("user.name", username)
+        .respond()
+        .status(HttpStatus.SC_OK)
+        .content(driver.getResourceBytes(resourceName))
+        .contentType(ContentType.JSON.toString());
+
+    Response response = given()
+        .auth().preemptive().basic(username, password)
+        .header("X-XSRF-Header", "jksdhfkhdsf")
+        .header("Accept", ContentType.JSON.toString())
+        .header("X-Forwarded-Host", host)
+        .header("X-Forwarded-Proto", scheme)
+        .header("X-Forwarded-Port", port)
+        .header("X-Forwarded-Server", "what")
+        .header("X-Forwarded-For", "what, boo")
+        .expect()
+        .statusCode(HttpStatus.SC_OK)
+        .contentType(ContentType.JSON.toString())
+        .when().get(gatewayPath);
+
+    String link = response.getBody().jsonPath().getString("spouts[0].errorWorkerLogLink");
+    MatcherAssert.assertThat(link, is(
+        startsWith(scheme + "://" + host + ":" + port + "/")));
+    MatcherAssert.assertThat( link, containsString("/storm/logviewer") );
+
+    driver.assertComplete();
+
+    resourceName = "storm/topology-component-id.json";
+    path = "/api/v1/topology/WordCount-1-1424792039/component/spout";
+    gatewayPath = driver.getUrl( "STORM" ) + path;
+    driver.getMock("STORM")
+        .expect()
+        .method("GET")
+        .header("X-Forwarded-Host", host)
+        .header("X-Forwarded-Proto", scheme)
+        .header("X-Forwarded-Port", port)
+        .header("X-Forwarded-Context", "/gateway/cluster")
+        .header("X-Forwarded-Server", gatewayHostName)
+        .header("X-Forwarded-For", Matchers.containsString("what, boo"))
+        .pathInfo(path)
+        .queryParam("user.name", username)
+        .respond()
+        .status(HttpStatus.SC_OK)
+        .content(driver.getResourceBytes(resourceName))
+        .contentType(ContentType.JSON.toString());
+
+    response = given()
+        .auth().preemptive().basic(username, password)
+        .header("X-XSRF-Header", "jksdhfkhdsf")
+        .header("Accept", ContentType.JSON.toString())
+        .header("X-Forwarded-Host", host)
+        .header("X-Forwarded-Proto", scheme)
+        .header("X-Forwarded-Port", port)
+        .header("X-Forwarded-Server", "what")
+        .header("X-Forwarded-For", "what, boo")
+        .expect()
+//        .log().all()
+        .statusCode(HttpStatus.SC_OK)
+        .contentType( ContentType.JSON.toString() )
+        .when().get( gatewayPath );
+
+
+    link = response.getBody().jsonPath().getString("executorStats[0].workerLogLink");
+    MatcherAssert.assertThat(link, is(
+        startsWith(scheme + "://" + host + ":" + port + "/")));
+    MatcherAssert.assertThat( link, containsString("/storm/logviewer") );
+    driver.assertComplete();
+
+    //Test header rewrite using webhdfs
+    String root = "/tmp/GatewayBasicFuncTest/testBasicOutboundHeaderUseCase";
+
+    driver.getMock( "WEBHDFS" )
+        .expect()
+        .method( "PUT" )
+        .pathInfo("/v1" + root + "/dir/file")
+        .header("Host", driver.getRealAddr("WEBHDFS"))
+        .header("X-Forwarded-Host", host)
+        .header("X-Forwarded-Proto", scheme)
+        .header("X-Forwarded-Port", port)
+        .header("X-Forwarded-Context", "/gateway/cluster")
+        .header("X-Forwarded-Server", gatewayHostName)
+        .header("X-Forwarded-For", Matchers.containsString("what, boo"))
+        .queryParam("op", "CREATE")
+        .queryParam( "user.name", username )
+        .respond()
+        .status( HttpStatus.SC_TEMPORARY_REDIRECT )
+        .header("Location", driver.getRealUrl("DATANODE") + "/v1" + root + "/dir/file?op=CREATE&user.name=hdfs");
+    response = given()
+        //.log().all()
+        .auth().preemptive().basic(username, password)
+        .header("X-XSRF-Header", "jksdhfkhdsf")
+        .header("X-Forwarded-Host", host)
+        .header("X-Forwarded-Proto", scheme)
+        .header("X-Forwarded-Port", port)
+        .header("X-Forwarded-Server", "what")
+        .header("X-Forwarded-For", "what, boo")
+        .queryParam( "op", "CREATE" )
+        .expect()
+            //.log().ifError()
+        .statusCode( HttpStatus.SC_TEMPORARY_REDIRECT )
+        .when().put( driver.getUrl("WEBHDFS") + "/v1" + root + "/dir/file" );
+    String location = response.getHeader( "Location" );
+    //System.out.println( location );
+    log.debug( "Redirect location: " + response.getHeader( "Location" ) );
+    if( driver.isUseGateway() ) {
+      MatcherAssert.assertThat( location, is(startsWith(scheme + "://" + host + ":" + port + "/")));
+      MatcherAssert.assertThat( location, containsString( "?_=" ) );
+    }
+    MatcherAssert.assertThat(location, not(containsString("host=")));
+    MatcherAssert.assertThat(location, not(containsString("port=")));
+  }
 }

http://git-wip-us.apache.org/repos/asf/knox/blob/dd16033d/gateway-test/src/test/java/org/apache/hadoop/gateway/GatewayTestConfig.java
----------------------------------------------------------------------
diff --git a/gateway-test/src/test/java/org/apache/hadoop/gateway/GatewayTestConfig.java b/gateway-test/src/test/java/org/apache/hadoop/gateway/GatewayTestConfig.java
index baf206b..3f9fde5 100644
--- a/gateway-test/src/test/java/org/apache/hadoop/gateway/GatewayTestConfig.java
+++ b/gateway-test/src/test/java/org/apache/hadoop/gateway/GatewayTestConfig.java
@@ -36,6 +36,7 @@ public class GatewayTestConfig implements GatewayConfig {
   private boolean kerberosDebugEnabled = false;
   private String kerberosLoginConfig = "/etc/knox/conf/krb5JAASLogin.conf";
   private String frontendUrl = null;
+  private boolean xForwardedEnabled = true;
 
   public void setGatewayHomeDir( String gatewayHomeDir ) {
     this.gatewayHomeDir = gatewayHomeDir;
@@ -239,4 +240,13 @@ public class GatewayTestConfig implements GatewayConfig {
    public String getGatewayServicesDir() {
       return gatewayHomeDir + "/data/services";
    }
+
+  @Override
+  public boolean isXForwardedEnabled() {
+    return xForwardedEnabled;
+  }
+
+  public void setXForwardedEnabled(boolean enabled) {
+    xForwardedEnabled = enabled;
+  }
 }

http://git-wip-us.apache.org/repos/asf/knox/blob/dd16033d/gateway-test/src/test/java/org/apache/hadoop/gateway/deploy/DeploymentFactoryFuncTest.java
----------------------------------------------------------------------
diff --git a/gateway-test/src/test/java/org/apache/hadoop/gateway/deploy/DeploymentFactoryFuncTest.java b/gateway-test/src/test/java/org/apache/hadoop/gateway/deploy/DeploymentFactoryFuncTest.java
index 99fd5ab..9be668c 100644
--- a/gateway-test/src/test/java/org/apache/hadoop/gateway/deploy/DeploymentFactoryFuncTest.java
+++ b/gateway-test/src/test/java/org/apache/hadoop/gateway/deploy/DeploymentFactoryFuncTest.java
@@ -120,11 +120,16 @@ public class DeploymentFactoryFuncTest {
     Document gateway = parse( war.get( "WEB-INF/gateway.xml" ).getAsset().openStream() );
     //dump( gateway );
 
-    assertThat( gateway, hasXPath( "/gateway/resource[1]/filter[1]/role", equalTo( "authentication" ) ) );
-    assertThat( gateway, hasXPath( "/gateway/resource[1]/filter[1]/name", equalTo( "generic" ) ) );
-    assertThat( gateway, hasXPath( "/gateway/resource[1]/filter[1]/class", equalTo( "org.opensource.ExistingFilter" ) ) );
-    assertThat( gateway, hasXPath( "/gateway/resource[1]/filter[1]/param[1]/name", equalTo( "test-param-name" ) ) );
-    assertThat( gateway, hasXPath( "/gateway/resource[1]/filter[1]/param[1]/value", equalTo( "test-param-value" ) ) );
+    //by default the first filter will be the X-Forwarded header filter
+    assertThat( gateway, hasXPath( "/gateway/resource[1]/filter[1]/role", equalTo( "xforwardedheaders" ) ) );
+    assertThat( gateway, hasXPath( "/gateway/resource[1]/filter[1]/name", equalTo( "XForwardedHeaderFilter" ) ) );
+    assertThat( gateway, hasXPath( "/gateway/resource[1]/filter[1]/class", equalTo( "org.apache.hadoop.gateway.filter.XForwardedHeaderFilter" ) ) );
+
+    assertThat( gateway, hasXPath( "/gateway/resource[1]/filter[2]/role", equalTo( "authentication" ) ) );
+    assertThat( gateway, hasXPath( "/gateway/resource[1]/filter[2]/name", equalTo( "generic" ) ) );
+    assertThat( gateway, hasXPath( "/gateway/resource[1]/filter[2]/class", equalTo( "org.opensource.ExistingFilter" ) ) );
+    assertThat( gateway, hasXPath( "/gateway/resource[1]/filter[2]/param[1]/name", equalTo( "test-param-name" ) ) );
+    assertThat( gateway, hasXPath( "/gateway/resource[1]/filter[2]/param[1]/value", equalTo( "test-param-value" ) ) );
   }
 
   @Test
@@ -185,6 +190,8 @@ public class DeploymentFactoryFuncTest {
   @Test
   public void testSimpleTopology() throws IOException, SAXException, ParserConfigurationException, URISyntaxException {
     GatewayConfig config = new GatewayTestConfig();
+    //Testing without x-forwarded headers filter
+    ((GatewayTestConfig)config).setXForwardedEnabled(false);
     File targetDir = new File( System.getProperty( "user.dir" ), "target" );
     File gatewayDir = new File( targetDir, "gateway-home-" + UUID.randomUUID() );
     gatewayDir.mkdirs();

http://git-wip-us.apache.org/repos/asf/knox/blob/dd16033d/pom.xml
----------------------------------------------------------------------
diff --git a/pom.xml b/pom.xml
index f656816..4c1f236 100644
--- a/pom.xml
+++ b/pom.xml
@@ -47,6 +47,7 @@
         <module>gateway-spi</module>
         <module>gateway-server</module>
         <module>gateway-server-launcher</module>
+        <module>gateway-server-xforwarded-filter</module>
         <module>gateway-provider-rewrite</module>
         <module>gateway-provider-rewrite-func-hostmap-static</module>
         <module>gateway-provider-rewrite-func-service-registry</module>
@@ -537,6 +538,11 @@
             </dependency>
             <dependency>
                 <groupId>${gateway-group}</groupId>
+                <artifactId>gateway-server-xforwarded-filter</artifactId>
+                <version>${gateway-version}</version>
+            </dependency>
+            <dependency>
+                <groupId>${gateway-group}</groupId>
                 <artifactId>knox-cli-launcher</artifactId>
                 <version>${gateway-version}</version>
             </dependency>