Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[Security Solution] [Exceptions] Auto-populate exception flyout with alert’s “highlighted fields” values #159029

Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
18 commits
Select commit Hold shift + click to select a range
e123658
populate the rule exception with the alert highlighted fields
WafaaNasr Jun 5, 2023
d618d0a
Merge branch 'main' of https://github.com/elastic/kibana into 6405-au…
WafaaNasr Jun 5, 2023
702f330
add initial tests for the helper methods
WafaaNasr Jun 7, 2023
8cf53cb
Merge branch 'main' of https://github.com/elastic/kibana into 6405-au…
WafaaNasr Jun 8, 2023
ee9d078
handle fieldValue entry in case of array
WafaaNasr Jun 8, 2023
9156746
Merge branch 'main' of https://github.com/elastic/kibana into 6405-au…
WafaaNasr Jun 9, 2023
115eff7
add tests
WafaaNasr Jun 9, 2023
a88527c
Merge branch 'main' of https://github.com/elastic/kibana into 6405-au…
WafaaNasr Jun 12, 2023
033842c
add cypress test
WafaaNasr Jun 12, 2023
94283e4
Merge branch 'main' of https://github.com/elastic/kibana into 6405-au…
WafaaNasr Jun 12, 2023
a0cca84
fix comment assertion
WafaaNasr Jun 12, 2023
d6da91a
address comments
WafaaNasr Jun 13, 2023
78aedc3
fix stale state of exceptionListItems
WafaaNasr Jun 13, 2023
4daff25
Merge branch 'main' of https://github.com/elastic/kibana into 6405-au…
WafaaNasr Jun 13, 2023
9519b5b
Merge branch 'main' of https://github.com/elastic/kibana into 6405-au…
WafaaNasr Jun 14, 2023
e78b92a
Merge branch 'main' of https://github.com/elastic/kibana into 6405-au…
WafaaNasr Jun 14, 2023
ace134a
revert back changes until see if cypress is trusted
WafaaNasr Jun 14, 2023
8655a2d
Merge branch 'main' of https://github.com/elastic/kibana into 6405-au…
WafaaNasr Jun 15, 2023
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
* 2.0.
*/

import { deleteAlertsAndRules } from '../../../tasks/common';
import {
goToClosedAlertsOnRuleDetailsPage,
goToOpenedAlertsOnRuleDetailsPage,
Expand All @@ -28,13 +29,10 @@ import {
addExceptionFlyoutItemName,
selectCloseSingleAlerts,
submitNewExceptionItem,
validateExceptionConditionField,
} from '../../../tasks/exceptions';
import { ALERTS_COUNT, EMPTY_ALERT_TABLE } from '../../../screens/alerts';
import {
EXCEPTION_ITEM_CONTAINER,
FIELD_INPUT_PARENT,
NO_EXCEPTIONS_EXIST_PROMPT,
} from '../../../screens/exceptions';
import { NO_EXCEPTIONS_EXIST_PROMPT } from '../../../screens/exceptions';
import {
removeException,
goToAlertsTab,
Expand All @@ -43,10 +41,13 @@ import {

describe('Endpoint Exceptions workflows from Alert', () => {
const expectedNumberOfAlerts = 1;
beforeEach(() => {
before(() => {
esArchiverResetKibana();
esArchiverLoad('endpoint');
});
beforeEach(() => {
login();
deleteAlertsAndRules();
esArchiverLoad('endpoint');
createRule(getEndpointRule());
visitWithoutDateRange(DETECTIONS_RULE_MANAGEMENT_URL);
goToRuleDetails();
Expand All @@ -64,12 +65,8 @@ describe('Endpoint Exceptions workflows from Alert', () => {
openAddEndpointExceptionFromFirstAlert();

// As the endpoint.alerts-* is used to trigger the alert the
// file.Ext.code_signature will be populated as the first item
cy.get(EXCEPTION_ITEM_CONTAINER)
.eq(0)
.find(FIELD_INPUT_PARENT)
.eq(0)
.should('have.text', 'file.Ext.code_signature');
// file.Ext.code_signature will be auto-populated
validateExceptionConditionField('file.Ext.code_signature');

selectCloseSingleAlerts();
addExceptionFlyoutItemName('Sample Exception');
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@
*/

import { LOADING_INDICATOR } from '../../../screens/security_header';
import { getNewRule } from '../../../objects/rule';
import { getNewRule, getEndpointRule } from '../../../objects/rule';
import { ALERTS_COUNT, EMPTY_ALERT_TABLE } from '../../../screens/alerts';
import { createRule } from '../../../tasks/api_calls/rules';
import { goToRuleDetails } from '../../../tasks/alerts_detection_rules';
Expand All @@ -24,6 +24,8 @@ import {
submitNewExceptionItem,
validateExceptionItemFirstAffectedRuleNameInRulePage,
validateExceptionItemAffectsTheCorrectRulesInRulePage,
validateExceptionConditionField,
validateExceptionCommentCountAndText,
} from '../../../tasks/exceptions';
import {
esArchiverLoad,
Expand All @@ -47,19 +49,22 @@ describe('Rule Exceptions workflows from Alert', () => {
const EXPECTED_NUMBER_OF_ALERTS = '1 alert';
const ITEM_NAME = 'Sample Exception List Item';
const newRule = getNewRule();
before(() => {

beforeEach(() => {
esArchiverResetKibana();
esArchiverLoad('exceptions');
login();
postDataView('exceptions-*');
deleteAlertsAndRules();
});

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

beforeEach(() => {
deleteAlertsAndRules();
it('Creates an exception item from alert actions overflow menu and close all matching alerts', () => {
esArchiverLoad('exceptions');
login();
postDataView('exceptions-*');
createRule({
...newRule,
query: 'agent.name:*',
Expand All @@ -70,13 +75,7 @@ describe('Rule Exceptions workflows from Alert', () => {
visitWithoutDateRange(DETECTIONS_RULE_MANAGEMENT_URL);
goToRuleDetails();
waitForAlertsToPopulate();
});

afterEach(() => {
esArchiverUnload('exceptions_2');
});

it('Creates an exception item from alert actions overflow menu and close all matching alerts', () => {
cy.get(LOADING_INDICATOR).should('not.exist');
addExceptionFromFirstAlert();

Expand Down Expand Up @@ -120,4 +119,45 @@ 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();

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

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
*/
highlightedFieldsBasedOnAlertDoc.forEach((field, index) => {
validateExceptionConditionField(field);
});

/**
* 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'
);

addExceptionFlyoutItemName(ITEM_NAME);
submitNewExceptionItem();
});
});
14 changes: 8 additions & 6 deletions x-pack/plugins/security_solution/cypress/tasks/exceptions.ts
Original file line number Diff line number Diff line change
Expand Up @@ -164,6 +164,9 @@ export const addExceptionConditions = (exception: Exception) => {
});
};

export const validateExceptionConditionField = (value: string) => {
cy.get(EXCEPTION_ITEM_CONTAINER).contains('span', value);
};
export const submitNewExceptionItem = () => {
cy.get(CONFIRM_BTN).click();
cy.get(CONFIRM_BTN).should('not.exist');
Expand Down Expand Up @@ -193,14 +196,13 @@ export const selectOs = (os: string) => {
export const addExceptionComment = (comment: string) => {
cy.get(EXCEPTION_COMMENTS_ACCORDION_BTN).click();
cy.get(EXCEPTION_COMMENT_TEXT_AREA).type(`${comment}`);
// cy.root()
// .pipe(($el) => {
// return $el.find(EXCEPTION_COMMENT_TEXT_AREA);
// })
// .clear()
// .type(`${comment}`)
cy.get(EXCEPTION_COMMENT_TEXT_AREA).should('have.value', comment);
};

export const validateExceptionCommentCountAndText = (count: number, comment: string) => {
cy.get(EXCEPTION_COMMENTS_ACCORDION_BTN).contains('h3', count);
cy.get(EXCEPTION_COMMENT_TEXT_AREA).contains('textarea', comment);
};
export const clickOnShowComments = () => {
cy.get(EXCEPTION_ITEM_VIEWER_CONTAINER_SHOW_COMMENTS_BTN).click();
};
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -215,10 +215,17 @@ function getFieldsByRuleType(ruleType?: string): EventSummaryField[] {
}
}

/**
This function is exported because it is used in the Exception Component to
populate the conditions with the Highlighted Fields. Additionally, the new
Alert Summary Flyout also requires access to these fields.
As the Alert Summary components will undergo changes soon we will go with
exporting the function only for now.
*/
/**
* Assembles a list of fields to display based on the event
*/
function getEventFieldsToDisplay({
export function getEventFieldsToDisplay({
eventCategories,
eventCode,
eventRuleType,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,11 +8,11 @@
import React from 'react';
import type { ReactWrapper } from 'enzyme';
import { mount, shallow } from 'enzyme';
import { waitFor } from '@testing-library/react';
import { waitFor, render } from '@testing-library/react';

import { getExceptionListSchemaMock } from '@kbn/lists-plugin/common/schemas/response/exception_list_schema.mock';
import { getExceptionBuilderComponentLazy } from '@kbn/lists-plugin/public';
import type { EntriesArray } from '@kbn/securitysolution-io-ts-list-types';
import type { EntriesArray, EntryMatch } from '@kbn/securitysolution-io-ts-list-types';
import { ExceptionListTypeEnum } from '@kbn/securitysolution-io-ts-list-types';
import { getExceptionListItemSchemaMock } from '@kbn/lists-plugin/common/schemas/response/exception_list_item_schema.mock';
import { createStubIndexPattern, stubIndexPattern } from '@kbn/data-plugin/common/stubs';
Expand Down Expand Up @@ -612,6 +612,58 @@ describe('When the add exception modal is opened', () => {
});
});

describe('Auto populate rule exception', () => {
beforeEach(() => {
mockGetExceptionBuilderComponentLazy.mockImplementation((props) => {
return (
<span data-test-subj="alertExceptionBuilder">
{props.exceptionListItems &&
props.exceptionListItems[0] &&
props.exceptionListItems[0].entries.map(
({ field, operator, type, value }: EntryMatch) => (
<>
<span data-test-subj="entryField">{field} </span>
<span data-test-subj="entryOperator">{operator} </span>
<span data-test-subj="entryType">{type} </span>
<span data-test-subj="entryValue">{value} </span>
</>
)
)}
</span>
);
});
});
it('should auto populate the exception from alert highlighted fields', () => {
const wrapper = render(
(() => (
<TestProviders>
<AddExceptionFlyout
rules={[
{
...getRulesSchemaMock(),
exceptions_list: [],
} as Rule,
]}
isBulkAction={false}
alertData={alertDataMock}
isAlertDataLoading={false}
alertStatus="open"
isEndpointItem={false}
showAlertCloseOptions
onCancel={jest.fn()}
onConfirm={jest.fn()}
/>
</TestProviders>
))()
);
const { getByTestId } = wrapper;
expect(getByTestId('alertExceptionBuilder')).toBeInTheDocument();
expect(getByTestId('entryField')).toHaveTextContent('file.path');
expect(getByTestId('entryOperator')).toHaveTextContent('included');
expect(getByTestId('entryType')).toHaveTextContent('match');
expect(getByTestId('entryValue')).toHaveTextContent('test/path');
});
});
describe('bulk closeable alert data is passed in', () => {
let wrapper: ReactWrapper;
beforeEach(async () => {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,7 @@ import {
defaultEndpointExceptionItems,
retrieveAlertOsTypes,
filterIndexPatterns,
getPrepopulatedRuleExceptionWithHighlightFields,
} from '../../utils/helpers';
import type { AlertData } from '../../utils/types';
import { initialState, createExceptionItemsReducer } from './reducer';
Expand Down Expand Up @@ -335,12 +336,26 @@ export const AddExceptionFlyout = memo(function AddExceptionFlyout({
);

useEffect((): void => {
if (listType === ExceptionListTypeEnum.ENDPOINT && alertData != null) {
setInitialExceptionItems(
defaultEndpointExceptionItems(ENDPOINT_LIST_ID, exceptionItemName, alertData)
);
if (alertData) {
switch (listType) {
case ExceptionListTypeEnum.ENDPOINT: {
return setInitialExceptionItems(
defaultEndpointExceptionItems(ENDPOINT_LIST_ID, exceptionItemName, alertData)
);
}
case ExceptionListTypeEnum.RULE_DEFAULT: {
const populatedException = getPrepopulatedRuleExceptionWithHighlightFields({
alertData,
exceptionItemName,
});
if (populatedException) {
setComment(i18n.ADD_RULE_EXCEPTION_FROM_ALERT_COMMENT(alertData._id));
return setInitialExceptionItems([populatedException]);
}
}
}
}
}, [listType, exceptionItemName, alertData, setInitialExceptionItems]);
}, [listType, exceptionItemName, alertData, setInitialExceptionItems, setComment]);

const osTypesSelection = useMemo((): OsTypeArray => {
return hasAlertData ? retrieveAlertOsTypes(alertData) : selectedOs ? [...selectedOs] : [];
Expand Down Expand Up @@ -521,9 +536,10 @@ export const AddExceptionFlyout = memo(function AddExceptionFlyout({
<ExceptionItemComments
accordionTitle={
<SectionHeader size="xs">
<h3>{i18n.COMMENTS_SECTION_TITLE(0)}</h3>
<h3>{i18n.COMMENTS_SECTION_TITLE(newComment ? 1 : 0)}</h3>
</SectionHeader>
}
initialIsOpen={!!newComment}
newCommentValue={newComment}
newCommentOnChange={setComment}
/>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -83,3 +83,12 @@ export const COMMENTS_SECTION_TITLE = (comments: number) =>
values: { comments },
defaultMessage: 'Add comments ({comments})',
});

export const ADD_RULE_EXCEPTION_FROM_ALERT_COMMENT = (alertId: string) =>
i18n.translate(
'xpack.securitySolution.ruleExceptions.addExceptionFlyout.addRuleExceptionFromAlertComment',
{
values: { alertId },
defaultMessage: 'Exception conditions are pre-filled with relevant data from {alertId}.',
}
);
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@ interface ExceptionItemCommentsProps {
exceptionItemComments?: Comment[];
newCommentValue: string;
accordionTitle?: JSX.Element;
initialIsOpen?: boolean;
newCommentOnChange: (value: string) => void;
}

Expand All @@ -50,6 +51,7 @@ export const ExceptionItemComments = memo(function ExceptionItemComments({
exceptionItemComments,
newCommentValue,
accordionTitle,
initialIsOpen = false,
newCommentOnChange,
}: ExceptionItemCommentsProps) {
const [shouldShowComments, setShouldShowComments] = useState(false);
Expand Down Expand Up @@ -107,6 +109,7 @@ export const ExceptionItemComments = memo(function ExceptionItemComments({
return (
<div>
<CommentAccordion
initialIsOpen={initialIsOpen}
id={'add-exception-comments-accordion'}
buttonClassName={COMMENT_ACCORDION_BUTTON_CLASS_NAME}
buttonContent={accordionTitle ?? commentsAccordionTitle}
Expand Down
Loading