Skip to content

Commit

Permalink
[8.9] [Security Solution] [Exceptions] Fix Exception Auto-populate fr…
Browse files Browse the repository at this point in the history
…om Alert actions (#159908) (#160731)

# Backport

This will backport the following commits from `main` to `8.9`:
- [[Security Solution] [Exceptions] Fix Exception Auto-populate from
Alert actions (#159908)](#159908)

<!--- Backport version: 8.9.7 -->

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

<!--BACKPORT [{"author":{"name":"Wafaa
Nasr","email":"wafaa.nasr@elastic.co"},"sourceCommit":{"committedDate":"2023-06-28T11:14:19Z","message":"[Security
Solution] [Exceptions] Fix Exception Auto-populate from Alert actions
(#159908)\n\n## Summary\r\n\r\n- Addresses
https://github.com/elastic/kibana/issues/159784\r\n\r\n---------\r\n\r\nCo-authored-by:
Kibana Machine
<42973632+kibanamachine@users.noreply.github.com>","sha":"fdd709b02579b05124585b10fa6535d0a90480e2","branchLabelMapping":{"^v8.10.0$":"main","^v(\\d+).(\\d+).\\d+$":"$1.$2"}},"sourcePullRequest":{"labels":["release_note:fix","backport:skip","8.9
candidate","Team:Detection
Engine","v8.10.0"],"number":159908,"url":"https://github.com/elastic/kibana/pull/159908","mergeCommit":{"message":"[Security
Solution] [Exceptions] Fix Exception Auto-populate from Alert actions
(#159908)\n\n## Summary\r\n\r\n- Addresses
https://github.com/elastic/kibana/issues/159784\r\n\r\n---------\r\n\r\nCo-authored-by:
Kibana Machine
<42973632+kibanamachine@users.noreply.github.com>","sha":"fdd709b02579b05124585b10fa6535d0a90480e2"}},"sourceBranch":"main","suggestedTargetBranches":[],"targetPullRequestStates":[{"branch":"main","label":"v8.10.0","labelRegex":"^v8.10.0$","isSourceBranch":true,"state":"MERGED","url":"https://github.com/elastic/kibana/pull/159908","number":159908,"mergeCommit":{"message":"[Security
Solution] [Exceptions] Fix Exception Auto-populate from Alert actions
(#159908)\n\n## Summary\r\n\r\n- Addresses
https://github.com/elastic/kibana/issues/159784\r\n\r\n---------\r\n\r\nCo-authored-by:
Kibana Machine
<42973632+kibanamachine@users.noreply.github.com>","sha":"fdd709b02579b05124585b10fa6535d0a90480e2"}}]}]
BACKPORT-->
  • Loading branch information
WafaaNasr authored Jun 28, 2023
1 parent 3be4f61 commit 39e1bde
Show file tree
Hide file tree
Showing 8 changed files with 237 additions and 36 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@
* 2.0.
*/

import React, { useCallback, useEffect, useMemo, useReducer } from 'react';
import React, { useCallback, useEffect, useMemo, useReducer, useState } from 'react';
import { EuiFlexGroup, EuiFlexItem, EuiSpacer } from '@elastic/eui';
import styled from 'styled-components';
import { HttpStart } from '@kbn/core/public';
Expand Down Expand Up @@ -34,14 +34,14 @@ import {
} from '@kbn/securitysolution-list-utils';
import { DataViewBase } from '@kbn/es-query';
import type { AutocompleteStart } from '@kbn/unified-search-plugin/public';
import deepEqual from 'fast-deep-equal';

import { AndOrBadge } from '../and_or_badge';

import { BuilderExceptionListItemComponent } from './exception_item_renderer';
import { BuilderLogicButtons } from './logic_buttons';
import { getTotalErrorExist } from './selectors';
import { EntryFieldError, State, exceptionsBuilderReducer } from './reducer';

const MyInvisibleAndBadge = styled(EuiFlexItem)`
visibility: hidden;
`;
Expand Down Expand Up @@ -131,6 +131,7 @@ export const ExceptionBuilderComponent = ({
disableNested: isNestedDisabled,
disableOr: isOrDisabled,
});
const [areAllEntriesDeleted, setAreAllEntriesDeleted] = useState<boolean>(false);

const {
addNested,
Expand Down Expand Up @@ -252,6 +253,7 @@ export const ExceptionBuilderComponent = ({
// just add a default entry to it
if (updatedExceptions.length === 0) {
setDefaultExceptions(item);
setAreAllEntriesDeleted(true);
} else if (updatedExceptions.length > 0 && exceptionListItemSchema.is(item)) {
setUpdateExceptionsToDelete([...exceptionsToDelete, item]);
} else {
Expand Down Expand Up @@ -394,12 +396,36 @@ export const ExceptionBuilderComponent = ({
}
}, [exceptions, handleAddNewExceptionItem]);

/**
* This component relies on the "exceptionListItems" to pre-fill its entries,
* but any subsequent updates to the entries are not reflected back to
* the "exceptionListItems". To ensure correct behavior, we need to only
* fill the entries from the "exceptionListItems" during initialization.
*
* In the initialization phase, if there are "exceptionListItems" with
* pre-filled entries, the exceptions array will be empty. However,
* there are cases where the "exceptionListItems" may not be sent
* correctly during initialization, leading to the exceptions
* array being filled with empty entries. Therefore, we need to
* check if the exception is correctly populated with a valid
* "field" when the "exceptionListItems" has entries. that's why
* "exceptionsEntriesPopulated" is used
*
* It's important to differentiate this case from when the user
* deletes all the entries and the "exceptionListItems" has pre-filled values.
* that's why "allEntriesDeleted" is used
*
* deepEqual(exceptionListItems, exceptions) to handle the exceptionListItems in
* the EventFiltersFlyout
*/
useEffect(() => {
if (exceptionListItems.length > 0) {
if (!exceptionListItems.length || deepEqual(exceptionListItems, exceptions)) return;
const exceptionsEntriesPopulated = exceptions.some((exception) =>
exception.entries.some((entry) => entry.field)
);
if (!exceptionsEntriesPopulated && !areAllEntriesDeleted)
setUpdateExceptions(exceptionListItems);
}
// eslint-disable-next-line react-hooks/exhaustive-deps
}, []);
}, [areAllEntriesDeleted, exceptionListItems, exceptions, setUpdateExceptions]);

return (
<EuiFlexGroup gutterSize="s" direction="column" data-test-subj="exceptionsBuilderWrapper">
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,8 +7,10 @@

import { deleteAlertsAndRules } from '../../../tasks/common';
import {
expandFirstAlert,
goToClosedAlertsOnRuleDetailsPage,
goToOpenedAlertsOnRuleDetailsPage,
openAddEndpointExceptionFromAlertActionButton,
openAddEndpointExceptionFromFirstAlert,
} from '../../../tasks/alerts';
import { login, visitWithoutDateRange } from '../../../tasks/login';
Expand All @@ -26,13 +28,22 @@ import {
} from '../../../tasks/es_archiver';
import { DETECTIONS_RULE_MANAGEMENT_URL } from '../../../urls/navigation';
import {
addExceptionEntryFieldValue,
addExceptionEntryFieldValueValue,
addExceptionFlyoutItemName,
editExceptionFlyoutItemName,
selectCloseSingleAlerts,
submitNewExceptionItem,
validateExceptionConditionField,
} from '../../../tasks/exceptions';
import { ALERTS_COUNT, EMPTY_ALERT_TABLE } from '../../../screens/alerts';
import { NO_EXCEPTIONS_EXIST_PROMPT } from '../../../screens/exceptions';
import {
ADD_AND_BTN,
EXCEPTION_CARD_ITEM_CONDITIONS,
EXCEPTION_CARD_ITEM_NAME,
EXCEPTION_ITEM_VIEWER_CONTAINER,
NO_EXCEPTIONS_EXIST_PROMPT,
} from '../../../screens/exceptions';
import {
removeException,
goToAlertsTab,
Expand All @@ -41,10 +52,11 @@ import {

describe('Endpoint Exceptions workflows from Alert', () => {
const expectedNumberOfAlerts = 1;
before(() => {
esArchiverResetKibana();
});
const ITEM_NAME = 'Sample Exception List Item';
const ITEM_NAME_EDIT = 'Sample Exception List Item';
const ADDITIONAL_ENTRY = 'host.hostname';
beforeEach(() => {
esArchiverResetKibana();
login();
deleteAlertsAndRules();
esArchiverLoad('endpoint');
Expand All @@ -69,7 +81,7 @@ describe('Endpoint Exceptions workflows from Alert', () => {
validateExceptionConditionField('file.Ext.code_signature');

selectCloseSingleAlerts();
addExceptionFlyoutItemName('Sample Exception');
addExceptionFlyoutItemName(ITEM_NAME);
submitNewExceptionItem();

// Alerts table should now be empty from having added exception and closed
Expand Down Expand Up @@ -100,4 +112,39 @@ describe('Endpoint Exceptions workflows from Alert', () => {

cy.get(ALERTS_COUNT).should('have.text', `${expectedNumberOfAlerts} alert`);
});

it('Should be able to create Endpoint exception from Alerts take action button, and change multiple exception items without resetting to initial auto-prefilled entries', () => {
// Open first Alert Summary
expandFirstAlert();

// The Endpoint should populated with predefined fields
openAddEndpointExceptionFromAlertActionButton();

// As the endpoint.alerts-* is used to trigger the alert the
// file.Ext.code_signature will be auto-populated
validateExceptionConditionField('file.Ext.code_signature');
addExceptionFlyoutItemName(ITEM_NAME);

cy.get(ADD_AND_BTN).click();
// edit conditions
addExceptionEntryFieldValue(ADDITIONAL_ENTRY, 6);
addExceptionEntryFieldValueValue('foo', 4);

// Change the name again
editExceptionFlyoutItemName(ITEM_NAME_EDIT);

// validate the condition is still "agent.name" or got rest after the name is changed
validateExceptionConditionField(ADDITIONAL_ENTRY);

selectCloseSingleAlerts();
submitNewExceptionItem();

// Endpoint Exception will move to Endpoint List under Exception tab of rule
goToEndpointExceptionsTab();

// new exception item displays
cy.get(EXCEPTION_ITEM_VIEWER_CONTAINER).should('have.length', 1);
cy.get(EXCEPTION_CARD_ITEM_NAME).should('have.text', ITEM_NAME_EDIT);
cy.get(EXCEPTION_CARD_ITEM_CONDITIONS).contains('span', ADDITIONAL_ENTRY);
});
});
Original file line number Diff line number Diff line change
Expand Up @@ -12,8 +12,10 @@ import { createRule } from '../../../tasks/api_calls/rules';
import { goToRuleDetails } from '../../../tasks/alerts_detection_rules';
import {
addExceptionFromFirstAlert,
expandFirstAlert,
goToClosedAlertsOnRuleDetailsPage,
goToOpenedAlertsOnRuleDetailsPage,
openAddRuleExceptionFromAlertActionButton,
} from '../../../tasks/alerts';
import {
addExceptionEntryFieldValue,
Expand All @@ -26,6 +28,9 @@ import {
validateExceptionItemAffectsTheCorrectRulesInRulePage,
validateExceptionConditionField,
validateExceptionCommentCountAndText,
editExceptionFlyoutItemName,
validateHighlightedFieldsPopulatedAsExceptionConditions,
validateEmptyExceptionConditionField,
} from '../../../tasks/exceptions';
import {
esArchiverLoad,
Expand All @@ -42,26 +47,44 @@ import {

import { DETECTIONS_RULE_MANAGEMENT_URL } from '../../../urls/navigation';
import { postDataView, deleteAlertsAndRules } from '../../../tasks/common';
import { NO_EXCEPTIONS_EXIST_PROMPT } from '../../../screens/exceptions';
import {
ADD_AND_BTN,
ENTRY_DELETE_BTN,
EXCEPTION_CARD_ITEM_CONDITIONS,
EXCEPTION_CARD_ITEM_NAME,
EXCEPTION_ITEM_VIEWER_CONTAINER,
NO_EXCEPTIONS_EXIST_PROMPT,
} from '../../../screens/exceptions';
import { waitForAlertsToPopulate } from '../../../tasks/create_new_rule';

const loadEndpointRuleAndAlerts = () => {
esArchiverLoad('endpoint');
login();
createRule(getEndpointRule());
visitWithoutDateRange(DETECTIONS_RULE_MANAGEMENT_URL);
goToRuleDetails();
waitForAlertsToPopulate();
};

describe('Rule Exceptions workflows from Alert', () => {
const EXPECTED_NUMBER_OF_ALERTS = '1 alert';
const ITEM_NAME = 'Sample Exception List Item';
const ITEM_NAME = 'Sample Exception Item';
const ITEM_NAME_EDIT = 'Sample Exception Item Edit';
const ADDITIONAL_ENTRY = 'host.hostname';
const newRule = getNewRule();

beforeEach(() => {
esArchiverResetKibana();
deleteAlertsAndRules();
});
after(() => {
esArchiverUnload('exceptions');
deleteAlertsAndRules();
});
afterEach(() => {
esArchiverUnload('exceptions_2');
});

it('Creates an exception item from alert actions overflow menu and close all matching alerts', () => {
it('Should create a Rule exception item from alert actions overflow menu and close all matching alerts', () => {
esArchiverLoad('exceptions');
login();
postDataView('exceptions-*');
Expand Down Expand Up @@ -119,14 +142,8 @@ describe('Rule Exceptions workflows from Alert', () => {

cy.get(ALERTS_COUNT).should('have.text', '2 alerts');
});

it('Creates an exception item from alert actions overflow menu and auto populate the conditions using alert Highlighted fields ', () => {
esArchiverLoad('endpoint');
login();
createRule(getEndpointRule());
visitWithoutDateRange(DETECTIONS_RULE_MANAGEMENT_URL);
goToRuleDetails();
waitForAlertsToPopulate();
it('Should create a Rule exception item from alert actions overflow menu and auto populate the conditions using alert Highlighted fields', () => {
loadEndpointRuleAndAlerts();

cy.get(LOADING_INDICATOR).should('not.exist');
addExceptionFromFirstAlert();
Expand All @@ -144,20 +161,120 @@ describe('Rule Exceptions workflows from Alert', () => {
* fields are based on the alert document that should be generated
* when the endpoint rule runs
*/
highlightedFieldsBasedOnAlertDoc.forEach((field, index) => {
validateExceptionConditionField(field);
});
validateHighlightedFieldsPopulatedAsExceptionConditions(highlightedFieldsBasedOnAlertDoc);

/**
* Validate that the comments are opened by default with one comment added
* showing a text contains information about the pre-filled conditions
*/
validateExceptionCommentCountAndText(
1,
'Exception conditions are pre-filled with relevant data from alert with "id"'
);

addExceptionFlyoutItemName(ITEM_NAME);
submitNewExceptionItem();
});
it('Should create a Rule exception from Alerts take action button and change multiple exception items without resetting to initial auto-prefilled entries', () => {
loadEndpointRuleAndAlerts();

cy.get(LOADING_INDICATOR).should('not.exist');

// Open first Alert Summary
expandFirstAlert();

// The Rule exception should populated with highlighted fields
openAddRuleExceptionFromAlertActionButton();

const highlightedFieldsBasedOnAlertDoc = [
'host.name',
'agent.id',
'user.name',
'process.executable',
'file.path',
];

/**
* Validate the highlighted fields are auto populated, these
* fields are based on the alert document that should be generated
* when the endpoint rule runs
*/
validateHighlightedFieldsPopulatedAsExceptionConditions(highlightedFieldsBasedOnAlertDoc);

/**
* Validate that the comments are opened by default with one comment added
* showing a text contains information about the pre-filled conditions
*/
validateExceptionCommentCountAndText(
1,
'Exception conditions are pre-filled with relevant data from'
'Exception conditions are pre-filled with relevant data from alert with "id"'
);

addExceptionFlyoutItemName(ITEM_NAME);

cy.get(ADD_AND_BTN).click();

// edit conditions
addExceptionEntryFieldValue(ADDITIONAL_ENTRY, 5);
addExceptionEntryFieldValueValue('foo', 5);

// Change the name again
editExceptionFlyoutItemName(ITEM_NAME_EDIT);

// validate the condition is still 'host.hostname' or got rest after the name is changed
validateExceptionConditionField(ADDITIONAL_ENTRY);

submitNewExceptionItem();

goToExceptionsTab();

// new exception item displays
cy.get(EXCEPTION_ITEM_VIEWER_CONTAINER).should('have.length', 1);
cy.get(EXCEPTION_CARD_ITEM_NAME).should('have.text', ITEM_NAME_EDIT);
cy.get(EXCEPTION_CARD_ITEM_CONDITIONS).contains('span', 'host.hostname');
});
it('Should delete all prefilled exception entries when creating a Rule exception from Alerts take action button without resetting to initial auto-prefilled entries', () => {
loadEndpointRuleAndAlerts();

cy.get(LOADING_INDICATOR).should('not.exist');

// Open first Alert Summary
expandFirstAlert();

// The Rule exception should populated with highlighted fields
openAddRuleExceptionFromAlertActionButton();

const highlightedFieldsBasedOnAlertDoc = [
'host.name',
'agent.id',
'user.name',
'process.executable',
'file.path',
];

/**
* Validate the highlighted fields are auto populated, these
* fields are based on the alert document that should be generated
* when the endpoint rule runs
*/
validateHighlightedFieldsPopulatedAsExceptionConditions(highlightedFieldsBasedOnAlertDoc);

/**
* Delete all the highlighted fields to see if any condition
* will prefuilled again.
*/
const highlightedFieldsCount = highlightedFieldsBasedOnAlertDoc.length - 1;
highlightedFieldsBasedOnAlertDoc.forEach((_, index) =>
cy
.get(ENTRY_DELETE_BTN)
.eq(highlightedFieldsCount - index)
.click()
);

/**
* Validate that there are no highlighted fields are auto populated
* after the deletion
*/
validateEmptyExceptionConditionField();
});
});
Loading

0 comments on commit 39e1bde

Please sign in to comment.