Skip to content

Commit 832aefb

Browse files
bibith4yingsu00
authored andcommitted
Changes to enable ssl/tls in hms
Co-authored-by: Arin Mathew <[email protected]> Changes to move ssl related properties to seperate class
1 parent ab03978 commit 832aefb

File tree

27 files changed

+727
-22
lines changed

27 files changed

+727
-22
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-delta/src/main/java/com/facebook/presto/delta/DeltaModule.java

+2
Original file line numberDiff line numberDiff line change
@@ -44,6 +44,7 @@
4444
import com.facebook.presto.hive.metastore.InvalidateMetastoreCacheProcedure;
4545
import com.facebook.presto.hive.metastore.MetastoreCacheStats;
4646
import com.facebook.presto.hive.metastore.MetastoreConfig;
47+
import com.facebook.presto.hive.metastore.thrift.ThriftHiveMetastoreConfig;
4748
import com.facebook.presto.spi.procedure.Procedure;
4849
import com.fasterxml.jackson.databind.DeserializationContext;
4950
import com.fasterxml.jackson.databind.deser.std.FromStringDeserializer;
@@ -104,6 +105,7 @@ protected void setup(Binder binder)
104105
configBinder(binder).bindConfig(MetastoreConfig.class);
105106
configBinder(binder).bindConfig(HiveClientConfig.class);
106107
configBinder(binder).bindConfig(MetastoreClientConfig.class);
108+
configBinder(binder).bindConfig(ThriftHiveMetastoreConfig.class);
107109
binder.bind(MetastoreCacheStats.class).to(HiveMetastoreCacheStats.class).in(Scopes.SINGLETON);
108110
newExporter(binder).export(MetastoreCacheStats.class).as(generatedNameOf(MetastoreCacheStats.class, connectorId));
109111
binder.bind(ExtendedHiveMetastore.class).to(InMemoryCachingHiveMetastore.class).in(Scopes.SINGLETON);

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

+13-3
Original file line numberDiff line numberDiff line change
@@ -213,9 +213,9 @@ Metastore Configuration Properties
213213

214214
The required Hive metastore can be configured with a number of properties.
215215

216-
======================================================= ============================================================= ============
216+
======================================================== ============================================================= ============
217217
Property Name Description Default
218-
======================================================= ============================================================= ============
218+
======================================================== ============================================================= ============
219219
``hive.metastore-timeout`` Timeout for Hive metastore requests. ``10s``
220220

221221
``hive.metastore-cache-ttl`` Duration how long cached metastore data should be considered ``0s``
@@ -232,7 +232,17 @@ 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-
======================================================= ============================================================= ============
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+
245+
======================================================== ============================================================= ============
236246

237247
AWS Glue Catalog Configuration Properties
238248
-----------------------------------------

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-hadoop2/src/test/java/com/facebook/presto/hive/s3select/S3SelectTestHelper.java

+4-1
Original file line numberDiff line numberDiff line change
@@ -57,6 +57,7 @@
5757
import com.facebook.presto.hive.metastore.thrift.HiveCluster;
5858
import com.facebook.presto.hive.metastore.thrift.TestingHiveCluster;
5959
import com.facebook.presto.hive.metastore.thrift.ThriftHiveMetastore;
60+
import com.facebook.presto.hive.metastore.thrift.ThriftHiveMetastoreConfig;
6061
import com.facebook.presto.hive.s3.HiveS3Config;
6162
import com.facebook.presto.hive.s3.PrestoS3ConfigurationUpdater;
6263
import com.facebook.presto.hive.s3.S3ConfigurationUpdater;
@@ -110,6 +111,7 @@ public class S3SelectTestHelper
110111
private HiveClientConfig config;
111112
private CacheConfig cacheConfig;
112113
private MetastoreClientConfig metastoreClientConfig;
114+
private ThriftHiveMetastoreConfig thriftHiveMetastoreConfig;
113115

114116
public S3SelectTestHelper(String host,
115117
int port,
@@ -128,13 +130,14 @@ public S3SelectTestHelper(String host,
128130
config = hiveClientConfig;
129131
cacheConfig = new CacheConfig();
130132
metastoreClientConfig = new MetastoreClientConfig();
133+
thriftHiveMetastoreConfig = new ThriftHiveMetastoreConfig();
131134

132135
String proxy = System.getProperty("hive.metastore.thrift.client.socks-proxy");
133136
if (proxy != null) {
134137
metastoreClientConfig.setMetastoreSocksProxy(HostAndPort.fromString(proxy));
135138
}
136139

137-
HiveCluster hiveCluster = new TestingHiveCluster(metastoreClientConfig, host, port);
140+
HiveCluster hiveCluster = new TestingHiveCluster(metastoreClientConfig, thriftHiveMetastoreConfig, host, port);
138141
executor = newCachedThreadPool(daemonThreadsNamed("hive-%s"));
139142
HivePartitionManager hivePartitionManager = new HivePartitionManager(FUNCTION_AND_TYPE_MANAGER, config);
140143

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/MetastoreClientModule.java

+2
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@
1313
*/
1414
package com.facebook.presto.hive;
1515

16+
import com.facebook.presto.hive.metastore.thrift.ThriftHiveMetastoreConfig;
1617
import com.google.inject.Binder;
1718
import com.google.inject.Module;
1819

@@ -29,5 +30,6 @@ public MetastoreClientModule()
2930
public void configure(Binder binder)
3031
{
3132
configBinder(binder).bindConfig(MetastoreClientConfig.class);
33+
configBinder(binder).bindConfig(ThriftHiveMetastoreConfig.class);
3234
}
3335
}

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

+161-2
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 = "TLS";
3659

3760
public HiveMetastoreClientFactory(
3861
Optional<SSLContext> sslContext,
@@ -47,14 +70,150 @@ public HiveMetastoreClientFactory(
4770
}
4871

4972
@Inject
50-
public HiveMetastoreClientFactory(MetastoreClientConfig metastoreClientConfig, HiveMetastoreAuthentication metastoreAuthentication)
73+
public HiveMetastoreClientFactory(MetastoreClientConfig metastoreClientConfig, ThriftHiveMetastoreConfig thriftHiveMetastoreConfig, HiveMetastoreAuthentication metastoreAuthentication)
5174
{
52-
this(Optional.empty(), Optional.ofNullable(metastoreClientConfig.getMetastoreSocksProxy()), metastoreClientConfig.getMetastoreTimeout(), metastoreAuthentication);
75+
this(buildSslContext(thriftHiveMetastoreConfig.isTlsEnabled(),
76+
Optional.ofNullable(thriftHiveMetastoreConfig.getKeystorePath()),
77+
Optional.ofNullable(thriftHiveMetastoreConfig.getKeystorePassword()),
78+
Optional.ofNullable(thriftHiveMetastoreConfig.getTruststorePath()),
79+
Optional.ofNullable(thriftHiveMetastoreConfig.getTrustStorePassword())),
80+
Optional.ofNullable(metastoreClientConfig.getMetastoreSocksProxy()),
81+
metastoreClientConfig.getMetastoreTimeout(), metastoreAuthentication);
5382
}
5483

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

0 commit comments

Comments
 (0)