[Security Solution][Exceptions]- Increase exceptions test coverage (#152757)

## Summary

- Addresses https://github.com/elastic/security-team/issues/5947
- Adding tests to cover the yellow rows in [test
sheet](https://docs.google.com/spreadsheets/d/1Eb_317s7nkQ4axVA270Ja99PRS-NWrYZAEc-1aVuyXg/edit#gid=0)
- Organise the tests to correspond to the following
[structure](https://docs.google.com/spreadsheets/d/14DdtghpxgfEmWoc7kot4XgEva_4GDEC_uTej65MUjV8/edit?pli=1#gid=0)
- Removed the
`x-pack/plugins/security_solution/cypress/e2e/exceptions/alerts_table_flow/add_exception.cy.ts`
as it was duplicated from
`x-pack/plugins/security_solution/cypress/e2e/exceptions/exceptions_management_flow/exceptions_table.cy.ts`
- Skipped the `flyout_validation` tests until resolving this
[ticket](https://github.com/elastic/kibana/issues/154994)
- Regarding `Exception-List`, `Exception-List-Item` and `Rule with
exceptions` migrations test cases are handled by most of our old `FTR`
tests as most of them deal with `Exception List Schema` which doesn't
include the new props, like the `expire_time` that was introduced in
`8.7`, so adding new tests using the new schema can be treated as
testing the new versions against the existing scenarios whereas the
existing tests for the migrations (downgrade) tests
[4a75a51](4a75a51a3e)
- Tests under `x-pack/plugins/security_solution/cypress/upgrade_e2e` are
just POCs can't be used

# New tests folder structure based on workflow

<img width="432" alt="image"
src="https://user-images.githubusercontent.com/12671903/234849016-f6f227d1-fcaf-43cb-abe3-d3fc7f9cee00.png">

---------

Co-authored-by: Kibana Machine <42973632+kibanamachine@users.noreply.github.com>
This commit is contained in:
Wafaa Nasr 2023-06-07 15:15:30 +01:00 committed by GitHub
parent 5e907edc39
commit 005108684b
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
41 changed files with 3355 additions and 579 deletions

View file

@ -176,7 +176,7 @@ Object {
>
<button
class="euiButton emotion-euiButtonDisplay-m-defaultMinWidth-m-fill-primary"
data-test-subj="RightSideMenuItemsManageRulesButton"
data-test-subj="RightSideMenuItemsLinkRulesButton"
type="button"
>
<span
@ -563,7 +563,7 @@ Object {
>
<button
class="euiButton emotion-euiButtonDisplay-m-defaultMinWidth-m-fill-primary"
data-test-subj="RightSideMenuItemsManageRulesButton"
data-test-subj="RightSideMenuItemsLinkRulesButton"
type="button"
>
<span
@ -846,7 +846,7 @@ Object {
>
<button
class="euiButton emotion-euiButtonDisplay-m-defaultMinWidth-m-fill-primary"
data-test-subj="RightSideMenuItemsManageRulesButton"
data-test-subj="RightSideMenuItemsLinkRulesButton"
type="button"
>
<span
@ -1072,7 +1072,7 @@ Object {
>
<button
class="euiButton emotion-euiButtonDisplay-m-defaultMinWidth-m-fill-primary"
data-test-subj="RightSideMenuItemsManageRulesButton"
data-test-subj="RightSideMenuItemsLinkRulesButton"
type="button"
>
<span
@ -1327,7 +1327,7 @@ Object {
>
<button
class="euiButton emotion-euiButtonDisplay-m-defaultMinWidth-m-fill-primary"
data-test-subj="RightSideMenuItemsManageRulesButton"
data-test-subj="RightSideMenuItemsLinkRulesButton"
type="button"
>
<span
@ -1526,7 +1526,7 @@ Object {
>
<button
class="euiButton emotion-euiButtonDisplay-m-defaultMinWidth-m-fill-primary"
data-test-subj="RightSideMenuItemsManageRulesButton"
data-test-subj="RightSideMenuItemsLinkRulesButton"
type="button"
>
<span

View file

@ -233,7 +233,7 @@ Object {
>
<button
class="euiButton emotion-euiButtonDisplay-m-defaultMinWidth-m-fill-primary"
data-test-subj="ManageRulesButton"
data-test-subj="LinkRulesButton"
type="button"
>
<span
@ -300,7 +300,7 @@ Object {
>
<button
class="euiButton emotion-euiButtonDisplay-m-defaultMinWidth-m-fill-primary"
data-test-subj="ManageRulesButton"
data-test-subj="LinkRulesButton"
type="button"
>
<span
@ -424,7 +424,7 @@ Object {
>
<button
class="euiButton emotion-euiButtonDisplay-m-defaultMinWidth-m-fill-primary"
data-test-subj="ManageRulesButton"
data-test-subj="LinkRulesButton"
type="button"
>
<span
@ -591,7 +591,7 @@ Object {
>
<button
class="euiButton emotion-euiButtonDisplay-m-defaultMinWidth-m-fill-primary"
data-test-subj="ManageRulesButton"
data-test-subj="LinkRulesButton"
type="button"
>
<span
@ -1054,7 +1054,7 @@ Object {
>
<button
class="euiButton emotion-euiButtonDisplay-m-defaultMinWidth-m-fill-primary"
data-test-subj="ManageRulesButton"
data-test-subj="LinkRulesButton"
type="button"
>
<span
@ -1149,7 +1149,7 @@ Object {
>
<button
class="euiButton emotion-euiButtonDisplay-m-defaultMinWidth-m-fill-primary"
data-test-subj="ManageRulesButton"
data-test-subj="LinkRulesButton"
type="button"
>
<span

View file

@ -77,7 +77,7 @@ const MenuItemsComponent: FC<MenuItemsProps> = ({
{canUserEditList && (
<EuiFlexItem>
<EuiButton
data-test-subj={`${dataTestSubj || ''}ManageRulesButton`}
data-test-subj={`${dataTestSubj || ''}LinkRulesButton`}
fill
onClick={() => {
if (typeof onManageRules === 'function') onManageRules();

View file

@ -31,7 +31,7 @@ describe('MenuItems', () => {
);
expect(wrapper).toMatchSnapshot();
expect(wrapper.getByTestId('LinkedRulesMenuItems')).toHaveTextContent('Linked to 1 rules');
expect(wrapper.getByTestId('ManageRulesButton')).toBeInTheDocument();
expect(wrapper.getByTestId('LinkRulesButton')).toBeInTheDocument();
expect(wrapper.getByTestId('MenuActionsButtonIcon')).toBeInTheDocument();
});
it('should not render linkedRules HeaderMenu component, instead should render a text', () => {
@ -101,7 +101,7 @@ describe('MenuItems', () => {
/>
);
expect(wrapper).toMatchSnapshot();
expect(wrapper.queryByTestId('ManageRulesButton')).not.toBeInTheDocument();
expect(wrapper.queryByTestId('LinkRulesButton')).not.toBeInTheDocument();
});
it('should call onManageRules', () => {
const wrapper = render(
@ -115,7 +115,7 @@ describe('MenuItems', () => {
onManageRules={onManageRules}
/>
);
fireEvent.click(wrapper.getByTestId('ManageRulesButton'));
fireEvent.click(wrapper.getByTestId('LinkRulesButton'));
expect(onManageRules).toHaveBeenCalled();
});
it('should call onExportModalOpen', () => {

View file

@ -117,3 +117,6 @@ export const _VERSION = 'WzI5NywxXQ==';
export const VERSION = 1;
export const IMMUTABLE = false;
export const IMPORT_TIMEOUT = moment.duration(5, 'minutes');
/** Added in 8.7 */
export const EXPIRE_TIME = '2023-04-24T19:00:00.000Z';

View file

@ -11,6 +11,7 @@ import {
COMMENTS,
DESCRIPTION,
ENTRIES,
EXPIRE_TIME,
ITEM_ID,
ITEM_TYPE,
LIST_ID,
@ -60,3 +61,18 @@ export const getCreateExceptionListItemMinimalSchemaMockWithoutId =
os_types: OS_TYPES,
type: ITEM_TYPE,
});
/**
* Useful for testing newer exception list item versions, as the previous
* versions can be used to test migration cases
*/
export const getCreateExceptionListItemNewerVersionSchemaMock =
(): CreateExceptionListItemSchema => ({
description: DESCRIPTION,
entries: ENTRIES,
expire_time: EXPIRE_TIME,
list_id: LIST_ID,
name: NAME,
os_types: OS_TYPES,
type: ITEM_TYPE,
});

View file

@ -12,7 +12,7 @@ import {
ImportExceptionsListSchema,
} from '@kbn/securitysolution-io-ts-list-types';
import { ENTRIES } from '../../constants.mock';
import { ENTRIES, EXPIRE_TIME } from '../../constants.mock';
export const getImportExceptionsListSchemaMock = (
listId = 'detection_list_id'
@ -23,6 +23,11 @@ export const getImportExceptionsListSchemaMock = (
type: 'detection',
});
/**
This mock retains the previous properties of the Exception List item, enabling us to
conduct migration test cases. As it lacks the new "expire_time" property, and considering
the absence of API versioning, we can utilize this mock to simulate the migration scenarios.
*/
export const getImportExceptionsListItemSchemaMock = (
itemId = 'item_id_1',
listId = 'detection_list_id'
@ -35,6 +40,23 @@ export const getImportExceptionsListItemSchemaMock = (
type: 'simple',
});
/**
Please ensure that this mock is updated with the new properties of the Exception List item,
for example the inclusion of the "expire_time" property. This will allow us to test and evaluate
the new scenarios effectively.
*/
export const getImportExceptionsListItemNewerVersionSchemaMock = (
itemId = 'item_id_1',
listId = 'detection_list_id'
): ImportExceptionListItemSchema => ({
description: 'some description',
entries: ENTRIES,
expire_time: EXPIRE_TIME,
item_id: itemId,
list_id: listId,
name: 'Query with a rule id',
type: 'simple',
});
export const getImportExceptionsListSchemaDecodedMock = (
listId = 'detection_list_id'
): ImportExceptionListSchemaDecoded => ({

View file

@ -1,203 +0,0 @@
/*
* 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 { ROLES } from '../../../../common/test';
import { getExceptionList, expectedExportedExceptionList } from '../../../objects/exception';
import { getNewRule } from '../../../objects/rule';
import { createRule } from '../../../tasks/api_calls/rules';
import { login, visitWithoutDateRange, waitForPageWithoutDateRange } from '../../../tasks/login';
import { EXCEPTIONS_URL } from '../../../urls/navigation';
import {
deleteExceptionListWithRuleReferenceByListId,
deleteExceptionListWithoutRuleReferenceByListId,
exportExceptionList,
searchForExceptionList,
waitForExceptionsTableToBeLoaded,
clearSearchSelection,
} from '../../../tasks/exceptions_table';
import {
EXCEPTIONS_TABLE_DELETE_BTN,
EXCEPTIONS_TABLE_LIST_NAME,
EXCEPTIONS_TABLE_SHOWING_LISTS,
} from '../../../screens/exceptions';
import { createExceptionList } from '../../../tasks/api_calls/exceptions';
import { esArchiverResetKibana } from '../../../tasks/es_archiver';
import { TOASTER } from '../../../screens/alerts_detection_rules';
const getExceptionList1 = () => ({
...getExceptionList(),
name: 'Test a new list 1',
list_id: 'exception_list_1',
});
const getExceptionList2 = () => ({
...getExceptionList(),
name: 'Test list 2',
list_id: 'exception_list_2',
});
describe('Exceptions Table', () => {
before(() => {
esArchiverResetKibana();
// Create exception list associated with a rule
createExceptionList(getExceptionList2(), getExceptionList2().list_id).then((response) =>
createRule(
getNewRule({
exceptions_list: [
{
id: response.body.id,
list_id: getExceptionList2().list_id,
type: getExceptionList2().type,
namespace_type: getExceptionList2().namespace_type,
},
],
})
)
);
// Create exception list not used by any rules
createExceptionList(getExceptionList1(), getExceptionList1().list_id).as(
'exceptionListResponse'
);
login();
visitWithoutDateRange(EXCEPTIONS_URL);
// Using cy.contains because we do not care about the exact text,
// just checking number of lists shown
cy.contains(EXCEPTIONS_TABLE_SHOWING_LISTS, '3');
});
beforeEach(() => {
login();
visitWithoutDateRange(EXCEPTIONS_URL);
});
it('Exports exception list', function () {
cy.intercept(/(\/api\/exception_lists\/_export)/).as('export');
visitWithoutDateRange(EXCEPTIONS_URL);
waitForExceptionsTableToBeLoaded();
exportExceptionList(getExceptionList1().list_id);
cy.wait('@export').then(({ response }) => {
cy.wrap(response?.body).should(
'eql',
expectedExportedExceptionList(this.exceptionListResponse)
);
cy.get(TOASTER).should(
'have.text',
`Exception list "${getExceptionList1().name}" exported successfully`
);
});
});
it('Filters exception lists on search', () => {
visitWithoutDateRange(EXCEPTIONS_URL);
waitForExceptionsTableToBeLoaded();
// Using cy.contains because we do not care about the exact text,
// just checking number of lists shown
cy.contains(EXCEPTIONS_TABLE_SHOWING_LISTS, '3');
// Single word search
searchForExceptionList('Endpoint');
// Using cy.contains because we do not care about the exact text,
// just checking number of lists shown
cy.contains(EXCEPTIONS_TABLE_SHOWING_LISTS, '1');
cy.get(EXCEPTIONS_TABLE_LIST_NAME).should('have.text', 'Endpoint Security Exception List');
// Multi word search
clearSearchSelection();
searchForExceptionList('test');
// Using cy.contains because we do not care about the exact text,
// just checking number of lists shown
cy.contains(EXCEPTIONS_TABLE_SHOWING_LISTS, '2');
cy.get(EXCEPTIONS_TABLE_LIST_NAME).eq(1).should('have.text', 'Test list 2');
cy.get(EXCEPTIONS_TABLE_LIST_NAME).eq(0).should('have.text', 'Test a new list 1');
// Exact phrase search
clearSearchSelection();
searchForExceptionList(`"${getExceptionList1().name}"`);
// Using cy.contains because we do not care about the exact text,
// just checking number of lists shown
cy.contains(EXCEPTIONS_TABLE_SHOWING_LISTS, '1');
cy.get(EXCEPTIONS_TABLE_LIST_NAME).should('have.text', getExceptionList1().name);
// Field search
clearSearchSelection();
searchForExceptionList('list_id:endpoint_list');
// Using cy.contains because we do not care about the exact text,
// just checking number of lists shown
cy.contains(EXCEPTIONS_TABLE_SHOWING_LISTS, '1');
cy.get(EXCEPTIONS_TABLE_LIST_NAME).should('have.text', 'Endpoint Security Exception List');
clearSearchSelection();
// Using cy.contains because we do not care about the exact text,
// just checking number of lists shown
cy.contains(EXCEPTIONS_TABLE_SHOWING_LISTS, '3');
});
it('Deletes exception list without rule reference', () => {
visitWithoutDateRange(EXCEPTIONS_URL);
waitForExceptionsTableToBeLoaded();
// Using cy.contains because we do not care about the exact text,
// just checking number of lists shown
cy.contains(EXCEPTIONS_TABLE_SHOWING_LISTS, '3');
deleteExceptionListWithoutRuleReferenceByListId(getExceptionList1().list_id);
// Using cy.contains because we do not care about the exact text,
// just checking number of lists shown
cy.contains(EXCEPTIONS_TABLE_SHOWING_LISTS, '2');
});
it('Deletes exception list with rule reference', () => {
waitForPageWithoutDateRange(EXCEPTIONS_URL);
waitForExceptionsTableToBeLoaded();
// Using cy.contains because we do not care about the exact text,
// just checking number of lists shown
cy.contains(EXCEPTIONS_TABLE_SHOWING_LISTS, '2');
deleteExceptionListWithRuleReferenceByListId(getExceptionList2().list_id);
// Using cy.contains because we do not care about the exact text,
// just checking number of lists shown
cy.contains(EXCEPTIONS_TABLE_SHOWING_LISTS, '1');
});
});
describe('Exceptions Table - read only', () => {
before(() => {
// First we login as a privileged user to create exception list
esArchiverResetKibana();
login(ROLES.platform_engineer);
visitWithoutDateRange(EXCEPTIONS_URL, ROLES.platform_engineer);
createExceptionList(getExceptionList(), getExceptionList().list_id);
// Then we login as read-only user to test.
login(ROLES.reader);
visitWithoutDateRange(EXCEPTIONS_URL, ROLES.reader);
waitForExceptionsTableToBeLoaded();
cy.get(EXCEPTIONS_TABLE_SHOWING_LISTS).should('have.text', `Showing 1 list`);
});
it('Delete icon is not shown', () => {
cy.get(EXCEPTIONS_TABLE_DELETE_BTN).should('not.exist');
});
});

View file

@ -0,0 +1,106 @@
/*
* 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 {
goToClosedAlertsOnRuleDetailsPage,
goToOpenedAlertsOnRuleDetailsPage,
openAddEndpointExceptionFromFirstAlert,
} from '../../../tasks/alerts';
import { login, visitWithoutDateRange } from '../../../tasks/login';
import { getEndpointRule } from '../../../objects/rule';
import { goToRuleDetails } from '../../../tasks/alerts_detection_rules';
import { createRule } from '../../../tasks/api_calls/rules';
import {
waitForAlertsToPopulate,
waitForTheRuleToBeExecuted,
} from '../../../tasks/create_new_rule';
import {
esArchiverLoad,
esArchiverResetKibana,
esArchiverUnload,
} from '../../../tasks/es_archiver';
import { DETECTIONS_RULE_MANAGEMENT_URL } from '../../../urls/navigation';
import {
addExceptionFlyoutItemName,
selectCloseSingleAlerts,
submitNewExceptionItem,
} 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 {
removeException,
goToAlertsTab,
goToEndpointExceptionsTab,
} from '../../../tasks/rule_details';
describe('Endpoint Exceptions workflows from Alert', () => {
const expectedNumberOfAlerts = 1;
beforeEach(() => {
esArchiverResetKibana();
esArchiverLoad('endpoint');
login();
createRule(getEndpointRule());
visitWithoutDateRange(DETECTIONS_RULE_MANAGEMENT_URL);
goToRuleDetails();
waitForTheRuleToBeExecuted();
waitForAlertsToPopulate();
});
after(() => {
esArchiverUnload('endpoint');
esArchiverUnload('endpoint_2');
});
it('Should be able to create and close single Endpoint exception from overflow menu', () => {
// The Endpoint will populated with predefined fields
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');
selectCloseSingleAlerts();
addExceptionFlyoutItemName('Sample Exception');
submitNewExceptionItem();
// Alerts table should now be empty from having added exception and closed
// matching alert
cy.get(EMPTY_ALERT_TABLE).should('exist');
// Closed alert should appear in table
goToClosedAlertsOnRuleDetailsPage();
cy.get(ALERTS_COUNT).should('exist');
cy.get(ALERTS_COUNT).should('have.text', `${expectedNumberOfAlerts} alert`);
// Endpoint Exception will move to Endpoint List under Exception tab of rule
goToEndpointExceptionsTab();
// Remove the exception and load an event that would have matched that exception
// to show that said exception now starts to show up again
removeException();
// when removing exception and again, no more exist, empty screen shows again
cy.get(NO_EXCEPTIONS_EXIST_PROMPT).should('exist');
// load more docs
esArchiverLoad('endpoint_2');
goToAlertsTab();
goToOpenedAlertsOnRuleDetailsPage();
waitForTheRuleToBeExecuted();
waitForAlertsToPopulate();
cy.get(ALERTS_COUNT).should('have.text', `${expectedNumberOfAlerts} alert`);
});
});

View file

@ -0,0 +1,123 @@
/*
* 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 { LOADING_INDICATOR } from '../../../screens/security_header';
import { getNewRule } 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';
import {
addExceptionFromFirstAlert,
goToClosedAlertsOnRuleDetailsPage,
goToOpenedAlertsOnRuleDetailsPage,
} from '../../../tasks/alerts';
import {
addExceptionEntryFieldValue,
addExceptionEntryFieldValueValue,
addExceptionEntryOperatorValue,
addExceptionFlyoutItemName,
selectBulkCloseAlerts,
submitNewExceptionItem,
validateExceptionItemFirstAffectedRuleNameInRulePage,
validateExceptionItemAffectsTheCorrectRulesInRulePage,
} from '../../../tasks/exceptions';
import {
esArchiverLoad,
esArchiverResetKibana,
esArchiverUnload,
} from '../../../tasks/es_archiver';
import { login, visitWithoutDateRange } from '../../../tasks/login';
import {
goToAlertsTab,
goToExceptionsTab,
removeException,
waitForTheRuleToBeExecuted,
} from '../../../tasks/rule_details';
import { DETECTIONS_RULE_MANAGEMENT_URL } from '../../../urls/navigation';
import { postDataView, deleteAlertsAndRules } from '../../../tasks/common';
import { NO_EXCEPTIONS_EXIST_PROMPT } from '../../../screens/exceptions';
import { waitForAlertsToPopulate } from '../../../tasks/create_new_rule';
describe('Rule Exceptions workflows from Alert', () => {
const EXPECTED_NUMBER_OF_ALERTS = '1 alert';
const ITEM_NAME = 'Sample Exception List Item';
const newRule = getNewRule();
before(() => {
esArchiverResetKibana();
esArchiverLoad('exceptions');
login();
postDataView('exceptions-*');
});
after(() => {
esArchiverUnload('exceptions');
});
beforeEach(() => {
deleteAlertsAndRules();
createRule({
...newRule,
query: 'agent.name:*',
data_view_id: 'exceptions-*',
interval: '10s',
rule_id: 'rule_testing',
});
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();
addExceptionEntryFieldValue('agent.name', 0);
addExceptionEntryOperatorValue('is', 0);
addExceptionEntryFieldValueValue('foo', 0);
addExceptionFlyoutItemName(ITEM_NAME);
selectBulkCloseAlerts();
submitNewExceptionItem();
// Alerts table should now be empty from having added exception and closed
// matching alert
cy.get(EMPTY_ALERT_TABLE).should('exist');
// Closed alert should appear in table
goToClosedAlertsOnRuleDetailsPage();
cy.get(ALERTS_COUNT).should('exist');
cy.get(ALERTS_COUNT).should('have.text', `${EXPECTED_NUMBER_OF_ALERTS}`);
// Remove the exception and load an event that would have matched that exception
// to show that said exception now starts to show up again
goToExceptionsTab();
// Validate the exception is affecting the correct rule count and name
validateExceptionItemAffectsTheCorrectRulesInRulePage(1);
validateExceptionItemFirstAffectedRuleNameInRulePage(newRule.name);
// when removing exception and again, no more exist, empty screen shows again
removeException();
cy.get(NO_EXCEPTIONS_EXIST_PROMPT).should('exist');
// load more docs
esArchiverLoad('exceptions_2');
// now that there are no more exceptions, the docs should match and populate alerts
goToAlertsTab();
goToOpenedAlertsOnRuleDetailsPage();
waitForTheRuleToBeExecuted();
waitForAlertsToPopulate();
cy.get(ALERTS_COUNT).should('have.text', '2 alerts');
});
});

View file

@ -0,0 +1,238 @@
/*
* 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 type { ExceptionListTypeEnum, NamespaceType } from '@kbn/securitysolution-io-ts-list-types';
import { getException, getExceptionList } from '../../../objects/exception';
import { getNewRule } from '../../../objects/rule';
import { createRule } from '../../../tasks/api_calls/rules';
import { goToRuleDetails } from '../../../tasks/alerts_detection_rules';
import { esArchiverResetKibana } from '../../../tasks/es_archiver';
import { login, visitWithoutDateRange } from '../../../tasks/login';
import {
addExceptionFlyoutFromViewerHeader,
goToEndpointExceptionsTab,
goToExceptionsTab,
openEditException,
openExceptionFlyoutFromEmptyViewerPrompt,
} from '../../../tasks/rule_details';
import {
addExceptionComment,
addExceptionConditions,
addExceptionFlyoutItemName,
clickCopyCommentToClipboard,
submitEditedExceptionItem,
submitNewExceptionItem,
clickOnShowComments,
selectOs,
} from '../../../tasks/exceptions';
import { DETECTIONS_RULE_MANAGEMENT_URL } from '../../../urls/navigation';
import {
EXCEPTION_ITEM_VIEWER_CONTAINER,
EXCEPTION_ITEM_COMMENTS_CONTAINER_TEXT,
LOADING_SPINNER,
} from '../../../screens/exceptions';
import {
createEndpointExceptionList,
createExceptionList,
createExceptionListItem,
} from '../../../tasks/api_calls/exceptions';
import { ROLES } from '../../../../common/test';
interface ResponseType {
body: {
id: string;
list_id: string;
type: ExceptionListTypeEnum;
namespace_type: NamespaceType;
};
}
describe('Add, copy comments in different exceptions type and validate sharing them between users', () => {
describe('Rule exceptions', () => {
beforeEach(() => {
esArchiverResetKibana();
login();
const exceptionList = getExceptionList();
// create rule with exceptions
createExceptionList(exceptionList, exceptionList.list_id).then((response) => {
createRule({
...getNewRule(),
query: '*',
index: ['*'],
exceptions_list: [
{
id: response.body.id,
list_id: exceptionList.list_id,
type: exceptionList.type,
namespace_type: exceptionList.namespace_type,
},
],
rule_id: '2',
});
createExceptionListItem(exceptionList.list_id, {
list_id: exceptionList.list_id,
item_id: 'simple_list_item',
tags: [],
type: 'simple',
description: 'Test exception item 2',
name: 'Sample Exception List Item 2',
namespace_type: 'single',
entries: [
{
field: 'unique_value.test',
operator: 'included',
type: 'match_any',
value: ['foo'],
},
],
});
});
visitWithoutDateRange(DETECTIONS_RULE_MANAGEMENT_URL);
goToRuleDetails();
goToExceptionsTab();
});
it('Add comment on a new exception, add another comment has unicode from a different user and copy to clipboard', () => {
// User 1
// open add exception modal
addExceptionFlyoutFromViewerHeader();
cy.get(LOADING_SPINNER).should('not.exist');
// add exception item conditions
addExceptionConditions(getException());
// add exception item name
addExceptionFlyoutItemName('My item name');
// add exception comment
addExceptionComment('User 1 comment');
// submit
submitNewExceptionItem();
// new exception item displays
cy.get(EXCEPTION_ITEM_VIEWER_CONTAINER).should('have.length', 2);
// click on show comments
clickOnShowComments();
// copy the first comment to clipboard
clickCopyCommentToClipboard();
cy.get(EXCEPTION_ITEM_COMMENTS_CONTAINER_TEXT).eq(0).should('have.text', 'User 1 comment');
// User 2
// Login with different users to validate accessing comments of different users
login(ROLES.soc_manager);
// Navigate to Rule page
visitWithoutDateRange(DETECTIONS_RULE_MANAGEMENT_URL);
goToRuleDetails();
goToExceptionsTab();
// open edit exception modal
openEditException();
// add exception comment
addExceptionComment('User 2 comment @ using unicode');
// submit
submitEditedExceptionItem();
cy.get(EXCEPTION_ITEM_COMMENTS_CONTAINER_TEXT).eq(0).should('have.text', 'User 1 comment');
cy.get(EXCEPTION_ITEM_COMMENTS_CONTAINER_TEXT)
.eq(1)
.should('have.text', 'User 2 comment @ using unicode');
});
});
describe('Endpoint exceptions', () => {
beforeEach(() => {
esArchiverResetKibana();
login();
// create rule with exception
createEndpointExceptionList().then((response) => {
createRule({
...getNewRule(),
query: '*',
index: ['*'],
exceptions_list: [
{
id: (response as ResponseType).body.id,
list_id: (response as ResponseType).body.list_id,
type: (response as ResponseType).body.type,
namespace_type: (response as ResponseType).body.namespace_type,
},
],
rule_id: '2',
});
});
visitWithoutDateRange(DETECTIONS_RULE_MANAGEMENT_URL);
goToRuleDetails();
goToEndpointExceptionsTab();
});
it('Add comment on a new exception, and add another comment has unicode character from a different user', () => {
// User 1
// The Endpoint will populated with predefined fields
// open add exception modal
openExceptionFlyoutFromEmptyViewerPrompt();
// for endpoint exceptions, must specify OS
selectOs('windows');
// add exception item conditions
addExceptionConditions({
field: 'event.code',
operator: 'is',
values: ['foo'],
});
// add exception comment
addExceptionComment('User 1 comment');
// add exception item name
addExceptionFlyoutItemName('Endpoint exception');
// submit
submitNewExceptionItem();
// Endpoint Exception will move to Endpoint List under Exception tab of rule
goToEndpointExceptionsTab();
// click on show comments
clickOnShowComments();
cy.get(EXCEPTION_ITEM_COMMENTS_CONTAINER_TEXT).eq(0).should('have.text', 'User 1 comment');
// User 2
// Login with different users to validate accessing comments of different users
login(ROLES.soc_manager);
// Navigate to Rule page
visitWithoutDateRange(DETECTIONS_RULE_MANAGEMENT_URL);
goToRuleDetails();
// Endpoint Exception will move to Endpoint List under Exception tab of rule
goToEndpointExceptionsTab();
// open edit exception modal
openEditException();
// add exception comment
addExceptionComment('User 2 comment @ using unicode');
// submit
submitEditedExceptionItem();
cy.get(EXCEPTION_ITEM_COMMENTS_CONTAINER_TEXT).eq(0).should('have.text', 'User 1 comment');
cy.get(EXCEPTION_ITEM_COMMENTS_CONTAINER_TEXT)
.eq(1)
.should('have.text', 'User 2 comment @ using unicode');
});
});
});

View file

@ -62,20 +62,25 @@ import {
} from '../../../tasks/api_calls/exceptions';
import { getExceptionList } from '../../../objects/exception';
// Test Skipped until we fix the Flyout rerendering issue
// https://github.com/elastic/kibana/issues/154994
// NOTE: You might look at these tests and feel they're overkill,
// but the exceptions flyout has a lot of logic making it difficult
// to test in enzyme and very small changes can inadvertently add
// bugs. As the complexity within the builder grows, these should
// ensure the most basic logic holds.
describe('Exceptions flyout', () => {
describe.skip('Exceptions flyout', { testIsolation: false }, () => {
before(() => {
esArchiverResetKibana();
// this is a made-up index that has just the necessary
// mappings to conduct tests, avoiding loading large
// amounts of data like in auditbeat_exceptions
esArchiverLoad('exceptions');
esArchiverLoad('conflicts_1');
esArchiverLoad('conflicts_2');
// Comment the Conflicts here as they are skipped
// esArchiverLoad('conflicts_1');
// esArchiverLoad('conflicts_2');
login();
createExceptionList(getExceptionList(), getExceptionList().list_id).then((response) =>
createRule(
getNewRule({

View file

@ -0,0 +1,100 @@
/*
* 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 { getNewRule } from '../../../objects/rule';
import { createRule } from '../../../tasks/api_calls/rules';
import { goToRuleDetails } from '../../../tasks/alerts_detection_rules';
import { esArchiverResetKibana, esArchiverUnload } from '../../../tasks/es_archiver';
import { login, visitWithoutDateRange } from '../../../tasks/login';
import {
openExceptionFlyoutFromEmptyViewerPrompt,
goToExceptionsTab,
} from '../../../tasks/rule_details';
import {
addExceptionFlyoutItemName,
addTwoAndedConditions,
addTwoORedConditions,
submitNewExceptionItem,
} from '../../../tasks/exceptions';
import {
EXCEPTION_CARD_ITEM_NAME,
EXCEPTION_CARD_ITEM_CONDITIONS,
EXCEPTION_ITEM_VIEWER_CONTAINER,
} from '../../../screens/exceptions';
import { DETECTIONS_RULE_MANAGEMENT_URL } from '../../../urls/navigation';
describe(
'Add multiple conditions and validate the generated exceptions',
{ testIsolation: false },
() => {
beforeEach(() => {
esArchiverResetKibana();
login();
// At least create Rule with exceptions_list to be able to view created exceptions
createRule({
...getNewRule(),
query: 'agent.name:*',
index: ['exceptions*'],
exceptions_list: [],
rule_id: '2',
});
visitWithoutDateRange(DETECTIONS_RULE_MANAGEMENT_URL);
goToRuleDetails();
goToExceptionsTab();
});
after(() => {
esArchiverUnload('exceptions');
});
const exceptionName = 'My item name';
it('Use multipe AND conditions and validate it generates one exception', () => {
// open add exception modal
openExceptionFlyoutFromEmptyViewerPrompt();
// add exception item name
addExceptionFlyoutItemName(exceptionName);
// add Two ANDed condition
addTwoAndedConditions('agent.name', 'foo', '@timestamp', '123');
submitNewExceptionItem();
// Only one Exception should generated
cy.get(EXCEPTION_ITEM_VIEWER_CONTAINER).should('have.length', 1);
// validate the And operator is displayed correctly
cy.get(EXCEPTION_CARD_ITEM_NAME).should('have.text', exceptionName);
cy.get(EXCEPTION_CARD_ITEM_CONDITIONS).should(
'have.text',
' agent.nameIS fooAND @timestampIS 123'
);
});
it('Use multipe OR conditions and validate it generates multiple exceptions', () => {
// open add exception modal
openExceptionFlyoutFromEmptyViewerPrompt();
// add exception item name
addExceptionFlyoutItemName(exceptionName);
// exception item 1
// add Two ORed condition
addTwoORedConditions('agent.name', 'foo', '@timestamp', '123');
submitNewExceptionItem();
// Two Exceptions should be generated
cy.get(EXCEPTION_ITEM_VIEWER_CONTAINER).should('have.length', 2);
// validate the details of the first exception
cy.get(EXCEPTION_CARD_ITEM_NAME).eq(0).should('have.text', exceptionName);
});
}
);

View file

@ -0,0 +1,132 @@
/*
* 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 {
addExceptionEntryFieldMatchIncludedValue,
addExceptionEntryFieldValue,
addExceptionEntryOperatorValue,
addExceptionFlyoutItemName,
submitNewExceptionItem,
} from '../../../tasks/exceptions';
import { goToRuleDetails } from '../../../tasks/alerts_detection_rules';
import {
goToExceptionsTab,
openExceptionFlyoutFromEmptyViewerPrompt,
} from '../../../tasks/rule_details';
import { VALUE_LISTS_TABLE, VALUE_LISTS_ROW } from '../../../screens/lists';
import { getNewRule } from '../../../objects/rule';
import { cleanKibana } from '../../../tasks/common';
import { login, visitWithoutDateRange } from '../../../tasks/login';
import { DETECTIONS_RULE_MANAGEMENT_URL } from '../../../urls/navigation';
import {
createListsIndex,
waitForListsIndex,
waitForValueListsModalToBeLoaded,
selectValueListType,
selectValueListsFile,
uploadValueList,
openValueListsModal,
deleteValueListsFile,
closeValueListsModal,
} from '../../../tasks/lists';
import { createRule } from '../../../tasks/api_calls/rules';
import { esArchiverLoad, esArchiverUnload } from '../../../tasks/es_archiver';
import {
CLOSE_ALERTS_CHECKBOX,
EXCEPTIONS_TABLE_MODAL,
EXCEPTION_CARD_ITEM_CONDITIONS,
EXCEPTION_CARD_ITEM_NAME,
EXCEPTION_ITEM_VIEWER_CONTAINER,
NO_EXCEPTIONS_EXIST_PROMPT,
} from '../../../screens/exceptions';
const goToRulesAndOpenValueListModal = () => {
visitWithoutDateRange(DETECTIONS_RULE_MANAGEMENT_URL);
waitForListsIndex();
waitForValueListsModalToBeLoaded();
openValueListsModal();
};
describe('Use Value list in exception entry', () => {
before(() => {
cleanKibana();
login();
esArchiverLoad('exceptions');
createRule({
...getNewRule(),
query: 'user.name:*',
index: ['exceptions*'],
exceptions_list: [],
rule_id: '2',
});
visitWithoutDateRange(DETECTIONS_RULE_MANAGEMENT_URL);
});
beforeEach(() => {
createListsIndex();
});
afterEach(() => {
esArchiverUnload('exceptions');
});
it('Should use value list in exception entry, and validate deleting value list prompt', () => {
const ITEM_NAME = 'Exception item with value list';
const ITEM_FIELD = 'agent.name';
goToRulesAndOpenValueListModal();
// Add new value list of type keyword
const listName = 'value_list.txt';
selectValueListType('keyword');
selectValueListsFile(listName);
uploadValueList();
cy.get(VALUE_LISTS_TABLE)
.find(VALUE_LISTS_ROW)
.should(($row) => {
expect($row.text()).to.contain(listName);
expect($row.text()).to.contain('Keywords');
});
closeValueListsModal();
goToRuleDetails();
goToExceptionsTab();
// open add exception modal
openExceptionFlyoutFromEmptyViewerPrompt();
// add exception item name
addExceptionFlyoutItemName(ITEM_NAME);
addExceptionEntryFieldValue(ITEM_FIELD, 0);
addExceptionEntryOperatorValue('is in list', 0);
addExceptionEntryFieldMatchIncludedValue('value_list.txt', 0);
// The Close all alerts that match attributes in this exception option is disabled
cy.get(CLOSE_ALERTS_CHECKBOX).should('exist');
cy.get(CLOSE_ALERTS_CHECKBOX).should('have.attr', 'disabled');
// Create exception
submitNewExceptionItem();
// displays existing exception items
cy.get(EXCEPTION_ITEM_VIEWER_CONTAINER).should('have.length', 1);
cy.get(NO_EXCEPTIONS_EXIST_PROMPT).should('not.exist');
cy.get(EXCEPTION_CARD_ITEM_NAME).should('have.text', ITEM_NAME);
cy.get(EXCEPTION_CARD_ITEM_CONDITIONS).should(
'have.text',
` ${ITEM_FIELD}included in value_list.txt`
);
// Go back to value list to delete the existing one
goToRulesAndOpenValueListModal();
deleteValueListsFile(listName);
// Toast should be shown because of exception reference
cy.get(EXCEPTIONS_TABLE_MODAL).should('exist');
});
});

View file

@ -107,6 +107,9 @@ describe('Add endpoint exception from rule details', () => {
// open add exception modal
openExceptionFlyoutFromEmptyViewerPrompt();
// submit button is disabled if no paramerters were added
cy.get(CONFIRM_BTN).should('have.attr', 'disabled');
// for endpoint exceptions, must specify OS
selectOs('windows');

View file

@ -5,26 +5,18 @@
* 2.0.
*/
import { LOADING_INDICATOR } from '../../../screens/security_header';
import { getNewRule } 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';
import {
addExceptionFromFirstAlert,
goToClosedAlertsOnRuleDetailsPage,
goToOpenedAlertsOnRuleDetailsPage,
} from '../../../tasks/alerts';
import {
addExceptionEntryFieldValue,
addExceptionEntryFieldValueValue,
addExceptionEntryOperatorValue,
addExceptionFlyoutItemName,
editException,
editExceptionFlyoutItemName,
selectBulkCloseAlerts,
submitEditedExceptionItem,
submitNewExceptionItem,
} from '../../../tasks/exceptions';
import {
esArchiverLoad,
@ -89,49 +81,7 @@ describe('Add exception using data views from rule details', () => {
esArchiverUnload('exceptions_2');
});
it('Creates an exception item from alert actions overflow menu', () => {
cy.get(LOADING_INDICATOR).should('not.exist');
addExceptionFromFirstAlert();
addExceptionEntryFieldValue('agent.name', 0);
addExceptionEntryOperatorValue('is', 0);
addExceptionEntryFieldValueValue('foo', 0);
addExceptionFlyoutItemName(ITEM_NAME);
selectBulkCloseAlerts();
submitNewExceptionItem();
// Alerts table should now be empty from having added exception and closed
// matching alert
cy.get(EMPTY_ALERT_TABLE).should('exist');
// Closed alert should appear in table
goToClosedAlertsOnRuleDetailsPage();
cy.get(ALERTS_COUNT).should('exist');
cy.get(ALERTS_COUNT).should('have.text', `${NUMBER_OF_AUDITBEAT_EXCEPTIONS_ALERTS}`);
// Remove the exception and load an event that would have matched that exception
// to show that said exception now starts to show up again
goToExceptionsTab();
// when removing exception and again, no more exist, empty screen shows again
removeException();
cy.get(NO_EXCEPTIONS_EXIST_PROMPT).should('exist');
// load more docs
esArchiverLoad('exceptions_2');
// now that there are no more exceptions, the docs should match and populate alerts
goToAlertsTab();
goToOpenedAlertsOnRuleDetailsPage();
waitForTheRuleToBeExecuted();
waitForAlertsToPopulate();
cy.get(ALERTS_COUNT).should('exist');
cy.get(ALERTS_COUNT).should('have.text', '2 alerts');
});
it('Creates an exception item', () => {
it('Creates an exception item and close all matching alerts', () => {
goToExceptionsTab();
// when no exceptions exist, empty component shows with action to add exception
cy.get(NO_EXCEPTIONS_EXIST_PROMPT).should('exist');

View file

@ -1,65 +0,0 @@
/*
* 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 {
esArchiverLoad,
esArchiverUnload,
esArchiverResetKibana,
} from '../../../tasks/es_archiver';
import { getNewRule } from '../../../objects/rule';
import { login, visitWithoutDateRange } from '../../../tasks/login';
import { createRule, deleteCustomRule } from '../../../tasks/api_calls/rules';
import { editException, editExceptionFlyoutItemName } from '../../../tasks/exceptions';
import { EXCEPTIONS_URL } from '../../../urls/navigation';
import {
CONFIRM_BTN,
MANAGE_EXCEPTION_CREATE_BUTTON_MENU,
MANAGE_EXCEPTION_CREATE_BUTTON_EXCEPTION,
RULE_ACTION_LINK_RULE_SWITCH,
} from '../../../screens/exceptions';
describe('Add/edit exception from exception management page', () => {
before(() => {
esArchiverResetKibana();
esArchiverLoad('exceptions');
createRule(getNewRule());
});
beforeEach(() => {
login();
visitWithoutDateRange(EXCEPTIONS_URL);
});
after(() => {
esArchiverUnload('exceptions');
});
afterEach(() => {
deleteCustomRule();
});
describe('create exception item', () => {
it('create exception item', () => {
const FIELD_DIFFERENT_FROM_EXISTING_ITEM_FIELD = 'agent.name';
cy.get(MANAGE_EXCEPTION_CREATE_BUTTON_MENU).click();
cy.get(MANAGE_EXCEPTION_CREATE_BUTTON_EXCEPTION).click();
// edit exception item name
editExceptionFlyoutItemName('Name');
editException(FIELD_DIFFERENT_FROM_EXISTING_ITEM_FIELD, 0, 0);
// should select some rules
cy.get(CONFIRM_BTN).should('have.attr', 'disabled');
// select rule
cy.get(RULE_ACTION_LINK_RULE_SWITCH).find('button').click();
// should be available to submit
cy.get(CONFIRM_BTN).should('not.have.attr', 'disabled');
});
});
});

View file

@ -5,22 +5,27 @@
* 2.0.
*/
import { getExceptionList } from '../../../objects/exception';
import { getNewRule } from '../../../objects/rule';
import { esArchiverResetKibana } from '../../../../tasks/es_archiver';
import { getExceptionList } from '../../../../objects/exception';
import { getNewRule } from '../../../../objects/rule';
import { login, visitWithoutDateRange } from '../../../tasks/login';
import { createRule } from '../../../tasks/api_calls/rules';
import { exceptionsListDetailsUrl } from '../../../urls/navigation';
import { login, visitWithoutDateRange } from '../../../../tasks/login';
import { createRule } from '../../../../tasks/api_calls/rules';
import { EXCEPTIONS_URL, exceptionsListDetailsUrl } from '../../../../urls/navigation';
import {
createSharedExceptionList,
editExceptionLisDetails,
linkSharedListToRulesFromListDetails,
saveLinkedRules,
validateSharedListLinkedRules,
waitForExceptionListDetailToBeLoaded,
} from '../../../tasks/exceptions_table';
import { createExceptionList } from '../../../tasks/api_calls/exceptions';
import { esArchiverResetKibana } from '../../../tasks/es_archiver';
} from '../../../../tasks/exceptions_table';
import { createExceptionList } from '../../../../tasks/api_calls/exceptions';
import {
EXCEPTIONS_LIST_MANAGEMENT_NAME,
EXCEPTIONS_LIST_MANAGEMENT_DESCRIPTION,
} from '../../../screens/exceptions';
EXCEPTION_LIST_DETAILS_LINK_RULES_BTN,
} from '../../../../screens/exceptions';
const LIST_NAME = 'My exception list';
const UPDATED_LIST_NAME = 'Updated exception list';
@ -34,7 +39,9 @@ const getExceptionList1 = () => ({
list_id: 'exception_list_test',
});
describe('Exception list management page', () => {
const EXCEPTION_LIST_NAME = 'Newly created list';
describe('Exception list detail page', () => {
before(() => {
esArchiverResetKibana();
login();
@ -54,15 +61,17 @@ describe('Exception list management page', () => {
})
)
);
createRule(getNewRule({ name: 'Rule to link to shared list' }));
});
beforeEach(() => {
login();
visitWithoutDateRange(exceptionsListDetailsUrl(getExceptionList1().list_id));
waitForExceptionListDetailToBeLoaded();
visitWithoutDateRange(EXCEPTIONS_URL);
});
it('Edits list details', () => {
it('Should edit list details', () => {
visitWithoutDateRange(exceptionsListDetailsUrl(getExceptionList1().list_id));
waitForExceptionListDetailToBeLoaded();
// Check list details are loaded
cy.get(EXCEPTIONS_LIST_MANAGEMENT_NAME).should('have.text', LIST_NAME);
cy.get(EXCEPTIONS_LIST_MANAGEMENT_DESCRIPTION).should('have.text', LIST_DESCRIPTION);
@ -92,4 +101,28 @@ describe('Exception list management page', () => {
visitWithoutDateRange(exceptionsListDetailsUrl(getExceptionList1().list_id));
cy.get(EXCEPTIONS_LIST_MANAGEMENT_DESCRIPTION).should('have.text', 'Add a description');
});
it('Should create a new list and link it to two rules', () => {
createSharedExceptionList(
{ name: 'Newly created list', description: 'This is my list.' },
true
);
// After creation - directed to list detail page
cy.get(EXCEPTIONS_LIST_MANAGEMENT_NAME).should('have.text', EXCEPTION_LIST_NAME);
// Open Link rules flyout
cy.get(EXCEPTION_LIST_DETAILS_LINK_RULES_BTN).click();
// Link the first two Rules
linkSharedListToRulesFromListDetails(2);
// Save the 2 linked Rules
saveLinkedRules();
const linkedRulesNames = ['Rule to link to shared list', 'New Rule Test'];
// Validate the number of linked rules as well as the Rules' names
validateSharedListLinkedRules(2, linkedRulesNames);
});
});

View file

@ -0,0 +1,152 @@
/*
* 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 {
esArchiverLoad,
esArchiverUnload,
esArchiverResetKibana,
} from '../../../tasks/es_archiver';
import { getNewRule } from '../../../objects/rule';
import { login, visitWithoutDateRange } from '../../../tasks/login';
import { createRule } from '../../../tasks/api_calls/rules';
import {
addExceptionFlyoutItemName,
editException,
editExceptionFlyoutItemName,
linkFirstRuleOnExceptionFlyout,
linkFirstSharedListOnExceptionFlyout,
editFirstExceptionItemInListDetailPage,
submitEditedExceptionItem,
submitNewExceptionItem,
deleteFirstExceptionItemInListDetailPage,
} from '../../../tasks/exceptions';
import { DETECTIONS_RULE_MANAGEMENT_URL, EXCEPTIONS_URL } from '../../../urls/navigation';
import {
CONFIRM_BTN,
EXCEPTION_ITEM_VIEWER_CONTAINER,
EXCEPTION_CARD_ITEM_NAME,
EXCEPTIONS_LIST_MANAGEMENT_NAME,
EXECPTION_ITEM_CARD_HEADER_TITLE,
EMPTY_EXCEPTIONS_VIEWER,
} from '../../../screens/exceptions';
import { goToRuleDetails } from '../../../tasks/alerts_detection_rules';
import { goToExceptionsTab } from '../../../tasks/rule_details';
import {
addExceptionListFromSharedExceptionListHeaderMenu,
createSharedExceptionList,
findSharedExceptionListItemsByName,
waitForExceptionsTableToBeLoaded,
} from '../../../tasks/exceptions_table';
describe('Add, edit and delete exception', () => {
before(() => {
esArchiverResetKibana();
esArchiverLoad('exceptions');
createRule(getNewRule());
});
beforeEach(() => {
login();
visitWithoutDateRange(EXCEPTIONS_URL);
waitForExceptionsTableToBeLoaded();
});
after(() => {
esArchiverUnload('exceptions');
});
const exceptionName = 'My item name';
const exceptionNameEdited = 'My item name edited';
const FIELD_DIFFERENT_FROM_EXISTING_ITEM_FIELD = 'agent.name';
const EXCEPTION_LIST_NAME = 'Newly created list';
describe('Add, Edit and delete Exception item', () => {
it('should create exception item from Shared Exception List page and linked to a Rule', () => {
// Click on "Create shared exception list" button on the header
// Click on "Create exception item"
addExceptionListFromSharedExceptionListHeaderMenu();
// Add exception item name
addExceptionFlyoutItemName(exceptionName);
// Add Condition
editException(FIELD_DIFFERENT_FROM_EXISTING_ITEM_FIELD, 0, 0);
// Confirm button should disabled until a rule(s) is selected
cy.get(CONFIRM_BTN).should('have.attr', 'disabled');
// select rule
linkFirstRuleOnExceptionFlyout();
// should be able to submit
cy.get(CONFIRM_BTN).should('not.have.attr', 'disabled');
submitNewExceptionItem();
// Navigate to Rule page
visitWithoutDateRange(DETECTIONS_RULE_MANAGEMENT_URL);
goToRuleDetails();
goToExceptionsTab();
// Only one Exception should generated
cy.get(EXCEPTION_ITEM_VIEWER_CONTAINER).should('have.length', 1);
// validate the And operator is displayed correctly
cy.get(EXCEPTION_CARD_ITEM_NAME).should('have.text', exceptionName);
});
it('should create exception item from Shared Exception List page, linked to a Shared List and validate Edit/delete in list detail page', function () {
createSharedExceptionList(
{ name: 'Newly created list', description: 'This is my list.' },
true
);
// After creation - directed to list detail page
cy.get(EXCEPTIONS_LIST_MANAGEMENT_NAME).should('have.text', EXCEPTION_LIST_NAME);
// Go back to Shared Exception List
visitWithoutDateRange(EXCEPTIONS_URL);
// Click on "Create shared exception list" button on the header
// Click on "Create exception item"
addExceptionListFromSharedExceptionListHeaderMenu();
// Add exception item name
addExceptionFlyoutItemName(exceptionName);
// Add Condition
editException(FIELD_DIFFERENT_FROM_EXISTING_ITEM_FIELD, 0, 0);
// select shared list radio option and select the first one
linkFirstSharedListOnExceptionFlyout();
submitNewExceptionItem();
// New exception is added to the new List
findSharedExceptionListItemsByName(`${EXCEPTION_LIST_NAME}`, [exceptionName]);
// Click on the first exception overflow menu items
// Open the edit modal
editFirstExceptionItemInListDetailPage();
// edit exception item name
editExceptionFlyoutItemName(exceptionNameEdited);
// submit
submitEditedExceptionItem();
// check the new name after edit
cy.get(EXECPTION_ITEM_CARD_HEADER_TITLE).should('have.text', exceptionNameEdited);
// Click on the first exception overflow menu items
// delete the exception
deleteFirstExceptionItemInListDetailPage();
cy.get(EMPTY_EXCEPTIONS_VIEWER).should('exist');
});
});
});

View file

@ -0,0 +1,140 @@
/*
* 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 { createRule } from '../../../../tasks/api_calls/rules';
import { getExceptionList } from '../../../../objects/exception';
import { assertNumberOfExceptionItemsExists } from '../../../../tasks/exceptions';
import {
assertExceptionListsExists,
duplicateSharedExceptionListFromListsManagementPageByListId,
findSharedExceptionListItemsByName,
waitForExceptionsTableToBeLoaded,
} from '../../../../tasks/exceptions_table';
import { login, visitWithoutDateRange } from '../../../../tasks/login';
import { EXCEPTIONS_URL } from '../../../../urls/navigation';
import {
createExceptionList,
createExceptionListItem,
} from '../../../../tasks/api_calls/exceptions';
import { esArchiverResetKibana } from '../../../../tasks/es_archiver';
import { getNewRule } from '../../../../objects/rule';
const expiredDate = new Date(Date.now() - 1000000).toISOString();
const futureDate = new Date(Date.now() + 1000000).toISOString();
const EXCEPTION_LIST_NAME = 'My test list';
const EXCEPTION_LIST_TO_DUPLICATE_NAME = 'A test list 2';
const EXCEPTION_LIST_ITEM_NAME = 'Sample Exception List Item 1';
const EXCEPTION_LIST_ITEM_NAME_2 = 'Sample Exception List Item 2';
const getExceptionList1 = () => ({
...getExceptionList(),
name: EXCEPTION_LIST_NAME,
list_id: 'exception_list_1',
});
const getExceptionList2 = () => ({
...getExceptionList(),
name: EXCEPTION_LIST_TO_DUPLICATE_NAME,
list_id: 'exception_list_2',
});
describe('Duplicate List', () => {
beforeEach(() => {
esArchiverResetKibana();
login();
createRule(getNewRule({ name: 'Another rule' }));
// Create exception list associated with a rule
createExceptionList(getExceptionList2(), getExceptionList2().list_id).then((response) =>
createRule(
getNewRule({
exceptions_list: [
{
id: response.body.id,
list_id: getExceptionList2().list_id,
type: getExceptionList2().type,
namespace_type: getExceptionList2().namespace_type,
},
],
})
)
);
// Create exception list not used by any rules
createExceptionList(getExceptionList1(), getExceptionList1().list_id).as(
'exceptionListResponse'
);
// Create exception list associated with a rule
createExceptionList(getExceptionList2(), getExceptionList2().list_id);
createExceptionListItem(getExceptionList2().list_id, {
list_id: getExceptionList2().list_id,
item_id: 'simple_list_item_1',
tags: [],
type: 'simple',
description: 'Test exception item',
name: EXCEPTION_LIST_ITEM_NAME,
namespace_type: 'single',
entries: [
{
field: 'host.name',
operator: 'included',
type: 'match_any',
value: ['some host', 'another host'],
},
],
expire_time: expiredDate,
});
createExceptionListItem(getExceptionList2().list_id, {
list_id: getExceptionList2().list_id,
item_id: 'simple_list_item_2',
tags: [],
type: 'simple',
description: 'Test exception item',
name: EXCEPTION_LIST_ITEM_NAME_2,
namespace_type: 'single',
entries: [
{
field: 'host.name',
operator: 'included',
type: 'match_any',
value: ['some host', 'another host'],
},
],
expire_time: futureDate,
});
visitWithoutDateRange(EXCEPTIONS_URL);
waitForExceptionsTableToBeLoaded();
});
it('Duplicate exception list with expired items', function () {
duplicateSharedExceptionListFromListsManagementPageByListId(getExceptionList2().list_id, true);
// After duplication - check for new list
assertExceptionListsExists([`${EXCEPTION_LIST_TO_DUPLICATE_NAME} [Duplicate]`]);
findSharedExceptionListItemsByName(`${EXCEPTION_LIST_TO_DUPLICATE_NAME} [Duplicate]`, [
EXCEPTION_LIST_ITEM_NAME,
EXCEPTION_LIST_ITEM_NAME_2,
]);
assertNumberOfExceptionItemsExists(2);
});
it('Duplicate exception list without expired items', function () {
duplicateSharedExceptionListFromListsManagementPageByListId(getExceptionList2().list_id, false);
// After duplication - check for new list
assertExceptionListsExists([`${EXCEPTION_LIST_TO_DUPLICATE_NAME} [Duplicate]`]);
findSharedExceptionListItemsByName(`${EXCEPTION_LIST_TO_DUPLICATE_NAME} [Duplicate]`, [
EXCEPTION_LIST_ITEM_NAME_2,
]);
assertNumberOfExceptionItemsExists(1);
});
});

View file

@ -4,39 +4,39 @@
* 2.0; you may not use this file except in compliance with the Elastic License
* 2.0.
*/
import { getExceptionList } from '../../../objects/exception';
import { getNewRule } from '../../../objects/rule';
import { createRule } from '../../../tasks/api_calls/rules';
import { login, visitWithoutDateRange } from '../../../tasks/login';
import { EXCEPTIONS_URL } from '../../../urls/navigation';
import { getExceptionList } from '../../../../objects/exception';
import { getNewRule } from '../../../../objects/rule';
import {
searchForExceptionList,
waitForExceptionsTableToBeLoaded,
clearSearchSelection,
} from '../../../tasks/exceptions_table';
import {
EXCEPTIONS_TABLE_LIST_NAME,
EXCEPTIONS_TABLE_SHOWING_LISTS,
} from '../../../screens/exceptions';
import { createExceptionList } from '../../../tasks/api_calls/exceptions';
import { esArchiverResetKibana } from '../../../tasks/es_archiver';
EXCEPTIONS_TABLE_LIST_NAME,
} from '../../../../screens/exceptions';
import { createExceptionList } from '../../../../tasks/api_calls/exceptions';
import { createRule } from '../../../../tasks/api_calls/rules';
import { esArchiverResetKibana } from '../../../../tasks/es_archiver';
import {
waitForExceptionsTableToBeLoaded,
searchForExceptionList,
clearSearchSelection,
} from '../../../../tasks/exceptions_table';
import { login, visitWithoutDateRange } from '../../../../tasks/login';
import { EXCEPTIONS_URL } from '../../../../urls/navigation';
const EXCEPTION_LIST_NAME = 'My test list';
const EXCEPTION_LIST_NAME_TWO = 'A test list 2';
const getExceptionList1 = () => ({
...getExceptionList(),
name: 'Test a new list 1',
name: EXCEPTION_LIST_NAME,
list_id: 'exception_list_1',
});
const getExceptionList2 = () => ({
...getExceptionList(),
name: 'Test list 2',
name: EXCEPTION_LIST_NAME_TWO,
list_id: 'exception_list_2',
});
describe('Exceptions Table', () => {
before(() => {
describe('Filter Lists', () => {
beforeEach(() => {
esArchiverResetKibana();
login();
@ -59,9 +59,6 @@ describe('Exceptions Table', () => {
createExceptionList(getExceptionList1(), getExceptionList1().list_id).as(
'exceptionListResponse'
);
});
beforeEach(() => {
login();
visitWithoutDateRange(EXCEPTIONS_URL);
});
@ -88,8 +85,8 @@ describe('Exceptions Table', () => {
// Using cy.contains because we do not care about the exact text,
// just checking number of lists shown
cy.contains(EXCEPTIONS_TABLE_SHOWING_LISTS, '2');
cy.get(EXCEPTIONS_TABLE_LIST_NAME).eq(1).should('have.text', 'Test list 2');
cy.get(EXCEPTIONS_TABLE_LIST_NAME).eq(0).should('have.text', 'Test a new list 1');
cy.get(EXCEPTIONS_TABLE_LIST_NAME).eq(1).should('have.text', EXCEPTION_LIST_NAME_TWO);
cy.get(EXCEPTIONS_TABLE_LIST_NAME).eq(0).should('have.text', EXCEPTION_LIST_NAME);
// Exact phrase search
clearSearchSelection();

View file

@ -0,0 +1,85 @@
/*
* 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 {
IMPORT_SHARED_EXCEPTION_LISTS_CLOSE_BTN,
EXCEPTIONS_TABLE_SHOWING_LISTS,
} from '../../../../screens/exceptions';
import {
waitForExceptionsTableToBeLoaded,
importExceptionLists,
importExceptionListWithSelectingOverwriteExistingOption,
importExceptionListWithSelectingCreateNewOption,
validateImportExceptionListWentSuccessfully,
validateImportExceptionListFailedBecauseExistingListFound,
} from '../../../../tasks/exceptions_table';
import { login, visitWithoutDateRange } from '../../../../tasks/login';
import { EXCEPTIONS_URL } from '../../../../urls/navigation';
import { esArchiverResetKibana } from '../../../../tasks/es_archiver';
describe('Import Lists', () => {
const LIST_TO_IMPORT_FILENAME = 'cypress/fixtures/7_16_exception_list.ndjson';
before(() => {
esArchiverResetKibana();
});
beforeEach(() => {
login();
visitWithoutDateRange(EXCEPTIONS_URL);
waitForExceptionsTableToBeLoaded();
cy.intercept(/(\/api\/exception_lists\/_import)/).as('import');
});
it('Should import exception list successfully if the list does not exist', () => {
importExceptionLists(LIST_TO_IMPORT_FILENAME);
validateImportExceptionListWentSuccessfully();
cy.get(IMPORT_SHARED_EXCEPTION_LISTS_CLOSE_BTN).click();
// Validate table items count
cy.contains(EXCEPTIONS_TABLE_SHOWING_LISTS, '1');
});
it('Should not import exception list if it exists', () => {
importExceptionLists(LIST_TO_IMPORT_FILENAME);
validateImportExceptionListFailedBecauseExistingListFound();
// Validate table items count
cy.contains(EXCEPTIONS_TABLE_SHOWING_LISTS, '1');
});
it('Should import exception list if it exists but the user selected overwrite checkbox', () => {
importExceptionLists(LIST_TO_IMPORT_FILENAME);
validateImportExceptionListFailedBecauseExistingListFound();
// Validate table items count
cy.contains(EXCEPTIONS_TABLE_SHOWING_LISTS, '1');
importExceptionListWithSelectingOverwriteExistingOption();
validateImportExceptionListWentSuccessfully();
// Validate table items count
cy.contains(EXCEPTIONS_TABLE_SHOWING_LISTS, '1');
});
it('Should import exception list if it exists but the user selected create new checkbox', () => {
importExceptionLists(LIST_TO_IMPORT_FILENAME);
validateImportExceptionListFailedBecauseExistingListFound();
// Validate table items count
cy.contains(EXCEPTIONS_TABLE_SHOWING_LISTS, '1');
importExceptionListWithSelectingCreateNewOption();
validateImportExceptionListWentSuccessfully();
// Validate table items count
cy.contains(EXCEPTIONS_TABLE_SHOWING_LISTS, '2');
});
});

View file

@ -4,18 +4,14 @@
* 2.0; you may not use this file except in compliance with the Elastic License
* 2.0.
*/
import { getExceptionList, expectedExportedExceptionList } from '../../../../objects/exception';
import { getNewRule } from '../../../../objects/rule';
import { getExceptionList, expectedExportedExceptionList } from '../../../objects/exception';
import { getNewRule } from '../../../objects/rule';
import { createRule } from '../../../../tasks/api_calls/rules';
import { login, visitWithoutDateRange, waitForPageWithoutDateRange } from '../../../../tasks/login';
import { createRule } from '../../../tasks/api_calls/rules';
import { login, visitWithoutDateRange, waitForPageWithoutDateRange } from '../../../tasks/login';
import { EXCEPTIONS_URL } from '../../../urls/navigation';
import { EXCEPTIONS_URL } from '../../../../urls/navigation';
import {
assertExceptionListsExists,
duplicateSharedExceptionListFromListsManagementPageByListId,
findSharedExceptionListItemsByName,
deleteExceptionListWithoutRuleReferenceByListId,
deleteExceptionListWithRuleReferenceByListId,
exportExceptionList,
@ -23,21 +19,18 @@ import {
createSharedExceptionList,
linkRulesToExceptionList,
assertNumberLinkedRules,
} from '../../../tasks/exceptions_table';
} from '../../../../tasks/exceptions_table';
import {
EXCEPTIONS_LIST_MANAGEMENT_NAME,
EXCEPTIONS_TABLE_SHOWING_LISTS,
} from '../../../screens/exceptions';
import { createExceptionList, createExceptionListItem } from '../../../tasks/api_calls/exceptions';
import { esArchiverResetKibana } from '../../../tasks/es_archiver';
import { assertNumberOfExceptionItemsExists } from '../../../tasks/exceptions';
} from '../../../../screens/exceptions';
import { createExceptionList } from '../../../../tasks/api_calls/exceptions';
import { esArchiverResetKibana } from '../../../../tasks/es_archiver';
import { TOASTER } from '../../../screens/alerts_detection_rules';
import { TOASTER } from '../../../../screens/alerts_detection_rules';
const EXCEPTION_LIST_NAME = 'My shared list';
const EXCEPTION_LIST_NAME = 'My test list';
const EXCEPTION_LIST_TO_DUPLICATE_NAME = 'A test list 2';
const EXCEPTION_LIST_ITEM_NAME = 'Sample Exception List Item 1';
const EXCEPTION_LIST_ITEM_NAME_2 = 'Sample Exception List Item 2';
const getExceptionList1 = () => ({
...getExceptionList(),
@ -51,14 +44,10 @@ const getExceptionList2 = () => ({
list_id: 'exception_list_2',
});
const expiredDate = new Date(Date.now() - 1000000).toISOString();
const futureDate = new Date(Date.now() + 1000000).toISOString();
describe('Manage shared exception list', () => {
describe('Create/Export/Delete', () => {
describe('Manage lists from "Shared Exception Lists" page', () => {
describe('Create/Export/Delete List', () => {
before(() => {
esArchiverResetKibana();
login();
createRule(getNewRule({ name: 'Another rule' }));
@ -151,87 +140,4 @@ describe('Manage shared exception list', () => {
cy.contains(EXCEPTIONS_TABLE_SHOWING_LISTS, '2');
});
});
describe('Duplicate', () => {
beforeEach(() => {
esArchiverResetKibana();
login();
// Create exception list associated with a rule
createExceptionList(getExceptionList2(), getExceptionList2().list_id);
createExceptionListItem(getExceptionList2().list_id, {
list_id: getExceptionList2().list_id,
item_id: 'simple_list_item_1',
tags: [],
type: 'simple',
description: 'Test exception item',
name: EXCEPTION_LIST_ITEM_NAME,
namespace_type: 'single',
entries: [
{
field: 'host.name',
operator: 'included',
type: 'match_any',
value: ['some host', 'another host'],
},
],
expire_time: expiredDate,
});
createExceptionListItem(getExceptionList2().list_id, {
list_id: getExceptionList2().list_id,
item_id: 'simple_list_item_2',
tags: [],
type: 'simple',
description: 'Test exception item',
name: EXCEPTION_LIST_ITEM_NAME_2,
namespace_type: 'single',
entries: [
{
field: 'host.name',
operator: 'included',
type: 'match_any',
value: ['some host', 'another host'],
},
],
expire_time: futureDate,
});
visitWithoutDateRange(EXCEPTIONS_URL);
waitForExceptionsTableToBeLoaded();
});
it('Duplicate exception list with expired items', function () {
duplicateSharedExceptionListFromListsManagementPageByListId(
getExceptionList2().list_id,
true
);
// After duplication - check for new list
assertExceptionListsExists([`${EXCEPTION_LIST_TO_DUPLICATE_NAME} [Duplicate]`]);
findSharedExceptionListItemsByName(`${EXCEPTION_LIST_TO_DUPLICATE_NAME} [Duplicate]`, [
EXCEPTION_LIST_ITEM_NAME,
EXCEPTION_LIST_ITEM_NAME_2,
]);
assertNumberOfExceptionItemsExists(2);
});
it('Duplicate exception list without expired items', function () {
duplicateSharedExceptionListFromListsManagementPageByListId(
getExceptionList2().list_id,
false
);
// After duplication - check for new list
assertExceptionListsExists([`${EXCEPTION_LIST_TO_DUPLICATE_NAME} [Duplicate]`]);
findSharedExceptionListItemsByName(`${EXCEPTION_LIST_TO_DUPLICATE_NAME} [Duplicate]`, [
EXCEPTION_LIST_ITEM_NAME_2,
]);
assertNumberOfExceptionItemsExists(1);
});
});
});

View file

@ -5,29 +5,27 @@
* 2.0.
*/
import { esArchiverResetKibana } from '../../../tasks/es_archiver';
import { cleanKibana } from '../../../tasks/common';
import { ROLES } from '../../../../common/test';
import { getExceptionList } from '../../../objects/exception';
import { esArchiverResetKibana } from '../../../../tasks/es_archiver';
import { ROLES } from '../../../../../common/test';
import { getExceptionList } from '../../../../objects/exception';
import {
EXCEPTIONS_OVERFLOW_ACTIONS_BTN,
EXCEPTIONS_TABLE_SHOWING_LISTS,
} from '../../../screens/exceptions';
import { createExceptionList, deleteExceptionList } from '../../../tasks/api_calls/exceptions';
} from '../../../../screens/exceptions';
import { createExceptionList, deleteExceptionList } from '../../../../tasks/api_calls/exceptions';
import {
dismissCallOut,
getCallOut,
waitForCallOutToBeShown,
} from '../../../tasks/common/callouts';
import { login, visitWithoutDateRange } from '../../../tasks/login';
import { EXCEPTIONS_URL } from '../../../urls/navigation';
} from '../../../../tasks/common/callouts';
import { login, visitWithoutDateRange } from '../../../../tasks/login';
import { EXCEPTIONS_URL } from '../../../../urls/navigation';
const MISSING_PRIVILEGES_CALLOUT = 'missing-user-privileges';
describe('All exception lists - read only', () => {
describe('Shared exception lists - read only', () => {
before(() => {
esArchiverResetKibana();
cleanKibana();
});
beforeEach(() => {

View file

@ -0,0 +1,2 @@
{"_version":"WzI2MzA4LDFd","created_at":"2023-04-26T11:48:28.348Z","created_by":"elastic","description":"Test exception list description","id":"427a27c0-e428-11ed-90b9-27ae1019f1a1","immutable":false,"list_id":"exception_list_1","name":"My shared list","namespace_type":"single","os_types":[],"tags":[],"tie_breaker_id":"745f10e2-8647-4151-9dae-787a24301d2d","type":"detection","updated_at":"2023-04-26T11:48:28.348Z","updated_by":"elastic","version":1}
{"exported_exception_list_count":1,"exported_exception_list_item_count":0,"missing_exception_list_item_count":0,"missing_exception_list_items":[],"missing_exception_lists":[],"missing_exception_lists_count":0}

View file

@ -602,3 +602,23 @@ export const expectedExportedRule = (ruleResponse: Cypress.Response<RuleResponse
return `${JSON.stringify(rule)}\n${JSON.stringify(details)}\n`;
};
export const getEndpointRule = (): QueryRuleCreateProps => ({
type: 'query',
query: 'event.kind:alert and event.module:(endpoint and not endgame)',
index: ['endpoint.alerts-*'],
name: 'Endpoint Rule',
description: 'The new rule description.',
severity: 'high',
risk_score: 17,
interval: '10s',
from: 'now-50000h',
max_signals: 100,
exceptions_list: [
{
id: 'endpoint_list',
list_id: 'endpoint_list',
namespace_type: 'agnostic',
type: 'endpoint',
},
],
});

View file

@ -130,6 +130,8 @@ export const TOASTER_BODY = '[data-test-subj="globalToastList"] [data-test-subj=
export const TOASTER_ERROR_BTN = '[data-test-subj="errorToastBtn"]';
export const TOASTER_CLOSE_ICON = '[data-test-subj="toastCloseButton"]';
export const RULE_IMPORT_OVERWRITE_CHECKBOX = '[id="importDataModalCheckboxLabel"]';
export const RULE_IMPORT_OVERWRITE_EXCEPTIONS_CHECKBOX =

View file

@ -19,6 +19,8 @@ export const FIELD_INPUT_PARENT =
export const LOADING_SPINNER = '[data-test-subj="loading-spinner"]';
export const EXCEPTION_FLYOUT_LOADING_SPINNER = '[data-test-subj="loadingAddExceptionFlyout"]';
export const OPERATOR_INPUT = '[data-test-subj="operatorAutocompleteComboBox"]';
export const VALUES_INPUT =
@ -29,6 +31,9 @@ export const VALUES_MATCH_ANY_INPUT =
export const ADD_AND_BTN = '[data-test-subj="exceptionsAndButton"]';
export const VALUES_MATCH_INCLUDED_INPUT =
'[data-test-subj="valuesAutocompleteComboBox listsComboxBox"] [data-test-subj="comboBoxInput"]';
export const ADD_OR_BTN = '[data-test-subj="exceptionsOrButton"]';
export const ADD_NESTED_BTN = '[data-test-subj="exceptionsNestedButton"]';
@ -99,7 +104,7 @@ export const EXCEPTION_FLYOUT_VERSION_CONFLICT =
export const EXCEPTION_FLYOUT_LIST_DELETED_ERROR = '[data-test-subj="errorCalloutContainer"]';
// Exceptions all items view
// Exceptions all items view in Rule pages
export const NO_EXCEPTIONS_EXIST_PROMPT =
'[data-test-subj="exceptionItemViewerEmptyPrompts-empty"]';
@ -121,6 +126,13 @@ export const EXCEPTION_CARD_ITEM_NAME = '[data-test-subj="exceptionItemCardHeade
export const EXCEPTION_CARD_ITEM_CONDITIONS =
'[data-test-subj="exceptionItemCardConditions-condition"]';
// Affected Rules
export const EXCEPTION_CARD_ITEM_AFFECTED_RULES =
'[data-test-subj="exceptionItemCardMetaInfo-affectedRulesButton"]';
export const EXCEPTION_CARD_ITEM_AFFECTED_RULES_MENU_ITEM =
'div.euiContextMenuPanel button.euiContextMenuItem';
// Exception flyout components
export const EXCEPTION_ITEM_NAME_INPUT = 'input[data-test-subj="exceptionFlyoutNameInput"]';
@ -138,6 +150,22 @@ export const OS_SELECTION_SECTION = '[data-test-subj="osSelectionDropdown"]';
export const OS_INPUT = '[data-test-subj="osSelectionDropdown"] [data-test-subj="comboBoxInput"]';
// Exception Item comments
export const EXCEPTION_COMMENTS_ACCORDION_BTN = '[data-test-subj="exceptionItemCommentsAccordion"]';
export const EXCEPTION_COMMENT_TEXT_AREA = '[data-test-subj="newExceptionItemCommentTextArea"]';
export const EXCEPTION_ITEM_VIEWER_CONTAINER_SHOW_COMMENTS_BTN =
'[data-test-subj="exceptionsViewerCommentAccordion"]';
export const EXCEPTION_ITEM_COMMENTS_CONTAINER =
'[data-test-subj="exceptionsViewerCommentAccordion"] li';
export const EXCEPTION_ITEM_COMMENTS_CONTAINER_TEXT =
'[data-test-subj="exceptionsViewerCommentAccordion"] li div.euiCommentEvent__body';
export const EXCEPTION_ITEM_COMMENT_COPY_BTN = '[data-test-subj="clipboard"]';
// Shared Exception List Management Page
export const MANAGE_EXCEPTION_CREATE_BUTTON_MENU =
'[data-test-subj="manageExceptionListCreateButton"]';
@ -150,6 +178,8 @@ export const MANAGE_EXCEPTION_CREATE_LIST_BUTTON =
export const RULE_ACTION_LINK_RULE_SWITCH = '[data-test-subj="ruleActionLinkRuleSwitch"]';
export const LINK_TO_SHARED_LIST_RADIO = '[data-test-subj="addToListsRadioOptionLabel"]';
export const CREATE_SHARED_EXCEPTION_LIST_NAME_INPUT =
'input[data-test-subj="createSharedExceptionListNameInput"]';
@ -159,14 +189,8 @@ export const CREATE_SHARED_EXCEPTION_LIST_DESCRIPTION_INPUT =
export const CREATE_SHARED_EXCEPTION_LIST_BTN =
'button[data-test-subj="exception-lists-form-create-shared"]';
export const exceptionsTableListManagementListContainerByListId = (listId: string) =>
`[data-test-subj="exceptionsManagementListCard-${listId}"]`;
// Exception List detail page
export const LINKED_RULES_BADGE = '[data-test-subj="exceptionListCardLinkedRulesBadge"]';
export const MANAGE_RULES_SAVE = '[data-test-subj="manageListRulesSaveButton"]';
// Exception list management
export const EXCEPTIONS_LIST_MANAGEMENT_NAME =
'[data-test-subj="exceptionListManagementTitleText"]';
@ -175,7 +199,6 @@ export const EXCEPTIONS_LIST_MANAGEMENT_EDIT_NAME_BTN =
export const EXCEPTIONS_LIST_MANAGEMENT_EDIT_MODAL_NAME_INPUT =
'[data-test-subj="editModalNameTextField"]';
export const EXCEPTIONS_LIST_MANAGEMENT_DESCRIPTION =
'[data-test-subj="exceptionListManagementDescriptionText"]';
@ -188,3 +211,62 @@ export const EXCEPTIONS_LIST_DETAILS_HEADER =
'[data-test-subj="exceptionListManagementPageHeader"]';
export const EXCEPTION_LIST_DETAILS_CARD_ITEM_NAME = '[data-test-subj="exceptionItemCardHeader"]';
export const EXCEPTION_LIST_DETAILS_LINK_RULES_BTN =
'[data-test-subj="exceptionListManagementRightSideMenuItemsLinkRulesButton"]';
// Import shared exception list
export const IMPORT_SHARED_EXCEPTION_LISTS_BTN = '[data-test-subj="importSharedExceptionList"]';
export const IMPORT_SHARED_EXCEPTION_LISTS_CONFIRM_BTN =
'[data-test-subj="exception-lists-form-import-action"]';
export const IMPORT_SHARED_EXCEPTION_LISTS_CLOSE_BTN =
'[data-test-subj="exceptionListsImportFormCloseBTN"]';
export const IMPORT_SHARED_EXCEPTION_LISTS_OVERWRITE_EXISTING_CHECKBOX =
'[data-test-subj="importExceptionListOverwriteExistingCheckbox"]';
export const IMPORT_SHARED_EXCEPTION_LISTS_OVERWRITE_CREATE_NEW_CHECKBOX =
'[data-test-subj="importExceptionListCreateNewCheckbox"]';
export const exceptionsTableListManagementListContainerByListId = (listId: string) =>
`[data-test-subj="exceptionsManagementListCard-${listId}"]`;
export const LINKED_RULES_BADGE = '[data-test-subj="exceptionListCardLinkedRulesBadge"]';
export const MANAGE_RULES_SAVE = '[data-test-subj="manageListRulesSaveButton"]';
export const LINK_RULES_FLYOUT_LINK_SWITCH = '[data-test-subj="ruleActionLinkRuleSwitch"]';
export const EXCEPTION_LIST_DETAIL_LINKED_TO_RULES_HEADER_MENU =
'[data-test-subj="exceptionListManagementRightSideMenuItemsLinkedRulesMenuEmptyButton"]';
export const EXCEPTION_LIST_DETAIL_LINKED_TO_RULES_HEADER_MENU_ITEM =
'[data-test-subj="exceptionListManagementRightSideMenuItemsLinkedRulesMenuMenuPanel"] div button';
export const EXCEPTION_LIST_DETAIL_MENU_ITEMS =
'[data-test-subj="exceptionListManagementRightSideMenuItemsMenuActionsButtonIcon"]';
export const EXCEPTION_LIST_DETAIL_EXPORT_BTN =
'[data-test-subj="exceptionListManagementRightSideMenuItemsMenuActionsActionItem1"]';
export const EXCEPTION_LIST_DETAIL_DUPLICATE_BTN =
'[data-test-subj="exceptionListManagementRightSideMenuItemsMenuActionsActionItem3"]';
export const EXCEPTION_LIST_DETAIL_DELETE_BTN =
'[data-test-subj="exceptionListManagementRightSideMenuItemsMenuActionsActionItem3"]';
// Exception card in Shared List Detail Page
export const EXCEPTION_ITEM_HEADER_ACTION_MENU =
'[data-test-subj="exceptionItemCardHeaderButtonIcon"]';
export const EXCEPTION_ITEM_OVERFLOW_ACTION_EDIT =
'[data-test-subj="exceptionItemCardHeaderActionItemedit"]';
export const EXCEPTION_ITEM_OVERFLOW_ACTION_DELETE =
'[data-test-subj="exceptionItemCardHeaderActionItemdelete"]';
export const EXECPTION_ITEM_CARD_HEADER_TITLE = '[data-test-subj="exceptionItemCardHeaderTitle"]';
export const EMPTY_EXCEPTIONS_VIEWER = '[data-test-subj="emptyViewerState"]';

View file

@ -65,6 +65,7 @@ import {
DUPLICATE_WITHOUT_EXCEPTIONS_OPTION,
DUPLICATE_WITH_EXCEPTIONS_OPTION,
DUPLICATE_WITH_EXCEPTIONS_WITHOUT_EXPIRED_OPTION,
TOASTER_CLOSE_ICON,
} from '../screens/alerts_detection_rules';
import type { RULES_MONITORING_TABLE } from '../screens/alerts_detection_rules';
import { EUI_CHECKBOX } from '../screens/common/controls';
@ -571,6 +572,10 @@ export const clickErrorToastBtn = () => {
cy.get(TOASTER_ERROR_BTN).click();
};
export const closeErrorToast = () => {
cy.get(TOASTER_CLOSE_ICON).click();
};
export const goToEditRuleActionsSettingsOf = (name: string) => {
goToTheRuleDetailsOf(name);
goToRuleEditSettings();

View file

@ -28,7 +28,22 @@ import {
EXCEPTION_FIELD_MAPPING_CONFLICTS_TOOLTIP,
EXCEPTION_FIELD_MAPPING_CONFLICTS_ACCORDION_ICON,
EXCEPTION_FIELD_MAPPING_CONFLICTS_DESCRIPTION,
EXCEPTION_COMMENT_TEXT_AREA,
EXCEPTION_COMMENTS_ACCORDION_BTN,
EXCEPTION_ITEM_VIEWER_CONTAINER_SHOW_COMMENTS_BTN,
EXCEPTION_ITEM_COMMENTS_CONTAINER,
EXCEPTION_ITEM_COMMENT_COPY_BTN,
VALUES_MATCH_INCLUDED_INPUT,
EXCEPTION_ITEM_VIEWER_CONTAINER,
EXCEPTION_CARD_ITEM_AFFECTED_RULES,
EXCEPTION_CARD_ITEM_AFFECTED_RULES_MENU_ITEM,
ADD_AND_BTN,
ADD_OR_BTN,
RULE_ACTION_LINK_RULE_SWITCH,
LINK_TO_SHARED_LIST_RADIO,
EXCEPTION_ITEM_HEADER_ACTION_MENU,
EXCEPTION_ITEM_OVERFLOW_ACTION_EDIT,
EXCEPTION_ITEM_OVERFLOW_ACTION_DELETE,
} from '../screens/exceptions';
export const assertNumberOfExceptionItemsExists = (numberOfItems: number) => {
@ -98,6 +113,10 @@ export const addExceptionEntryFieldMatchAnyValue = (value: string, index = 0) =>
cy.get(VALUES_MATCH_ANY_INPUT).eq(index).type(`${value}{enter}`);
cy.get(EXCEPTION_FLYOUT_TITLE).click();
};
export const addExceptionEntryFieldMatchIncludedValue = (value: string, index = 0) => {
cy.get(VALUES_MATCH_INCLUDED_INPUT).eq(index).type(`${value}{enter}`);
cy.get(EXCEPTION_FLYOUT_TITLE).click();
};
export const closeExceptionBuilderFlyout = () => {
cy.get(CANCEL_BTN).click();
@ -170,3 +189,91 @@ export const selectOs = (os: string) => {
cy.get(OS_SELECTION_SECTION).should('exist');
cy.get(OS_INPUT).type(`${os}{downArrow}{enter}`);
};
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 clickOnShowComments = () => {
cy.get(EXCEPTION_ITEM_VIEWER_CONTAINER_SHOW_COMMENTS_BTN).click();
};
export const clickCopyCommentToClipboard = () => {
// Disable window prompt which is used in link creation by copy-to-clipboard library
// as this prompt pauses test execution during `cypress open`
cy.window().then((win) => {
cy.stub(win, 'prompt').returns('DISABLED WINDOW PROMPT');
});
cy.get(EXCEPTION_ITEM_COMMENTS_CONTAINER).first().find(EXCEPTION_ITEM_COMMENT_COPY_BTN).click();
};
export const validateExceptionItemAffectsTheCorrectRulesInRulePage = (rulesCount: number) => {
cy.get(EXCEPTION_ITEM_VIEWER_CONTAINER).should('have.length', rulesCount);
cy.get(EXCEPTION_CARD_ITEM_AFFECTED_RULES).should('have.text', `Affects ${rulesCount} rule`);
};
export const validateExceptionItemFirstAffectedRuleNameInRulePage = (ruleName: string) => {
cy.get(EXCEPTION_CARD_ITEM_AFFECTED_RULES).click();
cy.get(EXCEPTION_CARD_ITEM_AFFECTED_RULES_MENU_ITEM).first().should('have.text', ruleName);
};
export const addTwoAndedConditions = (
firstEntryField: string,
firstEntryFieldValue: string,
secondEntryField: string,
secondEntryFieldValue: string
) => {
addExceptionEntryFieldValue(firstEntryField, 0);
addExceptionEntryFieldValueValue(firstEntryFieldValue, 0);
cy.get(ADD_AND_BTN).click();
addExceptionEntryFieldValue(secondEntryField, 1);
addExceptionEntryFieldValueValue(secondEntryFieldValue, 1);
};
export const addTwoORedConditions = (
firstEntryField: string,
firstEntryFieldValue: string,
secondEntryField: string,
secondEntryFieldValue: string
) => {
addExceptionEntryFieldValue(firstEntryField, 0);
addExceptionEntryFieldValueValue(firstEntryFieldValue, 0);
cy.get(ADD_OR_BTN).click();
addExceptionEntryFieldValue(secondEntryField, 1);
addExceptionEntryFieldValueValue(secondEntryFieldValue, 1);
};
export const linkFirstRuleOnExceptionFlyout = () => {
cy.get(RULE_ACTION_LINK_RULE_SWITCH).find('button').click();
};
export const linkFirstSharedListOnExceptionFlyout = () => {
cy.get(LINK_TO_SHARED_LIST_RADIO).click();
cy.get(RULE_ACTION_LINK_RULE_SWITCH).find('button').click();
};
export const editFirstExceptionItemInListDetailPage = () => {
// Click on the first exception overflow menu items
cy.get(EXCEPTION_ITEM_HEADER_ACTION_MENU).click();
// Open the edit modal
cy.get(EXCEPTION_ITEM_OVERFLOW_ACTION_EDIT).click();
};
export const deleteFirstExceptionItemInListDetailPage = () => {
// Click on the first exception overflow menu items
cy.get(EXCEPTION_ITEM_HEADER_ACTION_MENU).click();
// Delete exception
cy.get(EXCEPTION_ITEM_OVERFLOW_ACTION_DELETE).click();
};

View file

@ -5,6 +5,7 @@
* 2.0.
*/
import { INPUT_FILE, TOASTER, TOASTER_BODY } from '../screens/alerts_detection_rules';
import {
EXCEPTIONS_TABLE,
EXCEPTIONS_TABLE_SEARCH,
@ -35,7 +36,15 @@ import {
RULE_ACTION_LINK_RULE_SWITCH,
LINKED_RULES_BADGE,
MANAGE_RULES_SAVE,
IMPORT_SHARED_EXCEPTION_LISTS_BTN,
IMPORT_SHARED_EXCEPTION_LISTS_CONFIRM_BTN,
EXCEPTION_LIST_DETAIL_LINKED_TO_RULES_HEADER_MENU,
EXCEPTION_LIST_DETAIL_LINKED_TO_RULES_HEADER_MENU_ITEM,
MANAGE_EXCEPTION_CREATE_BUTTON_EXCEPTION,
IMPORT_SHARED_EXCEPTION_LISTS_OVERWRITE_CREATE_NEW_CHECKBOX,
IMPORT_SHARED_EXCEPTION_LISTS_OVERWRITE_EXISTING_CHECKBOX,
} from '../screens/exceptions';
import { closeErrorToast } from './alerts_detection_rules';
import { assertExceptionItemsExists } from './exceptions';
export const clearSearchSelection = () => {
@ -46,6 +55,15 @@ export const expandExceptionActions = () => {
cy.get(EXCEPTIONS_OVERFLOW_ACTIONS_BTN).first().click();
};
export const importExceptionLists = (listsFile: string) => {
cy.get(IMPORT_SHARED_EXCEPTION_LISTS_BTN).click();
cy.get(INPUT_FILE).should('exist');
cy.get(INPUT_FILE).trigger('click');
cy.get(INPUT_FILE).selectFile(listsFile);
cy.get(INPUT_FILE).trigger('change');
cy.get(IMPORT_SHARED_EXCEPTION_LISTS_CONFIRM_BTN).click();
};
export const exportExceptionList = (listId: string) => {
cy.get(exceptionsTableListManagementListContainerByListId(listId))
.find(EXCEPTIONS_OVERFLOW_ACTIONS_BTN)
@ -67,7 +85,7 @@ export const linkRulesToExceptionList = (listId: string, ruleSwitch: number = 0)
.click();
cy.get(EXCEPTIONS_TABLE_LINK_RULES_BTN).first().click();
cy.get(RULE_ACTION_LINK_RULE_SWITCH).eq(ruleSwitch).find('button').click();
cy.get(MANAGE_RULES_SAVE).first().click();
saveLinkedRules();
};
export const deleteExceptionListWithoutRuleReferenceByListId = (listId: string) => {
@ -198,3 +216,71 @@ export const editExceptionLisDetails = ({
cy.get(EXCEPTIONS_LIST_EDIT_DETAILS_SAVE_BTN).first().click();
};
export const clickOnLinkRulesByRuleRowOrderInListDetail = (ruleSwitch: number = 0) => {
cy.get(RULE_ACTION_LINK_RULE_SWITCH).eq(ruleSwitch).find('button').click();
};
export const linkSharedListToRulesFromListDetails = (numberOfRules: number) => {
for (let i = 0; i < numberOfRules; i++) {
clickOnLinkRulesByRuleRowOrderInListDetail(i);
}
};
export const saveLinkedRules = () => {
cy.get(MANAGE_RULES_SAVE).first().click();
};
export const validateSharedListLinkedRules = (
numberOfRules: number,
linkedRulesNames: string[]
) => {
cy.get(EXCEPTION_LIST_DETAIL_LINKED_TO_RULES_HEADER_MENU).should(
'have.text',
`Linked to ${numberOfRules} rules`
);
cy.get(EXCEPTION_LIST_DETAIL_LINKED_TO_RULES_HEADER_MENU).click();
linkedRulesNames.forEach((ruleName) => {
cy.get(EXCEPTION_LIST_DETAIL_LINKED_TO_RULES_HEADER_MENU_ITEM).contains('a', ruleName);
});
};
export const addExceptionListFromSharedExceptionListHeaderMenu = () => {
// Click on "Create shared exception list" button on the header
cy.get(MANAGE_EXCEPTION_CREATE_BUTTON_MENU).click();
// Click on "Create exception item"
cy.get(MANAGE_EXCEPTION_CREATE_BUTTON_EXCEPTION).click();
};
export const importExceptionListWithSelectingOverwriteExistingOption = () => {
// { force: true }: The EuiCheckbox component's label covers the checkbox
cy.get(IMPORT_SHARED_EXCEPTION_LISTS_OVERWRITE_EXISTING_CHECKBOX).check({ force: true });
// Close the Error toast to be able to import again
closeErrorToast();
// Import after selecting overwrite option
cy.get(IMPORT_SHARED_EXCEPTION_LISTS_CONFIRM_BTN).click();
};
export const importExceptionListWithSelectingCreateNewOption = () => {
// { force: true }: The EuiCheckbox component's label covers the checkbox
cy.get(IMPORT_SHARED_EXCEPTION_LISTS_OVERWRITE_CREATE_NEW_CHECKBOX).check({ force: true });
// Close the Error toast to be able to import again
closeErrorToast();
// Import after selecting overwrite option
cy.get(IMPORT_SHARED_EXCEPTION_LISTS_CONFIRM_BTN).click();
};
export const validateImportExceptionListWentSuccessfully = () => {
cy.wait('@import').then(({ response }) => {
cy.wrap(response?.statusCode).should('eql', 200);
cy.get(TOASTER).should('have.text', `Exception list imported`);
});
};
export const validateImportExceptionListFailedBecauseExistingListFound = () => {
cy.wait('@import').then(({ response }) => {
cy.wrap(response?.statusCode).should('eql', 200);
cy.get(TOASTER).should('have.text', 'There was an error uploading the exception list.');
cy.get(TOASTER_BODY).should('contain', 'Found that list_id');
});
};

View file

@ -172,6 +172,7 @@ export const ImportExceptionListFlyout = React.memo(
id={'basicCheckboxId'}
label={i18n.IMPORT_EXCEPTION_LIST_OVERWRITE}
checked={overwrite}
data-test-subj="importExceptionListOverwriteExistingCheckbox"
onChange={(e) => {
setOverwrite(!overwrite);
setAsNewList(false);
@ -180,6 +181,7 @@ export const ImportExceptionListFlyout = React.memo(
<EuiCheckbox
id={'createNewListCheckbox'}
label={i18n.IMPORT_EXCEPTION_LIST_AS_NEW_LIST}
data-test-subj="importExceptionListCreateNewCheckbox"
checked={asNewList}
onChange={(e) => {
setAsNewList(!asNewList);
@ -193,6 +195,7 @@ export const ImportExceptionListFlyout = React.memo(
<EuiFlexGroup justifyContent="spaceBetween">
<EuiFlexItem grow={false}>
<EuiButtonEmpty
data-test-subj="exceptionListsImportFormCloseBTN"
iconType="cross"
onClick={() => setDisplayImportListFlyout(false)}
flush="left"

View file

@ -512,7 +512,11 @@ export const SharedLists = React.memo(() => {
]}
/>
</EuiPopover>,
<EuiButton iconType={'importAction'} onClick={() => setDisplayImportListFlyout(true)}>
<EuiButton
data-test-subj="importSharedExceptionList"
iconType={'importAction'}
onClick={() => setDisplayImportListFlyout(true)}
>
{i18n.IMPORT_EXCEPTION_LIST_BUTTON}
</EuiButton>,
]}

View file

@ -8,8 +8,14 @@
import expect from '@kbn/expect';
import { EXCEPTION_LIST_ITEM_URL, EXCEPTION_LIST_URL } from '@kbn/securitysolution-list-constants';
import { getCreateExceptionListItemMinimalSchemaMock } from '@kbn/lists-plugin/common/schemas/request/create_exception_list_item_schema.mock';
import { getCreateExceptionListMinimalSchemaMock } from '@kbn/lists-plugin/common/schemas/request/create_exception_list_schema.mock';
import {
getCreateExceptionListItemMinimalSchemaMock,
getCreateExceptionListItemNewerVersionSchemaMock,
} from '@kbn/lists-plugin/common/schemas/request/create_exception_list_item_schema.mock';
import {
getCreateExceptionListDetectionSchemaMock,
getCreateExceptionListMinimalSchemaMock,
} from '@kbn/lists-plugin/common/schemas/request/create_exception_list_schema.mock';
import { DETECTION_ENGINE_RULES_URL } from '@kbn/security-solution-plugin/common/constants';
import { ROLES } from '@kbn/security-solution-plugin/common/test';
import { FtrProviderContext } from '../../common/ftr_provider_context';
@ -47,81 +53,330 @@ export default ({ getService }: FtrProviderContext): void => {
await deleteAllRules(supertest, log);
});
it('should be able to reimport a rule referencing an exception list with existing comments', async () => {
// create an exception list
const { body: exceptionBody } = await supertestWithoutAuth
.post(EXCEPTION_LIST_URL)
.auth(ROLES.soc_manager, 'changeme')
.set('kbn-xsrf', 'true')
.send(getCreateExceptionListMinimalSchemaMock())
.expect(200);
describe('Endpoint Exception', () => {
/*
Following the release of version 8.7, this test can be considered as an evaluation of exporting
an outdated List Item. A notable distinction lies in the absence of the "expire_time" property
within the getCreateExceptionListMinimalSchemaMock, which allows for differentiation between older
and newer versions. The rationale behind this approach is the lack of version tracking for both List and Rule,
thereby enabling simulation of migration scenarios.
*/
it('should be able to reimport a rule referencing an old version of endpoint exception list with existing comments', async () => {
// create an exception list
const { body: exceptionBody } = await supertestWithoutAuth
.post(EXCEPTION_LIST_URL)
.auth(ROLES.soc_manager, 'changeme')
.set('kbn-xsrf', 'true')
.send(getCreateExceptionListMinimalSchemaMock())
.expect(200);
// create an exception list item
const { body: exceptionItemBody } = await supertestWithoutAuth
.post(EXCEPTION_LIST_ITEM_URL)
.auth(ROLES.soc_manager, 'changeme')
.set('kbn-xsrf', 'true')
.send({
...getCreateExceptionListItemMinimalSchemaMock(),
comments: [{ comment: 'this exception item rocks' }],
})
.expect(200);
// create an exception list item
const { body: exceptionItemBody } = await supertestWithoutAuth
.post(EXCEPTION_LIST_ITEM_URL)
.auth(ROLES.soc_manager, 'changeme')
.set('kbn-xsrf', 'true')
.send({
...getCreateExceptionListItemMinimalSchemaMock(), // using Old version of Exception List
comments: [{ comment: 'this exception item rocks' }],
})
.expect(200);
const { body: exceptionItem } = await supertest
.get(`${EXCEPTION_LIST_ITEM_URL}?item_id=${exceptionItemBody.item_id}`)
.set('kbn-xsrf', 'true')
.expect(200);
const { body: exceptionItem } = await supertest
.get(`${EXCEPTION_LIST_ITEM_URL}?item_id=${exceptionItemBody.item_id}`)
.set('kbn-xsrf', 'true')
.expect(200);
expect(exceptionItem.comments).to.eql([
{
comment: 'this exception item rocks',
created_at: `${exceptionItem.comments[0].created_at}`,
created_by: 'soc_manager',
id: `${exceptionItem.comments[0].id}`,
},
]);
await createRule(supertest, log, {
...getSimpleRule(),
exceptions_list: [
expect(exceptionItem.comments).to.eql([
{
id: exceptionBody.id,
list_id: exceptionBody.list_id,
type: exceptionBody.type,
namespace_type: exceptionBody.namespace_type,
comment: 'this exception item rocks',
created_at: `${exceptionItem.comments[0].created_at}`,
created_by: 'soc_manager',
id: `${exceptionItem.comments[0].id}`,
},
],
]);
await createRule(supertest, log, {
...getSimpleRule(),
exceptions_list: [
{
id: exceptionBody.id,
list_id: exceptionBody.list_id,
type: exceptionBody.type,
namespace_type: exceptionBody.namespace_type,
},
],
});
const { body } = await supertest
.post(`${DETECTION_ENGINE_RULES_URL}/_export`)
.set('kbn-xsrf', 'true')
.send()
.expect(200)
.parse(binaryToString);
await supertest
.post(`${DETECTION_ENGINE_RULES_URL}/_import?overwrite=true&overwrite_exceptions=true`)
.set('kbn-xsrf', 'true')
.attach('file', Buffer.from(body), 'rules.ndjson')
.expect(200);
const { body: exceptionItemFind2 } = await supertest
.get(`${EXCEPTION_LIST_ITEM_URL}?item_id=${exceptionItemBody.item_id}`)
.set('kbn-xsrf', 'true')
.expect(200);
// NOTE: Existing comment is uploaded successfully
// however, the meta now reflects who imported it,
// not who created the initial comment
expect(exceptionItemFind2.comments).to.eql([
{
comment: 'this exception item rocks',
created_at: `${exceptionItemFind2.comments[0].created_at}`,
created_by: 'elastic',
id: `${exceptionItemFind2.comments[0].id}`,
},
]);
});
const { body } = await supertest
.post(`${DETECTION_ENGINE_RULES_URL}/_export`)
.set('kbn-xsrf', 'true')
.send()
.expect(200)
.parse(binaryToString);
it('should be able to reimport a rule referencing a new version of endpoint exception list with existing comments', async () => {
// create an exception list
const { body: exceptionBody } = await supertestWithoutAuth
.post(EXCEPTION_LIST_URL)
.auth(ROLES.soc_manager, 'changeme')
.set('kbn-xsrf', 'true')
.send(getCreateExceptionListMinimalSchemaMock())
.expect(200);
await supertest
.post(`${DETECTION_ENGINE_RULES_URL}/_import?overwrite=true&overwrite_exceptions=true`)
.set('kbn-xsrf', 'true')
.attach('file', Buffer.from(body), 'rules.ndjson')
.expect(200);
// create an exception list item
const { body: exceptionItemBody } = await supertestWithoutAuth
.post(EXCEPTION_LIST_ITEM_URL)
.auth(ROLES.soc_manager, 'changeme')
.set('kbn-xsrf', 'true')
.send({
...getCreateExceptionListItemNewerVersionSchemaMock(), // using newer version of Exception List
comments: [{ comment: 'this exception item rocks' }],
})
.expect(200);
const { body: exceptionItemFind2 } = await supertest
.get(`${EXCEPTION_LIST_ITEM_URL}?item_id=${exceptionItemBody.item_id}`)
.set('kbn-xsrf', 'true')
.expect(200);
const { body: exceptionItem } = await supertest
.get(`${EXCEPTION_LIST_ITEM_URL}?item_id=${exceptionItemBody.item_id}`)
.set('kbn-xsrf', 'true')
.expect(200);
// NOTE: Existing comment is uploaded successfully
// however, the meta now reflects who imported it,
// not who created the initial comment
expect(exceptionItemFind2.comments).to.eql([
{
comment: 'this exception item rocks',
created_at: `${exceptionItemFind2.comments[0].created_at}`,
created_by: 'elastic',
id: `${exceptionItemFind2.comments[0].id}`,
},
]);
expect(exceptionItem.comments).to.eql([
{
comment: 'this exception item rocks',
created_at: `${exceptionItem.comments[0].created_at}`,
created_by: 'soc_manager',
id: `${exceptionItem.comments[0].id}`,
},
]);
await createRule(supertest, log, {
...getSimpleRule(),
exceptions_list: [
{
id: exceptionBody.id,
list_id: exceptionBody.list_id,
type: exceptionBody.type,
namespace_type: exceptionBody.namespace_type,
},
],
});
const { body } = await supertest
.post(`${DETECTION_ENGINE_RULES_URL}/_export`)
.set('kbn-xsrf', 'true')
.send()
.expect(200)
.parse(binaryToString);
await supertest
.post(`${DETECTION_ENGINE_RULES_URL}/_import?overwrite=true&overwrite_exceptions=true`)
.set('kbn-xsrf', 'true')
.attach('file', Buffer.from(body), 'rules.ndjson')
.expect(200);
const { body: exceptionItemFind2 } = await supertest
.get(`${EXCEPTION_LIST_ITEM_URL}?item_id=${exceptionItemBody.item_id}`)
.set('kbn-xsrf', 'true')
.expect(200);
// NOTE: Existing comment is uploaded successfully
// however, the meta now reflects who imported it,
// not who created the initial comment
expect(exceptionItemFind2.comments).to.eql([
{
comment: 'this exception item rocks',
created_at: `${exceptionItemFind2.comments[0].created_at}`,
created_by: 'elastic',
id: `${exceptionItemFind2.comments[0].id}`,
},
]);
});
});
describe('Detection Exception', () => {
/*
Following the release of version 8.7, this test can be considered as an evaluation of exporting
an outdated List Item. A notable distinction lies in the absence of the "expire_time" property
within the getCreateExceptionListMinimalSchemaMock, which allows for differentiation between older
and newer versions. The rationale behind this approach is the lack of version tracking for both List and Rule,
thereby enabling simulation of migration scenarios.
*/
it('should be able to reimport a rule referencing an old version of detection exception list with existing comments', async () => {
// create an exception list
const { body: exceptionBody } = await supertestWithoutAuth
.post(EXCEPTION_LIST_URL)
.auth(ROLES.soc_manager, 'changeme')
.set('kbn-xsrf', 'true')
.send(getCreateExceptionListDetectionSchemaMock())
.expect(200);
// create an exception list item
const { body: exceptionItemBody } = await supertestWithoutAuth
.post(EXCEPTION_LIST_ITEM_URL)
.auth(ROLES.soc_manager, 'changeme')
.set('kbn-xsrf', 'true')
.send({
...getCreateExceptionListItemMinimalSchemaMock(), // using Old version of Exception List
comments: [{ comment: 'this exception item rocks' }],
})
.expect(200);
const { body: exceptionItem } = await supertest
.get(`${EXCEPTION_LIST_ITEM_URL}?item_id=${exceptionItemBody.item_id}`)
.set('kbn-xsrf', 'true')
.expect(200);
expect(exceptionItem.comments).to.eql([
{
comment: 'this exception item rocks',
created_at: `${exceptionItem.comments[0].created_at}`,
created_by: 'soc_manager',
id: `${exceptionItem.comments[0].id}`,
},
]);
await createRule(supertest, log, {
...getSimpleRule(),
exceptions_list: [
{
id: exceptionBody.id,
list_id: exceptionBody.list_id,
type: exceptionBody.type,
namespace_type: exceptionBody.namespace_type,
},
],
});
const { body } = await supertest
.post(`${DETECTION_ENGINE_RULES_URL}/_export`)
.set('kbn-xsrf', 'true')
.send()
.expect(200)
.parse(binaryToString);
await supertest
.post(`${DETECTION_ENGINE_RULES_URL}/_import?overwrite=true&overwrite_exceptions=true`)
.set('kbn-xsrf', 'true')
.attach('file', Buffer.from(body), 'rules.ndjson')
.expect(200);
const { body: exceptionItemFind2 } = await supertest
.get(`${EXCEPTION_LIST_ITEM_URL}?item_id=${exceptionItemBody.item_id}`)
.set('kbn-xsrf', 'true')
.expect(200);
// NOTE: Existing comment is uploaded successfully
// however, the meta now reflects who imported it,
// not who created the initial comment
expect(exceptionItemFind2.comments).to.eql([
{
comment: 'this exception item rocks',
created_at: `${exceptionItemFind2.comments[0].created_at}`,
created_by: 'elastic',
id: `${exceptionItemFind2.comments[0].id}`,
},
]);
});
it('should be able to reimport a rule referencing a new version of detection exception list with existing comments', async () => {
// create an exception list
const { body: exceptionBody } = await supertestWithoutAuth
.post(EXCEPTION_LIST_URL)
.auth(ROLES.soc_manager, 'changeme')
.set('kbn-xsrf', 'true')
.send(getCreateExceptionListDetectionSchemaMock())
.expect(200);
// create an exception list item
const { body: exceptionItemBody } = await supertestWithoutAuth
.post(EXCEPTION_LIST_ITEM_URL)
.auth(ROLES.soc_manager, 'changeme')
.set('kbn-xsrf', 'true')
.send({
...getCreateExceptionListItemNewerVersionSchemaMock(), // using newer version of Exception List
comments: [{ comment: 'this exception item rocks' }],
})
.expect(200);
const { body: exceptionItem } = await supertest
.get(`${EXCEPTION_LIST_ITEM_URL}?item_id=${exceptionItemBody.item_id}`)
.set('kbn-xsrf', 'true')
.expect(200);
expect(exceptionItem.comments).to.eql([
{
comment: 'this exception item rocks',
created_at: `${exceptionItem.comments[0].created_at}`,
created_by: 'soc_manager',
id: `${exceptionItem.comments[0].id}`,
},
]);
await createRule(supertest, log, {
...getSimpleRule(),
exceptions_list: [
{
id: exceptionBody.id,
list_id: exceptionBody.list_id,
type: exceptionBody.type,
namespace_type: exceptionBody.namespace_type,
},
],
});
const { body } = await supertest
.post(`${DETECTION_ENGINE_RULES_URL}/_export`)
.set('kbn-xsrf', 'true')
.send()
.expect(200)
.parse(binaryToString);
await supertest
.post(`${DETECTION_ENGINE_RULES_URL}/_import?overwrite=true&overwrite_exceptions=true`)
.set('kbn-xsrf', 'true')
.attach('file', Buffer.from(body), 'rules.ndjson')
.expect(200);
const { body: exceptionItemFind2 } = await supertest
.get(`${EXCEPTION_LIST_ITEM_URL}?item_id=${exceptionItemBody.item_id}`)
.set('kbn-xsrf', 'true')
.expect(200);
// NOTE: Existing comment is uploaded successfully
// however, the meta now reflects who imported it,
// not who created the initial comment
expect(exceptionItemFind2.comments).to.eql([
{
comment: 'this exception item rocks',
created_at: `${exceptionItemFind2.comments[0].created_at}`,
created_by: 'elastic',
id: `${exceptionItemFind2.comments[0].id}`,
},
]);
});
});
});
};

View file

@ -15,6 +15,7 @@ import {
toNdJsonString,
getImportExceptionsListItemSchemaMock,
getImportExceptionsListSchemaMock,
getImportExceptionsListItemNewerVersionSchemaMock,
} from '@kbn/lists-plugin/common/schemas/request/import_exceptions_schema.mock';
import { ROLES } from '@kbn/security-solution-plugin/common/test';
import { FtrProviderContext } from '../../common/ftr_provider_context';
@ -1421,6 +1422,53 @@ export default ({ getService }: FtrProviderContext): void => {
await deleteAllExceptions(supertest, log);
});
/*
Following the release of version 8.7, this test can be considered as an evaluation of exporting
an outdated List Item. A notable distinction lies in the absence of the "expire_time" property
within the getCreateExceptionListMinimalSchemaMock, which allows for differentiation between older
and newer versions. The rationale behind this approach is the lack of version tracking for both List and Rule,
thereby enabling simulation of migration scenarios.
*/
it('should be able to import a rule and an old version exception list, then delete it successfully', async () => {
const simpleRule = getSimpleRule('rule-1');
// import old exception version
const { body } = await supertest
.post(`${DETECTION_ENGINE_RULES_URL}/_import`)
.set('kbn-xsrf', 'true')
.attach(
'file',
Buffer.from(
toNdJsonString([
simpleRule,
getImportExceptionsListSchemaMock('test_list_id'),
getImportExceptionsListItemSchemaMock('test_item_id', 'test_list_id'),
])
),
'rules.ndjson'
)
.expect(200);
expect(body).to.eql({
success: true,
success_count: 1,
rules_count: 1,
errors: [],
exceptions_errors: [],
exceptions_success: true,
exceptions_success_count: 1,
action_connectors_success: true,
action_connectors_success_count: 0,
action_connectors_errors: [],
action_connectors_warnings: [],
});
// delete the exception list item by its item_id
await supertest
.delete(`${EXCEPTION_LIST_ITEM_URL}?item_id=${'test_item_id'}`)
.set('kbn-xsrf', 'true')
.expect(200);
});
it('should be able to import a rule and an exception list', async () => {
const simpleRule = getSimpleRule('rule-1');
@ -1433,7 +1481,7 @@ export default ({ getService }: FtrProviderContext): void => {
toNdJsonString([
simpleRule,
getImportExceptionsListSchemaMock('test_list_id'),
getImportExceptionsListItemSchemaMock('test_item_id', 'test_list_id'),
getImportExceptionsListItemNewerVersionSchemaMock('test_item_id', 'test_list_id'),
])
),
'rules.ndjson'

View file

@ -9,7 +9,11 @@
import expect from '@kbn/expect';
import type { CreateExceptionListItemSchema } from '@kbn/securitysolution-io-ts-list-types';
import { EXCEPTION_LIST_ITEM_URL, EXCEPTION_LIST_URL } from '@kbn/securitysolution-list-constants';
import {
EXCEPTION_LIST_ITEM_URL,
EXCEPTION_LIST_URL,
LIST_URL,
} from '@kbn/securitysolution-list-constants';
import type {
RuleCreateProps,
EqlRuleCreateProps,
@ -18,7 +22,10 @@ import type {
ThresholdRuleCreateProps,
} from '@kbn/security-solution-plugin/common/detection_engine/rule_schema';
import { getCreateExceptionListItemMinimalSchemaMock } from '@kbn/lists-plugin/common/schemas/request/create_exception_list_item_schema.mock';
import { getCreateExceptionListMinimalSchemaMock } from '@kbn/lists-plugin/common/schemas/request/create_exception_list_schema.mock';
import {
getCreateExceptionListDetectionSchemaMock,
getCreateExceptionListMinimalSchemaMock,
} from '@kbn/lists-plugin/common/schemas/request/create_exception_list_schema.mock';
import { DETECTION_ENGINE_RULES_URL } from '@kbn/security-solution-plugin/common/constants';
import { ROLES } from '@kbn/security-solution-plugin/common/test';
@ -834,8 +841,176 @@ export default ({ getService }: FtrProviderContext) => {
const signalsOpen = await getOpenSignals(supertest, log, es, createdRule);
expect(signalsOpen.hits.hits.length).equal(0);
});
it('should Not allow deleting value list when there are references and ignoreReferences is false', async () => {
const valueListId = 'value-list-id';
await importFile(supertest, log, 'keyword', ['suricata-sensor-amsterdam'], valueListId);
const rule: QueryRuleCreateProps = {
...getSimpleRule(),
query: 'host.name: "suricata-sensor-amsterdam"',
};
await createRuleWithExceptionEntries(supertest, log, rule, [
[
{
field: 'host.name',
operator: 'included',
type: 'list',
list: {
id: valueListId,
type: 'keyword',
},
},
],
]);
const deleteReferences = false;
const ignoreReferences = false;
// Delete the value list
await supertest
.delete(
`${LIST_URL}?deleteReferences=${deleteReferences}&id=${valueListId}&ignoreReferences=${ignoreReferences}`
)
.set('kbn-xsrf', 'true')
.send()
.expect(409);
});
});
});
});
describe('Synchronizations', () => {
/*
This test to mimic if we have two browser tabs, and the user tried to
edit an exception in a tab after deleting it in another
*/
it('should Not edit an exception after being deleted', async () => {
const { list_id: skippedListId, ...newExceptionItem } =
getCreateExceptionListDetectionSchemaMock();
const {
body: { id, list_id, namespace_type, type },
} = await supertest
.post(EXCEPTION_LIST_URL)
.set('kbn-xsrf', 'true')
.send(newExceptionItem)
.expect(200);
const ruleWithException: RuleCreateProps = {
...getSimpleRule(),
exceptions_list: [
{
id,
list_id,
namespace_type,
type,
},
],
};
await createRule(supertest, log, ruleWithException);
// Delete the exception
await supertest
.delete(`${EXCEPTION_LIST_ITEM_URL}?id=${id}&namespace_type=single`)
.set('kbn-xsrf', 'true')
.send()
.expect(200);
// Edit after delete as if it was opened in another browser tab
const { body } = await supertest
.put(`${EXCEPTION_LIST_ITEM_URL}`)
.set('kbn-xsrf', 'true')
.send({
id: list_id,
item_id: id,
name: 'edit',
entries: [{ field: 'ss', operator: 'included', type: 'match', value: 'ss' }],
namespace_type,
description: 'Exception list item - Edit',
type: 'simple',
})
.expect(404);
expect(body).to.eql({
message: `exception list item id: "${list_id}" does not exist`,
status_code: 404,
});
});
/*
This test to mimic if we have two browser tabs, and the user tried to
edit an exception with value-list was deleted in another tab
*/
it('should Not allow editing an Exception with deleted ValueList', async () => {
await createListsIndex(supertest, log);
const valueListId = 'value-list-id';
await importFile(supertest, log, 'keyword', ['suricata-sensor-amsterdam'], valueListId);
const rule: QueryRuleCreateProps = {
...getSimpleRule(),
query: 'host.name: "suricata-sensor-amsterdam"',
};
const { exceptions_list: exceptionsList } = await createRuleWithExceptionEntries(
supertest,
log,
rule,
[
[
{
field: 'host.name',
operator: 'included',
type: 'list',
list: {
id: valueListId,
type: 'keyword',
},
},
],
]
);
const deleteReferences = false;
const ignoreReferences = true;
const { id, list_id, namespace_type } = exceptionsList[0];
// Delete the value list
await supertest
.delete(
`${LIST_URL}?deleteReferences=${deleteReferences}&id=${valueListId}&ignoreReferences=${ignoreReferences}`
)
.set('kbn-xsrf', 'true')
.send()
.expect(200);
// edit the exception with the deleted value list
await supertest
.put(`${EXCEPTION_LIST_ITEM_URL}`)
.set('kbn-xsrf', 'true')
.send({
id: list_id,
item_id: id,
name: 'edit',
entries: [
{
field: 'host.name',
operator: 'included',
type: 'list',
list: {
id: valueListId,
type: 'keyword',
},
},
],
namespace_type,
description: 'Exception list item - Edit',
type: 'simple',
})
.expect(404);
// expect(signalsOpen.hits.hits.length).equal(0);
// expect(body).to.eql({
// message: `exception list item id: "${list_id}" does not exist`,
// status_code: 404,
// });
await deleteListsIndex(supertest, log);
});
});
});
};

View file

@ -10,6 +10,6 @@ import { FtrProviderContext } from '../../common/ftr_provider_context';
// eslint-disable-next-line import/no-default-export
export default ({ loadTestFile }: FtrProviderContext): void => {
describe('detection engine api security and spaces enabled - Group 3', function () {
loadTestFile(require.resolve('./create_exceptions'));
loadTestFile(require.resolve('./exceptions_workflows'));
});
};

View file

@ -0,0 +1,129 @@
{
"type": "doc",
"value": {
"id": "_aZE5nwBOpWiDwiSth_F",
"index": "endpoint.alerts-cypress",
"source": {
"@timestamp": "2023-02-15T16:37:12.225Z",
"file": {
"Ext": {
"code_signature": {
"subject_name": "test",
"trusted": true
}
},
"path": "123",
"hash": {
"sha256": "test"
}
},
"host": {
"hostname": "test.local",
"architecture": "x86_64",
"os": {
"platform": "darwin",
"version": "10.16",
"family": "darwin",
"name": "Mac OS X",
"kernel": "21.3.0",
"build": "21D62",
"type": "macos"
},
"id": "44426D67-79AB-547C-7777-440AB8F5DDD2",
"ip": [
"fe80::bade:48ff:fe00:1122",
"fe81::4ab:9565:1199:be3",
"192.168.5.175",
"fe80::40d7:d0ff:fe66:f55",
"fe81::40d8:d0ff:fe66:f55",
"fe82::c2c:6bdf:3307:dce0",
"fe83::5069:fcd5:e31c:7059",
"fe80::ce81:b2c:bd2c:69e",
"fe80::febc:bbc1:c517:827b",
"fe80::6d09:bee6:55a5:539d",
"fe80::c920:752e:1e0e:edc9",
"fe80::a4a:ca38:761f:83e2"
],
"mac": [
"ad:df:48:00:11:22",
"a6:86:e7:ae:5a:b6",
"a9:83:e7:ae:5a:b6",
"43:d8:d0:66:0f:55",
"42:d8:d0:66:0f:57",
"82:70:c7:c2:3c:01",
"82:70:c6:c2:4c:00",
"82:76:a6:c2:3c:05",
"82:70:c6:b2:3c:04",
"82:71:a6:c2:3c:01"
],
"name": "siem-kibana"
},
"agent": {
"type": "auditbeat",
"version": "8.1.0",
"ephemeral_id": "f6df090f-656a-4a79-a6a1-0c8671c9752d",
"id": "0ebd469b-c164-4734-00e6-96d018098dc7",
"name": "test.local"
},
"event": {
"module": "endpoint",
"dataset": "process",
"kind": "alert",
"category": ["process"],
"type": ["start"],
"action": "process_started",
"code": "test"
},
"destination": {
"port": 80
},
"process": {
"start": "2022-03-04T19:41:32.902Z",
"pid": 30884,
"working_directory": "/Users/test/security_solution",
"hash": {
"sha1": "ae2d46c38fa207efbea5fcecd6294eebbf5af00f"
},
"parent": {
"pid": 777
},
"executable": "/bin/zsh",
"name": "zsh",
"args": ["-zsh"],
"entity_id": "q6pltOhTWlQx3BCD",
"entry_leader": {
"entity_id": "q6pltOhTWlQx3BCD",
"name": "fake entry",
"pid": 2342342
}
},
"message": "Process zsh (PID: 27884) by user test STARTED",
"user": {
"id": "505",
"group": {
"name": "staff",
"id": "20"
},
"effective": {
"id": "505",
"group": {
"id": "20"
}
},
"saved": {
"id": "505",
"group": {
"id": "20"
}
},
"name": "test"
},
"service": {
"type": "system"
},
"ecs": {
"version": "8.0.0"
}
}
}
}

View file

@ -0,0 +1,494 @@
{
"type": "index",
"value": {
"aliases": {
"endgame": {
"is_write_index": false
}
},
"settings": {
"index": {
"refresh_interval": "5s"
},
"index.mapping.total_fields.limit": 2000
},
"index": "endpoint.alerts-cypress",
"mappings": {
"properties": {
"@timestamp": {
"type": "date"
},
"agent": {
"properties": {
"ephemeral_id": {
"type": "text",
"fields": {
"keyword": {
"type": "keyword",
"ignore_above": 256
}
}
},
"id": {
"type": "text",
"fields": {
"keyword": {
"type": "keyword",
"ignore_above": 256
}
}
},
"name": {
"type": "text",
"fields": {
"keyword": {
"type": "keyword",
"ignore_above": 256
}
}
},
"type": {
"type": "text",
"fields": {
"keyword": {
"type": "keyword",
"ignore_above": 256
}
}
},
"version": {
"type": "text",
"fields": {
"keyword": {
"type": "keyword",
"ignore_above": 256
}
}
}
}
},
"destination": {
"properties": {
"port": {
"type": "long"
}
}
},
"ecs": {
"properties": {
"version": {
"type": "text",
"fields": {
"keyword": {
"type": "keyword",
"ignore_above": 256
}
}
}
}
},
"event": {
"properties": {
"action": {
"type": "text",
"fields": {
"keyword": {
"type": "keyword",
"ignore_above": 256
}
}
},
"category": {
"type": "text",
"fields": {
"keyword": {
"type": "keyword",
"ignore_above": 256
}
}
},
"dataset": {
"type": "text",
"fields": {
"keyword": {
"type": "keyword",
"ignore_above": 256
}
}
},
"kind": {
"type": "text",
"fields": {
"keyword": {
"type": "keyword",
"ignore_above": 256
}
}
},
"module": {
"type": "text",
"fields": {
"keyword": {
"type": "keyword",
"ignore_above": 256
}
}
},
"type": {
"type": "text",
"fields": {
"keyword": {
"type": "keyword",
"ignore_above": 256
}
}
}
}
},
"host": {
"properties": {
"architecture": {
"type": "text",
"fields": {
"keyword": {
"type": "keyword",
"ignore_above": 256
}
}
},
"hostname": {
"type": "text",
"fields": {
"keyword": {
"type": "keyword",
"ignore_above": 256
}
}
},
"id": {
"type": "text",
"fields": {
"keyword": {
"type": "keyword",
"ignore_above": 256
}
}
},
"ip": {
"type": "text",
"fields": {
"keyword": {
"type": "keyword",
"ignore_above": 256
}
}
},
"mac": {
"type": "text",
"fields": {
"keyword": {
"type": "keyword",
"ignore_above": 256
}
}
},
"name": {
"type": "text",
"fields": {
"keyword": {
"type": "keyword",
"ignore_above": 256
}
}
},
"os": {
"properties": {
"build": {
"type": "text",
"fields": {
"keyword": {
"type": "keyword",
"ignore_above": 256
}
}
},
"family": {
"type": "text",
"fields": {
"keyword": {
"type": "keyword",
"ignore_above": 256
}
}
},
"kernel": {
"type": "text",
"fields": {
"keyword": {
"type": "keyword",
"ignore_above": 256
}
}
},
"name": {
"type": "text",
"fields": {
"keyword": {
"type": "keyword",
"ignore_above": 256
}
}
},
"platform": {
"type": "text",
"fields": {
"keyword": {
"type": "keyword",
"ignore_above": 256
}
}
},
"type": {
"type": "text",
"fields": {
"keyword": {
"type": "keyword",
"ignore_above": 256
}
}
},
"version": {
"type": "text",
"fields": {
"keyword": {
"type": "keyword",
"ignore_above": 256
}
}
}
}
}
}
},
"message": {
"type": "text",
"fields": {
"keyword": {
"type": "keyword",
"ignore_above": 256
}
}
},
"process": {
"properties": {
"args": {
"type": "text",
"fields": {
"keyword": {
"type": "keyword",
"ignore_above": 256
}
}
},
"entity_id": {
"type": "text",
"fields": {
"keyword": {
"type": "keyword",
"ignore_above": 256
}
}
},
"entry_leader": {
"properties": {
"entity_id": {
"type": "text",
"fields": {
"keyword": {
"type": "keyword",
"ignore_above": 256
}
}
},
"name": {
"type": "text",
"fields": {
"keyword": {
"type": "keyword",
"ignore_above": 256
}
}
},
"pid": {
"type": "long"
}
}
},
"executable": {
"type": "text",
"fields": {
"keyword": {
"type": "keyword",
"ignore_above": 256
}
}
},
"hash": {
"properties": {
"sha1": {
"type": "text",
"fields": {
"keyword": {
"type": "keyword",
"ignore_above": 256
}
}
}
}
},
"name": {
"type": "text",
"fields": {
"keyword": {
"type": "keyword",
"ignore_above": 256
}
}
},
"parent": {
"properties": {
"pid": {
"type": "long"
}
}
},
"pid": {
"type": "long"
},
"start": {
"type": "date"
},
"working_directory": {
"type": "text",
"fields": {
"keyword": {
"type": "keyword",
"ignore_above": 256
}
}
}
}
},
"service": {
"properties": {
"type": {
"type": "text",
"fields": {
"keyword": {
"type": "keyword",
"ignore_above": 256
}
}
}
}
},
"user": {
"properties": {
"effective": {
"properties": {
"group": {
"properties": {
"id": {
"type": "text",
"fields": {
"keyword": {
"type": "keyword",
"ignore_above": 256
}
}
}
}
},
"id": {
"type": "text",
"fields": {
"keyword": {
"type": "keyword",
"ignore_above": 256
}
}
}
}
},
"group": {
"properties": {
"id": {
"type": "text",
"fields": {
"keyword": {
"type": "keyword",
"ignore_above": 256
}
}
},
"name": {
"type": "text",
"fields": {
"keyword": {
"type": "keyword",
"ignore_above": 256
}
}
}
}
},
"id": {
"type": "text",
"fields": {
"keyword": {
"type": "keyword",
"ignore_above": 256
}
}
},
"name": {
"type": "text",
"fields": {
"keyword": {
"type": "keyword",
"ignore_above": 256
}
}
},
"saved": {
"properties": {
"group": {
"properties": {
"id": {
"type": "text",
"fields": {
"keyword": {
"type": "keyword",
"ignore_above": 256
}
}
}
}
},
"id": {
"type": "text",
"fields": {
"keyword": {
"type": "keyword",
"ignore_above": 256
}
}
}
}
}
}
}
}
}
}
}

View file

@ -0,0 +1,129 @@
{
"type": "doc",
"value": {
"id": "_wZE5nwBOpWiDwiSth_W",
"index": "endpoint.alerts-cypress-1",
"source": {
"@timestamp": "2023-02-15T16:37:12.225Z",
"file": {
"Ext": {
"code_signature": {
"subject_name": "test",
"trusted": true
}
},
"path": "123",
"hash": {
"sha256": "test"
}
},
"host": {
"hostname": "test.local",
"architecture": "x86_64",
"os": {
"platform": "darwin",
"version": "10.16",
"family": "darwin",
"name": "Mac OS X",
"kernel": "21.3.0",
"build": "21D62",
"type": "macos"
},
"id": "44426D67-79AB-547C-7777-440AB8F5DDD2",
"ip": [
"fe80::bade:48ff:fe00:1122",
"fe81::4ab:9565:1199:be3",
"192.168.5.175",
"fe80::40d7:d0ff:fe66:f55",
"fe81::40d8:d0ff:fe66:f55",
"fe82::c2c:6bdf:3307:dce0",
"fe83::5069:fcd5:e31c:7059",
"fe80::ce81:b2c:bd2c:69e",
"fe80::febc:bbc1:c517:827b",
"fe80::6d09:bee6:55a5:539d",
"fe80::c920:752e:1e0e:edc9",
"fe80::a4a:ca38:761f:83e2"
],
"mac": [
"ad:df:48:00:11:22",
"a6:86:e7:ae:5a:b6",
"a9:83:e7:ae:5a:b6",
"43:d8:d0:66:0f:55",
"42:d8:d0:66:0f:57",
"82:70:c7:c2:3c:01",
"82:70:c6:c2:4c:00",
"82:76:a6:c2:3c:05",
"82:70:c6:b2:3c:04",
"82:71:a6:c2:3c:01"
],
"name": "siem-kibana"
},
"agent": {
"type": "auditbeat",
"version": "8.1.0",
"ephemeral_id": "f6df090f-656a-4a79-a6a1-0c8671c9752d",
"id": "0ebd469b-c164-4734-00e6-96d018098dc7",
"name": "test.local"
},
"event": {
"module": "endpoint",
"dataset": "process",
"kind": "alert",
"category": ["process"],
"type": ["start"],
"action": "process_started",
"code": "test"
},
"destination": {
"port": 80
},
"process": {
"start": "2022-03-04T19:41:32.902Z",
"pid": 30884,
"working_directory": "/Users/test/security_solution",
"hash": {
"sha1": "ae2d46c38fa207efbea5fcecd6294eebbf5af00f"
},
"parent": {
"pid": 777
},
"executable": "/bin/zsh",
"name": "zsh",
"args": ["-zsh"],
"entity_id": "q6pltOhTWlQx3BCD",
"entry_leader": {
"entity_id": "q6pltOhTWlQx3BCD",
"name": "fake entry",
"pid": 2342342
}
},
"message": "Process zsh (PID: 27884) by user test STARTED",
"user": {
"id": "505",
"group": {
"name": "staff",
"id": "20"
},
"effective": {
"id": "505",
"group": {
"id": "20"
}
},
"saved": {
"id": "505",
"group": {
"id": "20"
}
},
"name": "test"
},
"service": {
"type": "system"
},
"ecs": {
"version": "8.0.0"
}
}
}
}

View file

@ -0,0 +1,494 @@
{
"type": "index",
"value": {
"aliases": {
"endgame": {
"is_write_index": false
}
},
"settings": {
"index": {
"refresh_interval": "5s"
},
"index.mapping.total_fields.limit": 2000
},
"index": "endpoint.alerts-cypress-1",
"mappings": {
"properties": {
"@timestamp": {
"type": "date"
},
"agent": {
"properties": {
"ephemeral_id": {
"type": "text",
"fields": {
"keyword": {
"type": "keyword",
"ignore_above": 256
}
}
},
"id": {
"type": "text",
"fields": {
"keyword": {
"type": "keyword",
"ignore_above": 256
}
}
},
"name": {
"type": "text",
"fields": {
"keyword": {
"type": "keyword",
"ignore_above": 256
}
}
},
"type": {
"type": "text",
"fields": {
"keyword": {
"type": "keyword",
"ignore_above": 256
}
}
},
"version": {
"type": "text",
"fields": {
"keyword": {
"type": "keyword",
"ignore_above": 256
}
}
}
}
},
"destination": {
"properties": {
"port": {
"type": "long"
}
}
},
"ecs": {
"properties": {
"version": {
"type": "text",
"fields": {
"keyword": {
"type": "keyword",
"ignore_above": 256
}
}
}
}
},
"event": {
"properties": {
"action": {
"type": "text",
"fields": {
"keyword": {
"type": "keyword",
"ignore_above": 256
}
}
},
"category": {
"type": "text",
"fields": {
"keyword": {
"type": "keyword",
"ignore_above": 256
}
}
},
"dataset": {
"type": "text",
"fields": {
"keyword": {
"type": "keyword",
"ignore_above": 256
}
}
},
"kind": {
"type": "text",
"fields": {
"keyword": {
"type": "keyword",
"ignore_above": 256
}
}
},
"module": {
"type": "text",
"fields": {
"keyword": {
"type": "keyword",
"ignore_above": 256
}
}
},
"type": {
"type": "text",
"fields": {
"keyword": {
"type": "keyword",
"ignore_above": 256
}
}
}
}
},
"host": {
"properties": {
"architecture": {
"type": "text",
"fields": {
"keyword": {
"type": "keyword",
"ignore_above": 256
}
}
},
"hostname": {
"type": "text",
"fields": {
"keyword": {
"type": "keyword",
"ignore_above": 256
}
}
},
"id": {
"type": "text",
"fields": {
"keyword": {
"type": "keyword",
"ignore_above": 256
}
}
},
"ip": {
"type": "text",
"fields": {
"keyword": {
"type": "keyword",
"ignore_above": 256
}
}
},
"mac": {
"type": "text",
"fields": {
"keyword": {
"type": "keyword",
"ignore_above": 256
}
}
},
"name": {
"type": "text",
"fields": {
"keyword": {
"type": "keyword",
"ignore_above": 256
}
}
},
"os": {
"properties": {
"build": {
"type": "text",
"fields": {
"keyword": {
"type": "keyword",
"ignore_above": 256
}
}
},
"family": {
"type": "text",
"fields": {
"keyword": {
"type": "keyword",
"ignore_above": 256
}
}
},
"kernel": {
"type": "text",
"fields": {
"keyword": {
"type": "keyword",
"ignore_above": 256
}
}
},
"name": {
"type": "text",
"fields": {
"keyword": {
"type": "keyword",
"ignore_above": 256
}
}
},
"platform": {
"type": "text",
"fields": {
"keyword": {
"type": "keyword",
"ignore_above": 256
}
}
},
"type": {
"type": "text",
"fields": {
"keyword": {
"type": "keyword",
"ignore_above": 256
}
}
},
"version": {
"type": "text",
"fields": {
"keyword": {
"type": "keyword",
"ignore_above": 256
}
}
}
}
}
}
},
"message": {
"type": "text",
"fields": {
"keyword": {
"type": "keyword",
"ignore_above": 256
}
}
},
"process": {
"properties": {
"args": {
"type": "text",
"fields": {
"keyword": {
"type": "keyword",
"ignore_above": 256
}
}
},
"entity_id": {
"type": "text",
"fields": {
"keyword": {
"type": "keyword",
"ignore_above": 256
}
}
},
"entry_leader": {
"properties": {
"entity_id": {
"type": "text",
"fields": {
"keyword": {
"type": "keyword",
"ignore_above": 256
}
}
},
"name": {
"type": "text",
"fields": {
"keyword": {
"type": "keyword",
"ignore_above": 256
}
}
},
"pid": {
"type": "long"
}
}
},
"executable": {
"type": "text",
"fields": {
"keyword": {
"type": "keyword",
"ignore_above": 256
}
}
},
"hash": {
"properties": {
"sha1": {
"type": "text",
"fields": {
"keyword": {
"type": "keyword",
"ignore_above": 256
}
}
}
}
},
"name": {
"type": "text",
"fields": {
"keyword": {
"type": "keyword",
"ignore_above": 256
}
}
},
"parent": {
"properties": {
"pid": {
"type": "long"
}
}
},
"pid": {
"type": "long"
},
"start": {
"type": "date"
},
"working_directory": {
"type": "text",
"fields": {
"keyword": {
"type": "keyword",
"ignore_above": 256
}
}
}
}
},
"service": {
"properties": {
"type": {
"type": "text",
"fields": {
"keyword": {
"type": "keyword",
"ignore_above": 256
}
}
}
}
},
"user": {
"properties": {
"effective": {
"properties": {
"group": {
"properties": {
"id": {
"type": "text",
"fields": {
"keyword": {
"type": "keyword",
"ignore_above": 256
}
}
}
}
},
"id": {
"type": "text",
"fields": {
"keyword": {
"type": "keyword",
"ignore_above": 256
}
}
}
}
},
"group": {
"properties": {
"id": {
"type": "text",
"fields": {
"keyword": {
"type": "keyword",
"ignore_above": 256
}
}
},
"name": {
"type": "text",
"fields": {
"keyword": {
"type": "keyword",
"ignore_above": 256
}
}
}
}
},
"id": {
"type": "text",
"fields": {
"keyword": {
"type": "keyword",
"ignore_above": 256
}
}
},
"name": {
"type": "text",
"fields": {
"keyword": {
"type": "keyword",
"ignore_above": 256
}
}
},
"saved": {
"properties": {
"group": {
"properties": {
"id": {
"type": "text",
"fields": {
"keyword": {
"type": "keyword",
"ignore_above": 256
}
}
}
}
},
"id": {
"type": "text",
"fields": {
"keyword": {
"type": "keyword",
"ignore_above": 256
}
}
}
}
}
}
}
}
}
}
}