| /** |
| * 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.CommonConfigurationKeysPublic; |
| import org.apache.hadoop.fs.FileSystem; |
| import org.apache.hadoop.fs.FileSystemTestHelper; |
| import org.apache.hadoop.fs.FsShell; |
| 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.hadoop.security.UserGroupInformation; |
| import org.apache.hadoop.util.ToolRunner; |
| import org.apache.log4j.Level; |
| import org.apache.log4j.Logger; |
| import org.junit.After; |
| 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 Path nestedEZBaseFile = new Path(rootDir, "nestedEZBaseFile"); |
| private Path topEZBaseFile = new Path(rootDir, "topEZBaseFile"); |
| |
| private Path topEZDir; |
| private Path nestedEZDir; |
| |
| private Path topEZFile; |
| private Path nestedEZFile; |
| |
| private Path topEZRawFile; |
| private Path nestedEZRawFile; |
| |
| |
| // 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(CommonConfigurationKeysPublic.HADOOP_SECURITY_KEY_PROVIDER_PATH, |
| 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); |
| // enable trash for testing |
| conf.setLong(DFSConfigKeys.FS_TRASH_INTERVAL_KEY, 1); |
| 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); |
| } |
| |
| @After |
| public void tearDown() throws Exception { |
| if (cluster != null) { |
| cluster.shutdown(); |
| cluster = null; |
| } |
| } |
| |
| @Test(timeout = 60000) |
| public void testNestedEncryptionZones() throws Exception { |
| initTopEZDirAndNestedEZDir(new Path(rootDir, "topEZ")); |
| verifyEncryption(); |
| |
| // Restart NameNode to test if nested EZs can be loaded from edit logs |
| cluster.restartNameNodes(); |
| cluster.waitActive(); |
| fs = cluster.getFileSystem(); |
| 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(); |
| fs = cluster.getFileSystem(); |
| verifyEncryption(); |
| |
| renameChildrenOfEZ(); |
| |
| // 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")); |
| } |
| |
| // Should be able to rename the root dir of an EZ. |
| fs.rename(topEZDir, new Path(rootDir, "newTopEZ")); |
| |
| // Should be able to rename the nested EZ dir within the same top EZ. |
| fs.rename(new Path(rootDir, "newTopEZ/nestedEZ"), |
| new Path(rootDir, "newTopEZ/newNestedEZ")); |
| } |
| |
| @Test(timeout = 60000) |
| public void testNestedEZWithRoot() throws Exception { |
| initTopEZDirAndNestedEZDir(rootDir); |
| verifyEncryption(); |
| |
| // test rename file |
| renameChildrenOfEZ(); |
| |
| final String currentUser = |
| UserGroupInformation.getCurrentUser().getShortUserName(); |
| final Path suffixTrashPath = new Path( |
| FileSystem.TRASH_PREFIX, currentUser); |
| |
| final Path rootTrash = fs.getTrashRoot(rootDir); |
| final Path topEZTrash = fs.getTrashRoot(topEZFile); |
| final Path nestedEZTrash = fs.getTrashRoot(nestedEZFile); |
| |
| final Path expectedTopEZTrash = fs.makeQualified( |
| new Path(topEZDir, suffixTrashPath)); |
| final Path expectedNestedEZTrash = fs.makeQualified( |
| new Path(nestedEZDir, suffixTrashPath)); |
| |
| assertEquals("Top ez trash should be " + expectedTopEZTrash, |
| expectedTopEZTrash, topEZTrash); |
| assertEquals("Root trash should be equal with TopEZFile trash", |
| topEZTrash, rootTrash); |
| assertEquals("Nested ez Trash should be " + expectedNestedEZTrash, |
| expectedNestedEZTrash, nestedEZTrash); |
| |
| // delete rename file and test trash |
| FsShell shell = new FsShell(fs.getConf()); |
| final Path topTrashFile = new Path( |
| shell.getCurrentTrashDir(topEZFile) + "/" + topEZFile); |
| final Path nestedTrashFile = new Path( |
| shell.getCurrentTrashDir(nestedEZFile) + "/" + nestedEZFile); |
| |
| ToolRunner.run(shell, new String[]{"-rm", topEZFile.toString()}); |
| ToolRunner.run(shell, new String[]{"-rm", nestedEZFile.toString()}); |
| |
| assertTrue("File not in trash : " + topTrashFile, fs.exists(topTrashFile)); |
| assertTrue( |
| "File not in trash : " + nestedTrashFile, fs.exists(nestedTrashFile)); |
| } |
| |
| private void renameChildrenOfEZ() throws Exception{ |
| Path renamedTopEZFile = new Path(topEZDir, "renamedFile"); |
| Path renamedNestedEZFile = new Path(nestedEZDir, "renamedFile"); |
| |
| //Should be able to rename files within the same EZ. |
| fs.rename(topEZFile, renamedTopEZFile); |
| fs.rename(nestedEZFile, renamedNestedEZFile); |
| |
| topEZFile = renamedTopEZFile; |
| nestedEZFile = renamedNestedEZFile; |
| topEZRawFile = new Path(rawDir + topEZFile.toUri().getPath()); |
| nestedEZRawFile = new Path(rawDir + nestedEZFile.toUri().getPath()); |
| 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) { |
| String exceptionMsg = e.getMessage(); |
| assertTrue(exceptionMsg.contains( |
| "can't be moved from") && exceptionMsg.contains("encryption zone")); |
| } |
| } |
| |
| private void initTopEZDirAndNestedEZDir(Path topPath) throws Exception { |
| |
| // init fs root directory |
| fs.delete(rootDir, true); |
| |
| |
| // init top and nested path or file |
| topEZDir = topPath; |
| nestedEZDir = new Path(topEZDir, "nestedEZ"); |
| |
| topEZFile = new Path(topEZDir, "file"); |
| nestedEZFile = new Path(nestedEZDir, "file"); |
| |
| topEZRawFile = new Path(rawDir + topEZFile.toUri().getPath()); |
| nestedEZRawFile = new Path(rawDir + nestedEZFile.toUri().getPath()); |
| |
| // create ez zone |
| fs.mkdir(topEZDir, FsPermission.getDirDefault()); |
| fs.createEncryptionZone(topEZDir, TOP_EZ_KEY); |
| fs.mkdir(nestedEZDir, FsPermission.getDirDefault()); |
| fs.createEncryptionZone(nestedEZDir, NESTED_EZ_KEY); |
| |
| // create files |
| 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); |
| |
| } |
| |
| 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); |
| } |
| } |