[Security Solution][Admin][Policy][Event Filters] Update event filters creation to include more match options (#170495)

## Summary

- [x] Adds a `matches` and `does not match` operator option to all
eligible event filter creation entry fields that support matches
- [x] Updates the existing warning to only appear if that is the
`file.path.text` entry field is selected
- [x] Generalize the warning for wildcard usage if operator is matches
and a wildcard is used in the value
- [x] Updates wildcard warning tooltip to include "Creating event
filters with both `matches` and `does not match` operators may
significantly decrease performance."
- [x] Unit tests

# Screenshots

![efmatch](9efad4f7-e49f-4542-9052-08e578ea4f6b)

Warning about wildcards affecting Endpoint performance
<img width="1219" alt="image"
src="5bceec10-6387-44d5-bc7f-76de1816ce46">


# Event Filter & Artifact 
**LINUX**

![image](4bba92ff-965a-47d9-b2e8-0a94b322acd8)
<details open>
<summary> linux artifact entry </summary>
<p>

```
{
  "entries": [
    {
      "type": "simple",
      "entries": [
        {
          "field": "event.category",
          "operator": "included",
          "type": "wildcard_cased",
          "value": "network"
        },
        {
          "field": "process.name",
          "operator": "included",
          "type": "exact_cased",
          "value": "network"
        }
      ]
    }
  ]
}
```
</p>
</details open>

**WINDOWS**

![image](e44020c7-0701-482a-bb74-6a1150b5552c)
<details open>
<summary> windows artifact entry </summary>
<p>

```
{
  "entries": [
    {
      "type": "simple",
      "entries": [
        {
          "field": "event.kind",
          "operator": "included",
          "type": "wildcard_cased",
          "value": "event"
        },
        {
          "field": "process.name",
          "operator": "included",
          "type": "exact_caseless",
          "value": "event"
        },
        {
          "field": "event.category",
          "operator": "included",
          "type": "wildcard_cased",
          "value": "authentication"
        },
        {
          "field": "process.name",
          "operator": "included",
          "type": "exact_caseless",
          "value": "authentication"
        }
      ]
    }
  ]
}
```
</p>
</details open>

**MAC**

![image](9c1782f8-2386-4cf0-8236-fa613bb6f9ee)

<details open>
<summary> mac artifact entry</summary>
<p>

```
{
  "entries": [
    {
      "type": "simple",
      "entries": [
        {
          "field": "event.id",
          "operator": "included",
          "type": "wildcard_cased",
          "value": "071e1cfc-8333-4c6c-965a-00678c7b1d61"
        },
        {
          "field": "process.name",
          "operator": "included",
          "type": "exact_caseless",
          "value": "071e1cfc-8333-4c6c-965a-00678c7b1d61"
        },
        {
          "field": "file.path",
          "operator": "included",
          "type": "wildcard_cased",
          "value": "C:\\My Documents\\business\\January\\processName"
        },
        {
          "field": "process.name",
          "operator": "included",
          "type": "exact_caseless",
          "value": "C:\\My Documents\\business\\January\\processName"
        }
      ]
    }
  ]
}
```
</p>
</details open>
This commit is contained in:
Candace Park 2023-11-27 02:39:44 -05:00 committed by GitHub
parent 7f61770f44
commit 69b2cd2b38
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
13 changed files with 106 additions and 41 deletions

View file

@ -14,7 +14,7 @@ import { AutocompleteFieldWildcardComponent } from '.';
import { useFieldValueAutocomplete } from '../hooks/use_field_value_autocomplete';
import { fields, getField } from '../fields/index.mock';
import { autocompleteStartMock } from '../autocomplete/index.mock';
import { FILENAME_WILDCARD_WARNING, FILEPATH_WARNING } from '@kbn/securitysolution-utils';
import { WILDCARD_WARNING, FILEPATH_WARNING } from '@kbn/securitysolution-utils';
jest.mock('../hooks/use_field_value_autocomplete');
jest.mock('../translations', () => ({
@ -368,7 +368,7 @@ describe('AutocompleteFieldWildcardComponent', () => {
placeholder="Placeholder text"
selectedField={getField('file.path.text')}
selectedValue="invalid path"
warning={FILENAME_WILDCARD_WARNING}
warning={WILDCARD_WARNING}
/>
);
@ -384,7 +384,7 @@ describe('AutocompleteFieldWildcardComponent', () => {
const helpText = wrapper
.find('[data-test-subj="valuesAutocompleteWildcardLabel"] div.euiFormHelpText')
.at(0);
expect(helpText.text()).toEqual(FILENAME_WILDCARD_WARNING);
expect(helpText.text()).toEqual(WILDCARD_WARNING);
expect(helpText.find('.euiToolTipAnchor')).toBeTruthy();
});
test('should show the warning helper text if the new value contains spaces when change', async () => {
@ -412,7 +412,7 @@ describe('AutocompleteFieldWildcardComponent', () => {
placeholder="Placeholder text"
selectedField={getField('file.path.text')}
selectedValue="invalid path"
warning={FILENAME_WILDCARD_WARNING}
warning={WILDCARD_WARNING}
/>
);

View file

@ -223,6 +223,7 @@ describe('operator', () => {
{ label: 'is one of' },
{ label: 'is not one of' },
{ label: 'matches' },
{ label: 'does not match' },
]);
});

View file

@ -109,6 +109,7 @@ export const EVENT_FILTERS_OPERATORS: OperatorOption[] = [
isOneOfOperator,
isNotOneOfOperator,
matchesOperator,
doesNotMatchOperator,
];
/*

View file

@ -11,11 +11,43 @@ import {
hasSimpleExecutableName,
OperatingSystem,
ConditionEntryField,
validatePotentialWildcardInput,
validateFilePathInput,
FILENAME_WILDCARD_WARNING,
validateWildcardInput,
WILDCARD_WARNING,
FILEPATH_WARNING,
} from '.';
describe('validatePotentialWildcardInput', () => {
it('warns on wildcard when field is file.path.text', () => {
expect(
validatePotentialWildcardInput({
field: 'file.path.text',
os: OperatingSystem.WINDOWS,
value: 'c:\\path*.exe',
})
).toEqual(WILDCARD_WARNING);
});
it('warns on wildcard when field is not file.path.text', () => {
expect(
validatePotentialWildcardInput({
field: 'event.category',
os: OperatingSystem.WINDOWS,
value: 'some*value',
})
).toEqual(WILDCARD_WARNING);
});
});
describe('validateWildcardInput', () => {
it('warns on wildcard for fields that are not file paths', () => {
expect(validateWildcardInput('*')).toEqual(WILDCARD_WARNING);
});
it('does not warn if no wildcard', () => {
expect(validateWildcardInput('non-wildcard')).toEqual(undefined);
});
});
describe('validateFilePathInput', () => {
describe('windows', () => {
const os = OperatingSystem.WINDOWS;
@ -36,15 +68,13 @@ describe('validateFilePathInput', () => {
});
it('warns on wildcard in file name at the end of the path', () => {
expect(validateFilePathInput({ os, value: 'c:\\path*.exe' })).toEqual(
FILENAME_WILDCARD_WARNING
);
expect(validateFilePathInput({ os, value: 'c:\\path*.exe' })).toEqual(WILDCARD_WARNING);
expect(
validateFilePathInput({
os,
value: 'C:\\Windows\\*\\FILENAME.EXE-*.gz',
})
).toEqual(FILENAME_WILDCARD_WARNING);
).toEqual(WILDCARD_WARNING);
});
it('warns on unix paths or non-windows paths', () => {
@ -65,20 +95,23 @@ describe('validateFilePathInput', () => {
: OperatingSystem.LINUX;
it('does not warn on valid filenames', () => {
expect(validateFilePathInput({ os, value: '/opt/*/FILENAME.EXE-1231205124.gz' })).not.toEqual(
FILENAME_WILDCARD_WARNING
);
expect(
validateFilePathInput({
os,
value: '/opt/*/FILENAME.EXE-1231205124.gz',
})
).not.toEqual(WILDCARD_WARNING);
expect(
validateFilePathInput({
os,
value: "/opt/*/test$ as2@13---12!@#A,DS.#$^&$!#~ 'as'd.华语.txt",
})
).not.toEqual(FILENAME_WILDCARD_WARNING);
).not.toEqual(WILDCARD_WARNING);
});
it('warns on wildcard in file name at the end of the path', () => {
expect(validateFilePathInput({ os, value: '/opt/bin*' })).toEqual(FILENAME_WILDCARD_WARNING);
expect(validateFilePathInput({ os, value: '/opt/bin*' })).toEqual(WILDCARD_WARNING);
expect(validateFilePathInput({ os, value: '/opt/FILENAME.EXE-*.gz' })).toEqual(
FILENAME_WILDCARD_WARNING
WILDCARD_WARNING
);
});

View file

@ -8,8 +8,8 @@
import { i18n } from '@kbn/i18n';
export const FILENAME_WILDCARD_WARNING = i18n.translate('utils.filename.wildcardWarning', {
defaultMessage: `Using wildcards in file paths can impact Endpoint performance`,
export const WILDCARD_WARNING = i18n.translate('utils.wildcardWarning', {
defaultMessage: `Using wildcards can impact Endpoint performance`,
});
export const FILEPATH_WARNING = i18n.translate('utils.filename.pathWarning', {
@ -52,39 +52,60 @@ export enum OperatingSystem {
export type EntryTypes = 'match' | 'wildcard' | 'match_any';
export type TrustedAppEntryTypes = Extract<EntryTypes, 'match' | 'wildcard'>;
export const validateFilePathInput = ({
export const validatePotentialWildcardInput = ({
field = '',
os,
value = '',
}: {
field?: string;
os: OperatingSystem;
value?: string;
}): string | undefined => {
const textInput = value.trim();
if (field === 'file.path.text') {
return validateFilePathInput({ os, value: textInput });
}
return validateWildcardInput(textInput);
};
export const validateFilePathInput = ({
os,
value,
}: {
os: OperatingSystem;
value: string;
}): string | undefined => {
const isValidFilePath = isPathValid({
os,
field: 'file.path.text',
type: 'wildcard',
value: textInput,
value,
});
const hasSimpleFileName = hasSimpleExecutableName({
os,
type: 'wildcard',
value: textInput,
value,
});
if (!textInput.length) {
if (!value.length) {
return FILEPATH_WARNING;
}
if (isValidFilePath) {
if (hasSimpleFileName !== undefined && !hasSimpleFileName) {
return FILENAME_WILDCARD_WARNING;
return WILDCARD_WARNING;
}
} else {
return FILEPATH_WARNING;
}
};
export const validateWildcardInput = (value?: string): string | undefined => {
if (/[*?]/.test(value ?? '')) {
return WILDCARD_WARNING;
}
};
export const hasSimpleExecutableName = ({
os,
type,

View file

@ -20,7 +20,7 @@ import {
isOperator,
matchesOperator,
} from '@kbn/securitysolution-list-utils';
import { validateFilePathInput } from '@kbn/securitysolution-utils';
import { validatePotentialWildcardInput } from '@kbn/securitysolution-utils';
import { useFindListsBySize } from '@kbn/securitysolution-list-hooks';
import type { FieldSpec } from '@kbn/data-plugin/common';
import { fields, getField } from '@kbn/data-plugin/common/mocks';
@ -1050,7 +1050,7 @@ describe('BuilderEntryItem', () => {
test('it invokes "setWarningsExist" when invalid value in field value input', async () => {
const mockSetWarningsExists = jest.fn();
(validateFilePathInput as jest.Mock).mockReturnValue('some warning message');
(validatePotentialWildcardInput as jest.Mock).mockReturnValue('some warning message');
wrapper = mount(
<BuilderEntryItem
autocompleteService={autocompleteStartMock}
@ -1099,7 +1099,7 @@ describe('BuilderEntryItem', () => {
test('it does not invoke "setWarningsExist" when valid value in field value input', async () => {
const mockSetWarningsExists = jest.fn();
(validateFilePathInput as jest.Mock).mockReturnValue(undefined);
(validatePotentialWildcardInput as jest.Mock).mockReturnValue(undefined);
wrapper = mount(
<BuilderEntryItem
autocompleteService={autocompleteStartMock}

View file

@ -30,6 +30,7 @@ import {
EXCEPTION_OPERATORS_ONLY_LISTS,
FormattedBuilderEntry,
OperatorOption,
fieldSupportsMatches,
getEntryOnFieldChange,
getEntryOnListChange,
getEntryOnMatchAnyChange,
@ -50,9 +51,9 @@ import {
OperatorComponent,
} from '@kbn/securitysolution-autocomplete';
import {
FILENAME_WILDCARD_WARNING,
OperatingSystem,
validateFilePathInput,
WILDCARD_WARNING,
validatePotentialWildcardInput,
} from '@kbn/securitysolution-utils';
import { DataViewBase, DataViewFieldBase } from '@kbn/es-query';
import type { AutocompleteStart } from '@kbn/unified-search-plugin/public';
@ -310,11 +311,11 @@ export const BuilderEntryItem: React.FC<EntryItemProps> = ({
const renderOperatorInput = (isFirst: boolean): JSX.Element => {
// for event filters forms
// show extra operators for wildcards when field is `file.path.text`
const isFilePathTextField = entry.field !== undefined && entry.field.name === 'file.path.text';
// show extra operators for wildcards when field supports matches
const doesFieldSupportMatches = entry.field !== undefined && fieldSupportsMatches(entry.field);
const isEventFilterList = listType === 'endpoint_events';
const augmentedOperatorsList =
operatorsList && isFilePathTextField && isEventFilterList
operatorsList && doesFieldSupportMatches && isEventFilterList
? operatorsList
: operatorsList?.filter((operator) => operator.type !== OperatorTypeEnum.WILDCARD);
@ -358,8 +359,8 @@ export const BuilderEntryItem: React.FC<EntryItemProps> = ({
}
};
// show this when wildcard filename with matches operator
const getWildcardWarning = (precedingWarning: string): React.ReactNode => {
// show this when wildcard with matches operator
const getWildcardWarningInfo = (precedingWarning: string): React.ReactNode => {
return (
<p>
{precedingWarning}{' '}
@ -368,7 +369,7 @@ export const BuilderEntryItem: React.FC<EntryItemProps> = ({
content={
<FormattedMessage
id="xpack.lists.exceptions.builder.exceptionMatchesOperator.warningMessage.wildcardInFilepath"
defaultMessage="To make a more efficient event filter, use multiple conditions and make them as specific as possible when using wildcards in the path values. For instance, adding a process.name or file.name field."
defaultMessage="To make a more efficient event filter, use multiple conditions and make them as specific as possible when using wildcards in the values. For instance, adding a process.name or file.name field. Creating event filters with both `matches` and `does not match` operators may significantly decrease performance."
/>
}
size="m"
@ -430,11 +431,13 @@ export const BuilderEntryItem: React.FC<EntryItemProps> = ({
if (osTypes) {
[os] = osTypes as OperatingSystem[];
}
const warning = validateFilePathInput({ os, value: wildcardValue });
const warning = validatePotentialWildcardInput({
field: entry.field?.name,
os,
value: wildcardValue,
});
actualWarning =
warning === FILENAME_WILDCARD_WARNING
? warning && getWildcardWarning(warning)
: warning;
warning === WILDCARD_WARNING ? warning && getWildcardWarningInfo(warning) : warning;
}
return (

View file

@ -212,7 +212,7 @@ describe('Exception builder helpers', () => {
expect(output).toEqual(expected);
});
test('it returns all fields unfiletered if "item.nested" is not "child" or "parent"', () => {
test('it returns all fields unfiltered if "item.nested" is not "child" or "parent"', () => {
const payloadIndexPattern = getMockIndexPattern();
const payloadItem: FormattedBuilderEntry = getMockBuilderEntry();
const output = getFilteredIndexPatterns(payloadIndexPattern, payloadItem);

View file

@ -17,6 +17,7 @@ import {
OS_WINDOWS,
CONDITION_AND,
CONDITION_OPERATOR_TYPE_WILDCARD_MATCHES,
CONDITION_OPERATOR_TYPE_DOES_NOT_MATCH,
CONDITION_OPERATOR_TYPE_NESTED,
CONDITION_OPERATOR_TYPE_MATCH,
CONDITION_OPERATOR_TYPE_MATCH_ANY,
@ -47,6 +48,7 @@ const OPERATOR_TYPE_LABELS_INCLUDED = Object.freeze({
const OPERATOR_TYPE_LABELS_EXCLUDED = Object.freeze({
[ListOperatorTypeEnum.MATCH_ANY]: CONDITION_OPERATOR_TYPE_NOT_MATCH_ANY,
[ListOperatorTypeEnum.MATCH]: CONDITION_OPERATOR_TYPE_NOT_MATCH,
[ListOperatorTypeEnum.WILDCARD]: CONDITION_OPERATOR_TYPE_DOES_NOT_MATCH,
});
const EuiFlexGroupNested = styled(EuiFlexGroup)`

View file

@ -68,6 +68,13 @@ export const CONDITION_OPERATOR_TYPE_WILDCARD_MATCHES = i18n.translate(
}
);
export const CONDITION_OPERATOR_TYPE_DOES_NOT_MATCH = i18n.translate(
'xpack.securitySolution.artifactCard.conditions.wildcardDoesNotMatchOperator',
{
defaultMessage: 'DOES NOT MATCH',
}
);
export const CONDITION_OPERATOR_TYPE_NESTED = i18n.translate(
'xpack.securitySolution.artifactCard.conditions.nestedOperator',
{

View file

@ -43451,7 +43451,6 @@
"unifiedDocViewer.sourceViewer.errorMessageTitle": "Une erreur s'est produite.",
"unifiedDocViewer.sourceViewer.refresh": "Actualiser",
"utils.filename.pathWarning": "Le chemin est peut-être incorrectement formé ; vérifiez la valeur",
"utils.filename.wildcardWarning": "l'utilisation de caractères génériques dans les chemins de fichier peut affecter les performances du point de terminaison",
"visTypeGauge.advancedSettings.visualization.legacyGaugeChartsLibrary.description": "Active la bibliothèque de graphiques héritée pour les graphiques de jauge dans Visualize.",
"visTypeGauge.advancedSettings.visualization.legacyGaugeChartsLibrary.name": "Bibliothèque de graphiques héritée pour les jauges",
"visTypeGauge.controls.gaugeOptions.alignmentLabel": "Alignement",

View file

@ -43441,7 +43441,6 @@
"unifiedDocViewer.sourceViewer.errorMessageTitle": "エラーが発生しました",
"unifiedDocViewer.sourceViewer.refresh": "更新",
"utils.filename.pathWarning": "パスの形式が正しくない可能性があります。値を検証してください",
"utils.filename.wildcardWarning": "ファイルパスでワイルドカードを使用すると、エンドポイントのパフォーマンスに影響する可能性があります",
"visTypeGauge.advancedSettings.visualization.legacyGaugeChartsLibrary.description": "Visualizeでゲージグラフのレガシーグラフライブラリを有効にします。",
"visTypeGauge.advancedSettings.visualization.legacyGaugeChartsLibrary.name": "ゲージグラフのレガシーグラフライブラリ",
"visTypeGauge.controls.gaugeOptions.alignmentLabel": "アラインメント",

View file

@ -43435,7 +43435,6 @@
"unifiedDocViewer.sourceViewer.errorMessageTitle": "发生错误",
"unifiedDocViewer.sourceViewer.refresh": "刷新",
"utils.filename.pathWarning": "路径的格式可能不正确;请验证值",
"utils.filename.wildcardWarning": "在文件路径中使用通配符可能会影响终端性能",
"visTypeGauge.advancedSettings.visualization.legacyGaugeChartsLibrary.description": "在 Visualize 中启用仪表盘图表的旧版图表库。",
"visTypeGauge.advancedSettings.visualization.legacyGaugeChartsLibrary.name": "仪表盘旧版图表库",
"visTypeGauge.controls.gaugeOptions.alignmentLabel": "对齐方式",