[Security Solutions][Detections] - Fix exception list table referential deletion (#87231)
### Summary This PR concentrates on fixing the deletion on the exceptions list table view. This fix is intermediary and a more thorough, backend solution is needed. Currently, if you delete an exception list, it deletes the exception list SO, but does not remove references to it from rules. This PR allows for a quick fix conducting this logic client side.
This commit is contained in:
parent
a4bbf470e2
commit
51efc19920
|
@ -86,7 +86,7 @@ export interface ApiCallByIdProps {
|
|||
export interface ApiCallMemoProps {
|
||||
id: string;
|
||||
namespaceType: NamespaceType;
|
||||
onError: (arg: string[]) => void;
|
||||
onError: (arg: Error) => void;
|
||||
onSuccess: () => void;
|
||||
}
|
||||
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
{
|
||||
"list_id": "detection_list_1",
|
||||
"item_id": "simple_list_item_two_non-value_list",
|
||||
"item_id": "simple_list_item_one_non-value_list",
|
||||
"tags": [
|
||||
"user added string for a tag",
|
||||
"malware"
|
||||
|
|
|
@ -0,0 +1,28 @@
|
|||
{
|
||||
"list_id": "detection_list_2",
|
||||
"item_id": "simple_list_item_two_non-value_list",
|
||||
"tags": [
|
||||
"user added string for a tag",
|
||||
"malware"
|
||||
],
|
||||
"type": "simple",
|
||||
"description": "This is a sample exception list item with two non-value list entries",
|
||||
"name": "Sample Detection Exception List Item",
|
||||
"os_types": [
|
||||
"windows"
|
||||
],
|
||||
"comments": [],
|
||||
"entries": [
|
||||
{
|
||||
"field": "actingProcess.file.signer",
|
||||
"operator": "excluded",
|
||||
"type": "exists"
|
||||
},
|
||||
{
|
||||
"field": "host.name",
|
||||
"operator": "included",
|
||||
"type": "match_any",
|
||||
"value": ["some host", "another host"]
|
||||
}
|
||||
]
|
||||
}
|
|
@ -0,0 +1,28 @@
|
|||
{
|
||||
"list_id": "detection_list_3",
|
||||
"item_id": "simple_list_item_three_non-value_list",
|
||||
"tags": [
|
||||
"user added string for a tag",
|
||||
"malware"
|
||||
],
|
||||
"type": "simple",
|
||||
"description": "This is a sample exception list item with two non-value list entries",
|
||||
"name": "Sample Detection Exception List Item",
|
||||
"os_types": [
|
||||
"windows"
|
||||
],
|
||||
"comments": [],
|
||||
"entries": [
|
||||
{
|
||||
"field": "actingProcess.file.signer",
|
||||
"operator": "excluded",
|
||||
"type": "exists"
|
||||
},
|
||||
{
|
||||
"field": "host.name",
|
||||
"operator": "included",
|
||||
"type": "match_any",
|
||||
"value": ["some host", "another host"]
|
||||
}
|
||||
]
|
||||
}
|
|
@ -9,6 +9,7 @@ import React from 'react';
|
|||
import { EuiButtonIcon, EuiBasicTableColumn, EuiToolTip } from '@elastic/eui';
|
||||
import { History } from 'history';
|
||||
|
||||
import { Spacer } from '../../../../../../common/components/page';
|
||||
import { NamespaceType } from '../../../../../../../../lists/common';
|
||||
import { FormatUrl } from '../../../../../../common/components/link_to';
|
||||
import { LinkAnchor } from '../../../../../../common/components/links';
|
||||
|
@ -17,15 +18,10 @@ import { ExceptionListInfo } from './use_all_exception_lists';
|
|||
import { getRuleDetailsUrl } from '../../../../../../common/components/link_to/redirect_to_detection_engine';
|
||||
|
||||
export type AllExceptionListsColumns = EuiBasicTableColumn<ExceptionListInfo>;
|
||||
export type Func = (arg: {
|
||||
id: string;
|
||||
listId: string;
|
||||
namespaceType: NamespaceType;
|
||||
}) => () => void;
|
||||
|
||||
export const getAllExceptionListsColumns = (
|
||||
onExport: Func,
|
||||
onDelete: Func,
|
||||
onExport: (arg: { id: string; listId: string; namespaceType: NamespaceType }) => () => void,
|
||||
onDelete: (arg: { id: string; listId: string; namespaceType: NamespaceType }) => () => void,
|
||||
history: History,
|
||||
formatUrl: FormatUrl
|
||||
): AllExceptionListsColumns[] => [
|
||||
|
@ -64,8 +60,9 @@ export const getAllExceptionListsColumns = (
|
|||
return (
|
||||
<>
|
||||
{value.map(({ id, name }, index) => (
|
||||
<>
|
||||
<Spacer key={id}>
|
||||
<LinkAnchor
|
||||
key={id}
|
||||
data-test-subj="ruleName"
|
||||
onClick={(ev: { preventDefault: () => void }) => {
|
||||
ev.preventDefault();
|
||||
|
@ -76,7 +73,7 @@ export const getAllExceptionListsColumns = (
|
|||
{name}
|
||||
</LinkAnchor>
|
||||
{index !== value.length - 1 ? ', ' : ''}
|
||||
</>
|
||||
</Spacer>
|
||||
))}
|
||||
</>
|
||||
);
|
||||
|
@ -120,11 +117,7 @@ export const getAllExceptionListsColumns = (
|
|||
render: ({ id, list_id: listId, namespace_type: namespaceType }: ExceptionListInfo) => (
|
||||
<EuiButtonIcon
|
||||
color="danger"
|
||||
onClick={onDelete({
|
||||
id,
|
||||
listId,
|
||||
namespaceType,
|
||||
})}
|
||||
onClick={onDelete({ id, listId, namespaceType })}
|
||||
aria-label="Delete exception list"
|
||||
iconType="trash"
|
||||
/>
|
||||
|
|
|
@ -29,6 +29,8 @@ import { AllRulesUtilityBar } from '../utility_bar';
|
|||
import { LastUpdatedAt } from '../../../../../../common/components/last_updated';
|
||||
import { AllExceptionListsColumns, getAllExceptionListsColumns } from './columns';
|
||||
import { useAllExceptionLists } from './use_all_exception_lists';
|
||||
import { ReferenceErrorModal } from '../../../../../components/value_lists_management_modal/reference_error_modal';
|
||||
import { patchRule } from '../../../../../containers/detection_engine/rules/api';
|
||||
|
||||
// Known lost battle with Eui :(
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||
|
@ -48,12 +50,33 @@ interface ExceptionListsTableProps {
|
|||
formatUrl: FormatUrl;
|
||||
}
|
||||
|
||||
interface ReferenceModalState {
|
||||
contentText: string;
|
||||
rulesReferences: string[];
|
||||
isLoading: boolean;
|
||||
listId: string;
|
||||
listNamespaceType: NamespaceType;
|
||||
}
|
||||
|
||||
const exceptionReferenceModalInitialState: ReferenceModalState = {
|
||||
contentText: '',
|
||||
rulesReferences: [],
|
||||
isLoading: false,
|
||||
listId: '',
|
||||
listNamespaceType: 'single',
|
||||
};
|
||||
|
||||
export const ExceptionListsTable = React.memo<ExceptionListsTableProps>(
|
||||
({ formatUrl, history, hasNoPermissions, loading }) => {
|
||||
const {
|
||||
services: { http, notifications },
|
||||
} = useKibana();
|
||||
const { exportExceptionList } = useApi(http);
|
||||
const { exportExceptionList, deleteExceptionList } = useApi(http);
|
||||
|
||||
const [showReferenceErrorModal, setShowReferenceErrorModal] = useState(false);
|
||||
const [referenceModalState, setReferenceModalState] = useState<ReferenceModalState>(
|
||||
exceptionReferenceModalInitialState
|
||||
);
|
||||
const [filters, setFilters] = useState<ExceptionListFilter>({
|
||||
name: null,
|
||||
list_id: null,
|
||||
|
@ -67,15 +90,36 @@ export const ExceptionListsTable = React.memo<ExceptionListsTableProps>(
|
|||
notifications,
|
||||
showTrustedApps: false,
|
||||
});
|
||||
const [loadingTableInfo, data] = useAllExceptionLists({
|
||||
exceptionLists: exceptions ?? [],
|
||||
});
|
||||
const [loadingTableInfo, exceptionListsWithRuleRefs, exceptionsListsRef] = useAllExceptionLists(
|
||||
{
|
||||
exceptionLists: exceptions ?? [],
|
||||
}
|
||||
);
|
||||
const [initLoading, setInitLoading] = useState(true);
|
||||
const [lastUpdated, setLastUpdated] = useState(Date.now());
|
||||
const [deletingListIds, setDeletingListIds] = useState<string[]>([]);
|
||||
const [exportingListIds, setExportingListIds] = useState<string[]>([]);
|
||||
const [exportDownload, setExportDownload] = useState<{ name?: string; blob?: Blob }>({});
|
||||
|
||||
const handleDeleteSuccess = useCallback(
|
||||
(listId?: string) => () => {
|
||||
notifications.toasts.addSuccess({
|
||||
title: i18n.exceptionDeleteSuccessMessage(listId ?? referenceModalState.listId),
|
||||
});
|
||||
},
|
||||
[notifications.toasts, referenceModalState.listId]
|
||||
);
|
||||
|
||||
const handleDeleteError = useCallback(
|
||||
(err: Error & { body?: { message: string } }): void => {
|
||||
notifications.toasts.addError(err, {
|
||||
title: i18n.EXCEPTION_DELETE_ERROR,
|
||||
toastMessage: err.body != null ? err.body.message : err.message,
|
||||
});
|
||||
},
|
||||
[notifications.toasts]
|
||||
);
|
||||
|
||||
const handleDelete = useCallback(
|
||||
({
|
||||
id,
|
||||
|
@ -88,14 +132,45 @@ export const ExceptionListsTable = React.memo<ExceptionListsTableProps>(
|
|||
}) => async () => {
|
||||
try {
|
||||
setDeletingListIds((ids) => [...ids, id]);
|
||||
if (refreshExceptions != null) {
|
||||
await refreshExceptions();
|
||||
}
|
||||
|
||||
if (exceptionsListsRef[id] != null && exceptionsListsRef[id].rules.length === 0) {
|
||||
await deleteExceptionList({
|
||||
id,
|
||||
namespaceType,
|
||||
onError: handleDeleteError,
|
||||
onSuccess: handleDeleteSuccess(listId),
|
||||
});
|
||||
|
||||
if (refreshExceptions != null) {
|
||||
refreshExceptions();
|
||||
}
|
||||
} else {
|
||||
setReferenceModalState({
|
||||
contentText: i18n.referenceErrorMessage(exceptionsListsRef[id].rules.length),
|
||||
rulesReferences: exceptionsListsRef[id].rules.map(({ name }) => name),
|
||||
isLoading: true,
|
||||
listId: id,
|
||||
listNamespaceType: namespaceType,
|
||||
});
|
||||
setShowReferenceErrorModal(true);
|
||||
}
|
||||
// route to patch rules with associated exception list
|
||||
} catch (error) {
|
||||
notifications.toasts.addError(error, { title: i18n.EXCEPTION_DELETE_ERROR });
|
||||
handleDeleteError(error);
|
||||
} finally {
|
||||
setDeletingListIds((ids) => [...ids.filter((_id) => _id !== id)]);
|
||||
}
|
||||
},
|
||||
[notifications.toasts]
|
||||
[
|
||||
deleteExceptionList,
|
||||
exceptionsListsRef,
|
||||
handleDeleteError,
|
||||
handleDeleteSuccess,
|
||||
refreshExceptions,
|
||||
]
|
||||
);
|
||||
|
||||
const handleExportSuccess = useCallback(
|
||||
|
@ -182,6 +257,67 @@ export const ExceptionListsTable = React.memo<ExceptionListsTableProps>(
|
|||
setFilters(formattedFilter);
|
||||
}, []);
|
||||
|
||||
const handleCloseReferenceErrorModal = useCallback((): void => {
|
||||
setDeletingListIds([]);
|
||||
setShowReferenceErrorModal(false);
|
||||
setReferenceModalState({
|
||||
contentText: '',
|
||||
rulesReferences: [],
|
||||
isLoading: false,
|
||||
listId: '',
|
||||
listNamespaceType: 'single',
|
||||
});
|
||||
}, []);
|
||||
|
||||
const handleReferenceDelete = useCallback(async (): Promise<void> => {
|
||||
const exceptionListId = referenceModalState.listId;
|
||||
const exceptionListNamespaceType = referenceModalState.listNamespaceType;
|
||||
const relevantRules = exceptionsListsRef[exceptionListId].rules;
|
||||
|
||||
try {
|
||||
await Promise.all(
|
||||
relevantRules.map((rule) => {
|
||||
const abortCtrl = new AbortController();
|
||||
const exceptionLists = (rule.exceptions_list ?? []).filter(
|
||||
({ id }) => id !== exceptionListId
|
||||
);
|
||||
|
||||
return patchRule({
|
||||
ruleProperties: {
|
||||
rule_id: rule.rule_id,
|
||||
exceptions_list: exceptionLists,
|
||||
},
|
||||
signal: abortCtrl.signal,
|
||||
});
|
||||
})
|
||||
);
|
||||
|
||||
await deleteExceptionList({
|
||||
id: exceptionListId,
|
||||
namespaceType: exceptionListNamespaceType,
|
||||
onError: handleDeleteError,
|
||||
onSuccess: handleDeleteSuccess(),
|
||||
});
|
||||
} catch (err) {
|
||||
handleDeleteError(err);
|
||||
} finally {
|
||||
setReferenceModalState(exceptionReferenceModalInitialState);
|
||||
setDeletingListIds([]);
|
||||
setShowReferenceErrorModal(false);
|
||||
if (refreshExceptions != null) {
|
||||
refreshExceptions();
|
||||
}
|
||||
}
|
||||
}, [
|
||||
referenceModalState.listId,
|
||||
referenceModalState.listNamespaceType,
|
||||
exceptionsListsRef,
|
||||
deleteExceptionList,
|
||||
handleDeleteError,
|
||||
handleDeleteSuccess,
|
||||
refreshExceptions,
|
||||
]);
|
||||
|
||||
const paginationMemo = useMemo(
|
||||
() => ({
|
||||
pageIndex: pagination.page - 1,
|
||||
|
@ -196,7 +332,7 @@ export const ExceptionListsTable = React.memo<ExceptionListsTableProps>(
|
|||
setExportDownload({});
|
||||
}, []);
|
||||
|
||||
const tableItems = (data ?? []).map((item) => ({
|
||||
const tableItems = (exceptionListsWithRuleRefs ?? []).map((item) => ({
|
||||
...item,
|
||||
isDeleting: deletingListIds.includes(item.id),
|
||||
isExporting: exportingListIds.includes(item.id),
|
||||
|
@ -204,11 +340,6 @@ export const ExceptionListsTable = React.memo<ExceptionListsTableProps>(
|
|||
|
||||
return (
|
||||
<>
|
||||
<AutoDownload
|
||||
blob={exportDownload.blob}
|
||||
name={`${exportDownload.name}.ndjson`}
|
||||
onDownload={handleOnDownload}
|
||||
/>
|
||||
<Panel loading={!initLoading && loadingTableInfo} data-test-subj="allExceptionListsPanel">
|
||||
<>
|
||||
{loadingTableInfo && (
|
||||
|
@ -235,7 +366,7 @@ export const ExceptionListsTable = React.memo<ExceptionListsTableProps>(
|
|||
/>
|
||||
</HeaderSection>
|
||||
|
||||
{loadingTableInfo && !initLoading && (
|
||||
{loadingTableInfo && !initLoading && !showReferenceErrorModal && (
|
||||
<Loader data-test-subj="loadingPanelAllRulesTable" overlay size="xl" />
|
||||
)}
|
||||
{initLoading ? (
|
||||
|
@ -245,7 +376,7 @@ export const ExceptionListsTable = React.memo<ExceptionListsTableProps>(
|
|||
<AllRulesUtilityBar
|
||||
showBulkActions={false}
|
||||
userHasNoPermissions={hasNoPermissions}
|
||||
paginationTotal={data.length ?? 0}
|
||||
paginationTotal={exceptionListsWithRuleRefs.length ?? 0}
|
||||
numberSelectedItems={0}
|
||||
onRefresh={handleRefresh}
|
||||
/>
|
||||
|
@ -263,9 +394,23 @@ export const ExceptionListsTable = React.memo<ExceptionListsTableProps>(
|
|||
)}
|
||||
</>
|
||||
</Panel>
|
||||
<AutoDownload
|
||||
blob={exportDownload.blob}
|
||||
name={`${exportDownload.name}.ndjson`}
|
||||
onDownload={handleOnDownload}
|
||||
/>
|
||||
<ReferenceErrorModal
|
||||
cancelText={i18n.REFERENCE_MODAL_CANCEL_BUTTON}
|
||||
confirmText={i18n.REFERENCE_MODAL_CONFIRM_BUTTON}
|
||||
contentText={referenceModalState.contentText}
|
||||
onCancel={handleCloseReferenceErrorModal}
|
||||
onClose={handleCloseReferenceErrorModal}
|
||||
onConfirm={handleReferenceDelete}
|
||||
references={referenceModalState.rulesReferences}
|
||||
showModal={showReferenceErrorModal}
|
||||
titleText={i18n.REFERENCE_MODAL_TITLE}
|
||||
/>
|
||||
</>
|
||||
);
|
||||
}
|
||||
);
|
||||
|
||||
ExceptionListsTable.displayName = 'ExceptionListsTable';
|
||||
|
|
|
@ -96,3 +96,37 @@ export const EXCEPTION_DELETE_ERROR = i18n.translate(
|
|||
defaultMessage: 'Error occurred deleting exception list',
|
||||
}
|
||||
);
|
||||
|
||||
export const exceptionDeleteSuccessMessage = (listId: string) =>
|
||||
i18n.translate('xpack.securitySolution.exceptions.referenceModalSuccessDescription', {
|
||||
defaultMessage: 'Exception list - {listId} - deleted successfully.',
|
||||
values: { listId },
|
||||
});
|
||||
|
||||
export const REFERENCE_MODAL_TITLE = i18n.translate(
|
||||
'xpack.securitySolution.exceptions.referenceModalTitle',
|
||||
{
|
||||
defaultMessage: 'Remove exception list',
|
||||
}
|
||||
);
|
||||
|
||||
export const REFERENCE_MODAL_CANCEL_BUTTON = i18n.translate(
|
||||
'xpack.securitySolution.exceptions.referenceModalCancelButton',
|
||||
{
|
||||
defaultMessage: 'Cancel',
|
||||
}
|
||||
);
|
||||
|
||||
export const REFERENCE_MODAL_CONFIRM_BUTTON = i18n.translate(
|
||||
'xpack.securitySolution.exceptions.referenceModalDeleteButton',
|
||||
{
|
||||
defaultMessage: 'Remove exception list',
|
||||
}
|
||||
);
|
||||
|
||||
export const referenceErrorMessage = (referenceCount: number) =>
|
||||
i18n.translate('xpack.securitySolution.exceptions.referenceModalDescription', {
|
||||
defaultMessage:
|
||||
'This exception list is associated with ({referenceCount}) {referenceCount, plural, =1 {rule} other {rules}}. Removing this exception list will also remove its reference from the associated rules.',
|
||||
values: { referenceCount },
|
||||
});
|
||||
|
|
|
@ -4,16 +4,20 @@
|
|||
* you may not use this file except in compliance with the Elastic License.
|
||||
*/
|
||||
|
||||
import { useEffect, useState } from 'react';
|
||||
import { useCallback, useEffect, useState } from 'react';
|
||||
|
||||
import { Rule } from '../../../../../containers/detection_engine/rules';
|
||||
import { ExceptionListSchema } from '../../../../../../../../lists/common';
|
||||
|
||||
import { fetchRules } from '../../../../../containers/detection_engine/rules/api';
|
||||
|
||||
export interface ExceptionListInfo extends ExceptionListSchema {
|
||||
rules: Array<{ name: string; id: string }>;
|
||||
rules: Rule[];
|
||||
}
|
||||
|
||||
export type UseAllExceptionListsReturn = [boolean, ExceptionListInfo[]];
|
||||
export type UseAllExceptionListsReturn = [
|
||||
boolean,
|
||||
ExceptionListInfo[],
|
||||
Record<string, ExceptionListInfo>
|
||||
];
|
||||
|
||||
/**
|
||||
* Hook for preparing exception lists table info. For now, we need to do a table scan
|
||||
|
@ -30,7 +34,46 @@ export const useAllExceptionLists = ({
|
|||
exceptionLists: ExceptionListSchema[];
|
||||
}): UseAllExceptionListsReturn => {
|
||||
const [loading, setLoading] = useState(true);
|
||||
const [exceptionsListInfo, setExceptionsListInfo] = useState<ExceptionListInfo[]>([]);
|
||||
const [exceptions, setExceptions] = useState<ExceptionListInfo[]>([]);
|
||||
const [exceptionsListsInfo, setExceptionsListInfo] = useState<Record<string, ExceptionListInfo>>(
|
||||
{}
|
||||
);
|
||||
|
||||
const handleExceptionsInfo = useCallback(
|
||||
(rules: Rule[]): Record<string, ExceptionListInfo> => {
|
||||
const listsSkeleton = exceptionLists.reduce<Record<string, ExceptionListInfo>>(
|
||||
(acc, { id, ...rest }) => {
|
||||
acc[id] = {
|
||||
...rest,
|
||||
id,
|
||||
rules: [],
|
||||
};
|
||||
|
||||
return acc;
|
||||
},
|
||||
{}
|
||||
);
|
||||
|
||||
return rules.reduce<Record<string, ExceptionListInfo>>((acc, rule) => {
|
||||
const ruleExceptionLists = rule.exceptions_list;
|
||||
|
||||
if (ruleExceptionLists != null && ruleExceptionLists.length > 0) {
|
||||
ruleExceptionLists.forEach((ex) => {
|
||||
const list = acc[ex.id];
|
||||
if (list != null) {
|
||||
acc[ex.id] = {
|
||||
...list,
|
||||
rules: [...list.rules, rule],
|
||||
};
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
return acc;
|
||||
}, listsSkeleton);
|
||||
},
|
||||
[exceptionLists]
|
||||
);
|
||||
|
||||
useEffect(() => {
|
||||
let isSubscribed = true;
|
||||
|
@ -45,19 +88,6 @@ export const useAllExceptionLists = ({
|
|||
try {
|
||||
setLoading(true);
|
||||
|
||||
const listsSkeleton = exceptionLists.reduce<Record<string, ExceptionListInfo>>(
|
||||
(acc, { id, ...rest }) => {
|
||||
acc[id] = {
|
||||
...rest,
|
||||
id,
|
||||
rules: [],
|
||||
};
|
||||
|
||||
return acc;
|
||||
},
|
||||
{}
|
||||
);
|
||||
|
||||
const { data: rules } = await fetchRules({
|
||||
pagination: {
|
||||
page: 1,
|
||||
|
@ -67,29 +97,14 @@ export const useAllExceptionLists = ({
|
|||
signal: abortCtrl.signal,
|
||||
});
|
||||
|
||||
const updatedLists = rules.reduce<Record<string, ExceptionListInfo>>((acc, rule) => {
|
||||
const exceptions = rule.exceptions_list;
|
||||
|
||||
if (exceptions != null && exceptions.length > 0) {
|
||||
exceptions.forEach((ex) => {
|
||||
const list = acc[ex.id];
|
||||
if (list != null) {
|
||||
acc[ex.id] = {
|
||||
...list,
|
||||
rules: [...list.rules, { id: rule.id, name: rule.name }],
|
||||
};
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
return acc;
|
||||
}, listsSkeleton);
|
||||
const updatedLists = handleExceptionsInfo(rules);
|
||||
|
||||
const lists = Object.keys(updatedLists).map<ExceptionListInfo>(
|
||||
(listKey) => updatedLists[listKey]
|
||||
);
|
||||
|
||||
setExceptionsListInfo(lists);
|
||||
setExceptions(lists);
|
||||
setExceptionsListInfo(updatedLists);
|
||||
|
||||
if (isSubscribed) {
|
||||
setLoading(false);
|
||||
|
@ -107,7 +122,7 @@ export const useAllExceptionLists = ({
|
|||
isSubscribed = false;
|
||||
abortCtrl.abort();
|
||||
};
|
||||
}, [exceptionLists.length, exceptionLists]);
|
||||
}, [exceptionLists.length, handleExceptionsInfo]);
|
||||
|
||||
return [loading, exceptionsListInfo];
|
||||
return [loading, exceptions, exceptionsListsInfo];
|
||||
};
|
||||
|
|
|
@ -0,0 +1,14 @@
|
|||
{
|
||||
"name": "Rule 2",
|
||||
"description": "Sample rule with multiple exception list",
|
||||
"rule_id": "query-with-multiple-exception-list",
|
||||
"risk_score": 1,
|
||||
"severity": "high",
|
||||
"type": "query",
|
||||
"query": "host.name: *",
|
||||
"interval": "30s",
|
||||
"exceptions_list": [
|
||||
{ "id": "7b4e8f40-4f0c-11eb-865c-8bc292d0513c", "list_id": "detection_list_1", "namespace_type": "single", "type": "detection" },
|
||||
{ "id": "e8ac0a60-4edd-11eb-865c-8bc292d0513c", "list_id": "detection_list_3", "namespace_type": "single", "type": "detection" }
|
||||
]
|
||||
}
|
|
@ -0,0 +1,11 @@
|
|||
{
|
||||
"name": "Rule 1",
|
||||
"description": "Sample rule with single exception list",
|
||||
"rule_id": "query-with-single-exception-list",
|
||||
"risk_score": 1,
|
||||
"severity": "high",
|
||||
"type": "query",
|
||||
"query": "host.name: *",
|
||||
"interval": "30s",
|
||||
"exceptions_list": [{ "id": "7b4e8f40-4f0c-11eb-865c-8bc292d0513c", "list_id": "detection_list_1", "namespace_type": "single", "type": "detection" }]
|
||||
}
|
Loading…
Reference in a new issue