From 51d33036316c1b23587b652285d000b01991ae6f Mon Sep 17 00:00:00 2001 From: Darshit Chanpura Date: Sun, 7 Dec 2025 18:17:24 -0800 Subject: [PATCH 1/7] Resource Sharing technical deep dive Signed-off-by: Darshit Chanpura --- ...14-Resource-Sharing-Technical-Deep-Dive.md | 311 ++++++++++++++++++ 1 file changed, 311 insertions(+) create mode 100644 _posts/2025-12-14-Resource-Sharing-Technical-Deep-Dive.md diff --git a/_posts/2025-12-14-Resource-Sharing-Technical-Deep-Dive.md b/_posts/2025-12-14-Resource-Sharing-Technical-Deep-Dive.md new file mode 100644 index 0000000000..3bf414a6d0 --- /dev/null +++ b/_posts/2025-12-14-Resource-Sharing-Technical-Deep-Dive.md @@ -0,0 +1,311 @@ +--- +layout: post +title: "Under the hood: Designing resource-level access control in OpenSearch" +authors: + - dchanp + - cwperks +date: 2025-12-01 +categories: + - technical-post +meta_keywords: security, resource sharing, access control, distributed systems, extensibility, plugins, authorization +meta_description: "A deep dive into the architecture, design decisions, and migration path behind OpenSearch’s Resource Sharing and Access Control framework." +tags: + - security + - access control + - resource sharing + - dashboards + - opensearch 3.3 +--- + +In [Part 1]({{site.baseurl}}/blog/introducing-resource-sharing), we introduced **resource sharing and access control** as a new way to collaborate on plugin-defined resources such as anomaly detectors and machine learning (ML) models. + +This post explores the engineering decisions behind that feature: + +* Why the legacy `filter_by_backend_roles` model wasn’t enough +* How we designed a **resource-centric** authorization model +* How plugins integrate using the new **Security SPI** +* How access checks work at query time +* How to migrate safely from legacy behavior + +If you’re building or operating plugins in OpenSearch, this is the post you probably care about. + +--- + +## From backend roles to resource owners + +Before resource sharing, most OpenSearch plugins used a simple pattern: + +* Each resource (detector, model, report, and so on) stored **identity metadata** (creator, backend roles). +* Visibility was controlled by checking for **backend role overlap** between creator and viewer. +* In Anomaly Detection, this was controlled by `plugins.anomaly_detection.filter_by_backend_roles`. In ML Commons, by `plugins.ml_commons.model_access_control_enabled`. + +This approach worked for basic multi-tenancy but had significant limitations. + +### Shortcomings of `filter_by_backend_roles` + +1. **Implicit, role-coupled sharing** + + If two users shared a backend role, they could see each other’s resources. This meant: + + * No way for the owner to say “share with Alice, but not with Bob” if both users shared a role. + * Removing access required changes to role mappings, not the resource itself. + +2. **Overly broad cluster privileges** + + Because access was controlled at the **role** level, not the **resource** level: + + * Roles needed powerful cluster permissions just so users could operate on their own resources. + * It was difficult to grant “read-only access to this one thing” without broader privileges. + +3. **Distributed, plugin-specific metadata** + + Each plugin implemented its own access logic: + + * Different JSON structures for “owner” and “shared with” + * Different user experience (UX) patterns in Dashboards + * No central place to audit “who can see what” + +The new framework addresses all these issues. + +--- + +## Design goals + +Our goals in designing resource sharing focused on four principles: + +1. **Resource-centric security** + Authorization should be based on who owns a resource and who it is shared with—not accidental backend role overlaps. + +2. **Centralized, reusable logic** + * One shared framework inside the Security plugin + * Plugins declare what is shareable and which actions exist + * Security handles how access is evaluated + +3. **Minimal changes to plugin APIs** + Plugins should: + * Continue exposing their existing REST APIs (such as `/detectors`, `/models`, `/reports`) + * Delegate authorization to the Security framework + * Avoid duplicating “get current user” boilerplate + +4. **Safe migration** + We needed: + * A way to import legacy sharing data into the new framework + * Feature flags and per-type rollout + * A reversible, observable migration process + +--- + +## High-level architecture + +At a high level, resource sharing divides responsibilities into three areas: + +* **Resource plugins**: own functional resources (detectors, models, reports, dashboards) +* **Security plugin**: manages the sharing model and access evaluation +* **System indices**: store both resource data and corresponding sharing metadata + +```mermaid +flowchart TD + U[User / OpenSearch Dashboards] -->|Create/Read/Update/Delete| PL[Resource Plugin] + + subgraph Security[Security Plugin] + API[Security REST Endpoints - Dashboards Share & List-Accessible] + RAE[ResourceAccessEvaluator - automatic evaluation] + MAP[Index - SharingIndex Mapping] + end + + subgraph Data[System Indices - Per Plugin] + RIDX1[(Resource Index A)] + RSIDX1[(Sharing Index A)] + RIDX2[(Resource Index B)] + RSIDX2[(Sharing Index B)] + end + + PL --> RIDX1 + PL --> RIDX2 + +%% Auto-eval for resource requests + PL --> RAE + RAE --> MAP + MAP -->|resolve| RSIDX1 + MAP -->|resolve| RSIDX2 + +%% Dashboards Access Mgmt flows + U -->|Share UI GET/PUT/PATCH| API + API --> MAP + MAP --> RSIDX1 & RSIDX2 + +```` + +--- + +## The resource-sharing data model + +The framework introduces a dedicated sharing document per resource, stored in a Security-managed index. + +### Sharing document structure + +```json +{ + "resource_id": "model-group-123", + "created_by": { + "user": "bob", + "tenant": "analytics-tenant" + }, + "share_with": { + "sample_read_only": { + "users": ["user1", "user2"], + "roles": ["viewer_role"], + "backend_roles": ["data_analyst"] + }, + "sample_read_write": { + "users": ["admin_user"], + "roles": ["editor_role"] + } + } +} +``` + +### Access levels as action groups + +```yaml +resource_types: + sample-resource: + sample_read_only: + allowed_actions: + - "cluster:admin/sample-resource-plugin/get" + sample_read_write: + allowed_actions: + - "cluster:admin/sample-resource-plugin/*" + sample_full_access: + allowed_actions: + - "cluster:admin/sample-resource-plugin/*" + - "cluster:admin/security/resource/share" +``` + +--- + +## Query-time evaluation + +1. **Implicit filtering using `all_shared_principals`** + +```json +{ + "name": "sharedDashboard", + "all_shared_principals": [ + "user:alice", + "role:analytics_team" + ] +} +``` + +2. **Explicit checks using `ResourceSharingClient`** + +```java +verifyAccess(resourceId, resourceIndex, action, listener); +getAccessibleResourceIds(resourceIndex, listener); +isFeatureEnabledForType(resourceType); +``` + +--- + +## Developer integration + +1. **Add SPI dependency** in `build.gradle` +2. **Implement `ResourceSharingExtension`** +3. **Define action groups in `resource-action-groups.yml`** +4. **Use plugin client and system indices** + +--- + +## Cluster controls + +```yaml +plugins.security.experimental.resource_sharing.enabled: true +plugins.security.experimental.resource_sharing.protected_types: ["anomaly-detector", "ml-model"] +``` + +```curl +PUT _cluster/settings +{ + "persistent": { + "plugins.security.experimental.resource_sharing.enabled": true, + "plugins.security.experimental.resource_sharing.protected_types": [ + "anomaly-detector", + "ml-model" + ] + } +} +``` + +--- + +## Migration from legacy behavior + +```http +POST /_plugins/_security/api/resources/migrate +``` + +Example: + +```json +{ + "source_index": ".sample_resource", + "username_path": "/owner", + "backend_roles_path": "/backend_roles", + "default_owner": "some_user", + "default_access_level": { + "sample-resource": "read_only" + } +} +``` + +```mermaid +sequenceDiagram + participant Admin as Cluster Admin + participant MAPI as Migration API + participant Core as Security Plugin (Migration) + participant Src as Resource Index + participant RSIDX as Sharing Index + + Admin->>MAPI: POST /resources/migrate {...} + MAPI->>Core: Validate request & resolve sharing index + Core->>Src: Scroll existing resource documents + + loop each document + Core->>RSIDX: Upsert sharing doc\n{resource_id, created_by.user,\n share_with[default].backend_roles} + end + + RSIDX-->>Core: Migration stats + Core-->>Admin: 200 OK (summary + skipped IDs) +``` + +--- + +## Dashboards integration + +Dashboards uses: + +* `PUT /_plugins/_security/api/resource/share` +* `POST /_plugins/_security/api/resource/share` +* `GET /_plugins/_security/api/resource/list` +* `GET /_plugins/_security/api/resource/types` + +To enable a consistent UX for resource sharing. + +--- + +## Putting it all together + +This framework moves OpenSearch from: + +* **Role-centric visibility** → based on backend role overlap + to: +* **Resource-centric control** → based on ownership and explicit sharing + +If you’re building a plugin and want to adopt resource sharing: + +1. Implement `ResourceSharingExtension` +2. Define your resource action groups +3. Enable the feature for your resource type in a test cluster + +You’ll inherit a full, centralized sharing model—automatically. From c0549be9b6640eb1b1ebe878e8cfc7014db3662f Mon Sep 17 00:00:00 2001 From: Darshit Chanpura Date: Sun, 7 Dec 2025 18:50:11 -0800 Subject: [PATCH 2/7] Adds a bit more technical material Signed-off-by: Darshit Chanpura --- ...14-Resource-Sharing-Technical-Deep-Dive.md | 593 +++++++++++++++--- 1 file changed, 504 insertions(+), 89 deletions(-) diff --git a/_posts/2025-12-14-Resource-Sharing-Technical-Deep-Dive.md b/_posts/2025-12-14-Resource-Sharing-Technical-Deep-Dive.md index 3bf414a6d0..a87a607d25 100644 --- a/_posts/2025-12-14-Resource-Sharing-Technical-Deep-Dive.md +++ b/_posts/2025-12-14-Resource-Sharing-Technical-Deep-Dive.md @@ -4,7 +4,7 @@ title: "Under the hood: Designing resource-level access control in OpenSearch" authors: - dchanp - cwperks -date: 2025-12-01 +date: 2025-12-14 categories: - technical-post meta_keywords: security, resource sharing, access control, distributed systems, extensibility, plugins, authorization @@ -17,17 +17,18 @@ tags: - opensearch 3.3 --- -In [Part 1]({{site.baseurl}}/blog/introducing-resource-sharing), we introduced **resource sharing and access control** as a new way to collaborate on plugin-defined resources such as anomaly detectors and machine learning (ML) models. +In [Part 1]({{site.baseurl}}/blog/introducing-resource-sharing), we introduced resource sharing and access control as a new way to collaborate on plugin-defined resources such as anomaly detectors and machine learning (ML) models. -This post explores the engineering decisions behind that feature: +This post looks under the hood at how that feature is built and how you can adopt it in your own plugins: -* Why the legacy `filter_by_backend_roles` model wasn’t enough -* How we designed a **resource-centric** authorization model -* How plugins integrate using the new **Security SPI** +* Why the legacy `filter_by_backend_roles` model was not enough +* How we designed a resource-centric authorization model +* How plugins integrate using the new Security SPI * How access checks work at query time * How to migrate safely from legacy behavior +* A practical onboarding path for plugin developers -If you’re building or operating plugins in OpenSearch, this is the post you probably care about. +If you are building or operating plugins on OpenSearch, this is the post you probably care about. --- @@ -35,120 +36,139 @@ If you’re building or operating plugins in OpenSearch, this is the post you pr Before resource sharing, most OpenSearch plugins used a simple pattern: -* Each resource (detector, model, report, and so on) stored **identity metadata** (creator, backend roles). -* Visibility was controlled by checking for **backend role overlap** between creator and viewer. +* Each resource (detector, model, report, and so on) stored identity metadata (creator, backend roles). +* Visibility was controlled by checking for backend role overlap between creator and viewer. * In Anomaly Detection, this was controlled by `plugins.anomaly_detection.filter_by_backend_roles`. In ML Commons, by `plugins.ml_commons.model_access_control_enabled`. This approach worked for basic multi-tenancy but had significant limitations. ### Shortcomings of `filter_by_backend_roles` -1. **Implicit, role-coupled sharing** +1. Implicit, role-coupled sharing - If two users shared a backend role, they could see each other’s resources. This meant: + If two users shared a backend role, they could see each other’s resources. That meant: - * No way for the owner to say “share with Alice, but not with Bob” if both users shared a role. - * Removing access required changes to role mappings, not the resource itself. + * No way for the owner to say “share with Alice, but not with Bob” if both users shared a role. + * Removing access required role-mapping changes, not a change on the resource itself. -2. **Overly broad cluster privileges** +2. Overly broad cluster privileges - Because access was controlled at the **role** level, not the **resource** level: + Because access was controlled at the role level, not the resource level: - * Roles needed powerful cluster permissions just so users could operate on their own resources. - * It was difficult to grant “read-only access to this one thing” without broader privileges. + * Roles needed powerful cluster permissions just so users could operate on their own resources. + * It was difficult to grant “read-only access to this one detector” without granting broader capabilities. -3. **Distributed, plugin-specific metadata** +3. Distributed, plugin-specific metadata Each plugin implemented its own access logic: - * Different JSON structures for “owner” and “shared with” - * Different user experience (UX) patterns in Dashboards - * No central place to audit “who can see what” + * Different JSON shapes for “owner” and “shared with” + * Different user experience patterns in OpenSearch Dashboards + * No central place to audit “who can see what” -The new framework addresses all these issues. +The new framework is designed to fix all of this. --- ## Design goals -Our goals in designing resource sharing focused on four principles: +When we started designing resource sharing, we focused on a few core principles. -1. **Resource-centric security** - Authorization should be based on who owns a resource and who it is shared with—not accidental backend role overlaps. +1. Resource-centric security -2. **Centralized, reusable logic** - * One shared framework inside the Security plugin - * Plugins declare what is shareable and which actions exist - * Security handles how access is evaluated + Authorization should be driven by who owns this resource and who it is shared with, not by backend-role overlaps that happen to exist. + +2. Centralized, reusable logic + + * One framework inside the Security plugin + * Plugins declare what is shareable and which actions exist + * Security handles how access is evaluated + +3. Minimal changes to plugin APIs -3. **Minimal changes to plugin APIs** Plugins should: - * Continue exposing their existing REST APIs (such as `/detectors`, `/models`, `/reports`) - * Delegate authorization to the Security framework - * Avoid duplicating “get current user” boilerplate -4. **Safe migration** - We needed: - * A way to import legacy sharing data into the new framework - * Feature flags and per-type rollout - * A reversible, observable migration process + * Keep exposing their existing REST APIs (such as `/detectors`, `/models`, `/reports`) + * Delegate authorization to the Security framework + * Avoid copying and pasting “get current user” boilerplate + +4. Safe migration + + Existing clusters cannot lose access patterns overnight. We needed: + + * A way to import legacy sharing data into the new framework + * Feature flags and per-type rollout + * A reversible, observable migration step --- ## High-level architecture -At a high level, resource sharing divides responsibilities into three areas: +At a high level, resource sharing splits responsibility across three parts: -* **Resource plugins**: own functional resources (detectors, models, reports, dashboards) -* **Security plugin**: manages the sharing model and access evaluation -* **System indices**: store both resource data and corresponding sharing metadata +* Resource plugins: own the functional resource (detectors, models, reports, dashboards) +* Security plugin: owns the sharing model and access evaluation +* System indices: store resources and the corresponding sharing metadata ```mermaid flowchart TD - U[User / OpenSearch Dashboards] -->|Create/Read/Update/Delete| PL[Resource Plugin] + U[User / OpenSearch Dashboards] -->|Create / Read / Update / Delete| PL[Resource Plugin] subgraph Security[Security Plugin] - API[Security REST Endpoints - Dashboards Share & List-Accessible] - RAE[ResourceAccessEvaluator - automatic evaluation] - MAP[Index - SharingIndex Mapping] + API[Security REST APIs - Resource share & list] + RAE[ResourceAccessEvaluator - query-time authorization] + MAP[Index Mapping & Routing - resource -> sharing index] end - subgraph Data[System Indices - Per Plugin] - RIDX1[(Resource Index A)] - RSIDX1[(Sharing Index A)] - RIDX2[(Resource Index B)] - RSIDX2[(Sharing Index B)] + subgraph Data[System Indices - per plugin] + RIDX1[(Resource Index A)] + RSIDX1[(Sharing Index A)] + RIDX2[(Resource Index B)] + RSIDX2[(Sharing Index B)] end PL --> RIDX1 PL --> RIDX2 -%% Auto-eval for resource requests + %% Query-time evaluation PL --> RAE RAE --> MAP MAP -->|resolve| RSIDX1 MAP -->|resolve| RSIDX2 -%% Dashboards Access Mgmt flows - U -->|Share UI GET/PUT/PATCH| API + %% Dashboards access management flows + U -->|Share UI - GET/PUT/POST/PATCH| API API --> MAP MAP --> RSIDX1 & RSIDX2 +``` + +Key ideas: + +* Each resource index (for example, `.opendistro-anomaly-detectors`) is paired with a sharing index owned by Security. +* Plugins consult the Resource Sharing SPI instead of doing their own access checks. +* OpenSearch Dashboards uses Security REST endpoints to share, list, and manage resources. -```` +The feature is introduced in the Security plugin as an experimental capability, behind the flag: + +```yaml +plugins.security.experimental.resource_sharing.enabled: true +``` --- ## The resource-sharing data model -The framework introduces a dedicated sharing document per resource, stored in a Security-managed index. +Resource sharing introduces a dedicated sharing document per resource, stored in a Security-managed index. + +### Sharing document -### Sharing document structure +For each resource, Security stores a document like: ```json { "resource_id": "model-group-123", "created_by": { - "user": "bob", + "user": "darshit", "tenant": "analytics-tenant" }, "share_with": { @@ -159,71 +179,408 @@ The framework introduces a dedicated sharing document per resource, stored in a }, "sample_read_write": { "users": ["admin_user"], - "roles": ["editor_role"] + "roles": ["editor_role"], + "backend_roles": ["content_manager"] } } } ``` +* `resource_id` – Unique ID within the plugin’s resource index. +* `created_by` – Logical owner (user and, optionally, tenant). +* `share_with` – Map of access levels (action groups) to recipients. + +Each access level under `share_with` defines three scopes: + +* `users` – usernames with this access level +* `roles` – OpenSearch roles with this access level +* `backend_roles` – backend roles with this access level + ### Access levels as action groups +In the plugin, access levels are defined as resource action groups in `resource-action-groups.yml`: + ```yaml resource_types: sample-resource: sample_read_only: allowed_actions: - "cluster:admin/sample-resource-plugin/get" + sample_read_write: allowed_actions: - "cluster:admin/sample-resource-plugin/*" + sample_full_access: allowed_actions: - "cluster:admin/sample-resource-plugin/*" - "cluster:admin/security/resource/share" ``` +Security reads this file at startup and uses it to answer questions like, “Does this user have `sample_read_write` on resource X?” + +### Public vs private vs restricted + +The `share_with` structure lets you express common patterns. + +* Private (default) + +```json +{ + "share_with": {} +} +``` + +Visible only to the owner and super-admins. + +* Public + +```json +{ + "share_with": { + "default": { + "users": ["*"] + } + } +} +``` + +Any authenticated user can access the resource at the `default` access level. + +* Restricted + +```json +{ + "share_with": { + "default": { + "users": ["alice"], + "roles": ["analytics_viewer"], + "backend_roles": ["fraud-team"] + } + } +} +``` + +Only the listed principals can access this resource via that access level. + --- -## Query-time evaluation +## Query-time evaluation: how results are filtered + +There are two complementary pieces to runtime evaluation: + +1. Implicit filtering for list/search APIs +2. Explicit checks for point operations or custom flows + +### 1. Implicit filtering through `all_shared_principals` -1. **Implicit filtering using `all_shared_principals`** +When a plugin lists resources, we want it to not care about who the current user is. Instead: + +1. The plugin exposes a list or search API (for example, `GET /_plugins/_reports/definitions`). +2. Inside the handler, the plugin issues a search against its system index using a plugin client (a system-level subject). +3. The Security plugin attaches a Document-level security (DLS) query behind the scenes. +4. The DLS query checks an `all_shared_principals` field on each resource document. + +A resource document might look like: ```json { "name": "sharedDashboard", + "description": "Shared with multiple principals", + "type": "dashboard", + "created_at": "2025-09-02T14:30:00Z", "all_shared_principals": [ - "user:alice", - "role:analytics_team" + "user:resource_sharing_test_user_alice", + "user:resource_sharing_test_user_bob", + "role:analytics_team", + "role:all_access", + "role:auditor" ] } ``` -2. **Explicit checks using `ResourceSharingClient`** +If the authenticated user is: + +* `username: resource_sharing_test_user_alice` +* with role `analytics_team` + +then Security limits the result set to documents where `all_shared_principals` contains: + +* `user:resource_sharing_test_user_alice` +* or `role:analytics_team` +* or a wildcard such as `user:*` for public visibility + +To make this work in a future-proof way: + +* Plugins declare themselves as `IdentityAwarePlugin` so they can use their plugin subject to access system indices. +* The plugin uses its plugin client instead of stashing thread context manually. +* The Security plugin injects the DLS filter automatically. + +Here is a snippet from anomaly-detection plugin for usage of PluginClient to perform search: + +```java +public void search(SearchRequest request, String resourceType, ActionListener actionListener) { + User user = ParseUtils.getUserContext(client); + boolean shouldUseResourceAuthz = ParseUtils.shouldUseResourceAuthz(resourceType); + ActionListener listener = wrapRestActionListener(actionListener, CommonMessages.FAIL_TO_SEARCH); + try (ThreadContext.StoredContext context = client.threadPool().getThreadContext().stashContext()) { + if (pluginClient != null && shouldUseResourceAuthz) { + // request will be auto-filtered in security plugin + pluginClient.search(request, actionListener); + } else { + validateRole(request, user, listener); + } + } catch (Exception e) { + logger.error(e); + listener.onFailure(e); + } +} +``` + +### 2. Explicit checks using `ResourceSharingClient` + +For operations that cannot rely solely on DLS (for example, get-by-id, update, delete, or non-index-backed resources), plugins call the `ResourceSharingClient` from the SPI. + +The SPI provides three main methods: ```java -verifyAccess(resourceId, resourceIndex, action, listener); -getAccessibleResourceIds(resourceIndex, listener); -isFeatureEnabledForType(resourceType); +void verifyAccess(String resourceId, + String resourceIndex, + String action, + ActionListener listener); + +void getAccessibleResourceIds(String resourceIndex, + ActionListener> listener); + +boolean isFeatureEnabledForType(String resourceType); ``` +#### `isFeatureEnabledForType`: guard rails + +Use this as your top-level guard: + +```java +public static boolean shouldUseResourceAuthz(String resourceType) { + var client = ResourceSharingClientAccessor.getInstance().getResourceSharingClient(); + return client != null && client.isFeatureEnabledForType(resourceType); +} +``` + +If this returns `false`, you can safely fall back to your legacy behavior (for example, `filter_by_backend_roles`). + +#### `verifyAccess`: point checks + +Use `verifyAccess` when you want to enforce access on one specific resource and a specific action. + +Typical examples: + +* `GET /_plugins/_my_plugin/resources/{id}` +* `DELETE /_plugins/_my_plugin/resources/{id}` +* Custom operations such as `/_plugins/_my_plugin/resources/{id}/_search` + +Pseudocode: + +```java +public void getResourceById(String id, RestChannel channel) { + if (!shouldUseResourceAuthz("sample-resource")) { + // Legacy path + getResourceLegacy(id, channel); + return; + } + + var client = ResourceSharingClientAccessor.getInstance().getResourceSharingClient(); + // if this is a protected resource request verifyAccess is not required as the resource access will be evaluated directly + // call verify access only if resource is protected via hierarchy + client.verifyAccess( + id, + ".sample_resource", + "cluster:admin/sample-resource-plugin/get", + ActionListener.wrap( + allowed -> { + if (Boolean.FALSE.equals(allowed)) { + channel.sendResponse(forbidden(id)); + return; + } + // Now safe to read from the index + fetchAndReturnResource(id, channel); + }, + e -> channel.sendResponse(toErrorResponse(e)) + ) + ); +} +``` + +If the Security plugin is disabled, the SPI implementation safely becomes a no-op and treats the request as allowed, so your plugin does not have to special-case that. + +#### `getAccessibleResourceIds`: cross-index flows + +When you need to filter by ID but cannot rely on DLS, you can retrieve the set of resource IDs that the current user can access and apply your own filters. + +For example: + +```java +public void getResources(RestChannel channel) { + client.getAccessibleResourceIds(".sample_resource", ActionListener.wrap( + accessibleIds -> { + // Add a terms filter on the resource ID + SearchSourceBuilder source = new SearchSourceBuilder() + .query(QueryBuilders.termsQuery("_id", accessibleIds)); + // ... + }, + e -> channel.sendResponse(toErrorResponse(e)) + )); +} +``` + +This is a good fit for cases where: + +* You need a custom query that does not go through the standard DLS filter path, or +* You are composing results across multiple indices and want to intersect with accessible IDs. + --- -## Developer integration +## Developer integration: becoming a “resource plugin” + +To opt in, a plugin implements the Resource Sharing SPI and follows a few conventions. + +### 1. Add SPI dependency and extend the Security plugin + +In `build.gradle`: + +```gradle +configurations { + opensearchPlugin +} + +dependencies { + compileOnly group: 'org.opensearch', name: 'opensearch-security-spi', version: "${opensearch_build}" + opensearchPlugin "org.opensearch.plugin:opensearch-security:${opensearch_build}@zip" +} + +opensearchplugin { + name '' + description '' + classname '' + extendedPlugins = ['opensearch-security;optional=true'] +} +``` + +### 2. Implement `ResourceSharingExtension` and register it + +Create a class that implements `org.opensearch.security.spi.ResourceSharingExtension`. This class tells Security: + +* Which resource indices you own +* Which resource types are shareable +* How those types map to your action groups + +Then register it using Java’s SPI mechanism: + +```text +src/main/resources/META-INF/services/org.opensearch.security.spi.ResourceSharingExtension +``` + +The file must contain exactly one line with the fully qualified class name, for example: + +```text +org.opensearch.sample.SampleResourceSharingExtension +``` + +### 3. Provide `resource-action-groups.yml` + +Define your resource types and action groups: + +```yaml +resource_types: + sample-resource: + sample_read_only: + allowed_actions: + - "cluster:admin/sample-resource-plugin/get" + + sample_read_write: + allowed_actions: + - "cluster:admin/sample-resource-plugin/*" + + sample_full_access: + allowed_actions: + - "cluster:admin/sample-resource-plugin/*" + - "cluster:admin/security/resource/share" +``` + +The `resource_types` keys must match the types you declare in `ResourceSharingExtension`. + +### 4. Use system indices and a plugin client + +* Store resources in system indices and keep system index protection enabled. +* Use a plugin client when reading or writing those indices so Security can apply DLS. +* Avoid accessing system indices using ad-hoc `ThreadContext.stashContext` calls; use the identity-aware mechanisms instead. + +### 5. Wire in the `ResourceSharingClient` -1. **Add SPI dependency** in `build.gradle` -2. **Implement `ResourceSharingExtension`** -3. **Define action groups in `resource-action-groups.yml`** -4. **Use plugin client and system indices** +Implement a small accessor to get the SPI client (typically a singleton wrapper): + +```java +public class ResourceSharingClientAccessor { + private static final ResourceSharingClientAccessor INSTANCE = new ResourceSharingClientAccessor(); + + private volatile ResourceSharingClient client; + + public static ResourceSharingClientAccessor getInstance() { + return INSTANCE; + } + + public void setResourceSharingClient(ResourceSharingClient client) { + this.client = client; + } + + public ResourceSharingClient getResourceSharingClient() { + return client; + } +} +``` + +During plugin initialization, Security injects the `ResourceSharingClient` for you. Your handlers then call: + +* `isFeatureEnabledForType` to decide whether to use resource-level auth +* `verifyAccess` to guard point operations +* `getAccessibleResourceIds` for custom flows + +### 6. Test with resource sharing enabled + +In your integration test cluster setup: + +```gradle +integTest { + systemProperty "resource_sharing.enabled", System.getProperty("resource_sharing.enabled") +} + +testCluster { + nodeSetting "plugins.security.system_indices.enabled", "true" + if (System.getProperty("resource_sharing.enabled") == "true") { + nodeSetting "plugins.security.experimental.resource_sharing.enabled", "true" + nodeSetting "plugins.security.experimental.resource_sharing.protected_types", + "[\"sample-resource\"]" + } +} +``` + +This lets you run tests with resource sharing turned on and verify access behavior. --- -## Cluster controls +## Cluster controls: feature flags and protected types + +Cluster admins control rollout using two settings: ```yaml plugins.security.experimental.resource_sharing.enabled: true -plugins.security.experimental.resource_sharing.protected_types: ["anomaly-detector", "ml-model"] +plugins.security.experimental.resource_sharing.protected_types: ["anomaly-detector", "forecaster", "ml-model"] ``` +* `enabled` – Master feature flag, disabled by default. +* `protected_types` – List of resource types that should use resource-level auth. + +From OpenSearch 3.4 onward, these can be updated dynamically: + ```curl PUT _cluster/settings { @@ -231,34 +588,57 @@ PUT _cluster/settings "plugins.security.experimental.resource_sharing.enabled": true, "plugins.security.experimental.resource_sharing.protected_types": [ "anomaly-detector", + "forecaster", "ml-model" ] } } ``` +This lets you: + +* Enable the framework globally +* Opt in specific resource types gradually +* Roll back by clearing the protected types list + --- -## Migration from legacy behavior +## Migration: from legacy metadata to shared resources + +Existing clusters already have detectors, models, and other resources with baked-in backend-role–based access. + +To avoid breaking those, the Security plugin exposes a Migration API: ```http POST /_plugins/_security/api/resources/migrate ``` +You provide: + +* `source_index` – Where the existing resources live +* `username_path` – JSON pointer to the owner field in each document +* `backend_roles_path` – JSON pointer to the backend roles array +* `default_owner` – Fallback when ownership cannot be inferred +* `default_access_level` – Mapping from resource type to default action group + Example: -```json +```curl +POST /_plugins/_security/api/resources/migrate { "source_index": ".sample_resource", "username_path": "/owner", "backend_roles_path": "/backend_roles", "default_owner": "some_user", "default_access_level": { - "sample-resource": "read_only" + "sample-resource": "read_only", + "sample-resource-group": "read-only-group" } } ``` +The migration flow looks like this: + ```mermaid sequenceDiagram participant Admin as Cluster Admin @@ -279,33 +659,68 @@ sequenceDiagram Core-->>Admin: 200 OK (summary + skipped IDs) ``` +The response summary includes: + +* How many resources were migrated +* How many were skipped (for missing type or owner) +* Which resources ended up using `default_owner` + +Only REST admins or super-admin users can run this API. + --- -## Dashboards integration +## Dashboards: share, list, manage -Dashboards uses: +Once the framework is enabled and plugins are onboarded, OpenSearch Dashboards builds on top of the Security REST APIs: * `PUT /_plugins/_security/api/resource/share` +* `PATCH /_plugins/_security/api/resource/share` * `POST /_plugins/_security/api/resource/share` +* `GET /_plugins/_security/api/resource/share` * `GET /_plugins/_security/api/resource/list` * `GET /_plugins/_security/api/resource/types` -To enable a consistent UX for resource sharing. +This powers UI experiences like: + +* “Share this detector with these users and roles.” +* “Show me resources I can access, and whether I can reshare them.” +* “List all resource types and their access levels.” + +The Security plugin centralizes the logic; each feature plugin focuses on its own domain (detectors, models, dashboards, reports). --- ## Putting it all together -This framework moves OpenSearch from: +The Resource Sharing and Access Control framework moves OpenSearch from: + +* Role-centric visibility + “If we share a backend role, we see each other’s stuff.” + +to: + +* Resource-centric control + “This detector is owned by X, shared with Y, under access level Z.” + +For operators, this means: + +* Clearer auditability of who can access what +* Safer, more incremental rollouts using feature flags and protected types +* A single, consistent sharing experience in OpenSearch Dashboards + +For plugin authors, it means: -* **Role-centric visibility** → based on backend role overlap - to: -* **Resource-centric control** → based on ownership and explicit sharing +* Less boilerplate access-control code +* A standard SPI to plug into +* Automatic DLS-based filtering for list and search APIs +* A migration path from legacy `filter_by_backend_roles` and plugin-specific metadata -If you’re building a plugin and want to adopt resource sharing: +If you are building a plugin and want to adopt resource sharing, a good starting path is: -1. Implement `ResourceSharingExtension` -2. Define your resource action groups -3. Enable the feature for your resource type in a test cluster +1. Implement `ResourceSharingExtension` and register your plugin as a resource plugin. +2. Define your resource action groups in `resource-action-groups.yml`. +3. Mark your resource indices as system indices and use a plugin client for access. +4. Use `isFeatureEnabledForType` and `verifyAccess` in your handlers. +5. Enable the feature for your resource type in a test cluster and iterate. -You’ll inherit a full, centralized sharing model—automatically. +From there, your plugin can inherit a full, centralized sharing model with consistent behavior across the OpenSearch ecosystem. From 7fb5849fe86d30bbe43e0d47db1631b1a86b70d1 Mon Sep 17 00:00:00 2001 From: Darshit Chanpura Date: Sun, 7 Dec 2025 19:01:24 -0800 Subject: [PATCH 3/7] Adds flow-framework resource-types Signed-off-by: Darshit Chanpura --- _posts/2025-12-14-Resource-Sharing-Technical-Deep-Dive.md | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/_posts/2025-12-14-Resource-Sharing-Technical-Deep-Dive.md b/_posts/2025-12-14-Resource-Sharing-Technical-Deep-Dive.md index a87a607d25..92e4cc25c6 100644 --- a/_posts/2025-12-14-Resource-Sharing-Technical-Deep-Dive.md +++ b/_posts/2025-12-14-Resource-Sharing-Technical-Deep-Dive.md @@ -573,7 +573,7 @@ Cluster admins control rollout using two settings: ```yaml plugins.security.experimental.resource_sharing.enabled: true -plugins.security.experimental.resource_sharing.protected_types: ["anomaly-detector", "forecaster", "ml-model"] +plugins.security.experimental.resource_sharing.protected_types: ["anomaly-detector", "forecaster", "ml-model", "workflow", "workflow_state"] ``` * `enabled` – Master feature flag, disabled by default. @@ -589,7 +589,9 @@ PUT _cluster/settings "plugins.security.experimental.resource_sharing.protected_types": [ "anomaly-detector", "forecaster", - "ml-model" + "ml-model", + "workflow", + "workflow_state" ] } } From d3e47ddfeb050f576fbac598c865bffbb7dcb395 Mon Sep 17 00:00:00 2001 From: Darshit Chanpura Date: Sun, 7 Dec 2025 22:20:32 -0800 Subject: [PATCH 4/7] Corrects path Signed-off-by: Darshit Chanpura --- _posts/2025-12-14-Resource-Sharing-Technical-Deep-Dive.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/_posts/2025-12-14-Resource-Sharing-Technical-Deep-Dive.md b/_posts/2025-12-14-Resource-Sharing-Technical-Deep-Dive.md index 92e4cc25c6..819a52b5e5 100644 --- a/_posts/2025-12-14-Resource-Sharing-Technical-Deep-Dive.md +++ b/_posts/2025-12-14-Resource-Sharing-Technical-Deep-Dive.md @@ -467,7 +467,7 @@ opensearchplugin { ### 2. Implement `ResourceSharingExtension` and register it -Create a class that implements `org.opensearch.security.spi.ResourceSharingExtension`. This class tells Security: +Create a class that implements `org.opensearch.security.spi.resources.ResourceSharingExtension`. This class tells Security: * Which resource indices you own * Which resource types are shareable From 5c64299f8f6f3933d61938f00f18bea1c583750b Mon Sep 17 00:00:00 2001 From: Fanit Kolchina Date: Mon, 15 Dec 2025 09:42:36 -0500 Subject: [PATCH 5/7] Doc review Signed-off-by: Fanit Kolchina --- ...14-Resource-Sharing-Technical-Deep-Dive.md | 316 +++++++++--------- 1 file changed, 163 insertions(+), 153 deletions(-) diff --git a/_posts/2025-12-14-Resource-Sharing-Technical-Deep-Dive.md b/_posts/2025-12-14-Resource-Sharing-Technical-Deep-Dive.md index 819a52b5e5..991038c5b4 100644 --- a/_posts/2025-12-14-Resource-Sharing-Technical-Deep-Dive.md +++ b/_posts/2025-12-14-Resource-Sharing-Technical-Deep-Dive.md @@ -1,6 +1,6 @@ --- layout: post -title: "Under the hood: Designing resource-level access control in OpenSearch" +title: "Technical deep dive: Designing resource-level access control in OpenSearch" authors: - dchanp - cwperks @@ -17,18 +17,18 @@ tags: - opensearch 3.3 --- -In [Part 1]({{site.baseurl}}/blog/introducing-resource-sharing), we introduced resource sharing and access control as a new way to collaborate on plugin-defined resources such as anomaly detectors and machine learning (ML) models. +In [Part 1 of the resource sharing blog series](https://opensearch.org/blog/introducing-resource-sharing/), we introduced resource sharing and access control as a new way to collaborate on plugin-defined resources such as anomaly detectors and machine learning (ML) models. -This post looks under the hood at how that feature is built and how you can adopt it in your own plugins: +This post examines the underlying architecture of that feature and demonstrates how you can adopt it in your own plugins: -* Why the legacy `filter_by_backend_roles` model was not enough -* How we designed a resource-centric authorization model -* How plugins integrate using the new Security SPI -* How access checks work at query time -* How to migrate safely from legacy behavior -* A practical onboarding path for plugin developers +* The limitations of the legacy `filter_by_backend_roles` model. +* The design of a resource-centric authorization model. +* Plugin integration using the new Security SPI. +* Access check functionality at query time. +* Safe migration from legacy behavior. +* A practical onboarding path for plugin developers. -If you are building or operating plugins on OpenSearch, this is the post you probably care about. +If you're building or operating plugins on OpenSearch, this post provides essential information for your development process. --- @@ -36,35 +36,37 @@ If you are building or operating plugins on OpenSearch, this is the post you pro Before resource sharing, most OpenSearch plugins used a simple pattern: -* Each resource (detector, model, report, and so on) stored identity metadata (creator, backend roles). +* Each resource (for example, detector, model, or report) stored identity metadata (creator, backend roles). * Visibility was controlled by checking for backend role overlap between creator and viewer. -* In Anomaly Detection, this was controlled by `plugins.anomaly_detection.filter_by_backend_roles`. In ML Commons, by `plugins.ml_commons.model_access_control_enabled`. +* In the Anomaly Detection plugin, visibility control was managed by `plugins.anomaly_detection.filter_by_backend_roles`. In the ML Commons plugin, visibility control was managed by `plugins.ml_commons.model_access_control_enabled`. This approach worked for basic multi-tenancy but had significant limitations. -### Shortcomings of `filter_by_backend_roles` +### Shortcomings of filter_by_backend_roles -1. Implicit, role-coupled sharing +The backend role approach created several significant problems: - If two users shared a backend role, they could see each other’s resources. That meant: +1. **Implicit, role-coupled sharing** - * No way for the owner to say “share with Alice, but not with Bob” if both users shared a role. + If two users shared a backend role, they could see each other's resources. This created the following issues: + + * No way for the owner to specify *share with Alice, but not with Bob* if both users shared a role. * Removing access required role-mapping changes, not a change on the resource itself. -2. Overly broad cluster privileges +2. **Overly broad cluster privileges** - Because access was controlled at the role level, not the resource level: + Because access was controlled at the role level rather than the resource level, the following problems arose: * Roles needed powerful cluster permissions just so users could operate on their own resources. - * It was difficult to grant “read-only access to this one detector” without granting broader capabilities. + * It was difficult to grant *read-only access to this one detector* without granting broader capabilities. -3. Distributed, plugin-specific metadata +3. **Distributed, plugin-specific metadata** - Each plugin implemented its own access logic: + Each plugin implemented its own access logic, resulting in: - * Different JSON shapes for “owner” and “shared with” - * Different user experience patterns in OpenSearch Dashboards - * No central place to audit “who can see what” + * Different JSON shapes for `owner` and `shared with`. + * Different user experience patterns in OpenSearch Dashboards. + * No central place to audit *access permissions and resource visibility*. The new framework is designed to fix all of this. @@ -72,29 +74,29 @@ The new framework is designed to fix all of this. ## Design goals -When we started designing resource sharing, we focused on a few core principles. +When we started designing resource sharing, we focused on the following core principles: -1. Resource-centric security +1. **Resource-centric security** Authorization should be driven by who owns this resource and who it is shared with, not by backend-role overlaps that happen to exist. -2. Centralized, reusable logic +2. **Centralized, reusable logic**: * One framework inside the Security plugin - * Plugins declare what is shareable and which actions exist - * Security handles how access is evaluated + * Plugins declare what is shareable and the actions that exist + * The Security plugin handles how access is evaluated -3. Minimal changes to plugin APIs +3. **Minimal changes to plugin APIs** - Plugins should: + Plugins should maintain their existing approach while integrating with the new framework: * Keep exposing their existing REST APIs (such as `/detectors`, `/models`, `/reports`) - * Delegate authorization to the Security framework - * Avoid copying and pasting “get current user” boilerplate + * Delegate authorization to the Security plugin framework + * Avoid copying and pasting "get current user" boilerplate -4. Safe migration +4. **Safe migration** - Existing clusters cannot lose access patterns overnight. We needed: + Existing clusters cannot lose access patterns overnight. We needed to provide: * A way to import legacy sharing data into the new framework * Feature flags and per-type rollout @@ -106,9 +108,9 @@ When we started designing resource sharing, we focused on a few core principles. At a high level, resource sharing splits responsibility across three parts: -* Resource plugins: own the functional resource (detectors, models, reports, dashboards) -* Security plugin: owns the sharing model and access evaluation -* System indices: store resources and the corresponding sharing metadata +* **Resource plugins**: Own the functional resource (detectors, models, reports, dashboards). +* **Security plugin**: Owns the sharing model and access evaluation. +* **System indexes**: Store resources and the corresponding sharing metadata. ```mermaid flowchart TD @@ -120,7 +122,7 @@ flowchart TD MAP[Index Mapping & Routing - resource -> sharing index] end - subgraph Data[System Indices - per plugin] + subgraph Data[System Indexes - per plugin] RIDX1[(Resource Index A)] RSIDX1[(Sharing Index A)] RIDX2[(Resource Index B)] @@ -144,9 +146,9 @@ flowchart TD Key ideas: -* Each resource index (for example, `.opendistro-anomaly-detectors`) is paired with a sharing index owned by Security. -* Plugins consult the Resource Sharing SPI instead of doing their own access checks. -* OpenSearch Dashboards uses Security REST endpoints to share, list, and manage resources. +* Each resource index (for example, `.opendistro-anomaly-detectors`) is paired with a sharing index owned by the Security plugin. +* Plugins consult the Resource Sharing SPI instead of performing their own access checks. +* OpenSearch Dashboards uses Security plugin REST endpoints to share, list, and manage resources. The feature is introduced in the Security plugin as an experimental capability, behind the flag: @@ -158,11 +160,11 @@ plugins.security.experimental.resource_sharing.enabled: true ## The resource-sharing data model -Resource sharing introduces a dedicated sharing document per resource, stored in a Security-managed index. +Resource sharing introduces a dedicated sharing document per resource, stored in a Security-plugin-managed index. ### Sharing document -For each resource, Security stores a document like: +For each resource, the Security plugin stores a document as follows: ```json { @@ -186,15 +188,17 @@ For each resource, Security stores a document like: } ``` -* `resource_id` – Unique ID within the plugin’s resource index. -* `created_by` – Logical owner (user and, optionally, tenant). -* `share_with` – Map of access levels (action groups) to recipients. +The sharing document contains three key fields: + +* `resource_id` — A unique ID within the plugin's resource index. +* `created_by` — The logical owner (user and, optionally, tenant). +* `share_with` — A map of access levels (action groups) to recipients. -Each access level under `share_with` defines three scopes: +Each access level under `share_with` defines the following scopes: -* `users` – usernames with this access level -* `roles` – OpenSearch roles with this access level -* `backend_roles` – backend roles with this access level +* `users` — usernames with this access level +* `roles` — OpenSearch roles with this access level +* `backend_roles` — backend roles with this access level ### Access levels as action groups @@ -217,13 +221,13 @@ resource_types: - "cluster:admin/security/resource/share" ``` -Security reads this file at startup and uses it to answer questions like, “Does this user have `sample_read_write` on resource X?” +The Security plugin reads this file at startup and uses it to answer questions like “Does this user have `sample_read_write` on resource X?” -### Public vs private vs restricted +### Public, private, and restricted sharing patterns -The `share_with` structure lets you express common patterns. +The `share_with` structure lets you express the following common patterns. -* Private (default) +**Private (default)**: ```json { @@ -231,9 +235,9 @@ The `share_with` structure lets you express common patterns. } ``` -Visible only to the owner and super-admins. +Visible only to the owner and superadmins. -* Public +**Public**: ```json { @@ -247,7 +251,7 @@ Visible only to the owner and super-admins. Any authenticated user can access the resource at the `default` access level. -* Restricted +**Restricted**: ```json { @@ -269,19 +273,19 @@ Only the listed principals can access this resource via that access level. There are two complementary pieces to runtime evaluation: -1. Implicit filtering for list/search APIs -2. Explicit checks for point operations or custom flows +1. **Implicit filtering** for List and Search APIs. +2. **Explicit checks** for point operations or custom flows. -### 1. Implicit filtering through `all_shared_principals` +### 1. Implicit filtering through all_shared_principals -When a plugin lists resources, we want it to not care about who the current user is. Instead: +When a plugin lists resources, we want it to operate without needing to know the current user's identity. The system accomplishes this through the following process: -1. The plugin exposes a list or search API (for example, `GET /_plugins/_reports/definitions`). -2. Inside the handler, the plugin issues a search against its system index using a plugin client (a system-level subject). +1. The plugin exposes a list or Search API (for example, `GET /_plugins/_reports/definitions`). +2. Within the handler, the plugin issues a search against its system index using a plugin client (a system-level subject). 3. The Security plugin attaches a Document-level security (DLS) query behind the scenes. -4. The DLS query checks an `all_shared_principals` field on each resource document. +4. This DLS query checks an `all_shared_principals` field on each resource document. -A resource document might look like: +A resource document might look as follows: ```json { @@ -304,19 +308,19 @@ If the authenticated user is: * `username: resource_sharing_test_user_alice` * with role `analytics_team` -then Security limits the result set to documents where `all_shared_principals` contains: +then the Security plugin limits the result set to documents where `all_shared_principals` contains: * `user:resource_sharing_test_user_alice` * or `role:analytics_team` * or a wildcard such as `user:*` for public visibility -To make this work in a future-proof way: +To make this work in a future-proof way, the implementation follows these principles: -* Plugins declare themselves as `IdentityAwarePlugin` so they can use their plugin subject to access system indices. +* Plugins declare themselves as `IdentityAwarePlugin` so they can use their plugin subject to access system indexes. * The plugin uses its plugin client instead of stashing thread context manually. * The Security plugin injects the DLS filter automatically. -Here is a snippet from anomaly-detection plugin for usage of PluginClient to perform search: +The following is a code snippet from the Anomaly Detection plugin that demonstrates `PluginClient` usage for search operations: ```java public void search(SearchRequest request, String resourceType, ActionListener actionListener) { @@ -337,9 +341,9 @@ public void search(SearchRequest request, String resourceType, ActionListener>Admin: 200 OK (summary + skipped IDs) ``` -The response summary includes: +The response summary includes the following information: -* How many resources were migrated -* How many were skipped (for missing type or owner) -* Which resources ended up using `default_owner` +* How many resources were migrated. +* How many were skipped (for missing type or owner). +* The resources that were assigned `default_owner`. -Only REST admins or super-admin users can run this API. +Only REST admins or superadmin users can run this API. --- -## Dashboards: share, list, manage +## Share, list, and manage resources in OpenSearch Dashboards -Once the framework is enabled and plugins are onboarded, OpenSearch Dashboards builds on top of the Security REST APIs: +Once the framework is enabled and plugins are onboarded, OpenSearch Dashboards builds on top of the Security plugin REST APIs: * `PUT /_plugins/_security/api/resource/share` * `PATCH /_plugins/_security/api/resource/share` @@ -682,47 +686,53 @@ Once the framework is enabled and plugins are onboarded, OpenSearch Dashboards b * `GET /_plugins/_security/api/resource/list` * `GET /_plugins/_security/api/resource/types` -This powers UI experiences like: +These APIs enable OpenSearch Dashboards to provide comprehensive resource management capabilities: -* “Share this detector with these users and roles.” -* “Show me resources I can access, and whether I can reshare them.” -* “List all resource types and their access levels.” +* Sharing detectors with specific users and roles. +* Displaying accessible resources and resharing permissions for the current user. +* Listing all available resource types and their corresponding access levels. -The Security plugin centralizes the logic; each feature plugin focuses on its own domain (detectors, models, dashboards, reports). +The Security plugin centralizes the logic; each feature plugin focuses on its own domain (detectors, models, dashboards, or reports). --- -## Putting it all together +## Framework benefits and adoption The Resource Sharing and Access Control framework moves OpenSearch from: -* Role-centric visibility - “If we share a backend role, we see each other’s stuff.” +**Role-centric visibility**: +"If we share a backend role, we see each other's resources." to: -* Resource-centric control - “This detector is owned by X, shared with Y, under access level Z.” +**Resource-centric control**: +"This detector is owned by X, shared with Y, under access level Z." For operators, this means: -* Clearer auditability of who can access what -* Safer, more incremental rollouts using feature flags and protected types -* A single, consistent sharing experience in OpenSearch Dashboards +* Clearer auditability of who can access what. +* Safer, more incremental rollouts using feature flags and protected types. +* A single, consistent sharing experience in OpenSearch Dashboards. -For plugin authors, it means: +For plugin authors, it provides: -* Less boilerplate access-control code -* A standard SPI to plug into -* Automatic DLS-based filtering for list and search APIs -* A migration path from legacy `filter_by_backend_roles` and plugin-specific metadata +* Less boilerplate access-control code. +* A standard SPI to plug into. +* Automatic DLS-based filtering for list and search APIs. +* A migration path from legacy `filter_by_backend_roles` and plugin-specific metadata. -If you are building a plugin and want to adopt resource sharing, a good starting path is: +--- + +## Try resource sharing and share your feedback + +Resource sharing and access control is available as an experimental feature in OpenSearch 3.3 and later. If you're developing a plugin and want to adopt resource sharing, start with performing these steps: 1. Implement `ResourceSharingExtension` and register your plugin as a resource plugin. 2. Define your resource action groups in `resource-action-groups.yml`. -3. Mark your resource indices as system indices and use a plugin client for access. +3. Mark your resource indexes as system indexes and use a plugin client for access. 4. Use `isFeatureEnabledForType` and `verifyAccess` in your handlers. 5. Enable the feature for your resource type in a test cluster and iterate. -From there, your plugin can inherit a full, centralized sharing model with consistent behavior across the OpenSearch ecosystem. +After this, your plugin can inherit a complete, centralized sharing model with consistent behavior across the OpenSearch platform. + +Your input helps us improve the feature before it becomes generally available. Please share your experiences, questions, and suggestions on the [OpenSearch Forum](https://forum.opensearch.org/). \ No newline at end of file From 22490fb9989863f8d56f7124aa39adfed1bacd8e Mon Sep 17 00:00:00 2001 From: Fanit Kolchina Date: Mon, 15 Dec 2025 09:52:55 -0500 Subject: [PATCH 6/7] Change highlighting to yaml to display properly Signed-off-by: Fanit Kolchina --- _posts/2025-12-14-Resource-Sharing-Technical-Deep-Dive.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/_posts/2025-12-14-Resource-Sharing-Technical-Deep-Dive.md b/_posts/2025-12-14-Resource-Sharing-Technical-Deep-Dive.md index 991038c5b4..1f5f6ceb2e 100644 --- a/_posts/2025-12-14-Resource-Sharing-Technical-Deep-Dive.md +++ b/_posts/2025-12-14-Resource-Sharing-Technical-Deep-Dive.md @@ -112,7 +112,7 @@ At a high level, resource sharing splits responsibility across three parts: * **Security plugin**: Owns the sharing model and access evaluation. * **System indexes**: Store resources and the corresponding sharing metadata. -```mermaid +```yaml flowchart TD U[User / OpenSearch Dashboards] -->|Create / Read / Update / Delete| PL[Resource Plugin] @@ -645,7 +645,7 @@ POST /_plugins/_security/api/resources/migrate The migration flow looks like this: -```mermaid +```yaml sequenceDiagram participant Admin as Cluster Admin participant MAPI as Migration API From 387e095f42814b14568d48b24f3efb5341df0c4f Mon Sep 17 00:00:00 2001 From: Nathan Bower Date: Mon, 15 Dec 2025 11:56:05 -0500 Subject: [PATCH 7/7] Apply suggestions from code review Signed-off-by: Nathan Bower --- ...14-Resource-Sharing-Technical-Deep-Dive.md | 92 +++++++++---------- 1 file changed, 46 insertions(+), 46 deletions(-) diff --git a/_posts/2025-12-14-Resource-Sharing-Technical-Deep-Dive.md b/_posts/2025-12-14-Resource-Sharing-Technical-Deep-Dive.md index 1f5f6ceb2e..275c01803c 100644 --- a/_posts/2025-12-14-Resource-Sharing-Technical-Deep-Dive.md +++ b/_posts/2025-12-14-Resource-Sharing-Technical-Deep-Dive.md @@ -51,7 +51,7 @@ The backend role approach created several significant problems: If two users shared a backend role, they could see each other's resources. This created the following issues: * No way for the owner to specify *share with Alice, but not with Bob* if both users shared a role. - * Removing access required role-mapping changes, not a change on the resource itself. + * Removing access required role-mapping changes, not a change to the resource itself. 2. **Overly broad cluster privileges** @@ -82,31 +82,31 @@ When we started designing resource sharing, we focused on the following core pri 2. **Centralized, reusable logic**: - * One framework inside the Security plugin - * Plugins declare what is shareable and the actions that exist - * The Security plugin handles how access is evaluated + * One framework inside the Security plugin. + * Plugins declare what is shareable and the actions that exist. + * The Security plugin handles how access is evaluated. 3. **Minimal changes to plugin APIs** Plugins should maintain their existing approach while integrating with the new framework: - * Keep exposing their existing REST APIs (such as `/detectors`, `/models`, `/reports`) - * Delegate authorization to the Security plugin framework - * Avoid copying and pasting "get current user" boilerplate + * Keep exposing their existing REST APIs (such as `/detectors`, `/models`, `/reports`). + * Delegate authorization to the Security plugin framework. + * Avoid copying and pasting "get current user" boilerplate. 4. **Safe migration** Existing clusters cannot lose access patterns overnight. We needed to provide: - * A way to import legacy sharing data into the new framework - * Feature flags and per-type rollout - * A reversible, observable migration step + * A way to import legacy sharing data into the new framework. + * Feature flags and per-type rollout. + * A reversible, observable migration step. --- ## High-level architecture -At a high level, resource sharing splits responsibility across three parts: +At a high level, resource sharing splits responsibility across three elements: * **Resource plugins**: Own the functional resource (detectors, models, reports, dashboards). * **Security plugin**: Owns the sharing model and access evaluation. @@ -196,9 +196,9 @@ The sharing document contains three key fields: Each access level under `share_with` defines the following scopes: -* `users` — usernames with this access level +* `users` — Usernames with this access level * `roles` — OpenSearch roles with this access level -* `backend_roles` — backend roles with this access level +* `backend_roles` — Backend roles with this access level ### Access levels as action groups @@ -235,7 +235,7 @@ The `share_with` structure lets you express the following common patterns. } ``` -Visible only to the owner and superadmins. +The resource is visible only to the owner and superadmins. **Public**: @@ -265,27 +265,27 @@ Any authenticated user can access the resource at the `default` access level. } ``` -Only the listed principals can access this resource via that access level. +Only the listed principals can access this resource through that access level. --- -## Query-time evaluation: how results are filtered +## Query-time evaluation: How results are filtered -There are two complementary pieces to runtime evaluation: +There are two complementary aspects of runtime evaluation: -1. **Implicit filtering** for List and Search APIs. -2. **Explicit checks** for point operations or custom flows. +1. **Implicit filtering** for List and Search APIs +2. **Explicit checks** for point operations or custom flows ### 1. Implicit filtering through all_shared_principals -When a plugin lists resources, we want it to operate without needing to know the current user's identity. The system accomplishes this through the following process: +When a plugin lists resources, it should operate without needing to know the current user's identity. The system accomplishes this through the following process: 1. The plugin exposes a list or Search API (for example, `GET /_plugins/_reports/definitions`). 2. Within the handler, the plugin issues a search against its system index using a plugin client (a system-level subject). -3. The Security plugin attaches a Document-level security (DLS) query behind the scenes. +3. The Security plugin attaches a document-level security (DLS) query behind the scenes. 4. This DLS query checks an `all_shared_principals` field on each resource document. -A resource document might look as follows: +A resource document might appear as follows: ```json { @@ -341,7 +341,7 @@ public void search(SearchRequest request, String resourceType, ActionListener