/**
 * 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.BufferedReader;
import java.io.File;
import java.io.FileWriter;
import java.io.IOException;
import java.io.InputStreamReader;
import java.io.OutputStreamWriter;
import java.io.PrintWriter;
import java.io.Writer;
import java.net.URI;
import java.security.NoSuchAlgorithmException;
import java.security.PrivilegedAction;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;

import org.apache.hadoop.conf.Configuration;
import org.apache.hadoop.crypto.key.kms.KMSClientProvider;
import org.apache.hadoop.crypto.key.kms.server.KMSConfiguration;
import org.apache.hadoop.crypto.key.kms.server.KeyAuthorizationKeyProvider;
import org.apache.hadoop.crypto.key.kms.server.MiniKMS;
import org.apache.hadoop.fs.CommonConfigurationKeysPublic;
import org.apache.hadoop.fs.FSDataInputStream;
import org.apache.hadoop.fs.FSDataOutputStream;
import org.apache.hadoop.fs.FileSystemTestHelper;
import org.apache.hadoop.fs.Path;
import org.apache.hadoop.security.UserGroupInformation;
import org.apache.hadoop.security.authorize.ProxyUsers;
import org.junit.Assert;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertTrue;
import org.junit.BeforeClass;
import org.junit.Test;

/**
 * This class tests the ACLs system through the full code path.  It overlaps
 * slightly with the ACL tests in common, but the approach is more holistic.
 *
 * <b>NOTE:</b> Because of the mechanics of JAXP, when the KMS config files are
 * written to disk, a config param with a blank value ("") will be written in a
 * way that the KMS will read as unset, which is different from blank. For this
 * reason, when testing the effects of blank config params, this test class
 * sets the values of those config params to a space (" ").  A whitespace value
 * will be preserved by JAXP when writing out the config files and will be
 * interpreted by KMS as a blank value. (The KMS strips whitespace from ACL
 * values before interpreting them.)
 */
public class TestAclsEndToEnd {
  private static final Log LOG =
      LogFactory.getLog(TestAclsEndToEnd.class.getName());
  private static final String TEXT =
      "The blue zone is for loading and unloading only. "
      + "Please park in the red zone.";
  private static final Path ZONE1 = new Path("/tmp/BLUEZONE");
  private static final Path ZONE2 = new Path("/tmp/REDZONE");
  private static final Path ZONE3 = new Path("/tmp/LOADINGZONE");
  private static final Path ZONE4 = new Path("/tmp/UNLOADINGZONE");
  private static final Path FILE1 = new Path(ZONE1, "file1");
  private static final Path FILE1A = new Path(ZONE1, "file1a");
  private static final Path FILE2 = new Path(ZONE2, "file2");
  private static final Path FILE3 = new Path(ZONE3, "file3");
  private static final Path FILE4 = new Path(ZONE4, "file4");
  private static final String KEY1 = "key1";
  private static final String KEY2 = "key2";
  private static final String KEY3 = "key3";
  private static UserGroupInformation realUgi;
  private static String realUser;

  private MiniKMS miniKMS;
  private File kmsDir;
  private MiniDFSCluster cluster;
  private DistributedFileSystem fs;

  @BeforeClass
  public static void captureUser() throws IOException {
    realUgi = UserGroupInformation.getCurrentUser();
    realUser = System.getProperty("user.name");
  }

  /**
   * Extract the URI for the miniKMS.
   *
   * @return the URI for the miniKMS
   */
  private String getKeyProviderURI() {
    return KMSClientProvider.SCHEME_NAME + "://" +
        miniKMS.getKMSUrl().toExternalForm().replace("://", "@");
  }

  /**
   * Write out the config files needed by the miniKMS.  The miniKMS doesn't
   * provide a way to set the configs directly, so the only way to pass config
   * parameters is to write them out into config files.
   *
   * @param confDir the directory into which to write the configs
   * @param conf the config to write.
   * @throws IOException
   */
  private void writeConf(File confDir, Configuration conf)
      throws IOException {
    URI keystore = new Path(kmsDir.getAbsolutePath(), "kms.keystore").toUri();

    conf.set(KMSConfiguration.KEY_PROVIDER_URI, "jceks://file@" + keystore);
    conf.set("hadoop.kms.authentication.type", "simple");

    Writer writer =
        new FileWriter(new File(confDir, KMSConfiguration.KMS_SITE_XML));
    conf.writeXml(writer);
    writer.close();

    writer = new FileWriter(new File(confDir, KMSConfiguration.KMS_ACLS_XML));
    conf.writeXml(writer);
    writer.close();

    //create empty core-site.xml
    writer = new FileWriter(new File(confDir, "core-site.xml"));
    new Configuration(false).writeXml(writer);
    writer.close();
  }

  /**
   * Setup a fresh miniKMS and miniDFS.
   *
   * @param conf the configuration to use for both the miniKMS and miniDFS
   * @throws Exception thrown if setup fails
   */
  private void setup(Configuration conf) throws Exception {
    setup(conf, true, true);
  }

  /**
   * Setup a fresh miniDFS and a miniKMS.  The resetKms parameter controls
   * whether the miniKMS will start fresh or reuse the existing data.
   *
   * @param conf the configuration to use for both the miniKMS and miniDFS
   * @param resetKms whether to start a fresh miniKMS
   * @throws Exception thrown if setup fails
   */
  private void setup(Configuration conf, boolean resetKms) throws Exception {
    setup(conf, resetKms, true);
  }

  /**
   * Setup a miniDFS and miniKMS.  The resetKms and resetDfs parameters control
   * whether the services will start fresh or reuse the existing data.
   *
   * @param conf the configuration to use for both the miniKMS and miniDFS
   * @param resetKms whether to start a fresh miniKMS
   * @param resetDfs whether to start a fresh miniDFS
   * @throws Exception thrown if setup fails
   */
  private void setup(Configuration conf, boolean resetKms, boolean resetDfs)
          throws Exception {
    if (resetKms) {
      FileSystemTestHelper fsHelper = new FileSystemTestHelper();

      kmsDir = new File(fsHelper.getTestRootDir()).getAbsoluteFile();

      Assert.assertTrue(kmsDir.mkdirs());
    }

    writeConf(kmsDir, conf);

    MiniKMS.Builder miniKMSBuilder = new MiniKMS.Builder();

    miniKMS = miniKMSBuilder.setKmsConfDir(kmsDir).build();
    miniKMS.start();

    conf = new HdfsConfiguration();

    // Set up java key store
    conf.set(ProxyUsers.CONF_HADOOP_PROXYUSER + "." + realUser + ".users",
        "keyadmin,hdfs,user");
    conf.set(ProxyUsers.CONF_HADOOP_PROXYUSER + "." + realUser + ".hosts",
        "*");
    conf.set(CommonConfigurationKeysPublic.HADOOP_SECURITY_KEY_PROVIDER_PATH,
        getKeyProviderURI());
    conf.setBoolean(DFSConfigKeys.DFS_NAMENODE_DELEGATION_TOKEN_ALWAYS_USE_KEY,
        true);

    MiniDFSCluster.Builder clusterBuilder = new MiniDFSCluster.Builder(conf);

    cluster = clusterBuilder.numDataNodes(1).format(resetDfs).build();
    fs = cluster.getFileSystem();
  }

  /**
   * Stop the miniKMS and miniDFS.
   */
  private void teardown() {
    // Restore login user
    UserGroupInformation.setLoginUser(realUgi);

    if (cluster != null) {
      cluster.shutdown();
    }

    miniKMS.stop();
  }

  /**
   * Return a new {@link Configuration} with KMS ACLs appropriate to pass the
   * full ACL test in {@link #doFullAclTest()} set.
   *
   * @param hdfsUgi the hdfs user
   * @param keyadminUgi the keyadmin user
   * @return the configuration
   */
  private static Configuration getBaseConf(UserGroupInformation hdfsUgi,
      UserGroupInformation keyadminUgi) {
    Configuration conf = new Configuration();

    conf.set(KMSConfiguration.CONFIG_PREFIX + "acl.CREATE",
        keyadminUgi.getUserName());
    conf.set(KMSConfiguration.CONFIG_PREFIX + "acl.DELETE",
        keyadminUgi.getUserName());
    conf.set(KMSConfiguration.CONFIG_PREFIX + "acl.ROLLOVER",
        keyadminUgi.getUserName());
    conf.set(KMSConfiguration.CONFIG_PREFIX + "acl.GET", " ");
    conf.set(KMSConfiguration.CONFIG_PREFIX + "acl.GET_KEYS",
        keyadminUgi.getUserName());
    conf.set(KMSConfiguration.CONFIG_PREFIX + "acl.GET_METADATA",
        hdfsUgi.getUserName());
    conf.set(KMSConfiguration.CONFIG_PREFIX + "acl.SET_KEY_MATERIAL", " ");
    conf.set(KMSConfiguration.CONFIG_PREFIX + "acl.GENERATE_EEK",
        hdfsUgi.getUserName());
    conf.set(KMSConfiguration.CONFIG_PREFIX + "acl.DECRYPT_EEK", "*");

    return conf;
  }

  /**
   * Set the recommended blacklists.
   *
   * @param hdfsUgi the hdfs user
   */
  private static void setBlacklistAcls(Configuration conf,
      UserGroupInformation hdfsUgi) {

    conf.set(KMSConfiguration.CONFIG_PREFIX + "blacklist.CREATE",
        hdfsUgi.getUserName());
    conf.set(KMSConfiguration.CONFIG_PREFIX + "blacklist.DELETE",
        hdfsUgi.getUserName());
    conf.set(KMSConfiguration.CONFIG_PREFIX + "blacklist.ROLLOVER",
        hdfsUgi.getUserName());
    conf.set(KMSConfiguration.CONFIG_PREFIX + "blacklist.GET", "*");
    conf.set(KMSConfiguration.CONFIG_PREFIX + "blacklist.SET_KEY_MATERIAL",
        "*");
    conf.set(KMSConfiguration.CONFIG_PREFIX + "blacklist.DECRYPT_EEK",
        hdfsUgi.getUserName());
  }

  /**
   * Set the key ACLs appropriate to pass the full ACL test in
   * {@link #doFullAclTest()} using the specified prefix.  The prefix should
   * either be "whitelist.key.acl." or "key.acl.key1.".
   *
   * @param conf the configuration
   * @param prefix the ACL prefix
   * @param hdfsUgi the hdfs user
   * @param keyadminUgi the keyadmin user
   * @param userUgi the normal user
   */
  private static void setKeyAcls(Configuration conf, String prefix,
      UserGroupInformation hdfsUgi,
      UserGroupInformation keyadminUgi,
      UserGroupInformation userUgi) {

    conf.set(prefix + "MANAGEMENT", keyadminUgi.getUserName());
    conf.set(prefix + "READ", hdfsUgi.getUserName());
    conf.set(prefix + "GENERATE_EEK", hdfsUgi.getUserName());
    conf.set(KeyAuthorizationKeyProvider.KEY_ACL + KEY1 + ".DECRYPT_EEK",
        userUgi.getUserName());
  }

  /**
   * Test the full life cycle of a key using a config with whitelist key ACLs.
   * The configuration used is the correct configuration to pass the full ACL
   * test in {@link #doFullAclTest()}.
   *
   * @throws Exception thrown on test failure
   */
  @Test
  public void testGoodWithWhitelist() throws Exception {
    UserGroupInformation hdfsUgi =
        UserGroupInformation.createProxyUserForTesting("hdfs",
          realUgi, new String[] {"supergroup"});
    UserGroupInformation keyadminUgi =
        UserGroupInformation.createProxyUserForTesting("keyadmin",
          realUgi, new String[] {"keyadmin"});
    UserGroupInformation userUgi =
        UserGroupInformation.createProxyUserForTesting("user",
          realUgi,  new String[] {"staff"});

    Configuration conf = getBaseConf(hdfsUgi, keyadminUgi);

    setBlacklistAcls(conf, hdfsUgi);
    setKeyAcls(conf, KMSConfiguration.WHITELIST_KEY_ACL_PREFIX,
        hdfsUgi, keyadminUgi, userUgi);
    doFullAclTest(conf, hdfsUgi, keyadminUgi, userUgi);
  }

  /**
   * Test the full life cycle of a key using a config with key ACLs.
   * The configuration used is the correct configuration to pass the full ACL
   * test in {@link #doFullAclTest()}.
   *
   * @throws Exception thrown on test failure
   */
  @Test
  public void testGoodWithKeyAcls() throws Exception {
    UserGroupInformation hdfsUgi =
        UserGroupInformation.createProxyUserForTesting("hdfs",
          realUgi, new String[] {"supergroup"});
    UserGroupInformation keyadminUgi =
        UserGroupInformation.createProxyUserForTesting("keyadmin",
          realUgi, new String[] {"keyadmin"});
    UserGroupInformation userUgi =
        UserGroupInformation.createProxyUserForTesting("user",
          realUgi,  new String[] {"staff"});
    Configuration conf = getBaseConf(hdfsUgi, keyadminUgi);

    setBlacklistAcls(conf, hdfsUgi);
    setKeyAcls(conf, KeyAuthorizationKeyProvider.KEY_ACL + KEY1 + ".",
        hdfsUgi, keyadminUgi, userUgi);
    doFullAclTest(conf, hdfsUgi, keyadminUgi, userUgi);
  }

  /**
   * Test the full life cycle of a key using a config with whitelist key ACLs
   * and without blacklist ACLs.  The configuration used is the correct
   * configuration to pass the full ACL test in {@link #doFullAclTest()}.
   *
   * @throws Exception thrown on test failure
   */
  @Test
  public void testGoodWithWhitelistWithoutBlacklist() throws Exception {
    UserGroupInformation hdfsUgi =
        UserGroupInformation.createProxyUserForTesting("hdfs",
          realUgi, new String[] {"supergroup"});
    UserGroupInformation keyadminUgi =
        UserGroupInformation.createProxyUserForTesting("keyadmin",
          realUgi, new String[] {"keyadmin"});
    UserGroupInformation userUgi =
        UserGroupInformation.createProxyUserForTesting("user",
          realUgi,  new String[] {"staff"});
    Configuration conf = getBaseConf(hdfsUgi, keyadminUgi);

    setKeyAcls(conf, KMSConfiguration.WHITELIST_KEY_ACL_PREFIX,
        hdfsUgi, keyadminUgi, userUgi);
    doFullAclTest(conf, hdfsUgi, keyadminUgi, userUgi);
  }

  /**
   * Test the full life cycle of a key using a config with whitelist key ACLs
   * and without blacklist ACLs. The configuration used is the correct
   * configuration to pass the full ACL test in {@link #doFullAclTest()}.
   *
   * @throws Exception thrown on test failure
   */
  @Test
  public void testGoodWithKeyAclsWithoutBlacklist() throws Exception {
    UserGroupInformation hdfsUgi =
        UserGroupInformation.createProxyUserForTesting("hdfs",
          realUgi, new String[] {"supergroup"});
    UserGroupInformation keyadminUgi =
        UserGroupInformation.createProxyUserForTesting("keyadmin",
          realUgi, new String[] {"keyadmin"});
    UserGroupInformation userUgi =
        UserGroupInformation.createProxyUserForTesting("user",
          realUgi,  new String[] {"staff"});
    Configuration conf = getBaseConf(hdfsUgi, keyadminUgi);

    setKeyAcls(conf, KeyAuthorizationKeyProvider.KEY_ACL + KEY1 + ".",
        hdfsUgi, keyadminUgi, userUgi);
    doFullAclTest(conf, hdfsUgi, keyadminUgi, userUgi);
  }

  /**
   * Run a full key life cycle test using the provided configuration and users.
   *
   * @param conf the configuration
   * @param hdfs the user to use as the hdfs user
   * @param keyadmin the user to use as the keyadmin user
   * @param user the user to use as the normal user
   * @throws Exception thrown if there is a test failure
   */
  private void doFullAclTest(final Configuration conf,
      final UserGroupInformation hdfsUgi,
      final UserGroupInformation keyadminUgi,
      final UserGroupInformation userUgi) throws Exception {

    try {
      setup(conf);

      // Create a test key
      assertTrue("Exception during creation of key " + KEY1 + " by "
          + keyadminUgi.getUserName(), createKey(keyadminUgi, KEY1, conf));

      // Fail to create a test key
      assertFalse("Allowed creation of key " + KEY2 + " by "
          + hdfsUgi.getUserName(), createKey(hdfsUgi, KEY2, conf));
      assertFalse("Allowed creation of key " + KEY2 + " by "
          + userUgi.getUserName(), createKey(userUgi, KEY2, conf));

      // Create a directory and chown it to the normal user.
      fs.mkdirs(ZONE1);
      fs.setOwner(ZONE1, userUgi.getUserName(),
          userUgi.getPrimaryGroupName());

      // Create an EZ
      assertTrue("Exception during creation of EZ " + ZONE1 + " by "
          + hdfsUgi.getUserName() + " using key " + KEY1,
            createEncryptionZone(hdfsUgi, KEY1, ZONE1));

      // Fail to create an EZ
      assertFalse("Allowed creation of EZ " + ZONE2 + " by "
          + keyadminUgi.getUserName() + " using key " + KEY1,
            createEncryptionZone(keyadminUgi, KEY1, ZONE2));
      assertFalse("Allowed creation of EZ " + ZONE2 + " by "
          + userUgi.getUserName() + " using key " + KEY1,
            createEncryptionZone(userUgi, KEY1, ZONE2));

      // Create a file in the zone
      assertTrue("Exception during creation of file " + FILE1 + " by "
          + userUgi.getUserName(), createFile(userUgi, FILE1, TEXT));

      // Fail to create a file in the zone
      assertFalse("Allowed creation of file " + FILE1A + " by "
          + hdfsUgi.getUserName(), createFile(hdfsUgi, FILE1A, TEXT));
      assertFalse("Allowed creation of file " + FILE1A + " by "
          + keyadminUgi.getUserName(), createFile(keyadminUgi, FILE1A, TEXT));

      // Read a file in the zone
      assertTrue("Exception while reading file " + FILE1 + " by "
          + userUgi.getUserName(), compareFile(userUgi, FILE1, TEXT));

      // Fail to read a file in the zone
      assertFalse("Allowed reading of file " + FILE1 + " by "
          + hdfsUgi.getUserName(), compareFile(hdfsUgi, FILE1, TEXT));
      assertFalse("Allowed reading of file " + FILE1 + " by "
          + keyadminUgi.getUserName(), compareFile(keyadminUgi, FILE1, TEXT));

      // Remove the zone
      fs.delete(ZONE1, true);

      // Fail to remove the key
      assertFalse("Allowed deletion of file " + FILE1 + " by "
          + hdfsUgi.getUserName(), deleteKey(hdfsUgi, KEY1));
      assertFalse("Allowed deletion of file " + FILE1 + " by "
          + userUgi.getUserName(), deleteKey(userUgi, KEY1));

      // Remove
      assertTrue("Exception during deletion of file " + FILE1 + " by "
          + keyadminUgi.getUserName(), deleteKey(keyadminUgi, KEY1));
    } finally {
      fs.delete(ZONE1, true);
      fs.delete(ZONE2, true);
      teardown();
    }
  }

  /**
   * Test that key creation is correctly governed by ACLs.
   * @throws Exception thrown if setup fails
   */
  @Test
  public void testCreateKey() throws Exception {
    Configuration conf = new Configuration();

    // Correct config with whitelist ACL
    conf.set(KMSConfiguration.CONFIG_PREFIX + "acl.CREATE",
        realUgi.getUserName());
    conf.set(KMSConfiguration.WHITELIST_KEY_ACL_PREFIX + "MANAGEMENT",
        realUgi.getUserName());

    try {
      setup(conf);

      assertTrue("Exception during key creation with correct config"
          + " using whitelist key ACLs", createKey(realUgi, KEY1, conf));
    } finally {
      teardown();
    }

    conf = new Configuration();

    // Correct config with default ACL
    conf.set(KMSConfiguration.CONFIG_PREFIX + "acl.CREATE",
        realUgi.getUserName());
    conf.set(KMSConfiguration.DEFAULT_KEY_ACL_PREFIX + "MANAGEMENT",
        realUgi.getUserName());

    try {
      setup(conf);

      assertTrue("Exception during key creation with correct config"
          + " using default key ACLs", createKey(realUgi, KEY2, conf));
    } finally {
      teardown();
    }

    conf = new Configuration();

    // Denied because of blacklist
    conf.set(KMSConfiguration.CONFIG_PREFIX + "acl.CREATE",
        realUgi.getUserName());
    conf.set(KMSConfiguration.CONFIG_PREFIX + "blacklist.CREATE",
        realUgi.getUserName());
    conf.set(KMSConfiguration.WHITELIST_KEY_ACL_PREFIX + "MANAGEMENT",
        realUgi.getUserName());

    try {
      setup(conf);

      assertFalse("Allowed key creation with blacklist for CREATE",
          createKey(realUgi, KEY3, conf));
    } finally {
      teardown();
    }

    conf = new Configuration();

    // Missing KMS ACL
    conf.set(KMSConfiguration.CONFIG_PREFIX + "acl.CREATE", " ");
    conf.set(KMSConfiguration.WHITELIST_KEY_ACL_PREFIX + "MANAGEMENT",
        realUgi.getUserName());

    try {
      setup(conf);

      assertFalse("Allowed key creation without CREATE KMS ACL",
          createKey(realUgi, KEY3, conf));
    } finally {
      teardown();
    }

    conf = new Configuration();

    // Missing key ACL
    conf.set(KMSConfiguration.CONFIG_PREFIX + "acl.CREATE",
        realUgi.getUserName());

    try {
      setup(conf);

      assertFalse("Allowed key creation without MANAGMENT key ACL",
          createKey(realUgi, KEY3, conf));
    } finally {
      teardown();
    }

    conf = new Configuration();

    // Denied because the key ACL set ignores the default ACL set for key3
    conf.set(KMSConfiguration.CONFIG_PREFIX + "acl.CREATE",
        realUgi.getUserName());
    conf.set(KMSConfiguration.DEFAULT_KEY_ACL_PREFIX + "MANAGEMENT",
        realUgi.getUserName());
    conf.set(KeyAuthorizationKeyProvider.KEY_ACL + KEY3 + ".DECRYPT_EEK",
        realUgi.getUserName());

    try {
      setup(conf);

      assertFalse("Allowed key creation when default key ACL should have been"
          + " overridden by key ACL", createKey(realUgi, KEY3, conf));
    } finally {
      teardown();
    }

    conf = new Configuration();

    // Allowed because the default setting for KMS ACLs is fully permissive
    conf.set(KMSConfiguration.WHITELIST_KEY_ACL_PREFIX + "MANAGEMENT",
        realUgi.getUserName());

    try {
      setup(conf);

      assertTrue("Exception during key creation with default KMS ACLs",
          createKey(realUgi, KEY3, conf));
    } finally {
      teardown();
    }
  }

  /**
   * Test that zone creation is correctly governed by ACLs.
   * @throws Exception thrown if setup fails
   */
  @Test
  public void testCreateEncryptionZone() throws Exception {
    Configuration conf = new Configuration();

    // Create a test key
    conf.set(KMSConfiguration.CONFIG_PREFIX + "acl.CREATE",
        realUgi.getUserName());
    conf.set(KMSConfiguration.WHITELIST_KEY_ACL_PREFIX + "MANAGEMENT",
        realUgi.getUserName());

    try {
      setup(conf);

      assertTrue("Exception during key creation",
          createKey(realUgi, KEY1, conf));
    } finally {
      teardown();
    }

    // We tear everything down and then restart it with the ACLs we want to
    // test so that there's no contamination from the ACLs needed for setup.
    // To make that work, we have to tell the setup() method not to create a
    // new KMS directory.
    conf = new Configuration();

    // Correct config with whitelist ACL
    conf.set(KMSConfiguration.CONFIG_PREFIX + "acl.GET_METADATA",
        realUgi.getUserName());
    conf.set(KMSConfiguration.CONFIG_PREFIX + "acl.GENERATE_EEK",
        realUgi.getUserName());
    conf.set(KMSConfiguration.WHITELIST_KEY_ACL_PREFIX + "READ",
        realUgi.getUserName());
    conf.set(KMSConfiguration.WHITELIST_KEY_ACL_PREFIX + "GENERATE_EEK",
        realUgi.getUserName());

    try {
      setup(conf, false);

      fs.mkdirs(ZONE1);

      assertTrue("Exception during zone creation with correct config using"
          + " whitelist key ACLs", createEncryptionZone(realUgi, KEY1, ZONE1));
    } finally {
      fs.delete(ZONE1, true);
      teardown();
    }

    conf = new Configuration();

    // Correct config with default ACL
    conf.set(KMSConfiguration.CONFIG_PREFIX + "acl.GET_METADATA",
        realUgi.getUserName());
    conf.set(KMSConfiguration.CONFIG_PREFIX + "acl.GENERATE_EEK",
        realUgi.getUserName());
    conf.set(KMSConfiguration.DEFAULT_KEY_ACL_PREFIX + "READ",
        realUgi.getUserName());
    conf.set(KMSConfiguration.DEFAULT_KEY_ACL_PREFIX + "GENERATE_EEK",
        realUgi.getUserName());

    try {
      setup(conf, false);

      fs.mkdirs(ZONE2);

      assertTrue("Exception during zone creation with correct config using"
          + " default key ACLs", createEncryptionZone(realUgi, KEY1, ZONE2));
    } finally {
      fs.delete(ZONE2, true);
      teardown();
    }

    conf = new Configuration();

    // Denied because the key ACL set ignores the default ACL set for key1
    conf.set(KMSConfiguration.CONFIG_PREFIX + "acl.GET_METADATA",
        realUgi.getUserName());
    conf.set(KMSConfiguration.CONFIG_PREFIX + "acl.GENERATE_EEK",
        realUgi.getUserName());
    conf.set(KMSConfiguration.DEFAULT_KEY_ACL_PREFIX + "READ",
        realUgi.getUserName());
    conf.set(KMSConfiguration.DEFAULT_KEY_ACL_PREFIX + "GENERATE_EEK",
        realUgi.getUserName());
    conf.set(KeyAuthorizationKeyProvider.KEY_ACL + KEY1 + ".DECRYPT_EEK",
        realUgi.getUserName());

    try {
      setup(conf, false);

      fs.mkdirs(ZONE3);

      assertFalse("Allowed creation of zone when default key ACLs should have"
          + " been overridden by key ACL",
            createEncryptionZone(realUgi, KEY1, ZONE3));
    } finally {
      fs.delete(ZONE3, true);
      teardown();
    }

    conf = new Configuration();

    // Correct config with blacklist
    conf.set(KMSConfiguration.CONFIG_PREFIX + "acl.GET_METADATA",
        realUgi.getUserName());
    conf.set(KMSConfiguration.CONFIG_PREFIX + "acl.GENERATE_EEK",
        realUgi.getUserName());
    conf.set(KMSConfiguration.CONFIG_PREFIX + "blacklist.GET_METADATA",
        realUgi.getUserName());
    conf.set(KMSConfiguration.WHITELIST_KEY_ACL_PREFIX + "READ",
        realUgi.getUserName());
    conf.set(KMSConfiguration.WHITELIST_KEY_ACL_PREFIX + "GENERATE_EEK",
        realUgi.getUserName());

    try {
      setup(conf, false);

      fs.mkdirs(ZONE3);

      assertFalse("Allowed zone creation of zone with blacklisted GET_METADATA",
          createEncryptionZone(realUgi, KEY1, ZONE3));
    } finally {
      fs.delete(ZONE3, true);
      teardown();
    }

    conf = new Configuration();

    // Correct config with blacklist
    conf.set(KMSConfiguration.CONFIG_PREFIX + "acl.GET_METADATA",
        realUgi.getUserName());
    conf.set(KMSConfiguration.CONFIG_PREFIX + "acl.GENERATE_EEK",
        realUgi.getUserName());
    conf.set(KMSConfiguration.CONFIG_PREFIX + "blacklist.GENERATE_EEK",
        realUgi.getUserName());
    conf.set(KMSConfiguration.WHITELIST_KEY_ACL_PREFIX + "READ",
        realUgi.getUserName());
    conf.set(KMSConfiguration.WHITELIST_KEY_ACL_PREFIX + "GENERATE_EEK",
        realUgi.getUserName());

    try {
      setup(conf, false);

      fs.mkdirs(ZONE3);

      assertFalse("Allowed zone creation of zone with blacklisted GENERATE_EEK",
          createEncryptionZone(realUgi, KEY1, ZONE3));
    } finally {
      fs.delete(ZONE3, true);
      teardown();
    }

    conf = new Configuration();

    // Missing KMS ACL but works because defaults for KMS ACLs are fully
    // permissive
    conf.set(KMSConfiguration.WHITELIST_KEY_ACL_PREFIX + "READ",
        realUgi.getUserName());
    conf.set(KMSConfiguration.WHITELIST_KEY_ACL_PREFIX + "GENERATE_EEK",
        realUgi.getUserName());

    try {
      setup(conf, false);

      fs.mkdirs(ZONE3);

      assertTrue("Exception during zone creation with default KMS ACLs",
          createEncryptionZone(realUgi, KEY1, ZONE3));
    } finally {
      fs.delete(ZONE3, true);
      teardown();
    }

    conf = new Configuration();

    // Missing GET_METADATA KMS ACL
    conf.set(KMSConfiguration.CONFIG_PREFIX + "acl.GET_METADATA", " ");
    conf.set(KMSConfiguration.CONFIG_PREFIX + "acl.GENERATE_EEK",
        realUgi.getUserName());
    conf.set(KMSConfiguration.WHITELIST_KEY_ACL_PREFIX + "READ",
        realUgi.getUserName());
    conf.set(KMSConfiguration.WHITELIST_KEY_ACL_PREFIX + "GENERATE_EEK",
        realUgi.getUserName());

    try {
      setup(conf, false);

      fs.mkdirs(ZONE4);

      assertFalse("Allowed zone creation without GET_METADATA KMS ACL",
          createEncryptionZone(realUgi, KEY1, ZONE4));
    } finally {
      fs.delete(ZONE4, true);
      teardown();
    }

    conf = new Configuration();

    // Missing GET_METADATA KMS ACL
    conf.set(KMSConfiguration.CONFIG_PREFIX + "acl.GET_METADATA",
        realUgi.getUserName());
    conf.set(KMSConfiguration.CONFIG_PREFIX + "acl.GENERATE_EEK", " ");
    conf.set(KMSConfiguration.WHITELIST_KEY_ACL_PREFIX + "READ",
        realUgi.getUserName());
    conf.set(KMSConfiguration.WHITELIST_KEY_ACL_PREFIX + "GENERATE_EEK",
        realUgi.getUserName());

    try {
      setup(conf, false);

      fs.mkdirs(ZONE4);

      assertFalse("Allowed zone creation without GENERATE_EEK KMS ACL",
          createEncryptionZone(realUgi, KEY1, ZONE4));
    } finally {
      fs.delete(ZONE4, true);
      teardown();
    }

    conf = new Configuration();

    // Missing READ key ACL
    conf.set(KMSConfiguration.CONFIG_PREFIX + "acl.GET_METADATA",
        realUgi.getUserName());
    conf.set(KMSConfiguration.CONFIG_PREFIX + "acl.GENERATE_EEK",
        realUgi.getUserName());
    conf.set(KMSConfiguration.WHITELIST_KEY_ACL_PREFIX + "GENERATE_EEK",
        realUgi.getUserName());

    try {
      setup(conf, false);

      fs.mkdirs(ZONE4);

      assertFalse("Allowed zone creation without READ ACL",
          createEncryptionZone(realUgi, KEY1, ZONE4));
    } finally {
      fs.delete(ZONE4, true);
      teardown();
    }

    conf = new Configuration();

    // Missing GENERATE_EEK key ACL
    conf.set(KMSConfiguration.CONFIG_PREFIX + "acl.GET_METADATA",
        realUgi.getUserName());
    conf.set(KMSConfiguration.CONFIG_PREFIX + "acl.GENERATE_EEK",
        realUgi.getUserName());
    conf.set(KMSConfiguration.WHITELIST_KEY_ACL_PREFIX + "READ",
        realUgi.getUserName());

    try {
      setup(conf, false);

      fs.mkdirs(ZONE4);

      assertFalse("Allowed zone creation without GENERATE_EEK ACL",
          createEncryptionZone(realUgi, KEY1, ZONE4));
    } finally {
      fs.delete(ZONE4, true);
      teardown();
    }
  }

  /**
   * Test that in-zone file creation is correctly governed by ACLs.
   * @throws Exception thrown if setup fails
   */
  @Test
  public void testCreateFileInEncryptionZone() throws Exception {
    Configuration conf = new Configuration();

    // Create a test key
    conf.set(KMSConfiguration.CONFIG_PREFIX + "acl.CREATE",
        realUgi.getUserName());
    conf.set(KMSConfiguration.CONFIG_PREFIX + "acl.GET_METADATA",
        realUgi.getUserName());
    conf.set(KMSConfiguration.CONFIG_PREFIX + "acl.GENERATE_EEK",
        realUgi.getUserName());
    conf.set(KMSConfiguration.WHITELIST_KEY_ACL_PREFIX + "MANAGEMENT",
        realUgi.getUserName());
    conf.set(KMSConfiguration.WHITELIST_KEY_ACL_PREFIX + "READ",
        realUgi.getUserName());
    conf.set(KMSConfiguration.WHITELIST_KEY_ACL_PREFIX + "GENERATE_EEK",
        realUgi.getUserName());

    assertTrue(new File(kmsDir, "kms.keystore").length() == 0);

    try {
      setup(conf);

      assertTrue("Exception during key creation",
          createKey(realUgi, KEY1, conf));
      fs.mkdirs(ZONE1);
      assertTrue("Exception during zone creation",
          createEncryptionZone(realUgi, KEY1, ZONE1));
      fs.mkdirs(ZONE2);
      assertTrue("Exception during zone creation",
          createEncryptionZone(realUgi, KEY1, ZONE2));
      fs.mkdirs(ZONE3);
      assertTrue("Exception during zone creation",
          createEncryptionZone(realUgi, KEY1, ZONE3));
      fs.mkdirs(ZONE4);
      assertTrue("Exception during zone creation",
          createEncryptionZone(realUgi, KEY1, ZONE4));
    } catch (Throwable ex) {
      fs.delete(ZONE1, true);
      fs.delete(ZONE2, true);
      fs.delete(ZONE3, true);
      fs.delete(ZONE4, true);

      throw ex;
    } finally {
      teardown();
    }

    // We tear everything down and then restart it with the ACLs we want to
    // test so that there's no contamination from the ACLs needed for setup.
    // To make that work, we have to tell the setup() method not to create a
    // new KMS directory or DFS dierctory.

    conf = new Configuration();

    // Correct config with whitelist ACLs
    conf.set(KMSConfiguration.CONFIG_PREFIX + "acl.GENERATE_EEK",
        realUgi.getUserName());
    conf.set(KMSConfiguration.CONFIG_PREFIX + "acl.DECRYPT_EEK",
        realUgi.getUserName());
    conf.set(KMSConfiguration.WHITELIST_KEY_ACL_PREFIX + "GENERATE_EEK",
        realUgi.getUserName());
    conf.set(KMSConfiguration.WHITELIST_KEY_ACL_PREFIX + "DECRYPT_EEK",
        realUgi.getUserName());

    try {
      setup(conf, false, false);

      assertTrue("Exception during file creation with correct config"
          + " using whitelist ACL", createFile(realUgi, FILE1, TEXT));
    } finally {
      fs.delete(ZONE1, true);
      teardown();
    }

    conf = new Configuration();

    // Correct config with default ACLs
    conf.set(KMSConfiguration.CONFIG_PREFIX + "acl.GENERATE_EEK",
        realUgi.getUserName());
    conf.set(KMSConfiguration.CONFIG_PREFIX + "acl.DECRYPT_EEK",
        realUgi.getUserName());
    conf.set(KMSConfiguration.DEFAULT_KEY_ACL_PREFIX + "GENERATE_EEK",
        realUgi.getUserName());
    conf.set(KMSConfiguration.DEFAULT_KEY_ACL_PREFIX + "DECRYPT_EEK",
        realUgi.getUserName());

    try {
      setup(conf, false, false);

      assertTrue("Exception during file creation with correct config"
          + " using whitelist ACL", createFile(realUgi, FILE2, TEXT));
    } finally {
      fs.delete(ZONE2, true);
      teardown();
    }

    conf = new Configuration();

    // Denied because the key ACL set ignores the default ACL set for key1
    conf.set(KMSConfiguration.CONFIG_PREFIX + "acl.GENERATE_EEK",
        realUgi.getUserName());
    conf.set(KMSConfiguration.CONFIG_PREFIX + "acl.DECRYPT_EEK",
        realUgi.getUserName());
    conf.set(KMSConfiguration.DEFAULT_KEY_ACL_PREFIX + "GENERATE_EEK",
        realUgi.getUserName());
    conf.set(KMSConfiguration.DEFAULT_KEY_ACL_PREFIX + "DECRYPT_EEK",
        realUgi.getUserName());
    conf.set(KeyAuthorizationKeyProvider.KEY_ACL + KEY1 + ".READ",
        realUgi.getUserName());

    try {
      setup(conf, false, false);

      assertFalse("Allowed file creation when default key ACLs should have been"
          + " overridden by key ACL", createFile(realUgi, FILE3, TEXT));
    } catch (Exception ex) {
      fs.delete(ZONE3, true);

      throw ex;
    } finally {
      teardown();
    }

    conf = new Configuration();

    // Denied by blacklist
    conf.set(KMSConfiguration.CONFIG_PREFIX + "acl.GENERATE_EEK",
        realUgi.getUserName());
    conf.set(KMSConfiguration.CONFIG_PREFIX + "acl.DECRYPT_EEK",
        realUgi.getUserName());
    conf.set(KMSConfiguration.CONFIG_PREFIX + "blacklist.GENERATE_EEK",
        realUgi.getUserName());
    conf.set(KMSConfiguration.WHITELIST_KEY_ACL_PREFIX + "GENERATE_EEK",
        realUgi.getUserName());
    conf.set(KMSConfiguration.WHITELIST_KEY_ACL_PREFIX + "DECRYPT_EEK",
        realUgi.getUserName());

    try {
      setup(conf, false, false);

      assertFalse("Allowed file creation with blacklist for GENERATE_EEK",
          createFile(realUgi, FILE3, TEXT));
    } catch (Exception ex) {
      fs.delete(ZONE3, true);

      throw ex;
    } finally {
      teardown();
    }

    conf = new Configuration();

    // Denied by blacklist
    conf.set(KMSConfiguration.CONFIG_PREFIX + "acl.GENERATE_EEK",
        realUgi.getUserName());
    conf.set(KMSConfiguration.CONFIG_PREFIX + "acl.DECRYPT_EEK",
        realUgi.getUserName());
    conf.set(KMSConfiguration.CONFIG_PREFIX + "blacklist.DECRYPT_EEK",
        realUgi.getUserName());
    conf.set(KMSConfiguration.WHITELIST_KEY_ACL_PREFIX + "GENERATE_EEK",
        realUgi.getUserName());
    conf.set(KMSConfiguration.WHITELIST_KEY_ACL_PREFIX + "DECRYPT_EEK",
        realUgi.getUserName());

    try {
      setup(conf, false, false);

      assertFalse("Allowed file creation with blacklist for DECRYPT_EEK",
          createFile(realUgi, FILE3, TEXT));
    } catch (Exception ex) {
      fs.delete(ZONE3, true);

      throw ex;
    } finally {
      teardown();
    }

    conf = new Configuration();

    // Allowed because default KMS ACLs are fully permissive
    conf.set(KMSConfiguration.WHITELIST_KEY_ACL_PREFIX + "GENERATE_EEK",
        realUgi.getUserName());
    conf.set(KMSConfiguration.WHITELIST_KEY_ACL_PREFIX + "DECRYPT_EEK",
        realUgi.getUserName());

    try {
      setup(conf, false, false);

      assertTrue("Exception during file creation with default KMS ACLs",
          createFile(realUgi, FILE3, TEXT));
    } catch (Exception ex) {
      fs.delete(ZONE3, true);

      throw ex;
    } finally {
      teardown();
    }

    conf = new Configuration();

    // Denied because of missing GENERATE_EEK KMS ACL
    conf.set(KMSConfiguration.CONFIG_PREFIX + "acl.GENERATE_EEK", " ");
    conf.set(KMSConfiguration.CONFIG_PREFIX + "acl.DECRYPT_EEK",
        realUgi.getUserName());
    conf.set(KMSConfiguration.WHITELIST_KEY_ACL_PREFIX + "GENERATE_EEK",
        realUgi.getUserName());
    conf.set(KMSConfiguration.WHITELIST_KEY_ACL_PREFIX + "DECRYPT_EEK",
        realUgi.getUserName());

    try {
      setup(conf, false, false);

      assertFalse("Allowed file creation without GENERATE_EEK KMS ACL",
          createFile(realUgi, FILE4, TEXT));
    } catch (Exception ex) {
      fs.delete(ZONE3, true);

      throw ex;
    } finally {
      teardown();
    }

    conf = new Configuration();

    // Denied because of missing DECRYPT_EEK KMS ACL
    conf.set(KMSConfiguration.CONFIG_PREFIX + "acl.GENERATE_EEK",
        realUgi.getUserName());
    conf.set(KMSConfiguration.CONFIG_PREFIX + "acl.DECRYPT_EEK", " ");
    conf.set(KMSConfiguration.WHITELIST_KEY_ACL_PREFIX + "GENERATE_EEK",
        realUgi.getUserName());
    conf.set(KMSConfiguration.WHITELIST_KEY_ACL_PREFIX + "DECRYPT_EEK",
        realUgi.getUserName());

    try {
      setup(conf, false, false);

      assertFalse("Allowed file creation without DECRYPT_EEK KMS ACL",
          createFile(realUgi, FILE3, TEXT));
    } catch (Exception ex) {
      fs.delete(ZONE3, true);

      throw ex;
    } finally {
      teardown();
    }

    conf = new Configuration();

    // Denied because of missing GENERATE_EEK key ACL
    conf.set(KMSConfiguration.CONFIG_PREFIX + "acl.GENERATE_EEK",
        realUgi.getUserName());
    conf.set(KMSConfiguration.CONFIG_PREFIX + "acl.DECRYPT_EEK",
        realUgi.getUserName());
    conf.set(KMSConfiguration.WHITELIST_KEY_ACL_PREFIX + "DECRYPT_EEK",
        realUgi.getUserName());

    try {
      setup(conf, false, false);

      assertFalse("Allowed file creation without GENERATE_EEK key ACL",
          createFile(realUgi, FILE3, TEXT));
    } catch (Exception ex) {
      fs.delete(ZONE3, true);

      throw ex;
    } finally {
      teardown();
    }

    conf = new Configuration();

    // Denied because of missing DECRYPT_EEK key ACL
    conf.set(KMSConfiguration.CONFIG_PREFIX + "acl.GENERATE_EEK",
        realUgi.getUserName());
    conf.set(KMSConfiguration.CONFIG_PREFIX + "acl.DECRYPT_EEK",
        realUgi.getUserName());
    conf.set(KMSConfiguration.WHITELIST_KEY_ACL_PREFIX + "GENERATE_EEK",
        realUgi.getUserName());

    try {
      setup(conf, false, false);

      assertFalse("Allowed file creation without DECRYPT_EEK key ACL",
          createFile(realUgi, FILE3, TEXT));
    } catch (Exception ex) {
      fs.delete(ZONE3, true);

      throw ex;
    } finally {
      teardown();
    }
  }

  /**
   * Test that in-zone file read is correctly governed by ACLs.
   * @throws Exception thrown if setup fails
   */
  @Test
  public void testReadFileInEncryptionZone() throws Exception {
    Configuration conf = new Configuration();

    // Create a test key
    conf.set(KMSConfiguration.CONFIG_PREFIX + "acl.CREATE",
        realUgi.getUserName());
    conf.set(KMSConfiguration.CONFIG_PREFIX + "acl.GET_METADATA",
        realUgi.getUserName());
    conf.set(KMSConfiguration.CONFIG_PREFIX + "acl.GENERATE_EEK",
        realUgi.getUserName());
    conf.set(KMSConfiguration.CONFIG_PREFIX + "acl.DECRYPT_EEK",
        realUgi.getUserName());
    conf.set(KMSConfiguration.WHITELIST_KEY_ACL_PREFIX + "MANAGEMENT",
        realUgi.getUserName());
    conf.set(KMSConfiguration.WHITELIST_KEY_ACL_PREFIX + "READ",
        realUgi.getUserName());
    conf.set(KMSConfiguration.WHITELIST_KEY_ACL_PREFIX + "GENERATE_EEK",
        realUgi.getUserName());
    conf.set(KMSConfiguration.WHITELIST_KEY_ACL_PREFIX + "DECRYPT_EEK",
        realUgi.getUserName());

    assertTrue(new File(kmsDir, "kms.keystore").length() == 0);

    try {
      setup(conf);

      assertTrue("Exception during key creation",
          createKey(realUgi, KEY1, conf));
      fs.mkdirs(ZONE1);
      assertTrue("Exception during zone creation",
          createEncryptionZone(realUgi, KEY1, ZONE1));
      assertTrue("Exception during file creation",
              createFile(realUgi, FILE1, TEXT));
    } catch (Throwable ex) {
      fs.delete(ZONE1, true);

      throw ex;
    } finally {
      teardown();
    }

    // We tear everything down and then restart it with the ACLs we want to
    // test so that there's no contamination from the ACLs needed for setup.
    // To make that work, we have to tell the setup() method not to create a
    // new KMS directory or DFS dierctory.

    conf = new Configuration();

    // Correct config with whitelist ACLs
    conf.set(KMSConfiguration.CONFIG_PREFIX + "acl.DECRYPT_EEK",
        realUgi.getUserName());
    conf.set(KMSConfiguration.WHITELIST_KEY_ACL_PREFIX + "DECRYPT_EEK",
        realUgi.getUserName());

    try {
      setup(conf, false, false);

      assertTrue("Exception while reading file with correct config with"
          + " whitelist ACLs", compareFile(realUgi, FILE1, TEXT));
    } catch (Throwable ex) {
      fs.delete(ZONE1, true);

      throw ex;
    } finally {
      teardown();
    }

    conf = new Configuration();

    // Correct config with default ACLs
    conf.set(KMSConfiguration.CONFIG_PREFIX + "acl.DECRYPT_EEK",
        realUgi.getUserName());
    conf.set(KMSConfiguration.DEFAULT_KEY_ACL_PREFIX + "DECRYPT_EEK",
        realUgi.getUserName());

    try {
      setup(conf, false, false);

      assertTrue("Exception while reading file with correct config"
          + " with default ACLs", compareFile(realUgi, FILE1, TEXT));
    } catch (Throwable ex) {
      fs.delete(ZONE1, true);

      throw ex;
    } finally {
      teardown();
    }

    conf = new Configuration();

    // Denied because the key ACL set ignores the default ACL set for key1
    conf.set(KMSConfiguration.CONFIG_PREFIX + "acl.DECRYPT_EEK",
        realUgi.getUserName());
    conf.set(KMSConfiguration.DEFAULT_KEY_ACL_PREFIX + "DECRYPT_EEK",
        realUgi.getUserName());
    conf.set(KeyAuthorizationKeyProvider.KEY_ACL + KEY1 + ".READ",
        realUgi.getUserName());

    try {
      setup(conf, false, false);

      assertFalse("Allowed file read when default key ACLs should have been"
          + " overridden by key ACL", compareFile(realUgi, FILE1, TEXT));
    } catch (Throwable ex) {
      fs.delete(ZONE1, true);

      throw ex;
    } finally {
      teardown();
    }

    conf = new Configuration();

    // Denied by blacklist
    conf.set(KMSConfiguration.CONFIG_PREFIX + "acl.DECRYPT_EEK",
        realUgi.getUserName());
    conf.set(KMSConfiguration.CONFIG_PREFIX + "blacklist.DECRYPT_EEK",
        realUgi.getUserName());
    conf.set(KMSConfiguration.WHITELIST_KEY_ACL_PREFIX + "DECRYPT_EEK",
        realUgi.getUserName());

    try {
      setup(conf, false, false);

      assertFalse("Allowed file read with blacklist for DECRYPT_EEK",
          compareFile(realUgi, FILE1, TEXT));
    } catch (Throwable ex) {
      fs.delete(ZONE1, true);

      throw ex;
    } finally {
      teardown();
    }

    conf = new Configuration();

    // Allowed because default KMS ACLs are fully permissive
    conf.set(KMSConfiguration.WHITELIST_KEY_ACL_PREFIX + "DECRYPT_EEK",
        realUgi.getUserName());

    try {
      setup(conf, false, false);

      assertTrue("Exception while reading file with default KMS ACLs",
          compareFile(realUgi, FILE1, TEXT));
    } catch (Throwable ex) {
      fs.delete(ZONE1, true);

      throw ex;
    } finally {
      teardown();
    }

    conf = new Configuration();

    // Denied because of missing DECRYPT_EEK KMS ACL
    conf.set(KMSConfiguration.CONFIG_PREFIX + "acl.DECRYPT_EEK", " ");
    conf.set(KMSConfiguration.WHITELIST_KEY_ACL_PREFIX + "DECRYPT_EEK",
        realUgi.getUserName());

    try {
      setup(conf, false, false);

      assertFalse("Allowed file read without DECRYPT_EEK KMS ACL",
          compareFile(realUgi, FILE1, TEXT));
    } catch (Throwable ex) {
      fs.delete(ZONE1, true);

      throw ex;
    } finally {
      teardown();
    }

    // Denied because of missing DECRYPT_EEK key ACL
    conf = new Configuration();

    try {
      setup(conf, false, false);

      assertFalse("Allowed file read without DECRYPT_EEK key ACL",
          compareFile(realUgi, FILE1, TEXT));
    } catch (Throwable ex) {
      fs.delete(ZONE1, true);

      throw ex;
    } finally {
      teardown();
    }
  }

  /**
   * Test that key deletion is correctly governed by ACLs.
   * @throws Exception thrown if setup fails
   */
  @Test
  public void testDeleteKey() throws Exception {
    Configuration conf = new Configuration();

    // Create a test key
    conf.set(KMSConfiguration.CONFIG_PREFIX + "acl.CREATE",
        realUgi.getUserName());
    conf.set(KMSConfiguration.WHITELIST_KEY_ACL_PREFIX + "MANAGEMENT",
        realUgi.getUserName());

    try {
      setup(conf);

      assertTrue("Exception during key creation",
          createKey(realUgi, KEY1, conf));
      assertTrue("Exception during key creation",
          createKey(realUgi, KEY2, conf));
      assertTrue("Exception during key creation",
          createKey(realUgi, KEY3, conf));
    } finally {
      teardown();
    }

    // We tear everything down and then restart it with the ACLs we want to
    // test so that there's no contamination from the ACLs needed for setup.
    // To make that work, we have to tell the setup() method not to create a
    // new KMS directory.

    conf = new Configuration();

    // Correct config with whitelist ACL
    conf.set(KMSConfiguration.CONFIG_PREFIX + "acl.DELETE",
        realUgi.getUserName());
    conf.set(KMSConfiguration.WHITELIST_KEY_ACL_PREFIX + "MANAGEMENT",
        realUgi.getUserName());

    try {
      setup(conf, false);

      assertTrue("Exception during key deletion with correct config"
          + " using whitelist key ACLs", deleteKey(realUgi, KEY1));
    } finally {
      teardown();
    }

    conf = new Configuration();

    // Correct config with default ACL
    conf.set(KMSConfiguration.CONFIG_PREFIX + "acl.DELETE",
        realUgi.getUserName());
    conf.set(KMSConfiguration.DEFAULT_KEY_ACL_PREFIX + "MANAGEMENT",
        realUgi.getUserName());

    try {
      setup(conf, false);

      assertTrue("Exception during key deletion with correct config"
          + " using default key ACLs", deleteKey(realUgi, KEY2));
    } finally {
      teardown();
    }

    conf = new Configuration();

    // Denied because of blacklist
    conf.set(KMSConfiguration.CONFIG_PREFIX + "acl.DELETE",
        realUgi.getUserName());
    conf.set(KMSConfiguration.CONFIG_PREFIX + "blacklist.DELETE",
        realUgi.getUserName());
    conf.set(KMSConfiguration.WHITELIST_KEY_ACL_PREFIX + "MANAGEMENT",
        realUgi.getUserName());

    try {
      setup(conf, false);

      assertFalse("Allowed key deletion with blacklist for DELETE",
          deleteKey(realUgi, KEY3));
    } finally {
      teardown();
    }

    conf = new Configuration();

    // Missing KMS ACL
    conf.set(KMSConfiguration.CONFIG_PREFIX + "acl.DELETE", " ");
    conf.set(KMSConfiguration.WHITELIST_KEY_ACL_PREFIX + "MANAGEMENT",
        realUgi.getUserName());

    try {
      setup(conf, false);

      assertFalse("Allowed key deletion without DELETE KMS ACL",
          deleteKey(realUgi, KEY3));
    } finally {
      teardown();
    }

    conf = new Configuration();


    // Missing key ACL
    conf.set(KMSConfiguration.CONFIG_PREFIX + "acl.DELETE",
        realUgi.getUserName());

    try {
      setup(conf, false);

      assertFalse("Allowed key deletion without MANAGMENT key ACL",
          deleteKey(realUgi, KEY3));
    } finally {
      teardown();
    }

    conf = new Configuration();

    // Denied because the key ACL set ignores the default ACL set for key3
    conf.set(KMSConfiguration.CONFIG_PREFIX + "acl.DELETE",
        realUgi.getUserName());
    conf.set(KMSConfiguration.DEFAULT_KEY_ACL_PREFIX + "MANAGEMENT",
        realUgi.getUserName());
    conf.set(KeyAuthorizationKeyProvider.KEY_ACL + KEY3 + ".DECRYPT_EEK",
        realUgi.getUserName());

    try {
      setup(conf, false);

      assertFalse("Allowed key deletion when default key ACL should have been"
          + " overridden by key ACL", deleteKey(realUgi, KEY3));
    } finally {
      teardown();
    }

    conf = new Configuration();

    // Allowed because the default setting for KMS ACLs is fully permissive
    conf.set(KMSConfiguration.WHITELIST_KEY_ACL_PREFIX + "MANAGEMENT",
        realUgi.getUserName());

    try {
      setup(conf, false);

      assertTrue("Exception during key deletion with default KMS ACLs",
          deleteKey(realUgi, KEY3));
    } finally {
      teardown();
    }
  }

  /**
   * Create a key as the specified user.
   *
   * @param ugi the target user
   * @param key the target key
   * @param conf the configuration
   * @return whether the key creation succeeded
   */
  private boolean createKey(UserGroupInformation ugi, final String key,
      final Configuration conf) {

    return doUserOp(ugi, new UserOp() {
      @Override
      public void execute() throws IOException {
        try {
          DFSTestUtil.createKey(key, cluster, conf);
        } catch (NoSuchAlgorithmException ex) {
          throw new IOException(ex);
        }
      }
    });
  }

  /**
   * Create a zone as the specified user.
   *
   * @param ugi the target user
   * @param key the target key
   * @param zone the target zone
   * @return whether the zone creation succeeded
   */
  private boolean createEncryptionZone(UserGroupInformation ugi,
      final String key, final Path zone) {

    return doUserOp(ugi, new UserOp() {
      @Override
      public void execute() throws IOException {
        cluster.getFileSystem().createEncryptionZone(zone, key);
      }
    });
  }

  /**
   * Create a file as the specified user.
   *
   * @param ugi the target user
   * @param file the target file
   * @param text the target file contents
   * @return whether the file creation succeeded
   */
  private boolean createFile(UserGroupInformation ugi,
      final Path file, final String text) {

    return doUserOp(ugi, new UserOp() {
      @Override
      public void execute() throws IOException {
        FSDataOutputStream dout = cluster.getFileSystem().create(file);
        PrintWriter out = new PrintWriter(new OutputStreamWriter(dout));

        out.println(text);
        out.close();
      }
    });
  }

  /**
   * Read a file as the specified user and compare the contents to expectations.
   *
   * @param ugi the target user
   * @param file the target file
   * @param text the expected file contents
   * @return true if the file read succeeded and the contents were as expected
   */
  private boolean compareFile(UserGroupInformation ugi,
      final Path file, final String text) {

    return doUserOp(ugi, new UserOp() {
      @Override
      public void execute() throws IOException {
        FSDataInputStream din =  cluster.getFileSystem().open(file);
        BufferedReader in = new BufferedReader(new InputStreamReader(din));

        assertEquals("The text read does not match the text written",
            text, in.readLine());
      }
    });
  }

  /**
   * Delete a key as the specified user.
   *
   * @param ugi the target user
   * @param key the target key
   * @return whether the key deletion succeeded
   */
  private boolean deleteKey(UserGroupInformation ugi, final String key)
      throws IOException, InterruptedException {

    return doUserOp(ugi, new UserOp() {
      @Override
      public void execute() throws IOException {
        cluster.getNameNode().getNamesystem().getProvider().deleteKey(key);
      }
    });
  }

  /**
   * Perform an operation as the given user.  This method requires setting the
   * login user. This method does not restore the login user to the setting
   * from prior to the method call.
   *
   * @param ugi the target user
   * @param op the operation to perform
   * @return true if the operation succeeded without throwing an exception
   */
  private boolean doUserOp(UserGroupInformation ugi, final UserOp op) {
    UserGroupInformation.setLoginUser(ugi);

    // Create a test key
    return ugi.doAs(new PrivilegedAction<Boolean>() {
      @Override
      public Boolean run() {
        try {
          op.execute();

          return true;
        } catch (IOException ex) {
          LOG.error("IOException thrown during doAs() operation", ex);

          return false;
        }
      }
    });
  }

  /**
   * Simple interface that defines an operation to perform.
   */
  private static interface UserOp {
    public void execute() throws IOException;
  }
}
