Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[Security Solution] host isolation exceptions listing under policy integration details tab #120361

Merged
Merged
Show file tree
Hide file tree
Changes from 61 commits
Commits
Show all changes
67 commits
Select commit Hold shift + click to select a range
904295c
Move effected policies component to common management components
Nov 17, 2021
df52a53
Allow description prop and use fallback generic copy
Nov 17, 2021
67be8ed
Style borders removing radius
Nov 17, 2021
3e4ac9f
Rename variable
Nov 17, 2021
2e1801c
Move policy ingest service to a common service
Nov 17, 2021
4e57894
Revert "Move policy ingest service to a common service"
Nov 18, 2021
e914304
Revert "Move policy ingest service to a common service"
Nov 18, 2021
a5da9b6
Merge remote-tracking branch 'upstream/main' into feature/host-isolat…
Nov 18, 2021
0dd114e
Move query to custom hook
Nov 18, 2021
3f6ee48
Merge remote-tracking branch 'upstream/main' into feature/host-isolat…
Nov 19, 2021
51e98f0
WIP: Add policies to host isolation exceptions
Nov 19, 2021
7aa40e7
Apply policies and submit
Nov 19, 2021
96922e0
Fetch policies inside the form flyout
Nov 19, 2021
0582054
Preserve previous policy selection
Nov 19, 2021
1d41e48
Show error for fetching policies
Nov 19, 2021
0f18595
Improve form render performance
Nov 19, 2021
9772deb
Merge remote-tracking branch 'upstream/main' into feature/host-isolat…
Nov 29, 2021
1cc62cb
Move policies specific hook to its own folder
Nov 29, 2021
de4db40
Fix existing form tests
Nov 29, 2021
9ee5593
Update tests for host isolation exceptions form
Nov 29, 2021
f399c1a
Fix form tests
Nov 29, 2021
985a84f
Move policies service to its own folder
Nov 29, 2021
270f54c
Display the name of policies on the artifact card
Nov 29, 2021
e247c3c
Merge remote-tracking branch 'upstream/main' into feature/host-isolat…
Nov 29, 2021
247948d
Merge remote-tracking branch 'upstream/main' into feature/host-isolat…
Nov 30, 2021
3aa2ef0
Show error if policies can't be loaded
Nov 30, 2021
ae65581
Update generator to generate with policies
Nov 30, 2021
bf4689a
Generator: print help messages
Nov 30, 2021
bce15e8
Show the how isolation exceptions tab
Nov 30, 2021
9aeec03
Merge remote-tracking branch 'upstream/main' into feature/host-isolat…
Dec 1, 2021
6b6cc2a
Add empty state
Dec 1, 2021
6c7ca65
Refactor hook to use params instead of internally location hook
Dec 1, 2021
3bd986c
Extract function to utils
Dec 1, 2021
66bd6bc
WIP: exceptions list
Dec 1, 2021
2f41edd
WIP: Display host isolation exceptions list on the policy tab
Dec 2, 2021
2912ece
also display global policies on the list
Dec 2, 2021
885cfcb
Expand and collapse functionality
Dec 2, 2021
ad7f2a0
Add pagination
Dec 2, 2021
84a5fc1
Add searching capabilities
Dec 2, 2021
2fec3cd
Fix copy
Dec 2, 2021
3df990c
Show non existent and non-assigned messages
Dec 2, 2021
4afd6c2
Add comments
Dec 2, 2021
9fafa15
Fix empty state logic
Dec 3, 2021
2c8c800
Remove unused imports
Dec 3, 2021
5cc50fa
Fix generator script
Dec 3, 2021
3be204b
Move empty state request back to tab
Dec 3, 2021
46b4075
Merge remote-tracking branch 'upstream/main' into feature/host-isolat…
Dec 3, 2021
676836a
Add tests
Dec 3, 2021
19a3f2b
Fix host isolation exceptions list test
Dec 3, 2021
414c837
Test for hostIsolationExceptions tab
Dec 3, 2021
37e998c
Add tests for listing
Dec 3, 2021
d7795f4
Add test for filter
Dec 3, 2021
62a2955
Remove import
Dec 3, 2021
71ff3ed
Merge remote-tracking branch 'upstream/main' into feature/host-isolat…
Dec 6, 2021
f3a60be
Remove unused import
Dec 6, 2021
81632eb
Fix types after merge
Dec 6, 2021
3bf18d9
Revert "Fix generator script"
Dec 6, 2021
e689eec
Revert "Generator: print help messages"
Dec 6, 2021
06d7f6c
Revert "Update generator to generate with policies"
Dec 6, 2021
c9d1f2e
Merge remote-tracking branch 'upstream/main' into feature/host-isolat…
Dec 13, 2021
0fd6912
Fix types
Dec 13, 2021
6daaf3f
Fix merge errors
Dec 13, 2021
61eb7bc
PR review apply suggestions
Dec 13, 2021
59e0daf
Fix kql error
Dec 13, 2021
d896ef3
rename file
Dec 13, 2021
8fe6968
remove unecessary mock
Dec 13, 2021
ec0566a
Remove unnecessary mock
Dec 13, 2021
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 @@ -9,9 +9,11 @@ import type { FoundExceptionListItemSchema } from '@kbn/securitysolution-io-ts-l

import { getExceptionListItemSchemaMock } from './exception_list_item_schema.mock';

export const getFoundExceptionListItemSchemaMock = (): FoundExceptionListItemSchema => ({
data: [getExceptionListItemSchemaMock()],
export const getFoundExceptionListItemSchemaMock = (
count: number = 1
): FoundExceptionListItemSchema => ({
data: Array.from({ length: count }, getExceptionListItemSchemaMock),
academo marked this conversation as resolved.
Show resolved Hide resolved
page: 1,
per_page: 1,
total: 1,
total: count,
});
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ export const MANAGEMENT_ROUTING_POLICIES_PATH = `${MANAGEMENT_PATH}/:tabName(${A
export const MANAGEMENT_ROUTING_POLICY_DETAILS_FORM_PATH = `${MANAGEMENT_PATH}/:tabName(${AdministrationSubTab.policies})/:policyId/settings`;
export const MANAGEMENT_ROUTING_POLICY_DETAILS_TRUSTED_APPS_PATH = `${MANAGEMENT_PATH}/:tabName(${AdministrationSubTab.policies})/:policyId/trustedApps`;
export const MANAGEMENT_ROUTING_POLICY_DETAILS_EVENT_FILTERS_PATH = `${MANAGEMENT_PATH}/:tabName(${AdministrationSubTab.policies})/:policyId/eventFilters`;
export const MANAGEMENT_ROUTING_POLICY_DETAILS_HOST_ISOLATION_EXCEPTIONS_PATH = `${MANAGEMENT_PATH}/:tabName(${AdministrationSubTab.policies})/:policyId/hostIsolationExceptions`;
/** @deprecated use the paths defined above instead */
export const MANAGEMENT_ROUTING_POLICY_DETAILS_PATH_OLD = `${MANAGEMENT_PATH}/:tabName(${AdministrationSubTab.policies})/:policyId`;
export const MANAGEMENT_ROUTING_TRUSTED_APPS_PATH = `${MANAGEMENT_PATH}/:tabName(${AdministrationSubTab.trustedApps})`;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,10 +6,16 @@
*/

import { isEmpty } from 'lodash/fp';
import { generatePath } from 'react-router-dom';
// eslint-disable-next-line import/no-nodejs-modules
import querystring from 'querystring';

import { generatePath } from 'react-router-dom';
import { appendSearch } from '../../common/components/link_to/helpers';
import { EndpointIndexUIQueryParams } from '../pages/endpoint_hosts/types';
import { EventFiltersPageLocation } from '../pages/event_filters/types';
import { HostIsolationExceptionsPageLocation } from '../pages/host_isolation_exceptions/types';
import { PolicyDetailsArtifactsPageLocation } from '../pages/policy/types';
import { TrustedAppsListPageLocation } from '../pages/trusted_apps/state';
import { AdministrationSubTab } from '../types';
import {
MANAGEMENT_DEFAULT_PAGE,
MANAGEMENT_DEFAULT_PAGE_SIZE,
Expand All @@ -19,17 +25,11 @@ import {
MANAGEMENT_ROUTING_HOST_ISOLATION_EXCEPTIONS_PATH,
MANAGEMENT_ROUTING_POLICIES_PATH,
MANAGEMENT_ROUTING_POLICY_DETAILS_FORM_PATH,
MANAGEMENT_ROUTING_POLICY_DETAILS_HOST_ISOLATION_EXCEPTIONS_PATH,
MANAGEMENT_ROUTING_POLICY_DETAILS_TRUSTED_APPS_PATH,
MANAGEMENT_ROUTING_POLICY_DETAILS_EVENT_FILTERS_PATH,
MANAGEMENT_ROUTING_TRUSTED_APPS_PATH,
} from './constants';
import { AdministrationSubTab } from '../types';
import { appendSearch } from '../../common/components/link_to/helpers';
import { EndpointIndexUIQueryParams } from '../pages/endpoint_hosts/types';
import { TrustedAppsListPageLocation } from '../pages/trusted_apps/state';
import { EventFiltersPageLocation } from '../pages/event_filters/types';
import { HostIsolationExceptionsPageLocation } from '../pages/host_isolation_exceptions/types';
import { PolicyDetailsArtifactsPageLocation } from '../pages/policy/types';

// Taken from: https://github.com/microsoft/TypeScript/issues/12936#issuecomment-559034150
type ExactKeys<T1, T2> = Exclude<keyof T1, keyof T2> extends never ? T1 : never;
Expand Down Expand Up @@ -390,3 +390,16 @@ export const getHostIsolationExceptionsListPath = (
querystring.stringify(normalizeHostIsolationExceptionsPageLocation(location))
)}`;
};

export const getPolicyHostIsolationExceptionsPath = (
policyId: string,
location?: Partial<PolicyDetailsArtifactsPageLocation>
) => {
const path = generatePath(MANAGEMENT_ROUTING_POLICY_DETAILS_HOST_ISOLATION_EXCEPTIONS_PATH, {
tabName: AdministrationSubTab.policies,
policyId,
});
return `${path}${appendSearch(
querystring.stringify(normalizePolicyDetailsArtifactsListPageLocation(location))
)}`;
};
30 changes: 30 additions & 0 deletions x-pack/plugins/security_solution/public/management/common/utils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -49,3 +49,33 @@ export const parsePoliciesToKQL = (includedPolicies: string, excludedPolicies: s

return `(${kuery.join(' AND ')})`;
};

/**
* Takes a list of policies (string[]) and an existing kuery
* (string) and returns an unified KQL with and AND
* @param policies string[] a list of policies ids
* @param kuery string an existing KQL.
*/
export const parsePoliciesAndFilterToKql = ({
policies,
kuery,
}: {
policies?: string[];
kuery?: string;
}): string | undefined => {
let kql: string | undefined;

if (policies && policies.length) {
const policiesKQL = parsePoliciesToKQL(policies.join(','), '');
kql = `(${policiesKQL})`;
}

if (kuery) {
if (kql) {
kql += ` AND (${kuery})`;
} else {
kql = `(${kuery})`;
}
}
return kql;
academo marked this conversation as resolved.
Show resolved Hide resolved
};
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@ import {
MANAGEMENT_STORE_HOST_ISOLATION_EXCEPTIONS_NAMESPACE,
} from '../../../common/constants';
import { getHostIsolationExceptionsListPath } from '../../../common/routing';
import { parseQueryFilterToKQL } from '../../../common/utils';
import { parsePoliciesAndFilterToKql, parseQueryFilterToKQL } from '../../../common/utils';
import {
getHostIsolationExceptionItems,
getHostIsolationExceptionSummary,
Expand Down Expand Up @@ -87,23 +87,37 @@ export function useCanSeeHostIsolationExceptionsMenu(): boolean {

const SEARCHABLE_FIELDS: Readonly<string[]> = [`name`, `description`, `entries.value`];

export function useFetchHostIsolationExceptionsList(): QueryObserverResult<
FoundExceptionListItemSchema,
ServerApiError
> {
export function useFetchHostIsolationExceptionsList({
filter,
page,
perPage,
policies,
enabled = true,
}: {
filter?: string;
page: number;
perPage: number;
policies?: string[];
enabled?: boolean;
}): QueryObserverResult<FoundExceptionListItemSchema, ServerApiError> {
const http = useHttp();
const location = useHostIsolationExceptionsSelector(getCurrentLocation);

return useQuery<FoundExceptionListItemSchema, ServerApiError>(
['hostIsolationExceptions', 'list', location.filter, location.page_size, location.page_index],
['hostIsolationExceptions', 'list', filter, perPage, page, policies],
() => {
const kql = parsePoliciesAndFilterToKql({
policies,
kuery: filter ? parseQueryFilterToKQL(filter, SEARCHABLE_FIELDS) : undefined,
});

return getHostIsolationExceptionItems({
http,
page: location.page_index + 1,
perPage: location.page_size,
filter: parseQueryFilterToKQL(location.filter, SEARCHABLE_FIELDS) || undefined,
page: page + 1,
perPage,
filter: kql,
});
}
},
{ enabled }
);
}

Expand All @@ -114,7 +128,7 @@ export function useGetHostIsolationExceptionFormEntry({
}: {
id?: string;
onSuccess: (data: CreateExceptionListItemSchema | UpdateExceptionListItemSchema) => void;
onError: (error: ServerApiError) => void;
onError?: (error: ServerApiError) => void;
ashokaditya marked this conversation as resolved.
Show resolved Hide resolved
}): QueryObserverResult {
const http = useHttp();
return useQuery<UpdateExceptionListItemSchema | CreateExceptionListItemSchema, ServerApiError>(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -91,7 +91,9 @@ describe('When on the host isolation exceptions page', () => {

describe('And data exists', () => {
beforeEach(async () => {
getHostIsolationExceptionItemsMock.mockImplementation(getFoundExceptionListItemSchemaMock);
getHostIsolationExceptionItemsMock.mockImplementation(() =>
getFoundExceptionListItemSchemaMock(1)
);
});

it('should show loading indicator while retrieving data and hide it when it gets it', async () => {
Expand Down Expand Up @@ -167,7 +169,7 @@ describe('When on the host isolation exceptions page', () => {
await waitFor(() =>
expect(getHostIsolationExceptionItemsMock).toHaveBeenLastCalledWith({
filter:
'(exception-list-agnostic.attributes.name:(*this*does*not*exists*) OR exception-list-agnostic.attributes.description:(*this*does*not*exists*) OR exception-list-agnostic.attributes.entries.value:(*this*does*not*exists*))',
'((exception-list-agnostic.attributes.name:(*this*does*not*exists*) OR exception-list-agnostic.attributes.description:(*this*does*not*exists*) OR exception-list-agnostic.attributes.entries.value:(*this*does*not*exists*)))',
academo marked this conversation as resolved.
Show resolved Hide resolved
ashokaditya marked this conversation as resolved.
Show resolved Hide resolved
http: mockedContext.coreStart.http,
page: 1,
perPage: 10,
Expand All @@ -185,7 +187,9 @@ describe('When on the host isolation exceptions page', () => {
describe('has canIsolateHost privileges', () => {
beforeEach(async () => {
setEndpointPrivileges({ canIsolateHost: true });
getHostIsolationExceptionItemsMock.mockImplementation(getFoundExceptionListItemSchemaMock);
getHostIsolationExceptionItemsMock.mockImplementation(() =>
getFoundExceptionListItemSchemaMock(1)
);
});

it('should show the create flyout when the add button is pressed', async () => {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -55,7 +55,12 @@ export const HostIsolationExceptionsList = () => {

const [itemToDelete, setItemToDelete] = useState<ExceptionListItemSchema | null>(null);

const { isLoading, data, error, refetch } = useFetchHostIsolationExceptionsList();
const { isLoading, data, error, refetch } = useFetchHostIsolationExceptionsList({
filter: location.filter,
page: location.page_index,
perPage: location.page_size,
});

const toasts = useToasts();

// load the list of policies>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ import {
MANAGEMENT_ROUTING_POLICY_DETAILS_TRUSTED_APPS_PATH,
MANAGEMENT_ROUTING_POLICY_DETAILS_EVENT_FILTERS_PATH,
MANAGEMENT_ROUTING_POLICY_DETAILS_PATH_OLD,
MANAGEMENT_ROUTING_POLICY_DETAILS_HOST_ISOLATION_EXCEPTIONS_PATH,
} from '../../common/constants';
import { NotFoundPage } from '../../../app/404';
import { getPolicyDetailPath } from '../../common/routing';
Expand All @@ -25,6 +26,7 @@ export const PolicyContainer = memo(() => {
MANAGEMENT_ROUTING_POLICY_DETAILS_FORM_PATH,
MANAGEMENT_ROUTING_POLICY_DETAILS_TRUSTED_APPS_PATH,
MANAGEMENT_ROUTING_POLICY_DETAILS_EVENT_FILTERS_PATH,
MANAGEMENT_ROUTING_POLICY_DETAILS_HOST_ISOLATION_EXCEPTIONS_PATH,
]}
exact
component={PolicyDetails}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,12 +7,13 @@

import { matchPath } from 'react-router-dom';
import { createSelector } from 'reselect';
import { PolicyDetailsSelector, PolicyDetailsState } from '../../../types';
import {
MANAGEMENT_ROUTING_POLICY_DETAILS_FORM_PATH,
MANAGEMENT_ROUTING_POLICY_DETAILS_HOST_ISOLATION_EXCEPTIONS_PATH,
MANAGEMENT_ROUTING_POLICY_DETAILS_TRUSTED_APPS_PATH,
MANAGEMENT_ROUTING_POLICY_DETAILS_EVENT_FILTERS_PATH,
} from '../../../../../common/constants';
import { PolicyDetailsSelector, PolicyDetailsState } from '../../../types';

/**
* Returns current artifacts location
Expand All @@ -37,7 +38,7 @@ export const isOnPolicyFormView: PolicyDetailsSelector<boolean> = createSelector
}
);

/** Returns a boolean of whether the user is on the policy trusted app page or not */
/** Returns a boolean of whether the user is on the policy details page or not */
academo marked this conversation as resolved.
Show resolved Hide resolved
export const isOnPolicyTrustedAppsView: PolicyDetailsSelector<boolean> = createSelector(
getUrlLocationPathname,
(pathname) => {
Expand All @@ -62,3 +63,16 @@ export const isOnPolicyEventFiltersView: PolicyDetailsSelector<boolean> = create
);
}
);

/** Returns a boolean of whether the user is on the host isolation exceptions page or not */
export const isOnHostIsolationExceptionsView: PolicyDetailsSelector<boolean> = createSelector(
getUrlLocationPathname,
(pathname) => {
return (
matchPath(pathname ?? '', {
path: MANAGEMENT_ROUTING_POLICY_DETAILS_HOST_ISOLATION_EXCEPTIONS_PATH,
exact: true,
}) !== null
);
}
);
Original file line number Diff line number Diff line change
Expand Up @@ -5,8 +5,8 @@
* 2.0.
*/

import { createSelector } from 'reselect';
import { matchPath } from 'react-router-dom';
import { createSelector } from 'reselect';
import { ILicense } from '../../../../../../../../licensing/common/types';
import { unsetPolicyFeaturesAccordingToLicenseLevel } from '../../../../../../../common/license/policy_config';
import { PolicyDetailsState } from '../../../types';
Expand All @@ -20,6 +20,7 @@ import {
import { policyFactory as policyConfigFactory } from '../../../../../../../common/endpoint/models/policy_config';
import {
MANAGEMENT_ROUTING_POLICY_DETAILS_FORM_PATH,
MANAGEMENT_ROUTING_POLICY_DETAILS_HOST_ISOLATION_EXCEPTIONS_PATH,
MANAGEMENT_ROUTING_POLICY_DETAILS_TRUSTED_APPS_PATH,
MANAGEMENT_ROUTING_POLICY_DETAILS_EVENT_FILTERS_PATH,
} from '../../../../../common/constants';
Expand All @@ -28,6 +29,7 @@ import { getPolicyDataForUpdate } from '../../../../../../../common/endpoint/ser
import {
isOnPolicyTrustedAppsView,
isOnPolicyEventFiltersView,
isOnHostIsolationExceptionsView,
isOnPolicyFormView,
} from './policy_common_selectors';

Expand Down Expand Up @@ -90,7 +92,8 @@ export const needsToRefresh = (state: Immutable<PolicyDetailsState>): boolean =>
export const isOnPolicyDetailsPage = (state: Immutable<PolicyDetailsState>) =>
isOnPolicyFormView(state) ||
isOnPolicyTrustedAppsView(state) ||
isOnPolicyEventFiltersView(state);
isOnPolicyEventFiltersView(state) ||
isOnHostIsolationExceptionsView(state);

/** Returns the license info fetched from the license service */
export const license = (state: Immutable<PolicyDetailsState>) => {
Expand All @@ -107,6 +110,7 @@ export const policyIdFromParams: (state: Immutable<PolicyDetailsState>) => strin
MANAGEMENT_ROUTING_POLICY_DETAILS_FORM_PATH,
MANAGEMENT_ROUTING_POLICY_DETAILS_TRUSTED_APPS_PATH,
MANAGEMENT_ROUTING_POLICY_DETAILS_EVENT_FILTERS_PATH,
MANAGEMENT_ROUTING_POLICY_DETAILS_HOST_ISOLATION_EXCEPTIONS_PATH,
],
exact: true,
})?.params?.policyId ?? ''
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
/*
* 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 { EuiButton, EuiEmptyPrompt, EuiPageTemplate } from '@elastic/eui';
import { FormattedMessage } from '@kbn/i18n-react';
import React from 'react';

export const PolicyHostIsolationExceptionsEmptyUnexisting = ({
academo marked this conversation as resolved.
Show resolved Hide resolved
toHostIsolationList,
}: {
toHostIsolationList: string;
}) => {
return (
<EuiPageTemplate template="centeredContent">
<EuiEmptyPrompt
iconType="plusInCircle"
data-test-subj="policy-host-isolation-exceptions-empty-unexisting"
title={
<h2>
<FormattedMessage
id="xpack.securitySolution.endpoint.policy.hostIsolationExceptions.empty.unexisting.title"
defaultMessage="No host isolation exceptions exist"
/>
</h2>
}
body={
<FormattedMessage
id="xpack.securitySolution.endpoint.policy.hostIsolationExceptions.empty.unexisting.content"
defaultMessage="There are currently no host isolation exceptions applied to your endpoints."
/>
}
actions={
<EuiButton color="primary" fill href={toHostIsolationList}>
<FormattedMessage
id="xpack.securitySolution.endpoint.policy.hostIsolationExceptions.empty.unexisting.action"
defaultMessage="Add host isolation exceptions"
/>
</EuiButton>
}
/>
</EuiPageTemplate>
);
};

PolicyHostIsolationExceptionsEmptyUnexisting.displayName =
'PolicyHostIsolationExceptionsEmptyUnexisting';
ashokaditya marked this conversation as resolved.
Show resolved Hide resolved
Loading