15
15
16
16
import com .facebook .presto .hive .MetastoreClientConfig ;
17
17
import com .facebook .presto .hive .authentication .HiveMetastoreAuthentication ;
18
+ import com .facebook .presto .spi .PrestoException ;
18
19
import com .google .common .net .HostAndPort ;
20
+ import io .airlift .security .pem .PemReader ;
19
21
import io .airlift .units .Duration ;
20
22
import org .apache .thrift .transport .TTransportException ;
21
23
22
24
import javax .inject .Inject ;
25
+ import javax .net .ssl .KeyManager ;
26
+ import javax .net .ssl .KeyManagerFactory ;
23
27
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 ;
24
32
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 ;
25
45
import java .util .Optional ;
26
46
47
+ import static com .facebook .presto .hive .HiveErrorCode .HIVE_METASTORE_INITIALIZE_SSL_ERROR ;
27
48
import static java .lang .Math .toIntExact ;
49
+ import static java .util .Collections .list ;
28
50
import static java .util .Objects .requireNonNull ;
29
51
30
52
public class HiveMetastoreClientFactory
@@ -33,6 +55,7 @@ public class HiveMetastoreClientFactory
33
55
private final Optional <HostAndPort > socksProxy ;
34
56
private final int timeoutMillis ;
35
57
private final HiveMetastoreAuthentication metastoreAuthentication ;
58
+ public static final String PROTOCOL = "TLS" ;
36
59
37
60
public HiveMetastoreClientFactory (
38
61
Optional <SSLContext > sslContext ,
@@ -47,14 +70,150 @@ public HiveMetastoreClientFactory(
47
70
}
48
71
49
72
@ Inject
50
- public HiveMetastoreClientFactory (MetastoreClientConfig metastoreClientConfig , HiveMetastoreAuthentication metastoreAuthentication )
73
+ public HiveMetastoreClientFactory (MetastoreClientConfig metastoreClientConfig , ThriftHiveMetastoreConfig thriftHiveMetastoreConfig , HiveMetastoreAuthentication metastoreAuthentication )
51
74
{
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 );
53
82
}
54
83
55
84
public HiveMetastoreClient create (HostAndPort address , Optional <String > token )
56
85
throws TTransportException
57
86
{
58
87
return new ThriftHiveMetastoreClient (Transport .create (address , sslContext , socksProxy , timeoutMillis , metastoreAuthentication , token ));
59
88
}
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
+ }
60
219
}
0 commit comments