From 04e5366ae8e3178158220205bb7cd4682001d323 Mon Sep 17 00:00:00 2001 From: David Pilato Date: Mon, 3 Feb 2020 15:39:54 +0100 Subject: [PATCH] Add a test for secured cluster This can be now activated for free with the default basic license. Also change latest version to 7.9.2. --- docs/modules/elasticsearch.md | 13 ++- modules/elasticsearch/build.gradle | 2 +- .../elasticsearch/ElasticsearchContainer.java | 10 +- .../ElasticsearchContainerTest.java | 106 +++++++++++++++--- 4 files changed, 111 insertions(+), 20 deletions(-) diff --git a/docs/modules/elasticsearch.md b/docs/modules/elasticsearch.md index 9959d0b7926..f84a7840e4c 100644 --- a/docs/modules/elasticsearch.md +++ b/docs/modules/elasticsearch.md @@ -3,7 +3,7 @@ This module helps running [elasticsearch](https://www.elastic.co/products/elasticsearch) using Testcontainers. -Note that it's based on the [official Docker image](https://www.elastic.co/guide/en/elasticsearch/reference/6.3/docker.html) provided by elastic. +Note that it's based on the [official Docker image](https://www.elastic.co/guide/en/elasticsearch/reference/current/docker.html) provided by elastic. ## Usage example @@ -15,10 +15,19 @@ You can start an elasticsearch container instance from any Java application by u -Note that if you are still using the [TransportClient](https://www.elastic.co/guide/en/elasticsearch/client/java-api/6.3/transport-client.html) +Note that if you are still using the [TransportClient](https://www.elastic.co/guide/en/elasticsearch/client/java-api/current/transport-client.html) (not recommended as it is deprecated), the default cluster name is set to `docker-cluster` so you need to change `cluster.name` setting or set `client.transport.ignore_cluster_name` to `true`. +## Secure your Elasticsearch cluster + +The default distribution of Elasticsearch comes with the basic license which contains security feature. +You can turn on security by providing some extra environment settings: + + +[HttpClient](../../modules/elasticsearch/src/test/java/org/testcontainers/elasticsearch/ElasticsearchContainerTest.java) inside_block:httpClientSecuredContainer + + ## Choose your Elasticsearch license If you prefer to start a Docker image with the pure OSS version (which means with no security in older versions or diff --git a/modules/elasticsearch/build.gradle b/modules/elasticsearch/build.gradle index e6de1daa7ab..126722759ec 100644 --- a/modules/elasticsearch/build.gradle +++ b/modules/elasticsearch/build.gradle @@ -3,5 +3,5 @@ description = "TestContainers :: elasticsearch" dependencies { compile project(':testcontainers') testCompile "org.elasticsearch.client:elasticsearch-rest-client:7.9.2" - testCompile "org.elasticsearch.client:transport:6.7.1" + testCompile "org.elasticsearch.client:transport:7.9.2" } diff --git a/modules/elasticsearch/src/main/java/org/testcontainers/elasticsearch/ElasticsearchContainer.java b/modules/elasticsearch/src/main/java/org/testcontainers/elasticsearch/ElasticsearchContainer.java index 7ed660d38c6..e0880aa7849 100644 --- a/modules/elasticsearch/src/main/java/org/testcontainers/elasticsearch/ElasticsearchContainer.java +++ b/modules/elasticsearch/src/main/java/org/testcontainers/elasticsearch/ElasticsearchContainer.java @@ -23,7 +23,9 @@ public class ElasticsearchContainer extends GenericContainer basic + * Elasticsearch default username, when secured */ private static final String ELASTICSEARCH_USERNAME = "elastic"; /** - * Elasticsearch 5.x default password. In 6.x images, there's no security by default as shipped with a basic license. + * From 6.8, we can optionally activate security with a default password. */ private static final String ELASTICSEARCH_PASSWORD = "changeme"; private RestClient client = null; + private RestClient anonymousClient = null; @After public void stopRestClient() throws IOException { @@ -56,11 +55,16 @@ public void stopRestClient() throws IOException { client.close(); client = null; } + if (anonymousClient != null) { + anonymousClient.close(); + anonymousClient = null; + } } @SuppressWarnings("deprecation") // Using deprecated constructor for verification of backwards compatibility @Test - public void elasticsearchDefaultTest() throws IOException { + @Deprecated // We will remove this test in the future + public void elasticsearchDeprecatedCtorTest() throws IOException { // Create the elasticsearch container. try (ElasticsearchContainer container = new ElasticsearchContainer() .withEnv("foo", "bar") // dummy env for compiler checking correct generics usage @@ -71,7 +75,29 @@ public void elasticsearchDefaultTest() throws IOException { // Do whatever you want with the rest client ... Response response = getClient(container).performRequest(new Request("GET", "/")); assertThat(response.getStatusLine().getStatusCode(), is(200)); - assertThat(EntityUtils.toString(response.getEntity()), containsString(DEFAULT_TAG)); + assertThat(EntityUtils.toString(response.getEntity()), containsString(ELASTICSEARCH_VERSION)); + + // The default image is running with the features under Elastic License + response = getClient(container).performRequest(new Request("GET", "/_xpack/")); + assertThat(response.getStatusLine().getStatusCode(), is(200)); + // For now we test that we have the monitoring feature available + assertThat(EntityUtils.toString(response.getEntity()), containsString("monitoring")); + } + } + + @Test + public void elasticsearchDefaultTest() throws IOException { + // Create the elasticsearch container. + try (ElasticsearchContainer container = new ElasticsearchContainer(ELASTICSEARCH_IMAGE) + .withEnv("foo", "bar") // dummy env for compiler checking correct generics usage + ) { + // Start the container. This step might take some time... + container.start(); + + // Do whatever you want with the rest client ... + Response response = getClient(container).performRequest(new Request("GET", "/")); + assertThat(response.getStatusLine().getStatusCode(), is(200)); + assertThat(EntityUtils.toString(response.getEntity()), containsString(ELASTICSEARCH_VERSION)); // The default image is running with the features under Elastic License response = getClient(container).performRequest(new Request("GET", "/_xpack/")); @@ -81,6 +107,26 @@ public void elasticsearchDefaultTest() throws IOException { } } + @Test + public void elasticsearchSecuredTest() throws IOException { + try (ElasticsearchContainer container = new ElasticsearchContainer(ELASTICSEARCH_IMAGE) + .withEnv("ELASTIC_PASSWORD", ELASTICSEARCH_PASSWORD) + .withEnv("xpack.security.enabled", "true") + ) { + container.start(); + + // The cluster should be secured so it must fail when we try to access / without credentials + assertThrows("We should not be able to access / URI with an anonymous client.", + ResponseException.class, + () -> getAnonymousClient(container).performRequest(new Request("GET", "/"))); + + // But it should work when we try to access / with the proper login and password + Response response = getClient(container).performRequest(new Request("GET", "/")); + assertThat(response.getStatusLine().getStatusCode(), is(200)); + assertThat(EntityUtils.toString(response.getEntity()), containsString(ELASTICSEARCH_VERSION)); + } + } + @Test public void elasticsearchVersion() throws IOException { try (ElasticsearchContainer container = new ElasticsearchContainer(ELASTICSEARCH_IMAGE)) { @@ -113,12 +159,11 @@ public void elasticsearchOssImage() throws IOException { } } - @SuppressWarnings("deprecation") // Using deprecated constructor for verification of backwards compatibility @Test public void restClientClusterHealth() throws IOException { // httpClientContainer { // Create the elasticsearch container. - try (ElasticsearchContainer container = new ElasticsearchContainer()) { + try (ElasticsearchContainer container = new ElasticsearchContainer(ELASTICSEARCH_IMAGE)) { // Start the container. This step might take some time... container.start(); @@ -140,15 +185,41 @@ public void restClientClusterHealth() throws IOException { // } } + @Test + public void restClientSecuredClusterHealth() throws IOException { + // httpClientSecuredContainer { + // Create the elasticsearch container. + try (ElasticsearchContainer container = new ElasticsearchContainer(ELASTICSEARCH_IMAGE) + // With a password + .withEnv("ELASTIC_PASSWORD", ELASTICSEARCH_PASSWORD) + .withEnv("xpack.security.enabled", "true")) { + // Start the container. This step might take some time... + container.start(); + + // Create the secured client. + final CredentialsProvider credentialsProvider = new BasicCredentialsProvider(); + credentialsProvider.setCredentials(AuthScope.ANY, + new UsernamePasswordCredentials(ELASTICSEARCH_USERNAME, ELASTICSEARCH_PASSWORD)); + + client = RestClient.builder(HttpHost.create(container.getHttpHostAddress())) + .setHttpClientConfigCallback(httpClientBuilder -> httpClientBuilder.setDefaultCredentialsProvider(credentialsProvider)) + .build(); + + Response response = client.performRequest(new Request("GET", "/_cluster/health")); + // }} + assertThat(response.getStatusLine().getStatusCode(), is(200)); + assertThat(EntityUtils.toString(response.getEntity()), containsString("cluster_name")); + // httpClientSecuredContainer {{ + } + // } + } + + @SuppressWarnings("deprecation") // The TransportClient will be removed in Elasticsearch 8. @Test public void transportClientClusterHealth() { // transportClientContainer { // Create the elasticsearch container. - try (ElasticsearchContainer container = new ElasticsearchContainer( - DockerImageName - .parse("docker.elastic.co/elasticsearch/elasticsearch") - .withTag("6.4.1") - )){ + try (ElasticsearchContainer container = new ElasticsearchContainer(ELASTICSEARCH_IMAGE)){ // Start the container. This step might take some time... container.start(); @@ -182,4 +253,11 @@ private RestClient getClient(ElasticsearchContainer container) { return client; } + private RestClient getAnonymousClient(ElasticsearchContainer container) { + if (anonymousClient == null) { + anonymousClient = RestClient.builder(HttpHost.create(container.getHttpHostAddress())).build(); + } + + return anonymousClient; + } }