Skip to content

Commit

Permalink
[Security Solution][Endpoint Exceptions] Warning callout for incomple…
Browse files Browse the repository at this point in the history
…te code signature for endpoint exceptions (elastic#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)
  • Loading branch information
parkiino authored and tkajtoch committed Nov 12, 2024
1 parent 40c3db9 commit 0a5ffb5
Show file tree
Hide file tree
Showing 6 changed files with 230 additions and 58 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -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';
Expand Down Expand Up @@ -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(
<TestProviders>
<AddExceptionFlyout
rules={[
{
...getRulesSchemaMock(),
index: ['filebeat-*'],
exceptions_list: [
{
id: 'endpoint_list',
list_id: 'endpoint_list',
namespace_type: 'agnostic',
type: 'endpoint',
},
],
} as Rule,
]}
isBulkAction={false}
alertData={undefined}
isAlertDataLoading={undefined}
alertStatus={undefined}
isEndpointItem
showAlertCloseOptions
onCancel={jest.fn()}
onConfirm={jest.fn()}
/>
</TestProviders>
);
});

const mountWrapper = mount(
<TestProviders>
<AddExceptionFlyout
rules={[
it('should show a warning callout if wildcard is used', async () => {
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()}
/>
</TestProviders>
);
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();
});
});
});

Expand Down Expand Up @@ -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 });
});
});
});
Original file line number Diff line number Diff line change
Expand Up @@ -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';
Expand Down Expand Up @@ -158,6 +164,7 @@ export const AddExceptionFlyout = memo(function AddExceptionFlyout({
expireTime,
expireErrorExists,
wildcardWarningExists,
partialCodeSignatureWarningExists,
},
dispatch,
] = useReducer(createExceptionItemsReducer(), {
Expand Down Expand Up @@ -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,
Expand Down Expand Up @@ -564,6 +575,7 @@ export const AddExceptionFlyout = memo(function AddExceptionFlyout({
getExtendedFields={getExtendedFields}
/>
{wildcardWarningExists && <WildCardWithWrongOperatorCallout />}
{partialCodeSignatureWarningExists && <PartialCodeSignatureCallout />}
{listType !== ExceptionListTypeEnum.ENDPOINT && !sharedListToAddTo?.length && (
<>
<EuiHorizontalRule />
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,7 @@ export interface State {
expireTime: Moment | undefined;
expireErrorExists: boolean;
wildcardWarningExists: boolean;
partialCodeSignatureWarningExists: boolean;
}

export const initialState: State = {
Expand All @@ -57,6 +58,7 @@ export const initialState: State = {
expireTime: undefined,
expireErrorExists: false,
wildcardWarningExists: false,
partialCodeSignatureWarningExists: false,
};

export type Action =
Expand Down Expand Up @@ -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': {
Expand Down Expand Up @@ -184,6 +190,13 @@ export const createExceptionItemsReducer =
wildcardWarningExists: warningExists,
};
}
case 'setPartialCodeSignature': {
const { warningExists } = action;
return {
...state,
partialCodeSignatureWarningExists: warningExists,
};
}
case 'setComment': {
const { comment } = action;

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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', () => {
Expand Down
Loading

0 comments on commit 0a5ffb5

Please sign in to comment.