Skip to content

Onboards Alerting Plugin to Centralized Resource Authz#2180

Draft
DarshitChanpura wants to merge 31 commits into
opensearch-project:mainfrom
DarshitChanpura:onbaord-resource-authz
Draft

Onboards Alerting Plugin to Centralized Resource Authz#2180
DarshitChanpura wants to merge 31 commits into
opensearch-project:mainfrom
DarshitChanpura:onbaord-resource-authz

Conversation

@DarshitChanpura

@DarshitChanpura DarshitChanpura commented Jun 20, 2026

Copy link
Copy Markdown
Member

Description

Implements resource-access-control for monitor.

Related Issues

Resolves #2195

Check List

  • New functionality includes testing.
  • New functionality has been documented.
  • API changes companion pull request created.
  • Commits are signed per the DCO using --signoff.
  • Public documentation issue/PR created.

By submitting this pull request, I confirm that my contribution is made under the terms of the Apache 2.0 license.
For more information on following Developer Certificate of Origin and signing off your commits, please check here.

Signed-off-by: Darshit Chanpura <dchanp@amazon.com>
@DarshitChanpura
DarshitChanpura force-pushed the onbaord-resource-authz branch from 5ad666c to 9a6e142 Compare June 20, 2026 07:07
Signed-off-by: Darshit Chanpura <dchanp@amazon.com>
@DarshitChanpura
DarshitChanpura force-pushed the onbaord-resource-authz branch from 9a6e142 to b8fba5f Compare June 20, 2026 07:08
Signed-off-by: Darshit Chanpura <dchanp@amazon.com>
@DarshitChanpura
DarshitChanpura force-pushed the onbaord-resource-authz branch from 2f06126 to 2576e6b Compare June 20, 2026 22:08
search(alertIndex, searchSourceBuilder, actionListener, tenantId)
} else if (ResourceSharingClientAccessor.getResourceSharingClient() != null) {
// resource sharing framework is enabled - access control handled by security plugin
search(alertIndex, searchSourceBuilder, actionListener, tenantId)

@lezzago lezzago Jul 20, 2026

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This will break RBAC for alerting users if they have both flags enabled since the monitors themselves contains a user attribute that we filter on that matches the backend roles. This will be an issue for all the apis

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Addressed. When resource sharing is enabled the RSC path is now checked first in every affected transport action and:

  • Primary-resource operations (get/index/delete monitor & workflow, search monitor, get destinations) route through the security plugin's ActionFilter via DocRequest/getAccessibleResourceIds — backend-role filtering is skipped entirely so the two paths don't conflict.
  • Subordinate-resource searches (get alerts, get workflow alerts, search alerting comments) resolve the accessible monitor IDs via rsc.getAccessibleResourceIds("monitor", ...) and use those to filter, since those indices aren't registered resources themselves.

New coverage in SecureResourceSharingMonitorRestApiIT exercises this end-to-end (users without all_access so RSC is the sole gate).

…bled

Signed-off-by: Darshit Chanpura <dchanp@amazon.com>
Signed-off-by: Darshit Chanpura <dchanp@amazon.com>
Signed-off-by: Darshit Chanpura <dchanp@amazon.com>
Skip backend-role validation, permission checks, and filter injection
across all transport actions when the resource-sharing client is set.
For primary resources (monitors, workflows), rely on the security
plugin's DLS at the index layer. For subordinate resources (comments),
scope results by accessible monitor IDs via getAccessibleResourceIds.

Signed-off-by: Darshit Chanpura <dchanp@amazon.com>
…fFoundError

Storing the RSC accessor result in a local val that lambdas close over
forces the JVM to link ResourceSharingClient when the closure is created.
Without the security plugin installed at runtime that class is absent,
crashing the node with NoClassDefFoundError. Call the accessor fresh
inside the lambda instead.

Signed-off-by: Darshit Chanpura <dchanp@amazon.com>
val canAccess = user == null || !doFilterForUser(user) ||
// when resource sharing is enabled, security plugin gates access at the index layer
val canAccess = user == null ||
ResourceSharingClientAccessor.getResourceSharingClient() != null ||

@vikhy-aws vikhy-aws Jul 21, 2026

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nit: rsc != null. Same comment in other files too.

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done — every call site now uses a local val rsc = ResourceSharingClientAccessor.getResourceSharingClient() and references rsc in checks.

Comment on lines +187 to +190
if (rsc == null) {
log.debug("checking user permissions in index comment")
checkUserPermissionsWithResource(user, alert.monitorUser, actionListener, "monitor", alert.monitorId)
}

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Where does the check happen when rsc is not null?

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

When rsc != null the check happens in the security plugin's ActionFilter at the alert fetch layer (getAlert() above): GetAlertsRequest implements DocRequest (returning the monitor id) so the ActionFilter rejects the request with 403 if the user lacks access to the underlying monitor. No additional verifyAccess is needed for direct access — that's only used for transitive checks (e.g. access to a resource via its parent).

return setOf(
object : ResourceProvider {
override fun resourceType(): String = "monitor"
override fun resourceIndexName(): String = SCHEDULED_JOBS_INDEX

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Alerts and comments are treated as subordinate resources rather than registered resource types, so we don't register their indices with the sharing framework. Instead, access is inherited from the underlying monitor:

  • TransportGetAlertsAction / TransportGetWorkflowAlertsAction call rsc.getAccessibleResourceIds("monitor", ...) and add a monitor_id terms filter to the alert search.
  • TransportSearchAlertingCommentAction does the same via alert IDs derived from accessible monitors.
  • TransportIndexAlertingCommentAction / TransportDeleteAlertingCommentAction rely on the ActionFilter blocking the underlying alert fetch (via DocRequest on the requests, which points at the monitor).

New tests bob cannot see alice's monitor alerts without share and bob can see alice's monitor alerts after share in SecureResourceSharingMonitorRestApiIT cover this.

  resource sharing is enabled

Signed-off-by: Darshit Chanpura <dchanp@amazon.com>
Rewrites SecureResourceSharingMonitorRestApiIT to exercise the full
matrix of security plugin ActionFilter paths that DocRequest enables:
- GET / UPDATE / DELETE with insufficient and sufficient share levels
- SEARCH DLS filtering per user
- alerts subordinate to monitor share via getAccessibleResourceIds
- SHARE and REVOKE round-trips

Users no longer carry all_access so RSC is the sole authorization gate.
Also updates existing unit tests to pass the new PluginClient constructor
parameter on the affected transport actions.

Signed-off-by: Darshit Chanpura <dchanp@amazon.com>
@DarshitChanpura

Copy link
Copy Markdown
Member Author

Heads up: CI on this branch will fail until common-utils#980 merges. That PR adds DocRequest to alerting request classes (GetAlertsRequest, AcknowledgeAlertRequest, IndexWorkflowRequest, GetWorkflowAlertsRequest, GetFindingsRequest, AcknowledgeChainedAlertRequest) which the transport actions here depend on for the security plugin's ActionFilter interception.

…s-load time

Store the client as Any? and return Any? from getResourceSharingClient()
so the JVM does not resolve ResourceSharingClient when loading the
accessor class. This prevents NoClassDefFoundError in test clusters that
run without the security plugin installed. Callers cast to
ResourceSharingClient inside null-guarded blocks where the security
plugin is guaranteed to be present.

Signed-off-by: Darshit Chanpura <dchanp@amazon.com>
- Introduce ResourceSharingUtils with MONITOR_RESOURCE_TYPE constant and
  shouldUseResourceAuthz() helper, mirroring reporting plugin's pattern.
- Replace all `rsc != null` / `rsc == null` checks in transport actions
  with shouldUseResourceAuthz() so admin flows (and non-RSC deployments)
  fall through to the existing filter-by-backend-roles path when the
  security plugin is present but the RSC feature flag is disabled.
- Move the "monitor" literal out of every call site into a single
  constant referenced by both the extension and the utility helper.

Signed-off-by: Darshit Chanpura <dchanp@amazon.com>
Adds tests for scenarios missing from the initial suite:
- read-write share: can delete but cannot re-share (share permission
  belongs only to full-access)
- read-write share: owner sees edits made by the shared user
- full-access share: owner sees the deletion made by the shared user

Signed-off-by: Darshit Chanpura <dchanp@amazon.com>
Reorganizes the suite around scenarios rather than API surfaces and adds
coverage for cases the earlier version missed:

- Owner-side positive controls (owner can always get / update / delete)
- Default-deny on every mutating action (get, update, delete, re-share)
- Explicit per-access-level positive and negative assertions
  (read-only, read-write, full-access) including "read-write cannot
  re-share" — the share permission belongs only to full-access
- Third-user isolation: share to bob does not grant carol access
- Cross-resource isolation: share on monitor A does not grant access to B
- Search DLS visibility (owned, shared, other-users')
- Subordinate resources: alerts and comments inherit monitor access;
  acknowledge and comment require read-write
- Downgrade: re-sharing at a lower level narrows permissions
- Revoke: removes access; does not affect other users' shares

Also introduces carol as a third user and switches to constants
(RS_ALICE/RS_BOB/RS_CAROL, READ_ONLY/READ_WRITE/FULL_ACCESS) to keep
assertions readable.

Signed-off-by: Darshit Chanpura <dchanp@amazon.com>
- Map all three users (alice, bob, carol) to alerting_full_access in a
  single PUT rolesmapping call; the previous per-user PUTs replaced each
  other, leaving only the last-created user with the role.
- Create a shared test index and grant all three users index-level read
  access to it, then point the sample monitor's SearchInput at that
  index. Without this the monitor create fails at the security plugin's
  index-permission check ("User doesn't have read permissions for one or
  more configured index []").
- Clean up the test index and its role/rolesmapping in @after.

Signed-off-by: Darshit Chanpura <dchanp@amazon.com>
Removes the default parameter and updates every call site to pass
ResourceSharingUtils.MONITOR_RESOURCE_TYPE explicitly. Forces callers
to state which resource type they are gating on and future-proofs
against alerting registering additional resource types.

Signed-off-by: Darshit Chanpura <dchanp@amazon.com>
The security plugin's share API rejects unknown resource types with
"No allowed values configured for resource_type" when the experimental
resource_sharing feature is on but protected_types is not set. Add the
setting so the plugin recognizes "monitor" as a protected type.

Signed-off-by: Darshit Chanpura <dchanp@amazon.com>
Alerting was pinned to common-utils 3.7.0.0-SNAPSHOT via a TODO from an
earlier version bump. The DocRequest implementations that the
resource-sharing framework relies on landed on common-utils main
(3.8.0.0-SNAPSHOT), so tests that expect the security plugin to
intercept get/delete/index requests by resource id were seeing 200s
instead of 403s. Removing the pin and tracking opensearch_build keeps
alerting aligned with the OpenSearch line it is built against.

Signed-off-by: Darshit Chanpura <dchanp@amazon.com>
Both resource types share `.opendistro-alerting-config`, so both are registered
as `ResourceProvider`s with `typeField = resource_type` — the security plugin
reads that top-level indexed field at shard-write time to route to the right
provider. Adds `resource_type` and `all_shared_principals` to the scheduled-jobs
mapping so the discriminator and DLS filter fields are indexed.

Async writes to internal indices go through `SdkClientExtensions.{put,get,delete}
DataObjectStashed` which stash the caller's transient auth per-call and restore
via `whenComplete` — mirrors ml-commons / flow-framework. Coroutine `.use { }`
around a suspend body doesn't survive resume-on-different-thread; per-call
stashing does.

Legacy `rbac_roles` validation in the monitor/workflow write actions is skipped
when RSC is active; the sharing entry is now the sole gate. The
`alerting_read_only` / `read_write` / `full_access` access levels are declared
for both `monitor` and `workflow` in `resource-access-levels.yml`.

`RSC_MIGRATION.md` documents the two-step upgrade path: alerting-side backfill
of `resource_type` and scratch owner fields via update-by-query, then the
security plugin's `POST /_plugins/_security/api/resources/migrate` seeds
sharing entries.

Signed-off-by: Darshit Chanpura <dchanp@amazon.com>
The base test helpers (`updateMonitorWithClient` / `deleteMonitorWithClient`)
re-fetch through the admin client after the mutation, which 403s under RSC
because admin holds no share entry. Local `updateMonitorAs` / `deleteMonitorAs`
skip that re-fetch.

Other adjustments so the suite reflects framework semantics:
- Revoke uses `PATCH /_plugins/_security/api/resource/share` with a `revoke`
  body, not `POST /revoke` (which doesn't exist).
- The framework's `PUT /share` is add-only, not replace, so access-level
  downgrade needs an explicit revoke-then-share sequence.
- `test alerts inherit denial when monitor is not shared` accepts either 403
  (cluster-action gate rejects a user with no shares anywhere) or a 200 with
  empty results (DLS-filtered) — both satisfy the guarantee.
- User/role setup is idempotent across tests; @after no longer deletes users
  since config-cache reloads under repeated PUTs exposed a role-mapping race.
- `waitForResourceSharingEntry` polls the sharing index after monitor create
  because the security plugin records the entry asynchronously from postIndex.
- Two comment-flow tests marked `@Ignore` pending a stash inside
  `CommentsIndices.createOrUpdateInitialCommentsHistoryIndex` — non-owner
  calls throw an uncaught exception mid-coroutine and hang the response.

Signed-off-by: Darshit Chanpura <dchanp@amazon.com>
Design belongs in the PR description or an internal wiki, not tracked in the
alerting source tree. Content moved out of band.

Signed-off-by: Darshit Chanpura <dchanp@amazon.com>
Admin-only endpoint that runs an update-by-query on `.opendistro-alerting-config`
to backfill the fields the security plugin's resource-sharing framework needs on
existing monitor and workflow docs:

  1. Top-level `resource_type` = "monitor" or "workflow", copied from the wrapper
     key. This is what `AlertingResourceSharingExtension`'s `ResourceProvider.typeField`
     points at, so postIndex on future writes can classify docs by type.
  2. Top-level `_migration_user_name` and `_migration_backend_roles`, copied from
     `<wrapper>.user.*`. These let the security plugin's
     `POST /_plugins/_security/api/resources/migrate` address monitors and workflows
     in a single call via `username_path: "/_migration_user_name"`.

The Painless script is idempotent — docs already carrying `resource_type` and
docs without either wrapper (metadata records) return `ctx.op = 'noop'`. The
query pre-filters via `must_not exists resource_type` so the noop branch only
runs on rare concurrent-write windows.

Response is a plain counters shape: `updated`, `noops`, `failures`, `took_millis`.
The transport action name `cluster:admin/opensearch/alerting/rsc/migrate` should
be granted only via `all_access` — not through the per-resource access levels.

Ships with an IT that seeds legacy-shape monitor/workflow/metadata/already-
migrated docs directly, hits the endpoint, and verifies each transitions
correctly (or noops).

Signed-off-by: Darshit Chanpura <dchanp@amazon.com>
@DarshitChanpura
DarshitChanpura force-pushed the onbaord-resource-authz branch from 1e540ae to a550f12 Compare July 22, 2026 21:30
The security plugin's `POST /_plugins/_security/api/resources/migrate` scans the
entire source index and 400s if any doc's `resource_type` is null.
`<monitorId>-metadata` records aren't shareable resources, so alerting's migrate
now deletes them via delete-by-query on `metadata` field existence before the
resource_type backfill. Metadata regenerates on next monitor execution.

Adds `MigrateToRscE2ERestApiIT` covering the full lifecycle:
  phase 1: RSC disabled dynamically → alice creates a monitor via alerting REST,
    legacy backend-roles path works.
  phase 2: strip resource_type + sharing entry to simulate a truly-legacy doc.
  phase 3: enable RSC + protected_types → alice's GET returns 403 (no share).
  phase 4: call POST /_plugins/_alerting/_migrate_to_rsc, then
    POST /_plugins/_security/api/resources/migrate.
  phase 5: alice's GET returns 200 again — sharing entry now exists.

Also updates the metadata test in MigrateToRscRestApiIT to expect deletion
instead of untouched.

Signed-off-by: Darshit Chanpura <dchanp@amazon.com>
isSweepableJobType inspected only the very first FIELD_NAME to decide if a doc
was schedulable. Under resource-sharing, alerting docs are written with
`resource_type` (and possibly `all_shared_principals` / migration scratch
fields) at the top level before the `monitor`/`workflow` wrapper — so the
sweeper would silently skip newly-written jobs and they'd never be scheduled.
Iterate top-level fields until we find a sweepable type, skipping any extras.

Signed-off-by: Darshit Chanpura <dchanp@amazon.com>
opensearch-project/security#6323 changes getResourceTypeForIndexOp to iterate
all matching ResourceProviders and pick the first whose typeField extraction
resolves. That lets us drop the redundant top-level `resource_type` field
and rely on the pre-existing nested `monitor.type` / `workflow.type` fields
as type discriminators.

Changes:
- AlertingResourceSharingExtension: typeField is now `monitor.type` and
  `workflow.type` per provider (previously both pointed at the shared
  `resource_type` field).
- TransportIndexMonitorAction / TransportIndexWorkflowAction: drop the
  `with_resource_type=true` XContent param from storage writes; the stored
  doc goes back to just `{"<wrapper>": {...}}`.
- scheduled-jobs.json mapping: drop the `resource_type` keyword field.
- TransportMigrateToRscAction: drop the resource_type backfill from the
  Painless script; keep the scratch owner-field copy (still needed by the
  security migrate endpoint's `username_path`). Idempotency filter now
  gates on `_migration_user_name` existence.
- Migrate ITs updated to match the new doc shape (no resource_type).

Depends on: opensearch-project/security#6323 (must land in opensearch_build
before local E2E ITs on the sharing race path will pass). Migrate UBQ tests
that don't touch the security plugin's classification path continue to pass.

Signed-off-by: Darshit Chanpura <dchanp@amazon.com>
opensearch-project/security#6323 landed two framework capabilities that
render the alerting-side migration pre-processor obsolete:

  1. `ResourcePluginInfo.getResourceTypeForIndexOp` now iterates every
     registered provider on a shared index and picks the first one whose
     `typeField` extraction resolves — no top-level discriminator field
     needed on stored docs.
  2. `ResourceProvider` gained `ownerNamePath()` / `ownerBackendRolesPath()`
     methods. When declared, they override the request-level `username_path`
     / `backend_roles_path` on `POST /_plugins/_security/api/resources/migrate`
     for docs classified as that type — so a single migrate call attributes
     owners for both monitor and workflow docs sharing one index without any
     scratch fields.

Alerting cleanup:
- `AlertingResourceSharingExtension` declares per-type ownerNamePath and
  ownerBackendRolesPath (`/monitor/user/name`, `/workflow/user/name`, etc.).
- Delete `TransportMigrateToRscAction`, `RestMigrateToRscAction`,
  `MigrateToRscAction`/`Request`/`Response`, and unwire from `AlertingPlugin`
  — the endpoint no longer has a job to do.
- Delete the standalone `MigrateToRscRestApiIT` (unit tests for the deleted
  endpoint).
- Rename `MigrateToRscE2ERestApiIT` -> `RscMigrateE2ERestApiIT` and update it
  to call security's migrate endpoint directly. The security plugin now reads
  owner metadata from the per-provider paths so the request body no longer
  needs alerting-specific `_migration_*` scratch fields.

Verified locally against a security build carrying #6323: 1/1 E2E test
passes (create legacy monitor with RSC disabled → enable RSC → verify 403 →
run security migrate → verify alice recovers access).

Signed-off-by: Darshit Chanpura <dchanp@amazon.com>
…tale comment

- AlertingResourceSharingExtensionTests: previous test asserted a single
  provider was registered. Now that the extension registers both monitor and
  workflow, replace that assertion and add per-provider coverage that pins
  typeField (`monitor.type` / `workflow.type`) and the per-type owner paths
  (`/monitor/user/name`, `/workflow/user/name`, and matching backend_roles
  paths). These are the contract downstream security PR #6323 reads from.

- JobSweeper.isSweepableJobType: drop mention of `resource_type` and the
  alerting migration scratch fields from the skip-loop comment. Neither is
  ever emitted after the resource_type revert; only security-injected
  top-level fields like `all_shared_principals` are relevant here.

Signed-off-by: Darshit Chanpura <dchanp@amazon.com>
Two test-expectation adjustments after running the RSC IT suite against the
merged upstream security build (opensearch-project/security#6323):

- 'test owner sees delete performed by read-write shared user': under RSC
  the sharing entry is removed alongside the doc, so alice's GET hits the
  RSC gate (403) before the transport action can return NOT_FOUND (404).
  Accept either — both semantically mean 'no longer accessible'.

- 'test alerts inherit access when monitor is shared read-only': bob has
  a read-only share on alice's monitor but the alerts GET returns an empty
  result. `getAccessibleResourceIds` correctly reports the monitor as
  accessible, so this looks like DLS on the alerts index filtering bob out
  even though the alerts index isn't itself a resource-sharing-protected
  type. Marked @ignore with a FIXME; needs a separate investigation and
  possibly an alerts-index DLS exemption, but doesn't block the core RSC
  framework onboarding.

Also add a refresh of `.opendistro-alerting-config-sharing` inside
`shareResource` so downstream `getAccessibleResourceIds` searches see the
newly-written sharing entry without an ad-hoc sleep.

Signed-off-by: Darshit Chanpura <dchanp@amazon.com>
…ments

- Bump AlertIndicesIT verifyIndexSchemaVersion expectations 8->9 to match
  scheduled-jobs.json mapping bump for all_shared_principals.
- Restore stashContext().use wrapper around comment-action coroutine launch
  to preserve prior behavior; per-call putDataObjectStashed retained for
  ResourceIndexListener.

Signed-off-by: Darshit Chanpura <dchanp@amazon.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Onboard alerting plugin to Centralized Resource AuthZ framework

3 participants