/**
* 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.hive.jdbc;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.fail;
import java.io.File;
import java.net.URLEncoder;
import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.sql.Statement;
import java.util.HashMap;
import java.util.Map;
import org.apache.hadoop.fs.Path;
import org.apache.hadoop.util.Shell;
import org.apache.hadoop.hive.conf.HiveConf;
import org.apache.hadoop.hive.conf.HiveConf.ConfVars;
import org.apache.hive.jdbc.miniHS2.MiniHS2;
import org.junit.After;
import org.junit.Assert;
import org.junit.Assume;
import org.junit.Before;
import org.junit.BeforeClass;
import org.junit.Test;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
public class TestSSL {
private static final Logger LOG = LoggerFactory.getLogger(TestSSL.class);
private static final String KEY_STORE_NAME = "keystore.jks";
private static final String TRUST_STORE_NAME = "truststore.jks";
private static final String KEY_STORE_PASSWORD = "HiveJdbc";
private static final String JAVA_TRUST_STORE_PROP = "javax.net.ssl.trustStore";
private static final String JAVA_TRUST_STORE_PASS_PROP = "javax.net.ssl.trustStorePassword";
private static final String HS2_BINARY_MODE = "binary";
private static final String HS2_HTTP_MODE = "http";
private static final String HS2_HTTP_ENDPOINT = "cliservice";
private static final String HS2_BINARY_AUTH_MODE = "NONE";
private static final String HS2_HTTP_AUTH_MODE = "NOSASL";
private MiniHS2 miniHS2 = null;
private static HiveConf conf = new HiveConf();
private Connection hs2Conn = null;
private String dataFileDir = conf.get("test.data.files");
private Map<String, String> confOverlay;
private final String SSL_CONN_PARAMS = ";ssl=true;sslTrustStore=" + URLEncoder.encode(dataFileDir + File.separator +
TRUST_STORE_NAME) + ";trustStorePassword=" + KEY_STORE_PASSWORD;
@BeforeClass
public static void beforeTest() throws Exception {
Class.forName(MiniHS2.getJdbcDriverName());
}
@Before
public void setUp() throws Exception {
DriverManager.setLoginTimeout(0);
if (!System.getProperty("test.data.files", "").isEmpty()) {
dataFileDir = System.getProperty("test.data.files");
}
dataFileDir = dataFileDir.replace('\\', '/').replace("c:", "");
miniHS2 = new MiniHS2(conf);
confOverlay = new HashMap<String, String>();
}
@After
public void tearDown() throws Exception {
if (hs2Conn != null) {
hs2Conn.close();
}
if (miniHS2 != null && miniHS2.isStarted()) {
miniHS2.stop();
}
System.clearProperty(JAVA_TRUST_STORE_PROP);
System.clearProperty(JAVA_TRUST_STORE_PASS_PROP);
}
private int execCommand(String cmd) throws Exception {
int exitCode;
try {
String output = Shell.execCommand("bash", "-c", cmd);
LOG.info("Output from '" + cmd + "': " + output) ;
exitCode = 0;
} catch (Shell.ExitCodeException e) {
exitCode = e.getExitCode();
LOG.info("Error executing '" + cmd + "', exitCode = " + exitCode, e);
}
return exitCode;
}
/***
* Tests to ensure SSLv2 and SSLv3 are disabled
*/
@Test
public void testSSLVersion() throws Exception {
Assume.assumeTrue(execCommand("which openssl") == 0); // we need openssl
Assume.assumeTrue(System.getProperty("os.name").toLowerCase()
.contains("linux")); // we depend on linux openssl exit codes
setSslConfOverlay(confOverlay);
// Test in binary mode
setBinaryConfOverlay(confOverlay);
// Start HS2 with SSL
miniHS2.start(confOverlay);
// make SSL connection
hs2Conn = DriverManager.getConnection(miniHS2.getJdbcURL() + ";ssl=true;sslTrustStore=" +
dataFileDir + File.separator + TRUST_STORE_NAME + ";trustStorePassword=" +
KEY_STORE_PASSWORD, System.getProperty("user.name"), "bar");
hs2Conn.close();
Assert.assertEquals("Expected exit code of 1", 1,
execCommand("openssl s_client -connect " + miniHS2.getHost() + ":" + miniHS2.getBinaryPort()
+ " -ssl2 < /dev/null"));
Assert.assertEquals("Expected exit code of 1", 1,
execCommand("openssl s_client -connect " + miniHS2.getHost() + ":" + miniHS2.getBinaryPort()
+ " -ssl3 < /dev/null"));
miniHS2.stop();
// Test in http mode
setHttpConfOverlay(confOverlay);
miniHS2.start(confOverlay);
// make SSL connection
try {
hs2Conn = DriverManager.getConnection(miniHS2.getJdbcURL() +
";ssl=true;sslTrustStore=" + dataFileDir + File.separator +
TRUST_STORE_NAME + ";trustStorePassword=" + KEY_STORE_PASSWORD +
"?hive.server2.transport.mode=" + HS2_HTTP_MODE +
";hive.server2.thrift.http.path=" + HS2_HTTP_ENDPOINT,
System.getProperty("user.name"), "bar");
Assert.fail("Expected SQLException during connect");
} catch (SQLException e) {
LOG.info("Expected exception: " + e, e);
Assert.assertEquals("08S01", e.getSQLState().trim());
Throwable cause = e.getCause();
Assert.assertNotNull(cause);
while (cause.getCause() != null) {
cause = cause.getCause();
}
Assert.assertEquals("org.apache.http.NoHttpResponseException", cause.getClass().getName());
Assert.assertEquals("The target server failed to respond", cause.getMessage());
}
miniHS2.stop();
}
/***
* Test SSL client with non-SSL server fails
* @throws Exception
*/
@Test
public void testInvalidConfig() throws Exception {
clearSslConfOverlay(confOverlay);
// Test in binary mode
setBinaryConfOverlay(confOverlay);
miniHS2.start(confOverlay);
DriverManager.setLoginTimeout(4);
try {
hs2Conn = DriverManager.getConnection(miniHS2.getJdbcURL("default", SSL_CONN_PARAMS),
System.getProperty("user.name"), "bar");
fail("SSL connection should fail with NON-SSL server");
} catch (SQLException e) {
// expected error
assertEquals("08S01", e.getSQLState().trim());
}
System.setProperty(JAVA_TRUST_STORE_PROP, dataFileDir + File.separator + TRUST_STORE_NAME );
System.setProperty(JAVA_TRUST_STORE_PASS_PROP, KEY_STORE_PASSWORD);
try {
hs2Conn = DriverManager.getConnection(miniHS2.getJdbcURL() + ";ssl=true",
System.getProperty("user.name"), "bar");
fail("SSL connection should fail with NON-SSL server");
} catch (SQLException e) {
// expected error
assertEquals("08S01", e.getSQLState().trim());
}
miniHS2.stop();
// Test in http mode with ssl properties specified in url
System.clearProperty(JAVA_TRUST_STORE_PROP);
System.clearProperty(JAVA_TRUST_STORE_PASS_PROP);
setHttpConfOverlay(confOverlay);
miniHS2.start(confOverlay);
try {
hs2Conn = DriverManager.getConnection(miniHS2.getJdbcURL("default", SSL_CONN_PARAMS),
System.getProperty("user.name"), "bar");
fail("SSL connection should fail with NON-SSL server");
} catch (SQLException e) {
// expected error
assertEquals("08S01", e.getSQLState().trim());
}
}
/***
* Test non-SSL client with SSL server fails
* @throws Exception
*/
@Test
public void testConnectionMismatch() throws Exception {
setSslConfOverlay(confOverlay);
// Test in binary mode
setBinaryConfOverlay(confOverlay);
miniHS2.start(confOverlay);
// Start HS2 with SSL
try {
hs2Conn = DriverManager.getConnection(miniHS2.getJdbcURL(), System.getProperty("user.name"), "bar");
fail("NON SSL connection should fail with SSL server");
} catch (SQLException e) {
// expected error
assertEquals("08S01", e.getSQLState().trim());
}
try {
hs2Conn = DriverManager.getConnection(miniHS2.getJdbcURL()+ ";ssl=false",
System.getProperty("user.name"), "bar");
fail("NON SSL connection should fail with SSL server");
} catch (SQLException e) {
// expected error
assertEquals("08S01", e.getSQLState().trim());
}
miniHS2.stop();
// Test in http mode
setHttpConfOverlay(confOverlay);
miniHS2.start(confOverlay);
try {
hs2Conn = DriverManager.getConnection(miniHS2.getJdbcURL("default", ";ssl=false"),
System.getProperty("user.name"), "bar");
fail("NON SSL connection should fail with SSL server");
} catch (SQLException e) {
// expected error
assertEquals("08S01", e.getSQLState().trim());
}
}
/***
* Test SSL client connection to SSL server
* @throws Exception
*/
@Test
public void testSSLConnectionWithURL() throws Exception {
setSslConfOverlay(confOverlay);
// Test in binary mode
setBinaryConfOverlay(confOverlay);
// Start HS2 with SSL
miniHS2.start(confOverlay);
// make SSL connection
hs2Conn = DriverManager.getConnection(miniHS2.getJdbcURL("default", SSL_CONN_PARAMS),
System.getProperty("user.name"), "bar");
hs2Conn.close();
miniHS2.stop();
// Test in http mode
setHttpConfOverlay(confOverlay);
miniHS2.start(confOverlay);
// make SSL connection
hs2Conn = DriverManager.getConnection(miniHS2.getJdbcURL("default", SSL_CONN_PARAMS),
System.getProperty("user.name"), "bar");
hs2Conn.close();
}
/***
* Test SSL client connection to SSL server
* @throws Exception
*/
@Test
public void testSSLConnectionWithProperty() throws Exception {
setSslConfOverlay(confOverlay);
// Test in binary mode
setBinaryConfOverlay(confOverlay);
// Start HS2 with SSL
miniHS2.start(confOverlay);
System.setProperty(JAVA_TRUST_STORE_PROP, dataFileDir + File.separator + TRUST_STORE_NAME );
System.setProperty(JAVA_TRUST_STORE_PASS_PROP, KEY_STORE_PASSWORD);
// make SSL connection
hs2Conn = DriverManager.getConnection(miniHS2.getJdbcURL() + ";ssl=true",
System.getProperty("user.name"), "bar");
hs2Conn.close();
miniHS2.stop();
// Test in http mode
setHttpConfOverlay(confOverlay);
miniHS2.start(confOverlay);
// make SSL connection
hs2Conn = DriverManager.getConnection(miniHS2.getJdbcURL("default", SSL_CONN_PARAMS),
System.getProperty("user.name"), "bar");
hs2Conn.close();
}
/**
* Start HS2 in SSL mode, open a SSL connection and fetch data
* @throws Exception
*/
@Test
public void testSSLFetch() throws Exception {
setSslConfOverlay(confOverlay);
// Test in binary mode
setBinaryConfOverlay(confOverlay);
// Start HS2 with SSL
miniHS2.start(confOverlay);
String tableName = "sslTab";
Path dataFilePath = new Path(dataFileDir, "kv1.txt");
// make SSL connection
hs2Conn = DriverManager.getConnection(miniHS2.getJdbcURL("default", SSL_CONN_PARAMS),
System.getProperty("user.name"), "bar");
// Set up test data
setupTestTableWithData(tableName, dataFilePath, hs2Conn);
Statement stmt = hs2Conn.createStatement();
ResultSet res = stmt.executeQuery("SELECT * FROM " + tableName);
int rowCount = 0;
while (res.next()) {
++rowCount;
assertEquals("val_" + res.getInt(1), res.getString(2));
}
// read result over SSL
assertEquals(500, rowCount);
hs2Conn.close();
}
/**
* Start HS2 in Http mode with SSL enabled, open a SSL connection and fetch data
* @throws Exception
*/
@Test
public void testSSLFetchHttp() throws Exception {
setSslConfOverlay(confOverlay);
// Test in http mode
setHttpConfOverlay(confOverlay);
miniHS2.start(confOverlay);
String tableName = "sslTab";
Path dataFilePath = new Path(dataFileDir, "kv1.txt");
// make SSL connection
hs2Conn = DriverManager.getConnection(miniHS2.getJdbcURL("default", SSL_CONN_PARAMS),
System.getProperty("user.name"), "bar");
// Set up test data
setupTestTableWithData(tableName, dataFilePath, hs2Conn);
Statement stmt = hs2Conn.createStatement();
ResultSet res = stmt.executeQuery("SELECT * FROM " + tableName);
int rowCount = 0;
while (res.next()) {
++rowCount;
assertEquals("val_" + res.getInt(1), res.getString(2));
}
// read result over SSL
assertEquals(500, rowCount);
hs2Conn.close();
}
private void setupTestTableWithData(String tableName, Path dataFilePath,
Connection hs2Conn) throws Exception {
Statement stmt = hs2Conn.createStatement();
stmt.execute("set hive.support.concurrency = false");
stmt.execute("drop table if exists " + tableName);
stmt.execute("create table " + tableName
+ " (under_col int comment 'the under column', value string)");
// load data
stmt.execute("load data local inpath '"
+ dataFilePath.toString() + "' into table " + tableName);
stmt.close();
}
private void setSslConfOverlay(Map<String, String> confOverlay) {
confOverlay.put(ConfVars.HIVE_SERVER2_USE_SSL.varname, "true");
confOverlay.put(ConfVars.HIVE_SERVER2_SSL_KEYSTORE_PATH.varname,
dataFileDir + File.separator + KEY_STORE_NAME);
confOverlay.put(ConfVars.HIVE_SERVER2_SSL_KEYSTORE_PASSWORD.varname,
KEY_STORE_PASSWORD);
}
private void clearSslConfOverlay(Map<String, String> confOverlay) {
confOverlay.put(ConfVars.HIVE_SERVER2_USE_SSL.varname, "false");
}
// Currently http mode works with server in NOSASL auth mode & doesn't support doAs
private void setHttpConfOverlay(Map<String, String> confOverlay) {
confOverlay.put(ConfVars.HIVE_SERVER2_TRANSPORT_MODE.varname, HS2_HTTP_MODE);
confOverlay.put(ConfVars.HIVE_SERVER2_THRIFT_HTTP_PATH.varname, HS2_HTTP_ENDPOINT);
confOverlay.put(ConfVars.HIVE_SERVER2_AUTHENTICATION.varname, HS2_HTTP_AUTH_MODE);
confOverlay.put(ConfVars.HIVE_SERVER2_ENABLE_DOAS.varname, "false");
}
private void setBinaryConfOverlay(Map<String, String> confOverlay) {
confOverlay.put(ConfVars.HIVE_SERVER2_TRANSPORT_MODE.varname, HS2_BINARY_MODE);
confOverlay.put(ConfVars.HIVE_SERVER2_AUTHENTICATION.varname, HS2_BINARY_AUTH_MODE);
confOverlay.put(ConfVars.HIVE_SERVER2_ENABLE_DOAS.varname, "true");
}
}