From 0a5ffb5ce274e61cf9a17f1e0c2404577848cccc Mon Sep 17 00:00:00 2001
From: Candace Park <56409205+parkiino@users.noreply.github.com>
Date: Tue, 12 Nov 2024 12:11:16 -0500
Subject: [PATCH] [Security Solution][Endpoint Exceptions] Warning callout for
incomplete code signature for endpoint exceptions (#198245)
## Summary
Navigate to Security Solution > Manage > Rules > Add Endpoint Exception
- [x] Warning callout shown in endpoint exceptions when code signature
field is incomplete (i.e. process.code_signature.subject_name w/o
process.code_signature.trusted or vice versa)
- [x] For mac operating systems, process.code_signature.team_id is also
accepted as an equivalent to subject_name
- [ ] Warning callout is also shown for nested entries for this code
signature field: process.Ext.code_signature
- [x] Unit Tests
# Screenshots
Subject name only -- warning is present
![image](https://github.com/user-attachments/assets/eccf4d49-a4b1-47fc-8c51-bddf4fd6664f)
Trusted field only -- warning is present
![image](https://github.com/user-attachments/assets/d3ba6716-e7d1-4709-a5b1-1e472964b6e3)
Both subject name and trusted fields -- no warning is present
![image](https://github.com/user-attachments/assets/11b179ff-278e-4ec6-a749-638f428215aa)
---
.../add_exception_flyout/index.test.tsx | 176 ++++++++++++------
.../components/add_exception_flyout/index.tsx | 16 +-
.../add_exception_flyout/reducer.ts | 15 +-
.../edit_exception_flyout/index.test.tsx | 52 ++++++
.../edit_exception_flyout/index.tsx | 17 +-
.../edit_exception_flyout/reducer.ts | 12 ++
6 files changed, 230 insertions(+), 58 deletions(-)
diff --git a/x-pack/plugins/security_solution/public/detection_engine/rule_exceptions/components/add_exception_flyout/index.test.tsx b/x-pack/plugins/security_solution/public/detection_engine/rule_exceptions/components/add_exception_flyout/index.test.tsx
index 0d2e937931e21..269f4db62f305 100644
--- a/x-pack/plugins/security_solution/public/detection_engine/rule_exceptions/components/add_exception_flyout/index.test.tsx
+++ b/x-pack/plugins/security_solution/public/detection_engine/rule_exceptions/components/add_exception_flyout/index.test.tsx
@@ -16,6 +16,7 @@ import { getExceptionListItemSchemaMock } from '@kbn/lists-plugin/common/schemas
import { createStubIndexPattern, stubIndexPattern } from '@kbn/data-plugin/common/stubs';
import { AddExceptionFlyout } from '.';
+import { initialState as exceptionItemsInitialState, createExceptionItemsReducer } from './reducer';
import { useFetchIndex } from '../../../../common/containers/source';
import { useCreateOrUpdateException } from '../../logic/use_create_update_exception';
import { useFetchIndexPatterns } from '../../logic/use_exception_flyout_data';
@@ -153,65 +154,123 @@ describe('When the add exception modal is opened', () => {
expect(wrapper.find('ExceptionsAddToRulesOrLists').exists()).toBeFalsy();
});
- it('should show a warning callout if wildcard is used', async () => {
- mockUseFetchIndex.mockImplementation(() => [
- false,
- {
- indexPatterns: stubIndexPattern,
- },
- ]);
+ describe('warning callouts', () => {
+ let mountWrapper: ReactWrapper;
+ beforeEach(() => {
+ mockUseFetchIndex.mockImplementation(() => [
+ false,
+ {
+ indexPatterns: stubIndexPattern,
+ },
+ ]);
+
+ mountWrapper = mount(
+
+
+
+ );
+ });
- const mountWrapper = mount(
-
- {
+ const callProps = mockGetExceptionBuilderComponentLazy.mock.calls[0][0];
+ await waitFor(() =>
+ callProps.onChange({
+ exceptionItems: [
{
- ...getRulesSchemaMock(),
- index: ['filebeat-*'],
- exceptions_list: [
+ ...getExceptionListItemSchemaMock(),
+ entries: [
{
- id: 'endpoint_list',
- list_id: 'endpoint_list',
- namespace_type: 'agnostic',
- type: 'endpoint',
+ field: 'event.category',
+ operator: 'included',
+ type: 'match',
+ value: 'wildcardvalue*?',
},
],
- } as Rule,
- ]}
- isBulkAction={false}
- alertData={undefined}
- isAlertDataLoading={undefined}
- alertStatus={undefined}
- isEndpointItem
- showAlertCloseOptions
- onCancel={jest.fn()}
- onConfirm={jest.fn()}
- />
-
- );
- const callProps = mockGetExceptionBuilderComponentLazy.mock.calls[0][0];
- await waitFor(() =>
- callProps.onChange({
- exceptionItems: [
- {
- ...getExceptionListItemSchemaMock(),
- entries: [
- {
- field: 'event.category',
- operator: 'included',
- type: 'match',
- value: 'wildcardvalue*?',
- },
- ],
- },
- ],
- })
- );
+ },
+ ],
+ })
+ );
- mountWrapper.update();
- expect(
- mountWrapper.find('[data-test-subj="wildcardWithWrongOperatorCallout"]').exists()
- ).toBeTruthy();
+ mountWrapper.update();
+ expect(
+ mountWrapper.find('[data-test-subj="wildcardWithWrongOperatorCallout"]').exists()
+ ).toBeTruthy();
+ });
+
+ it('should show a warning callout if there is a partial code signature entry with only subject_name', async () => {
+ const callProps = mockGetExceptionBuilderComponentLazy.mock.calls[0][0];
+ await waitFor(() =>
+ callProps.onChange({
+ exceptionItems: [
+ {
+ ...getExceptionListItemSchemaMock(),
+ entries: [
+ {
+ field: 'process.code_signature.subject_name',
+ operator: 'included',
+ type: 'match',
+ value: 'asdf',
+ },
+ ],
+ },
+ ],
+ })
+ );
+
+ mountWrapper.update();
+ expect(
+ mountWrapper.find('[data-test-subj="partialCodeSignatureCallout"]').exists()
+ ).toBeTruthy();
+ });
+
+ it('should show a warning callout if there is a partial code signature entry with only trusted field', async () => {
+ const callProps = mockGetExceptionBuilderComponentLazy.mock.calls[0][0];
+ await waitFor(() =>
+ callProps.onChange({
+ exceptionItems: [
+ {
+ ...getExceptionListItemSchemaMock(),
+ entries: [
+ {
+ field: 'process.code_signature.trusted',
+ operator: 'included',
+ type: 'match',
+ value: 'true',
+ },
+ ],
+ },
+ ],
+ })
+ );
+
+ mountWrapper.update();
+ expect(
+ mountWrapper.find('[data-test-subj="partialCodeSignatureCallout"]').exists()
+ ).toBeTruthy();
+ });
});
});
@@ -961,4 +1020,15 @@ describe('When the add exception modal is opened', () => {
});
});
});
+ describe('the reducer', () => {
+ it('should update partialCodeSignatureWarningExists, when warning is true', () => {
+ const updatedState = createExceptionItemsReducer();
+ expect(
+ updatedState(exceptionItemsInitialState, {
+ type: 'setPartialCodeSignature',
+ warningExists: true,
+ })
+ ).toEqual({ ...exceptionItemsInitialState, partialCodeSignatureWarningExists: true });
+ });
+ });
});
diff --git a/x-pack/plugins/security_solution/public/detection_engine/rule_exceptions/components/add_exception_flyout/index.tsx b/x-pack/plugins/security_solution/public/detection_engine/rule_exceptions/components/add_exception_flyout/index.tsx
index 85790713cfa3f..092e3e779f5b5 100644
--- a/x-pack/plugins/security_solution/public/detection_engine/rule_exceptions/components/add_exception_flyout/index.tsx
+++ b/x-pack/plugins/security_solution/public/detection_engine/rule_exceptions/components/add_exception_flyout/index.tsx
@@ -26,13 +26,19 @@ import {
import { ENDPOINT_LIST_ID } from '@kbn/securitysolution-list-constants';
import { ExceptionListTypeEnum } from '@kbn/securitysolution-io-ts-list-types';
import type { OsTypeArray, ExceptionListSchema } from '@kbn/securitysolution-io-ts-list-types';
-import { hasWrongOperatorWithWildcard } from '@kbn/securitysolution-list-utils';
+import {
+ hasWrongOperatorWithWildcard,
+ hasPartialCodeSignatureEntry,
+} from '@kbn/securitysolution-list-utils';
import type {
ExceptionsBuilderExceptionItem,
ExceptionsBuilderReturnExceptionItem,
} from '@kbn/securitysolution-list-utils';
-import { WildCardWithWrongOperatorCallout } from '@kbn/securitysolution-exception-list-components';
+import {
+ WildCardWithWrongOperatorCallout,
+ PartialCodeSignatureCallout,
+} from '@kbn/securitysolution-exception-list-components';
import type { Moment } from 'moment';
import type { Status } from '../../../../../common/api/detection_engine';
import * as i18n from './translations';
@@ -158,6 +164,7 @@ export const AddExceptionFlyout = memo(function AddExceptionFlyout({
expireTime,
expireErrorExists,
wildcardWarningExists,
+ partialCodeSignatureWarningExists,
},
dispatch,
] = useReducer(createExceptionItemsReducer(), {
@@ -190,6 +197,10 @@ export const AddExceptionFlyout = memo(function AddExceptionFlyout({
type: 'setWildcardWithWrongOperator',
warningExists: hasWrongOperatorWithWildcard(items),
});
+ dispatch({
+ type: 'setPartialCodeSignature',
+ warningExists: hasPartialCodeSignatureEntry(items),
+ });
dispatch({
type: 'setExceptionItems',
items,
@@ -564,6 +575,7 @@ export const AddExceptionFlyout = memo(function AddExceptionFlyout({
getExtendedFields={getExtendedFields}
/>
{wildcardWarningExists && }
+ {partialCodeSignatureWarningExists && }
{listType !== ExceptionListTypeEnum.ENDPOINT && !sharedListToAddTo?.length && (
<>
diff --git a/x-pack/plugins/security_solution/public/detection_engine/rule_exceptions/components/add_exception_flyout/reducer.ts b/x-pack/plugins/security_solution/public/detection_engine/rule_exceptions/components/add_exception_flyout/reducer.ts
index 01b0ce85b45d1..e91c1c9328a7b 100644
--- a/x-pack/plugins/security_solution/public/detection_engine/rule_exceptions/components/add_exception_flyout/reducer.ts
+++ b/x-pack/plugins/security_solution/public/detection_engine/rule_exceptions/components/add_exception_flyout/reducer.ts
@@ -35,6 +35,7 @@ export interface State {
expireTime: Moment | undefined;
expireErrorExists: boolean;
wildcardWarningExists: boolean;
+ partialCodeSignatureWarningExists: boolean;
}
export const initialState: State = {
@@ -57,6 +58,7 @@ export const initialState: State = {
expireTime: undefined,
expireErrorExists: false,
wildcardWarningExists: false,
+ partialCodeSignatureWarningExists: false,
};
export type Action =
@@ -135,11 +137,15 @@ export type Action =
| {
type: 'setWildcardWithWrongOperator';
warningExists: boolean;
+ }
+ | {
+ type: 'setPartialCodeSignature';
+ warningExists: boolean;
};
export const createExceptionItemsReducer =
() =>
- /* eslint complexity: ["error", 22]*/
+ /* eslint complexity: ["error", 23]*/
(state: State, action: Action): State => {
switch (action.type) {
case 'setExceptionItemMeta': {
@@ -184,6 +190,13 @@ export const createExceptionItemsReducer =
wildcardWarningExists: warningExists,
};
}
+ case 'setPartialCodeSignature': {
+ const { warningExists } = action;
+ return {
+ ...state,
+ partialCodeSignatureWarningExists: warningExists,
+ };
+ }
case 'setComment': {
const { comment } = action;
diff --git a/x-pack/plugins/security_solution/public/detection_engine/rule_exceptions/components/edit_exception_flyout/index.test.tsx b/x-pack/plugins/security_solution/public/detection_engine/rule_exceptions/components/edit_exception_flyout/index.test.tsx
index 07723db415daf..13c8c59e07732 100644
--- a/x-pack/plugins/security_solution/public/detection_engine/rule_exceptions/components/edit_exception_flyout/index.test.tsx
+++ b/x-pack/plugins/security_solution/public/detection_engine/rule_exceptions/components/edit_exception_flyout/index.test.tsx
@@ -313,6 +313,58 @@ describe('When the edit exception modal is opened', () => {
wrapper.find('[data-test-subj="wildcardWithWrongOperatorCallout"]').exists()
).toBeTruthy();
});
+
+ it('should show a warning callout if there is a partial code signature entry with only subject_name', async () => {
+ const callProps = mockGetExceptionBuilderComponentLazy.mock.calls[0][0];
+ await waitFor(() =>
+ callProps.onChange({
+ exceptionItems: [
+ {
+ ...getExceptionListItemSchemaMock(),
+ entries: [
+ {
+ field: 'process.code_signature.subject_name',
+ operator: 'included',
+ type: 'match',
+ value: 'asdf',
+ },
+ ],
+ },
+ ],
+ })
+ );
+
+ wrapper.update();
+ expect(
+ wrapper.find('[data-test-subj="partialCodeSignatureCallout"]').exists()
+ ).toBeTruthy();
+ });
+
+ it('should show a warning callout if there is a partial code signature entry with only trusted field', async () => {
+ const callProps = mockGetExceptionBuilderComponentLazy.mock.calls[0][0];
+ await waitFor(() =>
+ callProps.onChange({
+ exceptionItems: [
+ {
+ ...getExceptionListItemSchemaMock(),
+ entries: [
+ {
+ field: 'process.code_signature.trusted',
+ operator: 'included',
+ type: 'match',
+ value: 'true',
+ },
+ ],
+ },
+ ],
+ })
+ );
+
+ wrapper.update();
+ expect(
+ wrapper.find('[data-test-subj="partialCodeSignatureCallout"]').exists()
+ ).toBeTruthy();
+ });
});
describe('when exception entry fields and index allow user to bulk close', () => {
diff --git a/x-pack/plugins/security_solution/public/detection_engine/rule_exceptions/components/edit_exception_flyout/index.tsx b/x-pack/plugins/security_solution/public/detection_engine/rule_exceptions/components/edit_exception_flyout/index.tsx
index b6336e3326e25..e45cb4ab742ad 100644
--- a/x-pack/plugins/security_solution/public/detection_engine/rule_exceptions/components/edit_exception_flyout/index.tsx
+++ b/x-pack/plugins/security_solution/public/detection_engine/rule_exceptions/components/edit_exception_flyout/index.tsx
@@ -26,10 +26,16 @@ import {
ExceptionListTypeEnum,
} from '@kbn/securitysolution-io-ts-list-types';
-import { hasWrongOperatorWithWildcard } from '@kbn/securitysolution-list-utils';
+import {
+ hasWrongOperatorWithWildcard,
+ hasPartialCodeSignatureEntry,
+} from '@kbn/securitysolution-list-utils';
import type { ExceptionsBuilderReturnExceptionItem } from '@kbn/securitysolution-list-utils';
-import { WildCardWithWrongOperatorCallout } from '@kbn/securitysolution-exception-list-components';
+import {
+ WildCardWithWrongOperatorCallout,
+ PartialCodeSignatureCallout,
+} from '@kbn/securitysolution-exception-list-components';
import type { Moment } from 'moment';
import moment from 'moment';
@@ -116,6 +122,7 @@ const EditExceptionFlyoutComponent: React.FC = ({
expireTime,
expireErrorExists,
wildcardWarningExists,
+ partialCodeSignatureWarningExists,
},
dispatch,
] = useReducer(createExceptionItemsReducer(), {
@@ -130,6 +137,7 @@ const EditExceptionFlyoutComponent: React.FC = ({
expireTime: itemToEdit.expire_time !== undefined ? moment(itemToEdit.expire_time) : undefined,
expireErrorExists: false,
wildcardWarningExists: false,
+ partialCodeSignatureWarningExists: false,
});
const allowLargeValueLists = useMemo((): boolean => {
@@ -170,6 +178,10 @@ const EditExceptionFlyoutComponent: React.FC = ({
type: 'setWildcardWithWrongOperator',
warningExists: hasWrongOperatorWithWildcard(items),
});
+ dispatch({
+ type: 'setPartialCodeSignature',
+ warningExists: hasPartialCodeSignatureEntry(items),
+ });
dispatch({
type: 'setExceptionItems',
items,
@@ -417,6 +429,7 @@ const EditExceptionFlyoutComponent: React.FC = ({
getExtendedFields={getExtendedFields}
/>
{wildcardWarningExists && }
+ {partialCodeSignatureWarningExists && }
{!openedFromListDetailPage && listType === ExceptionListTypeEnum.DETECTION && (
<>
diff --git a/x-pack/plugins/security_solution/public/detection_engine/rule_exceptions/components/edit_exception_flyout/reducer.ts b/x-pack/plugins/security_solution/public/detection_engine/rule_exceptions/components/edit_exception_flyout/reducer.ts
index 351af20900291..ee92dd28ca597 100644
--- a/x-pack/plugins/security_solution/public/detection_engine/rule_exceptions/components/edit_exception_flyout/reducer.ts
+++ b/x-pack/plugins/security_solution/public/detection_engine/rule_exceptions/components/edit_exception_flyout/reducer.ts
@@ -20,6 +20,7 @@ export interface State {
expireTime: Moment | undefined;
expireErrorExists: boolean;
wildcardWarningExists: boolean;
+ partialCodeSignatureWarningExists: boolean;
}
export type Action =
@@ -66,6 +67,10 @@ export type Action =
| {
type: 'setWildcardWithWrongOperator';
warningExists: boolean;
+ }
+ | {
+ type: 'setPartialCodeSignature';
+ warningExists: boolean;
};
export const createExceptionItemsReducer =
@@ -162,6 +167,13 @@ export const createExceptionItemsReducer =
wildcardWarningExists: warningExists,
};
}
+ case 'setPartialCodeSignature': {
+ const { warningExists } = action;
+ return {
+ ...state,
+ partialCodeSignatureWarningExists: warningExists,
+ };
+ }
default:
return state;
}