Skip to content

Commit

Permalink
[Security Solution][Exceptions] - Exception Modal Part I (#70639)
Browse files Browse the repository at this point in the history
* adds 2 menu items to alert page, progress on exception modal

* adds enriching

* remove unused useExceptionList()

* implements some types

* move add exception modal files

* Exception builder changes to support latest schema

* Changes to lists plugin schemas and fix api bug

Needed to make the schemas more forgiving. Before this change they required name,
description, etc for creation and update.

The update item API was using the wrong url.

* Adding and editing exceptions working

- Modifies add_exception_modal component
- Creates edit_exception_modal component
- Creates shared comments component
- Creates use_add_exception api hook for adding or editing exceptions
- Updates viewer code to support adding and editing exceptions
- Updates alerts table code to use updated version of add_exception_modal

* fixes duplicate types

* updates os tag input

* fixes comment style

* removes checkbox programatically

* grahpql updates to expose exceptions_list

* Add fetch_or_create_exception_list hook

* fixes data population

* refactor use_add_exception hook, add tests

* fix rebase issues, pending updates to edit modal

* fix edit modal and default endpoint exceptions

* adds second checkbox

* adds signal index stuff

* switches boolean logic

* fix some type errors

* remove unnecesary code

* fixes checkbox logic in edit modal

* fixes recursive prop passing

* addresses comments/fixes types

* Revert schema type changes

* type fixes

* fixes regular exception modal

* fix more type errors, remove console log

* fix tests

* move add exception hook, lint

* close alert checkbox closes alert

* address PR comments

* add type to patch rule call, fix ts errors

* fix lint

* fix merge problems after conflict

* Address PR comments

* undo graphql type change

Co-authored-by: Davis Plumlee <[email protected]>
  • Loading branch information
peluja1012 and dplumlee authored Jul 8, 2020
1 parent 5f53597 commit 8facae7
Show file tree
Hide file tree
Showing 26 changed files with 2,212 additions and 64 deletions.
6 changes: 6 additions & 0 deletions x-pack/plugins/lists/public/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,12 @@ export { useFindLists } from './lists/hooks/use_find_lists';
export { useImportList } from './lists/hooks/use_import_list';
export { useDeleteList } from './lists/hooks/use_delete_list';
export { useExportList } from './lists/hooks/use_export_list';
export {
addExceptionListItem,
updateExceptionListItem,
fetchExceptionListById,
addExceptionList,
} from './exceptions/api';
export {
ExceptionList,
ExceptionIdentifiers,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,8 @@ import {
UpdateTimelineLoading,
} from './types';
import { Ecs } from '../../../graphql/types';
import { AddExceptionOnClick } from '../../../common/components/exceptions/add_exception_modal';
import { getMappedNonEcsValue } from '../../../common/components/exceptions/helpers';

export const buildAlertStatusFilter = (status: Status): Filter[] => [
{
Expand Down Expand Up @@ -172,6 +174,13 @@ export const requiredFieldsForActions = [
'signal.rule.query',
'signal.rule.to',
'signal.rule.id',

// Endpoint exception fields
'file.path',
'file.Ext.code_signature.subject_name',
'file.Ext.code_signature.trusted',
'file.hash.sha1',
'host.os.family',
];

interface AlertActionArgs {
Expand All @@ -188,6 +197,12 @@ interface AlertActionArgs {
status: Status;
timelineId: string;
updateTimelineIsLoading: UpdateTimelineLoading;
openAddExceptionModal: ({
exceptionListType,
alertData,
ruleName,
ruleId,
}: AddExceptionOnClick) => void;
}

export const getAlertActions = ({
Expand All @@ -204,6 +219,7 @@ export const getAlertActions = ({
status,
timelineId,
updateTimelineIsLoading,
openAddExceptionModal,
}: AlertActionArgs): TimelineRowAction[] => {
const openAlertActionComponent: TimelineRowAction = {
ariaLabel: 'Open alert',
Expand Down Expand Up @@ -289,5 +305,52 @@ export const getAlertActions = ({
...(FILTER_OPEN !== status ? [openAlertActionComponent] : []),
...(FILTER_CLOSED !== status ? [closeAlertActionComponent] : []),
...(FILTER_IN_PROGRESS !== status ? [inProgressAlertActionComponent] : []),
// TODO: disable this option if the alert is not an Endpoint alert
{
onClick: ({ ecsData, data }: TimelineRowActionOnClick) => {
const ruleNameValue = getMappedNonEcsValue({ data, fieldName: 'signal.rule.name' });
const ruleId = getMappedNonEcsValue({ data, fieldName: 'signal.rule.id' });
if (ruleId !== undefined && ruleId.length > 0) {
openAddExceptionModal({
ruleName: ruleNameValue ? ruleNameValue[0] : '',
ruleId: ruleId[0],
exceptionListType: 'endpoint',
alertData: {
ecsData,
nonEcsData: data,
},
});
}
},
id: 'addEndpointException',
isActionDisabled: () => !canUserCRUD || !hasIndexWrite,
dataTestSubj: 'add-endpoint-exception-menu-item',
ariaLabel: 'Add Endpoint Exception',
content: <EuiText size="m">{i18n.ACTION_ADD_ENDPOINT_EXCEPTION}</EuiText>,
displayType: 'contextMenu',
},
{
onClick: ({ ecsData, data }: TimelineRowActionOnClick) => {
const ruleNameValue = getMappedNonEcsValue({ data, fieldName: 'signal.rule.name' });
const ruleId = getMappedNonEcsValue({ data, fieldName: 'signal.rule.id' });
if (ruleId !== undefined && ruleId.length > 0) {
openAddExceptionModal({
ruleName: ruleNameValue ? ruleNameValue[0] : '',
ruleId: ruleId[0],
exceptionListType: 'detection',
alertData: {
ecsData,
nonEcsData: data,
},
});
}
},
id: 'addException',
isActionDisabled: () => !canUserCRUD || !hasIndexWrite,
dataTestSubj: 'add-exception-menu-item',
ariaLabel: 'Add Exception',
content: <EuiText size="m">{i18n.ACTION_ADD_EXCEPTION}</EuiText>,
displayType: 'contextMenu',
},
];
};
Original file line number Diff line number Diff line change
Expand Up @@ -50,6 +50,10 @@ import {
} from '../../../common/components/toasters';
import { Ecs } from '../../../graphql/types';
import { getInvestigateInResolverAction } from '../../../timelines/components/timeline/body/helpers';
import {
AddExceptionModal,
AddExceptionOnClick,
} from '../../../common/components/exceptions/add_exception_modal';

interface OwnProps {
timelineId: TimelineIdLiteral;
Expand All @@ -64,6 +68,13 @@ interface OwnProps {

type AlertsTableComponentProps = OwnProps & PropsFromRedux;

const addExceptionModalInitialState: AddExceptionOnClick = {
ruleName: '',
ruleId: '',
exceptionListType: 'detection',
alertData: undefined,
};

export const AlertsTableComponent: React.FC<AlertsTableComponentProps> = ({
timelineId,
canUserCRUD,
Expand Down Expand Up @@ -92,6 +103,10 @@ export const AlertsTableComponent: React.FC<AlertsTableComponentProps> = ({

const [showClearSelectionAction, setShowClearSelectionAction] = useState(false);
const [filterGroup, setFilterGroup] = useState<Status>(FILTER_OPEN);
const [shouldShowAddExceptionModal, setShouldShowAddExceptionModal] = useState(false);
const [addExceptionModalState, setAddExceptionModalState] = useState<AddExceptionOnClick>(
addExceptionModalInitialState
);
const [{ browserFields, indexPatterns }] = useFetchIndexPatterns(
signalsIndex !== '' ? [signalsIndex] : []
);
Expand Down Expand Up @@ -192,6 +207,21 @@ export const AlertsTableComponent: React.FC<AlertsTableComponentProps> = ({
[dispatchToaster]
);

const openAddExceptionModalCallback = useCallback(
({ ruleName, ruleId, exceptionListType, alertData }: AddExceptionOnClick) => {
if (alertData !== null && alertData !== undefined) {
setShouldShowAddExceptionModal(true);
setAddExceptionModalState({
ruleName,
ruleId,
exceptionListType,
alertData,
});
}
},
[setShouldShowAddExceptionModal, setAddExceptionModalState]
);

// Catches state change isSelectAllChecked->false upon user selection change to reset utility bar
useEffect(() => {
if (!isSelectAllChecked) {
Expand Down Expand Up @@ -306,6 +336,7 @@ export const AlertsTableComponent: React.FC<AlertsTableComponentProps> = ({
status: filterGroup,
timelineId,
updateTimelineIsLoading,
openAddExceptionModal: openAddExceptionModalCallback,
}),
[
apolloClient,
Expand All @@ -320,6 +351,7 @@ export const AlertsTableComponent: React.FC<AlertsTableComponentProps> = ({
updateTimelineIsLoading,
onAlertStatusUpdateSuccess,
onAlertStatusUpdateFailure,
openAddExceptionModalCallback,
]
);
const defaultIndices = useMemo(() => [signalsIndex], [signalsIndex]);
Expand Down Expand Up @@ -360,6 +392,19 @@ export const AlertsTableComponent: React.FC<AlertsTableComponentProps> = ({
[onFilterGroupChangedCallback]
);

const closeAddExceptionModal = useCallback(() => {
setShouldShowAddExceptionModal(false);
setAddExceptionModalState(addExceptionModalInitialState);
}, [setShouldShowAddExceptionModal, setAddExceptionModalState]);

const onAddExceptionCancel = useCallback(() => {
closeAddExceptionModal();
}, [closeAddExceptionModal]);

const onAddExceptionConfirm = useCallback(() => {
closeAddExceptionModal();
}, [closeAddExceptionModal]);

if (loading || isEmpty(signalsIndex)) {
return (
<EuiPanel>
Expand All @@ -370,16 +415,28 @@ export const AlertsTableComponent: React.FC<AlertsTableComponentProps> = ({
}

return (
<StatefulEventsViewer
defaultIndices={defaultIndices}
pageFilters={defaultFiltersMemo}
defaultModel={alertsDefaultModel}
end={to}
headerFilterGroup={headerFilterGroup}
id={timelineId}
start={from}
utilityBar={utilityBarCallback}
/>
<>
<StatefulEventsViewer
defaultIndices={defaultIndices}
pageFilters={defaultFiltersMemo}
defaultModel={alertsDefaultModel}
end={to}
headerFilterGroup={headerFilterGroup}
id={timelineId}
start={from}
utilityBar={utilityBarCallback}
/>
{shouldShowAddExceptionModal === true && addExceptionModalState.alertData !== null && (
<AddExceptionModal
ruleName={addExceptionModalState.ruleName}
ruleId={addExceptionModalState.ruleId}
exceptionListType={addExceptionModalState.exceptionListType}
alertData={addExceptionModalState.alertData}
onCancel={onAddExceptionCancel}
onConfirm={onAddExceptionConfirm}
/>
)}
</>
);
};

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -122,6 +122,20 @@ export const ACTION_INVESTIGATE_IN_TIMELINE = i18n.translate(
}
);

export const ACTION_ADD_EXCEPTION = i18n.translate(
'xpack.securitySolution.detectionEngine.alerts.actions.addException',
{
defaultMessage: 'Add exception',
}
);

export const ACTION_ADD_ENDPOINT_EXCEPTION = i18n.translate(
'xpack.securitySolution.detectionEngine.alerts.actions.addEndpointException',
{
defaultMessage: 'Add Endpoint exception',
}
);

export const CLOSED_ALERT_SUCCESS_TOAST = (totalAlerts: number) =>
i18n.translate('xpack.securitySolution.detectionEngine.alerts.closedAlertSuccessToastMessage', {
values: { totalAlerts },
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@

import {
AddRulesProps,
PatchRuleProps,
NewRule,
PrePackagedRulesStatusResponse,
BasicFetchProps,
Expand All @@ -20,6 +21,9 @@ import { ruleMock, savedRuleMock, rulesMock } from '../mock';
export const addRule = async ({ rule, signal }: AddRulesProps): Promise<NewRule> =>
Promise.resolve(ruleMock);

export const patchRule = async ({ ruleProperties, signal }: PatchRuleProps): Promise<NewRule> =>
Promise.resolve(ruleMock);

export const getPrePackagedRulesStatus = async ({
signal,
}: {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@ import {
ImportDataResponse,
PrePackagedRulesStatusResponse,
BulkRuleResponse,
PatchRuleProps,
} from './types';
import { KibanaServices } from '../../../../common/lib/kibana';
import * as i18n from '../../../pages/detection_engine/rules/translations';
Expand All @@ -47,6 +48,21 @@ export const addRule = async ({ rule, signal }: AddRulesProps): Promise<NewRule>
signal,
});

/**
* Patch provided Rule
*
* @param ruleProperties to patch
* @param signal to cancel request
*
* @throws An error if response is not OK
*/
export const patchRule = async ({ ruleProperties, signal }: PatchRuleProps): Promise<NewRule> =>
KibanaServices.get().http.fetch<NewRule>(DETECTION_ENGINE_RULES_URL, {
method: 'PATCH',
body: JSON.stringify(ruleProperties),
signal,
});

/**
* Fetches all rules from the Detection Engine API
*
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@ import {
listArray,
listArrayOrUndefined,
} from '../../../../../common/detection_engine/schemas/types';
import { PatchRulesSchema } from '../../../../../common/detection_engine/schemas/request/patch_rules_schema';

/**
* Params is an "record", since it is a type of AlertActionParams which is action templates.
Expand Down Expand Up @@ -80,6 +81,11 @@ export interface AddRulesProps {
signal: AbortSignal;
}

export interface PatchRuleProps {
ruleProperties: PatchRulesSchema;
signal: AbortSignal;
}

const MetaRule = t.intersection([
t.type({
from: t.string,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -439,6 +439,7 @@ export const RuleDetailsPageComponent: FC<PropsFromRedux> = ({
{ruleDetailTab === RuleDetailTabs.exceptions && (
<ExceptionsViewer
ruleId={ruleId ?? ''}
ruleName={rule?.name ?? ''}
availableListTypes={exceptionLists.allowedExceptionListTypes}
commentsAccordionId={'ruleDetailsTabExceptions'}
exceptionListsMeta={exceptionLists.lists}
Expand Down
Loading

0 comments on commit 8facae7

Please sign in to comment.