/*
* Copyright 2015 floragunn UG (haftungsbeschränkt)
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*
*/
package com.floragunn.searchguard;
import io.netty.handler.ssl.OpenSsl;
import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
import java.io.Reader;
import java.io.StringReader;
import java.io.StringWriter;
import java.io.UnsupportedEncodingException;
import java.net.URL;
import java.net.URLDecoder;
import java.nio.charset.StandardCharsets;
import java.security.KeyStore;
import java.util.HashSet;
import java.util.Objects;
import java.util.Set;
import javax.net.ssl.SSLContext;
import javax.xml.bind.DatatypeConverter;
import junit.framework.Assert;
import org.apache.commons.io.FileUtils;
import org.apache.commons.io.IOUtils;
import org.apache.http.Header;
import org.apache.http.client.methods.CloseableHttpResponse;
import org.apache.http.client.methods.HttpDelete;
import org.apache.http.client.methods.HttpGet;
import org.apache.http.client.methods.HttpHead;
import org.apache.http.client.methods.HttpPost;
import org.apache.http.client.methods.HttpPut;
import org.apache.http.client.methods.HttpUriRequest;
import org.apache.http.config.SocketConfig;
import org.apache.http.conn.ssl.SSLConnectionSocketFactory;
import org.apache.http.conn.ssl.SSLContextBuilder;
import org.apache.http.conn.ssl.SSLContexts;
import org.apache.http.entity.StringEntity;
import org.apache.http.impl.client.CloseableHttpClient;
import org.apache.http.impl.client.HttpClientBuilder;
import org.apache.http.impl.client.HttpClients;
import org.elasticsearch.ElasticsearchTimeoutException;
import org.elasticsearch.action.admin.cluster.health.ClusterHealthResponse;
import org.elasticsearch.action.admin.cluster.node.info.NodeInfo;
import org.elasticsearch.action.admin.cluster.node.info.NodesInfoRequest;
import org.elasticsearch.action.admin.cluster.node.info.NodesInfoResponse;
import org.elasticsearch.client.Client;
import org.elasticsearch.cluster.health.ClusterHealthStatus;
import org.elasticsearch.common.bytes.BytesReference;
import org.elasticsearch.common.logging.ESLogger;
import org.elasticsearch.common.logging.Loggers;
import org.elasticsearch.common.settings.Settings;
import org.elasticsearch.common.transport.InetSocketTransportAddress;
import org.elasticsearch.common.unit.TimeValue;
import org.elasticsearch.common.xcontent.XContentBuilder;
import org.elasticsearch.common.xcontent.XContentFactory;
import org.elasticsearch.common.xcontent.XContentParser;
import org.elasticsearch.common.xcontent.XContentType;
import org.elasticsearch.node.Node;
import org.elasticsearch.node.PluginAwareNode;
import org.junit.After;
import org.junit.Before;
import org.junit.Rule;
import org.junit.rules.TestName;
import org.junit.rules.TestWatcher;
import org.junit.runner.Description;
import com.floragunn.searchguard.ssl.SearchGuardSSLPlugin;
import com.google.common.base.Strings;
public abstract class AbstractUnitTest {
static {
System.out.println("OS: " + System.getProperty("os.name") + " " + System.getProperty("os.arch") + " "
+ System.getProperty("os.version"));
System.out.println("Java Version: " + System.getProperty("java.version") + " " + System.getProperty("java.vendor"));
System.out.println("JVM Impl.: " + System.getProperty("java.vm.version") + " " + System.getProperty("java.vm.vendor") + " "
+ System.getProperty("java.vm.name"));
System.out.println("Open SSL available: "+OpenSsl.isAvailable());
System.out.println("Open SSL version: "+OpenSsl.versionString());
}
@Rule
public TestName name = new TestName();
protected final String clustername = "searchguard_ssl_testcluster";
protected Node esNode1;
private Node esNode2;
private Node esNode3;
private String httpHost = null;
private int httpPort = -1;
protected Set<InetSocketTransportAddress> httpAdresses = new HashSet<InetSocketTransportAddress>();
protected String nodeHost;
protected int nodePort;
protected boolean enableHTTPClientSSL = false;
protected boolean enableHTTPClientSSLv3Only = false;
protected boolean sendHTTPClientCertificate = false;
protected boolean trustHTTPServerCertificate = false;
protected String keystore = "node-0-keystore.jks";
@Rule
public final TestWatcher testWatcher = new TestWatcher() {
@Override
protected void starting(final Description description) {
final String methodName = description.getMethodName();
String className = description.getClassName();
className = className.substring(className.lastIndexOf('.') + 1);
System.out.println("---------------- Starting JUnit-test: " + className + " " + methodName + " ----------------");
}
@Override
protected void failed(final Throwable e, final Description description) {
final String methodName = description.getMethodName();
String className = description.getClassName();
className = className.substring(className.lastIndexOf('.') + 1);
System.out.println(">>>> " + className + " " + methodName + " FAILED due to " + e);
}
@Override
protected void finished(final Description description) {
// System.out.println("-----------------------------------------------------------------------------------------");
}
};
protected AbstractUnitTest() {
super();
}
// @formatter:off
private Settings.Builder getDefaultSettingsBuilder(final int nodenum, final boolean dataNode, final boolean masterNode) {
return Settings.settingsBuilder()
.put("node.name", "searchguard_testnode_" + nodenum)
.put("node.data", dataNode)
.put("node.master", masterNode)
.put("cluster.name", clustername)
.put("path.data", "data/data")
.put("path.work", "data/work")
.put("path.logs", "data/logs")
.put("path.conf", "data/config")
.put("path.plugins", "data/plugins")
.put("index.number_of_shards", "1")
.put("index.number_of_replicas", "0")
.put("http.enabled", true)
.put("cluster.routing.allocation.disk.watermark.high","1mb")
.put("cluster.routing.allocation.disk.watermark.low","1mb")
.put("http.cors.enabled", true)
.put("node.local", false)
.put("discovery.zen.minimum_master_nodes", 1)
.put("path.home",".");
}
// @formatter:on
protected final ESLogger log = Loggers.getLogger(this.getClass());
protected final String getHttpServerUri() {
final String address = "http" + (enableHTTPClientSSL ? "s" : "") + "://" + httpHost + ":" + httpPort;
log.debug("Connect to {}", address);
return address;
}
public final void startES(final Settings settings) throws Exception {
startES(settings, 30, 3);
}
public final void startES(final Settings settings, int timeOutSec, int assertNodes) throws Exception {
FileUtils.deleteDirectory(new File("data"));
esNode1 = new PluginAwareNode(getDefaultSettingsBuilder(1, false, true).put(
settings == null ? Settings.Builder.EMPTY_SETTINGS : settings).build(), SearchGuardSSLPlugin.class, SearchGuardPlugin.class);
esNode2 = new PluginAwareNode(getDefaultSettingsBuilder(2, true, true).put(
settings == null ? Settings.Builder.EMPTY_SETTINGS : settings).build(), SearchGuardSSLPlugin.class, SearchGuardPlugin.class);
esNode3 = new PluginAwareNode(getDefaultSettingsBuilder(3, true, false).put(
settings == null ? Settings.Builder.EMPTY_SETTINGS : settings).build(), SearchGuardSSLPlugin.class, SearchGuardPlugin.class);
esNode1.start();
esNode2.start();
esNode3.start();
waitForCluster(ClusterHealthStatus.GREEN, TimeValue.timeValueSeconds(timeOutSec), esNode1.client(), assertNodes);
}
protected Client client() {
return esNode1.client();
}
@Before
public void setUp() throws Exception {
enableHTTPClientSSL = false;
enableHTTPClientSSLv3Only = false;
}
@After
public void tearDown() throws Exception {
Thread.sleep(500);
if (esNode3 != null) {
esNode3.close();
}
if (esNode2 != null) {
esNode2.close();
}
if (esNode1 != null) {
esNode1.close();
}
}
protected void waitForGreenClusterState(final Client client) throws IOException {
waitForCluster(ClusterHealthStatus.GREEN, TimeValue.timeValueSeconds(30), client, 3);
}
protected void waitForCluster(final ClusterHealthStatus status, final TimeValue timeout, final Client client, int assertNodes) throws IOException {
try {
log.debug("waiting for cluster state {}", status.name());
final ClusterHealthResponse healthResponse = client.admin().cluster().prepareHealth().setWaitForStatus(status)
.setTimeout(timeout).setWaitForNodes(String.valueOf(assertNodes)).execute().actionGet();
if (healthResponse.isTimedOut()) {
throw new IOException("cluster state is " + healthResponse.getStatus().name() + " with "
+ healthResponse.getNumberOfNodes() + " nodes");
} else {
log.debug("... cluster state ok " + healthResponse.getStatus().name() + " with " + healthResponse.getNumberOfNodes()
+ " nodes");
}
org.junit.Assert.assertEquals(assertNodes, healthResponse.getNumberOfNodes());
final NodesInfoResponse res = esNode1.client().admin().cluster().nodesInfo(new NodesInfoRequest()).actionGet();
final NodeInfo[] nodes = res.getNodes();
for (int i = 0; i < nodes.length; i++) {
final NodeInfo nodeInfo = nodes[i];
if (nodeInfo.getHttp() != null && nodeInfo.getHttp().address() != null) {
final InetSocketTransportAddress is = (InetSocketTransportAddress) nodeInfo.getHttp().address().publishAddress();
httpPort = is.getPort();
httpHost = is.getHost();
httpAdresses.add(is);
}
final InetSocketTransportAddress is = (InetSocketTransportAddress) nodeInfo.getTransport().getAddress().publishAddress();
nodePort = is.getPort();
nodeHost = is.getHost();
}
} catch (final ElasticsearchTimeoutException e) {
throw new IOException("timeout, cluster does not respond to health request, cowardly refusing to continue with operations");
}
}
public File getAbsoluteFilePathFromClassPath(final String fileNameFromClasspath) {
File file = null;
final URL fileUrl = AbstractUnitTest.class.getClassLoader().getResource(fileNameFromClasspath);
if (fileUrl != null) {
try {
file = new File(URLDecoder.decode(fileUrl.getFile(), "UTF-8"));
} catch (final UnsupportedEncodingException e) {
return null;
}
if (file.exists() && file.canRead()) {
return file;
} else {
log.error("Cannot read from {}, maybe the file does not exists? ", file.getAbsolutePath());
}
} else {
log.error("Failed to load " + fileNameFromClasspath);
}
return null;
}
protected String executeSimpleRequest(final String request) throws Exception {
CloseableHttpClient httpClient = null;
CloseableHttpResponse response = null;
try {
httpClient = getHTTPClient();
response = httpClient.execute(new HttpGet(getHttpServerUri() + "/" + request));
if (response.getStatusLine().getStatusCode() >= 300) {
throw new Exception("Statuscode " + response.getStatusLine().getStatusCode());
}
return IOUtils.toString(response.getEntity().getContent(), StandardCharsets.UTF_8);
} finally {
if (response != null) {
response.close();
}
if (httpClient != null) {
httpClient.close();
}
}
}
protected class HttpResponse {
private final CloseableHttpResponse inner;
private final String body;
private final Header[] header;
private final int statusCode;
private final String statusReason;
public HttpResponse(CloseableHttpResponse inner) throws IllegalStateException, IOException {
super();
this.inner = inner;
this.body = inner.getEntity() == null? null : IOUtils.toString(inner.getEntity().getContent(), StandardCharsets.UTF_8);
this.header = inner.getAllHeaders();
this.statusCode = inner.getStatusLine().getStatusCode();
this.statusReason = inner.getStatusLine().getReasonPhrase();
inner.close();
}
public CloseableHttpResponse getInner() {
return inner;
}
public String getBody() {
return body;
}
public Header[] getHeader() {
return header;
}
public int getStatusCode() {
return statusCode;
}
public String getStatusReason() {
return statusReason;
}
}
protected HttpResponse executeGetRequest(final String request, Header... header) throws Exception {
return executeRequest(new HttpGet(getHttpServerUri() + "/" + request), header);
}
protected HttpResponse executeHeadRequest(final String request, Header... header) throws Exception {
return executeRequest(new HttpHead(getHttpServerUri() + "/" + request), header);
}
protected HttpResponse executePutRequest(final String request, String body, Header... header) throws Exception {
HttpPut uriRequest = new HttpPut(getHttpServerUri() + "/" + request);
if(!Strings.isNullOrEmpty(body)) {
uriRequest.setEntity(new StringEntity(body));
}
return executeRequest(uriRequest, header);
}
protected HttpResponse executePostRequest(final String request, String body, Header... header) throws Exception {
HttpPost uriRequest = new HttpPost(getHttpServerUri() + "/" + request);
if(!Strings.isNullOrEmpty(body)) {
uriRequest.setEntity(new StringEntity(body));
}
return executeRequest(uriRequest, header);
}
protected HttpResponse executeDeleteRequest(final String request, Header... header) throws Exception {
return executeRequest(new HttpDelete(getHttpServerUri() + "/" + request), header);
}
protected HttpResponse executeRequest(HttpUriRequest uriRequest, Header... header) throws Exception {
CloseableHttpClient httpClient = null;
try {
httpClient = getHTTPClient();
if(header != null && header.length > 0) {
for (int i = 0; i < header.length; i++) {
Header h = header[i];
uriRequest.addHeader(h);
}
}
HttpResponse res = new HttpResponse(httpClient.execute(uriRequest));
log.trace(res.getBody());
return res;
} finally {
if (httpClient != null) {
httpClient.close();
}
}
}
protected final CloseableHttpClient getHTTPClient() throws Exception {
final HttpClientBuilder hcb = HttpClients.custom();
if (enableHTTPClientSSL) {
log.debug("Configure HTTP client with SSL");
final KeyStore myTrustStore = KeyStore.getInstance("JKS");
myTrustStore.load(new FileInputStream(getAbsoluteFilePathFromClassPath("truststore.jks")), "changeit".toCharArray());
final KeyStore keyStore = KeyStore.getInstance("JKS");
keyStore.load(new FileInputStream(getAbsoluteFilePathFromClassPath(keystore)), "changeit".toCharArray());
final SSLContextBuilder sslContextbBuilder = SSLContexts.custom().useTLS();
if (trustHTTPServerCertificate) {
sslContextbBuilder.loadTrustMaterial(myTrustStore);
}
if (sendHTTPClientCertificate) {
sslContextbBuilder.loadKeyMaterial(keyStore, "changeit".toCharArray());
}
final SSLContext sslContext = sslContextbBuilder.build();
String[] protocols = null;
if (enableHTTPClientSSLv3Only) {
protocols = new String[] { "SSLv3" };
} else {
protocols = new String[] { "TLSv1", "TLSv1.1", "TLSv1.2" };
}
final SSLConnectionSocketFactory sslsf = new SSLConnectionSocketFactory(sslContext, protocols, null,
SSLConnectionSocketFactory.ALLOW_ALL_HOSTNAME_VERIFIER);
hcb.setSSLSocketFactory(sslsf);
}
hcb.setDefaultSocketConfig(SocketConfig.custom().setSoTimeout(60 * 1000).build());
return hcb.build();
}
protected final String loadFile(final String file) throws IOException {
final StringWriter sw = new StringWriter();
IOUtils.copy(this.getClass().getResourceAsStream("/" + file), sw, StandardCharsets.UTF_8);
return sw.toString();
}
protected BytesReference readYamlContent(final String file) {
try {
return readXContent(new StringReader(loadFile(file)), XContentType.YAML);
} catch (IOException e) {
throw new RuntimeException(e);
}
}
protected BytesReference readXContent(final Reader reader, final XContentType xContentType) throws IOException {
XContentParser parser = null;
try {
parser = XContentFactory.xContent(xContentType).createParser(reader);
parser.nextToken();
final XContentBuilder builder = XContentFactory.jsonBuilder();
builder.copyCurrentStructure(parser);
return builder.bytes();
} finally {
if (parser != null) {
parser.close();
}
}
}
public static String encodeBasicHeader(final String username, final String password) {
return new String(DatatypeConverter.printBase64Binary((username + ":" + Objects.requireNonNull(password)).getBytes(StandardCharsets.UTF_8)));
}
}