Skip to content

Commit

Permalink
Add a test for secured cluster
Browse files Browse the repository at this point in the history
This can be now activated for free with the default basic license.
Also change latest version to 7.9.2.
  • Loading branch information
dadoonet committed Oct 15, 2020
1 parent ae5010a commit 04e5366
Show file tree
Hide file tree
Showing 4 changed files with 111 additions and 20 deletions.
13 changes: 11 additions & 2 deletions docs/modules/elasticsearch.md
Original file line number Diff line number Diff line change
Expand Up @@ -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

Expand All @@ -15,10 +15,19 @@ You can start an elasticsearch container instance from any Java application by u
<!--/codeinclude-->


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:

<!--codeinclude-->
[HttpClient](../../modules/elasticsearch/src/test/java/org/testcontainers/elasticsearch/ElasticsearchContainerTest.java) inside_block:httpClientSecuredContainer
<!--/codeinclude-->

## 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
Expand Down
2 changes: 1 addition & 1 deletion modules/elasticsearch/build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -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"
}
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,9 @@ public class ElasticsearchContainer extends GenericContainer<ElasticsearchContai

/**
* Elasticsearch Default Transport port
* The TransportClient will be removed in Elasticsearch 8. No need to expose this port anymore in the future.
*/
@Deprecated
private static final int ELASTICSEARCH_DEFAULT_TCP_PORT = 9300;

/**
Expand All @@ -35,7 +37,8 @@ public class ElasticsearchContainer extends GenericContainer<ElasticsearchContai
/**
* Elasticsearch Default version
*/
protected static final String DEFAULT_TAG = "6.4.1";
@Deprecated
protected static final String DEFAULT_TAG = "7.9.2";

/**
* @deprecated use {@link ElasticsearchContainer(DockerImageName)} instead
Expand All @@ -47,15 +50,15 @@ public ElasticsearchContainer() {

/**
* Create an Elasticsearch Container by passing the full docker image name
* @param dockerImageName Full docker image name as a {@link String}, like: docker.elastic.co/elasticsearch/elasticsearch:6.4.1
* @param dockerImageName Full docker image name as a {@link String}, like: docker.elastic.co/elasticsearch/elasticsearch:7.9.2
*/
public ElasticsearchContainer(String dockerImageName) {
this(DockerImageName.parse(dockerImageName));
}

/**
* Create an Elasticsearch Container by passing the full docker image name
* @param dockerImageName Full docker image name as a {@link DockerImageName}, like: docker.elastic.co/elasticsearch/elasticsearch:6.4.1
* @param dockerImageName Full docker image name as a {@link DockerImageName}, like: DockerImageName.parse("docker.elastic.co/elasticsearch/elasticsearch:7.9.2")
*/
public ElasticsearchContainer(final DockerImageName dockerImageName) {
super(dockerImageName);
Expand All @@ -76,6 +79,7 @@ public String getHttpHostAddress() {
return getHost() + ":" + getMappedPort(ELASTICSEARCH_DEFAULT_PORT);
}

@Deprecated // The TransportClient will be removed in Elasticsearch 8. No need to expose this port anymore in the future.
public InetSocketAddress getTcpHost() {
return new InetSocketAddress(getHost(), getMappedPort(ELASTICSEARCH_DEFAULT_TCP_PORT));
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,6 @@
import static org.hamcrest.CoreMatchers.is;
import static org.hamcrest.MatcherAssert.assertThat;
import static org.rnorth.visibleassertions.VisibleAssertions.assertThrows;
import static org.testcontainers.elasticsearch.ElasticsearchContainer.DEFAULT_TAG;

import java.io.IOException;
import org.apache.http.HttpHost;
Expand All @@ -13,7 +12,6 @@
import org.apache.http.client.CredentialsProvider;
import org.apache.http.impl.client.BasicCredentialsProvider;
import org.apache.http.util.EntityUtils;
import org.elasticsearch.Version;
import org.elasticsearch.action.admin.cluster.health.ClusterHealthResponse;
import org.elasticsearch.client.Request;
import org.elasticsearch.client.Response;
Expand All @@ -32,35 +30,41 @@ public class ElasticsearchContainerTest {
/**
* Elasticsearch version which should be used for the Tests
*/
private static final String ELASTICSEARCH_VERSION = Version.CURRENT.toString();
private static final String ELASTICSEARCH_VERSION = "7.9.2";
private static final DockerImageName ELASTICSEARCH_IMAGE =
DockerImageName
.parse("docker.elastic.co/elasticsearch/elasticsearch")
.withTag(ELASTICSEARCH_VERSION);

/**
* Elasticsearch default username, when secured with a license > 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 {
if (client != null) {
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
Expand All @@ -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/"));
Expand All @@ -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)) {
Expand Down Expand Up @@ -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();

Expand All @@ -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();

Expand Down Expand Up @@ -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;
}
}

0 comments on commit 04e5366

Please sign in to comment.