mirror of
https://github.com/elastic/kibana.git
synced 2025-04-23 09:19:04 -04:00
[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   ### 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:
parent
a679ab5370
commit
e0e0a26b43
163 changed files with 2820 additions and 1078 deletions
|
@ -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';
|
||||
|
|
10
packages/kbn-rule-data-utils/src/rule_types/index.ts
Normal file
10
packages/kbn-rule-data-utils/src/rule_types/index.ts
Normal 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';
|
|
@ -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';
|
10
packages/kbn-rule-data-utils/src/rule_types/stack_rules.ts
Normal file
10
packages/kbn-rule-data-utils/src/rule_types/stack_rules.ts
Normal 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';
|
|
@ -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();
|
||||
|
|
|
@ -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": [
|
||||
|
|
|
@ -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({
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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) {
|
||||
|
|
|
@ -61,6 +61,7 @@ const ruleType: jest.Mocked<UntypedNormalizedRuleType> = {
|
|||
mappings: { fieldMap: { field: { type: 'keyword', required: false } } },
|
||||
shouldWrite: true,
|
||||
},
|
||||
validLegacyConsumers: [],
|
||||
};
|
||||
|
||||
const mockLegacyAlertsClient = legacyAlertsClientMock.create();
|
||||
|
|
|
@ -99,6 +99,7 @@ const ruleType: jest.Mocked<UntypedNormalizedRuleType> = {
|
|||
validate: {
|
||||
params: schema.any(),
|
||||
},
|
||||
validLegacyConsumers: [],
|
||||
};
|
||||
|
||||
const testAlert1 = {
|
||||
|
|
|
@ -29,6 +29,7 @@ const ruleType: jest.Mocked<UntypedNormalizedRuleType> = {
|
|||
mappings: { fieldMap: { field: { type: 'keyword', required: false } } },
|
||||
shouldWrite: true,
|
||||
},
|
||||
validLegacyConsumers: [],
|
||||
};
|
||||
|
||||
describe('formatRule', () => {
|
||||
|
|
|
@ -203,6 +203,7 @@ const ruleType: jest.Mocked<UntypedNormalizedRuleType> = {
|
|||
validate: {
|
||||
params: { validate: (params) => params },
|
||||
},
|
||||
validLegacyConsumers: [],
|
||||
};
|
||||
|
||||
const ruleTypeWithAlertDefinition: jest.Mocked<UntypedNormalizedRuleType> = {
|
||||
|
|
|
@ -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: [],
|
||||
},
|
||||
])
|
||||
);
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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())])
|
||||
|
|
|
@ -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> {
|
||||
|
|
|
@ -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({
|
||||
|
|
|
@ -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({
|
||||
|
|
File diff suppressed because it is too large
Load diff
|
@ -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;
|
||||
|
|
|
@ -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: [],
|
||||
},
|
||||
]),
|
||||
{
|
||||
|
|
|
@ -46,6 +46,7 @@ const ruleType: jest.Mocked<UntypedNormalizedRuleType> = {
|
|||
validate: {
|
||||
params: schema.any(),
|
||||
},
|
||||
validLegacyConsumers: [],
|
||||
};
|
||||
|
||||
const context: RuleContextOpts = {
|
||||
|
|
|
@ -26,6 +26,7 @@ describe('createAlertEventLogRecordObject', () => {
|
|||
validate: {
|
||||
params: schema.any(),
|
||||
},
|
||||
validLegacyConsumers: [],
|
||||
};
|
||||
|
||||
test('created alert event "execute-start"', async () => {
|
||||
|
|
|
@ -79,6 +79,7 @@ describe('findRulesRoute', () => {
|
|||
"includeSnoozeData": true,
|
||||
"options": Object {
|
||||
"defaultSearchOperator": "OR",
|
||||
"filterConsumers": undefined,
|
||||
"page": 1,
|
||||
"perPage": 1,
|
||||
},
|
||||
|
|
|
@ -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 } : {}),
|
||||
|
|
|
@ -51,6 +51,7 @@ const ruleTypes = [
|
|||
defaultScheduleInterval: '10m',
|
||||
hasAlertsMappings: false,
|
||||
hasFieldsForAAD: false,
|
||||
validLegacyConsumers: [],
|
||||
} as RegistryAlertTypeWithAuth,
|
||||
];
|
||||
|
||||
|
|
|
@ -55,6 +55,7 @@ const ruleTypes = [
|
|||
defaultScheduleInterval: '10m',
|
||||
hasAlertsMappings: false,
|
||||
hasFieldsForAAD: false,
|
||||
validLegacyConsumers: [],
|
||||
} as RegistryAlertTypeWithAuth,
|
||||
];
|
||||
|
||||
|
|
|
@ -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,
|
||||
];
|
||||
|
||||
|
|
|
@ -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 } : {}),
|
||||
});
|
||||
|
|
|
@ -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,
|
||||
];
|
||||
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -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 [],
|
||||
},
|
||||
}
|
||||
`);
|
||||
|
|
|
@ -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),
|
||||
};
|
||||
}
|
||||
|
|
|
@ -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']);
|
||||
}
|
||||
});
|
||||
});
|
||||
});
|
|
@ -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 };
|
|
@ -46,6 +46,7 @@ const ruleType: jest.Mocked<UntypedNormalizedRuleType> = {
|
|||
validate: {
|
||||
params: { validate: (params) => params },
|
||||
},
|
||||
validLegacyConsumers: [],
|
||||
};
|
||||
|
||||
const context = {
|
||||
|
|
|
@ -32,6 +32,7 @@ describe('validateActions', () => {
|
|||
context: 'context',
|
||||
mappings: { fieldMap: { field: { type: 'fieldType', required: false } } },
|
||||
},
|
||||
validLegacyConsumers: [],
|
||||
};
|
||||
|
||||
const data = {
|
||||
|
|
|
@ -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) {
|
||||
|
|
|
@ -181,6 +181,7 @@ describe('bulkDelete', () => {
|
|||
validate: {
|
||||
params: schema.any(),
|
||||
},
|
||||
validLegacyConsumers: [],
|
||||
});
|
||||
});
|
||||
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -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({
|
||||
|
|
|
@ -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: [],
|
||||
},
|
||||
])
|
||||
);
|
||||
|
|
|
@ -122,6 +122,7 @@ export function getBeforeSetup(
|
|||
validate: {
|
||||
params: { validate: (params) => params },
|
||||
},
|
||||
validLegacyConsumers: [],
|
||||
}));
|
||||
rulesClientParams.getEventLogClient.mockResolvedValue(
|
||||
eventLogClient ?? eventLogClientMock.create()
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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({
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -81,6 +81,7 @@ const ruleType: NormalizedRuleType<
|
|||
mappings: { fieldMap: { field: { type: 'fieldType', required: false } } },
|
||||
},
|
||||
autoRecoverAlerts: false,
|
||||
validLegacyConsumers: [],
|
||||
};
|
||||
const rule = {
|
||||
id: '1',
|
||||
|
|
|
@ -152,6 +152,7 @@ export const ruleType: jest.Mocked<UntypedNormalizedRuleType> = {
|
|||
context: 'test',
|
||||
mappings: { fieldMap: { field: { type: 'keyword', required: false } } },
|
||||
},
|
||||
validLegacyConsumers: [],
|
||||
};
|
||||
|
||||
export const mockRunNowResponse = {
|
||||
|
|
|
@ -63,6 +63,7 @@ const ruleType: UntypedNormalizedRuleType = {
|
|||
validate: {
|
||||
params: schema.any(),
|
||||
},
|
||||
validLegacyConsumers: [],
|
||||
};
|
||||
let fakeTimer: sinon.SinonFakeTimers;
|
||||
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -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",
|
||||
|
|
|
@ -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: [
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -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,
|
||||
];
|
||||
|
|
|
@ -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]
|
||||
|
|
|
@ -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]);
|
||||
}
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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';
|
||||
|
||||
|
|
|
@ -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;
|
||||
|
||||
|
|
|
@ -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>
|
||||
|
|
|
@ -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}
|
||||
|
|
|
@ -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';
|
||||
|
|
|
@ -47,7 +47,7 @@ const configSchema = schema.object({
|
|||
}),
|
||||
}),
|
||||
thresholdRule: schema.object({
|
||||
enabled: schema.boolean({ defaultValue: false }),
|
||||
enabled: schema.boolean({ defaultValue: true }),
|
||||
}),
|
||||
}),
|
||||
customThresholdRule: schema.object({
|
||||
|
|
|
@ -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 {
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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,
|
||||
});
|
||||
});
|
||||
|
||||
|
|
|
@ -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,
|
||||
});
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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, {
|
||||
|
|
|
@ -37,6 +37,7 @@ describe('bulkUpdateCases', () => {
|
|||
auditLogger,
|
||||
ruleDataService: ruleDataServiceMock.create(),
|
||||
getRuleType: jest.fn(),
|
||||
getAlertIndicesAlias: jest.fn(),
|
||||
};
|
||||
|
||||
beforeEach(() => {
|
||||
|
|
|
@ -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', () => {
|
||||
|
|
|
@ -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', () => {
|
||||
|
|
|
@ -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(() => {
|
||||
|
|
|
@ -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 () => {
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
|
|
@ -166,6 +166,7 @@ export class RuleRegistryPlugin
|
|||
securityPluginSetup: security,
|
||||
ruleDataService,
|
||||
getRuleType: plugins.alerting.getType,
|
||||
getAlertIndicesAlias: plugins.alerting.getAlertIndicesAlias,
|
||||
});
|
||||
|
||||
const getRacClientWithRequest = (request: KibanaRequest) => {
|
||||
|
|
|
@ -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: {
|
||||
|
|
|
@ -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 };
|
||||
};
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -5,6 +5,4 @@
|
|||
* 2.0.
|
||||
*/
|
||||
|
||||
export const STACK_ALERTS_FEATURE_ID = 'stackAlerts';
|
||||
|
||||
export const MAX_SELECTABLE_GROUP_BY_TERMS = 4;
|
||||
|
|
|
@ -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';
|
||||
|
|
|
@ -24,6 +24,7 @@
|
|||
"requiredBundles": [
|
||||
"esUiShared",
|
||||
"textBasedLanguages"
|
||||
]
|
||||
],
|
||||
"extraPublicDirs": ["common"]
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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';
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
);
|
||||
}
|
||||
|
|
|
@ -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 {
|
||||
|
|
|
@ -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;
|
||||
|
||||
|
|
|
@ -5,6 +5,5 @@
|
|||
* 2.0.
|
||||
*/
|
||||
|
||||
export const ES_QUERY_ID = '.es-query';
|
||||
export const ActionGroupId = 'query matched';
|
||||
export const ConditionMetAlertInstanceId = 'query matched';
|
||||
|
|
|
@ -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 {
|
||||
|
|
|
@ -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';
|
||||
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -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';
|
||||
|
|
|
@ -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/**/*",
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -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];
|
||||
|
|
|
@ -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,
|
||||
});
|
||||
};
|
||||
|
||||
|
|
|
@ -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) => {
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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
Loading…
Add table
Add a link
Reference in a new issue