Skip to content

Commit

Permalink
[Security Solutions] Fix host isolation exception list showing up on …
Browse files Browse the repository at this point in the history
…the exceptions list (#114987)
  • Loading branch information
academo authored Oct 15, 2021
1 parent c5f3be6 commit 3e6516c
Show file tree
Hide file tree
Showing 8 changed files with 351 additions and 68 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,7 @@ export interface UseExceptionListsProps {
initialPagination?: Pagination;
showTrustedApps: boolean;
showEventFilters: boolean;
showHostIsolationExceptions: boolean;
}

export interface UseExceptionListProps {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,7 @@ const DEFAULT_PAGINATION = {
* @param notifications kibana service for displaying toasters
* @param showTrustedApps boolean - include/exclude trusted app lists
* @param showEventFilters boolean - include/exclude event filters lists
* @param showHostIsolationExceptions boolean - include/exclude host isolation exceptions lists
* @param initialPagination
*
*/
Expand All @@ -53,6 +54,7 @@ export const useExceptionLists = ({
notifications,
showTrustedApps = false,
showEventFilters = false,
showHostIsolationExceptions = false,
}: UseExceptionListsProps): ReturnExceptionLists => {
const [exceptionLists, setExceptionLists] = useState<ExceptionListSchema[]>([]);
const [pagination, setPagination] = useState<Pagination>(initialPagination);
Expand All @@ -62,8 +64,14 @@ export const useExceptionLists = ({
const namespaceTypesAsString = useMemo(() => namespaceTypes.join(','), [namespaceTypes]);
const filters = useMemo(
(): string =>
getFilters({ filters: filterOptions, namespaceTypes, showTrustedApps, showEventFilters }),
[namespaceTypes, filterOptions, showTrustedApps, showEventFilters]
getFilters({
filters: filterOptions,
namespaceTypes,
showTrustedApps,
showEventFilters,
showHostIsolationExceptions,
}),
[namespaceTypes, filterOptions, showTrustedApps, showEventFilters, showHostIsolationExceptions]
);

const fetchData = useCallback(async (): Promise<void> => {
Expand Down
224 changes: 164 additions & 60 deletions packages/kbn-securitysolution-list-utils/src/get_filters/index.test.ts

Large diffs are not rendered by default.

Original file line number Diff line number Diff line change
Expand Up @@ -11,25 +11,32 @@ import { getGeneralFilters } from '../get_general_filters';
import { getSavedObjectTypes } from '../get_saved_object_types';
import { getTrustedAppsFilter } from '../get_trusted_apps_filter';
import { getEventFiltersFilter } from '../get_event_filters_filter';
import { getHostIsolationExceptionsFilter } from '../get_host_isolation_exceptions_filter';

export interface GetFiltersParams {
filters: ExceptionListFilter;
namespaceTypes: NamespaceType[];
showTrustedApps: boolean;
showEventFilters: boolean;
showHostIsolationExceptions: boolean;
}

export const getFilters = ({
filters,
namespaceTypes,
showTrustedApps,
showEventFilters,
showHostIsolationExceptions,
}: GetFiltersParams): string => {
const namespaces = getSavedObjectTypes({ namespaceType: namespaceTypes });
const generalFilters = getGeneralFilters(filters, namespaces);
const trustedAppsFilter = getTrustedAppsFilter(showTrustedApps, namespaces);
const eventFiltersFilter = getEventFiltersFilter(showEventFilters, namespaces);
return [generalFilters, trustedAppsFilter, eventFiltersFilter]
const hostIsolationExceptionsFilter = getHostIsolationExceptionsFilter(
showHostIsolationExceptions,
namespaces
);
return [generalFilters, trustedAppsFilter, eventFiltersFilter, hostIsolationExceptionsFilter]
.filter((filter) => filter.trim() !== '')
.join(' AND ');
};
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
/*
* 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 and the Server Side Public License, v 1; you may not use this file except
* in compliance with, at your election, the Elastic License 2.0 or the Server
* Side Public License, v 1.
*/

import { getHostIsolationExceptionsFilter } from '.';

describe('getHostIsolationExceptionsFilter', () => {
test('it returns filter to search for "exception-list" namespace host isolation exceptions', () => {
const filter = getHostIsolationExceptionsFilter(true, ['exception-list']);

expect(filter).toEqual(
'(exception-list.attributes.list_id: endpoint_host_isolation_exceptions*)'
);
});

test('it returns filter to search for "exception-list" and "agnostic" namespace host isolation exceptions', () => {
const filter = getHostIsolationExceptionsFilter(true, [
'exception-list',
'exception-list-agnostic',
]);

expect(filter).toEqual(
'(exception-list.attributes.list_id: endpoint_host_isolation_exceptions* OR exception-list-agnostic.attributes.list_id: endpoint_host_isolation_exceptions*)'
);
});

test('it returns filter to exclude "exception-list" namespace host isolation exceptions', () => {
const filter = getHostIsolationExceptionsFilter(false, ['exception-list']);

expect(filter).toEqual(
'(not exception-list.attributes.list_id: endpoint_host_isolation_exceptions*)'
);
});

test('it returns filter to exclude "exception-list" and "agnostic" namespace host isolation exceptions', () => {
const filter = getHostIsolationExceptionsFilter(false, [
'exception-list',
'exception-list-agnostic',
]);

expect(filter).toEqual(
'(not exception-list.attributes.list_id: endpoint_host_isolation_exceptions* AND not exception-list-agnostic.attributes.list_id: endpoint_host_isolation_exceptions*)'
);
});
});
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
/*
* 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 and the Server Side Public License, v 1; you may not use this file except
* in compliance with, at your election, the Elastic License 2.0 or the Server
* Side Public License, v 1.
*/

import { ENDPOINT_HOST_ISOLATION_EXCEPTIONS_LIST_ID } from '@kbn/securitysolution-list-constants';
import { SavedObjectType } from '../types';

export const getHostIsolationExceptionsFilter = (
showFilter: boolean,
namespaceTypes: SavedObjectType[]
): string => {
if (showFilter) {
const filters = namespaceTypes.map((namespace) => {
return `${namespace}.attributes.list_id: ${ENDPOINT_HOST_ISOLATION_EXCEPTIONS_LIST_ID}*`;
});
return `(${filters.join(' OR ')})`;
} else {
const filters = namespaceTypes.map((namespace) => {
return `not ${namespace}.attributes.list_id: ${ENDPOINT_HOST_ISOLATION_EXCEPTIONS_LIST_ID}*`;
});
return `(${filters.join(' AND ')})`;
}
};
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,7 @@ describe('useExceptionLists', () => {
namespaceTypes: ['single', 'agnostic'],
notifications: mockKibanaNotificationsService,
showEventFilters: false,
showHostIsolationExceptions: false,
showTrustedApps: false,
})
);
Expand Down Expand Up @@ -86,6 +87,7 @@ describe('useExceptionLists', () => {
namespaceTypes: ['single', 'agnostic'],
notifications: mockKibanaNotificationsService,
showEventFilters: false,
showHostIsolationExceptions: false,
showTrustedApps: false,
})
);
Expand Down Expand Up @@ -127,6 +129,7 @@ describe('useExceptionLists', () => {
namespaceTypes: ['single', 'agnostic'],
notifications: mockKibanaNotificationsService,
showEventFilters: false,
showHostIsolationExceptions: false,
showTrustedApps: true,
})
);
Expand All @@ -137,7 +140,7 @@ describe('useExceptionLists', () => {

expect(spyOnfetchExceptionLists).toHaveBeenCalledWith({
filters:
'(exception-list.attributes.list_id: endpoint_trusted_apps* OR exception-list-agnostic.attributes.list_id: endpoint_trusted_apps*) AND (not exception-list.attributes.list_id: endpoint_event_filters* AND not exception-list-agnostic.attributes.list_id: endpoint_event_filters*)',
'(exception-list.attributes.list_id: endpoint_trusted_apps* OR exception-list-agnostic.attributes.list_id: endpoint_trusted_apps*) AND (not exception-list.attributes.list_id: endpoint_event_filters* AND not exception-list-agnostic.attributes.list_id: endpoint_event_filters*) AND (not exception-list.attributes.list_id: endpoint_host_isolation_exceptions* AND not exception-list-agnostic.attributes.list_id: endpoint_host_isolation_exceptions*)',
http: mockKibanaHttpService,
namespaceTypes: 'single,agnostic',
pagination: { page: 1, perPage: 20 },
Expand All @@ -163,6 +166,7 @@ describe('useExceptionLists', () => {
namespaceTypes: ['single', 'agnostic'],
notifications: mockKibanaNotificationsService,
showEventFilters: false,
showHostIsolationExceptions: false,
showTrustedApps: false,
})
);
Expand All @@ -173,7 +177,7 @@ describe('useExceptionLists', () => {

expect(spyOnfetchExceptionLists).toHaveBeenCalledWith({
filters:
'(not exception-list.attributes.list_id: endpoint_trusted_apps* AND not exception-list-agnostic.attributes.list_id: endpoint_trusted_apps*) AND (not exception-list.attributes.list_id: endpoint_event_filters* AND not exception-list-agnostic.attributes.list_id: endpoint_event_filters*)',
'(not exception-list.attributes.list_id: endpoint_trusted_apps* AND not exception-list-agnostic.attributes.list_id: endpoint_trusted_apps*) AND (not exception-list.attributes.list_id: endpoint_event_filters* AND not exception-list-agnostic.attributes.list_id: endpoint_event_filters*) AND (not exception-list.attributes.list_id: endpoint_host_isolation_exceptions* AND not exception-list-agnostic.attributes.list_id: endpoint_host_isolation_exceptions*)',
http: mockKibanaHttpService,
namespaceTypes: 'single,agnostic',
pagination: { page: 1, perPage: 20 },
Expand All @@ -199,6 +203,7 @@ describe('useExceptionLists', () => {
namespaceTypes: ['single', 'agnostic'],
notifications: mockKibanaNotificationsService,
showEventFilters: true,
showHostIsolationExceptions: false,
showTrustedApps: false,
})
);
Expand All @@ -209,7 +214,7 @@ describe('useExceptionLists', () => {

expect(spyOnfetchExceptionLists).toHaveBeenCalledWith({
filters:
'(not exception-list.attributes.list_id: endpoint_trusted_apps* AND not exception-list-agnostic.attributes.list_id: endpoint_trusted_apps*) AND (exception-list.attributes.list_id: endpoint_event_filters* OR exception-list-agnostic.attributes.list_id: endpoint_event_filters*)',
'(not exception-list.attributes.list_id: endpoint_trusted_apps* AND not exception-list-agnostic.attributes.list_id: endpoint_trusted_apps*) AND (exception-list.attributes.list_id: endpoint_event_filters* OR exception-list-agnostic.attributes.list_id: endpoint_event_filters*) AND (not exception-list.attributes.list_id: endpoint_host_isolation_exceptions* AND not exception-list-agnostic.attributes.list_id: endpoint_host_isolation_exceptions*)',
http: mockKibanaHttpService,
namespaceTypes: 'single,agnostic',
pagination: { page: 1, perPage: 20 },
Expand All @@ -235,6 +240,7 @@ describe('useExceptionLists', () => {
namespaceTypes: ['single', 'agnostic'],
notifications: mockKibanaNotificationsService,
showEventFilters: false,
showHostIsolationExceptions: false,
showTrustedApps: false,
})
);
Expand All @@ -245,7 +251,81 @@ describe('useExceptionLists', () => {

expect(spyOnfetchExceptionLists).toHaveBeenCalledWith({
filters:
'(not exception-list.attributes.list_id: endpoint_trusted_apps* AND not exception-list-agnostic.attributes.list_id: endpoint_trusted_apps*) AND (not exception-list.attributes.list_id: endpoint_event_filters* AND not exception-list-agnostic.attributes.list_id: endpoint_event_filters*)',
'(not exception-list.attributes.list_id: endpoint_trusted_apps* AND not exception-list-agnostic.attributes.list_id: endpoint_trusted_apps*) AND (not exception-list.attributes.list_id: endpoint_event_filters* AND not exception-list-agnostic.attributes.list_id: endpoint_event_filters*) AND (not exception-list.attributes.list_id: endpoint_host_isolation_exceptions* AND not exception-list-agnostic.attributes.list_id: endpoint_host_isolation_exceptions*)',
http: mockKibanaHttpService,
namespaceTypes: 'single,agnostic',
pagination: { page: 1, perPage: 20 },
signal: new AbortController().signal,
});
});
});

test('fetches host isolation exceptions lists if "hostIsolationExceptionsFilter" is true', async () => {
const spyOnfetchExceptionLists = jest.spyOn(api, 'fetchExceptionLists');

await act(async () => {
const { waitForNextUpdate } = renderHook<UseExceptionListsProps, ReturnExceptionLists>(() =>
useExceptionLists({
errorMessage: 'Uh oh',
filterOptions: {},
http: mockKibanaHttpService,
initialPagination: {
page: 1,
perPage: 20,
total: 0,
},
namespaceTypes: ['single', 'agnostic'],
notifications: mockKibanaNotificationsService,
showEventFilters: false,
showHostIsolationExceptions: true,
showTrustedApps: false,
})
);
// NOTE: First `waitForNextUpdate` is initialization
// Second call applies the params
await waitForNextUpdate();
await waitForNextUpdate();

expect(spyOnfetchExceptionLists).toHaveBeenCalledWith({
filters:
'(not exception-list.attributes.list_id: endpoint_trusted_apps* AND not exception-list-agnostic.attributes.list_id: endpoint_trusted_apps*) AND (not exception-list.attributes.list_id: endpoint_event_filters* AND not exception-list-agnostic.attributes.list_id: endpoint_event_filters*) AND (exception-list.attributes.list_id: endpoint_host_isolation_exceptions* OR exception-list-agnostic.attributes.list_id: endpoint_host_isolation_exceptions*)',
http: mockKibanaHttpService,
namespaceTypes: 'single,agnostic',
pagination: { page: 1, perPage: 20 },
signal: new AbortController().signal,
});
});
});

test('does not fetch host isolation exceptions lists if "showHostIsolationExceptions" is false', async () => {
const spyOnfetchExceptionLists = jest.spyOn(api, 'fetchExceptionLists');

await act(async () => {
const { waitForNextUpdate } = renderHook<UseExceptionListsProps, ReturnExceptionLists>(() =>
useExceptionLists({
errorMessage: 'Uh oh',
filterOptions: {},
http: mockKibanaHttpService,
initialPagination: {
page: 1,
perPage: 20,
total: 0,
},
namespaceTypes: ['single', 'agnostic'],
notifications: mockKibanaNotificationsService,
showEventFilters: false,
showHostIsolationExceptions: false,
showTrustedApps: false,
})
);
// NOTE: First `waitForNextUpdate` is initialization
// Second call applies the params
await waitForNextUpdate();
await waitForNextUpdate();

expect(spyOnfetchExceptionLists).toHaveBeenCalledWith({
filters:
'(not exception-list.attributes.list_id: endpoint_trusted_apps* AND not exception-list-agnostic.attributes.list_id: endpoint_trusted_apps*) AND (not exception-list.attributes.list_id: endpoint_event_filters* AND not exception-list-agnostic.attributes.list_id: endpoint_event_filters*) AND (not exception-list.attributes.list_id: endpoint_host_isolation_exceptions* AND not exception-list-agnostic.attributes.list_id: endpoint_host_isolation_exceptions*)',
http: mockKibanaHttpService,
namespaceTypes: 'single,agnostic',
pagination: { page: 1, perPage: 20 },
Expand Down Expand Up @@ -274,6 +354,7 @@ describe('useExceptionLists', () => {
namespaceTypes: ['single', 'agnostic'],
notifications: mockKibanaNotificationsService,
showEventFilters: false,
showHostIsolationExceptions: false,
showTrustedApps: false,
})
);
Expand All @@ -284,7 +365,7 @@ describe('useExceptionLists', () => {

expect(spyOnfetchExceptionLists).toHaveBeenCalledWith({
filters:
'(exception-list.attributes.created_by:Moi OR exception-list-agnostic.attributes.created_by:Moi) AND (exception-list.attributes.name.text:Sample Endpoint OR exception-list-agnostic.attributes.name.text:Sample Endpoint) AND (not exception-list.attributes.list_id: endpoint_trusted_apps* AND not exception-list-agnostic.attributes.list_id: endpoint_trusted_apps*) AND (not exception-list.attributes.list_id: endpoint_event_filters* AND not exception-list-agnostic.attributes.list_id: endpoint_event_filters*)',
'(exception-list.attributes.created_by:Moi OR exception-list-agnostic.attributes.created_by:Moi) AND (exception-list.attributes.name.text:Sample Endpoint OR exception-list-agnostic.attributes.name.text:Sample Endpoint) AND (not exception-list.attributes.list_id: endpoint_trusted_apps* AND not exception-list-agnostic.attributes.list_id: endpoint_trusted_apps*) AND (not exception-list.attributes.list_id: endpoint_event_filters* AND not exception-list-agnostic.attributes.list_id: endpoint_event_filters*) AND (not exception-list.attributes.list_id: endpoint_host_isolation_exceptions* AND not exception-list-agnostic.attributes.list_id: endpoint_host_isolation_exceptions*)',
http: mockKibanaHttpService,
namespaceTypes: 'single,agnostic',
pagination: { page: 1, perPage: 20 },
Expand Down Expand Up @@ -318,6 +399,7 @@ describe('useExceptionLists', () => {
namespaceTypes,
notifications,
showEventFilters,
showHostIsolationExceptions: false,
showTrustedApps,
}),
{
Expand All @@ -333,6 +415,7 @@ describe('useExceptionLists', () => {
namespaceTypes: ['single'],
notifications: mockKibanaNotificationsService,
showEventFilters: false,
showHostIsolationExceptions: false,
showTrustedApps: false,
},
}
Expand All @@ -354,6 +437,7 @@ describe('useExceptionLists', () => {
namespaceTypes: ['single', 'agnostic'],
notifications: mockKibanaNotificationsService,
showEventFilters: false,
showHostIsolationExceptions: false,
showTrustedApps: false,
});
// NOTE: Only need one call here because hook already initilaized
Expand Down Expand Up @@ -382,6 +466,7 @@ describe('useExceptionLists', () => {
namespaceTypes: ['single', 'agnostic'],
notifications: mockKibanaNotificationsService,
showEventFilters: false,
showHostIsolationExceptions: false,
showTrustedApps: false,
})
);
Expand Down Expand Up @@ -421,6 +506,7 @@ describe('useExceptionLists', () => {
namespaceTypes: ['single', 'agnostic'],
notifications: mockKibanaNotificationsService,
showEventFilters: false,
showHostIsolationExceptions: false,
showTrustedApps: false,
})
);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -85,6 +85,7 @@ export const ExceptionListsTable = React.memo(() => {
notifications,
showTrustedApps: false,
showEventFilters: false,
showHostIsolationExceptions: false,
});
const [loadingTableInfo, exceptionListsWithRuleRefs, exceptionsListsRef] = useAllExceptionLists({
exceptionLists: exceptions ?? [],
Expand Down

0 comments on commit 3e6516c

Please sign in to comment.