Skip to content

Commit 2e8df56

Browse files
bibith4Shijin
authored and
Shijin
committed
Changes to enable ssl/tls in hms
Co-authored-by: Arin Mathew <[email protected]> Added default value to correct formating
1 parent fa53b84 commit 2e8df56

File tree

15 files changed

+505
-12
lines changed

15 files changed

+505
-12
lines changed

Diff for: .github/workflows/hive-tests.yml

+4-1
Original file line numberDiff line numberDiff line change
@@ -114,6 +114,9 @@ jobs:
114114
run: |
115115
export MAVEN_OPTS="${MAVEN_INSTALL_OPTS}"
116116
./mvnw install ${MAVEN_FAST_INSTALL} -am -pl :presto-hive
117-
- name: Run Hive Dockerized Tests
117+
- name: Run Hive Insert Overwrite Tests
118118
if: needs.changes.outputs.codechange == 'true'
119119
run: ./mvnw test ${MAVEN_TEST} -pl :presto-hive -P test-hive-insert-overwrite
120+
- name: Run Hive SSL Enabled Tests
121+
if: needs.changes.outputs.codechange == 'true'
122+
run: ./mvnw test ${MAVEN_TEST} -pl :presto-hive -P test-ssl-enabled-hms

Diff for: presto-docs/src/main/sphinx/connector/hive.rst

+10
Original file line numberDiff line numberDiff line change
@@ -232,6 +232,16 @@ Property Name Descriptio
232232
``hive.invalidate-metastore-cache-procedure-enabled`` When enabled, users will be able to invalidate metastore false
233233
cache on demand.
234234

235+
``hive.metastore.thrift.client.tls.enabled`` Whether TLS security is enabled. false
236+
237+
``hive.metastore.thrift.client.tls.keystore.path`` Path to the PEM or JKS key store. NONE
238+
239+
``hive.metastore.thrift.client.tls.keystore.password`` Password for the key store. NONE
240+
241+
``hive.metastore.thrift.client.tls.truststore.path`` Path to the PEM or JKS trust store. NONE
242+
243+
``hive.metastore.thrift.client.tls.truststore.password`` Password for the trust store NONE
244+
235245
======================================================= ============================================================= ============
236246

237247
AWS Glue Catalog Configuration Properties

Diff for: presto-hive-common/src/main/java/com/facebook/presto/hive/HiveErrorCode.java

+1
Original file line numberDiff line numberDiff line change
@@ -75,6 +75,7 @@ public enum HiveErrorCode
7575
HIVE_METASTORE_USER_ERROR(47, USER_ERROR),
7676
HIVE_RANGER_SERVER_ERROR(48, EXTERNAL),
7777
HIVE_FUNCTION_INITIALIZATION_ERROR(49, EXTERNAL),
78+
HIVE_METASTORE_INITIALIZE_SSL_ERROR(50, EXTERNAL),
7879
/**/;
7980

8081
private final ErrorCode errorCode;

Diff for: presto-hive-metastore/pom.xml

+6
Original file line numberDiff line numberDiff line change
@@ -71,6 +71,12 @@
7171
<artifactId>slice</artifactId>
7272
</dependency>
7373

74+
<dependency>
75+
<groupId>io.airlift</groupId>
76+
<artifactId>security</artifactId>
77+
<version>200</version>
78+
</dependency>
79+
7480
<dependency>
7581
<groupId>com.fasterxml.jackson.core</groupId>
7682
<artifactId>jackson-core</artifactId>

Diff for: presto-hive-metastore/src/main/java/com/facebook/presto/hive/MetastoreClientConfig.java

+63
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,7 @@
2525
import javax.validation.constraints.Min;
2626
import javax.validation.constraints.NotNull;
2727

28+
import java.io.File;
2829
import java.util.concurrent.TimeUnit;
2930

3031
import static java.util.concurrent.TimeUnit.MINUTES;
@@ -54,6 +55,12 @@ public class MetastoreClientConfig
5455
private boolean deleteFilesOnTableDrop;
5556
private boolean invalidateMetastoreCacheProcedureEnabled;
5657

58+
private boolean metastoreTlsEnabled;
59+
private File metastoreTlsKeystorePath;
60+
private String metastoreTlsKeystorePassword;
61+
private File metastoreTlsTruststorePath;
62+
private String metastoreTlsTruststorePassword;
63+
5764
public HostAndPort getMetastoreSocksProxy()
5865
{
5966
return metastoreSocksProxy;
@@ -317,4 +324,60 @@ public MetastoreClientConfig setInvalidateMetastoreCacheProcedureEnabled(boolean
317324
this.invalidateMetastoreCacheProcedureEnabled = invalidateMetastoreCacheProcedureEnabled;
318325
return this;
319326
}
327+
328+
@Config("hive.metastore.thrift.client.tls.enabled")
329+
@ConfigDescription("Enable TLS security for HMS")
330+
public MetastoreClientConfig setMetastoreTlsEnabled(boolean enabled)
331+
{
332+
this.metastoreTlsEnabled = enabled;
333+
return this;
334+
}
335+
public boolean getMetastoreTlsEnabled()
336+
{
337+
return metastoreTlsEnabled;
338+
}
339+
@Config("hive.metastore.thrift.client.tls.keystore.path")
340+
@ConfigDescription("Path to JKS or PEM key store")
341+
public MetastoreClientConfig setMetastoreTlsKeystorePath(File keystorePath)
342+
{
343+
this.metastoreTlsKeystorePath = keystorePath;
344+
return this;
345+
}
346+
public File getMetastoreTlsKeystorePath()
347+
{
348+
return metastoreTlsKeystorePath;
349+
}
350+
@Config("hive.metastore.thrift.client.tls.keystore.password")
351+
@ConfigDescription("Password to key store")
352+
public MetastoreClientConfig setMetastoreTlsKeystorePassword(String keystorePassword)
353+
{
354+
this.metastoreTlsKeystorePassword = keystorePassword;
355+
return this;
356+
}
357+
public String getMetastoreTlsKeystorePassword()
358+
{
359+
return metastoreTlsKeystorePassword;
360+
}
361+
@Config("hive.metastore.thrift.client.tls.truststore.path")
362+
@ConfigDescription("Path to JKS or PEM trust store")
363+
public MetastoreClientConfig setMetastoreTlsTruststorePath(File truststorePath)
364+
{
365+
this.metastoreTlsTruststorePath = truststorePath;
366+
return this;
367+
}
368+
public File getMetastoreTlsTruststorePath()
369+
{
370+
return metastoreTlsTruststorePath;
371+
}
372+
@Config("hive.metastore.thrift.client.tls.truststore.password")
373+
@ConfigDescription("Path to trust store")
374+
public MetastoreClientConfig setMetastoreTlsTruststorePassword(String truststorePassword)
375+
{
376+
this.metastoreTlsTruststorePassword = truststorePassword;
377+
return this;
378+
}
379+
public String getMetastoreTlsTruststorePassword()
380+
{
381+
return metastoreTlsTruststorePassword;
382+
}
320383
}

Diff for: presto-hive-metastore/src/main/java/com/facebook/presto/hive/metastore/thrift/HiveMetastoreClientFactory.java

+150-1
Original file line numberDiff line numberDiff line change
@@ -15,16 +15,38 @@
1515

1616
import com.facebook.presto.hive.MetastoreClientConfig;
1717
import com.facebook.presto.hive.authentication.HiveMetastoreAuthentication;
18+
import com.facebook.presto.spi.PrestoException;
1819
import com.google.common.net.HostAndPort;
20+
import io.airlift.security.pem.PemReader;
1921
import io.airlift.units.Duration;
2022
import org.apache.thrift.transport.TTransportException;
2123

2224
import javax.inject.Inject;
25+
import javax.net.ssl.KeyManager;
26+
import javax.net.ssl.KeyManagerFactory;
2327
import javax.net.ssl.SSLContext;
28+
import javax.net.ssl.TrustManager;
29+
import javax.net.ssl.TrustManagerFactory;
30+
import javax.net.ssl.X509TrustManager;
31+
import javax.security.auth.x500.X500Principal;
2432

33+
import java.io.File;
34+
import java.io.FileInputStream;
35+
import java.io.IOException;
36+
import java.io.InputStream;
37+
import java.security.GeneralSecurityException;
38+
import java.security.KeyStore;
39+
import java.security.cert.Certificate;
40+
import java.security.cert.CertificateExpiredException;
41+
import java.security.cert.CertificateNotYetValidException;
42+
import java.security.cert.X509Certificate;
43+
import java.util.Arrays;
44+
import java.util.List;
2545
import java.util.Optional;
2646

47+
import static com.facebook.presto.hive.HiveErrorCode.HIVE_METASTORE_INITIALIZE_SSL_ERROR;
2748
import static java.lang.Math.toIntExact;
49+
import static java.util.Collections.list;
2850
import static java.util.Objects.requireNonNull;
2951

3052
public class HiveMetastoreClientFactory
@@ -33,6 +55,7 @@ public class HiveMetastoreClientFactory
3355
private final Optional<HostAndPort> socksProxy;
3456
private final int timeoutMillis;
3557
private final HiveMetastoreAuthentication metastoreAuthentication;
58+
public static final String PROTOCOL = "SSL";
3659

3760
public HiveMetastoreClientFactory(
3861
Optional<SSLContext> sslContext,
@@ -49,12 +72,138 @@ public HiveMetastoreClientFactory(
4972
@Inject
5073
public HiveMetastoreClientFactory(MetastoreClientConfig metastoreClientConfig, HiveMetastoreAuthentication metastoreAuthentication)
5174
{
52-
this(Optional.empty(), Optional.ofNullable(metastoreClientConfig.getMetastoreSocksProxy()), metastoreClientConfig.getMetastoreTimeout(), metastoreAuthentication);
75+
this(metastoreSslContext(metastoreClientConfig.getMetastoreTlsEnabled(), Optional.ofNullable(metastoreClientConfig.getMetastoreTlsKeystorePath()), Optional.ofNullable(metastoreClientConfig.getMetastoreTlsKeystorePassword()), Optional.ofNullable(metastoreClientConfig.getMetastoreTlsTruststorePath()), Optional.ofNullable(metastoreClientConfig.getMetastoreTlsTruststorePassword())), Optional.ofNullable(metastoreClientConfig.getMetastoreSocksProxy()), metastoreClientConfig.getMetastoreTimeout(), metastoreAuthentication);
5376
}
5477

5578
public HiveMetastoreClient create(HostAndPort address, Optional<String> token)
5679
throws TTransportException
5780
{
5881
return new ThriftHiveMetastoreClient(Transport.create(address, sslContext, socksProxy, timeoutMillis, metastoreAuthentication, token));
5982
}
83+
84+
/**
85+
* Reads the truststore and keystore and returns the SSLContext
86+
* @param metastoreTlsEnabled
87+
* @param metastoreKeyStorePath
88+
* @param metastoreKeyStorePassword
89+
* @param metastoreTrustStorePath
90+
* @param metastoreTrustStorePassword
91+
* @return SSLContext
92+
*/
93+
private static Optional<SSLContext> metastoreSslContext(boolean metastoreTlsEnabled, Optional<File> metastoreKeyStorePath, Optional<String> metastoreKeyStorePassword, Optional<File> metastoreTrustStorePath, Optional<String> metastoreTrustStorePassword)
94+
{
95+
if (!metastoreTlsEnabled || (!metastoreKeyStorePath.isPresent() && !metastoreTrustStorePath.isPresent())) {
96+
return Optional.empty();
97+
}
98+
99+
try {
100+
KeyStore metastoreKeyStore = null;
101+
KeyManager[] metastoreKeyManagers = null;
102+
if (metastoreKeyStorePath.isPresent()) {
103+
char[] keyManagerPassword;
104+
try {
105+
// attempt to read the key store as a PEM file
106+
metastoreKeyStore = PemReader.loadKeyStore(metastoreKeyStorePath.get(), metastoreKeyStorePath.get(), metastoreKeyStorePassword);
107+
// for PEM encoded keys, the password is used to decrypt the specific key (and does not protect the keystore itself)
108+
keyManagerPassword = new char[0];
109+
}
110+
catch (GeneralSecurityException | IOException ignored) {
111+
keyManagerPassword = metastoreKeyStorePassword.map(String::toCharArray).orElse(null);
112+
113+
metastoreKeyStore = KeyStore.getInstance(KeyStore.getDefaultType());
114+
try (InputStream in = new FileInputStream(metastoreKeyStorePath.get())) {
115+
metastoreKeyStore.load(in, keyManagerPassword);
116+
}
117+
}
118+
validateKeyStoreCertificates(metastoreKeyStore);
119+
final KeyManagerFactory metastoreKeyManagerFactory = KeyManagerFactory.getInstance(KeyManagerFactory.getDefaultAlgorithm());
120+
metastoreKeyManagerFactory.init(metastoreKeyStore, keyManagerPassword);
121+
metastoreKeyManagers = metastoreKeyManagerFactory.getKeyManagers();
122+
}
123+
124+
// load TrustStore if configured, otherwise use KeyStore
125+
KeyStore metastoreTrustStore = metastoreKeyStore;
126+
if (metastoreTrustStorePath.isPresent()) {
127+
metastoreTrustStore = getTrustStore(metastoreTrustStorePath.get(), metastoreTrustStorePassword);
128+
}
129+
130+
// create TrustManagerFactory
131+
final TrustManagerFactory trustManagerFactory = TrustManagerFactory.getInstance(TrustManagerFactory.getDefaultAlgorithm());
132+
trustManagerFactory.init(metastoreTrustStore);
133+
134+
// get X509TrustManager
135+
final TrustManager[] trustManagers = trustManagerFactory.getTrustManagers();
136+
if (trustManagers.length != 1 || !(trustManagers[0] instanceof X509TrustManager)) {
137+
throw new RuntimeException("Unexpected default trust managers:" + Arrays.toString(trustManagers));
138+
}
139+
140+
// create SSLContext
141+
final SSLContext sslContext = SSLContext.getInstance(PROTOCOL);
142+
sslContext.init(metastoreKeyManagers, trustManagers, null);
143+
return Optional.of(sslContext);
144+
}
145+
catch (GeneralSecurityException | IOException e) {
146+
throw new PrestoException(HIVE_METASTORE_INITIALIZE_SSL_ERROR, e);
147+
}
148+
}
149+
150+
/**
151+
* Reads the truststore certificate and returns it
152+
* @param trustStorePath
153+
* @param trustStorePassword
154+
* @throws IOException
155+
* @throws GeneralSecurityException
156+
*/
157+
private static KeyStore getTrustStore(File trustStorePath, Optional<String> trustStorePassword)
158+
throws IOException, GeneralSecurityException
159+
{
160+
final KeyStore trustStore = KeyStore.getInstance(KeyStore.getDefaultType());
161+
try {
162+
// attempt to read the trust store as a PEM file
163+
final List<X509Certificate> certificateChain = PemReader.readCertificateChain(trustStorePath);
164+
if (!certificateChain.isEmpty()) {
165+
trustStore.load(null, null);
166+
for (X509Certificate certificate : certificateChain) {
167+
final X500Principal principal = certificate.getSubjectX500Principal();
168+
trustStore.setCertificateEntry(principal.getName(), certificate);
169+
}
170+
return trustStore;
171+
}
172+
}
173+
catch (IOException | GeneralSecurityException ignored) {
174+
}
175+
176+
try (InputStream in = new FileInputStream(trustStorePath)) {
177+
trustStore.load(in, trustStorePassword.map(String::toCharArray).orElse(null));
178+
}
179+
return trustStore;
180+
}
181+
182+
/**
183+
* Validate keystore certificate
184+
* @param keyStore
185+
* @throws GeneralSecurityException
186+
*/
187+
private static void validateKeyStoreCertificates(KeyStore keyStore) throws GeneralSecurityException
188+
{
189+
for (String alias : list(keyStore.aliases())) {
190+
if (!keyStore.isKeyEntry(alias)) {
191+
continue;
192+
}
193+
final Certificate certificate = keyStore.getCertificate(alias);
194+
if (!(certificate instanceof X509Certificate)) {
195+
continue;
196+
}
197+
198+
try {
199+
((X509Certificate) certificate).checkValidity();
200+
}
201+
catch (CertificateExpiredException e) {
202+
throw new CertificateExpiredException("KeyStore certificate is expired: " + e.getMessage());
203+
}
204+
catch (CertificateNotYetValidException e) {
205+
throw new CertificateNotYetValidException("KeyStore certificate is not yet valid: " + e.getMessage());
206+
}
207+
}
208+
}
60209
}

Diff for: presto-hive-metastore/src/test/java/com/facebook/presto/hive/metastore/TestMetastoreClientConfig.java

+18-2
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,7 @@
2222
import io.airlift.units.Duration;
2323
import org.testng.annotations.Test;
2424

25+
import java.io.File;
2526
import java.util.Map;
2627
import java.util.concurrent.TimeUnit;
2728

@@ -50,7 +51,12 @@ public void testDefaults()
5051
.setPartitionCacheColumnCountLimit(500)
5152
.setHiveMetastoreAuthenticationType(HiveMetastoreAuthenticationType.NONE)
5253
.setDeleteFilesOnTableDrop(false)
53-
.setInvalidateMetastoreCacheProcedureEnabled(false));
54+
.setInvalidateMetastoreCacheProcedureEnabled(false)
55+
.setMetastoreTlsEnabled(false)
56+
.setMetastoreTlsKeystorePath(null)
57+
.setMetastoreTlsKeystorePassword(null)
58+
.setMetastoreTlsTruststorePath(null)
59+
.setMetastoreTlsTruststorePassword(null));
5460
}
5561

5662
@Test
@@ -77,6 +83,11 @@ public void testExplicitPropertyMappings()
7783
.put("hive.metastore.authentication.type", "KERBEROS")
7884
.put("hive.metastore.thrift.delete-files-on-table-drop", "true")
7985
.put("hive.invalidate-metastore-cache-procedure-enabled", "true")
86+
.put("hive.metastore.thrift.client.tls.enabled", "true")
87+
.put("hive.metastore.thrift.client.tls.keystore.path", "/tmp/keystore")
88+
.put("hive.metastore.thrift.client.tls.keystore.password", "tmp-keystore-password")
89+
.put("hive.metastore.thrift.client.tls.truststore.path", "/tmp/truststore")
90+
.put("hive.metastore.thrift.client.tls.truststore.password", "tmp-truststore-password")
8091
.build();
8192

8293
MetastoreClientConfig expected = new MetastoreClientConfig()
@@ -99,7 +110,12 @@ public void testExplicitPropertyMappings()
99110
.setPartitionCacheColumnCountLimit(50)
100111
.setHiveMetastoreAuthenticationType(HiveMetastoreAuthenticationType.KERBEROS)
101112
.setDeleteFilesOnTableDrop(true)
102-
.setInvalidateMetastoreCacheProcedureEnabled(true);
113+
.setInvalidateMetastoreCacheProcedureEnabled(true)
114+
.setMetastoreTlsEnabled(true)
115+
.setMetastoreTlsKeystorePath(new File("/tmp/keystore"))
116+
.setMetastoreTlsKeystorePassword("tmp-keystore-password")
117+
.setMetastoreTlsTruststorePath(new File("/tmp/truststore"))
118+
.setMetastoreTlsTruststorePassword("tmp-truststore-password");
103119

104120
ConfigAssertions.assertFullMapping(properties, expected);
105121
}

Diff for: presto-hive/pom.xml

+16
Original file line numberDiff line numberDiff line change
@@ -697,5 +697,21 @@
697697
</plugins>
698698
</build>
699699
</profile>
700+
<profile>
701+
<id>test-ssl-enabled-hms</id>
702+
<build>
703+
<plugins>
704+
<plugin>
705+
<groupId>org.apache.maven.plugins</groupId>
706+
<artifactId>maven-surefire-plugin</artifactId>
707+
<configuration>
708+
<includes>
709+
<include>**/TestHiveSslEnableTrustStore.java</include>
710+
</includes>
711+
</configuration>
712+
</plugin>
713+
</plugins>
714+
</build>
715+
</profile>
700716
</profiles>
701717
</project>

0 commit comments

Comments
 (0)