diff --git a/.github/actions/create-bwc-build/action.yaml b/.github/actions/create-bwc-build/action.yaml
index 8960849333..0f9e373b16 100644
--- a/.github/actions/create-bwc-build/action.yaml
+++ b/.github/actions/create-bwc-build/action.yaml
@@ -42,7 +42,7 @@ runs:
uses: gradle/gradle-build-action@v2
with:
cache-disabled: true
- arguments: assemble
+ arguments: :assemble
build-root-directory: ${{ inputs.plugin-branch }}
- id: get-opensearch-version
diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml
index 7f9e94ad6a..4d334810b8 100644
--- a/.github/workflows/ci.yml
+++ b/.github/workflows/ci.yml
@@ -151,7 +151,7 @@ jobs:
with:
cache-disabled: true
arguments: |
- integrationTest -Dbuild.snapshot=false --tests org.opensearch.security.ResourceFocusedTests
+ :integrationTest -Dbuild.snapshot=false --tests org.opensearch.security.ResourceFocusedTests
backward-compatibility-build:
runs-on: ubuntu-latest
@@ -208,7 +208,7 @@ jobs:
- uses: github/codeql-action/init@v3
with:
languages: java
- - run: ./gradlew clean assemble
+ - run: ./gradlew clean :assemble
- uses: github/codeql-action/analyze@v3
build-artifact-names:
@@ -238,13 +238,13 @@ jobs:
echo ${{ env.SECURITY_PLUGIN_VERSION_ONLY_NUMBER }}
echo ${{ env.TEST_QUALIFIER }}
- - run: ./gradlew clean assemble && test -s ./build/distributions/opensearch-security-${{ env.SECURITY_PLUGIN_VERSION }}.zip
+ - run: ./gradlew clean :assemble && test -s ./build/distributions/opensearch-security-${{ env.SECURITY_PLUGIN_VERSION }}.zip
- - run: ./gradlew clean assemble -Dbuild.snapshot=false && test -s ./build/distributions/opensearch-security-${{ env.SECURITY_PLUGIN_VERSION_NO_SNAPSHOT }}.zip
+ - run: ./gradlew clean :assemble -Dbuild.snapshot=false && test -s ./build/distributions/opensearch-security-${{ env.SECURITY_PLUGIN_VERSION_NO_SNAPSHOT }}.zip
- - run: ./gradlew clean assemble -Dbuild.snapshot=false -Dbuild.version_qualifier=${{ env.TEST_QUALIFIER }} && test -s ./build/distributions/opensearch-security-${{ env.SECURITY_PLUGIN_VERSION_ONLY_NUMBER }}-${{ env.TEST_QUALIFIER }}.zip
+ - run: ./gradlew clean :assemble -Dbuild.snapshot=false -Dbuild.version_qualifier=${{ env.TEST_QUALIFIER }} && test -s ./build/distributions/opensearch-security-${{ env.SECURITY_PLUGIN_VERSION_ONLY_NUMBER }}-${{ env.TEST_QUALIFIER }}.zip
- - run: ./gradlew clean assemble -Dbuild.version_qualifier=${{ env.TEST_QUALIFIER }} && test -s ./build/distributions/opensearch-security-${{ env.SECURITY_PLUGIN_VERSION_ONLY_NUMBER }}-${{ env.TEST_QUALIFIER }}-SNAPSHOT.zip
+ - run: ./gradlew clean :assemble -Dbuild.version_qualifier=${{ env.TEST_QUALIFIER }} && test -s ./build/distributions/opensearch-security-${{ env.SECURITY_PLUGIN_VERSION_ONLY_NUMBER }}-${{ env.TEST_QUALIFIER }}-SNAPSHOT.zip
- run: ./gradlew clean publishPluginZipPublicationToZipStagingRepository && test -s ./build/distributions/opensearch-security-${{ env.SECURITY_PLUGIN_VERSION }}.zip && test -s ./build/distributions/opensearch-security-${{ env.SECURITY_PLUGIN_VERSION }}.pom
diff --git a/.github/workflows/plugin_install.yml b/.github/workflows/plugin_install.yml
index 3f8d61795c..c427b160c4 100644
--- a/.github/workflows/plugin_install.yml
+++ b/.github/workflows/plugin_install.yml
@@ -32,7 +32,7 @@ jobs:
uses: gradle/gradle-build-action@v3
with:
cache-disabled: true
- arguments: assemble
+ arguments: :assemble
# Move and rename the plugin for installation
- name: Move and rename the plugin for installation
diff --git a/build.gradle b/build.gradle
index 9eafa6f896..06b7aa156b 100644
--- a/build.gradle
+++ b/build.gradle
@@ -574,6 +574,7 @@ tasks.integrationTest.finalizedBy(jacocoTestReport) // report is always generate
check.dependsOn integrationTest
dependencies {
+ implementation project(path: ":${rootProject.name}-spi", configuration: 'shadow')
implementation "org.opensearch.plugin:transport-netty4-client:${opensearch_version}"
implementation "org.opensearch.client:opensearch-rest-high-level-client:${opensearch_version}"
implementation "org.apache.httpcomponents.client5:httpclient5-cache:${versions.httpclient5}"
@@ -724,29 +725,29 @@ dependencies {
compileOnly "org.opensearch:opensearch:${opensearch_version}"
//integration test framework:
- integrationTestImplementation('com.carrotsearch.randomizedtesting:randomizedtesting-runner:2.8.2') {
- exclude(group: 'junit', module: 'junit')
- }
- integrationTestImplementation 'junit:junit:4.13.2'
- integrationTestImplementation "org.opensearch.plugin:reindex-client:${opensearch_version}"
- integrationTestImplementation "org.opensearch.plugin:percolator-client:${opensearch_version}"
- integrationTestImplementation 'commons-io:commons-io:2.18.0'
- integrationTestImplementation "org.apache.logging.log4j:log4j-core:${versions.log4j}"
- integrationTestImplementation "org.apache.logging.log4j:log4j-jul:${versions.log4j}"
- integrationTestImplementation 'org.hamcrest:hamcrest:2.2'
- integrationTestImplementation "org.bouncycastle:bcpkix-jdk18on:${versions.bouncycastle}"
- integrationTestImplementation "org.bouncycastle:bcutil-jdk18on:${versions.bouncycastle}"
- integrationTestImplementation('org.awaitility:awaitility:4.2.2') {
- exclude(group: 'org.hamcrest', module: 'hamcrest')
- }
- integrationTestImplementation 'com.unboundid:unboundid-ldapsdk:4.0.14'
- integrationTestImplementation "org.opensearch.plugin:mapper-size:${opensearch_version}"
- integrationTestImplementation "org.apache.httpcomponents:httpclient-cache:4.5.14"
- integrationTestImplementation "org.apache.httpcomponents:httpclient:4.5.14"
- integrationTestImplementation "org.apache.httpcomponents:fluent-hc:4.5.14"
- integrationTestImplementation "org.apache.httpcomponents:httpcore:4.4.16"
- integrationTestImplementation "org.apache.httpcomponents:httpasyncclient:4.1.5"
- integrationTestImplementation "org.mockito:mockito-core:5.14.2"
+// integrationTestImplementation('com.carrotsearch.randomizedtesting:randomizedtesting-runner:2.8.2') {
+// exclude(group: 'junit', module: 'junit')
+// }
+// integrationTestImplementation 'junit:junit:4.13.2'
+// integrationTestImplementation "org.opensearch.plugin:reindex-client:${opensearch_version}"
+// integrationTestImplementation "org.opensearch.plugin:percolator-client:${opensearch_version}"
+// integrationTestImplementation 'commons-io:commons-io:2.18.0'
+// integrationTestImplementation "org.apache.logging.log4j:log4j-core:${versions.log4j}"
+// integrationTestImplementation "org.apache.logging.log4j:log4j-jul:${versions.log4j}"
+// integrationTestImplementation 'org.hamcrest:hamcrest:2.2'
+// integrationTestImplementation "org.bouncycastle:bcpkix-jdk18on:${versions.bouncycastle}"
+// integrationTestImplementation "org.bouncycastle:bcutil-jdk18on:${versions.bouncycastle}"
+// integrationTestImplementation('org.awaitility:awaitility:4.2.2') {
+// exclude(group: 'org.hamcrest', module: 'hamcrest')
+// }
+// integrationTestImplementation 'com.unboundid:unboundid-ldapsdk:4.0.14'
+// integrationTestImplementation "org.opensearch.plugin:mapper-size:${opensearch_version}"
+// integrationTestImplementation "org.apache.httpcomponents:httpclient-cache:4.5.14"
+// integrationTestImplementation "org.apache.httpcomponents:httpclient:4.5.14"
+// integrationTestImplementation "org.apache.httpcomponents:fluent-hc:4.5.14"
+// integrationTestImplementation "org.apache.httpcomponents:httpcore:4.4.16"
+// integrationTestImplementation "org.apache.httpcomponents:httpasyncclient:4.1.5"
+// integrationTestImplementation "org.mockito:mockito-core:5.14.2"
//spotless
implementation('com.google.googlejavaformat:google-java-format:1.25.2') {
@@ -754,6 +755,42 @@ dependencies {
}
}
+allprojects {
+ configurations {
+ integrationTestImplementation.extendsFrom implementation
+ integrationTestRuntimeOnly.extendsFrom runtimeOnly
+ }
+ dependencies {
+ //integration test framework:
+ integrationTestImplementation('com.carrotsearch.randomizedtesting:randomizedtesting-runner:2.8.2') {
+ exclude(group: 'junit', module: 'junit')
+ }
+ integrationTestImplementation 'junit:junit:4.13.2'
+ integrationTestImplementation "org.opensearch.plugin:reindex-client:${opensearch_version}"
+ integrationTestImplementation "org.opensearch.plugin:percolator-client:${opensearch_version}"
+ integrationTestImplementation 'commons-io:commons-io:2.18.0'
+ integrationTestImplementation "org.apache.logging.log4j:log4j-core:${versions.log4j}"
+ integrationTestImplementation "org.apache.logging.log4j:log4j-jul:${versions.log4j}"
+ integrationTestImplementation 'org.hamcrest:hamcrest:2.2'
+ integrationTestImplementation "org.bouncycastle:bcpkix-jdk18on:${versions.bouncycastle}"
+ integrationTestImplementation "org.bouncycastle:bcutil-jdk18on:${versions.bouncycastle}"
+ integrationTestImplementation('org.awaitility:awaitility:4.2.2') {
+ exclude(group: 'org.hamcrest', module: 'hamcrest')
+ }
+ integrationTestImplementation 'com.unboundid:unboundid-ldapsdk:4.0.14'
+ integrationTestImplementation "org.opensearch.plugin:mapper-size:${opensearch_version}"
+ integrationTestImplementation "org.apache.httpcomponents:httpclient-cache:4.5.14"
+ integrationTestImplementation "org.apache.httpcomponents:httpclient:4.5.14"
+ integrationTestImplementation "org.apache.httpcomponents:fluent-hc:4.5.14"
+ integrationTestImplementation "org.apache.httpcomponents:httpcore:4.4.16"
+ integrationTestImplementation "org.apache.httpcomponents:httpasyncclient:4.1.5"
+ integrationTestImplementation("org.mockito:mockito-core:5.14.2") {
+ exclude(group: 'net.bytebuddy', module: 'byte-buddy')
+ }
+ integrationTestImplementation "net.bytebuddy:byte-buddy:${versions.bytebuddy}"
+ }
+}
+
jar {
libsDirName = '.'
into '', {
diff --git a/checkstyle/checkstyle.xml b/checkstyle/checkstyle.xml
index a9c1a8f765..7fe4a703de 100644
--- a/checkstyle/checkstyle.xml
+++ b/checkstyle/checkstyle.xml
@@ -205,12 +205,12 @@
-
-
-
-
-
-
+
+
+
+
+
+
@@ -228,12 +228,12 @@
-
-
-
-
-
-
+
+
+
+
+
+
diff --git a/sample-extension-plugin/build.gradle b/sample-extension-plugin/build.gradle
new file mode 100644
index 0000000000..d601890483
--- /dev/null
+++ b/sample-extension-plugin/build.gradle
@@ -0,0 +1,268 @@
+/*
+ * Copyright OpenSearch Contributors
+ * SPDX-License-Identifier: Apache-2.0
+ */
+
+apply plugin: 'opensearch.opensearchplugin'
+apply plugin: 'opensearch.testclusters'
+apply plugin: 'opensearch.java-rest-test'
+
+import org.opensearch.gradle.test.RestIntegTestTask
+import org.opensearch.gradle.testclusters.StandaloneRestIntegTestTask
+import org.apache.tools.ant.taskdefs.condition.Os
+
+import java.util.concurrent.Callable
+
+
+opensearchplugin {
+ name 'opensearch-security-sample-extension'
+ description 'Sample plugin that extends OpenSearch Security Resource Sharing Extension'
+ classname 'org.opensearch.security.sampleextension.SampleExtensionPlugin'
+ extendedPlugins = ['opensearch-security']
+}
+
+ext {
+ projectSubstitutions = [:]
+ licenseFile = rootProject.file('LICENSE.txt')
+ noticeFile = rootProject.file('NOTICE.txt')
+}
+
+repositories {
+ mavenLocal()
+ mavenCentral()
+ maven { url "https://aws.oss.sonatype.org/content/repositories/snapshots" }
+}
+
+//buildscript {
+// ext {
+// dependencies {
+// classpath "org.opensearch.gradle:build-tools:${opensearch_version}"
+// }
+// }
+//}
+
+configurations {
+ all {
+ resolutionStrategy {
+ force 'org.slf4j:slf4j-api:1.7.36'
+ force 'commons-codec:commons-codec:1.17.1'
+ force "org.apache.httpcomponents:httpclient:4.5.14"
+ force "org.apache.httpcomponents:httpcore:4.4.16"
+ force "com.carrotsearch.randomizedtesting:randomizedtesting-runner:2.8.1"
+ force "org.hamcrest:hamcrest:2.2"
+ force "com.fasterxml.jackson:jackson-bom:${versions.jackson}"
+ force "com.fasterxml.jackson.core:jackson-core:${versions.jackson}"
+ force "com.fasterxml.jackson.datatype:jackson-datatype-jdk8:${versions.jackson}"
+ force "com.fasterxml.jackson.core:jackson-databind:${versions.jackson_databind}"
+ }
+ }
+}
+
+// TODO Make this plugin have an integration test dependency on integrationTest from root project
+dependencies {
+ compileOnly project(path: ":${rootProject.name}-spi", configuration: 'shadow')
+ // integrationTestImplementation project(":")
+ integrationTestImplementation rootProject.sourceSets.main.output
+ integrationTestImplementation rootProject.sourceSets.integrationTest.output
+ testImplementation "com.fasterxml.jackson.core:jackson-annotations:${versions.jackson}"
+ testImplementation "com.fasterxml.jackson.core:jackson-databind:${versions.jackson_databind}"
+
+ integrationTestImplementation "org.opensearch.plugin:transport-netty4-client:${opensearch_version}"
+ integrationTestImplementation "org.opensearch.client:opensearch-rest-high-level-client:${opensearch_version}"
+ integrationTestImplementation "com.google.guava:guava:${guava_version}"
+ integrationTestImplementation 'org.greenrobot:eventbus-java:3.3.1'
+ integrationTestImplementation 'org.ldaptive:ldaptive:1.2.3'
+ integrationTestImplementation 'com.password4j:password4j:1.8.2'
+ // Action privileges: check tables and compact collections
+ integrationTestImplementation 'com.selectivem.collections:special-collections-complete:1.4.0'
+ // JSON patch
+ integrationTestImplementation 'com.flipkart.zjsonpatch:zjsonpatch:0.4.16'
+ //Password generation
+ integrationTestImplementation 'org.passay:passay:1.6.5'
+ integrationTestImplementation 'org.slf4j:slf4j-api:1.7.36'
+ integrationTestImplementation "org.apache.commons:commons-lang3:${versions.commonslang}"
+
+ integrationTestImplementation "com.fasterxml.jackson.core:jackson-annotations:${versions.jackson}"
+ integrationTestImplementation "com.fasterxml.jackson.core:jackson-databind:${versions.jackson_databind}"
+
+ integrationTestImplementation "org.opensearch:opensearch:${opensearch_version}"
+}
+
+def es_tmp_dir = rootProject.file('build/private/es_tmp').absoluteFile
+es_tmp_dir.mkdirs()
+
+File repo = file("$buildDir/testclusters/repo")
+def _numNodes = findProperty('numNodes') as Integer ?: 1
+
+licenseHeaders.enabled = true
+validateNebulaPom.enabled = false
+testingConventions.enabled = false
+loggerUsageCheck.enabled = false
+
+javaRestTest.dependsOn(rootProject.assemble)
+javaRestTest {
+ systemProperty 'tests.security.manager', 'false'
+}
+testClusters.javaRestTest {
+ testDistribution = 'INTEG_TEST'
+}
+
+task integTest(type: RestIntegTestTask) {
+ description = "Run tests against a cluster"
+ testClassesDirs = sourceSets.test.output.classesDirs
+ classpath = sourceSets.test.runtimeClasspath
+}
+tasks.named("check").configure { dependsOn(integTest) }
+
+//create source set 'integrationTest'
+//add classes from the main source set to the compilation and runtime classpaths of the integrationTest
+sourceSets {
+ integrationTest {
+ java {
+ srcDir file ('src/integrationTest/java')
+ compileClasspath += sourceSets.main.output
+ runtimeClasspath += sourceSets.main.output
+ }
+ resources {
+ srcDir file('src/integrationTest/resources')
+ }
+ processIntegrationTestResources {
+ duplicatesStrategy(DuplicatesStrategy.INCLUDE)
+ }
+ }
+}
+
+task integrationTest(type: Test) {
+ testClassesDirs = sourceSets.integrationTest.output.classesDirs
+ classpath = sourceSets.integrationTest.runtimeClasspath
+}
+
+configurations {
+ integrationTestImplementation.extendsFrom(rootProject.configurations.implementation)
+ integrationTestImplementation.extendsFrom(rootProject.configurations.testImplementation)
+ integrationTestImplementation.extendsFrom(rootProject.configurations.integrationTestImplementation)
+}
+
+integTest {
+ if (project.hasProperty('excludeTests')) {
+ project.properties['excludeTests']?.replaceAll('\\s', '')?.split('[,;]')?.each {
+ exclude "${it}"
+ }
+ }
+ systemProperty 'tests.security.manager', 'false'
+ systemProperty 'java.io.tmpdir', es_tmp_dir.absolutePath
+
+ systemProperty "https", System.getProperty("https")
+ systemProperty "user", System.getProperty("user")
+ systemProperty "password", System.getProperty("password")
+ // 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 {
+ // Tell the test JVM if the cluster JVM is running under a debugger so that tests can
+ // use longer timeouts for requests.
+ def isDebuggingCluster = getDebug() || System.getProperty("test.debug") != null
+ systemProperty 'cluster.debug', isDebuggingCluster
+ // Set number of nodes system property to be used in tests
+ systemProperty 'cluster.number_of_nodes', "${_numNodes}"
+ // There seems to be an issue when running multi node run or integ tasks with unicast_hosts
+ // not being written, the waitForAllConditions ensures it's written
+ getClusters().forEach { cluster ->
+ cluster.waitForAllConditions()
+ }
+ }
+
+ // The -Dcluster.debug option makes the cluster debuggable; this makes the tests debuggable
+ if (System.getProperty("test.debug") != null) {
+ jvmArgs '-agentlib:jdwp=transport=dt_socket,server=n,suspend=y,address=8000'
+ }
+ if (System.getProperty("tests.rest.bwcsuite") == null) {
+ filter {
+ excludeTestsMatching "org.opensearch.security.sampleextension.bwc.*IT"
+ }
+ }
+}
+project.getTasks().getByName('bundlePlugin').dependsOn(rootProject.tasks.getByName('build'))
+Zip bundle = (Zip) project.getTasks().getByName("bundlePlugin");
+Zip rootBundle = (Zip) rootProject.getTasks().getByName("bundlePlugin");
+integTest.dependsOn(bundle)
+integTest.getClusters().forEach{c -> {
+ c.plugin(rootProject.getObjects().fileProperty().value(rootBundle.getArchiveFile()))
+ c.plugin(project.getObjects().fileProperty().value(bundle.getArchiveFile()))
+}}
+
+testClusters.integTest {
+ testDistribution = 'INTEG_TEST'
+
+ // Cluster shrink exception thrown if we try to set numberOfNodes to 1, so only apply if > 1
+ if (_numNodes > 1) numberOfNodes = _numNodes
+ // When running integration tests it doesn't forward the --debug-jvm to the cluster anymore
+ // i.e. we have to use a custom property to flag when we want to debug OpenSearch JVM
+ // since we also support multi node integration tests we increase debugPort per node
+ if (System.getProperty("cluster.debug") != null) {
+ def debugPort = 5005
+ nodes.forEach { node ->
+ node.jvmArgs("-agentlib:jdwp=transport=dt_socket,server=n,suspend=y,address=*:${debugPort}")
+ debugPort += 1
+ }
+ }
+ setting 'path.repo', repo.absolutePath
+}
+
+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)
+ }
+
+ 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("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\"]")
+ }
+}
+
+run {
+ doFirst {
+ // There seems to be an issue when running multi node run or integ tasks with unicast_hosts
+ // not being written, the waitForAllConditions ensures it's written
+ getClusters().forEach { cluster ->
+ cluster.waitForAllConditions()
+ }
+ }
+ useCluster testClusters.integTest
+}
+
+// As of ES 7.7 the sample-extension-plugin is being added to the list of plugins for the testCluster during build before
+// the security plugin is causing build failures.
+// The security zip is added explicitly above but the sample-extension-plugin is added implicitly at some time during evaluation.
+// Will need to do a deep dive to find out exactly what task adds the sample-extension-plugin and add security there but a temporary hack is to
+// reorder the plugins list after evaluation but prior to task execution when the plugins are installed.
+//afterEvaluate {
+// testClusters.javaRestTest.nodes.each { node ->
+// def nodePlugins = node.plugins
+// def firstPlugin = nodePlugins.get(0)
+// if (firstPlugin.provider == project.bundlePlugin.archiveFile) {
+// nodePlugins.remove(0)
+// nodePlugins.add(firstPlugin)
+// }
+// }
+//}
diff --git a/sample-extension-plugin/src/integrationTest/java/org/opensearch/security/sampleextension/SampleExtensionPluginTests.java b/sample-extension-plugin/src/integrationTest/java/org/opensearch/security/sampleextension/SampleExtensionPluginTests.java
new file mode 100644
index 0000000000..8d22e01410
--- /dev/null
+++ b/sample-extension-plugin/src/integrationTest/java/org/opensearch/security/sampleextension/SampleExtensionPluginTests.java
@@ -0,0 +1,95 @@
+/*
+ * SPDX-License-Identifier: Apache-2.0
+ *
+ * The OpenSearch Contributors require contributions made to
+ * this file be licensed under the Apache-2.0 license or a
+ * compatible open source license.
+ *
+ * Modifications Copyright OpenSearch Contributors. See
+ * GitHub history for details.
+ */
+
+package org.opensearch.security.sampleextension;
+
+import com.carrotsearch.randomizedtesting.annotations.ThreadLeakScope;
+import org.apache.http.HttpStatus;
+import org.junit.ClassRule;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+import org.opensearch.test.framework.cluster.ClusterManager;
+import org.opensearch.test.framework.cluster.LocalCluster;
+import org.opensearch.test.framework.cluster.TestRestClient;
+import org.opensearch.test.framework.cluster.TestRestClient.HttpResponse;
+
+import static org.hamcrest.MatcherAssert.assertThat;
+import static org.hamcrest.Matchers.containsString;
+import static org.hamcrest.Matchers.equalTo;
+import static org.opensearch.test.framework.TestSecurityConfig.AuthcDomain.AUTHC_HTTPBASIC_INTERNAL;
+import static org.opensearch.test.framework.TestSecurityConfig.User.USER_ADMIN;
+
+@RunWith(com.carrotsearch.randomizedtesting.RandomizedRunner.class)
+@ThreadLeakScope(ThreadLeakScope.Scope.NONE)
+public class SampleExtensionPluginTests {
+
+ @ClassRule
+ public static LocalCluster cluster = new LocalCluster.Builder().clusterManager(ClusterManager.SINGLENODE)
+ .plugin(SampleExtensionPlugin.class)
+ .anonymousAuth(true)
+ .authc(AUTHC_HTTPBASIC_INTERNAL)
+ .users(USER_ADMIN)
+ .build();
+
+ @Test
+ public void testSecurityRoles() throws Exception {
+ try (TestRestClient client = cluster.getRestClient(USER_ADMIN)) {
+ HttpResponse response = client.getAuthInfo();
+ response.assertStatusCode(HttpStatus.SC_OK);
+
+ // Check username
+ assertThat(response.getTextFromJsonBody("/user_name"), equalTo("admin"));
+ System.out.println("Response: " + response.getBody());
+ HttpResponse pluginsResponse = client.get("_cat/plugins?s=component&h=name,component,version,description");
+ System.out.println("pluginsResponse: " + pluginsResponse.getBody());
+ assertThat(pluginsResponse.getBody(), containsString("org.opensearch.security.OpenSearchSecurityPlugin"));
+ assertThat(pluginsResponse.getBody(), containsString("org.opensearch.security.sampleextension.SampleExtensionPlugin"));
+ }
+ }
+
+ @Test
+ public void testCreateAndUpdateOwnSampleResource() throws Exception {
+ String resourceId;
+ try (TestRestClient client = cluster.getRestClient(USER_ADMIN)) {
+ String sampleResource = "{\"name\":\"sample\"}";
+ HttpResponse response = client.postJson("_plugins/resource_sharing_example/resource", sampleResource);
+ response.assertStatusCode(HttpStatus.SC_OK);
+ System.out.println("Response: " + response.getBody());
+
+ resourceId = response.getTextFromJsonBody("/resourceId");
+
+ System.out.println("resourceId: " + resourceId);
+ Thread.sleep(2000);
+ }
+ try (TestRestClient client = cluster.getRestClient(cluster.getAdminCertificate())) {
+ HttpResponse response = client.postJson(".resource-sharing/_search", "{\"query\" : {\"match_all\" : {}}}");
+ System.out.println("Resource sharing entries: " + response.getBody());
+ }
+
+ try (TestRestClient client = cluster.getRestClient(USER_ADMIN)) {
+ Thread.sleep(1000);
+
+ HttpResponse getResponse = client.get("_plugins/resource_sharing_example/resource/" + resourceId);
+ getResponse.assertStatusCode(HttpStatus.SC_OK);
+ System.out.println("Get Response: " + getResponse.getBody());
+
+ String sampleResourceUpdated = "{\"name\":\"sampleUpdated\"}";
+ HttpResponse updateResponse = client.putJson(
+ "_plugins/resource_sharing_example/resource/update/" + resourceId,
+ sampleResourceUpdated
+ );
+ updateResponse.assertStatusCode(HttpStatus.SC_OK);
+ System.out.println("Update Response: " + updateResponse.getBody());
+ }
+ }
+
+}
diff --git a/sample-extension-plugin/src/main/java/org/opensearch/security/sampleextension/SampleExtensionPlugin.java b/sample-extension-plugin/src/main/java/org/opensearch/security/sampleextension/SampleExtensionPlugin.java
new file mode 100644
index 0000000000..6431446329
--- /dev/null
+++ b/sample-extension-plugin/src/main/java/org/opensearch/security/sampleextension/SampleExtensionPlugin.java
@@ -0,0 +1,150 @@
+/*
+ * Copyright OpenSearch Contributors
+ * SPDX-License-Identifier: Apache-2.0
+ *
+ * The OpenSearch Contributors require contributions made to
+ * this file be licensed under the Apache-2.0 license or a
+ * compatible open source license.
+ */
+package org.opensearch.security.sampleextension;
+
+import java.util.Collection;
+import java.util.Collections;
+import java.util.List;
+import java.util.function.Supplier;
+
+import org.apache.logging.log4j.LogManager;
+import org.apache.logging.log4j.Logger;
+
+import org.opensearch.action.ActionRequest;
+import org.opensearch.client.Client;
+import org.opensearch.cluster.metadata.IndexNameExpressionResolver;
+import org.opensearch.cluster.node.DiscoveryNodes;
+import org.opensearch.cluster.service.ClusterService;
+import org.opensearch.common.settings.ClusterSettings;
+import org.opensearch.common.settings.IndexScopedSettings;
+import org.opensearch.common.settings.Settings;
+import org.opensearch.common.settings.SettingsFilter;
+import org.opensearch.core.action.ActionResponse;
+import org.opensearch.core.common.io.stream.NamedWriteableRegistry;
+import org.opensearch.core.xcontent.NamedXContentRegistry;
+import org.opensearch.env.Environment;
+import org.opensearch.env.NodeEnvironment;
+import org.opensearch.indices.SystemIndexDescriptor;
+import org.opensearch.plugins.ActionPlugin;
+import org.opensearch.plugins.Plugin;
+import org.opensearch.plugins.SystemIndexPlugin;
+import org.opensearch.repositories.RepositoriesService;
+import org.opensearch.rest.RestController;
+import org.opensearch.rest.RestHandler;
+import org.opensearch.script.ScriptService;
+import org.opensearch.security.sampleextension.actions.create.CreateSampleResourceAction;
+import org.opensearch.security.sampleextension.actions.create.CreateSampleResourceRestAction;
+import org.opensearch.security.sampleextension.actions.create.CreateSampleResourceTransportAction;
+import org.opensearch.security.sampleextension.actions.get.GetSampleResourceAction;
+import org.opensearch.security.sampleextension.actions.get.GetSampleResourceRestAction;
+import org.opensearch.security.sampleextension.actions.get.GetSampleResourceTransportAction;
+import org.opensearch.security.sampleextension.actions.list.ListSampleResourceAction;
+import org.opensearch.security.sampleextension.actions.list.ListSampleResourceRestAction;
+import org.opensearch.security.sampleextension.actions.list.ListSampleResourceTransportAction;
+import org.opensearch.security.sampleextension.actions.update.UpdateSampleResourceAction;
+import org.opensearch.security.sampleextension.actions.update.UpdateSampleResourceRestAction;
+import org.opensearch.security.sampleextension.actions.update.UpdateSampleResourceTransportAction;
+import org.opensearch.security.sampleextension.resource.SampleResourceParser;
+import org.opensearch.security.sampleextension.resource.SampleResourceSharingServiceProvider;
+import org.opensearch.security.spi.ResourceParser;
+import org.opensearch.security.spi.ResourceSharingExtension;
+import org.opensearch.security.spi.ResourceSharingService;
+import org.opensearch.security.spi.SharableResource;
+import org.opensearch.threadpool.ThreadPool;
+import org.opensearch.watcher.ResourceWatcherService;
+
+/**
+ * Sample Security Resource Sharing extension plugin.
+ *
+ * It use ".sample_extension_resources" index to manage its resources, and exposes a REST API
+ *
+ */
+public class SampleExtensionPlugin extends Plugin implements ActionPlugin, SystemIndexPlugin, ResourceSharingExtension {
+ private static final Logger log = LogManager.getLogger(SampleExtensionPlugin.class);
+
+ public static final String RESOURCE_INDEX_NAME = ".sample_extension_resources";
+
+ private Client client;
+
+ @Override
+ public Collection