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

Run IT tests with security plugin #335

Merged
merged 13 commits into from
Aug 17, 2023
43 changes: 43 additions & 0 deletions .github/workflows/integ-tests-with-security.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
name: Security Plugin IT

on:
pull_request:
push:
branches-ignore:
- 'dependabot/**'
paths:
- 'integ-test/**'
- '.github/workflows/integ-tests-with-security.yml'

jobs:
security-it:
strategy:
fail-fast: false
matrix:
os: [ ubuntu-latest, windows-latest, macos-latest ]
java: [ 11, 17 ]

runs-on: ${{ matrix.os }}

steps:
- uses: actions/checkout@v3

- name: Set up JDK ${{ matrix.java }}
uses: actions/setup-java@v3
with:
distribution: 'temurin'
java-version: ${{ matrix.java }}

- name: Build with Gradle
run: ./gradlew integTestWithSecurity

- name: Upload test reports
if: ${{ always() }}
uses: actions/upload-artifact@v2
continue-on-error: true
with:
name: test-reports-${{ matrix.os }}-${{ matrix.java }}
path: |
integ-test/build/reports/**
integ-test/build/testclusters/*/logs/*
integ-test/build/testclusters/*/config/*
149 changes: 143 additions & 6 deletions integ-test/build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,10 @@

import org.opensearch.gradle.test.RestIntegTestTask
import org.opensearch.gradle.testclusters.StandaloneRestIntegTestTask
import org.opensearch.gradle.testclusters.OpenSearchCluster

import groovy.xml.XmlParser
import java.nio.file.Paths
import java.util.concurrent.Callable
import java.util.stream.Collectors

Expand Down Expand Up @@ -57,6 +60,77 @@ ext {
projectSubstitutions = [:]
licenseFile = rootProject.file('LICENSE.TXT')
noticeFile = rootProject.file('NOTICE')

getSecurityPluginDownloadLink = { ->
var repo = "https://aws.oss.sonatype.org/content/repositories/snapshots/org/opensearch/plugin/" +

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The Security Plugin maintainers (myself included) have had trouble with build breaks and fixing them in a timely manner - it would be unfortunate for your integration tests to be flaky due to downstream issues. This isn't an issue with release builds with the trade-off of longer time between updates.

"opensearch-security/$opensearch_build/"
var metadataFile = Paths.get(projectDir.toString(), "build", "maven-metadata.xml").toAbsolutePath().toFile()
download.run {
src repo + "maven-metadata.xml"
dest metadataFile
}
def metadata = new XmlParser().parse(metadataFile)
def securitySnapshotVersion = metadata.versioning.snapshotVersions[0].snapshotVersion[0].value[0].text()

return repo + "opensearch-security-${securitySnapshotVersion}.zip"
}

File downloadedSecurityPlugin = null

configureSecurityPlugin = { OpenSearchCluster cluster ->

cluster.getNodes().forEach { node ->
node.getCredentials().add(Map.of('useradd', 'admin', '-p', 'admin'))
}

var projectAbsPath = projectDir.getAbsolutePath()

// add a check to avoid re-downloading multiple times during single test run
if (downloadedSecurityPlugin == null) {
downloadedSecurityPlugin = Paths.get(projectAbsPath, 'bin', 'opensearch-security-snapshot.zip').toFile()
download.run {
src getSecurityPluginDownloadLink()
acarbonetto marked this conversation as resolved.
Show resolved Hide resolved
dest downloadedSecurityPlugin
}
}

// Config below including files are copied from security demo configuration
['esnode.pem', 'esnode-key.pem', 'root-ca.pem'].forEach { file ->
File local = Paths.get(projectAbsPath, 'bin', file).toFile()
download.run {
src "https://github.com/raw/opensearch-project/security/main/bwc-test/src/test/resources/security/" + file
dest local
overwrite false
}
cluster.extraConfigFile file, local
}
[
'plugins.security.ssl.transport.pemcert_filepath' : 'esnode.pem',
'plugins.security.ssl.transport.pemkey_filepath' : 'esnode-key.pem',
'plugins.security.ssl.transport.pemtrustedcas_filepath' : 'root-ca.pem',
'plugins.security.ssl.transport.enforce_hostname_verification' : 'false',
// https is disabled : because `OpenSearchCluster` is hardcoded to validate cluster health by http
// refer how IT framework implemented in security plugin and reuse/copy to activate https
'plugins.security.ssl.http.enabled' : 'false',
'plugins.security.ssl.http.pemcert_filepath' : 'esnode.pem',
'plugins.security.ssl.http.pemkey_filepath' : 'esnode-key.pem',
'plugins.security.ssl.http.pemtrustedcas_filepath' : 'root-ca.pem',
'plugins.security.allow_unsafe_democertificates' : 'true',

'plugins.security.allow_default_init_securityindex' : 'true',
//'plugins.security.authcz.admin_dn' : 'CN=kirk,OU=client,O=client,L=test,C=de',
'plugins.security.authcz.admin_dn' : 'CN=admin,OU=SSL,O=Test,L=Test,C=DE',
'plugins.security.audit.type' : 'internal_opensearch',
'plugins.security.enable_snapshot_restore_privilege' : 'true',
'plugins.security.check_snapshot_restore_write_privileges' : 'true',
'plugins.security.restapi.roles_enabled' : '["all_access", "security_rest_api_access"]',
'plugins.security.system_indices.enabled' : 'true'
].forEach { name, value ->
cluster.setting name, value
}

cluster.plugin provider((Callable<RegularFile>) (() -> (RegularFile) (() -> downloadedSecurityPlugin)))
}
}

tasks.withType(licenseHeaders.class) {
Expand Down Expand Up @@ -103,6 +177,7 @@ dependencies {
testImplementation group: 'com.h2database', name: 'h2', version: '2.2.220'
testImplementation group: 'org.xerial', name: 'sqlite-jdbc', version: '3.41.2.2'
testImplementation group: 'com.google.code.gson', name: 'gson', version: '2.8.9'
testCompileOnly 'org.apiguardian:apiguardian-api:1.1.2'

// Needed for BWC tests
zipArchive group: 'org.opensearch.plugin', name:'opensearch-sql-plugin', version: "${bwcVersion}-SNAPSHOT"
Expand All @@ -124,21 +199,23 @@ compileTestJava {

testClusters.all {
testDistribution = 'archive'
Yury-Fridlyand marked this conversation as resolved.
Show resolved Hide resolved
plugin ":opensearch-sql-plugin"

// debug with command, ./gradlew opensearch-sql:run -DdebugJVM. --debug-jvm does not work with keystore.
if (System.getProperty("debugJVM") != null) {
jvmArgs '-agentlib:jdwp=transport=dt_socket,server=y,suspend=n,address=*:5005'
}
}

testClusters.integTest {
plugin ":opensearch-sql-plugin"
setting "plugins.query.datasources.encryption.masterkey", "1234567812345678"
}

testClusters {
integTest {
setting "plugins.query.datasources.encryption.masterkey", "1234567812345678"
}
remoteCluster {
plugin ":opensearch-sql-plugin"
}
integTestWithSecurity {
}
remoteIntegTestWithSecurity {
}
}

Expand Down Expand Up @@ -218,6 +295,66 @@ task integJdbcTest(type: RestIntegTestTask) {
}
}

task integTestWithSecurity(type: RestIntegTestTask) {
useCluster testClusters.integTestWithSecurity
useCluster testClusters.remoteIntegTestWithSecurity

// Don't use `getClusters`: cluster order is important. IT framework adds/uses a cluster
// named as the task as default and uses it to init default REST client
systemProperty "cluster.names", "integTestWithSecurity,anotherintegTestWithSecurity"

getClusters().forEach { cluster ->
configureSecurityPlugin(cluster)
}

useJUnitPlatform()
dependsOn ':opensearch-sql-plugin:bundlePlugin'
testLogging {
events "passed", "skipped", "failed"
}
afterTest { desc, result ->
logger.quiet "${desc.className}.${desc.name}: ${result.resultType} ${(result.getEndTime() - result.getStartTime())/1000}s"
}

systemProperty 'tests.security.manager', 'false'
systemProperty 'project.root', project.projectDir.absolutePath

// Set default query size limit
systemProperty 'defaultQuerySizeLimit', '10000'

// 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 {
systemProperty 'cluster.debug', getDebug()
getClusters().forEach { cluster ->

String allTransportSocketURI = cluster.nodes.stream().flatMap { node ->
node.getAllTransportPortURI().stream()
}.collect(Collectors.joining(","))
String allHttpSocketURI = cluster.nodes.stream().flatMap { node ->
node.getAllHttpSocketURI().stream()
}.collect(Collectors.joining(","))

systemProperty "tests.rest.${cluster.name}.http_hosts", "${-> allHttpSocketURI}"
systemProperty "tests.rest.${cluster.name}.transport_hosts", "${-> allTransportSocketURI}"
}

systemProperty "https", "false"
systemProperty "user", "admin"
systemProperty "password", "admin"
}

if (System.getProperty("test.debug") != null) {
jvmArgs '-agentlib:jdwp=transport=dt_socket,server=y,suspend=y,address=*:5005'
}

// NOTE: this IT config discovers only junit5 (jupiter) tests.
// https://github.com/opensearch-project/sql/issues/1974
filter {
includeTestsMatching 'org.opensearch.sql.ppl.CrossClusterSearchIT'
}
}

// Run PPL ITs and new, legacy and comparison SQL ITs with new SQL engine enabled
integTest {
useCluster testClusters.remoteCluster
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -105,27 +105,25 @@ protected RestClient buildClient(Settings settings, HttpHost[] hosts) throws IOE
}

// Modified from initClient in OpenSearchRestTestCase
public void initRemoteClient() throws IOException {
if (remoteClient == null) {
assert remoteAdminClient == null;
String cluster = getTestRestCluster(REMOTE_CLUSTER);
String[] stringUrls = cluster.split(",");
List<HttpHost> hosts = new ArrayList<>(stringUrls.length);
for (String stringUrl : stringUrls) {
int portSeparator = stringUrl.lastIndexOf(':');
if (portSeparator < 0) {
throw new IllegalArgumentException("Illegal cluster url [" + stringUrl + "]");
}
String host = stringUrl.substring(0, portSeparator);
int port = Integer.valueOf(stringUrl.substring(portSeparator + 1));
hosts.add(buildHttpHost(host, port));
public void initRemoteClient(String clusterName) throws IOException {
remoteClient = remoteAdminClient = initClient(clusterName);
}

public RestClient initClient(String clusterName) throws IOException {
acarbonetto marked this conversation as resolved.
Show resolved Hide resolved
String cluster = getTestRestCluster(clusterName);
String[] stringUrls = cluster.split(",");
List<HttpHost> hosts = new ArrayList<>(stringUrls.length);
for (String stringUrl : stringUrls) {
int portSeparator = stringUrl.lastIndexOf(':');
if (portSeparator < 0) {
throw new IllegalArgumentException("Illegal cluster url [" + stringUrl + "]");
}
final List<HttpHost> clusterHosts = unmodifiableList(hosts);
remoteClient = buildClient(restClientSettings(), clusterHosts.toArray(new HttpHost[0]));
remoteAdminClient = buildClient(restAdminSettings(), clusterHosts.toArray(new HttpHost[0]));
String host = stringUrl.substring(0, portSeparator);
int port = Integer.valueOf(stringUrl.substring(portSeparator + 1));
hosts.add(buildHttpHost(host, port));
}
assert remoteClient != null;
assert remoteAdminClient != null;
final List<HttpHost> clusterHosts = unmodifiableList(hosts);
return buildClient(restClientSettings(), clusterHosts.toArray(new HttpHost[0]));
}

/**
Expand Down Expand Up @@ -201,6 +199,22 @@ protected static void wipeAllOpenSearchIndices(RestClient client) throws IOExcep
}
}

protected static void configureClient(RestClientBuilder builder, Settings settings)
acarbonetto marked this conversation as resolved.
Show resolved Hide resolved
acarbonetto marked this conversation as resolved.
Show resolved Hide resolved
throws IOException {
String userName = System.getProperty("user");
String password = System.getProperty("password");
if (userName != null && password != null) {
builder.setHttpClientConfigCallback(httpClientBuilder -> {
BasicCredentialsProvider credentialsProvider = new BasicCredentialsProvider();
credentialsProvider.setCredentials(
new AuthScope(null, -1),
new UsernamePasswordCredentials(userName, password.toCharArray()));
return httpClientBuilder.setDefaultCredentialsProvider(credentialsProvider);
});
}
OpenSearchRestTestCase.configureClient(builder, settings);
}

protected static void configureHttpsClient(RestClientBuilder builder, Settings settings,
HttpHost httpHost)
throws IOException {
Expand Down Expand Up @@ -252,15 +266,26 @@ protected static void configureHttpsClient(RestClientBuilder builder, Settings s
* Initialize rest client to remote cluster,
* and create a connection to it from the coordinating cluster.
*/
public void configureMultiClusters() throws IOException {
initRemoteClient();
public void configureMultiClusters(String remote)
throws IOException {
initRemoteClient(remote);

Request connectionRequest = new Request("PUT", "_cluster/settings");
String connectionSetting = "{\"persistent\": {\"cluster\": {\"remote\": {\""
+ REMOTE_CLUSTER
+ "\": {\"seeds\": [\""
+ getTestTransportCluster(REMOTE_CLUSTER).split(",")[0]
+ "\"]}}}}}";
String connectionSetting = String.format(
acarbonetto marked this conversation as resolved.
Show resolved Hide resolved
"{"
+ "\"persistent\": {"
+ " \"cluster\": {"
+ " \"remote\": {"
+ " \"%s\": {"
+ " \"seeds\": ["
+ " \"%s\""
+ " ]"
+ " }"
+ " }"
+ " }"
+ "}"
+ "}",
remote, getTestTransportCluster(remote).split(",")[0]);
connectionRequest.setJsonEntity(connectionSetting);
adminClient().performRequest(connectionRequest);
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -44,9 +44,9 @@
import org.junit.Before;
import org.opensearch.client.Request;
import org.opensearch.client.Response;
import org.opensearch.core.xcontent.XContentBuilder;
import org.opensearch.common.xcontent.XContentFactory;
import org.opensearch.core.rest.RestStatus;
import org.opensearch.core.xcontent.XContentBuilder;

/**
* SQL plugin integration test base class (migrated from SQLIntegTestCase)
Expand Down
Loading
Loading