|
@@ -0,0 +1,215 @@
|
|
|
+/**
|
|
|
+ * 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.hdfs.server.namenode;
|
|
|
+
|
|
|
+import org.apache.hadoop.conf.Configuration;
|
|
|
+import org.apache.hadoop.crypto.key.JavaKeyStoreProvider;
|
|
|
+import org.apache.hadoop.fs.FileSystemTestHelper;
|
|
|
+import org.apache.hadoop.fs.Path;
|
|
|
+import org.apache.hadoop.fs.permission.FsPermission;
|
|
|
+import org.apache.hadoop.hdfs.DFSConfigKeys;
|
|
|
+import org.apache.hadoop.hdfs.DFSTestUtil;
|
|
|
+import org.apache.hadoop.hdfs.DistributedFileSystem;
|
|
|
+import org.apache.hadoop.hdfs.HdfsConfiguration;
|
|
|
+import org.apache.hadoop.hdfs.MiniDFSCluster;
|
|
|
+import org.apache.hadoop.hdfs.protocol.HdfsConstants;
|
|
|
+import org.apache.log4j.Level;
|
|
|
+import org.apache.log4j.Logger;
|
|
|
+import org.junit.Before;
|
|
|
+import org.junit.Test;
|
|
|
+
|
|
|
+import java.io.File;
|
|
|
+
|
|
|
+import static org.junit.Assert.assertEquals;
|
|
|
+import static org.junit.Assert.assertTrue;
|
|
|
+import static org.junit.Assert.fail;
|
|
|
+
|
|
|
+/**
|
|
|
+ * Test the behavior of nested encryption zones.
|
|
|
+ */
|
|
|
+public class TestNestedEncryptionZones {
|
|
|
+ private File testRootDir;
|
|
|
+ private final String TOP_EZ_KEY = "topezkey";
|
|
|
+ private final String NESTED_EZ_KEY = "nestedezkey";
|
|
|
+
|
|
|
+ private MiniDFSCluster cluster;
|
|
|
+ protected DistributedFileSystem fs;
|
|
|
+
|
|
|
+ private final Path rootDir = new Path("/");
|
|
|
+ private final Path rawDir = new Path("/.reserved/raw/");
|
|
|
+ private final Path topEZDir = new Path(rootDir, "topEZ");
|
|
|
+ private final Path nestedEZDir = new Path(topEZDir, "nestedEZ");
|
|
|
+
|
|
|
+ private final Path topEZBaseFile = new Path(rootDir, "topEZBaseFile");
|
|
|
+ private Path topEZFile = new Path(topEZDir, "file");
|
|
|
+ private Path topEZRawFile = new Path(rawDir, "topEZ/file");
|
|
|
+
|
|
|
+ private final Path nestedEZBaseFile = new Path(rootDir, "nestedEZBaseFile");
|
|
|
+ private Path nestedEZFile = new Path(nestedEZDir, "file");
|
|
|
+ private Path nestedEZRawFile = new Path(rawDir, "topEZ/nestedEZ/file");
|
|
|
+
|
|
|
+ // File length
|
|
|
+ private final int len = 8196;
|
|
|
+
|
|
|
+ private String getKeyProviderURI() {
|
|
|
+ return JavaKeyStoreProvider.SCHEME_NAME + "://file" +
|
|
|
+ new Path(testRootDir.toString(), "test.jks").toUri();
|
|
|
+ }
|
|
|
+
|
|
|
+ private void setProvider() {
|
|
|
+ // Need to set the client's KeyProvider to the NN's for JKS,
|
|
|
+ // else the updates do not get flushed properly
|
|
|
+ fs.getClient().setKeyProvider(cluster.getNameNode().getNamesystem()
|
|
|
+ .getProvider());
|
|
|
+ }
|
|
|
+
|
|
|
+ @Before
|
|
|
+ public void setup() throws Exception {
|
|
|
+ Configuration conf = new HdfsConfiguration();
|
|
|
+ FileSystemTestHelper fsHelper = new FileSystemTestHelper();
|
|
|
+ // Set up java key store
|
|
|
+ String testRoot = fsHelper.getTestRootDir();
|
|
|
+ testRootDir = new File(testRoot).getAbsoluteFile();
|
|
|
+ conf.set(DFSConfigKeys.DFS_ENCRYPTION_KEY_PROVIDER_URI, getKeyProviderURI());
|
|
|
+ conf.setBoolean(DFSConfigKeys.DFS_NAMENODE_DELEGATION_TOKEN_ALWAYS_USE_KEY, true);
|
|
|
+ // Lower the batch size for testing
|
|
|
+ conf.setInt(DFSConfigKeys.DFS_NAMENODE_LIST_ENCRYPTION_ZONES_NUM_RESPONSES,
|
|
|
+ 2);
|
|
|
+ cluster = new MiniDFSCluster.Builder(conf).numDataNodes(1).build();
|
|
|
+ Logger.getLogger(EncryptionZoneManager.class).setLevel(Level.TRACE);
|
|
|
+ fs = cluster.getFileSystem();
|
|
|
+ setProvider();
|
|
|
+
|
|
|
+ // Create test keys and EZs
|
|
|
+ DFSTestUtil.createKey(TOP_EZ_KEY, cluster, conf);
|
|
|
+ DFSTestUtil.createKey(NESTED_EZ_KEY, cluster, conf);
|
|
|
+ fs.mkdir(topEZDir, FsPermission.getDirDefault());
|
|
|
+ fs.createEncryptionZone(topEZDir, TOP_EZ_KEY);
|
|
|
+ fs.mkdir(nestedEZDir, FsPermission.getDirDefault());
|
|
|
+ fs.createEncryptionZone(nestedEZDir, NESTED_EZ_KEY);
|
|
|
+
|
|
|
+ DFSTestUtil.createFile(fs, topEZBaseFile, len, (short) 1, 0xFEED);
|
|
|
+ DFSTestUtil.createFile(fs, topEZFile, len, (short) 1, 0xFEED);
|
|
|
+ DFSTestUtil.createFile(fs, nestedEZBaseFile, len, (short) 1, 0xFEED);
|
|
|
+ DFSTestUtil.createFile(fs, nestedEZFile, len, (short) 1, 0xFEED);
|
|
|
+ }
|
|
|
+
|
|
|
+ @Test(timeout = 60000)
|
|
|
+ public void testNestedEncryptionZones() throws Exception {
|
|
|
+ verifyEncryption();
|
|
|
+
|
|
|
+ // Restart NameNode to test if nested EZs can be loaded from edit logs
|
|
|
+ cluster.restartNameNodes();
|
|
|
+ cluster.waitActive();
|
|
|
+ verifyEncryption();
|
|
|
+
|
|
|
+ // Checkpoint and restart NameNode, to test if nested EZs can be loaded
|
|
|
+ // from fsimage
|
|
|
+ fs.setSafeMode(HdfsConstants.SafeModeAction.SAFEMODE_ENTER);
|
|
|
+ fs.saveNamespace();
|
|
|
+ fs.setSafeMode(HdfsConstants.SafeModeAction.SAFEMODE_LEAVE);
|
|
|
+ cluster.restartNameNodes();
|
|
|
+ cluster.waitActive();
|
|
|
+ verifyEncryption();
|
|
|
+
|
|
|
+ Path renamedTopEZFile = new Path(topEZDir, "renamedFile");
|
|
|
+ Path renamedNestedEZFile = new Path(nestedEZDir, "renamedFile");
|
|
|
+ try {
|
|
|
+ fs.rename(topEZFile, renamedTopEZFile);
|
|
|
+ fs.rename(nestedEZFile, renamedNestedEZFile);
|
|
|
+ } catch (Exception e) {
|
|
|
+ fail("Should be able to rename files within the same EZ.");
|
|
|
+ }
|
|
|
+
|
|
|
+ topEZFile = renamedTopEZFile;
|
|
|
+ nestedEZFile = renamedNestedEZFile;
|
|
|
+ topEZRawFile = new Path(rawDir, "topEZ/renamedFile");
|
|
|
+ nestedEZRawFile = new Path(rawDir, "topEZ/nestedEZ/renamedFile");
|
|
|
+ verifyEncryption();
|
|
|
+
|
|
|
+ // Verify that files in top EZ cannot be moved into the nested EZ, and
|
|
|
+ // vice versa.
|
|
|
+ try {
|
|
|
+ fs.rename(topEZFile, new Path(nestedEZDir, "movedTopEZFile"));
|
|
|
+ fail("Shouldn't be able to rename between top EZ and nested EZ.");
|
|
|
+ } catch (Exception e) {
|
|
|
+ assertTrue(e.getMessage().contains(
|
|
|
+ "can't be moved from encryption zone " + topEZDir.toString() +
|
|
|
+ " to encryption zone " + nestedEZDir.toString()));
|
|
|
+ }
|
|
|
+ try {
|
|
|
+ fs.rename(nestedEZFile, new Path(topEZDir, "movedNestedEZFile"));
|
|
|
+ fail("Shouldn't be able to rename between top EZ and nested EZ.");
|
|
|
+ } catch (Exception e) {
|
|
|
+ assertTrue(e.getMessage().contains(
|
|
|
+ "can't be moved from encryption zone " + nestedEZDir.toString() +
|
|
|
+ " to encryption zone " + topEZDir.toString()));
|
|
|
+ }
|
|
|
+
|
|
|
+ // Verify that the nested EZ cannot be moved out of the top EZ.
|
|
|
+ try {
|
|
|
+ fs.rename(nestedEZFile, new Path(rootDir, "movedNestedEZFile"));
|
|
|
+ fail("Shouldn't be able to move the nested EZ out of the top EZ.");
|
|
|
+ } catch (Exception e) {
|
|
|
+ assertTrue(e.getMessage().contains(
|
|
|
+ "can't be moved from an encryption zone"));
|
|
|
+ }
|
|
|
+
|
|
|
+ // Verify that a non-nested EZ cannot be moved into another EZ
|
|
|
+ Path topEZ2Dir = new Path(rootDir, "topEZ2");
|
|
|
+ fs.mkdir(topEZ2Dir, FsPermission.getDirDefault());
|
|
|
+ fs.createEncryptionZone(topEZ2Dir, TOP_EZ_KEY);
|
|
|
+ try {
|
|
|
+ fs.rename(topEZ2Dir, new Path(topEZDir, "topEZ2"));
|
|
|
+ fail("Shouldn't be able to move a non-nested EZ into another " +
|
|
|
+ "existing EZ.");
|
|
|
+ } catch (Exception e){
|
|
|
+ assertTrue(e.getMessage().contains(
|
|
|
+ "can't be moved into an encryption zone"));
|
|
|
+ }
|
|
|
+
|
|
|
+ try {
|
|
|
+ fs.rename(topEZDir, new Path(rootDir, "newTopEZDir"));
|
|
|
+ } catch (Exception e) {
|
|
|
+ fail("Should be able to rename the root dir of an EZ.");
|
|
|
+ }
|
|
|
+
|
|
|
+ try {
|
|
|
+ fs.rename(new Path(rootDir, "newTopEZDir/nestedEZDir"),
|
|
|
+ new Path(rootDir, "newTopEZDir/newNestedEZDir"));
|
|
|
+ } catch (Exception e) {
|
|
|
+ fail("Should be able to rename the nested EZ dir within " +
|
|
|
+ "the same top EZ.");
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ private void verifyEncryption() throws Exception {
|
|
|
+ assertEquals("Top EZ dir is encrypted",
|
|
|
+ true, fs.getFileStatus(topEZDir).isEncrypted());
|
|
|
+ assertEquals("Nested EZ dir is encrypted",
|
|
|
+ true, fs.getFileStatus(nestedEZDir).isEncrypted());
|
|
|
+ assertEquals("Top zone file is encrypted",
|
|
|
+ true, fs.getFileStatus(topEZFile).isEncrypted());
|
|
|
+ assertEquals("Nested zone file is encrypted",
|
|
|
+ true, fs.getFileStatus(nestedEZFile).isEncrypted());
|
|
|
+
|
|
|
+ DFSTestUtil.verifyFilesEqual(fs, topEZBaseFile, topEZFile, len);
|
|
|
+ DFSTestUtil.verifyFilesEqual(fs, nestedEZBaseFile, nestedEZFile, len);
|
|
|
+ DFSTestUtil.verifyFilesNotEqual(fs, topEZRawFile, nestedEZRawFile, len);
|
|
|
+ }
|
|
|
+}
|