Author: clamb Date: Wed Jun 18 22:33:50 2014 New Revision: 1603658 URL: http://svn.apache.org/r1603658 Log: HDFS-6386. HDFS Encryption Zones (clamb)
Added: hadoop/common/branches/fs-encryption/hadoop-hdfs-project/hadoop-hdfs/src/test/java/org/apache/hadoop/hdfs/TestEncryptionZonesAPI.java (with props) Modified: hadoop/common/branches/fs-encryption/hadoop-hdfs-project/hadoop-hdfs/CHANGES-fs-encryption.txt hadoop/common/branches/fs-encryption/hadoop-hdfs-project/hadoop-hdfs/src/main/java/org/apache/hadoop/hdfs/DFSClient.java hadoop/common/branches/fs-encryption/hadoop-hdfs-project/hadoop-hdfs/src/main/java/org/apache/hadoop/hdfs/protocol/HdfsConstants.java hadoop/common/branches/fs-encryption/hadoop-hdfs-project/hadoop-hdfs/src/main/java/org/apache/hadoop/hdfs/protocolPB/ClientNamenodeProtocolTranslatorPB.java hadoop/common/branches/fs-encryption/hadoop-hdfs-project/hadoop-hdfs/src/main/java/org/apache/hadoop/hdfs/server/namenode/FSDirectory.java hadoop/common/branches/fs-encryption/hadoop-hdfs-project/hadoop-hdfs/src/main/java/org/apache/hadoop/hdfs/server/namenode/FSNamesystem.java hadoop/common/branches/fs-encryption/hadoop-hdfs-project/hadoop-hdfs/src/main/java/org/apache/hadoop/hdfs/server/namenode/NameNode.java Modified: hadoop/common/branches/fs-encryption/hadoop-hdfs-project/hadoop-hdfs/CHANGES-fs-encryption.txt URL: http://svn.apache.org/viewvc/hadoop/common/branches/fs-encryption/hadoop-hdfs-project/hadoop-hdfs/CHANGES-fs-encryption.txt?rev=1603658&r1=1603657&r2=1603658&view=diff ============================================================================== --- hadoop/common/branches/fs-encryption/hadoop-hdfs-project/hadoop-hdfs/CHANGES-fs-encryption.txt (original) +++ hadoop/common/branches/fs-encryption/hadoop-hdfs-project/hadoop-hdfs/CHANGES-fs-encryption.txt Wed Jun 18 22:33:50 2014 @@ -10,6 +10,8 @@ fs-encryption (Unreleased) IMPROVEMENTS + HDFS-6386. HDFS Encryption Zones (clamb) + HDFS-6473. Protocol and API for Encryption Zones (clamb) HDFS-6392. Wire crypto streams for encrypted files in Modified: hadoop/common/branches/fs-encryption/hadoop-hdfs-project/hadoop-hdfs/src/main/java/org/apache/hadoop/hdfs/DFSClient.java URL: http://svn.apache.org/viewvc/hadoop/common/branches/fs-encryption/hadoop-hdfs-project/hadoop-hdfs/src/main/java/org/apache/hadoop/hdfs/DFSClient.java?rev=1603658&r1=1603657&r2=1603658&view=diff ============================================================================== --- hadoop/common/branches/fs-encryption/hadoop-hdfs-project/hadoop-hdfs/src/main/java/org/apache/hadoop/hdfs/DFSClient.java (original) +++ hadoop/common/branches/fs-encryption/hadoop-hdfs-project/hadoop-hdfs/src/main/java/org/apache/hadoop/hdfs/DFSClient.java Wed Jun 18 22:33:50 2014 @@ -2802,7 +2802,11 @@ public class DFSClient implements java.i public List<EncryptionZone> listEncryptionZones() throws IOException { checkOpen(); - return namenode.listEncryptionZones(); + try { + return namenode.listEncryptionZones(); + } catch (RemoteException re) { + throw re.unwrapRemoteException(AccessControlException.class); + } } public void setXAttr(String src, String name, byte[] value, Modified: hadoop/common/branches/fs-encryption/hadoop-hdfs-project/hadoop-hdfs/src/main/java/org/apache/hadoop/hdfs/protocol/HdfsConstants.java URL: http://svn.apache.org/viewvc/hadoop/common/branches/fs-encryption/hadoop-hdfs-project/hadoop-hdfs/src/main/java/org/apache/hadoop/hdfs/protocol/HdfsConstants.java?rev=1603658&r1=1603657&r2=1603658&view=diff ============================================================================== --- hadoop/common/branches/fs-encryption/hadoop-hdfs-project/hadoop-hdfs/src/main/java/org/apache/hadoop/hdfs/protocol/HdfsConstants.java (original) +++ hadoop/common/branches/fs-encryption/hadoop-hdfs-project/hadoop-hdfs/src/main/java/org/apache/hadoop/hdfs/protocol/HdfsConstants.java Wed Jun 18 22:33:50 2014 @@ -162,6 +162,11 @@ public class HdfsConstants { public static final String SEPARATOR_DOT_SNAPSHOT_DIR = Path.SEPARATOR + DOT_SNAPSHOT_DIR; + public static final String CRYPTO_XATTR_KEY_ID = "system.hdfs.crypto.key-id"; + public static final String CRYPTO_XATTR_KEY_VERSION_ID = + "system.hdfs.crypto.key-version-id"; + public static final String CRYPTO_XATTR_IV = "system.hdfs.crypto.iv"; + public static final int CRYPTO_KEY_SIZE = 128; /* Temporary until we stop hard-coding these values. */ public static final byte[] KEY = "0123456789012345".getBytes(); public static final byte[] IV = "ABCDEFGJIJKLMNOP".getBytes(); Modified: hadoop/common/branches/fs-encryption/hadoop-hdfs-project/hadoop-hdfs/src/main/java/org/apache/hadoop/hdfs/protocolPB/ClientNamenodeProtocolTranslatorPB.java URL: http://svn.apache.org/viewvc/hadoop/common/branches/fs-encryption/hadoop-hdfs-project/hadoop-hdfs/src/main/java/org/apache/hadoop/hdfs/protocolPB/ClientNamenodeProtocolTranslatorPB.java?rev=1603658&r1=1603657&r2=1603658&view=diff ============================================================================== --- hadoop/common/branches/fs-encryption/hadoop-hdfs-project/hadoop-hdfs/src/main/java/org/apache/hadoop/hdfs/protocolPB/ClientNamenodeProtocolTranslatorPB.java (original) +++ hadoop/common/branches/fs-encryption/hadoop-hdfs-project/hadoop-hdfs/src/main/java/org/apache/hadoop/hdfs/protocolPB/ClientNamenodeProtocolTranslatorPB.java Wed Jun 18 22:33:50 2014 @@ -1282,9 +1282,13 @@ public class ClientNamenodeProtocolTrans @Override public void createEncryptionZone(String src, String keyId) throws IOException { - final CreateEncryptionZoneRequestProto req = - CreateEncryptionZoneRequestProto.newBuilder(). - setSrc(src).setKeyId(keyId).build(); + final CreateEncryptionZoneRequestProto.Builder builder = + CreateEncryptionZoneRequestProto.newBuilder(); + builder.setSrc(src); + if (keyId != null && !keyId.isEmpty()) { + builder.setKeyId(keyId); + } + CreateEncryptionZoneRequestProto req = builder.build(); try { rpcProxy.createEncryptionZone(null, req); } catch (ServiceException e) { Modified: hadoop/common/branches/fs-encryption/hadoop-hdfs-project/hadoop-hdfs/src/main/java/org/apache/hadoop/hdfs/server/namenode/FSDirectory.java URL: http://svn.apache.org/viewvc/hadoop/common/branches/fs-encryption/hadoop-hdfs-project/hadoop-hdfs/src/main/java/org/apache/hadoop/hdfs/server/namenode/FSDirectory.java?rev=1603658&r1=1603657&r2=1603658&view=diff ============================================================================== --- hadoop/common/branches/fs-encryption/hadoop-hdfs-project/hadoop-hdfs/src/main/java/org/apache/hadoop/hdfs/server/namenode/FSDirectory.java (original) +++ hadoop/common/branches/fs-encryption/hadoop-hdfs-project/hadoop-hdfs/src/main/java/org/apache/hadoop/hdfs/server/namenode/FSDirectory.java Wed Jun 18 22:33:50 2014 @@ -32,6 +32,7 @@ import java.util.concurrent.locks.Reentr import org.apache.hadoop.HadoopIllegalArgumentException; import org.apache.hadoop.conf.Configuration; +import org.apache.hadoop.crypto.CryptoCodec; import org.apache.hadoop.fs.ContentSummary; import org.apache.hadoop.fs.FileAlreadyExistsException; import org.apache.hadoop.fs.Options; @@ -50,6 +51,7 @@ import org.apache.hadoop.fs.permission.P import org.apache.hadoop.hdfs.DFSConfigKeys; import org.apache.hadoop.hdfs.DFSUtil; import org.apache.hadoop.hdfs.DistributedFileSystem; +import org.apache.hadoop.hdfs.XAttrHelper; import org.apache.hadoop.hdfs.protocol.AclException; import org.apache.hadoop.hdfs.protocol.Block; import org.apache.hadoop.hdfs.protocol.ClientProtocol; @@ -84,6 +86,10 @@ import com.google.common.annotations.Vis import com.google.common.base.Preconditions; import com.google.common.collect.Lists; +import static org.apache.hadoop.hdfs.protocol.HdfsConstants.CRYPTO_XATTR_KEY_ID; +import static org.apache.hadoop.hdfs.protocol.HdfsConstants.CRYPTO_XATTR_IV; +import static org.apache.hadoop.hdfs.protocol.HdfsConstants.CRYPTO_XATTR_KEY_VERSION_ID; + /************************************************* * FSDirectory stores the filesystem directory state. * It handles writing/loading values to disk, and logging @@ -130,6 +136,7 @@ public class FSDirectory implements Clos private final INodeMap inodeMap; // Synchronized by dirLock private long yieldCount = 0; // keep track of lock yield count. private final int inodeXAttrsLimit; //inode xattrs max limit + private final CryptoCodec codec; // lock to protect the directory and BlockMap private final ReentrantReadWriteLock dirLock; @@ -198,6 +205,7 @@ public class FSDirectory implements Clos this.inodeXAttrsLimit = conf.getInt( DFSConfigKeys.DFS_NAMENODE_MAX_XATTRS_PER_INODE_KEY, DFSConfigKeys.DFS_NAMENODE_MAX_XATTRS_PER_INODE_DEFAULT); + this.codec = CryptoCodec.getInstance(conf); Preconditions.checkArgument(this.inodeXAttrsLimit >= 0, "Cannot set a negative limit on the number of xattrs per inode (%s).", DFSConfigKeys.DFS_NAMENODE_MAX_XATTRS_PER_INODE_KEY); @@ -2662,6 +2670,44 @@ public class FSDirectory implements Clos return xAttrs; } + XAttr createEncryptionZone(String src, String keyId) + throws IOException { + writeLock(); + try { + if (isNonEmptyDirectory(src)) { + throw new IOException( + "Attempt to create an encryption zone for a non-empty directory."); + } + final XAttr keyIdXAttr = + XAttrHelper.buildXAttr(CRYPTO_XATTR_KEY_ID, keyId.getBytes()); + unprotectedSetXAttr(src, keyIdXAttr, EnumSet.of(XAttrSetFlag.CREATE)); + return keyIdXAttr; + } finally { + writeUnlock(); + } + } + + XAttr deleteEncryptionZone(String src) + throws IOException { + writeLock(); + try { + if (isNonEmptyDirectory(src)) { + throw new IOException( + "Attempt to delete an encryption zone for a non-empty directory."); + } + final XAttr keyIdXAttr = + XAttrHelper.buildXAttr(CRYPTO_XATTR_KEY_ID, null); + final XAttr removedXAttr = unprotectedRemoveXAttr(src, keyIdXAttr); + if (removedXAttr == null) { + throw new IOException( + src + " does not appear to be the root of an encryption zone"); + } + return removedXAttr; + } finally { + writeUnlock(); + } + } + void setXAttr(String src, XAttr xAttr, EnumSet<XAttrSetFlag> flag) throws IOException { writeLock(); Modified: hadoop/common/branches/fs-encryption/hadoop-hdfs-project/hadoop-hdfs/src/main/java/org/apache/hadoop/hdfs/server/namenode/FSNamesystem.java URL: http://svn.apache.org/viewvc/hadoop/common/branches/fs-encryption/hadoop-hdfs-project/hadoop-hdfs/src/main/java/org/apache/hadoop/hdfs/server/namenode/FSNamesystem.java?rev=1603658&r1=1603657&r2=1603658&view=diff ============================================================================== --- hadoop/common/branches/fs-encryption/hadoop-hdfs-project/hadoop-hdfs/src/main/java/org/apache/hadoop/hdfs/server/namenode/FSNamesystem.java (original) +++ hadoop/common/branches/fs-encryption/hadoop-hdfs-project/hadoop-hdfs/src/main/java/org/apache/hadoop/hdfs/server/namenode/FSNamesystem.java Wed Jun 18 22:33:50 2014 @@ -83,12 +83,16 @@ import static org.apache.hadoop.hdfs.DFS import static org.apache.hadoop.hdfs.DFSConfigKeys.DFS_PERMISSIONS_SUPERUSERGROUP_KEY; import static org.apache.hadoop.hdfs.DFSConfigKeys.DFS_REPLICATION_DEFAULT; import static org.apache.hadoop.hdfs.DFSConfigKeys.DFS_REPLICATION_KEY; +import static org.apache.hadoop.hdfs.protocol.HdfsConstants.CRYPTO_KEY_SIZE; +import static org.apache.hadoop.hdfs.protocol.HdfsConstants.CRYPTO_XATTR_IV; +import static org.apache.hadoop.hdfs.protocol.HdfsConstants.CRYPTO_XATTR_KEY_VERSION_ID; import static org.apache.hadoop.util.Time.now; import java.io.*; import java.lang.management.ManagementFactory; import java.net.InetAddress; import java.net.URI; +import java.security.NoSuchAlgorithmException; import java.util.ArrayList; import java.util.Arrays; import java.util.Collection; @@ -102,6 +106,7 @@ import java.util.LinkedHashSet; import java.util.List; import java.util.Map; import java.util.Set; +import java.util.UUID; import java.util.concurrent.TimeUnit; import java.util.concurrent.locks.ReentrantLock; import java.util.concurrent.locks.ReentrantReadWriteLock; @@ -116,6 +121,9 @@ import org.apache.commons.logging.impl.L import org.apache.hadoop.HadoopIllegalArgumentException; import org.apache.hadoop.classification.InterfaceAudience; import org.apache.hadoop.conf.Configuration; +import org.apache.hadoop.crypto.key.KeyProvider; +import org.apache.hadoop.crypto.key.KeyProvider.KeyVersion; +import org.apache.hadoop.crypto.key.KeyProviderFactory; import org.apache.hadoop.fs.BatchedRemoteIterator.BatchedListEntries; import org.apache.hadoop.fs.CacheFlag; import org.apache.hadoop.fs.ContentSummary; @@ -145,6 +153,7 @@ import org.apache.hadoop.hdfs.DFSUtil; import org.apache.hadoop.hdfs.HAUtil; import org.apache.hadoop.hdfs.HdfsConfiguration; import org.apache.hadoop.hdfs.StorageType; +import org.apache.hadoop.hdfs.XAttrHelper; import org.apache.hadoop.hdfs.protocol.AclException; import org.apache.hadoop.hdfs.protocol.AlreadyBeingCreatedException; import org.apache.hadoop.hdfs.protocol.Block; @@ -261,6 +270,7 @@ import org.mortbay.util.ajax.JSON; import com.google.common.annotations.VisibleForTesting; import com.google.common.base.Charsets; +import com.google.common.base.Joiner; import com.google.common.base.Preconditions; import com.google.common.collect.ImmutableMap; import com.google.common.collect.Lists; @@ -515,6 +525,11 @@ public class FSNamesystem implements Nam private final NNConf nnConf; + private KeyProvider provider = null; + private KeyProvider.Options providerOptions = null; + + private final Map<String, EncryptionZone> encryptionZones; + /** * Set the last allocated inode id when fsimage or editlog is loaded. */ @@ -675,6 +690,8 @@ public class FSNamesystem implements Nam */ FSNamesystem(Configuration conf, FSImage fsImage, boolean ignoreRetryCache) throws IOException { + initializeKeyProvider(conf); + providerOptions = KeyProvider.options(conf); if (conf.getBoolean(DFS_NAMENODE_AUDIT_LOG_ASYNC_KEY, DFS_NAMENODE_AUDIT_LOG_ASYNC_DEFAULT)) { LOG.info("Enabling async auditlog"); @@ -781,6 +798,7 @@ public class FSNamesystem implements Nam auditLoggers.get(0) instanceof DefaultAuditLogger; this.retryCache = ignoreRetryCache ? null : initRetryCache(conf); this.nnConf = new NNConf(conf); + this.encryptionZones = new HashMap<String, EncryptionZone>(); } catch(IOException e) { LOG.error(getClass().getSimpleName() + " initialization failed.", e); close(); @@ -826,6 +844,42 @@ public class FSNamesystem implements Nam } } + private void initializeKeyProvider(final Configuration conf) { + try { + final List<KeyProvider> providers = KeyProviderFactory.getProviders(conf); + if (providers == null) { + return; + } + + if (providers.size() == 0) { + LOG.info("No KeyProviders found."); + return; + } + + if (providers.size() > 1) { + final String err = + "Multiple KeyProviders found. Only one is permitted."; + LOG.error(err); + throw new RuntimeException(err); + } + provider = providers.get(0); + if (provider.isTransient()) { + final String err = + "A KeyProvider was found but it is a transient provider."; + LOG.error(err); + throw new RuntimeException(err); + } + LOG.info("Found KeyProvider: " + provider.toString()); + } catch (IOException e) { + LOG.error("Exception while initializing KeyProvider", e); + } + } + + @VisibleForTesting + public KeyProvider getProvider() { + return provider; + } + @VisibleForTesting static RetryCache initRetryCache(Configuration conf) { boolean enable = conf.getBoolean(DFS_NAMENODE_ENABLE_RETRY_CACHE_KEY, @@ -2358,7 +2412,7 @@ public class FSNamesystem implements Nam throw ie; } } - + /** * Append to an existing file for append. * <p> @@ -8057,14 +8111,206 @@ public class FSNamesystem implements Nam } } - void createEncryptionZone(final String src, final String keyId) - throws IOException { + /** + * Create an encryption zone on directory src either using keyIdArg if + * supplied or generating a keyId if it's null. + * + * @param src the path of a directory which will be the root of the + * encryption zone. The directory must be empty. + * + * @param keyIdArg an optional keyId of a key in the configured + * KeyProvider. If this is null, then a a new key is generated. + * + * @throws AccessControlException if the caller is not the superuser. + * + * @throws UnresolvedLinkException if the path can't be resolved. + * + * @throws SafeModeException if the Namenode is in safe mode. + */ + void createEncryptionZone(final String src, String keyIdArg) + throws IOException, UnresolvedLinkException, + SafeModeException, AccessControlException { + final CacheEntry cacheEntry = RetryCache.waitForCompletion(retryCache); + if (cacheEntry != null && cacheEntry.isSuccess()) { + return; // Return previous response + } + + boolean createdKey = false; + String keyId = keyIdArg; + boolean success = false; + try { + if (keyId == null || keyId.isEmpty()) { + keyId = createNewKey(src); + createdKey = true; + } else { + if (provider.getCurrentKey(keyId) == null) { + + /* + * It would be nice if we threw something more specific than + * IOException when the key is not found, but the KeyProvider API + * doesn't provide for that. If that API is ever changed to throw + * something more specific (e.g. UnknownKeyException) then we can + * update this to match it, or better yet, just rethrow the + * KeyProvider's exception. + */ + throw new IOException("Key " + keyId + " doesn't exist."); + } + } + createEncryptionZoneInt(src, keyId, cacheEntry != null); + success = true; + } catch (AccessControlException e) { + logAuditEvent(false, "createEncryptionZone", src); + throw e; + } finally { + RetryCache.setState(cacheEntry, success); + if (!success && createdKey) { + /* Unwind key creation. */ + provider.deleteKey(keyId); + } + } } - void deleteEncryptionZone(final String src) throws IOException { + private void createEncryptionZoneInt(final String srcArg, String keyId, + final boolean logRetryCache) throws IOException { + String src = srcArg; + HdfsFileStatus resultingStat = null; + checkSuperuserPrivilege(); + checkOperation(OperationCategory.WRITE); + final byte[][] pathComponents = + FSDirectory.getPathComponentsForReservedPath(src); + writeLock(); + try { + checkSuperuserPrivilege(); + checkOperation(OperationCategory.WRITE); + checkNameNodeSafeMode("Cannot create encryption zone on " + src); + src = FSDirectory.resolvePath(src, pathComponents, dir); + + EncryptionZone ez = getEncryptionZoneForPath(src); + if (ez != null) { + throw new IOException("Directory " + src + + " is already in an encryption zone. (" + ez.getPath() + ")"); + } + + final XAttr keyIdXAttr = dir.createEncryptionZone(src, keyId); + getEditLog().logSetXAttr(src, keyIdXAttr, logRetryCache); + encryptionZones.put(src, new EncryptionZone(src, keyId)); + resultingStat = getAuditFileInfo(src, false); + } finally { + writeUnlock(); + } + getEditLog().logSync(); + logAuditEvent(true, "createEncryptionZone", src, null, resultingStat); + } + + private String createNewKey(String src) + throws IOException { + final String keyId = UUID.randomUUID().toString(); + // TODO pass in hdfs://HOST:PORT (HDFS-6490) + providerOptions.setDescription(src); + providerOptions.setBitLength(CRYPTO_KEY_SIZE); + try { + provider.createKey(keyId, providerOptions); + } catch (NoSuchAlgorithmException e) { + throw new IOException(e); + } + return keyId; + } + + /** + * Delete the encryption zone on directory src. + * + * @param src the path of a directory which is the root of the encryption + * zone. The directory must be empty and must be marked as an encryption + * zone. + * + * @throws AccessControlException if the caller is not the superuser. + * + * @throws UnresolvedLinkException if the path can't be resolved. + * + * @throws SafeModeException if the Namenode is in safe mode. + */ + void deleteEncryptionZone(final String src) + throws IOException, UnresolvedLinkException, + SafeModeException, AccessControlException { + final CacheEntry cacheEntry = RetryCache.waitForCompletion(retryCache); + if (cacheEntry != null && cacheEntry.isSuccess()) { + return; // Return previous response + } + + boolean success = false; + try { + deleteEncryptionZoneInt(src, cacheEntry != null); + encryptionZones.remove(src); + success = true; + } catch (AccessControlException e) { + logAuditEvent(false, "deleteEncryptionZone", src); + throw e; + } finally { + RetryCache.setState(cacheEntry, success); + } + } + + private void deleteEncryptionZoneInt(final String srcArg, + final boolean logRetryCache) throws IOException { + String src = srcArg; + HdfsFileStatus resultingStat = null; + checkSuperuserPrivilege(); + checkOperation(OperationCategory.WRITE); + final byte[][] pathComponents = + FSDirectory.getPathComponentsForReservedPath(src); + writeLock(); + try { + checkSuperuserPrivilege(); + checkOperation(OperationCategory.WRITE); + checkNameNodeSafeMode("Cannot delete encryption zone on " + src); + src = FSDirectory.resolvePath(src, pathComponents, dir); + final EncryptionZone ez = encryptionZones.get(src); + if (ez == null) { + throw new IOException("Directory " + src + + " is not the root of an encryption zone."); + } + final XAttr removedXAttr = dir.deleteEncryptionZone(src); + if (removedXAttr != null) { + getEditLog().logRemoveXAttr(src, removedXAttr); + } + encryptionZones.remove(src); + resultingStat = getAuditFileInfo(src, false); + } finally { + writeUnlock(); + } + getEditLog().logSync(); + logAuditEvent(true, "deleteEncryptionZone", src, null, resultingStat); } List<EncryptionZone> listEncryptionZones() throws IOException { + boolean success = false; + checkSuperuserPrivilege(); + checkOperation(OperationCategory.READ); + readLock(); + try { + checkSuperuserPrivilege(); + checkOperation(OperationCategory.READ); + final List<EncryptionZone> ret = + Lists.newArrayList(encryptionZones.values()); + success = true; + return ret; + } finally { + readUnlock(); + logAuditEvent(success, "listEncryptionZones", null); + } + } + + /** Lookup the encryption zone of a path. */ + private EncryptionZone getEncryptionZoneForPath(String src) { + final String[] components = INode.getPathNames(src); + for (int i = components.length; i > 0; i--) { + final List<String> l = Arrays.asList(Arrays.copyOfRange(components, 0, i)); + String p = Joiner.on(Path.SEPARATOR).join(l); + final EncryptionZone ret = encryptionZones.get(p); + if (ret != null) { + return ret; + } + } return null; } Modified: hadoop/common/branches/fs-encryption/hadoop-hdfs-project/hadoop-hdfs/src/main/java/org/apache/hadoop/hdfs/server/namenode/NameNode.java URL: http://svn.apache.org/viewvc/hadoop/common/branches/fs-encryption/hadoop-hdfs-project/hadoop-hdfs/src/main/java/org/apache/hadoop/hdfs/server/namenode/NameNode.java?rev=1603658&r1=1603657&r2=1603658&view=diff ============================================================================== --- hadoop/common/branches/fs-encryption/hadoop-hdfs-project/hadoop-hdfs/src/main/java/org/apache/hadoop/hdfs/server/namenode/NameNode.java (original) +++ hadoop/common/branches/fs-encryption/hadoop-hdfs-project/hadoop-hdfs/src/main/java/org/apache/hadoop/hdfs/server/namenode/NameNode.java Wed Jun 18 22:33:50 2014 @@ -27,8 +27,6 @@ import org.apache.commons.logging.LogFac import org.apache.hadoop.HadoopIllegalArgumentException; import org.apache.hadoop.classification.InterfaceAudience; import org.apache.hadoop.conf.Configuration; -import org.apache.hadoop.crypto.key.KeyProvider; -import org.apache.hadoop.crypto.key.KeyProviderFactory; import org.apache.hadoop.fs.FileSystem; import org.apache.hadoop.fs.Trash; import org.apache.hadoop.ha.HAServiceProtocol.HAServiceState; @@ -272,9 +270,6 @@ public class NameNode implements NameNod private NameNodeRpcServer rpcServer; - /* The KeyProvider, if any. */ - private KeyProvider provider = null; - private JvmPauseMonitor pauseMonitor; private ObjectName nameNodeStatusBeanName; /** @@ -586,7 +581,6 @@ public class NameNode implements NameNod startHttpServer(conf); } loadNamesystem(conf); - initializeKeyProvider(conf); rpcServer = createRpcServer(conf); if (clientNamenodeAddress == null) { @@ -705,37 +699,6 @@ public class NameNode implements NameNod } } - private void initializeKeyProvider(final Configuration conf) { - try { - final List<KeyProvider> providers = KeyProviderFactory.getProviders(conf); - if (providers == null) { - return; - } - - if (providers.size() == 0) { - LOG.info("No KeyProviders found."); - return; - } - - if (providers.size() > 1) { - final String err = - "Multiple KeyProviders found. Only one is permitted."; - LOG.error(err); - throw new RuntimeException(err); - } - provider = providers.get(0); - if (provider.isTransient()) { - final String err = - "A KeyProvider was found but it is a transient provider."; - LOG.error(err); - throw new RuntimeException(err); - } - LOG.info("Found KeyProvider: " + provider.toString()); - } catch (IOException e) { - LOG.error("Exception while initializing KeyProvider", e); - } - } - /** * Start NameNode. * <p> Added: hadoop/common/branches/fs-encryption/hadoop-hdfs-project/hadoop-hdfs/src/test/java/org/apache/hadoop/hdfs/TestEncryptionZonesAPI.java URL: http://svn.apache.org/viewvc/hadoop/common/branches/fs-encryption/hadoop-hdfs-project/hadoop-hdfs/src/test/java/org/apache/hadoop/hdfs/TestEncryptionZonesAPI.java?rev=1603658&view=auto ============================================================================== --- hadoop/common/branches/fs-encryption/hadoop-hdfs-project/hadoop-hdfs/src/test/java/org/apache/hadoop/hdfs/TestEncryptionZonesAPI.java (added) +++ hadoop/common/branches/fs-encryption/hadoop-hdfs-project/hadoop-hdfs/src/test/java/org/apache/hadoop/hdfs/TestEncryptionZonesAPI.java Wed Jun 18 22:33:50 2014 @@ -0,0 +1,404 @@ +/** + * 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; + +import java.io.File; +import java.io.IOException; +import java.security.NoSuchAlgorithmException; +import java.security.PrivilegedExceptionAction; +import java.util.HashSet; +import java.util.List; +import java.util.Set; +import java.util.UUID; + +import org.apache.hadoop.conf.Configuration; +import org.apache.hadoop.crypto.key.JavaKeyStoreProvider; +import org.apache.hadoop.crypto.key.KeyProvider; +import org.apache.hadoop.crypto.key.KeyProviderFactory; +import org.apache.hadoop.fs.FileSystem; +import org.apache.hadoop.fs.Path; +import org.apache.hadoop.fs.permission.FsPermission; +import org.apache.hadoop.hdfs.client.HdfsAdmin; +import org.apache.hadoop.hdfs.protocol.EncryptionZone; +import org.apache.hadoop.security.AccessControlException; +import org.apache.hadoop.security.UserGroupInformation; +import org.apache.hadoop.test.GenericTestUtils; +import org.junit.After; +import org.junit.Before; +import org.junit.Test; + +import com.google.common.base.Preconditions; + +import static org.junit.Assert.fail; + +public class TestEncryptionZonesAPI { + + private static final Path TEST_PATH = new Path("/test"); + private static final Path TEST_PATH_WITH_CHILD = new Path(TEST_PATH, "foo"); + private static final Path TEST_PATH_WITH_MULTIPLE_CHILDREN = + new Path(TEST_PATH_WITH_CHILD, "baz"); + private static final String TEST_KEYID = "mykeyid"; + private final Configuration conf = new Configuration(); + private MiniDFSCluster cluster; + private static File tmpDir; + private FileSystem fs; + + @Before + public void setUpCluster() throws IOException { + tmpDir = new File(System.getProperty("test.build.data", "target"), + UUID.randomUUID().toString()).getAbsoluteFile(); + conf.set(KeyProviderFactory.KEY_PROVIDER_PATH, + JavaKeyStoreProvider.SCHEME_NAME + "://file" + tmpDir + "/test.jks"); + cluster = new MiniDFSCluster.Builder(conf).numDataNodes(1).build(); + fs = cluster.getFileSystem(); + } + + @After + public void shutDownCluster() { + if (cluster != null) { + cluster.shutdown(); + } + } + + /** Test failure of Create EZ on a directory that doesn't exist. */ + @Test(timeout = 30000) + public void testCreateEncryptionZoneDirectoryDoesntExist() throws Exception { + final HdfsAdmin dfsAdmin = + new HdfsAdmin(FileSystem.getDefaultUri(conf), conf); + try { + dfsAdmin.createEncryptionZone(TEST_PATH, null); + fail("expected /test doesn't exist"); + } catch (IOException e) { + GenericTestUtils.assertExceptionContains("cannot find", e); + } + } + + /** Test failure of Create EZ on a directory which is already an EZ. */ + @Test(timeout = 30000) + public void testCreateEncryptionZoneWhichAlreadyExists() + throws Exception { + final HdfsAdmin dfsAdmin = + new HdfsAdmin(FileSystem.getDefaultUri(conf), conf); + FileSystem.mkdirs(fs, TEST_PATH, new FsPermission((short) 0777)); + dfsAdmin.createEncryptionZone(TEST_PATH, null); + try { + dfsAdmin.createEncryptionZone(TEST_PATH, null); + } catch (IOException e) { + GenericTestUtils.assertExceptionContains("already in an encryption zone", + e); + } + } + + /** Test success of Create EZ in which a key is created. */ + @Test(timeout = 30000) + public void testCreateEncryptionZoneAndGenerateKeyDirectoryEmpty() + throws Exception { + final HdfsAdmin dfsAdmin = + new HdfsAdmin(FileSystem.getDefaultUri(conf), conf); + FileSystem.mkdirs(fs, TEST_PATH, new FsPermission((short) 0777)); + dfsAdmin.createEncryptionZone(TEST_PATH, null); + } + + /** Test failure of Create EZ operation in an existing EZ. */ + @Test(timeout = 30000) + public void testCreateEncryptionZoneInExistingEncryptionZone() + throws Exception { + final HdfsAdmin dfsAdmin = + new HdfsAdmin(FileSystem.getDefaultUri(conf), conf); + FileSystem.mkdirs(fs, TEST_PATH, new FsPermission((short) 0777)); + dfsAdmin.createEncryptionZone(TEST_PATH, null); + FileSystem.mkdirs(fs, TEST_PATH_WITH_CHILD, + new FsPermission((short) 0777)); + try { + dfsAdmin.createEncryptionZone(TEST_PATH_WITH_CHILD, null); + fail("EZ in an EZ"); + } catch (IOException e) { + GenericTestUtils.assertExceptionContains("already in an encryption zone", e); + } + } + + /** Test failure of creating an EZ using a non-empty directory. */ + @Test(timeout = 30000) + public void testCreateEncryptionZoneAndGenerateKeyDirectoryNotEmpty() + throws Exception { + final HdfsAdmin dfsAdmin = + new HdfsAdmin(FileSystem.getDefaultUri(conf), conf); + FileSystem.mkdirs(fs, TEST_PATH, new FsPermission((short) 0777)); + FileSystem.create(fs, new Path("/test/foo"), + new FsPermission((short) 0777)); + try { + dfsAdmin.createEncryptionZone(TEST_PATH, null); + fail("expected key doesn't exist"); + } catch (IOException e) { + GenericTestUtils.assertExceptionContains("create an encryption zone", e); + } + } + + /** Test failure of creating an EZ passing a key that doesn't exist. */ + @Test(timeout = 30000) + public void testCreateEncryptionZoneKeyDoesntExist() throws Exception { + final HdfsAdmin dfsAdmin = + new HdfsAdmin(FileSystem.getDefaultUri(conf), conf); + try { + dfsAdmin.createEncryptionZone(TEST_PATH, TEST_KEYID); + fail("expected key doesn't exist"); + } catch (IOException e) { + GenericTestUtils.assertExceptionContains("doesn't exist.", e); + } + final List<EncryptionZone> zones = dfsAdmin.listEncryptionZones(); + Preconditions.checkState(zones.size() == 0, "More than one zone found?"); + } + + /** Test success of creating an EZ when they key exists. */ + @Test(timeout = 30000) + public void testCreateEncryptionZoneKeyExist() throws Exception { + final HdfsAdmin dfsAdmin = + new HdfsAdmin(FileSystem.getDefaultUri(conf), conf); + FileSystem.mkdirs(fs, TEST_PATH, new FsPermission((short) 0777)); + createAKey(TEST_KEYID); + dfsAdmin.createEncryptionZone(TEST_PATH, TEST_KEYID); + final List<EncryptionZone> zones = dfsAdmin.listEncryptionZones(); + Preconditions.checkState(zones.size() == 1, "More than one zone found?"); + final EncryptionZone ez = zones.get(0); + GenericTestUtils.assertMatches(ez.toString(), + "EncryptionZone \\[path=/test, keyId="); + } + + /** Helper function to create a key in the Key Provider. */ + private void createAKey(String keyId) + throws NoSuchAlgorithmException, IOException { + KeyProvider provider = + cluster.getNameNode().getNamesystem().getProvider(); + final KeyProvider.Options options = KeyProvider.options(conf); + provider.createKey(keyId, options); + provider.flush(); + } + + /** Test failure of create/delete encryption zones as a non super user. */ + @Test(timeout = 30000) + public void testCreateAndDeleteEncryptionZoneAsNonSuperUser() + throws Exception { + final HdfsAdmin dfsAdmin = + new HdfsAdmin(FileSystem.getDefaultUri(conf), conf); + + final UserGroupInformation user = UserGroupInformation. + createUserForTesting("user", new String[] { "mygroup" }); + + FileSystem.mkdirs(fs, TEST_PATH, new FsPermission((short) 0700)); + + user.doAs(new PrivilegedExceptionAction<Object>() { + @Override + public Object run() throws Exception { + final HdfsAdmin userAdmin = + new HdfsAdmin(FileSystem.getDefaultUri(conf), conf); + try { + userAdmin.createEncryptionZone(TEST_PATH, null); + fail("createEncryptionZone is superuser-only operation"); + } catch (AccessControlException e) { + GenericTestUtils.assertExceptionContains( + "Superuser privilege is required", e); + } + return null; + } + }); + dfsAdmin.createEncryptionZone(TEST_PATH, null); + + user.doAs(new PrivilegedExceptionAction<Object>() { + @Override + public Object run() throws Exception { + final HdfsAdmin userAdmin = + new HdfsAdmin(FileSystem.getDefaultUri(conf), conf); + try { + userAdmin.deleteEncryptionZone(TEST_PATH); + fail("deleteEncryptionZone is superuser-only operation"); + } catch (AccessControlException e) { + GenericTestUtils.assertExceptionContains( + "Superuser privilege is required", e); + } + return null; + } + }); + } + + /** Test failure of deleting an EZ passing a directory that doesn't exist. */ + @Test(timeout = 30000) + public void testDeleteEncryptionZoneDirectoryDoesntExist() throws Exception { + final HdfsAdmin dfsAdmin = + new HdfsAdmin(FileSystem.getDefaultUri(conf), conf); + try { + dfsAdmin.deleteEncryptionZone(TEST_PATH); + fail("Directory doesn't exist"); + } catch (IOException e) { + GenericTestUtils.assertExceptionContains( + "is not the root of an encryption zone", e); + } + } + + /** Test failure of deleting an EZ which is not empty. */ + @Test(timeout = 30000) + public void testDeleteEncryptionZoneAndGenerateKeyDirectoryNotEmpty() + throws Exception { + final HdfsAdmin dfsAdmin = + new HdfsAdmin(FileSystem.getDefaultUri(conf), conf); + FileSystem.mkdirs(fs, TEST_PATH, new FsPermission((short) 0777)); + dfsAdmin.createEncryptionZone(TEST_PATH, null); + FileSystem.create(fs, new Path("/test/foo"), + new FsPermission((short) 0777)); + try { + dfsAdmin.deleteEncryptionZone(TEST_PATH); + fail("Directory not empty"); + } catch (IOException e) { + GenericTestUtils.assertExceptionContains("non-empty directory", e); + } + } + + /** Test success of deleting an EZ. */ + @Test(timeout = 30000) + public void testDeleteEncryptionZone() + throws Exception { + final HdfsAdmin dfsAdmin = + new HdfsAdmin(FileSystem.getDefaultUri(conf), conf); + FileSystem.mkdirs(fs, TEST_PATH, new FsPermission((short) 0777)); + dfsAdmin.createEncryptionZone(TEST_PATH, null); + List<EncryptionZone> zones = dfsAdmin.listEncryptionZones(); + Preconditions.checkState(zones.size() == 1, "More than one zone found?"); + dfsAdmin.deleteEncryptionZone(TEST_PATH); + zones = dfsAdmin.listEncryptionZones(); + Preconditions.checkState(zones.size() == 0, "More than one zone found?"); + } + + /** + * Test failure of deleting an EZ on a subdir that is not the root of an EZ. + */ + @Test(timeout = 30000) + public void testDeleteEncryptionZoneInExistingEncryptionZone() + throws Exception { + final HdfsAdmin dfsAdmin = + new HdfsAdmin(FileSystem.getDefaultUri(conf), conf); + FileSystem.mkdirs(fs, TEST_PATH, new FsPermission((short) 0777)); + dfsAdmin.createEncryptionZone(TEST_PATH, null); + FileSystem.mkdirs(fs, TEST_PATH_WITH_CHILD, new FsPermission((short) 0777)); + try { + dfsAdmin.deleteEncryptionZone(TEST_PATH_WITH_CHILD); + fail("EZ in an EZ"); + } catch (IOException e) { + GenericTestUtils.assertExceptionContains( + "is not the root of an encryption zone", e); + } + } + + /** + * Test success of creating and deleting an encryption zone a few levels down. + */ + @Test(timeout = 30000) + public void testCreateAndDeleteEncryptionZoneDownAFewLevels() + throws Exception { + final HdfsAdmin dfsAdmin = + new HdfsAdmin(FileSystem.getDefaultUri(conf), conf); + FileSystem.mkdirs(fs, TEST_PATH, new FsPermission((short) 0777)); + dfsAdmin.createEncryptionZone(TEST_PATH, null); + FileSystem.mkdirs(fs, TEST_PATH_WITH_MULTIPLE_CHILDREN, + new FsPermission((short) 0777)); + try { + dfsAdmin.deleteEncryptionZone(TEST_PATH_WITH_MULTIPLE_CHILDREN); + fail("EZ in an EZ"); + } catch (IOException e) { + GenericTestUtils.assertExceptionContains( + "is not the root of an encryption zone", e); + } + final List<EncryptionZone> zones = dfsAdmin.listEncryptionZones(); + Preconditions.checkState(zones.size() == 1, "More than one zone found?"); + final EncryptionZone ez = zones.get(0); + GenericTestUtils.assertMatches(ez.toString(), + "EncryptionZone \\[path=/test, keyId="); + } + + /** Test failure of creating an EZ using a non-empty directory. */ + @Test(timeout = 30000) + public void testCreateFileInEncryptionZone() throws Exception { + final HdfsAdmin dfsAdmin = + new HdfsAdmin(FileSystem.getDefaultUri(conf), conf); + FileSystem.mkdirs(fs, TEST_PATH, new FsPermission((short) 0777)); + dfsAdmin.createEncryptionZone(TEST_PATH, null); + FileSystem.create(fs, TEST_PATH_WITH_CHILD, new FsPermission((short) 0777)); + + final List<EncryptionZone> zones = dfsAdmin.listEncryptionZones(); + final EncryptionZone ez = zones.get(0); + GenericTestUtils.assertMatches(ez.toString(), + "EncryptionZone \\[path=/test, keyId="); + } + + /** Test listing encryption zones. */ + @Test(timeout = 30000) + public void testListEncryptionZones() throws Exception { + final HdfsAdmin dfsAdmin = + new HdfsAdmin(FileSystem.getDefaultUri(conf), conf); + final int N_EZs = 5; + final Set<String> ezPathNames = new HashSet<String>(N_EZs); + for (int i = 0; i < N_EZs; i++) { + final Path p = new Path(TEST_PATH, "" + i); + ezPathNames.add(p.toString()); + FileSystem.mkdirs(fs, p, new FsPermission((short) 0777)); + dfsAdmin.createEncryptionZone(p, null); + } + + final List<EncryptionZone> zones = dfsAdmin.listEncryptionZones(); + Preconditions.checkState(zones.size() == N_EZs, "wrong number of EZs returned"); + for (EncryptionZone z : zones) { + final String ezPathName = z.getPath(); + Preconditions.checkState(ezPathNames.remove( + ezPathName), "Path " + ezPathName + " not returned from listEZ"); + } + Preconditions.checkState(ezPathNames.size() == 0); + } + + /** Test listing encryption zones as a non super user. */ + @Test(timeout = 30000) + public void testListEncryptionZonesAsNonSuperUser() throws Exception { + final HdfsAdmin dfsAdmin = + new HdfsAdmin(FileSystem.getDefaultUri(conf), conf); + + final UserGroupInformation user = UserGroupInformation. + createUserForTesting("user", new String[] {"mygroup"}); + + final Path TEST_PATH_SUPERUSER_ONLY = new Path(TEST_PATH, "superuseronly"); + final Path TEST_PATH_ALL = new Path(TEST_PATH, "accessall"); + + FileSystem.mkdirs(fs, TEST_PATH_SUPERUSER_ONLY, + new FsPermission((short) 0700)); + dfsAdmin.createEncryptionZone(TEST_PATH_SUPERUSER_ONLY, null); + FileSystem.mkdirs(fs, TEST_PATH_ALL, + new FsPermission((short) 0707)); + dfsAdmin.createEncryptionZone(TEST_PATH_ALL, null); + + user.doAs(new PrivilegedExceptionAction<Object>() { + @Override + public Object run() throws Exception { + final HdfsAdmin userAdmin = + new HdfsAdmin(FileSystem.getDefaultUri(conf), conf); + try { + final List<EncryptionZone> zones = userAdmin.listEncryptionZones(); + } catch (AccessControlException e) { + GenericTestUtils.assertExceptionContains( + "Superuser privilege is required", e); + } + return null; + } + }); + } +} Propchange: hadoop/common/branches/fs-encryption/hadoop-hdfs-project/hadoop-hdfs/src/test/java/org/apache/hadoop/hdfs/TestEncryptionZonesAPI.java ------------------------------------------------------------------------------ svn:eol-style = native