You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@ambari.apache.org by js...@apache.org on 2014/12/15 16:53:23 UTC
ambari git commit: AMBARI-8660. Provide encrypt and decrypt
functionality in KerberosCredential
Repository: ambari
Updated Branches:
refs/heads/trunk bde8a5bb1 -> 93fc4221d
AMBARI-8660. Provide encrypt and decrypt functionality in KerberosCredential
Project: http://git-wip-us.apache.org/repos/asf/ambari/repo
Commit: http://git-wip-us.apache.org/repos/asf/ambari/commit/93fc4221
Tree: http://git-wip-us.apache.org/repos/asf/ambari/tree/93fc4221
Diff: http://git-wip-us.apache.org/repos/asf/ambari/diff/93fc4221
Branch: refs/heads/trunk
Commit: 93fc4221dd3dda65115dc366798d840766377b9f
Parents: bde8a5b
Author: Robert Levas <rl...@hortonworks.com>
Authored: Mon Dec 15 10:50:38 2014 -0500
Committer: John Speidel <js...@hortonworks.com>
Committed: Mon Dec 15 10:52:13 2014 -0500
----------------------------------------------------------------------
.../kerberos/KerberosCredential.java | 181 +++++++++++++++++++
.../kerberos/KerberosCredentialTest.java | 103 +++++++++++
2 files changed, 284 insertions(+)
----------------------------------------------------------------------
http://git-wip-us.apache.org/repos/asf/ambari/blob/93fc4221/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
index adb2edc..19997e7 100644
--- 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
@@ -17,16 +17,183 @@
*/
package org.apache.ambari.server.serveraction.kerberos;
+import com.google.gson.Gson;
+import com.google.gson.JsonSyntaxException;
+import org.apache.ambari.server.AmbariException;
+import org.apache.commons.codec.binary.Base64;
+
+import javax.crypto.BadPaddingException;
+import javax.crypto.Cipher;
+import javax.crypto.IllegalBlockSizeException;
+import javax.crypto.NoSuchPaddingException;
+import javax.crypto.spec.SecretKeySpec;
+import java.security.InvalidKeyException;
+import java.security.NoSuchAlgorithmException;
+import java.util.Arrays;
+import java.util.Map;
+
/**
* KerberosCredential encapsulates data needed to authenticate an identity to a KDC.
+ * <p/>
+ * This class has the ability to encrypt and decrypt itself using the AES encryption algorithm.
*/
public class KerberosCredential {
+ /**
+ * A property name used to hold the KDC administrator's principal value.
+ */
+ public static final String KEY_NAME_PRINCIPAL = "principal";
+ /**
+ * A property name used to hold the KDC administrator's password value.
+ */
+ public static final String KEY_NAME_PASSWORD = "password";
+ /**
+ * A property name used to hold the KDC administrator's (base64-encoded) keytab
+ * value.
+ */
+ public static final String KEY_NAME_KEYTAB = "keytab";
+
+ /**
+ * This principal value
+ */
private String principal = null;
+
+ /**
+ * The plaintext password value
+ */
private String password = null;
+
+ /**
+ * A base64-encoded keytab
+ */
private String keytab = null;
/**
+ * Given a Map of attributes, attempts to safely retrieve the data needed to create a
+ * KerberosCredential representing a KDC administrator.
+ * <p/>
+ * It is expected that the following properties exist in the Map:
+ * <ul>
+ * <li>principal</li>
+ * <li>password (optional)</li>
+ * <li>keytab (optional)</li>
+ * </ul>
+ * <p/>
+ * Each of these properties may be prefixed with some prefix value to generate a relevant key value.
+ * If prefix was "kerberos_admin/", then the key representing the principal would be computed
+ * to be "kerberos_admin/principal".
+ *
+ * @param map a Map of attributes containing the values needed to create a new KerberosCredential
+ * @param prefix a String containing the prefix to used along with the base key name (principal, etc...)
+ * to create the relevant key name ([prefix]base_key. etc...)
+ * @return a KerberosCredential or null if commandParameters is null
+ */
+ public static KerberosCredential fromMap(Map<String, Object> map, String prefix) {
+ KerberosCredential credential = null;
+
+ if (map != null) {
+ Object attribute;
+ String principal;
+ String password;
+ String keytab;
+
+ if (prefix == null) {
+ prefix = "";
+ }
+
+ attribute = map.get(prefix + KEY_NAME_PRINCIPAL);
+ principal = (attribute == null) ? null : attribute.toString();
+
+ attribute = map.get(prefix + KEY_NAME_PASSWORD);
+ password = (attribute == null) ? null : attribute.toString();
+
+ attribute = map.get(prefix + KEY_NAME_KEYTAB);
+ keytab = (attribute == null) ? null : attribute.toString();
+
+ if (((principal != null) && !principal.isEmpty()) ||
+ ((password != null) && !password.isEmpty()) ||
+ ((keytab != null) && !keytab.isEmpty())) {
+ credential = new KerberosCredential(principal, password, keytab);
+ }
+ }
+
+ return credential;
+ }
+
+ /**
+ * Decrypts a String containing base64-encoded encrypted data into a new KerberosCredential.
+ * <p/>
+ * Given a key and a base64-encoded set of bytes containing encrypted data (ideally obtained from
+ * {@link #encrypt(KerberosCredential, byte[])} or {@link #encrypt(byte[])}, decodes and decrypts
+ * into a new KerberosCredential.
+ *
+ * @param cipherText a String containing base64-encoded encrypted data
+ * @param key an array of bytes used to decrypt the encrypted data
+ * @return a new KerberosCredential
+ * @throws AmbariException if an error occurs while decrypting the data
+ */
+ public static KerberosCredential decrypt(String cipherText, byte[] key) throws AmbariException {
+ if (cipherText == null) {
+ return null;
+ } else {
+ try {
+ SecretKeySpec secretKey = new SecretKeySpec(Arrays.copyOf(key, 16), "AES");
+ Cipher cipher = Cipher.getInstance("AES/ECB/PKCS5Padding");
+ cipher.init(Cipher.DECRYPT_MODE, secretKey);
+ byte[] plaintext = cipher.doFinal(Base64.decodeBase64(cipherText));
+ return new Gson().fromJson(new String(plaintext), KerberosCredential.class);
+ } catch (NoSuchAlgorithmException e) {
+ throw new AmbariException("Failed to decrypt cipher text due to invalid encryption algorithm", e);
+ } catch (NoSuchPaddingException e) {
+ throw new AmbariException("Failed to decrypt cipher text due to invalid padding scheme algorithm", e);
+ } catch (IllegalBlockSizeException e) {
+ throw new AmbariException("Failed to decrypt cipher text due to invalid block size", e);
+ } catch (BadPaddingException e) {
+ throw new AmbariException("Failed to decrypt cipher text due to invalid padding", e);
+ } catch (InvalidKeyException e) {
+ throw new AmbariException("Failed to decrypt cipher text due to invalid key", e);
+ } catch (JsonSyntaxException e) {
+ throw new AmbariException("Failed to decrypt cipher, cannot parse data into a KerberosCredential", e);
+ }
+ }
+ }
+
+ /**
+ * Encrypts a KerberosCredential into a base64-encoded set of bytes.
+ * <p/>
+ * Given a KerberosCredential and a key, serializes the data into a JSON-formatted string and
+ * encrypts it.
+ *
+ * @param kerberosCredential the KerberosCredential to encrypt
+ * @param key an array of bytes used to decrypt the encrypted data
+ * @return a String containing base64-encoded encrypted data
+ * @throws AmbariException if an error occurs while encrypting the KerberosCredential
+ */
+ public static String encrypt(KerberosCredential kerberosCredential, byte[] key) throws AmbariException {
+ if (kerberosCredential == null) {
+ return null;
+ } else {
+ try {
+ SecretKeySpec secretKey = new SecretKeySpec(Arrays.copyOf(key, 16), "AES");
+ Cipher cipher = Cipher.getInstance("AES/ECB/PKCS5Padding");
+ cipher.init(Cipher.ENCRYPT_MODE, secretKey);
+ String plaintext = new Gson().toJson(kerberosCredential);
+ return Base64.encodeBase64String(cipher.doFinal(plaintext.getBytes()));
+ } catch (NoSuchAlgorithmException e) {
+ throw new AmbariException("Failed to encrypt plaintext due to invalid encryption algorithm", e);
+ } catch (NoSuchPaddingException e) {
+ throw new AmbariException("Failed to encrypt plaintext due to invalid padding scheme algorithm", e);
+ } catch (IllegalBlockSizeException e) {
+ throw new AmbariException("Failed to encrypt plaintext due to invalid key", e);
+ } catch (BadPaddingException e) {
+ throw new AmbariException("Failed to encrypt plaintext due to unexpected reasons", e);
+ } catch (InvalidKeyException e) {
+ throw new AmbariException("Failed to encrypt plaintext due to invalid key", e);
+ }
+ }
+ }
+
+ /**
* Creates an empty KerberosCredential
*/
public KerberosCredential() {
@@ -89,4 +256,18 @@ public class KerberosCredential {
public void setKeytab(String keytab) {
this.keytab = keytab;
}
+
+ /**
+ * Encrypts this KerberosCredential into a base64-encoded set of bytes.
+ * <p/>
+ * Serializes this KerberosCredential into a JSON-formatted string and
+ * encrypts it using the supplied key.
+ *
+ * @param key an array of bytes used to decrypt the encrypted data
+ * @return a String containing base64-encoded encrypted data
+ * @throws AmbariException if an error occurs while encrypting the KerberosCredential
+ */
+ public String encrypt(byte[] key) throws AmbariException {
+ return encrypt(this, key);
+ }
}
http://git-wip-us.apache.org/repos/asf/ambari/blob/93fc4221/ambari-server/src/test/java/org/apache/ambari/server/serveraction/kerberos/KerberosCredentialTest.java
----------------------------------------------------------------------
diff --git a/ambari-server/src/test/java/org/apache/ambari/server/serveraction/kerberos/KerberosCredentialTest.java b/ambari-server/src/test/java/org/apache/ambari/server/serveraction/kerberos/KerberosCredentialTest.java
new file mode 100644
index 0000000..305b122
--- /dev/null
+++ b/ambari-server/src/test/java/org/apache/ambari/server/serveraction/kerberos/KerberosCredentialTest.java
@@ -0,0 +1,103 @@
+/*
+ * 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.eclipse.persistence.internal.helper.Helper;
+import org.junit.Test;
+
+import java.util.HashMap;
+import java.util.Map;
+
+import static org.junit.Assert.*;
+
+public class KerberosCredentialTest {
+
+ @Test
+ public void testFromMap() throws Exception {
+ KerberosCredential kerberosCredential;
+ Map<String, Object> attributes = new HashMap<String, Object>();
+ attributes.put(KerberosCredential.KEY_NAME_PRINCIPAL, "admin/admin@EXAMPLE.COM");
+ attributes.put(KerberosCredential.KEY_NAME_KEYTAB, "bogus_base64-encoded_data");
+
+ attributes.put("kerberos_admin/" + KerberosCredential.KEY_NAME_PRINCIPAL, "admin/admin@FOOBAR.COM");
+ attributes.put("kerberos_admin/" + KerberosCredential.KEY_NAME_PASSWORD, "t0p_s3cr3t");
+
+ // Test with an empty prefix
+ kerberosCredential = KerberosCredential.fromMap(attributes, "");
+ Assert.assertNotNull(kerberosCredential);
+ Assert.assertEquals("admin/admin@EXAMPLE.COM", kerberosCredential.getPrincipal());
+ Assert.assertNull(kerberosCredential.getPassword());
+ Assert.assertEquals("bogus_base64-encoded_data", kerberosCredential.getKeytab());
+
+ // Test with a NULL prefix
+ kerberosCredential = KerberosCredential.fromMap(attributes, null);
+ Assert.assertNotNull(kerberosCredential);
+ Assert.assertEquals("admin/admin@EXAMPLE.COM", kerberosCredential.getPrincipal());
+ Assert.assertNull(kerberosCredential.getPassword());
+ Assert.assertEquals("bogus_base64-encoded_data", kerberosCredential.getKeytab());
+
+ // Test with a prefix
+ kerberosCredential = KerberosCredential.fromMap(attributes, "kerberos_admin/");
+ Assert.assertNotNull(kerberosCredential);
+ Assert.assertEquals("admin/admin@FOOBAR.COM", kerberosCredential.getPrincipal());
+ Assert.assertEquals("t0p_s3cr3t", kerberosCredential.getPassword());
+ Assert.assertNull(kerberosCredential.getKeytab());
+
+ // Test with a prefix that does not resolve to any existing keys
+ kerberosCredential = KerberosCredential.fromMap(attributes, "invalid/");
+ Assert.assertNull(kerberosCredential);
+ }
+
+ @Test
+ public void testEncryptAndDecrypt() throws Exception {
+ byte[] key = "This is my key".getBytes();
+ KerberosCredential credential;
+ String cipherText;
+ KerberosCredential decryptedCredential;
+
+ credential = new KerberosCredential("admin/admin@FOOBAR.COM", "t0p_s3cr3t", null);
+ cipherText = credential.encrypt(key);
+ Assert.assertNotNull(cipherText);
+
+ // Test a successful case
+ decryptedCredential = KerberosCredential.decrypt(cipherText, key);
+ Assert.assertNotNull(decryptedCredential);
+ Assert.assertEquals(credential.getPrincipal(), decryptedCredential.getPrincipal());
+ Assert.assertEquals(credential.getPassword(), decryptedCredential.getPassword());
+ Assert.assertEquals(credential.getKeytab(), decryptedCredential.getKeytab());
+
+ // Test an invalid key
+ try {
+ decryptedCredential = KerberosCredential.decrypt(cipherText, "not the key".getBytes());
+ Assert.fail("Should have thrown AmbariException");
+ } catch (AmbariException e) {
+ // this is expected
+ }
+
+ // Test an invalid cipher text
+ try {
+ decryptedCredential = KerberosCredential.decrypt("I am not encrypted data", key);
+ Assert.fail("Should have thrown AmbariException");
+ } catch (AmbariException e) {
+ // this is expected
+ }
+ }
+}
\ No newline at end of file