You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@ambari.apache.org by sm...@apache.org on 2018/10/08 20:20:45 UTC
[ambari] branch trunk updated: AMBARI-24742. Implementing a new
service to be used in case we want to encrypt/decrypt sensitive information
using custom/environment master key (also injected into Guice) (#2430)
This is an automated email from the ASF dual-hosted git repository.
smolnar pushed a commit to branch trunk
in repository https://gitbox.apache.org/repos/asf/ambari.git
The following commit(s) were added to refs/heads/trunk by this push:
new cf74bb1 AMBARI-24742. Implementing a new service to be used in case we want to encrypt/decrypt sensitive information using custom/environment master key (also injected into Guice) (#2430)
cf74bb1 is described below
commit cf74bb1b22677bd4886748a75d003033b2cbcf74
Author: Sandor Molnar <sm...@apache.org>
AuthorDate: Mon Oct 8 22:20:40 2018 +0200
AMBARI-24742. Implementing a new service to be used in case we want to encrypt/decrypt sensitive information using custom/environment master key (also injected into Guice) (#2430)
---
.../ambari/server/controller/ControllerModule.java | 3 +
.../security/encryption/AESEncryptionService.java | 112 +++++++++++++++
.../security/encryption/EncryptionService.java | 153 +++++++++++++++++++++
.../security/encryption/MasterKeyServiceImpl.java | 53 ++-----
.../apache/ambari/server/utils/TextEncoding.java | 24 ++++
.../internal/CredentialResourceProviderTest.java | 2 +-
.../encryption/CredentialProviderTest.java | 2 +-
.../encryption/CredentialStoreServiceImplTest.java | 2 +-
.../security/encryption/CredentialStoreTest.java | 2 +-
.../security/encryption/EncryptionServiceTest.java | 100 ++++++++++++++
.../security/encryption/MasterKeyServiceTest.java | 10 +-
11 files changed, 413 insertions(+), 50 deletions(-)
diff --git a/ambari-server/src/main/java/org/apache/ambari/server/controller/ControllerModule.java b/ambari-server/src/main/java/org/apache/ambari/server/controller/ControllerModule.java
index 6df8951..972dbce 100644
--- a/ambari-server/src/main/java/org/apache/ambari/server/controller/ControllerModule.java
+++ b/ambari-server/src/main/java/org/apache/ambari/server/controller/ControllerModule.java
@@ -113,8 +113,10 @@ import org.apache.ambari.server.security.SecurityHelperImpl;
import org.apache.ambari.server.security.authorization.AuthorizationHelper;
import org.apache.ambari.server.security.authorization.internal.InternalAuthenticationInterceptor;
import org.apache.ambari.server.security.authorization.internal.RunWithInternalSecurityContext;
+import org.apache.ambari.server.security.encryption.AESEncryptionService;
import org.apache.ambari.server.security.encryption.CredentialStoreService;
import org.apache.ambari.server.security.encryption.CredentialStoreServiceImpl;
+import org.apache.ambari.server.security.encryption.EncryptionService;
import org.apache.ambari.server.serveraction.kerberos.KerberosOperationHandlerFactory;
import org.apache.ambari.server.serveraction.users.CollectionPersisterService;
import org.apache.ambari.server.serveraction.users.CollectionPersisterServiceFactory;
@@ -328,6 +330,7 @@ public class ControllerModule extends AbstractModule {
bind(KerberosHelper.class).to(KerberosHelperImpl.class);
bind(CredentialStoreService.class).to(CredentialStoreServiceImpl.class);
+ bind(EncryptionService.class).to(AESEncryptionService.class);
bind(Configuration.class).toInstance(configuration);
bind(OsFamily.class).toInstance(os_family);
diff --git a/ambari-server/src/main/java/org/apache/ambari/server/security/encryption/AESEncryptionService.java b/ambari-server/src/main/java/org/apache/ambari/server/security/encryption/AESEncryptionService.java
new file mode 100644
index 0000000..ffb92ba
--- /dev/null
+++ b/ambari-server/src/main/java/org/apache/ambari/server/security/encryption/AESEncryptionService.java
@@ -0,0 +1,112 @@
+/*
+ * 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
+ * <p/>
+ * http://www.apache.org/licenses/LICENSE-2.0
+ * <p/>
+ * 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.security.encryption;
+
+import java.io.UnsupportedEncodingException;
+import java.nio.charset.StandardCharsets;
+
+import org.apache.ambari.server.utils.TextEncoding;
+import org.apache.commons.codec.binary.Base64;
+import org.apache.commons.codec.binary.Hex;
+
+import com.google.inject.Singleton;
+
+@Singleton
+public class AESEncryptionService implements EncryptionService {
+
+ private static final String ENCODED_TEXT_FIELD_DELIMITER = "::";
+ private static final String UTF_8_CHARSET = StandardCharsets.UTF_8.name();
+
+ private MasterKeyService environmentMasterKeyService;
+
+ @Override
+ public String encrypt(String toBeEncrypted) throws Exception {
+ return encrypt(toBeEncrypted, TextEncoding.BASE_64);
+ }
+
+ @Override
+ public String encrypt(String toBeEncrypted, TextEncoding textEncoding) throws Exception {
+ return encrypt(toBeEncrypted, getAmbariMasterKey(), textEncoding);
+ }
+
+ @Override
+ public String encrypt(String toBeEncrypted, String key) throws Exception {
+ return encrypt(toBeEncrypted, key, TextEncoding.BASE_64);
+ }
+
+ @Override
+ public String encrypt(String toBeEncrypted, String key, TextEncoding textEncoding) throws Exception {
+ final AESEncryptor aes = new AESEncryptor(key);
+ final EncryptionResult encryptionResult = aes.encrypt(toBeEncrypted);
+ return TextEncoding.BASE_64 == textEncoding ? encodeEncryptionResultBase64(encryptionResult) : encodeEncryptionResultBinHex(encryptionResult);
+ }
+
+ private final String getAmbariMasterKey() {
+ initEnvironmentMasterKeyService();
+ return String.valueOf(environmentMasterKeyService.getMasterSecret());
+ }
+
+ private void initEnvironmentMasterKeyService() {
+ if (environmentMasterKeyService == null) {
+ environmentMasterKeyService = new MasterKeyServiceImpl();
+ if (!environmentMasterKeyService.isMasterKeyInitialized()) {
+ throw new SecurityException("You are trying to use a persisted master key but its initialization has been failed!");
+ }
+ }
+ }
+
+ private String encodeEncryptionResultBase64(EncryptionResult encryptionResult) throws UnsupportedEncodingException {
+ return Base64.encodeBase64String((Base64.encodeBase64String(encryptionResult.salt) + ENCODED_TEXT_FIELD_DELIMITER + Base64.encodeBase64String(encryptionResult.iv)
+ + ENCODED_TEXT_FIELD_DELIMITER + Base64.encodeBase64String(encryptionResult.cipher)).getBytes(UTF_8_CHARSET));
+ }
+
+ private String encodeEncryptionResultBinHex(EncryptionResult encryptionResult) throws UnsupportedEncodingException {
+ return Hex.encodeHexString((Hex.encodeHexString(encryptionResult.salt) + ENCODED_TEXT_FIELD_DELIMITER + Hex.encodeHexString(encryptionResult.iv)
+ + ENCODED_TEXT_FIELD_DELIMITER + Hex.encodeHexString(encryptionResult.cipher)).getBytes(UTF_8_CHARSET));
+ }
+
+ @Override
+ public String decrypt(String toBeDecrypted) throws Exception {
+ return decrypt(toBeDecrypted, TextEncoding.BASE_64);
+ }
+
+ @Override
+ public String decrypt(String toBeDecrypted, TextEncoding textEncoding) throws Exception {
+ return decrypt(toBeDecrypted, getAmbariMasterKey(), textEncoding);
+ }
+
+ @Override
+ public String decrypt(String toBeDecrypted, String key) throws Exception {
+ return decrypt(toBeDecrypted, key, TextEncoding.BASE_64);
+ }
+
+ @Override
+ public String decrypt(String toBeDecrypted, String key, TextEncoding textEncoding) throws Exception {
+ final byte[] decodedValue = TextEncoding.BASE_64 == textEncoding ? Base64.decodeBase64(toBeDecrypted) : Hex.decodeHex(toBeDecrypted.toCharArray());
+ final String decodedText = new String(decodedValue, UTF_8_CHARSET);
+ final String[] decodedParts = decodedText.split(ENCODED_TEXT_FIELD_DELIMITER);
+ final AESEncryptor aes = new AESEncryptor(key);
+ if (TextEncoding.BASE_64 == textEncoding) {
+ return new String(aes.decrypt(Base64.decodeBase64(decodedParts[0]), Base64.decodeBase64(decodedParts[1]), Base64.decodeBase64(decodedParts[2])), UTF_8_CHARSET);
+ } else {
+ return new String(
+ aes.decrypt(Hex.decodeHex(decodedParts[0].toCharArray()), Hex.decodeHex(decodedParts[1].toCharArray()), Hex.decodeHex(decodedParts[2].toCharArray())),
+ UTF_8_CHARSET);
+ }
+ }
+}
diff --git a/ambari-server/src/main/java/org/apache/ambari/server/security/encryption/EncryptionService.java b/ambari-server/src/main/java/org/apache/ambari/server/security/encryption/EncryptionService.java
new file mode 100644
index 0000000..2f4c2f9
--- /dev/null
+++ b/ambari-server/src/main/java/org/apache/ambari/server/security/encryption/EncryptionService.java
@@ -0,0 +1,153 @@
+/*
+ * 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
+ * <p/>
+ * http://www.apache.org/licenses/LICENSE-2.0
+ * <p/>
+ * 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.security.encryption;
+
+import org.apache.ambari.server.utils.TextEncoding;
+
+/**
+ * This interface provides useful methods to encrypt/decrypt sensitive
+ * information. As of now the underlying implementation uses AES encryption
+ * which may be changed in the future (or you will have the option to plugin
+ * your own security algorithm).
+ *
+ * Text encoding takes places during encryption thus decryption starts with text
+ * decoding of the same encoding type. Currently the following text encoding
+ * types are supported:
+ * <ul>
+ * <li>BASE_64 (default)
+ * <li>BinHex
+ * </ul>
+ *
+ * @see TextEncoding
+ *
+ */
+public interface EncryptionService {
+
+ /**
+ * Encrypts the given text using Ambari's master key found in the environment.
+ * The returned value will be encoded with BASE_64 encoding.
+ *
+ * @param toBeEncrypted
+ * the text to be encrypted
+ * @return the String representation of the encrypted text
+ * @throws Exception
+ * in case any error happened during the encryption process
+ */
+ String encrypt(String toBeEncrypted) throws Exception;
+
+ /**
+ * Encrypts the given text using Ambari's master key found in the environment.
+ * The returned value will be encoded with the given text encoding.
+ *
+ * @param toBeEncrypted
+ * the text to be encrypted
+ * @param textEncoding
+ * the text encoding which the encrypted text is encoded with
+ * @return the String representation of the encrypted text
+ * @throws Exception
+ * in case any error happened during the encryption process
+ */
+ String encrypt(String toBeEncrypted, TextEncoding textEncoding) throws Exception;
+
+ /**
+ * Encrypts the given text using the given key. The returned value will be
+ * encoded with BASE_64 encoding.
+ *
+ * @param toBeEncrypted
+ * the text to be encrypted
+ * @param key
+ * the key to be used for encryption
+ * @return the String representation of the encrypted text
+ * @throws Exception
+ * in case any error happened during the encryption process
+ */
+ String encrypt(String toBeEncrypted, String key) throws Exception;
+
+ /**
+ * Encrypts the given text using the given key.The returned value will be
+ * encoded with the given text encoding.
+ *
+ * @param toBeEncrypted
+ * the text to be encrypted
+ * @param key
+ * the key to be used for encryption
+ * @param textEncoding
+ * the text encoding which the encrypted text is encoded with
+ * @return the String representation of the encrypted text
+ * @throws Exception
+ * in case any error happened during the encryption process
+ */
+ String encrypt(String toBeEncrypted, String key, TextEncoding textEncoding) throws Exception;
+
+ /**
+ * Decrypts the given text (must be encoded with BASE_64 encoding) using
+ * Ambari's master key found in the environment.
+ *
+ * @param toBeDecrypted
+ * the text to be decrypted
+ * @return the String representation of the decrypted text
+ * @throws Exception
+ * in case any error happened during the decryption process
+ */
+ public String decrypt(String toBeDecrypted) throws Exception;
+
+ /**
+ * Decrypts the given text (must be encoded with the given text encoding) using
+ * Ambari's master key found in the environment.
+ *
+ * @param toBeDecrypted
+ * the text to be decrypted
+ * @param textEncoding
+ * the text encoding which <code>toBeDecrypted</code> is encoded with
+ * @return the String representation of the decrypted text
+ * @throws Exception
+ * in case any error happened during the decryption process
+ */
+ public String decrypt(String toBeDecrypted, TextEncoding textEncoding) throws Exception;
+
+ /**
+ * Decrypts the given text (must be encoded with BASE_64 encoding) using the
+ * given key.
+ *
+ * @param toBeDecrypted
+ * the text to be decrypted
+ * @param key
+ * the key to be used for decryption
+ * @return the String representation of the decrypted text
+ * @throws Exception
+ * in case any error happened during the decryption process
+ */
+ public String decrypt(String toBeDecrypted, String key) throws Exception;
+
+ /**
+ * Decrypts the given text (must be encoded with the given text encoding) using
+ * the given key.
+ *
+ * @param toBeDecrypted
+ * the text to be decrypted
+ * @param key
+ * the key to be used for decryption
+ * @param textEncoding
+ * the text encoding which <code>toBeDecrypted</code> is encoded with
+ * @return the String representation of the decrypted text
+ * @throws Exception
+ * in case any error happened during the decryption process
+ */
+ public String decrypt(String toBeDecrypted, String key, TextEncoding textEncoding) throws Exception;
+
+}
diff --git a/ambari-server/src/main/java/org/apache/ambari/server/security/encryption/MasterKeyServiceImpl.java b/ambari-server/src/main/java/org/apache/ambari/server/security/encryption/MasterKeyServiceImpl.java
index 3b38856..8a8dd58 100644
--- a/ambari-server/src/main/java/org/apache/ambari/server/security/encryption/MasterKeyServiceImpl.java
+++ b/ambari-server/src/main/java/org/apache/ambari/server/security/encryption/MasterKeyServiceImpl.java
@@ -32,7 +32,6 @@ import java.util.Map;
import org.apache.ambari.server.AmbariException;
import org.apache.ambari.server.configuration.Configuration;
import org.apache.ambari.server.utils.AmbariPath;
-import org.apache.commons.codec.binary.Base64;
import org.apache.commons.io.FileUtils;
import org.apache.commons.net.ntp.TimeStamp;
import org.slf4j.Logger;
@@ -42,7 +41,7 @@ public class MasterKeyServiceImpl implements MasterKeyService {
private static final Logger LOG = LoggerFactory.getLogger(MasterKeyServiceImpl.class);
private static final String MASTER_PASSPHRASE = "masterpassphrase";
private static final String MASTER_PERSISTENCE_TAG_PREFIX = "#1.0# ";
- private static final AESEncryptor aes = new AESEncryptor(MASTER_PASSPHRASE);
+ private final EncryptionService encryptionService = new AESEncryptionService();
private char[] master = null;
@@ -118,7 +117,8 @@ public class MasterKeyServiceImpl implements MasterKeyService {
}
}
- if (persistMasterKey && !MasterKeyServiceImpl.initializeMasterKeyFile(new File(masterKeyLocation), masterKey)) {
+ final MasterKeyServiceImpl masterKeyService = new MasterKeyServiceImpl(masterKey);
+ if (persistMasterKey && !masterKeyService.initializeMasterKeyFile(new File(masterKeyLocation), masterKey)) {
System.exit(1);
} else {
System.exit(0);
@@ -137,14 +137,13 @@ public class MasterKeyServiceImpl implements MasterKeyService {
* @param masterKey the master key
* @return true if the master key was written to the specified file; otherwise false
*/
- public static boolean initializeMasterKeyFile(File masterKeyFile, String masterKey) {
+ public boolean initializeMasterKeyFile(File masterKeyFile, String masterKey) {
LOG.debug("Persisting master key into {}", masterKeyFile.getAbsolutePath());
- EncryptionResult atom = null;
-
+ String encryptedMasterKey = null;
if (masterKey != null) {
try {
- atom = aes.encrypt(masterKey);
+ encryptedMasterKey = encryptionService.encrypt(masterKey, MASTER_PASSPHRASE);
} catch (Exception e) {
LOG.error(String.format("Failed to encrypt master key, no changes have been made: %s", e.getLocalizedMessage()), e);
return false;
@@ -154,22 +153,12 @@ public class MasterKeyServiceImpl implements MasterKeyService {
if (masterKeyFile.exists()) {
if ((masterKeyFile.length() == 0) || isMasterKeyFile(masterKeyFile)) {
LOG.info(String.format("Master key file exists at %s, resetting.", masterKeyFile.getAbsolutePath()));
- FileChannel fileChannel = null;
- try {
- fileChannel = new FileOutputStream(masterKeyFile).getChannel();
+ try (FileOutputStream fos = new FileOutputStream(masterKeyFile); FileChannel fileChannel = fos.getChannel();) {
fileChannel.truncate(0);
} catch (FileNotFoundException e) {
LOG.error(String.format("Failed to open key file at %s: %s", masterKeyFile.getAbsolutePath(), e.getLocalizedMessage()), e);
} catch (IOException e) {
LOG.error(String.format("Failed to reset key file at %s: %s", masterKeyFile.getAbsolutePath(), e.getLocalizedMessage()), e);
- } finally {
- if (fileChannel != null) {
- try {
- fileChannel.close();
- } catch (IOException e) {
- // Ignore...
- }
- }
}
} else {
LOG.info(String.format("File exists at %s, but may not be a master key file. " +
@@ -178,16 +167,11 @@ public class MasterKeyServiceImpl implements MasterKeyService {
}
}
- if (atom != null) {
+ if (encryptedMasterKey != null) {
try {
ArrayList<String> lines = new ArrayList<>();
lines.add(MASTER_PERSISTENCE_TAG_PREFIX + TimeStamp.getCurrentTime().toDateString());
-
- String line = Base64.encodeBase64String((
- Base64.encodeBase64String(atom.salt) + "::" +
- Base64.encodeBase64String(atom.iv) + "::" +
- Base64.encodeBase64String(atom.cipher)).getBytes("UTF8"));
- lines.add(line);
+ lines.add(encryptedMasterKey);
FileUtils.writeLines(masterKeyFile, "UTF8", lines);
// restrict os permissions to only the user running this process
@@ -212,22 +196,11 @@ public class MasterKeyServiceImpl implements MasterKeyService {
* @return true if the file is identitified as "master key" file; otherwise false
*/
private static boolean isMasterKeyFile(File file) {
- FileReader reader = null;
-
- try {
- reader = new FileReader(file);
+ try (FileReader reader = new FileReader(file);) {
char[] buffer = new char[MASTER_PERSISTENCE_TAG_PREFIX.length()];
return (reader.read(buffer) == buffer.length) && Arrays.equals(buffer, MASTER_PERSISTENCE_TAG_PREFIX.toCharArray());
} catch (Exception e) {
// Ignore, assume the file is not a master key file...
- } finally {
- if (reader != null) {
- try {
- reader.close();
- } catch (IOException e) {
- // Ignore...
- }
- }
}
return false;
@@ -302,11 +275,7 @@ public class MasterKeyServiceImpl implements MasterKeyService {
List<String> lines = FileUtils.readLines(masterFile, "UTF8");
String tag = lines.get(0);
LOG.info("Loading from persistent master: " + tag);
- String line = new String(Base64.decodeBase64(lines.get(1)));
- String[] parts = line.split("::");
- master = new String(aes.decrypt(Base64.decodeBase64(parts[0]),
- Base64.decodeBase64(parts[1]), Base64.decodeBase64(parts[2])),
- "UTF8").toCharArray();
+ master = encryptionService.decrypt(lines.get(1), MASTER_PASSPHRASE).toCharArray();
} catch (Exception e) {
e.printStackTrace();
throw e;
diff --git a/ambari-server/src/main/java/org/apache/ambari/server/utils/TextEncoding.java b/ambari-server/src/main/java/org/apache/ambari/server/utils/TextEncoding.java
new file mode 100644
index 0000000..92a7340
--- /dev/null
+++ b/ambari-server/src/main/java/org/apache/ambari/server/utils/TextEncoding.java
@@ -0,0 +1,24 @@
+/*
+ * 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
+ * <p/>
+ * http://www.apache.org/licenses/LICENSE-2.0
+ * <p/>
+ * 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.utils;
+
+public enum TextEncoding {
+
+ BASE_64, BIN_HEX;
+
+}
diff --git a/ambari-server/src/test/java/org/apache/ambari/server/controller/internal/CredentialResourceProviderTest.java b/ambari-server/src/test/java/org/apache/ambari/server/controller/internal/CredentialResourceProviderTest.java
index 08c9839..14a9ea9 100644
--- a/ambari-server/src/test/java/org/apache/ambari/server/controller/internal/CredentialResourceProviderTest.java
+++ b/ambari-server/src/test/java/org/apache/ambari/server/controller/internal/CredentialResourceProviderTest.java
@@ -81,7 +81,7 @@ public class CredentialResourceProviderTest {
public void setUp() throws Exception {
tmpFolder.create();
final File masterKeyFile = tmpFolder.newFile(Configuration.MASTER_KEY_FILENAME_DEFAULT);
- Assert.assertTrue(MasterKeyServiceImpl.initializeMasterKeyFile(masterKeyFile, "secret"));
+ Assert.assertTrue(new MasterKeyServiceImpl("dummyKey").initializeMasterKeyFile(masterKeyFile, "secret"));
injector = Guice.createInjector(new AbstractModule() {
@Override
diff --git a/ambari-server/src/test/java/org/apache/ambari/server/security/encryption/CredentialProviderTest.java b/ambari-server/src/test/java/org/apache/ambari/server/security/encryption/CredentialProviderTest.java
index 7abbcbb..cded559 100644
--- a/ambari-server/src/test/java/org/apache/ambari/server/security/encryption/CredentialProviderTest.java
+++ b/ambari-server/src/test/java/org/apache/ambari/server/security/encryption/CredentialProviderTest.java
@@ -40,7 +40,7 @@ public class CredentialProviderTest {
private void createMasterKey() throws IOException {
File f = tmpFolder.newFile(Configuration.MASTER_KEY_FILENAME_DEFAULT);
- Assert.assertTrue(MasterKeyServiceImpl.initializeMasterKeyFile(f, "blahblah!"));
+ Assert.assertTrue(new MasterKeyServiceImpl("dummyKey").initializeMasterKeyFile(f, "blahblah!"));
MasterKeyService ms = new MasterKeyServiceImpl(f);
if (!ms.isMasterKeyInitialized()) {
throw new ExceptionInInitializerError("Cannot create master key.");
diff --git a/ambari-server/src/test/java/org/apache/ambari/server/security/encryption/CredentialStoreServiceImplTest.java b/ambari-server/src/test/java/org/apache/ambari/server/security/encryption/CredentialStoreServiceImplTest.java
index 7f9f3fd..3a35710 100644
--- a/ambari-server/src/test/java/org/apache/ambari/server/security/encryption/CredentialStoreServiceImplTest.java
+++ b/ambari-server/src/test/java/org/apache/ambari/server/security/encryption/CredentialStoreServiceImplTest.java
@@ -55,7 +55,7 @@ public class CredentialStoreServiceImplTest {
public void setUp() throws Exception {
tmpFolder.create();
final File masterKeyFile = tmpFolder.newFile(Configuration.MASTER_KEY_FILENAME_DEFAULT);
- Assert.assertTrue(MasterKeyServiceImpl.initializeMasterKeyFile(masterKeyFile, "secret"));
+ Assert.assertTrue(new MasterKeyServiceImpl("dummyKey").initializeMasterKeyFile(masterKeyFile, "secret"));
Injector injector = Guice.createInjector(new AbstractModule() {
@Override
diff --git a/ambari-server/src/test/java/org/apache/ambari/server/security/encryption/CredentialStoreTest.java b/ambari-server/src/test/java/org/apache/ambari/server/security/encryption/CredentialStoreTest.java
index f83c46a..c9487ed 100644
--- a/ambari-server/src/test/java/org/apache/ambari/server/security/encryption/CredentialStoreTest.java
+++ b/ambari-server/src/test/java/org/apache/ambari/server/security/encryption/CredentialStoreTest.java
@@ -241,7 +241,7 @@ public class CredentialStoreTest {
@Override
public MasterKeyService createPersisted(File masterKeyFile, String masterKey) {
- MasterKeyServiceImpl.initializeMasterKeyFile(masterKeyFile, masterKey);
+ new MasterKeyServiceImpl("dummyKey").initializeMasterKeyFile(masterKeyFile, masterKey);
return new MasterKeyServiceImpl(masterKeyFile);
}
}
diff --git a/ambari-server/src/test/java/org/apache/ambari/server/security/encryption/EncryptionServiceTest.java b/ambari-server/src/test/java/org/apache/ambari/server/security/encryption/EncryptionServiceTest.java
new file mode 100644
index 0000000..49bd871
--- /dev/null
+++ b/ambari-server/src/test/java/org/apache/ambari/server/security/encryption/EncryptionServiceTest.java
@@ -0,0 +1,100 @@
+/*
+ * 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
+ * <p/>
+ * http://www.apache.org/licenses/LICENSE-2.0
+ * <p/>
+ * 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.security.encryption;
+
+import static org.easymock.EasyMock.expect;
+import static org.junit.Assert.assertEquals;
+import static org.powermock.api.easymock.PowerMock.mockStatic;
+import static org.powermock.api.easymock.PowerMock.replayAll;
+import static org.powermock.api.easymock.PowerMock.verifyAll;
+
+import java.io.File;
+import java.util.HashMap;
+import java.util.Map;
+
+import org.apache.ambari.server.configuration.Configuration;
+import org.apache.ambari.server.utils.TextEncoding;
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.rules.TemporaryFolder;
+import org.junit.runner.RunWith;
+import org.powermock.core.classloader.annotations.PowerMockIgnore;
+import org.powermock.core.classloader.annotations.PrepareForTest;
+import org.powermock.modules.junit4.PowerMockRunner;
+
+import junit.framework.Assert;
+
+@RunWith(PowerMockRunner.class)
+@PrepareForTest({ MasterKeyServiceImpl.class })
+@PowerMockIgnore({ "javax.crypto.*" })
+public class EncryptionServiceTest {
+
+ @Rule
+ private final TemporaryFolder tmpFolder = new TemporaryFolder();
+
+ private final EncryptionService encryptionService = new AESEncryptionService();
+
+ @Test
+ public void testEncryptAndDecryptUsingCustomKeyWithBase64Encoding() throws Exception {
+ testEncryptAndDecryptUsingCustomKey(TextEncoding.BASE_64);
+ }
+
+ @Test
+ public void testEncryptAndDecryptUsingCustomKeyWithBinHex64Encoding() throws Exception {
+ testEncryptAndDecryptUsingCustomKey(TextEncoding.BIN_HEX);
+ }
+
+ public void testEncryptAndDecryptUsingCustomKey(TextEncoding textEncoding) throws Exception {
+ final String key = "mySuperS3cr3tMast3rKey!";
+ final String toBeEncrypted = "mySuperS3cr3tP4ssW0rD!";
+ final String encrypted = encryptionService.encrypt(toBeEncrypted, key, textEncoding);
+ final String decrypted = encryptionService.decrypt(encrypted, key, textEncoding);
+ assertEquals(toBeEncrypted, decrypted);
+ }
+
+ @Test
+ public void testEncryptAndDecryptUsingPersistedMasterKey() throws Exception {
+ final String fileDir = tmpFolder.newFolder("keys").getAbsolutePath();
+ final File masterKeyFile = new File(fileDir, "master");
+ final String masterKey = "mySuperS3cr3tMast3rKey!";
+ final MasterKeyServiceImpl ms = new MasterKeyServiceImpl("dummyKey");
+ Assert.assertTrue(ms.initializeMasterKeyFile(masterKeyFile, masterKey));
+
+ final String toBeEncrypted = "mySuperS3cr3tP4ssW0rD!";
+
+ setupEnvironmentVariableExpectations(masterKeyFile);
+ final String encrypted = encryptionService.encrypt(toBeEncrypted);
+ final String decrypted = encryptionService.decrypt(encrypted);
+ verifyAll();
+ assertEquals(toBeEncrypted, decrypted);
+ }
+
+ @Test(expected = SecurityException.class)
+ public void shouldThrowSecurityExceptionInCaseOfEncryptingWithNonExistingPersistedMasterKey() throws Exception {
+ final String toBeEncrypted = "mySuperS3cr3tP4ssW0rD!";
+ encryptionService.encrypt(toBeEncrypted);
+ }
+
+ private void setupEnvironmentVariableExpectations(final File masterKeyFile) {
+ final Map<String, String> sysEnvironment = new HashMap<>();
+ sysEnvironment.put(Configuration.MASTER_KEY_LOCATION.getKey(), masterKeyFile.getAbsolutePath());
+ mockStatic(System.class);
+ expect(System.getenv()).andReturn(sysEnvironment);
+ replayAll();
+ }
+}
diff --git a/ambari-server/src/test/java/org/apache/ambari/server/security/encryption/MasterKeyServiceTest.java b/ambari-server/src/test/java/org/apache/ambari/server/security/encryption/MasterKeyServiceTest.java
index 5f76072..032bb23 100644
--- a/ambari-server/src/test/java/org/apache/ambari/server/security/encryption/MasterKeyServiceTest.java
+++ b/ambari-server/src/test/java/org/apache/ambari/server/security/encryption/MasterKeyServiceTest.java
@@ -64,9 +64,10 @@ public class MasterKeyServiceTest extends TestCase {
@Test
public void testInitializeMasterKey() throws Exception {
File masterKeyFile = new File(fileDir, "master");
- Assert.assertTrue(MasterKeyServiceImpl.initializeMasterKeyFile(masterKeyFile, "ThisisSomePassPhrase"));
+ MasterKeyServiceImpl ms = new MasterKeyServiceImpl("dummyKey");
+ Assert.assertTrue(ms.initializeMasterKeyFile(masterKeyFile, "ThisisSomePassPhrase"));
- MasterKeyService ms = new MasterKeyServiceImpl(masterKeyFile);
+ ms = new MasterKeyServiceImpl(masterKeyFile);
Assert.assertTrue(ms.isMasterKeyInitialized());
Assert.assertTrue(masterKeyFile.exists());
@@ -112,9 +113,10 @@ public class MasterKeyServiceTest extends TestCase {
public void testReadFromEnvAsPath() throws Exception {
// Create a master key
File masterKeyFile = new File(fileDir, "master");
- Assert.assertTrue(MasterKeyServiceImpl.initializeMasterKeyFile(masterKeyFile, "ThisisSomePassPhrase"));
+ MasterKeyServiceImpl ms = new MasterKeyServiceImpl("dummyKey");
+ Assert.assertTrue(ms.initializeMasterKeyFile(masterKeyFile, "ThisisSomePassPhrase"));
- MasterKeyService ms = new MasterKeyServiceImpl(masterKeyFile);
+ ms = new MasterKeyServiceImpl(masterKeyFile);
Assert.assertTrue(ms.isMasterKeyInitialized());
Assert.assertTrue(masterKeyFile.exists());