[RAM] .es-query and .observability.rules.threshold RBAC (#166032)

## Summary

This PR is updating Discover's rule to be created under the
`stackAlerts` consumer and we created an [breaking change
issue](https://github.com/elastic/dev/issues/2344) to explain the
consequences of this update.

We also fix the rule's consumer for all rule types created under the
observability rule management to use their producer instead of `alerts`.
Also, we add the ability for the ES Query and new Generic Threshold
rules type to pick the consumer associated to the rule. The
`ensureAuthorized` and the `filter` functions have modified and
simplified to support this use case please check the newest unit test
added in
`x-pack/plugins/alerting/server/authorization/alerting_authorization.test.ts`.

There is now a dropdown in the rule form to prompt the user when
creating ES Query/Generic threshold rules to select the consumer based
on their authorized consumers (we can no longer use `alerts` for these).
If there is only 1 option, then the dropdown will not be shown and the
option will be chosen automatically.

Generic threshold rules will have the following possible consumers:
 - infrastructure
 - logs

ES query rules will have the following possible consumers:
 - infrastructure
 - logs
 - stackAlerts (only from the stack management rule page)

## To Test:
### Single Consumer:
1. Create a user with only `logs` feature enabled (ensuring
`stackAlerts` is not enabled).
2. Navigate to the O11Y rule management page
3. Click the create rule button
4. Assert that both ES query and generic threshold rules are available
5. Click ES query and fill out the relevant information and create the
rule
6. Assert that the rule created has `logs` set in the `consumer` field
7. Repeat 5-6 for the generic threshold rule
8. Repeat 2-7 but on the Stack Management rules page  
9. Repeat 1-8 for the `infrastructure` feature. 

### Multiple Consumers:
1. Create a user with `logs`, `infrastructure` and `apm` features
enabled (ensuring `stackAlerts` is not enabled).
2. Navigate to the O11Y rule management page
3. Click the create rule button
4. Assert that both ES query and generic threshold rules are available
5. Click ES query and fill out the relevant information and create the
rule
6. A dropdown should prompt the user to select between 1 of the 3
consumers, select 1
7. Assert that the rule was created with the selected consumer
8. Repeat 5-7 for the generic threshold rule
9. Repeat 2-8 but on the Stack Management rules page

![Screenshot from 2023-08-08
16-45-43](8c5b644a-8bab-4c1b-93b0-acfa956af19c)

![consumer_dropdown_open](a03b7e97-e90e-4bbc-bed0-94a6c677d31d)


### Checklist
- [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

---------

Co-authored-by: Jiawei Wu <74562234+JiaweiWu@users.noreply.github.com>
Co-authored-by: kibanamachine <42973632+kibanamachine@users.noreply.github.com>
This commit is contained in:
Xavier Mouligneau 2023-09-21 18:10:28 -04:00 committed by GitHub
parent a679ab5370
commit e0e0a26b43
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
163 changed files with 2820 additions and 1078 deletions

View file

@ -14,3 +14,4 @@ export * from './src/alerts_as_data_severity';
export * from './src/alerts_as_data_status';
export * from './src/alerts_as_data_cases';
export * from './src/routes/stack_rule_paths';
export * from './src/rule_types';

View file

@ -0,0 +1,10 @@
/*
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
* or more contributor license agreements. Licensed under the Elastic License
* 2.0 and the Server Side Public License, v 1; you may not use this file except
* in compliance with, at your election, the Elastic License 2.0 or the Server
* Side Public License, v 1.
*/
export * from './stack_rules';
export * from './o11y_rules';

View file

@ -0,0 +1,9 @@
/*
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
* or more contributor license agreements. Licensed under the Elastic License
* 2.0 and the Server Side Public License, v 1; you may not use this file except
* in compliance with, at your election, the Elastic License 2.0 or the Server
* Side Public License, v 1.
*/
export const OBSERVABILITY_THRESHOLD_RULE_TYPE_ID = 'observability.rules.custom_threshold';

View file

@ -0,0 +1,10 @@
/*
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
* or more contributor license agreements. Licensed under the Elastic License
* 2.0 and the Server Side Public License, v 1; you may not use this file except
* in compliance with, at your election, the Elastic License 2.0 or the Server
* Side Public License, v 1.
*/
export const STACK_ALERTS_FEATURE_ID = 'stackAlerts';
export const ES_QUERY_ID = '.es-query';

View file

@ -13,6 +13,7 @@ import { FormattedMessage } from '@kbn/i18n-react';
import type { DataView } from '@kbn/data-plugin/common';
import { KibanaRenderContextProvider } from '@kbn/react-kibana-context-render';
import { KibanaContextProvider } from '@kbn/kibana-react-plugin/public';
import { STACK_ALERTS_FEATURE_ID } from '@kbn/rule-data-utils';
import { DiscoverStateContainer } from '../../services/discover_state';
import { DiscoverServices } from '../../../../build_services';
@ -97,7 +98,7 @@ export function AlertsPopover({
return triggersActionsUi?.getAddRuleFlyout({
metadata: discoverMetadata,
consumer: 'discover',
consumer: STACK_ALERTS_FEATURE_ID,
onClose: (_, metadata) => {
onFinishFlyoutInteraction(metadata as EsQueryAlertMetaData);
onClose();

View file

@ -72,6 +72,7 @@
"@kbn/react-kibana-context-render",
"@kbn/unified-data-table",
"@kbn/no-data-page-plugin",
"@kbn/rule-data-utils",
"@kbn/global-search-plugin"
],
"exclude": [

View file

@ -24,6 +24,7 @@ export const aggregateRulesRequestBodySchema = schema.object({
)
),
filter: schema.maybe(schema.string()),
filter_consumers: schema.maybe(schema.arrayOf(schema.string())),
});
export const aggregateRulesResponseBodySchema = schema.object({

View file

@ -16,4 +16,4 @@ import { SanitizedRule } from '../../common';
* originally registered to {@link PluginSetupContract.registerNavigation}.
*
*/
export type AlertNavigationHandler = (rule: SanitizedRule) => string;
export type AlertNavigationHandler = (rule: SanitizedRule) => string | undefined;

View file

@ -138,7 +138,8 @@ export class AlertingPublicPlugin
if (this.alertNavigationRegistry!.has(rule.consumer, ruleType)) {
const navigationHandler = this.alertNavigationRegistry!.get(rule.consumer, ruleType);
return navigationHandler(rule);
const navUrl = navigationHandler(rule);
if (navUrl) return navUrl;
}
if (rule.viewInAppRelativeUrl) {

View file

@ -61,6 +61,7 @@ const ruleType: jest.Mocked<UntypedNormalizedRuleType> = {
mappings: { fieldMap: { field: { type: 'keyword', required: false } } },
shouldWrite: true,
},
validLegacyConsumers: [],
};
const mockLegacyAlertsClient = legacyAlertsClientMock.create();

View file

@ -99,6 +99,7 @@ const ruleType: jest.Mocked<UntypedNormalizedRuleType> = {
validate: {
params: schema.any(),
},
validLegacyConsumers: [],
};
const testAlert1 = {

View file

@ -29,6 +29,7 @@ const ruleType: jest.Mocked<UntypedNormalizedRuleType> = {
mappings: { fieldMap: { field: { type: 'keyword', required: false } } },
shouldWrite: true,
},
validLegacyConsumers: [],
};
describe('formatRule', () => {

View file

@ -203,6 +203,7 @@ const ruleType: jest.Mocked<UntypedNormalizedRuleType> = {
validate: {
params: { validate: (params) => params },
},
validLegacyConsumers: [],
};
const ruleTypeWithAlertDefinition: jest.Mocked<UntypedNormalizedRuleType> = {

View file

@ -82,6 +82,7 @@ describe('aggregate()', () => {
enabledInLicense: true,
hasAlertsMappings: false,
hasFieldsForAAD: false,
validLegacyConsumers: [],
},
]);
beforeEach(() => {
@ -166,6 +167,7 @@ describe('aggregate()', () => {
enabledInLicense: true,
hasAlertsMappings: false,
hasFieldsForAAD: false,
validLegacyConsumers: [],
},
])
);

View file

@ -6,6 +6,7 @@
*/
import { KueryNode, nodeBuilder } from '@kbn/es-query';
import { isEmpty } from 'lodash';
import { findRulesSo } from '../../../../data/rule';
import { AlertingAuthorizationEntity } from '../../../../authorization';
import { ruleAuditEvent, RuleAuditAction } from '../../../../rules_client/common/audit_events';
@ -21,13 +22,14 @@ export async function aggregateRules<T = Record<string, unknown>>(
params: AggregateParams<T>
): Promise<T> {
const { options = {}, aggs } = params;
const { filter, page = 1, perPage = 0, ...restOptions } = options;
const { filter, page = 1, perPage = 0, filterConsumers, ...restOptions } = options;
let authorizationTuple;
try {
authorizationTuple = await context.authorization.getFindAuthorizationFilter(
AlertingAuthorizationEntity.Rule,
alertingAuthorizationFilterOpts
alertingAuthorizationFilterOpts,
isEmpty(filterConsumers) ? undefined : new Set(filterConsumers)
);
validateRuleAggregationFields(aggs);
aggregateOptionsSchema.validate(options);

View file

@ -16,6 +16,7 @@ export const aggregateOptionsSchema = schema.object({
id: schema.string(),
})
),
filterConsumers: schema.maybe(schema.arrayOf(schema.string())),
// filter type is `string | KueryNode`, but `KueryNode` has no schema to import yet
filter: schema.maybe(
schema.oneOf([schema.string(), schema.recordOf(schema.string(), schema.any())])

View file

@ -19,6 +19,7 @@ export type AggregateOptions = TypeOf<typeof aggregateOptionsSchema> & {
filter?: string | KueryNode;
page?: AggregateOptionsSchemaTypes['page'];
perPage?: AggregateOptionsSchemaTypes['perPage'];
filterConsumers?: string[];
};
export interface AggregateParams<AggregationResult> {

View file

@ -243,6 +243,7 @@ describe('bulkEdit()', () => {
validate: {
params: { validate: (params) => params },
},
validLegacyConsumers: [],
});
(migrateLegacyActions as jest.Mock).mockResolvedValue(migrateLegacyActionsMock);
@ -745,6 +746,7 @@ describe('bulkEdit()', () => {
mappings: { fieldMap: { field: { type: 'keyword', required: false } } },
shouldWrite: true,
},
validLegacyConsumers: [],
});
const existingAction = {
frequency: {
@ -2351,6 +2353,7 @@ describe('bulkEdit()', () => {
return { state: {} };
},
producer: 'alerts',
validLegacyConsumers: [],
});
const result = await rulesClient.bulkEdit({
@ -2395,6 +2398,7 @@ describe('bulkEdit()', () => {
return { state: {} };
},
producer: 'alerts',
validLegacyConsumers: [],
});
const result = await rulesClient.bulkEdit({

View file

@ -1551,6 +1551,7 @@ describe('create()', () => {
validate: {
params: { validate: (params) => params },
},
validLegacyConsumers: [],
}));
const data = getMockData({
params: ruleParams,
@ -1738,6 +1739,7 @@ describe('create()', () => {
validate: {
params: { validate: (params) => params },
},
validLegacyConsumers: [],
}));
const data = getMockData({
params: ruleParams,
@ -2557,6 +2559,7 @@ describe('create()', () => {
return { state: {} };
},
producer: 'alerts',
validLegacyConsumers: [],
});
await expect(rulesClient.create({ data })).rejects.toThrowErrorMatchingInlineSnapshot(
`"params invalid: [param1]: expected value of type [string] but got [undefined]"`
@ -3031,6 +3034,7 @@ describe('create()', () => {
validate: {
params: { validate: (params) => params },
},
validLegacyConsumers: [],
}));
const createdAttributes = {
...data,
@ -3103,6 +3107,7 @@ describe('create()', () => {
validate: {
params: { validate: (params) => params },
},
validLegacyConsumers: [],
}));
const data = getMockData({ schedule: { interval: '1s' } });
@ -3140,6 +3145,7 @@ describe('create()', () => {
validate: {
params: { validate: (params) => params },
},
validLegacyConsumers: [],
}));
const data = getMockData({
@ -3232,6 +3238,7 @@ describe('create()', () => {
validate: {
params: { validate: (params) => params },
},
validLegacyConsumers: [],
}));
const data = getMockData({
@ -3281,6 +3288,7 @@ describe('create()', () => {
validate: {
params: { validate: (params) => params },
},
validLegacyConsumers: [],
}));
const data = getMockData({
@ -3343,6 +3351,7 @@ describe('create()', () => {
validate: {
params: { validate: (params) => params },
},
validLegacyConsumers: [],
}));
const data = getMockData({
@ -3423,6 +3432,7 @@ describe('create()', () => {
validate: {
params: { validate: (params) => params },
},
validLegacyConsumers: [],
}));
const data = getMockData({
@ -3627,6 +3637,7 @@ describe('create()', () => {
mappings: { fieldMap: { field: { type: 'keyword', required: false } } },
shouldWrite: true,
},
validLegacyConsumers: [],
}));
const data = getMockData({
@ -3679,6 +3690,7 @@ describe('create()', () => {
validate: {
params: { validate: (params) => params },
},
validLegacyConsumers: [],
}));
const data = getMockData({

View file

@ -6,7 +6,7 @@
*/
import Boom from '@hapi/boom';
import { map, mapValues, fromPairs, has } from 'lodash';
import { has, isEmpty } from 'lodash';
import { KibanaRequest } from '@kbn/core/server';
import { JsonObject } from '@kbn/utility-types';
import { KueryNode } from '@kbn/es-query';
@ -167,42 +167,26 @@ export class AlertingAuthorization {
);
}
public async ensureAuthorized({ ruleTypeId, consumer, operation, entity }: EnsureAuthorizedOpts) {
public async ensureAuthorized({
ruleTypeId,
consumer: legacyConsumer,
operation,
entity,
}: EnsureAuthorizedOpts) {
const { authorization } = this;
const ruleType = this.ruleTypeRegistry.get(ruleTypeId);
const consumer = getValidConsumer({
validLegacyConsumers: ruleType.validLegacyConsumers,
legacyConsumer,
producer: ruleType.producer,
});
const isAvailableConsumer = has(await this.allPossibleConsumers, consumer);
if (authorization && this.shouldCheckAuthorization()) {
const ruleType = this.ruleTypeRegistry.get(ruleTypeId);
const requiredPrivilegesByScope = {
consumer: authorization.actions.alerting.get(ruleTypeId, consumer, entity, operation),
producer: authorization.actions.alerting.get(
ruleTypeId,
ruleType.producer,
entity,
operation
),
};
// Skip authorizing consumer if consumer is the Rules Management consumer (`alerts`)
// This means that rules and their derivative alerts created in the Rules Management UI
// will only be subject to checking if user has access to the rule producer.
const shouldAuthorizeConsumer = consumer !== ALERTS_FEATURE_ID;
const checkPrivileges = authorization.checkPrivilegesDynamicallyWithRequest(this.request);
const { hasAllRequested, privileges } = await checkPrivileges({
kibana:
shouldAuthorizeConsumer && consumer !== ruleType.producer
? [
// check for access at consumer level
requiredPrivilegesByScope.consumer,
// check for access at producer level
requiredPrivilegesByScope.producer,
]
: [
// skip consumer privilege checks under `alerts` as all rule types can
// be created under `alerts` if you have producer level privileges
requiredPrivilegesByScope.producer,
],
const { hasAllRequested } = await checkPrivileges({
kibana: [authorization.actions.alerting.get(ruleTypeId, consumer, entity, operation)],
});
if (!isAvailableConsumer) {
@ -213,51 +197,31 @@ export class AlertingAuthorization {
* as Privileged.
* This check will ensure we don't accidentally let these through
*/
throw Boom.forbidden(
getUnauthorizedMessage(ruleTypeId, ScopeType.Consumer, consumer, operation, entity)
);
throw Boom.forbidden(getUnauthorizedMessage(ruleTypeId, legacyConsumer, operation, entity));
}
if (!hasAllRequested) {
const authorizedPrivileges = map(
privileges.kibana.filter((privilege) => privilege.authorized),
'privilege'
);
const unauthorizedScopes = mapValues(
requiredPrivilegesByScope,
(privilege) => !authorizedPrivileges.includes(privilege)
);
const [unauthorizedScopeType, unauthorizedScope] =
shouldAuthorizeConsumer && unauthorizedScopes.consumer
? [ScopeType.Consumer, consumer]
: [ScopeType.Producer, ruleType.producer];
throw Boom.forbidden(
getUnauthorizedMessage(
ruleTypeId,
unauthorizedScopeType,
unauthorizedScope,
operation,
entity
)
);
throw Boom.forbidden(getUnauthorizedMessage(ruleTypeId, consumer, operation, entity));
}
} else if (!isAvailableConsumer) {
throw Boom.forbidden(
getUnauthorizedMessage(ruleTypeId, ScopeType.Consumer, consumer, operation, entity)
);
throw Boom.forbidden(getUnauthorizedMessage(ruleTypeId, consumer, operation, entity));
}
}
public async getFindAuthorizationFilter(
authorizationEntity: AlertingAuthorizationEntity,
filterOpts: AlertingAuthorizationFilterOpts
filterOpts: AlertingAuthorizationFilterOpts,
featuresIds?: Set<string>
): Promise<{
filter?: KueryNode | JsonObject;
ensureRuleTypeIsAuthorized: (ruleTypeId: string, consumer: string, auth: string) => void;
}> {
return this.getAuthorizationFilter(authorizationEntity, filterOpts, ReadOperations.Find);
return this.getAuthorizationFilter(
authorizationEntity,
filterOpts,
ReadOperations.Find,
featuresIds
);
}
public async getAuthorizedRuleTypes(
@ -276,7 +240,8 @@ export class AlertingAuthorization {
public async getAuthorizationFilter(
authorizationEntity: AlertingAuthorizationEntity,
filterOpts: AlertingAuthorizationFilterOpts,
operation: WriteOperations | ReadOperations
operation: WriteOperations | ReadOperations,
featuresIds?: Set<string>
): Promise<{
filter?: KueryNode | JsonObject;
ensureRuleTypeIsAuthorized: (ruleTypeId: string, consumer: string, auth: string) => void;
@ -285,7 +250,8 @@ export class AlertingAuthorization {
const { authorizedRuleTypes } = await this.augmentRuleTypesWithAuthorization(
this.ruleTypeRegistry.list(),
[operation],
authorizationEntity
authorizationEntity,
featuresIds
);
if (!authorizedRuleTypes.size) {
@ -311,13 +277,7 @@ export class AlertingAuthorization {
ensureRuleTypeIsAuthorized: (ruleTypeId: string, consumer: string, authType: string) => {
if (!authorizedRuleTypeIdsToConsumers.has(`${ruleTypeId}/${consumer}/${authType}`)) {
throw Boom.forbidden(
getUnauthorizedMessage(
ruleTypeId,
ScopeType.Consumer,
consumer,
'find',
authorizationEntity
)
getUnauthorizedMessage(ruleTypeId, consumer, 'find', authorizationEntity)
);
} else {
if (authorizedEntries.has(ruleTypeId)) {
@ -376,6 +336,9 @@ export class AlertingAuthorization {
string,
[RegistryAlertTypeWithAuth, string, HasPrivileges, IsAuthorizedAtProducerLevel]
>();
const allPossibleConsumers = await this.allPossibleConsumers;
const addLegacyConsumerPrivileges = (legacyConsumer: string) =>
legacyConsumer === ALERTS_FEATURE_ID || isEmpty(featuresIds);
for (const feature of fIds) {
const featureDef = this.features
.getKibanaFeatures()
@ -401,6 +364,31 @@ export class AlertingAuthorization {
ruleTypeAuth.producer === feature,
]
);
// FUTURE ENGINEER
// We are just trying to add back the legacy consumers associated
// to the rule type to get back the privileges that was given at one point
if (!isEmpty(ruleTypeAuth.validLegacyConsumers)) {
ruleTypeAuth.validLegacyConsumers.forEach((legacyConsumer) => {
if (addLegacyConsumerPrivileges(legacyConsumer)) {
if (!allPossibleConsumers[legacyConsumer]) {
allPossibleConsumers[legacyConsumer] = {
read: true,
all: true,
};
}
privilegeToRuleType.set(
this.authorization!.actions.alerting.get(
ruleTypeId,
legacyConsumer,
authorizationEntity,
operation
),
[ruleTypeAuth, legacyConsumer, hasPrivilegeByOperation(operation), false]
);
}
});
}
}
}
}
@ -418,7 +406,7 @@ export class AlertingAuthorization {
? // has access to all features
this.augmentWithAuthorizedConsumers(
new Set(ruleTypesAuthorized.values()),
await this.allPossibleConsumers
allPossibleConsumers
)
: // only has some of the required privileges
privileges.kibana.reduce((authorizedRuleTypes, { authorized, privilege }) => {
@ -433,10 +421,14 @@ export class AlertingAuthorization {
if (isAuthorizedAtProducerLevel) {
// granting privileges under the producer automatically authorized the Rules Management UI as well
ruleType.authorizedConsumers[ALERTS_FEATURE_ID] = mergeHasPrivileges(
hasPrivileges,
ruleType.authorizedConsumers[ALERTS_FEATURE_ID]
);
ruleType.validLegacyConsumers.forEach((legacyConsumer) => {
if (addLegacyConsumerPrivileges(legacyConsumer)) {
ruleType.authorizedConsumers[legacyConsumer] = mergeHasPrivileges(
hasPrivileges,
ruleType.authorizedConsumers[legacyConsumer]
);
}
});
}
authorizedRuleTypes.add(ruleType);
}
@ -488,22 +480,30 @@ function asAuthorizedConsumers(
consumers: string[],
hasPrivileges: HasPrivileges
): AuthorizedConsumers {
return fromPairs(consumers.map((feature) => [feature, hasPrivileges]));
}
enum ScopeType {
Consumer,
Producer,
return consumers.reduce<AuthorizedConsumers>((acc, feature) => {
acc[feature] = hasPrivileges;
return acc;
}, {});
}
function getUnauthorizedMessage(
alertTypeId: string,
scopeType: ScopeType,
scope: string,
operation: string,
entity: string
): string {
return `Unauthorized to ${operation} a "${alertTypeId}" ${entity} ${
scopeType === ScopeType.Consumer ? `for "${scope}"` : `by "${scope}"`
}`;
return `Unauthorized by "${scope}" to ${operation} "${alertTypeId}" ${entity}`;
}
export const getValidConsumer = ({
validLegacyConsumers,
legacyConsumer,
producer,
}: {
validLegacyConsumers: string[];
legacyConsumer: string;
producer: string;
}): string =>
legacyConsumer === ALERTS_FEATURE_ID || validLegacyConsumers.includes(legacyConsumer)
? producer
: legacyConsumer;

View file

@ -34,6 +34,7 @@ describe('asKqlFiltersByRuleTypeAndConsumer', () => {
enabledInLicense: true,
hasAlertsMappings: false,
hasFieldsForAAD: false,
validLegacyConsumers: [],
},
]),
{
@ -71,6 +72,7 @@ describe('asKqlFiltersByRuleTypeAndConsumer', () => {
enabledInLicense: true,
hasAlertsMappings: false,
hasFieldsForAAD: false,
validLegacyConsumers: [],
},
]),
{
@ -111,6 +113,7 @@ describe('asKqlFiltersByRuleTypeAndConsumer', () => {
enabledInLicense: true,
hasAlertsMappings: false,
hasFieldsForAAD: false,
validLegacyConsumers: [],
},
{
actionGroups: [],
@ -130,6 +133,7 @@ describe('asKqlFiltersByRuleTypeAndConsumer', () => {
enabledInLicense: true,
hasAlertsMappings: false,
hasFieldsForAAD: false,
validLegacyConsumers: [],
},
{
actionGroups: [],
@ -149,6 +153,7 @@ describe('asKqlFiltersByRuleTypeAndConsumer', () => {
enabledInLicense: true,
hasAlertsMappings: false,
hasFieldsForAAD: false,
validLegacyConsumers: [],
},
]),
{
@ -189,6 +194,7 @@ describe('asKqlFiltersByRuleTypeAndConsumer', () => {
enabledInLicense: true,
hasAlertsMappings: false,
hasFieldsForAAD: false,
validLegacyConsumers: [],
},
{
actionGroups: [],
@ -208,6 +214,7 @@ describe('asKqlFiltersByRuleTypeAndConsumer', () => {
enabledInLicense: true,
hasAlertsMappings: false,
hasFieldsForAAD: false,
validLegacyConsumers: [],
},
]),
{
@ -249,6 +256,7 @@ describe('asKqlFiltersByRuleTypeAndConsumer', () => {
enabledInLicense: true,
hasAlertsMappings: false,
hasFieldsForAAD: false,
validLegacyConsumers: [],
},
{
actionGroups: [],
@ -268,6 +276,7 @@ describe('asKqlFiltersByRuleTypeAndConsumer', () => {
enabledInLicense: true,
hasAlertsMappings: false,
hasFieldsForAAD: false,
validLegacyConsumers: [],
},
]),
{
@ -303,6 +312,7 @@ describe('asKqlFiltersByRuleTypeAndConsumer', () => {
enabledInLicense: true,
hasAlertsMappings: false,
hasFieldsForAAD: false,
validLegacyConsumers: [],
},
]),
{
@ -339,6 +349,7 @@ describe('asEsDslFiltersByRuleTypeAndConsumer', () => {
enabledInLicense: true,
hasAlertsMappings: false,
hasFieldsForAAD: false,
validLegacyConsumers: [],
},
]),
{
@ -403,6 +414,7 @@ describe('asEsDslFiltersByRuleTypeAndConsumer', () => {
enabledInLicense: true,
hasAlertsMappings: false,
hasFieldsForAAD: false,
validLegacyConsumers: [],
},
]),
{
@ -475,6 +487,7 @@ describe('asEsDslFiltersByRuleTypeAndConsumer', () => {
enabledInLicense: true,
hasAlertsMappings: false,
hasFieldsForAAD: false,
validLegacyConsumers: [],
},
{
actionGroups: [],
@ -494,6 +507,7 @@ describe('asEsDslFiltersByRuleTypeAndConsumer', () => {
enabledInLicense: true,
hasAlertsMappings: false,
hasFieldsForAAD: false,
validLegacyConsumers: [],
},
{
actionGroups: [],
@ -513,6 +527,7 @@ describe('asEsDslFiltersByRuleTypeAndConsumer', () => {
enabledInLicense: true,
hasAlertsMappings: false,
hasFieldsForAAD: false,
validLegacyConsumers: [],
},
]),
{
@ -678,6 +693,7 @@ describe('asEsDslFiltersByRuleTypeAndConsumer', () => {
enabledInLicense: true,
hasAlertsMappings: false,
hasFieldsForAAD: false,
validLegacyConsumers: [],
},
]),
{

View file

@ -46,6 +46,7 @@ const ruleType: jest.Mocked<UntypedNormalizedRuleType> = {
validate: {
params: schema.any(),
},
validLegacyConsumers: [],
};
const context: RuleContextOpts = {

View file

@ -26,6 +26,7 @@ describe('createAlertEventLogRecordObject', () => {
validate: {
params: schema.any(),
},
validLegacyConsumers: [],
};
test('created alert event "execute-start"', async () => {

View file

@ -79,6 +79,7 @@ describe('findRulesRoute', () => {
"includeSnoozeData": true,
"options": Object {
"defaultSearchOperator": "OR",
"filterConsumers": undefined,
"page": 1,
"perPage": 1,
},

View file

@ -47,6 +47,7 @@ const querySchema = schema.object({
),
fields: schema.maybe(schema.arrayOf(schema.string())),
filter: schema.maybe(schema.string()),
filter_consumers: schema.maybe(schema.arrayOf(schema.string())),
});
const rewriteQueryReq: RewriteRequestCase<FindOptions> = ({
@ -56,11 +57,13 @@ const rewriteQueryReq: RewriteRequestCase<FindOptions> = ({
per_page: perPage,
sort_field: sortField,
sort_order: sortOrder,
filter_consumers: filterConsumers,
...rest
}) => ({
...rest,
defaultSearchOperator,
perPage,
filterConsumers,
...(sortField ? { sortField } : {}),
...(sortOrder ? { sortOrder } : {}),
...(hasReference ? { hasReference } : {}),

View file

@ -51,6 +51,7 @@ const ruleTypes = [
defaultScheduleInterval: '10m',
hasAlertsMappings: false,
hasFieldsForAAD: false,
validLegacyConsumers: [],
} as RegistryAlertTypeWithAuth,
];

View file

@ -55,6 +55,7 @@ const ruleTypes = [
defaultScheduleInterval: '10m',
hasAlertsMappings: false,
hasFieldsForAAD: false,
validLegacyConsumers: [],
} as RegistryAlertTypeWithAuth,
];

View file

@ -64,6 +64,7 @@ describe('listAlertTypesRoute', () => {
enabledInLicense: true,
hasAlertsMappings: false,
hasFieldsForAAD: false,
validLegacyConsumers: [],
} as RegistryAlertTypeWithAuth,
];
rulesClient.listRuleTypes.mockResolvedValueOnce(new Set(listTypes));
@ -98,6 +99,7 @@ describe('listAlertTypesRoute', () => {
"id": "recovered",
"name": "Recovered",
},
"validLegacyConsumers": Array [],
},
],
}
@ -143,6 +145,7 @@ describe('listAlertTypesRoute', () => {
enabledInLicense: true,
hasAlertsMappings: false,
hasFieldsForAAD: false,
validLegacyConsumers: [],
} as RegistryAlertTypeWithAuth,
];
@ -198,6 +201,7 @@ describe('listAlertTypesRoute', () => {
enabledInLicense: true,
hasAlertsMappings: false,
hasFieldsForAAD: false,
validLegacyConsumers: [],
} as RegistryAlertTypeWithAuth,
];

View file

@ -13,11 +13,13 @@ export const transformAggregateQueryRequest: RewriteRequestCase<AggregateOptions
default_search_operator: defaultSearchOperator,
search_fields: searchFields,
has_reference: hasReference,
filter_consumers: filterConsumers,
filter,
}) => ({
defaultSearchOperator,
...(hasReference ? { hasReference } : {}),
...(searchFields ? { searchFields } : {}),
...(search ? { search } : {}),
...(filterConsumers ? { filterConsumers } : {}),
...(filter ? { filter } : {}),
});

View file

@ -62,9 +62,12 @@ describe('ruleTypesRoute', () => {
doesSetRecoveryContext: false,
hasAlertsMappings: true,
hasFieldsForAAD: false,
validLegacyConsumers: [],
} as RegistryAlertTypeWithAuth,
];
const expectedResult: Array<AsApiContract<RegistryAlertTypeWithAuth>> = [
const expectedResult: Array<
AsApiContract<Omit<RegistryAlertTypeWithAuth, 'validLegacyConsumers'>>
> = [
{
id: '1',
name: 'name',
@ -172,6 +175,7 @@ describe('ruleTypesRoute', () => {
enabledInLicense: true,
hasAlertsMappings: false,
hasFieldsForAAD: false,
validLegacyConsumers: [],
} as RegistryAlertTypeWithAuth,
];
@ -227,6 +231,7 @@ describe('ruleTypesRoute', () => {
enabledInLicense: true,
hasAlertsMappings: false,
hasFieldsForAAD: false,
validLegacyConsumers: [],
} as RegistryAlertTypeWithAuth,
];

View file

@ -8,10 +8,10 @@
import { IRouter } from '@kbn/core/server';
import { ILicenseState } from '../lib';
import { RegistryAlertTypeWithAuth } from '../authorization';
import { RewriteResponseCase, verifyAccessAndContext } from './lib';
import { verifyAccessAndContext } from './lib';
import { AlertingRequestHandlerContext, BASE_ALERTING_API_PATH } from '../types';
const rewriteBodyRes: RewriteResponseCase<RegistryAlertTypeWithAuth[]> = (results) => {
const rewriteBodyRes = (results: RegistryAlertTypeWithAuth[]) => {
return results.map(
({
enabledInLicense,
@ -27,8 +27,9 @@ const rewriteBodyRes: RewriteResponseCase<RegistryAlertTypeWithAuth[]> = (result
doesSetRecoveryContext,
hasAlertsMappings,
hasFieldsForAAD,
validLegacyConsumers,
...rest
}) => ({
}: RegistryAlertTypeWithAuth) => ({
...rest,
enabled_in_license: enabledInLicense,
recovery_action_group: recoveryActionGroup,

View file

@ -590,40 +590,41 @@ describe('Create Lifecycle', () => {
});
const ruleType = registry.get('test');
expect(ruleType).toMatchInlineSnapshot(`
Object {
"actionGroups": Array [
Object {
"id": "default",
"name": "Default",
},
Object {
"id": "recovered",
"name": "Recovered",
},
],
"actionVariables": Object {
"context": Array [],
"params": Array [],
"state": Array [],
},
"defaultActionGroupId": "default",
"executor": [MockFunction],
"id": "test",
"isExportable": true,
"minimumLicenseRequired": "basic",
"name": "Test",
"producer": "alerts",
"recoveryActionGroup": Object {
"id": "recovered",
"name": "Recovered",
},
"validate": Object {
"params": Object {
"validate": [Function],
},
},
}
`);
Object {
"actionGroups": Array [
Object {
"id": "default",
"name": "Default",
},
Object {
"id": "recovered",
"name": "Recovered",
},
],
"actionVariables": Object {
"context": Array [],
"params": Array [],
"state": Array [],
},
"defaultActionGroupId": "default",
"executor": [MockFunction],
"id": "test",
"isExportable": true,
"minimumLicenseRequired": "basic",
"name": "Test",
"producer": "alerts",
"recoveryActionGroup": Object {
"id": "recovered",
"name": "Recovered",
},
"validLegacyConsumers": Array [],
"validate": Object {
"params": Object {
"validate": [Function],
},
},
}
`);
});
test(`should throw an error if type isn't registered`, () => {
@ -713,6 +714,7 @@ describe('Create Lifecycle', () => {
"name": "Recovered",
},
"ruleTaskTimeout": "20m",
"validLegacyConsumers": Array [],
},
}
`);

View file

@ -38,6 +38,7 @@ import { getRuleTypeFeatureUsageName } from './lib/get_rule_type_feature_usage_n
import { InMemoryMetrics } from './monitoring';
import { AlertingRulesConfig } from '.';
import { AlertsService } from './alerts_service/alerts_service';
import { getRuleTypeIdValidLegacyConsumers } from './rule_type_registry_deprecated_consumers';
export interface ConstructorOptions {
logger: Logger;
@ -70,6 +71,7 @@ export interface RegistryRuleType
enabledInLicense: boolean;
hasFieldsForAAD: boolean;
hasAlertsMappings: boolean;
validLegacyConsumers: string[];
}
/**
@ -102,6 +104,7 @@ export type NormalizedRuleType<
RecoveryActionGroupId extends string,
AlertData extends RuleAlertData
> = {
validLegacyConsumers: string[];
actionGroups: Array<ActionGroup<ActionGroupIds | RecoveryActionGroupId>>;
} & Omit<
RuleType<
@ -386,6 +389,7 @@ export class RuleTypeRegistry {
doesSetRecoveryContext,
alerts,
fieldsForAAD,
validLegacyConsumers,
},
]) => {
// KEEP the type here to be safe if not the map is ignoring it for some reason
@ -409,6 +413,7 @@ export class RuleTypeRegistry {
).isValid,
hasFieldsForAAD: Boolean(fieldsForAAD),
hasAlertsMappings: !!alerts,
validLegacyConsumers,
...(alerts ? { alerts } : {}),
};
return ruleType;
@ -499,5 +504,6 @@ function augmentActionGroupsWithReserved<
...ruleType,
actionGroups: [...actionGroups, ...reservedActionGroups],
recoveryActionGroup: recoveryActionGroup ?? RecoveredActionGroup,
validLegacyConsumers: getRuleTypeIdValidLegacyConsumers(id),
};
}

View file

@ -0,0 +1,103 @@
/*
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
* or more contributor license agreements. Licensed under the Elastic License
* 2.0; you may not use this file except in compliance with the Elastic License
* 2.0.
*/
import {
getRuleTypeIdValidLegacyConsumers,
ruleTypeIdWithValidLegacyConsumers,
} from './rule_type_registry_deprecated_consumers';
describe('rule_type_registry_deprecated_consumers', () => {
describe('ruleTypeIdWithValidLegacyConsumers', () => {
test('Only these rule type ids should be in the list', () => {
expect(Object.keys(ruleTypeIdWithValidLegacyConsumers)).toMatchInlineSnapshot(`
Array [
"example.always-firing",
"transform_health",
".index-threshold",
".geo-containment",
".es-query",
"xpack.ml.anomaly_detection_alert",
"xpack.ml.anomaly_detection_jobs_health",
"xpack.synthetics.alerts.monitorStatus",
"xpack.synthetics.alerts.tls",
"xpack.uptime.alerts.monitorStatus",
"xpack.uptime.alerts.tlsCertificate",
"xpack.uptime.alerts.durationAnomaly",
"xpack.uptime.alerts.tls",
"siem.eqlRule",
"siem.savedQueryRule",
"siem.indicatorRule",
"siem.mlRule",
"siem.queryRule",
"siem.thresholdRule",
"siem.newTermsRule",
"siem.notifications",
"slo.rules.burnRate",
"metrics.alert.anomaly",
"logs.alert.document.count",
"metrics.alert.inventory.threshold",
"metrics.alert.threshold",
"monitoring_alert_cluster_health",
"monitoring_alert_license_expiration",
"monitoring_alert_cpu_usage",
"monitoring_alert_missing_monitoring_data",
"monitoring_alert_disk_usage",
"monitoring_alert_thread_pool_search_rejections",
"monitoring_alert_thread_pool_write_rejections",
"monitoring_alert_jvm_memory_usage",
"monitoring_alert_nodes_changed",
"monitoring_alert_logstash_version_mismatch",
"monitoring_alert_kibana_version_mismatch",
"monitoring_alert_elasticsearch_version_mismatch",
"monitoring_ccr_read_exceptions",
"monitoring_shard_size",
"apm.transaction_duration",
"apm.anomaly",
"apm.error_rate",
"apm.transaction_error_rate",
"test.always-firing",
"test.always-firing-alert-as-data",
"test.authorization",
"test.cancellableRule",
"test.cumulative-firing",
"test.exceedsAlertLimit",
"test.failing",
"test.gold.noop",
"test.longRunning",
"test.multipleSearches",
"test.never-firing",
"test.noop",
"test.onlyContextVariables",
"test.onlyStateVariables",
"test.patternFiring",
"test.patternFiringAad",
"test.patternFiringAutoRecoverFalse",
"test.patternLongRunning",
"test.patternLongRunning.cancelAlertsOnRuleTimeout",
"test.patternSuccessOrFailure",
"test.restricted-noop",
"test.throw",
"test.unrestricted-noop",
"test.validation",
]
`);
});
});
describe('getRuleTypeIdValidLegacyConsumers', () => {
test('".es-query" should have "alerts" & "discover" as legacy consumers', () => {
expect(getRuleTypeIdValidLegacyConsumers('.es-query')).toEqual(['alerts', 'discover']);
});
test('All other rule types except ".es-query" should have "alerts" as legacy consumer', () => {
for (const ruleTypeId of Object.keys(ruleTypeIdWithValidLegacyConsumers).filter(
(rt) => rt !== '.es-query'
)) {
expect(getRuleTypeIdValidLegacyConsumers(ruleTypeId)).toEqual(['alerts']);
}
});
});
});

View file

@ -0,0 +1,88 @@
/*
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
* or more contributor license agreements. Licensed under the Elastic License
* 2.0; you may not use this file except in compliance with the Elastic License
* 2.0.
*/
import { ALERTS_FEATURE_ID } from './types';
export const ruleTypeIdWithValidLegacyConsumers: Record<string, string[]> = {
'example.always-firing': [ALERTS_FEATURE_ID],
transform_health: [ALERTS_FEATURE_ID],
'.index-threshold': [ALERTS_FEATURE_ID],
'.geo-containment': [ALERTS_FEATURE_ID],
'.es-query': [ALERTS_FEATURE_ID, 'discover'],
'xpack.ml.anomaly_detection_alert': [ALERTS_FEATURE_ID],
'xpack.ml.anomaly_detection_jobs_health': [ALERTS_FEATURE_ID],
'xpack.synthetics.alerts.monitorStatus': [ALERTS_FEATURE_ID],
'xpack.synthetics.alerts.tls': [ALERTS_FEATURE_ID],
'xpack.uptime.alerts.monitorStatus': [ALERTS_FEATURE_ID],
'xpack.uptime.alerts.tlsCertificate': [ALERTS_FEATURE_ID],
'xpack.uptime.alerts.durationAnomaly': [ALERTS_FEATURE_ID],
'xpack.uptime.alerts.tls': [ALERTS_FEATURE_ID],
'siem.eqlRule': [ALERTS_FEATURE_ID],
'siem.savedQueryRule': [ALERTS_FEATURE_ID],
'siem.indicatorRule': [ALERTS_FEATURE_ID],
'siem.mlRule': [ALERTS_FEATURE_ID],
'siem.queryRule': [ALERTS_FEATURE_ID],
'siem.thresholdRule': [ALERTS_FEATURE_ID],
'siem.newTermsRule': [ALERTS_FEATURE_ID],
'siem.notifications': [ALERTS_FEATURE_ID],
'slo.rules.burnRate': [ALERTS_FEATURE_ID],
'metrics.alert.anomaly': [ALERTS_FEATURE_ID],
'logs.alert.document.count': [ALERTS_FEATURE_ID],
'metrics.alert.inventory.threshold': [ALERTS_FEATURE_ID],
'metrics.alert.threshold': [ALERTS_FEATURE_ID],
monitoring_alert_cluster_health: [ALERTS_FEATURE_ID],
monitoring_alert_license_expiration: [ALERTS_FEATURE_ID],
monitoring_alert_cpu_usage: [ALERTS_FEATURE_ID],
monitoring_alert_missing_monitoring_data: [ALERTS_FEATURE_ID],
monitoring_alert_disk_usage: [ALERTS_FEATURE_ID],
monitoring_alert_thread_pool_search_rejections: [ALERTS_FEATURE_ID],
monitoring_alert_thread_pool_write_rejections: [ALERTS_FEATURE_ID],
monitoring_alert_jvm_memory_usage: [ALERTS_FEATURE_ID],
monitoring_alert_nodes_changed: [ALERTS_FEATURE_ID],
monitoring_alert_logstash_version_mismatch: [ALERTS_FEATURE_ID],
monitoring_alert_kibana_version_mismatch: [ALERTS_FEATURE_ID],
monitoring_alert_elasticsearch_version_mismatch: [ALERTS_FEATURE_ID],
monitoring_ccr_read_exceptions: [ALERTS_FEATURE_ID],
monitoring_shard_size: [ALERTS_FEATURE_ID],
'apm.transaction_duration': [ALERTS_FEATURE_ID],
'apm.anomaly': [ALERTS_FEATURE_ID],
'apm.error_rate': [ALERTS_FEATURE_ID],
'apm.transaction_error_rate': [ALERTS_FEATURE_ID],
'test.always-firing': [ALERTS_FEATURE_ID],
'test.always-firing-alert-as-data': [ALERTS_FEATURE_ID],
'test.authorization': [ALERTS_FEATURE_ID],
'test.cancellableRule': [ALERTS_FEATURE_ID],
'test.cumulative-firing': [ALERTS_FEATURE_ID],
'test.exceedsAlertLimit': [ALERTS_FEATURE_ID],
'test.failing': [ALERTS_FEATURE_ID],
'test.gold.noop': [ALERTS_FEATURE_ID],
'test.longRunning': [ALERTS_FEATURE_ID],
'test.multipleSearches': [ALERTS_FEATURE_ID],
'test.never-firing': [ALERTS_FEATURE_ID],
'test.noop': [ALERTS_FEATURE_ID],
'test.onlyContextVariables': [ALERTS_FEATURE_ID],
'test.onlyStateVariables': [ALERTS_FEATURE_ID],
'test.patternFiring': [ALERTS_FEATURE_ID],
'test.patternFiringAad': [ALERTS_FEATURE_ID],
'test.patternFiringAutoRecoverFalse': [ALERTS_FEATURE_ID],
'test.patternLongRunning': [ALERTS_FEATURE_ID],
'test.patternLongRunning.cancelAlertsOnRuleTimeout': [ALERTS_FEATURE_ID],
'test.patternSuccessOrFailure': [ALERTS_FEATURE_ID],
'test.restricted-noop': [ALERTS_FEATURE_ID],
'test.throw': [ALERTS_FEATURE_ID],
'test.unrestricted-noop': [ALERTS_FEATURE_ID],
'test.validation': [ALERTS_FEATURE_ID],
};
const getRuleTypeIdValidLegacyConsumers = (ruleTypeId: string): string[] => {
if (ruleTypeIdWithValidLegacyConsumers[ruleTypeId]) {
return ruleTypeIdWithValidLegacyConsumers[ruleTypeId];
}
return [];
};
export { getRuleTypeIdValidLegacyConsumers };

View file

@ -46,6 +46,7 @@ const ruleType: jest.Mocked<UntypedNormalizedRuleType> = {
validate: {
params: { validate: (params) => params },
},
validLegacyConsumers: [],
};
const context = {

View file

@ -32,6 +32,7 @@ describe('validateActions', () => {
context: 'context',
mappings: { fieldMap: { field: { type: 'fieldType', required: false } } },
},
validLegacyConsumers: [],
};
const data = {

View file

@ -7,7 +7,7 @@
import Boom from '@hapi/boom';
import type * as estypes from '@elastic/elasticsearch/lib/api/typesWithBodyKey';
import { pick } from 'lodash';
import { isEmpty, pick } from 'lodash';
import { KueryNode, nodeBuilder } from '@kbn/es-query';
import { AlertConsumers } from '@kbn/rule-data-utils';
import { RawRule, RuleTypeParams, SanitizedRule, Rule } from '../../types';
@ -34,6 +34,7 @@ export interface FindParams {
options?: FindOptions;
excludeFromPublicApi?: boolean;
includeSnoozeData?: boolean;
featuresIds?: string[];
}
export interface FindOptions extends IndexType {
@ -50,6 +51,7 @@ export interface FindOptions extends IndexType {
};
fields?: string[];
filter?: string | KueryNode;
filterConsumers?: string[];
}
export interface FindResult<Params extends RuleTypeParams> {
@ -62,7 +64,7 @@ export interface FindResult<Params extends RuleTypeParams> {
export async function find<Params extends RuleTypeParams = never>(
context: RulesClientContext,
{
options: { fields, ...options } = {},
options: { fields, filterConsumers, ...options } = {},
excludeFromPublicApi = false,
includeSnoozeData = false,
}: FindParams = {}
@ -71,7 +73,8 @@ export async function find<Params extends RuleTypeParams = never>(
try {
authorizationTuple = await context.authorization.getFindAuthorizationFilter(
AlertingAuthorizationEntity.Rule,
alertingAuthorizationFilterOpts
alertingAuthorizationFilterOpts,
isEmpty(filterConsumers) ? undefined : new Set(filterConsumers)
);
} catch (error) {
context.auditLogger?.log(
@ -84,7 +87,6 @@ export async function find<Params extends RuleTypeParams = never>(
}
const { filter: authorizationFilter, ensureRuleTypeIsAuthorized } = authorizationTuple;
const filterKueryNode = buildKueryNodeFilter(options.filter);
let sortField = mapSortField(options.sortField);
if (excludeFromPublicApi) {

View file

@ -181,6 +181,7 @@ describe('bulkDelete', () => {
validate: {
params: schema.any(),
},
validLegacyConsumers: [],
});
});

View file

@ -91,6 +91,7 @@ describe('find()', () => {
enabledInLicense: true,
hasAlertsMappings: false,
hasFieldsForAAD: false,
validLegacyConsumers: [],
},
]);
beforeEach(() => {
@ -153,6 +154,7 @@ describe('find()', () => {
enabledInLicense: true,
hasAlertsMappings: false,
hasFieldsForAAD: false,
validLegacyConsumers: [],
},
])
);
@ -466,6 +468,7 @@ describe('find()', () => {
enabledInLicense: true,
hasAlertsMappings: false,
hasFieldsForAAD: false,
validLegacyConsumers: [],
},
])
);
@ -484,6 +487,7 @@ describe('find()', () => {
validate: {
params: schema.any(),
},
validLegacyConsumers: [],
}));
ruleTypeRegistry.get.mockImplementationOnce(() => ({
id: '123',
@ -504,6 +508,7 @@ describe('find()', () => {
validate: {
params: schema.any(),
},
validLegacyConsumers: [],
}));
unsecuredSavedObjectsClient.find.mockResolvedValue({
total: 2,
@ -674,6 +679,7 @@ describe('find()', () => {
enabledInLicense: true,
hasAlertsMappings: false,
hasFieldsForAAD: false,
validLegacyConsumers: [],
},
])
);
@ -692,6 +698,7 @@ describe('find()', () => {
validate: {
params: schema.any(),
},
validLegacyConsumers: [],
}));
ruleTypeRegistry.get.mockImplementationOnce(() => ({
id: '123',
@ -712,6 +719,7 @@ describe('find()', () => {
validate: {
params: schema.any(),
},
validLegacyConsumers: [],
}));
unsecuredSavedObjectsClient.find.mockResolvedValue({
total: 2,

View file

@ -319,6 +319,7 @@ describe('get()', () => {
validate: {
params: { validate: (params) => params },
},
validLegacyConsumers: [],
}));
const rulesClient = new RulesClient(rulesClientParams);
unsecuredSavedObjectsClient.get.mockResolvedValueOnce({
@ -445,6 +446,7 @@ describe('get()', () => {
validate: {
params: { validate: (params) => params },
},
validLegacyConsumers: [],
}));
const rulesClient = new RulesClient(rulesClientParams);
unsecuredSavedObjectsClient.get.mockResolvedValueOnce({

View file

@ -70,6 +70,7 @@ const listedTypes = new Set<RegistryRuleType>([
enabledInLicense: true,
hasAlertsMappings: false,
hasFieldsForAAD: false,
validLegacyConsumers: [],
},
]);
@ -123,6 +124,7 @@ describe('getTags()', () => {
enabledInLicense: true,
hasAlertsMappings: false,
hasFieldsForAAD: false,
validLegacyConsumers: [],
},
])
);

View file

@ -122,6 +122,7 @@ export function getBeforeSetup(
validate: {
params: { validate: (params) => params },
},
validLegacyConsumers: [],
}));
rulesClientParams.getEventLogClient.mockResolvedValue(
eventLogClient ?? eventLogClientMock.create()

View file

@ -76,6 +76,7 @@ describe('listRuleTypes', () => {
enabledInLicense: true,
hasAlertsMappings: false,
hasFieldsForAAD: false,
validLegacyConsumers: [],
};
const myAppAlertType: RegistryRuleType = {
actionGroups: [],
@ -90,6 +91,7 @@ describe('listRuleTypes', () => {
enabledInLicense: true,
hasAlertsMappings: false,
hasFieldsForAAD: false,
validLegacyConsumers: [],
};
const setOfAlertTypes = new Set([myAppAlertType, alertingAlertType]);
@ -134,6 +136,7 @@ describe('listRuleTypes', () => {
enabledInLicense: true,
hasAlertsMappings: false,
hasFieldsForAAD: false,
validLegacyConsumers: [],
},
{
id: 'myOtherType',
@ -147,6 +150,7 @@ describe('listRuleTypes', () => {
enabledInLicense: true,
hasAlertsMappings: false,
hasFieldsForAAD: false,
validLegacyConsumers: [],
},
]);
beforeEach(() => {
@ -170,6 +174,7 @@ describe('listRuleTypes', () => {
enabledInLicense: true,
hasAlertsMappings: false,
hasFieldsForAAD: false,
validLegacyConsumers: [],
},
]);
authorization.filterByRuleTypeAuthorization.mockResolvedValue(authorizedTypes);

View file

@ -294,6 +294,7 @@ describe('resolve()', () => {
validate: {
params: { validate: (params) => params },
},
validLegacyConsumers: [],
}));
const rulesClient = new RulesClient(rulesClientParams);
unsecuredSavedObjectsClient.resolve.mockResolvedValueOnce({
@ -430,6 +431,7 @@ describe('resolve()', () => {
validate: {
params: { validate: (params) => params },
},
validLegacyConsumers: [],
}));
const rulesClient = new RulesClient(rulesClientParams);
unsecuredSavedObjectsClient.resolve.mockResolvedValueOnce({

View file

@ -186,6 +186,7 @@ describe('update()', () => {
validate: {
params: { validate: (params) => params },
},
validLegacyConsumers: [],
});
(migrateLegacyActions as jest.Mock).mockResolvedValue({
hasLegacyActions: false,
@ -1008,6 +1009,7 @@ describe('update()', () => {
validate: {
params: { validate: (params) => params },
},
validLegacyConsumers: [],
}));
unsecuredSavedObjectsClient.create.mockResolvedValueOnce({
id: '1',
@ -1523,6 +1525,7 @@ describe('update()', () => {
return { state: {} };
},
producer: 'alerts',
validLegacyConsumers: [],
});
await expect(
rulesClient.update({
@ -1907,6 +1910,7 @@ describe('update()', () => {
validate: {
params: { validate: (params) => params },
},
validLegacyConsumers: [],
});
encryptedSavedObjects.getDecryptedAsInternalUser.mockResolvedValueOnce({
id: alertId,

View file

@ -373,6 +373,7 @@ beforeEach(() => {
validate: {
params: { validate: (params) => params },
},
validLegacyConsumers: [],
}));
ruleTypeRegistry.get.mockReturnValue({
@ -390,6 +391,7 @@ beforeEach(() => {
validate: {
params: { validate: (params) => params },
},
validLegacyConsumers: [],
});
rulesClient = new RulesClient(rulesClientParams);

View file

@ -81,6 +81,7 @@ const ruleType: NormalizedRuleType<
mappings: { fieldMap: { field: { type: 'fieldType', required: false } } },
},
autoRecoverAlerts: false,
validLegacyConsumers: [],
};
const rule = {
id: '1',

View file

@ -152,6 +152,7 @@ export const ruleType: jest.Mocked<UntypedNormalizedRuleType> = {
context: 'test',
mappings: { fieldMap: { field: { type: 'keyword', required: false } } },
},
validLegacyConsumers: [],
};
export const mockRunNowResponse = {

View file

@ -63,6 +63,7 @@ const ruleType: UntypedNormalizedRuleType = {
validate: {
params: schema.any(),
},
validLegacyConsumers: [],
};
let fakeTimer: sinon.SinonFakeTimers;

View file

@ -12,17 +12,14 @@ import {
LicensingPluginSetup,
LicensingApiRequestHandlerContext,
} from '@kbn/licensing-plugin/server';
import { APM_INDEX_SETTINGS_SAVED_OBJECT_TYPE } from '@kbn/apm-data-access-plugin/server/saved_objects/apm_indices';
import { OBSERVABILITY_THRESHOLD_RULE_TYPE_ID } from '@kbn/observability-plugin/common/constants';
import {
ApmRuleType,
APM_SERVER_FEATURE_ID,
} from '../common/rules/apm_rule_types';
const ruleTypes = [
...Object.values(ApmRuleType),
OBSERVABILITY_THRESHOLD_RULE_TYPE_ID,
];
const ruleTypes = Object.values(ApmRuleType);
export const APM_FEATURE = {
id: APM_SERVER_FEATURE_ID,

View file

@ -680,15 +680,11 @@ Array [
"privilege": Object {
"alerting": Object {
"alert": Object {
"all": Array [
".es-query",
],
"all": Array [],
"read": Array [],
},
"rule": Object {
"all": Array [
".es-query",
],
"all": Array [],
"read": Array [],
},
},
@ -739,18 +735,6 @@ Array [
},
Object {
"privilege": Object {
"alerting": Object {
"alert": Object {
"all": Array [
".es-query",
],
},
"rule": Object {
"all": Array [
".es-query",
],
},
},
"app": Array [
"discover",
"kibana",
@ -1284,15 +1268,11 @@ Array [
"privilege": Object {
"alerting": Object {
"alert": Object {
"all": Array [
".es-query",
],
"all": Array [],
"read": Array [],
},
"rule": Object {
"all": Array [
".es-query",
],
"all": Array [],
"read": Array [],
},
},
@ -1343,18 +1323,6 @@ Array [
},
Object {
"privilege": Object {
"alerting": Object {
"alert": Object {
"all": Array [
".es-query",
],
},
"rule": Object {
"all": Array [
".es-query",
],
},
},
"app": Array [
"discover",
"kibana",

View file

@ -32,7 +32,6 @@ export const buildOSSFeatures = ({
category: DEFAULT_APP_CATEGORIES.kibana,
app: ['discover', 'kibana'],
catalogue: ['discover'],
alerting: ['.es-query'],
privileges: {
all: {
app: ['discover', 'kibana'],
@ -43,14 +42,6 @@ export const buildOSSFeatures = ({
read: ['index-pattern'],
},
ui: ['show', 'save', 'saveQuery'],
alerting: {
rule: {
all: ['.es-query'],
},
alert: {
all: ['.es-query'],
},
},
},
read: {
app: ['discover', 'kibana'],
@ -60,14 +51,6 @@ export const buildOSSFeatures = ({
read: ['index-pattern', 'search', 'query'],
},
ui: ['show'],
alerting: {
rule: {
all: ['.es-query'],
},
alert: {
all: ['.es-query'],
},
},
},
},
subFeatures: [

View file

@ -8,8 +8,9 @@
import { i18n } from '@kbn/i18n';
import { DEFAULT_APP_CATEGORIES } from '@kbn/core/server';
import { logViewSavedObjectName } from '@kbn/logs-shared-plugin/server';
import { OBSERVABILITY_THRESHOLD_RULE_TYPE_ID } from '@kbn/rule-data-utils';
import { ES_QUERY_ID } from '@kbn/rule-data-utils';
import { metricsDataSourceSavedObjectName } from '@kbn/metrics-data-access-plugin/server';
import { OBSERVABILITY_THRESHOLD_RULE_TYPE_ID } from '@kbn/observability-plugin/common/constants';
import { LOG_DOCUMENT_COUNT_RULE_TYPE_ID } from '../common/alerting/logs/log_threshold/types';
import {
METRIC_INVENTORY_THRESHOLD_ALERT_TYPE_ID,
@ -21,6 +22,7 @@ import { infraSourceConfigurationSavedObjectName } from './lib/sources/saved_obj
const metricRuleTypes = [
METRIC_THRESHOLD_ALERT_TYPE_ID,
METRIC_INVENTORY_THRESHOLD_ALERT_TYPE_ID,
ES_QUERY_ID,
OBSERVABILITY_THRESHOLD_RULE_TYPE_ID,
];
@ -83,7 +85,11 @@ export const METRICS_FEATURE = {
},
};
const logsRuleTypes = [LOG_DOCUMENT_COUNT_RULE_TYPE_ID, OBSERVABILITY_THRESHOLD_RULE_TYPE_ID];
const logsRuleTypes = [
LOG_DOCUMENT_COUNT_RULE_TYPE_ID,
ES_QUERY_ID,
OBSERVABILITY_THRESHOLD_RULE_TYPE_ID,
];
export const LOGS_FEATURE = {
id: LOGS_FEATURE_ID,

View file

@ -8,9 +8,9 @@
import { i18n } from '@kbn/i18n';
import { AlertConsumers } from '@kbn/rule-data-utils';
import type { ValidFeatureId } from '@kbn/rule-data-utils';
import type { RuleCreationValidConsumer } from '@kbn/triggers-actions-ui-plugin/public';
export const SLO_BURN_RATE_RULE_TYPE_ID = 'slo.rules.burnRate';
export const OBSERVABILITY_THRESHOLD_RULE_TYPE_ID = 'observability.rules.custom_threshold';
export const INVALID_EQUATION_REGEX = /[^A-Z|+|\-|\s|\d+|\.|\(|\)|\/|\*|>|<|=|\?|\:|&|\!|\|]+/g;
export const ALERT_STATUS_ALL = 'all';
@ -56,3 +56,8 @@ export const observabilityAlertFeatureIds: ValidFeatureId[] = [
AlertConsumers.SLO,
AlertConsumers.OBSERVABILITY,
];
export const observabilityRuleCreationValidConsumers: RuleCreationValidConsumer[] = [
AlertConsumers.INFRASTRUCTURE,
AlertConsumers.LOGS,
];

View file

@ -6,12 +6,14 @@
*/
import React, { useCallback, useContext, useMemo } from 'react';
import { OBSERVABILITY_THRESHOLD_RULE_TYPE_ID } from '../../../../common/constants';
import { OBSERVABILITY_THRESHOLD_RULE_TYPE_ID } from '@kbn/rule-data-utils';
import { MetricsExplorerSeries } from '../../../../common/custom_threshold_rule/metrics_explorer';
import { TriggerActionsContext } from './triggers_actions_context';
import { useAlertPrefillContext } from '../helpers/use_alert_prefill';
import { MetricsExplorerOptions } from '../hooks/use_metrics_explorer_options';
import { observabilityRuleCreationValidConsumers } from '../../../../common/constants';
interface Props {
visible?: boolean;
@ -28,7 +30,7 @@ export function AlertFlyout(props: Props) {
() =>
triggersActionsUI &&
triggersActionsUI.getAddRuleFlyout({
consumer: 'alerts',
consumer: 'logs',
onClose: onCloseFlyout,
canChangeTrigger: false,
ruleTypeId: OBSERVABILITY_THRESHOLD_RULE_TYPE_ID,
@ -36,6 +38,7 @@ export function AlertFlyout(props: Props) {
currentOptions: props.options,
series: props.series,
},
validConsumers: observabilityRuleCreationValidConsumers,
}),
// eslint-disable-next-line react-hooks/exhaustive-deps
[triggersActionsUI, onCloseFlyout]

View file

@ -6,10 +6,13 @@
*/
import { useMemo } from 'react';
import { ES_QUERY_ID } from '@kbn/rule-data-utils';
import { usePluginContext } from './use_plugin_context';
export function useGetFilteredRuleTypes() {
const { observabilityRuleTypeRegistry } = usePluginContext();
return useMemo(() => observabilityRuleTypeRegistry.list(), [observabilityRuleTypeRegistry]);
return useMemo(() => {
return [...observabilityRuleTypeRegistry.list(), ES_QUERY_ID];
}, [observabilityRuleTypeRegistry]);
}

View file

@ -19,6 +19,7 @@ import { useKibana } from '../../utils/kibana_react';
import { useHasData } from '../../hooks/use_has_data';
import { usePluginContext } from '../../hooks/use_plugin_context';
import { useTimeBuckets } from '../../hooks/use_time_buckets';
import { useGetFilteredRuleTypes } from '../../hooks/use_get_filtered_rule_types';
import { useToasts } from '../../hooks/use_toast';
import { LoadingObservability } from '../../components/loading_observability';
import { renderRuleStats, RuleStatsState } from './components/rule_stats';
@ -33,7 +34,6 @@ import { getAlertSummaryTimeRange } from '../../utils/alert_summary_widget';
import { observabilityAlertFeatureIds } from '../../../common/constants';
import { ALERTS_URL_STORAGE_KEY } from '../../../common/constants';
import { HeaderMenu } from '../overview/components/header_menu/header_menu';
import { useGetFilteredRuleTypes } from '../../hooks/use_get_filtered_rule_types';
const ALERTS_SEARCH_BAR_ID = 'alerts-search-bar-o11y';
const ALERTS_PER_PAGE = 50;
@ -130,6 +130,7 @@ function InternalAlertsPage() {
const response = await loadRuleAggregations({
http,
typesFilter: filteredRuleTypes,
filterConsumers: observabilityAlertFeatureIds,
});
const { ruleExecutionStatus, ruleMutedStatus, ruleEnabledStatus, ruleSnoozedStatus } =
response;

View file

@ -20,8 +20,7 @@ import { CaseAttachmentsWithoutOwner } from '@kbn/cases-plugin/public';
import { AttachmentType } from '@kbn/cases-plugin/common';
import { EcsSecurityExtension as Ecs } from '@kbn/securitysolution-ecs';
import { TimelineNonEcsData } from '@kbn/timelines-plugin/common';
import { ALERT_RULE_TYPE_ID } from '@kbn/rule-data-utils';
import { ALERT_RULE_TYPE_ID, OBSERVABILITY_THRESHOLD_RULE_TYPE_ID } from '@kbn/rule-data-utils';
import { useKibana } from '../../../utils/kibana_react';
import { useGetUserCasesPermissions } from '../../../hooks/use_get_user_cases_permissions';
@ -32,7 +31,6 @@ import { RULE_DETAILS_PAGE_ID } from '../../rule_details/constants';
import type { ObservabilityRuleTypeRegistry } from '../../..';
import type { ConfigSchema } from '../../../plugin';
import type { TopAlert } from '../../../typings/alerts';
import { OBSERVABILITY_THRESHOLD_RULE_TYPE_ID } from '../../../../common/constants';
const ALERT_DETAILS_PAGE_ID = 'alert-details-o11y';

View file

@ -17,6 +17,7 @@ import { useKibana } from '../../utils/kibana_react';
import { usePluginContext } from '../../hooks/use_plugin_context';
import { useFetchRule } from '../../hooks/use_fetch_rule';
import { useFetchRuleTypes } from '../../hooks/use_fetch_rule_types';
import { useGetFilteredRuleTypes } from '../../hooks/use_get_filtered_rule_types';
import { PageTitle } from './components/page_title';
import { DeleteConfirmationModal } from './components/delete_confirmation_modal';
import { CenterJustifiedSpinner } from '../../components/center_justified_spinner';
@ -39,7 +40,6 @@ import {
} from '../../utils/alert_summary_widget';
import type { AlertStatus } from '../../../common/typings';
import { HeaderMenu } from '../overview/components/header_menu/header_menu';
import { useGetFilteredRuleTypes } from '../../hooks/use_get_filtered_rule_types';
export type TabId = typeof RULE_DETAILS_ALERTS_TAB | typeof RULE_DETAILS_EXECUTION_TAB;

View file

@ -18,6 +18,7 @@ import { RULES_LOGS_PATH, RULES_PATH } from '../../../common/locators/paths';
import { useKibana } from '../../utils/kibana_react';
import { usePluginContext } from '../../hooks/use_plugin_context';
import { useGetFilteredRuleTypes } from '../../hooks/use_get_filtered_rule_types';
import { observabilityRuleCreationValidConsumers } from '../../../common/constants';
import { HeaderMenu } from '../overview/components/header_menu/header_menu';
import { RulesTab } from './rules_tab';
@ -141,6 +142,7 @@ export function RulesPage({ activeTab = RULES_TAB_NAME }: RulesPageProps) {
<AddRuleFlyout
consumer={ALERTS_FEATURE_ID}
filteredRuleTypes={filteredRuleTypes}
validConsumers={observabilityRuleCreationValidConsumers}
onClose={() => {
setAddRuleFlyoutVisibility(false);
}}
@ -148,6 +150,7 @@ export function RulesPage({ activeTab = RULES_TAB_NAME }: RulesPageProps) {
setRefresh(new Date());
return Promise.resolve();
}}
useRuleProducer={true}
/>
)}
</ObservabilityPageTemplate>

View file

@ -11,6 +11,7 @@ import { createKbnUrlStateStorage } from '@kbn/kibana-utils-plugin/public';
import { RuleStatus } from '@kbn/triggers-actions-ui-plugin/public';
import { useKibana } from '../../utils/kibana_react';
import { useGetFilteredRuleTypes } from '../../hooks/use_get_filtered_rule_types';
import { observabilityAlertFeatureIds } from '../../../common/constants';
interface RulesTabProps {
setRefresh: React.Dispatch<React.SetStateAction<Date>>;
@ -73,6 +74,7 @@ export function RulesTab({ setRefresh, stateRefresh }: RulesTabProps) {
return (
<RuleList
filterConsumers={observabilityAlertFeatureIds}
filteredRuleTypes={filteredRuleTypes}
lastRunOutcomeFilter={stateLastResponse}
refresh={stateRefresh}

View file

@ -7,15 +7,12 @@
import { lazy } from 'react';
import { i18n } from '@kbn/i18n';
import { ALERT_REASON } from '@kbn/rule-data-utils';
import { ALERT_REASON, OBSERVABILITY_THRESHOLD_RULE_TYPE_ID } from '@kbn/rule-data-utils';
import { SLO_ID_FIELD, SLO_INSTANCE_ID_FIELD } from '../../common/field_names/slo';
import { ConfigSchema } from '../plugin';
import { ObservabilityRuleTypeRegistry } from './create_observability_rule_type_registry';
import {
OBSERVABILITY_THRESHOLD_RULE_TYPE_ID,
SLO_BURN_RATE_RULE_TYPE_ID,
} from '../../common/constants';
import { SLO_BURN_RATE_RULE_TYPE_ID } from '../../common/constants';
import { validateBurnRateRule } from '../components/burn_rate_rule_editor/validation';
import { validateMetricThreshold } from '../components/custom_threshold/components/validation';
import { formatReason } from '../components/custom_threshold/rule_data_formatters';

View file

@ -47,7 +47,7 @@ const configSchema = schema.object({
}),
}),
thresholdRule: schema.object({
enabled: schema.boolean({ defaultValue: false }),
enabled: schema.boolean({ defaultValue: true }),
}),
}),
customThresholdRule: schema.object({

View file

@ -11,6 +11,7 @@ import { i18n } from '@kbn/i18n';
import { IRuleTypeAlerts, GetViewInAppRelativeUrlFnOpts } from '@kbn/alerting-plugin/server';
import { IBasePath, Logger } from '@kbn/core/server';
import { legacyExperimentalFieldMap } from '@kbn/alerts-as-data-utils';
import { OBSERVABILITY_THRESHOLD_RULE_TYPE_ID } from '@kbn/rule-data-utils';
import { createLifecycleExecutor, IRuleDataClient } from '@kbn/rule-registry-plugin/server';
import { LicenseType } from '@kbn/licensing-plugin/server';
import { LocatorPublic } from '@kbn/share-plugin/common';
@ -22,7 +23,6 @@ import {
observabilityPaths,
} from '../../../../common';
import { Comparator } from '../../../../common/custom_threshold_rule/types';
import { OBSERVABILITY_THRESHOLD_RULE_TYPE_ID } from '../../../../common/constants';
import { THRESHOLD_RULE_REGISTRATION_CONTEXT } from '../../../common/constants';
import {

View file

@ -28,10 +28,7 @@ import { SpacesPluginSetup } from '@kbn/spaces-plugin/server';
import { UsageCollectionSetup } from '@kbn/usage-collection-plugin/server';
import { ObservabilityConfig } from '.';
import { casesFeatureId, observabilityFeatureId, sloFeatureId } from '../common';
import {
SLO_BURN_RATE_RULE_TYPE_ID,
OBSERVABILITY_THRESHOLD_RULE_TYPE_ID,
} from '../common/constants';
import { SLO_BURN_RATE_RULE_TYPE_ID } from '../common/constants';
import {
kubernetesGuideConfig,
kubernetesGuideId,
@ -73,7 +70,7 @@ interface PluginStart {
alerting: PluginStartContract;
}
const sloRuleTypes = [SLO_BURN_RATE_RULE_TYPE_ID, OBSERVABILITY_THRESHOLD_RULE_TYPE_ID];
const sloRuleTypes = [SLO_BURN_RATE_RULE_TYPE_ID];
export class ObservabilityPlugin implements Plugin<ObservabilityPluginSetup> {
private logger: Logger;

View file

@ -10,7 +10,6 @@ import { PublicMethodsOf } from '@kbn/utility-types';
import { Filter, buildEsQuery, EsQueryConfig } from '@kbn/es-query';
import { decodeVersion, encodeHitVersion } from '@kbn/securitysolution-es-utils';
import {
AlertConsumers,
ALERT_TIME_RANGE,
ALERT_STATUS,
getEsQueryConfig,
@ -29,7 +28,7 @@ import {
InlineScript,
QueryDslQueryContainer,
} from '@elastic/elasticsearch/lib/api/typesWithBodyKey';
import { RuleTypeParams } from '@kbn/alerting-plugin/server';
import { RuleTypeParams, PluginStartContract as AlertingStart } from '@kbn/alerting-plugin/server';
import {
ReadOperations,
AlertingAuthorization,
@ -50,7 +49,7 @@ import {
SPACE_IDS,
} from '../../common/technical_rule_data_field_names';
import { ParsedTechnicalFields } from '../../common/parse_technical_fields';
import { Dataset, IRuleDataService } from '../rule_data_plugin_service';
import { IRuleDataService } from '../rule_data_plugin_service';
import { getAuthzFilter, getSpacesFilter } from '../lib';
import { fieldDescriptorToBrowserFieldMapper } from './browser_fields';
@ -81,6 +80,7 @@ export interface ConstructorOptions {
esClient: ElasticsearchClient;
ruleDataService: IRuleDataService;
getRuleType: RuleTypeRegistry['get'];
getAlertIndicesAlias: AlertingStart['getAlertIndicesAlias'];
}
export interface UpdateOptions<Params extends RuleTypeParams> {
@ -137,6 +137,7 @@ interface SingleSearchAfterAndAudit {
operation: WriteOperations.Update | ReadOperations.Find | ReadOperations.Get;
sort?: estypes.SortOptions[] | undefined;
lastSortIds?: Array<string | number> | undefined;
featureIds?: string[];
}
/**
@ -152,6 +153,7 @@ export class AlertsClient {
private readonly spaceId: string | undefined;
private readonly ruleDataService: IRuleDataService;
private readonly getRuleType: RuleTypeRegistry['get'];
private getAlertIndicesAlias!: AlertingStart['getAlertIndicesAlias'];
constructor(options: ConstructorOptions) {
this.logger = options.logger;
@ -163,6 +165,7 @@ export class AlertsClient {
this.spaceId = this.authorization.getSpaceId();
this.ruleDataService = options.ruleDataService;
this.getRuleType = options.getRuleType;
this.getAlertIndicesAlias = options.getAlertIndicesAlias;
}
private getOutcome(
@ -281,6 +284,7 @@ export class AlertsClient {
operation,
sort,
lastSortIds = [],
featureIds,
}: SingleSearchAfterAndAudit) {
try {
const alertSpaceId = this.spaceId;
@ -294,7 +298,14 @@ export class AlertsClient {
let queryBody: estypes.SearchRequest['body'] = {
fields: [ALERT_RULE_TYPE_ID, ALERT_RULE_CONSUMER, ALERT_WORKFLOW_STATUS, SPACE_IDS],
query: await this.buildEsQueryWithAuthz(query, id, alertSpaceId, operation, config),
query: await this.buildEsQueryWithAuthz(
query,
id,
alertSpaceId,
operation,
config,
featureIds ? new Set(featureIds) : undefined
),
aggs,
_source,
track_total_hits: trackTotalHits,
@ -433,10 +444,15 @@ export class AlertsClient {
id: string | null | undefined,
alertSpaceId: string,
operation: WriteOperations.Update | ReadOperations.Get | ReadOperations.Find,
config: EsQueryConfig
config: EsQueryConfig,
featuresIds?: Set<string>
) {
try {
const authzFilter = (await getAuthzFilter(this.authorization, operation)) as Filter;
const authzFilter = (await getAuthzFilter(
this.authorization,
operation,
featuresIds
)) as Filter;
const spacesFilter = getSpacesFilter(alertSpaceId) as unknown as Filter;
let esQuery;
if (id != null) {
@ -681,6 +697,7 @@ export class AlertsClient {
},
},
size: 0,
featureIds,
});
let activeAlertCount = 0;
@ -1006,35 +1023,16 @@ export class AlertsClient {
public async getAuthorizedAlertsIndices(featureIds: string[]): Promise<string[] | undefined> {
try {
// ATTENTION FUTURE DEVELOPER when you are a super user the augmentedRuleTypes.authorizedRuleTypes will
// return all of the features that you can access and does not care about your featureIds
const augmentedRuleTypes = await this.authorization.getAugmentedRuleTypesWithAuthorization(
featureIds,
[ReadOperations.Find, ReadOperations.Get, WriteOperations.Update],
AlertingAuthorizationEntity.Alert
const authorizedRuleTypes = await this.authorization.getAuthorizedRuleTypes(
AlertingAuthorizationEntity.Alert,
new Set(featureIds)
);
// As long as the user can read a minimum of one type of rule type produced by the provided feature,
// the user should be provided that features' alerts index.
// Limiting which alerts that user can read on that index will be done via the findAuthorizationFilter
const authorizedFeatures = new Set<string>();
for (const ruleType of augmentedRuleTypes.authorizedRuleTypes) {
authorizedFeatures.add(ruleType.producer);
}
const validAuthorizedFeatures = Array.from(authorizedFeatures).filter(
(feature): feature is ValidFeatureId =>
featureIds.includes(feature) && isValidFeatureId(feature)
const indices = this.getAlertIndicesAlias(
authorizedRuleTypes.map((art: { id: any }) => art.id),
this.spaceId
);
const toReturn = validAuthorizedFeatures.map((feature) => {
const index = this.ruleDataService.findIndexByFeature(feature, Dataset.alerts);
if (index == null) {
throw new Error(`This feature id ${feature} should be associated to an alert index`);
}
return (
index?.getPrimaryAlias(feature === AlertConsumers.SIEM ? this.spaceId ?? '*' : '*') ?? ''
);
});
return toReturn;
return indices;
} catch (exc) {
const errMessage = `getAuthorizedAlertsIndices failed to get authorized rule types: ${exc}`;
this.logger.error(errMessage);

View file

@ -26,6 +26,7 @@ const alertsClientFactoryParams: AlertsClientFactoryProps = {
esClient: {} as ElasticsearchClient,
ruleDataService: ruleDataServiceMock.create(),
getRuleType: jest.fn(),
getAlertIndicesAlias: jest.fn(),
};
const auditLogger = auditLoggerMock.create();
@ -53,6 +54,7 @@ describe('AlertsClientFactory', () => {
esClient: {},
ruleDataService: alertsClientFactoryParams.ruleDataService,
getRuleType: alertsClientFactoryParams.getRuleType,
getAlertIndicesAlias: alertsClientFactoryParams.getAlertIndicesAlias,
});
});

View file

@ -8,7 +8,10 @@
import { PublicMethodsOf } from '@kbn/utility-types';
import { ElasticsearchClient, KibanaRequest, Logger } from '@kbn/core/server';
import type { RuleTypeRegistry } from '@kbn/alerting-plugin/server/types';
import { AlertingAuthorization } from '@kbn/alerting-plugin/server';
import {
AlertingAuthorization,
PluginStartContract as AlertingStart,
} from '@kbn/alerting-plugin/server';
import { SecurityPluginSetup } from '@kbn/security-plugin/server';
import { IRuleDataService } from '../rule_data_plugin_service';
import { AlertsClient } from './alerts_client';
@ -20,6 +23,7 @@ export interface AlertsClientFactoryProps {
securityPluginSetup: SecurityPluginSetup | undefined;
ruleDataService: IRuleDataService | null;
getRuleType: RuleTypeRegistry['get'];
getAlertIndicesAlias: AlertingStart['getAlertIndicesAlias'];
}
export class AlertsClientFactory {
@ -32,6 +36,7 @@ export class AlertsClientFactory {
private securityPluginSetup!: SecurityPluginSetup | undefined;
private ruleDataService!: IRuleDataService | null;
private getRuleType!: RuleTypeRegistry['get'];
private getAlertIndicesAlias!: AlertingStart['getAlertIndicesAlias'];
public initialize(options: AlertsClientFactoryProps) {
/**
@ -48,6 +53,7 @@ export class AlertsClientFactory {
this.securityPluginSetup = options.securityPluginSetup;
this.ruleDataService = options.ruleDataService;
this.getRuleType = options.getRuleType;
this.getAlertIndicesAlias = options.getAlertIndicesAlias;
}
public async create(request: KibanaRequest): Promise<AlertsClient> {
@ -60,6 +66,7 @@ export class AlertsClientFactory {
esClient: this.esClient,
ruleDataService: this.ruleDataService!,
getRuleType: this.getRuleType,
getAlertIndicesAlias: this.getAlertIndicesAlias,
});
}
}

View file

@ -31,6 +31,7 @@ const alertsClientParams: jest.Mocked<ConstructorOptions> = {
auditLogger,
ruleDataService: ruleDataServiceMock.create(),
getRuleType: jest.fn(),
getAlertIndicesAlias: jest.fn(),
};
const DEFAULT_SPACE = 'test_default_space_id';
@ -334,10 +335,10 @@ describe('bulkUpdate()', () => {
status: 'closed',
})
).rejects.toThrowErrorMatchingInlineSnapshot(`
"queryAndAuditAllAlerts threw an error: Unable to retrieve alerts with query \\"kibana.alert.status: active\\" and operation update
Error: Unable to retrieve alert details for alert with id of \\"null\\" or with query \\"kibana.alert.status: active\\" and operation update
Error: Error: Unauthorized for fake.rule and apm"
`);
"queryAndAuditAllAlerts threw an error: Unable to retrieve alerts with query \\"kibana.alert.status: active\\" and operation update
Error: Unable to retrieve alert details for alert with id of \\"null\\" or with query \\"kibana.alert.status: active\\" and operation update
Error: Error: Unauthorized for fake.rule and apm"
`);
expect(auditLogger.log).toHaveBeenNthCalledWith(1, {
message: `Failed attempt to update alert [id=${fakeAlertId}]`,
@ -401,10 +402,10 @@ describe('bulkUpdate()', () => {
status: 'closed',
})
).rejects.toThrowErrorMatchingInlineSnapshot(`
"queryAndAuditAllAlerts threw an error: Unable to retrieve alerts with query \\"kibana.alert.status: active\\" and operation update
Error: Unable to retrieve alert details for alert with id of \\"null\\" or with query \\"kibana.alert.status: active\\" and operation update
Error: Error: Unauthorized for fake.rule and apm"
`);
"queryAndAuditAllAlerts threw an error: Unable to retrieve alerts with query \\"kibana.alert.status: active\\" and operation update
Error: Unable to retrieve alert details for alert with id of \\"null\\" or with query \\"kibana.alert.status: active\\" and operation update
Error: Error: Unauthorized for fake.rule and apm"
`);
expect(auditLogger.log).toHaveBeenCalledTimes(2);
expect(auditLogger.log).toHaveBeenNthCalledWith(1, {

View file

@ -37,6 +37,7 @@ describe('bulkUpdateCases', () => {
auditLogger,
ruleDataService: ruleDataServiceMock.create(),
getRuleType: jest.fn(),
getAlertIndicesAlias: jest.fn(),
};
beforeEach(() => {

View file

@ -30,6 +30,7 @@ const alertsClientParams: jest.Mocked<ConstructorOptions> = {
auditLogger,
ruleDataService: ruleDataServiceMock.create(),
getRuleType: jest.fn(),
getAlertIndicesAlias: jest.fn(),
};
const DEFAULT_SPACE = 'test_default_space_id';
@ -420,9 +421,9 @@ describe('find()', () => {
index: '.alerts-observability.apm.alerts',
})
).rejects.toThrowErrorMatchingInlineSnapshot(`
"Unable to retrieve alert details for alert with id of \\"undefined\\" or with query \\"[object Object]\\" and operation find
Error: Error: Unauthorized for fake.rule and apm"
`);
"Unable to retrieve alert details for alert with id of \\"undefined\\" or with query \\"[object Object]\\" and operation find
Error: Error: Unauthorized for fake.rule and apm"
`);
expect(auditLogger.log).toHaveBeenNthCalledWith(1, {
message: `Failed attempt to access alert [id=${fakeAlertId}]`,
@ -450,9 +451,9 @@ describe('find()', () => {
index: '.alerts-observability.apm.alerts',
})
).rejects.toThrowErrorMatchingInlineSnapshot(`
"Unable to retrieve alert details for alert with id of \\"undefined\\" or with query \\"[object Object]\\" and operation find
Error: Error: something went wrong"
`);
"Unable to retrieve alert details for alert with id of \\"undefined\\" or with query \\"[object Object]\\" and operation find
Error: Error: something went wrong"
`);
});
describe('authorization', () => {

View file

@ -31,6 +31,7 @@ const alertsClientParams: jest.Mocked<ConstructorOptions> = {
auditLogger,
ruleDataService: ruleDataServiceMock.create(),
getRuleType: jest.fn(),
getAlertIndicesAlias: jest.fn(),
};
const DEFAULT_SPACE = 'test_default_space_id';
@ -266,9 +267,9 @@ describe('get()', () => {
await expect(alertsClient.get({ id: fakeAlertId, index: '.alerts-observability.apm.alerts' }))
.rejects.toThrowErrorMatchingInlineSnapshot(`
"Unable to retrieve alert details for alert with id of \\"myfakeid1\\" or with query \\"undefined\\" and operation get
Error: Error: Unauthorized for fake.rule and apm"
`);
"Unable to retrieve alert details for alert with id of \\"myfakeid1\\" or with query \\"undefined\\" and operation get
Error: Error: Unauthorized for fake.rule and apm"
`);
expect(auditLogger.log).toHaveBeenNthCalledWith(1, {
message: `Failed attempt to access alert [id=${fakeAlertId}]`,
@ -293,9 +294,9 @@ describe('get()', () => {
await expect(
alertsClient.get({ id: 'NoxgpHkBqbdrfX07MqXV', index: '.alerts-observability.apm.alerts' })
).rejects.toThrowErrorMatchingInlineSnapshot(`
"Unable to retrieve alert details for alert with id of \\"NoxgpHkBqbdrfX07MqXV\\" or with query \\"undefined\\" and operation get
Error: Error: something went wrong"
`);
"Unable to retrieve alert details for alert with id of \\"NoxgpHkBqbdrfX07MqXV\\" or with query \\"undefined\\" and operation get
Error: Error: something went wrong"
`);
});
describe('authorization', () => {

View file

@ -32,6 +32,7 @@ describe('remove cases from alerts', () => {
auditLogger,
ruleDataService: ruleDataServiceMock.create(),
getRuleType: jest.fn(),
getAlertIndicesAlias: jest.fn(),
};
beforeEach(() => {
@ -89,6 +90,7 @@ describe('remove cases from alerts', () => {
auditLogger,
ruleDataService: ruleDataServiceMock.create(),
getRuleType: jest.fn(),
getAlertIndicesAlias: jest.fn(),
};
beforeEach(() => {

View file

@ -30,6 +30,7 @@ const alertsClientParams: jest.Mocked<ConstructorOptions> = {
auditLogger,
ruleDataService: ruleDataServiceMock.create(),
getRuleType: jest.fn(),
getAlertIndicesAlias: jest.fn(),
};
const DEFAULT_SPACE = 'test_default_space_id';
@ -257,9 +258,9 @@ describe('update()', () => {
index: '.alerts-observability.apm.alerts',
})
).rejects.toThrowErrorMatchingInlineSnapshot(`
"Unable to retrieve alert details for alert with id of \\"myfakeid1\\" or with query \\"undefined\\" and operation update
Error: Error: Unauthorized for fake.rule and apm"
`);
"Unable to retrieve alert details for alert with id of \\"myfakeid1\\" or with query \\"undefined\\" and operation update
Error: Error: Unauthorized for fake.rule and apm"
`);
expect(auditLogger.log).toHaveBeenNthCalledWith(1, {
message: `Failed attempt to update alert [id=${fakeAlertId}]`,
@ -289,9 +290,9 @@ describe('update()', () => {
index: '.alerts-observability.apm.alerts',
})
).rejects.toThrowErrorMatchingInlineSnapshot(`
"Unable to retrieve alert details for alert with id of \\"NoxgpHkBqbdrfX07MqXV\\" or with query \\"undefined\\" and operation update
Error: Error: something went wrong on update"
`);
"Unable to retrieve alert details for alert with id of \\"NoxgpHkBqbdrfX07MqXV\\" or with query \\"undefined\\" and operation update
Error: Error: something went wrong on update"
`);
});
test(`throws an error if ES client update fails`, async () => {

View file

@ -19,7 +19,8 @@ import {
export async function getAuthzFilter(
authorization: PublicMethodsOf<AlertingAuthorization>,
operation: WriteOperations.Update | ReadOperations.Get | ReadOperations.Find
operation: WriteOperations.Update | ReadOperations.Get | ReadOperations.Find,
featuresIds?: Set<string>
) {
const { filter } = await authorization.getAuthorizationFilter(
AlertingAuthorizationEntity.Alert,
@ -27,7 +28,8 @@ export async function getAuthzFilter(
type: AlertingAuthorizationFilterType.ESDSL,
fieldNames: { consumer: ALERT_RULE_CONSUMER, ruleTypeId: ALERT_RULE_TYPE_ID },
},
operation
operation,
featuresIds
);
return filter;
}

View file

@ -166,6 +166,7 @@ export class RuleRegistryPlugin
securityPluginSetup: security,
ruleDataService,
getRuleType: plugins.alerting.getType,
getAlertIndicesAlias: plugins.alerting.getAlertIndicesAlias,
});
const getRacClientWithRequest = (request: KibanaRequest) => {

View file

@ -35,11 +35,13 @@ export const getBrowserFieldsByFeatureId = (router: IRouter<RacRequestHandlerCon
const racContext = await context.rac;
const alertsClient = await racContext.getAlertsClient();
const { featureIds = [] } = request.query;
const indices = await alertsClient.getAuthorizedAlertsIndices(
Array.isArray(featureIds) ? featureIds : [featureIds]
const onlyO11yFeatureIds = (Array.isArray(featureIds) ? featureIds : [featureIds]).filter(
(fId) => fId !== 'siem'
);
const o11yIndices =
indices?.filter((index) => index.startsWith('.alerts-observability')) ?? [];
(onlyO11yFeatureIds
? await alertsClient.getAuthorizedAlertsIndices(onlyO11yFeatureIds)
: []) ?? [];
if (o11yIndices.length === 0) {
return response.notFound({
body: {

View file

@ -72,20 +72,18 @@ export const ruleRegistrySearchStrategyProvider = (
alerting.getAlertingAuthorizationWithRequest(deps.request),
]);
let authzFilter;
if (!siemRequest) {
const fIds = new Set(featureIds);
if (!siemRequest && featureIds.length > 0) {
authzFilter = (await getAuthzFilter(
authorization,
ReadOperations.Find
ReadOperations.Find,
fIds
)) as estypes.QueryDslQueryContainer;
}
const authorizedRuleTypes =
featureIds.length > 0
? await authorization.getAuthorizedRuleTypes(
AlertingAuthorizationEntity.Alert,
new Set(featureIds)
)
? await authorization.getAuthorizedRuleTypes(AlertingAuthorizationEntity.Alert, fIds)
: [];
return { space, authzFilter, authorizedRuleTypes };
};

View file

@ -57,6 +57,7 @@ const getResponse = async () => {
};
const esClientMock = elasticsearchServiceMock.createElasticsearchClient(getResponse());
const getAlertIndicesAliasMock = jest.fn();
const alertsClientParams: jest.Mocked<ConstructorOptions> = {
logger: loggingSystemMock.create().get(),
authorization: alertingAuthMock,
@ -64,6 +65,7 @@ const alertsClientParams: jest.Mocked<ConstructorOptions> = {
ruleDataService: ruleDataServiceMock.create(),
esClient: esClientMock,
getRuleType: jest.fn(),
getAlertIndicesAlias: getAlertIndicesAliasMock,
};
export function getAlertsClientMockInstance(esClient?: ElasticsearchClient) {
@ -86,6 +88,20 @@ export function resetAlertingAuthMock() {
authorizedRuleTypes.add({ producer: 'apm' });
return Promise.resolve({ authorizedRuleTypes });
});
// @ts-expect-error
alertingAuthMock.getAuthorizedRuleTypes.mockImplementation(async () => {
const authorizedRuleTypes = [
{
producer: 'apm',
id: 'apm.error_rate',
alerts: {
context: 'observability.apm',
},
},
];
return Promise.resolve(authorizedRuleTypes);
});
getAlertIndicesAliasMock.mockReturnValue(['.alerts-observability.apm-default']);
alertingAuthMock.ensureAuthorized.mockImplementation(
// @ts-expect-error

View file

@ -5,6 +5,4 @@
* 2.0.
*/
export const STACK_ALERTS_FEATURE_ID = 'stackAlerts';
export const MAX_SELECTABLE_GROUP_BY_TERMS = 4;

View file

@ -11,7 +11,6 @@ export {
ComparatorFnNames,
getHumanReadableComparator,
} from './comparator';
export { STACK_ALERTS_FEATURE_ID } from './constants';
export type { EsqlTable } from './esql_query_utils';
export { rowToDocument, transformDatatableToEsqlTable, toEsQueryHits } from './esql_query_utils';

View file

@ -24,6 +24,7 @@
"requiredBundles": [
"esUiShared",
"textBasedLanguages"
]
],
"extraPublicDirs": ["common"]
}
}

View file

@ -24,9 +24,9 @@ import {
isGroupAggregation,
parseAggregationResults,
} from '@kbn/triggers-actions-ui-plugin/public/common';
import { STACK_ALERTS_FEATURE_ID } from '@kbn/rule-data-utils';
import { getComparatorScript } from '../../../../common';
import { Comparator } from '../../../../common/comparator_types';
import { STACK_ALERTS_FEATURE_ID } from '../../../../common';
import { CommonRuleParams, EsQueryRuleMetaData, EsQueryRuleParams, SearchType } from '../types';
import { DEFAULT_VALUES } from '../constants';
import { DataViewSelectPopover } from '../../components/data_view_select_popover';

View file

@ -10,17 +10,17 @@ import { i18n } from '@kbn/i18n';
import { RuleTypeModel } from '@kbn/triggers-actions-ui-plugin/public';
import { PluginSetupContract as AlertingSetup } from '@kbn/alerting-plugin/public';
import { SanitizedRule } from '@kbn/alerting-plugin/common';
import { ES_QUERY_ID, STACK_ALERTS_FEATURE_ID } from '@kbn/rule-data-utils';
import { EsQueryRuleParams, SearchType } from './types';
import { validateExpression } from './validation';
import { isSearchSourceRule } from './util';
const PLUGIN_ID = 'discover';
const ES_QUERY_ALERT_TYPE = '.es-query';
export function getRuleType(alerting: AlertingSetup): RuleTypeModel<EsQueryRuleParams> {
registerNavigation(alerting);
return {
id: ES_QUERY_ALERT_TYPE,
id: ES_QUERY_ID,
description: i18n.translate('xpack.stackAlerts.esQuery.ui.alertType.descriptionText', {
defaultMessage: 'Alert when matches are found during the latest query run.',
}),
@ -46,9 +46,33 @@ export function getRuleType(alerting: AlertingSetup): RuleTypeModel<EsQueryRuleP
function registerNavigation(alerting: AlertingSetup) {
alerting.registerNavigation(
PLUGIN_ID,
ES_QUERY_ALERT_TYPE,
(alert: SanitizedRule<EsQueryRuleParams<SearchType.searchSource>>) => {
return `/app/discover#/viewAlert/${alert.id}`;
ES_QUERY_ID,
(rule: SanitizedRule<EsQueryRuleParams<SearchType.searchSource>>) => {
return `/app/discover#/viewAlert/${rule.id}`;
}
);
alerting.registerNavigation(
STACK_ALERTS_FEATURE_ID,
ES_QUERY_ID,
(rule: SanitizedRule<EsQueryRuleParams<SearchType.searchSource>>) => {
if (isSearchSourceRule(rule.params)) return `/app/discover#/viewAlert/${rule.id}`;
return;
}
);
alerting.registerNavigation(
'logs',
ES_QUERY_ID,
(rule: SanitizedRule<EsQueryRuleParams<SearchType.searchSource>>) => {
if (isSearchSourceRule(rule.params)) return `/app/discover#/viewAlert/${rule.id}`;
return;
}
);
alerting.registerNavigation(
'infrastructure',
ES_QUERY_ID,
(rule: SanitizedRule<EsQueryRuleParams<SearchType.searchSource>>) => {
if (isSearchSourceRule(rule.params)) return `/app/discover#/viewAlert/${rule.id}`;
return;
}
);
}

View file

@ -19,7 +19,7 @@ import type { IStorageWrapper } from '@kbn/kibana-utils-plugin/public';
import type { UsageCollectionStart } from '@kbn/usage-collection-plugin/public';
import type { UnifiedSearchPublicPluginStart } from '@kbn/unified-search-plugin/public';
import type { DataPublicPluginStart } from '@kbn/data-plugin/public';
import { STACK_ALERTS_FEATURE_ID } from '../../../../common/constants';
import { STACK_ALERTS_FEATURE_ID } from '@kbn/rule-data-utils';
function validateQuery(query: Query) {
try {

View file

@ -9,10 +9,10 @@ import { i18n } from '@kbn/i18n';
import { KibanaFeatureConfig } from '@kbn/features-plugin/common';
import { DEFAULT_APP_CATEGORIES } from '@kbn/core/server';
import { TRANSFORM_RULE_TYPE } from '@kbn/transform-plugin/common';
import { STACK_ALERTS_FEATURE_ID } from '@kbn/rule-data-utils';
import { ES_QUERY_ID as ElasticsearchQuery } from '@kbn/rule-data-utils';
import { ID as IndexThreshold } from './rule_types/index_threshold/rule_type';
import { GEO_CONTAINMENT_ID as GeoContainment } from './rule_types/geo_containment';
import { ES_QUERY_ID as ElasticsearchQuery } from './rule_types/es_query/constants';
import { STACK_ALERTS_FEATURE_ID } from '../common';
const TransformHealth = TRANSFORM_RULE_TYPE.TRANSFORM_HEALTH;

View file

@ -5,6 +5,5 @@
* 2.0.
*/
export const ES_QUERY_ID = '.es-query';
export const ActionGroupId = 'query matched';
export const ConditionMetAlertInstanceId = 'query matched';

View file

@ -12,10 +12,10 @@ import {
parseAggregationResults,
} from '@kbn/triggers-actions-ui-plugin/common';
import { isGroupAggregation } from '@kbn/triggers-actions-ui-plugin/common';
import { ES_QUERY_ID } from '@kbn/rule-data-utils';
import { getComparatorScript } from '../../../../common';
import { OnlyEsQueryRuleParams } from '../types';
import { buildSortedEventsQuery } from '../../../../common/build_sorted_events_query';
import { ES_QUERY_ID } from '../constants';
import { getSearchParams } from './get_search_params';
export interface FetchEsQueryOpts {

View file

@ -8,6 +8,7 @@
import { i18n } from '@kbn/i18n';
import { CoreSetup } from '@kbn/core/server';
import { extractReferences, injectReferences } from '@kbn/data-plugin/common';
import { ES_QUERY_ID, STACK_ALERTS_FEATURE_ID } from '@kbn/rule-data-utils';
import { StackAlert } from '@kbn/alerts-as-data-utils';
import { STACK_ALERTS_AAD_CONFIG } from '..';
import { RuleType } from '../../types';
@ -18,9 +19,8 @@ import {
EsQueryRuleParamsSchema,
EsQueryRuleState,
} from './rule_type_params';
import { STACK_ALERTS_FEATURE_ID } from '../../../common';
import { ExecutorOptions } from './types';
import { ActionGroupId, ES_QUERY_ID } from './constants';
import { ActionGroupId } from './constants';
import { executor } from './executor';
import { isSearchSourceRule } from './util';

View file

@ -9,7 +9,7 @@ import { i18n } from '@kbn/i18n';
import { schema } from '@kbn/config-schema';
import { SavedObjectReference } from '@kbn/core/server';
import { RuleParamsAndRefs } from '@kbn/alerting-plugin/server';
import { STACK_ALERTS_FEATURE_ID } from '../../../common';
import { STACK_ALERTS_FEATURE_ID } from '@kbn/rule-data-utils';
import type {
GeoContainmentRuleType,
GeoContainmentExtractedRuleParams,

View file

@ -12,15 +12,14 @@ import {
} from '@kbn/triggers-actions-ui-plugin/server';
import { isGroupAggregation } from '@kbn/triggers-actions-ui-plugin/common';
import { StackAlert } from '@kbn/alerts-as-data-utils';
import { ALERT_EVALUATION_VALUE, ALERT_REASON } from '@kbn/rule-data-utils';
import {
ALERT_EVALUATION_VALUE,
ALERT_REASON,
STACK_ALERTS_FEATURE_ID,
} from '@kbn/rule-data-utils';
import { expandFlattenedAlert } from '@kbn/alerting-plugin/server/alerts_client/lib';
import { ALERT_EVALUATION_CONDITIONS, ALERT_TITLE, STACK_ALERTS_AAD_CONFIG } from '..';
import {
ComparatorFns,
getComparatorScript,
getHumanReadableComparator,
STACK_ALERTS_FEATURE_ID,
} from '../../../common';
import { ComparatorFns, getComparatorScript, getHumanReadableComparator } from '../../../common';
import { ActionContext, BaseActionContext, addMessages } from './action_context';
import { Params, ParamsSchema } from './rule_type_params';
import { RuleType, RuleExecutorOptions, StackAlertsStartDeps } from '../../types';

View file

@ -32,12 +32,6 @@
"@kbn/i18n-react",
"@kbn/charts-plugin",
"@kbn/es-ui-shared-plugin",
"@kbn/core-http-browser",
"@kbn/core-doc-links-browser",
"@kbn/core-ui-settings-server",
"@kbn/kibana-utils-plugin",
"@kbn/usage-collection-plugin",
"@kbn/react-field",
"@kbn/core-elasticsearch-server-mocks",
"@kbn/logging-mocks",
"@kbn/share-plugin",
@ -47,6 +41,12 @@
"@kbn/text-based-languages",
"@kbn/text-based-editor",
"@kbn/expressions-plugin",
"@kbn/core-http-browser",
"@kbn/core-doc-links-browser",
"@kbn/core-ui-settings-server",
"@kbn/kibana-utils-plugin",
"@kbn/usage-collection-plugin",
"@kbn/react-field",
],
"exclude": [
"target/**/*",

View file

@ -6,7 +6,6 @@
*/
import { DEFAULT_APP_CATEGORIES } from '@kbn/core/server';
import { OBSERVABILITY_THRESHOLD_RULE_TYPE_ID } from '@kbn/observability-plugin/common/constants';
import { i18n } from '@kbn/i18n';
import {
SubFeaturePrivilegeGroupConfig,
@ -26,11 +25,7 @@ const UPTIME_RULE_TYPES = [
'xpack.uptime.alerts.durationAnomaly',
];
const ruleTypes = [
...UPTIME_RULE_TYPES,
...SYNTHETICS_RULE_TYPES,
OBSERVABILITY_THRESHOLD_RULE_TYPE_ID,
];
const ruleTypes = [...UPTIME_RULE_TYPES, ...SYNTHETICS_RULE_TYPES];
const elasticManagedLocationsEnabledPrivilege: SubFeaturePrivilegeGroupConfig = {
groupType: 'independent' as SubFeaturePrivilegeGroupType,

View file

@ -6,7 +6,7 @@
*/
import { i18n } from '@kbn/i18n';
import { ES_QUERY_ID, OBSERVABILITY_THRESHOLD_RULE_TYPE_ID } from '@kbn/rule-data-utils';
export {
BASE_ALERTING_API_PATH,
INTERNAL_BASE_ALERTING_API_PATH,
@ -119,3 +119,5 @@ export const CONNECTOR_LOCKED_COLUMNS = ['timestamp', 'status', 'connector_name'
export const GLOBAL_CONNECTOR_EXECUTION_DEFAULT_INITIAL_VISIBLE_COLUMNS = [
...CONNECTOR_LOCKED_COLUMNS,
];
export const MULTI_CONSUMER_RULE_TYPE_IDS = [OBSERVABILITY_THRESHOLD_RULE_TYPE_ID, ES_QUERY_ID];

View file

@ -25,11 +25,12 @@ const initializeAggregationResult = (values: readonly string[]) => {
interface UseLoadRuleAggregationsQueryProps {
filters: RulesListFilters;
enabled: boolean;
filterConsumers?: string[];
refresh?: Date;
}
export const useLoadRuleAggregationsQuery = (props: UseLoadRuleAggregationsQueryProps) => {
const { filters, enabled, refresh } = props;
const { filters, enabled, refresh, filterConsumers } = props;
const {
http,
@ -46,6 +47,7 @@ export const useLoadRuleAggregationsQuery = (props: UseLoadRuleAggregationsQuery
ruleLastRunOutcomesFilter: filters.ruleLastRunOutcomes,
ruleStatusesFilter: filters.ruleStatuses,
tagsFilter: filters.tags,
filterConsumers,
});
};

View file

@ -20,10 +20,11 @@ type UseLoadRulesQueryProps = Omit<LoadRulesProps, 'http'> & {
sort: LoadRulesProps['sort'];
enabled: boolean;
refresh?: Date;
filterConsumers?: string[];
};
export const useLoadRulesQuery = (props: UseLoadRulesQueryProps) => {
const { filters, page, sort, onPage, enabled, refresh } = props;
const { filterConsumers, filters, page, sort, onPage, enabled, refresh } = props;
const {
http,
notifications: { toasts },
@ -51,6 +52,7 @@ export const useLoadRulesQuery = (props: UseLoadRulesQueryProps) => {
{
refresh: refresh?.toISOString(),
},
filterConsumers,
],
queryFn: () => {
return loadRulesWithKueryFilter({
@ -66,6 +68,7 @@ export const useLoadRulesQuery = (props: UseLoadRulesQueryProps) => {
tagsFilter: filters.tags,
kueryNode: filters.kueryNode,
sort,
filterConsumers,
});
},
onSuccess: (response) => {

View file

@ -29,8 +29,20 @@ export function hasAllPrivilege(
): boolean {
return ruleType?.authorizedConsumers[ruleConsumer]?.all ?? false;
}
export function hasAllPrivilegeWithProducerCheck(
ruleConsumer: InitialRule['consumer'],
ruleType?: RuleType
): boolean {
if (ruleConsumer === ruleType?.producer) {
return true;
}
return hasAllPrivilege(ruleConsumer, ruleType);
}
export function hasReadPrivilege(rule: InitialRule, ruleType?: RuleType): boolean {
return ruleType?.authorizedConsumers[rule.consumer]?.read ?? false;
}
export const hasManageApiKeysCapability = (capabilities: Capabilities) =>
capabilities?.management?.security?.api_keys;

View file

@ -44,6 +44,7 @@ export async function loadRuleAggregations({
ruleExecutionStatusesFilter,
ruleStatusesFilter,
tagsFilter,
filterConsumers,
}: LoadRuleAggregationsProps): Promise<AggregateRulesResponse> {
const filters = mapFiltersToKql({
typesFilter,
@ -60,6 +61,7 @@ export async function loadRuleAggregations({
search: searchText,
filter: filters.length ? filters.join(' and ') : undefined,
default_search_operator: 'AND',
filter_consumers: filterConsumers,
}),
}
);

Some files were not shown because too many files have changed in this diff Show more