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 = "SSL" ;
36
59
37
60
public HiveMetastoreClientFactory (
38
61
Optional <SSLContext > sslContext ,
@@ -49,12 +72,138 @@ public HiveMetastoreClientFactory(
49
72
@ Inject
50
73
public HiveMetastoreClientFactory (MetastoreClientConfig metastoreClientConfig , HiveMetastoreAuthentication metastoreAuthentication )
51
74
{
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 );
53
76
}
54
77
55
78
public HiveMetastoreClient create (HostAndPort address , Optional <String > token )
56
79
throws TTransportException
57
80
{
58
81
return new ThriftHiveMetastoreClient (Transport .create (address , sslContext , socksProxy , timeoutMillis , metastoreAuthentication , token ));
59
82
}
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
+ }
60
209
}
0 commit comments