[Response Ops] Alert search strategy (#124430)

* Initial code for search strategy in rule registry for use in triggers actions ui

* WIP

* More

* Bump this up

* Add a couple basic tests

* More separation

* Some api tests

* Fix types

* fix type

* Remove tests

* add this back in, not sure why this happened

* Remove test code

* PR feedback

* Fix typing

* Fix unit tests

* Skip this test due to errors

* Add more tests

* Use fields api

* Add issue link

* PR feedback

* Fix types and test

* Use nested key TS definition

Co-authored-by: Kibana Machine <42973632+kibanamachine@users.noreply.github.com>
This commit is contained in:
Chris Roberson 2022-02-25 14:41:58 -05:00 committed by GitHub
parent 61fc407e90
commit 5d1ef0e7e5
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
18 changed files with 691 additions and 23 deletions

View file

@ -6,3 +6,4 @@
*/
export const BASE_RAC_ALERTS_API_PATH = '/internal/rac/alerts';
export const MAX_ALERT_SEARCH_SIZE = 1000;

View file

@ -4,4 +4,6 @@
* 2.0; you may not use this file except in compliance with the Elastic License
* 2.0.
*/
export { parseTechnicalFields } from './parse_technical_fields';
export { parseTechnicalFields, type ParsedTechnicalFields } from './parse_technical_fields';
export type { RuleRegistrySearchRequest, RuleRegistrySearchResponse } from './search_strategy';
export { BASE_RAC_ALERTS_API_PATH } from './constants';

View file

@ -0,0 +1,58 @@
/*
* 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 { ValidFeatureId } from '@kbn/rule-data-utils';
import { Ecs } from 'kibana/server';
import * as estypes from '@elastic/elasticsearch/lib/api/typesWithBodyKey';
import { IEsSearchRequest, IEsSearchResponse } from 'src/plugins/data/common';
export type RuleRegistrySearchRequest = IEsSearchRequest & {
featureIds: ValidFeatureId[];
query?: { bool: estypes.QueryDslBoolQuery };
};
type Prev = [
never,
0,
1,
2,
3,
4,
5,
6,
7,
8,
9,
10,
11,
12,
13,
14,
15,
16,
17,
18,
19,
20,
...Array<0>
];
type Join<K, P> = K extends string | number
? P extends string | number
? `${K}${'' extends P ? '' : '.'}${P}`
: never
: never;
type DotNestedKeys<T, D extends number = 10> = [D] extends [never]
? never
: T extends object
? { [K in keyof T]-?: Join<K, DotNestedKeys<T[K], Prev[D]>> }[keyof T]
: '';
type EcsFieldsResponse = {
[Property in DotNestedKeys<Ecs>]: string[];
};
export type RuleRegistrySearchResponse = IEsSearchResponse<EcsFieldsResponse>;

View file

@ -8,6 +8,6 @@
"kibanaVersion": "kibana",
"configPath": ["xpack", "ruleRegistry"],
"requiredPlugins": ["alerting", "data", "triggersActionsUi"],
"optionalPlugins": ["security"],
"optionalPlugins": ["security", "spaces"],
"server": true
}

View file

@ -21,7 +21,7 @@ import {
InlineScript,
QueryDslQueryContainer,
} from '@elastic/elasticsearch/lib/api/typesWithBodyKey';
import { AlertTypeParams, AlertingAuthorizationFilterType } from '../../../alerting/server';
import { AlertTypeParams } from '../../../alerting/server';
import {
ReadOperations,
AlertingAuthorization,
@ -39,6 +39,7 @@ import {
} from '../../common/technical_rule_data_field_names';
import { ParsedTechnicalFields } from '../../common/parse_technical_fields';
import { Dataset, IRuleDataService } from '../rule_data_plugin_service';
import { getAuthzFilter, getSpacesFilter } from '../lib';
// TODO: Fix typings https://github.com/elastic/kibana/issues/101776
type NonNullableProps<Obj extends {}, Props extends keyof Obj> = Omit<Obj, Props> & {
@ -369,14 +370,8 @@ export class AlertsClient {
config: EsQueryConfig
) {
try {
const { filter: authzFilter } = await this.authorization.getAuthorizationFilter(
AlertingAuthorizationEntity.Alert,
{
type: AlertingAuthorizationFilterType.ESDSL,
fieldNames: { consumer: ALERT_RULE_CONSUMER, ruleTypeId: ALERT_RULE_TYPE_ID },
},
operation
);
const authzFilter = (await getAuthzFilter(this.authorization, operation)) as Filter;
const spacesFilter = getSpacesFilter(alertSpaceId) as unknown as Filter;
let esQuery;
if (id != null) {
esQuery = { query: `_id:${id}`, language: 'kuery' };
@ -388,10 +383,7 @@ export class AlertsClient {
const builtQuery = buildEsQuery(
undefined,
esQuery == null ? { query: ``, language: 'kuery' } : esQuery,
[
authzFilter as unknown as Filter,
{ query: { term: { [SPACE_IDS]: alertSpaceId } } } as unknown as Filter,
],
[authzFilter, spacesFilter],
config
);
if (query != null && typeof query === 'object') {

View file

@ -0,0 +1,20 @@
/*
* 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 { alertingAuthorizationMock } from '../../../alerting/server/authorization/alerting_authorization.mock';
import { ReadOperations } from '../../../alerting/server';
import { getAuthzFilter } from './get_authz_filter';
describe('getAuthzFilter()', () => {
it('should call `getAuthorizationFilter`', async () => {
const authorization = alertingAuthorizationMock.create();
authorization.getAuthorizationFilter.mockImplementationOnce(async () => {
return { filter: { test: true }, ensureRuleTypeIsAuthorized: () => {} };
});
const filter = await getAuthzFilter(authorization, ReadOperations.Find);
expect(filter).toStrictEqual({ test: true });
});
});

View file

@ -0,0 +1,33 @@
/*
* 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 { PublicMethodsOf } from '@kbn/utility-types';
import {
ReadOperations,
WriteOperations,
AlertingAuthorization,
AlertingAuthorizationEntity,
AlertingAuthorizationFilterType,
} from '../../../alerting/server';
import {
ALERT_RULE_CONSUMER,
ALERT_RULE_TYPE_ID,
} from '../../common/technical_rule_data_field_names';
export async function getAuthzFilter(
authorization: PublicMethodsOf<AlertingAuthorization>,
operation: WriteOperations.Update | ReadOperations.Get | ReadOperations.Find
) {
const { filter } = await authorization.getAuthorizationFilter(
AlertingAuthorizationEntity.Alert,
{
type: AlertingAuthorizationFilterType.ESDSL,
fieldNames: { consumer: ALERT_RULE_CONSUMER, ruleTypeId: ALERT_RULE_TYPE_ID },
},
operation
);
return filter;
}

View file

@ -0,0 +1,20 @@
/*
* 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 { getSpacesFilter } from '.';
describe('getSpacesFilter()', () => {
it('should return a spaces filter', () => {
expect(getSpacesFilter('1')).toStrictEqual({
term: {
'kibana.space_ids': '1',
},
});
});
it('should return undefined if no space id is provided', () => {
expect(getSpacesFilter()).toBeUndefined();
});
});

View file

@ -0,0 +1,11 @@
/*
* 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 { SPACE_IDS } from '../../common/technical_rule_data_field_names';
export function getSpacesFilter(spaceId?: string) {
return spaceId ? { term: { [SPACE_IDS]: spaceId } } : undefined;
}

View file

@ -0,0 +1,8 @@
/*
* 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.
*/
export { getAuthzFilter } from './get_authz_filter';
export { getSpacesFilter } from './get_spaces_filter';

View file

@ -17,6 +17,11 @@ import {
import { PluginStartContract as AlertingStart } from '../../alerting/server';
import { SecurityPluginSetup } from '../../security/server';
import { SpacesPluginStart } from '../../spaces/server';
import {
PluginStart as DataPluginStart,
PluginSetup as DataPluginSetup,
} from '../../../../src/plugins/data/server';
import { RuleRegistryPluginConfig } from './config';
import { IRuleDataService, RuleDataService } from './rule_data_plugin_service';
@ -24,13 +29,17 @@ import { AlertsClientFactory } from './alert_data_client/alerts_client_factory';
import { AlertsClient } from './alert_data_client/alerts_client';
import { RacApiRequestHandlerContext, RacRequestHandlerContext } from './types';
import { defineRoutes } from './routes';
import { ruleRegistrySearchStrategyProvider } from './search_strategy';
export interface RuleRegistryPluginSetupDependencies {
security?: SecurityPluginSetup;
data: DataPluginSetup;
}
export interface RuleRegistryPluginStartDependencies {
alerting: AlertingStart;
data: DataPluginStart;
spaces?: SpacesPluginStart;
}
export interface RuleRegistryPluginSetupContract {
@ -95,6 +104,22 @@ export class RuleRegistryPlugin
this.ruleDataService.initializeService();
core.getStartServices().then(([_, depsStart]) => {
const ruleRegistrySearchStrategy = ruleRegistrySearchStrategyProvider(
depsStart.data,
this.ruleDataService!,
depsStart.alerting,
logger,
plugins.security,
depsStart.spaces
);
plugins.data.search.registerSearchStrategy(
'ruleRegistryAlertsSearchStrategy',
ruleRegistrySearchStrategy
);
});
// ALERTS ROUTES
const router = core.http.createRouter<RacRequestHandlerContext>();
core.http.registerRouteHandlerContext<RacRequestHandlerContext, 'rac'>(

View file

@ -0,0 +1,8 @@
/*
* 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.
*/
export { ruleRegistrySearchStrategyProvider } from './search_strategy';

View file

@ -0,0 +1,202 @@
/*
* 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 { of } from 'rxjs';
import { merge } from 'lodash';
import { loggerMock } from '@kbn/logging-mocks';
import { AlertConsumers } from '@kbn/rule-data-utils';
import { ruleRegistrySearchStrategyProvider, EMPTY_RESPONSE } from './search_strategy';
import { ruleDataServiceMock } from '../rule_data_plugin_service/rule_data_plugin_service.mock';
import { dataPluginMock } from '../../../../../src/plugins/data/server/mocks';
import { SearchStrategyDependencies } from '../../../../../src/plugins/data/server';
import { alertsMock } from '../../../alerting/server/mocks';
import { securityMock } from '../../../security/server/mocks';
import { spacesMock } from '../../../spaces/server/mocks';
import { RuleRegistrySearchRequest } from '../../common/search_strategy';
import { IndexInfo } from '../rule_data_plugin_service/index_info';
import * as getAuthzFilterImport from '../lib/get_authz_filter';
const getBasicResponse = (overwrites = {}) => {
return merge(
{
isPartial: false,
isRunning: false,
total: 0,
loaded: 0,
rawResponse: {
took: 1,
timed_out: false,
_shards: {
failed: 0,
successful: 1,
total: 1,
},
hits: {
max_score: 0,
hits: [],
total: 0,
},
},
},
overwrites
);
};
describe('ruleRegistrySearchStrategyProvider()', () => {
const data = dataPluginMock.createStartContract();
const ruleDataService = ruleDataServiceMock.create();
const alerting = alertsMock.createStart();
const security = securityMock.createSetup();
const spaces = spacesMock.createStart();
const logger = loggerMock.create();
const response = getBasicResponse({
rawResponse: {
hits: {
hits: [
{
_source: {
foo: 1,
},
},
],
},
},
});
let getAuthzFilterSpy: jest.SpyInstance;
beforeEach(() => {
ruleDataService.findIndicesByFeature.mockImplementation(() => {
return [
{
baseName: 'test',
} as IndexInfo,
];
});
data.search.getSearchStrategy.mockImplementation(() => {
return {
search: () => of(response),
};
});
getAuthzFilterSpy = jest
.spyOn(getAuthzFilterImport, 'getAuthzFilter')
.mockImplementation(async () => {
return {};
});
});
afterEach(() => {
ruleDataService.findIndicesByFeature.mockClear();
data.search.getSearchStrategy.mockClear();
getAuthzFilterSpy.mockClear();
});
it('should handle a basic search request', async () => {
const request: RuleRegistrySearchRequest = {
featureIds: [AlertConsumers.LOGS],
};
const options = {};
const deps = {
request: {},
};
const strategy = ruleRegistrySearchStrategyProvider(
data,
ruleDataService,
alerting,
logger,
security,
spaces
);
const result = await strategy
.search(request, options, deps as unknown as SearchStrategyDependencies)
.toPromise();
expect(result).toBe(response);
});
it('should use the active space in siem queries', async () => {
const request: RuleRegistrySearchRequest = {
featureIds: [AlertConsumers.SIEM],
};
const options = {};
const deps = {
request: {},
};
spaces.spacesService.getActiveSpace.mockImplementation(async () => {
return {
id: 'testSpace',
name: 'Test Space',
disabledFeatures: [],
};
});
ruleDataService.findIndicesByFeature.mockImplementation(() => {
return [
{
baseName: 'myTestIndex',
} as unknown as IndexInfo,
];
});
let searchRequest: RuleRegistrySearchRequest = {} as unknown as RuleRegistrySearchRequest;
data.search.getSearchStrategy.mockImplementation(() => {
return {
search: (_request) => {
searchRequest = _request as unknown as RuleRegistrySearchRequest;
return of(response);
},
};
});
const strategy = ruleRegistrySearchStrategyProvider(
data,
ruleDataService,
alerting,
logger,
security,
spaces
);
await strategy
.search(request, options, deps as unknown as SearchStrategyDependencies)
.toPromise();
spaces.spacesService.getActiveSpace.mockClear();
expect(searchRequest?.params?.index).toStrictEqual(['myTestIndex-testSpace*']);
});
it('should return an empty response if no valid indices are found', async () => {
const request: RuleRegistrySearchRequest = {
featureIds: [AlertConsumers.LOGS],
};
const options = {};
const deps = {
request: {},
};
ruleDataService.findIndicesByFeature.mockImplementationOnce(() => {
return [];
});
const strategy = ruleRegistrySearchStrategyProvider(
data,
ruleDataService,
alerting,
logger,
security,
spaces
);
const result = await strategy
.search(request, options, deps as unknown as SearchStrategyDependencies)
.toPromise();
expect(result).toBe(EMPTY_RESPONSE);
});
});

View file

@ -0,0 +1,149 @@
/*
* 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 { map, mergeMap, catchError } from 'rxjs/operators';
import type * as estypes from '@elastic/elasticsearch/lib/api/typesWithBodyKey';
import { Logger } from 'src/core/server';
import { from, of } from 'rxjs';
import { isValidFeatureId } from '@kbn/rule-data-utils';
import { ENHANCED_ES_SEARCH_STRATEGY } from '../../../../../src/plugins/data/common';
import { ISearchStrategy, PluginStart } from '../../../../../src/plugins/data/server';
import {
RuleRegistrySearchRequest,
RuleRegistrySearchResponse,
} from '../../common/search_strategy';
import { ReadOperations, PluginStartContract as AlertingStart } from '../../../alerting/server';
import { SecurityPluginSetup } from '../../../security/server';
import { SpacesPluginStart } from '../../../spaces/server';
import { IRuleDataService } from '..';
import { Dataset } from '../rule_data_plugin_service/index_options';
import { MAX_ALERT_SEARCH_SIZE } from '../../common/constants';
import { AlertAuditAction, alertAuditEvent } from '../';
import { getSpacesFilter, getAuthzFilter } from '../lib';
export const EMPTY_RESPONSE: RuleRegistrySearchResponse = {
rawResponse: {} as RuleRegistrySearchResponse['rawResponse'],
};
export const ruleRegistrySearchStrategyProvider = (
data: PluginStart,
ruleDataService: IRuleDataService,
alerting: AlertingStart,
logger: Logger,
security?: SecurityPluginSetup,
spaces?: SpacesPluginStart
): ISearchStrategy<RuleRegistrySearchRequest, RuleRegistrySearchResponse> => {
const es = data.search.getSearchStrategy(ENHANCED_ES_SEARCH_STRATEGY);
return {
search: (request, options, deps) => {
const securityAuditLogger = security?.audit.asScoped(deps.request);
const getActiveSpace = async () => spaces?.spacesService.getActiveSpace(deps.request);
const getAsync = async () => {
const [space, authorization] = await Promise.all([
getActiveSpace(),
alerting.getAlertingAuthorizationWithRequest(deps.request),
]);
const authzFilter = (await getAuthzFilter(
authorization,
ReadOperations.Find
)) as estypes.QueryDslQueryContainer;
return { space, authzFilter };
};
return from(getAsync()).pipe(
mergeMap(({ space, authzFilter }) => {
const indices: string[] = request.featureIds.reduce((accum: string[], featureId) => {
if (!isValidFeatureId(featureId)) {
logger.warn(
`Found invalid feature '${featureId}' while using rule registry search strategy. No alert data from this feature will be searched.`
);
return accum;
}
return [
...accum,
...ruleDataService
.findIndicesByFeature(featureId, Dataset.alerts)
.map((indexInfo) => {
return featureId === 'siem'
? `${indexInfo.baseName}-${space?.id ?? ''}*`
: `${indexInfo.baseName}*`;
}),
];
}, []);
if (indices.length === 0) {
return of(EMPTY_RESPONSE);
}
const filter = request.query?.bool?.filter
? Array.isArray(request.query?.bool?.filter)
? request.query?.bool?.filter
: [request.query?.bool?.filter]
: [];
if (authzFilter) {
filter.push(authzFilter);
}
if (space?.id) {
filter.push(getSpacesFilter(space.id) as estypes.QueryDslQueryContainer);
}
const query = {
bool: {
...request.query?.bool,
filter,
},
};
const params = {
index: indices,
body: {
_source: false,
fields: ['*'],
size: MAX_ALERT_SEARCH_SIZE,
query,
},
};
return es.search({ ...request, params }, options, deps);
}),
map((response) => {
// Do we have to loop over each hit? Yes.
// ecs auditLogger requires that we log each alert independently
if (securityAuditLogger != null) {
response.rawResponse.hits?.hits?.forEach((hit) => {
securityAuditLogger.log(
alertAuditEvent({
action: AlertAuditAction.FIND,
id: hit._id,
outcome: 'success',
})
);
});
}
return response;
}),
catchError((err) => {
// check if auth error, if yes, write to ecs logger
if (securityAuditLogger != null && err?.output?.statusCode === 403) {
securityAuditLogger.log(
alertAuditEvent({
action: AlertAuditAction.FIND,
outcome: 'failure',
error: err,
})
);
}
throw err;
})
);
},
cancel: async (id, options, deps) => {
if (es.cancel) {
return es.cancel(id, options, deps);
}
},
};
};

View file

@ -19,6 +19,5 @@
{ "path": "../../../src/plugins/data/tsconfig.json" },
{ "path": "../alerting/tsconfig.json" },
{ "path": "../security/tsconfig.json" },
{ "path": "../triggers_actions_ui/tsconfig.json" }
]
}

View file

@ -17,6 +17,7 @@
{ "path": "../../../src/core/tsconfig.json" },
{ "path": "../alerting/tsconfig.json" },
{ "path": "../features/tsconfig.json" },
{ "path": "../rule_registry/tsconfig.json" },
{ "path": "../../../src/plugins/data/tsconfig.json" },
{ "path": "../../../src/plugins/saved_objects/tsconfig.json" },
{ "path": "../../../src/plugins/home/tsconfig.json" },

View file

@ -10,8 +10,7 @@ import { createSpacesAndUsers, deleteSpacesAndUsers } from '../../../common/lib/
// eslint-disable-next-line import/no-default-export
export default ({ loadTestFile, getService }: FtrProviderContext): void => {
// FAILING: https://github.com/elastic/kibana/issues/110153
describe.skip('rules security and spaces enabled: basic', function () {
describe('rules security and spaces enabled: basic', function () {
// Fastest ciGroup for the moment.
this.tags('ciGroup5');
@ -24,10 +23,12 @@ export default ({ loadTestFile, getService }: FtrProviderContext): void => {
});
// Basic
loadTestFile(require.resolve('./get_alert_by_id'));
loadTestFile(require.resolve('./update_alert'));
loadTestFile(require.resolve('./bulk_update_alerts'));
loadTestFile(require.resolve('./find_alerts'));
loadTestFile(require.resolve('./get_alerts_index'));
// FAILING: https://github.com/elastic/kibana/issues/110153
// loadTestFile(require.resolve('./get_alert_by_id'));
// loadTestFile(require.resolve('./update_alert'));
// loadTestFile(require.resolve('./bulk_update_alerts'));
// loadTestFile(require.resolve('./find_alerts'));
// loadTestFile(require.resolve('./get_alerts_index'));
loadTestFile(require.resolve('./search_strategy'));
});
};

View file

@ -0,0 +1,138 @@
/*
* 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 expect from '@kbn/expect';
import { AlertConsumers } from '@kbn/rule-data-utils';
import { FtrProviderContext } from '../../../common/ftr_provider_context';
import { RuleRegistrySearchResponse } from '../../../../../plugins/rule_registry/common/search_strategy';
import {
deleteSignalsIndex,
createSignalsIndex,
deleteAllAlerts,
getRuleForSignalTesting,
createRule,
waitForSignalsToBePresent,
waitForRuleSuccessOrStatus,
} from '../../../../detection_engine_api_integration/utils';
import { ID } from '../../../../detection_engine_api_integration/security_and_spaces/tests/generating_signals';
import { QueryCreateSchema } from '../../../../../plugins/security_solution/common/detection_engine/schemas/request';
// eslint-disable-next-line import/no-default-export
export default ({ getService }: FtrProviderContext) => {
const esArchiver = getService('esArchiver');
const supertest = getService('supertest');
const bsearch = getService('bsearch');
const log = getService('log');
const SPACE1 = 'space1';
describe('ruleRegistryAlertsSearchStrategy', () => {
describe('logs', () => {
beforeEach(async () => {
await esArchiver.load('x-pack/test/functional/es_archives/observability/alerts');
});
afterEach(async () => {
await esArchiver.unload('x-pack/test/functional/es_archives/observability/alerts');
});
it('should return alerts from log rules', async () => {
const result = await bsearch.send<RuleRegistrySearchResponse>({
supertest,
options: {
featureIds: [AlertConsumers.LOGS],
},
strategy: 'ruleRegistryAlertsSearchStrategy',
});
expect(result.rawResponse.hits.total).to.eql(5);
const consumers = result.rawResponse.hits.hits.map((hit) => {
return hit.fields?.['kibana.alert.rule.consumer'];
});
expect(consumers.every((consumer) => consumer === AlertConsumers.LOGS));
});
});
describe('siem', () => {
beforeEach(async () => {
await deleteSignalsIndex(supertest, log);
await createSignalsIndex(supertest, log);
});
afterEach(async () => {
await deleteSignalsIndex(supertest, log);
await deleteAllAlerts(supertest, log);
});
before(async () => {
await esArchiver.load('x-pack/test/functional/es_archives/auditbeat/hosts');
});
after(async () => {
await esArchiver.unload('x-pack/test/functional/es_archives/auditbeat/hosts');
});
it('should return alerts from siem rules', async () => {
const rule: QueryCreateSchema = {
...getRuleForSignalTesting(['auditbeat-*']),
query: `_id:${ID}`,
};
const { id: createdId } = await createRule(supertest, log, rule);
await waitForRuleSuccessOrStatus(supertest, log, createdId);
await waitForSignalsToBePresent(supertest, log, 1, [createdId]);
const result = await bsearch.send<RuleRegistrySearchResponse>({
supertest,
options: {
featureIds: [AlertConsumers.SIEM],
},
strategy: 'ruleRegistryAlertsSearchStrategy',
});
expect(result.rawResponse.hits.total).to.eql(1);
const consumers = result.rawResponse.hits.hits.map(
(hit) => hit.fields?.['kibana.alert.rule.consumer']
);
expect(consumers.every((consumer) => consumer === AlertConsumers.SIEM));
});
});
describe('apm', () => {
beforeEach(async () => {
await esArchiver.load('x-pack/test/functional/es_archives/rule_registry/alerts');
});
afterEach(async () => {
await esArchiver.unload('x-pack/test/functional/es_archives/rule_registry/alerts');
});
it('should return alerts from apm rules', async () => {
const result = await bsearch.send<RuleRegistrySearchResponse>({
supertest,
options: {
featureIds: [AlertConsumers.APM],
},
strategy: 'ruleRegistryAlertsSearchStrategy',
space: SPACE1,
});
expect(result.rawResponse.hits.total).to.eql(2);
const consumers = result.rawResponse.hits.hits.map(
(hit) => hit.fields?.['kibana.alert.rule.consumer']
);
expect(consumers.every((consumer) => consumer === AlertConsumers.APM));
});
});
describe('empty response', () => {
it('should return an empty response', async () => {
const result = await bsearch.send<RuleRegistrySearchResponse>({
supertest,
options: {
featureIds: [],
},
strategy: 'ruleRegistryAlertsSearchStrategy',
space: SPACE1,
});
expect(result.rawResponse).to.eql({});
});
});
});
};