Skip to content

Commit

Permalink
Run Rest Integ Tests with the Security plugin installed (opensearch-p…
Browse files Browse the repository at this point in the history
…roject#645)

* Make .opendistro-job-scheduler-lock a System Index

Signed-off-by: Craig Perkins <cwperx@amazon.com>

* Switch back to private

Signed-off-by: Craig Perkins <cwperx@amazon.com>

* WIP on writing a CI check with security installed

Signed-off-by: Craig Perkins <cwperx@amazon.com>

* Add CI Check to run Rest IntegTests with Security

Signed-off-by: Craig Perkins <cwperx@amazon.com>

* Fake settings

Signed-off-by: Craig Perkins <cwperx@amazon.com>

* Configure https client for sample extension plugin

Signed-off-by: Craig Perkins <cwperx@amazon.com>

* Fix forbidden apis

Signed-off-by: Craig Perkins <cwperx@amazon.com>

* spotlessApply

Signed-off-by: Craig Perkins <cwperx@amazon.com>

* Uncomment lint

Signed-off-by: Craig Perkins <cwperx@amazon.com>

* Remove settings

Signed-off-by: Craig Perkins <cwperx@amazon.com>

* JDK 21

Signed-off-by: Craig Perkins <cwperx@amazon.com>

* Add sample-extension-plugin/build.gradle to version bump

Signed-off-by: Craig Perkins <cwperx@amazon.com>

* Remove duplicate changes in sample plugin build.gradle

Signed-off-by: Craig Perkins <cwperx@amazon.com>

* remove other changes in sample plugin build.gradle

Signed-off-by: Craig Perkins <cwperx@amazon.com>

---------

Signed-off-by: Craig Perkins <cwperx@amazon.com>
  • Loading branch information
cwperks authored Jan 15, 2025
1 parent 3f541ea commit f1ad865
Show file tree
Hide file tree
Showing 17 changed files with 1,072 additions and 25 deletions.
32 changes: 32 additions & 0 deletions .github/workflows/test-with-security.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
name: BWC Test Workflow
# This workflow is triggered on pull requests and pushes to main or an OpenSearch release branch
on:
pull_request:
branches:
- "*"
push:
branches:
- "*"

jobs:
build:
strategy:
matrix:
java: [ 21 ]
# Job name
name: Build and test Job-scheduler
# This job runs on Linux
runs-on: ubuntu-latest
steps:
# This step uses the setup-java Github action: https://github.com/actions/setup-java
- name: Set Up JDK ${{ matrix.java }}
uses: actions/setup-java@v1
with:
java-version: ${{ matrix.java }}
# This step uses the checkout Github action: https://github.com/actions/checkout
- name: Checkout Branch
uses: actions/checkout@v4
- name: Run Job-scheduler Integ Tests with Security
run: |
echo "Running integ tests with security..."
./gradlew :integTest --tests "*RestIT" -Dhttps=true -Dsecurity=true -Dtests.opensearch.secure=true -Dtests.opensearch.username=admin -Dtests.opensearch.password=admin -Duser=admin -Dpassword=admin
79 changes: 79 additions & 0 deletions build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -5,11 +5,20 @@
import com.github.jengelman.gradle.plugins.shadow.ShadowPlugin
import org.opensearch.gradle.test.RestIntegTestTask

import java.util.concurrent.Callable

buildscript {
ext {
opensearch_version = System.getProperty("opensearch.version", "3.0.0-SNAPSHOT")
isSnapshot = "true" == System.getProperty("build.snapshot", "true")
buildVersionQualifier = System.getProperty("build.version_qualifier", "")
// 2.2.0-SNAPSHOT -> 2.2.0.0-SNAPSHOT
version_tokens = opensearch_version.tokenize('-')
opensearch_build = version_tokens[0] + '.0'
if (isSnapshot) {
opensearch_build += "-SNAPSHOT"
}
security_plugin_version = System.getProperty("security.version", opensearch_build)
}

repositories {
Expand Down Expand Up @@ -58,6 +67,10 @@ opensearchplugin {
classname 'org.opensearch.jobscheduler.JobSchedulerPlugin'
}

configurations {
opensearchPlugin
}

javaRestTest {
// add "-Dtests.security.manager=false" to VM options if you want to run integ tests in IntelliJ
systemProperty 'tests.security.manager', 'false'
Expand Down Expand Up @@ -168,6 +181,7 @@ dependencies {
implementation('com.google.googlejavaformat:google-java-format:1.25.2') {
exclude group: 'com.google.guava'
}
opensearchPlugin "org.opensearch.plugin:opensearch-security:${security_plugin_version}@zip"
}

// RPM & Debian build
Expand Down Expand Up @@ -230,6 +244,66 @@ integTest.getClusters().forEach{c -> {
c.plugin(project.getObjects().fileProperty().value(bundle.getArchiveFile()))
}}

ext.resolvePluginFile = { pluginId ->
return new Callable<RegularFile>() {
@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")

// === Setup security test ===
// This flag indicates the existence of security plugin
def securityEnabled = System.getProperty("security", "false") == "true" || System.getProperty("https", "false") == "true"
afterEvaluate {
testClusters.integTest.nodes.each { node ->
def plugins = node.plugins
def firstPlugin = plugins.get(0)
if (firstPlugin.provider == project.bundlePlugin.archiveFile) {
plugins.remove(0)
plugins.add(firstPlugin)
}

if (securityEnabled) {
node.extraConfigFile("kirk.pem", file("src/test/resources/security/kirk.pem"))
node.extraConfigFile("kirk-key.pem", file("src/test/resources/security/kirk-key.pem"))
node.extraConfigFile("esnode.pem", file("src/test/resources/security/esnode.pem"))
node.extraConfigFile("esnode-key.pem", file("src/test/resources/security/esnode-key.pem"))
node.extraConfigFile("root-ca.pem", file("src/test/resources/security/root-ca.pem"))
node.setting("network.bind_host", "127.0.0.1")
node.setting("network.publish_host", "127.0.0.1")
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")
// node.setting("plugins.security.system_indices.indices", "[\".opendistro-ism-config\"]")
}
}
}

testClusters.integTest {
testDistribution = 'INTEG_TEST'

Expand All @@ -245,6 +319,10 @@ testClusters.integTest {
debugPort += 1
}
}

if (securityEnabled) {
plugin(provider(securityPluginFile))
}
setting 'path.repo', repo.absolutePath
}

Expand Down Expand Up @@ -344,5 +422,6 @@ task updateVersion {
// String tokenization to support -SNAPSHOT
// Include the required files that needs to be updated with new Version
ant.replaceregexp(file:'build.gradle', match: '"opensearch.version", "\\d.*"', replace: '"opensearch.version", "' + newVersion.tokenize('-')[0] + '-SNAPSHOT"', flags:'g', byline:true)
ant.replaceregexp(file:'sample-extension-plugin/build.gradle', match: '"opensearch.version", "\\d.*"', replace: '"opensearch.version", "' + newVersion.tokenize('-')[0] + '-SNAPSHOT"', flags:'g', byline:true)
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -8,37 +8,171 @@
*/
package org.opensearch.jobscheduler.sampleextension;

import org.apache.hc.client5.http.impl.nio.PoolingAsyncClientConnectionManager;
import org.apache.hc.client5.http.impl.nio.PoolingAsyncClientConnectionManagerBuilder;
import org.apache.hc.client5.http.ssl.ClientTlsStrategyBuilder;
import org.apache.hc.client5.http.ssl.NoopHostnameVerifier;
import org.apache.hc.core5.function.Factory;
import org.apache.hc.core5.http.Header;
import org.apache.hc.core5.http.HttpEntity;
import org.apache.hc.core5.http.ContentType;
import org.apache.hc.core5.http.HttpHost;
import org.apache.hc.core5.http.io.entity.StringEntity;
import org.apache.hc.core5.http.message.BasicHeader;
import org.apache.hc.core5.http.nio.ssl.TlsStrategy;
import org.apache.hc.core5.reactor.ssl.TlsDetails;
import org.apache.hc.core5.ssl.SSLContextBuilder;
import org.apache.hc.core5.util.Timeout;
import org.junit.Assert;
import org.opensearch.client.Request;
import org.opensearch.client.RequestOptions;
import org.opensearch.client.Response;
import org.opensearch.client.RestClient;
import org.opensearch.client.RestClientBuilder;
import org.opensearch.client.WarningsHandler;
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.LoggingDeprecationHandler;
import org.opensearch.core.xcontent.NamedXContentRegistry;
import org.opensearch.common.xcontent.json.JsonXContent;
import org.opensearch.jobscheduler.spi.schedule.IntervalSchedule;
import org.opensearch.core.rest.RestStatus;
import org.opensearch.test.rest.OpenSearchRestTestCase;

import javax.net.ssl.SSLEngine;
import java.io.IOException;
import java.net.URI;
import java.net.URISyntaxException;
import java.nio.charset.StandardCharsets;
import java.nio.file.Path;
import java.time.Instant;
import java.time.temporal.ChronoUnit;
import java.util.Base64;
import java.util.Collections;
import java.util.HashMap;
import java.util.Locale;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.Optional;
import java.util.Timer;
import java.util.TimerTask;

public class SampleExtensionIntegTestCase 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()) {
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()
.put("http.port", 9200)
.put("plugins.security.ssl.http.enabled", isHttps())
.put("plugins.security.ssl.http.pemcert_filepath", "sample.pem")
.put("plugins.security.ssl.http.keystore_filepath", "test-kirk.jks")
.put("plugins.security.ssl.http.keystore_password", "changeit")
.build();
// return Settings.builder().put("strictDeprecationMode", false).put("http.port", 9200).build();
}

@Override
protected RestClient buildClient(Settings settings, HttpHost[] hosts) throws IOException {
boolean strictDeprecationMode = settings.getAsBoolean("strictDeprecationMode", true);
RestClientBuilder builder = RestClient.builder(hosts);
if (isHttps()) {
String keystore = settings.get("plugins.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, hosts).build();
} else {
configureHttpsClient(builder, settings);
builder.setStrictDeprecationMode(strictDeprecationMode);
return builder.build();
}
} else {
configureClient(builder, settings);
builder.setStrictDeprecationMode(strictDeprecationMode);
return builder.build();
}

}

protected static void configureHttpsClient(RestClientBuilder builder, Settings settings) throws IOException {
Map<String, String> headers = new HashMap<>(ThreadContext.buildDefaultHeaders(settings));
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"));

headers.put(
"Authorization",
"Basic " + Base64.getEncoder().encodeToString((userName + ":" + password).getBytes(StandardCharsets.UTF_8))
);
Header[] defaultHeaders = new Header[headers.size()];
int i = 0;
for (Map.Entry<String, String> entry : headers.entrySet()) {
defaultHeaders[i++] = new BasicHeader(entry.getKey(), entry.getValue());
}
builder.setDefaultHeaders(defaultHeaders);
builder.setHttpClientConfigCallback(httpClientBuilder -> {
try {
final TlsStrategy tlsStrategy = ClientTlsStrategyBuilder.create()
.setSslContext(SSLContextBuilder.create().loadTrustMaterial(null, (chains, authType) -> true).build())
// disable the certificate since our testing cluster just uses the default security configuration
.setHostnameVerifier(NoopHostnameVerifier.INSTANCE)
// See please https://issues.apache.org/jira/browse/HTTPCLIENT-2219
.setTlsDetailsFactory(new Factory<SSLEngine, TlsDetails>() {
@Override
public TlsDetails create(final SSLEngine sslEngine) {
return new TlsDetails(sslEngine.getSession(), sslEngine.getApplicationProtocol());
}
})
.build();

final PoolingAsyncClientConnectionManager connectionManager = PoolingAsyncClientConnectionManagerBuilder.create()
.setTlsStrategy(tlsStrategy)
.build();

return httpClientBuilder.setConnectionManager(connectionManager);
} 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.getMillis())))
);
if (settings.hasValue(CLIENT_PATH_PREFIX)) {
builder.setPathPrefix(settings.get(CLIENT_PATH_PREFIX));
}
}

protected SampleJobParameter createWatcherJob(String jobId, SampleJobParameter jobParameter) throws IOException {
return createWatcherJobWithClient(client(), jobId, jobParameter);
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@
import java.time.Instant;
import java.time.temporal.ChronoUnit;

public class SampleJobRunnerIT extends SampleExtensionIntegTestCase {
public class SampleJobRunnerRestIT extends SampleExtensionIntegTestCase {

public void testJobCreateWithCorrectParams() throws IOException {
SampleJobParameter jobParameter = new SampleJobParameter();
Expand Down
Loading

0 comments on commit f1ad865

Please sign in to comment.