From f14783dabc2a4f7c7c97a34c6cdaf0329cc1a755 Mon Sep 17 00:00:00 2001 From: Craig Perkins Date: Thu, 31 Jul 2025 11:24:48 -0400 Subject: [PATCH 1/5] Add a PR check to run integ tests with security Signed-off-by: Craig Perkins --- .github/workflows/test_security.yml | 54 ++++ build.gradle | 231 +++++++++++++++++- .../integTest/PluginRestTestCase.kt | 17 +- ...eportsSchedulerBackwardsCompatibilityIT.kt | 2 - 4 files changed, 283 insertions(+), 21 deletions(-) create mode 100644 .github/workflows/test_security.yml diff --git a/.github/workflows/test_security.yml b/.github/workflows/test_security.yml new file mode 100644 index 00000000..ec7c851b --- /dev/null +++ b/.github/workflows/test_security.yml @@ -0,0 +1,54 @@ +name: Test search-relevance on Secure Cluster +on: + schedule: + - cron: '0 0 * * *' # every night + push: + branches: + - "*" + - "feature/**" + pull_request: + branches: + - "*" + - "feature/**" + +jobs: + Get-CI-Image-Tag: + uses: opensearch-project/opensearch-build/.github/workflows/get-ci-image-tag.yml@main + with: + product: opensearch + + integ-test-with-security-linux: + strategy: + matrix: + java: [21, 24] + + name: Run Integration Tests on Linux + runs-on: ubuntu-latest + needs: Get-CI-Image-Tag + container: + # using the same image which is used by opensearch-build team to build the OpenSearch Distribution + # this image tag is subject to change as more dependencies and updates will arrive over time + image: ${{ needs.Get-CI-Image-Tag.outputs.ci-image-version-linux }} + # need to switch to root so that github actions can install runner binary on container without permission issues. + options: ${{ needs.Get-CI-Image-Tag.outputs.ci-image-start-options }} + + steps: + - name: Run start commands + run: ${{ needs.Get-CI-Image-Tag.outputs.ci-image-start-command }} + + - name: Checkout search-relevance + uses: actions/checkout@v4 + with: + submodules: true + + - name: Setup Java ${{ matrix.java }} + uses: actions/setup-java@v4 + with: + distribution: 'temurin' + java-version: ${{ matrix.java }} + + - name: Run tests + # switching the user, as OpenSearch cluster can only be started as root/Administrator on linux-deb/linux-rpm/windows-zip. + run: | + chown -R 1000:1000 `pwd` + su `id -un 1000` -c "whoami && java -version && ./gradlew integTest -Dhttps=true -Dtests.opensearch.username=admin -Dtests.opensearch.password=admin -Dusername=admin -Dpassword=admin -Dtests.opensearch.secure=true -Dsecurity.enabled=true" \ No newline at end of file diff --git a/build.gradle b/build.gradle index 993682cc..735ee5d5 100644 --- a/build.gradle +++ b/build.gradle @@ -3,9 +3,24 @@ * SPDX-License-Identifier: Apache-2.0 */ -import org.opensearch.gradle.test.RestIntegTestTask +import javax.net.ssl.HostnameVerifier +import javax.net.ssl.HttpsURLConnection +import javax.net.ssl.SSLContext +import javax.net.ssl.SSLSession +import javax.net.ssl.TrustManager +import javax.net.ssl.X509TrustManager +import java.nio.charset.StandardCharsets +import java.nio.file.Files import java.util.concurrent.Callable +import java.security.GeneralSecurityException +import java.security.cert.X509Certificate +import org.opensearch.gradle.test.RestIntegTestTask import org.opensearch.gradle.testclusters.StandaloneRestIntegTestTask +import org.opensearch.gradle.testclusters.OpenSearchCluster + +import java.util.concurrent.TimeUnit +import java.util.function.Predicate +import java.util.stream.Collectors buildscript { ext { @@ -50,6 +65,7 @@ plugins { id "com.netflix.nebula.ospackage-base" version "11.6.0" id "com.dorongold.task-tree" version "1.5" id 'java-library' + id "de.undercouch.download" version "5.6.0" } apply plugin: 'java' @@ -118,6 +134,7 @@ configurations { testCompile testRuntime zipArchive + opensearchPlugin } detekt { @@ -149,6 +166,17 @@ ext { projectSubstitutions = [:] licenseFile = rootProject.file('LICENSE.txt') noticeFile = rootProject.file('NOTICE.txt') + ['esnode.pem', 'esnode-key.pem', 'kirk.pem', 'kirk-key.pem', 'root-ca.pem', 'sample.pem', 'test-kirk.jks'].forEach { file -> + File local = getLayout().getBuildDirectory().file(file).get().getAsFile() + download.run { + src "https://raw.githubusercontent.com/opensearch-project/security/refs/heads/main/bwc-test/src/test/resources/security/" + file + dest local + overwrite false + } + processTestResources { + from(local) + } + } } plugins.withId('java') { @@ -177,6 +205,7 @@ repositories { dependencies { zipArchive group: 'org.opensearch.plugin', name:'opensearch-job-scheduler', version: "${opensearch_build}" + opensearchPlugin "org.opensearch.plugin:opensearch-security:${opensearch_build}@zip" implementation "org.opensearch:opensearch:${opensearch_version}" implementation "org.jetbrains.kotlin:kotlin-stdlib:${kotlin_version}" implementation "org.jetbrains.kotlin:kotlin-stdlib-common:${kotlin_version}" @@ -249,6 +278,135 @@ tasks.withType(licenseHeaders.class) { additionalLicense 'AL ', 'Apache', 'Licensed under the Apache License, Version 2.0 (the "License")' } +// Re-write WaitForHttpResource with updated code to support security plugin use case +class WaitForClusterYellow { + + private URL url + private String username + private String password + Set validResponseCodes = Collections.singleton(200) + + WaitForClusterYellow(String protocol, String host, int numberOfNodes) throws MalformedURLException { + this(new URL(protocol + "://" + host + "/_cluster/health?wait_for_nodes=>=" + numberOfNodes + "&wait_for_status=yellow")) + } + + WaitForClusterYellow(URL url) { + this.url = url + } + + boolean wait(int durationInMs) throws GeneralSecurityException, InterruptedException, IOException { + final long waitUntil = System.nanoTime() + TimeUnit.MILLISECONDS.toNanos(durationInMs) + final long sleep = 100 + + IOException failure = null + while (true) { + try { + checkResource() + return true + } catch (IOException e) { + failure = e + } + if (System.nanoTime() < waitUntil) { + Thread.sleep(sleep) + } else { + throw failure + } + } + } + + void setUsername(String username) { + this.username = username + } + + void setPassword(String password) { + this.password = password + } + + void checkResource() throws IOException { + final HttpURLConnection connection = buildConnection() + connection.connect() + final Integer response = connection.getResponseCode() + if (validResponseCodes.contains(response)) { + return + } else { + throw new IOException(response + " " + connection.getResponseMessage()) + } + } + + HttpURLConnection buildConnection() throws IOException { + final HttpURLConnection connection = (HttpURLConnection) this.@url.openConnection() + + if (connection instanceof HttpsURLConnection) { + TrustManager[] trustAllCerts = [new X509TrustManager() { + X509Certificate[] getAcceptedIssuers() { + return null + } + + void checkClientTrusted(X509Certificate[] certs, String authType) { + } + + void checkServerTrusted(X509Certificate[] certs, String authType) { + } + } + ] as TrustManager[] + SSLContext sc = SSLContext.getInstance("SSL") + sc.init(null, trustAllCerts, new java.security.SecureRandom()) + connection.setSSLSocketFactory(sc.getSocketFactory()) + // Create all-trusting host name verifier + HostnameVerifier allHostsValid = new HostnameVerifier() { + boolean verify(String hostname, SSLSession session) { + return true + } + } + // Install the all-trusting host verifier + connection.setHostnameVerifier(allHostsValid) + } + + configureBasicAuth(connection) + connection.setRequestMethod("GET") + return connection + } + + void configureBasicAuth(HttpURLConnection connection) { + if (username != null) { + if (password == null) { + throw new IllegalStateException("Basic Auth user [" + username + "] has been set, but no password has been configured") + } + connection.setRequestProperty( + "Authorization", + "Basic " + Base64.getEncoder().encodeToString((username + ":" + password).getBytes(StandardCharsets.UTF_8)) + ) + } + } + +} + +def waitForClusterSetup(OpenSearchCluster cluster, Boolean securityEnabled) { + cluster.@waitConditions.clear() + String unicastUris = cluster.nodes.stream().flatMap { node -> + node.getAllTransportPortURI().stream() + }.collect(Collectors.joining("\n")) + cluster.nodes.forEach { node -> + try { + Files.write(node.getConfigDir().resolve("unicast_hosts.txt"), unicastUris.getBytes(StandardCharsets.UTF_8)) + } catch (IOException e) { + throw new java.io.UncheckedIOException("Failed to write configuation files for " + this, e) + } + } + + Predicate pred = { + String protocol = securityEnabled ? "https" : "http" + String host = System.getProperty("tests.cluster", cluster.getFirstNode().getHttpSocketURI()) + WaitForClusterYellow wait = new WaitForClusterYellow(protocol, host, cluster.nodes.size()) + wait.setUsername(System.getProperty("user", "admin")) + wait.setPassword(System.getProperty("password", "admin")) + return wait.wait(180000) + } + + cluster.@waitConditions.put("cluster health yellow", pred) + cluster.waitForAllConditions() +} + task integTest(type: RestIntegTestTask) { description = "Run tests against a cluster" testClassesDirs = sourceSets.test.output.classesDirs @@ -256,14 +414,20 @@ task integTest(type: RestIntegTestTask) { } tasks.named("check").configure { dependsOn(integTest) } +// === Setup security test === +// This flag indicates the existence of security plugin +def securityEnabled = System.getProperty("security.enabled", "false") == "true" + integTest { dependsOn "bundlePlugin" systemProperty 'tests.security.manager', 'false' + systemProperty 'tests.security.manager', 'false' systemProperty 'java.io.tmpdir', opensearch_tmp_dir.absolutePath - - systemProperty "https", System.getProperty("https") - systemProperty "user", System.getProperty("user") - systemProperty "password", System.getProperty("password") + systemProperty 'buildDir', buildDir.path + systemProperty "https", securityEnabled + systemProperty "security", securityEnabled + systemProperty "user", System.getProperty("user", "admin") + systemProperty "password", "admin" // Tell the test JVM if the cluster JVM is running under a debugger so that tests can use longer timeouts for // requests. The 'doFirst' delays reading the debug setting on the cluster till execution time. doFirst { @@ -276,7 +440,7 @@ integTest { // There seems to be an issue when running multi node run or integ tasks with unicast_hosts // not being written, the waitForAllConditions ensures it's written getClusters().forEach { cluster -> - cluster.waitForAllConditions() + waitForClusterSetup(cluster, securityEnabled) } } @@ -302,6 +466,26 @@ Zip bundle = (Zip) project.getTasks().getByName("bundlePlugin"); integTest.dependsOn(bundle) integTest.getClusters().forEach{c -> c.plugin(project.getObjects().fileProperty().value(bundle.getArchiveFile()))} + +ext.resolvePluginFile = { pluginId -> + return new Callable() { + @Override + RegularFile call() throws Exception { + return new RegularFile() { + @Override + File getAsFile() { + return configurations.opensearchPlugin.resolvedConfiguration.resolvedArtifacts + .find { ResolvedArtifact f -> + f.name.startsWith(pluginId) + } + .file + } + } + } + } +} +def securityPluginFile = resolvePluginFile("opensearch-security") + testClusters.integTest { testDistribution = "INTEG_TEST" // need to install job-scheduler first, need to assemble job-scheduler first @@ -330,6 +514,41 @@ testClusters.integTest { } } setting 'path.repo', repo.absolutePath + + if (securityEnabled) { + plugin(provider(securityPluginFile)) + } + + nodes.each { node -> +// def plugins = node.plugins +// def firstPlugin = plugins.get(0) +// plugins.remove(0) +// plugins.add(firstPlugin) + + if (securityEnabled) { + node.extraConfigFile("kirk.pem", file("build/resources/test/kirk.pem")) + node.extraConfigFile("kirk-key.pem", file("build/resources/test/kirk-key.pem")) + node.extraConfigFile("esnode.pem", file("build/resources/test/esnode.pem")) + node.extraConfigFile("esnode-key.pem", file("build/resources/test/esnode-key.pem")) + node.extraConfigFile("root-ca.pem", file("build/resources/test/root-ca.pem")) + node.setting("plugins.security.ssl.transport.pemcert_filepath", "esnode.pem") + node.setting("plugins.security.ssl.transport.pemkey_filepath", "esnode-key.pem") + node.setting("plugins.security.ssl.transport.pemtrustedcas_filepath", "root-ca.pem") + node.setting("plugins.security.ssl.transport.enforce_hostname_verification", "false") + node.setting("plugins.security.ssl.http.enabled", "true") + node.setting("plugins.security.ssl.http.pemcert_filepath", "esnode.pem") + node.setting("plugins.security.ssl.http.pemkey_filepath", "esnode-key.pem") + node.setting("plugins.security.ssl.http.pemtrustedcas_filepath", "root-ca.pem") + node.setting("plugins.security.allow_unsafe_democertificates", "true") + node.setting("plugins.security.allow_default_init_securityindex", "true") + node.setting("plugins.security.authcz.admin_dn", "\n - CN=kirk,OU=client,O=client,L=test,C=de") + node.setting("plugins.security.audit.type", "internal_opensearch") + node.setting("plugins.security.enable_snapshot_restore_privilege", "true") + node.setting("plugins.security.check_snapshot_restore_write_privileges", "true") + node.setting("plugins.security.restapi.roles_enabled", "[\"all_access\", \"security_rest_api_access\"]") + node.setting("plugins.security.system_indices.enabled", "true") + } + } } // For job-scheduler and reports-scheduler, the latest opensearch releases appear to be 1.1.0.0. diff --git a/src/test/kotlin/org/opensearch/integTest/PluginRestTestCase.kt b/src/test/kotlin/org/opensearch/integTest/PluginRestTestCase.kt index b7e6c2eb..cf0c119d 100644 --- a/src/test/kotlin/org/opensearch/integTest/PluginRestTestCase.kt +++ b/src/test/kotlin/org/opensearch/integTest/PluginRestTestCase.kt @@ -9,7 +9,6 @@ import com.google.gson.JsonObject import org.apache.hc.core5.http.HttpHost import org.junit.After import org.junit.AfterClass -import org.junit.Before import org.opensearch.client.Request import org.opensearch.client.RequestOptions import org.opensearch.client.Response @@ -50,16 +49,16 @@ abstract class PluginRestTestCase : OpenSearchRestTestCase() { } } + /** + * wipeAllIndices won't work since it cannot delete security index. Use wipeAllODFEIndices instead. + */ override fun preserveIndicesUponCompletion(): Boolean { return true } - open fun preserveODFEIndicesAfterTest(): Boolean = false - @Throws(IOException::class) @After open fun wipeAllODFEIndices() { - if (preserveODFEIndicesAfterTest()) return val response = client().performRequest(Request("GET", "/_cat/indices?format=json&expand_wildcards=all")) val xContentType = MediaType.fromMediaType(response.entity.contentType) xContentType.xContent().createParser( @@ -107,7 +106,7 @@ abstract class PluginRestTestCase : OpenSearchRestTestCase() { // create adminDN (super-admin) client val uri = javaClass.classLoader.getResource("security/sample.pem").toURI() val configPath = PathUtils.get(uri).parent.toAbsolutePath() - SecureRestClientBuilder(settings, configPath).setSocketTimeout(60000).build() + SecureRestClientBuilder(settings, configPath, hosts).setSocketTimeout(60000).build() } false -> { // create client with passed user @@ -124,14 +123,6 @@ abstract class PluginRestTestCase : OpenSearchRestTestCase() { } } - @Before - @Throws(InterruptedException::class) - open fun setup() { - if (client() == null) { - initClient() - } - } - fun executeRequest( method: String, url: String, diff --git a/src/test/kotlin/org/opensearch/integTest/bwc/ReportsSchedulerBackwardsCompatibilityIT.kt b/src/test/kotlin/org/opensearch/integTest/bwc/ReportsSchedulerBackwardsCompatibilityIT.kt index 58c3057d..a190c21a 100644 --- a/src/test/kotlin/org/opensearch/integTest/bwc/ReportsSchedulerBackwardsCompatibilityIT.kt +++ b/src/test/kotlin/org/opensearch/integTest/bwc/ReportsSchedulerBackwardsCompatibilityIT.kt @@ -28,8 +28,6 @@ class ReportsSchedulerBackwardsCompatibilityIT : PluginRestTestCase() { override fun preserveTemplatesUponCompletion(): Boolean = true - override fun preserveODFEIndicesAfterTest(): Boolean = true - override fun restClientSettings(): Settings { return Settings.builder() .put(super.restClientSettings()) From 93513fb9f2aef699132483a22bcdafaf1894d36c Mon Sep 17 00:00:00 2001 From: Craig Perkins Date: Thu, 31 Jul 2025 11:38:25 -0400 Subject: [PATCH 2/5] Only run on JDK21 Signed-off-by: Craig Perkins --- .github/workflows/test_security.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/test_security.yml b/.github/workflows/test_security.yml index ec7c851b..92a3f9aa 100644 --- a/.github/workflows/test_security.yml +++ b/.github/workflows/test_security.yml @@ -1,4 +1,4 @@ -name: Test search-relevance on Secure Cluster +name: Test reporting on Secure Cluster on: schedule: - cron: '0 0 * * *' # every night @@ -20,7 +20,7 @@ jobs: integ-test-with-security-linux: strategy: matrix: - java: [21, 24] + java: [21] name: Run Integration Tests on Linux runs-on: ubuntu-latest From 9d996708cdf51ca0624d1837dac36f13c90682ed Mon Sep 17 00:00:00 2001 From: Craig Perkins Date: Thu, 31 Jul 2025 13:41:22 -0400 Subject: [PATCH 3/5] Fix bwc test Signed-off-by: Craig Perkins --- src/test/kotlin/org/opensearch/integTest/PluginRestTestCase.kt | 3 +++ .../integTest/bwc/ReportsSchedulerBackwardsCompatibilityIT.kt | 2 ++ 2 files changed, 5 insertions(+) diff --git a/src/test/kotlin/org/opensearch/integTest/PluginRestTestCase.kt b/src/test/kotlin/org/opensearch/integTest/PluginRestTestCase.kt index cf0c119d..1f8e27ea 100644 --- a/src/test/kotlin/org/opensearch/integTest/PluginRestTestCase.kt +++ b/src/test/kotlin/org/opensearch/integTest/PluginRestTestCase.kt @@ -56,9 +56,12 @@ abstract class PluginRestTestCase : OpenSearchRestTestCase() { return true } + open fun preserveODFEIndicesAfterTest(): Boolean = false + @Throws(IOException::class) @After open fun wipeAllODFEIndices() { + if (preserveODFEIndicesAfterTest()) return val response = client().performRequest(Request("GET", "/_cat/indices?format=json&expand_wildcards=all")) val xContentType = MediaType.fromMediaType(response.entity.contentType) xContentType.xContent().createParser( diff --git a/src/test/kotlin/org/opensearch/integTest/bwc/ReportsSchedulerBackwardsCompatibilityIT.kt b/src/test/kotlin/org/opensearch/integTest/bwc/ReportsSchedulerBackwardsCompatibilityIT.kt index a190c21a..58c3057d 100644 --- a/src/test/kotlin/org/opensearch/integTest/bwc/ReportsSchedulerBackwardsCompatibilityIT.kt +++ b/src/test/kotlin/org/opensearch/integTest/bwc/ReportsSchedulerBackwardsCompatibilityIT.kt @@ -28,6 +28,8 @@ class ReportsSchedulerBackwardsCompatibilityIT : PluginRestTestCase() { override fun preserveTemplatesUponCompletion(): Boolean = true + override fun preserveODFEIndicesAfterTest(): Boolean = true + override fun restClientSettings(): Settings { return Settings.builder() .put(super.restClientSettings()) From 0396e68b685724308903dc5a144e43b85b8fb7da Mon Sep 17 00:00:00 2001 From: Craig Perkins Date: Fri, 1 Aug 2025 10:03:31 -0400 Subject: [PATCH 4/5] Use wait-for-cluster-setup plugin from core to simplify security testing setup Signed-off-by: Craig Perkins --- build.gradle | 137 +++------------------------------------------------ 1 file changed, 7 insertions(+), 130 deletions(-) diff --git a/build.gradle b/build.gradle index 735ee5d5..d089e589 100644 --- a/build.gradle +++ b/build.gradle @@ -74,6 +74,7 @@ apply plugin: 'idea' apply plugin: 'opensearch.opensearchplugin' apply plugin: 'opensearch.pluginzip' apply plugin: 'opensearch.testclusters' +apply plugin: 'opensearch.wait-for-cluster-setup' apply plugin: 'io.gitlab.arturbosch.detekt' apply plugin: 'org.jetbrains.kotlin.jvm' apply plugin: 'org.jetbrains.kotlin.plugin.allopen' @@ -278,135 +279,6 @@ tasks.withType(licenseHeaders.class) { additionalLicense 'AL ', 'Apache', 'Licensed under the Apache License, Version 2.0 (the "License")' } -// Re-write WaitForHttpResource with updated code to support security plugin use case -class WaitForClusterYellow { - - private URL url - private String username - private String password - Set validResponseCodes = Collections.singleton(200) - - WaitForClusterYellow(String protocol, String host, int numberOfNodes) throws MalformedURLException { - this(new URL(protocol + "://" + host + "/_cluster/health?wait_for_nodes=>=" + numberOfNodes + "&wait_for_status=yellow")) - } - - WaitForClusterYellow(URL url) { - this.url = url - } - - boolean wait(int durationInMs) throws GeneralSecurityException, InterruptedException, IOException { - final long waitUntil = System.nanoTime() + TimeUnit.MILLISECONDS.toNanos(durationInMs) - final long sleep = 100 - - IOException failure = null - while (true) { - try { - checkResource() - return true - } catch (IOException e) { - failure = e - } - if (System.nanoTime() < waitUntil) { - Thread.sleep(sleep) - } else { - throw failure - } - } - } - - void setUsername(String username) { - this.username = username - } - - void setPassword(String password) { - this.password = password - } - - void checkResource() throws IOException { - final HttpURLConnection connection = buildConnection() - connection.connect() - final Integer response = connection.getResponseCode() - if (validResponseCodes.contains(response)) { - return - } else { - throw new IOException(response + " " + connection.getResponseMessage()) - } - } - - HttpURLConnection buildConnection() throws IOException { - final HttpURLConnection connection = (HttpURLConnection) this.@url.openConnection() - - if (connection instanceof HttpsURLConnection) { - TrustManager[] trustAllCerts = [new X509TrustManager() { - X509Certificate[] getAcceptedIssuers() { - return null - } - - void checkClientTrusted(X509Certificate[] certs, String authType) { - } - - void checkServerTrusted(X509Certificate[] certs, String authType) { - } - } - ] as TrustManager[] - SSLContext sc = SSLContext.getInstance("SSL") - sc.init(null, trustAllCerts, new java.security.SecureRandom()) - connection.setSSLSocketFactory(sc.getSocketFactory()) - // Create all-trusting host name verifier - HostnameVerifier allHostsValid = new HostnameVerifier() { - boolean verify(String hostname, SSLSession session) { - return true - } - } - // Install the all-trusting host verifier - connection.setHostnameVerifier(allHostsValid) - } - - configureBasicAuth(connection) - connection.setRequestMethod("GET") - return connection - } - - void configureBasicAuth(HttpURLConnection connection) { - if (username != null) { - if (password == null) { - throw new IllegalStateException("Basic Auth user [" + username + "] has been set, but no password has been configured") - } - connection.setRequestProperty( - "Authorization", - "Basic " + Base64.getEncoder().encodeToString((username + ":" + password).getBytes(StandardCharsets.UTF_8)) - ) - } - } - -} - -def waitForClusterSetup(OpenSearchCluster cluster, Boolean securityEnabled) { - cluster.@waitConditions.clear() - String unicastUris = cluster.nodes.stream().flatMap { node -> - node.getAllTransportPortURI().stream() - }.collect(Collectors.joining("\n")) - cluster.nodes.forEach { node -> - try { - Files.write(node.getConfigDir().resolve("unicast_hosts.txt"), unicastUris.getBytes(StandardCharsets.UTF_8)) - } catch (IOException e) { - throw new java.io.UncheckedIOException("Failed to write configuation files for " + this, e) - } - } - - Predicate pred = { - String protocol = securityEnabled ? "https" : "http" - String host = System.getProperty("tests.cluster", cluster.getFirstNode().getHttpSocketURI()) - WaitForClusterYellow wait = new WaitForClusterYellow(protocol, host, cluster.nodes.size()) - wait.setUsername(System.getProperty("user", "admin")) - wait.setPassword(System.getProperty("password", "admin")) - return wait.wait(180000) - } - - cluster.@waitConditions.put("cluster health yellow", pred) - cluster.waitForAllConditions() -} - task integTest(type: RestIntegTestTask) { description = "Run tests against a cluster" testClassesDirs = sourceSets.test.output.classesDirs @@ -440,7 +312,12 @@ integTest { // There seems to be an issue when running multi node run or integ tasks with unicast_hosts // not being written, the waitForAllConditions ensures it's written getClusters().forEach { cluster -> - waitForClusterSetup(cluster, securityEnabled) + tasks.create(name: "waitForCluster${cluster.name}", type: org.opensearch.gradle.WaitForClusterSetupTask) { + it.cluster = cluster + it.securityEnabled = securityEnabled + it.username = username + it.password = password + }.setupCluster() } } From 7df77657ea2a8650ba71f56e4239c093f7ebb4ae Mon Sep 17 00:00:00 2001 From: Craig Perkins Date: Fri, 1 Aug 2025 11:11:43 -0400 Subject: [PATCH 5/5] Remove block now in core Signed-off-by: Craig Perkins --- build.gradle | 129 --------------------------------------------------- 1 file changed, 129 deletions(-) diff --git a/build.gradle b/build.gradle index d505d2bf..dfdfd002 100644 --- a/build.gradle +++ b/build.gradle @@ -285,135 +285,6 @@ tasks.withType(licenseHeaders.class) { additionalLicense 'AL ', 'Apache', 'Licensed under the Apache License, Version 2.0 (the "License")' } -// Re-write WaitForHttpResource with updated code to support security plugin use case -class WaitForClusterYellow { - - private URL url - private String username - private String password - Set validResponseCodes = Collections.singleton(200) - - WaitForClusterYellow(String protocol, String host, int numberOfNodes) throws MalformedURLException { - this(new URL(protocol + "://" + host + "/_cluster/health?wait_for_nodes=>=" + numberOfNodes + "&wait_for_status=yellow")) - } - - WaitForClusterYellow(URL url) { - this.url = url - } - - boolean wait(int durationInMs) throws GeneralSecurityException, InterruptedException, IOException { - final long waitUntil = System.nanoTime() + TimeUnit.MILLISECONDS.toNanos(durationInMs) - final long sleep = 100 - - IOException failure = null - while (true) { - try { - checkResource() - return true - } catch (IOException e) { - failure = e - } - if (System.nanoTime() < waitUntil) { - Thread.sleep(sleep) - } else { - throw failure - } - } - } - - void setUsername(String username) { - this.username = username - } - - void setPassword(String password) { - this.password = password - } - - void checkResource() throws IOException { - final HttpURLConnection connection = buildConnection() - connection.connect() - final Integer response = connection.getResponseCode() - if (validResponseCodes.contains(response)) { - return - } else { - throw new IOException(response + " " + connection.getResponseMessage()) - } - } - - HttpURLConnection buildConnection() throws IOException { - final HttpURLConnection connection = (HttpURLConnection) this.@url.openConnection() - - if (connection instanceof HttpsURLConnection) { - TrustManager[] trustAllCerts = [new X509TrustManager() { - X509Certificate[] getAcceptedIssuers() { - return null - } - - void checkClientTrusted(X509Certificate[] certs, String authType) { - } - - void checkServerTrusted(X509Certificate[] certs, String authType) { - } - } - ] as TrustManager[] - SSLContext sc = SSLContext.getInstance("SSL") - sc.init(null, trustAllCerts, new java.security.SecureRandom()) - connection.setSSLSocketFactory(sc.getSocketFactory()) - // Create all-trusting host name verifier - HostnameVerifier allHostsValid = new HostnameVerifier() { - boolean verify(String hostname, SSLSession session) { - return true - } - } - // Install the all-trusting host verifier - connection.setHostnameVerifier(allHostsValid) - } - - configureBasicAuth(connection) - connection.setRequestMethod("GET") - return connection - } - - void configureBasicAuth(HttpURLConnection connection) { - if (username != null) { - if (password == null) { - throw new IllegalStateException("Basic Auth user [" + username + "] has been set, but no password has been configured") - } - connection.setRequestProperty( - "Authorization", - "Basic " + Base64.getEncoder().encodeToString((username + ":" + password).getBytes(StandardCharsets.UTF_8)) - ) - } - } - -} - -def waitForClusterSetup(OpenSearchCluster cluster, Boolean securityEnabled) { - cluster.@waitConditions.clear() - String unicastUris = cluster.nodes.stream().flatMap { node -> - node.getAllTransportPortURI().stream() - }.collect(Collectors.joining("\n")) - cluster.nodes.forEach { node -> - try { - Files.write(node.getConfigDir().resolve("unicast_hosts.txt"), unicastUris.getBytes(StandardCharsets.UTF_8)) - } catch (IOException e) { - throw new java.io.UncheckedIOException("Failed to write configuation files for " + this, e) - } - } - - Predicate pred = { - String protocol = securityEnabled ? "https" : "http" - String host = System.getProperty("tests.cluster", cluster.getFirstNode().getHttpSocketURI()) - WaitForClusterYellow wait = new WaitForClusterYellow(protocol, host, cluster.nodes.size()) - wait.setUsername(System.getProperty("user", "admin")) - wait.setPassword(System.getProperty("password", "admin")) - return wait.wait(180000) - } - - cluster.@waitConditions.put("cluster health yellow", pred) - cluster.waitForAllConditions() -} - task integTest(type: RestIntegTestTask) { description = "Run tests against a cluster" testClassesDirs = sourceSets.test.output.classesDirs