-
Notifications
You must be signed in to change notification settings - Fork 338
Description
This proposal has seen few edits of designs, please read through comments to understand the latest approach.
Updated as of: 28-Aug-2025
[HLD] Resource Sharing & Access Control in OpenSearch
1) Overview
Starting in OpenSearch v3.2.0, the Resource Sharing & Access Control framework enables document-level authorization for plugin-defined resources.
Key highlights:
- 1:1 backing sharing indices per resource index (clean ownership & isolation).
- Automatic evaluation via ResourceAccessEvaluator (no plugin in-house verification via
filter_by_backend_role). - Dashboards-native APIs to fetch, update, and list sharing info.
- Migration API to move legacy per-plugin sharing into security-owned indices.
- Action-groups defined by plugins will be the ones available for sharing.
Feature flag (experimental, default: false):
plugins.security.experimental.resource_sharing.enabled: true2) Motivation
Problems: index-scoped RBAC can’t restrict specific documents; plugins built custom auth; no central UX for sharing.
Solution: standardize sharing with:
- Backing sharing index per resource index
- Central evaluation in Security Plugin
- First-class Dashboards APIs & UI for access management
3) Architecture & Components
-
Resource Plugins define resources (stored in system indices) and register a
ResourceSharingExtension. -
Security Plugin:
- Maintains Index→SharingIndex mapping
- Stores sharing docs in per-plugin backing indices
- Runs ResourceAccessEvaluator for auto authz
- Exposes Dashboards Share APIs and List-Accessible API
3.1 Architecture (with Dashboards flows)
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
4) Data Model (Per-Plugin Backing Sharing Index)
Each resource index has a backing sharing index storing the sharing document:
{
"resource_id": "resource-123",
"created_by": {
"user": "abc"
},
"share_with": {
"READ_ONLY": {
"users": ["user1", "user2"],
"roles": ["viewer_role"],
"backend_roles": ["data_analyst"]
},
"READ_WRITE": {
"users": ["admin_user"],
"roles": ["editor_role"],
"backend_roles": ["content_manager"]
}
}
}| Field | Type | Description |
|---|---|---|
resource_id |
String | Unique document ID in the resource index |
created_by |
Object | { "user": <username> } |
share_with |
Object | Access-level → principals (READ_ONLY, READ_WRITE) |
Mapping: Resource index → Sharing index (convention):
.plugins-ml-model-group→.plugins-ml-model-group-sharingad-detectors→ad-detectors-sharing
Note: The relationship between entries in this index and the resource metadata stored in plugin’s index is many-to-one.
Access scope is defined in 3 ways:
public: A shared_with entry exists, and contains users entry mapped to * .restricted: A shared_with entry exists, and contains users entry not mapped to * .private: A shared_with entry is empty or doesn’t exist.
Here are 3 examples of entries in resource_sharing index, one for each scope.
- Private:
{
"resource_id": "<doc_id>",
"created_by": {
"user": "darshit"
}
}- Restricted:
{
"resource_id": "<doc_id>",
"created_by": {
"user": "darshit"
},
"share_with": {
"read_only": {
"users": [
"derek"
],
"roles": [],
"backend_roles": []
},
"read_write": {
"users": [
"craig"
],
"roles": [],
"backend_roles": []
}
}
}- Public:
{
"resource_id": "<doc_id>",
"created_by": {
"user": "darshit"
},
"share_with": {
"read_only": {
"users": ["*"],
"roles": ["*"],
"backend_roles": ["*"]
},
"read_write": {
"users": [
"*"
],
"roles": ["*"],
"backend_roles": ["*"]
}
}
}NOTE: Each user, role and backend_role must be individually added as there is no pattern matching at present by design.
5) Automatic Access Evaluation
All DocRequest paths are intercepted by the Security Filter and evaluated by ResourceAccessEvaluator.
5.1 Evaluation flow
sequenceDiagram
participant User
participant Plugin as Resource Plugin
participant SecFilter as Security Filter
participant RAE as ResourceAccessEvaluator
participant Map as Index→SharingIndex Mapping
participant RSIDX as Backing Sharing Index
User->>Plugin: Request (e.g., GET /resource/{id})
Plugin->>SecFilter: Routed through security
SecFilter->>RAE: Evaluate (user, roles, backend_roles, action)
RAE->>Map: Resolve sharing index for resource_type
Map-->>RAE: sharing_index_name
RAE->>RSIDX: Fetch sharing doc by resource_id
RSIDX-->>RAE: share_with + created_by
RAE->>RAE: Check required access level
alt Allowed
RAE-->>SecFilter: ALLOW
SecFilter-->>Plugin: Proceed
Plugin-->>User: 200 OK
else Denied
RAE-->>SecFilter: DENY
SecFilter-->>Plugin: 403 Forbidden
Plugin-->>User: 403
end
6) Dashboards APIs (Access Management UI)
Base path (security dashboards endpoints):
/_plugins/_security/api/resource/share
6.1 Fetch Sharing Info (Dashboards)
GET /_plugins/_security/api/resource/share?resource_id=<id>&resource_type=<idx>Response:
{
"sharing_info": {
"resource_id": "resource-123",
"created_by": { "user": "abc" },
"share_with": {
"READ_ONLY": { "users": ["alice"], "roles": [], "backend_roles": [] },
"READ_WRITE": { "users": ["bob"] }
}
}
}6.2 Replace Sharing (PUT)
PUT /_plugins/_security/api/resource/shareBody:
{
"resource_id": "resource-123",
"resource_type": "sample-resource-index",
"share_with": {
"READ_ONLY": { "users": ["alice"] },
"READ_WRITE": { "users": ["bob"] }
}
}6.3 Patch Sharing (non-destructive)
PATCH /_plugins/_security/api/resource/shareBody (example):
{
"resource_id": "resource-123",
"resource_type": "sample-resource-index",
"add": {
"READ_ONLY": { "users": ["charlie"] }
"FULL_ACCESS": { "users": ["alice"] }
},
"revoke": {
"READ_ONLY": { "users": ["alice"] },
"READ_WRITE": { "users": ["bob"] }
}
}6.4 List Available Resource Types
(available as drop-down in UI to fetch corresponding sharing records)
GET /_plugins/_security/api/resource/typesAs user alice, above http request would return following response:
Response:
{
"types": [
{
"type": "org.opensearch.sample.SampleResource",
"index": ".sample_resource",
"action_groups": ["sample_read_only", "sample_read_write", "sample_full_access"]
}
]
}6.5 List Accessible Resources (for UI tables)
GET /_plugins/_security/api/resource/list?resource_type=<idx>As user alice, above http request would return following response:
Response:
{
"resources": [
{
"resource_id": "resource-123",
"created_by": {
"user": "abc"
},
"share_with": {
"READ_ONLY": {
"users": [
"alice"
],
"roles": [],
"backend_roles": []
}
},
"can_share": false
},
{
"resource_id": "resource-456",
"created_by": {
"user": "def"
},
"share_with": {
"FULL_ACCESS": {
"users": [
"alice"
]
}
}
"can_share": true
}
]
}6.5 Dashboards flows (integrated)
sequenceDiagram
participant UI as Dashboards UI (Access Mgmt)
participant API as Security Dashboards Share API
participant Core as Security Plugin Core
participant Map as Index→SharingIndex Mapping
participant RSIDX as Backing Sharing Index
rect rgb(120,120,120)
UI->>API: GET /share?resource_id&resource_type
API->>Core: Authz (owner/admin/share-manager)
Core->>Map: Resolve sharing index
Map-->>Core: sharing_index_name
Core->>RSIDX: GET sharing doc
RSIDX-->>Core: sharing_info
Core-->>UI: 200 (sharing_info)
end
rect rgb(120,120,120)
UI->>API: PUT/PATCH /share
API->>Core: Validate + authorize
Core->>Map: Resolve sharing index
Map-->>Core: sharing_index_name
Core->>RSIDX: Upsert / Patch sharing doc
RSIDX-->>Core: ack (updated doc)
Core-->>UI: 200 (updated sharing_info)
end
rect rgb(120,120,120)
UI->>API: GET /share/accessible?resource_type&from&size
API->>Core: Compute principals (user/roles/backend_roles)
Core->>RSIDX: Query share_with contains principals
RSIDX-->>Core: [resource_ids...], total
Core-->>UI: 200 {resource_ids, total, page}
end
7) Plugin Developer Requirements
-
Mark resource indices as system indices (prevents direct access).
-
Implement
ResourceSharingExtensionand register via:src/main/resources/META-INF/services/org.opensearch.security.spi.ResourceSharingExtension -
Declare action-groups per resource in
resource-action-groups.ymlfile:src/main/resources/resource-action-groups.yml -
Depend on
opensearch-security-spi(compileOnly) and extend security plugin (optional). -
Rely on ResourceAccessEvaluator via Security Filter for access verification. Do not call
verifyAccessunless a hierarchical evaluation is needed. -
Use client methods where applicable (
share,revoke,getAccessibleResourceIds) for non-filter flows (e.g., management tasks).
8) Migration API (to per-index backing sharing)
POST /_plugins/_security/api/resources/migrateRequest (example):
{
"source_index": "sample-plugin-index",
"username_path": "/owner",
"backend_roles_path": "/access/backend_roles",
"default_access_level": "READ_ONLY"
}Summary response:
{
"summary": "Migration complete. migrated 10; skippedNoUser 2; failed 1",
"skippedResources": ["doc-17", "doc-22"]
}Migration flow
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 Backing Sharing Index
Admin->>MAPI: POST /resources/migrate {...}
MAPI->>Core: Validate + resolve backing sharing index
Core->>Src: Scroll documents
loop each doc
Core->>RSIDX: Upsert sharing doc<br>{resource_id, created_by.user,share_with[default].backend_roles}
end
RSIDX-->>Core: Stats
Core-->>Admin: 200 OK (summary)
9) Security Rules & Best Practices
- Keep system index protection ON to prevent direct index access.
- Least privilege: share only required principals; avoid wildcards unless necessary.
- Owners & super-admins retain full control irrespective of config.
- Ensure consistent resource_id handling between the resource index and sharing index.
10) End-to-End Lifecycle (happy path)
sequenceDiagram
participant Dev as Plugin Dev
participant Plugin as Resource Plugin
participant Sec as Security Plugin
participant UI as Dashboards
participant User as End User
Dev->>Plugin: Implement ResourceSharingExtension + DocRequest
Plugin->>Sec: Register (SPI) on startup
User->>Plugin: Create resource (doc in resource index)
UI->>Sec: PUT/PATCH /share (configure share_with)
Sec->>Sec: Write to backing sharing index
User->>Plugin: Read/Search resource
Plugin->>Sec: (Security Filter) → ResourceAccessEvaluator
Sec-->>Plugin: ALLOW / DENY
Plugin-->>User: Response (200 / 403)
11) License
Apache 2.0 — © OpenSearch Contributors.
Visual representations: #4500 (comment)
Old designs: #4500 (comment)
Metadata
Metadata
Assignees
Labels
Type
Projects
Status