SQL: Fix SSL for JDBC and CLI for real this time (elastic/x-pack-elasticsearch#3277)
Previously I'd added tests for JDBC and CLI that I *thought* used SSL but they didn't! I wasn't careful... Testing changes: * Actually enable SSL/HTTPS in the `qa:sql:security:ssl` subproject. * Rework how `RemoteCli` handles security. This allows us to configure SSL, the keystore, and the username and password in a much less error prone way. * Fix up JDBC tests to properly use SSL. * Allow the `CliFixture` to specify the keystore location. * Switch `CliFixture` and `RemoteCli` from sending the password in the connection string to filling out the prompt for it. * Have `CliFixture` also send the keystore password when a keystore is configured. This makes the following production code changes: * Allow the CLI to configure the keystore location with the `-k`/`-keystore_location` parameters. * If the keystore location is configured then the CLI will prompt for the password. * Allow the configuration of urls starting with `https`. * Improve the exception thrown when the URL doesn't parse by adding a suppressed exception with the original parse error, before we tried to add `http://` to the front of it. Original commit: elastic/x-pack-elasticsearch@97fac4a3b4
This commit is contained in:
parent
4bebc307c3
commit
236f64a70e
|
@ -1,3 +1,7 @@
|
|||
integTestRunner {
|
||||
systemProperty 'tests.ssl.enabled', 'false'
|
||||
}
|
||||
|
||||
integTestCluster {
|
||||
waitCondition = { node, ant ->
|
||||
File tmpFile = new File(node.cwd, 'wait.success')
|
||||
|
|
|
@ -7,6 +7,7 @@ package org.elasticsearch.xpack.qa.sql.security;
|
|||
|
||||
import org.elasticsearch.common.settings.Settings;
|
||||
import org.elasticsearch.xpack.qa.sql.cli.ErrorsTestCase;
|
||||
import org.elasticsearch.xpack.qa.sql.cli.RemoteCli.SecurityConfig;
|
||||
|
||||
public class CliErrorsIT extends ErrorsTestCase {
|
||||
@Override
|
||||
|
@ -15,7 +16,12 @@ public class CliErrorsIT extends ErrorsTestCase {
|
|||
}
|
||||
|
||||
@Override
|
||||
protected String esUrlPrefix() {
|
||||
return CliSecurityIT.adminEsUrlPrefix();
|
||||
protected String getProtocol() {
|
||||
return RestSqlIT.SSL_ENABLED ? "https" : "http";
|
||||
}
|
||||
|
||||
@Override
|
||||
protected SecurityConfig securityConfig() {
|
||||
return CliSecurityIT.adminSecurityConfig();
|
||||
}
|
||||
}
|
||||
|
|
|
@ -7,6 +7,7 @@ package org.elasticsearch.xpack.qa.sql.security;
|
|||
|
||||
import org.elasticsearch.common.settings.Settings;
|
||||
import org.elasticsearch.xpack.qa.sql.cli.FetchSizeTestCase;
|
||||
import org.elasticsearch.xpack.qa.sql.cli.RemoteCli.SecurityConfig;
|
||||
|
||||
public class CliFetchSizeIT extends FetchSizeTestCase {
|
||||
@Override
|
||||
|
@ -15,7 +16,12 @@ public class CliFetchSizeIT extends FetchSizeTestCase {
|
|||
}
|
||||
|
||||
@Override
|
||||
protected String esUrlPrefix() {
|
||||
return CliSecurityIT.adminEsUrlPrefix();
|
||||
protected String getProtocol() {
|
||||
return RestSqlIT.SSL_ENABLED ? "https" : "http";
|
||||
}
|
||||
|
||||
@Override
|
||||
protected SecurityConfig securityConfig() {
|
||||
return CliSecurityIT.adminSecurityConfig();
|
||||
}
|
||||
}
|
||||
|
|
|
@ -6,8 +6,12 @@
|
|||
package org.elasticsearch.xpack.qa.sql.security;
|
||||
|
||||
import org.elasticsearch.common.CheckedConsumer;
|
||||
import org.elasticsearch.common.io.PathUtils;
|
||||
import org.elasticsearch.xpack.qa.sql.cli.RemoteCli;
|
||||
|
||||
import org.elasticsearch.xpack.qa.sql.cli.RemoteCli.SecurityConfig;
|
||||
import java.net.URISyntaxException;
|
||||
import java.nio.file.Files;
|
||||
import java.nio.file.Path;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Iterator;
|
||||
import java.util.List;
|
||||
|
@ -19,18 +23,43 @@ import static org.hamcrest.Matchers.containsString;
|
|||
import static org.hamcrest.Matchers.startsWith;
|
||||
|
||||
public class CliSecurityIT extends SqlSecurityTestCase {
|
||||
static final String NO_INIT_CONNECTION_CHECK_PREFIX = "-c false ";
|
||||
static String adminEsUrlPrefix() {
|
||||
return "test_admin:x-pack-test-password@";
|
||||
static SecurityConfig adminSecurityConfig() {
|
||||
String keystoreLocation;
|
||||
String keystorePassword;
|
||||
if (RestSqlIT.SSL_ENABLED) {
|
||||
Path keyStore;
|
||||
try {
|
||||
keyStore = PathUtils.get(RestSqlIT.class.getResource("/test-node.jks").toURI());
|
||||
} catch (URISyntaxException e) {
|
||||
throw new RuntimeException("exception while reading the store", e);
|
||||
}
|
||||
if (!Files.exists(keyStore)) {
|
||||
throw new IllegalStateException("Keystore file [" + keyStore + "] does not exist.");
|
||||
}
|
||||
keystoreLocation = keyStore.toAbsolutePath().toString();
|
||||
keystorePassword = "keypass";
|
||||
} else {
|
||||
keystoreLocation = null;
|
||||
keystorePassword = null;
|
||||
}
|
||||
return new SecurityConfig(RestSqlIT.SSL_ENABLED, "test_admin", "x-pack-test-password", keystoreLocation, keystorePassword);
|
||||
}
|
||||
|
||||
/**
|
||||
* Perform security test actions using the CLI.
|
||||
*/
|
||||
private static class CliActions implements Actions {
|
||||
private SecurityConfig userSecurity(String user) {
|
||||
SecurityConfig admin = adminSecurityConfig();
|
||||
if (user == null) {
|
||||
return admin;
|
||||
}
|
||||
return new SecurityConfig(RestSqlIT.SSL_ENABLED, user, "testpass", admin.keystoreLocation(), admin.keystorePassword());
|
||||
}
|
||||
|
||||
@Override
|
||||
public void queryWorksAsAdmin() throws Exception {
|
||||
try (RemoteCli cli = new RemoteCli(adminEsUrlPrefix() + elasticsearchAddress())) {
|
||||
try (RemoteCli cli = new RemoteCli(elasticsearchAddress(), true, adminSecurityConfig())) {
|
||||
assertThat(cli.command("SELECT * FROM test ORDER BY a"), containsString("a | b | c"));
|
||||
assertEquals("---------------+---------------+---------------", cli.readLine());
|
||||
assertThat(cli.readLine(), containsString("1 |2 |3"));
|
||||
|
@ -56,7 +85,7 @@ public class CliSecurityIT extends SqlSecurityTestCase {
|
|||
public void expectMatchesAdmin(String adminSql, String user, String userSql,
|
||||
CheckedConsumer<RemoteCli, Exception> customizer) throws Exception {
|
||||
List<String> adminResult = new ArrayList<>();
|
||||
try (RemoteCli cli = new RemoteCli(adminEsUrlPrefix() + elasticsearchAddress())) {
|
||||
try (RemoteCli cli = new RemoteCli(elasticsearchAddress(), true, adminSecurityConfig())) {
|
||||
customizer.accept(cli);
|
||||
adminResult.add(cli.command(adminSql));
|
||||
String line;
|
||||
|
@ -68,7 +97,7 @@ public class CliSecurityIT extends SqlSecurityTestCase {
|
|||
}
|
||||
|
||||
Iterator<String> expected = adminResult.iterator();
|
||||
try (RemoteCli cli = new RemoteCli(userPrefix(user) + elasticsearchAddress())) {
|
||||
try (RemoteCli cli = new RemoteCli(elasticsearchAddress(), true, userSecurity(user))) {
|
||||
customizer.accept(cli);
|
||||
assertTrue(expected.hasNext());
|
||||
assertEquals(expected.next(), cli.command(userSql));
|
||||
|
@ -86,7 +115,7 @@ public class CliSecurityIT extends SqlSecurityTestCase {
|
|||
|
||||
@Override
|
||||
public void expectDescribe(Map<String, String> columns, String user) throws Exception {
|
||||
try (RemoteCli cli = new RemoteCli(userPrefix(user) + elasticsearchAddress())) {
|
||||
try (RemoteCli cli = new RemoteCli(elasticsearchAddress(), true, userSecurity(user))) {
|
||||
assertThat(cli.command("DESCRIBE test"), containsString("column | type"));
|
||||
assertEquals("---------------+---------------", cli.readLine());
|
||||
for (Map.Entry<String, String> column : columns.entrySet()) {
|
||||
|
@ -98,7 +127,7 @@ public class CliSecurityIT extends SqlSecurityTestCase {
|
|||
|
||||
@Override
|
||||
public void expectShowTables(List<String> tables, String user) throws Exception {
|
||||
try (RemoteCli cli = new RemoteCli(userPrefix(user) + elasticsearchAddress())) {
|
||||
try (RemoteCli cli = new RemoteCli(elasticsearchAddress(), true, userSecurity(user))) {
|
||||
assertThat(cli.command("SHOW TABLES"), containsString("table"));
|
||||
assertEquals("---------------", cli.readLine());
|
||||
for (String table : tables) {
|
||||
|
@ -110,7 +139,7 @@ public class CliSecurityIT extends SqlSecurityTestCase {
|
|||
|
||||
@Override
|
||||
public void expectUnknownIndex(String user, String sql) throws Exception {
|
||||
try (RemoteCli cli = new RemoteCli(userPrefix(user) + elasticsearchAddress())) {
|
||||
try (RemoteCli cli = new RemoteCli(elasticsearchAddress(), true, userSecurity(user))) {
|
||||
assertThat(cli.command(sql), containsString("Bad request"));
|
||||
assertThat(cli.readLine(), containsString("Unknown index"));
|
||||
}
|
||||
|
@ -118,29 +147,25 @@ public class CliSecurityIT extends SqlSecurityTestCase {
|
|||
|
||||
@Override
|
||||
public void expectForbidden(String user, String sql) throws Exception {
|
||||
// Skip initial check to make sure it doesn't trip
|
||||
try (RemoteCli cli = new RemoteCli(NO_INIT_CONNECTION_CHECK_PREFIX + userPrefix(user) + elasticsearchAddress())) {
|
||||
/*
|
||||
* Cause the CLI to skip its connection test on startup so we
|
||||
* can get a forbidden exception when we run the query.
|
||||
*/
|
||||
try (RemoteCli cli = new RemoteCli(elasticsearchAddress(), false, userSecurity(user))) {
|
||||
assertThat(cli.command(sql), containsString("is unauthorized for user [" + user + "]"));
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void expectUnknownColumn(String user, String sql, String column) throws Exception {
|
||||
try (RemoteCli cli = new RemoteCli(userPrefix(user) + elasticsearchAddress())) {
|
||||
try (RemoteCli cli = new RemoteCli(elasticsearchAddress(), true, userSecurity(user))) {
|
||||
assertThat(cli.command(sql), containsString("[1;31mBad request"));
|
||||
assertThat(cli.readLine(), containsString("Unknown column [" + column + "][1;23;31m][0m"));
|
||||
}
|
||||
}
|
||||
|
||||
private String userPrefix(String user) {
|
||||
if (user == null) {
|
||||
return adminEsUrlPrefix();
|
||||
}
|
||||
return user + ":testpass@";
|
||||
}
|
||||
}
|
||||
|
||||
public CliSecurityIT() {
|
||||
super(new CliActions());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -6,6 +6,7 @@
|
|||
package org.elasticsearch.xpack.qa.sql.security;
|
||||
|
||||
import org.elasticsearch.common.settings.Settings;
|
||||
import org.elasticsearch.xpack.qa.sql.cli.RemoteCli.SecurityConfig;
|
||||
import org.elasticsearch.xpack.qa.sql.cli.SelectTestCase;
|
||||
|
||||
public class CliSelectIT extends SelectTestCase {
|
||||
|
@ -15,7 +16,12 @@ public class CliSelectIT extends SelectTestCase {
|
|||
}
|
||||
|
||||
@Override
|
||||
protected String esUrlPrefix() {
|
||||
return CliSecurityIT.adminEsUrlPrefix();
|
||||
protected String getProtocol() {
|
||||
return RestSqlIT.SSL_ENABLED ? "https" : "http";
|
||||
}
|
||||
|
||||
@Override
|
||||
protected SecurityConfig securityConfig() {
|
||||
return CliSecurityIT.adminSecurityConfig();
|
||||
}
|
||||
}
|
||||
|
|
|
@ -6,6 +6,7 @@
|
|||
package org.elasticsearch.xpack.qa.sql.security;
|
||||
|
||||
import org.elasticsearch.common.settings.Settings;
|
||||
import org.elasticsearch.xpack.qa.sql.cli.RemoteCli.SecurityConfig;
|
||||
import org.elasticsearch.xpack.qa.sql.cli.ShowTestCase;
|
||||
|
||||
public class CliShowIT extends ShowTestCase {
|
||||
|
@ -15,7 +16,12 @@ public class CliShowIT extends ShowTestCase {
|
|||
}
|
||||
|
||||
@Override
|
||||
protected String esUrlPrefix() {
|
||||
return CliSecurityIT.adminEsUrlPrefix();
|
||||
protected String getProtocol() {
|
||||
return RestSqlIT.SSL_ENABLED ? "https" : "http";
|
||||
}
|
||||
|
||||
@Override
|
||||
protected SecurityConfig securityConfig() {
|
||||
return CliSecurityIT.adminSecurityConfig();
|
||||
}
|
||||
}
|
||||
|
|
|
@ -16,6 +16,11 @@ public class JdbcConnectionIT extends ConnectionTestCase {
|
|||
return RestSqlIT.securitySettings();
|
||||
}
|
||||
|
||||
@Override
|
||||
protected String getProtocol() {
|
||||
return RestSqlIT.SSL_ENABLED ? "https" : "http";
|
||||
}
|
||||
|
||||
@Override
|
||||
protected Properties connectionProperties() {
|
||||
Properties properties = super.connectionProperties();
|
||||
|
|
|
@ -20,6 +20,11 @@ public class JdbcCsvSpecIT extends CsvSpecTestCase {
|
|||
return RestSqlIT.securitySettings();
|
||||
}
|
||||
|
||||
@Override
|
||||
protected String getProtocol() {
|
||||
return RestSqlIT.SSL_ENABLED ? "https" : "http";
|
||||
}
|
||||
|
||||
@Override
|
||||
protected Properties connectionProperties() {
|
||||
Properties sp = super.connectionProperties();
|
||||
|
|
|
@ -16,6 +16,11 @@ public class JdbcDatabaseMetaDataIT extends DatabaseMetaDataTestCase {
|
|||
return RestSqlIT.securitySettings();
|
||||
}
|
||||
|
||||
@Override
|
||||
protected String getProtocol() {
|
||||
return RestSqlIT.SSL_ENABLED ? "https" : "http";
|
||||
}
|
||||
|
||||
@Override
|
||||
protected Properties connectionProperties() {
|
||||
Properties properties = super.connectionProperties();
|
||||
|
|
|
@ -16,6 +16,11 @@ public class JdbcErrorsIT extends ErrorsTestCase {
|
|||
return RestSqlIT.securitySettings();
|
||||
}
|
||||
|
||||
@Override
|
||||
protected String getProtocol() {
|
||||
return RestSqlIT.SSL_ENABLED ? "https" : "http";
|
||||
}
|
||||
|
||||
@Override
|
||||
protected Properties connectionProperties() {
|
||||
Properties properties = super.connectionProperties();
|
||||
|
|
|
@ -16,6 +16,11 @@ public class JdbcFetchSizeIT extends FetchSizeTestCase {
|
|||
return RestSqlIT.securitySettings();
|
||||
}
|
||||
|
||||
@Override
|
||||
protected String getProtocol() {
|
||||
return RestSqlIT.SSL_ENABLED ? "https" : "http";
|
||||
}
|
||||
|
||||
@Override
|
||||
protected Properties connectionProperties() {
|
||||
Properties properties = super.connectionProperties();
|
||||
|
|
|
@ -8,7 +8,11 @@ package org.elasticsearch.xpack.qa.sql.security;
|
|||
import org.elasticsearch.action.admin.indices.get.GetIndexAction;
|
||||
import org.elasticsearch.common.CheckedConsumer;
|
||||
import org.elasticsearch.common.CheckedFunction;
|
||||
import org.elasticsearch.common.io.PathUtils;
|
||||
import org.elasticsearch.xpack.qa.sql.jdbc.LocalH2;
|
||||
import java.net.URISyntaxException;
|
||||
import java.nio.file.Files;
|
||||
import java.nio.file.Path;
|
||||
import java.sql.Connection;
|
||||
import java.sql.DriverManager;
|
||||
import java.sql.ResultSet;
|
||||
|
@ -21,6 +25,7 @@ import java.util.Properties;
|
|||
import static org.elasticsearch.xpack.qa.sql.jdbc.JdbcAssert.assertResultSets;
|
||||
import static org.elasticsearch.xpack.qa.sql.jdbc.JdbcIntegrationTestCase.elasticsearchAddress;
|
||||
import static org.elasticsearch.xpack.qa.sql.jdbc.JdbcIntegrationTestCase.randomKnownTimeZone;
|
||||
import static org.elasticsearch.xpack.qa.sql.security.RestSqlIT.SSL_ENABLED;
|
||||
import static org.hamcrest.Matchers.contains;
|
||||
import static org.hamcrest.Matchers.containsString;
|
||||
|
||||
|
@ -31,6 +36,7 @@ public class JdbcSecurityIT extends SqlSecurityTestCase {
|
|||
properties.put("user", "test_admin");
|
||||
properties.put("password", "x-pack-test-password");
|
||||
// end::admin_properties
|
||||
addSslPropertiesIfNeeded(properties);
|
||||
return properties;
|
||||
}
|
||||
|
||||
|
@ -38,7 +44,8 @@ public class JdbcSecurityIT extends SqlSecurityTestCase {
|
|||
Properties props = new Properties();
|
||||
props.put("timezone", randomKnownTimeZone());
|
||||
props.putAll(properties);
|
||||
return DriverManager.getConnection("jdbc:es://" + elasticsearchAddress(), props);
|
||||
String scheme = SSL_ENABLED ? "https" : "http";
|
||||
return DriverManager.getConnection("jdbc:es://" + scheme + "://" + elasticsearchAddress(), props);
|
||||
}
|
||||
|
||||
static Properties userProperties(String user) {
|
||||
|
@ -48,9 +55,32 @@ public class JdbcSecurityIT extends SqlSecurityTestCase {
|
|||
Properties prop = new Properties();
|
||||
prop.put("user", user);
|
||||
prop.put("password", "testpass");
|
||||
addSslPropertiesIfNeeded(prop);
|
||||
return prop;
|
||||
}
|
||||
|
||||
private static void addSslPropertiesIfNeeded(Properties properties) {
|
||||
if (false == SSL_ENABLED) {
|
||||
return;
|
||||
}
|
||||
Path keyStore;
|
||||
try {
|
||||
keyStore = PathUtils.get(RestSqlIT.class.getResource("/test-node.jks").toURI());
|
||||
} catch (URISyntaxException e) {
|
||||
throw new RuntimeException("exception while reading the store", e);
|
||||
}
|
||||
if (!Files.exists(keyStore)) {
|
||||
throw new IllegalStateException("Keystore file [" + keyStore + "] does not exist.");
|
||||
}
|
||||
String keyStoreStr = keyStore.toAbsolutePath().toString();
|
||||
|
||||
properties.put("ssl", "true");
|
||||
properties.put("ssl.keystore.location", keyStoreStr);
|
||||
properties.put("ssl.keystore.pass", "keypass");
|
||||
properties.put("ssl.truststore.location", keyStoreStr);
|
||||
properties.put("ssl.truststore.pass", "keypass");
|
||||
}
|
||||
|
||||
static void expectActionMatchesAdmin(CheckedFunction<Connection, ResultSet, SQLException> adminAction,
|
||||
String user, CheckedFunction<Connection, ResultSet, SQLException> userAction) throws Exception {
|
||||
try (Connection adminConnection = es(adminProperties());
|
||||
|
|
|
@ -16,6 +16,11 @@ public class JdbcShowTablesIT extends ShowTablesTestCase {
|
|||
return RestSqlIT.securitySettings();
|
||||
}
|
||||
|
||||
@Override
|
||||
protected String getProtocol() {
|
||||
return RestSqlIT.SSL_ENABLED ? "https" : "http";
|
||||
}
|
||||
|
||||
@Override
|
||||
protected Properties connectionProperties() {
|
||||
Properties sp = super.connectionProperties();
|
||||
|
|
|
@ -16,6 +16,11 @@ public class JdbcSimpleExampleIT extends SimpleExampleTestCase {
|
|||
return RestSqlIT.securitySettings();
|
||||
}
|
||||
|
||||
@Override
|
||||
protected String getProtocol() {
|
||||
return RestSqlIT.SSL_ENABLED ? "https" : "http";
|
||||
}
|
||||
|
||||
@Override
|
||||
protected Properties connectionProperties() {
|
||||
Properties properties = super.connectionProperties();
|
||||
|
|
|
@ -20,6 +20,11 @@ public class JdbcSqlSpecIT extends SqlSpecTestCase {
|
|||
return RestSqlIT.securitySettings();
|
||||
}
|
||||
|
||||
@Override
|
||||
protected String getProtocol() {
|
||||
return RestSqlIT.SSL_ENABLED ? "https" : "http";
|
||||
}
|
||||
|
||||
@Override
|
||||
protected Properties connectionProperties() {
|
||||
Properties sp = super.connectionProperties();
|
||||
|
|
|
@ -5,27 +5,54 @@
|
|||
*/
|
||||
package org.elasticsearch.xpack.qa.sql.security;
|
||||
|
||||
import org.elasticsearch.common.Booleans;
|
||||
import org.elasticsearch.common.io.PathUtils;
|
||||
import org.elasticsearch.common.settings.SecureString;
|
||||
import org.elasticsearch.common.settings.Settings;
|
||||
import org.elasticsearch.common.util.concurrent.ThreadContext;
|
||||
import org.elasticsearch.test.rest.ESRestTestCase;
|
||||
import org.elasticsearch.xpack.qa.sql.rest.RestSqlTestCase;
|
||||
|
||||
import static org.elasticsearch.xpack.security.authc.support.UsernamePasswordToken.basicAuthHeaderValue;
|
||||
|
||||
import java.net.URISyntaxException;
|
||||
import java.nio.file.Files;
|
||||
import java.nio.file.Path;
|
||||
|
||||
/**
|
||||
* Integration test for the rest sql action. The one that speaks json directly to a
|
||||
* user rather than to the JDBC driver or CLI.
|
||||
*/
|
||||
public class RestSqlIT extends RestSqlTestCase {
|
||||
static final boolean SSL_ENABLED = Booleans.parseBoolean(System.getProperty("tests.ssl.enabled"));
|
||||
|
||||
static Settings securitySettings() {
|
||||
String token = basicAuthHeaderValue("test_admin", new SecureString("x-pack-test-password".toCharArray()));
|
||||
return Settings.builder()
|
||||
.put(ThreadContext.PREFIX + ".Authorization", token)
|
||||
.build();
|
||||
Settings.Builder builder = Settings.builder()
|
||||
.put(ThreadContext.PREFIX + ".Authorization", token);
|
||||
if (SSL_ENABLED) {
|
||||
Path keyStore;
|
||||
try {
|
||||
keyStore = PathUtils.get(RestSqlIT.class.getResource("/test-node.jks").toURI());
|
||||
} catch (URISyntaxException e) {
|
||||
throw new RuntimeException("exception while reading the store", e);
|
||||
}
|
||||
if (!Files.exists(keyStore)) {
|
||||
throw new IllegalStateException("Keystore file [" + keyStore + "] does not exist.");
|
||||
}
|
||||
builder.put(ESRestTestCase.TRUSTSTORE_PATH, keyStore)
|
||||
.put(ESRestTestCase.TRUSTSTORE_PASSWORD, "keypass");
|
||||
}
|
||||
return builder.build();
|
||||
}
|
||||
|
||||
@Override
|
||||
protected Settings restClientSettings() {
|
||||
return securitySettings();
|
||||
}
|
||||
|
||||
@Override
|
||||
protected String getProtocol() {
|
||||
return RestSqlIT.SSL_ENABLED ? "https" : "http";
|
||||
}
|
||||
}
|
||||
|
|
|
@ -26,7 +26,6 @@ import java.util.Map;
|
|||
|
||||
import static java.util.Collections.singletonMap;
|
||||
import static org.elasticsearch.xpack.qa.sql.rest.RestSqlTestCase.columnInfo;
|
||||
import static java.util.Collections.emptyMap;
|
||||
import static java.util.Collections.singletonList;
|
||||
import static org.hamcrest.Matchers.containsString;
|
||||
import static org.hamcrest.Matchers.empty;
|
||||
|
|
|
@ -49,6 +49,8 @@ import static org.hamcrest.Matchers.hasItems;
|
|||
public abstract class SqlSecurityTestCase extends ESRestTestCase {
|
||||
/**
|
||||
* Actions taken by this test.
|
||||
* <p>
|
||||
* For methods that take {@code user} a {@code null} user means "use the admin".
|
||||
*/
|
||||
protected interface Actions {
|
||||
void queryWorksAsAdmin() throws Exception;
|
||||
|
@ -181,6 +183,11 @@ public abstract class SqlSecurityTestCase extends ESRestTestCase {
|
|||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
protected String getProtocol() {
|
||||
return RestSqlIT.SSL_ENABLED ? "https" : "http";
|
||||
}
|
||||
|
||||
public void testQueryWorksAsAdmin() throws Exception {
|
||||
actions.queryWorksAsAdmin();
|
||||
new AuditLogAsserter()
|
||||
|
|
|
@ -1,4 +1,8 @@
|
|||
grant {
|
||||
// Needed to read the audit log file
|
||||
permission java.io.FilePermission "${tests.audit.logfile}", "read";
|
||||
|
||||
//// Required by ssl subproject:
|
||||
// Required for the net client to setup ssl rather than use global ssl.
|
||||
permission java.lang.RuntimePermission "setFactory";
|
||||
};
|
||||
|
|
|
@ -1,61 +1,366 @@
|
|||
import org.elasticsearch.gradle.LoggedExec
|
||||
import org.elasticsearch.gradle.MavenFilteringHack
|
||||
import org.elasticsearch.gradle.test.NodeInfo
|
||||
|
||||
import javax.net.ssl.HttpsURLConnection
|
||||
import javax.net.ssl.KeyManagerFactory
|
||||
import javax.net.ssl.SSLContext
|
||||
import javax.net.ssl.TrustManagerFactory
|
||||
import java.nio.charset.StandardCharsets
|
||||
import java.security.KeyStore
|
||||
import java.security.SecureRandom
|
||||
|
||||
String outputDir = "generated-resources/${project.name}"
|
||||
task copyTestNodeKeystore(type: Copy) {
|
||||
from project(':x-pack-elasticsearch:plugin')
|
||||
.file('src/test/resources/org/elasticsearch/xpack/security/transport/ssl/certs/simple/testnode.jks')
|
||||
into outputDir
|
||||
// Tell the tests we're running with ssl enabled
|
||||
integTestRunner {
|
||||
systemProperty 'tests.ssl.enabled', 'true'
|
||||
}
|
||||
|
||||
// needed to be consistent with ssl host checking
|
||||
Object san = new SanEvaluator()
|
||||
|
||||
// location of generated keystores and certificates
|
||||
File keystoreDir = new File(project.buildDir, 'keystore')
|
||||
|
||||
// Generate the node's keystore
|
||||
File nodeKeystore = new File(keystoreDir, 'test-node.jks')
|
||||
task createNodeKeyStore(type: LoggedExec) {
|
||||
doFirst {
|
||||
if (nodeKeystore.parentFile.exists() == false) {
|
||||
nodeKeystore.parentFile.mkdirs()
|
||||
}
|
||||
if (nodeKeystore.exists()) {
|
||||
delete nodeKeystore
|
||||
}
|
||||
}
|
||||
executable = new File(project.javaHome, 'bin/keytool')
|
||||
standardInput = new ByteArrayInputStream('FirstName LastName\nUnit\nOrganization\nCity\nState\nNL\nyes\n\n'.getBytes('UTF-8'))
|
||||
args '-genkey',
|
||||
'-alias', 'test-node',
|
||||
'-keystore', nodeKeystore,
|
||||
'-keyalg', 'RSA',
|
||||
'-keysize', '2048',
|
||||
'-validity', '712',
|
||||
'-dname', 'CN=smoke-test-plugins-ssl',
|
||||
'-keypass', 'keypass',
|
||||
'-storepass', 'keypass',
|
||||
'-ext', san
|
||||
}
|
||||
|
||||
// Generate the client's keystore
|
||||
File clientKeyStore = new File(keystoreDir, 'test-client.jks')
|
||||
task createClientKeyStore(type: LoggedExec) {
|
||||
doFirst {
|
||||
if (clientKeyStore.parentFile.exists() == false) {
|
||||
clientKeyStore.parentFile.mkdirs()
|
||||
}
|
||||
if (clientKeyStore.exists()) {
|
||||
delete clientKeyStore
|
||||
}
|
||||
}
|
||||
executable = new File(project.javaHome, 'bin/keytool')
|
||||
standardInput = new ByteArrayInputStream('FirstName LastName\nUnit\nOrganization\nCity\nState\nNL\nyes\n\n'.getBytes('UTF-8'))
|
||||
args '-genkey',
|
||||
'-alias', 'test-client',
|
||||
'-keystore', clientKeyStore,
|
||||
'-keyalg', 'RSA',
|
||||
'-keysize', '2048',
|
||||
'-validity', '712',
|
||||
'-dname', 'CN=smoke-test-plugins-ssl',
|
||||
'-keypass', 'keypass',
|
||||
'-storepass', 'keypass',
|
||||
'-ext', san
|
||||
}
|
||||
|
||||
// Export the node's certificate
|
||||
File nodeCertificate = new File(keystoreDir, 'test-node.cert')
|
||||
task exportNodeCertificate(type: LoggedExec) {
|
||||
doFirst {
|
||||
if (nodeCertificate.parentFile.exists() == false) {
|
||||
nodeCertificate.parentFile.mkdirs()
|
||||
}
|
||||
if (nodeCertificate.exists()) {
|
||||
delete nodeCertificate
|
||||
}
|
||||
}
|
||||
executable = new File(project.javaHome, 'bin/keytool')
|
||||
args '-export',
|
||||
'-alias', 'test-node',
|
||||
'-keystore', nodeKeystore,
|
||||
'-storepass', 'keypass',
|
||||
'-file', nodeCertificate
|
||||
}
|
||||
|
||||
// Import the node certificate in the client's keystore
|
||||
task importNodeCertificateInClientKeyStore(type: LoggedExec) {
|
||||
dependsOn exportNodeCertificate
|
||||
executable = new File(project.javaHome, 'bin/keytool')
|
||||
args '-import',
|
||||
'-alias', 'test-node',
|
||||
'-keystore', clientKeyStore,
|
||||
'-storepass', 'keypass',
|
||||
'-file', nodeCertificate,
|
||||
'-noprompt'
|
||||
}
|
||||
|
||||
// Export the client's certificate
|
||||
File clientCertificate = new File(keystoreDir, 'test-client.cert')
|
||||
task exportClientCertificate(type: LoggedExec) {
|
||||
doFirst {
|
||||
if (clientCertificate.parentFile.exists() == false) {
|
||||
clientCertificate.parentFile.mkdirs()
|
||||
}
|
||||
if (clientCertificate.exists()) {
|
||||
delete clientCertificate
|
||||
}
|
||||
}
|
||||
executable = new File(project.javaHome, 'bin/keytool')
|
||||
args '-export',
|
||||
'-alias', 'test-client',
|
||||
'-keystore', clientKeyStore,
|
||||
'-storepass', 'keypass',
|
||||
'-file', clientCertificate
|
||||
}
|
||||
|
||||
// Import the client certificate in the node's keystore
|
||||
task importClientCertificateInNodeKeyStore(type: LoggedExec) {
|
||||
dependsOn exportClientCertificate
|
||||
executable = new File(project.javaHome, 'bin/keytool')
|
||||
args '-import',
|
||||
'-alias', 'test-client',
|
||||
'-keystore', nodeKeystore,
|
||||
'-storepass', 'keypass',
|
||||
'-file', clientCertificate,
|
||||
'-noprompt'
|
||||
}
|
||||
|
||||
forbiddenPatterns {
|
||||
exclude '**/*.cert'
|
||||
}
|
||||
|
||||
// Add keystores to test classpath: it expects it there
|
||||
sourceSets.test.resources.srcDir(keystoreDir)
|
||||
processTestResources.dependsOn(
|
||||
createNodeKeyStore, createClientKeyStore,
|
||||
importNodeCertificateInClientKeyStore, importClientCertificateInNodeKeyStore
|
||||
)
|
||||
|
||||
integTestCluster.dependsOn(importClientCertificateInNodeKeyStore)
|
||||
|
||||
|
||||
integTestCluster {
|
||||
// The setup that we actually want
|
||||
setting 'xpack.security.http.ssl.enabled', 'true'
|
||||
setting 'xpack.security.transport.ssl.enabled', 'true'
|
||||
|
||||
// ceremony to set up ssl
|
||||
setting 'xpack.ssl.keystore.path', 'test-node.jks'
|
||||
keystoreSetting 'xpack.ssl.keystore.secure_password', 'keypass'
|
||||
|
||||
// copy keystores into config/
|
||||
extraConfigFile nodeKeystore.name, nodeKeystore
|
||||
extraConfigFile clientKeyStore.name, clientKeyStore
|
||||
|
||||
// Override the wait condition to work properly with security and SSL
|
||||
waitCondition = { NodeInfo node, AntBuilder ant ->
|
||||
File tmpFile = new File(node.cwd, 'wait.success')
|
||||
|
||||
// wait up to two minutes
|
||||
final long stopTime = System.currentTimeMillis() + (2 * 60000L);
|
||||
Exception lastException = null;
|
||||
|
||||
while (System.currentTimeMillis() < stopTime) {
|
||||
lastException = null;
|
||||
// we use custom wait logic here as the elastic user is not available immediately and ant.get will fail when a 401 is returned
|
||||
HttpURLConnection httpURLConnection = null;
|
||||
try {
|
||||
httpURLConnection = (HttpURLConnection) new URL("http://${node.httpUri()}/_cluster/health?wait_for_nodes=${numNodes}&wait_for_status=yellow").openConnection();
|
||||
httpURLConnection.setRequestProperty("Authorization", "Basic " +
|
||||
Base64.getEncoder().encodeToString("test_admin:x-pack-test-password".getBytes(StandardCharsets.UTF_8)));
|
||||
httpURLConnection.setRequestMethod("GET");
|
||||
httpURLConnection.setConnectTimeout(1000);
|
||||
httpURLConnection.setReadTimeout(30000); // read needs to wait for nodes!
|
||||
httpURLConnection.connect();
|
||||
if (httpURLConnection.getResponseCode() == 200) {
|
||||
tmpFile.withWriter StandardCharsets.UTF_8.name(), {
|
||||
it.write(httpURLConnection.getInputStream().getText(StandardCharsets.UTF_8.name()))
|
||||
}
|
||||
break;
|
||||
}
|
||||
} catch (Exception e) {
|
||||
logger.debug("failed to call cluster health", e)
|
||||
lastException = e
|
||||
} finally {
|
||||
if (httpURLConnection != null) {
|
||||
httpURLConnection.disconnect();
|
||||
}
|
||||
KeyStore keyStore = KeyStore.getInstance("JKS");
|
||||
keyStore.load(clientKeyStore.newInputStream(), 'keypass'.toCharArray());
|
||||
KeyManagerFactory kmf = KeyManagerFactory.getInstance(KeyManagerFactory.getDefaultAlgorithm());
|
||||
kmf.init(keyStore, 'keypass'.toCharArray());
|
||||
TrustManagerFactory tmf = TrustManagerFactory.getInstance(TrustManagerFactory.getDefaultAlgorithm());
|
||||
tmf.init(keyStore);
|
||||
SSLContext sslContext = SSLContext.getInstance("TLSv1.2");
|
||||
sslContext.init(kmf.getKeyManagers(), tmf.getTrustManagers(), new SecureRandom());
|
||||
for (int i = 0; i < 10; i++) {
|
||||
// we use custom wait logic here for HTTPS
|
||||
HttpsURLConnection httpURLConnection = null;
|
||||
try {
|
||||
httpURLConnection = (HttpsURLConnection) new URL("https://${node.httpUri()}/_cluster/health?wait_for_nodes=${numNodes}&wait_for_status=yellow").openConnection();
|
||||
httpURLConnection.setSSLSocketFactory(sslContext.getSocketFactory());
|
||||
httpURLConnection.setRequestProperty("Authorization", "Basic " +
|
||||
Base64.getEncoder().encodeToString("test_admin:x-pack-test-password".getBytes(StandardCharsets.UTF_8)));
|
||||
httpURLConnection.setRequestMethod("GET");
|
||||
httpURLConnection.connect();
|
||||
if (httpURLConnection.getResponseCode() == 200) {
|
||||
tmpFile.withWriter StandardCharsets.UTF_8.name(), {
|
||||
it.write(httpURLConnection.getInputStream().getText(StandardCharsets.UTF_8.name()))
|
||||
}
|
||||
}
|
||||
} catch (IOException e) {
|
||||
if (i == 9) {
|
||||
logger.error("final attempt of calling cluster health failed", e)
|
||||
} else {
|
||||
logger.debug("failed to call cluster health", e)
|
||||
}
|
||||
} finally {
|
||||
if (httpURLConnection != null) {
|
||||
httpURLConnection.disconnect();
|
||||
}
|
||||
}
|
||||
|
||||
// did not start, so wait a bit before trying again
|
||||
Thread.sleep(500L);
|
||||
}
|
||||
if (tmpFile.exists() == false && lastException != null) {
|
||||
logger.error("final attempt of calling cluster health failed", lastException)
|
||||
// did not start, so wait a bit before trying again
|
||||
Thread.sleep(500L);
|
||||
}
|
||||
|
||||
return tmpFile.exists()
|
||||
}
|
||||
setting 'xpack.security.transport.ssl.enabled', 'true'
|
||||
setting 'xpack.ssl.keystore.path', 'testnode.jks'
|
||||
keystoreSetting 'xpack.ssl.keystore.secure_password', 'testnode'
|
||||
dependsOn copyTestNodeKeystore
|
||||
extraConfigFile 'testnode.jks', new File(outputDir + '/testnode.jks')
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
/** A lazy evaluator to find the san to use for certificate generation. */
|
||||
class SanEvaluator {
|
||||
|
||||
private static String san = null
|
||||
|
||||
String toString() {
|
||||
synchronized (SanEvaluator.class) {
|
||||
if (san == null) {
|
||||
san = getSubjectAlternativeNameString()
|
||||
}
|
||||
}
|
||||
return san
|
||||
}
|
||||
|
||||
// Code stolen from NetworkUtils/InetAddresses/NetworkAddress to support SAN
|
||||
/** Return all interfaces (and subinterfaces) on the system */
|
||||
private static List<NetworkInterface> getInterfaces() throws SocketException {
|
||||
List<NetworkInterface> all = new ArrayList<>();
|
||||
addAllInterfaces(all, Collections.list(NetworkInterface.getNetworkInterfaces()));
|
||||
Collections.sort(all, new Comparator<NetworkInterface>() {
|
||||
@Override
|
||||
public int compare(NetworkInterface left, NetworkInterface right) {
|
||||
return Integer.compare(left.getIndex(), right.getIndex());
|
||||
}
|
||||
});
|
||||
return all;
|
||||
}
|
||||
|
||||
/** Helper for getInterfaces, recursively adds subinterfaces to {@code target} */
|
||||
private static void addAllInterfaces(List<NetworkInterface> target, List<NetworkInterface> level) {
|
||||
if (!level.isEmpty()) {
|
||||
target.addAll(level);
|
||||
for (NetworkInterface intf : level) {
|
||||
addAllInterfaces(target, Collections.list(intf.getSubInterfaces()));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private static String getSubjectAlternativeNameString() {
|
||||
List<InetAddress> list = new ArrayList<>();
|
||||
for (NetworkInterface intf : getInterfaces()) {
|
||||
if (intf.isUp()) {
|
||||
// NOTE: some operating systems (e.g. BSD stack) assign a link local address to the loopback interface
|
||||
// while technically not a loopback address, some of these treat them as one (e.g. OS X "localhost") so we must too,
|
||||
// otherwise things just won't work out of box. So we include all addresses from loopback interfaces.
|
||||
for (InetAddress address : Collections.list(intf.getInetAddresses())) {
|
||||
if (intf.isLoopback() || address.isLoopbackAddress()) {
|
||||
list.add(address);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
if (list.isEmpty()) {
|
||||
throw new IllegalArgumentException("no up-and-running loopback addresses found, got " + getInterfaces());
|
||||
}
|
||||
|
||||
StringBuilder builder = new StringBuilder("san=");
|
||||
for (int i = 0; i < list.size(); i++) {
|
||||
InetAddress address = list.get(i);
|
||||
String hostAddress;
|
||||
if (address instanceof Inet6Address) {
|
||||
hostAddress = compressedIPV6Address((Inet6Address)address);
|
||||
} else {
|
||||
hostAddress = address.getHostAddress();
|
||||
}
|
||||
builder.append("ip:").append(hostAddress);
|
||||
String hostname = address.getHostName();
|
||||
if (hostname.equals(address.getHostAddress()) == false) {
|
||||
builder.append(",dns:").append(hostname);
|
||||
}
|
||||
|
||||
if (i != (list.size() - 1)) {
|
||||
builder.append(",");
|
||||
}
|
||||
}
|
||||
|
||||
return builder.toString();
|
||||
}
|
||||
|
||||
private static String compressedIPV6Address(Inet6Address inet6Address) {
|
||||
byte[] bytes = inet6Address.getAddress();
|
||||
int[] hextets = new int[8];
|
||||
for (int i = 0; i < hextets.length; i++) {
|
||||
hextets[i] = (bytes[2 * i] & 255) << 8 | bytes[2 * i + 1] & 255;
|
||||
}
|
||||
compressLongestRunOfZeroes(hextets);
|
||||
return hextetsToIPv6String(hextets);
|
||||
}
|
||||
|
||||
/**
|
||||
* Identify and mark the longest run of zeroes in an IPv6 address.
|
||||
*
|
||||
* <p>Only runs of two or more hextets are considered. In case of a tie, the
|
||||
* leftmost run wins. If a qualifying run is found, its hextets are replaced
|
||||
* by the sentinel value -1.
|
||||
*
|
||||
* @param hextets {@code int[]} mutable array of eight 16-bit hextets
|
||||
*/
|
||||
private static void compressLongestRunOfZeroes(int[] hextets) {
|
||||
int bestRunStart = -1;
|
||||
int bestRunLength = -1;
|
||||
int runStart = -1;
|
||||
for (int i = 0; i < hextets.length + 1; i++) {
|
||||
if (i < hextets.length && hextets[i] == 0) {
|
||||
if (runStart < 0) {
|
||||
runStart = i;
|
||||
}
|
||||
} else if (runStart >= 0) {
|
||||
int runLength = i - runStart;
|
||||
if (runLength > bestRunLength) {
|
||||
bestRunStart = runStart;
|
||||
bestRunLength = runLength;
|
||||
}
|
||||
runStart = -1;
|
||||
}
|
||||
}
|
||||
if (bestRunLength >= 2) {
|
||||
Arrays.fill(hextets, bestRunStart, bestRunStart + bestRunLength, -1);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Convert a list of hextets into a human-readable IPv6 address.
|
||||
*
|
||||
* <p>In order for "::" compression to work, the input should contain negative
|
||||
* sentinel values in place of the elided zeroes.
|
||||
*
|
||||
* @param hextets {@code int[]} array of eight 16-bit hextets, or -1s
|
||||
*/
|
||||
private static String hextetsToIPv6String(int[] hextets) {
|
||||
/*
|
||||
* While scanning the array, handle these state transitions:
|
||||
* start->num => "num" start->gap => "::"
|
||||
* num->num => ":num" num->gap => "::"
|
||||
* gap->num => "num" gap->gap => ""
|
||||
*/
|
||||
StringBuilder buf = new StringBuilder(39);
|
||||
boolean lastWasNumber = false;
|
||||
for (int i = 0; i < hextets.length; i++) {
|
||||
boolean thisIsNumber = hextets[i] >= 0;
|
||||
if (thisIsNumber) {
|
||||
if (lastWasNumber) {
|
||||
buf.append(':');
|
||||
}
|
||||
buf.append(Integer.toHexString(hextets[i]));
|
||||
} else {
|
||||
if (i == 0 || lastWasNumber) {
|
||||
buf.append("::");
|
||||
}
|
||||
}
|
||||
lastWasNumber = thisIsNumber;
|
||||
}
|
||||
return buf.toString();
|
||||
}
|
||||
}
|
||||
|
|
|
@ -17,6 +17,7 @@ import org.elasticsearch.common.xcontent.XContentBuilder;
|
|||
import org.elasticsearch.common.xcontent.json.JsonXContent;
|
||||
import org.elasticsearch.test.rest.ESRestTestCase;
|
||||
import org.elasticsearch.transport.client.PreBuiltTransportClient;
|
||||
import org.elasticsearch.xpack.qa.sql.cli.RemoteCli.SecurityConfig;
|
||||
import org.elasticsearch.xpack.qa.sql.embed.CliHttpServer;
|
||||
import org.elasticsearch.xpack.qa.sql.rest.RestSqlTestCase;
|
||||
import org.junit.After;
|
||||
|
@ -65,7 +66,7 @@ public abstract class CliIntegrationTestCase extends ESRestTestCase {
|
|||
*/
|
||||
@Before
|
||||
public void startCli() throws IOException {
|
||||
cli = new RemoteCli(esUrlPrefix() + ES.get());
|
||||
cli = new RemoteCli(ES.get(), true, securityConfig());
|
||||
}
|
||||
|
||||
@After
|
||||
|
@ -79,11 +80,10 @@ public abstract class CliIntegrationTestCase extends ESRestTestCase {
|
|||
}
|
||||
|
||||
/**
|
||||
* Prefix to the Elasticsearch URL. Override to add
|
||||
* authentication support.
|
||||
* Override to add security configuration to the cli.
|
||||
*/
|
||||
protected String esUrlPrefix() {
|
||||
return "";
|
||||
protected SecurityConfig securityConfig() {
|
||||
return null;
|
||||
}
|
||||
|
||||
protected void index(String index, CheckedConsumer<XContentBuilder, IOException> body) throws IOException {
|
||||
|
|
|
@ -7,6 +7,7 @@ package org.elasticsearch.xpack.qa.sql.cli;
|
|||
|
||||
import org.apache.logging.log4j.Logger;
|
||||
import org.elasticsearch.SpecialPermission;
|
||||
import org.elasticsearch.common.Nullable;
|
||||
import org.elasticsearch.common.logging.Loggers;
|
||||
|
||||
import java.io.BufferedReader;
|
||||
|
@ -23,7 +24,9 @@ import java.security.AccessController;
|
|||
import java.security.PrivilegedAction;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.function.Predicate;
|
||||
|
||||
import static org.elasticsearch.test.ESTestCase.randomBoolean;
|
||||
import static org.hamcrest.Matchers.empty;
|
||||
import static org.hamcrest.Matchers.endsWith;
|
||||
import static org.hamcrest.Matchers.not;
|
||||
|
@ -57,7 +60,9 @@ public class RemoteCli implements Closeable {
|
|||
private final PrintWriter out;
|
||||
private final BufferedReader in;
|
||||
|
||||
public RemoteCli(String elasticsearchAddress) throws IOException {
|
||||
public RemoteCli(String elasticsearchAddress, boolean checkConnectionOnStartup,
|
||||
@Nullable SecurityConfig security) throws IOException {
|
||||
// Connect
|
||||
SecurityManager sm = System.getSecurityManager();
|
||||
if (sm != null) {
|
||||
sm.checkPermission(new SpecialPermission());
|
||||
|
@ -75,10 +80,39 @@ public class RemoteCli implements Closeable {
|
|||
});
|
||||
logger.info("connected");
|
||||
socket.setSoTimeout(10000);
|
||||
|
||||
out = new PrintWriter(new OutputStreamWriter(socket.getOutputStream(), StandardCharsets.UTF_8), true);
|
||||
out.println(elasticsearchAddress);
|
||||
in = new BufferedReader(new InputStreamReader(socket.getInputStream(), StandardCharsets.UTF_8));
|
||||
|
||||
// Start the CLI
|
||||
String command;
|
||||
if (security == null) {
|
||||
command = elasticsearchAddress;
|
||||
} else {
|
||||
command = security.user + "@" + elasticsearchAddress;
|
||||
if (security.https) {
|
||||
command = "https://" + command;
|
||||
} else if (randomBoolean()) {
|
||||
command = "http://" + command;
|
||||
}
|
||||
if (security.keystoreLocation != null) {
|
||||
command = command + " -keystore_location " + security.keystoreLocation;
|
||||
}
|
||||
}
|
||||
if (false == checkConnectionOnStartup) {
|
||||
command += " -check false";
|
||||
}
|
||||
out.println(command);
|
||||
|
||||
// Feed it passwords if needed
|
||||
if (security != null && security.keystoreLocation != null) {
|
||||
assertEquals("keystore password: ", readUntil(s -> s.endsWith(": ")));
|
||||
out.println(security.keystorePassword);
|
||||
}
|
||||
if (security != null) {
|
||||
assertEquals("password: ", readUntil(s -> s.endsWith(": ")));
|
||||
out.println(security.password);
|
||||
}
|
||||
|
||||
// Throw out the logo and warnings about making a dumb terminal
|
||||
while (false == readLine().contains("SQL"));
|
||||
// Throw out the empty line before all the good stuff
|
||||
|
@ -136,4 +170,64 @@ public class RemoteCli implements Closeable {
|
|||
logger.info("in : {}", line);
|
||||
return line;
|
||||
}
|
||||
|
||||
private String readUntil(Predicate<String> end) throws IOException {
|
||||
StringBuilder b = new StringBuilder();
|
||||
String result;
|
||||
do {
|
||||
int c = in.read();
|
||||
if (c == -1) {
|
||||
throw new IOException("got eof before end");
|
||||
}
|
||||
b.append((char) c);
|
||||
result = b.toString();
|
||||
} while (false == end.test(result));
|
||||
logger.info("in : {}", result);
|
||||
return result;
|
||||
}
|
||||
|
||||
public static class SecurityConfig {
|
||||
private final boolean https;
|
||||
private final String user;
|
||||
private final String password;
|
||||
@Nullable
|
||||
private final String keystoreLocation;
|
||||
@Nullable
|
||||
private final String keystorePassword;
|
||||
|
||||
public SecurityConfig(boolean https, String user, String password,
|
||||
@Nullable String keystoreLocation, @Nullable String keystorePassword) {
|
||||
if (user == null) {
|
||||
throw new IllegalArgumentException(
|
||||
"[user] is required. Send [null] instead of a SecurityConfig to run without security.");
|
||||
}
|
||||
if (password == null) {
|
||||
throw new IllegalArgumentException(
|
||||
"[password] is required. Send [null] instead of a SecurityConfig to run without security.");
|
||||
}
|
||||
if (keystoreLocation == null) {
|
||||
if (keystorePassword != null) {
|
||||
throw new IllegalArgumentException("[keystorePassword] cannot be specified if [keystoreLocation] is not specified");
|
||||
}
|
||||
} else {
|
||||
if (keystorePassword == null) {
|
||||
throw new IllegalArgumentException("[keystorePassword] is required if [keystoreLocation] is specified");
|
||||
}
|
||||
}
|
||||
|
||||
this.https = https;
|
||||
this.user = user;
|
||||
this.password = password;
|
||||
this.keystoreLocation = keystoreLocation;
|
||||
this.keystorePassword = keystorePassword;
|
||||
}
|
||||
|
||||
public String keystoreLocation() {
|
||||
return keystoreLocation;
|
||||
}
|
||||
|
||||
public String keystorePassword() {
|
||||
return keystorePassword;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -70,7 +70,8 @@ public abstract class JdbcIntegrationTestCase extends ESRestTestCase {
|
|||
// JDBC only supports a single node at a time so we just give it one.
|
||||
return cluster.split(",")[0];
|
||||
/* This doesn't include "jdbc:es://" because we want the example in
|
||||
* esJdbc to be obvious. */
|
||||
* esJdbc to be obvious and because we want to use getProtocol to add
|
||||
* https if we are running against https. */
|
||||
}
|
||||
|
||||
public Connection esJdbc() throws SQLException {
|
||||
|
@ -81,8 +82,9 @@ public abstract class JdbcIntegrationTestCase extends ESRestTestCase {
|
|||
}
|
||||
|
||||
protected Connection useDriverManager() throws SQLException {
|
||||
String elasticsearchAddress = getProtocol() + "://" + elasticsearchAddress();
|
||||
// tag::connect-dm
|
||||
String address = "jdbc:es://" + elasticsearchAddress(); // <1>
|
||||
String address = "jdbc:es://" + elasticsearchAddress; // <1>
|
||||
Properties connectionProperties = connectionProperties(); // <2>
|
||||
Connection connection = DriverManager.getConnection(address, connectionProperties);
|
||||
// end::connect-dm
|
||||
|
@ -91,9 +93,10 @@ public abstract class JdbcIntegrationTestCase extends ESRestTestCase {
|
|||
}
|
||||
|
||||
protected Connection useDataSource() throws SQLException {
|
||||
String elasticsearchAddress = getProtocol() + "://" + elasticsearchAddress();
|
||||
// tag::connect-ds
|
||||
JdbcDataSource dataSource = new JdbcDataSource();
|
||||
String address = "jdbc:es://" + elasticsearchAddress(); // <1>
|
||||
String address = "jdbc:es://" + elasticsearchAddress; // <1>
|
||||
dataSource.setUrl(address);
|
||||
Properties connectionProperties = connectionProperties(); // <2>
|
||||
dataSource.setProperties(connectionProperties);
|
||||
|
@ -140,4 +143,4 @@ public abstract class JdbcIntegrationTestCase extends ESRestTestCase {
|
|||
Collections.sort(ids);
|
||||
return randomFrom(ids);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -26,12 +26,16 @@ import org.elasticsearch.xpack.sql.client.shared.Version;
|
|||
|
||||
import java.io.IOException;
|
||||
import java.net.ConnectException;
|
||||
import java.nio.file.Files;
|
||||
import java.nio.file.Path;
|
||||
import java.nio.file.Paths;
|
||||
import java.util.Arrays;
|
||||
import java.util.List;
|
||||
import java.util.logging.LogManager;
|
||||
|
||||
public class Cli extends Command {
|
||||
private final OptionSpec<Boolean> debugOption;
|
||||
private final OptionSpec<String> keystoreLocation;
|
||||
private final OptionSpec<Boolean> checkOption;
|
||||
private final OptionSpec<String> connectionString;
|
||||
|
||||
|
@ -41,6 +45,12 @@ public class Cli extends Command {
|
|||
"Enable debug logging")
|
||||
.withRequiredArg().ofType(Boolean.class)
|
||||
.defaultsTo(Boolean.parseBoolean(System.getProperty("cli.debug", "false")));
|
||||
this.keystoreLocation = parser.acceptsAll(
|
||||
Arrays.asList("k", "keystore_location"),
|
||||
"Location of a keystore to use when setting up SSL. "
|
||||
+ "If specified then the CLI will prompt for a keystore password. "
|
||||
+ "If specified when the uri isn't https then an error is thrown.")
|
||||
.withRequiredArg().ofType(String.class);
|
||||
this.checkOption = parser.acceptsAll(Arrays.asList("c", "check"),
|
||||
"Enable initial connection check on startup")
|
||||
.withRequiredArg().ofType(Boolean.class)
|
||||
|
@ -77,15 +87,21 @@ public class Cli extends Command {
|
|||
@Override
|
||||
protected void execute(org.elasticsearch.cli.Terminal terminal, OptionSet options) throws Exception {
|
||||
boolean debug = debugOption.value(options);
|
||||
boolean check = checkOption.value(options);
|
||||
boolean checkConnection = checkOption.value(options);
|
||||
List<String> args = connectionString.values(options);
|
||||
if (args.size() > 1) {
|
||||
throw new UserException(ExitCodes.USAGE, "expecting a single uri");
|
||||
}
|
||||
execute(args.size() == 1 ? args.get(0) : null, debug, check);
|
||||
String uri = args.size() == 1 ? args.get(0) : null;
|
||||
args = keystoreLocation.values(options);
|
||||
if (args.size() > 1) {
|
||||
throw new UserException(ExitCodes.USAGE, "expecting a single keystore file");
|
||||
}
|
||||
String keystoreLocationValue = args.size() == 1 ? args.get(0) : null;
|
||||
execute(uri, debug, keystoreLocationValue, checkConnection);
|
||||
}
|
||||
|
||||
private void execute(String uri, boolean debug, boolean check) throws Exception {
|
||||
private void execute(String uri, boolean debug, String keystoreLocation, boolean checkConnection) throws Exception {
|
||||
CliCommand cliCommand = new CliCommands(
|
||||
new PrintLogoCommand(),
|
||||
new ClearScreenCliCommand(),
|
||||
|
@ -96,10 +112,10 @@ public class Cli extends Command {
|
|||
);
|
||||
try (CliTerminal cliTerminal = new JLineTerminal()) {
|
||||
ConnectionBuilder connectionBuilder = new ConnectionBuilder(cliTerminal);
|
||||
ConnectionConfiguration con = connectionBuilder.buildConnection(uri);
|
||||
ConnectionConfiguration con = connectionBuilder.buildConnection(uri, keystoreLocation);
|
||||
CliSession cliSession = new CliSession(new CliHttpClient(con));
|
||||
cliSession.setDebug(debug);
|
||||
if (check) {
|
||||
if (checkConnection) {
|
||||
checkConnection(cliSession, cliTerminal, con);
|
||||
}
|
||||
new CliRepl(cliTerminal, cliSession, cliCommand).execute();
|
||||
|
|
|
@ -5,10 +5,14 @@
|
|||
*/
|
||||
package org.elasticsearch.xpack.sql.cli;
|
||||
|
||||
import org.elasticsearch.cli.ExitCodes;
|
||||
import org.elasticsearch.cli.UserException;
|
||||
import org.elasticsearch.xpack.sql.client.shared.ConnectionConfiguration;
|
||||
|
||||
import java.net.URI;
|
||||
import java.nio.file.Files;
|
||||
import java.nio.file.Path;
|
||||
import java.nio.file.Paths;
|
||||
import java.util.Properties;
|
||||
|
||||
import static org.elasticsearch.xpack.sql.client.shared.UriUtils.parseURI;
|
||||
|
@ -27,14 +31,19 @@ public class ConnectionBuilder {
|
|||
this.cliTerminal = cliTerminal;
|
||||
}
|
||||
|
||||
public ConnectionConfiguration buildConnection(String arg) throws UserException {
|
||||
/**
|
||||
* Build the connection.
|
||||
* @param connectionStringArg the connection string to connect to
|
||||
* @param keystoreLocation the location of the keystore to configure. If null then use the system keystore.
|
||||
*/
|
||||
public ConnectionConfiguration buildConnection(String connectionStringArg, String keystoreLocation) throws UserException {
|
||||
final URI uri;
|
||||
final String connectionString;
|
||||
Properties properties = new Properties();
|
||||
String user = null;
|
||||
String password = null;
|
||||
if (arg != null) {
|
||||
connectionString = arg;
|
||||
if (connectionStringArg != null) {
|
||||
connectionString = connectionStringArg;
|
||||
uri = removeQuery(parseURI(connectionString, DEFAULT_URI), connectionString, DEFAULT_URI);
|
||||
user = uri.getUserInfo();
|
||||
if (user != null) {
|
||||
|
@ -49,6 +58,29 @@ public class ConnectionBuilder {
|
|||
connectionString = DEFAULT_CONNECTION_STRING;
|
||||
}
|
||||
|
||||
if (keystoreLocation != null) {
|
||||
if (false == "https".equals(uri.getScheme())) {
|
||||
throw new UserException(ExitCodes.USAGE, "keystore file specified without https");
|
||||
}
|
||||
Path p = Paths.get(keystoreLocation);
|
||||
checkIfExists("keystore file", p);
|
||||
String keystorePassword = cliTerminal.readPassword("keystore password: ");
|
||||
|
||||
/*
|
||||
* Set both the keystore and truststore settings which is required
|
||||
* to everything work smoothly. I'm not totally sure why we have
|
||||
* two settings but that is a problem for another day.
|
||||
*/
|
||||
properties.put("ssl.keystore.location", keystoreLocation);
|
||||
properties.put("ssl.keystore.pass", keystorePassword);
|
||||
properties.put("ssl.truststore.location", keystoreLocation);
|
||||
properties.put("ssl.truststore.pass", keystorePassword);
|
||||
}
|
||||
|
||||
if ("https".equals(uri.getScheme())) {
|
||||
properties.put("ssl", "true");
|
||||
}
|
||||
|
||||
if (user != null) {
|
||||
if (password == null) {
|
||||
password = cliTerminal.readPassword("password: ");
|
||||
|
@ -57,7 +89,20 @@ public class ConnectionBuilder {
|
|||
properties.setProperty(ConnectionConfiguration.AUTH_PASS, password);
|
||||
}
|
||||
|
||||
return newConnectionConfiguration(uri, connectionString, properties);
|
||||
}
|
||||
|
||||
protected ConnectionConfiguration newConnectionConfiguration(URI uri, String connectionString, Properties properties) {
|
||||
return new ConnectionConfiguration(uri, connectionString, properties);
|
||||
}
|
||||
|
||||
protected void checkIfExists(String name, Path p) throws UserException {
|
||||
if (false == Files.exists(p)) {
|
||||
throw new UserException(ExitCodes.USAGE, name + " [" + p + "] doesn't exist");
|
||||
}
|
||||
if (false == Files.isRegularFile(p)) {
|
||||
throw new UserException(ExitCodes.USAGE, name + " [" + p + "] isn't a regular file");
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -5,10 +5,14 @@
|
|||
*/
|
||||
package org.elasticsearch.xpack.sql.cli;
|
||||
|
||||
import org.elasticsearch.cli.UserException;
|
||||
import org.elasticsearch.test.ESTestCase;
|
||||
import org.elasticsearch.xpack.sql.client.shared.ConnectionConfiguration;
|
||||
|
||||
import org.elasticsearch.xpack.sql.client.shared.SslConfig;
|
||||
import java.net.URI;
|
||||
import java.nio.file.Path;
|
||||
import java.util.Properties;
|
||||
import java.util.concurrent.atomic.AtomicBoolean;
|
||||
|
||||
import static org.mockito.Matchers.any;
|
||||
import static org.mockito.Mockito.mock;
|
||||
|
@ -22,7 +26,7 @@ public class ConnectionBuilderTests extends ESTestCase {
|
|||
public void testDefaultConnection() throws Exception {
|
||||
CliTerminal testTerminal = mock(CliTerminal.class);
|
||||
ConnectionBuilder connectionBuilder = new ConnectionBuilder(testTerminal);
|
||||
ConnectionConfiguration con = connectionBuilder.buildConnection(null);
|
||||
ConnectionConfiguration con = connectionBuilder.buildConnection(null, null);
|
||||
assertNull(con.authUser());
|
||||
assertNull(con.authPass());
|
||||
assertEquals("http://localhost:9200/", con.connectionString());
|
||||
|
@ -38,7 +42,7 @@ public class ConnectionBuilderTests extends ESTestCase {
|
|||
public void testBasicConnection() throws Exception {
|
||||
CliTerminal testTerminal = mock(CliTerminal.class);
|
||||
ConnectionBuilder connectionBuilder = new ConnectionBuilder(testTerminal);
|
||||
ConnectionConfiguration con = connectionBuilder.buildConnection("http://foobar:9242/");
|
||||
ConnectionConfiguration con = connectionBuilder.buildConnection("http://foobar:9242/", null);
|
||||
assertNull(con.authUser());
|
||||
assertNull(con.authPass());
|
||||
assertEquals("http://foobar:9242/", con.connectionString());
|
||||
|
@ -49,7 +53,7 @@ public class ConnectionBuilderTests extends ESTestCase {
|
|||
public void testUserAndPasswordConnection() throws Exception {
|
||||
CliTerminal testTerminal = mock(CliTerminal.class);
|
||||
ConnectionBuilder connectionBuilder = new ConnectionBuilder(testTerminal);
|
||||
ConnectionConfiguration con = connectionBuilder.buildConnection("http://user:pass@foobar:9242/");
|
||||
ConnectionConfiguration con = connectionBuilder.buildConnection("http://user:pass@foobar:9242/", null);
|
||||
assertEquals("user", con.authUser());
|
||||
assertEquals("pass", con.authPass());
|
||||
assertEquals("http://user:pass@foobar:9242/", con.connectionString());
|
||||
|
@ -61,7 +65,7 @@ public class ConnectionBuilderTests extends ESTestCase {
|
|||
CliTerminal testTerminal = mock(CliTerminal.class);
|
||||
when(testTerminal.readPassword("password: ")).thenReturn("password");
|
||||
ConnectionBuilder connectionBuilder = new ConnectionBuilder(testTerminal);
|
||||
ConnectionConfiguration con = connectionBuilder.buildConnection("http://user@foobar:9242/");
|
||||
ConnectionConfiguration con = connectionBuilder.buildConnection("http://user@foobar:9242/", null);
|
||||
assertEquals("user", con.authUser());
|
||||
assertEquals("password", con.authPass());
|
||||
assertEquals("http://user@foobar:9242/", con.connectionString());
|
||||
|
@ -69,4 +73,37 @@ public class ConnectionBuilderTests extends ESTestCase {
|
|||
verify(testTerminal, times(1)).readPassword(any());
|
||||
verifyNoMoreInteractions(testTerminal);
|
||||
}
|
||||
|
||||
public void testKeystoreAndUserInteractiveConnection() throws Exception {
|
||||
CliTerminal testTerminal = mock(CliTerminal.class);
|
||||
when(testTerminal.readPassword("keystore password: ")).thenReturn("keystore password");
|
||||
when(testTerminal.readPassword("password: ")).thenReturn("password");
|
||||
AtomicBoolean called = new AtomicBoolean(false);
|
||||
ConnectionBuilder connectionBuilder = new ConnectionBuilder(testTerminal) {
|
||||
@Override
|
||||
protected void checkIfExists(String name, Path p) {
|
||||
// Stubbed so we don't need permission to read the file
|
||||
}
|
||||
|
||||
@Override
|
||||
protected ConnectionConfiguration newConnectionConfiguration(URI uri, String connectionString,
|
||||
Properties properties) {
|
||||
// Stub building the actual configuration because we don't have permission to read the keystore.
|
||||
assertEquals("true", properties.get(SslConfig.SSL));
|
||||
assertEquals("keystore_location", properties.get(SslConfig.SSL_KEYSTORE_LOCATION));
|
||||
assertEquals("keystore password", properties.get(SslConfig.SSL_KEYSTORE_PASS));
|
||||
assertEquals("keystore_location", properties.get(SslConfig.SSL_TRUSTSTORE_LOCATION));
|
||||
assertEquals("keystore password", properties.get(SslConfig.SSL_TRUSTSTORE_PASS));
|
||||
|
||||
called.set(true);
|
||||
return null;
|
||||
}
|
||||
};
|
||||
assertNull(connectionBuilder.buildConnection("https://user@foobar:9242/", "keystore_location"));
|
||||
assertTrue(called.get());
|
||||
verify(testTerminal, times(2)).readPassword(any());
|
||||
verifyNoMoreInteractions(testTerminal);
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
|
|
@ -30,7 +30,7 @@ import static org.elasticsearch.xpack.sql.client.shared.UriUtils.removeQuery;
|
|||
// jdbc:es://[host|ip]
|
||||
// jdbc:es://[host|ip]:port/(prefix)
|
||||
// jdbc:es://[host|ip]:port/(prefix)(?options=value&)
|
||||
//
|
||||
//
|
||||
// Additional properties can be specified either through the Properties object or in the URL. In case of duplicates, the URL wins.
|
||||
//
|
||||
|
||||
|
@ -50,7 +50,7 @@ public class JdbcConfiguration extends ConnectionConfiguration {
|
|||
public static final String TIME_ZONE = "timezone";
|
||||
// follow the JDBC spec and use the JVM default...
|
||||
// to avoid inconsistency, the default is picked up once at startup and reused across connections
|
||||
// to cater to the principle of least surprise
|
||||
// to cater to the principle of least surprise
|
||||
// really, the way to move forward is to specify a calendar or the timezone manually
|
||||
static final String TIME_ZONE_DEFAULT = TimeZone.getDefault().getID();
|
||||
|
||||
|
@ -94,7 +94,7 @@ public class JdbcConfiguration extends ConnectionConfiguration {
|
|||
|
||||
private static URI parseUrl(String u) throws JdbcSQLException {
|
||||
String url = u;
|
||||
String format = "jdbc:es://[host[:port]]*/[prefix]*[?[option=value]&]*";
|
||||
String format = "jdbc:es://[http|https]?[host[:port]]*/[prefix]*[?[option=value]&]*";
|
||||
if (!canAccept(u)) {
|
||||
throw new JdbcSQLException("Expected [" + URL_PREFIX + "] url, received [" + u + "]");
|
||||
}
|
||||
|
@ -108,7 +108,7 @@ public class JdbcConfiguration extends ConnectionConfiguration {
|
|||
|
||||
private static String removeJdbcPrefix(String connectionString) throws JdbcSQLException {
|
||||
if (connectionString.startsWith(URL_PREFIX)) {
|
||||
return "http://" + connectionString.substring(URL_PREFIX.length());
|
||||
return connectionString.substring(URL_PREFIX.length());
|
||||
} else {
|
||||
throw new JdbcSQLException("Expected [" + URL_PREFIX + "] url, received [" + connectionString + "]");
|
||||
}
|
||||
|
@ -183,7 +183,7 @@ public class JdbcConfiguration extends ConnectionConfiguration {
|
|||
DriverPropertyInfo prop = new DriverPropertyInfo(option, value);
|
||||
info.add(prop);
|
||||
}
|
||||
|
||||
|
||||
return info.toArray(new DriverPropertyInfo[info.size()]);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -26,30 +26,30 @@ import javax.net.ssl.SSLSocketFactory;
|
|||
import javax.net.ssl.TrustManager;
|
||||
import javax.net.ssl.TrustManagerFactory;
|
||||
|
||||
class SslConfig {
|
||||
public class SslConfig {
|
||||
|
||||
private static final String SSL = "ssl";
|
||||
public static final String SSL = "ssl";
|
||||
private static final String SSL_DEFAULT = "false";
|
||||
|
||||
private static final String SSL_PROTOCOL = "ssl.protocol";
|
||||
public static final String SSL_PROTOCOL = "ssl.protocol";
|
||||
private static final String SSL_PROTOCOL_DEFAULT = "TLS"; // SSL alternative
|
||||
|
||||
private static final String SSL_KEYSTORE_LOCATION = "ssl.keystore.location";
|
||||
public static final String SSL_KEYSTORE_LOCATION = "ssl.keystore.location";
|
||||
private static final String SSL_KEYSTORE_LOCATION_DEFAULT = "";
|
||||
|
||||
private static final String SSL_KEYSTORE_PASS = "ssl.keystore.pass";
|
||||
public static final String SSL_KEYSTORE_PASS = "ssl.keystore.pass";
|
||||
private static final String SSL_KEYSTORE_PASS_DEFAULT = "";
|
||||
|
||||
private static final String SSL_KEYSTORE_TYPE = "ssl.keystore.type";
|
||||
public static final String SSL_KEYSTORE_TYPE = "ssl.keystore.type";
|
||||
private static final String SSL_KEYSTORE_TYPE_DEFAULT = "JKS"; // PCKS12
|
||||
|
||||
private static final String SSL_TRUSTSTORE_LOCATION = "ssl.truststore.location";
|
||||
public static final String SSL_TRUSTSTORE_LOCATION = "ssl.truststore.location";
|
||||
private static final String SSL_TRUSTSTORE_LOCATION_DEFAULT = "";
|
||||
|
||||
private static final String SSL_TRUSTSTORE_PASS = "ssl.truststore.pass";
|
||||
public static final String SSL_TRUSTSTORE_PASS = "ssl.truststore.pass";
|
||||
private static final String SSL_TRUSTSTORE_PASS_DEFAULT = "";
|
||||
|
||||
private static final String SSL_TRUSTSTORE_TYPE = "ssl.truststore.type";
|
||||
public static final String SSL_TRUSTSTORE_TYPE = "ssl.truststore.type";
|
||||
private static final String SSL_TRUSTSTORE_TYPE_DEFAULT = "JKS";
|
||||
|
||||
static final Set<String> OPTION_NAMES = new LinkedHashSet<>(Arrays.asList(SSL, SSL_PROTOCOL,
|
||||
|
@ -63,7 +63,6 @@ class SslConfig {
|
|||
private final SSLContext sslContext;
|
||||
|
||||
SslConfig(Properties settings) {
|
||||
// ssl
|
||||
enabled = StringUtils.parseBoolean(settings.getProperty(SSL, SSL_DEFAULT));
|
||||
protocol = settings.getProperty(SSL_PROTOCOL, SSL_PROTOCOL_DEFAULT);
|
||||
keystoreLocation = settings.getProperty(SSL_KEYSTORE_LOCATION, SSL_KEYSTORE_LOCATION_DEFAULT);
|
||||
|
|
|
@ -33,12 +33,14 @@ public final class UriUtils {
|
|||
// check if URI can be parsed correctly without adding scheme
|
||||
// if the connection string is in format host:port or just host, the host is going to be null
|
||||
// if the connection string contains IPv6 localhost [::1] the parsing will fail
|
||||
URISyntaxException firstException = null;
|
||||
try {
|
||||
uri = new URI(connectionString);
|
||||
if (uri.getHost() == null || uri.getScheme() == null) {
|
||||
uri = null;
|
||||
}
|
||||
} catch (URISyntaxException e) {
|
||||
firstException = e;
|
||||
uri = null;
|
||||
}
|
||||
|
||||
|
@ -47,7 +49,12 @@ public final class UriUtils {
|
|||
try {
|
||||
return new URI("http://" + connectionString);
|
||||
} catch (URISyntaxException e) {
|
||||
throw new IllegalArgumentException("Invalid connection configuration [" + connectionString + "]: " + e.getMessage(), e);
|
||||
IllegalArgumentException ie =
|
||||
new IllegalArgumentException("Invalid connection configuration [" + connectionString + "]: " + e.getMessage(), e);
|
||||
if (firstException != null) {
|
||||
ie.addSuppressed(firstException);
|
||||
}
|
||||
throw ie;
|
||||
}
|
||||
} else {
|
||||
// We managed to parse URI and all necessary pieces are present, let's make sure the scheme is correct
|
||||
|
@ -70,4 +77,3 @@ public final class UriUtils {
|
|||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -1,6 +0,0 @@
|
|||
grant {
|
||||
// Required for the net client to setup ssl rather than use global ssl.
|
||||
permission java.lang.RuntimePermission "setFactory";
|
||||
// Required to connect to the test ssl server.
|
||||
permission java.net.SocketPermission "*", "connect,resolve";
|
||||
};
|
|
@ -83,8 +83,8 @@ public class CliFixture {
|
|||
try {
|
||||
println("accepting on localhost:" + server.getLocalPort());
|
||||
Socket s = server.accept();
|
||||
String url = new BufferedReader(new InputStreamReader(s.getInputStream(), StandardCharsets.UTF_8)).readLine();
|
||||
if (url == null || url.isEmpty()) {
|
||||
String line = new BufferedReader(new InputStreamReader(s.getInputStream(), StandardCharsets.UTF_8)).readLine();
|
||||
if (line == null || line.isEmpty()) {
|
||||
continue;
|
||||
}
|
||||
List<String> command = new ArrayList<>();
|
||||
|
@ -100,7 +100,7 @@ public class CliFixture {
|
|||
command.add("-Dorg.jline.terminal.dumb=true");
|
||||
command.add("-jar");
|
||||
command.add(cliJar.toString());
|
||||
command.addAll(Arrays.asList(url.split(" ")));
|
||||
command.addAll(Arrays.asList(line.split(" ")));
|
||||
ProcessBuilder cliBuilder = new ProcessBuilder(command);
|
||||
cliBuilder.redirectErrorStream(true);
|
||||
Process process = cliBuilder.start();
|
||||
|
|
Loading…
Reference in New Issue