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 {
|
integTestCluster {
|
||||||
waitCondition = { node, ant ->
|
waitCondition = { node, ant ->
|
||||||
File tmpFile = new File(node.cwd, 'wait.success')
|
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.common.settings.Settings;
|
||||||
import org.elasticsearch.xpack.qa.sql.cli.ErrorsTestCase;
|
import org.elasticsearch.xpack.qa.sql.cli.ErrorsTestCase;
|
||||||
|
import org.elasticsearch.xpack.qa.sql.cli.RemoteCli.SecurityConfig;
|
||||||
|
|
||||||
public class CliErrorsIT extends ErrorsTestCase {
|
public class CliErrorsIT extends ErrorsTestCase {
|
||||||
@Override
|
@Override
|
||||||
|
@ -15,7 +16,12 @@ public class CliErrorsIT extends ErrorsTestCase {
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
protected String esUrlPrefix() {
|
protected String getProtocol() {
|
||||||
return CliSecurityIT.adminEsUrlPrefix();
|
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.common.settings.Settings;
|
||||||
import org.elasticsearch.xpack.qa.sql.cli.FetchSizeTestCase;
|
import org.elasticsearch.xpack.qa.sql.cli.FetchSizeTestCase;
|
||||||
|
import org.elasticsearch.xpack.qa.sql.cli.RemoteCli.SecurityConfig;
|
||||||
|
|
||||||
public class CliFetchSizeIT extends FetchSizeTestCase {
|
public class CliFetchSizeIT extends FetchSizeTestCase {
|
||||||
@Override
|
@Override
|
||||||
|
@ -15,7 +16,12 @@ public class CliFetchSizeIT extends FetchSizeTestCase {
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
protected String esUrlPrefix() {
|
protected String getProtocol() {
|
||||||
return CliSecurityIT.adminEsUrlPrefix();
|
return RestSqlIT.SSL_ENABLED ? "https" : "http";
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected SecurityConfig securityConfig() {
|
||||||
|
return CliSecurityIT.adminSecurityConfig();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -6,8 +6,12 @@
|
||||||
package org.elasticsearch.xpack.qa.sql.security;
|
package org.elasticsearch.xpack.qa.sql.security;
|
||||||
|
|
||||||
import org.elasticsearch.common.CheckedConsumer;
|
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;
|
||||||
|
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.ArrayList;
|
||||||
import java.util.Iterator;
|
import java.util.Iterator;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
@ -19,18 +23,43 @@ import static org.hamcrest.Matchers.containsString;
|
||||||
import static org.hamcrest.Matchers.startsWith;
|
import static org.hamcrest.Matchers.startsWith;
|
||||||
|
|
||||||
public class CliSecurityIT extends SqlSecurityTestCase {
|
public class CliSecurityIT extends SqlSecurityTestCase {
|
||||||
static final String NO_INIT_CONNECTION_CHECK_PREFIX = "-c false ";
|
static SecurityConfig adminSecurityConfig() {
|
||||||
static String adminEsUrlPrefix() {
|
String keystoreLocation;
|
||||||
return "test_admin:x-pack-test-password@";
|
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.
|
* Perform security test actions using the CLI.
|
||||||
*/
|
*/
|
||||||
private static class CliActions implements Actions {
|
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
|
@Override
|
||||||
public void queryWorksAsAdmin() throws Exception {
|
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"));
|
assertThat(cli.command("SELECT * FROM test ORDER BY a"), containsString("a | b | c"));
|
||||||
assertEquals("---------------+---------------+---------------", cli.readLine());
|
assertEquals("---------------+---------------+---------------", cli.readLine());
|
||||||
assertThat(cli.readLine(), containsString("1 |2 |3"));
|
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,
|
public void expectMatchesAdmin(String adminSql, String user, String userSql,
|
||||||
CheckedConsumer<RemoteCli, Exception> customizer) throws Exception {
|
CheckedConsumer<RemoteCli, Exception> customizer) throws Exception {
|
||||||
List<String> adminResult = new ArrayList<>();
|
List<String> adminResult = new ArrayList<>();
|
||||||
try (RemoteCli cli = new RemoteCli(adminEsUrlPrefix() + elasticsearchAddress())) {
|
try (RemoteCli cli = new RemoteCli(elasticsearchAddress(), true, adminSecurityConfig())) {
|
||||||
customizer.accept(cli);
|
customizer.accept(cli);
|
||||||
adminResult.add(cli.command(adminSql));
|
adminResult.add(cli.command(adminSql));
|
||||||
String line;
|
String line;
|
||||||
|
@ -68,7 +97,7 @@ public class CliSecurityIT extends SqlSecurityTestCase {
|
||||||
}
|
}
|
||||||
|
|
||||||
Iterator<String> expected = adminResult.iterator();
|
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);
|
customizer.accept(cli);
|
||||||
assertTrue(expected.hasNext());
|
assertTrue(expected.hasNext());
|
||||||
assertEquals(expected.next(), cli.command(userSql));
|
assertEquals(expected.next(), cli.command(userSql));
|
||||||
|
@ -86,7 +115,7 @@ public class CliSecurityIT extends SqlSecurityTestCase {
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void expectDescribe(Map<String, String> columns, String user) throws Exception {
|
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"));
|
assertThat(cli.command("DESCRIBE test"), containsString("column | type"));
|
||||||
assertEquals("---------------+---------------", cli.readLine());
|
assertEquals("---------------+---------------", cli.readLine());
|
||||||
for (Map.Entry<String, String> column : columns.entrySet()) {
|
for (Map.Entry<String, String> column : columns.entrySet()) {
|
||||||
|
@ -98,7 +127,7 @@ public class CliSecurityIT extends SqlSecurityTestCase {
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void expectShowTables(List<String> tables, String user) throws Exception {
|
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"));
|
assertThat(cli.command("SHOW TABLES"), containsString("table"));
|
||||||
assertEquals("---------------", cli.readLine());
|
assertEquals("---------------", cli.readLine());
|
||||||
for (String table : tables) {
|
for (String table : tables) {
|
||||||
|
@ -110,7 +139,7 @@ public class CliSecurityIT extends SqlSecurityTestCase {
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void expectUnknownIndex(String user, String sql) throws Exception {
|
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.command(sql), containsString("Bad request"));
|
||||||
assertThat(cli.readLine(), containsString("Unknown index"));
|
assertThat(cli.readLine(), containsString("Unknown index"));
|
||||||
}
|
}
|
||||||
|
@ -118,26 +147,22 @@ public class CliSecurityIT extends SqlSecurityTestCase {
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void expectForbidden(String user, String sql) throws Exception {
|
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 + "]"));
|
assertThat(cli.command(sql), containsString("is unauthorized for user [" + user + "]"));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void expectUnknownColumn(String user, String sql, String column) throws Exception {
|
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.command(sql), containsString("[1;31mBad request"));
|
||||||
assertThat(cli.readLine(), containsString("Unknown column [" + column + "][1;23;31m][0m"));
|
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() {
|
public CliSecurityIT() {
|
||||||
|
|
|
@ -6,6 +6,7 @@
|
||||||
package org.elasticsearch.xpack.qa.sql.security;
|
package org.elasticsearch.xpack.qa.sql.security;
|
||||||
|
|
||||||
import org.elasticsearch.common.settings.Settings;
|
import org.elasticsearch.common.settings.Settings;
|
||||||
|
import org.elasticsearch.xpack.qa.sql.cli.RemoteCli.SecurityConfig;
|
||||||
import org.elasticsearch.xpack.qa.sql.cli.SelectTestCase;
|
import org.elasticsearch.xpack.qa.sql.cli.SelectTestCase;
|
||||||
|
|
||||||
public class CliSelectIT extends SelectTestCase {
|
public class CliSelectIT extends SelectTestCase {
|
||||||
|
@ -15,7 +16,12 @@ public class CliSelectIT extends SelectTestCase {
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
protected String esUrlPrefix() {
|
protected String getProtocol() {
|
||||||
return CliSecurityIT.adminEsUrlPrefix();
|
return RestSqlIT.SSL_ENABLED ? "https" : "http";
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected SecurityConfig securityConfig() {
|
||||||
|
return CliSecurityIT.adminSecurityConfig();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -6,6 +6,7 @@
|
||||||
package org.elasticsearch.xpack.qa.sql.security;
|
package org.elasticsearch.xpack.qa.sql.security;
|
||||||
|
|
||||||
import org.elasticsearch.common.settings.Settings;
|
import org.elasticsearch.common.settings.Settings;
|
||||||
|
import org.elasticsearch.xpack.qa.sql.cli.RemoteCli.SecurityConfig;
|
||||||
import org.elasticsearch.xpack.qa.sql.cli.ShowTestCase;
|
import org.elasticsearch.xpack.qa.sql.cli.ShowTestCase;
|
||||||
|
|
||||||
public class CliShowIT extends ShowTestCase {
|
public class CliShowIT extends ShowTestCase {
|
||||||
|
@ -15,7 +16,12 @@ public class CliShowIT extends ShowTestCase {
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
protected String esUrlPrefix() {
|
protected String getProtocol() {
|
||||||
return CliSecurityIT.adminEsUrlPrefix();
|
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();
|
return RestSqlIT.securitySettings();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected String getProtocol() {
|
||||||
|
return RestSqlIT.SSL_ENABLED ? "https" : "http";
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
protected Properties connectionProperties() {
|
protected Properties connectionProperties() {
|
||||||
Properties properties = super.connectionProperties();
|
Properties properties = super.connectionProperties();
|
||||||
|
|
|
@ -20,6 +20,11 @@ public class JdbcCsvSpecIT extends CsvSpecTestCase {
|
||||||
return RestSqlIT.securitySettings();
|
return RestSqlIT.securitySettings();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected String getProtocol() {
|
||||||
|
return RestSqlIT.SSL_ENABLED ? "https" : "http";
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
protected Properties connectionProperties() {
|
protected Properties connectionProperties() {
|
||||||
Properties sp = super.connectionProperties();
|
Properties sp = super.connectionProperties();
|
||||||
|
|
|
@ -16,6 +16,11 @@ public class JdbcDatabaseMetaDataIT extends DatabaseMetaDataTestCase {
|
||||||
return RestSqlIT.securitySettings();
|
return RestSqlIT.securitySettings();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected String getProtocol() {
|
||||||
|
return RestSqlIT.SSL_ENABLED ? "https" : "http";
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
protected Properties connectionProperties() {
|
protected Properties connectionProperties() {
|
||||||
Properties properties = super.connectionProperties();
|
Properties properties = super.connectionProperties();
|
||||||
|
|
|
@ -16,6 +16,11 @@ public class JdbcErrorsIT extends ErrorsTestCase {
|
||||||
return RestSqlIT.securitySettings();
|
return RestSqlIT.securitySettings();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected String getProtocol() {
|
||||||
|
return RestSqlIT.SSL_ENABLED ? "https" : "http";
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
protected Properties connectionProperties() {
|
protected Properties connectionProperties() {
|
||||||
Properties properties = super.connectionProperties();
|
Properties properties = super.connectionProperties();
|
||||||
|
|
|
@ -16,6 +16,11 @@ public class JdbcFetchSizeIT extends FetchSizeTestCase {
|
||||||
return RestSqlIT.securitySettings();
|
return RestSqlIT.securitySettings();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected String getProtocol() {
|
||||||
|
return RestSqlIT.SSL_ENABLED ? "https" : "http";
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
protected Properties connectionProperties() {
|
protected Properties connectionProperties() {
|
||||||
Properties properties = super.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.action.admin.indices.get.GetIndexAction;
|
||||||
import org.elasticsearch.common.CheckedConsumer;
|
import org.elasticsearch.common.CheckedConsumer;
|
||||||
import org.elasticsearch.common.CheckedFunction;
|
import org.elasticsearch.common.CheckedFunction;
|
||||||
|
import org.elasticsearch.common.io.PathUtils;
|
||||||
import org.elasticsearch.xpack.qa.sql.jdbc.LocalH2;
|
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.Connection;
|
||||||
import java.sql.DriverManager;
|
import java.sql.DriverManager;
|
||||||
import java.sql.ResultSet;
|
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.JdbcAssert.assertResultSets;
|
||||||
import static org.elasticsearch.xpack.qa.sql.jdbc.JdbcIntegrationTestCase.elasticsearchAddress;
|
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.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.contains;
|
||||||
import static org.hamcrest.Matchers.containsString;
|
import static org.hamcrest.Matchers.containsString;
|
||||||
|
|
||||||
|
@ -31,6 +36,7 @@ public class JdbcSecurityIT extends SqlSecurityTestCase {
|
||||||
properties.put("user", "test_admin");
|
properties.put("user", "test_admin");
|
||||||
properties.put("password", "x-pack-test-password");
|
properties.put("password", "x-pack-test-password");
|
||||||
// end::admin_properties
|
// end::admin_properties
|
||||||
|
addSslPropertiesIfNeeded(properties);
|
||||||
return properties;
|
return properties;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -38,7 +44,8 @@ public class JdbcSecurityIT extends SqlSecurityTestCase {
|
||||||
Properties props = new Properties();
|
Properties props = new Properties();
|
||||||
props.put("timezone", randomKnownTimeZone());
|
props.put("timezone", randomKnownTimeZone());
|
||||||
props.putAll(properties);
|
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) {
|
static Properties userProperties(String user) {
|
||||||
|
@ -48,9 +55,32 @@ public class JdbcSecurityIT extends SqlSecurityTestCase {
|
||||||
Properties prop = new Properties();
|
Properties prop = new Properties();
|
||||||
prop.put("user", user);
|
prop.put("user", user);
|
||||||
prop.put("password", "testpass");
|
prop.put("password", "testpass");
|
||||||
|
addSslPropertiesIfNeeded(prop);
|
||||||
return 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,
|
static void expectActionMatchesAdmin(CheckedFunction<Connection, ResultSet, SQLException> adminAction,
|
||||||
String user, CheckedFunction<Connection, ResultSet, SQLException> userAction) throws Exception {
|
String user, CheckedFunction<Connection, ResultSet, SQLException> userAction) throws Exception {
|
||||||
try (Connection adminConnection = es(adminProperties());
|
try (Connection adminConnection = es(adminProperties());
|
||||||
|
|
|
@ -16,6 +16,11 @@ public class JdbcShowTablesIT extends ShowTablesTestCase {
|
||||||
return RestSqlIT.securitySettings();
|
return RestSqlIT.securitySettings();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected String getProtocol() {
|
||||||
|
return RestSqlIT.SSL_ENABLED ? "https" : "http";
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
protected Properties connectionProperties() {
|
protected Properties connectionProperties() {
|
||||||
Properties sp = super.connectionProperties();
|
Properties sp = super.connectionProperties();
|
||||||
|
|
|
@ -16,6 +16,11 @@ public class JdbcSimpleExampleIT extends SimpleExampleTestCase {
|
||||||
return RestSqlIT.securitySettings();
|
return RestSqlIT.securitySettings();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected String getProtocol() {
|
||||||
|
return RestSqlIT.SSL_ENABLED ? "https" : "http";
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
protected Properties connectionProperties() {
|
protected Properties connectionProperties() {
|
||||||
Properties properties = super.connectionProperties();
|
Properties properties = super.connectionProperties();
|
||||||
|
|
|
@ -20,6 +20,11 @@ public class JdbcSqlSpecIT extends SqlSpecTestCase {
|
||||||
return RestSqlIT.securitySettings();
|
return RestSqlIT.securitySettings();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected String getProtocol() {
|
||||||
|
return RestSqlIT.SSL_ENABLED ? "https" : "http";
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
protected Properties connectionProperties() {
|
protected Properties connectionProperties() {
|
||||||
Properties sp = super.connectionProperties();
|
Properties sp = super.connectionProperties();
|
||||||
|
|
|
@ -5,27 +5,54 @@
|
||||||
*/
|
*/
|
||||||
package org.elasticsearch.xpack.qa.sql.security;
|
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.SecureString;
|
||||||
import org.elasticsearch.common.settings.Settings;
|
import org.elasticsearch.common.settings.Settings;
|
||||||
import org.elasticsearch.common.util.concurrent.ThreadContext;
|
import org.elasticsearch.common.util.concurrent.ThreadContext;
|
||||||
|
import org.elasticsearch.test.rest.ESRestTestCase;
|
||||||
import org.elasticsearch.xpack.qa.sql.rest.RestSqlTestCase;
|
import org.elasticsearch.xpack.qa.sql.rest.RestSqlTestCase;
|
||||||
|
|
||||||
import static org.elasticsearch.xpack.security.authc.support.UsernamePasswordToken.basicAuthHeaderValue;
|
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
|
* Integration test for the rest sql action. The one that speaks json directly to a
|
||||||
* user rather than to the JDBC driver or CLI.
|
* user rather than to the JDBC driver or CLI.
|
||||||
*/
|
*/
|
||||||
public class RestSqlIT extends RestSqlTestCase {
|
public class RestSqlIT extends RestSqlTestCase {
|
||||||
|
static final boolean SSL_ENABLED = Booleans.parseBoolean(System.getProperty("tests.ssl.enabled"));
|
||||||
|
|
||||||
static Settings securitySettings() {
|
static Settings securitySettings() {
|
||||||
String token = basicAuthHeaderValue("test_admin", new SecureString("x-pack-test-password".toCharArray()));
|
String token = basicAuthHeaderValue("test_admin", new SecureString("x-pack-test-password".toCharArray()));
|
||||||
return Settings.builder()
|
Settings.Builder builder = Settings.builder()
|
||||||
.put(ThreadContext.PREFIX + ".Authorization", token)
|
.put(ThreadContext.PREFIX + ".Authorization", token);
|
||||||
.build();
|
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
|
@Override
|
||||||
protected Settings restClientSettings() {
|
protected Settings restClientSettings() {
|
||||||
return securitySettings();
|
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 java.util.Collections.singletonMap;
|
||||||
import static org.elasticsearch.xpack.qa.sql.rest.RestSqlTestCase.columnInfo;
|
import static org.elasticsearch.xpack.qa.sql.rest.RestSqlTestCase.columnInfo;
|
||||||
import static java.util.Collections.emptyMap;
|
|
||||||
import static java.util.Collections.singletonList;
|
import static java.util.Collections.singletonList;
|
||||||
import static org.hamcrest.Matchers.containsString;
|
import static org.hamcrest.Matchers.containsString;
|
||||||
import static org.hamcrest.Matchers.empty;
|
import static org.hamcrest.Matchers.empty;
|
||||||
|
|
|
@ -49,6 +49,8 @@ import static org.hamcrest.Matchers.hasItems;
|
||||||
public abstract class SqlSecurityTestCase extends ESRestTestCase {
|
public abstract class SqlSecurityTestCase extends ESRestTestCase {
|
||||||
/**
|
/**
|
||||||
* Actions taken by this test.
|
* Actions taken by this test.
|
||||||
|
* <p>
|
||||||
|
* For methods that take {@code user} a {@code null} user means "use the admin".
|
||||||
*/
|
*/
|
||||||
protected interface Actions {
|
protected interface Actions {
|
||||||
void queryWorksAsAdmin() throws Exception;
|
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 {
|
public void testQueryWorksAsAdmin() throws Exception {
|
||||||
actions.queryWorksAsAdmin();
|
actions.queryWorksAsAdmin();
|
||||||
new AuditLogAsserter()
|
new AuditLogAsserter()
|
||||||
|
|
|
@ -1,4 +1,8 @@
|
||||||
grant {
|
grant {
|
||||||
// Needed to read the audit log file
|
// Needed to read the audit log file
|
||||||
permission java.io.FilePermission "${tests.audit.logfile}", "read";
|
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 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.nio.charset.StandardCharsets
|
||||||
|
import java.security.KeyStore
|
||||||
|
import java.security.SecureRandom
|
||||||
|
|
||||||
String outputDir = "generated-resources/${project.name}"
|
// Tell the tests we're running with ssl enabled
|
||||||
task copyTestNodeKeystore(type: Copy) {
|
integTestRunner {
|
||||||
from project(':x-pack-elasticsearch:plugin')
|
systemProperty 'tests.ssl.enabled', 'true'
|
||||||
.file('src/test/resources/org/elasticsearch/xpack/security/transport/ssl/certs/simple/testnode.jks')
|
|
||||||
into outputDir
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 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 {
|
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
|
// Override the wait condition to work properly with security and SSL
|
||||||
waitCondition = { NodeInfo node, AntBuilder ant ->
|
waitCondition = { NodeInfo node, AntBuilder ant ->
|
||||||
File tmpFile = new File(node.cwd, 'wait.success')
|
File tmpFile = new File(node.cwd, 'wait.success')
|
||||||
|
KeyStore keyStore = KeyStore.getInstance("JKS");
|
||||||
// wait up to two minutes
|
keyStore.load(clientKeyStore.newInputStream(), 'keypass'.toCharArray());
|
||||||
final long stopTime = System.currentTimeMillis() + (2 * 60000L);
|
KeyManagerFactory kmf = KeyManagerFactory.getInstance(KeyManagerFactory.getDefaultAlgorithm());
|
||||||
Exception lastException = null;
|
kmf.init(keyStore, 'keypass'.toCharArray());
|
||||||
|
TrustManagerFactory tmf = TrustManagerFactory.getInstance(TrustManagerFactory.getDefaultAlgorithm());
|
||||||
while (System.currentTimeMillis() < stopTime) {
|
tmf.init(keyStore);
|
||||||
lastException = null;
|
SSLContext sslContext = SSLContext.getInstance("TLSv1.2");
|
||||||
// we use custom wait logic here as the elastic user is not available immediately and ant.get will fail when a 401 is returned
|
sslContext.init(kmf.getKeyManagers(), tmf.getTrustManagers(), new SecureRandom());
|
||||||
HttpURLConnection httpURLConnection = null;
|
for (int i = 0; i < 10; i++) {
|
||||||
try {
|
// we use custom wait logic here for HTTPS
|
||||||
httpURLConnection = (HttpURLConnection) new URL("http://${node.httpUri()}/_cluster/health?wait_for_nodes=${numNodes}&wait_for_status=yellow").openConnection();
|
HttpsURLConnection httpURLConnection = null;
|
||||||
httpURLConnection.setRequestProperty("Authorization", "Basic " +
|
try {
|
||||||
Base64.getEncoder().encodeToString("test_admin:x-pack-test-password".getBytes(StandardCharsets.UTF_8)));
|
httpURLConnection = (HttpsURLConnection) new URL("https://${node.httpUri()}/_cluster/health?wait_for_nodes=${numNodes}&wait_for_status=yellow").openConnection();
|
||||||
httpURLConnection.setRequestMethod("GET");
|
httpURLConnection.setSSLSocketFactory(sslContext.getSocketFactory());
|
||||||
httpURLConnection.setConnectTimeout(1000);
|
httpURLConnection.setRequestProperty("Authorization", "Basic " +
|
||||||
httpURLConnection.setReadTimeout(30000); // read needs to wait for nodes!
|
Base64.getEncoder().encodeToString("test_admin:x-pack-test-password".getBytes(StandardCharsets.UTF_8)));
|
||||||
httpURLConnection.connect();
|
httpURLConnection.setRequestMethod("GET");
|
||||||
if (httpURLConnection.getResponseCode() == 200) {
|
httpURLConnection.connect();
|
||||||
tmpFile.withWriter StandardCharsets.UTF_8.name(), {
|
if (httpURLConnection.getResponseCode() == 200) {
|
||||||
it.write(httpURLConnection.getInputStream().getText(StandardCharsets.UTF_8.name()))
|
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();
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
} 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
|
// did not start, so wait a bit before trying again
|
||||||
Thread.sleep(500L);
|
Thread.sleep(500L);
|
||||||
}
|
|
||||||
if (tmpFile.exists() == false && lastException != null) {
|
|
||||||
logger.error("final attempt of calling cluster health failed", lastException)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return tmpFile.exists()
|
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.common.xcontent.json.JsonXContent;
|
||||||
import org.elasticsearch.test.rest.ESRestTestCase;
|
import org.elasticsearch.test.rest.ESRestTestCase;
|
||||||
import org.elasticsearch.transport.client.PreBuiltTransportClient;
|
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.embed.CliHttpServer;
|
||||||
import org.elasticsearch.xpack.qa.sql.rest.RestSqlTestCase;
|
import org.elasticsearch.xpack.qa.sql.rest.RestSqlTestCase;
|
||||||
import org.junit.After;
|
import org.junit.After;
|
||||||
|
@ -65,7 +66,7 @@ public abstract class CliIntegrationTestCase extends ESRestTestCase {
|
||||||
*/
|
*/
|
||||||
@Before
|
@Before
|
||||||
public void startCli() throws IOException {
|
public void startCli() throws IOException {
|
||||||
cli = new RemoteCli(esUrlPrefix() + ES.get());
|
cli = new RemoteCli(ES.get(), true, securityConfig());
|
||||||
}
|
}
|
||||||
|
|
||||||
@After
|
@After
|
||||||
|
@ -79,11 +80,10 @@ public abstract class CliIntegrationTestCase extends ESRestTestCase {
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Prefix to the Elasticsearch URL. Override to add
|
* Override to add security configuration to the cli.
|
||||||
* authentication support.
|
|
||||||
*/
|
*/
|
||||||
protected String esUrlPrefix() {
|
protected SecurityConfig securityConfig() {
|
||||||
return "";
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
protected void index(String index, CheckedConsumer<XContentBuilder, IOException> body) throws IOException {
|
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.apache.logging.log4j.Logger;
|
||||||
import org.elasticsearch.SpecialPermission;
|
import org.elasticsearch.SpecialPermission;
|
||||||
|
import org.elasticsearch.common.Nullable;
|
||||||
import org.elasticsearch.common.logging.Loggers;
|
import org.elasticsearch.common.logging.Loggers;
|
||||||
|
|
||||||
import java.io.BufferedReader;
|
import java.io.BufferedReader;
|
||||||
|
@ -23,7 +24,9 @@ import java.security.AccessController;
|
||||||
import java.security.PrivilegedAction;
|
import java.security.PrivilegedAction;
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
import java.util.List;
|
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.empty;
|
||||||
import static org.hamcrest.Matchers.endsWith;
|
import static org.hamcrest.Matchers.endsWith;
|
||||||
import static org.hamcrest.Matchers.not;
|
import static org.hamcrest.Matchers.not;
|
||||||
|
@ -57,7 +60,9 @@ public class RemoteCli implements Closeable {
|
||||||
private final PrintWriter out;
|
private final PrintWriter out;
|
||||||
private final BufferedReader in;
|
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();
|
SecurityManager sm = System.getSecurityManager();
|
||||||
if (sm != null) {
|
if (sm != null) {
|
||||||
sm.checkPermission(new SpecialPermission());
|
sm.checkPermission(new SpecialPermission());
|
||||||
|
@ -75,10 +80,39 @@ public class RemoteCli implements Closeable {
|
||||||
});
|
});
|
||||||
logger.info("connected");
|
logger.info("connected");
|
||||||
socket.setSoTimeout(10000);
|
socket.setSoTimeout(10000);
|
||||||
|
|
||||||
out = new PrintWriter(new OutputStreamWriter(socket.getOutputStream(), StandardCharsets.UTF_8), true);
|
out = new PrintWriter(new OutputStreamWriter(socket.getOutputStream(), StandardCharsets.UTF_8), true);
|
||||||
out.println(elasticsearchAddress);
|
|
||||||
in = new BufferedReader(new InputStreamReader(socket.getInputStream(), StandardCharsets.UTF_8));
|
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
|
// Throw out the logo and warnings about making a dumb terminal
|
||||||
while (false == readLine().contains("SQL"));
|
while (false == readLine().contains("SQL"));
|
||||||
// Throw out the empty line before all the good stuff
|
// Throw out the empty line before all the good stuff
|
||||||
|
@ -136,4 +170,64 @@ public class RemoteCli implements Closeable {
|
||||||
logger.info("in : {}", line);
|
logger.info("in : {}", line);
|
||||||
return 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.
|
// JDBC only supports a single node at a time so we just give it one.
|
||||||
return cluster.split(",")[0];
|
return cluster.split(",")[0];
|
||||||
/* This doesn't include "jdbc:es://" because we want the example in
|
/* 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 {
|
public Connection esJdbc() throws SQLException {
|
||||||
|
@ -81,8 +82,9 @@ public abstract class JdbcIntegrationTestCase extends ESRestTestCase {
|
||||||
}
|
}
|
||||||
|
|
||||||
protected Connection useDriverManager() throws SQLException {
|
protected Connection useDriverManager() throws SQLException {
|
||||||
|
String elasticsearchAddress = getProtocol() + "://" + elasticsearchAddress();
|
||||||
// tag::connect-dm
|
// tag::connect-dm
|
||||||
String address = "jdbc:es://" + elasticsearchAddress(); // <1>
|
String address = "jdbc:es://" + elasticsearchAddress; // <1>
|
||||||
Properties connectionProperties = connectionProperties(); // <2>
|
Properties connectionProperties = connectionProperties(); // <2>
|
||||||
Connection connection = DriverManager.getConnection(address, connectionProperties);
|
Connection connection = DriverManager.getConnection(address, connectionProperties);
|
||||||
// end::connect-dm
|
// end::connect-dm
|
||||||
|
@ -91,9 +93,10 @@ public abstract class JdbcIntegrationTestCase extends ESRestTestCase {
|
||||||
}
|
}
|
||||||
|
|
||||||
protected Connection useDataSource() throws SQLException {
|
protected Connection useDataSource() throws SQLException {
|
||||||
|
String elasticsearchAddress = getProtocol() + "://" + elasticsearchAddress();
|
||||||
// tag::connect-ds
|
// tag::connect-ds
|
||||||
JdbcDataSource dataSource = new JdbcDataSource();
|
JdbcDataSource dataSource = new JdbcDataSource();
|
||||||
String address = "jdbc:es://" + elasticsearchAddress(); // <1>
|
String address = "jdbc:es://" + elasticsearchAddress; // <1>
|
||||||
dataSource.setUrl(address);
|
dataSource.setUrl(address);
|
||||||
Properties connectionProperties = connectionProperties(); // <2>
|
Properties connectionProperties = connectionProperties(); // <2>
|
||||||
dataSource.setProperties(connectionProperties);
|
dataSource.setProperties(connectionProperties);
|
||||||
|
|
|
@ -26,12 +26,16 @@ import org.elasticsearch.xpack.sql.client.shared.Version;
|
||||||
|
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
import java.net.ConnectException;
|
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.Arrays;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.logging.LogManager;
|
import java.util.logging.LogManager;
|
||||||
|
|
||||||
public class Cli extends Command {
|
public class Cli extends Command {
|
||||||
private final OptionSpec<Boolean> debugOption;
|
private final OptionSpec<Boolean> debugOption;
|
||||||
|
private final OptionSpec<String> keystoreLocation;
|
||||||
private final OptionSpec<Boolean> checkOption;
|
private final OptionSpec<Boolean> checkOption;
|
||||||
private final OptionSpec<String> connectionString;
|
private final OptionSpec<String> connectionString;
|
||||||
|
|
||||||
|
@ -41,6 +45,12 @@ public class Cli extends Command {
|
||||||
"Enable debug logging")
|
"Enable debug logging")
|
||||||
.withRequiredArg().ofType(Boolean.class)
|
.withRequiredArg().ofType(Boolean.class)
|
||||||
.defaultsTo(Boolean.parseBoolean(System.getProperty("cli.debug", "false")));
|
.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"),
|
this.checkOption = parser.acceptsAll(Arrays.asList("c", "check"),
|
||||||
"Enable initial connection check on startup")
|
"Enable initial connection check on startup")
|
||||||
.withRequiredArg().ofType(Boolean.class)
|
.withRequiredArg().ofType(Boolean.class)
|
||||||
|
@ -77,15 +87,21 @@ public class Cli extends Command {
|
||||||
@Override
|
@Override
|
||||||
protected void execute(org.elasticsearch.cli.Terminal terminal, OptionSet options) throws Exception {
|
protected void execute(org.elasticsearch.cli.Terminal terminal, OptionSet options) throws Exception {
|
||||||
boolean debug = debugOption.value(options);
|
boolean debug = debugOption.value(options);
|
||||||
boolean check = checkOption.value(options);
|
boolean checkConnection = checkOption.value(options);
|
||||||
List<String> args = connectionString.values(options);
|
List<String> args = connectionString.values(options);
|
||||||
if (args.size() > 1) {
|
if (args.size() > 1) {
|
||||||
throw new UserException(ExitCodes.USAGE, "expecting a single uri");
|
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(
|
CliCommand cliCommand = new CliCommands(
|
||||||
new PrintLogoCommand(),
|
new PrintLogoCommand(),
|
||||||
new ClearScreenCliCommand(),
|
new ClearScreenCliCommand(),
|
||||||
|
@ -96,10 +112,10 @@ public class Cli extends Command {
|
||||||
);
|
);
|
||||||
try (CliTerminal cliTerminal = new JLineTerminal()) {
|
try (CliTerminal cliTerminal = new JLineTerminal()) {
|
||||||
ConnectionBuilder connectionBuilder = new ConnectionBuilder(cliTerminal);
|
ConnectionBuilder connectionBuilder = new ConnectionBuilder(cliTerminal);
|
||||||
ConnectionConfiguration con = connectionBuilder.buildConnection(uri);
|
ConnectionConfiguration con = connectionBuilder.buildConnection(uri, keystoreLocation);
|
||||||
CliSession cliSession = new CliSession(new CliHttpClient(con));
|
CliSession cliSession = new CliSession(new CliHttpClient(con));
|
||||||
cliSession.setDebug(debug);
|
cliSession.setDebug(debug);
|
||||||
if (check) {
|
if (checkConnection) {
|
||||||
checkConnection(cliSession, cliTerminal, con);
|
checkConnection(cliSession, cliTerminal, con);
|
||||||
}
|
}
|
||||||
new CliRepl(cliTerminal, cliSession, cliCommand).execute();
|
new CliRepl(cliTerminal, cliSession, cliCommand).execute();
|
||||||
|
|
|
@ -5,10 +5,14 @@
|
||||||
*/
|
*/
|
||||||
package org.elasticsearch.xpack.sql.cli;
|
package org.elasticsearch.xpack.sql.cli;
|
||||||
|
|
||||||
|
import org.elasticsearch.cli.ExitCodes;
|
||||||
import org.elasticsearch.cli.UserException;
|
import org.elasticsearch.cli.UserException;
|
||||||
import org.elasticsearch.xpack.sql.client.shared.ConnectionConfiguration;
|
import org.elasticsearch.xpack.sql.client.shared.ConnectionConfiguration;
|
||||||
|
|
||||||
import java.net.URI;
|
import java.net.URI;
|
||||||
|
import java.nio.file.Files;
|
||||||
|
import java.nio.file.Path;
|
||||||
|
import java.nio.file.Paths;
|
||||||
import java.util.Properties;
|
import java.util.Properties;
|
||||||
|
|
||||||
import static org.elasticsearch.xpack.sql.client.shared.UriUtils.parseURI;
|
import static org.elasticsearch.xpack.sql.client.shared.UriUtils.parseURI;
|
||||||
|
@ -27,14 +31,19 @@ public class ConnectionBuilder {
|
||||||
this.cliTerminal = cliTerminal;
|
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 URI uri;
|
||||||
final String connectionString;
|
final String connectionString;
|
||||||
Properties properties = new Properties();
|
Properties properties = new Properties();
|
||||||
String user = null;
|
String user = null;
|
||||||
String password = null;
|
String password = null;
|
||||||
if (arg != null) {
|
if (connectionStringArg != null) {
|
||||||
connectionString = arg;
|
connectionString = connectionStringArg;
|
||||||
uri = removeQuery(parseURI(connectionString, DEFAULT_URI), connectionString, DEFAULT_URI);
|
uri = removeQuery(parseURI(connectionString, DEFAULT_URI), connectionString, DEFAULT_URI);
|
||||||
user = uri.getUserInfo();
|
user = uri.getUserInfo();
|
||||||
if (user != null) {
|
if (user != null) {
|
||||||
|
@ -49,6 +58,29 @@ public class ConnectionBuilder {
|
||||||
connectionString = DEFAULT_CONNECTION_STRING;
|
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 (user != null) {
|
||||||
if (password == null) {
|
if (password == null) {
|
||||||
password = cliTerminal.readPassword("password: ");
|
password = cliTerminal.readPassword("password: ");
|
||||||
|
@ -57,7 +89,20 @@ public class ConnectionBuilder {
|
||||||
properties.setProperty(ConnectionConfiguration.AUTH_PASS, password);
|
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);
|
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;
|
package org.elasticsearch.xpack.sql.cli;
|
||||||
|
|
||||||
|
import org.elasticsearch.cli.UserException;
|
||||||
import org.elasticsearch.test.ESTestCase;
|
import org.elasticsearch.test.ESTestCase;
|
||||||
import org.elasticsearch.xpack.sql.client.shared.ConnectionConfiguration;
|
import org.elasticsearch.xpack.sql.client.shared.ConnectionConfiguration;
|
||||||
|
import org.elasticsearch.xpack.sql.client.shared.SslConfig;
|
||||||
import java.net.URI;
|
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.Matchers.any;
|
||||||
import static org.mockito.Mockito.mock;
|
import static org.mockito.Mockito.mock;
|
||||||
|
@ -22,7 +26,7 @@ public class ConnectionBuilderTests extends ESTestCase {
|
||||||
public void testDefaultConnection() throws Exception {
|
public void testDefaultConnection() throws Exception {
|
||||||
CliTerminal testTerminal = mock(CliTerminal.class);
|
CliTerminal testTerminal = mock(CliTerminal.class);
|
||||||
ConnectionBuilder connectionBuilder = new ConnectionBuilder(testTerminal);
|
ConnectionBuilder connectionBuilder = new ConnectionBuilder(testTerminal);
|
||||||
ConnectionConfiguration con = connectionBuilder.buildConnection(null);
|
ConnectionConfiguration con = connectionBuilder.buildConnection(null, null);
|
||||||
assertNull(con.authUser());
|
assertNull(con.authUser());
|
||||||
assertNull(con.authPass());
|
assertNull(con.authPass());
|
||||||
assertEquals("http://localhost:9200/", con.connectionString());
|
assertEquals("http://localhost:9200/", con.connectionString());
|
||||||
|
@ -38,7 +42,7 @@ public class ConnectionBuilderTests extends ESTestCase {
|
||||||
public void testBasicConnection() throws Exception {
|
public void testBasicConnection() throws Exception {
|
||||||
CliTerminal testTerminal = mock(CliTerminal.class);
|
CliTerminal testTerminal = mock(CliTerminal.class);
|
||||||
ConnectionBuilder connectionBuilder = new ConnectionBuilder(testTerminal);
|
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.authUser());
|
||||||
assertNull(con.authPass());
|
assertNull(con.authPass());
|
||||||
assertEquals("http://foobar:9242/", con.connectionString());
|
assertEquals("http://foobar:9242/", con.connectionString());
|
||||||
|
@ -49,7 +53,7 @@ public class ConnectionBuilderTests extends ESTestCase {
|
||||||
public void testUserAndPasswordConnection() throws Exception {
|
public void testUserAndPasswordConnection() throws Exception {
|
||||||
CliTerminal testTerminal = mock(CliTerminal.class);
|
CliTerminal testTerminal = mock(CliTerminal.class);
|
||||||
ConnectionBuilder connectionBuilder = new ConnectionBuilder(testTerminal);
|
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("user", con.authUser());
|
||||||
assertEquals("pass", con.authPass());
|
assertEquals("pass", con.authPass());
|
||||||
assertEquals("http://user:pass@foobar:9242/", con.connectionString());
|
assertEquals("http://user:pass@foobar:9242/", con.connectionString());
|
||||||
|
@ -61,7 +65,7 @@ public class ConnectionBuilderTests extends ESTestCase {
|
||||||
CliTerminal testTerminal = mock(CliTerminal.class);
|
CliTerminal testTerminal = mock(CliTerminal.class);
|
||||||
when(testTerminal.readPassword("password: ")).thenReturn("password");
|
when(testTerminal.readPassword("password: ")).thenReturn("password");
|
||||||
ConnectionBuilder connectionBuilder = new ConnectionBuilder(testTerminal);
|
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("user", con.authUser());
|
||||||
assertEquals("password", con.authPass());
|
assertEquals("password", con.authPass());
|
||||||
assertEquals("http://user@foobar:9242/", con.connectionString());
|
assertEquals("http://user@foobar:9242/", con.connectionString());
|
||||||
|
@ -69,4 +73,37 @@ public class ConnectionBuilderTests extends ESTestCase {
|
||||||
verify(testTerminal, times(1)).readPassword(any());
|
verify(testTerminal, times(1)).readPassword(any());
|
||||||
verifyNoMoreInteractions(testTerminal);
|
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);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -94,7 +94,7 @@ public class JdbcConfiguration extends ConnectionConfiguration {
|
||||||
|
|
||||||
private static URI parseUrl(String u) throws JdbcSQLException {
|
private static URI parseUrl(String u) throws JdbcSQLException {
|
||||||
String url = u;
|
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)) {
|
if (!canAccept(u)) {
|
||||||
throw new JdbcSQLException("Expected [" + URL_PREFIX + "] url, received [" + 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 {
|
private static String removeJdbcPrefix(String connectionString) throws JdbcSQLException {
|
||||||
if (connectionString.startsWith(URL_PREFIX)) {
|
if (connectionString.startsWith(URL_PREFIX)) {
|
||||||
return "http://" + connectionString.substring(URL_PREFIX.length());
|
return connectionString.substring(URL_PREFIX.length());
|
||||||
} else {
|
} else {
|
||||||
throw new JdbcSQLException("Expected [" + URL_PREFIX + "] url, received [" + connectionString + "]");
|
throw new JdbcSQLException("Expected [" + URL_PREFIX + "] url, received [" + connectionString + "]");
|
||||||
}
|
}
|
||||||
|
|
|
@ -26,30 +26,30 @@ import javax.net.ssl.SSLSocketFactory;
|
||||||
import javax.net.ssl.TrustManager;
|
import javax.net.ssl.TrustManager;
|
||||||
import javax.net.ssl.TrustManagerFactory;
|
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_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_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_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_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_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_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_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";
|
private static final String SSL_TRUSTSTORE_TYPE_DEFAULT = "JKS";
|
||||||
|
|
||||||
static final Set<String> OPTION_NAMES = new LinkedHashSet<>(Arrays.asList(SSL, SSL_PROTOCOL,
|
static final Set<String> OPTION_NAMES = new LinkedHashSet<>(Arrays.asList(SSL, SSL_PROTOCOL,
|
||||||
|
@ -63,7 +63,6 @@ class SslConfig {
|
||||||
private final SSLContext sslContext;
|
private final SSLContext sslContext;
|
||||||
|
|
||||||
SslConfig(Properties settings) {
|
SslConfig(Properties settings) {
|
||||||
// ssl
|
|
||||||
enabled = StringUtils.parseBoolean(settings.getProperty(SSL, SSL_DEFAULT));
|
enabled = StringUtils.parseBoolean(settings.getProperty(SSL, SSL_DEFAULT));
|
||||||
protocol = settings.getProperty(SSL_PROTOCOL, SSL_PROTOCOL_DEFAULT);
|
protocol = settings.getProperty(SSL_PROTOCOL, SSL_PROTOCOL_DEFAULT);
|
||||||
keystoreLocation = settings.getProperty(SSL_KEYSTORE_LOCATION, SSL_KEYSTORE_LOCATION_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
|
// 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 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
|
// if the connection string contains IPv6 localhost [::1] the parsing will fail
|
||||||
|
URISyntaxException firstException = null;
|
||||||
try {
|
try {
|
||||||
uri = new URI(connectionString);
|
uri = new URI(connectionString);
|
||||||
if (uri.getHost() == null || uri.getScheme() == null) {
|
if (uri.getHost() == null || uri.getScheme() == null) {
|
||||||
uri = null;
|
uri = null;
|
||||||
}
|
}
|
||||||
} catch (URISyntaxException e) {
|
} catch (URISyntaxException e) {
|
||||||
|
firstException = e;
|
||||||
uri = null;
|
uri = null;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -47,7 +49,12 @@ public final class UriUtils {
|
||||||
try {
|
try {
|
||||||
return new URI("http://" + connectionString);
|
return new URI("http://" + connectionString);
|
||||||
} catch (URISyntaxException e) {
|
} 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 {
|
} else {
|
||||||
// We managed to parse URI and all necessary pieces are present, let's make sure the scheme is correct
|
// 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 {
|
try {
|
||||||
println("accepting on localhost:" + server.getLocalPort());
|
println("accepting on localhost:" + server.getLocalPort());
|
||||||
Socket s = server.accept();
|
Socket s = server.accept();
|
||||||
String url = new BufferedReader(new InputStreamReader(s.getInputStream(), StandardCharsets.UTF_8)).readLine();
|
String line = new BufferedReader(new InputStreamReader(s.getInputStream(), StandardCharsets.UTF_8)).readLine();
|
||||||
if (url == null || url.isEmpty()) {
|
if (line == null || line.isEmpty()) {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
List<String> command = new ArrayList<>();
|
List<String> command = new ArrayList<>();
|
||||||
|
@ -100,7 +100,7 @@ public class CliFixture {
|
||||||
command.add("-Dorg.jline.terminal.dumb=true");
|
command.add("-Dorg.jline.terminal.dumb=true");
|
||||||
command.add("-jar");
|
command.add("-jar");
|
||||||
command.add(cliJar.toString());
|
command.add(cliJar.toString());
|
||||||
command.addAll(Arrays.asList(url.split(" ")));
|
command.addAll(Arrays.asList(line.split(" ")));
|
||||||
ProcessBuilder cliBuilder = new ProcessBuilder(command);
|
ProcessBuilder cliBuilder = new ProcessBuilder(command);
|
||||||
cliBuilder.redirectErrorStream(true);
|
cliBuilder.redirectErrorStream(true);
|
||||||
Process process = cliBuilder.start();
|
Process process = cliBuilder.start();
|
||||||
|
|
Loading…
Reference in New Issue