From e56eda6438a9a0a7f8385c1c8cc2a3d835f812cc Mon Sep 17 00:00:00 2001 From: Zac Blanco Date: Mon, 7 Oct 2024 10:54:53 -0700 Subject: [PATCH] [CLI] Add flag to disable certificate verification Cherry-pick of trinodb/trino@1c5b9215a2d1b04b5f84e2b3e86 with minor modifications for Presto Co-authored-by: Lewuathe --- .../facebook/presto/cli/ClientOptions.java | 4 + .../java/com/facebook/presto/cli/Console.java | 1 + .../com/facebook/presto/cli/QueryRunner.java | 9 +- .../facebook/presto/cli/AbstractCliTest.java | 15 ++- .../presto/cli/TestInsecureQueryRunner.java | 102 ++++++++++++++++++ .../src/test/resources/insecure-ssl-test.jks | Bin 0 -> 2253 bytes .../facebook/presto/client/OkHttpUtil.java | 36 +++++++ .../src/main/sphinx/clients/presto-cli.rst | 46 ++++++-- 8 files changed, 199 insertions(+), 14 deletions(-) create mode 100644 presto-cli/src/test/java/com/facebook/presto/cli/TestInsecureQueryRunner.java create mode 100644 presto-cli/src/test/resources/insecure-ssl-test.jks 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 b9dc8a8ceff69..6a05483e1e511 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 0ec67846ae204..0deff11d0a1fb 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 4823ce5ccb2cc..1ecffacc29bc2 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 c34f2af6f6cc9..eb2267e1bd797 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 0000000000000..b86ac24c937f8 --- /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 0000000000000000000000000000000000000000..21d80eda222bf31fba97d00300024344f1c03bbf GIT binary patch literal 2253 zcmchYX*3iH8^>qH49(aZON8vh*ai(IMnnrCHIZFO#h4lUu40O736X6^WGrI~qf2rl zTUkPO#+vM8DauZ-bI<#}=bqE|`{DoL`8{8r^FPn|{|{FVR{#J2=t#i7#p_D)zH{5x z>yDDQH$lngj;KqQD42!sH@WblH3rU<-h7ilPF zRK4C;F&S)2&+jtsX=&ECmF98QR$yAjqjlA z(Rbw`F|>!;=>Vn?6rU{_KDuTQZYOS`i{IKYjUeh|wD^#O@Ip>sY?69RwN~dJK@c}KDZQE1K!AV?sSys(D%Wd9l@SF2b}P+43!0kl^`z^|H#iPN&^ za9PvCW{N|&I`$cJWwh*a6Kj~!3}CH5TTghW``6>}x$7O-uHnNPlD;>a!FidNBa1ZI zpz6=3tkO+}O9!m^Zo`K@m**kYgq(OcmT(A%0Fm_84U%#*4e7F)8D_;VK6fK z0Pk$JHY3-Xw1b-op$j{123=3n_C`kR($|MxBRpP*6^jcdEk5Ng6Fi^CTE9#iC~NPC zn5lP;CE%$_c8-xW4COX;rlRu=hr3xdsw8NEGs%n)o3?ikh}r6atB9Dz@$z>}@O-@VWiG!RbXwt+ zhJEx&xoTSC49BPMbc;|9l3Ec9y*{9rh(CB%!WeIx+tH0HR&v@g<=+vP?3FP75Svo` zb}VH_@f#?1-du%14{5y~YMHjLeUbB5avhRTRgH+7*snR^5+yn#P#@Lua2=s0d5*)r zTNjJTpuYEGvYF$Q@otaR>M}%s)?@xtu+~thxl#)1$JC1gmmn)$#iVtq z;Xf{@2j{{K_pOD+RuL><1LI z-_$4AU*BkBoy(2^-A=sD_mdWV_6yHmkV3qB?a=rF=mDID)f?T} zz%ff_7IGV0OxdI~;b7-UDB|FB{Ip3v|NW@!Wp4GnnortYH)xx0CDs+~%>t8WvP{pI zYfA8q9rh=mmU?v8Lo^$Truu5xdt_<2K2Vi26QL?mLZ5?19Gc}HSIOd~i^5a96<s9s~+vEz?2cNbyqFyZCOi@mq%4~+8J+IKshwagC!jZ}Pauj~J+X@w6sF|NBw zU$c%8a}*kp=G}dnX}4C^ahH1OO|Kha;cBB4^x?OS=|?3*E`Irm0EdTutGvDsQ?C?A zd9l(-_1r^ER~Olzr<&X^Lp2c^gfUAm7-;WlzrJ!BS7Yv;Hk-C}b!VrSfGG4}_9G72 zWfAY0SB(phr-`WxBE2jWw8*O1R;wVJhN_Zg{1s+3Fbx(Ro9c&@Z+nZ@eSCX7iIMu& zy!x=3(r?*LahJdLz9EAckhiA{uUhRn5hs$OUya92IG|n1F1Q6nK(11`Wx^~BL^^Ea zENa|T8_z5#?OyoKq>se+N)Rd<;^H~r9X_QW2ahFYQ0%Tg8Q_z!t4r6pmfw!ld%@Ab zp{(pktI#pAOh(p{&PV+C72P>F#cNR|s;AAv6Sf~K1D*EoIExuDySJ-yXc;gwnPT?2 z@|@WA>Ytzc?Cxe!u)CiMPgTe(^UP=sYqTiR#zdswIxPzko%}afG`jl2n57*tkEkiMYnXvs25aD1W{PIAhoS>(*c(I|LGgW_J4_iO$ z%cpj}f0F>}Hk_M>C8y>v4HgRoNbfWqSF(s?y8xt07)5dWmZ(-msU*?qP1$z`si5~+9x9`O8c8Qn+4j>mEh;?xx+)PbEn4nb=TRApU0Q=u=kd^-^6uIG~Z@2 zq9$ne9AK|_=4wOBna>V&AQm71m?MEYg%Umb5K%6$0GO}-+&(8%|uGnWYbs6VB9{>OV literal 0 HcmV?d00001 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 20026f984aa1a..78175f158dbb0 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 dce94ed5522ef..54098c62fb25d 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 +