Skip to content

Commit

Permalink
HBASE-27342 Use Hadoop Credentials API to retrieve passwords of TLS k…
Browse files Browse the repository at this point in the history
…ey/trust stores (apache#4751)

Signed-off-by: Andrew Purtell <apurtell@apache.org>
Signed-off-by: Duo Zhang <zhangduo@apache.org>
Signed-off-by: Balazs Meszaros <meszibalu@apache.org>
  • Loading branch information
anmolnar authored and bbeaudreault committed Apr 2, 2023
1 parent 040362f commit 45eecf2
Show file tree
Hide file tree
Showing 11 changed files with 83 additions and 102 deletions.
3 changes: 3 additions & 0 deletions bin/hbase
Original file line number Diff line number Diff line change
Expand Up @@ -107,6 +107,7 @@ show_usage() {
echo " cellcounter Run CellCounter tool"
echo " pre-upgrade Run Pre-Upgrade validator tool"
echo " hbtop Run HBTop tool"
echo " credential Run the Hadoop Credential Shell"
echo " CLASSNAME Run the class named CLASSNAME"
}

Expand Down Expand Up @@ -759,6 +760,8 @@ elif [ "$COMMAND" = "hbtop" ] ; then
HBASE_HBTOP_OPTS="${HBASE_HBTOP_OPTS} -Dlog4j2.configurationFile=file:${HBASE_HOME}/conf/log4j2-hbtop.properties"
fi
HBASE_OPTS="${HBASE_OPTS} ${HBASE_HBTOP_OPTS}"
elif [ "$COMMAND" = "credential" ] ; then
CLASS='org.apache.hadoop.security.alias.CredentialShell'
else
CLASS=$COMMAND
if [[ "$CLASS" =~ .*IntegrationTest.* ]] ; then
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,6 @@
import java.io.IOException;
import java.net.SocketAddress;
import java.util.concurrent.atomic.AtomicReference;
import javax.net.ssl.SSLException;
import org.apache.hadoop.conf.Configuration;
import org.apache.hadoop.hbase.HBaseInterfaceAudience;
import org.apache.hadoop.hbase.HConstants;
Expand Down Expand Up @@ -89,7 +88,7 @@ protected void closeInternal() {
}
}

SslContext getSslContext() throws X509Exception, SSLException {
SslContext getSslContext() throws X509Exception, IOException {
SslContext result = sslContextForClient.get();
if (result == null) {
result = X509Util.createSslContextForClient(conf);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,6 @@
*/
package org.apache.hadoop.hbase.io.crypto.tls;

import java.io.BufferedInputStream;
import java.io.File;
import java.io.IOException;
import java.io.InputStream;
Expand All @@ -32,7 +31,6 @@
import javax.net.ssl.CertPathTrustManagerParameters;
import javax.net.ssl.KeyManager;
import javax.net.ssl.KeyManagerFactory;
import javax.net.ssl.SSLException;
import javax.net.ssl.TrustManager;
import javax.net.ssl.TrustManagerFactory;
import javax.net.ssl.X509ExtendedTrustManager;
Expand Down Expand Up @@ -65,6 +63,7 @@
public final class X509Util {

private static final Logger LOG = LoggerFactory.getLogger(X509Util.class);
private static final char[] EMPTY_CHAR_ARRAY = new char[0];

// Config
static final String CONFIG_PREFIX = "hbase.rpc.tls.";
Expand Down Expand Up @@ -140,12 +139,12 @@ static String[] getDefaultCipherSuitesForJavaVersion(String javaVersion) {
}

public static SslContext createSslContextForClient(Configuration config)
throws X509Exception, SSLException {
throws X509Exception, IOException {

SslContextBuilder sslContextBuilder = SslContextBuilder.forClient();

String keyStoreLocation = config.get(TLS_CONFIG_KEYSTORE_LOCATION, "");
String keyStorePassword = config.get(TLS_CONFIG_KEYSTORE_PASSWORD, "");
char[] keyStorePassword = config.getPassword(TLS_CONFIG_KEYSTORE_PASSWORD);
String keyStoreType = config.get(TLS_CONFIG_KEYSTORE_TYPE, "");

if (keyStoreLocation.isEmpty()) {
Expand All @@ -156,7 +155,7 @@ public static SslContext createSslContextForClient(Configuration config)
}

String trustStoreLocation = config.get(TLS_CONFIG_TRUSTSTORE_LOCATION, "");
String trustStorePassword = config.get(TLS_CONFIG_TRUSTSTORE_PASSWORD, "");
char[] trustStorePassword = config.getPassword(TLS_CONFIG_TRUSTSTORE_PASSWORD);
String trustStoreType = config.get(TLS_CONFIG_TRUSTSTORE_TYPE, "");

boolean sslCrlEnabled = config.getBoolean(TLS_CONFIG_CLR, false);
Expand All @@ -177,9 +176,9 @@ public static SslContext createSslContextForClient(Configuration config)
}

public static SslContext createSslContextForServer(Configuration config)
throws X509Exception, SSLException {
throws X509Exception, IOException {
String keyStoreLocation = config.get(TLS_CONFIG_KEYSTORE_LOCATION, "");
String keyStorePassword = config.get(TLS_CONFIG_KEYSTORE_PASSWORD, "");
char[] keyStorePassword = config.getPassword(TLS_CONFIG_KEYSTORE_PASSWORD);
String keyStoreType = config.get(TLS_CONFIG_KEYSTORE_TYPE, "");

if (keyStoreLocation.isEmpty()) {
Expand All @@ -193,7 +192,7 @@ public static SslContext createSslContextForServer(Configuration config)
.forServer(createKeyManager(keyStoreLocation, keyStorePassword, keyStoreType));

String trustStoreLocation = config.get(TLS_CONFIG_TRUSTSTORE_LOCATION, "");
String trustStorePassword = config.get(TLS_CONFIG_TRUSTSTORE_PASSWORD, "");
char[] trustStorePassword = config.getPassword(TLS_CONFIG_TRUSTSTORE_PASSWORD);
String trustStoreType = config.get(TLS_CONFIG_TRUSTSTORE_TYPE, "");

boolean sslCrlEnabled = config.getBoolean(TLS_CONFIG_CLR, false);
Expand Down Expand Up @@ -225,27 +224,25 @@ public static SslContext createSslContextForServer(Configuration config)
* @return the key manager.
* @throws KeyManagerException if something goes wrong.
*/
static X509KeyManager createKeyManager(String keyStoreLocation, String keyStorePassword,
static X509KeyManager createKeyManager(String keyStoreLocation, char[] keyStorePassword,
String keyStoreType) throws KeyManagerException {

if (keyStorePassword == null) {
keyStorePassword = "";
}

if (keyStoreType == null) {
keyStoreType = "jks";
}

if (keyStorePassword == null) {
keyStorePassword = EMPTY_CHAR_ARRAY;
}

try {
char[] password = keyStorePassword.toCharArray();
KeyStore ks = KeyStore.getInstance(keyStoreType);
try (InputStream inputStream =
new BufferedInputStream(Files.newInputStream(new File(keyStoreLocation).toPath()))) {
ks.load(inputStream, password);
try (InputStream inputStream = Files.newInputStream(new File(keyStoreLocation).toPath())) {
ks.load(inputStream, keyStorePassword);
}

KeyManagerFactory kmf = KeyManagerFactory.getInstance("PKIX");
kmf.init(ks, password);
kmf.init(ks, keyStorePassword);

for (KeyManager km : kmf.getKeyManagers()) {
if (km instanceof X509KeyManager) {
Expand All @@ -272,23 +269,21 @@ static X509KeyManager createKeyManager(String keyStoreLocation, String keyStoreP
* @return the trust manager.
* @throws TrustManagerException if something goes wrong.
*/
static X509TrustManager createTrustManager(String trustStoreLocation, String trustStorePassword,
static X509TrustManager createTrustManager(String trustStoreLocation, char[] trustStorePassword,
String trustStoreType, boolean crlEnabled, boolean ocspEnabled) throws TrustManagerException {

if (trustStorePassword == null) {
trustStorePassword = "";
}

if (trustStoreType == null) {
trustStoreType = "jks";
}

if (trustStorePassword == null) {
trustStorePassword = EMPTY_CHAR_ARRAY;
}

try {
char[] password = trustStorePassword.toCharArray();
KeyStore ts = KeyStore.getInstance(trustStoreType);
try (InputStream inputStream =
new BufferedInputStream(Files.newInputStream(new File(trustStoreLocation).toPath()))) {
ts.load(inputStream, password);
try (InputStream inputStream = Files.newInputStream(new File(trustStoreLocation).toPath())) {
ts.load(inputStream, trustStorePassword);
}

PKIXBuilderParameters pbParams = new PKIXBuilderParameters(ts, new X509CertSelector());
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -19,12 +19,13 @@

import static org.hamcrest.MatcherAssert.assertThat;
import static org.hamcrest.Matchers.containsString;
import static org.hamcrest.Matchers.equalTo;
import static org.junit.Assert.assertArrayEquals;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertThrows;
import static org.junit.Assert.assertTrue;
import static org.junit.Assume.assumeTrue;
import static org.junit.Assume.assumeThat;
import static org.mockito.Mockito.mock;

import java.io.File;
Expand All @@ -42,7 +43,6 @@
import org.apache.hadoop.hbase.exceptions.KeyManagerException;
import org.apache.hadoop.hbase.exceptions.SSLContextException;
import org.apache.hadoop.hbase.exceptions.TrustManagerException;
import org.apache.hadoop.hbase.exceptions.X509Exception;
import org.apache.hadoop.hbase.testclassification.MiscTests;
import org.apache.hadoop.hbase.testclassification.SmallTests;
import org.bouncycastle.jce.provider.BouncyCastleProvider;
Expand Down Expand Up @@ -74,6 +74,7 @@ public class TestX509Util {
HBaseClassTestRule.forClass(TestX509Util.class);

private static final HBaseCommonTestingUtility UTIL = new HBaseCommonTestingUtility();
private static final char[] EMPTY_CHAR_ARRAY = new char[0];

private static X509TestContextProvider PROVIDER;

Expand All @@ -84,7 +85,7 @@ public class TestX509Util {
public X509KeyType certKeyType;

@Parameterized.Parameter(value = 2)
public String keyPassword;
public char[] keyPassword;

@Parameterized.Parameter(value = 3)
public Integer paramIndex;
Expand All @@ -100,7 +101,7 @@ public static Collection<Object[]> data() {
int paramIndex = 0;
for (X509KeyType caKeyType : X509KeyType.values()) {
for (X509KeyType certKeyType : X509KeyType.values()) {
for (String keyPassword : new String[] { "", "pa$$w0rd" }) {
for (char[] keyPassword : new char[][] { "".toCharArray(), "pa$$w0rd".toCharArray() }) {
params.add(new Object[] { caKeyType, certKeyType, keyPassword, paramIndex++ });
}
}
Expand Down Expand Up @@ -172,13 +173,6 @@ public void testCreateSSLContextWithoutKeyStoreLocationClient() throws Exception
X509Util.createSslContextForClient(conf);
}

@Test(expected = X509Exception.class)
public void testCreateSSLContextWithoutKeyStorePassword() throws Exception {
assumeTrue(x509TestContext.isKeyStoreEncrypted());
conf.unset(X509Util.TLS_CONFIG_KEYSTORE_PASSWORD);
X509Util.createSslContextForServer(conf);
}

@Test
public void testCreateSSLContextWithoutTrustStoreLocationClient() throws Exception {
conf.unset(X509Util.TLS_CONFIG_TRUSTSTORE_LOCATION);
Expand Down Expand Up @@ -220,7 +214,7 @@ public void testLoadJKSKeyStore() throws Exception {

@Test
public void testLoadJKSKeyStoreNullPassword() throws Exception {
assumeTrue(x509TestContext.getKeyStorePassword().isEmpty());
assumeThat(x509TestContext.getKeyStorePassword(), equalTo(EMPTY_CHAR_ARRAY));
// Make sure that empty password and null password are treated the same
X509Util.createKeyManager(
x509TestContext.getKeyStoreFile(KeyStoreFileType.JKS).getAbsolutePath(), null,
Expand All @@ -237,12 +231,12 @@ public void testLoadJKSKeyStoreFileTypeDefaultToJks() throws Exception {
}

@Test
public void testLoadJKSKeyStoreWithWrongPassword() throws Exception {
public void testLoadJKSKeyStoreWithWrongPassword() {
assertThrows(KeyManagerException.class, () -> {
// Attempting to load with the wrong key password should fail
X509Util.createKeyManager(
x509TestContext.getKeyStoreFile(KeyStoreFileType.JKS).getAbsolutePath(), "wrong password",
KeyStoreFileType.JKS.getPropertyValue());
x509TestContext.getKeyStoreFile(KeyStoreFileType.JKS).getAbsolutePath(),
"wrong password".toCharArray(), KeyStoreFileType.JKS.getPropertyValue());
});
}

Expand All @@ -256,9 +250,7 @@ public void testLoadJKSTrustStore() throws Exception {

@Test
public void testLoadJKSTrustStoreNullPassword() throws Exception {
if (!x509TestContext.getTrustStorePassword().isEmpty()) {
return;
}
assumeThat(x509TestContext.getTrustStorePassword(), equalTo(EMPTY_CHAR_ARRAY));
// Make sure that empty password and null password are treated the same
X509Util.createTrustManager(
x509TestContext.getTrustStoreFile(KeyStoreFileType.JKS).getAbsolutePath(), null,
Expand All @@ -279,8 +271,8 @@ public void testLoadJKSTrustStoreWithWrongPassword() throws Exception {
assertThrows(TrustManagerException.class, () -> {
// Attempting to load with the wrong key password should fail
X509Util.createTrustManager(
x509TestContext.getTrustStoreFile(KeyStoreFileType.JKS).getAbsolutePath(), "wrong password",
KeyStoreFileType.JKS.getPropertyValue(), true, true);
x509TestContext.getTrustStoreFile(KeyStoreFileType.JKS).getAbsolutePath(),
"wrong password".toCharArray(), KeyStoreFileType.JKS.getPropertyValue(), true, true);
});
}

Expand All @@ -294,9 +286,7 @@ public void testLoadPKCS12KeyStore() throws Exception {

@Test
public void testLoadPKCS12KeyStoreNullPassword() throws Exception {
if (!x509TestContext.getKeyStorePassword().isEmpty()) {
return;
}
assumeThat(x509TestContext.getKeyStorePassword(), equalTo(EMPTY_CHAR_ARRAY));
// Make sure that empty password and null password are treated the same
X509Util.createKeyManager(
x509TestContext.getKeyStoreFile(KeyStoreFileType.PKCS12).getAbsolutePath(), null,
Expand All @@ -309,7 +299,7 @@ public void testLoadPKCS12KeyStoreWithWrongPassword() throws Exception {
// Attempting to load with the wrong key password should fail
X509Util.createKeyManager(
x509TestContext.getKeyStoreFile(KeyStoreFileType.PKCS12).getAbsolutePath(),
"wrong password", KeyStoreFileType.PKCS12.getPropertyValue());
"wrong password".toCharArray(), KeyStoreFileType.PKCS12.getPropertyValue());
});
}

Expand All @@ -324,9 +314,7 @@ public void testLoadPKCS12TrustStore() throws Exception {

@Test
public void testLoadPKCS12TrustStoreNullPassword() throws Exception {
if (!x509TestContext.getTrustStorePassword().isEmpty()) {
return;
}
assumeThat(x509TestContext.getTrustStorePassword(), equalTo(EMPTY_CHAR_ARRAY));
// Make sure that empty password and null password are treated the same
X509Util.createTrustManager(
x509TestContext.getTrustStoreFile(KeyStoreFileType.PKCS12).getAbsolutePath(), null,
Expand All @@ -339,7 +327,7 @@ public void testLoadPKCS12TrustStoreWithWrongPassword() throws Exception {
// Attempting to load with the wrong key password should fail
X509Util.createTrustManager(
x509TestContext.getTrustStoreFile(KeyStoreFileType.PKCS12).getAbsolutePath(),
"wrong password", KeyStoreFileType.PKCS12.getPropertyValue(), true, true);
"wrong password".toCharArray(), KeyStoreFileType.PKCS12.getPropertyValue(), true, true);
});
}

Expand Down
Loading

0 comments on commit 45eecf2

Please sign in to comment.