Expand SQL client support
- SSL (tested) - Basic auth - Proxying (HTTP and SOCKS) Original commit: elastic/x-pack-elasticsearch@3abc49897e
This commit is contained in:
parent
9cbe332fc3
commit
38ea150f17
|
@ -5,12 +5,12 @@
|
||||||
*/
|
*/
|
||||||
package org.elasticsearch.xpack.sql.cli;
|
package org.elasticsearch.xpack.sql.cli;
|
||||||
|
|
||||||
|
import org.elasticsearch.xpack.sql.net.client.ConnectionConfiguration;
|
||||||
|
|
||||||
import java.net.MalformedURLException;
|
import java.net.MalformedURLException;
|
||||||
import java.net.URL;
|
import java.net.URL;
|
||||||
import java.util.Properties;
|
import java.util.Properties;
|
||||||
|
|
||||||
import org.elasticsearch.xpack.sql.net.client.ConnectionConfiguration;
|
|
||||||
|
|
||||||
//
|
//
|
||||||
// Supports the following syntax
|
// Supports the following syntax
|
||||||
//
|
//
|
||||||
|
@ -74,7 +74,7 @@ public class CliConfiguration extends ConnectionConfiguration {
|
||||||
public URL asUrl() {
|
public URL asUrl() {
|
||||||
// TODO: need to assemble all the various params here
|
// TODO: need to assemble all the various params here
|
||||||
try {
|
try {
|
||||||
return new URL(isSSL() ? "https" : "http", hostAndPort.ip, port(), urlFile);
|
return new URL(isSSLEnabled() ? "https" : "http", hostAndPort.ip, port(), urlFile);
|
||||||
} catch (MalformedURLException ex) {
|
} catch (MalformedURLException ex) {
|
||||||
throw new IllegalArgumentException("Cannot connect to server " + originalUrl, ex);
|
throw new IllegalArgumentException("Cannot connect to server " + originalUrl, ex);
|
||||||
}
|
}
|
||||||
|
|
|
@ -176,7 +176,7 @@ public class JdbcConfiguration extends ConnectionConfiguration {
|
||||||
public URL asUrl() {
|
public URL asUrl() {
|
||||||
// TODO: need to assemble all the various params here
|
// TODO: need to assemble all the various params here
|
||||||
try {
|
try {
|
||||||
return new URL(isSSL() ? "https" : "http", hostAndPort.ip, port(), urlFile);
|
return new URL(isSSLEnabled() ? "https" : "http", hostAndPort.ip, port(), urlFile);
|
||||||
} catch (MalformedURLException ex) {
|
} catch (MalformedURLException ex) {
|
||||||
throw new JdbcException(ex, "Cannot connect to server %s", originalUrl);
|
throw new JdbcException(ex, "Cannot connect to server %s", originalUrl);
|
||||||
}
|
}
|
||||||
|
|
|
@ -4,3 +4,21 @@ forbiddenApisMain {
|
||||||
// does not depend on core, so only jdk and http signatures should be checked
|
// does not depend on core, so only jdk and http signatures should be checked
|
||||||
signaturesURLs = [this.class.getResource('/forbidden/jdk-signatures.txt')]
|
signaturesURLs = [this.class.getResource('/forbidden/jdk-signatures.txt')]
|
||||||
}
|
}
|
||||||
|
|
||||||
|
forbiddenApisTest {
|
||||||
|
bundledSignatures -= 'jdk-non-portable'
|
||||||
|
bundledSignatures += 'jdk-internal'
|
||||||
|
}
|
||||||
|
|
||||||
|
// Allow for com.sun.net.httpserver.* usage for testing
|
||||||
|
eclipse {
|
||||||
|
classpath.file {
|
||||||
|
whenMerged { cp ->
|
||||||
|
def con = entries.find { e ->
|
||||||
|
e.kind == "con" && e.toString().contains("org.eclipse.jdt.launching.JRE_CONTAINER")
|
||||||
|
}
|
||||||
|
con.accessRules.add(new org.gradle.plugins.ide.eclipse.model.AccessRule(
|
||||||
|
"accessible", "com/sun/net/httpserver/*"))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
@ -5,8 +5,6 @@
|
||||||
*/
|
*/
|
||||||
package org.elasticsearch.xpack.sql.net.client;
|
package org.elasticsearch.xpack.sql.net.client;
|
||||||
|
|
||||||
import org.elasticsearch.xpack.sql.net.client.util.StringUtils;
|
|
||||||
|
|
||||||
import java.util.Properties;
|
import java.util.Properties;
|
||||||
import java.util.concurrent.TimeUnit;
|
import java.util.concurrent.TimeUnit;
|
||||||
|
|
||||||
|
@ -35,48 +33,30 @@ public class ConnectionConfiguration {
|
||||||
// Timeouts
|
// Timeouts
|
||||||
|
|
||||||
// 30s
|
// 30s
|
||||||
static final String CONNECT_TIMEOUT = "connect.timeout";
|
private static final String CONNECT_TIMEOUT = "connect.timeout";
|
||||||
static final String CONNECT_TIMEOUT_DEFAULT = String.valueOf(TimeUnit.SECONDS.toMillis(30));
|
private static final String CONNECT_TIMEOUT_DEFAULT = String.valueOf(TimeUnit.SECONDS.toMillis(30));
|
||||||
|
|
||||||
// 1m
|
// 1m
|
||||||
static final String NETWORK_TIMEOUT = "network.timeout";
|
private static final String NETWORK_TIMEOUT = "network.timeout";
|
||||||
static final String NETWORK_TIMEOUT_DEFAULT = String.valueOf(TimeUnit.MINUTES.toMillis(1));
|
private static final String NETWORK_TIMEOUT_DEFAULT = String.valueOf(TimeUnit.MINUTES.toMillis(1));
|
||||||
|
|
||||||
// 1m
|
// 1m
|
||||||
static final String QUERY_TIMEOUT = "query.timeout";
|
private static final String QUERY_TIMEOUT = "query.timeout";
|
||||||
static final String QUERY_TIMEOUT_DEFAULT = String.valueOf(TimeUnit.MINUTES.toMillis(1));
|
private static final String QUERY_TIMEOUT_DEFAULT = String.valueOf(TimeUnit.MINUTES.toMillis(1));
|
||||||
|
|
||||||
// 5m
|
// 5m
|
||||||
static final String PAGE_TIMEOUT = "page.timeout";
|
private static final String PAGE_TIMEOUT = "page.timeout";
|
||||||
static final String PAGE_TIMEOUT_DEFAULT = String.valueOf(TimeUnit.MINUTES.toMillis(5));
|
private static final String PAGE_TIMEOUT_DEFAULT = String.valueOf(TimeUnit.MINUTES.toMillis(5));
|
||||||
|
|
||||||
static final String PAGE_SIZE = "page.size";
|
private static final String PAGE_SIZE = "page.size";
|
||||||
static final String PAGE_SIZE_DEFAULT = "1000";
|
private static final String PAGE_SIZE_DEFAULT = "1000";
|
||||||
|
|
||||||
static final String SSL = "ssl";
|
// Auth
|
||||||
static final String SSL_DEFAULT = "false";
|
|
||||||
|
|
||||||
static final String SSL_PROTOCOL = "ssl.protocol";
|
private static final String AUTH_USER = "user";
|
||||||
static final String SSL_PROTOCOL_DEFAULT = "TLS"; // SSL alternative
|
private static final String AUTH_PASS = "pass";
|
||||||
|
|
||||||
static final String SSL_KEYSTORE_LOCATION = "ssl.keystore.location";
|
// Proxy
|
||||||
static final String SSL_KEYSTORE_LOCATION_DEFAULT = "";
|
|
||||||
|
|
||||||
static final String SSL_KEYSTORE_PASS = "ssl.keystore.location";
|
|
||||||
static final String SSL_KEYSTORE_PASS_DEFAULT = "";
|
|
||||||
|
|
||||||
static final String SSL_KEYSTORE_TYPE = "ssl.keystore.type";
|
|
||||||
static final String SSL_KEYSTORE_TYPE_DEFAULT = "JKS"; // PCKS12
|
|
||||||
|
|
||||||
static final String SSL_TRUSTSTORE_LOCATION = "ssl.keystore.location";
|
|
||||||
static final String SSL_TRUSTSTORE_LOCATION_DEFAULT = "";
|
|
||||||
|
|
||||||
static final String SSL_TRUSTSTORE_PASS = "ssl.keystore.location";
|
|
||||||
static final String SSL_TRUSTSTORE_PASS_DEFAULT = "";
|
|
||||||
|
|
||||||
static final String SSL_TRUSTSTORE_TYPE = "ssl.keystore.location";
|
|
||||||
static final String SSL_TRUSTSTORE_TYPE_DEFAULT = "ssl.keystore.location";
|
|
||||||
|
|
||||||
|
|
||||||
private final Properties settings;
|
private final Properties settings;
|
||||||
|
|
||||||
|
@ -86,7 +66,12 @@ public class ConnectionConfiguration {
|
||||||
|
|
||||||
private long pageTimeout;
|
private long pageTimeout;
|
||||||
private int pageSize;
|
private int pageSize;
|
||||||
private final boolean ssl;
|
|
||||||
|
private final String user, pass;
|
||||||
|
|
||||||
|
|
||||||
|
private final SslConfig sslConfig;
|
||||||
|
private final ProxyConfig proxyConfig;
|
||||||
|
|
||||||
public ConnectionConfiguration(Properties props) {
|
public ConnectionConfiguration(Properties props) {
|
||||||
settings = props != null ? new Properties(props) : new Properties();
|
settings = props != null ? new Properties(props) : new Properties();
|
||||||
|
@ -97,17 +82,31 @@ public class ConnectionConfiguration {
|
||||||
// page
|
// page
|
||||||
pageTimeout = Long.parseLong(settings.getProperty(PAGE_TIMEOUT, PAGE_TIMEOUT_DEFAULT));
|
pageTimeout = Long.parseLong(settings.getProperty(PAGE_TIMEOUT, PAGE_TIMEOUT_DEFAULT));
|
||||||
pageSize = Integer.parseInt(settings.getProperty(PAGE_SIZE, PAGE_SIZE_DEFAULT));
|
pageSize = Integer.parseInt(settings.getProperty(PAGE_SIZE, PAGE_SIZE_DEFAULT));
|
||||||
ssl = StringUtils.parseBoolean(settings.getProperty(SSL, SSL_DEFAULT));
|
|
||||||
|
// auth
|
||||||
|
user = settings.getProperty(AUTH_USER);
|
||||||
|
pass = settings.getProperty(AUTH_PASS);
|
||||||
|
|
||||||
|
sslConfig = new SslConfig(props);
|
||||||
|
proxyConfig = new ProxyConfig(props);
|
||||||
|
}
|
||||||
|
|
||||||
|
protected boolean isSSLEnabled() {
|
||||||
|
return sslConfig.isEnabled();
|
||||||
|
}
|
||||||
|
|
||||||
|
SslConfig sslConfig() {
|
||||||
|
return sslConfig;
|
||||||
|
}
|
||||||
|
|
||||||
|
ProxyConfig proxyConfig() {
|
||||||
|
return proxyConfig;
|
||||||
}
|
}
|
||||||
|
|
||||||
protected Properties settings() {
|
protected Properties settings() {
|
||||||
return settings;
|
return settings;
|
||||||
}
|
}
|
||||||
|
|
||||||
protected boolean isSSL() {
|
|
||||||
return ssl;
|
|
||||||
}
|
|
||||||
|
|
||||||
public void connectTimeout(long millis) {
|
public void connectTimeout(long millis) {
|
||||||
connectTimeout = millis;
|
connectTimeout = millis;
|
||||||
}
|
}
|
||||||
|
@ -139,4 +138,14 @@ public class ConnectionConfiguration {
|
||||||
public int pageSize() {
|
public int pageSize() {
|
||||||
return pageSize;
|
return pageSize;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// auth
|
||||||
|
|
||||||
|
public String authUser() {
|
||||||
|
return user;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String authPass() {
|
||||||
|
return pass;
|
||||||
|
}
|
||||||
}
|
}
|
|
@ -8,6 +8,7 @@ package org.elasticsearch.xpack.sql.net.client;
|
||||||
import org.elasticsearch.xpack.sql.net.client.util.Bytes;
|
import org.elasticsearch.xpack.sql.net.client.util.Bytes;
|
||||||
import org.elasticsearch.xpack.sql.net.client.util.CheckedConsumer;
|
import org.elasticsearch.xpack.sql.net.client.util.CheckedConsumer;
|
||||||
import org.elasticsearch.xpack.sql.net.client.util.IOUtils;
|
import org.elasticsearch.xpack.sql.net.client.util.IOUtils;
|
||||||
|
import org.elasticsearch.xpack.sql.net.client.util.StringUtils;
|
||||||
|
|
||||||
import java.io.Closeable;
|
import java.io.Closeable;
|
||||||
import java.io.DataOutput;
|
import java.io.DataOutput;
|
||||||
|
@ -16,10 +17,14 @@ import java.io.IOException;
|
||||||
import java.io.InputStream;
|
import java.io.InputStream;
|
||||||
import java.io.OutputStream;
|
import java.io.OutputStream;
|
||||||
import java.net.HttpURLConnection;
|
import java.net.HttpURLConnection;
|
||||||
|
import java.net.Proxy;
|
||||||
import java.net.URL;
|
import java.net.URL;
|
||||||
|
import java.util.Base64;
|
||||||
import java.util.function.Function;
|
import java.util.function.Function;
|
||||||
import java.util.zip.GZIPInputStream;
|
import java.util.zip.GZIPInputStream;
|
||||||
|
|
||||||
|
import javax.net.ssl.HttpsURLConnection;
|
||||||
|
|
||||||
public class JreHttpUrlConnection implements Closeable {
|
public class JreHttpUrlConnection implements Closeable {
|
||||||
public static <R> R http(URL url, ConnectionConfiguration cfg, Function<JreHttpUrlConnection, R> handler) {
|
public static <R> R http(URL url, ConnectionConfiguration cfg, Function<JreHttpUrlConnection, R> handler) {
|
||||||
try (JreHttpUrlConnection con = new JreHttpUrlConnection(url, cfg)) {
|
try (JreHttpUrlConnection con = new JreHttpUrlConnection(url, cfg)) {
|
||||||
|
@ -35,20 +40,51 @@ public class JreHttpUrlConnection implements Closeable {
|
||||||
public JreHttpUrlConnection(URL url, ConnectionConfiguration cfg) throws ClientException {
|
public JreHttpUrlConnection(URL url, ConnectionConfiguration cfg) throws ClientException {
|
||||||
this.url = url;
|
this.url = url;
|
||||||
try {
|
try {
|
||||||
con = (HttpURLConnection) url.openConnection();
|
// due to the way the URL API is designed, the proxy needs to be passed in first
|
||||||
|
Proxy p = cfg.proxyConfig().proxy();
|
||||||
|
con = (HttpURLConnection) (p != null ? url.openConnection(p) : url.openConnection());
|
||||||
} catch (IOException ex) {
|
} catch (IOException ex) {
|
||||||
throw new ClientException(ex, "Cannot setup connection to %s (%s)", url, ex.getMessage());
|
throw new ClientException(ex, "Cannot setup connection to %s (%s)", url, ex.getMessage());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// the rest of the connection setup
|
||||||
|
setupConnection(cfg);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void setupConnection(ConnectionConfiguration cfg) {
|
||||||
|
// setup basic stuff first
|
||||||
|
|
||||||
|
// timeouts
|
||||||
con.setConnectTimeout((int) cfg.connectTimeout());
|
con.setConnectTimeout((int) cfg.connectTimeout());
|
||||||
con.setReadTimeout((int) cfg.networkTimeout());
|
con.setReadTimeout((int) cfg.networkTimeout());
|
||||||
|
|
||||||
|
// disable content caching
|
||||||
con.setAllowUserInteraction(false);
|
con.setAllowUserInteraction(false);
|
||||||
con.setUseCaches(false);
|
con.setUseCaches(false);
|
||||||
|
|
||||||
|
// HTTP params
|
||||||
// HttpURL adds this header by default, HttpS does not
|
// HttpURL adds this header by default, HttpS does not
|
||||||
// adding it here to be consistent
|
// adding it here to be consistent
|
||||||
con.setRequestProperty("Accept-Charset", "UTF-8");
|
con.setRequestProperty("Accept-Charset", "UTF-8");
|
||||||
//con.setRequestProperty("Accept-Encoding", GZIP);
|
//con.setRequestProperty("Accept-Encoding", GZIP);
|
||||||
|
|
||||||
|
setupSSL(cfg);
|
||||||
|
setupBasicAuth(cfg);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void setupSSL(ConnectionConfiguration cfg) {
|
||||||
|
if (cfg.sslConfig().isEnabled()) {
|
||||||
|
HttpsURLConnection https = (HttpsURLConnection) con;
|
||||||
|
https.setSSLSocketFactory(cfg.sslConfig().sslSocketFactory());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void setupBasicAuth(ConnectionConfiguration cfg) {
|
||||||
|
if (StringUtils.hasText(cfg.authUser())) {
|
||||||
|
String basicValue = cfg.authUser() + ":" + cfg.authPass();
|
||||||
|
String encoded = StringUtils.asUTFString(Base64.getEncoder().encode(StringUtils.toUTF(basicValue)));
|
||||||
|
con.setRequestProperty("Authorization", "Basic " + encoded);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public boolean head() throws ClientException {
|
public boolean head() throws ClientException {
|
||||||
|
|
|
@ -0,0 +1,69 @@
|
||||||
|
/*
|
||||||
|
* 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.net.client;
|
||||||
|
|
||||||
|
import org.elasticsearch.xpack.sql.net.client.util.StringUtils;
|
||||||
|
|
||||||
|
import java.net.InetSocketAddress;
|
||||||
|
import java.net.Proxy;
|
||||||
|
import java.net.URI;
|
||||||
|
import java.net.URISyntaxException;
|
||||||
|
import java.util.Properties;
|
||||||
|
|
||||||
|
class ProxyConfig {
|
||||||
|
|
||||||
|
private static final String HTTP_PROXY = "proxy.http";
|
||||||
|
private static final String HTTP_PROXY_DEFAULT = StringUtils.EMPTY;
|
||||||
|
private static final String SOCKS_PROXY = "proxy.socks";
|
||||||
|
private static final String SOCKS_PROXY_DEFAULT = StringUtils.EMPTY;
|
||||||
|
|
||||||
|
private final Proxy proxy;
|
||||||
|
|
||||||
|
ProxyConfig(Properties settings) {
|
||||||
|
Proxy.Type type = null;
|
||||||
|
// try http first
|
||||||
|
Object[] address = host(settings.getProperty(HTTP_PROXY, HTTP_PROXY_DEFAULT), 80);
|
||||||
|
type = Proxy.Type.HTTP;
|
||||||
|
// nope, check socks
|
||||||
|
if (address == null) {
|
||||||
|
address = host(settings.getProperty(SOCKS_PROXY, SOCKS_PROXY_DEFAULT), 1080);
|
||||||
|
type = Proxy.Type.SOCKS;
|
||||||
|
}
|
||||||
|
if (address != null) {
|
||||||
|
proxy = createProxy(type, address);
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
proxy = null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@SuppressForbidden(reason = "create the actual proxy")
|
||||||
|
private Proxy createProxy(Proxy.Type type, Object[] address) {
|
||||||
|
return new Proxy(type, new InetSocketAddress((String) address[0], (int) address[1]));
|
||||||
|
}
|
||||||
|
|
||||||
|
boolean enabled() {
|
||||||
|
return proxy != null;
|
||||||
|
}
|
||||||
|
|
||||||
|
Proxy proxy() {
|
||||||
|
return proxy;
|
||||||
|
}
|
||||||
|
|
||||||
|
// returns hostname (string), port (int)
|
||||||
|
private static Object[] host(String address, int defaultPort) {
|
||||||
|
if (!StringUtils.hasText(address)) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
try {
|
||||||
|
URI uri = new URI(address);
|
||||||
|
Object[] results = { uri.getHost(), uri.getPort() > 0 ? uri.getPort() : defaultPort };
|
||||||
|
return results;
|
||||||
|
} catch (URISyntaxException ex) {
|
||||||
|
throw new ClientException("Unrecognized address format %s", address);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,164 @@
|
||||||
|
/*
|
||||||
|
* 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.net.client;
|
||||||
|
|
||||||
|
import org.elasticsearch.xpack.sql.net.client.util.StringUtils;
|
||||||
|
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.io.InputStream;
|
||||||
|
import java.nio.file.Files;
|
||||||
|
import java.nio.file.Path;
|
||||||
|
import java.nio.file.Paths;
|
||||||
|
import java.nio.file.StandardOpenOption;
|
||||||
|
import java.security.GeneralSecurityException;
|
||||||
|
import java.security.KeyStore;
|
||||||
|
import java.util.Objects;
|
||||||
|
import java.util.Properties;
|
||||||
|
|
||||||
|
import javax.net.ssl.KeyManager;
|
||||||
|
import javax.net.ssl.KeyManagerFactory;
|
||||||
|
import javax.net.ssl.SSLContext;
|
||||||
|
import javax.net.ssl.SSLSocketFactory;
|
||||||
|
import javax.net.ssl.TrustManager;
|
||||||
|
import javax.net.ssl.TrustManagerFactory;
|
||||||
|
|
||||||
|
class SslConfig {
|
||||||
|
|
||||||
|
private static final String SSL = "ssl";
|
||||||
|
private static final String SSL_DEFAULT = "false";
|
||||||
|
|
||||||
|
private static final String SSL_PROTOCOL = "ssl.protocol";
|
||||||
|
private static final String SSL_PROTOCOL_DEFAULT = "TLS"; // SSL alternative
|
||||||
|
|
||||||
|
private static final String SSL_KEYSTORE_LOCATION = "ssl.keystore.location";
|
||||||
|
private static final String SSL_KEYSTORE_LOCATION_DEFAULT = "";
|
||||||
|
|
||||||
|
private static final String SSL_KEYSTORE_PASS = "ssl.keystore.pass";
|
||||||
|
private static final String SSL_KEYSTORE_PASS_DEFAULT = "";
|
||||||
|
|
||||||
|
private static final String SSL_KEYSTORE_TYPE = "ssl.keystore.type";
|
||||||
|
private static final String SSL_KEYSTORE_TYPE_DEFAULT = "JKS"; // PCKS12
|
||||||
|
|
||||||
|
private static final String SSL_TRUSTSTORE_LOCATION = "ssl.truststore.location";
|
||||||
|
private static final String SSL_TRUSTSTORE_LOCATION_DEFAULT = "";
|
||||||
|
|
||||||
|
private static final String SSL_TRUSTSTORE_PASS = "ssl.truststore.pass";
|
||||||
|
private static final String SSL_TRUSTSTORE_PASS_DEFAULT = "";
|
||||||
|
|
||||||
|
private static final String SSL_TRUSTSTORE_TYPE = "ssl.truststore.type";
|
||||||
|
private static final String SSL_TRUSTSTORE_TYPE_DEFAULT = "JKS";
|
||||||
|
|
||||||
|
private final boolean enabled;
|
||||||
|
private final String protocol, keystoreLocation, keystorePass, keystoreType;
|
||||||
|
private final String truststoreLocation, truststorePass, truststoreType;
|
||||||
|
|
||||||
|
private final SSLContext sslContext;
|
||||||
|
|
||||||
|
SslConfig(Properties settings) {
|
||||||
|
// ssl
|
||||||
|
enabled = StringUtils.parseBoolean(settings.getProperty(SSL, SSL_DEFAULT));
|
||||||
|
protocol = settings.getProperty(SSL_PROTOCOL, SSL_PROTOCOL_DEFAULT);
|
||||||
|
keystoreLocation = settings.getProperty(SSL_KEYSTORE_LOCATION, SSL_KEYSTORE_LOCATION_DEFAULT);
|
||||||
|
keystorePass = settings.getProperty(SSL_KEYSTORE_PASS, SSL_KEYSTORE_PASS_DEFAULT);
|
||||||
|
keystoreType = settings.getProperty(SSL_KEYSTORE_TYPE, SSL_KEYSTORE_TYPE_DEFAULT);
|
||||||
|
truststoreLocation = settings.getProperty(SSL_TRUSTSTORE_LOCATION, SSL_TRUSTSTORE_LOCATION_DEFAULT);
|
||||||
|
truststorePass = settings.getProperty(SSL_TRUSTSTORE_PASS, SSL_TRUSTSTORE_PASS_DEFAULT);
|
||||||
|
truststoreType = settings.getProperty(SSL_TRUSTSTORE_TYPE, SSL_TRUSTSTORE_TYPE_DEFAULT);
|
||||||
|
|
||||||
|
sslContext = enabled ? createSSLContext() : null;
|
||||||
|
}
|
||||||
|
|
||||||
|
// ssl
|
||||||
|
boolean isEnabled() {
|
||||||
|
return enabled;
|
||||||
|
}
|
||||||
|
|
||||||
|
SSLSocketFactory sslSocketFactory() {
|
||||||
|
return sslContext.getSocketFactory();
|
||||||
|
}
|
||||||
|
|
||||||
|
private SSLContext createSSLContext() {
|
||||||
|
SSLContext ctx;
|
||||||
|
try {
|
||||||
|
ctx = SSLContext.getInstance(protocol);
|
||||||
|
ctx.init(loadKeyManagers(), loadTrustManagers(), null);
|
||||||
|
} catch (Exception ex) {
|
||||||
|
throw new ClientException(ex, "Failed to initialize SSL - %s", ex.getMessage());
|
||||||
|
}
|
||||||
|
|
||||||
|
return ctx;
|
||||||
|
}
|
||||||
|
|
||||||
|
private KeyManager[] loadKeyManagers() throws GeneralSecurityException, IOException {
|
||||||
|
if (!StringUtils.hasText(keystoreLocation)) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
char[] pass = (StringUtils.hasText(keystorePass) ? keystorePass.trim().toCharArray() : null);
|
||||||
|
KeyStore keyStore = loadKeyStore(keystoreLocation, pass, keystoreType);
|
||||||
|
KeyManagerFactory kmFactory = KeyManagerFactory.getInstance(KeyManagerFactory.getDefaultAlgorithm());
|
||||||
|
kmFactory.init(keyStore, pass);
|
||||||
|
return kmFactory.getKeyManagers();
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
private KeyStore loadKeyStore(String location, char[] pass, String keyStoreType) throws GeneralSecurityException, IOException {
|
||||||
|
KeyStore keyStore = KeyStore.getInstance(keyStoreType);
|
||||||
|
Path path = Paths.get(location);
|
||||||
|
|
||||||
|
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);
|
||||||
|
}
|
||||||
|
|
||||||
|
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;
|
||||||
|
}
|
||||||
|
|
||||||
|
private TrustManager[] loadTrustManagers() throws GeneralSecurityException, IOException {
|
||||||
|
KeyStore keyStore = null;
|
||||||
|
|
||||||
|
if (StringUtils.hasText(truststoreLocation)) {
|
||||||
|
char[] pass = (StringUtils.hasText(truststorePass) ? truststorePass.trim().toCharArray() : null);
|
||||||
|
keyStore = loadKeyStore(truststoreLocation, pass, truststoreType);
|
||||||
|
}
|
||||||
|
|
||||||
|
TrustManagerFactory tmFactory = TrustManagerFactory.getInstance(TrustManagerFactory.getDefaultAlgorithm());
|
||||||
|
tmFactory.init(keyStore);
|
||||||
|
return tmFactory.getTrustManagers();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean equals(Object obj) {
|
||||||
|
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)
|
||||||
|
&& Objects.equals(keystoreLocation, other.keystoreLocation)
|
||||||
|
&& Objects.equals(keystorePass, other.keystorePass)
|
||||||
|
&& Objects.equals(keystoreType, other.keystoreType)
|
||||||
|
&& Objects.equals(truststoreLocation, other.truststoreLocation)
|
||||||
|
&& Objects.equals(truststorePass, other.truststorePass)
|
||||||
|
&& Objects.equals(truststoreType, other.truststoreType);
|
||||||
|
}
|
||||||
|
|
||||||
|
public int hashCode() {
|
||||||
|
return getClass().hashCode();
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,119 @@
|
||||||
|
/*
|
||||||
|
* 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.net.client;
|
||||||
|
|
||||||
|
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;
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,115 @@
|
||||||
|
/*
|
||||||
|
* 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.net.client;
|
||||||
|
|
||||||
|
|
||||||
|
import org.elasticsearch.common.io.PathUtils;
|
||||||
|
import org.elasticsearch.test.ESTestCase;
|
||||||
|
import org.elasticsearch.xpack.sql.net.client.util.Bytes;
|
||||||
|
import org.junit.After;
|
||||||
|
import org.junit.Before;
|
||||||
|
import org.junit.ClassRule;
|
||||||
|
import org.junit.rules.ExternalResource;
|
||||||
|
|
||||||
|
import java.io.ByteArrayInputStream;
|
||||||
|
import java.io.DataInputStream;
|
||||||
|
import java.net.URL;
|
||||||
|
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;
|
||||||
|
|
||||||
|
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(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(JreHttpUrlConnection.http(sslServer, cfg, JreHttpUrlConnection::head));
|
||||||
|
}
|
||||||
|
|
||||||
|
public void testSslPost() throws Exception {
|
||||||
|
String message = UUID.randomUUID().toString();
|
||||||
|
Bytes b = JreHttpUrlConnection.http(sslServer, cfg, c -> {
|
||||||
|
return c.post(o -> {
|
||||||
|
o.writeUTF(message);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
String received = new DataInputStream(new ByteArrayInputStream(b.bytes())).readUTF();
|
||||||
|
assertEquals(message, received);
|
||||||
|
}
|
||||||
|
}
|
Binary file not shown.
|
@ -0,0 +1,13 @@
|
||||||
|
# setup of the SSL files
|
||||||
|
|
||||||
|
# generate keys for server and client
|
||||||
|
$ keytool -v -genkey -keyalg rsa -alias server -keypass password -keystore server.keystore -storepass password -validity 99999 -ext SAN=dns:localhost,ip:127.0.0.1
|
||||||
|
$ keytool -v -genkey -keyalg rsa -alias client -keypass password -keystore client.keystore -storepass password -validity 99999 -ext SAN=dns:localhost,ip:127.0.0.1
|
||||||
|
|
||||||
|
# generate certificates
|
||||||
|
$ keytool -v -export -alias server -file server.crt -keystore server.keystore -storepass password
|
||||||
|
$ keytool -v -export -alias client -file client.crt -keystore client.keystore -storepass password
|
||||||
|
|
||||||
|
# import the client cert into the server keystore and vice-versa
|
||||||
|
$ keytool -v -importcert -alias client -file client.crt -keystore server.keystore -storepass password
|
||||||
|
$ keytool -v -importcert -alias server -file server.crt -keystore client.keystore -storepass password
|
Binary file not shown.
Loading…
Reference in New Issue