Skip to content

Commit

Permalink
[8.x] [ResponseOps][Alerts] Fix authorization issues with `disco…
Browse files Browse the repository at this point in the history
…ver` as consumers (#192321) (#194441)

# Backport

This will backport the following commits from `main` to `8.x`:
- [[ResponseOps][Alerts] Fix authorization issues with
`discover` as consumers
(#192321)](#192321)

<!--- Backport version: 9.4.3 -->

### Questions ?
Please refer to the [Backport tool
documentation](https://github.com/sqren/backport)

<!--BACKPORT [{"author":{"name":"Christos
Nasikas","email":"[email protected]"},"sourceCommit":{"committedDate":"2024-09-30T14:11:00Z","message":"[ResponseOps][Alerts]
Fix authorization issues with `discover` as consumers (#192321)\n\n##
Summary\r\n\r\nAlerts use its own RBAC model. The RBAC relies on a
property called\r\n`consumer`. The consumer is tight coupled with the
feature ID. It\r\ndenotes the user's access to the rule and the alerts.
For example, a\r\nuser with access to the \"Logs\" feature has access
only to alerts and\r\nrules with the `consumer` set as `logs`. Users can
create an ES Query\r\nrule from Discover. When the feature
was\r\n[implemented](#124534)
(v8.3.0)\r\nthe consumer was set to `discover`. Then
it\r\n[changed](#166032) (v8.11.0)
to\r\n`stackAlerts` (visible only on the stack management page) and
then\r\n[to](#171364) (v8.12.0)
`alerts`\r\nso it can be visible in Observability. Users who created
rules that\r\ngenerated alerts with the `discover` consumer cannot see
the alerts\r\ngenerated by the rule when they upgrade Kibana to 8.11+
even as\r\nsuperusers. This PR fixes the issues around the `discover`
consumer.\r\n\r\nI added the following alert document to the
`data.json.gz` to test for\r\nalerts with `discover`
consumer.\r\n\r\n```\r\n{\r\n \"type\": \"doc\",\r\n \"value\": {\r\n
\"id\": \"1b75bfe9-d2f5-47e9-bac6-b082dd9c9e97\",\r\n \"index\":
\".internal.alerts-stack.alerts-default-000001\",\r\n \"source\": {\r\n
\"@timestamp\": \"2021-10-19T14:00:38.749Z\",\r\n \"event.action\":
\"active\",\r\n \"event.kind\": \"signal\",\r\n
\"kibana.alert.duration.us\": 1370302000,\r\n
\"kibana.alert.evaluation.threshold\": -1,\r\n
\"kibana.alert.evaluation.value\": 80,\r\n \"kibana.alert.instance.id\":
\"query matched\",\r\n \"kibana.alert.reason\": \"Document count is 80
in the last 100d in .kibana_alerting_cases index. Alert when greater
than -1.\",\r\n \"kibana.alert.rule.category\": \"Elasticsearch
query\",\r\n \"kibana.alert.rule.consumer\": \"discover\",\r\n
\"kibana.alert.rule.name\": \"EsQuery discover\",\r\n
\"kibana.alert.rule.producer\": \"stackAlerts\",\r\n
\"kibana.alert.rule.rule_type_id\": \".es-query\",\r\n
\"kibana.alert.rule.uuid\":
\"25c14920-faa7-4a9a-830c-ce32c8211237\",\r\n \"kibana.alert.start\":
\"2021-10-19T15:00:41.555Z\",\r\n \"kibana.alert.status\":
\"active\",\r\n \"kibana.alert.time_range\": {\r\n \"gte\":
\"2021-10-19T15:00:41.555Z\"\r\n },\r\n \"kibana.alert.uuid\":
\"23237979-75bf-4b68-a210-ce5056b93356\",\r\n
\"kibana.alert.workflow_status\": \"open\",\r\n \"kibana.space_ids\":
[\r\n \"default\"\r\n ],\r\n \"kibana.version\": \"8.0.0\",\r\n
\"tags\": []\r\n }\r\n }\r\n}\r\n```\r\n\r\n## Testing\r\n\r\n1. Create
a rule with the consumer as `discover`.
See\r\nhttps://github.com//issues/184595 for
instructions.\r\n2. Go to the rule details page.\r\n3. Verify that you
do not get any error toaster and you can see
the\r\nalerts.\r\n\r\nFixes:
https://github.com/elastic/kibana/issues/184595\r\n\r\n###
Checklist\r\n\r\nDelete any items that are not applicable to this
PR.\r\n\r\n- [x] [Unit or
functional\r\ntests](https://www.elastic.co/guide/en/kibana/master/development-tests.html)\r\nwere
updated or added to match the most common scenarios\r\n\r\n### For
maintainers\r\n\r\n- [x] This was checked for breaking API changes and
was
[labeled\r\nappropriately](https://www.elastic.co/guide/en/kibana/master/contributing.html#kibana-release-notes-process)\r\n\r\n##
Release notes\r\nFix an issue with rules not being accessible created
from Discover\r\nbefore 8.11.0.\r\n\r\n---------\r\n\r\nCo-authored-by:
Elastic Machine
<[email protected]>","sha":"396931f5056600e633dba64dab81a66096d05f72","branchLabelMapping":{"^v9.0.0$":"main","^v8.16.0$":"8.x","^v(\\d+).(\\d+).\\d+$":"$1.$2"}},"sourcePullRequest":{"labels":["bug","release_note:fix","Feature:Alerting","Team:ResponseOps","v9.0.0","Feature:Alerting/RulesFramework","backport:prev-major","v8.16.0","v8.15.3"],"title":"[ResponseOps][Alerts]
Fix authorization issues with `discover` as
consumers","number":192321,"url":"https://github.com/elastic/kibana/pull/192321","mergeCommit":{"message":"[ResponseOps][Alerts]
Fix authorization issues with `discover` as consumers (#192321)\n\n##
Summary\r\n\r\nAlerts use its own RBAC model. The RBAC relies on a
property called\r\n`consumer`. The consumer is tight coupled with the
feature ID. It\r\ndenotes the user's access to the rule and the alerts.
For example, a\r\nuser with access to the \"Logs\" feature has access
only to alerts and\r\nrules with the `consumer` set as `logs`. Users can
create an ES Query\r\nrule from Discover. When the feature
was\r\n[implemented](#124534)
(v8.3.0)\r\nthe consumer was set to `discover`. Then
it\r\n[changed](#166032) (v8.11.0)
to\r\n`stackAlerts` (visible only on the stack management page) and
then\r\n[to](#171364) (v8.12.0)
`alerts`\r\nso it can be visible in Observability. Users who created
rules that\r\ngenerated alerts with the `discover` consumer cannot see
the alerts\r\ngenerated by the rule when they upgrade Kibana to 8.11+
even as\r\nsuperusers. This PR fixes the issues around the `discover`
consumer.\r\n\r\nI added the following alert document to the
`data.json.gz` to test for\r\nalerts with `discover`
consumer.\r\n\r\n```\r\n{\r\n \"type\": \"doc\",\r\n \"value\": {\r\n
\"id\": \"1b75bfe9-d2f5-47e9-bac6-b082dd9c9e97\",\r\n \"index\":
\".internal.alerts-stack.alerts-default-000001\",\r\n \"source\": {\r\n
\"@timestamp\": \"2021-10-19T14:00:38.749Z\",\r\n \"event.action\":
\"active\",\r\n \"event.kind\": \"signal\",\r\n
\"kibana.alert.duration.us\": 1370302000,\r\n
\"kibana.alert.evaluation.threshold\": -1,\r\n
\"kibana.alert.evaluation.value\": 80,\r\n \"kibana.alert.instance.id\":
\"query matched\",\r\n \"kibana.alert.reason\": \"Document count is 80
in the last 100d in .kibana_alerting_cases index. Alert when greater
than -1.\",\r\n \"kibana.alert.rule.category\": \"Elasticsearch
query\",\r\n \"kibana.alert.rule.consumer\": \"discover\",\r\n
\"kibana.alert.rule.name\": \"EsQuery discover\",\r\n
\"kibana.alert.rule.producer\": \"stackAlerts\",\r\n
\"kibana.alert.rule.rule_type_id\": \".es-query\",\r\n
\"kibana.alert.rule.uuid\":
\"25c14920-faa7-4a9a-830c-ce32c8211237\",\r\n \"kibana.alert.start\":
\"2021-10-19T15:00:41.555Z\",\r\n \"kibana.alert.status\":
\"active\",\r\n \"kibana.alert.time_range\": {\r\n \"gte\":
\"2021-10-19T15:00:41.555Z\"\r\n },\r\n \"kibana.alert.uuid\":
\"23237979-75bf-4b68-a210-ce5056b93356\",\r\n
\"kibana.alert.workflow_status\": \"open\",\r\n \"kibana.space_ids\":
[\r\n \"default\"\r\n ],\r\n \"kibana.version\": \"8.0.0\",\r\n
\"tags\": []\r\n }\r\n }\r\n}\r\n```\r\n\r\n## Testing\r\n\r\n1. Create
a rule with the consumer as `discover`.
See\r\nhttps://github.com//issues/184595 for
instructions.\r\n2. Go to the rule details page.\r\n3. Verify that you
do not get any error toaster and you can see
the\r\nalerts.\r\n\r\nFixes:
https://github.com/elastic/kibana/issues/184595\r\n\r\n###
Checklist\r\n\r\nDelete any items that are not applicable to this
PR.\r\n\r\n- [x] [Unit or
functional\r\ntests](https://www.elastic.co/guide/en/kibana/master/development-tests.html)\r\nwere
updated or added to match the most common scenarios\r\n\r\n### For
maintainers\r\n\r\n- [x] This was checked for breaking API changes and
was
[labeled\r\nappropriately](https://www.elastic.co/guide/en/kibana/master/contributing.html#kibana-release-notes-process)\r\n\r\n##
Release notes\r\nFix an issue with rules not being accessible created
from Discover\r\nbefore 8.11.0.\r\n\r\n---------\r\n\r\nCo-authored-by:
Elastic Machine
<[email protected]>","sha":"396931f5056600e633dba64dab81a66096d05f72"}},"sourceBranch":"main","suggestedTargetBranches":["8.x","8.15"],"targetPullRequestStates":[{"branch":"main","label":"v9.0.0","branchLabelMappingKey":"^v9.0.0$","isSourceBranch":true,"state":"MERGED","url":"https://github.com/elastic/kibana/pull/192321","number":192321,"mergeCommit":{"message":"[ResponseOps][Alerts]
Fix authorization issues with `discover` as consumers (#192321)\n\n##
Summary\r\n\r\nAlerts use its own RBAC model. The RBAC relies on a
property called\r\n`consumer`. The consumer is tight coupled with the
feature ID. It\r\ndenotes the user's access to the rule and the alerts.
For example, a\r\nuser with access to the \"Logs\" feature has access
only to alerts and\r\nrules with the `consumer` set as `logs`. Users can
create an ES Query\r\nrule from Discover. When the feature
was\r\n[implemented](#124534)
(v8.3.0)\r\nthe consumer was set to `discover`. Then
it\r\n[changed](#166032) (v8.11.0)
to\r\n`stackAlerts` (visible only on the stack management page) and
then\r\n[to](#171364) (v8.12.0)
`alerts`\r\nso it can be visible in Observability. Users who created
rules that\r\ngenerated alerts with the `discover` consumer cannot see
the alerts\r\ngenerated by the rule when they upgrade Kibana to 8.11+
even as\r\nsuperusers. This PR fixes the issues around the `discover`
consumer.\r\n\r\nI added the following alert document to the
`data.json.gz` to test for\r\nalerts with `discover`
consumer.\r\n\r\n```\r\n{\r\n \"type\": \"doc\",\r\n \"value\": {\r\n
\"id\": \"1b75bfe9-d2f5-47e9-bac6-b082dd9c9e97\",\r\n \"index\":
\".internal.alerts-stack.alerts-default-000001\",\r\n \"source\": {\r\n
\"@timestamp\": \"2021-10-19T14:00:38.749Z\",\r\n \"event.action\":
\"active\",\r\n \"event.kind\": \"signal\",\r\n
\"kibana.alert.duration.us\": 1370302000,\r\n
\"kibana.alert.evaluation.threshold\": -1,\r\n
\"kibana.alert.evaluation.value\": 80,\r\n \"kibana.alert.instance.id\":
\"query matched\",\r\n \"kibana.alert.reason\": \"Document count is 80
in the last 100d in .kibana_alerting_cases index. Alert when greater
than -1.\",\r\n \"kibana.alert.rule.category\": \"Elasticsearch
query\",\r\n \"kibana.alert.rule.consumer\": \"discover\",\r\n
\"kibana.alert.rule.name\": \"EsQuery discover\",\r\n
\"kibana.alert.rule.producer\": \"stackAlerts\",\r\n
\"kibana.alert.rule.rule_type_id\": \".es-query\",\r\n
\"kibana.alert.rule.uuid\":
\"25c14920-faa7-4a9a-830c-ce32c8211237\",\r\n \"kibana.alert.start\":
\"2021-10-19T15:00:41.555Z\",\r\n \"kibana.alert.status\":
\"active\",\r\n \"kibana.alert.time_range\": {\r\n \"gte\":
\"2021-10-19T15:00:41.555Z\"\r\n },\r\n \"kibana.alert.uuid\":
\"23237979-75bf-4b68-a210-ce5056b93356\",\r\n
\"kibana.alert.workflow_status\": \"open\",\r\n \"kibana.space_ids\":
[\r\n \"default\"\r\n ],\r\n \"kibana.version\": \"8.0.0\",\r\n
\"tags\": []\r\n }\r\n }\r\n}\r\n```\r\n\r\n## Testing\r\n\r\n1. Create
a rule with the consumer as `discover`.
See\r\nhttps://github.com//issues/184595 for
instructions.\r\n2. Go to the rule details page.\r\n3. Verify that you
do not get any error toaster and you can see
the\r\nalerts.\r\n\r\nFixes:
https://github.com/elastic/kibana/issues/184595\r\n\r\n###
Checklist\r\n\r\nDelete any items that are not applicable to this
PR.\r\n\r\n- [x] [Unit or
functional\r\ntests](https://www.elastic.co/guide/en/kibana/master/development-tests.html)\r\nwere
updated or added to match the most common scenarios\r\n\r\n### For
maintainers\r\n\r\n- [x] This was checked for breaking API changes and
was
[labeled\r\nappropriately](https://www.elastic.co/guide/en/kibana/master/contributing.html#kibana-release-notes-process)\r\n\r\n##
Release notes\r\nFix an issue with rules not being accessible created
from Discover\r\nbefore 8.11.0.\r\n\r\n---------\r\n\r\nCo-authored-by:
Elastic Machine
<[email protected]>","sha":"396931f5056600e633dba64dab81a66096d05f72"}},{"branch":"8.x","label":"v8.16.0","branchLabelMappingKey":"^v8.16.0$","isSourceBranch":false,"state":"NOT_CREATED"},{"branch":"8.15","label":"v8.15.3","branchLabelMappingKey":"^v(\\d+).(\\d+).\\d+$","isSourceBranch":false,"state":"NOT_CREATED"}]}]
BACKPORT-->

Co-authored-by: Christos Nasikas <[email protected]>
  • Loading branch information
kibanamachine and cnasikas authored Sep 30, 2024
1 parent ad413cc commit 464430d
Show file tree
Hide file tree
Showing 7 changed files with 180 additions and 47 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -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';
Expand Down Expand Up @@ -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 () => {
Expand Down Expand Up @@ -1274,6 +1273,10 @@ describe('AlertingAuthorization', () => {
"all": true,
"read": true,
},
"discover": Object {
"all": true,
"read": true,
},
"myApp": Object {
"all": true,
"read": true,
Expand Down Expand Up @@ -1311,6 +1314,10 @@ describe('AlertingAuthorization', () => {
"all": true,
"read": true,
},
"discover": Object {
"all": true,
"read": true,
},
"myApp": Object {
"all": true,
"read": true,
Expand Down Expand Up @@ -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))"`
);
});
});
Expand Down Expand Up @@ -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)))"`
);
});
});
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ import { KueryNode } from '@kbn/es-query';
import { SecurityPluginSetup } from '@kbn/security-plugin/server';
import { 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 {
Expand Down Expand Up @@ -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;
Expand Down Expand Up @@ -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,
})
Expand Down Expand Up @@ -328,7 +331,22 @@ export class AlertingAuthorization {
hasAllRequested: boolean;
authorizedRuleTypes: Set<RegistryAlertTypeWithAuth>;
}> {
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
Expand All @@ -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) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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];
Expand Down
Binary file not shown.
18 changes: 18 additions & 0 deletions x-pack/test/rule_registry/common/lib/authentication/roles.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -494,6 +511,7 @@ export const allRoles = [
securitySolutionOnlyReadSpacesAll,
observabilityOnlyAllSpacesAll,
logsOnlyAllSpacesAll,
stackAlertsOnlyAllSpacesAll,
observabilityOnlyReadSpacesAll,
observabilityOnlyAllSpacesAllWithReadESIndices,
observabilityMinReadAlertsRead,
Expand Down
8 changes: 8 additions & 0 deletions x-pack/test/rule_registry/common/lib/authentication/users.ts
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,7 @@ import {
observabilityMinReadAlertsAllSpacesAll,
observabilityOnlyAllSpacesAllWithReadESIndices,
securitySolutionOnlyAllSpacesAllWithReadESIndices,
stackAlertsOnlyAllSpacesAll,
} from './roles';
import { User } from './types';

Expand Down Expand Up @@ -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',
Expand Down Expand Up @@ -290,6 +297,7 @@ export const allUsers = [
secOnlyReadSpacesAll,
obsOnlySpacesAll,
logsOnlySpacesAll,
stackAlertsOnlySpacesAll,
obsSecSpacesAll,
obsSecReadSpacesAll,
obsMinReadAlertsRead,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,8 @@ import {
obsOnlySpacesAll,
logsOnlySpacesAll,
secOnlySpacesAllEsReadAll,
stackAlertsOnlySpacesAll,
superUser,
} from '../../../common/lib/authentication/users';

type RuleRegistrySearchResponseWithErrors = RuleRegistrySearchResponse & {
Expand Down Expand Up @@ -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<RuleRegistrySearchResponse>({
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<RuleRegistrySearchResponse>({
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<RuleRegistrySearchResponseWithErrors>({
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<RuleRegistrySearchResponse>({
Expand Down

0 comments on commit 464430d

Please sign in to comment.