Drop the ssl tests against the java builtin https server. They were
failing and the failures were undebuggable. I still don't know what was
happening because you can't get any logging out of the server.

Add SSL tests against Elasticsearch because that is what actually needs
to work.

relates elastic/x-pack-elasticsearch#2870

Original commit: elastic/x-pack-elasticsearch@284cf7fb58
This commit is contained in:
Nik Everett 2017-11-27 18:52:16 -05:00 committed by GitHub
parent 5c88fa0b3b
commit df802b40c8
9 changed files with 172 additions and 334 deletions

View File

@ -67,6 +67,8 @@ case $key in
":x-pack-elasticsearch:qa:sql:multinode:check"
":x-pack-elasticsearch:qa:sql:no-security:check"
":x-pack-elasticsearch:qa:sql:security:check"
":x-pack-elasticsearch:qa:sql:security:no-ssl:check"
":x-pack-elasticsearch:qa:sql:security:ssl:check"
)
;;
releaseTest)

View File

@ -24,7 +24,7 @@ dependencies {
runtime "com.h2database:h2:1.4.194"
// used for running debug tests
runtime 'org.antlr:antlr4-runtime:4.5.3'
// There are *no* CLI testing dependencies because we
// communicate fork a new CLI process when we need it.
@ -88,12 +88,9 @@ thirdPartyAudit.excludes = [
subprojects {
apply plugin: 'elasticsearch.standalone-rest-test'
apply plugin: 'elasticsearch.rest-test'
configurations {
cliFixture
}
dependencies {
/* Since we're a standalone rest test we actually get transitive
* dependencies but we don't really want them because they cause
@ -135,39 +132,44 @@ subprojects {
testRuntime "org.elasticsearch.client:transport:${version}"
}
task cliFixture(type: org.elasticsearch.gradle.test.AntFixture) {
Project cli = project(':x-pack-elasticsearch:sql:cli')
dependsOn project.configurations.cliFixture
dependsOn cli.jar
executable = new File(project.javaHome, 'bin/java')
env 'CLASSPATH', "${ -> project.configurations.cliFixture.asPath }"
args 'org.elasticsearch.xpack.sql.cli.fixture.CliFixture',
baseDir, "${ -> cli.jar.outputs.files.singleFile}"
}
if (project.name != 'security') {
// The security project just configures it subprojects
apply plugin: 'elasticsearch.rest-test'
integTestCluster {
distribution = 'zip'
plugin project(':x-pack-elasticsearch:plugin').path
setting 'xpack.monitoring.enabled', 'false'
setting 'xpack.ml.enabled', 'false'
setting 'xpack.watcher.enabled', 'false'
setting 'script.max_compilations_rate', '1000/1m'
dependsOn cliFixture
}
task cliFixture(type: org.elasticsearch.gradle.test.AntFixture) {
Project cli = project(':x-pack-elasticsearch:sql:cli')
dependsOn project.configurations.cliFixture
dependsOn cli.jar
executable = new File(project.javaHome, 'bin/java')
env 'CLASSPATH', "${ -> project.configurations.cliFixture.asPath }"
args 'org.elasticsearch.xpack.sql.cli.fixture.CliFixture',
baseDir, "${ -> cli.jar.outputs.files.singleFile}"
}
integTestRunner {
systemProperty 'tests.cli.fixture', "${ -> cliFixture.addressAndPort }"
finalizedBy cliFixture.stopTask
}
integTestCluster {
distribution = 'zip'
plugin project(':x-pack-elasticsearch:plugin').path
setting 'xpack.monitoring.enabled', 'false'
setting 'xpack.ml.enabled', 'false'
setting 'xpack.watcher.enabled', 'false'
setting 'script.max_compilations_rate', '1000/1m'
dependsOn cliFixture
}
task run(type: RunTask) {
distribution = 'zip'
plugin project(':x-pack-elasticsearch:plugin').path
setting 'xpack.monitoring.enabled', 'false'
setting 'xpack.ml.enabled', 'false'
setting 'xpack.watcher.enabled', 'false'
setting 'script.max_compilations_rate', '1000/1m'
dependsOn cliFixture
integTestRunner {
systemProperty 'tests.cli.fixture', "${ -> cliFixture.addressAndPort }"
finalizedBy cliFixture.stopTask
}
task run(type: RunTask) {
distribution = 'zip'
plugin project(':x-pack-elasticsearch:plugin').path
setting 'xpack.monitoring.enabled', 'false'
setting 'xpack.ml.enabled', 'false'
setting 'xpack.watcher.enabled', 'false'
setting 'script.max_compilations_rate', '1000/1m'
dependsOn cliFixture
}
run.finalizedBy cliFixture.stopTask
}
run.finalizedBy cliFixture.stopTask
}

View File

@ -4,53 +4,54 @@ dependencies {
}
}
integTestCluster {
// Setup auditing so we can use it in some tests
setting 'xpack.security.audit.enabled', 'true'
setting 'xpack.security.audit.outputs', 'logfile'
// Setup roles used by tests
extraConfigFile 'x-pack/roles.yml', 'roles.yml'
/* Setup the one admin user that we run the tests as.
* Tests use "run as" to get different users. */
setupCommand 'setupUser#test_admin',
'bin/x-pack/users', 'useradd', 'test_admin', '-p', 'x-pack-test-password', '-r', 'superuser'
// Override the wait condition to work properly with security
waitCondition = { node, ant ->
File tmpFile = new File(node.cwd, 'wait.success')
ant.get(src: "http://${node.httpUri()}/_cluster/health?wait_for_nodes=>=${numNodes}&wait_for_status=yellow",
dest: tmpFile.toString(),
username: 'test_admin',
password: 'x-pack-test-password',
ignoreerrors: true,
retries: 10)
return tmpFile.exists()
}
}
integTestRunner {
systemProperty 'tests.audit.logfile',
"${ -> integTest.nodes[0].homeDir}/logs/${ -> integTest.nodes[0].clusterName }_access.log"
}
run {
// Setup auditing so we can use it in some tests
setting 'xpack.security.audit.enabled', 'true'
setting 'xpack.security.audit.outputs', 'logfile'
// Setup roles used by tests
extraConfigFile 'x-pack/roles.yml', 'roles.yml'
/* Setup the one admin user that we run the tests as.
* Tests use "run as" to get different users. */
setupCommand 'setupUser#test_admin',
'bin/x-pack/users', 'useradd', 'test_admin', '-p', 'x-pack-test-password', '-r', 'superuser'
// Override the wait condition to work properly with security
waitCondition = { node, ant ->
File tmpFile = new File(node.cwd, 'wait.success')
ant.get(src: "http://${node.httpUri()}/_cluster/health?wait_for_nodes=>=${numNodes}&wait_for_status=yellow",
dest: tmpFile.toString(),
username: 'test_admin',
password: 'x-pack-test-password',
ignoreerrors: true,
retries: 10)
return tmpFile.exists()
Project mainProject = project
subprojects {
// Use resources from the parent project in subprojects
sourceSets {
test {
java {
srcDirs = ["${mainProject.projectDir}/src/test/java"]
}
resources {
srcDirs = ["${mainProject.projectDir}/src/test/resources"]
}
}
}
dependencies {
testCompile(project(path: ':x-pack-elasticsearch:plugin', configuration: 'runtime')) {
transitive = false
}
}
integTestCluster {
// Setup auditing so we can use it in some tests
setting 'xpack.security.audit.enabled', 'true'
setting 'xpack.security.audit.outputs', 'logfile'
// Setup roles used by tests
extraConfigFile 'x-pack/roles.yml', '../roles.yml'
/* Setup the one admin user that we run the tests as.
* Tests use "run as" to get different users. */
setupCommand 'setupUser#test_admin',
'bin/x-pack/users', 'useradd', 'test_admin', '-p', 'x-pack-test-password', '-r', 'superuser'
// Subprojects override the wait condition to work properly with security
}
integTestRunner {
systemProperty 'tests.audit.logfile',
"${ -> integTest.nodes[0].homeDir}/logs/${ -> integTest.nodes[0].clusterName }_access.log"
}
run {
// Setup auditing so we can use it in some tests
setting 'xpack.security.audit.enabled', 'true'
setting 'xpack.security.audit.outputs', 'logfile'
// Setup roles used by tests
extraConfigFile 'x-pack/roles.yml', '../roles.yml'
/* Setup the one admin user that we run the tests as.
* Tests use "run as" to get different users. */
setupCommand 'setupUser#test_admin',
'bin/x-pack/users', 'useradd', 'test_admin', '-p', 'x-pack-test-password', '-r', 'superuser'
}
}

View File

@ -0,0 +1,12 @@
integTestCluster {
waitCondition = { node, ant ->
File tmpFile = new File(node.cwd, 'wait.success')
ant.get(src: "http://${node.httpUri()}/_cluster/health?wait_for_nodes=>=${numNodes}&wait_for_status=yellow",
dest: tmpFile.toString(),
username: 'test_admin',
password: 'x-pack-test-password',
ignoreerrors: true,
retries: 10)
return tmpFile.exists()
}
}

View File

@ -0,0 +1,61 @@
import org.elasticsearch.gradle.test.NodeInfo
import java.nio.charset.StandardCharsets
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
}
integTestCluster {
// 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();
}
}
// 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)
}
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')
}

View File

@ -29,6 +29,7 @@ import java.sql.SQLInvalidAuthorizationSpecException;
import java.sql.SQLRecoverableException;
import java.sql.SQLSyntaxErrorException;
import java.sql.SQLTimeoutException;
import java.util.Arrays;
import java.util.Base64;
import java.util.function.Function;
import java.util.zip.GZIPInputStream;

View File

@ -51,11 +51,11 @@ class SslConfig {
private 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,
SSL_KEYSTORE_LOCATION, SSL_KEYSTORE_PASS, SSL_KEYSTORE_TYPE,
static final Set<String> OPTION_NAMES = new LinkedHashSet<>(Arrays.asList(SSL, SSL_PROTOCOL,
SSL_KEYSTORE_LOCATION, SSL_KEYSTORE_PASS, SSL_KEYSTORE_TYPE,
SSL_TRUSTSTORE_LOCATION, SSL_TRUSTSTORE_PASS, SSL_TRUSTSTORE_TYPE));
private final boolean enabled;
private final String protocol, keystoreLocation, keystorePass, keystoreType;
private final String truststoreLocation, truststorePass, truststoreType;
@ -116,15 +116,15 @@ class SslConfig {
if (!Files.exists(path)) {
throw new ClientException(
"Expected to find keystore file at [%s] but was unable to. Make sure you have specified a valid URI.", location);
"Expected to find keystore file at [%s] but was unable to. Make sure you have specified a valid URI.", location);
}
try (InputStream in = Files.newInputStream(Paths.get(location), StandardOpenOption.READ)) {
keyStore.load(in, pass);
} catch (Exception ex) {
throw new ClientException(ex, "Cannot open keystore [%s] - %s", location, ex.getMessage());
} finally {
}
return keyStore;
}
@ -147,11 +147,11 @@ class SslConfig {
if (this == obj) {
return true;
}
if (obj == null || getClass() != obj.getClass()) {
return false;
}
SslConfig other = (SslConfig) obj;
return Objects.equals(enabled, other.enabled)
&& Objects.equals(protocol, other.protocol)
@ -166,4 +166,4 @@ class SslConfig {
public int hashCode() {
return getClass().hashCode();
}
}
}

View File

@ -1,119 +0,0 @@
/*
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
* or more contributor license agreements. Licensed under the Elastic License;
* you may not use this file except in compliance with the Elastic License.
*/
package org.elasticsearch.xpack.sql.client.shared;
import com.sun.net.httpserver.HttpExchange;
import com.sun.net.httpserver.HttpHandler;
import com.sun.net.httpserver.HttpsConfigurator;
import com.sun.net.httpserver.HttpsParameters;
import com.sun.net.httpserver.HttpsServer;
import org.elasticsearch.common.io.Streams;
import java.io.IOException;
import java.net.HttpURLConnection;
import java.net.InetAddress;
import java.net.InetSocketAddress;
import java.security.KeyStore;
import java.security.cert.CertificateException;
import java.security.cert.X509Certificate;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import javax.net.ssl.KeyManagerFactory;
import javax.net.ssl.SSLContext;
import javax.net.ssl.SSLParameters;
import javax.net.ssl.TrustManager;
import javax.net.ssl.X509TrustManager;
@SuppressWarnings("restriction")
public class BasicSSLServer {
@SuppressForbidden(reason = "it's a test, not production code")
private static class EchoHandler implements HttpHandler {
public void handle(HttpExchange e) throws IOException {
e.sendResponseHeaders(HttpURLConnection.HTTP_OK, 0);
Streams.copy(e.getRequestBody(), e.getResponseBody());
}
}
private HttpsServer server;
private ExecutorService executor;
public void start(int port) throws Exception {
// similar to Executors.newCached but with a smaller bound and much smaller keep-alive
executor = Executors.newCachedThreadPool();
server = HttpsServer.create(new InetSocketAddress(InetAddress.getLoopbackAddress(), port), port);
server.setHttpsConfigurator(httpConfigurator());
server.createContext("/ssl", new EchoHandler());
server.setExecutor(executor);
server.start();
}
private static HttpsConfigurator httpConfigurator() throws Exception {
char[] pass = "password".toCharArray();
// so this works on JDK 7 as well
SSLContext sslContext = SSLContext.getInstance("TLSv1.2");
KeyStore ks = KeyStore.getInstance("JKS");
KeyManagerFactory kmf = KeyManagerFactory.getInstance("SunX509");
ks.load(BasicSSLServer.class.getResourceAsStream("/ssl/server.keystore"), pass);
kmf.init(ks, pass);
TrustManager[] trustAll = new TrustManager[] {
new X509TrustManager() {
@Override
public X509Certificate[] getAcceptedIssuers() {
return new X509Certificate[0];
}
@Override
public void checkServerTrusted(X509Certificate[] chain, String authType) throws CertificateException {}
@Override
public void checkClientTrusted(X509Certificate[] chain, String authType) throws CertificateException {}
}
};
// chain
sslContext.init(kmf.getKeyManagers(), trustAll, null);
HttpsConfigurator configurator = new HttpsConfigurator(sslContext) {
public void configure(HttpsParameters params) {
try {
SSLContext c = getSSLContext();
SSLParameters defaults = c.getDefaultSSLParameters();
params.setSSLParameters(defaults);
// client can send a cert if they want to (use Need to force the client to present one)
//params.setWantClientAuth(true);
} catch (Exception ex) {
throw new RuntimeException(ex);
}
}
};
return configurator;
}
public void stop() {
server.stop(1);
server = null;
executor.shutdownNow();
executor = null;
}
public InetSocketAddress address() {
return server != null ? server.getAddress() : null;
}
public String url() {
return server != null ? "https://localhost:" + address().getPort() + "/ssl" : null;
}
}

View File

@ -1,122 +0,0 @@
/*
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
* or more contributor license agreements. Licensed under the Elastic License;
* you may not use this file except in compliance with the Elastic License.
*/
package org.elasticsearch.xpack.sql.client.shared;
import org.apache.lucene.util.LuceneTestCase.AwaitsFix;
import org.elasticsearch.common.io.PathUtils;
import org.elasticsearch.test.ESTestCase;
import org.elasticsearch.xpack.sql.client.shared.JreHttpUrlConnection.ResponseOrException;
import org.junit.After;
import org.junit.Before;
import org.junit.ClassRule;
import org.junit.rules.ExternalResource;
import java.io.DataInput;
import java.net.URI;
import java.net.URL;
import java.security.AccessController;
import java.security.PrivilegedAction;
import java.util.Arrays;
import java.util.Properties;
import java.util.UUID;
import javax.net.ssl.SSLContext;
import javax.net.ssl.SSLSocket;
import javax.net.ssl.SSLSocketFactory;
@AwaitsFix(bugUrl = "https://github.com/elastic/x-pack-elasticsearch/issues/2074")
public class SSLTests extends ESTestCase {
private static URL sslServer;
@ClassRule
public static ExternalResource SSL_SERVER = new ExternalResource() {
private BasicSSLServer server;
@Override
protected void before() throws Throwable {
server = new BasicSSLServer();
server.start(0);
sslServer = new URL(server.url());
}
@Override
protected void after() {
sslServer = null;
try {
server.stop();
} catch (Exception ex) {
throw new RuntimeException(ex);
}
}
};
private ConnectionConfiguration cfg;
@Before
public void setup() throws Exception {
Properties prop = new Properties();
// ssl config
prop.setProperty("ssl", "true");
// specify the TLS just in case (who knows what else will be deprecated across JDKs)
prop.setProperty("ssl.protocol", "TLSv1.2");
prop.setProperty("ssl.keystore.location",
PathUtils.get(getClass().getResource("/ssl/client.keystore").toURI()).toRealPath().toString());
prop.setProperty("ssl.keystore.pass", "password");
// set the truststore as well since otherwise there will be cert errors ...
prop.setProperty("ssl.truststore.location",
PathUtils.get(getClass().getResource("/ssl/client.keystore").toURI()).toRealPath().toString());
prop.setProperty("ssl.truststore.pass", "password");
//prop.setProperty("ssl.accept.self.signed.certs", "true");
cfg = new ConnectionConfiguration(URI.create(sslServer.toString()), sslServer.toString(), prop);
}
@After
public void destroy() {
cfg = null;
}
public void testSslSetup() throws Exception {
SSLContext context = SSLContext.getDefault();
SSLSocketFactory factory = context.getSocketFactory();
SSLSocket socket = (SSLSocket) factory.createSocket();
String[] protocols = socket.getSupportedProtocols();
logger.info("Supported Protocols: {}", protocols.length);
logger.info("{}", Arrays.toString(protocols));
protocols = socket.getEnabledProtocols();
logger.info("Enabled Protocols: {}", protocols.length);
logger.info("{}", Arrays.toString(protocols));
String[] ciphers = socket.getSupportedCipherSuites();
logger.info("{}", Arrays.toString(ciphers));
}
public void testSslHead() throws Exception {
assertTrue(AccessController.doPrivileged((PrivilegedAction<Boolean>) () -> {
return JreHttpUrlConnection.http("", null, cfg, JreHttpUrlConnection::head);
}));
}
public void testSslPost() throws Exception {
String message = UUID.randomUUID().toString();
String received = AccessController.doPrivileged((PrivilegedAction<ResponseOrException<String>>) () ->
JreHttpUrlConnection.http("", null, cfg, c ->
c.post(
out -> out.writeUTF(message),
DataInput::readUTF
)
)
).getResponseOrThrowException();
assertEquals(message, received);
}
}