This is an automated email from the ASF dual-hosted git repository. kharekartik pushed a commit to branch master in repository https://gitbox.apache.org/repos/asf/pinot.git
The following commit(s) were added to refs/heads/master by this push: new 23a81d07b5 Add TLS configuration to JDBC driver (#8578) 23a81d07b5 is described below commit 23a81d07b52dad6181b696562e07cdfa0932d191 Author: Kartik Khare <kharekar...@gmail.com> AuthorDate: Wed May 11 20:09:23 2022 +0530 Add TLS configuration to JDBC driver (#8578) * Add TLS configuration to JDBC driver * Add TLS tests for JDBC * use TLSUtil properties parser instead of JDBC utils Co-authored-by: Kartik Khare <kharekartik@Kartiks-MacBook-Pro.local> --- pinot-clients/pinot-jdbc-client/pom.xml | 4 ++ .../java/org/apache/pinot/client/PinotDriver.java | 35 ++++++++++--- .../controller/PinotControllerTransport.java | 31 ++++++++++-- .../PinotControllerTransportFactory.java | 59 ++++++++++++++++++++++ .../org/apache/pinot/client/utils/DriverUtils.java | 19 +++++-- pinot-integration-tests/pom.xml | 4 ++ .../integration/tests/TlsIntegrationTest.java | 51 +++++++++++++++++++ pom.xml | 5 ++ 8 files changed, 195 insertions(+), 13 deletions(-) diff --git a/pinot-clients/pinot-jdbc-client/pom.xml b/pinot-clients/pinot-jdbc-client/pom.xml index 1bb924a789..b61deab966 100644 --- a/pinot-clients/pinot-jdbc-client/pom.xml +++ b/pinot-clients/pinot-jdbc-client/pom.xml @@ -65,6 +65,10 @@ <artifactId>pinot-java-client</artifactId> <version>${project.version}</version> </dependency> + <dependency> + <groupId>org.apache.pinot</groupId> + <artifactId>pinot-common</artifactId> + </dependency> <dependency> <groupId>org.testng</groupId> <artifactId>testng</artifactId> diff --git a/pinot-clients/pinot-jdbc-client/src/main/java/org/apache/pinot/client/PinotDriver.java b/pinot-clients/pinot-jdbc-client/src/main/java/org/apache/pinot/client/PinotDriver.java index 8dfc88b77e..4055d788d3 100644 --- a/pinot-clients/pinot-jdbc-client/src/main/java/org/apache/pinot/client/PinotDriver.java +++ b/pinot-clients/pinot-jdbc-client/src/main/java/org/apache/pinot/client/PinotDriver.java @@ -18,6 +18,7 @@ */ package org.apache.pinot.client; +import com.google.common.annotations.VisibleForTesting; import com.google.common.collect.Iterables; import java.net.URI; import java.sql.Connection; @@ -32,9 +33,13 @@ import java.util.Map; import java.util.Properties; import java.util.logging.Logger; import java.util.stream.Collectors; +import javax.net.ssl.SSLContext; import org.apache.commons.lang3.tuple.Pair; import org.apache.pinot.client.controller.PinotControllerTransport; +import org.apache.pinot.client.controller.PinotControllerTransportFactory; import org.apache.pinot.client.utils.DriverUtils; +import org.apache.pinot.common.utils.TlsUtils; +import org.apache.pinot.spi.utils.CommonConstants; import org.slf4j.LoggerFactory; @@ -45,6 +50,14 @@ public class PinotDriver implements Driver { public static final String DEFAULT_TENANT = "DefaultTenant"; public static final String INFO_SCHEME = "scheme"; public static final String INFO_HEADERS = "headers"; + private SSLContext _sslContext = null; + + public PinotDriver() { } + + @VisibleForTesting + public PinotDriver(SSLContext sslContext) { + _sslContext = sslContext; + } @Override public Connection connect(String url, Properties info) @@ -52,9 +65,20 @@ public class PinotDriver implements Driver { try { LOGGER.info("Initiating connection to database for url: " + url); JsonAsyncHttpPinotClientTransportFactory factory = new JsonAsyncHttpPinotClientTransportFactory(); + PinotControllerTransportFactory pinotControllerTransportFactory = new PinotControllerTransportFactory(); - if (info.contains(INFO_SCHEME)) { + if (info.containsKey(INFO_SCHEME)) { factory.setScheme(info.getProperty(INFO_SCHEME)); + pinotControllerTransportFactory.setScheme(info.getProperty(INFO_SCHEME)); + if (info.getProperty(INFO_SCHEME).contentEquals(CommonConstants.HTTPS_PROTOCOL)) { + if (_sslContext == null) { + factory.setSslContext(DriverUtils.getSSLContextFromJDBCProps(info)); + pinotControllerTransportFactory.setSslContext(TlsUtils.getSslContext()); + } else { + factory.setSslContext(_sslContext); + pinotControllerTransportFactory.setSslContext(_sslContext); + } + } } Map<String, String> headers = @@ -62,18 +86,17 @@ public class PinotDriver implements Driver { entry -> Pair .of(entry.getKey().toString().substring(INFO_HEADERS.length() + 1), entry.getValue().toString())) .collect(Collectors.toMap(Pair::getKey, Pair::getValue)); + if (!headers.isEmpty()) { factory.setHeaders(headers); + pinotControllerTransportFactory.setHeaders(headers); } PinotClientTransport pinotClientTransport = factory.buildTransport(); + PinotControllerTransport pinotControllerTransport = pinotControllerTransportFactory.buildTransport(); String controllerUrl = DriverUtils.getControllerFromURL(url); String tenant = info.getProperty(INFO_TENANT, DEFAULT_TENANT); - if (!headers.isEmpty()) { - PinotControllerTransport pinotControllerTransport = new PinotControllerTransport(headers); - return new PinotConnection(info, controllerUrl, pinotClientTransport, tenant, pinotControllerTransport); - } - return new PinotConnection(info, controllerUrl, pinotClientTransport, tenant); + return new PinotConnection(info, controllerUrl, pinotClientTransport, tenant, pinotControllerTransport); } catch (Exception e) { throw new SQLException(String.format("Failed to connect to url : %s", url), e); } diff --git a/pinot-clients/pinot-jdbc-client/src/main/java/org/apache/pinot/client/controller/PinotControllerTransport.java b/pinot-clients/pinot-jdbc-client/src/main/java/org/apache/pinot/client/controller/PinotControllerTransport.java index ac6860ce46..d0df4e2116 100644 --- a/pinot-clients/pinot-jdbc-client/src/main/java/org/apache/pinot/client/controller/PinotControllerTransport.java +++ b/pinot-clients/pinot-jdbc-client/src/main/java/org/apache/pinot/client/controller/PinotControllerTransport.java @@ -18,16 +18,23 @@ */ package org.apache.pinot.client.controller; +import io.netty.handler.ssl.ClientAuth; +import io.netty.handler.ssl.JdkSslContext; import java.io.IOException; +import java.util.Collections; import java.util.Map; import java.util.concurrent.ExecutionException; import java.util.concurrent.Future; +import javax.annotation.Nullable; +import javax.net.ssl.SSLContext; import org.apache.pinot.client.PinotClientException; import org.apache.pinot.client.controller.response.ControllerTenantBrokerResponse; import org.apache.pinot.client.controller.response.SchemaResponse; import org.apache.pinot.client.controller.response.TableResponse; +import org.apache.pinot.spi.utils.CommonConstants; import org.asynchttpclient.AsyncHttpClient; import org.asynchttpclient.BoundRequestBuilder; +import org.asynchttpclient.DefaultAsyncHttpClientConfig; import org.asynchttpclient.Dsl; import org.asynchttpclient.Response; import org.slf4j.Logger; @@ -38,19 +45,35 @@ public class PinotControllerTransport { private static final Logger LOGGER = LoggerFactory.getLogger(PinotControllerTransport.class); - AsyncHttpClient _httpClient = Dsl.asyncHttpClient(); Map<String, String> _headers; + private final String _scheme; + private final AsyncHttpClient _httpClient; + public PinotControllerTransport() { + this(Collections.emptyMap(), CommonConstants.HTTP_PROTOCOL, null); } public PinotControllerTransport(Map<String, String> headers) { + this(headers, CommonConstants.HTTP_PROTOCOL, null); + } + + public PinotControllerTransport(Map<String, String> headers, String scheme, + @Nullable SSLContext sslContext) { _headers = headers; + _scheme = scheme; + + DefaultAsyncHttpClientConfig.Builder builder = Dsl.config(); + if (sslContext != null) { + builder.setSslContext(new JdkSslContext(sslContext, true, ClientAuth.OPTIONAL)); + } + + _httpClient = Dsl.asyncHttpClient(builder.build()); } public TableResponse getAllTables(String controllerAddress) { try { - String url = "http://" + controllerAddress + "/tables"; + String url = _scheme + "://" + controllerAddress + "/tables"; BoundRequestBuilder requestBuilder = _httpClient.prepareGet(url); if (_headers != null) { _headers.forEach((k, v) -> requestBuilder.addHeader(k, v)); @@ -68,7 +91,7 @@ public class PinotControllerTransport { public SchemaResponse getTableSchema(String table, String controllerAddress) { try { - String url = "http://" + controllerAddress + "/tables/" + table + "/schema"; + String url = _scheme + "://" + controllerAddress + "/tables/" + table + "/schema"; BoundRequestBuilder requestBuilder = _httpClient.prepareGet(url); if (_headers != null) { _headers.forEach((k, v) -> requestBuilder.addHeader(k, v)); @@ -86,7 +109,7 @@ public class PinotControllerTransport { public ControllerTenantBrokerResponse getBrokersFromController(String controllerAddress, String tenant) { try { - String url = "http://" + controllerAddress + "/v2/brokers/tenants/" + tenant; + String url = _scheme + "://" + controllerAddress + "/v2/brokers/tenants/" + tenant; BoundRequestBuilder requestBuilder = _httpClient.prepareGet(url); if (_headers != null) { _headers.forEach((k, v) -> requestBuilder.addHeader(k, v)); diff --git a/pinot-clients/pinot-jdbc-client/src/main/java/org/apache/pinot/client/controller/PinotControllerTransportFactory.java b/pinot-clients/pinot-jdbc-client/src/main/java/org/apache/pinot/client/controller/PinotControllerTransportFactory.java new file mode 100644 index 0000000000..ead3ee637f --- /dev/null +++ b/pinot-clients/pinot-jdbc-client/src/main/java/org/apache/pinot/client/controller/PinotControllerTransportFactory.java @@ -0,0 +1,59 @@ +/** + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you 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 org.apache.pinot.client.controller; + +import java.util.HashMap; +import java.util.Map; +import javax.net.ssl.SSLContext; +import org.apache.pinot.spi.utils.CommonConstants; + + +public class PinotControllerTransportFactory { + private Map<String, String> _headers = new HashMap<>(); + private String _scheme = CommonConstants.HTTP_PROTOCOL; + private SSLContext _sslContext = null; + + public PinotControllerTransport buildTransport() { + return new PinotControllerTransport(_headers, _scheme, _sslContext); + } + + public Map<String, String> getHeaders() { + return _headers; + } + + public void setHeaders(Map<String, String> headers) { + _headers = headers; + } + + public String getScheme() { + return _scheme; + } + + public void setScheme(String scheme) { + _scheme = scheme; + } + + public SSLContext getSslContext() { + return _sslContext; + } + + public void setSslContext(SSLContext sslContext) { + _sslContext = sslContext; + } +} diff --git a/pinot-clients/pinot-jdbc-client/src/main/java/org/apache/pinot/client/utils/DriverUtils.java b/pinot-clients/pinot-jdbc-client/src/main/java/org/apache/pinot/client/utils/DriverUtils.java index 3f68460aaa..710a9d1725 100644 --- a/pinot-clients/pinot-jdbc-client/src/main/java/org/apache/pinot/client/utils/DriverUtils.java +++ b/pinot-clients/pinot-jdbc-client/src/main/java/org/apache/pinot/client/utils/DriverUtils.java @@ -24,8 +24,14 @@ import java.sql.Timestamp; import java.sql.Types; import java.util.Collections; import java.util.List; +import java.util.Properties; import java.util.regex.Matcher; import java.util.regex.Pattern; +import javax.net.ssl.SSLContext; +import org.apache.commons.configuration.MapConfiguration; +import org.apache.pinot.common.config.TlsConfig; +import org.apache.pinot.common.utils.TlsUtils; +import org.apache.pinot.spi.env.PinotConfiguration; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -34,14 +40,21 @@ public class DriverUtils { public static final String SCHEME = "jdbc"; public static final String DRIVER = "pinot"; public static final Logger LOG = LoggerFactory.getLogger(DriverUtils.class); - public static final String QUERY_SEPERATOR = "&"; - public static final String PARAM_SEPERATOR = "="; - public static final String CONTROLLER = "controller"; private static final String LIMIT_STATEMENT_REGEX = "\\s(limit)\\s"; + // SSL Properties + public static final String PINOT_JDBC_TLS_PREFIX = "pinot.jdbc.tls"; + private DriverUtils() { } + public static SSLContext getSSLContextFromJDBCProps(Properties properties) { + TlsConfig tlsConfig = TlsUtils.extractTlsConfig( + new PinotConfiguration(new MapConfiguration(properties)), PINOT_JDBC_TLS_PREFIX); + TlsUtils.installDefaultSSLSocketFactory(tlsConfig); + return TlsUtils.getSslContext(); + } + public static List<String> getBrokersFromURL(String url) { if (url.toLowerCase().startsWith("jdbc:")) { url = url.substring(5); diff --git a/pinot-integration-tests/pom.xml b/pinot-integration-tests/pom.xml index a688371d59..37e4375ea0 100644 --- a/pinot-integration-tests/pom.xml +++ b/pinot-integration-tests/pom.xml @@ -193,6 +193,10 @@ <groupId>org.apache.pinot</groupId> <artifactId>pinot-java-client</artifactId> </dependency> + <dependency> + <groupId>org.apache.pinot</groupId> + <artifactId>pinot-jdbc-client</artifactId> + </dependency> <dependency> <groupId>org.apache.pinot</groupId> <artifactId>pinot-server</artifactId> diff --git a/pinot-integration-tests/src/test/java/org/apache/pinot/integration/tests/TlsIntegrationTest.java b/pinot-integration-tests/src/test/java/org/apache/pinot/integration/tests/TlsIntegrationTest.java index bbc86c2e5d..22ed5fc4ac 100644 --- a/pinot-integration-tests/src/test/java/org/apache/pinot/integration/tests/TlsIntegrationTest.java +++ b/pinot-integration-tests/src/test/java/org/apache/pinot/integration/tests/TlsIntegrationTest.java @@ -24,10 +24,13 @@ import java.io.File; import java.io.IOException; import java.io.UnsupportedEncodingException; import java.net.URL; +import java.sql.ResultSet; +import java.sql.Statement; import java.util.Collections; import java.util.HashMap; import java.util.List; import java.util.Map; +import java.util.Properties; import java.util.stream.Collectors; import org.apache.commons.configuration.ConfigurationException; import org.apache.commons.io.FileUtils; @@ -44,6 +47,7 @@ import org.apache.http.ssl.SSLContextBuilder; import org.apache.pinot.client.Connection; import org.apache.pinot.client.ConnectionFactory; import org.apache.pinot.client.JsonAsyncHttpPinotClientTransportFactory; +import org.apache.pinot.client.PinotDriver; import org.apache.pinot.client.ResultSetGroup; import org.apache.pinot.common.helix.ExtraInstanceConfig; import org.apache.pinot.common.utils.SimpleHttpResponse; @@ -496,6 +500,53 @@ public class TlsIntegrationTest extends BaseClusterIntegrationTest { } } + @Test + public void testJDBCClient() + throws Exception { + String query = "SELECT count(*) FROM " + getTableName(); + java.sql.Connection connection = getValidJDBCConnection(DEFAULT_CONTROLLER_PORT); + Statement statement = connection.createStatement(); + ResultSet resultSet = statement.executeQuery(query); + resultSet.first(); + Assert.assertTrue(resultSet.getLong(1) > 0); + + try { + java.sql.Connection invalidConnection = getInValidJDBCConnection(DEFAULT_CONTROLLER_PORT); + statement = invalidConnection.createStatement(); + resultSet = statement.executeQuery(query); + Assert.fail("Should not allow queries with invalid TLS configuration"); + } catch (Exception e) { + // this should fail + } + } + + private java.sql.Connection getValidJDBCConnection(int controllerPort) throws Exception { + SSLContextBuilder sslContextBuilder = SSLContextBuilder.create(); + sslContextBuilder.setKeyStoreType(PKCS_12); + sslContextBuilder.loadKeyMaterial(_tlsStorePKCS12, PASSWORD_CHAR, PASSWORD_CHAR); + sslContextBuilder.loadTrustMaterial(_tlsStorePKCS12, PASSWORD_CHAR); + + PinotDriver pinotDriver = new PinotDriver(sslContextBuilder.build()); + Properties jdbcProps = new Properties(); + jdbcProps.setProperty(PinotDriver.INFO_SCHEME, CommonConstants.HTTPS_PROTOCOL); + jdbcProps.setProperty(PinotDriver.INFO_HEADERS + "." + CLIENT_HEADER.getName(), CLIENT_HEADER.getValue()); + return pinotDriver.connect("jdbc:pinot://localhost:" + controllerPort, jdbcProps); + } + + private java.sql.Connection getInValidJDBCConnection(int controllerPort) throws Exception { + SSLContextBuilder sslContextBuilder = SSLContextBuilder.create(); + sslContextBuilder.setKeyStoreType(PKCS_12); + sslContextBuilder.loadKeyMaterial(_tlsStoreEmptyPKCS12, PASSWORD_CHAR, PASSWORD_CHAR); + sslContextBuilder.loadTrustMaterial(_tlsStorePKCS12, PASSWORD_CHAR); + + PinotDriver pinotDriver = new PinotDriver(sslContextBuilder.build()); + Properties jdbcProps = new Properties(); + + jdbcProps.setProperty(PinotDriver.INFO_SCHEME, CommonConstants.HTTPS_PROTOCOL); + jdbcProps.setProperty(PinotDriver.INFO_HEADERS + "." + CLIENT_HEADER.getName(), CLIENT_HEADER.getValue()); + return pinotDriver.connect("jdbc:pinot://localhost:" + controllerPort, jdbcProps); + } + private static CloseableHttpClient makeClient(String keyStoreType, URL keyStoreUrl, URL trustStoreUrl) { try { SSLContextBuilder sslContextBuilder = SSLContextBuilder.create(); diff --git a/pom.xml b/pom.xml index 567c03da6c..2d0114f42a 100644 --- a/pom.xml +++ b/pom.xml @@ -322,6 +322,11 @@ <artifactId>pinot-java-client</artifactId> <version>${project.version}</version> </dependency> + <dependency> + <groupId>org.apache.pinot</groupId> + <artifactId>pinot-jdbc-client</artifactId> + <version>${project.version}</version> + </dependency> <dependency> <groupId>org.apache.pinot</groupId> <artifactId>pinot-core</artifactId> --------------------------------------------------------------------- To unsubscribe, e-mail: commits-unsubscr...@pinot.apache.org For additional commands, e-mail: commits-h...@pinot.apache.org