diff --git a/presto-cli/src/main/java/com/facebook/presto/cli/ClientOptions.java b/presto-cli/src/main/java/com/facebook/presto/cli/ClientOptions.java index b9dc8a8ceff6..6a05483e1e51 100644 --- a/presto-cli/src/main/java/com/facebook/presto/cli/ClientOptions.java +++ b/presto-cli/src/main/java/com/facebook/presto/cli/ClientOptions.java @@ -86,9 +86,13 @@ public class ClientOptions @Option(name = "--truststore-type", title = "truststore type", description = "Truststore type") public String trustStoreType = KeyStore.getDefaultType(); + @Option(name = "--access-token", title = "access token", description = "Access token") public String accessToken; + @Option(name = "--insecure", title = "trust all certificates", description = "Skip validation of HTTP server certificates (should only be used for debugging)") + public boolean insecure; + @Option(name = "--user", title = "user", description = "Username") public String user = System.getProperty("user.name"); diff --git a/presto-cli/src/main/java/com/facebook/presto/cli/Console.java b/presto-cli/src/main/java/com/facebook/presto/cli/Console.java index 0ec67846ae20..0deff11d0a1f 100644 --- a/presto-cli/src/main/java/com/facebook/presto/cli/Console.java +++ b/presto-cli/src/main/java/com/facebook/presto/cli/Console.java @@ -134,6 +134,7 @@ public boolean run() Optional.ofNullable(clientOptions.truststorePassword), Optional.ofNullable(clientOptions.trustStoreType), Optional.ofNullable(clientOptions.accessToken), + clientOptions.insecure, Optional.ofNullable(clientOptions.user), clientOptions.password ? Optional.of(getPassword()) : Optional.empty(), Optional.ofNullable(clientOptions.krb5Principal), diff --git a/presto-cli/src/main/java/com/facebook/presto/cli/QueryRunner.java b/presto-cli/src/main/java/com/facebook/presto/cli/QueryRunner.java index 4823ce5ccb2c..1ecffacc29bc 100644 --- a/presto-cli/src/main/java/com/facebook/presto/cli/QueryRunner.java +++ b/presto-cli/src/main/java/com/facebook/presto/cli/QueryRunner.java @@ -14,6 +14,7 @@ package com.facebook.presto.cli; import com.facebook.presto.client.ClientSession; +import com.facebook.presto.client.OkHttpUtil; import com.facebook.presto.client.StatementClient; import com.google.common.net.HostAndPort; import okhttp3.OkHttpClient; @@ -63,6 +64,7 @@ public QueryRunner( Optional truststorePassword, Optional trustStoreType, Optional accessToken, + boolean insecureSsl, Optional user, Optional password, Optional kerberosPrincipal, @@ -77,7 +79,12 @@ public QueryRunner( this.debug = debug; this.runtime = runtime; - this.sslSetup = builder -> setupSsl(builder, keystorePath, keystorePassword, keyStoreType, truststorePath, truststorePassword, trustStoreType); + if (insecureSsl) { + this.sslSetup = OkHttpUtil::setupInsecureSsl; + } + else { + this.sslSetup = builder -> setupSsl(builder, keystorePath, keystorePassword, keyStoreType, truststorePath, truststorePassword, trustStoreType); + } OkHttpClient.Builder builder = new OkHttpClient.Builder(); diff --git a/presto-cli/src/test/java/com/facebook/presto/cli/AbstractCliTest.java b/presto-cli/src/test/java/com/facebook/presto/cli/AbstractCliTest.java index c34f2af6f6cc..eb2267e1bd79 100644 --- a/presto-cli/src/test/java/com/facebook/presto/cli/AbstractCliTest.java +++ b/presto-cli/src/test/java/com/facebook/presto/cli/AbstractCliTest.java @@ -114,14 +114,19 @@ protected MockResponse createMockResponse() protected void executeQueries(List queries) { - Console console = new Console(); QueryRunner queryRunner = createQueryRunner(createMockClientSession()); + executeQueries(queryRunner, queries); + } + + protected void executeQueries(QueryRunner queryRunner, List queries) + { + Console console = new Console(); for (String query : queries) { console.executeCommand(queryRunner, query, CSV, false); } } - protected static QueryRunner createQueryRunner(ClientSession clientSession) + protected static QueryRunner createQueryRunner(ClientSession clientSession, boolean insecureSsl) { return new QueryRunner( clientSession, @@ -136,6 +141,7 @@ protected static QueryRunner createQueryRunner(ClientSession clientSession) Optional.empty(), Optional.empty(), Optional.empty(), + insecureSsl, Optional.empty(), Optional.empty(), Optional.empty(), @@ -147,6 +153,11 @@ protected static QueryRunner createQueryRunner(ClientSession clientSession) true); } + protected static QueryRunner createQueryRunner(ClientSession clientSession) + { + return createQueryRunner(clientSession, false); + } + protected static void assertHeaders(String headerName, Headers headers, Set expectedSessionHeaderValues) { assertEquals(ImmutableSet.copyOf(headers.values(headerName)), expectedSessionHeaderValues); diff --git a/presto-cli/src/test/java/com/facebook/presto/cli/TestInsecureQueryRunner.java b/presto-cli/src/test/java/com/facebook/presto/cli/TestInsecureQueryRunner.java new file mode 100644 index 000000000000..b86ac24c937f --- /dev/null +++ b/presto-cli/src/test/java/com/facebook/presto/cli/TestInsecureQueryRunner.java @@ -0,0 +1,102 @@ +/* + * 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 com.facebook.presto.cli; + +import com.google.common.collect.ImmutableList; +import okhttp3.mockwebserver.MockWebServer; +import org.testng.annotations.AfterMethod; +import org.testng.annotations.BeforeMethod; +import org.testng.annotations.Test; + +import javax.net.ssl.KeyManagerFactory; +import javax.net.ssl.SSLContext; +import javax.net.ssl.TrustManagerFactory; + +import java.io.IOException; +import java.io.InputStream; +import java.security.KeyManagementException; +import java.security.KeyStore; +import java.security.KeyStoreException; +import java.security.NoSuchAlgorithmException; +import java.security.SecureRandom; +import java.security.UnrecoverableKeyException; +import java.security.cert.CertificateException; + +import static com.google.common.io.Resources.getResource; +import static java.util.concurrent.TimeUnit.SECONDS; +import static org.testng.Assert.assertEquals; + +@Test(singleThreaded = true) +public class TestInsecureQueryRunner + extends AbstractCliTest +{ + @Override + @BeforeMethod + public void setup() + throws IOException + { + server = new MockWebServer(); + SSLContext sslContext = buildTestSslContext(); + server.useHttps(sslContext.getSocketFactory(), false); + server.start(); + } + + @Override + @AfterMethod(alwaysRun = true) + public void teardown() + throws IOException + { + server.close(); + } + + @Test + public void testInsecureConnection() + { + server.enqueue(createMockResponse()); + server.enqueue(createMockResponse()); + executeQueries(createQueryRunner(createMockClientSession(), true), + ImmutableList.of("query with insecure mode;")); + try { + assertEquals(server.takeRequest(1, SECONDS).getPath(), "/v1/statement"); + } + catch (InterruptedException e) { + Thread.currentThread().interrupt(); + throw new RuntimeException(e); + } + } + + private SSLContext buildTestSslContext() + throws IOException + { + try { + // Load self-signed certificate + char[] serverKeyStorePassword = "insecure-ssl-test".toCharArray(); + KeyStore serverKeyStore = KeyStore.getInstance(KeyStore.getDefaultType()); + try (InputStream in = getResource(getClass(), "/insecure-ssl-test.jks").openStream()) { + serverKeyStore.load(in, serverKeyStorePassword); + } + String kmfAlgorithm = KeyManagerFactory.getDefaultAlgorithm(); + KeyManagerFactory kmf = KeyManagerFactory.getInstance(kmfAlgorithm); + kmf.init(serverKeyStore, serverKeyStorePassword); + TrustManagerFactory trustManagerFactory = TrustManagerFactory.getInstance(kmfAlgorithm); + trustManagerFactory.init(serverKeyStore); + SSLContext sslContext = SSLContext.getInstance("SSL"); + sslContext.init(kmf.getKeyManagers(), trustManagerFactory.getTrustManagers(), new SecureRandom()); + return sslContext; + } + catch (KeyStoreException | NoSuchAlgorithmException | CertificateException | UnrecoverableKeyException | KeyManagementException e) { + throw new IOException("failed to initialize SSL context", e); + } + } +} diff --git a/presto-cli/src/test/resources/insecure-ssl-test.jks b/presto-cli/src/test/resources/insecure-ssl-test.jks new file mode 100644 index 000000000000..21d80eda222b Binary files /dev/null and b/presto-cli/src/test/resources/insecure-ssl-test.jks differ diff --git a/presto-client/src/main/java/com/facebook/presto/client/OkHttpUtil.java b/presto-client/src/main/java/com/facebook/presto/client/OkHttpUtil.java index 20026f984aa1..78175f158dbb 100644 --- a/presto-client/src/main/java/com/facebook/presto/client/OkHttpUtil.java +++ b/presto-client/src/main/java/com/facebook/presto/client/OkHttpUtil.java @@ -41,6 +41,7 @@ import java.net.Proxy; import java.security.GeneralSecurityException; import java.security.KeyStore; +import java.security.SecureRandom; import java.security.cert.Certificate; import java.security.cert.CertificateExpiredException; import java.security.cert.CertificateNotYetValidException; @@ -138,6 +139,41 @@ private static InetSocketAddress toUnresolvedAddress(HostAndPort address) return InetSocketAddress.createUnresolved(address.getHost(), address.getPort()); } + public static void setupInsecureSsl(OkHttpClient.Builder clientBuilder) + { + try { + X509TrustManager trustAllCerts = new X509TrustManager() + { + @Override + public void checkClientTrusted(X509Certificate[] chain, String authType) + { + throw new UnsupportedOperationException("checkClientTrusted should not be called"); + } + + @Override + public void checkServerTrusted(X509Certificate[] chain, String authType) + { + // skip validation of server certificate + } + + @Override + public X509Certificate[] getAcceptedIssuers() + { + return new X509Certificate[0]; + } + }; + + SSLContext sslContext = SSLContext.getInstance("SSL"); + sslContext.init(null, new TrustManager[] {trustAllCerts}, new SecureRandom()); + + clientBuilder.sslSocketFactory(sslContext.getSocketFactory(), trustAllCerts); + clientBuilder.hostnameVerifier((hostname, session) -> true); + } + catch (GeneralSecurityException e) { + throw new ClientException("Error setting up SSL: " + e.getMessage(), e); + } + } + public static void setupSsl( OkHttpClient.Builder clientBuilder, Optional keyStorePath, diff --git a/presto-docs/src/main/sphinx/clients/presto-cli.rst b/presto-docs/src/main/sphinx/clients/presto-cli.rst index dce94ed5522e..54098c62fb25 100644 --- a/presto-docs/src/main/sphinx/clients/presto-cli.rst +++ b/presto-docs/src/main/sphinx/clients/presto-cli.rst @@ -72,16 +72,19 @@ Run the CLI with the ``--help`` option to see the online help. ./presto --help +NAME + presto - Presto interactive console + SYNOPSIS presto [--access-token ] [--catalog ] [--client-info ] [--client-request-timeout ] [--client-tags ] [--debug] [--disable-compression] - [--execute ] [--extra-credential ...] - [(-f | --file )] [(-h | --help)] - [--http-proxy ] [--ignore-errors] - [--keystore-password ] - [--keystore-path ] + [--disable-redirects] [--execute ] + [--extra-credential ...] [(-f | --file )] + [(-h | --help)] [--http-proxy ] [--ignore-errors] + [--insecure] [--keystore-password ] + [--keystore-path ] [--keystore-type ] [--krb5-config-path ] [--krb5-credential-cache-path ] [--krb5-disable-remote-service-hostname-canonicalization] @@ -90,10 +93,12 @@ SYNOPSIS [--krb5-remote-service-name ] [--log-levels-file ] [--output-format ] [--password] [--resource-estimate ...] - [--schema ] [--server ] [--session ...] - [--socks-proxy ] [--source ] - [--truststore-password ] - [--truststore-path ] [--user ] [--version] + [--runtime-stats] [--schema ] [--server ] + [--session ...] [--socks-proxy ] + [--source ] [--truststore-password ] + [--truststore-path ] + [--truststore-type ] [--user ] + [--validate-nexturi-source] [--version] OPTIONS --access-token @@ -117,6 +122,9 @@ OPTIONS --disable-compression Disable compression of query results + --disable-redirects + Disable client following redirects from server + --execute Execute specified statements and exit @@ -137,12 +145,19 @@ OPTIONS Continue processing in batch mode when an error occurs (default is to exit immediately) + --insecure + Skip validation of HTTP server certificates (should only be used for + debugging) + --keystore-password Keystore password --keystore-path Keystore path + --keystore-type + Keystore type + --krb5-config-path Kerberos config file path (default: /etc/krb5.conf) @@ -166,7 +181,7 @@ OPTIONS Configure log levels for debugging using this file --output-format - Output format for batch mode [ALIGNED, VERTICAL, CSV, TSV, + Output format for batch mode [ALIGNED, VERTICAL, JSON, CSV, TSV, CSV_HEADER, TSV_HEADER, NULL] (default: CSV) --password @@ -177,7 +192,8 @@ OPTIONS key=value) --runtime-stats - Enable runtime stats information. Flag must be used in conjunction with the --debug flag + Enable runtime stats information. Flag must be used in conjunction + with the --debug flag --schema Default schema @@ -201,8 +217,16 @@ OPTIONS --truststore-path Truststore path + --truststore-type + Truststore type + --user Username + --validate-nexturi-source + Validate nextUri server host and port does not change during query + execution + --version Display version information and exit +