From 3bfffc73d554eb9459f487583456296979020dd5 Mon Sep 17 00:00:00 2001 From: Mark Boyd Date: Mon, 26 Jan 2026 16:17:34 -0500 Subject: [PATCH 01/25] add setting for filter by backend roles access strategy Signed-off-by: Mark Boyd --- .../org/opensearch/alerting/settings/AlertingSettings.kt | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/alerting/src/main/kotlin/org/opensearch/alerting/settings/AlertingSettings.kt b/alerting/src/main/kotlin/org/opensearch/alerting/settings/AlertingSettings.kt index 2ae09aead..ed25e9905 100644 --- a/alerting/src/main/kotlin/org/opensearch/alerting/settings/AlertingSettings.kt +++ b/alerting/src/main/kotlin/org/opensearch/alerting/settings/AlertingSettings.kt @@ -302,5 +302,11 @@ class AlertingSettings { Setting.Property.NodeScope, Setting.Property.Dynamic ) + + val FILTER_BY_BACKEND_ROLES_ACCESS_STRATEGY = Setting.simpleString( + "plugins.alerting.filter_by_backend_roles_access_strategy", + "intersect", + Setting.Property.NodeScope, Setting.Property.Dynamic + ) } } From 7573a66a583f3e221af112c6b5d695b1edf17d8a Mon Sep 17 00:00:00 2001 From: Mark Boyd Date: Mon, 26 Jan 2026 16:18:00 -0500 Subject: [PATCH 02/25] stub out logic for checking resource access by filter by backend roles access strategy Signed-off-by: Mark Boyd --- .../alerting/transport/SecureTransportAction.kt | 16 +++++++++++++++- 1 file changed, 15 insertions(+), 1 deletion(-) diff --git a/alerting/src/main/kotlin/org/opensearch/alerting/transport/SecureTransportAction.kt b/alerting/src/main/kotlin/org/opensearch/alerting/transport/SecureTransportAction.kt index 54667e125..1ed5c031e 100644 --- a/alerting/src/main/kotlin/org/opensearch/alerting/transport/SecureTransportAction.kt +++ b/alerting/src/main/kotlin/org/opensearch/alerting/transport/SecureTransportAction.kt @@ -38,9 +38,13 @@ private val log = LogManager.getLogger(SecureTransportAction::class.java) interface SecureTransportAction { var filterByEnabled: Boolean + val filterByAccessStrategy: String fun listenFilterBySettingChange(clusterService: ClusterService) { clusterService.clusterSettings.addSettingsUpdateConsumer(AlertingSettings.FILTER_BY_BACKEND_ROLES) { filterByEnabled = it } + clusterService.clusterSettings.addSettingsUpdateConsumer(AlertingSettings.FILTER_BY_BACKEND_ROLES_ACCESS_STRATEGY) { + filterByAccessStrategy = it + } } fun readUserFromThreadContext(client: Client): User? { @@ -100,6 +104,16 @@ interface SecureTransportAction { return true } + fun checkUserBackendRolesAccess(userBackendRoles: List, resourceBackendRoles: List): Boolean { + if (filter_by_backend_roles_access_strategy == "intersect") { + return resourceBackendRoles.intersect(userBackendRoles).isEmpty() + } else if (filter_by_backend_roles_access_strategy == "all") { + return !resourceBackendRoles.equals(userBackendRoles) + } + // TODO: this should never be reached + return false + } + /** * If FilterBy is enabled, this function verifies that the requester user has FilterBy permissions to access * the resource. If FilterBy is disabled, we will assume the user has permissions and return true. @@ -122,7 +136,7 @@ interface SecureTransportAction { if ( resourceBackendRoles == null || requesterBackendRoles == null || - resourceBackendRoles.intersect(requesterBackendRoles).isEmpty() + checkUserBackendRolesAccess(requesterBackendRoles, resourceBackendRoles) ) { actionListener.onFailure( AlertingException.wrap( From 35e758fe7636d3645fe580c9ef455a9698d3287a Mon Sep 17 00:00:00 2001 From: Mark Boyd Date: Fri, 6 Mar 2026 15:27:58 -0500 Subject: [PATCH 03/25] adding integration test for getting monitor for user with multiple backend roles Signed-off-by: Mark Boyd --- .../transport/TransportGetMonitorAction.kt | 3 ++ .../alerting/AlertingRestTestCase.kt | 14 ++++++++ .../resthandler/SecureMonitorRestApiIT.kt | 34 +++++++++++++++++++ 3 files changed, 51 insertions(+) diff --git a/alerting/src/main/kotlin/org/opensearch/alerting/transport/TransportGetMonitorAction.kt b/alerting/src/main/kotlin/org/opensearch/alerting/transport/TransportGetMonitorAction.kt index 97e65ec50..4e4c00a22 100644 --- a/alerting/src/main/kotlin/org/opensearch/alerting/transport/TransportGetMonitorAction.kt +++ b/alerting/src/main/kotlin/org/opensearch/alerting/transport/TransportGetMonitorAction.kt @@ -90,6 +90,9 @@ class TransportGetMonitorAction @Inject constructor( return } + log.info("FOOBAR") + println("FOOBAR2") + /* * Remove security context before you call elasticsearch api's. By this time, permissions required * to call this api are validated. diff --git a/alerting/src/test/kotlin/org/opensearch/alerting/AlertingRestTestCase.kt b/alerting/src/test/kotlin/org/opensearch/alerting/AlertingRestTestCase.kt index 70a3d49bd..46697246c 100644 --- a/alerting/src/test/kotlin/org/opensearch/alerting/AlertingRestTestCase.kt +++ b/alerting/src/test/kotlin/org/opensearch/alerting/AlertingRestTestCase.kt @@ -1482,6 +1482,20 @@ abstract class AlertingRestTestCase : ODFERestTestCase() { assertEquals(updateResponse.statusLine.toString(), 200, updateResponse.statusLine.statusCode) } + fun setFilterByBackendRolesStrategy(strategy String) { + val updateResponse = client().makeRequest( + "PUT", "_cluster/settings", + emptyMap(), + StringEntity( + XContentFactory.jsonBuilder().startObject().field("persistent") + .startObject().field(AlertingSettings.FILTER_BY_BACKEND_ROLES_ACCESS_STRATEGY.key, strategy).endObject() + .endObject().string(), + ContentType.APPLICATION_JSON + ) + ) + assertEquals(updateResponse.statusLine.toString(), 200, updateResponse.statusLine.statusCode) + } + fun disableFilterBy() { val updateResponse = client().makeRequest( "PUT", "_cluster/settings", diff --git a/alerting/src/test/kotlin/org/opensearch/alerting/resthandler/SecureMonitorRestApiIT.kt b/alerting/src/test/kotlin/org/opensearch/alerting/resthandler/SecureMonitorRestApiIT.kt index 2f7fca92e..d6fad6561 100644 --- a/alerting/src/test/kotlin/org/opensearch/alerting/resthandler/SecureMonitorRestApiIT.kt +++ b/alerting/src/test/kotlin/org/opensearch/alerting/resthandler/SecureMonitorRestApiIT.kt @@ -43,6 +43,7 @@ import org.opensearch.alerting.randomDocumentLevelMonitor import org.opensearch.alerting.randomQueryLevelMonitor import org.opensearch.alerting.randomQueryLevelTrigger import org.opensearch.alerting.randomTemplateScript +import org.opensearch.alerting.randomUser import org.opensearch.client.Response import org.opensearch.client.ResponseException import org.opensearch.client.RestClient @@ -323,6 +324,39 @@ class SecureMonitorRestApiIT : AlertingRestTestCase() { } } + fun `test get monitor with enable filterBy for a user with multiple backend roles`() { + enableFilterBy() + setFilterByBackendRolesStrategy("all") + + createUserWithTestDataAndCustomRole( + user, + TEST_HR_INDEX, + TEST_HR_ROLE, + listOf(TEST_HR_BACKEND_ROLE, "foobar"), + getClusterPermissionsFromCustomRole(ALERTING_GET_MONITOR_ACCESS) + ) + + val user2 = randomUser(roles = listOf(TEST_HR_ROLE)) + + println("user name: ${user2.name}") + + val monitor = randomQueryLevelMonitor(user = user2) + + println("monitor ID: ${monitor.id}") + + try { + val getMonitorResponse = userClient?.makeRequest( + "GET", + "$ALERTING_BASE_URI/${monitor.id}", + null, + BasicHeader(HttpHeaders.CONTENT_TYPE, "application/json") + ) + assertEquals("Get monitor failed", RestStatus.OK, getMonitorResponse?.restStatus()) + } finally { + deleteRoleAndRoleMapping(TEST_HR_ROLE) + } + } + fun getDocs(response: Response?): Any? { val hits = createParser( XContentType.JSON.xContent(), From 9bee2317725c40d4b188b6803a97eeeb49d312a5 Mon Sep 17 00:00:00 2001 From: Mark Boyd Date: Fri, 6 Mar 2026 15:34:42 -0500 Subject: [PATCH 04/25] simplify code for testing Signed-off-by: Mark Boyd --- .../transport/SecureTransportAction.kt | 23 ++++++++++--------- .../alerting/AlertingRestTestCase.kt | 2 +- .../resthandler/SecureMonitorRestApiIT.kt | 2 +- 3 files changed, 14 insertions(+), 13 deletions(-) diff --git a/alerting/src/main/kotlin/org/opensearch/alerting/transport/SecureTransportAction.kt b/alerting/src/main/kotlin/org/opensearch/alerting/transport/SecureTransportAction.kt index 1ed5c031e..19007bae7 100644 --- a/alerting/src/main/kotlin/org/opensearch/alerting/transport/SecureTransportAction.kt +++ b/alerting/src/main/kotlin/org/opensearch/alerting/transport/SecureTransportAction.kt @@ -38,13 +38,13 @@ private val log = LogManager.getLogger(SecureTransportAction::class.java) interface SecureTransportAction { var filterByEnabled: Boolean - val filterByAccessStrategy: String + // var filterByAccessStrategy: String fun listenFilterBySettingChange(clusterService: ClusterService) { clusterService.clusterSettings.addSettingsUpdateConsumer(AlertingSettings.FILTER_BY_BACKEND_ROLES) { filterByEnabled = it } - clusterService.clusterSettings.addSettingsUpdateConsumer(AlertingSettings.FILTER_BY_BACKEND_ROLES_ACCESS_STRATEGY) { - filterByAccessStrategy = it - } + // clusterService.clusterSettings.addSettingsUpdateConsumer(AlertingSettings.FILTER_BY_BACKEND_ROLES_ACCESS_STRATEGY) { + // filterByAccessStrategy = it + // } } fun readUserFromThreadContext(client: Client): User? { @@ -105,13 +105,14 @@ interface SecureTransportAction { } fun checkUserBackendRolesAccess(userBackendRoles: List, resourceBackendRoles: List): Boolean { - if (filter_by_backend_roles_access_strategy == "intersect") { - return resourceBackendRoles.intersect(userBackendRoles).isEmpty() - } else if (filter_by_backend_roles_access_strategy == "all") { - return !resourceBackendRoles.equals(userBackendRoles) - } - // TODO: this should never be reached - return false + return !resourceBackendRoles.equals(userBackendRoles) + // if (filterByAccessStrategy == "intersect") { + // return resourceBackendRoles.intersect(userBackendRoles).isEmpty() + // } else if (filterByAccessStrategy == "all") { + // return !resourceBackendRoles.equals(userBackendRoles) + // } + // // TODO: this should never be reached + // return false } /** diff --git a/alerting/src/test/kotlin/org/opensearch/alerting/AlertingRestTestCase.kt b/alerting/src/test/kotlin/org/opensearch/alerting/AlertingRestTestCase.kt index 46697246c..f4426ecfe 100644 --- a/alerting/src/test/kotlin/org/opensearch/alerting/AlertingRestTestCase.kt +++ b/alerting/src/test/kotlin/org/opensearch/alerting/AlertingRestTestCase.kt @@ -1482,7 +1482,7 @@ abstract class AlertingRestTestCase : ODFERestTestCase() { assertEquals(updateResponse.statusLine.toString(), 200, updateResponse.statusLine.statusCode) } - fun setFilterByBackendRolesStrategy(strategy String) { + fun setFilterByBackendRolesStrategy(strategy: String) { val updateResponse = client().makeRequest( "PUT", "_cluster/settings", emptyMap(), diff --git a/alerting/src/test/kotlin/org/opensearch/alerting/resthandler/SecureMonitorRestApiIT.kt b/alerting/src/test/kotlin/org/opensearch/alerting/resthandler/SecureMonitorRestApiIT.kt index d6fad6561..c567a13f7 100644 --- a/alerting/src/test/kotlin/org/opensearch/alerting/resthandler/SecureMonitorRestApiIT.kt +++ b/alerting/src/test/kotlin/org/opensearch/alerting/resthandler/SecureMonitorRestApiIT.kt @@ -326,7 +326,7 @@ class SecureMonitorRestApiIT : AlertingRestTestCase() { fun `test get monitor with enable filterBy for a user with multiple backend roles`() { enableFilterBy() - setFilterByBackendRolesStrategy("all") + // setFilterByBackendRolesStrategy("all") createUserWithTestDataAndCustomRole( user, From d7a1e3705a7550391166199d1ec96f80757dd611 Mon Sep 17 00:00:00 2001 From: Mark Boyd Date: Fri, 6 Mar 2026 16:07:08 -0500 Subject: [PATCH 05/25] update logic for comparing backend roles when evaluating access to compare sorted lists Signed-off-by: Mark Boyd --- .../org/opensearch/alerting/transport/SecureTransportAction.kt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/alerting/src/main/kotlin/org/opensearch/alerting/transport/SecureTransportAction.kt b/alerting/src/main/kotlin/org/opensearch/alerting/transport/SecureTransportAction.kt index 19007bae7..3ccc76fac 100644 --- a/alerting/src/main/kotlin/org/opensearch/alerting/transport/SecureTransportAction.kt +++ b/alerting/src/main/kotlin/org/opensearch/alerting/transport/SecureTransportAction.kt @@ -105,7 +105,7 @@ interface SecureTransportAction { } fun checkUserBackendRolesAccess(userBackendRoles: List, resourceBackendRoles: List): Boolean { - return !resourceBackendRoles.equals(userBackendRoles) + return !resourceBackendRoles.sorted().equals(userBackendRoles.sorted()) // if (filterByAccessStrategy == "intersect") { // return resourceBackendRoles.intersect(userBackendRoles).isEmpty() // } else if (filterByAccessStrategy == "all") { From e1a21017edbd3fd8c30c616ff94df9706d892096 Mon Sep 17 00:00:00 2001 From: Mark Boyd Date: Fri, 6 Mar 2026 16:07:29 -0500 Subject: [PATCH 06/25] remove debugging code Signed-off-by: Mark Boyd --- .../opensearch/alerting/transport/TransportGetMonitorAction.kt | 3 --- 1 file changed, 3 deletions(-) diff --git a/alerting/src/main/kotlin/org/opensearch/alerting/transport/TransportGetMonitorAction.kt b/alerting/src/main/kotlin/org/opensearch/alerting/transport/TransportGetMonitorAction.kt index 4e4c00a22..97e65ec50 100644 --- a/alerting/src/main/kotlin/org/opensearch/alerting/transport/TransportGetMonitorAction.kt +++ b/alerting/src/main/kotlin/org/opensearch/alerting/transport/TransportGetMonitorAction.kt @@ -90,9 +90,6 @@ class TransportGetMonitorAction @Inject constructor( return } - log.info("FOOBAR") - println("FOOBAR2") - /* * Remove security context before you call elasticsearch api's. By this time, permissions required * to call this api are validated. From 148ef21b5453506613463ebba64994daf88eedd6 Mon Sep 17 00:00:00 2001 From: Mark Boyd Date: Fri, 6 Mar 2026 16:07:58 -0500 Subject: [PATCH 07/25] add tests for access to monitors when filterByAccessStrategy is all Signed-off-by: Mark Boyd --- .../resthandler/SecureMonitorRestApiIT.kt | 92 +++++++++++++++++-- 1 file changed, 82 insertions(+), 10 deletions(-) diff --git a/alerting/src/test/kotlin/org/opensearch/alerting/resthandler/SecureMonitorRestApiIT.kt b/alerting/src/test/kotlin/org/opensearch/alerting/resthandler/SecureMonitorRestApiIT.kt index c567a13f7..ad2d2880e 100644 --- a/alerting/src/test/kotlin/org/opensearch/alerting/resthandler/SecureMonitorRestApiIT.kt +++ b/alerting/src/test/kotlin/org/opensearch/alerting/resthandler/SecureMonitorRestApiIT.kt @@ -43,7 +43,6 @@ import org.opensearch.alerting.randomDocumentLevelMonitor import org.opensearch.alerting.randomQueryLevelMonitor import org.opensearch.alerting.randomQueryLevelTrigger import org.opensearch.alerting.randomTemplateScript -import org.opensearch.alerting.randomUser import org.opensearch.client.Response import org.opensearch.client.ResponseException import org.opensearch.client.RestClient @@ -324,36 +323,109 @@ class SecureMonitorRestApiIT : AlertingRestTestCase() { } } - fun `test get monitor with enable filterBy for a user with multiple backend roles`() { + fun `test get monitor for backend roles in same order where filterByAccessStrategy is all`() { enableFilterBy() + if (!isHttps()) { + // if security is disabled and filter by is enabled, we can't create monitor + // refer: `test create monitor with enable filter by` + return + } // setFilterByBackendRolesStrategy("all") - createUserWithTestDataAndCustomRole( + val monitor = randomQueryLevelMonitor(enabled = true) + + createUserWithRoles( user, + listOf(ALERTING_FULL_ACCESS_ROLE, READALL_AND_MONITOR_ROLE), + listOf(TEST_HR_BACKEND_ROLE, "role2"), + false + ) + + val createdMonitor = createMonitorWithClient(userClient!!, monitor = monitor, listOf(TEST_HR_BACKEND_ROLE, "role2")) + assertNotNull("The monitor was not created", createdMonitor) + + createUserRolesMapping(ALERTING_FULL_ACCESS_ROLE, arrayOf()) + createUserRolesMapping(READALL_AND_MONITOR_ROLE, arrayOf()) + + // getUser should have access to the monitor + val getUser = "getUser" + createUserWithTestDataAndCustomRole( + getUser, TEST_HR_INDEX, TEST_HR_ROLE, - listOf(TEST_HR_BACKEND_ROLE, "foobar"), + listOf(TEST_HR_BACKEND_ROLE, "role2"), getClusterPermissionsFromCustomRole(ALERTING_GET_MONITOR_ACCESS) ) + val getUserClient = SecureRestClientBuilder(clusterHosts.toTypedArray(), isHttps(), getUser, password) + .setSocketTimeout(60000) + .setConnectionRequestTimeout(180000) + .build() + + try { + val getMonitorResponse = getUserClient?.makeRequest( + "GET", + "$ALERTING_BASE_URI/${createdMonitor.id}", + null, + BasicHeader(HttpHeaders.CONTENT_TYPE, "application/json") + ) + assertEquals("Get monitor failed", RestStatus.OK, getMonitorResponse?.restStatus()) + } finally { + deleteRoleAndRoleMapping(TEST_HR_ROLE) + deleteUser(getUser) + getUserClient?.close() + } + } + + fun `test get monitor for backend roles in different order where filterByAccessStrategy is all`() { + enableFilterBy() + if (!isHttps()) { + // if security is disabled and filter by is enabled, we can't create monitor + // refer: `test create monitor with enable filter by` + return + } + // setFilterByBackendRolesStrategy("all") + + val monitor = randomQueryLevelMonitor(enabled = true) - val user2 = randomUser(roles = listOf(TEST_HR_ROLE)) + createUserWithRoles( + user, + listOf(ALERTING_FULL_ACCESS_ROLE, READALL_AND_MONITOR_ROLE), + listOf(TEST_HR_BACKEND_ROLE, "role2"), + false + ) - println("user name: ${user2.name}") + val createdMonitor = createMonitorWithClient(userClient!!, monitor = monitor, listOf(TEST_HR_BACKEND_ROLE, "role2")) + assertNotNull("The monitor was not created", createdMonitor) - val monitor = randomQueryLevelMonitor(user = user2) + createUserRolesMapping(ALERTING_FULL_ACCESS_ROLE, arrayOf()) + createUserRolesMapping(READALL_AND_MONITOR_ROLE, arrayOf()) - println("monitor ID: ${monitor.id}") + // getUser should have access to the monitor + val getUser = "getUser" + createUserWithTestDataAndCustomRole( + getUser, + TEST_HR_INDEX, + TEST_HR_ROLE, + listOf("role2", TEST_HR_BACKEND_ROLE), + getClusterPermissionsFromCustomRole(ALERTING_GET_MONITOR_ACCESS) + ) + val getUserClient = SecureRestClientBuilder(clusterHosts.toTypedArray(), isHttps(), getUser, password) + .setSocketTimeout(60000) + .setConnectionRequestTimeout(180000) + .build() try { - val getMonitorResponse = userClient?.makeRequest( + val getMonitorResponse = getUserClient?.makeRequest( "GET", - "$ALERTING_BASE_URI/${monitor.id}", + "$ALERTING_BASE_URI/${createdMonitor.id}", null, BasicHeader(HttpHeaders.CONTENT_TYPE, "application/json") ) assertEquals("Get monitor failed", RestStatus.OK, getMonitorResponse?.restStatus()) } finally { deleteRoleAndRoleMapping(TEST_HR_ROLE) + deleteUser(getUser) + getUserClient?.close() } } From 3b555994efd38077bdae75edcc7b8578c714e301 Mon Sep 17 00:00:00 2001 From: Mark Boyd Date: Fri, 6 Mar 2026 16:20:09 -0500 Subject: [PATCH 08/25] integrate filterbyAccessStrategy setting into transport classes Signed-off-by: Mark Boyd --- .../transport/SecureTransportAction.kt | 23 +++++++++---------- .../TransportDeleteAlertingCommentAction.kt | 1 + .../transport/TransportDeleteMonitorAction.kt | 1 + .../TransportDeleteWorkflowAction.kt | 1 + .../TransportDocLevelMonitorFanOutAction.kt | 1 + .../transport/TransportGetAlertsAction.kt | 1 + .../TransportGetDestinationsAction.kt | 1 + .../transport/TransportGetFindingsAction.kt | 1 + .../transport/TransportGetMonitorAction.kt | 1 + .../TransportGetRemoteIndexesAction.kt | 1 + .../transport/TransportGetWorkflowAction.kt | 1 + .../TransportGetWorkflowAlertsAction.kt | 3 +++ .../TransportIndexAlertingCommentAction.kt | 1 + .../transport/TransportIndexMonitorAction.kt | 1 + .../transport/TransportIndexWorkflowAction.kt | 3 +++ .../TransportSearchAlertingCommentAction.kt | 2 ++ .../transport/TransportSearchMonitorAction.kt | 3 +++ .../resthandler/SecureMonitorRestApiIT.kt | 3 ++- 18 files changed, 36 insertions(+), 13 deletions(-) diff --git a/alerting/src/main/kotlin/org/opensearch/alerting/transport/SecureTransportAction.kt b/alerting/src/main/kotlin/org/opensearch/alerting/transport/SecureTransportAction.kt index 3ccc76fac..c70c78315 100644 --- a/alerting/src/main/kotlin/org/opensearch/alerting/transport/SecureTransportAction.kt +++ b/alerting/src/main/kotlin/org/opensearch/alerting/transport/SecureTransportAction.kt @@ -38,13 +38,13 @@ private val log = LogManager.getLogger(SecureTransportAction::class.java) interface SecureTransportAction { var filterByEnabled: Boolean - // var filterByAccessStrategy: String + var filterByAccessStrategy: String fun listenFilterBySettingChange(clusterService: ClusterService) { clusterService.clusterSettings.addSettingsUpdateConsumer(AlertingSettings.FILTER_BY_BACKEND_ROLES) { filterByEnabled = it } - // clusterService.clusterSettings.addSettingsUpdateConsumer(AlertingSettings.FILTER_BY_BACKEND_ROLES_ACCESS_STRATEGY) { - // filterByAccessStrategy = it - // } + clusterService.clusterSettings.addSettingsUpdateConsumer(AlertingSettings.FILTER_BY_BACKEND_ROLES_ACCESS_STRATEGY) { + filterByAccessStrategy = it + } } fun readUserFromThreadContext(client: Client): User? { @@ -105,14 +105,13 @@ interface SecureTransportAction { } fun checkUserBackendRolesAccess(userBackendRoles: List, resourceBackendRoles: List): Boolean { - return !resourceBackendRoles.sorted().equals(userBackendRoles.sorted()) - // if (filterByAccessStrategy == "intersect") { - // return resourceBackendRoles.intersect(userBackendRoles).isEmpty() - // } else if (filterByAccessStrategy == "all") { - // return !resourceBackendRoles.equals(userBackendRoles) - // } - // // TODO: this should never be reached - // return false + if (filterByAccessStrategy == "intersect") { + return resourceBackendRoles.intersect(userBackendRoles).isEmpty() + } else if (filterByAccessStrategy == "all") { + return !resourceBackendRoles.sorted().equals(userBackendRoles.sorted()) + } + // TODO: this should never be reached + return false } /** diff --git a/alerting/src/main/kotlin/org/opensearch/alerting/transport/TransportDeleteAlertingCommentAction.kt b/alerting/src/main/kotlin/org/opensearch/alerting/transport/TransportDeleteAlertingCommentAction.kt index 09f8d2e00..bdc6fd817 100644 --- a/alerting/src/main/kotlin/org/opensearch/alerting/transport/TransportDeleteAlertingCommentAction.kt +++ b/alerting/src/main/kotlin/org/opensearch/alerting/transport/TransportDeleteAlertingCommentAction.kt @@ -60,6 +60,7 @@ class TransportDeleteAlertingCommentAction @Inject constructor( @Volatile private var alertingCommentsEnabled = AlertingSettings.ALERTING_COMMENTS_ENABLED.get(settings) @Volatile override var filterByEnabled = AlertingSettings.FILTER_BY_BACKEND_ROLES.get(settings) + @Volatile override var filterByAccessStrategy = AlertingSettings.FILTER_BY_BACKEND_ROLES_ACCESS_STRATEGY.get(settings) init { clusterService.clusterSettings.addSettingsUpdateConsumer(AlertingSettings.ALERTING_COMMENTS_ENABLED) { diff --git a/alerting/src/main/kotlin/org/opensearch/alerting/transport/TransportDeleteMonitorAction.kt b/alerting/src/main/kotlin/org/opensearch/alerting/transport/TransportDeleteMonitorAction.kt index b28311bd0..15567b381 100644 --- a/alerting/src/main/kotlin/org/opensearch/alerting/transport/TransportDeleteMonitorAction.kt +++ b/alerting/src/main/kotlin/org/opensearch/alerting/transport/TransportDeleteMonitorAction.kt @@ -56,6 +56,7 @@ class TransportDeleteMonitorAction @Inject constructor( SecureTransportAction { @Volatile override var filterByEnabled = AlertingSettings.FILTER_BY_BACKEND_ROLES.get(settings) + @Volatile override var filterByAccessStrategy = AlertingSettings.FILTER_BY_BACKEND_ROLES_ACCESS_STRATEGY.get(settings) init { listenFilterBySettingChange(clusterService) diff --git a/alerting/src/main/kotlin/org/opensearch/alerting/transport/TransportDeleteWorkflowAction.kt b/alerting/src/main/kotlin/org/opensearch/alerting/transport/TransportDeleteWorkflowAction.kt index bf0d44eab..cb976ed61 100644 --- a/alerting/src/main/kotlin/org/opensearch/alerting/transport/TransportDeleteWorkflowAction.kt +++ b/alerting/src/main/kotlin/org/opensearch/alerting/transport/TransportDeleteWorkflowAction.kt @@ -83,6 +83,7 @@ class TransportDeleteWorkflowAction @Inject constructor( private val log = LogManager.getLogger(javaClass) @Volatile override var filterByEnabled = AlertingSettings.FILTER_BY_BACKEND_ROLES.get(settings) + @Volatile override var filterByAccessStrategy = AlertingSettings.FILTER_BY_BACKEND_ROLES_ACCESS_STRATEGY.get(settings) init { listenFilterBySettingChange(clusterService) diff --git a/alerting/src/main/kotlin/org/opensearch/alerting/transport/TransportDocLevelMonitorFanOutAction.kt b/alerting/src/main/kotlin/org/opensearch/alerting/transport/TransportDocLevelMonitorFanOutAction.kt index 2e71cf5af..c49c76f01 100644 --- a/alerting/src/main/kotlin/org/opensearch/alerting/transport/TransportDocLevelMonitorFanOutAction.kt +++ b/alerting/src/main/kotlin/org/opensearch/alerting/transport/TransportDocLevelMonitorFanOutAction.kt @@ -196,6 +196,7 @@ class TransportDocLevelMonitorFanOutAction @Volatile override var filterByEnabled = AlertingSettings.FILTER_BY_BACKEND_ROLES.get(settings) + @Volatile override var filterByAccessStrategy = AlertingSettings.FILTER_BY_BACKEND_ROLES_ACCESS_STRATEGY.get(settings) override fun doExecute( task: Task, diff --git a/alerting/src/main/kotlin/org/opensearch/alerting/transport/TransportGetAlertsAction.kt b/alerting/src/main/kotlin/org/opensearch/alerting/transport/TransportGetAlertsAction.kt index 1fc3ef83a..0495af8b9 100644 --- a/alerting/src/main/kotlin/org/opensearch/alerting/transport/TransportGetAlertsAction.kt +++ b/alerting/src/main/kotlin/org/opensearch/alerting/transport/TransportGetAlertsAction.kt @@ -73,6 +73,7 @@ class TransportGetAlertsAction @Inject constructor( @Volatile override var filterByEnabled = AlertingSettings.FILTER_BY_BACKEND_ROLES.get(settings) + @Volatile override var filterByAccessStrategy = AlertingSettings.FILTER_BY_BACKEND_ROLES_ACCESS_STRATEGY.get(settings) init { listenFilterBySettingChange(clusterService) diff --git a/alerting/src/main/kotlin/org/opensearch/alerting/transport/TransportGetDestinationsAction.kt b/alerting/src/main/kotlin/org/opensearch/alerting/transport/TransportGetDestinationsAction.kt index 60e5edb9d..bb138d00f 100644 --- a/alerting/src/main/kotlin/org/opensearch/alerting/transport/TransportGetDestinationsAction.kt +++ b/alerting/src/main/kotlin/org/opensearch/alerting/transport/TransportGetDestinationsAction.kt @@ -57,6 +57,7 @@ class TransportGetDestinationsAction @Inject constructor( SecureTransportAction { @Volatile override var filterByEnabled = AlertingSettings.FILTER_BY_BACKEND_ROLES.get(settings) + @Volatile override var filterByAccessStrategy = AlertingSettings.FILTER_BY_BACKEND_ROLES_ACCESS_STRATEGY.get(settings) init { listenFilterBySettingChange(clusterService) diff --git a/alerting/src/main/kotlin/org/opensearch/alerting/transport/TransportGetFindingsAction.kt b/alerting/src/main/kotlin/org/opensearch/alerting/transport/TransportGetFindingsAction.kt index c4a1f2dbb..31fd2b7df 100644 --- a/alerting/src/main/kotlin/org/opensearch/alerting/transport/TransportGetFindingsAction.kt +++ b/alerting/src/main/kotlin/org/opensearch/alerting/transport/TransportGetFindingsAction.kt @@ -71,6 +71,7 @@ class TransportGetFindingsSearchAction @Inject constructor( SecureTransportAction { @Volatile override var filterByEnabled = AlertingSettings.FILTER_BY_BACKEND_ROLES.get(settings) + @Volatile override var filterByAccessStrategy = AlertingSettings.FILTER_BY_BACKEND_ROLES_ACCESS_STRATEGY.get(settings) init { listenFilterBySettingChange(clusterService) diff --git a/alerting/src/main/kotlin/org/opensearch/alerting/transport/TransportGetMonitorAction.kt b/alerting/src/main/kotlin/org/opensearch/alerting/transport/TransportGetMonitorAction.kt index 97e65ec50..17a732ac8 100644 --- a/alerting/src/main/kotlin/org/opensearch/alerting/transport/TransportGetMonitorAction.kt +++ b/alerting/src/main/kotlin/org/opensearch/alerting/transport/TransportGetMonitorAction.kt @@ -69,6 +69,7 @@ class TransportGetMonitorAction @Inject constructor( @Volatile override var filterByEnabled = AlertingSettings.FILTER_BY_BACKEND_ROLES.get(settings) + @Volatile override var filterByAccessStrategy = AlertingSettings.FILTER_BY_BACKEND_ROLES_ACCESS_STRATEGY.get(settings) init { listenFilterBySettingChange(clusterService) diff --git a/alerting/src/main/kotlin/org/opensearch/alerting/transport/TransportGetRemoteIndexesAction.kt b/alerting/src/main/kotlin/org/opensearch/alerting/transport/TransportGetRemoteIndexesAction.kt index b106903a8..02f110fc5 100644 --- a/alerting/src/main/kotlin/org/opensearch/alerting/transport/TransportGetRemoteIndexesAction.kt +++ b/alerting/src/main/kotlin/org/opensearch/alerting/transport/TransportGetRemoteIndexesAction.kt @@ -61,6 +61,7 @@ class TransportGetRemoteIndexesAction @Inject constructor( SecureTransportAction { @Volatile override var filterByEnabled = AlertingSettings.FILTER_BY_BACKEND_ROLES.get(settings) + @Volatile override var filterByAccessStrategy = AlertingSettings.FILTER_BY_BACKEND_ROLES_ACCESS_STRATEGY.get(settings) @Volatile private var remoteMonitoringEnabled = CROSS_CLUSTER_MONITORING_ENABLED.get(settings) diff --git a/alerting/src/main/kotlin/org/opensearch/alerting/transport/TransportGetWorkflowAction.kt b/alerting/src/main/kotlin/org/opensearch/alerting/transport/TransportGetWorkflowAction.kt index d0d3e45f7..11a0dd86d 100644 --- a/alerting/src/main/kotlin/org/opensearch/alerting/transport/TransportGetWorkflowAction.kt +++ b/alerting/src/main/kotlin/org/opensearch/alerting/transport/TransportGetWorkflowAction.kt @@ -48,6 +48,7 @@ class TransportGetWorkflowAction @Inject constructor( private val log = LogManager.getLogger(javaClass) @Volatile override var filterByEnabled = AlertingSettings.FILTER_BY_BACKEND_ROLES.get(settings) + @Volatile override var filterByAccessStrategy = AlertingSettings.FILTER_BY_BACKEND_ROLES_ACCESS_STRATEGY.get(settings) init { listenFilterBySettingChange(clusterService) diff --git a/alerting/src/main/kotlin/org/opensearch/alerting/transport/TransportGetWorkflowAlertsAction.kt b/alerting/src/main/kotlin/org/opensearch/alerting/transport/TransportGetWorkflowAlertsAction.kt index dd26bf032..c0c9633b5 100644 --- a/alerting/src/main/kotlin/org/opensearch/alerting/transport/TransportGetWorkflowAlertsAction.kt +++ b/alerting/src/main/kotlin/org/opensearch/alerting/transport/TransportGetWorkflowAlertsAction.kt @@ -69,6 +69,9 @@ class TransportGetWorkflowAlertsAction @Inject constructor( @Volatile override var filterByEnabled = AlertingSettings.FILTER_BY_BACKEND_ROLES.get(settings) + @Volatile + override var filterByAccessStrategy = AlertingSettings.FILTER_BY_BACKEND_ROLES_ACCESS_STRATEGY.get(settings) + @Volatile private var isAlertHistoryEnabled = AlertingSettings.ALERT_HISTORY_ENABLED.get(settings) diff --git a/alerting/src/main/kotlin/org/opensearch/alerting/transport/TransportIndexAlertingCommentAction.kt b/alerting/src/main/kotlin/org/opensearch/alerting/transport/TransportIndexAlertingCommentAction.kt index 8592c505c..6b912f0cc 100644 --- a/alerting/src/main/kotlin/org/opensearch/alerting/transport/TransportIndexAlertingCommentAction.kt +++ b/alerting/src/main/kotlin/org/opensearch/alerting/transport/TransportIndexAlertingCommentAction.kt @@ -86,6 +86,7 @@ constructor( @Volatile private var indexTimeout = INDEX_TIMEOUT.get(settings) @Volatile override var filterByEnabled = AlertingSettings.FILTER_BY_BACKEND_ROLES.get(settings) + @Volatile override var filterByAccessStrategy = AlertingSettings.FILTER_BY_BACKEND_ROLES_ACCESS_STRATEGY.get(settings) init { clusterService.clusterSettings.addSettingsUpdateConsumer(ALERTING_COMMENTS_ENABLED) { alertingCommentsEnabled = it } diff --git a/alerting/src/main/kotlin/org/opensearch/alerting/transport/TransportIndexMonitorAction.kt b/alerting/src/main/kotlin/org/opensearch/alerting/transport/TransportIndexMonitorAction.kt index c36fc4957..a9ae56c54 100644 --- a/alerting/src/main/kotlin/org/opensearch/alerting/transport/TransportIndexMonitorAction.kt +++ b/alerting/src/main/kotlin/org/opensearch/alerting/transport/TransportIndexMonitorAction.kt @@ -111,6 +111,7 @@ class TransportIndexMonitorAction @Inject constructor( @Volatile private var maxActionThrottle = MAX_ACTION_THROTTLE_VALUE.get(settings) @Volatile private var allowList = ALLOW_LIST.get(settings) @Volatile override var filterByEnabled = AlertingSettings.FILTER_BY_BACKEND_ROLES.get(settings) + @Volatile override var filterByAccessStrategy = AlertingSettings.FILTER_BY_BACKEND_ROLES_ACCESS_STRATEGY.get(settings) init { clusterService.clusterSettings.addSettingsUpdateConsumer(ALERTING_MAX_MONITORS) { maxMonitors = it } diff --git a/alerting/src/main/kotlin/org/opensearch/alerting/transport/TransportIndexWorkflowAction.kt b/alerting/src/main/kotlin/org/opensearch/alerting/transport/TransportIndexWorkflowAction.kt index 3c80af129..1d3ef6663 100644 --- a/alerting/src/main/kotlin/org/opensearch/alerting/transport/TransportIndexWorkflowAction.kt +++ b/alerting/src/main/kotlin/org/opensearch/alerting/transport/TransportIndexWorkflowAction.kt @@ -119,6 +119,9 @@ class TransportIndexWorkflowAction @Inject constructor( @Volatile override var filterByEnabled = AlertingSettings.FILTER_BY_BACKEND_ROLES.get(settings) + @Volatile + override var filterByAccessStrategy = AlertingSettings.FILTER_BY_BACKEND_ROLES_ACCESS_STRATEGY.get(settings) + init { clusterService.clusterSettings.addSettingsUpdateConsumer(ALERTING_MAX_MONITORS) { maxMonitors = it } clusterService.clusterSettings.addSettingsUpdateConsumer(REQUEST_TIMEOUT) { requestTimeout = it } diff --git a/alerting/src/main/kotlin/org/opensearch/alerting/transport/TransportSearchAlertingCommentAction.kt b/alerting/src/main/kotlin/org/opensearch/alerting/transport/TransportSearchAlertingCommentAction.kt index 9e8c3d153..f41a9b6de 100644 --- a/alerting/src/main/kotlin/org/opensearch/alerting/transport/TransportSearchAlertingCommentAction.kt +++ b/alerting/src/main/kotlin/org/opensearch/alerting/transport/TransportSearchAlertingCommentAction.kt @@ -63,6 +63,8 @@ class TransportSearchAlertingCommentAction @Inject constructor( @Volatile private var alertingCommentsEnabled = AlertingSettings.ALERTING_COMMENTS_ENABLED.get(settings) @Volatile override var filterByEnabled: Boolean = AlertingSettings.FILTER_BY_BACKEND_ROLES.get(settings) + @Volatile override var filterByAccessStrategy: String = AlertingSettings.FILTER_BY_BACKEND_ROLES_ACCESS_STRATEGY.get(settings) + init { clusterService.clusterSettings.addSettingsUpdateConsumer(AlertingSettings.ALERTING_COMMENTS_ENABLED) { alertingCommentsEnabled = it diff --git a/alerting/src/main/kotlin/org/opensearch/alerting/transport/TransportSearchMonitorAction.kt b/alerting/src/main/kotlin/org/opensearch/alerting/transport/TransportSearchMonitorAction.kt index 21c633553..83323f5e1 100644 --- a/alerting/src/main/kotlin/org/opensearch/alerting/transport/TransportSearchMonitorAction.kt +++ b/alerting/src/main/kotlin/org/opensearch/alerting/transport/TransportSearchMonitorAction.kt @@ -62,6 +62,9 @@ class TransportSearchMonitorAction @Inject constructor( SecureTransportAction { @Volatile override var filterByEnabled: Boolean = AlertingSettings.FILTER_BY_BACKEND_ROLES.get(settings) + @Volatile + override var filterByAccessStrategy: String = AlertingSettings.FILTER_BY_BACKEND_ROLES_ACCESS_STRATEGY.get(settings) + init { listenFilterBySettingChange(clusterService) } diff --git a/alerting/src/test/kotlin/org/opensearch/alerting/resthandler/SecureMonitorRestApiIT.kt b/alerting/src/test/kotlin/org/opensearch/alerting/resthandler/SecureMonitorRestApiIT.kt index ad2d2880e..899582127 100644 --- a/alerting/src/test/kotlin/org/opensearch/alerting/resthandler/SecureMonitorRestApiIT.kt +++ b/alerting/src/test/kotlin/org/opensearch/alerting/resthandler/SecureMonitorRestApiIT.kt @@ -330,7 +330,7 @@ class SecureMonitorRestApiIT : AlertingRestTestCase() { // refer: `test create monitor with enable filter by` return } - // setFilterByBackendRolesStrategy("all") + setFilterByBackendRolesStrategy("all") val monitor = randomQueryLevelMonitor(enabled = true) @@ -406,6 +406,7 @@ class SecureMonitorRestApiIT : AlertingRestTestCase() { getUser, TEST_HR_INDEX, TEST_HR_ROLE, + // intentionally change order of backend roles from order on monitor to ensure order doesn't matter listOf("role2", TEST_HR_BACKEND_ROLE), getClusterPermissionsFromCustomRole(ALERTING_GET_MONITOR_ACCESS) ) From 9d17d0c89238546c7e05928da48da6d365f1332d Mon Sep 17 00:00:00 2001 From: Mark Boyd Date: Fri, 6 Mar 2026 16:22:10 -0500 Subject: [PATCH 09/25] add filterByAccessStrategy setting to plugin Signed-off-by: Mark Boyd --- .../src/main/kotlin/org/opensearch/alerting/AlertingPlugin.kt | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/alerting/src/main/kotlin/org/opensearch/alerting/AlertingPlugin.kt b/alerting/src/main/kotlin/org/opensearch/alerting/AlertingPlugin.kt index 3dc2cd571..0d3df167b 100644 --- a/alerting/src/main/kotlin/org/opensearch/alerting/AlertingPlugin.kt +++ b/alerting/src/main/kotlin/org/opensearch/alerting/AlertingPlugin.kt @@ -432,7 +432,8 @@ internal class AlertingPlugin : PainlessExtension, ActionPlugin, ScriptPlugin, R AlertingSettings.COMMENTS_MAX_CONTENT_SIZE, AlertingSettings.MAX_COMMENTS_PER_ALERT, AlertingSettings.MAX_COMMENTS_PER_NOTIFICATION, - AlertingSettings.NOTIFICATION_CONTEXT_RESULTS_ALLOWED_ROLES + AlertingSettings.NOTIFICATION_CONTEXT_RESULTS_ALLOWED_ROLES, + AlertingSettings.FILTER_BY_BACKEND_ROLES_ACCESS_STRATEGY ) } From f8b3d3ae956130dd2103412452b40b7f7e2de1b6 Mon Sep 17 00:00:00 2001 From: Mark Boyd Date: Fri, 6 Mar 2026 17:35:43 -0500 Subject: [PATCH 10/25] use enum and validator for filter_by_backend_roles_access_strategy setting Signed-off-by: Mark Boyd --- .../alerting/settings/AlertingSettings.kt | 6 +++-- .../FilterByBackendRolesAccessStrategy.kt | 21 ++++++++++++++++++ ...erByBackendRolesAccessStrategyValidator.kt | 22 +++++++++++++++++++ 3 files changed, 47 insertions(+), 2 deletions(-) create mode 100644 alerting/src/main/kotlin/org/opensearch/alerting/settings/FilterByBackendRolesAccessStrategy.kt create mode 100644 alerting/src/main/kotlin/org/opensearch/alerting/settings/FilterByBackendRolesAccessStrategyValidator.kt diff --git a/alerting/src/main/kotlin/org/opensearch/alerting/settings/AlertingSettings.kt b/alerting/src/main/kotlin/org/opensearch/alerting/settings/AlertingSettings.kt index ed25e9905..91fe8745e 100644 --- a/alerting/src/main/kotlin/org/opensearch/alerting/settings/AlertingSettings.kt +++ b/alerting/src/main/kotlin/org/opensearch/alerting/settings/AlertingSettings.kt @@ -305,8 +305,10 @@ class AlertingSettings { val FILTER_BY_BACKEND_ROLES_ACCESS_STRATEGY = Setting.simpleString( "plugins.alerting.filter_by_backend_roles_access_strategy", - "intersect", - Setting.Property.NodeScope, Setting.Property.Dynamic + FilterByBackendRolesAccessStrategy.INTERSECT.strategy, + FilterByBackendRolesAccessStrategyValidator(), + Setting.Property.NodeScope, + Setting.Property.Dynamic ) } } diff --git a/alerting/src/main/kotlin/org/opensearch/alerting/settings/FilterByBackendRolesAccessStrategy.kt b/alerting/src/main/kotlin/org/opensearch/alerting/settings/FilterByBackendRolesAccessStrategy.kt new file mode 100644 index 000000000..5da052742 --- /dev/null +++ b/alerting/src/main/kotlin/org/opensearch/alerting/settings/FilterByBackendRolesAccessStrategy.kt @@ -0,0 +1,21 @@ +/* + * Copyright OpenSearch Contributors + * SPDX-License-Identifier: Apache-2.0 + */ + +package org.opensearch.alerting.settings + +/** + * Defines the FilterByBackendRolesAccessStrategy + */ +enum class FilterByBackendRolesAccessStrategy(val strategy: String) { + /** + * Backend roles must intersect to have access + */ + INTERSECT("intersect"), + + /** + * Backend roles must be exactly equal to have access + */ + ALL("all"), +} diff --git a/alerting/src/main/kotlin/org/opensearch/alerting/settings/FilterByBackendRolesAccessStrategyValidator.kt b/alerting/src/main/kotlin/org/opensearch/alerting/settings/FilterByBackendRolesAccessStrategyValidator.kt new file mode 100644 index 000000000..5ba757d49 --- /dev/null +++ b/alerting/src/main/kotlin/org/opensearch/alerting/settings/FilterByBackendRolesAccessStrategyValidator.kt @@ -0,0 +1,22 @@ +/* + * Copyright OpenSearch Contributors + * SPDX-License-Identifier: Apache-2.0 + */ + +package org.opensearch.alerting.settings + +import org.opensearch.common.settings.Setting + +class FilterByBackendRolesAccessStrategyValidator : Setting.Validator { + override fun validate(strategy: String) { + val allStrategies: List = FilterByBackendRolesAccessStrategy.entries.map { it.strategy } + + when (strategy) { + FilterByBackendRolesAccessStrategy.INTERSECT.strategy, + FilterByBackendRolesAccessStrategy.ALL.strategy -> {} + else -> throw IllegalArgumentException( + "Setting value must be one of [$allStrategies]" + ) + } + } +} From 706f0a11d04a475444497b63ec57d09d39287d6b Mon Sep 17 00:00:00 2001 From: Mark Boyd Date: Fri, 6 Mar 2026 17:41:13 -0500 Subject: [PATCH 11/25] update code to use enum for filter by access strategy settings when comparing Signed-off-by: Mark Boyd --- .../alerting/transport/SecureTransportAction.kt | 11 ++++++++--- 1 file changed, 8 insertions(+), 3 deletions(-) diff --git a/alerting/src/main/kotlin/org/opensearch/alerting/transport/SecureTransportAction.kt b/alerting/src/main/kotlin/org/opensearch/alerting/transport/SecureTransportAction.kt index c70c78315..e8746fe07 100644 --- a/alerting/src/main/kotlin/org/opensearch/alerting/transport/SecureTransportAction.kt +++ b/alerting/src/main/kotlin/org/opensearch/alerting/transport/SecureTransportAction.kt @@ -8,6 +8,7 @@ package org.opensearch.alerting.transport import org.apache.logging.log4j.LogManager import org.opensearch.OpenSearchStatusException import org.opensearch.alerting.settings.AlertingSettings +import org.opensearch.alerting.settings.FilterByBackendRolesAccessStrategy import org.opensearch.cluster.service.ClusterService import org.opensearch.commons.ConfigConstants import org.opensearch.commons.alerting.util.AlertingException @@ -105,12 +106,16 @@ interface SecureTransportAction { } fun checkUserBackendRolesAccess(userBackendRoles: List, resourceBackendRoles: List): Boolean { - if (filterByAccessStrategy == "intersect") { + if (filterByAccessStrategy == FilterByBackendRolesAccessStrategy.INTERSECT.strategy) { return resourceBackendRoles.intersect(userBackendRoles).isEmpty() - } else if (filterByAccessStrategy == "all") { + } else if (filterByAccessStrategy == FilterByBackendRolesAccessStrategy.ALL.strategy) { return !resourceBackendRoles.sorted().equals(userBackendRoles.sorted()) } - // TODO: this should never be reached + // Not sure if this is necessary, since there is a validator + // on the setting itself + throw IllegalArgumentException( + "Invalid filter by access strategy: $filterByAccessStrategy" + ) return false } From 3bd3af46ca690dd97d78f69c3bc64239adc6b52e Mon Sep 17 00:00:00 2001 From: Mark Boyd Date: Fri, 6 Mar 2026 17:41:33 -0500 Subject: [PATCH 12/25] update integration test to set filter by backend roles strategy Signed-off-by: Mark Boyd --- .../opensearch/alerting/resthandler/SecureMonitorRestApiIT.kt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/alerting/src/test/kotlin/org/opensearch/alerting/resthandler/SecureMonitorRestApiIT.kt b/alerting/src/test/kotlin/org/opensearch/alerting/resthandler/SecureMonitorRestApiIT.kt index 899582127..11ef05386 100644 --- a/alerting/src/test/kotlin/org/opensearch/alerting/resthandler/SecureMonitorRestApiIT.kt +++ b/alerting/src/test/kotlin/org/opensearch/alerting/resthandler/SecureMonitorRestApiIT.kt @@ -383,7 +383,7 @@ class SecureMonitorRestApiIT : AlertingRestTestCase() { // refer: `test create monitor with enable filter by` return } - // setFilterByBackendRolesStrategy("all") + setFilterByBackendRolesStrategy("all") val monitor = randomQueryLevelMonitor(enabled = true) From 2736855def0724ad37599077e79175b660f13d1b Mon Sep 17 00:00:00 2001 From: Mark Boyd Date: Fri, 6 Mar 2026 17:43:22 -0500 Subject: [PATCH 13/25] update test names Signed-off-by: Mark Boyd --- .../opensearch/alerting/resthandler/SecureMonitorRestApiIT.kt | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/alerting/src/test/kotlin/org/opensearch/alerting/resthandler/SecureMonitorRestApiIT.kt b/alerting/src/test/kotlin/org/opensearch/alerting/resthandler/SecureMonitorRestApiIT.kt index 11ef05386..da771ca72 100644 --- a/alerting/src/test/kotlin/org/opensearch/alerting/resthandler/SecureMonitorRestApiIT.kt +++ b/alerting/src/test/kotlin/org/opensearch/alerting/resthandler/SecureMonitorRestApiIT.kt @@ -323,7 +323,7 @@ class SecureMonitorRestApiIT : AlertingRestTestCase() { } } - fun `test get monitor for backend roles in same order where filterByAccessStrategy is all`() { + fun `test get monitor succeeds for same backend roles in same order when filterByAccessStrategy is all`() { enableFilterBy() if (!isHttps()) { // if security is disabled and filter by is enabled, we can't create monitor @@ -376,7 +376,7 @@ class SecureMonitorRestApiIT : AlertingRestTestCase() { } } - fun `test get monitor for backend roles in different order where filterByAccessStrategy is all`() { + fun `test get monitor succeeds for same backend roles in different order when filterByAccessStrategy is all`() { enableFilterBy() if (!isHttps()) { // if security is disabled and filter by is enabled, we can't create monitor From b8f2615a89bbb83757ddc98bae2385b4a3979799 Mon Sep 17 00:00:00 2001 From: Mark Boyd Date: Fri, 6 Mar 2026 17:48:46 -0500 Subject: [PATCH 14/25] add test for get monitor access denied when filter by access strategy is all Signed-off-by: Mark Boyd --- .../resthandler/SecureMonitorRestApiIT.kt | 56 +++++++++++++++++++ 1 file changed, 56 insertions(+) diff --git a/alerting/src/test/kotlin/org/opensearch/alerting/resthandler/SecureMonitorRestApiIT.kt b/alerting/src/test/kotlin/org/opensearch/alerting/resthandler/SecureMonitorRestApiIT.kt index da771ca72..b2d8f387a 100644 --- a/alerting/src/test/kotlin/org/opensearch/alerting/resthandler/SecureMonitorRestApiIT.kt +++ b/alerting/src/test/kotlin/org/opensearch/alerting/resthandler/SecureMonitorRestApiIT.kt @@ -430,6 +430,62 @@ class SecureMonitorRestApiIT : AlertingRestTestCase() { } } + fun `test get monitor fails for different backend roles when filterByAccessStrategy is all`() { + enableFilterBy() + if (!isHttps()) { + // if security is disabled and filter by is enabled, we can't create monitor + // refer: `test create monitor with enable filter by` + return + } + setFilterByBackendRolesStrategy("all") + + val monitor = randomQueryLevelMonitor(enabled = true) + + createUserWithRoles( + user, + listOf(ALERTING_FULL_ACCESS_ROLE, READALL_AND_MONITOR_ROLE), + listOf(TEST_HR_BACKEND_ROLE, "role2"), + false + ) + + val createdMonitor = createMonitorWithClient(userClient!!, monitor = monitor, listOf(TEST_HR_BACKEND_ROLE, "role2")) + assertNotNull("The monitor was not created", createdMonitor) + + createUserRolesMapping(ALERTING_FULL_ACCESS_ROLE, arrayOf()) + createUserRolesMapping(READALL_AND_MONITOR_ROLE, arrayOf()) + + // getUser should NOT have access to the monitor + val getUser = "getUser" + createUserWithTestDataAndCustomRole( + getUser, + TEST_HR_INDEX, + TEST_HR_ROLE, + // roles are different from monitor backend roles specified above + listOf(TEST_HR_BACKEND_ROLE, "role1"), + getClusterPermissionsFromCustomRole(ALERTING_GET_MONITOR_ACCESS) + ) + val getUserClient = SecureRestClientBuilder(clusterHosts.toTypedArray(), isHttps(), getUser, password) + .setSocketTimeout(60000) + .setConnectionRequestTimeout(180000) + .build() + + try { + val getMonitorResponse = getUserClient?.makeRequest( + "GET", + "$ALERTING_BASE_URI/${createdMonitor.id}", + null, + BasicHeader(HttpHeaders.CONTENT_TYPE, "application/json") + ) + fail("Expected Forbidden exception") + } catch (e: ResponseException) { + assertEquals("Get monitor failed", RestStatus.FORBIDDEN.status, e.response.statusLine.statusCode) + } finally { + deleteRoleAndRoleMapping(TEST_HR_ROLE) + deleteUser(getUser) + getUserClient?.close() + } + } + fun getDocs(response: Response?): Any? { val hits = createParser( XContentType.JSON.xContent(), From 0b802699ca938f1f6adc7bcb53c2f403ccf501bf Mon Sep 17 00:00:00 2001 From: Mark Boyd Date: Wed, 11 Mar 2026 12:31:02 -0400 Subject: [PATCH 15/25] rename checkUserBackendRolesAccess -> doUserBackendRolesMatchResource to clarify logic behavior Signed-off-by: Mark Boyd --- .../alerting/transport/SecureTransportAction.kt | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/alerting/src/main/kotlin/org/opensearch/alerting/transport/SecureTransportAction.kt b/alerting/src/main/kotlin/org/opensearch/alerting/transport/SecureTransportAction.kt index e8746fe07..b36a4fc02 100644 --- a/alerting/src/main/kotlin/org/opensearch/alerting/transport/SecureTransportAction.kt +++ b/alerting/src/main/kotlin/org/opensearch/alerting/transport/SecureTransportAction.kt @@ -105,11 +105,11 @@ interface SecureTransportAction { return true } - fun checkUserBackendRolesAccess(userBackendRoles: List, resourceBackendRoles: List): Boolean { + fun doUserBackendRolesMatchResource(userBackendRoles: List, resourceBackendRoles: List): Boolean { if (filterByAccessStrategy == FilterByBackendRolesAccessStrategy.INTERSECT.strategy) { - return resourceBackendRoles.intersect(userBackendRoles).isEmpty() + return resourceBackendRoles.any { it in resourceBackendRoles } } else if (filterByAccessStrategy == FilterByBackendRolesAccessStrategy.ALL.strategy) { - return !resourceBackendRoles.sorted().equals(userBackendRoles.sorted()) + return resourceBackendRoles.sorted().equals(userBackendRoles.sorted()) } // Not sure if this is necessary, since there is a validator // on the setting itself @@ -141,7 +141,7 @@ interface SecureTransportAction { if ( resourceBackendRoles == null || requesterBackendRoles == null || - checkUserBackendRolesAccess(requesterBackendRoles, resourceBackendRoles) + !doUserBackendRolesMatchResource(requesterBackendRoles, resourceBackendRoles) ) { actionListener.onFailure( AlertingException.wrap( From f20c064276efc93f7896ec1a34a6267280527f22 Mon Sep 17 00:00:00 2001 From: Mark Boyd Date: Wed, 11 Mar 2026 14:43:19 -0400 Subject: [PATCH 16/25] fix typo in SecureTransPortAction.doUserBackendRolesMatchResource() logic Signed-off-by: Mark Boyd --- .../org/opensearch/alerting/transport/SecureTransportAction.kt | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/alerting/src/main/kotlin/org/opensearch/alerting/transport/SecureTransportAction.kt b/alerting/src/main/kotlin/org/opensearch/alerting/transport/SecureTransportAction.kt index b36a4fc02..1a83e244b 100644 --- a/alerting/src/main/kotlin/org/opensearch/alerting/transport/SecureTransportAction.kt +++ b/alerting/src/main/kotlin/org/opensearch/alerting/transport/SecureTransportAction.kt @@ -107,7 +107,7 @@ interface SecureTransportAction { fun doUserBackendRolesMatchResource(userBackendRoles: List, resourceBackendRoles: List): Boolean { if (filterByAccessStrategy == FilterByBackendRolesAccessStrategy.INTERSECT.strategy) { - return resourceBackendRoles.any { it in resourceBackendRoles } + return resourceBackendRoles.any { it in userBackendRoles } } else if (filterByAccessStrategy == FilterByBackendRolesAccessStrategy.ALL.strategy) { return resourceBackendRoles.sorted().equals(userBackendRoles.sorted()) } @@ -116,7 +116,6 @@ interface SecureTransportAction { throw IllegalArgumentException( "Invalid filter by access strategy: $filterByAccessStrategy" ) - return false } /** From d2b10ae05f7bc73c07de801eea64d69f2362f8c0 Mon Sep 17 00:00:00 2001 From: Mark Boyd Date: Wed, 11 Mar 2026 14:46:05 -0400 Subject: [PATCH 17/25] add check for filter by backend roles access strategy setting in alerting settings tests Signed-off-by: Mark Boyd --- .../org/opensearch/alerting/settings/AlertingSettingsTests.kt | 1 + 1 file changed, 1 insertion(+) diff --git a/alerting/src/test/kotlin/org/opensearch/alerting/settings/AlertingSettingsTests.kt b/alerting/src/test/kotlin/org/opensearch/alerting/settings/AlertingSettingsTests.kt index 6ee8c4997..011f685f8 100644 --- a/alerting/src/test/kotlin/org/opensearch/alerting/settings/AlertingSettingsTests.kt +++ b/alerting/src/test/kotlin/org/opensearch/alerting/settings/AlertingSettingsTests.kt @@ -80,6 +80,7 @@ class AlertingSettingsTests : OpenSearchTestCase() { AlertingSettings.REQUEST_TIMEOUT, AlertingSettings.MAX_ACTION_THROTTLE_VALUE, AlertingSettings.FILTER_BY_BACKEND_ROLES, + AlertingSettings.FILTER_BY_BACKEND_ROLES_ACCESS_STRATEGY, ScheduledJobSettings.SWEEP_PERIOD, ScheduledJobSettings.SWEEP_PAGE_SIZE, ScheduledJobSettings.SWEEP_BACKOFF_RETRY_COUNT, From 5af7b915d01584d57fdbada431f7baa71d03a194 Mon Sep 17 00:00:00 2001 From: Mark Boyd Date: Tue, 31 Mar 2026 10:21:38 -0400 Subject: [PATCH 18/25] add new filter by backend roles strategy of "exact" where backend roles must match exactly Signed-off-by: Mark Boyd --- .../settings/FilterByBackendRolesAccessStrategy.kt | 7 ++++++- .../FilterByBackendRolesAccessStrategyValidator.kt | 5 +++-- 2 files changed, 9 insertions(+), 3 deletions(-) diff --git a/alerting/src/main/kotlin/org/opensearch/alerting/settings/FilterByBackendRolesAccessStrategy.kt b/alerting/src/main/kotlin/org/opensearch/alerting/settings/FilterByBackendRolesAccessStrategy.kt index 5da052742..7359a02b4 100644 --- a/alerting/src/main/kotlin/org/opensearch/alerting/settings/FilterByBackendRolesAccessStrategy.kt +++ b/alerting/src/main/kotlin/org/opensearch/alerting/settings/FilterByBackendRolesAccessStrategy.kt @@ -9,13 +9,18 @@ package org.opensearch.alerting.settings * Defines the FilterByBackendRolesAccessStrategy */ enum class FilterByBackendRolesAccessStrategy(val strategy: String) { + /** + * Backend roles must be exactly equal to have access + */ + EXACT("exact"), + /** * Backend roles must intersect to have access */ INTERSECT("intersect"), /** - * Backend roles must be exactly equal to have access + * User backend roles must contain all resource backend roles to have access */ ALL("all"), } diff --git a/alerting/src/main/kotlin/org/opensearch/alerting/settings/FilterByBackendRolesAccessStrategyValidator.kt b/alerting/src/main/kotlin/org/opensearch/alerting/settings/FilterByBackendRolesAccessStrategyValidator.kt index 5ba757d49..3c5825f5e 100644 --- a/alerting/src/main/kotlin/org/opensearch/alerting/settings/FilterByBackendRolesAccessStrategyValidator.kt +++ b/alerting/src/main/kotlin/org/opensearch/alerting/settings/FilterByBackendRolesAccessStrategyValidator.kt @@ -12,8 +12,9 @@ class FilterByBackendRolesAccessStrategyValidator : Setting.Validator { val allStrategies: List = FilterByBackendRolesAccessStrategy.entries.map { it.strategy } when (strategy) { - FilterByBackendRolesAccessStrategy.INTERSECT.strategy, - FilterByBackendRolesAccessStrategy.ALL.strategy -> {} + FilterByBackendRolesAccessStrategy.ALL.strategy + FilterByBackendRolesAccessStrategy.EXACT.strategy, + FilterByBackendRolesAccessStrategy.INTERSECT.strategy -> {} else -> throw IllegalArgumentException( "Setting value must be one of [$allStrategies]" ) From e37ced2a103262d8dfbecf058ec71609a6d495e9 Mon Sep 17 00:00:00 2001 From: Mark Boyd Date: Tue, 31 Mar 2026 10:34:49 -0400 Subject: [PATCH 19/25] fix code formatting errors Signed-off-by: Mark Boyd --- .../settings/FilterByBackendRolesAccessStrategy.kt | 10 +++++----- .../FilterByBackendRolesAccessStrategyValidator.kt | 2 +- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/alerting/src/main/kotlin/org/opensearch/alerting/settings/FilterByBackendRolesAccessStrategy.kt b/alerting/src/main/kotlin/org/opensearch/alerting/settings/FilterByBackendRolesAccessStrategy.kt index 7359a02b4..b21d23187 100644 --- a/alerting/src/main/kotlin/org/opensearch/alerting/settings/FilterByBackendRolesAccessStrategy.kt +++ b/alerting/src/main/kotlin/org/opensearch/alerting/settings/FilterByBackendRolesAccessStrategy.kt @@ -9,6 +9,11 @@ package org.opensearch.alerting.settings * Defines the FilterByBackendRolesAccessStrategy */ enum class FilterByBackendRolesAccessStrategy(val strategy: String) { + /** + * User backend roles must contain all resource backend roles to have access + */ + ALL("all"), + /** * Backend roles must be exactly equal to have access */ @@ -18,9 +23,4 @@ enum class FilterByBackendRolesAccessStrategy(val strategy: String) { * Backend roles must intersect to have access */ INTERSECT("intersect"), - - /** - * User backend roles must contain all resource backend roles to have access - */ - ALL("all"), } diff --git a/alerting/src/main/kotlin/org/opensearch/alerting/settings/FilterByBackendRolesAccessStrategyValidator.kt b/alerting/src/main/kotlin/org/opensearch/alerting/settings/FilterByBackendRolesAccessStrategyValidator.kt index 3c5825f5e..0022f5fee 100644 --- a/alerting/src/main/kotlin/org/opensearch/alerting/settings/FilterByBackendRolesAccessStrategyValidator.kt +++ b/alerting/src/main/kotlin/org/opensearch/alerting/settings/FilterByBackendRolesAccessStrategyValidator.kt @@ -12,7 +12,7 @@ class FilterByBackendRolesAccessStrategyValidator : Setting.Validator { val allStrategies: List = FilterByBackendRolesAccessStrategy.entries.map { it.strategy } when (strategy) { - FilterByBackendRolesAccessStrategy.ALL.strategy + FilterByBackendRolesAccessStrategy.ALL.strategy, FilterByBackendRolesAccessStrategy.EXACT.strategy, FilterByBackendRolesAccessStrategy.INTERSECT.strategy -> {} else -> throw IllegalArgumentException( From fb7ac5dc3127362f8543c8e4c2e58626de93af84 Mon Sep 17 00:00:00 2001 From: Mark Boyd Date: Tue, 31 Mar 2026 10:35:13 -0400 Subject: [PATCH 20/25] add new logic for handling filter by backend roles strategy of ALL Signed-off-by: Mark Boyd --- .../opensearch/alerting/transport/SecureTransportAction.kt | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/alerting/src/main/kotlin/org/opensearch/alerting/transport/SecureTransportAction.kt b/alerting/src/main/kotlin/org/opensearch/alerting/transport/SecureTransportAction.kt index 1a83e244b..564ae24be 100644 --- a/alerting/src/main/kotlin/org/opensearch/alerting/transport/SecureTransportAction.kt +++ b/alerting/src/main/kotlin/org/opensearch/alerting/transport/SecureTransportAction.kt @@ -106,9 +106,11 @@ interface SecureTransportAction { } fun doUserBackendRolesMatchResource(userBackendRoles: List, resourceBackendRoles: List): Boolean { - if (filterByAccessStrategy == FilterByBackendRolesAccessStrategy.INTERSECT.strategy) { + if (filterByAccessStrategy == FilterByBackendRolesAccessStrategy.ALL.strategy) { + return userBackendRoles.containsAll(resourceBackendRoles) + } else if (filterByAccessStrategy == FilterByBackendRolesAccessStrategy.INTERSECT.strategy) { return resourceBackendRoles.any { it in userBackendRoles } - } else if (filterByAccessStrategy == FilterByBackendRolesAccessStrategy.ALL.strategy) { + } else if (filterByAccessStrategy == FilterByBackendRolesAccessStrategy.EXACT.strategy) { return resourceBackendRoles.sorted().equals(userBackendRoles.sorted()) } // Not sure if this is necessary, since there is a validator From 8f8e1d14c9d24b3da533110817396511c88a1426 Mon Sep 17 00:00:00 2001 From: Mark Boyd Date: Tue, 31 Mar 2026 10:35:34 -0400 Subject: [PATCH 21/25] update tests to clarify testing of filter by backend roles access strategy of exact Signed-off-by: Mark Boyd --- .../alerting/resthandler/SecureMonitorRestApiIT.kt | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/alerting/src/test/kotlin/org/opensearch/alerting/resthandler/SecureMonitorRestApiIT.kt b/alerting/src/test/kotlin/org/opensearch/alerting/resthandler/SecureMonitorRestApiIT.kt index b2d8f387a..92cbb8a45 100644 --- a/alerting/src/test/kotlin/org/opensearch/alerting/resthandler/SecureMonitorRestApiIT.kt +++ b/alerting/src/test/kotlin/org/opensearch/alerting/resthandler/SecureMonitorRestApiIT.kt @@ -323,14 +323,14 @@ class SecureMonitorRestApiIT : AlertingRestTestCase() { } } - fun `test get monitor succeeds for same backend roles in same order when filterByAccessStrategy is all`() { + fun `test get monitor succeeds for same backend roles in same order when filterByAccessStrategy is exact`() { enableFilterBy() if (!isHttps()) { // if security is disabled and filter by is enabled, we can't create monitor // refer: `test create monitor with enable filter by` return } - setFilterByBackendRolesStrategy("all") + setFilterByBackendRolesStrategy("exact") val monitor = randomQueryLevelMonitor(enabled = true) @@ -376,14 +376,14 @@ class SecureMonitorRestApiIT : AlertingRestTestCase() { } } - fun `test get monitor succeeds for same backend roles in different order when filterByAccessStrategy is all`() { + fun `test get monitor succeeds for same backend roles in different order when filterByAccessStrategy is exact`() { enableFilterBy() if (!isHttps()) { // if security is disabled and filter by is enabled, we can't create monitor // refer: `test create monitor with enable filter by` return } - setFilterByBackendRolesStrategy("all") + setFilterByBackendRolesStrategy("exact") val monitor = randomQueryLevelMonitor(enabled = true) @@ -430,14 +430,14 @@ class SecureMonitorRestApiIT : AlertingRestTestCase() { } } - fun `test get monitor fails for different backend roles when filterByAccessStrategy is all`() { + fun `test get monitor fails for different backend roles when filterByAccessStrategy is exact`() { enableFilterBy() if (!isHttps()) { // if security is disabled and filter by is enabled, we can't create monitor // refer: `test create monitor with enable filter by` return } - setFilterByBackendRolesStrategy("all") + setFilterByBackendRolesStrategy("exact") val monitor = randomQueryLevelMonitor(enabled = true) From cb7b9bb5c1c41d901bdecf2a18424dbbb9ce0351 Mon Sep 17 00:00:00 2001 From: Mark Boyd Date: Tue, 31 Mar 2026 11:15:29 -0400 Subject: [PATCH 22/25] add tests for filter by backend roles access strategy of all Signed-off-by: Mark Boyd --- .../resthandler/SecureMonitorRestApiIT.kt | 108 ++++++++++++++++++ 1 file changed, 108 insertions(+) diff --git a/alerting/src/test/kotlin/org/opensearch/alerting/resthandler/SecureMonitorRestApiIT.kt b/alerting/src/test/kotlin/org/opensearch/alerting/resthandler/SecureMonitorRestApiIT.kt index 92cbb8a45..0873de6b7 100644 --- a/alerting/src/test/kotlin/org/opensearch/alerting/resthandler/SecureMonitorRestApiIT.kt +++ b/alerting/src/test/kotlin/org/opensearch/alerting/resthandler/SecureMonitorRestApiIT.kt @@ -486,6 +486,114 @@ class SecureMonitorRestApiIT : AlertingRestTestCase() { } } + fun `test get monitor succeeds when filterByAccessStrategy is all and user backend roles contain all resource roles`() { + enableFilterBy() + if (!isHttps()) { + // if security is disabled and filter by is enabled, we can't create monitor + // refer: `test create monitor with enable filter by` + return + } + setFilterByBackendRolesStrategy("all") + + val monitor = randomQueryLevelMonitor(enabled = true) + + createUserWithRoles( + user, + listOf(ALERTING_FULL_ACCESS_ROLE, READALL_AND_MONITOR_ROLE), + listOf("role1", "role2"), + false + ) + + val createdMonitor = createMonitorWithClient(userClient!!, monitor = monitor, listOf("role1", "role2")) + assertNotNull("The monitor was not created", createdMonitor) + + createUserRolesMapping(ALERTING_FULL_ACCESS_ROLE, arrayOf()) + createUserRolesMapping(READALL_AND_MONITOR_ROLE, arrayOf()) + + // getUser should have access to the monitor + val getUser = "getUser" + createUserWithTestDataAndCustomRole( + getUser, + TEST_HR_INDEX, + TEST_HR_ROLE, + listOf("role1", "role2", "role3"), + getClusterPermissionsFromCustomRole(ALERTING_GET_MONITOR_ACCESS) + ) + val getUserClient = SecureRestClientBuilder(clusterHosts.toTypedArray(), isHttps(), getUser, password) + .setSocketTimeout(60000) + .setConnectionRequestTimeout(180000) + .build() + + try { + val getMonitorResponse = getUserClient?.makeRequest( + "GET", + "$ALERTING_BASE_URI/${createdMonitor.id}", + null, + BasicHeader(HttpHeaders.CONTENT_TYPE, "application/json") + ) + assertEquals("Get monitor failed", RestStatus.OK, getMonitorResponse?.restStatus()) + } finally { + deleteRoleAndRoleMapping(TEST_HR_ROLE) + deleteUser(getUser) + getUserClient?.close() + } + } + + fun `test get monitor fails when filterByAccessStrategy is all and user backend roles do not contain all resource roles`() { + enableFilterBy() + if (!isHttps()) { + // if security is disabled and filter by is enabled, we can't create monitor + // refer: `test create monitor with enable filter by` + return + } + setFilterByBackendRolesStrategy("all") + + val monitor = randomQueryLevelMonitor(enabled = true) + + createUserWithRoles( + user, + listOf(ALERTING_FULL_ACCESS_ROLE, READALL_AND_MONITOR_ROLE), + listOf("role1", "role2"), + false + ) + + val createdMonitor = createMonitorWithClient(userClient!!, monitor = monitor, listOf("role1", "role2")) + assertNotNull("The monitor was not created", createdMonitor) + + createUserRolesMapping(ALERTING_FULL_ACCESS_ROLE, arrayOf()) + createUserRolesMapping(READALL_AND_MONITOR_ROLE, arrayOf()) + + // getUser should have access to the monitor + val getUser = "getUser" + createUserWithTestDataAndCustomRole( + getUser, + TEST_HR_INDEX, + TEST_HR_ROLE, + listOf("role2", "role3"), + getClusterPermissionsFromCustomRole(ALERTING_GET_MONITOR_ACCESS) + ) + val getUserClient = SecureRestClientBuilder(clusterHosts.toTypedArray(), isHttps(), getUser, password) + .setSocketTimeout(60000) + .setConnectionRequestTimeout(180000) + .build() + + try { + val getMonitorResponse = getUserClient?.makeRequest( + "GET", + "$ALERTING_BASE_URI/${createdMonitor.id}", + null, + BasicHeader(HttpHeaders.CONTENT_TYPE, "application/json") + ) + fail("Expected Forbidden exception") + } catch (e: ResponseException) { + assertEquals("Get monitor failed", RestStatus.FORBIDDEN.status, e.response.statusLine.statusCode) + } finally { + deleteRoleAndRoleMapping(TEST_HR_ROLE) + deleteUser(getUser) + getUserClient?.close() + } + } + fun getDocs(response: Response?): Any? { val hits = createParser( XContentType.JSON.xContent(), From 23ab66ccec638927c6218e8ea9804f542a5d31ce Mon Sep 17 00:00:00 2001 From: Mark Boyd Date: Tue, 31 Mar 2026 11:20:34 -0400 Subject: [PATCH 23/25] update test setup to verify that filter by backend roles strategy of all is successful when roles are in a different order Signed-off-by: Mark Boyd --- .../opensearch/alerting/resthandler/SecureMonitorRestApiIT.kt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/alerting/src/test/kotlin/org/opensearch/alerting/resthandler/SecureMonitorRestApiIT.kt b/alerting/src/test/kotlin/org/opensearch/alerting/resthandler/SecureMonitorRestApiIT.kt index 0873de6b7..bba6d6b4c 100644 --- a/alerting/src/test/kotlin/org/opensearch/alerting/resthandler/SecureMonitorRestApiIT.kt +++ b/alerting/src/test/kotlin/org/opensearch/alerting/resthandler/SecureMonitorRestApiIT.kt @@ -516,7 +516,7 @@ class SecureMonitorRestApiIT : AlertingRestTestCase() { getUser, TEST_HR_INDEX, TEST_HR_ROLE, - listOf("role1", "role2", "role3"), + listOf("role3", "role2", "role1"), getClusterPermissionsFromCustomRole(ALERTING_GET_MONITOR_ACCESS) ) val getUserClient = SecureRestClientBuilder(clusterHosts.toTypedArray(), isHttps(), getUser, password) From fef3f4e204e0303bfa454c96fcd77cd60940a8df Mon Sep 17 00:00:00 2001 From: Mark Boyd Date: Tue, 31 Mar 2026 15:05:58 -0400 Subject: [PATCH 24/25] add unit tests for FilterByBackendRolesAccessStrategyValidator Signed-off-by: Mark Boyd --- ...ackendRolesAccessStrategyValidatorTests.kt | 46 +++++++++++++++++++ 1 file changed, 46 insertions(+) create mode 100644 alerting/src/test/kotlin/org/opensearch/alerting/settings/FilterByBackendRolesAccessStrategyValidatorTests.kt diff --git a/alerting/src/test/kotlin/org/opensearch/alerting/settings/FilterByBackendRolesAccessStrategyValidatorTests.kt b/alerting/src/test/kotlin/org/opensearch/alerting/settings/FilterByBackendRolesAccessStrategyValidatorTests.kt new file mode 100644 index 000000000..0f1d403a4 --- /dev/null +++ b/alerting/src/test/kotlin/org/opensearch/alerting/settings/FilterByBackendRolesAccessStrategyValidatorTests.kt @@ -0,0 +1,46 @@ +/* + * Copyright OpenSearch Contributors + * SPDX-License-Identifier: Apache-2.0 + */ + +package org.opensearch.alerting.settings + +import org.opensearch.test.OpenSearchTestCase +import kotlin.test.assertFailsWith + +class FilterByBackendRolesAccessStrategyValidatorTests : OpenSearchTestCase() { + + fun `test accepts strategy of all`() { + val validator = FilterByBackendRolesAccessStrategyValidator() + try { + validator.validate("all") + } catch (e: Exception) { + fail("Unexpected exception") + } + } + + fun `test accepts strategy of exact`() { + val validator = FilterByBackendRolesAccessStrategyValidator() + try { + validator.validate("exact") + } catch (e: Exception) { + fail("Unexpected exception") + } + } + + fun `test accepts strategy of intersect`() { + val validator = FilterByBackendRolesAccessStrategyValidator() + try { + validator.validate("intersect") + } catch (e: Exception) { + fail("Unexpected exception") + } + } + + fun `test rejects invalid strategy`() { + val validator = FilterByBackendRolesAccessStrategyValidator() + assertFailsWith(IllegalArgumentException::class, "Expected IllegalArgumentException") { + validator.validate("invalid") + } + } +} From 519f4146006a3a6c3473e5f44326be0f81df4d66 Mon Sep 17 00:00:00 2001 From: Mark Boyd Date: Tue, 31 Mar 2026 16:17:02 -0400 Subject: [PATCH 25/25] simplify tests in FilterByBackendRolesAccessStrategyValidatorTests Signed-off-by: Mark Boyd --- ...ackendRolesAccessStrategyValidatorTests.kt | 28 +++---------------- 1 file changed, 4 insertions(+), 24 deletions(-) diff --git a/alerting/src/test/kotlin/org/opensearch/alerting/settings/FilterByBackendRolesAccessStrategyValidatorTests.kt b/alerting/src/test/kotlin/org/opensearch/alerting/settings/FilterByBackendRolesAccessStrategyValidatorTests.kt index 0f1d403a4..9082fc224 100644 --- a/alerting/src/test/kotlin/org/opensearch/alerting/settings/FilterByBackendRolesAccessStrategyValidatorTests.kt +++ b/alerting/src/test/kotlin/org/opensearch/alerting/settings/FilterByBackendRolesAccessStrategyValidatorTests.kt @@ -10,31 +10,11 @@ import kotlin.test.assertFailsWith class FilterByBackendRolesAccessStrategyValidatorTests : OpenSearchTestCase() { - fun `test accepts strategy of all`() { + fun `test accepts valid strategies`() { val validator = FilterByBackendRolesAccessStrategyValidator() - try { - validator.validate("all") - } catch (e: Exception) { - fail("Unexpected exception") - } - } - - fun `test accepts strategy of exact`() { - val validator = FilterByBackendRolesAccessStrategyValidator() - try { - validator.validate("exact") - } catch (e: Exception) { - fail("Unexpected exception") - } - } - - fun `test accepts strategy of intersect`() { - val validator = FilterByBackendRolesAccessStrategyValidator() - try { - validator.validate("intersect") - } catch (e: Exception) { - fail("Unexpected exception") - } + validator.validate("all") + validator.validate("exact") + validator.validate("intersect") } fun `test rejects invalid strategy`() {