HDFS-10579. HDFS web interfaces lack configs for X-FRAME-OPTIONS protection. Contributed by Anu Engineer.
This commit is contained in:
parent
0fd3980a1f
commit
c447efebdb
@ -741,6 +741,12 @@ public class DFSConfigKeys extends CommonConfigurationKeys {
|
||||
// Security-related configs
|
||||
public static final String DFS_ENCRYPT_DATA_TRANSFER_KEY = "dfs.encrypt.data.transfer";
|
||||
public static final boolean DFS_ENCRYPT_DATA_TRANSFER_DEFAULT = false;
|
||||
public static final String DFS_XFRAME_OPTION_ENABLED = "dfs.xframe.enabled";
|
||||
public static final boolean DFS_XFRAME_OPTION_ENABLED_DEFAULT = true;
|
||||
|
||||
public static final String DFS_XFRAME_OPTION_VALUE = "dfs.xframe.value";
|
||||
public static final String DFS_XFRAME_OPTION_VALUE_DEFAULT = "SAMEORIGIN";
|
||||
|
||||
@Deprecated
|
||||
public static final String DFS_ENCRYPT_DATA_TRANSFER_CIPHER_KEY_BITLENGTH_KEY =
|
||||
HdfsClientConfigKeys.DFS_ENCRYPT_DATA_TRANSFER_CIPHER_KEY_BITLENGTH_KEY;
|
||||
|
@ -105,6 +105,16 @@ public DatanodeHttpServer(final Configuration conf,
|
||||
.addEndpoint(URI.create("http://localhost:0"))
|
||||
.setFindPort(true);
|
||||
|
||||
final boolean xFrameEnabled = conf.getBoolean(
|
||||
DFSConfigKeys.DFS_XFRAME_OPTION_ENABLED,
|
||||
DFSConfigKeys.DFS_XFRAME_OPTION_ENABLED_DEFAULT);
|
||||
|
||||
final String xFrameOptionValue = conf.getTrimmed(
|
||||
DFSConfigKeys.DFS_XFRAME_OPTION_VALUE,
|
||||
DFSConfigKeys.DFS_XFRAME_OPTION_VALUE_DEFAULT);
|
||||
|
||||
builder.configureXFrame(xFrameEnabled).setXFrameOption(xFrameOptionValue);
|
||||
|
||||
this.infoServer = builder.build();
|
||||
|
||||
this.infoServer.setAttribute("datanode", datanode);
|
||||
|
@ -29,6 +29,7 @@
|
||||
|
||||
import javax.servlet.ServletContext;
|
||||
|
||||
import com.google.common.annotations.VisibleForTesting;
|
||||
import org.apache.hadoop.classification.InterfaceAudience;
|
||||
import org.apache.hadoop.conf.Configuration;
|
||||
import org.apache.hadoop.ha.HAServiceProtocol;
|
||||
@ -139,6 +140,16 @@ void start() throws IOException {
|
||||
DFSConfigKeys.DFS_NAMENODE_KERBEROS_INTERNAL_SPNEGO_PRINCIPAL_KEY,
|
||||
DFSConfigKeys.DFS_NAMENODE_KEYTAB_FILE_KEY);
|
||||
|
||||
final boolean xFrameEnabled = conf.getBoolean(
|
||||
DFSConfigKeys.DFS_XFRAME_OPTION_ENABLED,
|
||||
DFSConfigKeys.DFS_XFRAME_OPTION_ENABLED_DEFAULT);
|
||||
|
||||
final String xFrameOptionValue = conf.getTrimmed(
|
||||
DFSConfigKeys.DFS_XFRAME_OPTION_VALUE,
|
||||
DFSConfigKeys.DFS_XFRAME_OPTION_VALUE_DEFAULT);
|
||||
|
||||
builder.configureXFrame(xFrameEnabled).setXFrameOption(xFrameOptionValue);
|
||||
|
||||
httpServer = builder.build();
|
||||
|
||||
if (policy.isHttpsEnabled()) {
|
||||
@ -310,4 +321,13 @@ static StartupProgress getStartupProgressFromContext(
|
||||
public static HAServiceProtocol.HAServiceState getNameNodeStateFromContext(ServletContext context) {
|
||||
return getNameNodeFromContext(context).getServiceState();
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the httpServer.
|
||||
* @return HttpServer2
|
||||
*/
|
||||
@VisibleForTesting
|
||||
public HttpServer2 getHttpServer() {
|
||||
return httpServer;
|
||||
}
|
||||
}
|
||||
|
@ -2951,6 +2951,30 @@
|
||||
</description>
|
||||
</property>
|
||||
|
||||
<property>
|
||||
<name>dfs.xframe.enabled</name>
|
||||
<value>true</value>
|
||||
<description>
|
||||
If true, then enables protection against clickjacking by returning
|
||||
X_FRAME_OPTIONS header value set to SAMEORIGIN.
|
||||
Clickjacking protection prevents an attacker from using transparent or
|
||||
opaque layers to trick a user into clicking on a button
|
||||
or link on another page.
|
||||
</description>
|
||||
</property>
|
||||
|
||||
<property>
|
||||
<name>dfs.xframe.value</name>
|
||||
<value>SAMEORIGIN</value>
|
||||
<description>
|
||||
This configration value allows user to specify the value for the
|
||||
X-FRAME-OPTIONS. The possible values for this field are
|
||||
DENY, SAMEORIGIN and ALLOW-FROM. Any other value will throw an
|
||||
exception when namenode and datanodes are starting up.
|
||||
</description>
|
||||
</property>
|
||||
|
||||
|
||||
<property>
|
||||
<name>dfs.balancer.keytab.enabled</name>
|
||||
<value>false</value>
|
||||
|
@ -0,0 +1,90 @@
|
||||
/**
|
||||
* 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
|
||||
* <p>
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
* <p>
|
||||
* 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.hdfs.server.datanode.web;
|
||||
|
||||
import org.apache.hadoop.conf.Configuration;
|
||||
import org.apache.hadoop.hdfs.DFSConfigKeys;
|
||||
import org.apache.hadoop.hdfs.HdfsConfiguration;
|
||||
import org.apache.hadoop.hdfs.MiniDFSCluster;
|
||||
import org.apache.hadoop.hdfs.server.datanode.DataNode;
|
||||
import org.apache.hadoop.http.HttpServer2;
|
||||
import org.junit.Assert;
|
||||
import org.junit.Rule;
|
||||
import org.junit.Test;
|
||||
import org.junit.rules.ExpectedException;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.net.HttpURLConnection;
|
||||
import java.net.URL;
|
||||
|
||||
/**
|
||||
* Test that X-Frame-Options works correctly with DatanodeHTTPServer.
|
||||
*/
|
||||
public class TestDatanodeHttpXFrame {
|
||||
@Rule
|
||||
public ExpectedException exception = ExpectedException.none();
|
||||
|
||||
@Test
|
||||
public void testDataNodeXFrameOptionsEnabled() throws Exception {
|
||||
boolean xFrameEnabled = true;
|
||||
MiniDFSCluster cluster = createCluster(xFrameEnabled, null);
|
||||
HttpURLConnection conn = getConn(cluster);
|
||||
String xfoHeader = conn.getHeaderField("X-FRAME-OPTIONS");
|
||||
Assert.assertTrue("X-FRAME-OPTIONS is absent in the header",
|
||||
xfoHeader != null);
|
||||
Assert.assertTrue(xfoHeader.endsWith(HttpServer2.XFrameOption
|
||||
.SAMEORIGIN.toString()));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testNameNodeXFrameOptionsDisabled() throws Exception {
|
||||
boolean xFrameEnabled = false;
|
||||
MiniDFSCluster cluster = createCluster(xFrameEnabled, null);
|
||||
HttpURLConnection conn = getConn(cluster);
|
||||
String xfoHeader = conn.getHeaderField("X-FRAME-OPTIONS");
|
||||
Assert.assertTrue("unexpected X-FRAME-OPTION in header", xfoHeader == null);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testDataNodeXFramewithInvalidOptions() throws Exception {
|
||||
exception.expect(IllegalArgumentException.class);
|
||||
createCluster(false, "Hadoop");
|
||||
}
|
||||
|
||||
private MiniDFSCluster createCluster(boolean enabled, String
|
||||
value) throws IOException {
|
||||
Configuration conf = new HdfsConfiguration();
|
||||
conf.setBoolean(DFSConfigKeys.DFS_XFRAME_OPTION_ENABLED, enabled);
|
||||
if (value != null) {
|
||||
conf.set(DFSConfigKeys.DFS_XFRAME_OPTION_VALUE, value);
|
||||
}
|
||||
MiniDFSCluster cluster =
|
||||
new MiniDFSCluster.Builder(conf).numDataNodes(1).build();
|
||||
cluster.waitActive();
|
||||
return cluster;
|
||||
}
|
||||
|
||||
private HttpURLConnection getConn(MiniDFSCluster cluster)
|
||||
throws IOException {
|
||||
DataNode datanode = cluster.getDataNodes().get(0);
|
||||
URL newURL = new URL("http://localhost:" + datanode.getInfoPort());
|
||||
HttpURLConnection conn = (HttpURLConnection) newURL.openConnection();
|
||||
conn.connect();
|
||||
return conn;
|
||||
}
|
||||
}
|
@ -0,0 +1,97 @@
|
||||
/**
|
||||
* 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
|
||||
* <p>
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
* <p>
|
||||
* 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.hdfs.server.namenode;
|
||||
|
||||
import org.apache.hadoop.conf.Configuration;
|
||||
import org.apache.hadoop.hdfs.DFSConfigKeys;
|
||||
import org.apache.hadoop.hdfs.HdfsConfiguration;
|
||||
import org.apache.hadoop.http.HttpServer2;
|
||||
import org.apache.hadoop.net.NetUtils;
|
||||
import org.junit.Assert;
|
||||
import org.junit.Rule;
|
||||
import org.junit.Test;
|
||||
import org.junit.rules.ExpectedException;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.net.HttpURLConnection;
|
||||
import java.net.InetSocketAddress;
|
||||
import java.net.MalformedURLException;
|
||||
import java.net.URL;
|
||||
|
||||
/**
|
||||
* A class to test the XFrameoptions of Namenode HTTP Server. We are not reusing
|
||||
* the TestNameNodeHTTPServer since it is a parameterized class and these
|
||||
* following tests will run multiple times doing the same thing, if we had the
|
||||
* code in that classs.
|
||||
*/
|
||||
public class TestNameNodeHttpServerXFrame {
|
||||
|
||||
@Rule
|
||||
public ExpectedException exception = ExpectedException.none();
|
||||
|
||||
public static URL getServerURL(HttpServer2 server)
|
||||
throws MalformedURLException {
|
||||
Assert.assertNotNull("No server", server);
|
||||
return new URL("http://"
|
||||
+ NetUtils.getHostPortString(server.getConnectorAddress(0)));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testNameNodeXFrameOptionsEnabled() throws Exception {
|
||||
HttpURLConnection conn = createServerwithXFrame(true, null);
|
||||
String xfoHeader = conn.getHeaderField("X-FRAME-OPTIONS");
|
||||
Assert.assertTrue("X-FRAME-OPTIONS is absent in the header",
|
||||
xfoHeader != null);
|
||||
Assert.assertTrue(xfoHeader.endsWith(HttpServer2.XFrameOption
|
||||
.SAMEORIGIN.toString()));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testNameNodeXFrameOptionsDisabled() throws Exception {
|
||||
HttpURLConnection conn = createServerwithXFrame(false, null);
|
||||
String xfoHeader = conn.getHeaderField("X-FRAME-OPTIONS");
|
||||
Assert.assertTrue("unexpected X-FRAME-OPTION in header", xfoHeader == null);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testNameNodeXFrameOptionsIllegalOption() throws Exception {
|
||||
exception.expect(IllegalArgumentException.class);
|
||||
createServerwithXFrame(true, "hadoop");
|
||||
}
|
||||
|
||||
private HttpURLConnection createServerwithXFrame(boolean enabled, String
|
||||
value) throws IOException {
|
||||
Configuration conf = new HdfsConfiguration();
|
||||
conf.set(DFSConfigKeys.DFS_NAMENODE_HTTPS_ADDRESS_KEY, "localhost:0");
|
||||
conf.setBoolean(DFSConfigKeys.DFS_XFRAME_OPTION_ENABLED, enabled);
|
||||
if (value != null) {
|
||||
conf.set(DFSConfigKeys.DFS_XFRAME_OPTION_VALUE, value);
|
||||
|
||||
}
|
||||
InetSocketAddress addr = InetSocketAddress.createUnresolved("localhost", 0);
|
||||
NameNodeHttpServer server = null;
|
||||
|
||||
server = new NameNodeHttpServer(conf, null, addr);
|
||||
server.start();
|
||||
|
||||
URL url = getServerURL(server.getHttpServer());
|
||||
HttpURLConnection conn = (HttpURLConnection) url.openConnection();
|
||||
conn.connect();
|
||||
return conn;
|
||||
}
|
||||
}
|
Loading…
Reference in New Issue
Block a user