Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ import {
entriesMatch,
entriesMatchAny,
entriesNested,
OsTypeArray,
} from '@kbn/securitysolution-io-ts-list-types';

import { hasLargeValueList } from '../has_large_value_list';
Expand Down Expand Up @@ -69,26 +70,87 @@ export const chunkExceptions = (
return chunk(chunkSize, exceptions);
};

export const buildExceptionItemFilter = (
exceptionItem: ExceptionItemSansLargeValueLists
): BooleanFilter | NestedFilter => {
const { entries } = exceptionItem;
/**
* Transforms the os_type into a regular filter as if the user had created it
* from the fields for the next state of transforms which will create the elastic filters
* from it.
*
* Note: We use two types of fields, the "host.os.type" and "host.os.name.caseless"
* The endpoint/endgame agent has been using "host.os.name.caseless" as the same value as the ECS
* value of "host.os.type" where the auditbeat, winlogbeat, etc... (other agents) are all using
* "host.os.type". In order to be compatible with both, I create an "OR" between these two data types
* where if either has a match then we will exclude it as part of the match. This should also be
* forwards compatible for endpoints/endgame agents when/if they upgrade to using "host.os.type"
* rather than using "host.os.name.caseless" values.
*
* Also we create another "OR" from the osType names so that if there are multiples such as ['windows', 'linux']
* this will exclude anything with either 'windows' or with 'linux'
* @param osTypes The os_type array from the REST interface that is an array such as ['windows', 'linux']
* @param entries The entries to join the OR's with before the elastic filter change out
*/
export const transformOsType = (
osTypes: OsTypeArray,
entries: NonListEntry[]
): NonListEntry[][] => {
const hostTypeTransformed = osTypes.map<NonListEntry[]>((osType) => {
return [
{ field: 'host.os.type', operator: 'included', type: 'match', value: osType },
...entries,
];
});
const caseLessTransformed = osTypes.map<NonListEntry[]>((osType) => {
return [
{ field: 'host.os.name.caseless', operator: 'included', type: 'match', value: osType },
...entries,
];
});
return [...hostTypeTransformed, ...caseLessTransformed];
};

if (entries.length === 1) {
return createInnerAndClauses(entries[0]);
} else {
/**
* This builds an exception item filter with the os type
* @param osTypes The os_type array from the REST interface that is an array such as ['windows', 'linux']
* @param entries The entries to join the OR's with before the elastic filter change out
*/
export const buildExceptionItemFilterWithOsType = (
osTypes: OsTypeArray,
entries: NonListEntry[]
): BooleanFilter[] => {
const entriesWithOsTypes = transformOsType(osTypes, entries);
return entriesWithOsTypes.map((entryWithOsType) => {
return {
bool: {
filter: entries.map((entry) => createInnerAndClauses(entry)),
filter: entryWithOsType.map((entry) => createInnerAndClauses(entry)),
},
};
});
};

export const buildExceptionItemFilter = (
exceptionItem: ExceptionItemSansLargeValueLists
): Array<BooleanFilter | NestedFilter> => {
const { entries, os_types: osTypes } = exceptionItem;
if (osTypes != null && osTypes.length > 0) {
return buildExceptionItemFilterWithOsType(osTypes, entries);
} else {
if (entries.length === 1) {
return [createInnerAndClauses(entries[0])];
} else {
return [
{
bool: {
filter: entries.map((entry) => createInnerAndClauses(entry)),
},
},
];
}
}
};

export const createOrClauses = (
exceptionItems: ExceptionItemSansLargeValueLists[]
): Array<BooleanFilter | NestedFilter> => {
return exceptionItems.map((exceptionItem) => buildExceptionItemFilter(exceptionItem));
return exceptionItems.flatMap((exceptionItem) => buildExceptionItemFilter(exceptionItem));
};

export const buildExceptionFilter = ({
Expand Down
171 changes: 86 additions & 85 deletions x-pack/plugins/lists/common/exceptions/build_exceptions_filter.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -611,114 +611,115 @@ describe('build_exceptions_filter', () => {
getEntryExistsExcludedMock(),
],
});

expect(exceptionItemFilter).toEqual({
bool: {
filter: [
{
nested: {
path: 'parent.field',
query: {
bool: {
filter: [
{
bool: {
minimum_should_match: 1,
should: [
{
match_phrase: {
'parent.field.host.name': 'some host name',
expect(exceptionItemFilter).toEqual([
{
bool: {
filter: [
{
nested: {
path: 'parent.field',
query: {
bool: {
filter: [
{
bool: {
minimum_should_match: 1,
should: [
{
match_phrase: {
'parent.field.host.name': 'some host name',
},
},
},
],
],
},
},
},
{
bool: {
must_not: {
bool: {
minimum_should_match: 1,
should: [
{
bool: {
minimum_should_match: 1,
should: [
{
match_phrase: {
'parent.field.host.name': 'some host name',
{
bool: {
must_not: {
bool: {
minimum_should_match: 1,
should: [
{
bool: {
minimum_should_match: 1,
should: [
{
match_phrase: {
'parent.field.host.name': 'some host name',
},
},
},
],
],
},
},
},
{
bool: {
minimum_should_match: 1,
should: [
{
match_phrase: {
'parent.field.host.name': 'some other host name',
{
bool: {
minimum_should_match: 1,
should: [
{
match_phrase: {
'parent.field.host.name': 'some other host name',
},
},
},
],
],
},
},
},
],
],
},
},
},
},
},
{
bool: {
minimum_should_match: 1,
should: [{ exists: { field: 'parent.field.host.name' } }],
{
bool: {
minimum_should_match: 1,
should: [{ exists: { field: 'parent.field.host.name' } }],
},
},
},
],
],
},
},
score_mode: 'none',
},
score_mode: 'none',
},
},
{
bool: {
minimum_should_match: 1,
should: [
{
bool: {
minimum_should_match: 1,
should: [{ match_phrase: { 'host.name': 'some "host" name' } }],
{
bool: {
minimum_should_match: 1,
should: [
{
bool: {
minimum_should_match: 1,
should: [{ match_phrase: { 'host.name': 'some "host" name' } }],
},
},
},
{
{
bool: {
minimum_should_match: 1,
should: [{ match_phrase: { 'host.name': 'some other host name' } }],
},
},
],
},
},
{
bool: {
must_not: {
bool: {
minimum_should_match: 1,
should: [{ match_phrase: { 'host.name': 'some other host name' } }],
should: [{ match_phrase: { 'host.name': 'some host name' } }],
},
},
],
},
},
{
bool: {
must_not: {
bool: {
minimum_should_match: 1,
should: [{ match_phrase: { 'host.name': 'some host name' } }],
},
},
},
},
{
bool: {
must_not: {
bool: { minimum_should_match: 1, should: [{ exists: { field: 'host.name' } }] },
{
bool: {
must_not: {
bool: { minimum_should_match: 1, should: [{ exists: { field: 'host.name' } }] },
},
},
},
},
],
],
},
},
});
]);
});
});

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -39,7 +39,7 @@ export const getExceptionListItemSchemaMock = (
meta: META,
name: NAME,
namespace_type: NAMESPACE_TYPE,
os_types: ['linux'],
os_types: [],
tags: ['user added string for a tag', 'malware'],
tie_breaker_id: TIE_BREAKER,
type: ITEM_TYPE,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -185,7 +185,7 @@ describe('Exception helpers', () => {
meta: {},
name: 'some name',
namespace_type: 'single',
os_types: ['linux'],
os_types: [],
tags: ['user added string for a tag', 'malware'],
type: 'simple',
};
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -157,7 +157,7 @@ describe('ExceptionDetails', () => {
});

test('it renders the operating system if one is specified in the exception item', () => {
const exceptionItem = getExceptionListItemSchemaMock();
const exceptionItem = getExceptionListItemSchemaMock({ os_types: ['linux'] });
const wrapper = mount(
<ThemeProvider theme={mockTheme}>
<ExceptionDetails
Expand All @@ -173,7 +173,7 @@ describe('ExceptionDetails', () => {
});

test('it renders the exception item creator', () => {
const exceptionItem = getExceptionListItemSchemaMock();
const exceptionItem = getExceptionListItemSchemaMock({ os_types: ['linux'] });
const wrapper = mount(
<ThemeProvider theme={mockTheme}>
<ExceptionDetails
Expand All @@ -191,7 +191,7 @@ describe('ExceptionDetails', () => {
});

test('it renders the exception item creation timestamp', () => {
const exceptionItem = getExceptionListItemSchemaMock();
const exceptionItem = getExceptionListItemSchemaMock({ os_types: ['linux'] });
const wrapper = mount(
<ThemeProvider theme={mockTheme}>
<ExceptionDetails
Expand All @@ -207,7 +207,7 @@ describe('ExceptionDetails', () => {
});

test('it renders the description if one is included on the exception item', () => {
const exceptionItem = getExceptionListItemSchemaMock();
const exceptionItem = getExceptionListItemSchemaMock({ os_types: ['linux'] });
const wrapper = mount(
<ThemeProvider theme={mockTheme}>
<ExceptionDetails
Expand All @@ -223,7 +223,7 @@ describe('ExceptionDetails', () => {
});

test('it renders with Name and Modified info when showName and showModified props are true', () => {
const exceptionItem = getExceptionListItemSchemaMock();
const exceptionItem = getExceptionListItemSchemaMock({ os_types: ['linux'] });
exceptionItem.comments = [];

const wrapper = mount(
Expand Down
Loading