mirror of
https://github.com/elastic/kibana.git
synced 2025-04-24 09:48:58 -04:00
Fix close all alerts in exceptions linked to shared exception with multiple rules (#189604)
## Fix close all alerts in exceptions linked to shared exception with multiple rules close: https://github.com/elastic/kibana/issues/189282 --------- Co-authored-by: Elastic Machine <elasticmachine@users.noreply.github.com>
This commit is contained in:
parent
e308d17195
commit
7b84fa8394
7 changed files with 209 additions and 6 deletions
|
@ -10,7 +10,7 @@ import type * as estypes from '@elastic/elasticsearch/lib/api/typesWithBodyKey';
|
|||
import type { ExceptionListItemSchema } from '@kbn/securitysolution-io-ts-list-types';
|
||||
import {
|
||||
buildAlertStatusesFilter,
|
||||
buildAlertsFilter,
|
||||
buildAlertsFilterByRuleIds,
|
||||
} from '../../../detections/components/alerts_table/default_config';
|
||||
import { getEsQueryFilter } from '../../../detections/containers/detection_engine/exceptions/get_es_query_filter';
|
||||
import type { IndexPatternArray } from '../../../../common/api/detection_engine/model/rule_schema';
|
||||
|
@ -76,10 +76,12 @@ export const useCloseAlertsFromExceptions = (): ReturnUseCloseAlertsFromExceptio
|
|||
'in-progress',
|
||||
]);
|
||||
|
||||
const filterByRuleIds = buildAlertsFilterByRuleIds(ruleStaticIds);
|
||||
|
||||
const filter = await getEsQueryFilter(
|
||||
'',
|
||||
'kuery',
|
||||
[...ruleStaticIds.flatMap((id) => buildAlertsFilter(id)), ...alertStatusFilter],
|
||||
[...filterByRuleIds, ...alertStatusFilter],
|
||||
bulkCloseIndex,
|
||||
prepareExceptionItemsForBulkClose(exceptionItems),
|
||||
false
|
||||
|
|
|
@ -16,6 +16,7 @@ import {
|
|||
buildThreatMatchFilter,
|
||||
getAlertsDefaultModel,
|
||||
getAlertsPreviewDefaultModel,
|
||||
buildAlertsFilterByRuleIds,
|
||||
} from './default_config';
|
||||
|
||||
jest.mock('./actions');
|
||||
|
@ -283,6 +284,75 @@ describe('alerts default_config', () => {
|
|||
});
|
||||
});
|
||||
|
||||
describe('buildAlertsFilterByRuleIds', () => {
|
||||
it('given an empty list of rule ids will return an empty filter', () => {
|
||||
const filters = buildAlertsFilterByRuleIds([]);
|
||||
expect(filters).toHaveLength(0);
|
||||
});
|
||||
|
||||
it('builds filter containing 1 rule id passed into function', () => {
|
||||
const filters = buildAlertsFilterByRuleIds(['rule-id-1']);
|
||||
const expected = {
|
||||
meta: {
|
||||
alias: null,
|
||||
disabled: false,
|
||||
negate: false,
|
||||
},
|
||||
query: {
|
||||
bool: {
|
||||
should: [
|
||||
{
|
||||
term: {
|
||||
'kibana.alert.rule.rule_id': 'rule-id-1',
|
||||
},
|
||||
},
|
||||
],
|
||||
minimum_should_match: 1,
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
expect(filters).toHaveLength(1);
|
||||
expect(filters[0]).toEqual(expected);
|
||||
});
|
||||
|
||||
it('builds filter containing 3 rule ids passed into function', () => {
|
||||
const filters = buildAlertsFilterByRuleIds(['rule-id-1', 'rule-id-2', 'rule-id-3']);
|
||||
const expected = {
|
||||
meta: {
|
||||
alias: null,
|
||||
disabled: false,
|
||||
negate: false,
|
||||
},
|
||||
query: {
|
||||
bool: {
|
||||
should: [
|
||||
{
|
||||
term: {
|
||||
'kibana.alert.rule.rule_id': 'rule-id-1',
|
||||
},
|
||||
},
|
||||
{
|
||||
term: {
|
||||
'kibana.alert.rule.rule_id': 'rule-id-2',
|
||||
},
|
||||
},
|
||||
{
|
||||
term: {
|
||||
'kibana.alert.rule.rule_id': 'rule-id-3',
|
||||
},
|
||||
},
|
||||
],
|
||||
minimum_should_match: 1,
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
expect(filters).toHaveLength(1);
|
||||
expect(filters[0]).toEqual(expected);
|
||||
});
|
||||
});
|
||||
|
||||
describe('getAlertsDefaultModel', () => {
|
||||
test('returns correct model for Basic license', () => {
|
||||
const licenseServiceMock = createLicenseServiceMock();
|
||||
|
|
|
@ -122,6 +122,34 @@ export const buildAlertsFilter = (ruleStaticId: string | null): Filter[] =>
|
|||
]
|
||||
: [];
|
||||
|
||||
export const buildAlertsFilterByRuleIds = (ruleIds: string[] | null): Filter[] => {
|
||||
if (ruleIds == null || ruleIds.length === 0) {
|
||||
return [];
|
||||
}
|
||||
|
||||
const combinedQuery = {
|
||||
bool: {
|
||||
should: ruleIds.map((ruleId) => ({
|
||||
term: {
|
||||
[ALERT_RULE_RULE_ID]: ruleId,
|
||||
},
|
||||
})),
|
||||
minimum_should_match: 1,
|
||||
},
|
||||
};
|
||||
|
||||
return [
|
||||
{
|
||||
meta: {
|
||||
alias: null,
|
||||
negate: false,
|
||||
disabled: false,
|
||||
},
|
||||
query: combinedQuery,
|
||||
},
|
||||
];
|
||||
};
|
||||
|
||||
export const buildShowBuildingBlockFilter = (showBuildingBlockAlerts: boolean): Filter[] =>
|
||||
showBuildingBlockAlerts
|
||||
? []
|
||||
|
|
|
@ -4,6 +4,12 @@
|
|||
* 2.0; you may not use this file except in compliance with the Elastic License
|
||||
* 2.0.
|
||||
*/
|
||||
import {
|
||||
createExceptionList,
|
||||
createExceptionListItem,
|
||||
deleteExceptionLists,
|
||||
linkRulesToExceptionList,
|
||||
} from '../../../../../../tasks/api_calls/exceptions';
|
||||
import { waitForAlertsToPopulate } from '../../../../../../tasks/create_new_rule';
|
||||
import {
|
||||
addExceptionFromFirstAlert,
|
||||
|
@ -12,25 +18,42 @@ import {
|
|||
} from '../../../../../../tasks/alerts';
|
||||
import { deleteAlertsAndRules, postDataView } from '../../../../../../tasks/api_calls/common';
|
||||
import { login } from '../../../../../../tasks/login';
|
||||
import { clickDisableRuleSwitch, visitRuleDetailsPage } from '../../../../../../tasks/rule_details';
|
||||
import {
|
||||
clickDisableRuleSwitch,
|
||||
visitRuleDetailsPage,
|
||||
openEditException,
|
||||
goToExceptionsTab,
|
||||
goToAlertsTab,
|
||||
} from '../../../../../../tasks/rule_details';
|
||||
import { createRule } from '../../../../../../tasks/api_calls/rules';
|
||||
import { getNewRule } from '../../../../../../objects/rule';
|
||||
import { getExceptionList } from '../../../../../../objects/exception';
|
||||
import { LOADING_INDICATOR } from '../../../../../../screens/security_header';
|
||||
import { ALERTS_COUNT } from '../../../../../../screens/alerts';
|
||||
import { ALERTS_COUNT, ALERT_EMBEDDABLE_EMPTY_PROMPT } from '../../../../../../screens/alerts';
|
||||
import {
|
||||
addExceptionEntryFieldValue,
|
||||
addExceptionEntryOperatorValue,
|
||||
addExceptionEntryFieldValueValue,
|
||||
addExceptionFlyoutItemName,
|
||||
selectBulkCloseAlerts,
|
||||
submitEditedExceptionItem,
|
||||
submitNewExceptionItem,
|
||||
} from '../../../../../../tasks/exceptions';
|
||||
|
||||
const EXCEPTION_LIST_NAME = 'My test list';
|
||||
|
||||
const getExceptionList1 = () => ({
|
||||
...getExceptionList(),
|
||||
name: EXCEPTION_LIST_NAME,
|
||||
list_id: 'exception_list_1',
|
||||
});
|
||||
|
||||
describe('Close matching Alerts ', { tags: ['@ess', '@serverless'] }, () => {
|
||||
const ITEM_NAME = 'Sample Exception Item';
|
||||
|
||||
beforeEach(() => {
|
||||
deleteAlertsAndRules();
|
||||
deleteExceptionLists();
|
||||
|
||||
cy.task('esArchiverUnload', { archiveName: 'exceptions' });
|
||||
cy.task('esArchiverLoad', { archiveName: 'exceptions' });
|
||||
|
@ -54,6 +77,7 @@ describe('Close matching Alerts ', { tags: ['@ess', '@serverless'] }, () => {
|
|||
after(() => {
|
||||
cy.task('esArchiverUnload', { archiveName: 'exceptions' });
|
||||
deleteAlertsAndRules();
|
||||
deleteExceptionLists();
|
||||
});
|
||||
|
||||
it('Should create a Rule exception item from alert actions overflow menu and close all matching alerts', () => {
|
||||
|
@ -77,4 +101,55 @@ describe('Close matching Alerts ', { tags: ['@ess', '@serverless'] }, () => {
|
|||
cy.get(LOADING_INDICATOR).should('not.exist');
|
||||
cy.get(ALERTS_COUNT).should('contain', '1');
|
||||
});
|
||||
|
||||
it('Should close all alerts from if several rules has shared exception list', () => {
|
||||
cy.get(ALERTS_COUNT).should('contain', '3');
|
||||
|
||||
let exceptionListId = '';
|
||||
createExceptionList(getExceptionList1(), getExceptionList1().list_id)
|
||||
.then((response) => {
|
||||
exceptionListId = response.body.id;
|
||||
createExceptionListItem(getExceptionList1().list_id, {
|
||||
item_id: '123',
|
||||
entries: [
|
||||
{
|
||||
field: 'user.name',
|
||||
operator: 'included',
|
||||
type: 'match_any',
|
||||
value: ['alice'],
|
||||
},
|
||||
],
|
||||
});
|
||||
})
|
||||
.then((response) => {
|
||||
return createRule(
|
||||
getNewRule({
|
||||
exceptions_list: [
|
||||
{
|
||||
id: response.body.id,
|
||||
list_id: getExceptionList1().list_id,
|
||||
type: getExceptionList1().type,
|
||||
namespace_type: getExceptionList1().namespace_type,
|
||||
},
|
||||
],
|
||||
})
|
||||
);
|
||||
})
|
||||
.then(() =>
|
||||
linkRulesToExceptionList('rule_testing', {
|
||||
id: exceptionListId,
|
||||
listId: getExceptionList1().list_id,
|
||||
})
|
||||
);
|
||||
|
||||
goToExceptionsTab();
|
||||
cy.reload();
|
||||
openEditException();
|
||||
selectBulkCloseAlerts();
|
||||
submitEditedExceptionItem();
|
||||
goToAlertsTab();
|
||||
|
||||
cy.get(ALERTS_COUNT).should('not.exist');
|
||||
cy.get(ALERT_EMBEDDABLE_EMPTY_PROMPT).should('exist');
|
||||
});
|
||||
});
|
||||
|
|
|
@ -31,7 +31,7 @@ export interface ExceptionListItem {
|
|||
namespace_type: 'single' | 'agnostic';
|
||||
tags: string[];
|
||||
type: 'simple';
|
||||
entries: Array<{ field: string; operator: string; type: string; value: string[] }>;
|
||||
entries: Array<{ field: string; operator: string; type: string; value: string[] | string }>;
|
||||
expire_time?: string;
|
||||
}
|
||||
|
||||
|
|
|
@ -47,7 +47,7 @@ export const createExceptionList = (
|
|||
|
||||
export const createExceptionListItem = (
|
||||
exceptionListId: string,
|
||||
exceptionListItem?: ExceptionListItem
|
||||
exceptionListItem?: Partial<ExceptionListItem>
|
||||
) =>
|
||||
rootRequest<ExceptionListItemSchema>({
|
||||
method: 'POST',
|
||||
|
@ -128,3 +128,27 @@ export const deleteExceptionLists = () => {
|
|||
export const deleteEndpointExceptionList = () => {
|
||||
deleteExceptionList('endpoint_list', 'agnostic');
|
||||
};
|
||||
|
||||
export const linkRulesToExceptionList = (
|
||||
ruleId: string,
|
||||
exceptionList: {
|
||||
id: string;
|
||||
listId: string;
|
||||
}
|
||||
) => {
|
||||
rootRequest({
|
||||
method: 'PATCH',
|
||||
url: `/api/detection_engine/rules`,
|
||||
body: {
|
||||
exceptions_list: [
|
||||
{
|
||||
id: exceptionList.id,
|
||||
list_id: exceptionList.listId,
|
||||
namespace_type: 'single',
|
||||
type: 'detection',
|
||||
},
|
||||
],
|
||||
rule_id: ruleId,
|
||||
},
|
||||
});
|
||||
};
|
||||
|
|
|
@ -36,6 +36,10 @@
|
|||
"user": {
|
||||
"type": "nested",
|
||||
"properties": {
|
||||
"name": {
|
||||
"ignore_above": 1024,
|
||||
"type": "keyword"
|
||||
},
|
||||
"first": {
|
||||
"type": "keyword"
|
||||
},
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue