Skip to content

Commit 280ea22

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

File tree

4 files changed

+292
-50
lines changed

4 files changed

+292
-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

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

60138
tasks.withType(licenseHeaders.class) {
@@ -97,6 +175,7 @@ dependencies {
97175
testImplementation group: 'com.h2database', name: 'h2', version: '2.2.220'
98176
testImplementation group: 'org.xerial', name: 'sqlite-jdbc', version: '3.41.2.2'
99177
testImplementation group: 'com.google.code.gson', name: 'gson', version: '2.8.9'
178+
testCompileOnly 'org.apiguardian:apiguardian-api:1.1.2'
100179
}
101180

102181
dependencyLicenses.enabled = false
@@ -113,21 +192,28 @@ compileTestJava {
113192
}
114193

115194
testClusters.all {
116-
testDistribution = 'archive'
117-
118195
// debug with command, ./gradlew opensearch-sql:run -DdebugJVM. --debug-jvm does not work with keystore.
119196
if (System.getProperty("debugJVM") != null) {
120197
jvmArgs '-agentlib:jdwp=transport=dt_socket,server=y,suspend=n,address=*:5005'
121198
}
122199
}
123200

124-
testClusters.integTest {
125-
plugin ":opensearch-sql-plugin"
126-
setting "plugins.query.datasources.encryption.masterkey", "1234567812345678"
127-
}
128-
129201
testClusters {
202+
integTest {
203+
testDistribution = 'archive'
204+
plugin ":opensearch-sql-plugin"
205+
setting "plugins.query.datasources.encryption.masterkey", "1234567812345678"
206+
}
130207
remoteCluster {
208+
testDistribution = 'archive'
209+
plugin ":opensearch-sql-plugin"
210+
}
211+
integTestWithSecurity {
212+
testDistribution = 'archive'
213+
plugin ":opensearch-sql-plugin"
214+
}
215+
remoteIntegTestWithSecurity {
216+
testDistribution = 'archive'
131217
plugin ":opensearch-sql-plugin"
132218
}
133219
}
@@ -208,6 +294,65 @@ task integJdbcTest(type: RestIntegTestTask) {
208294
}
209295
}
210296

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

293-
// Exclude this IT until running IT with security plugin enabled is ready
294-
exclude 'org/opensearch/sql/ppl/CrossClusterSearchIT.class'
438+
// Exclude this IT, because they executed in another task (:integTestWithSecurity)
439+
exclude 'org/opensearch/sql/security/**'
295440
}
296441

297442

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

Lines changed: 55 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;
@@ -45,8 +43,22 @@
4543
public abstract class OpenSearchSQLRestTestCase extends OpenSearchRestTestCase {
4644

4745
private static final Logger LOG = LogManager.getLogger();
48-
public static final String REMOTE_CLUSTER = "remoteCluster";
4946
public static final String MATCH_ALL_REMOTE_CLUSTER = "*";
47+
// Requires to insert cluster name and cluster transport address (host:port)
48+
public static final String REMOTE_CLUSTER_SETTING =
49+
"{"
50+
+ "\"persistent\": {"
51+
+ " \"cluster\": {"
52+
+ " \"remote\": {"
53+
+ " \"%s\": {"
54+
+ " \"seeds\": ["
55+
+ " \"%s\""
56+
+ " ]"
57+
+ " }"
58+
+ " }"
59+
+ " }"
60+
+ "}"
61+
+ "}";
5062

5163
private static RestClient remoteClient;
5264

@@ -102,27 +114,24 @@ protected RestClient buildClient(Settings settings, HttpHost[] hosts) throws IOE
102114
}
103115

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

128137
/** Get a comma delimited list of [host:port] to which to send REST requests. */
@@ -190,6 +199,26 @@ protected static void wipeAllOpenSearchIndices(RestClient client) throws IOExcep
190199
}
191200
}
192201

202+
/**
203+
* Configure authentication and pass <b>builder</b> to superclass to configure other stuff.<br>
204+
* By default, auth is configure when <b>https</b> is set only.
205+
*/
206+
protected static void configureClient(RestClientBuilder builder, Settings settings)
207+
throws IOException {
208+
String userName = System.getProperty("user");
209+
String password = System.getProperty("password");
210+
if (userName != null && password != null) {
211+
builder.setHttpClientConfigCallback(
212+
httpClientBuilder -> {
213+
BasicCredentialsProvider credentialsProvider = new BasicCredentialsProvider();
214+
credentialsProvider.setCredentials(
215+
new AuthScope(null, -1), new UsernamePasswordCredentials(userName, password));
216+
return httpClientBuilder.setDefaultCredentialsProvider(credentialsProvider);
217+
});
218+
}
219+
OpenSearchRestTestCase.configureClient(builder, settings);
220+
}
221+
193222
protected static void configureHttpsClient(RestClientBuilder builder, Settings settings)
194223
throws IOException {
195224
Map<String, String> headers = ThreadContext.buildDefaultHeaders(settings);
@@ -240,16 +269,13 @@ protected static void configureHttpsClient(RestClientBuilder builder, Settings s
240269
* Initialize rest client to remote cluster, and create a connection to it from the coordinating
241270
* cluster.
242271
*/
243-
public void configureMultiClusters() throws IOException {
244-
initRemoteClient();
272+
public void configureMultiClusters(String remote) throws IOException {
273+
initRemoteClient(remote);
245274

246275
Request connectionRequest = new Request("PUT", "_cluster/settings");
247276
String connectionSetting =
248-
"{\"persistent\": {\"cluster\": {\"remote\": {\""
249-
+ REMOTE_CLUSTER
250-
+ "\": {\"seeds\": [\""
251-
+ getTestTransportCluster(REMOTE_CLUSTER).split(",")[0]
252-
+ "\"]}}}}}";
277+
String.format(
278+
REMOTE_CLUSTER_SETTING, remote, getTestTransportCluster(remote).split(",")[0]);
253279
connectionRequest.setJsonEntity(connectionSetting);
254280
adminClient().performRequest(connectionRequest);
255281
}

0 commit comments

Comments
 (0)