allow the system truststore to be used if one is not specified
A truststore should not be required as the default system truststore can be used to validate certificates that have been signed by most commercial CAs. Additionally, the HttpClient is now a lifecycle component to prevent out of memory exceptions when starting up with a bad configuration; when an exception is thrown in the constructor, Guice will continue to try to create the object until the system runs out of memory. Closes elastic/elasticsearch#476 Original commit: elastic/x-pack-elasticsearch@2333e47ac1
This commit is contained in:
parent
5796a771c0
commit
09621f1267
|
@ -15,6 +15,7 @@ import org.elasticsearch.plugins.AbstractPlugin;
|
|||
import org.elasticsearch.watcher.actions.email.service.InternalEmailService;
|
||||
import org.elasticsearch.watcher.history.HistoryModule;
|
||||
import org.elasticsearch.watcher.license.LicenseService;
|
||||
import org.elasticsearch.watcher.support.http.HttpClient;
|
||||
import org.elasticsearch.watcher.support.init.InitializingService;
|
||||
|
||||
import java.util.Collection;
|
||||
|
@ -65,7 +66,8 @@ public class WatcherPlugin extends AbstractPlugin {
|
|||
// constructs
|
||||
InitializingService.class,
|
||||
LicenseService.class,
|
||||
InternalEmailService.class);
|
||||
InternalEmailService.class,
|
||||
HttpClient.class);
|
||||
}
|
||||
|
||||
@Override
|
||||
|
|
|
@ -5,10 +5,11 @@
|
|||
*/
|
||||
package org.elasticsearch.watcher.support.http;
|
||||
|
||||
import org.elasticsearch.ElasticsearchException;
|
||||
import org.elasticsearch.ElasticsearchIllegalStateException;
|
||||
import org.elasticsearch.ExceptionsHelper;
|
||||
import org.elasticsearch.common.base.Charsets;
|
||||
import org.elasticsearch.common.component.AbstractComponent;
|
||||
import org.elasticsearch.common.component.AbstractLifecycleComponent;
|
||||
import org.elasticsearch.common.inject.Inject;
|
||||
import org.elasticsearch.common.io.Streams;
|
||||
import org.elasticsearch.common.settings.Settings;
|
||||
|
@ -29,7 +30,7 @@ import java.util.Map;
|
|||
/**
|
||||
* Client class to wrap http connections
|
||||
*/
|
||||
public class HttpClient extends AbstractComponent {
|
||||
public class HttpClient extends AbstractLifecycleComponent<HttpClient> {
|
||||
|
||||
private static final String SETTINGS_SSL_PREFIX = "watcher.http.ssl.";
|
||||
private static final String SETTINGS_SSL_SHIELD_PREFIX = "shield.ssl.";
|
||||
|
@ -43,13 +44,18 @@ public class HttpClient extends AbstractComponent {
|
|||
public static final String SETTINGS_SSL_TRUSTSTORE_ALGORITHM = SETTINGS_SSL_PREFIX + "truststore.algorithm";
|
||||
private static final String SETTINGS_SSL_SHIELD_TRUSTSTORE_ALGORITHM = SETTINGS_SSL_SHIELD_PREFIX + "truststore.algorithm";
|
||||
|
||||
private final SSLSocketFactory sslSocketFactory;
|
||||
private final HttpAuthRegistry httpAuthRegistry;
|
||||
|
||||
private SSLSocketFactory sslSocketFactory;
|
||||
|
||||
@Inject
|
||||
public HttpClient(Settings settings, HttpAuthRegistry httpAuthRegistry) {
|
||||
super(settings);
|
||||
this.httpAuthRegistry = httpAuthRegistry;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void doStart() throws ElasticsearchException {
|
||||
if (!settings.getByPrefix(SETTINGS_SSL_PREFIX).getAsMap().isEmpty() ||
|
||||
!settings.getByPrefix(SETTINGS_SSL_SHIELD_PREFIX).getAsMap().isEmpty()) {
|
||||
sslSocketFactory = createSSLSocketFactory(settings);
|
||||
|
@ -59,6 +65,14 @@ public class HttpClient extends AbstractComponent {
|
|||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void doStop() throws ElasticsearchException {
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void doClose() throws ElasticsearchException {
|
||||
}
|
||||
|
||||
public HttpResponse execute(HttpRequest request) throws IOException {
|
||||
String queryString = null;
|
||||
if (request.params() != null && !request.params().isEmpty()) {
|
||||
|
@ -135,7 +149,7 @@ public class HttpClient extends AbstractComponent {
|
|||
String trustStoreAlgorithm = settings.get(SETTINGS_SSL_TRUSTSTORE_ALGORITHM, settings.get(SETTINGS_SSL_SHIELD_TRUSTSTORE_ALGORITHM, System.getProperty("ssl.TrustManagerFactory.algorithm")));
|
||||
|
||||
if (trustStore == null) {
|
||||
throw new RuntimeException("truststore is not configured, use " + SETTINGS_SSL_TRUSTSTORE);
|
||||
logger.debug("no truststore defined, using system default");
|
||||
}
|
||||
|
||||
if (trustStoreAlgorithm == null) {
|
||||
|
@ -148,27 +162,9 @@ public class HttpClient extends AbstractComponent {
|
|||
throw new ElasticsearchIllegalStateException("could not find truststore [" + trustStore + "]");
|
||||
}
|
||||
|
||||
KeyManager[] keyManagers;
|
||||
TrustManager[] trustManagers;
|
||||
try (InputStream trustStoreStream = Files.newInputStream(path)) {
|
||||
// Load TrustStore
|
||||
KeyStore ks = KeyStore.getInstance("jks");
|
||||
ks.load(trustStoreStream, trustStorePassword == null ? null : trustStorePassword.toCharArray());
|
||||
|
||||
KeyManagerFactory factory = KeyManagerFactory.getInstance(KeyManagerFactory.getDefaultAlgorithm());
|
||||
factory.init(ks, trustStorePassword == null ? null : trustStorePassword.toCharArray());
|
||||
keyManagers = factory.getKeyManagers();
|
||||
|
||||
// Initialize a trust manager factory with the trusted store
|
||||
TrustManagerFactory trustFactory = TrustManagerFactory.getInstance(trustStoreAlgorithm);
|
||||
trustFactory.init(ks);
|
||||
|
||||
// Retrieve the trust managers from the factory
|
||||
trustManagers = trustFactory.getTrustManagers();
|
||||
} catch (Exception e) {
|
||||
throw new RuntimeException("http client failed to initialize a TrustManagerFactory", e);
|
||||
}
|
||||
|
||||
// FIXME a keystore should be configurable and key_password also needs to be allowed. The fallback to Shield settings can be problematic without this
|
||||
KeyManager[] keyManagers = keyManagers(trustStore, trustStorePassword, trustStoreAlgorithm, trustStorePassword);
|
||||
TrustManager[] trustManagers = trustManagers(trustStore, trustStorePassword, trustStoreAlgorithm);
|
||||
sslContext = SSLContext.getInstance(sslContextProtocol);
|
||||
sslContext.init(keyManagers, trustManagers, new SecureRandom());
|
||||
} catch (Exception e) {
|
||||
|
@ -180,4 +176,49 @@ public class HttpClient extends AbstractComponent {
|
|||
public SSLSocketFactory getSslSocketFactory() {
|
||||
return sslSocketFactory;
|
||||
}
|
||||
|
||||
private static KeyManager[] keyManagers(String keyStore, String keyStorePassword, String keyStoreAlgorithm, String keyPassword) {
|
||||
if (keyStore == null) {
|
||||
return null;
|
||||
}
|
||||
|
||||
try {
|
||||
// Load KeyStore
|
||||
KeyStore ks = readKeystore(keyStore, keyStorePassword);
|
||||
|
||||
// Initialize KeyManagerFactory
|
||||
KeyManagerFactory kmf = KeyManagerFactory.getInstance(keyStoreAlgorithm);
|
||||
kmf.init(ks, keyPassword.toCharArray());
|
||||
return kmf.getKeyManagers();
|
||||
} catch (Exception e) {
|
||||
throw new RuntimeException("http client failed to initialize a KeyManagerFactory", e);
|
||||
}
|
||||
}
|
||||
|
||||
private static TrustManager[] trustManagers(String trustStorePath, String trustStorePassword, String trustStoreAlgorithm) {
|
||||
try {
|
||||
// Load TrustStore
|
||||
KeyStore ks = null;
|
||||
if (trustStorePath != null) {
|
||||
ks = readKeystore(trustStorePath, trustStorePassword);
|
||||
}
|
||||
|
||||
// Initialize a trust manager factory with the trusted store
|
||||
TrustManagerFactory trustFactory = TrustManagerFactory.getInstance(trustStoreAlgorithm);
|
||||
trustFactory.init(ks);
|
||||
return trustFactory.getTrustManagers();
|
||||
} catch (Exception e) {
|
||||
throw new RuntimeException("http client failed to initialize a TrustManagerFactory", e);
|
||||
}
|
||||
}
|
||||
|
||||
private static KeyStore readKeystore(String path, String password) throws Exception {
|
||||
try (InputStream in = Files.newInputStream(Paths.get(path))) {
|
||||
// Load TrustStore
|
||||
KeyStore ks = KeyStore.getInstance("jks");
|
||||
assert password != null;
|
||||
ks.load(in, password.toCharArray());
|
||||
return ks;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -59,7 +59,7 @@ public class IndexActionTests extends ElasticsearchIntegrationTest {
|
|||
Watch watch = WatcherTestUtils.createTestWatch("test_watch",
|
||||
ClientProxy.of(client()),
|
||||
ScriptServiceProxy.of(internalCluster().getInstance(ScriptService.class)),
|
||||
new HttpClient(ImmutableSettings.EMPTY, mock(HttpAuthRegistry.class)),
|
||||
new HttpClient(ImmutableSettings.EMPTY, mock(HttpAuthRegistry.class)).start(),
|
||||
new EmailService() {
|
||||
@Override
|
||||
public EmailService.EmailSent send(Email email, Authentication auth, Profile profile) {
|
||||
|
|
|
@ -14,6 +14,8 @@ import org.elasticsearch.common.base.Charsets;
|
|||
import org.elasticsearch.common.collect.ImmutableMap;
|
||||
import org.elasticsearch.common.settings.ImmutableSettings;
|
||||
import org.elasticsearch.test.ElasticsearchTestCase;
|
||||
import org.elasticsearch.test.junit.annotations.Network;
|
||||
import org.elasticsearch.test.rest.client.http.HttpRequestBuilder;
|
||||
import org.elasticsearch.watcher.support.http.auth.HttpAuthFactory;
|
||||
import org.elasticsearch.watcher.support.http.auth.HttpAuthRegistry;
|
||||
import org.elasticsearch.watcher.support.http.auth.basic.BasicAuth;
|
||||
|
@ -28,6 +30,7 @@ import java.nio.file.Path;
|
|||
import java.nio.file.Paths;
|
||||
|
||||
import static org.hamcrest.Matchers.equalTo;
|
||||
import static org.hamcrest.Matchers.notNullValue;
|
||||
import static org.hamcrest.Matchers.nullValue;
|
||||
import static org.hamcrest.core.Is.is;
|
||||
|
||||
|
@ -50,7 +53,7 @@ public class HttpClientTest extends ElasticsearchTestCase {
|
|||
try {
|
||||
webServer = new MockWebServer();
|
||||
webServer.start(webPort);
|
||||
httpClient = new HttpClient(ImmutableSettings.EMPTY, authRegistry);
|
||||
httpClient = new HttpClient(ImmutableSettings.EMPTY, authRegistry).start();
|
||||
return;
|
||||
} catch (BindException be) {
|
||||
logger.warn("port [{}] was already in use trying next port", webPort);
|
||||
|
@ -139,7 +142,7 @@ public class HttpClientTest extends ElasticsearchTestCase {
|
|||
ImmutableSettings.builder()
|
||||
.put(HttpClient.SETTINGS_SSL_TRUSTSTORE, resource.toString())
|
||||
.put(HttpClient.SETTINGS_SSL_TRUSTSTORE_PASSWORD, "testnode")
|
||||
.build(), authRegistry);
|
||||
.build(), authRegistry).start();
|
||||
webServer.useHttps(httpClient.getSslSocketFactory(), false);
|
||||
|
||||
webServer.enqueue(new MockResponse().setResponseCode(200).setBody("body"));
|
||||
|
@ -169,5 +172,16 @@ public class HttpClientTest extends ElasticsearchTestCase {
|
|||
assertThat(response.body(), nullValue());
|
||||
}
|
||||
|
||||
@Test
|
||||
@Network
|
||||
public void testHttpsWithoutTruststore() throws Exception {
|
||||
HttpClient httpClient = new HttpClient(ImmutableSettings.EMPTY, authRegistry).start();
|
||||
// Known server with a valid cert from a commercial CA
|
||||
HttpRequest.Builder request = HttpRequest.builder("www.elastic.co", 443).scheme(Scheme.HTTPS);
|
||||
HttpResponse response = httpClient.execute(request.build());
|
||||
assertThat(response.status(), equalTo(200));
|
||||
assertThat(response.hasContent(), is(true));
|
||||
assertThat(response.body(), notNullValue());
|
||||
}
|
||||
|
||||
}
|
||||
|
|
Loading…
Reference in New Issue