mirror of
https://github.com/elastic/kibana.git
synced 2025-04-24 01:38:56 -04:00
[DE][Exceptions] Allow numerous match_any values that differ in case (#167208)
## Summary Updates the exceptions flyout UI `match_any` operator to accept numerous duplicate values that differ in case. Prior to this change, a user could not add a field value of `foo` and `FOO` - the UI would display that the value is a duplicate. We now will allow this as exceptions are case sensitive and this is a necessary use case for the current exceptions behavior. Cypress tests and FTR tests are added.
This commit is contained in:
parent
8a29a5e2ca
commit
e9d2e782b5
9 changed files with 248 additions and 12 deletions
|
@ -184,6 +184,7 @@ export const AutocompleteFieldMatchAnyComponent: React.FC<AutocompleteFieldMatch
|
|||
onSearchChange={handleSearchChange}
|
||||
onCreateOption={handleCreateOption}
|
||||
isInvalid={selectedField != null && error != null}
|
||||
isCaseSensitive
|
||||
onBlur={setIsTouchedValue}
|
||||
data-test-subj="valuesAutocompleteMatchAny"
|
||||
fullWidth
|
||||
|
|
|
@ -620,6 +620,61 @@ export default ({ getService }: FtrProviderContext) => {
|
|||
expect(signalsOpen.hits.hits.length).toEqual(0);
|
||||
});
|
||||
|
||||
it('should be able to execute against an exception list that does include valid case sensitive entries and get back 0 signals', async () => {
|
||||
const rule: QueryRuleCreateProps = {
|
||||
name: 'Simple Rule Query',
|
||||
description: 'Simple Rule Query',
|
||||
enabled: true,
|
||||
risk_score: 1,
|
||||
rule_id: 'rule-1',
|
||||
severity: 'high',
|
||||
index: ['auditbeat-*'],
|
||||
type: 'query',
|
||||
from: '1900-01-01T00:00:00.000Z',
|
||||
query: 'host.name: "suricata-sensor-amsterdam"',
|
||||
};
|
||||
const rule2: QueryRuleCreateProps = {
|
||||
name: 'Simple Rule Query',
|
||||
description: 'Simple Rule Query',
|
||||
enabled: true,
|
||||
risk_score: 1,
|
||||
rule_id: 'rule-2',
|
||||
severity: 'high',
|
||||
index: ['auditbeat-*'],
|
||||
type: 'query',
|
||||
from: '1900-01-01T00:00:00.000Z',
|
||||
query: 'host.name: "suricata-sensor-amsterdam"',
|
||||
};
|
||||
const createdRule = await createRuleWithExceptionEntries(supertest, log, rule, [
|
||||
[
|
||||
{
|
||||
field: 'host.os.name',
|
||||
operator: 'included',
|
||||
type: 'match_any',
|
||||
value: ['ubuntu'],
|
||||
},
|
||||
],
|
||||
]);
|
||||
const createdRule2 = await createRuleWithExceptionEntries(supertest, log, rule2, [
|
||||
[
|
||||
{
|
||||
field: 'host.os.name', // This matches the query above which will exclude everything
|
||||
operator: 'included',
|
||||
type: 'match_any',
|
||||
value: ['ubuntu', 'Ubuntu'],
|
||||
},
|
||||
],
|
||||
]);
|
||||
const signalsOpen = await getOpenSignals(supertest, log, es, createdRule);
|
||||
const signalsOpen2 = await getOpenSignals(supertest, log, es, createdRule2);
|
||||
// Expect signals here because all values are "Ubuntu"
|
||||
// and exception is one of ["ubuntu"]
|
||||
expect(signalsOpen.hits.hits.length).toEqual(10);
|
||||
// Expect no signals here because all values are "Ubuntu"
|
||||
// and exception is one of ["ubuntu", "Ubuntu"]
|
||||
expect(signalsOpen2.hits.hits.length).toEqual(0);
|
||||
});
|
||||
|
||||
it('generates no signals when an exception is added for an EQL rule', async () => {
|
||||
const rule: EqlRuleCreateProps = {
|
||||
...getEqlRuleForSignalTesting(['auditbeat-*']),
|
||||
|
|
|
@ -67,6 +67,37 @@ export default ({ getService }: FtrProviderContext) => {
|
|||
expect(bodyToCompare).to.eql(getExceptionListItemResponseMockWithoutAutoGeneratedValues());
|
||||
});
|
||||
|
||||
it('should create a match any exception item with multiple case sensitive values', async () => {
|
||||
const entries = [
|
||||
{
|
||||
field: 'agent.name',
|
||||
operator: 'included',
|
||||
type: 'match_any',
|
||||
value: ['dll', 'DLL'],
|
||||
},
|
||||
];
|
||||
await supertest
|
||||
.post(EXCEPTION_LIST_URL)
|
||||
.set('kbn-xsrf', 'true')
|
||||
.send(getCreateExceptionListMinimalSchemaMock())
|
||||
.expect(200);
|
||||
|
||||
const { body } = await supertest
|
||||
.post(EXCEPTION_LIST_ITEM_URL)
|
||||
.set('kbn-xsrf', 'true')
|
||||
.send({
|
||||
...getCreateExceptionListItemMinimalSchemaMock(),
|
||||
entries,
|
||||
})
|
||||
.expect(200);
|
||||
|
||||
const bodyToCompare = removeExceptionListItemServerGeneratedProperties(body);
|
||||
expect(bodyToCompare).to.eql({
|
||||
...getExceptionListItemResponseMockWithoutAutoGeneratedValues(),
|
||||
entries,
|
||||
});
|
||||
});
|
||||
|
||||
it('should create a simple exception list item without an id', async () => {
|
||||
await supertest
|
||||
.post(EXCEPTION_LIST_URL)
|
||||
|
|
|
@ -125,7 +125,7 @@ describe.skip('Exceptions flyout', { tags: ['@ess', '@serverless', '@skipInServe
|
|||
cy.get(CONFIRM_BTN).should('be.disabled');
|
||||
|
||||
// add value again and button should be enabled again
|
||||
addExceptionEntryFieldMatchAnyValue('test', 0);
|
||||
addExceptionEntryFieldMatchAnyValue(['test'], 0);
|
||||
cy.get(CONFIRM_BTN).should('be.enabled');
|
||||
});
|
||||
|
||||
|
|
|
@ -0,0 +1,89 @@
|
|||
/*
|
||||
* 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 { RULE_STATUS } from '../../../screens/create_new_rule';
|
||||
|
||||
import { createRule } from '../../../tasks/api_calls/rules';
|
||||
import { login } from '../../../tasks/login';
|
||||
import {
|
||||
openExceptionFlyoutFromEmptyViewerPrompt,
|
||||
visitRuleDetailsPage,
|
||||
enablesRule,
|
||||
waitForTheRuleToBeExecuted,
|
||||
goToAlertsTab,
|
||||
} from '../../../tasks/rule_details';
|
||||
import {
|
||||
addExceptionEntryFieldMatchAnyValue,
|
||||
addExceptionEntryFieldValue,
|
||||
addExceptionEntryOperatorValue,
|
||||
addExceptionFlyoutItemName,
|
||||
submitNewExceptionItem,
|
||||
} from '../../../tasks/exceptions';
|
||||
import { CONFIRM_BTN } from '../../../screens/exceptions';
|
||||
import { deleteAlertsAndRules } from '../../../tasks/common';
|
||||
import { ALERTS_COUNT } from '../../../screens/alerts';
|
||||
import { waitForAlertsToPopulate } from '../../../tasks/create_new_rule';
|
||||
|
||||
describe('Exceptions match_any', { tags: ['@ess', '@serverless'] }, () => {
|
||||
before(() => {
|
||||
// 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
|
||||
cy.task('esArchiverLoad', { archiveName: 'exceptions' });
|
||||
});
|
||||
|
||||
beforeEach(() => {
|
||||
deleteAlertsAndRules();
|
||||
login();
|
||||
createRule(
|
||||
getNewRule({
|
||||
index: ['exceptions-*'],
|
||||
enabled: false,
|
||||
query: '*',
|
||||
from: 'now-438300h',
|
||||
})
|
||||
).then((rule) => visitRuleDetailsPage(rule.body.id, { tab: 'rule_exceptions' }));
|
||||
cy.get(RULE_STATUS).should('have.text', '—');
|
||||
});
|
||||
|
||||
after(() => {
|
||||
cy.task('esArchiverUnload', 'exceptions');
|
||||
});
|
||||
|
||||
it('Creates exception item', () => {
|
||||
cy.log('open add exception modal');
|
||||
openExceptionFlyoutFromEmptyViewerPrompt();
|
||||
|
||||
cy.log('add exception item name');
|
||||
addExceptionFlyoutItemName('My item name');
|
||||
|
||||
cy.log('add match_any entry');
|
||||
addExceptionEntryFieldValue('agent.name', 0);
|
||||
// Asserting double negative because it is easier to check
|
||||
// that an alert was created than that it was NOT (as if it is not
|
||||
// it could be for other reasons, like rule failure)
|
||||
addExceptionEntryOperatorValue('is not one of', 0);
|
||||
addExceptionEntryFieldMatchAnyValue(['foo', 'FOO'], 0);
|
||||
cy.get(CONFIRM_BTN).should('be.enabled');
|
||||
submitNewExceptionItem();
|
||||
|
||||
enablesRule();
|
||||
|
||||
goToAlertsTab();
|
||||
|
||||
waitForTheRuleToBeExecuted();
|
||||
waitForAlertsToPopulate();
|
||||
|
||||
// Will match document with value "foo" and document with value "FOO"
|
||||
cy.log('Asserting that alert is generated');
|
||||
cy.get(ALERTS_COUNT)
|
||||
.invoke('text')
|
||||
.should('match', /^[2].+$/);
|
||||
});
|
||||
});
|
|
@ -63,7 +63,7 @@ describe(
|
|||
'Add/edit exception from rule details',
|
||||
{ tags: ['@ess', '@serverless', '@brokenInServerless'] },
|
||||
() => {
|
||||
const NUMBER_OF_AUDITBEAT_EXCEPTIONS_ALERTS = '1 alert';
|
||||
const NUMBER_OF_AUDITBEAT_EXCEPTIONS_ALERTS = '3 alerts';
|
||||
const FIELD_DIFFERENT_FROM_EXISTING_ITEM_FIELD = 'agent.name';
|
||||
const ITEM_FIELD = 'unique_value.test';
|
||||
|
||||
|
@ -276,8 +276,8 @@ describe(
|
|||
// add exception item conditions
|
||||
addExceptionConditions({
|
||||
field: 'agent.name',
|
||||
operator: 'is',
|
||||
values: ['foo'],
|
||||
operator: 'is one of',
|
||||
values: ['foo', 'FOO', 'bar'],
|
||||
});
|
||||
|
||||
// Name is required so want to check that submit is still disabled
|
||||
|
|
|
@ -45,7 +45,7 @@ describe(
|
|||
'Add exception using data views from rule details',
|
||||
{ tags: ['@ess', '@serverless', '@brokenInServerless'] },
|
||||
() => {
|
||||
const NUMBER_OF_AUDITBEAT_EXCEPTIONS_ALERTS = '1 alert';
|
||||
const NUMBER_OF_AUDITBEAT_EXCEPTIONS_ALERTS = '3 alerts';
|
||||
const ITEM_NAME = 'Sample Exception List Item';
|
||||
|
||||
before(() => {
|
||||
|
@ -87,8 +87,8 @@ describe(
|
|||
addFirstExceptionFromRuleDetails(
|
||||
{
|
||||
field: 'agent.name',
|
||||
operator: 'is',
|
||||
values: ['foo'],
|
||||
operator: 'is one of',
|
||||
values: ['foo', 'FOO', 'bar'],
|
||||
},
|
||||
ITEM_NAME
|
||||
);
|
||||
|
|
|
@ -114,8 +114,10 @@ export const addExceptionEntryFieldValueValue = (value: string, index = 0) => {
|
|||
cy.get(EXCEPTION_FLYOUT_TITLE).click();
|
||||
};
|
||||
|
||||
export const addExceptionEntryFieldMatchAnyValue = (value: string, index = 0) => {
|
||||
cy.get(VALUES_MATCH_ANY_INPUT).eq(index).type(`${value}{enter}`);
|
||||
export const addExceptionEntryFieldMatchAnyValue = (values: string[], index = 0) => {
|
||||
values.forEach((value) => {
|
||||
cy.get(VALUES_MATCH_ANY_INPUT).eq(index).type(`${value}{enter}`);
|
||||
});
|
||||
cy.get(EXCEPTION_FLYOUT_TITLE).click();
|
||||
};
|
||||
export const addExceptionEntryFieldMatchIncludedValue = (value: string, index = 0) => {
|
||||
|
@ -164,9 +166,13 @@ export const selectCloseSingleAlerts = () => {
|
|||
export const addExceptionConditions = (exception: Exception) => {
|
||||
cy.get(FIELD_INPUT).type(`${exception.field}{downArrow}{enter}`);
|
||||
cy.get(OPERATOR_INPUT).type(`${exception.operator}{enter}`);
|
||||
exception.values.forEach((value) => {
|
||||
cy.get(VALUES_INPUT).type(`${value}{enter}`);
|
||||
});
|
||||
if (exception.operator === 'is one of') {
|
||||
addExceptionEntryFieldMatchAnyValue(exception.values, 0);
|
||||
} else {
|
||||
exception.values.forEach((value) => {
|
||||
cy.get(VALUES_INPUT).type(`${value}{enter}`);
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
export const validateExceptionConditionField = (value: string) => {
|
||||
|
|
|
@ -24,3 +24,57 @@
|
|||
}
|
||||
}
|
||||
}
|
||||
|
||||
{
|
||||
"type": "doc",
|
||||
"value": {
|
||||
"id": "_aZE5nwBOpWiDweSth_A",
|
||||
"index": "exceptions-0001",
|
||||
"source": {
|
||||
"@timestamp": "2019-09-01T00:41:04.527Z",
|
||||
"agent": {
|
||||
"name": "bar"
|
||||
},
|
||||
"unique_value": {
|
||||
"test": "another value"
|
||||
},
|
||||
"user" : [
|
||||
{
|
||||
"name" : "john",
|
||||
"id" : "c5baec68-e774-46dc-b728-417e71d68444"
|
||||
},
|
||||
{
|
||||
"name" : "alice",
|
||||
"id" : "6e831997-deab-4e56-9218-a90ef045556e"
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
{
|
||||
"type": "doc",
|
||||
"value": {
|
||||
"id": "_aZE5nwBOpWiDweSth_C",
|
||||
"index": "exceptions-0001",
|
||||
"source": {
|
||||
"@timestamp": "2019-09-01T00:41:09.527Z",
|
||||
"agent": {
|
||||
"name": "FOO"
|
||||
},
|
||||
"unique_value": {
|
||||
"test": "different value"
|
||||
},
|
||||
"user" : [
|
||||
{
|
||||
"name" : "john",
|
||||
"id" : "c5baec68-e774-46dc-b728-417e71d68444"
|
||||
},
|
||||
{
|
||||
"name" : "alice",
|
||||
"id" : "6e831997-deab-4e56-9218-a90ef045556e"
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue