/* * Copyright OpenSearch Contributors * SPDX-License-Identifier: Apache-2.0 */ package org.opensearch.search.asynchronous; import org.apache.hc.core5.http.Header; import org.apache.hc.core5.http.HttpHost; import org.apache.hc.client5.http.auth.AuthScope; import org.apache.hc.client5.http.auth.UsernamePasswordCredentials; import org.apache.hc.client5.http.impl.auth.BasicCredentialsProvider; import org.apache.hc.core5.http.message.BasicHeader; import org.apache.hc.core5.ssl.SSLContextBuilder; import org.apache.hc.core5.util.Timeout; import org.apache.hc.core5.http.nio.ssl.TlsStrategy; import org.apache.hc.client5.http.ssl.ClientTlsStrategyBuilder; import org.apache.hc.client5.http.impl.nio.PoolingAsyncClientConnectionManager; import org.apache.hc.client5.http.impl.nio.PoolingAsyncClientConnectionManagerBuilder; import org.junit.After; import org.opensearch.client.Request; import org.opensearch.client.Response; import org.opensearch.client.RestClient; import org.opensearch.client.RestClientBuilder; import org.opensearch.common.io.PathUtils; import org.opensearch.common.settings.Settings; import org.opensearch.common.unit.TimeValue; import org.opensearch.common.util.concurrent.ThreadContext; import org.opensearch.common.xcontent.XContentType; import org.opensearch.commons.rest.SecureRestClientBuilder; import org.opensearch.core.xcontent.DeprecationHandler; import org.opensearch.core.xcontent.NamedXContentRegistry; import org.opensearch.core.xcontent.XContentParser; import org.opensearch.test.rest.OpenSearchRestTestCase; import java.io.IOException; import java.net.URI; import java.net.URISyntaxException; import java.nio.file.Path; import java.util.Collections; import java.util.List; import java.util.Map; import java.util.Objects; import java.util.Optional; import java.util.stream.Collectors; import static org.opensearch.commons.ConfigConstants.OPENSEARCH_SECURITY_SSL_HTTP_ENABLED; import static org.opensearch.commons.ConfigConstants.OPENSEARCH_SECURITY_SSL_HTTP_KEYSTORE_FILEPATH; import static org.opensearch.commons.ConfigConstants.OPENSEARCH_SECURITY_SSL_HTTP_KEYSTORE_KEYPASSWORD; import static org.opensearch.commons.ConfigConstants.OPENSEARCH_SECURITY_SSL_HTTP_KEYSTORE_PASSWORD; import static org.opensearch.commons.ConfigConstants.OPENSEARCH_SECURITY_SSL_HTTP_PEMCERT_FILEPATH; public abstract class SecurityEnabledRestTestCase extends OpenSearchRestTestCase { protected boolean isHttps() { boolean isHttps = Optional.ofNullable(System.getProperty("https")).map("true"::equalsIgnoreCase).orElse(false); if (isHttps) { // currently only external cluster is supported for security enabled testing if (Optional.ofNullable(System.getProperty("tests.rest.cluster")).isPresent() == false) { throw new RuntimeException("cluster url should be provided for security enabled testing"); } } return isHttps; } @Override protected String getProtocol() { return isHttps() ? "https" : "http"; } @Override protected Settings restAdminSettings() { return Settings .builder() // disable the warning exception for admin client since it's only used for cleanup. .put("strictDeprecationMode", false) .put("http.port", 9200) .put(OPENSEARCH_SECURITY_SSL_HTTP_ENABLED, isHttps()) .put(OPENSEARCH_SECURITY_SSL_HTTP_PEMCERT_FILEPATH, System.getProperty( OPENSEARCH_SECURITY_SSL_HTTP_PEMCERT_FILEPATH, "sample.pem")) .put(OPENSEARCH_SECURITY_SSL_HTTP_KEYSTORE_FILEPATH, System.getProperty( OPENSEARCH_SECURITY_SSL_HTTP_KEYSTORE_FILEPATH, "test-kirk.jks")) .put(OPENSEARCH_SECURITY_SSL_HTTP_KEYSTORE_PASSWORD, System.getProperty( OPENSEARCH_SECURITY_SSL_HTTP_KEYSTORE_PASSWORD, "changeit")) .put(OPENSEARCH_SECURITY_SSL_HTTP_KEYSTORE_KEYPASSWORD, System.getProperty( OPENSEARCH_SECURITY_SSL_HTTP_KEYSTORE_KEYPASSWORD, "changeit")) .build(); } @Override protected RestClient buildClient(Settings settings, HttpHost[] hosts) throws IOException { RestClientBuilder builder = RestClient.builder(hosts); builder.setStrictDeprecationMode(false); if (isHttps()) { String keystore = settings.get(OPENSEARCH_SECURITY_SSL_HTTP_KEYSTORE_FILEPATH); if (Objects.nonNull(keystore)) { URI uri = null; try { uri = this.getClass().getClassLoader().getResource("security/sample.pem").toURI(); } catch (URISyntaxException e) { throw new RuntimeException(e); } Path configPath = PathUtils.get(uri).getParent().toAbsolutePath(); return new SecureRestClientBuilder(settings, configPath).build(); } else { configureHttpsClient(builder, settings); return builder.build(); } } else { configureClient(builder, settings); return builder.build(); } } @SuppressWarnings("unchecked") @After protected void wipeAllOSIndices() throws IOException { Response response = adminClient().performRequest(new Request("GET", "/_cat/indices?format=json&expand_wildcards=all")); XContentType xContentType = XContentType.fromMediaType(response.getEntity().getContentType()); try ( XContentParser parser = xContentType .xContent() .createParser( NamedXContentRegistry.EMPTY, DeprecationHandler.THROW_UNSUPPORTED_OPERATION, response.getEntity().getContent() ) ) { XContentParser.Token token = parser.nextToken(); List> parserList = null; if (token == XContentParser.Token.START_ARRAY) { parserList = parser.listOrderedMap().stream().map(obj -> (Map) obj).collect(Collectors.toList()); } else { parserList = Collections.singletonList(parser.mapOrdered()); } for (Map index : parserList) { String indexName = (String) index.get("index"); if (indexName != null && !".opendistro_security".equals(indexName)) { adminClient().performRequest(new Request("DELETE", "/" + indexName)); } } } } protected static void configureHttpsClient(RestClientBuilder builder, Settings settings) throws IOException { Map headers = ThreadContext.buildDefaultHeaders(settings); Header[] defaultHeaders = new Header[headers.size()]; int i = 0; for (Map.Entry entry : headers.entrySet()) { defaultHeaders[i++] = new BasicHeader(entry.getKey(), entry.getValue()); } builder.setDefaultHeaders(defaultHeaders); builder.setHttpClientConfigCallback(httpClientBuilder -> { String userName = Optional .ofNullable(System.getProperty("user")) .orElseThrow(() -> new RuntimeException("user name is missing")); String password = Optional .ofNullable(System.getProperty("password")) .orElseThrow(() -> new RuntimeException("password is missing")); BasicCredentialsProvider credentialsProvider = new BasicCredentialsProvider(); credentialsProvider.setCredentials(new AuthScope(new HttpHost("localhost", 9200)), new UsernamePasswordCredentials(userName, password.toCharArray())); try { final TlsStrategy tlsStrategy = ClientTlsStrategyBuilder.create() .setSslContext(SSLContextBuilder.create().loadTrustMaterial(null, (chains, authType) -> true).build()) .build(); final PoolingAsyncClientConnectionManager connectionManager = PoolingAsyncClientConnectionManagerBuilder.create() .setTlsStrategy(tlsStrategy) .build(); return httpClientBuilder .setConnectionManager(connectionManager) .setDefaultCredentialsProvider(credentialsProvider); } catch (Exception e) { throw new RuntimeException(e); } }); final String socketTimeoutString = settings.get(CLIENT_SOCKET_TIMEOUT); final TimeValue socketTimeout = TimeValue .parseTimeValue(socketTimeoutString == null ? "60s" : socketTimeoutString, CLIENT_SOCKET_TIMEOUT); builder.setRequestConfigCallback(conf -> conf.setResponseTimeout(Timeout.ofMilliseconds(Math.toIntExact(socketTimeout.millis())))); if (settings.hasValue(CLIENT_PATH_PREFIX)) { builder.setPathPrefix(settings.get(CLIENT_PATH_PREFIX)); } } /** * wipeAllIndices won't work since it cannot delete security index. Use wipeAllODFEIndices instead. */ @Override protected boolean preserveIndicesUponCompletion() { return true; } }