From f36b9e77b88fe4b56ef5ecdc2ff8cbfdd5169225 Mon Sep 17 00:00:00 2001 From: praveenkrishna Date: Wed, 16 Feb 2022 20:47:22 +0530 Subject: [PATCH] Extract SSLContext creation to plugin-toolkit --- lib/trino-plugin-toolkit/pom.xml | 5 + .../io/trino/plugin/base/ssl/SslUtils.java | 147 ++++++++++++++++++ .../cassandra/CassandraClientModule.java | 52 +------ plugin/trino-elasticsearch/pom.xml | 5 - .../client/ElasticsearchClient.java | 110 +------------ plugin/trino-password-authenticators/pom.xml | 10 +- .../ldap/JdkLdapAuthenticatorClient.java | 21 +-- 7 files changed, 163 insertions(+), 187 deletions(-) create mode 100644 lib/trino-plugin-toolkit/src/main/java/io/trino/plugin/base/ssl/SslUtils.java diff --git a/lib/trino-plugin-toolkit/pom.xml b/lib/trino-plugin-toolkit/pom.xml index ecbfff25bff8..6c2370ec917b 100644 --- a/lib/trino-plugin-toolkit/pom.xml +++ b/lib/trino-plugin-toolkit/pom.xml @@ -48,6 +48,11 @@ parameternames + + io.airlift + security + + io.airlift slice diff --git a/lib/trino-plugin-toolkit/src/main/java/io/trino/plugin/base/ssl/SslUtils.java b/lib/trino-plugin-toolkit/src/main/java/io/trino/plugin/base/ssl/SslUtils.java new file mode 100644 index 000000000000..4b3f5abfe725 --- /dev/null +++ b/lib/trino-plugin-toolkit/src/main/java/io/trino/plugin/base/ssl/SslUtils.java @@ -0,0 +1,147 @@ +/* + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package io.trino.plugin.base.ssl; + +import io.airlift.security.pem.PemReader; + +import javax.net.ssl.KeyManager; +import javax.net.ssl.KeyManagerFactory; +import javax.net.ssl.SSLContext; +import javax.net.ssl.TrustManager; +import javax.net.ssl.TrustManagerFactory; +import javax.net.ssl.X509TrustManager; +import javax.security.auth.x500.X500Principal; + +import java.io.File; +import java.io.FileInputStream; +import java.io.IOException; +import java.io.InputStream; +import java.security.GeneralSecurityException; +import java.security.KeyStore; +import java.security.cert.Certificate; +import java.security.cert.CertificateExpiredException; +import java.security.cert.CertificateNotYetValidException; +import java.security.cert.X509Certificate; +import java.util.Arrays; +import java.util.List; +import java.util.Optional; + +import static java.util.Collections.list; + +public final class SslUtils +{ + private SslUtils() {} + + public static SSLContext createSSLContext( + Optional keyStorePath, + Optional keyStorePassword, + Optional trustStorePath, + Optional trustStorePassword) + throws GeneralSecurityException, IOException + { + // load KeyStore if configured and get KeyManagers + KeyStore keyStore = null; + KeyManager[] keyManagers = null; + if (keyStorePath.isPresent()) { + char[] keyManagerPassword; + try { + // attempt to read the key store as a PEM file + keyStore = PemReader.loadKeyStore(keyStorePath.get(), keyStorePath.get(), keyStorePassword); + // for PEM encoded keys, the password is used to decrypt the specific key (and does not protect the keystore itself) + keyManagerPassword = new char[0]; + } + catch (IOException | GeneralSecurityException ignored) { + keyManagerPassword = keyStorePassword.map(String::toCharArray).orElse(null); + + keyStore = KeyStore.getInstance(KeyStore.getDefaultType()); + try (InputStream in = new FileInputStream(keyStorePath.get())) { + keyStore.load(in, keyManagerPassword); + } + } + validateCertificates(keyStore); + KeyManagerFactory keyManagerFactory = KeyManagerFactory.getInstance(KeyManagerFactory.getDefaultAlgorithm()); + keyManagerFactory.init(keyStore, keyManagerPassword); + keyManagers = keyManagerFactory.getKeyManagers(); + } + + // load TrustStore if configured, otherwise use KeyStore + KeyStore trustStore = keyStore; + if (trustStorePath.isPresent()) { + trustStore = loadTrustStore(trustStorePath.get(), trustStorePassword); + } + + // create TrustManagerFactory + TrustManagerFactory trustManagerFactory = TrustManagerFactory.getInstance(TrustManagerFactory.getDefaultAlgorithm()); + trustManagerFactory.init(trustStore); + + // get X509TrustManager + TrustManager[] trustManagers = trustManagerFactory.getTrustManagers(); + if (trustManagers.length != 1 || !(trustManagers[0] instanceof X509TrustManager)) { + throw new RuntimeException("Unexpected default trust managers:" + Arrays.toString(trustManagers)); + } + // create SSLContext + SSLContext result = SSLContext.getInstance("SSL"); + result.init(keyManagers, trustManagers, null); + return result; + } + + private static KeyStore loadTrustStore(File trustStorePath, Optional trustStorePassword) + throws IOException, GeneralSecurityException + { + KeyStore trustStore = KeyStore.getInstance(KeyStore.getDefaultType()); + try { + // attempt to read the trust store as a PEM file + List certificateChain = PemReader.readCertificateChain(trustStorePath); + if (!certificateChain.isEmpty()) { + trustStore.load(null, null); + for (X509Certificate certificate : certificateChain) { + X500Principal principal = certificate.getSubjectX500Principal(); + trustStore.setCertificateEntry(principal.getName(), certificate); + } + return trustStore; + } + } + catch (IOException | GeneralSecurityException ignored) { + } + + try (InputStream in = new FileInputStream(trustStorePath)) { + trustStore.load(in, trustStorePassword.map(String::toCharArray).orElse(null)); + } + return trustStore; + } + + private static void validateCertificates(KeyStore keyStore) + throws GeneralSecurityException + { + for (String alias : list(keyStore.aliases())) { + if (!keyStore.isKeyEntry(alias)) { + continue; + } + Certificate certificate = keyStore.getCertificate(alias); + if (!(certificate instanceof X509Certificate)) { + continue; + } + + try { + ((X509Certificate) certificate).checkValidity(); + } + catch (CertificateExpiredException e) { + throw new CertificateExpiredException("KeyStore certificate is expired: " + e.getMessage()); + } + catch (CertificateNotYetValidException e) { + throw new CertificateNotYetValidException("KeyStore certificate is not yet valid: " + e.getMessage()); + } + } + } +} diff --git a/plugin/trino-cassandra/src/main/java/io/trino/plugin/cassandra/CassandraClientModule.java b/plugin/trino-cassandra/src/main/java/io/trino/plugin/cassandra/CassandraClientModule.java index 55c87e51f2a2..ce8709dc0146 100644 --- a/plugin/trino-cassandra/src/main/java/io/trino/plugin/cassandra/CassandraClientModule.java +++ b/plugin/trino-cassandra/src/main/java/io/trino/plugin/cassandra/CassandraClientModule.java @@ -39,12 +39,7 @@ import javax.inject.Inject; import javax.inject.Singleton; -import javax.net.ssl.KeyManager; -import javax.net.ssl.KeyManagerFactory; import javax.net.ssl.SSLContext; -import javax.net.ssl.TrustManager; -import javax.net.ssl.TrustManagerFactory; -import javax.net.ssl.X509TrustManager; import javax.security.auth.x500.X500Principal; import java.io.File; @@ -59,7 +54,6 @@ import java.security.cert.CertificateNotYetValidException; import java.security.cert.X509Certificate; import java.util.ArrayList; -import java.util.Arrays; import java.util.List; import java.util.Optional; @@ -67,6 +61,7 @@ import static io.airlift.configuration.ConfigBinder.configBinder; import static io.airlift.json.JsonBinder.jsonBinder; import static io.airlift.json.JsonCodecBinder.jsonCodecBinder; +import static io.trino.plugin.base.ssl.SslUtils.createSSLContext; import static io.trino.plugin.cassandra.CassandraErrorCode.CASSANDRA_SSL_INITIALIZATION_FAILURE; import static java.lang.Math.toIntExact; import static java.util.Collections.list; @@ -216,50 +211,7 @@ private static Optional buildSslContext( } try { - // load KeyStore if configured and get KeyManagers - KeyStore keystore = null; - KeyManager[] keyManagers = null; - if (keystorePath.isPresent()) { - char[] keyManagerPassword; - try { - // attempt to read the key store as a PEM file - keystore = PemReader.loadKeyStore(keystorePath.get(), keystorePath.get(), keystorePassword); - // for PEM encoded keys, the password is used to decrypt the specific key (and does not protect the keystore itself) - keyManagerPassword = new char[0]; - } - catch (IOException | GeneralSecurityException ignored) { - keyManagerPassword = keystorePassword.map(String::toCharArray).orElse(null); - - keystore = KeyStore.getInstance(KeyStore.getDefaultType()); - try (InputStream in = new FileInputStream(keystorePath.get())) { - keystore.load(in, keyManagerPassword); - } - } - validateCertificates(keystore); - KeyManagerFactory keyManagerFactory = KeyManagerFactory.getInstance(KeyManagerFactory.getDefaultAlgorithm()); - keyManagerFactory.init(keystore, keyManagerPassword); - keyManagers = keyManagerFactory.getKeyManagers(); - } - - // load TrustStore if configured, otherwise use KeyStore - KeyStore truststore = keystore; - if (truststorePath.isPresent()) { - truststore = loadTrustStore(truststorePath.get(), truststorePassword); - } - - // create TrustManagerFactory - TrustManagerFactory trustManagerFactory = TrustManagerFactory.getInstance(TrustManagerFactory.getDefaultAlgorithm()); - trustManagerFactory.init(truststore); - - // get X509TrustManager - TrustManager[] trustManagers = trustManagerFactory.getTrustManagers(); - if (trustManagers.length != 1 || !(trustManagers[0] instanceof X509TrustManager)) { - throw new RuntimeException("Unexpected default trust managers:" + Arrays.toString(trustManagers)); - } - // create SSLContext - SSLContext result = SSLContext.getInstance("SSL"); - result.init(keyManagers, trustManagers, null); - return Optional.of(result); + return Optional.of(createSSLContext(keystorePath, keystorePassword, truststorePath, truststorePassword)); } catch (GeneralSecurityException | IOException e) { throw new TrinoException(CASSANDRA_SSL_INITIALIZATION_FAILURE, e); diff --git a/plugin/trino-elasticsearch/pom.xml b/plugin/trino-elasticsearch/pom.xml index b5e992f7a650..6d01f6ccf506 100644 --- a/plugin/trino-elasticsearch/pom.xml +++ b/plugin/trino-elasticsearch/pom.xml @@ -49,11 +49,6 @@ log - - io.airlift - security - - io.airlift stats diff --git a/plugin/trino-elasticsearch/src/main/java/io/trino/plugin/elasticsearch/client/ElasticsearchClient.java b/plugin/trino-elasticsearch/src/main/java/io/trino/plugin/elasticsearch/client/ElasticsearchClient.java index a7ec98250a47..3364d6614202 100644 --- a/plugin/trino-elasticsearch/src/main/java/io/trino/plugin/elasticsearch/client/ElasticsearchClient.java +++ b/plugin/trino-elasticsearch/src/main/java/io/trino/plugin/elasticsearch/client/ElasticsearchClient.java @@ -30,7 +30,6 @@ import io.airlift.json.JsonCodec; import io.airlift.json.ObjectMapperProvider; import io.airlift.log.Logger; -import io.airlift.security.pem.PemReader; import io.airlift.stats.TimeStat; import io.airlift.units.Duration; import io.trino.plugin.elasticsearch.AwsSecurityConfig; @@ -69,24 +68,11 @@ import javax.annotation.PostConstruct; import javax.annotation.PreDestroy; import javax.inject.Inject; -import javax.net.ssl.KeyManager; -import javax.net.ssl.KeyManagerFactory; import javax.net.ssl.SSLContext; -import javax.net.ssl.TrustManager; -import javax.net.ssl.TrustManagerFactory; -import javax.net.ssl.X509TrustManager; -import javax.security.auth.x500.X500Principal; import java.io.File; -import java.io.FileInputStream; import java.io.IOException; -import java.io.InputStream; import java.security.GeneralSecurityException; -import java.security.KeyStore; -import java.security.cert.Certificate; -import java.security.cert.CertificateExpiredException; -import java.security.cert.CertificateNotYetValidException; -import java.security.cert.X509Certificate; import java.util.Arrays; import java.util.Iterator; import java.util.List; @@ -105,6 +91,7 @@ import static com.google.common.collect.ImmutableMap.toImmutableMap; import static io.airlift.concurrent.Threads.daemonThreadsNamed; import static io.airlift.json.JsonCodec.jsonCodec; +import static io.trino.plugin.base.ssl.SslUtils.createSSLContext; import static io.trino.plugin.elasticsearch.ElasticsearchErrorCode.ELASTICSEARCH_CONNECTION_ERROR; import static io.trino.plugin.elasticsearch.ElasticsearchErrorCode.ELASTICSEARCH_INVALID_METADATA; import static io.trino.plugin.elasticsearch.ElasticsearchErrorCode.ELASTICSEARCH_INVALID_RESPONSE; @@ -113,7 +100,6 @@ import static java.lang.StrictMath.toIntExact; import static java.lang.String.format; import static java.nio.charset.StandardCharsets.UTF_8; -import static java.util.Collections.list; import static java.util.Objects.requireNonNull; import static java.util.concurrent.Executors.newSingleThreadScheduledExecutor; import static java.util.concurrent.TimeUnit.MILLISECONDS; @@ -295,105 +281,13 @@ private static Optional buildSslContext( } try { - // load KeyStore if configured and get KeyManagers - KeyStore keyStore = null; - KeyManager[] keyManagers = null; - if (keyStorePath.isPresent()) { - char[] keyManagerPassword; - try { - // attempt to read the key store as a PEM file - keyStore = PemReader.loadKeyStore(keyStorePath.get(), keyStorePath.get(), keyStorePassword); - // for PEM encoded keys, the password is used to decrypt the specific key (and does not protect the keystore itself) - keyManagerPassword = new char[0]; - } - catch (IOException | GeneralSecurityException ignored) { - keyManagerPassword = keyStorePassword.map(String::toCharArray).orElse(null); - - keyStore = KeyStore.getInstance(KeyStore.getDefaultType()); - try (InputStream in = new FileInputStream(keyStorePath.get())) { - keyStore.load(in, keyManagerPassword); - } - } - validateCertificates(keyStore); - KeyManagerFactory keyManagerFactory = KeyManagerFactory.getInstance(KeyManagerFactory.getDefaultAlgorithm()); - keyManagerFactory.init(keyStore, keyManagerPassword); - keyManagers = keyManagerFactory.getKeyManagers(); - } - - // load TrustStore if configured, otherwise use KeyStore - KeyStore trustStore = keyStore; - if (trustStorePath.isPresent()) { - trustStore = loadTrustStore(trustStorePath.get(), trustStorePassword); - } - - // create TrustManagerFactory - TrustManagerFactory trustManagerFactory = TrustManagerFactory.getInstance(TrustManagerFactory.getDefaultAlgorithm()); - trustManagerFactory.init(trustStore); - - // get X509TrustManager - TrustManager[] trustManagers = trustManagerFactory.getTrustManagers(); - if (trustManagers.length != 1 || !(trustManagers[0] instanceof X509TrustManager)) { - throw new RuntimeException("Unexpected default trust managers:" + Arrays.toString(trustManagers)); - } - // create SSLContext - SSLContext result = SSLContext.getInstance("SSL"); - result.init(keyManagers, trustManagers, null); - return Optional.of(result); + return Optional.of(createSSLContext(keyStorePath, keyStorePassword, trustStorePath, trustStorePassword)); } catch (GeneralSecurityException | IOException e) { throw new TrinoException(ELASTICSEARCH_SSL_INITIALIZATION_FAILURE, e); } } - private static KeyStore loadTrustStore(File trustStorePath, Optional trustStorePassword) - throws IOException, GeneralSecurityException - { - KeyStore trustStore = KeyStore.getInstance(KeyStore.getDefaultType()); - try { - // attempt to read the trust store as a PEM file - List certificateChain = PemReader.readCertificateChain(trustStorePath); - if (!certificateChain.isEmpty()) { - trustStore.load(null, null); - for (X509Certificate certificate : certificateChain) { - X500Principal principal = certificate.getSubjectX500Principal(); - trustStore.setCertificateEntry(principal.getName(), certificate); - } - return trustStore; - } - } - catch (IOException | GeneralSecurityException ignored) { - } - - try (InputStream in = new FileInputStream(trustStorePath)) { - trustStore.load(in, trustStorePassword.map(String::toCharArray).orElse(null)); - } - return trustStore; - } - - private static void validateCertificates(KeyStore keyStore) - throws GeneralSecurityException - { - for (String alias : list(keyStore.aliases())) { - if (!keyStore.isKeyEntry(alias)) { - continue; - } - Certificate certificate = keyStore.getCertificate(alias); - if (!(certificate instanceof X509Certificate)) { - continue; - } - - try { - ((X509Certificate) certificate).checkValidity(); - } - catch (CertificateExpiredException e) { - throw new CertificateExpiredException("KeyStore certificate is expired: " + e.getMessage()); - } - catch (CertificateNotYetValidException e) { - throw new CertificateNotYetValidException("KeyStore certificate is not yet valid: " + e.getMessage()); - } - } - } - private Set fetchNodes() { NodesResponse nodesResponse = doRequest("/_nodes/http", NODES_RESPONSE_CODEC::fromJson); diff --git a/plugin/trino-password-authenticators/pom.xml b/plugin/trino-password-authenticators/pom.xml index 914cc72473f4..aa7351d805cf 100644 --- a/plugin/trino-password-authenticators/pom.xml +++ b/plugin/trino-password-authenticators/pom.xml @@ -23,6 +23,11 @@ trino-collect + + io.trino + trino-plugin-toolkit + + io.airlift bootstrap @@ -43,11 +48,6 @@ log - - io.airlift - security - - io.airlift units diff --git a/plugin/trino-password-authenticators/src/main/java/io/trino/plugin/password/ldap/JdkLdapAuthenticatorClient.java b/plugin/trino-password-authenticators/src/main/java/io/trino/plugin/password/ldap/JdkLdapAuthenticatorClient.java index e73350d8ea73..6bfe33208fad 100644 --- a/plugin/trino-password-authenticators/src/main/java/io/trino/plugin/password/ldap/JdkLdapAuthenticatorClient.java +++ b/plugin/trino-password-authenticators/src/main/java/io/trino/plugin/password/ldap/JdkLdapAuthenticatorClient.java @@ -16,8 +16,8 @@ import com.google.common.collect.ImmutableMap; import com.google.common.collect.ImmutableSet; import io.airlift.log.Logger; -import io.airlift.security.pem.PemReader; import io.airlift.units.Duration; +import io.trino.plugin.base.ssl.SslUtils; import io.trino.spi.security.AccessDeniedException; import javax.inject.Inject; @@ -28,15 +28,10 @@ import javax.naming.directory.SearchControls; import javax.naming.directory.SearchResult; import javax.net.ssl.SSLContext; -import javax.net.ssl.TrustManager; -import javax.net.ssl.TrustManagerFactory; -import javax.net.ssl.X509TrustManager; import java.io.File; import java.io.IOException; import java.security.GeneralSecurityException; -import java.security.KeyStore; -import java.util.Arrays; import java.util.Map; import java.util.Optional; import java.util.Set; @@ -165,19 +160,7 @@ private Map createEnvironment(String userDistinguishedName, Stri private static SSLContext createSslContext(File trustCertificate) { try { - KeyStore trustStore = PemReader.loadTrustStore(trustCertificate); - - TrustManagerFactory trustManagerFactory = TrustManagerFactory.getInstance(TrustManagerFactory.getDefaultAlgorithm()); - trustManagerFactory.init(trustStore); - - TrustManager[] trustManagers = trustManagerFactory.getTrustManagers(); - if (trustManagers.length != 1 || !(trustManagers[0] instanceof X509TrustManager)) { - throw new RuntimeException("Unexpected default trust managers:" + Arrays.toString(trustManagers)); - } - - SSLContext sslContext = SSLContext.getInstance("SSL"); - sslContext.init(null, trustManagers, null); - return sslContext; + return SslUtils.createSSLContext(Optional.empty(), Optional.empty(), Optional.of(trustCertificate), Optional.empty()); } catch (GeneralSecurityException | IOException e) { throw new RuntimeException(e);