Skip to content

Commit 7e3a718

Browse files
Run IT tests with security plugin (#335) (opensearch-project#1986)
* Run IT tests with security plugin (#335) * Add extra IT flow. Signed-off-by: Yury-Fridlyand <[email protected]> * Remove unneeded files. Signed-off-by: Yury-Fridlyand <[email protected]> * Typo fix. Signed-off-by: Yury-Fridlyand <[email protected]> * Fix GHA matrix syntax. Signed-off-by: Yury-Fridlyand <[email protected]> * Fix GHA matrix syntax. Signed-off-by: Yury-Fridlyand <[email protected]> * Code clean up. Signed-off-by: Yury-Fridlyand <[email protected]> * Optimize downloading. Signed-off-by: Yury-Fridlyand <[email protected]> * Apply suggestions from code review Signed-off-by: Yury-Fridlyand <[email protected]> Co-authored-by: Andrew Carbonetto <[email protected]> * Update integ-test/build.gradle Signed-off-by: Yury-Fridlyand <[email protected]> Co-authored-by: Andrew Carbonetto <[email protected]> * Typo fix. Signed-off-by: Yury-Fridlyand <[email protected]> * Rework implementation. Signed-off-by: Yury-Fridlyand <[email protected]> * Address PR review. Signed-off-by: Yury-Fridlyand <[email protected]> * Address PR feedback + some fixes. Signed-off-by: Yury-Fridlyand <[email protected]> --------- Signed-off-by: Yury-Fridlyand <[email protected]> Co-authored-by: Andrew Carbonetto <[email protected]> * Minor fix. Signed-off-by: Yury-Fridlyand <[email protected]> * Address PR feedback. Signed-off-by: Yury-Fridlyand <[email protected]> * Typo fix. Signed-off-by: Yury-Fridlyand <[email protected]> --------- Signed-off-by: Yury-Fridlyand <[email protected]> Co-authored-by: Andrew Carbonetto <[email protected]>
1 parent 25beda0 commit 7e3a718

File tree

4 files changed

+293
-50
lines changed

4 files changed

+293
-50
lines changed
Lines changed: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,43 @@
1+
name: Security Plugin IT
2+
3+
on:
4+
pull_request:
5+
push:
6+
branches-ignore:
7+
- 'dependabot/**'
8+
paths:
9+
- 'integ-test/**'
10+
- '.github/workflows/integ-tests-with-security.yml'
11+
12+
jobs:
13+
security-it:
14+
strategy:
15+
fail-fast: false
16+
matrix:
17+
os: [ ubuntu-latest, windows-latest, macos-latest ]
18+
java: [ 11, 17 ]
19+
20+
runs-on: ${{ matrix.os }}
21+
22+
steps:
23+
- uses: actions/checkout@v3
24+
25+
- name: Set up JDK ${{ matrix.java }}
26+
uses: actions/setup-java@v3
27+
with:
28+
distribution: 'temurin'
29+
java-version: ${{ matrix.java }}
30+
31+
- name: Build with Gradle
32+
run: ./gradlew integTestWithSecurity
33+
34+
- name: Upload test reports
35+
if: ${{ always() }}
36+
uses: actions/upload-artifact@v2
37+
continue-on-error: true
38+
with:
39+
name: test-reports-${{ matrix.os }}-${{ matrix.java }}
40+
path: |
41+
integ-test/build/reports/**
42+
integ-test/build/testclusters/*/logs/*
43+
integ-test/build/testclusters/*/config/*

integ-test/build.gradle

Lines changed: 154 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -24,7 +24,10 @@
2424

2525
import org.opensearch.gradle.test.RestIntegTestTask
2626
import org.opensearch.gradle.testclusters.StandaloneRestIntegTestTask
27+
import org.opensearch.gradle.testclusters.OpenSearchCluster
2728

29+
import groovy.xml.XmlParser
30+
import java.nio.file.Paths
2831
import java.util.concurrent.Callable
2932
import java.util.stream.Collectors
3033

@@ -62,6 +65,81 @@ ext {
6265
projectSubstitutions = [:]
6366
licenseFile = rootProject.file('LICENSE.TXT')
6467
noticeFile = rootProject.file('NOTICE')
68+
69+
getSecurityPluginDownloadLink = { ->
70+
var repo = "https://aws.oss.sonatype.org/content/repositories/snapshots/org/opensearch/plugin/" +
71+
"opensearch-security/$opensearch_build/"
72+
var metadataFile = Paths.get(projectDir.toString(), "build", "maven-metadata.xml").toAbsolutePath().toFile()
73+
download.run {
74+
src repo + "maven-metadata.xml"
75+
dest metadataFile
76+
}
77+
def metadata = new XmlParser().parse(metadataFile)
78+
def securitySnapshotVersion = metadata.versioning.snapshotVersions[0].snapshotVersion[0].value[0].text()
79+
80+
return repo + "opensearch-security-${securitySnapshotVersion}.zip"
81+
}
82+
83+
File downloadedSecurityPlugin = null
84+
85+
configureSecurityPlugin = { OpenSearchCluster cluster ->
86+
87+
cluster.getNodes().forEach { node ->
88+
var creds = node.getCredentials()
89+
if (creds.isEmpty()) {
90+
creds.add(Map.of('useradd', 'admin', '-p', 'admin'))
91+
} else {
92+
creds.get(0).putAll(Map.of('useradd', 'admin', '-p', 'admin'))
93+
}
94+
}
95+
96+
var projectAbsPath = projectDir.getAbsolutePath()
97+
98+
// add a check to avoid re-downloading multiple times during single test run
99+
if (downloadedSecurityPlugin == null) {
100+
downloadedSecurityPlugin = Paths.get(projectAbsPath, 'bin', 'opensearch-security-snapshot.zip').toFile()
101+
download.run {
102+
src getSecurityPluginDownloadLink()
103+
dest downloadedSecurityPlugin
104+
}
105+
}
106+
107+
// Config below including files are copied from security demo configuration
108+
['esnode.pem', 'esnode-key.pem', 'root-ca.pem'].forEach { file ->
109+
File local = Paths.get(projectAbsPath, 'bin', file).toFile()
110+
download.run {
111+
src "https://raw.githubusercontent.com/opensearch-project/security/main/bwc-test/src/test/resources/security/" + file
112+
dest local
113+
overwrite false
114+
}
115+
cluster.extraConfigFile file, local
116+
}
117+
[
118+
// config copied from security plugin demo configuration
119+
'plugins.security.ssl.transport.pemcert_filepath' : 'esnode.pem',
120+
'plugins.security.ssl.transport.pemkey_filepath' : 'esnode-key.pem',
121+
'plugins.security.ssl.transport.pemtrustedcas_filepath' : 'root-ca.pem',
122+
'plugins.security.ssl.transport.enforce_hostname_verification' : 'false',
123+
// https is disabled to simplify test debugging
124+
'plugins.security.ssl.http.enabled' : 'false',
125+
'plugins.security.ssl.http.pemcert_filepath' : 'esnode.pem',
126+
'plugins.security.ssl.http.pemkey_filepath' : 'esnode-key.pem',
127+
'plugins.security.ssl.http.pemtrustedcas_filepath' : 'root-ca.pem',
128+
'plugins.security.allow_unsafe_democertificates' : 'true',
129+
130+
'plugins.security.allow_default_init_securityindex' : 'true',
131+
'plugins.security.authcz.admin_dn' : 'CN=kirk,OU=client,O=client,L=test,C=de',
132+
'plugins.security.audit.type' : 'internal_opensearch',
133+
'plugins.security.enable_snapshot_restore_privilege' : 'true',
134+
'plugins.security.check_snapshot_restore_write_privileges' : 'true',
135+
'plugins.security.restapi.roles_enabled' : '["all_access", "security_rest_api_access"]',
136+
'plugins.security.system_indices.enabled' : 'true'
137+
].forEach { name, value ->
138+
cluster.setting name, value
139+
}
140+
141+
cluster.plugin provider((Callable<RegularFile>) (() -> (RegularFile) (() -> downloadedSecurityPlugin)))
142+
}
65143
}
66144

67145
tasks.withType(licenseHeaders.class) {
@@ -108,6 +186,7 @@ dependencies {
108186
testImplementation group: 'com.h2database', name: 'h2', version: '2.2.220'
109187
testImplementation group: 'org.xerial', name: 'sqlite-jdbc', version: '3.41.2.2'
110188
testImplementation group: 'com.google.code.gson', name: 'gson', version: '2.8.9'
189+
testCompileOnly 'org.apiguardian:apiguardian-api:1.1.2'
111190

112191
// Needed for BWC tests
113192
zipArchive group: 'org.opensearch.plugin', name:'opensearch-sql-plugin', version: "${bwcVersion}-SNAPSHOT"
@@ -128,21 +207,28 @@ compileTestJava {
128207
}
129208

130209
testClusters.all {
131-
testDistribution = 'archive'
132-
133210
// debug with command, ./gradlew opensearch-sql:run -DdebugJVM. --debug-jvm does not work with keystore.
134211
if (System.getProperty("debugJVM") != null) {
135212
jvmArgs '-agentlib:jdwp=transport=dt_socket,server=y,suspend=n,address=*:5005'
136213
}
137214
}
138215

139-
testClusters.integTest {
140-
plugin ":opensearch-sql-plugin"
141-
setting "plugins.query.datasources.encryption.masterkey", "1234567812345678"
142-
}
143-
144216
testClusters {
217+
integTest {
218+
testDistribution = 'archive'
219+
plugin ":opensearch-sql-plugin"
220+
setting "plugins.query.datasources.encryption.masterkey", "1234567812345678"
221+
}
145222
remoteCluster {
223+
testDistribution = 'archive'
224+
plugin ":opensearch-sql-plugin"
225+
}
226+
integTestWithSecurity {
227+
testDistribution = 'archive'
228+
plugin ":opensearch-sql-plugin"
229+
}
230+
remoteIntegTestWithSecurity {
231+
testDistribution = 'archive'
146232
plugin ":opensearch-sql-plugin"
147233
}
148234
}
@@ -223,6 +309,65 @@ task integJdbcTest(type: RestIntegTestTask) {
223309
}
224310
}
225311

312+
task integTestWithSecurity(type: RestIntegTestTask) {
313+
useCluster testClusters.integTestWithSecurity
314+
useCluster testClusters.remoteIntegTestWithSecurity
315+
316+
systemProperty "cluster.names",
317+
getClusters().stream().map(cluster -> cluster.getName()).collect(Collectors.joining(","))
318+
319+
getClusters().forEach { cluster ->
320+
configureSecurityPlugin(cluster)
321+
}
322+
323+
useJUnitPlatform()
324+
dependsOn ':opensearch-sql-plugin:bundlePlugin'
325+
testLogging {
326+
events "passed", "skipped", "failed"
327+
}
328+
afterTest { desc, result ->
329+
logger.quiet "${desc.className}.${desc.name}: ${result.resultType} ${(result.getEndTime() - result.getStartTime())/1000}s"
330+
}
331+
332+
systemProperty 'tests.security.manager', 'false'
333+
systemProperty 'project.root', project.projectDir.absolutePath
334+
335+
// Set default query size limit
336+
systemProperty 'defaultQuerySizeLimit', '10000'
337+
338+
// Tell the test JVM if the cluster JVM is running under a debugger so that tests can use longer timeouts for
339+
// requests. The 'doFirst' delays reading the debug setting on the cluster till execution time.
340+
doFirst {
341+
systemProperty 'cluster.debug', getDebug()
342+
getClusters().forEach { cluster ->
343+
344+
String allTransportSocketURI = cluster.nodes.stream().flatMap { node ->
345+
node.getAllTransportPortURI().stream()
346+
}.collect(Collectors.joining(","))
347+
String allHttpSocketURI = cluster.nodes.stream().flatMap { node ->
348+
node.getAllHttpSocketURI().stream()
349+
}.collect(Collectors.joining(","))
350+
351+
systemProperty "tests.rest.${cluster.name}.http_hosts", "${-> allHttpSocketURI}"
352+
systemProperty "tests.rest.${cluster.name}.transport_hosts", "${-> allTransportSocketURI}"
353+
}
354+
355+
systemProperty "https", "false"
356+
systemProperty "user", "admin"
357+
systemProperty "password", "admin"
358+
}
359+
360+
if (System.getProperty("test.debug") != null) {
361+
jvmArgs '-agentlib:jdwp=transport=dt_socket,server=y,suspend=y,address=*:5005'
362+
}
363+
364+
// NOTE: this IT config discovers only junit5 (jupiter) tests.
365+
// https://github.com/opensearch-project/sql/issues/1974
366+
filter {
367+
includeTestsMatching 'org.opensearch.sql.security.CrossClusterSearchIT'
368+
}
369+
}
370+
226371
// Run PPL ITs and new, legacy and comparison SQL ITs with new SQL engine enabled
227372
integTest {
228373
useCluster testClusters.remoteCluster
@@ -305,8 +450,8 @@ integTest {
305450
// Exclude JDBC related tests
306451
exclude 'org/opensearch/sql/jdbc/**'
307452

308-
// Exclude this IT until running IT with security plugin enabled is ready
309-
exclude 'org/opensearch/sql/ppl/CrossClusterSearchIT.class'
453+
// Exclude this IT, because they executed in another task (:integTestWithSecurity)
454+
exclude 'org/opensearch/sql/security/**'
310455
}
311456

312457

integ-test/src/test/java/org/opensearch/sql/legacy/OpenSearchSQLRestTestCase.java

Lines changed: 56 additions & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -5,8 +5,6 @@
55

66
package org.opensearch.sql.legacy;
77

8-
import static java.util.Collections.unmodifiableList;
9-
108
import java.io.IOException;
119
import java.util.ArrayList;
1210
import java.util.List;
@@ -49,8 +47,22 @@
4947
public abstract class OpenSearchSQLRestTestCase extends OpenSearchRestTestCase {
5048

5149
private static final Logger LOG = LogManager.getLogger();
52-
public static final String REMOTE_CLUSTER = "remoteCluster";
5350
public static final String MATCH_ALL_REMOTE_CLUSTER = "*";
51+
// Requires to insert cluster name and cluster transport address (host:port)
52+
public static final String REMOTE_CLUSTER_SETTING =
53+
"{"
54+
+ "\"persistent\": {"
55+
+ " \"cluster\": {"
56+
+ " \"remote\": {"
57+
+ " \"%s\": {"
58+
+ " \"seeds\": ["
59+
+ " \"%s\""
60+
+ " ]"
61+
+ " }"
62+
+ " }"
63+
+ " }"
64+
+ "}"
65+
+ "}";
5466

5567
private static RestClient remoteClient;
5668

@@ -106,27 +118,24 @@ protected RestClient buildClient(Settings settings, HttpHost[] hosts) throws IOE
106118
}
107119

108120
// Modified from initClient in OpenSearchRestTestCase
109-
public void initRemoteClient() throws IOException {
110-
if (remoteClient == null) {
111-
assert remoteAdminClient == null;
112-
String cluster = getTestRestCluster(REMOTE_CLUSTER);
113-
String[] stringUrls = cluster.split(",");
114-
List<HttpHost> hosts = new ArrayList<>(stringUrls.length);
115-
for (String stringUrl : stringUrls) {
116-
int portSeparator = stringUrl.lastIndexOf(':');
117-
if (portSeparator < 0) {
118-
throw new IllegalArgumentException("Illegal cluster url [" + stringUrl + "]");
119-
}
120-
String host = stringUrl.substring(0, portSeparator);
121-
int port = Integer.valueOf(stringUrl.substring(portSeparator + 1));
122-
hosts.add(buildHttpHost(host, port));
121+
public void initRemoteClient(String clusterName) throws IOException {
122+
remoteClient = remoteAdminClient = initClient(clusterName);
123+
}
124+
125+
/** Configure http client for the given <b>cluster</b>. */
126+
public RestClient initClient(String clusterName) throws IOException {
127+
String[] stringUrls = getTestRestCluster(clusterName).split(",");
128+
List<HttpHost> hosts = new ArrayList<>(stringUrls.length);
129+
for (String stringUrl : stringUrls) {
130+
int portSeparator = stringUrl.lastIndexOf(':');
131+
if (portSeparator < 0) {
132+
throw new IllegalArgumentException("Illegal cluster url [" + stringUrl + "]");
123133
}
124-
final List<HttpHost> clusterHosts = unmodifiableList(hosts);
125-
remoteClient = buildClient(restClientSettings(), clusterHosts.toArray(new HttpHost[0]));
126-
remoteAdminClient = buildClient(restAdminSettings(), clusterHosts.toArray(new HttpHost[0]));
134+
String host = stringUrl.substring(0, portSeparator);
135+
int port = Integer.parseInt(stringUrl.substring(portSeparator + 1));
136+
hosts.add(buildHttpHost(host, port));
127137
}
128-
assert remoteClient != null;
129-
assert remoteAdminClient != null;
138+
return buildClient(restClientSettings(), hosts.toArray(new HttpHost[0]));
130139
}
131140

132141
/** Get a comma delimited list of [host:port] to which to send REST requests. */
@@ -200,6 +209,27 @@ protected static void wipeAllOpenSearchIndices(RestClient client) throws IOExcep
200209
}
201210
}
202211

212+
/**
213+
* Configure authentication and pass <b>builder</b> to superclass to configure other stuff.<br>
214+
* By default, auth is configure when <b>https</b> is set only.
215+
*/
216+
protected static void configureClient(RestClientBuilder builder, Settings settings)
217+
throws IOException {
218+
String userName = System.getProperty("user");
219+
String password = System.getProperty("password");
220+
if (userName != null && password != null) {
221+
builder.setHttpClientConfigCallback(
222+
httpClientBuilder -> {
223+
BasicCredentialsProvider credentialsProvider = new BasicCredentialsProvider();
224+
credentialsProvider.setCredentials(
225+
new AuthScope(null, -1),
226+
new UsernamePasswordCredentials(userName, password.toCharArray()));
227+
return httpClientBuilder.setDefaultCredentialsProvider(credentialsProvider);
228+
});
229+
}
230+
OpenSearchRestTestCase.configureClient(builder, settings);
231+
}
232+
203233
protected static void configureHttpsClient(
204234
RestClientBuilder builder, Settings settings, HttpHost httpHost) throws IOException {
205235
Map<String, String> headers = ThreadContext.buildDefaultHeaders(settings);
@@ -259,16 +289,13 @@ protected static void configureHttpsClient(
259289
* Initialize rest client to remote cluster, and create a connection to it from the coordinating
260290
* cluster.
261291
*/
262-
public void configureMultiClusters() throws IOException {
263-
initRemoteClient();
292+
public void configureMultiClusters(String remote) throws IOException {
293+
initRemoteClient(remote);
264294

265295
Request connectionRequest = new Request("PUT", "_cluster/settings");
266296
String connectionSetting =
267-
"{\"persistent\": {\"cluster\": {\"remote\": {\""
268-
+ REMOTE_CLUSTER
269-
+ "\": {\"seeds\": [\""
270-
+ getTestTransportCluster(REMOTE_CLUSTER).split(",")[0]
271-
+ "\"]}}}}}";
297+
String.format(
298+
REMOTE_CLUSTER_SETTING, remote, getTestTransportCluster(remote).split(",")[0]);
272299
connectionRequest.setJsonEntity(connectionSetting);
273300
adminClient().performRequest(connectionRequest);
274301
}

0 commit comments

Comments
 (0)