Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[Backport 2.x] Run Rest Integ Tests with the Security plugin installed (#645) #712

Merged
merged 2 commits into from
Jan 16, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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", "2.19.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 @@ -10,15 +10,27 @@

import org.apache.http.Header;
import org.apache.http.HttpEntity;
import org.apache.http.HttpHost;
import org.apache.http.auth.AuthScope;
import org.apache.http.auth.UsernamePasswordCredentials;
import org.apache.http.client.CredentialsProvider;
import org.apache.http.conn.ssl.NoopHostnameVerifier;
import org.apache.http.entity.ContentType;
import org.apache.http.entity.StringEntity;
import org.apache.http.impl.client.BasicCredentialsProvider;
import org.apache.http.message.BasicHeader;
import org.apache.http.ssl.SSLContextBuilder;
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;
Expand All @@ -27,18 +39,116 @@
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.time.Instant;
import java.time.temporal.ChronoUnit;
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).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 = ThreadContext.buildDefaultHeaders(settings);
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 -> {
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"));
CredentialsProvider credentialsProvider = new BasicCredentialsProvider();
credentialsProvider.setCredentials(AuthScope.ANY, new UsernamePasswordCredentials(userName, password));
try {
return httpClientBuilder.setDefaultCredentialsProvider(credentialsProvider)
// disable the certificate since our testing cluster just uses the default security configuration
.setSSLHostnameVerifier(NoopHostnameVerifier.INSTANCE)
.setSSLContext(SSLContextBuilder.create().loadTrustMaterial(null, (chains, authType) -> true).build());
} 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.setSocketTimeout(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
Loading