Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
30 commits
Select commit Hold shift + click to select a range
2246d91
Introduces resource sharing and access control SPI
DarshitChanpura Mar 17, 2025
c8245dc
Introduces a resource sharing client and completes resource access co…
DarshitChanpura Mar 17, 2025
7631bd0
Updates client to handle security disabled scenario
DarshitChanpura Mar 18, 2025
84b2a21
Updates build.gradle files and jarhell references
DarshitChanpura Mar 19, 2025
3d9c9c1
Disables jar for common
DarshitChanpura Mar 19, 2025
f167091
Removes common package and refactors classes
DarshitChanpura Mar 19, 2025
89d3a63
Conforms to SPI file name changes and removes spi package
DarshitChanpura Mar 20, 2025
643e44f
Updates plugin dev doc
DarshitChanpura Mar 20, 2025
52f4ac5
Fixes CI
DarshitChanpura Mar 20, 2025
e05ae8a
Updates exception types in client and root project
DarshitChanpura Mar 21, 2025
c0c9f9b
Addresses PR comments
DarshitChanpura Mar 21, 2025
0cac90d
Refactors client to address action-groups and updates core implementa…
DarshitChanpura Mar 26, 2025
95ff332
Merge remote-tracking branch 'upstream/feature/resource-permissions' …
DarshitChanpura Mar 26, 2025
b349a5c
Fixes build.gradle
DarshitChanpura Mar 26, 2025
31b0d0b
Fixes build-artifacts workflow
DarshitChanpura Mar 26, 2025
400a360
Addresses changes around recipient type registry removal
DarshitChanpura Mar 26, 2025
906908a
Makes ResourceIndexListener non-singleton, refactors a method and cha…
DarshitChanpura Mar 26, 2025
5ecac3a
Splits singular resource rest action to individual actions
DarshitChanpura Mar 27, 2025
f943168
Removes singleton pattern from ResourcePluginInfo
DarshitChanpura Mar 28, 2025
124f438
Restricts the client method arguments to ActionGroupRecipients type
DarshitChanpura Mar 30, 2025
754e87b
Adds ResourcePluginInfo as injectable component
DarshitChanpura Mar 31, 2025
7aa1b1d
Requires version to be passed when instantiating client
DarshitChanpura Mar 31, 2025
b383417
Updates client readme doc
DarshitChanpura Mar 31, 2025
0737060
[Resource Access Control] [Part1] Introduces SPI for resource access …
DarshitChanpura Apr 1, 2025
2760e3e
Merge branch 'feature/resource-permissions' into resource-sharing-client
DarshitChanpura Apr 1, 2025
7b8b28b
Fix DefaultObjectMapper imports and TestRestClient instantiations
DarshitChanpura Apr 1, 2025
162463a
Disables SPI jar to allow assemble task to succeed
DarshitChanpura Apr 1, 2025
f0f0255
Merge branch 'feature/resource-permissions' into resource-sharing-client
DarshitChanpura Apr 1, 2025
990b651
Corrects steps indentation for CI workflow file
DarshitChanpura Apr 1, 2025
877eacb
Fixes CI workflow
DarshitChanpura Apr 2, 2025
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
445 changes: 205 additions & 240 deletions .github/workflows/ci.yml

Large diffs are not rendered by default.

526 changes: 526 additions & 0 deletions RESOURCE_ACCESS_CONTROL_FOR_PLUGINS.md

Large diffs are not rendered by default.

24 changes: 14 additions & 10 deletions build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -440,14 +440,6 @@ publishing {
}
}

repositories {
mavenLocal()
mavenCentral()
maven { url "https://plugins.gradle.org/m2/" }
maven { url "https://aws.oss.sonatype.org/content/repositories/snapshots" }
maven { url "https://artifacts.opensearch.org/snapshots/lucene/" }
maven { url "https://build.shibboleth.net/nexus/content/repositories/releases" }
}

tasks.test.finalizedBy(jacocoTestReport) // report is always generated after tests run

Expand Down Expand Up @@ -511,6 +503,15 @@ configurations {
}

allprojects {
repositories {
mavenLocal()
mavenCentral()
maven { url "https://plugins.gradle.org/m2/" }
maven { url "https://aws.oss.sonatype.org/content/repositories/snapshots" }
maven { url "https://artifacts.opensearch.org/snapshots/lucene/" }
maven { url "https://build.shibboleth.net/nexus/content/repositories/releases" }
}

configurations {
integrationTestImplementation.extendsFrom implementation
compile.extendsFrom compileOnly
Expand Down Expand Up @@ -565,7 +566,8 @@ allprojects {
integrationTestImplementation 'org.slf4j:slf4j-api:2.0.12'
integrationTestImplementation 'com.selectivem.collections:special-collections-complete:1.4.0'
integrationTestImplementation "org.opensearch.plugin:lang-painless:${opensearch_version}"
integrationTestImplementation project(path:":opensearch-resource-sharing-spi", configuration: 'shadow')
integrationTestImplementation group: 'org.opensearch', name:'opensearch-resource-sharing-spi', version:"${opensearch_build}"
integrationTestImplementation project(path: ":${rootProject.name}-client", configuration: 'shadow')
}
}

Expand All @@ -589,6 +591,9 @@ sourceSets {

//add new task that runs integration tests
task integrationTest(type: Test) {
filter {
excludeTestsMatching 'org.opensearch.sample.*ResourcePlugin*'
}
doFirst {
// Only run resources tests on resource-test CI environments or locally
if (System.getenv('CI_ENVIRONMENT') != 'resource-test' && System.getenv('CI_ENVIRONMENT') != null) {
Expand Down Expand Up @@ -804,7 +809,6 @@ dependencies {
implementation('com.google.googlejavaformat:google-java-format:1.26.0') {
exclude group: 'com.google.guava'
}

}

jar {
Expand Down
232 changes: 232 additions & 0 deletions client/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,232 @@
# **Resource Sharing Client**

This package provides a **ResourceSharing client** that resource plugins can use to **implement access control** by communicating with the **OpenSearch Security Plugin**.

---

## **Usage**

### **1. Creating a Client Accessor with Singleton Pattern**
To ensure a single instance of the `ResourceSharingNodeClient`, use the **Singleton pattern**:

```java
public class ResourceSharingClientAccessor {
private static ResourceSharingNodeClient INSTANCE;

private ResourceSharingClientAccessor() {}

/**
* Get the resource sharing client instance.
*
* @param nodeClient The OpenSearch NodeClient instance.
* @param settings The OpenSearch settings.
* @param version The node version.s
* @return A singleton instance of ResourceSharingNodeClient.
*/
public static ResourceSharingNodeClient getResourceSharingClient(NodeClient nodeClient, Settings settings, Version version) {
if (INSTANCE == null) {
INSTANCE = new ResourceSharingNodeClient(nodeClient, settings, version);
}
return INSTANCE;
}
}
```

---

### **2. Using the Client in a Transport Action**
The following example demonstrates how to use the **Resource Sharing Client** inside a `TransportAction` to verify **delete permissions** before deleting a resource.

```java
@Inject
public DeleteResourceTransportAction(
Settings settings,
TransportService transportService,
ActionFilters actionFilters,
NodeClient nodeClient
) {
super(DeleteResourceAction.NAME, transportService, actionFilters, DeleteResourceRequest::new);
this.transportService = transportService;
this.nodeClient = nodeClient;
this.settings = settings;
}

@Override
protected void doExecute(Task task, DeleteResourceRequest request, ActionListener<DeleteResourceResponse> listener) {
String resourceId = request.getResourceId();

Version nodeVersion = transportService.getLocalNode().getVersion();
ResourceSharingClient resourceSharingClient = ResourceSharingClientAccessor.getResourceSharingClient(nodeClient, settings, nodeVersion);

resourceSharingClient.verifyResourceAccess(
resourceId,
RESOURCE_INDEX_NAME,
ActionListener.wrap(isAuthorized -> {
if (!isAuthorized) {
listener.onFailure(new OpenSearchStatusException("Current user is not authorized to delete resource: " + resourceId, RestStatus.FORBIDDEN));
return;
}

// Authorization successful, proceed with deletion
ThreadContext threadContext = transportService.getThreadPool().getThreadContext();
try (ThreadContext.StoredContext ignored = threadContext.stashContext()) {
deleteResource(resourceId, ActionListener.wrap(deleteResponse -> {
if (deleteResponse.getResult() == DocWriteResponse.Result.NOT_FOUND) {
listener.onFailure(new ResourceNotFoundException("Resource " + resourceId + " not found."));
} else {
listener.onResponse(new DeleteResourceResponse("Resource " + resourceId + " deleted successfully."));
}
}, exception -> {
log.error("Failed to delete resource: " + resourceId, exception);
listener.onFailure(exception);
}));
}
}, exception -> {
log.error("Failed to verify resource access: " + resourceId, exception);
listener.onFailure(exception);
})
);
}
```

---

## **Available Java APIs**

The **`ResourceSharingClient`** provides **four Java APIs** for **resource access control**, enabling plugins to **verify, share, revoke, and list** shareableResources.

**Package Location:**
[`org.opensearch.security.client.resources.ResourceSharingClient`](../client/src/main/java/org/opensearch/security/client/resources/ResourceSharingClient.java)

---

### **API Usage Examples**
Below are examples demonstrating how to use each API effectively.

---

### **1. `verifyResourceAccess`**
**Checks if the current user has access to a resource**.

#### **Method Signature:**
```java
void verifyResourceAccess(String resourceId, String resourceIndex, ActionListener<Boolean> listener);
```

#### **Example Usage:**
```java
resourceSharingClient.verifyResourceAccess(
"resource-123",
"resource_index",
ActionListener.wrap(isAuthorized -> {
if (isAuthorized) {
System.out.println("User has access to the resource.");
} else {
System.out.println("Access denied.");
}
}, e -> {
System.err.println("Failed to verify access: " + e.getMessage());
})
);
```
> **Use Case:** Before performing operations like **deletion or modifications**, ensure the user has the right permissions.

---

### **2. `shareResource`**
**Grants access to a resource** for specific users, roles, or backend roles.

#### **Method Signature:**
```java
void shareResource(String resourceId, String resourceIndex, SharedWithActionGroup.ActionGroupRecipients recipients, ActionListener<ResourceSharing> listener);
```

#### **Example Usage:**
```java

resourceSharingClient.shareResource(
request.getResourceId(),
RESOURCE_INDEX_NAME,
request.getShareWith(),
ActionListener.wrap(sharing -> {
ShareResourceResponse response = new ShareResourceResponse(sharing.getShareWith());
listener.onResponse(response);
}, listener::onFailure)
);
```
> **Use Case:** Used when an **owner/admin wants to share a resource** with specific users or groups.

---

### **3. `revokeResourceAccess`**
**Removes access permissions** for specified users, roles, or backend roles.

#### **Method Signature:**
```java
void revokeResourceAccess(String resourceId, String resourceIndex, SharedWithActionGroup.ActionGroupRecipients entitiesToRevoke, ActionListener<ResourceSharing> listener);
```

#### **Example Usage:**
```java
resourceSharingClient.revokeResourceAccess(
request.getResourceId(),
RESOURCE_INDEX_NAME,
request.getEntitiesToRevoke(),
ActionListener.wrap(success -> {
RevokeResourceAccessResponse response = new RevokeResourceAccessResponse(success.getShareWith());
listener.onResponse(response);
}, listener::onFailure)
);
```
> **Use Case:** When a user no longer needs access to a **resource**, their permissions can be revoked.

---

### **4. `listAllAccessibleResources`**
**Retrieves all shareableResources the current user has access to.**

#### **Method Signature:**
```java
void listAllAccessibleResources(String resourceIndex, ActionListener<Set<? extends ShareableResource>> listener);
```

#### **Example Usage:**
```java
resourceSharingClient.listAllAccessibleResources(
RESOURCE_INDEX_NAME,
ActionListener.wrap(
resources -> {
listener.onResponse(new GetResourceResponse((Set<SampleResource>) resources));
},
failure -> {
if (failure instanceof OpenSearchStatusException && ((OpenSearchStatusException) failure).status().equals(RestStatus.NOT_IMPLEMENTED)) {
getAllResourcesAction(listener);
return;
}
listener.onFailure(failure);
}
)
);
```
> **Use Case:** Helps a user identify **which shareableResources they can interact with**.

---

## **Conclusion**
These APIs provide essential methods for **fine-grained resource access control**, enabling:

✔ **Verification** of resource access.
✔ **Granting and revoking** access dynamically.
✔ **Retrieval** of all accessible shareableResources.

For further details, refer to the [`ResourceSharingClient` Java class](../client/src/main/java/org/opensearch/security/client/resources/ResourceSharingClient.java).

---

## **License**
This project is licensed under the **Apache 2.0 License**.

---

## **Copyright**
© OpenSearch Contributors.
105 changes: 105 additions & 0 deletions client/build.gradle
Original file line number Diff line number Diff line change
@@ -0,0 +1,105 @@
/*
* Copyright OpenSearch Contributors
* SPDX-License-Identifier: Apache-2.0
*/

plugins {
id 'java'
id 'maven-publish'
id 'io.github.goooler.shadow' version "8.1.7"
}

ext {
opensearch_version = System.getProperty("opensearch.version", "3.0.0-beta1-SNAPSHOT")
isSnapshot = "true" == System.getProperty("build.snapshot", "true")
buildVersionQualifier = System.getProperty("build.version_qualifier", "beta1")

// 2.0.0-rc1-SNAPSHOT -> 2.0.0.0-rc1-SNAPSHOT
version_tokens = opensearch_version.tokenize('-')
opensearch_build = version_tokens[0] + '.0'

if (buildVersionQualifier) {
opensearch_build += "-${buildVersionQualifier}"
}
if (isSnapshot) {
opensearch_build += "-SNAPSHOT"
}
}

repositories {
mavenLocal()
mavenCentral()
maven { url "https://aws.oss.sonatype.org/content/repositories/snapshots" }
}

dependencies {
compileOnly group: 'org.opensearch', name: 'opensearch', version: "${opensearch_version}"
compileOnly group: 'org.opensearch', name: 'opensearch-resource-sharing-spi', version: "${opensearch_build}"
compileOnly project(":")
}

shadowJar {
archiveClassifier.set(null)
}

java {
sourceCompatibility = JavaVersion.VERSION_21
targetCompatibility = JavaVersion.VERSION_21
}

task sourcesJar(type: Jar) {
archiveClassifier.set 'sources'
from sourceSets.main.allJava
}

task javadocJar(type: Jar) {
archiveClassifier.set 'javadoc'
from tasks.javadoc
}

publishing {
publications {
shadow(MavenPublication) { publication ->
project.shadow.component(publication)
artifact sourcesJar
artifact javadocJar
pom {
name.set("OpenSearch Security Client")
packaging = "jar"
description.set("OpenSearch Security Client")
url.set("https://github.com/opensearch-project/security")
licenses {
license {
name.set("The Apache License, Version 2.0")
url.set("http://www.apache.org/licenses/LICENSE-2.0.txt")
}
}
scm {
connection.set("scm:git@github.com:opensearch-project/security.git")
developerConnection.set("scm:git@github.com:opensearch-project/security.git")
url.set("https://github.com/opensearch-project/security.git")
}
developers {
developer {
name.set("OpenSearch Contributors")
url.set("https://github.com/opensearch-project")
}
}
}
}
}
repositories {
maven {
name = "Snapshots"
url = "https://aws.oss.sonatype.org/content/repositories/snapshots"
credentials {
username "$System.env.SONATYPE_USERNAME"
password "$System.env.SONATYPE_PASSWORD"
}
}
maven {
name = 'staging'
url = "${rootProject.buildDir}/local-staging-repo"
}
}
}
Loading
Loading