Skip to content

Commit f99d895

Browse files
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]>
1 parent e377cf2 commit f99d895

File tree

4 files changed

+287
-46
lines changed

4 files changed

+287
-46
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: 153 additions & 7 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

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

62141
tasks.withType(licenseHeaders.class) {
@@ -103,6 +182,7 @@ dependencies {
103182
testImplementation group: 'com.h2database', name: 'h2', version: '2.2.220'
104183
testImplementation group: 'org.xerial', name: 'sqlite-jdbc', version: '3.41.2.2'
105184
testImplementation group: 'com.google.code.gson', name: 'gson', version: '2.8.9'
185+
testCompileOnly 'org.apiguardian:apiguardian-api:1.1.2'
106186

107187
// Needed for BWC tests
108188
zipArchive group: 'org.opensearch.plugin', name:'opensearch-sql-plugin', version: "${bwcVersion}-SNAPSHOT"
@@ -123,21 +203,28 @@ compileTestJava {
123203
}
124204

125205
testClusters.all {
126-
testDistribution = 'archive'
127-
128206
// debug with command, ./gradlew opensearch-sql:run -DdebugJVM. --debug-jvm does not work with keystore.
129207
if (System.getProperty("debugJVM") != null) {
130208
jvmArgs '-agentlib:jdwp=transport=dt_socket,server=y,suspend=n,address=*:5005'
131209
}
132210
}
133211

134-
testClusters.integTest {
135-
plugin ":opensearch-sql-plugin"
136-
setting "plugins.query.datasources.encryption.masterkey", "1234567812345678"
137-
}
138-
139212
testClusters {
213+
integTest {
214+
testDistribution = 'archive'
215+
plugin ":opensearch-sql-plugin"
216+
setting "plugins.query.datasources.encryption.masterkey", "1234567812345678"
217+
}
140218
remoteCluster {
219+
testDistribution = 'archive'
220+
plugin ":opensearch-sql-plugin"
221+
}
222+
integTestWithSecurity {
223+
testDistribution = 'archive'
224+
plugin ":opensearch-sql-plugin"
225+
}
226+
remoteIntegTestWithSecurity {
227+
testDistribution = 'archive'
141228
plugin ":opensearch-sql-plugin"
142229
}
143230
}
@@ -218,6 +305,65 @@ task integJdbcTest(type: RestIntegTestTask) {
218305
}
219306
}
220307

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

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

Lines changed: 58 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -49,8 +49,22 @@
4949
public abstract class OpenSearchSQLRestTestCase extends OpenSearchRestTestCase {
5050

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

5569
private static RestClient remoteClient;
5670
/**
@@ -105,27 +119,24 @@ protected RestClient buildClient(Settings settings, HttpHost[] hosts) throws IOE
105119
}
106120

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

131142
/**
@@ -201,6 +212,26 @@ protected static void wipeAllOpenSearchIndices(RestClient client) throws IOExcep
201212
}
202213
}
203214

215+
/**
216+
* Configure authentication and pass <b>builder</b> to superclass to configure other stuff.<br>
217+
* By default, auth is configure when <b>https</b> is set only.
218+
*/
219+
protected static void configureClient(RestClientBuilder builder, Settings settings)
220+
throws IOException {
221+
String userName = System.getProperty("user");
222+
String password = System.getProperty("password");
223+
if (userName != null && password != null) {
224+
builder.setHttpClientConfigCallback(httpClientBuilder -> {
225+
BasicCredentialsProvider credentialsProvider = new BasicCredentialsProvider();
226+
credentialsProvider.setCredentials(
227+
new AuthScope(null, -1),
228+
new UsernamePasswordCredentials(userName, password.toCharArray()));
229+
return httpClientBuilder.setDefaultCredentialsProvider(credentialsProvider);
230+
});
231+
}
232+
OpenSearchRestTestCase.configureClient(builder, settings);
233+
}
234+
204235
protected static void configureHttpsClient(RestClientBuilder builder, Settings settings,
205236
HttpHost httpHost)
206237
throws IOException {
@@ -252,15 +283,15 @@ protected static void configureHttpsClient(RestClientBuilder builder, Settings s
252283
* Initialize rest client to remote cluster,
253284
* and create a connection to it from the coordinating cluster.
254285
*/
255-
public void configureMultiClusters() throws IOException {
256-
initRemoteClient();
286+
public void configureMultiClusters(String remote)
287+
throws IOException {
288+
initRemoteClient(remote);
257289

258290
Request connectionRequest = new Request("PUT", "_cluster/settings");
259-
String connectionSetting = "{\"persistent\": {\"cluster\": {\"remote\": {\""
260-
+ REMOTE_CLUSTER
261-
+ "\": {\"seeds\": [\""
262-
+ getTestTransportCluster(REMOTE_CLUSTER).split(",")[0]
263-
+ "\"]}}}}}";
291+
String connectionSetting = String.format(
292+
REMOTE_CLUSTER_SETTING,
293+
remote,
294+
getTestTransportCluster(remote).split(",")[0]);
264295
connectionRequest.setJsonEntity(connectionSetting);
265296
adminClient().performRequest(connectionRequest);
266297
}

0 commit comments

Comments
 (0)