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;
+ }
+
+
+}