SQL: Rework SSL testing (elastic/x-pack-elasticsearch#3126)
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:
parent
5c88fa0b3b
commit
df802b40c8
|
@ -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)
|
||||
|
|
|
@ -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,6 +132,10 @@ subprojects {
|
|||
testRuntime "org.elasticsearch.client:transport:${version}"
|
||||
}
|
||||
|
||||
if (project.name != 'security') {
|
||||
// The security project just configures it subprojects
|
||||
apply plugin: 'elasticsearch.rest-test'
|
||||
|
||||
task cliFixture(type: org.elasticsearch.gradle.test.AntFixture) {
|
||||
Project cli = project(':x-pack-elasticsearch:sql:cli')
|
||||
dependsOn project.configurations.cliFixture
|
||||
|
@ -170,4 +171,5 @@ subprojects {
|
|||
dependsOn cliFixture
|
||||
}
|
||||
run.finalizedBy cliFixture.stopTask
|
||||
}
|
||||
}
|
||||
|
|
|
@ -4,53 +4,54 @@ dependencies {
|
|||
}
|
||||
}
|
||||
|
||||
integTestCluster {
|
||||
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'
|
||||
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()
|
||||
// Subprojects override the wait condition to work properly with security
|
||||
}
|
||||
}
|
||||
|
||||
integTestRunner {
|
||||
integTestRunner {
|
||||
systemProperty 'tests.audit.logfile',
|
||||
"${ -> integTest.nodes[0].homeDir}/logs/${ -> integTest.nodes[0].clusterName }_access.log"
|
||||
}
|
||||
}
|
||||
|
||||
run {
|
||||
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'
|
||||
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()
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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()
|
||||
}
|
||||
}
|
|
@ -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')
|
||||
}
|
|
@ -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;
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
}
|
|
@ -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);
|
||||
}
|
||||
}
|
Loading…
Reference in New Issue