Skip to content

Commit

Permalink
Adds a sample resource plugin to demonstrate resource access control …
Browse files Browse the repository at this point in the history
…in action

Signed-off-by: Darshit Chanpura <dchanp@amazon.com>
  • Loading branch information
DarshitChanpura committed Nov 20, 2024
1 parent 9dfd5a0 commit 561e294
Show file tree
Hide file tree
Showing 35 changed files with 1,628 additions and 0 deletions.
166 changes: 166 additions & 0 deletions sample-resource-plugin/build.gradle
Original file line number Diff line number Diff line change
@@ -0,0 +1,166 @@
/*
* 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


opensearchplugin {
name 'opensearch-sample-resource-plugin'
description 'Sample plugin that extends OpenSearch Resource Plugin'
classname 'org.opensearch.sample.SampleResourcePlugin'
}

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" }
}

dependencies {
}

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) }

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
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
/*
* 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.sample;

import org.opensearch.core.common.io.stream.NamedWriteable;
import org.opensearch.core.xcontent.ToXContentFragment;

public abstract class Resource implements NamedWriteable, ToXContentFragment {
protected abstract String getResourceIndex();
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,182 @@
/*
* 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.sample;

import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.List;
import java.util.Map;
import java.util.function.Supplier;

import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;

import org.opensearch.accesscontrol.resources.ResourceService;
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.inject.Inject;
import org.opensearch.common.lifecycle.Lifecycle;
import org.opensearch.common.lifecycle.LifecycleComponent;
import org.opensearch.common.lifecycle.LifecycleListener;
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.ResourcePlugin;
import org.opensearch.plugins.SystemIndexPlugin;
import org.opensearch.repositories.RepositoriesService;
import org.opensearch.rest.RestController;
import org.opensearch.rest.RestHandler;
import org.opensearch.sample.actions.create.CreateResourceAction;
import org.opensearch.sample.actions.create.CreateResourceRestAction;
import org.opensearch.sample.actions.list.ListAccessibleResourcesAction;
import org.opensearch.sample.actions.list.ListAccessibleResourcesRestAction;
import org.opensearch.sample.actions.share.ShareResourceAction;
import org.opensearch.sample.actions.share.ShareResourceRestAction;
import org.opensearch.sample.actions.verify.VerifyResourceAccessAction;
import org.opensearch.sample.actions.verify.VerifyResourceAccessRestAction;
import org.opensearch.sample.transport.CreateResourceTransportAction;
import org.opensearch.sample.transport.ListAccessibleResourcesTransportAction;
import org.opensearch.sample.transport.ShareResourceTransportAction;
import org.opensearch.sample.transport.VerifyResourceAccessTransportAction;
import org.opensearch.script.ScriptService;
import org.opensearch.threadpool.ThreadPool;
import org.opensearch.watcher.ResourceWatcherService;

/**
* Sample Resource plugin.
* It uses ".sample_resources" index to manage its resources, and exposes a REST API
*
*/
public class SampleResourcePlugin extends Plugin implements ActionPlugin, SystemIndexPlugin, ResourcePlugin {
private static final Logger log = LogManager.getLogger(SampleResourcePlugin.class);

public static final String RESOURCE_INDEX_NAME = ".sample_resource_sharing_plugin";

public final static Map<String, Object> INDEX_SETTINGS = Map.of("index.number_of_shards", 1, "index.auto_expand_replicas", "0-all");

private Client client;

@Override
public Collection<Object> createComponents(
Client client,
ClusterService clusterService,
ThreadPool threadPool,
ResourceWatcherService resourceWatcherService,
ScriptService scriptService,
NamedXContentRegistry xContentRegistry,
Environment environment,
NodeEnvironment nodeEnvironment,
NamedWriteableRegistry namedWriteableRegistry,
IndexNameExpressionResolver indexNameExpressionResolver,
Supplier<RepositoriesService> repositoriesServiceSupplier
) {
this.client = client;
log.info("Loaded SampleResourcePlugin components.");
return Collections.emptyList();
}

@Override
public List<RestHandler> getRestHandlers(
Settings settings,
RestController restController,
ClusterSettings clusterSettings,
IndexScopedSettings indexScopedSettings,
SettingsFilter settingsFilter,
IndexNameExpressionResolver indexNameExpressionResolver,
Supplier<DiscoveryNodes> nodesInCluster
) {
return List.of(
new CreateResourceRestAction(),
new ListAccessibleResourcesRestAction(),
new VerifyResourceAccessRestAction(),
new ShareResourceRestAction()
);
}

@Override
public List<ActionHandler<? extends ActionRequest, ? extends ActionResponse>> getActions() {
return List.of(
new ActionHandler<>(CreateResourceAction.INSTANCE, CreateResourceTransportAction.class),
new ActionHandler<>(ListAccessibleResourcesAction.INSTANCE, ListAccessibleResourcesTransportAction.class),
new ActionHandler<>(ShareResourceAction.INSTANCE, ShareResourceTransportAction.class),
new ActionHandler<>(VerifyResourceAccessAction.INSTANCE, VerifyResourceAccessTransportAction.class)
);
}

@Override
public Collection<SystemIndexDescriptor> getSystemIndexDescriptors(Settings settings) {
final SystemIndexDescriptor systemIndexDescriptor = new SystemIndexDescriptor(RESOURCE_INDEX_NAME, "Example index with resources");
return Collections.singletonList(systemIndexDescriptor);
}

@Override
public String getResourceType() {
return "";
}

@Override
public String getResourceIndex() {
return RESOURCE_INDEX_NAME;
}

@Override
public Collection<Class<? extends LifecycleComponent>> getGuiceServiceClasses() {
final List<Class<? extends LifecycleComponent>> services = new ArrayList<>(1);
services.add(GuiceHolder.class);
return services;
}

public static class GuiceHolder implements LifecycleComponent {

private static ResourceService resourceService;

@Inject
public GuiceHolder(final ResourceService resourceService) {
GuiceHolder.resourceService = resourceService;
}

public static ResourceService getResourceService() {
return resourceService;
}

@Override
public void close() {}

@Override
public Lifecycle.State lifecycleState() {
return null;
}

@Override
public void addLifecycleListener(LifecycleListener listener) {}

@Override
public void removeLifecycleListener(LifecycleListener listener) {}

@Override
public void start() {}

@Override
public void stop() {}

}
}
Loading

0 comments on commit 561e294

Please sign in to comment.