From c7d3d8ce15f8b31236ff06a88cf0e0b04d4ab245 Mon Sep 17 00:00:00 2001 From: Christos Nasikas Date: Mon, 30 Sep 2024 17:11:00 +0300 Subject: [PATCH] [ResponseOps][Alerts] Fix authorization issues with `discover` as consumers (#192321) ## Summary Alerts use its own RBAC model. The RBAC relies on a property called `consumer`. The consumer is tight coupled with the feature ID. It denotes the user's access to the rule and the alerts. For example, a user with access to the "Logs" feature has access only to alerts and rules with the `consumer` set as `logs`. Users can create an ES Query rule from Discover. When the feature was [implemented](https://github.com/elastic/kibana/pull/124534) (v8.3.0) the consumer was set to `discover`. Then it [changed](https://github.com/elastic/kibana/pull/166032) (v8.11.0) to `stackAlerts` (visible only on the stack management page) and then [to](https://github.com/elastic/kibana/pull/171364) (v8.12.0) `alerts` so it can be visible in Observability. Users who created rules that generated alerts with the `discover` consumer cannot see the alerts generated by the rule when they upgrade Kibana to 8.11+ even as superusers. This PR fixes the issues around the `discover` consumer. I added the following alert document to the `data.json.gz` to test for alerts with `discover` consumer. ``` { "type": "doc", "value": { "id": "1b75bfe9-d2f5-47e9-bac6-b082dd9c9e97", "index": ".internal.alerts-stack.alerts-default-000001", "source": { "@timestamp": "2021-10-19T14:00:38.749Z", "event.action": "active", "event.kind": "signal", "kibana.alert.duration.us": 1370302000, "kibana.alert.evaluation.threshold": -1, "kibana.alert.evaluation.value": 80, "kibana.alert.instance.id": "query matched", "kibana.alert.reason": "Document count is 80 in the last 100d in .kibana_alerting_cases index. Alert when greater than -1.", "kibana.alert.rule.category": "Elasticsearch query", "kibana.alert.rule.consumer": "discover", "kibana.alert.rule.name": "EsQuery discover", "kibana.alert.rule.producer": "stackAlerts", "kibana.alert.rule.rule_type_id": ".es-query", "kibana.alert.rule.uuid": "25c14920-faa7-4a9a-830c-ce32c8211237", "kibana.alert.start": "2021-10-19T15:00:41.555Z", "kibana.alert.status": "active", "kibana.alert.time_range": { "gte": "2021-10-19T15:00:41.555Z" }, "kibana.alert.uuid": "23237979-75bf-4b68-a210-ce5056b93356", "kibana.alert.workflow_status": "open", "kibana.space_ids": [ "default" ], "kibana.version": "8.0.0", "tags": [] } } } ``` ## Testing 1. Create a rule with the consumer as `discover`. See https://github.com/elastic/kibana/issues/184595 for instructions. 2. Go to the rule details page. 3. Verify that you do not get any error toaster and you can see the alerts. Fixes: https://github.com/elastic/kibana/issues/184595 ### Checklist Delete any items that are not applicable to this PR. - [x] [Unit or functional tests](https://www.elastic.co/guide/en/kibana/master/development-tests.html) were updated or added to match the most common scenarios ### For maintainers - [x] This was checked for breaking API changes and was [labeled appropriately](https://www.elastic.co/guide/en/kibana/master/contributing.html#kibana-release-notes-process) ## Release notes Fix an issue with rules not being accessible created from Discover before 8.11.0. --------- Co-authored-by: Elastic Machine (cherry picked from commit 396931f5056600e633dba64dab81a66096d05f72) --- .../alerting_authorization.test.ts | 90 +++++++++--------- .../authorization/alerting_authorization.ts | 28 +++++- .../alerts_table/cells/render_cell_value.tsx | 2 +- .../observability/alerts/data.json.gz | Bin 4223 -> 4610 bytes .../common/lib/authentication/roles.ts | 18 ++++ .../common/lib/authentication/users.ts | 8 ++ .../tests/basic/search_strategy.ts | 81 ++++++++++++++++ 7 files changed, 180 insertions(+), 47 deletions(-) diff --git a/x-pack/plugins/alerting/server/authorization/alerting_authorization.test.ts b/x-pack/plugins/alerting/server/authorization/alerting_authorization.test.ts index da2774e263b58..a0af65c5fe2fd 100644 --- a/x-pack/plugins/alerting/server/authorization/alerting_authorization.test.ts +++ b/x-pack/plugins/alerting/server/authorization/alerting_authorization.test.ts @@ -5,7 +5,7 @@ * 2.0. */ -import { fromKueryExpression } from '@kbn/es-query'; +import { KueryNode, fromKueryExpression, toKqlExpression } from '@kbn/es-query'; import { KibanaRequest } from '@kbn/core/server'; import { ruleTypeRegistryMock } from '../rule_type_registry.mock'; import { securityMock } from '@kbn/security-plugin/server/mocks'; @@ -910,20 +910,19 @@ describe('AlertingAuthorization', () => { getSpaceId, }); ruleTypeRegistry.list.mockReturnValue(setOfAlertTypes); - expect( - ( - await alertAuthorization.getFindAuthorizationFilter(AlertingAuthorizationEntity.Rule, { - type: AlertingAuthorizationFilterType.KQL, - fieldNames: { - ruleTypeId: 'path.to.rule_type_id', - consumer: 'consumer-field', - }, - }) - ).filter - ).toEqual( - fromKueryExpression( - `((path.to.rule_type_id:myAppAlertType and consumer-field:(alerts or myApp or myOtherApp or myAppWithSubFeature)) or (path.to.rule_type_id:mySecondAppAlertType and consumer-field:(alerts or myApp or myOtherApp or myAppWithSubFeature)) or (path.to.rule_type_id:myOtherAppAlertType and consumer-field:(alerts or myApp or myOtherApp or myAppWithSubFeature)))` - ) + + const filter = ( + await alertAuthorization.getFindAuthorizationFilter(AlertingAuthorizationEntity.Rule, { + type: AlertingAuthorizationFilterType.KQL, + fieldNames: { + ruleTypeId: 'path.to.rule_type_id', + consumer: 'consumer-field', + }, + }) + ).filter; + + expect(toKqlExpression(filter as KueryNode)).toMatchInlineSnapshot( + `"((path.to.rule_type_id: myAppAlertType AND (consumer-field: alerts OR consumer-field: discover OR consumer-field: myApp OR consumer-field: myOtherApp OR consumer-field: myAppWithSubFeature)) OR (path.to.rule_type_id: mySecondAppAlertType AND (consumer-field: alerts OR consumer-field: discover OR consumer-field: myApp OR consumer-field: myOtherApp OR consumer-field: myAppWithSubFeature)) OR (path.to.rule_type_id: myOtherAppAlertType AND (consumer-field: alerts OR consumer-field: discover OR consumer-field: myApp OR consumer-field: myOtherApp OR consumer-field: myAppWithSubFeature)))"` ); }); test('throws if user has no privileges to any rule type', async () => { @@ -1274,6 +1273,10 @@ describe('AlertingAuthorization', () => { "all": true, "read": true, }, + "discover": Object { + "all": true, + "read": true, + }, "myApp": Object { "all": true, "read": true, @@ -1311,6 +1314,10 @@ describe('AlertingAuthorization', () => { "all": true, "read": true, }, + "discover": Object { + "all": true, + "read": true, + }, "myApp": Object { "all": true, "read": true, @@ -2251,20 +2258,18 @@ describe('AlertingAuthorization', () => { }); }); test('creates a filter based on the privileged types', async () => { - expect( - ( - await alertAuthorization.getFindAuthorizationFilter(AlertingAuthorizationEntity.Rule, { - type: AlertingAuthorizationFilterType.KQL, - fieldNames: { - ruleTypeId: 'path.to.rule_type_id', - consumer: 'consumer-field', - }, - }) - ).filter - ).toEqual( - fromKueryExpression( - `path.to.rule_type_id:.esQuery and consumer-field:(alerts or stackAlerts or discover)` - ) + const filter = ( + await alertAuthorization.getFindAuthorizationFilter(AlertingAuthorizationEntity.Rule, { + type: AlertingAuthorizationFilterType.KQL, + fieldNames: { + ruleTypeId: 'path.to.rule_type_id', + consumer: 'consumer-field', + }, + }) + ).filter; + + expect(toKqlExpression(filter as KueryNode)).toMatchInlineSnapshot( + `"(path.to.rule_type_id: .esQuery AND (consumer-field: alerts OR consumer-field: discover OR consumer-field: stackAlerts))"` ); }); }); @@ -2557,21 +2562,20 @@ describe('AlertingAuthorization', () => { expect(ruleTypeRegistry.get).toHaveBeenCalledTimes(1); }); }); + test('creates a filter based on the privileged types', async () => { - expect( - ( - await alertAuthorization.getFindAuthorizationFilter(AlertingAuthorizationEntity.Rule, { - type: AlertingAuthorizationFilterType.KQL, - fieldNames: { - ruleTypeId: 'path.to.rule_type_id', - consumer: 'consumer-field', - }, - }) - ).filter - ).toEqual( - fromKueryExpression( - `(path.to.rule_type_id:.esQuery and consumer-field:(alerts or stackAlerts or logs or discover)) or (path.to.rule_type_id:.logs-threshold-o11y and consumer-field:(alerts or stackAlerts or logs or discover)) or (path.to.rule_type_id:.threshold-rule-o11y and consumer-field:(alerts or stackAlerts or logs or discover))` - ) + const filter = ( + await alertAuthorization.getFindAuthorizationFilter(AlertingAuthorizationEntity.Rule, { + type: AlertingAuthorizationFilterType.KQL, + fieldNames: { + ruleTypeId: 'path.to.rule_type_id', + consumer: 'consumer-field', + }, + }) + ).filter; + + expect(toKqlExpression(filter as KueryNode)).toMatchInlineSnapshot( + `"((path.to.rule_type_id: .esQuery AND (consumer-field: alerts OR consumer-field: discover OR consumer-field: stackAlerts OR consumer-field: logs)) OR (path.to.rule_type_id: .logs-threshold-o11y AND (consumer-field: alerts OR consumer-field: discover OR consumer-field: stackAlerts OR consumer-field: logs)) OR (path.to.rule_type_id: .threshold-rule-o11y AND (consumer-field: alerts OR consumer-field: discover OR consumer-field: stackAlerts OR consumer-field: logs)))"` ); }); }); diff --git a/x-pack/plugins/alerting/server/authorization/alerting_authorization.ts b/x-pack/plugins/alerting/server/authorization/alerting_authorization.ts index 2102ff245b9f8..987b138b035ef 100644 --- a/x-pack/plugins/alerting/server/authorization/alerting_authorization.ts +++ b/x-pack/plugins/alerting/server/authorization/alerting_authorization.ts @@ -13,6 +13,7 @@ import { KueryNode } from '@kbn/es-query'; import { SecurityPluginSetup } from '@kbn/security-plugin/server'; import { PluginStartContract as FeaturesPluginStart } from '@kbn/features-plugin/server'; import { Space } from '@kbn/spaces-plugin/server'; +import { STACK_ALERTS_FEATURE_ID } from '@kbn/rule-data-utils'; import { RegistryRuleType } from '../rule_type_registry'; import { ALERTING_FEATURE_ID, RuleTypeRegistry } from '../types'; import { @@ -88,6 +89,8 @@ export interface ConstructorOptions { authorization?: SecurityPluginSetup['authz']; } +const DISCOVER_FEATURE_ID = 'discover'; + export class AlertingAuthorization { private readonly ruleTypeRegistry: RuleTypeRegistry; private readonly request: KibanaRequest; @@ -135,7 +138,7 @@ export class AlertingAuthorization { this.allPossibleConsumers = this.featuresIds.then((featuresIds) => { return featuresIds.size - ? asAuthorizedConsumers([ALERTING_FEATURE_ID, ...featuresIds], { + ? asAuthorizedConsumers([ALERTING_FEATURE_ID, DISCOVER_FEATURE_ID, ...featuresIds], { read: true, all: true, }) @@ -328,7 +331,22 @@ export class AlertingAuthorization { hasAllRequested: boolean; authorizedRuleTypes: Set; }> { - const fIds = featuresIds ?? (await this.featuresIds); + const fIds = new Set(featuresIds ?? (await this.featuresIds)); + + /** + * Temporary hack to fix issues with the discover consumer. + * Issue: https://github.com/elastic/kibana/issues/184595. + * PR https://github.com/elastic/kibana/pull/183756 will + * remove the hack and fix it in a generic way. + * + * The discover consumer should be authorized + * as the stackAlerts consumer. + */ + if (fIds.has(DISCOVER_FEATURE_ID)) { + fIds.delete(DISCOVER_FEATURE_ID); + fIds.add(STACK_ALERTS_FEATURE_ID); + } + if (this.authorization && this.shouldCheckAuthorization()) { const checkPrivileges = this.authorization.checkPrivilegesDynamicallyWithRequest( this.request @@ -347,11 +365,15 @@ export class AlertingAuthorization { >(); const allPossibleConsumers = await this.allPossibleConsumers; const addLegacyConsumerPrivileges = (legacyConsumer: string) => - legacyConsumer === ALERTING_FEATURE_ID || isEmpty(featuresIds); + legacyConsumer === ALERTING_FEATURE_ID || + legacyConsumer === DISCOVER_FEATURE_ID || + isEmpty(featuresIds); + for (const feature of fIds) { const featureDef = this.features .getKibanaFeatures() .find((kFeature) => kFeature.id === feature); + for (const ruleTypeId of featureDef?.alerting ?? []) { const ruleTypeAuth = ruleTypesWithAuthorization.find((rtwa) => rtwa.id === ruleTypeId); if (ruleTypeAuth) { diff --git a/x-pack/plugins/triggers_actions_ui/public/application/sections/alerts_table/cells/render_cell_value.tsx b/x-pack/plugins/triggers_actions_ui/public/application/sections/alerts_table/cells/render_cell_value.tsx index 2d76d4cd19aba..ad82a9d3ee97c 100644 --- a/x-pack/plugins/triggers_actions_ui/public/application/sections/alerts_table/cells/render_cell_value.tsx +++ b/x-pack/plugins/triggers_actions_ui/public/application/sections/alerts_table/cells/render_cell_value.tsx @@ -138,7 +138,7 @@ export function getAlertFormatters(fieldFormats: FieldFormatsRegistry) { const producer = rowData?.find(({ field }) => field === ALERT_RULE_PRODUCER)?.value?.[0]; const consumer: AlertConsumers = observabilityFeatureIds.includes(producer) ? 'observability' - : producer && (value === 'alerts' || value === 'stackAlerts') + : producer && (value === 'alerts' || value === 'stackAlerts' || value === 'discover') ? producer : value; const consumerData = alertProducersData[consumer]; diff --git a/x-pack/test/functional/es_archives/observability/alerts/data.json.gz b/x-pack/test/functional/es_archives/observability/alerts/data.json.gz index 72d24a0b36668d9c5aeda8be16e998f8cd4730b6..4017aaab1bddf1ca9ba2582b819b813dfdc42e2d 100644 GIT binary patch literal 4610 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 literal 4223 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 V6Ve`%GWrNF{|_Dp+$ZP!004?fEG7T| diff --git a/x-pack/test/rule_registry/common/lib/authentication/roles.ts b/x-pack/test/rule_registry/common/lib/authentication/roles.ts index 89878a10cc6d0..df887ea463e29 100644 --- a/x-pack/test/rule_registry/common/lib/authentication/roles.ts +++ b/x-pack/test/rule_registry/common/lib/authentication/roles.ts @@ -265,6 +265,23 @@ export const logsOnlyAllSpacesAll: Role = { }, }; +export const stackAlertsOnlyAllSpacesAll: Role = { + name: 'stack_alerts_only_all_spaces_all', + privileges: { + elasticsearch: { + indices: [], + }, + kibana: [ + { + feature: { + stackAlerts: ['all'], + }, + spaces: ['*'], + }, + ], + }, +}; + /** * This role exists to test that the alert search strategy allows * users who do not have access to security solutions the ability @@ -494,6 +511,7 @@ export const allRoles = [ securitySolutionOnlyReadSpacesAll, observabilityOnlyAllSpacesAll, logsOnlyAllSpacesAll, + stackAlertsOnlyAllSpacesAll, observabilityOnlyReadSpacesAll, observabilityOnlyAllSpacesAllWithReadESIndices, observabilityMinReadAlertsRead, diff --git a/x-pack/test/rule_registry/common/lib/authentication/users.ts b/x-pack/test/rule_registry/common/lib/authentication/users.ts index 2a63c296d842a..3d418ab9e779d 100644 --- a/x-pack/test/rule_registry/common/lib/authentication/users.ts +++ b/x-pack/test/rule_registry/common/lib/authentication/users.ts @@ -30,6 +30,7 @@ import { observabilityMinReadAlertsAllSpacesAll, observabilityOnlyAllSpacesAllWithReadESIndices, securitySolutionOnlyAllSpacesAllWithReadESIndices, + stackAlertsOnlyAllSpacesAll, } from './roles'; import { User } from './types'; @@ -176,6 +177,12 @@ export const logsOnlySpacesAll: User = { roles: [logsOnlyAllSpacesAll.name], }; +export const stackAlertsOnlySpacesAll: User = { + username: 'stack_alerts_only_all_spaces_all', + password: 'stack_alerts_only_all_spaces_all', + roles: [stackAlertsOnlyAllSpacesAll.name], +}; + export const obsOnlySpacesAllEsRead: User = { username: 'obs_only_all_spaces_all_es_read', password: 'obs_only_all_spaces_all_es_read', @@ -290,6 +297,7 @@ export const allUsers = [ secOnlyReadSpacesAll, obsOnlySpacesAll, logsOnlySpacesAll, + stackAlertsOnlySpacesAll, obsSecSpacesAll, obsSecReadSpacesAll, obsMinReadAlertsRead, diff --git a/x-pack/test/rule_registry/security_and_spaces/tests/basic/search_strategy.ts b/x-pack/test/rule_registry/security_and_spaces/tests/basic/search_strategy.ts index c42a266ccb298..c25b96fb8956c 100644 --- a/x-pack/test/rule_registry/security_and_spaces/tests/basic/search_strategy.ts +++ b/x-pack/test/rule_registry/security_and_spaces/tests/basic/search_strategy.ts @@ -13,6 +13,8 @@ import { obsOnlySpacesAll, logsOnlySpacesAll, secOnlySpacesAllEsReadAll, + stackAlertsOnlySpacesAll, + superUser, } from '../../../common/lib/authentication/users'; type RuleRegistrySearchResponseWithErrors = RuleRegistrySearchResponse & { @@ -346,6 +348,85 @@ export default ({ getService }: FtrProviderContext) => { }); }); + describe('discover', () => { + before(async () => { + await esArchiver.load('x-pack/test/functional/es_archives/observability/alerts'); + }); + after(async () => { + await esArchiver.unload('x-pack/test/functional/es_archives/observability/alerts'); + }); + + it('should return alerts from .es-query rule type with consumer discover with access only to stack rules', async () => { + const result = await secureBsearch.send({ + supertestWithoutAuth, + auth: { + username: stackAlertsOnlySpacesAll.username, + password: stackAlertsOnlySpacesAll.password, + }, + referer: 'test', + kibanaVersion, + internalOrigin: 'Kibana', + options: { + featureIds: ['discover'], + }, + strategy: 'privateRuleRegistryAlertsSearchStrategy', + }); + + expect(result.rawResponse.hits.total).to.eql(1); + + const consumers = result.rawResponse.hits.hits.map((hit) => { + return hit.fields?.['kibana.alert.rule.consumer']; + }); + + expect(consumers.every((consumer) => consumer === 'discover')); + }); + + it('should return alerts from .es-query rule type with consumer discover as superuser', async () => { + const result = await secureBsearch.send({ + supertestWithoutAuth, + auth: { + username: superUser.username, + password: superUser.password, + }, + referer: 'test', + kibanaVersion, + internalOrigin: 'Kibana', + options: { + featureIds: ['discover'], + }, + strategy: 'privateRuleRegistryAlertsSearchStrategy', + }); + + expect(result.rawResponse.hits.total).to.eql(1); + + const consumers = result.rawResponse.hits.hits.map((hit) => { + return hit.fields?.['kibana.alert.rule.consumer']; + }); + + expect(consumers.every((consumer) => consumer === 'discover')); + }); + + it('should not return alerts from .es-query rule type with consumer discover without access to stack rules', async () => { + const result = await secureBsearch.send({ + supertestWithoutAuth, + auth: { + username: logsOnlySpacesAll.username, + password: logsOnlySpacesAll.password, + }, + referer: 'test', + kibanaVersion, + internalOrigin: 'Kibana', + options: { + featureIds: ['discover'], + }, + strategy: 'privateRuleRegistryAlertsSearchStrategy', + }); + + expect(result.statusCode).to.be(500); + expect(result.message).to.be('Unauthorized to find alerts for any rule types'); + }); + }); + describe('empty response', () => { it('should return an empty response', async () => { const result = await secureBsearch.send({