You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@ambari.apache.org by rn...@apache.org on 2014/12/08 23:00:12 UTC

[1/2] ambari git commit: AMBARI-8454. Create server-side actions to create kerberos principals and keytabs. (Robert Levas via rnettleton)

Repository: ambari
Updated Branches:
  refs/heads/trunk ed0681a84 -> 1a18ddeed


http://git-wip-us.apache.org/repos/asf/ambari/blob/1a18ddee/ambari-server/src/test/java/org/apache/ambari/server/serveraction/kerberos/AbstractKerberosOperationHandlerTest.java
----------------------------------------------------------------------
diff --git a/ambari-server/src/test/java/org/apache/ambari/server/serveraction/kerberos/AbstractKerberosOperationHandlerTest.java b/ambari-server/src/test/java/org/apache/ambari/server/serveraction/kerberos/AbstractKerberosOperationHandlerTest.java
new file mode 100644
index 0000000..f21260a
--- /dev/null
+++ b/ambari-server/src/test/java/org/apache/ambari/server/serveraction/kerberos/AbstractKerberosOperationHandlerTest.java
@@ -0,0 +1,275 @@
+/*
+ * 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.ambari.server.serveraction.kerberos;
+
+import junit.framework.Assert;
+import org.apache.ambari.server.AmbariException;
+import org.apache.commons.codec.binary.Base64;
+import org.apache.directory.server.kerberos.shared.keytab.Keytab;
+import org.apache.directory.server.kerberos.shared.keytab.KeytabEntry;
+import org.junit.After;
+import org.junit.Test;
+import org.junit.Before;
+
+import java.io.File;
+import java.io.FileInputStream;
+import java.util.HashMap;
+import java.util.List;
+
+public abstract class AbstractKerberosOperationHandlerTest {
+
+  protected final KerberosOperationHandler handler;
+
+  protected AbstractKerberosOperationHandlerTest(KerberosOperationHandler handler) {
+    this.handler = handler;
+  }
+
+  @Before
+  public void startUp() throws AmbariException {
+    handler.open(new KerberosCredential(), "EXAMPLE.COM");
+  }
+
+  @After
+  public void cleanUp() throws AmbariException {
+    handler.close();
+  }
+
+  @Test
+  public void testCreateSecurePassword() throws Exception {
+    KerberosOperationHandler handler2 = new KerberosOperationHandler() {
+
+      @Override
+      public void open(KerberosCredential administratorCredentials, String defaultRealm) throws AmbariException {
+        setAdministratorCredentials(administratorCredentials);
+        setDefaultRealm(defaultRealm);
+      }
+
+      @Override
+      public void close() throws AmbariException {
+
+      }
+
+      @Override
+      public boolean principalExists(String principal) throws AmbariException {
+        return false;
+      }
+
+      @Override
+      public boolean createServicePrincipal(String principal, String password) throws AmbariException {
+        return false;
+      }
+
+      @Override
+      public boolean setPrincipalPassword(String principal, String password) throws AmbariException {
+        return false;
+      }
+
+      @Override
+      public boolean removeServicePrincipal(String principal) throws AmbariException {
+        return false;
+      }
+    };
+
+    String password1 = handler.createSecurePassword();
+    Assert.assertNotNull(password1);
+    Assert.assertEquals(KerberosOperationHandler.SECURE_PASSWORD_LENGTH, password1.length());
+
+    String password2 = handler2.createSecurePassword();
+    Assert.assertNotNull(password2);
+    Assert.assertEquals(KerberosOperationHandler.SECURE_PASSWORD_LENGTH, password2.length());
+
+    // Make sure the passwords are different... if they are the same, that indicated the random
+    // number generators are generating using the same pattern and that is not secure.
+    Assert.assertFalse((password1.equals(password2)));
+  }
+
+  @Test
+  public void testCreateSecurePasswordWithSize() throws Exception {
+    String password;
+
+    password = handler.createSecurePassword(10);
+    Assert.assertNotNull(password);
+    Assert.assertEquals(10, password.length());
+
+    password = handler.createSecurePassword(0);
+    Assert.assertNotNull(password);
+    Assert.assertEquals(KerberosOperationHandler.SECURE_PASSWORD_LENGTH, password.length());
+
+    password = handler.createSecurePassword(-20);
+    Assert.assertNotNull(password);
+    Assert.assertEquals(KerberosOperationHandler.SECURE_PASSWORD_LENGTH, password.length());
+  }
+
+  @Test
+  public void testCreateKeytabFileAllAtOnce() throws Exception {
+
+    File file = File.createTempFile("ambari_ut_", ".dat");
+    final String principal1 = "principal1@REALM.COM";
+    final String principal2 = "principal2@REALM.COM";
+
+    try {
+      Assert.assertTrue(handler.createKeytabFile(new HashMap<String, String>() {
+        {
+          put(principal1, handler.createSecurePassword());
+        }
+      }, file));
+
+      Keytab keytab = Keytab.read(file);
+      Assert.assertNotNull(keytab);
+
+      List<KeytabEntry> entries = keytab.getEntries();
+      Assert.assertNotNull(entries);
+      Assert.assertFalse(entries.isEmpty());
+
+      for (KeytabEntry entry : entries) {
+        Assert.assertEquals(principal1, entry.getPrincipalName());
+      }
+
+      Assert.assertTrue(handler.createKeytabFile(new HashMap<String, String>() {
+        {
+          put(principal1, handler.createSecurePassword());
+          put(principal2, handler.createSecurePassword());
+        }
+      }, file));
+
+      keytab = Keytab.read(file);
+      Assert.assertNotNull(keytab);
+
+      entries = keytab.getEntries();
+      Assert.assertNotNull(entries);
+      Assert.assertFalse(entries.isEmpty());
+    } finally {
+      if (!file.delete()) {
+        file.deleteOnExit();
+      }
+    }
+  }
+
+  @Test
+  public void testCreateKeytabFileOneAtATime() throws Exception {
+    File file = File.createTempFile("ambari_ut_", ".dat");
+    final String principal1 = "principal1@REALM.COM";
+    final String principal2 = "principal2@REALM.COM";
+    int count;
+
+    try {
+      Assert.assertTrue(handler.createKeytabFile(principal1, handler.createSecurePassword(), file));
+
+      Keytab keytab = Keytab.read(file);
+      Assert.assertNotNull(keytab);
+
+      List<KeytabEntry> entries = keytab.getEntries();
+      Assert.assertNotNull(entries);
+      Assert.assertFalse(entries.isEmpty());
+
+      count = entries.size();
+
+      for (KeytabEntry entry : entries) {
+        Assert.assertEquals(principal1, entry.getPrincipalName());
+      }
+
+      Assert.assertTrue(handler.createKeytabFile(principal2, handler.createSecurePassword(), file));
+
+      keytab = Keytab.read(file);
+      Assert.assertNotNull(keytab);
+
+      entries = keytab.getEntries();
+      Assert.assertNotNull(entries);
+      Assert.assertFalse(entries.isEmpty());
+
+      Assert.assertEquals(count * 2, entries.size());
+    } finally {
+      if (!file.delete()) {
+        file.deleteOnExit();
+      }
+    }
+  }
+
+  @Test
+  public void testCreateKeytabFileExceptions() throws Exception {
+    File file = File.createTempFile("ambari_ut_", ".dat");
+    final String principal1 = "principal1@REALM.COM";
+
+    try {
+      try {
+        handler.createKeytabFile(null, handler.createSecurePassword(), file);
+        Assert.fail("AmbariException not thrown with null principal");
+      } catch (Throwable t) {
+        Assert.assertEquals(AmbariException.class, t.getClass());
+      }
+
+      try {
+        handler.createKeytabFile(principal1, null, file);
+        Assert.fail("AmbariException not thrown with null password");
+      } catch (Throwable t) {
+        Assert.assertEquals(AmbariException.class, t.getClass());
+      }
+
+      try {
+        handler.createKeytabFile(principal1, handler.createSecurePassword(), null);
+        Assert.fail("AmbariException not thrown with null file");
+      } catch (Throwable t) {
+        Assert.assertEquals(AmbariException.class, t.getClass());
+      }
+    } finally {
+      if (!file.delete()) {
+        file.deleteOnExit();
+      }
+    }
+  }
+
+  @Test
+  public void testCreateKeytabFileFromBase64EncodedData() throws Exception {
+    File file = File.createTempFile("ambari_ut_", ".dat");
+    final String principal = "principal@REALM.COM";
+
+    try {
+      Assert.assertTrue(handler.createKeytabFile(principal, handler.createSecurePassword(), file));
+
+      FileInputStream fis = new FileInputStream(file);
+      byte[] data = new byte[(int) file.length()];
+
+      Assert.assertEquals(data.length, fis.read(data));
+      fis.close();
+
+      File f = handler.createKeytabFile(Base64.encodeBase64String(data));
+
+      try {
+        Keytab keytab = Keytab.read(f);
+        Assert.assertNotNull(keytab);
+
+        List<KeytabEntry> entries = keytab.getEntries();
+        Assert.assertNotNull(entries);
+        Assert.assertFalse(entries.isEmpty());
+
+        for (KeytabEntry entry : entries) {
+          Assert.assertEquals(principal, entry.getPrincipalName());
+        }
+      } finally {
+        if (!f.delete()) {
+          f.deleteOnExit();
+        }
+      }
+    } finally {
+      if (!file.delete()) {
+        file.deleteOnExit();
+      }
+    }
+  }
+}

http://git-wip-us.apache.org/repos/asf/ambari/blob/1a18ddee/ambari-server/src/test/java/org/apache/ambari/server/serveraction/kerberos/KerberosActionDataFileTest.java
----------------------------------------------------------------------
diff --git a/ambari-server/src/test/java/org/apache/ambari/server/serveraction/kerberos/KerberosActionDataFileTest.java b/ambari-server/src/test/java/org/apache/ambari/server/serveraction/kerberos/KerberosActionDataFileTest.java
new file mode 100644
index 0000000..71b3084
--- /dev/null
+++ b/ambari-server/src/test/java/org/apache/ambari/server/serveraction/kerberos/KerberosActionDataFileTest.java
@@ -0,0 +1,202 @@
+/*
+ * 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.ambari.server.serveraction.kerberos;
+
+import junit.framework.Assert;
+import org.junit.Test;
+
+import java.io.File;
+import java.util.Iterator;
+import java.util.Map;
+
+/**
+ * This is a test to see how well the KerberosActionDataFileBuilder and KerberosActionDataFileReader
+ * work when the data temporaryDirectory is opened, close, reopened, and appended to.
+ */
+public class KerberosActionDataFileTest {
+
+  @Test
+  public void testKerberosActionDataFile() throws Exception {
+    File file = File.createTempFile("ambari_ut_", "dat");
+    Assert.assertNotNull(file);
+
+    try {
+      // Write the data
+      KerberosActionDataFileBuilder builder = new KerberosActionDataFileBuilder(file);
+      Assert.assertFalse(builder.isClosed());
+
+      for (int i = 0; i < 10; i++) {
+        builder.addRecord("hostName" + i, "serviceName" + i, "serviceComponentName" + i,
+            "principal" + i, "principalConfiguration" + i, "keytabFilePath" + i,
+            "keytabFileOwnerName" + i, "keytabFileOwnerAccess" + i,
+            "keytabFileGroupName" + i, "keytabFileGroupAccess" + i,
+            "keytabFileConfiguration" + i);
+      }
+
+      // Add some odd characters
+      builder.addRecord("hostName's", "serviceName#", "serviceComponentName\"",
+          "principal", "principalConfiguration", "keytabFilePath",
+          "'keytabFileOwnerName'", "<keytabFileOwnerAccess>",
+          "\"keytabFileGroupName\"", "keytab,File,Group,Access",
+          "\"keytab,'File',Configuration\"");
+
+      builder.close();
+      Assert.assertTrue(builder.isClosed());
+
+      // Read the data...
+      KerberosActionDataFileReader reader = new KerberosActionDataFileReader(file);
+      Assert.assertFalse(reader.isClosed());
+
+      Iterator<Map<String, String>> iterator = reader.iterator();
+      Assert.assertNotNull(iterator);
+
+      // Test iterator
+      int i = 0;
+      while (iterator.hasNext()) {
+        Map<String, String> record = iterator.next();
+
+        if (i < 10) {
+          Assert.assertEquals("hostName" + i, record.get(KerberosActionDataFile.HOSTNAME));
+          Assert.assertEquals("serviceName" + i, record.get(KerberosActionDataFile.SERVICE));
+          Assert.assertEquals("serviceComponentName" + i, record.get(KerberosActionDataFile.COMPONENT));
+          Assert.assertEquals("principal" + i, record.get(KerberosActionDataFile.PRINCIPAL));
+          Assert.assertEquals("principalConfiguration" + i, record.get(KerberosActionDataFile.PRINCIPAL_CONFIGURATION));
+          Assert.assertEquals("keytabFilePath" + i, record.get(KerberosActionDataFile.KEYTAB_FILE_PATH));
+          Assert.assertEquals("keytabFileOwnerName" + i, record.get(KerberosActionDataFile.KEYTAB_FILE_OWNER_NAME));
+          Assert.assertEquals("keytabFileOwnerAccess" + i, record.get(KerberosActionDataFile.KEYTAB_FILE_OWNER_ACCESS));
+          Assert.assertEquals("keytabFileGroupName" + i, record.get(KerberosActionDataFile.KEYTAB_FILE_GROUP_NAME));
+          Assert.assertEquals("keytabFileGroupAccess" + i, record.get(KerberosActionDataFile.KEYTAB_FILE_GROUP_ACCESS));
+          Assert.assertEquals("keytabFileConfiguration" + i, record.get(KerberosActionDataFile.KEYTAB_FILE_CONFIGURATION));
+        } else {
+          Assert.assertEquals("hostName's", record.get(KerberosActionDataFile.HOSTNAME));
+          Assert.assertEquals("serviceName#", record.get(KerberosActionDataFile.SERVICE));
+          Assert.assertEquals("serviceComponentName\"", record.get(KerberosActionDataFile.COMPONENT));
+          Assert.assertEquals("principal", record.get(KerberosActionDataFile.PRINCIPAL));
+          Assert.assertEquals("principalConfiguration", record.get(KerberosActionDataFile.PRINCIPAL_CONFIGURATION));
+          Assert.assertEquals("keytabFilePath", record.get(KerberosActionDataFile.KEYTAB_FILE_PATH));
+          Assert.assertEquals("'keytabFileOwnerName'", record.get(KerberosActionDataFile.KEYTAB_FILE_OWNER_NAME));
+          Assert.assertEquals("<keytabFileOwnerAccess>", record.get(KerberosActionDataFile.KEYTAB_FILE_OWNER_ACCESS));
+          Assert.assertEquals("\"keytabFileGroupName\"", record.get(KerberosActionDataFile.KEYTAB_FILE_GROUP_NAME));
+          Assert.assertEquals("keytab,File,Group,Access", record.get(KerberosActionDataFile.KEYTAB_FILE_GROUP_ACCESS));
+          Assert.assertEquals("\"keytab,'File',Configuration\"", record.get(KerberosActionDataFile.KEYTAB_FILE_CONFIGURATION));
+        }
+
+        i++;
+      }
+
+      reader.close();
+      Assert.assertTrue(reader.isClosed());
+      reader.open();
+      Assert.assertFalse(reader.isClosed());
+
+      i = 0;
+      for (Map<String, String> record : reader) {
+        if (i < 10) {
+          Assert.assertEquals("hostName" + i, record.get(KerberosActionDataFile.HOSTNAME));
+          Assert.assertEquals("serviceName" + i, record.get(KerberosActionDataFile.SERVICE));
+          Assert.assertEquals("serviceComponentName" + i, record.get(KerberosActionDataFile.COMPONENT));
+          Assert.assertEquals("principal" + i, record.get(KerberosActionDataFile.PRINCIPAL));
+          Assert.assertEquals("principalConfiguration" + i, record.get(KerberosActionDataFile.PRINCIPAL_CONFIGURATION));
+          Assert.assertEquals("keytabFilePath" + i, record.get(KerberosActionDataFile.KEYTAB_FILE_PATH));
+          Assert.assertEquals("keytabFileOwnerName" + i, record.get(KerberosActionDataFile.KEYTAB_FILE_OWNER_NAME));
+          Assert.assertEquals("keytabFileOwnerAccess" + i, record.get(KerberosActionDataFile.KEYTAB_FILE_OWNER_ACCESS));
+          Assert.assertEquals("keytabFileGroupName" + i, record.get(KerberosActionDataFile.KEYTAB_FILE_GROUP_NAME));
+          Assert.assertEquals("keytabFileGroupAccess" + i, record.get(KerberosActionDataFile.KEYTAB_FILE_GROUP_ACCESS));
+          Assert.assertEquals("keytabFileConfiguration" + i, record.get(KerberosActionDataFile.KEYTAB_FILE_CONFIGURATION));
+        } else {
+          Assert.assertEquals("hostName's", record.get(KerberosActionDataFile.HOSTNAME));
+          Assert.assertEquals("serviceName#", record.get(KerberosActionDataFile.SERVICE));
+          Assert.assertEquals("serviceComponentName\"", record.get(KerberosActionDataFile.COMPONENT));
+          Assert.assertEquals("principal", record.get(KerberosActionDataFile.PRINCIPAL));
+          Assert.assertEquals("principalConfiguration", record.get(KerberosActionDataFile.PRINCIPAL_CONFIGURATION));
+          Assert.assertEquals("keytabFilePath", record.get(KerberosActionDataFile.KEYTAB_FILE_PATH));
+          Assert.assertEquals("'keytabFileOwnerName'", record.get(KerberosActionDataFile.KEYTAB_FILE_OWNER_NAME));
+          Assert.assertEquals("<keytabFileOwnerAccess>", record.get(KerberosActionDataFile.KEYTAB_FILE_OWNER_ACCESS));
+          Assert.assertEquals("\"keytabFileGroupName\"", record.get(KerberosActionDataFile.KEYTAB_FILE_GROUP_NAME));
+          Assert.assertEquals("keytab,File,Group,Access", record.get(KerberosActionDataFile.KEYTAB_FILE_GROUP_ACCESS));
+          Assert.assertEquals("\"keytab,'File',Configuration\"", record.get(KerberosActionDataFile.KEYTAB_FILE_CONFIGURATION));
+        }
+
+        i++;
+      }
+
+      reader.close();
+      Assert.assertTrue(reader.isClosed());
+
+      // Add an additional record
+      builder.open();
+      Assert.assertFalse(builder.isClosed());
+
+      builder.addRecord("hostName", "serviceName", "serviceComponentName",
+          "principal", "principalConfiguration", "keytabFilePath",
+          "keytabFileOwnerName", "keytabFileOwnerAccess",
+          "keytabFileGroupName", "keytabFileGroupAccess",
+          "keytabFileConfiguration");
+
+      builder.close();
+      Assert.assertTrue(builder.isClosed());
+
+      reader = new KerberosActionDataFileReader(file);
+      Assert.assertFalse(reader.isClosed());
+
+      i = 0;
+      for (Map<String, String> record : reader) {
+        i++;
+      }
+
+      Assert.assertEquals(12, i);
+
+      reader.close();
+      Assert.assertTrue(reader.isClosed());
+
+      // Add an additional record
+      builder = new KerberosActionDataFileBuilder(file);
+      Assert.assertFalse(builder.isClosed());
+
+      builder.addRecord("hostName", "serviceName", "serviceComponentName",
+          "principal", "principalConfiguration", "keytabFilePath",
+          "keytabFileOwnerName", "keytabFileOwnerAccess",
+          "keytabFileGroupName", "keytabFileGroupAccess",
+          "keytabFileConfiguration");
+
+      builder.close();
+      Assert.assertTrue(builder.isClosed());
+
+      reader.open();
+      Assert.assertFalse(reader.isClosed());
+
+      i = 0;
+      for (Map<String, String> record : reader) {
+        i++;
+      }
+
+      Assert.assertEquals(13, i);
+
+      reader.close();
+      Assert.assertTrue(reader.isClosed());
+
+
+    } finally {
+      if (!file.delete()) {
+        file.deleteOnExit();
+      }
+    }
+  }
+
+}
\ No newline at end of file

http://git-wip-us.apache.org/repos/asf/ambari/blob/1a18ddee/ambari-server/src/test/java/org/apache/ambari/server/serveraction/kerberos/KerberosOperationHandlerTest.java
----------------------------------------------------------------------
diff --git a/ambari-server/src/test/java/org/apache/ambari/server/serveraction/kerberos/KerberosOperationHandlerTest.java b/ambari-server/src/test/java/org/apache/ambari/server/serveraction/kerberos/KerberosOperationHandlerTest.java
new file mode 100644
index 0000000..296580e
--- /dev/null
+++ b/ambari-server/src/test/java/org/apache/ambari/server/serveraction/kerberos/KerberosOperationHandlerTest.java
@@ -0,0 +1,59 @@
+/*
+ * 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.ambari.server.serveraction.kerberos;
+
+import org.apache.ambari.server.AmbariException;
+
+public class KerberosOperationHandlerTest extends AbstractKerberosOperationHandlerTest {
+
+  public KerberosOperationHandlerTest() {
+    super(new KerberosOperationHandler() {
+      @Override
+      public void open(KerberosCredential administratorCredentials, String defaultRealm) throws AmbariException {
+        setAdministratorCredentials(administratorCredentials);
+        setDefaultRealm(defaultRealm);
+      }
+
+      @Override
+      public void close() throws AmbariException {
+
+      }
+
+      @Override
+      public boolean principalExists(String principal) throws AmbariException {
+        return false;
+      }
+
+      @Override
+      public boolean createServicePrincipal(String principal, String password) throws AmbariException {
+        return false;
+      }
+
+      @Override
+      public boolean setPrincipalPassword(String principal, String password) throws AmbariException {
+        return false;
+      }
+
+      @Override
+      public boolean removeServicePrincipal(String principal) throws AmbariException {
+        return false;
+      }
+    });
+  }
+}
\ No newline at end of file

http://git-wip-us.apache.org/repos/asf/ambari/blob/1a18ddee/ambari-server/src/test/java/org/apache/ambari/server/serveraction/kerberos/KerberosServerActionTest.java
----------------------------------------------------------------------
diff --git a/ambari-server/src/test/java/org/apache/ambari/server/serveraction/kerberos/KerberosServerActionTest.java b/ambari-server/src/test/java/org/apache/ambari/server/serveraction/kerberos/KerberosServerActionTest.java
new file mode 100644
index 0000000..65e5cd6
--- /dev/null
+++ b/ambari-server/src/test/java/org/apache/ambari/server/serveraction/kerberos/KerberosServerActionTest.java
@@ -0,0 +1,195 @@
+/*
+ * 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.ambari.server.serveraction.kerberos;
+
+import junit.framework.Assert;
+import org.apache.ambari.server.AmbariException;
+import org.apache.ambari.server.actionmanager.HostRoleCommand;
+import org.apache.ambari.server.actionmanager.HostRoleStatus;
+import org.apache.ambari.server.agent.CommandReport;
+import org.apache.ambari.server.agent.ExecutionCommand;
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Test;
+
+import java.io.File;
+import java.util.HashMap;
+import java.util.Map;
+import java.util.concurrent.ConcurrentHashMap;
+import java.util.concurrent.ConcurrentMap;
+
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.when;
+
+public class KerberosServerActionTest {
+
+  Map<String, String> commandParams = new HashMap<String, String>();
+  File temporaryDirectory;
+  private KerberosServerAction action;
+
+  @Before
+  public void setUp() throws Exception {
+    final ExecutionCommand mockExecutionCommand = mock(ExecutionCommand.class);
+    final HostRoleCommand mockHostRoleCommand = mock(HostRoleCommand.class);
+
+    temporaryDirectory = File.createTempFile("ambari_ut_", ".d");
+
+    Assert.assertTrue(temporaryDirectory.delete());
+    Assert.assertTrue(temporaryDirectory.mkdirs());
+
+    // Create a data file
+    KerberosActionDataFileBuilder builder =
+        new KerberosActionDataFileBuilder(new File(temporaryDirectory, KerberosActionDataFile.DATA_FILE_NAME));
+    for (int i = 0; i < 10; i++) {
+      builder.addRecord("hostName", "serviceName" + i, "serviceComponentName" + i,
+          "principal|_HOST|_REALM" + i, "principalConfiguration" + i, "keytabFilePath" + i,
+          "keytabFileOwnerName" + i, "keytabFileOwnerAccess" + i,
+          "keytabFileGroupName" + i, "keytabFileGroupAccess" + i,
+          "keytabFileConfiguration" + i);
+    }
+    builder.close();
+
+    commandParams.put(KerberosServerAction.DATA_DIRECTORY, temporaryDirectory.getAbsolutePath());
+    commandParams.put(KerberosServerAction.DEFAULT_REALM, "REALM.COM");
+    commandParams.put(KerberosServerAction.KDC_TYPE, KDCType.MIT_KDC.toString());
+    commandParams.put(KerberosServerAction.ADMINISTRATOR_PRINCIPAL, "principal");
+    commandParams.put(KerberosServerAction.ADMINISTRATOR_PASSWORD, "password");
+    commandParams.put(KerberosServerAction.ADMINISTRATOR_KEYTAB, "keytab");
+
+    when(mockExecutionCommand.getCommandParams()).thenReturn(commandParams);
+
+    action = new KerberosServerAction() {
+
+      @Override
+      protected CommandReport processIdentity(Map<String, String> identityRecord, String evaluatedPrincipal,
+                                              KerberosOperationHandler operationHandler,
+                                              Map<String, Object> requestSharedDataContext)
+          throws AmbariException {
+        Assert.assertNotNull(requestSharedDataContext);
+
+        if (requestSharedDataContext.get("FAIL") != null) {
+          return createCommandReport(1, HostRoleStatus.FAILED, "{}", "ERROR", "ERROR");
+        } else {
+          requestSharedDataContext.put(identityRecord.get(KerberosActionDataFile.PRINCIPAL), evaluatedPrincipal);
+          return null;
+        }
+      }
+
+      @Override
+      public CommandReport execute(ConcurrentMap<String, Object> requestSharedDataContext)
+          throws AmbariException, InterruptedException {
+        return processIdentities(requestSharedDataContext);
+      }
+    };
+
+
+    action.setExecutionCommand(mockExecutionCommand);
+    action.setHostRoleCommand(mockHostRoleCommand);
+  }
+
+  @After
+  public void tearDown() throws Exception {
+    if (temporaryDirectory != null) {
+      new File(temporaryDirectory, KerberosActionDataFile.DATA_FILE_NAME).delete();
+      temporaryDirectory.delete();
+    }
+  }
+
+  @Test
+  public void testGetCommandParameterValueStatic() throws Exception {
+    Assert.assertNull(action.getCommandParameterValue("nonexistingvalue"));
+    Assert.assertEquals("REALM.COM", action.getCommandParameterValue(KerberosServerAction.DEFAULT_REALM));
+  }
+
+  @Test
+  public void testGetDefaultRealmStatic() throws Exception {
+    Assert.assertEquals("REALM.COM", KerberosServerAction.getDefaultRealm(commandParams));
+  }
+
+  @Test
+  public void testGetKDCTypeStatic() throws Exception {
+    Assert.assertEquals(KDCType.MIT_KDC, KerberosServerAction.getKDCType(commandParams));
+  }
+
+  @Test
+  public void testGetDataDirectoryPathStatic() throws Exception {
+    Assert.assertEquals(temporaryDirectory.getAbsolutePath(),
+        KerberosServerAction.getDataDirectoryPath(commandParams));
+  }
+
+  @Test
+  public void testCreateAdministratorCredentialStatic() throws Exception {
+    KerberosCredential credential1 = new KerberosCredential("principal", "password", "keytab");
+    KerberosCredential credential2 = KerberosServerAction.getAdministratorCredential(commandParams);
+
+    Assert.assertEquals(credential1.getPrincipal(), credential2.getPrincipal());
+    Assert.assertEquals(credential1.getPassword(), credential2.getPassword());
+    Assert.assertEquals(credential1.getKeytab(), credential2.getKeytab());
+  }
+
+  @Test
+  public void testSetPrincipalPasswordMapStatic() throws Exception {
+    ConcurrentMap<String, Object> sharedMap = new ConcurrentHashMap<String, Object>();
+    Map<String, String> dataMap = new HashMap<String, String>();
+
+    KerberosServerAction.setPrincipalPasswordMap(sharedMap, dataMap);
+    Assert.assertSame(dataMap, KerberosServerAction.getPrincipalPasswordMap(sharedMap));
+  }
+
+  @Test
+  public void testGetPrincipalPasswordMapStatic() throws Exception {
+    ConcurrentMap<String, Object> sharedMap = new ConcurrentHashMap<String, Object>();
+    Assert.assertNotNull(KerberosServerAction.getPrincipalPasswordMap(sharedMap));
+  }
+
+  @Test
+  public void testGetCommandParameterValue() throws Exception {
+    Assert.assertNull(action.getCommandParameterValue("invalid_parameter"));
+    Assert.assertEquals(commandParams.get(KerberosServerAction.DEFAULT_REALM),
+        action.getCommandParameterValue(KerberosServerAction.DEFAULT_REALM));
+  }
+
+  @Test
+  public void testGetDataDirectoryPath() throws Exception {
+    Assert.assertEquals(temporaryDirectory.getAbsolutePath(), action.getDataDirectoryPath());
+  }
+
+  @Test
+  public void testProcessIdentitiesSuccess() throws Exception {
+    ConcurrentMap<String, Object> sharedMap = new ConcurrentHashMap<String, Object>();
+    CommandReport report = action.processIdentities(sharedMap);
+    Assert.assertNotNull(report);
+    Assert.assertEquals(HostRoleStatus.COMPLETED.toString(), report.getStatus());
+
+    for (Map.Entry<String, Object> entry : sharedMap.entrySet()) {
+      Assert.assertEquals(entry.getValue(),
+          entry.getKey().replace("_HOST", "hostName").replace("_REALM", "REALM.COM"));
+    }
+  }
+
+  @Test
+  public void testProcessIdentitiesFail() throws Exception {
+    ConcurrentMap<String, Object> sharedMap = new ConcurrentHashMap<String, Object>();
+    sharedMap.put("FAIL", "true");
+
+    CommandReport report = action.processIdentities(sharedMap);
+    Assert.assertNotNull(report);
+    Assert.assertEquals(HostRoleStatus.FAILED.toString(), report.getStatus());
+  }
+}
\ No newline at end of file

http://git-wip-us.apache.org/repos/asf/ambari/blob/1a18ddee/ambari-server/src/test/java/org/apache/ambari/server/serveraction/kerberos/MITKerberosOperationHandlerTest.java
----------------------------------------------------------------------
diff --git a/ambari-server/src/test/java/org/apache/ambari/server/serveraction/kerberos/MITKerberosOperationHandlerTest.java b/ambari-server/src/test/java/org/apache/ambari/server/serveraction/kerberos/MITKerberosOperationHandlerTest.java
new file mode 100644
index 0000000..b61b76f
--- /dev/null
+++ b/ambari-server/src/test/java/org/apache/ambari/server/serveraction/kerberos/MITKerberosOperationHandlerTest.java
@@ -0,0 +1,66 @@
+/*
+ * 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.ambari.server.serveraction.kerberos;
+
+import junit.framework.Assert;
+import org.apache.ambari.server.AmbariException;
+import org.junit.Test;
+
+public class MITKerberosOperationHandlerTest extends AbstractKerberosOperationHandlerTest {
+
+  public MITKerberosOperationHandlerTest() {
+    super(new MITKerberosOperationHandler());
+  }
+
+
+  @Test
+  public void testSetPrincipalPasswordExceptions() throws Exception {
+    try {
+      handler.setPrincipalPassword(null, "1234");
+      Assert.fail("AmbariException not thrown for null principal");
+    } catch (Throwable t) {
+      Assert.assertEquals(AmbariException.class, t.getClass());
+    }
+
+    try {
+      handler.createServicePrincipal("", "1234");
+      Assert.fail("AmbariException not thrown for empty principal");
+    } catch (Throwable t) {
+      Assert.assertEquals(AmbariException.class, t.getClass());
+    }
+  }
+
+  @Test
+  public void testCreateServicePrincipalExceptions() throws Exception {
+    try {
+      handler.createServicePrincipal(null, "1234");
+      Assert.fail("AmbariException not thrown for null principal");
+    } catch (Throwable t) {
+      Assert.assertEquals(AmbariException.class, t.getClass());
+    }
+
+    try {
+      handler.createServicePrincipal("", "1234");
+      Assert.fail("AmbariException not thrown for empty principal");
+    } catch (Throwable t) {
+      Assert.assertEquals(AmbariException.class, t.getClass());
+    }
+  }
+
+}
\ No newline at end of file


[2/2] ambari git commit: AMBARI-8454. Create server-side actions to create kerberos principals and keytabs. (Robert Levas via rnettleton)

Posted by rn...@apache.org.
AMBARI-8454. Create server-side actions to create kerberos principals and keytabs. (Robert Levas via rnettleton)


Project: http://git-wip-us.apache.org/repos/asf/ambari/repo
Commit: http://git-wip-us.apache.org/repos/asf/ambari/commit/1a18ddee
Tree: http://git-wip-us.apache.org/repos/asf/ambari/tree/1a18ddee
Diff: http://git-wip-us.apache.org/repos/asf/ambari/diff/1a18ddee

Branch: refs/heads/trunk
Commit: 1a18ddeed1b4c00a822a6c125eb4e73bf0af515a
Parents: ed0681a
Author: Robert Levas <rl...@hortonworks.com>
Authored: Mon Dec 8 16:59:05 2014 -0500
Committer: Bob Nettleton <rn...@hortonworks.com>
Committed: Mon Dec 8 16:59:05 2014 -0500

----------------------------------------------------------------------
 ambari-project/pom.xml                          |   5 +
 ambari-server/pom.xml                           |   4 +
 .../kerberos/CreateKeytabFilesServerAction.java | 161 +++++++
 .../kerberos/CreatePrincipalsServerAction.java  | 125 ++++++
 .../server/serveraction/kerberos/KDCType.java   |  34 ++
 .../kerberos/KerberosActionDataFile.java        |  38 ++
 .../kerberos/KerberosActionDataFileBuilder.java | 159 +++++++
 .../kerberos/KerberosActionDataFileReader.java  | 115 +++++
 .../kerberos/KerberosCredential.java            |  92 ++++
 .../kerberos/KerberosOperationHandler.java      | 425 +++++++++++++++++++
 .../KerberosOperationHandlerFactory.java        |  52 +++
 .../kerberos/KerberosServerAction.java          | 381 +++++++++++++++++
 .../kerberos/MITKerberosOperationHandler.java   | 337 +++++++++++++++
 .../AbstractKerberosOperationHandlerTest.java   | 275 ++++++++++++
 .../kerberos/KerberosActionDataFileTest.java    | 202 +++++++++
 .../kerberos/KerberosOperationHandlerTest.java  |  59 +++
 .../kerberos/KerberosServerActionTest.java      | 195 +++++++++
 .../MITKerberosOperationHandlerTest.java        |  66 +++
 18 files changed, 2725 insertions(+)
----------------------------------------------------------------------


http://git-wip-us.apache.org/repos/asf/ambari/blob/1a18ddee/ambari-project/pom.xml
----------------------------------------------------------------------
diff --git a/ambari-project/pom.xml b/ambari-project/pom.xml
index 930080e..1ce8595 100644
--- a/ambari-project/pom.xml
+++ b/ambari-project/pom.xml
@@ -119,6 +119,11 @@
         <version>2.1</version>
       </dependency>
       <dependency>
+        <groupId>org.apache.commons</groupId>
+        <artifactId>commons-csv</artifactId>
+        <version>1.1</version>
+      </dependency>
+      <dependency>
         <groupId>com.google.inject.extensions</groupId>
         <artifactId>guice-servlet</artifactId>
         <version>3.0</version>

http://git-wip-us.apache.org/repos/asf/ambari/blob/1a18ddee/ambari-server/pom.xml
----------------------------------------------------------------------
diff --git a/ambari-server/pom.xml b/ambari-server/pom.xml
index b11538d..f1170c9 100644
--- a/ambari-server/pom.xml
+++ b/ambari-server/pom.xml
@@ -1386,6 +1386,10 @@
       <artifactId>commons-io</artifactId>
     </dependency>
     <dependency>
+      <groupId>org.apache.commons</groupId>
+      <artifactId>commons-csv</artifactId>
+    </dependency>
+    <dependency>
       <groupId>com.google.inject.extensions</groupId>
       <artifactId>guice-assistedinject</artifactId>
     </dependency>

http://git-wip-us.apache.org/repos/asf/ambari/blob/1a18ddee/ambari-server/src/main/java/org/apache/ambari/server/serveraction/kerberos/CreateKeytabFilesServerAction.java
----------------------------------------------------------------------
diff --git a/ambari-server/src/main/java/org/apache/ambari/server/serveraction/kerberos/CreateKeytabFilesServerAction.java b/ambari-server/src/main/java/org/apache/ambari/server/serveraction/kerberos/CreateKeytabFilesServerAction.java
new file mode 100644
index 0000000..057bf18
--- /dev/null
+++ b/ambari-server/src/main/java/org/apache/ambari/server/serveraction/kerberos/CreateKeytabFilesServerAction.java
@@ -0,0 +1,161 @@
+/*
+ * 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.ambari.server.serveraction.kerberos;
+
+import org.apache.ambari.server.AmbariException;
+import org.apache.ambari.server.actionmanager.HostRoleStatus;
+import org.apache.ambari.server.agent.CommandReport;
+import org.apache.commons.codec.digest.DigestUtils;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import java.io.File;
+import java.util.Map;
+import java.util.concurrent.ConcurrentMap;
+
+/**
+ * CreateKeytabFilesServerAction is a ServerAction implementation that creates keytab files as
+ * instructed.
+ * <p/>
+ * This class mainly relies on the KerberosServerAction to iterate through metadata identifying
+ * the Kerberos keytab files that need to be created. For each identity in the metadata, this
+ * implementation's
+ * {@link KerberosServerAction#processIdentity(java.util.Map, String, KerberosOperationHandler, java.util.Map)}
+ * is invoked attempting the creation of the relevant keytab file.
+ */
+public class CreateKeytabFilesServerAction extends KerberosServerAction {
+  private final static Logger LOG = LoggerFactory.getLogger(CreateKeytabFilesServerAction.class);
+
+  /**
+   * Called to execute this action.  Upon invocation, calls
+   * {@link org.apache.ambari.server.serveraction.kerberos.KerberosServerAction#processIdentities(java.util.Map)} )}
+   * to iterate through the Kerberos identity metadata and call
+   * {@link org.apache.ambari.server.serveraction.kerberos.CreateKeytabFilesServerAction#processIdentities(java.util.Map)}
+   * for each identity to process.
+   *
+   * @param requestSharedDataContext a Map to be used a shared data among all ServerActions related
+   *                                 to a given request
+   * @return a CommandReport indicating the result of this action
+   * @throws AmbariException
+   * @throws InterruptedException
+   */
+  @Override
+  public CommandReport execute(ConcurrentMap<String, Object> requestSharedDataContext) throws
+      AmbariException, InterruptedException {
+    return processIdentities(requestSharedDataContext);
+  }
+
+
+  /**
+   * For each identity, create a keytab and append to a new or existing keytab file.
+   * <p/>
+   * It is expected that the {@link org.apache.ambari.server.serveraction.kerberos.CreatePrincipalsServerAction}
+   * (or similar) has executed before this action and a set of passwords has been created, map to
+   * their relevant (evaluated) principals and stored in the requestSharedDataContext.
+   * <p/>
+   * If a password exists for the current evaluatedPrincipal, use a
+   * {@link org.apache.ambari.server.serveraction.kerberos.KerberosOperationHandler} to generate
+   * the keytab file. To help avoid filename collisions and to build a structure that is easy to
+   * discover, each keytab file is stored in host-specific
+   * ({@link org.apache.ambari.server.serveraction.kerberos.KerberosActionDataFileReader#HOSTNAME})
+   * directory using the SHA1 hash of its destination file path
+   * ({@link org.apache.ambari.server.serveraction.kerberos.KerberosActionDataFileReader#KEYTAB_FILE_PATH})
+   * <p/>
+   * <pre>
+   *   data_directory
+   *   |- host1
+   *   |  |- 16a054404c8826cd604a27ac970e8cc4b9c7a3fa   (keytab file)
+   *   |  |- ...                                        (keytab files)
+   *   |  |- a3c09cae73406912e8c55296d1c85b674d24f576   (keytab file)
+   *   |- host2
+   *   |  |- ...
+   * </pre>
+   *
+   * @param identityRecord           a Map containing the data for the current identity record
+   * @param evaluatedPrincipal       a String indicating the relevant principal
+   * @param operationHandler         a KerberosOperationHandler used to perform Kerberos-related
+   *                                 tasks for specific Kerberos implementations
+   *                                 (MIT, Active Directory, etc...)
+   * @param requestSharedDataContext a Map to be used a shared data among all ServerActions related
+   *                                 to a given request
+   * @return a CommandReport, indicating an error condition; or null, indicating a success condition
+   * @throws AmbariException if an error occurs while processing the identity record
+   */
+  @Override
+  protected CommandReport processIdentity(Map<String, String> identityRecord, String evaluatedPrincipal,
+                                          KerberosOperationHandler operationHandler,
+                                          Map<String, Object> requestSharedDataContext)
+      throws AmbariException {
+    CommandReport commandReport = null;
+
+    if (identityRecord != null) {
+
+      if (operationHandler == null) {
+        String message = String.format("Failed to create keytab file for %s, missing handler", evaluatedPrincipal);
+        LOG.error(message);
+        commandReport = createCommandReport(1, HostRoleStatus.FAILED, "{}", "", message);
+      } else {
+        Map<String, String> principalPasswordMap = getPrincipalPasswordMap(requestSharedDataContext);
+
+        if (principalPasswordMap != null) {
+          String host = identityRecord.get(KerberosActionDataFileBuilder.HOSTNAME);
+          String keytabFilePath = identityRecord.get(KerberosActionDataFileBuilder.KEYTAB_FILE_PATH);
+
+          if ((host != null) && !host.isEmpty() && (keytabFilePath != null) && !keytabFilePath.isEmpty()) {
+            // Look up the current evaluatedPrincipal's password.
+            // If found create th keytab file, else skip it.
+            String password = principalPasswordMap.get(evaluatedPrincipal);
+
+            if (password == null) {
+              String message = String.format("Failed to create keytab file for %s, missing password", evaluatedPrincipal);
+              LOG.error(message);
+              commandReport = createCommandReport(1, HostRoleStatus.FAILED, "{}", "", message);
+            } else {
+              // Determine where to store the keytab file.  It should go into a host-specific
+              // directory under the previously determined data directory.
+              File hostDirectory = new File(getDataDirectoryPath(), host);
+
+              // Ensure the host directory exists...
+              if (hostDirectory.exists() || hostDirectory.mkdirs()) {
+                File keytabFile = new File(hostDirectory, DigestUtils.sha1Hex(keytabFilePath));
+
+                if (operationHandler.createKeytabFile(evaluatedPrincipal, password, keytabFile)) {
+                  LOG.debug("Successfully created keytab file for {} at {}",
+                      evaluatedPrincipal, keytabFile.getAbsolutePath());
+                } else {
+                  String message = String.format("Failed to create keytab file for %s at %s",
+                      evaluatedPrincipal, keytabFile.getAbsolutePath());
+                  LOG.error(message);
+                  commandReport = createCommandReport(1, HostRoleStatus.FAILED, "{}", "", message);
+                }
+              } else {
+                String message = String.format("Failed to create keytab file for %s, the container directory does not exist: %s",
+                    evaluatedPrincipal, hostDirectory.getAbsolutePath());
+                LOG.error(message);
+                commandReport = createCommandReport(1, HostRoleStatus.FAILED, "{}", "", message);
+              }
+            }
+          }
+        }
+      }
+    }
+
+    return commandReport;
+  }
+}

http://git-wip-us.apache.org/repos/asf/ambari/blob/1a18ddee/ambari-server/src/main/java/org/apache/ambari/server/serveraction/kerberos/CreatePrincipalsServerAction.java
----------------------------------------------------------------------
diff --git a/ambari-server/src/main/java/org/apache/ambari/server/serveraction/kerberos/CreatePrincipalsServerAction.java b/ambari-server/src/main/java/org/apache/ambari/server/serveraction/kerberos/CreatePrincipalsServerAction.java
new file mode 100644
index 0000000..3785e19
--- /dev/null
+++ b/ambari-server/src/main/java/org/apache/ambari/server/serveraction/kerberos/CreatePrincipalsServerAction.java
@@ -0,0 +1,125 @@
+/*
+ * 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.ambari.server.serveraction.kerberos;
+
+import org.apache.ambari.server.AmbariException;
+import org.apache.ambari.server.actionmanager.HostRoleStatus;
+import org.apache.ambari.server.agent.CommandReport;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import java.util.Map;
+import java.util.concurrent.ConcurrentMap;
+
+/**
+ * CreatePrincipalsServerAction is a ServerAction implementation that creates principals as instructed.
+ * <p/>
+ * This class mainly relies on the KerberosServerAction to iterate through metadata identifying
+ * the Kerberos principals that need to be created. For each identity in the metadata, this implementation's
+ * {@link KerberosServerAction#processIdentity(java.util.Map, String, KerberosOperationHandler, java.util.Map)}
+ * is invoked attempting the creation of the relevant principal.
+ */
+public class CreatePrincipalsServerAction extends KerberosServerAction {
+  private final static Logger LOG = LoggerFactory.getLogger(CreatePrincipalsServerAction.class);
+
+
+  /**
+   * Called to execute this action.  Upon invocation, calls
+   * {@link org.apache.ambari.server.serveraction.kerberos.KerberosServerAction#processIdentities(java.util.Map)}
+   * to iterate through the Kerberos identity metadata and call
+   * {@link org.apache.ambari.server.serveraction.kerberos.CreatePrincipalsServerAction#processIdentities(java.util.Map)}
+   * for each identity to process.
+   *
+   * @param requestSharedDataContext a Map to be used a shared data among all ServerActions related
+   *                                 to a given request
+   * @return a CommandReport indicating the result of this action
+   * @throws AmbariException
+   * @throws InterruptedException
+   */
+  @Override
+  public CommandReport execute(ConcurrentMap<String, Object> requestSharedDataContext) throws
+      AmbariException, InterruptedException {
+    return processIdentities(requestSharedDataContext);
+  }
+
+
+  /**
+   * For each identity, generate a unique password create a new or update an existing principal in
+   * an assume to be configured KDC.
+   * <p/>
+   * If a password has not been previously created the current evaluatedPrincipal, create a "secure"
+   * password using {@link KerberosOperationHandler#createSecurePassword()}.  Then if the principal
+   * does not exist in the KDC, create it using the generated password; else if it does exist update
+   * its password.  Finally store the generated password in the shared principal-to-password map so
+   * that subsequent process may use it if necessary.
+   *
+   * @param identityRecord           a Map containing the data for the current identity record
+   * @param evaluatedPrincipal       a String indicating the relevant principal
+   * @param operationHandler         a KerberosOperationHandler used to perform Kerberos-related
+   *                                 tasks for specific Kerberos implementations
+   *                                 (MIT, Active Directory, etc...)
+   * @param requestSharedDataContext a Map to be used a shared data among all ServerActions related
+   *                                 to a given request
+   * @return a CommandReport, indicating an error condition; or null, indicating a success condition
+   * @throws AmbariException if an error occurs while processing the identity record
+   */
+  @Override
+  protected CommandReport processIdentity(Map<String, String> identityRecord, String evaluatedPrincipal,
+                                          KerberosOperationHandler operationHandler,
+                                          Map<String, Object> requestSharedDataContext)
+      throws AmbariException {
+    CommandReport commandReport = null;
+
+    Map<String, String> principalPasswordMap = getPrincipalPasswordMap(requestSharedDataContext);
+
+    String password = principalPasswordMap.get(evaluatedPrincipal);
+
+    if (password == null) {
+      password = operationHandler.createSecurePassword();
+
+      if (operationHandler.principalExists(evaluatedPrincipal)) {
+        // Create a new password since we need to know what it is.
+        // A new password/key would have been generated after exporting the keytab anyways.
+        LOG.warn("Principal already exists, setting new password - {}", evaluatedPrincipal);
+
+        if (operationHandler.setPrincipalPassword(evaluatedPrincipal, password)) {
+          principalPasswordMap.put(evaluatedPrincipal, password);
+          LOG.debug("Successfully set password for principal {}", evaluatedPrincipal);
+        } else {
+          String message = String.format("Failed to set password for principal %s, unknown reason", evaluatedPrincipal);
+          LOG.error(message);
+          commandReport = createCommandReport(1, HostRoleStatus.FAILED, "{}", "", message);
+        }
+      } else {
+        LOG.debug("Creating new principal - {}", evaluatedPrincipal);
+
+        if (operationHandler.createServicePrincipal(evaluatedPrincipal, password)) {
+          principalPasswordMap.put(evaluatedPrincipal, password);
+          LOG.debug("Successfully created new principal {}", evaluatedPrincipal);
+        } else {
+          String message = String.format("Failed to create principal %s, unknown reason", evaluatedPrincipal);
+          LOG.error(message);
+          commandReport = createCommandReport(1, HostRoleStatus.FAILED, "{}", "", message);
+        }
+      }
+    }
+
+    return commandReport;
+  }
+}

http://git-wip-us.apache.org/repos/asf/ambari/blob/1a18ddee/ambari-server/src/main/java/org/apache/ambari/server/serveraction/kerberos/KDCType.java
----------------------------------------------------------------------
diff --git a/ambari-server/src/main/java/org/apache/ambari/server/serveraction/kerberos/KDCType.java b/ambari-server/src/main/java/org/apache/ambari/server/serveraction/kerberos/KDCType.java
new file mode 100644
index 0000000..51dcdd2
--- /dev/null
+++ b/ambari-server/src/main/java/org/apache/ambari/server/serveraction/kerberos/KDCType.java
@@ -0,0 +1,34 @@
+/*
+ * 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.ambari.server.serveraction.kerberos;
+
+/**
+ * Enumerates the supported KDC types
+ */
+public enum KDCType {
+  /**
+   * Indicates an MIT KDC (or similar)
+   */
+  MIT_KDC,
+
+  /**
+   * Indicates a Microsoft Active Directory
+   */
+  ACTIVE_DIRECTORY
+}

http://git-wip-us.apache.org/repos/asf/ambari/blob/1a18ddee/ambari-server/src/main/java/org/apache/ambari/server/serveraction/kerberos/KerberosActionDataFile.java
----------------------------------------------------------------------
diff --git a/ambari-server/src/main/java/org/apache/ambari/server/serveraction/kerberos/KerberosActionDataFile.java b/ambari-server/src/main/java/org/apache/ambari/server/serveraction/kerberos/KerberosActionDataFile.java
new file mode 100644
index 0000000..f2a0f06
--- /dev/null
+++ b/ambari-server/src/main/java/org/apache/ambari/server/serveraction/kerberos/KerberosActionDataFile.java
@@ -0,0 +1,38 @@
+/*
+ * 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.ambari.server.serveraction.kerberos;
+
+/**
+ * An interface to contain common record column names for KerberosActionDataFile classes.
+ */
+public interface KerberosActionDataFile {
+  public static final String DATA_FILE_NAME = "index.dat";
+
+  public static final String HOSTNAME = "hostname";
+  public static final String SERVICE = "service";
+  public static final String COMPONENT = "component";
+  public static final String PRINCIPAL = "principal";
+  public static final String PRINCIPAL_CONFIGURATION = "principal_configuration";
+  public static final String KEYTAB_FILE_PATH = "keytab_file_path";
+  public static final String KEYTAB_FILE_OWNER_NAME = "keytab_file_owner_name";
+  public static final String KEYTAB_FILE_OWNER_ACCESS = "keytab_file_owner_access";
+  public static final String KEYTAB_FILE_GROUP_NAME = "keytab_file_group_name";
+  public static final String KEYTAB_FILE_GROUP_ACCESS = "keytab_file_group_access";
+  public static final String KEYTAB_FILE_CONFIGURATION = "keytab_file_configuration";
+}

http://git-wip-us.apache.org/repos/asf/ambari/blob/1a18ddee/ambari-server/src/main/java/org/apache/ambari/server/serveraction/kerberos/KerberosActionDataFileBuilder.java
----------------------------------------------------------------------
diff --git a/ambari-server/src/main/java/org/apache/ambari/server/serveraction/kerberos/KerberosActionDataFileBuilder.java b/ambari-server/src/main/java/org/apache/ambari/server/serveraction/kerberos/KerberosActionDataFileBuilder.java
new file mode 100644
index 0000000..b19e6f4
--- /dev/null
+++ b/ambari-server/src/main/java/org/apache/ambari/server/serveraction/kerberos/KerberosActionDataFileBuilder.java
@@ -0,0 +1,159 @@
+/*
+ * 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.ambari.server.serveraction.kerberos;
+
+import org.apache.commons.csv.CSVFormat;
+import org.apache.commons.csv.CSVPrinter;
+
+import java.io.File;
+import java.io.FileWriter;
+import java.io.IOException;
+
+/**
+ * KerberosActionDataFileBuilder is an implementation of a KerberosActionDataFile that is used to
+ * create a new KerberosActionDataFile.
+ * <p/>
+ * This class encapsulates a {@link org.apache.commons.csv.CSVPrinter} to create a CSV-formatted file.
+ */
+public class KerberosActionDataFileBuilder implements KerberosActionDataFile {
+
+  private File file;
+  private CSVPrinter csvPrinter;
+
+  /**
+   * Creates a new KerberosActionDataFileBuilder
+   * <p/>
+   * The file is opened upon creation, so there is no need to manually open it unless manually
+   * closed before using.
+   *
+   * @param file a File declaring where to write the data
+   * @throws IOException
+   */
+  public KerberosActionDataFileBuilder(File file) throws IOException {
+    this.file = file;
+    open();
+  }
+
+
+  /**
+   * Opens the data file for writing.
+   * <p/>
+   * This may be called multiple times and the appropriate action will occur depending on if the
+   * file has been previously opened or closed.
+   *
+   * @throws IOException
+   */
+  public void open() throws IOException {
+    if (isClosed()) {
+      if (file == null) {
+        throw new IOException("Missing file path");
+      } else {
+        // If the file is empty, write the header; else don't write the header.
+        boolean writeHeader = file.length() == 0;
+
+        csvPrinter = new CSVPrinter(new FileWriter(file, true), CSVFormat.DEFAULT);
+
+        if (writeHeader) {
+          // Write the header....
+          csvPrinter.printRecord(HOSTNAME,
+              SERVICE,
+              COMPONENT,
+              PRINCIPAL,
+              PRINCIPAL_CONFIGURATION,
+              KEYTAB_FILE_PATH,
+              KEYTAB_FILE_OWNER_NAME,
+              KEYTAB_FILE_OWNER_ACCESS,
+              KEYTAB_FILE_GROUP_NAME,
+              KEYTAB_FILE_GROUP_ACCESS,
+              KEYTAB_FILE_CONFIGURATION);
+        }
+      }
+    }
+  }
+
+  /**
+   * Tests this KerberosActionDataFileBuilder to see if the data file is closed.
+   *
+   * @return true if closed; otherwise false
+   */
+  public boolean isClosed() {
+    return csvPrinter == null;
+  }
+
+  /**
+   * Closes the data file
+   *
+   * @throws IOException
+   */
+  public void close() throws IOException {
+    if (csvPrinter != null) {
+      csvPrinter.close();
+      csvPrinter = null;
+    }
+  }
+
+  /**
+   * Appends a new record to the data file
+   *
+   * @param hostName                a String containing the hostname column data
+   * @param serviceName             a String containing the service name column data
+   * @param serviceComponentName    a String containing the component name column data
+   * @param principal               a String containing the (raw, non-evaluated) principal "pattern"
+   *                                column data
+   * @param principalConfiguration  a String containing the principal's configuration property column data
+   *                                (expected to be the type and name of the configuration property
+   *                                to use to store the evaluated principal data in
+   *                                - i.e., config-type/property)
+   * @param keytabFilePath          a String containing the destination keytab file path column data
+   * @param keytabFileOwnerName     a String containing the keytab file owner name column data
+   * @param keytabFileOwnerAccess   a String containing the keytab file owner access column data
+   *                                (expected to be "r" or "rw")
+   * @param keytabFileGroupName     a String containing the keytab file group name column data
+   * @param keytabFileGroupAccess   a String containing the keytab file group access column data
+   *                                (expected to be "r", "rw", or "")
+   * @param keytabFileConfiguration a String containing the keytab's configuration property column data
+   *                                (expected to be the type and name of the configuration property
+   *                                to use to store the keytab file's absolute path in
+   *                                - i.e., config-type/property)
+   * @throws IOException
+   */
+  public void addRecord(String hostName, String serviceName, String serviceComponentName,
+                        String principal, String principalConfiguration, String keytabFilePath,
+                        String keytabFileOwnerName, String keytabFileOwnerAccess,
+                        String keytabFileGroupName, String keytabFileGroupAccess,
+                        String keytabFileConfiguration) throws IOException {
+
+    if (csvPrinter == null) {
+      throw new IOException("Data file is not open");
+    }
+
+    csvPrinter.printRecord(hostName,
+        serviceName,
+        serviceComponentName,
+        principal,
+        principalConfiguration,
+        keytabFilePath,
+        keytabFileOwnerName,
+        keytabFileOwnerAccess,
+        keytabFileGroupName,
+        keytabFileGroupAccess,
+        keytabFileConfiguration);
+  }
+
+}

http://git-wip-us.apache.org/repos/asf/ambari/blob/1a18ddee/ambari-server/src/main/java/org/apache/ambari/server/serveraction/kerberos/KerberosActionDataFileReader.java
----------------------------------------------------------------------
diff --git a/ambari-server/src/main/java/org/apache/ambari/server/serveraction/kerberos/KerberosActionDataFileReader.java b/ambari-server/src/main/java/org/apache/ambari/server/serveraction/kerberos/KerberosActionDataFileReader.java
new file mode 100644
index 0000000..f3d93f5
--- /dev/null
+++ b/ambari-server/src/main/java/org/apache/ambari/server/serveraction/kerberos/KerberosActionDataFileReader.java
@@ -0,0 +1,115 @@
+/*
+ * 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.ambari.server.serveraction.kerberos;
+
+import org.apache.commons.csv.CSVFormat;
+import org.apache.commons.csv.CSVParser;
+import org.apache.commons.csv.CSVRecord;
+
+import java.io.File;
+import java.io.IOException;
+import java.nio.charset.Charset;
+import java.util.Iterator;
+import java.util.Map;
+
+/**
+ * KerberosActionDataFileReader is an implementation of a KerberosActionDataFile that is used to
+ * read existing KerberosActionDataFiles.
+ * <p/>
+ * This class encapsulates a {@link org.apache.commons.csv.CSVParser} to read a CSV-formatted file.
+ */
+public class KerberosActionDataFileReader implements KerberosActionDataFile, Iterable<Map<String, String>> {
+
+  private File file;
+  private CSVParser csvParser = null;
+
+
+  /**
+   * Creates a new KerberosActionDataFileReader
+   * <p/>
+   * The file is opened upon creation, so there is no need to manually open it unless manually
+   * closed before using.
+   *
+   * @param file a File declaring where to write the data
+   * @throws IOException
+   */
+  public KerberosActionDataFileReader(File file) throws IOException {
+    this.file = file;
+    open();
+  }
+
+
+  /**
+   * Opens the data file for reading.
+   * <p/>
+   * This may be called multiple times and the appropriate action will occur depending on if the
+   * file has been previously opened or closed.
+   *
+   * @throws IOException
+   */
+  public void open() throws IOException {
+    if (isClosed()) {
+      csvParser = CSVParser.parse(file, Charset.defaultCharset(), CSVFormat.DEFAULT.withHeader());
+    }
+  }
+
+
+  /**
+   * Tests this KerberosActionDataFileReader  to see if the data file is closed.
+   *
+   * @return true if closed; otherwise false
+   */
+  public boolean isClosed() {
+    return csvParser == null;
+  }
+
+  public void close() throws IOException {
+    csvParser.close();
+    csvParser = null;
+  }
+
+  /**
+   * Gets an iterator to use to access the records in the data file.
+   * <p/>
+   * Each item is a Map of column names to values.
+   *
+   * @return an Iterator
+   */
+  @Override
+  public Iterator<Map<String, String>> iterator() {
+    return new Iterator<Map<String, String>>() {
+      Iterator<CSVRecord> iterator = csvParser.iterator();
+
+      @Override
+      public boolean hasNext() {
+        return iterator.hasNext();
+      }
+
+      @Override
+      public Map<String, String> next() {
+        return iterator.next().toMap();
+      }
+
+      @Override
+      public void remove() {
+        iterator.remove();
+      }
+    };
+  }
+}

http://git-wip-us.apache.org/repos/asf/ambari/blob/1a18ddee/ambari-server/src/main/java/org/apache/ambari/server/serveraction/kerberos/KerberosCredential.java
----------------------------------------------------------------------
diff --git a/ambari-server/src/main/java/org/apache/ambari/server/serveraction/kerberos/KerberosCredential.java b/ambari-server/src/main/java/org/apache/ambari/server/serveraction/kerberos/KerberosCredential.java
new file mode 100644
index 0000000..adb2edc
--- /dev/null
+++ b/ambari-server/src/main/java/org/apache/ambari/server/serveraction/kerberos/KerberosCredential.java
@@ -0,0 +1,92 @@
+/**
+ * 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.ambari.server.serveraction.kerberos;
+
+/**
+ * KerberosCredential encapsulates data needed to authenticate an identity to a KDC.
+ */
+public class KerberosCredential {
+
+  private String principal = null;
+  private String password = null;
+  private String keytab = null;
+
+  /**
+   * Creates an empty KerberosCredential
+   */
+  public KerberosCredential() {
+    principal = null;
+    password = null;
+    keytab = null;
+  }
+
+  /**
+   * Creates a new KerberosCredential
+   *
+   * @param principal a String containing the principal name for this Kerberos credential
+   * @param password  a String containing the password for this Kerberos credential
+   * @param keytab    a String containing the base64 encoded keytab for this Kerberos credential
+   */
+  public KerberosCredential(String principal, String password, String keytab) {
+    this.principal = principal;
+    this.password = password;
+    this.keytab = keytab;
+  }
+
+  /**
+   * @return a String containing the principal name for this Kerberos credential
+   */
+  public String getPrincipal() {
+    return principal;
+  }
+
+  /**
+   * @param principal a String containing the principal name for this Kerberos credential
+   */
+  public void setPrincipal(String principal) {
+    this.principal = principal;
+  }
+
+  /**
+   * @return a String containing the password for this Kerberos credential
+   */
+  public String getPassword() {
+    return password;
+  }
+
+  /**
+   * @param password a String containing the password for this Kerberos credential
+   */
+  public void setPassword(String password) {
+    this.password = password;
+  }
+
+  /**
+   * @return a String containing the base64 encoded keytab for this Kerberos credential
+   */
+  public String getKeytab() {
+    return keytab;
+  }
+
+  /**
+   * @param keytab a String containing the base64 encoded keytab for this Kerberos credential
+   */
+  public void setKeytab(String keytab) {
+    this.keytab = keytab;
+  }
+}

http://git-wip-us.apache.org/repos/asf/ambari/blob/1a18ddee/ambari-server/src/main/java/org/apache/ambari/server/serveraction/kerberos/KerberosOperationHandler.java
----------------------------------------------------------------------
diff --git a/ambari-server/src/main/java/org/apache/ambari/server/serveraction/kerberos/KerberosOperationHandler.java b/ambari-server/src/main/java/org/apache/ambari/server/serveraction/kerberos/KerberosOperationHandler.java
new file mode 100644
index 0000000..896ed31
--- /dev/null
+++ b/ambari-server/src/main/java/org/apache/ambari/server/serveraction/kerberos/KerberosOperationHandler.java
@@ -0,0 +1,425 @@
+/*
+ * 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.ambari.server.serveraction.kerberos;
+
+import org.apache.ambari.server.AmbariException;
+import org.apache.ambari.server.utils.ShellCommandUtil;
+import org.apache.commons.codec.binary.Base64;
+import org.apache.directory.server.kerberos.shared.crypto.encryption.KerberosKeyFactory;
+import org.apache.directory.server.kerberos.shared.keytab.Keytab;
+import org.apache.directory.server.kerberos.shared.keytab.KeytabEntry;
+import org.apache.directory.shared.kerberos.KerberosTime;
+import org.apache.directory.shared.kerberos.codec.types.EncryptionType;
+import org.apache.directory.shared.kerberos.components.EncryptionKey;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import java.io.File;
+import java.io.FileNotFoundException;
+import java.io.FileOutputStream;
+import java.io.IOException;
+import java.io.OutputStream;
+import java.security.SecureRandom;
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Map;
+
+/**
+ * KerberosOperationHandler is an abstract class providing basic implementations of common Kerberos
+ * operations (like generating secure passwords) and placeholders for KDC-specific operations
+ * (such as creating principals).
+ */
+public abstract class KerberosOperationHandler {
+  private final static Logger LOG = LoggerFactory.getLogger(KerberosOperationHandler.class);
+
+  private final static SecureRandom SECURE_RANDOM = new SecureRandom();
+
+  /**
+   * The number of characters to generate for a secure password
+   */
+  protected final static int SECURE_PASSWORD_LENGTH = 18;
+
+  /**
+   * The set of available characters to use when generating a secure password
+   */
+  private final static char[] SECURE_PASSWORD_CHARS =
+      "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ1234567890?.!$%^*()-_+=~".toCharArray();
+
+  private KerberosCredential administratorCredentials;
+  private String defaultRealm;
+
+
+  /**
+   * Create a secure (random) password using a secure random number generator and a set of (reasonable)
+   * characters.
+   *
+   * @return a String containing the new password
+   */
+  public String createSecurePassword() {
+    return createSecurePassword(SECURE_PASSWORD_LENGTH);
+  }
+
+  /**
+   * Create a secure (random) password using a secure random number generator and a set of (reasonable)
+   * characters.
+   *
+   * @param length an integer value declaring the length of the password to create,
+   *               if <1, a default will be used.
+   * @return a String containing the new password
+   */
+  public String createSecurePassword(int length) {
+    StringBuilder passwordBuilder;
+
+    // If the supplied length is less than 1 use the default value.
+    if (length < 1) {
+      length = SECURE_PASSWORD_LENGTH;
+    }
+
+    // Create a new StringBuilder and ensure its capacity is set for the length of the password to
+    // be generated
+    passwordBuilder = new StringBuilder(length);
+
+    // For each character to be added to the password, (securely) generate a random number to pull
+    // a random character from the character array
+    for (int i = 0; i < length; i++) {
+      passwordBuilder.append(SECURE_PASSWORD_CHARS[SECURE_RANDOM.nextInt(SECURE_PASSWORD_CHARS.length)]);
+    }
+
+    return passwordBuilder.toString();
+  }
+
+  /**
+   * Prepares and creates resources to be used by this KerberosOperationHandler
+   * <p/>
+   * It is expected that this KerberosOperationHandler will not be used before this call.
+   *
+   * @param administratorCredentials a KerberosCredential containing the administrative credentials
+   *                                 for the relevant KDC
+   * @param defaultRealm             a String declaring the default Kerberos realm (or domain)
+   */
+  public abstract void open(KerberosCredential administratorCredentials, String defaultRealm)
+      throws AmbariException;
+
+  /**
+   * Closes and cleans up any resources used by this KerberosOperationHandler
+   * <p/>
+   * It is expected that this KerberosOperationHandler will not be used after this call.
+   */
+  public abstract void close()
+      throws AmbariException;
+
+  /**
+   * Test to see if the specified principal exists in a previously configured KDC
+   * <p/>
+   * The implementation is specific to a particular type of KDC.
+   *
+   * @param principal a String containing the principal to test
+   * @return true if the principal exists; false otherwise
+   * @throws AmbariException
+   */
+  public abstract boolean principalExists(String principal)
+      throws AmbariException;
+
+  /**
+   * Creates a new principal in a previously configured KDC
+   * <p/>
+   * The implementation is specific to a particular type of KDC.
+   *
+   * @param principal a String containing the principal to add
+   * @param password  a String containing the password to use when creating the principal
+   * @return true if the principal was successfully created; otherwise false
+   * @throws AmbariException
+   */
+  public abstract boolean createServicePrincipal(String principal, String password)
+      throws AmbariException;
+
+  /**
+   * Updates the password for an existing principal in a previously configured KDC
+   * <p/>
+   * The implementation is specific to a particular type of KDC.
+   *
+   * @param principal a String containing the principal to update
+   * @param password  a String containing the password to set
+   * @return true if the password was successfully updated; otherwise false
+   * @throws AmbariException
+   */
+  public abstract boolean setPrincipalPassword(String principal, String password)
+      throws AmbariException;
+
+  /**
+   * Removes an existing principal in a previously configured KDC
+   * <p/>
+   * The implementation is specific to a particular type of KDC.
+   *
+   * @param principal a String containing the principal to remove
+   * @return true if the principal was successfully removed; otherwise false
+   * @throws AmbariException
+   */
+  public abstract boolean removeServicePrincipal(String principal)
+      throws AmbariException;
+
+  /**
+   * Create or append to a keytab file using the specified principal and password.
+   *
+   * @param principal  a String containing the principal to test
+   * @param password   a String containing the password to use when creating the principal
+   * @param keytabFile a File containing the absolute path to the keytab file
+   * @return true if the keytab file was successfully created; false otherwise
+   * @throws AmbariException
+   */
+  public boolean createKeytabFile(String principal, String password, File keytabFile)
+      throws AmbariException {
+    boolean success = false;
+
+    if ((principal == null) || principal.isEmpty()) {
+      throw new AmbariException("Failed to create keytab file, missing principal");
+    } else if (password == null) {
+      throw new AmbariException(String.format("Failed to create keytab file for %s, missing password", principal));
+    } else if (keytabFile == null) {
+      throw new AmbariException(String.format("Failed to create keytab file for %s, missing file path", principal));
+    } else {
+      // Create a set of keys and relevant keytab entries
+      Map<EncryptionType, EncryptionKey> keys = KerberosKeyFactory.getKerberosKeys(principal, password);
+
+      if (keys != null) {
+        KerberosTime timestamp = new KerberosTime();
+        List<KeytabEntry> keytabEntries = new ArrayList<KeytabEntry>();
+
+        Keytab keytab;
+
+        if (keytabFile.exists() && keytabFile.canRead() && (keytabFile.length() > 0)) {
+          // If the keytab file already exists, read it in and append the new keytabs to it so that
+          // potentially important data is not lost
+          try {
+            keytab = Keytab.read(keytabFile);
+          } catch (IOException e) {
+            // There was an issue reading in the existing keytab file... we might loose some keytabs
+            // but that is unlikely...
+            keytab = new Keytab();
+          }
+
+          // In case there were any existing keytab entries, add them to the new entries list do
+          // they are not lost
+          List<KeytabEntry> existingEntries = keytab.getEntries();
+          if ((existingEntries != null) && !existingEntries.isEmpty()) {
+            keytabEntries.addAll(existingEntries);
+          }
+        } else {
+          keytab = new Keytab();
+        }
+
+        for (EncryptionKey encryptionKey : keys.values()) {
+          keytabEntries.add(new KeytabEntry(principal, 1, timestamp, (byte) 0, encryptionKey));
+        }
+
+        keytab.setEntries(keytabEntries);
+
+        try {
+          keytab.write(keytabFile);
+          success = true;
+        } catch (IOException e) {
+          String message = String.format("Failed to export keytab file for %s", principal);
+          LOG.error(message, e);
+
+          if (!keytabFile.delete()) {
+            keytabFile.deleteOnExit();
+          }
+
+          throw new AmbariException(message, e);
+        }
+      }
+    }
+
+    return success;
+  }
+
+  /**
+   * Create a keytab file using the set of supplied principal-to-password map.
+   * <p/>
+   * If a file exists where filePath points to, it will be overwritten.
+   *
+   * @param credentials a Map of principals to password, each entry will be placed in the specified file
+   * @param keytabFile  a File containing the absolute path to the keytab file
+   * @return true if the keytab file was successfully created; false otherwise
+   * @throws AmbariException
+   */
+  public boolean createKeytabFile(Map<String, String> credentials, File keytabFile)
+      throws AmbariException {
+    boolean success = false;
+
+    if (credentials == null) {
+      throw new AmbariException("Failed to create keytab file, missing credentials");
+    } else if (keytabFile == null) {
+      throw new AmbariException("Failed to create keytab file, missing file path");
+    } else {
+      List<KeytabEntry> keytabEntries = new ArrayList<KeytabEntry>();
+      KerberosTime timestamp = new KerberosTime();
+
+      // For each set of credentials in the map, create a set of keys and relevant keytab entries
+      for (Map.Entry<String, String> entry : credentials.entrySet()) {
+        String principal = entry.getKey();
+        String password = entry.getValue();
+
+        if (principal == null) {
+          LOG.warn("Missing principal, skipping entry");
+        } else if (password == null) {
+          LOG.warn("Missing password, skipping entry");
+        } else {
+          Map<EncryptionType, EncryptionKey> keys = KerberosKeyFactory.getKerberosKeys(principal, password);
+
+          for (EncryptionKey encryptionKey : keys.values()) {
+            keytabEntries.add(new KeytabEntry(principal, 1, timestamp, (byte) 0, encryptionKey));
+          }
+        }
+      }
+
+      // If there are keytab entries, create and write the keytab file
+      if (!keytabEntries.isEmpty()) {
+        Keytab keytab = new Keytab();
+
+        keytab.setEntries(keytabEntries);
+
+        try {
+          keytab.write(keytabFile);
+          success = true;
+        } catch (IOException e) {
+          String message = String.format("Failed to export keytab file");
+          LOG.error(message, e);
+
+          if (!keytabFile.delete()) {
+            keytabFile.deleteOnExit();
+          }
+
+          throw new AmbariException(message, e);
+        }
+      }
+    }
+
+    return success;
+  }
+
+  public KerberosCredential getAdministratorCredentials() {
+    return administratorCredentials;
+  }
+
+  public void setAdministratorCredentials(KerberosCredential administratorCredentials) {
+    this.administratorCredentials = administratorCredentials;
+  }
+
+  public String getDefaultRealm() {
+    return defaultRealm;
+  }
+
+  public void setDefaultRealm(String defaultRealm) {
+    this.defaultRealm = defaultRealm;
+  }
+
+  /**
+   * Given base64-encoded keytab data, decode the String to binary data and write it to a (temporary)
+   * file.
+   * <p/>
+   * Upon success, a new file is created.  The caller is expected to clean up this file when done
+   * with it.
+   *
+   * @param keytabData a String containing base64-encoded keytab data
+   * @return a File pointing to the decoded keytab file or null if not successful
+   * @throws AmbariException
+   */
+  protected File createKeytabFile(String keytabData)
+      throws AmbariException {
+    boolean success = false;
+    File tempFile = null;
+
+    // Create a temporary file
+    try {
+      tempFile = File.createTempFile("temp", ".dat");
+    } catch (IOException e) {
+      LOG.error(String.format("Failed to create temporary keytab file: %s", e.getLocalizedMessage()), e);
+    }
+
+    if ((tempFile != null) && (keytabData != null)) {
+      OutputStream fos = null;
+
+      // Decoded the base64-encoded String and write it to the temporary file
+      try {
+        fos = new FileOutputStream(tempFile);
+        fos.write(Base64.decodeBase64(keytabData));
+        success = true;
+      } catch (FileNotFoundException e) {
+        String message = String.format("Failed to write to temporary keytab file %s: %s",
+            tempFile.getAbsolutePath(), e.getLocalizedMessage());
+        LOG.error(message, e);
+        throw new AmbariException(message, e);
+      } catch (IOException e) {
+        String message = String.format("Failed to write to temporary keytab file %s: %s",
+            tempFile.getAbsolutePath(), e.getLocalizedMessage());
+        LOG.error(message, e);
+        throw new AmbariException(message, e);
+      } finally {
+        if (fos != null) {
+          try {
+            fos.close();
+          } catch (IOException e) {
+            // Ignore this...
+          }
+        }
+
+        // If there was an issue, clean up the file
+        if (!success) {
+          if (!tempFile.delete()) {
+            tempFile.deleteOnExit();
+          }
+
+          tempFile = null;
+        }
+      }
+    }
+
+    return tempFile;
+  }
+
+  /**
+   * Executes a shell command.
+   * <p/>
+   * See {@link org.apache.ambari.server.utils.ShellCommandUtil#runCommand(String[])}
+   *
+   * @param command an array of String value representing the command and its arguments
+   * @return a ShellCommandUtil.Result declaring the result of the operation
+   * @throws AmbariException
+   */
+  protected ShellCommandUtil.Result executeCommand(String[] command)
+      throws AmbariException {
+
+    if ((command == null) || (command.length == 0)) {
+      return null;
+    } else {
+      try {
+        return ShellCommandUtil.runCommand(command);
+      } catch (IOException e) {
+        String message = String.format("Failed to execute the command: %s", e.getLocalizedMessage());
+        LOG.error(message, e);
+        throw new AmbariException(message, e);
+      } catch (InterruptedException e) {
+        String message = String.format("Failed to wait for the command to complete: %s", e.getLocalizedMessage());
+        LOG.error(message, e);
+        throw new AmbariException(message, e);
+      }
+    }
+  }
+}

http://git-wip-us.apache.org/repos/asf/ambari/blob/1a18ddee/ambari-server/src/main/java/org/apache/ambari/server/serveraction/kerberos/KerberosOperationHandlerFactory.java
----------------------------------------------------------------------
diff --git a/ambari-server/src/main/java/org/apache/ambari/server/serveraction/kerberos/KerberosOperationHandlerFactory.java b/ambari-server/src/main/java/org/apache/ambari/server/serveraction/kerberos/KerberosOperationHandlerFactory.java
new file mode 100644
index 0000000..61a637b
--- /dev/null
+++ b/ambari-server/src/main/java/org/apache/ambari/server/serveraction/kerberos/KerberosOperationHandlerFactory.java
@@ -0,0 +1,52 @@
+/*
+ * 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.ambari.server.serveraction.kerberos;
+
+/**
+ * KerberosOperationHandlerFactory gets relevant KerberosOperationHandlers given a KDCType.
+ */
+public class KerberosOperationHandlerFactory {
+
+  /**
+   * Gets a relevant KerberosOperationHandler give some KDCType.
+   * <p/>
+   * If no KDCType is specified, {@link org.apache.ambari.server.serveraction.kerberos.KDCType#MIT_KDC}
+   * will be assumed.
+   *
+   * @param kdcType the relevant KDCType
+   * @return a KerberosOperationHandler
+   */
+  public static KerberosOperationHandler getKerberosOperationHandler(KDCType kdcType) {
+    KerberosOperationHandler handler = null;
+
+    // If not specified, use KDCType.MIT_KDC as a default
+    if (kdcType == null) {
+      kdcType = KDCType.MIT_KDC;
+    }
+
+    switch (kdcType) {
+
+      case MIT_KDC:
+        handler = new MITKerberosOperationHandler();
+        break;
+    }
+
+    return handler;
+  }
+}

http://git-wip-us.apache.org/repos/asf/ambari/blob/1a18ddee/ambari-server/src/main/java/org/apache/ambari/server/serveraction/kerberos/KerberosServerAction.java
----------------------------------------------------------------------
diff --git a/ambari-server/src/main/java/org/apache/ambari/server/serveraction/kerberos/KerberosServerAction.java b/ambari-server/src/main/java/org/apache/ambari/server/serveraction/kerberos/KerberosServerAction.java
new file mode 100644
index 0000000..788e087
--- /dev/null
+++ b/ambari-server/src/main/java/org/apache/ambari/server/serveraction/kerberos/KerberosServerAction.java
@@ -0,0 +1,381 @@
+/*
+ * 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.ambari.server.serveraction.kerberos;
+
+import org.apache.ambari.server.AmbariException;
+import org.apache.ambari.server.actionmanager.HostRoleStatus;
+import org.apache.ambari.server.agent.CommandReport;
+import org.apache.ambari.server.serveraction.AbstractServerAction;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import java.io.File;
+import java.io.IOException;
+import java.util.HashMap;
+import java.util.Map;
+
+/**
+ * KerberosServerAction is an abstract class to be implemented by Kerberos-related
+ * {@link org.apache.ambari.server.serveraction.ServerAction} implementations.
+ * <p/>
+ * This class provides helper methods used to get common properties from the command parameters map
+ * and iterate through the Kerberos identity metadata file
+ * (see {@link org.apache.ambari.server.serveraction.kerberos.KerberosActionDataFile}).
+ */
+public abstract class KerberosServerAction extends AbstractServerAction {
+  /**
+   * A (command parameter) property name used to hold the absolute path to the directory that is to
+   * be used to store transient data while the request is being processed.  This is expected to be
+   * a temporary directory.
+   */
+  public static final String DATA_DIRECTORY = "data_directory";
+
+  /**
+   * A (command parameter) property name used to hold the KDC administrator's principal value.
+   * TODO (rlevas): For security purposes, this data should be moved to an encrypted storage facility.
+   */
+  public static final String ADMINISTRATOR_PRINCIPAL = "admin_principal";
+
+  /**
+   * A (command parameter) property name used to hold the KDC administrator's password value.
+   * TODO (rlevas): For security purposes, this data should be moved to an encrypted storage facility.
+   */
+  public static final String ADMINISTRATOR_PASSWORD = "admin_password";
+
+  /**
+   * A (command parameter) property name used to hold the KDC administrator's (base64-encoded) keytab
+   * value.
+   * TODO (rlevas): For security purposes, this data should be moved to an encrypted storage facility.
+   */
+  public static final String ADMINISTRATOR_KEYTAB = "admin_keytab";
+
+  /**
+   * A (command parameter) property name used to hold the default Kerberos realm value.
+   */
+  public static final String DEFAULT_REALM = "default_realm";
+
+  /**
+   * A (command parameter) property name used to hold the relevant KDC type value.  See
+   * {@link org.apache.ambari.server.serveraction.kerberos.KDCType} for valid values
+   */
+  public static final String KDC_TYPE = "kdc_type";
+
+  /*
+   * Kerberos action shared data entry names
+   */
+  private static final String PRINCIPAL_PASSWORD_MAP = "principal_password_map";
+
+  private static final Logger LOG = LoggerFactory.getLogger(KerberosServerAction.class);
+
+  /**
+   * Given a (command parameter) Map and a property name, attempts to safely retrieve the requested
+   * data.
+   *
+   * @param commandParameters a Map containing the dictionary of data to interrogate
+   * @param propertyName      a String declaring the name of the item from commandParameters to retrieve
+   * @return a String or null, depending on the property value and if it existed in commandParameters
+   */
+  protected static String getCommandParameterValue(Map<String, String> commandParameters, String propertyName) {
+    return ((commandParameters == null) || (propertyName == null)) ? null : commandParameters.get(propertyName);
+  }
+
+  /**
+   * Given a (command parameter) Map, attempts to safely retrieve the "default_realm" property.
+   *
+   * @param commandParameters a Map containing the dictionary of data to interrogate
+   * @return a String indicating the default realm or null (if not found or set)
+   */
+  protected static String getDefaultRealm(Map<String, String> commandParameters) {
+    return getCommandParameterValue(commandParameters, DEFAULT_REALM);
+  }
+
+  /**
+   * Given a (command parameter) Map, attempts to safely retrieve the "kdc_type" property.
+   * <p/>
+   * If not found, {@link org.apache.ambari.server.serveraction.kerberos.KDCType#MIT_KDC} will be
+   * returned as a default value.
+   *
+   * @param commandParameters a Map containing the dictionary of data to interrogate
+   * @return a KDCType or null (if not found or set)
+   */
+  protected static KDCType getKDCType(Map<String, String> commandParameters) {
+    String kdcType = getCommandParameterValue(commandParameters, KDC_TYPE);
+
+    return ((kdcType == null) || kdcType.isEmpty())
+        ? KDCType.MIT_KDC
+        : KDCType.valueOf(kdcType.toUpperCase().replace("-", "_"));
+  }
+
+  /**
+   * Given a (command parameter) Map, attempts to safely retrieve the "data_directory" property.
+   *
+   * @param commandParameters a Map containing the dictionary of data to interrogate
+   * @return a String indicating the data directory or null (if not found or set)
+   */
+  protected static String getDataDirectoryPath(Map<String, String> commandParameters) {
+    return getCommandParameterValue(commandParameters, DATA_DIRECTORY);
+  }
+
+  /**
+   * Given a (command parameter) Map, attempts to safely retrieve the data needed to create a
+   * {@link org.apache.ambari.server.serveraction.kerberos.KerberosCredential} representing a KDC
+   * administrator.
+   * <p/>
+   * TODO (rlevas): For security purposes, this data should be moved to an encrypted storage facility.
+   *
+   * @param commandParameters a Map containing the dictionary of data to interrogate
+   * @return a KerberosCredential or null if commandParameters is null
+   */
+  protected static KerberosCredential getAdministratorCredential(Map<String, String> commandParameters) {
+    return (commandParameters == null)
+        ? null
+        : new KerberosCredential(
+        commandParameters.get(ADMINISTRATOR_PRINCIPAL),
+        commandParameters.get(ADMINISTRATOR_PASSWORD),
+        commandParameters.get(ADMINISTRATOR_KEYTAB));
+  }
+
+  /**
+   * Sets the shared principal-to-password Map used to store principals and generated password for
+   * use within the current request context.
+   *
+   * @param requestSharedDataContext a Map to be used as shared data among all ServerActions related
+   *                                 to a given request
+   * @param principalPasswordMap     A Map of principals and password to store
+   */
+  protected static void setPrincipalPasswordMap(Map<String, Object> requestSharedDataContext,
+                                                Map<String, String> principalPasswordMap) {
+    if (requestSharedDataContext != null) {
+      requestSharedDataContext.put(PRINCIPAL_PASSWORD_MAP, principalPasswordMap);
+    }
+  }
+
+  /**
+   * Gets the shared principal-to-password Map used to store principals and generated password for
+   * use within the current request context.
+   * <p/>
+   * If the requested Map is not found in requestSharedDataContext, one will be created and stored,
+   * ensuring that a Map will always be returned, assuming requestSharedDataContext is not null.
+   *
+   * @param requestSharedDataContext a Map to be used a shared data among all ServerActions related
+   *                                 to a given request
+   * @return A Map of principals-to-password
+   */
+  protected static Map<String, String> getPrincipalPasswordMap(Map<String, Object> requestSharedDataContext) {
+    if (requestSharedDataContext == null) {
+      return null;
+    } else {
+      Object map = requestSharedDataContext.get(PRINCIPAL_PASSWORD_MAP);
+
+      if (map == null) {
+        map = new HashMap<String, String>();
+        requestSharedDataContext.put(PRINCIPAL_PASSWORD_MAP, map);
+      }
+
+      return (Map<String, String>) map;
+    }
+  }
+
+  /**
+   * Attempts to safely retrieve a property with the specified name from the this action's relevant
+   * command parameters Map.
+   *
+   * @param propertyName a String declaring the name of the item from commandParameters to retrieve
+   * @return the value of the requested property, or null if not found or set
+   */
+  protected String getCommandParameterValue(String propertyName) {
+    return getCommandParameterValue(getCommandParameters(), propertyName);
+  }
+
+  /**
+   * Attempts to safely retrieve the "data_directory" property from the this action's relevant
+   * command parameters Map.
+   *
+   * @return a String indicating the data directory or null (if not found or set)
+   */
+  protected String getDataDirectoryPath() {
+    return getDataDirectoryPath(getCommandParameters());
+  }
+
+  /**
+   * Iterates through the Kerberos identity metadata from the
+   * {@link org.apache.ambari.server.serveraction.kerberos.KerberosActionDataFile} and calls the
+   * implementing class to handle each identity found.
+   * <p/>
+   * Using the "data_directory" value from this action's command parameters map, creates a
+   * {@link org.apache.ambari.server.serveraction.kerberos.KerberosActionDataFileReader} to parse
+   * the relative index.dat file and iterate through its "records".  Each "record" is process using
+   * {@link #processRecord(java.util.Map, String, KerberosOperationHandler, java.util.Map)}.
+   *
+   * @param requestSharedDataContext a Map to be used a shared data among all ServerActions related
+   *                                 to a given request
+   * @return a CommandReport indicating the result of this operation
+   * @throws AmbariException
+   */
+  protected CommandReport processIdentities(Map<String, Object> requestSharedDataContext)
+      throws AmbariException {
+    CommandReport commandReport = null;
+    Map<String, String> commandParameters = getCommandParameters();
+
+    if (commandParameters != null) {
+      // Grab the relevant data from this action's command parameters map
+      KerberosCredential administratorCredential = getAdministratorCredential(commandParameters);
+      String defaultRealm = getDefaultRealm(commandParameters);
+      KDCType kdcType = getKDCType(commandParameters);
+      String dataDirectoryPath = getDataDirectoryPath(commandParameters);
+
+      if (dataDirectoryPath != null) {
+        File dataDirectory = new File(dataDirectoryPath);
+
+        if (!dataDirectory.exists() || !dataDirectory.isDirectory()) {
+          String message = String.format("Failed to process the identities, the data directory does not exist: %s",
+              dataDirectory.getAbsolutePath());
+          LOG.error(message);
+          throw new AmbariException(message);
+        }
+        // The "index" file is expected to be in the specified data directory and named "index.dat"
+        File indexFile = new File(dataDirectory, KerberosActionDataFileBuilder.DATA_FILE_NAME);
+
+        if (!indexFile.canRead()) {
+          String message = String.format("Failed to process the identities, cannot read the index file: %s",
+              indexFile.getAbsolutePath());
+          LOG.error(message);
+          throw new AmbariException(message);
+        }
+        // Create the data file reader to parse and iterate through the records
+        KerberosActionDataFileReader reader = null;
+        KerberosOperationHandler handler = KerberosOperationHandlerFactory.getKerberosOperationHandler(kdcType);
+
+        if (handler == null) {
+          String message = String.format("Failed to process the identities, cannot read the index file: %s",
+              indexFile.getAbsolutePath());
+          LOG.error(message);
+          throw new AmbariException(message);
+        }
+
+        handler.open(administratorCredential, defaultRealm);
+
+        try {
+          reader = new KerberosActionDataFileReader(indexFile);
+          for (Map<String, String> record : reader) {
+            // Process the current record
+            commandReport = processRecord(record, defaultRealm, handler, requestSharedDataContext);
+
+            // If the principal processor returns a CommandReport, than it is time to stop since
+            // an error condition has probably occurred, else all is assumed to be well.
+            if (commandReport != null) {
+              break;
+            }
+          }
+        } catch (IOException e) {
+          String message = String.format("Failed to process the identities, cannot read the index file: %s",
+              indexFile.getAbsolutePath());
+          LOG.error(message, e);
+          throw new AmbariException(message, e);
+        } finally {
+          if (reader != null) {
+            // The reader needs to be closed, if it fails to close ignore the exception since
+            // there is little we can or care to do about it now.
+            try {
+              reader.close();
+            } catch (IOException e) {
+              // Ignore this...
+            }
+          }
+
+          // The KerberosOperationHandler needs to be closed, if it fails to close ignore the
+          // exception since there is little we can or care to do about it now.
+          try {
+            handler.close();
+          } catch (AmbariException e) {
+            // Ignore this...
+          }
+        }
+      }
+    }
+
+    // If commandReport is null, we can assume this operation was a success, so return a successful
+    // CommandReport; else return the previously created CommandReport.
+    return (commandReport == null)
+        ? createCommandReport(0, HostRoleStatus.COMPLETED, "{}", null, null)
+        : commandReport;
+  }
+
+  /**
+   * Processes an identity as necessary.
+   * <p/>
+   * This method is called from {@link #processIdentities(Map)} for each
+   * identity "record" found in the Kerberos identity metadata file. After processing, it is expected
+   * that the return value is null on success and a CommandReport (indicating the error) on failure.
+   *
+   * @param identityRecord           a Map containing the data for the current identity record
+   * @param evaluatedPrincipal       a String indicating the relevant principal
+   * @param operationHandler         a KerberosOperationHandler used to perform Kerberos-related
+   *                                 tasks for specific Kerberos implementations
+   *                                 (MIT, Active Directory, etc...)
+   * @param requestSharedDataContext a Map to be used a shared data among all ServerActions related
+   *                                 to a given request
+   * @return a CommandReport, indicating an error condition; or null, indicating a success condition
+   * @throws AmbariException if an error occurs while processing the identity record
+   */
+  protected abstract CommandReport processIdentity(Map<String, String> identityRecord,
+                                                   String evaluatedPrincipal,
+                                                   KerberosOperationHandler operationHandler,
+                                                   Map<String, Object> requestSharedDataContext)
+      throws AmbariException;
+
+  /**
+   * Process and prepares an identity record to be handled by the implementing class.
+   * <p/>
+   * Given the data from the record Map, attempts to replace variables in the principal pattern to
+   * generate a concrete principal value to further process. This "evaluated principal" is then passed to
+   * {@link #processIdentity(java.util.Map, String, KerberosOperationHandler, java.util.Map)}
+   * to be handled as needed.
+   *
+   * @param record                   a Map containing the data for the current identity record
+   * @param defaultRealm             a String declaring the default Kerberos realm
+   * @param operationHandler         a KerberosOperationHandler used to perform Kerberos-related
+   *                                 tasks for specific Kerberos implementations
+   *                                 (MIT, Active Directory, etc...)
+   * @param requestSharedDataContext a Map to be used a shared data among all ServerActions related
+   *                                 to a given request
+   * @return a CommandReport, indicating an error condition; or null, indicating a success condition
+   * @throws AmbariException if an error occurs while processing the identity record
+   */
+  private CommandReport processRecord(Map<String, String> record, String defaultRealm,
+                                      KerberosOperationHandler operationHandler,
+                                      Map<String, Object> requestSharedDataContext)
+      throws AmbariException {
+    CommandReport commandReport = null;
+
+    if (record != null) {
+      String principal = record.get(KerberosActionDataFile.PRINCIPAL);
+      String host = record.get(KerberosActionDataFile.HOSTNAME);
+
+      if (principal != null) {
+        // Evaluate the principal "pattern" found in the record to generate the "evaluated p[rincipal"
+        // by replacing the _HOST and _REALM variables.
+        String evaluatedPrincipal = principal.replace("_HOST", host).replace("_REALM", defaultRealm);
+        commandReport = processIdentity(record, evaluatedPrincipal, operationHandler, requestSharedDataContext);
+      }
+    }
+
+    return commandReport;
+  }
+}

http://git-wip-us.apache.org/repos/asf/ambari/blob/1a18ddee/ambari-server/src/main/java/org/apache/ambari/server/serveraction/kerberos/MITKerberosOperationHandler.java
----------------------------------------------------------------------
diff --git a/ambari-server/src/main/java/org/apache/ambari/server/serveraction/kerberos/MITKerberosOperationHandler.java b/ambari-server/src/main/java/org/apache/ambari/server/serveraction/kerberos/MITKerberosOperationHandler.java
new file mode 100644
index 0000000..2ebfc97
--- /dev/null
+++ b/ambari-server/src/main/java/org/apache/ambari/server/serveraction/kerberos/MITKerberosOperationHandler.java
@@ -0,0 +1,337 @@
+/*
+ * 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.ambari.server.serveraction.kerberos;
+
+import org.apache.ambari.server.AmbariException;
+import org.apache.ambari.server.utils.ShellCommandUtil;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import java.io.File;
+import java.util.ArrayList;
+import java.util.List;
+
+/**
+ * MITKerberosOperationHandler is an implementation of a KerberosOperationHandler providing
+ * functionality specifically for an MIT KDC. See http://web.mit.edu/kerberos.
+ * <p/>
+ * It is assumed that a MIT Kerberos client is installed and that the kdamin shell command is
+ * available
+ */
+public class MITKerberosOperationHandler extends KerberosOperationHandler {
+  private final static Logger LOG = LoggerFactory.getLogger(MITKerberosOperationHandler.class);
+
+
+  @Override
+  public void open(KerberosCredential administratorCredentials, String defaultRealm) throws AmbariException {
+    setAdministratorCredentials(administratorCredentials);
+    setDefaultRealm(defaultRealm);
+  }
+
+  @Override
+  public void close() throws AmbariException {
+    // There is nothing to do here.
+  }
+
+  /**
+   * Test to see if the specified principal exists in a previously configured MIT KDC
+   * <p/>
+   * This implementation creates a query to send to the kadmin shell command and then interrogates
+   * the result from STDOUT to determine if the presence of the specified principal.
+   *
+   * @param principal a String containing the principal to test
+   * @return true if the principal exists; false otherwise
+   * @throws AmbariException
+   */
+  @Override
+  public boolean principalExists(String principal)
+      throws AmbariException {
+
+    if (principal == null) {
+      return false;
+    } else {
+      // Create the KAdmin query to execute:
+      String query = String.format("get_principal %s", principal);
+
+      try {
+        ShellCommandUtil.Result result = invokeKAdmin(query);
+
+        if (result != null) {
+          if (result.isSuccessful()) {
+            String stdOut = result.getStdout();
+
+            // If there is data from STDOUT, see if the following string exists:
+            //    Principal: <principal>
+            return (stdOut != null) && stdOut.contains(String.format("Principal: %s", principal));
+          } else {
+            LOG.warn("Failed to query for principal {}:\n\tExitCode: {}\n\tSTDOUT: {}\n\tSTDERR: {}",
+                principal, result.getExitCode(), result.getStdout(), result.getStderr());
+            throw new AmbariException(String.format("Failed to query for principal %s", principal));
+          }
+        } else {
+          String message = String.format("Failed to query for principal %s - Unknown reason", principal);
+          LOG.warn(message);
+          throw new AmbariException(message);
+        }
+      } catch (AmbariException e) {
+        LOG.error(String.format("Failed to query for principal %s", principal), e);
+        throw e;
+      }
+    }
+  }
+
+
+  /**
+   * Creates a new principal in a previously configured MIT KDC
+   * <p/>
+   * This implementation creates a query to send to the kadmin shell command and then interrogates
+   * the result from STDOUT to determine if the operation executed successfully.
+   *
+   * @param principal a String containing the principal add
+   * @param password  a String containing the password to use when creating the principal
+   * @return true if the principal was successfully created; otherwise false
+   * @throws AmbariException
+   */
+  @Override
+  public boolean createServicePrincipal(String principal, String password)
+      throws AmbariException {
+
+    if ((principal == null) || principal.isEmpty()) {
+      throw new AmbariException("Failed to create new principal - no principal specified");
+    } else {
+      // Create the kdamin query:  add_principal <-randkey|-pw <password>> <principal>
+      StringBuilder queryBuilder = new StringBuilder();
+
+      queryBuilder.append("add_principal");
+
+      // If a password was not supplied, have the KDC generate a random key, else use the supplied
+      // password
+      if ((password == null) || password.isEmpty()) {
+        queryBuilder.append(" -randkey");
+      } else {
+        queryBuilder.append(" -pw ");
+        queryBuilder.append(password);
+      }
+
+      queryBuilder.append(" ");
+      queryBuilder.append(principal);
+
+      try {
+        ShellCommandUtil.Result result = invokeKAdmin(queryBuilder.toString());
+
+        if (result != null) {
+          if (result.isSuccessful()) {
+            String stdOut = result.getStdout();
+
+            // If there is data from STDOUT, see if the following string exists:
+            //    Principal "<principal>" created
+            return (stdOut != null) && stdOut.contains(String.format("Principal \"%s\" created", principal));
+          } else {
+            LOG.warn("Failed to create service principal for {}:\n\tExitCode: {}\n\tSTDOUT: {}\n\tSTDERR: {}",
+                principal, result.getExitCode(), result.getStdout(), result.getStderr());
+            throw new AmbariException(String.format("Failed to create service principal for %s", principal));
+          }
+        } else {
+          String message = String.format("Failed to create service principal for %s - Unknown reason", principal);
+          LOG.warn(message);
+          throw new AmbariException(message);
+        }
+      } catch (AmbariException e) {
+        LOG.error(String.format("Failed to create new principal for %s", principal), e);
+        throw e;
+      }
+    }
+  }
+
+  /**
+   * Updates the password for an existing principal in a previously configured MIT KDC
+   * <p/>
+   * This implementation creates a query to send to the kadmin shell command and then interrogates
+   * the exit code to determine if the operation executed successfully.
+   *
+   * @param principal a String containing the principal to update
+   * @param password  a String containing the password to set
+   * @return true if the password was successfully updated; otherwise false
+   * @throws AmbariException
+   */
+  @Override
+  public boolean setPrincipalPassword(String principal, String password) throws AmbariException {
+    if ((principal == null) || principal.isEmpty()) {
+      throw new AmbariException("Failed to set password - no principal specified");
+    } else {
+      // Create the kdamin query:  change_password <-randkey|-pw <password>> <principal>
+      StringBuilder queryBuilder = new StringBuilder();
+
+      queryBuilder.append("change_password");
+
+      // If a password was not supplied, have the KDC generate a random key, else use the supplied
+      // password
+      if ((password == null) || password.isEmpty()) {
+        queryBuilder.append(" -randkey");
+      } else {
+        queryBuilder.append(" -pw ");
+        queryBuilder.append(password);
+      }
+
+      queryBuilder.append(" ");
+      queryBuilder.append(principal);
+
+      try {
+        ShellCommandUtil.Result result = invokeKAdmin(queryBuilder.toString());
+
+        if (result != null) {
+          if (result.isSuccessful()) {
+            return true;
+          } else {
+            LOG.warn("Failed to set password for {}:\n\tExitCode: {}\n\tSTDOUT: {}\n\tSTDERR: {}",
+                principal, result.getExitCode(), result.getStdout(), result.getStderr());
+            throw new AmbariException(String.format("Failed to update password for %s", principal));
+          }
+        } else {
+          String message = String.format("Failed to set password for %s - Unknown reason", principal);
+          LOG.warn(message);
+          throw new AmbariException(message);
+        }
+      } catch (AmbariException e) {
+        LOG.error(String.format("Failed to set password for %s", principal), e);
+        throw e;
+      }
+    }
+  }
+
+  /**
+   * Removes an existing principal in a previously configured KDC
+   * <p/>
+   * The implementation is specific to a particular type of KDC.
+   *
+   * @param principal a String containing the principal to remove
+   * @return true if the principal was successfully removed; otherwise false
+   * @throws AmbariException
+   */
+  @Override
+  public boolean removeServicePrincipal(String principal) throws AmbariException {
+    if ((principal == null) || principal.isEmpty()) {
+      throw new AmbariException("Failed to remove new principal - no principal specified");
+    } else {
+      try {
+        ShellCommandUtil.Result result = invokeKAdmin(String.format("delete_principal -force %s", principal));
+
+        if (result != null) {
+          if (result.isSuccessful()) {
+            String stdOut = result.getStdout();
+
+            // If there is data from STDOUT, see if the following string exists:
+            //    Principal "<principal>" created
+            return (stdOut != null) && !stdOut.contains("Principal does not exist");
+          } else {
+            LOG.warn("Failed to remove service principal for {}:\n\tExitCode: {}\n\tSTDOUT: {}\n\tSTDERR: {}",
+                principal, result.getExitCode(), result.getStdout(), result.getStderr());
+            throw new AmbariException(String.format("Failed to remove service principal for %s", principal));
+          }
+        } else {
+          String message = String.format("Failed to remove service principal for %s - Unknown reason", principal);
+          LOG.warn(message);
+          throw new AmbariException(message);
+        }
+      } catch (AmbariException e) {
+        LOG.error(String.format("Failed to remove new principal for %s", principal), e);
+        throw e;
+      }
+    }
+  }
+
+  /**
+   * Invokes the kadmin shell command to issue queries
+   *
+   * @param query a String containing the query to send to the kdamin command
+   * @return a ShellCommandUtil.Result containing the result of the operation
+   * @throws AmbariException
+   */
+  private ShellCommandUtil.Result invokeKAdmin(String query)
+      throws AmbariException {
+    ShellCommandUtil.Result result = null;
+
+    if ((query != null) && !query.isEmpty()) {
+      KerberosCredential administratorCredentials = getAdministratorCredentials();
+      String defaultRealm = getDefaultRealm();
+
+      List<String> command = new ArrayList<String>();
+      File tempKeytabFile = null;
+
+      try {
+        String adminPrincipal = (administratorCredentials == null)
+            ? null
+            : administratorCredentials.getPrincipal();
+
+        if ((adminPrincipal == null) || adminPrincipal.isEmpty()) {
+          // Set the kdamin interface to be kadmin.local
+          command.add("kadmin.local");
+        } else {
+          String adminPassword = administratorCredentials.getPassword();
+          String adminKeyTab = administratorCredentials.getKeytab();
+
+          // Set the kdamin interface to be kadmin
+          command.add("kadmin");
+
+          // Add the administrative principal
+          command.add("-p");
+          command.add(adminPrincipal);
+
+          if ((adminKeyTab != null) && !adminKeyTab.isEmpty()) {
+            tempKeytabFile = createKeytabFile(adminKeyTab);
+
+            if (tempKeytabFile != null) {
+              // Add keytab file administrative principal
+              command.add("-k");
+              command.add("-t");
+              command.add(tempKeytabFile.getAbsolutePath());
+            }
+          } else if (adminPassword != null) {
+            // Add password for administrative principal
+            command.add("-w");
+            command.add(adminPassword);
+          }
+        }
+
+        if ((defaultRealm != null) && !defaultRealm.isEmpty()) {
+          // Add default realm clause
+          command.add("-r");
+          command.add(defaultRealm);
+        }
+
+        // Add kadmin query
+        command.add("-q");
+        command.add(query.replace("\"", "\\\""));
+
+        result = executeCommand(command.toArray(new String[command.size()]));
+      } finally {
+        // If a temporary keytab file was created, clean it up.
+        if (tempKeytabFile != null) {
+          if (!tempKeytabFile.delete()) {
+            tempKeytabFile.deleteOnExit();
+          }
+        }
+      }
+    }
+
+    return result;
+  }
+
+
+}