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][Detections] Updates Rule Details to show all historical alerts for a given Rule #120053

Merged
merged 10 commits into from
Dec 8, 2021
Merged
Prev Previous commit
Next Next commit
Cleans up duplicate RuleRegistry functions
spong committed Dec 3, 2021

Verified

This commit was signed with the committer’s verified signature. The key has expired.
spong Garrett Spong
commit 4ad6ec0c012b9f67c785a99ecadf32cce8180c7e
3 changes: 3 additions & 0 deletions packages/kbn-rule-data-utils/src/technical_field_names.ts
Original file line number Diff line number Diff line change
@@ -24,6 +24,7 @@ const VERSION = `${KIBANA_NAMESPACE}.version` as const;

// Fields pertaining to the alert
const ALERT_ACTION_GROUP = `${ALERT_NAMESPACE}.action_group` as const;
const ALERT_BUILDING_BLOCK_TYPE = `${ALERT_NAMESPACE}.building_block_type` as const;
const ALERT_DURATION = `${ALERT_NAMESPACE}.duration.us` as const;
const ALERT_END = `${ALERT_NAMESPACE}.end` as const;
const ALERT_EVALUATION_THRESHOLD = `${ALERT_NAMESPACE}.evaluation.threshold` as const;
@@ -91,6 +92,7 @@ const fields = {
TAGS,
TIMESTAMP,
ALERT_ACTION_GROUP,
ALERT_BUILDING_BLOCK_TYPE,
ALERT_DURATION,
ALERT_END,
ALERT_EVALUATION_THRESHOLD,
@@ -141,6 +143,7 @@ const fields = {

export {
ALERT_ACTION_GROUP,
ALERT_BUILDING_BLOCK_TYPE,
ALERT_DURATION,
ALERT_END,
ALERT_EVALUATION_THRESHOLD,
Original file line number Diff line number Diff line change
@@ -44,7 +44,7 @@ describe('useAddOrUpdateException', () => {
let updateExceptionListItem: jest.SpyInstance<Promise<ExceptionListItemSchema>>;
let getQueryFilter: jest.SpyInstance<ReturnType<typeof getQueryFilterHelper.getQueryFilter>>;
let buildAlertStatusesFilter: jest.SpyInstance<
ReturnType<typeof buildFilterHelpers.buildAlertStatusesFilterRuleRegistry>
ReturnType<typeof buildFilterHelpers.buildAlertStatusesFilter>
>;
let buildAlertsRuleIdFilter: jest.SpyInstance<
ReturnType<typeof buildFilterHelpers.buildAlertsRuleIdFilter>
@@ -128,10 +128,7 @@ describe('useAddOrUpdateException', () => {

getQueryFilter = jest.spyOn(getQueryFilterHelper, 'getQueryFilter');

buildAlertStatusesFilter = jest.spyOn(
buildFilterHelpers,
'buildAlertStatusesFilterRuleRegistry'
);
buildAlertStatusesFilter = jest.spyOn(buildFilterHelpers, 'buildAlertStatusesFilter');

buildAlertsRuleIdFilter = jest.spyOn(buildFilterHelpers, 'buildAlertsRuleIdFilter');

Original file line number Diff line number Diff line change
@@ -19,11 +19,9 @@ import { getUpdateAlertsQuery } from '../../../detections/components/alerts_tabl
import {
buildAlertsRuleIdFilter,
buildAlertStatusesFilter,
buildAlertStatusesFilterRuleRegistry,
} from '../../../detections/components/alerts_table/default_config';
import { getQueryFilter } from '../../../../common/detection_engine/get_query_filter';
import { Index } from '../../../../common/detection_engine/schemas/common/schemas';
import { useIsExperimentalFeatureEnabled } from '../../hooks/use_experimental_features';
import { formatExceptionItemForUpdate, prepareExceptionItemsForBulkClose } from './helpers';
import { useKibana } from '../../lib/kibana';

@@ -84,8 +82,6 @@ export const useAddOrUpdateException = ({
},
[]
);
// TODO: Once we are past experimental phase this code should be removed
const ruleRegistryEnabled = useIsExperimentalFeatureEnabled('ruleRegistryEnabled');

useEffect(() => {
let isSubscribed = true;
@@ -131,10 +127,11 @@ export const useAddOrUpdateException = ({
}

if (bulkCloseIndex != null) {
// TODO: Once we are past experimental phase this code should be removed
const alertStatusFilter = ruleRegistryEnabled
? buildAlertStatusesFilterRuleRegistry(['open', 'acknowledged', 'in-progress'])
: buildAlertStatusesFilter(['open', 'acknowledged', 'in-progress']);
const alertStatusFilter = buildAlertStatusesFilter([
'open',
'acknowledged',
'in-progress',
]);
Comment on lines +130 to +134
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Garrett, do you know if a go/no-go decision has been made regarding going live with rule_registry in 8.0? There's still some ongoing discussions regarding AAD schema which is extremely concerning considering where we are in the release cycle. Wondering what are chances of us being asked to revert this stuff back to .siem-signals-* (which means we'd need the ruleRegistryEnabled feature flag in the code):

          // TODO: Once we are past experimental phase this code should be removed
          const alertStatusFilter = ruleRegistryEnabled
            ? buildAlertStatusesFilterRuleRegistry(['open', 'acknowledged', 'in-progress'])
            : buildAlertStatusesFilter(['open', 'acknowledged', 'in-progress']);

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Verified RuleRegistry is a go for security in 8.0, so all good here 🙂


const filter = getQueryFilter(
'',
@@ -185,14 +182,7 @@ export const useAddOrUpdateException = ({
isSubscribed = false;
abortCtrl.abort();
};
}, [
addExceptionListItem,
http,
onSuccess,
onError,
ruleRegistryEnabled,
updateExceptionListItem,
]);
}, [addExceptionListItem, http, onSuccess, onError, updateExceptionListItem]);

return [{ isLoading }, addOrUpdateException];
};
Original file line number Diff line number Diff line change
@@ -6,22 +6,14 @@
*/

import {
ALERT_DURATION,
ALERT_RULE_PRODUCER,
ALERT_START,
ALERT_BUILDING_BLOCK_TYPE,
ALERT_WORKFLOW_STATUS,
ALERT_UUID,
ALERT_RULE_UUID,
ALERT_RULE_NAME,
ALERT_RULE_CATEGORY,
ALERT_RULE_SEVERITY,
ALERT_RULE_RISK_SCORE,
ALERT_RULE_RULE_ID,
} from '@kbn/rule-data-utils/technical_field_names';

import type { Filter } from '@kbn/es-query';
import { defaultColumnHeaderType } from '../../../timelines/components/timeline/body/column_headers/default_headers';
import { ColumnHeaderOptions, RowRendererId } from '../../../../common/types/timeline';
import { RowRendererId } from '../../../../common/types/timeline';
import { Status } from '../../../../common/detection_engine/schemas/common/schemas';
import { SubsetTimelineModel } from '../../../timelines/store/timeline/model';
import { timelineDefaults } from '../../../timelines/store/timeline/defaults';
@@ -35,20 +27,20 @@ export const buildAlertStatusFilter = (status: Status): Filter[] => {
should: [
{
term: {
'kibana.alert.workflow_status': status,
[ALERT_WORKFLOW_STATUS]: status,
},
},
{
term: {
'kibana.alert.workflow_status': 'in-progress',
[ALERT_WORKFLOW_STATUS]: 'in-progress',
Comment on lines 28 to +34
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thank you for all the clean up 🙏

},
},
],
},
}
: {
term: {
'kibana.alert.workflow_status': status,
[ALERT_WORKFLOW_STATUS]: status,
},
};

@@ -59,7 +51,7 @@ export const buildAlertStatusFilter = (status: Status): Filter[] => {
negate: false,
disabled: false,
type: 'phrase',
key: 'kibana.alert.workflow_status',
key: ALERT_WORKFLOW_STATUS,
params: {
query: status,
},
@@ -77,7 +69,7 @@ export const buildAlertStatusesFilter = (statuses: Status[]): Filter[] => {
bool: {
should: statuses.map((status) => ({
term: {
'kibana.alert.workflow_status': status,
[ALERT_WORKFLOW_STATUS]: status,
},
})),
},
@@ -129,10 +121,10 @@ export const buildShowBuildingBlockFilter = (showBuildingBlockAlerts: boolean):
negate: true,
disabled: false,
type: 'exists',
key: 'kibana.alert.building_block_type',
key: ALERT_BUILDING_BLOCK_TYPE,
value: 'exists',
},
query: { exists: { field: 'kibana.alert.building_block_type' } },
query: { exists: { field: ALERT_BUILDING_BLOCK_TYPE } },
},
];

@@ -185,121 +177,3 @@ export const requiredFieldsForActions = [
'host.os.family',
'event.code',
];

// TODO: Once we are past experimental phase this code should be removed
export const buildAlertStatusFilterRuleRegistry = (status: Status): Filter[] => {
const combinedQuery =
status === 'acknowledged'
? {
bool: {
should: [
{
term: {
[ALERT_WORKFLOW_STATUS]: status,
},
},
{
term: {
[ALERT_WORKFLOW_STATUS]: 'in-progress',
},
},
],
},
}
: {
term: {
[ALERT_WORKFLOW_STATUS]: status,
},
};

return [
{
meta: {
alias: null,
negate: false,
disabled: false,
type: 'phrase',
key: ALERT_WORKFLOW_STATUS,
params: {
query: status,
},
},
query: combinedQuery,
},
];
};

// TODO: Once we are past experimental phase this code should be removed
export const buildAlertStatusesFilterRuleRegistry = (statuses: Status[]): Filter[] => {
const combinedQuery = {
bool: {
should: statuses.map((status) => ({
term: {
[ALERT_WORKFLOW_STATUS]: status,
},
})),
},
};

return [
{
meta: {
alias: null,
negate: false,
disabled: false,
},
query: combinedQuery,
},
];
};

export const buildShowBuildingBlockFilterRuleRegistry = (
showBuildingBlockAlerts: boolean
): Filter[] =>
showBuildingBlockAlerts
? []
: [
{
meta: {
alias: null,
negate: true,
disabled: false,
type: 'exists',
key: 'kibana.alert.building_block_type',
value: 'exists',
},
query: { exists: { field: 'kibana.alert.building_block_type' } },
},
];

export const requiredFieldMappingsForActionsRuleRegistry = {
'@timestamp': '@timestamp',
'event.kind': 'event.kind',
'rule.severity': ALERT_RULE_SEVERITY,
'rule.risk_score': ALERT_RULE_RISK_SCORE,
'alert.uuid': ALERT_UUID,
'alert.start': ALERT_START,
'event.action': 'event.action',
'alert.workflow_status': ALERT_WORKFLOW_STATUS,
'alert.duration.us': ALERT_DURATION,
'rule.uuid': ALERT_RULE_UUID,
'rule.name': ALERT_RULE_NAME,
'rule.category': ALERT_RULE_CATEGORY,
producer: ALERT_RULE_PRODUCER,
tags: 'tags',
};

export const alertsHeadersRuleRegistry: ColumnHeaderOptions[] = Object.entries(
requiredFieldMappingsForActionsRuleRegistry
).map<ColumnHeaderOptions>(([alias, field]) => ({
columnHeaderType: defaultColumnHeaderType,
displayAsText: alias,
id: field,
}));

export const alertsDefaultModelRuleRegistry: SubsetTimelineModel = {
...timelineDefaults,
columns: alertsHeadersRuleRegistry,
showCheckboxes: true,
excludedRowRendererIds: Object.values(RowRendererId),
};
Original file line number Diff line number Diff line change
@@ -40,9 +40,7 @@ import { updateAlertStatusAction } from './actions';
import { AditionalFiltersAction, AlertsUtilityBar } from './alerts_utility_bar';
import {
alertsDefaultModel,
alertsDefaultModelRuleRegistry,
buildAlertStatusFilter,
buildAlertStatusFilterRuleRegistry,
requiredFieldsForActions,
} from './default_config';
import { buildTimeRangeFilter } from './helpers';
@@ -106,8 +104,6 @@ export const AlertsTableComponent: React.FC<AlertsTableComponentProps> = ({
const kibana = useKibana();
const [, dispatchToaster] = useStateToaster();
const { addWarning } = useAppToasts();
// TODO: Once we are past experimental phase this code should be removed
const ruleRegistryEnabled = useIsExperimentalFeatureEnabled('ruleRegistryEnabled');
const ACTION_BUTTON_COUNT = 4;

const getGlobalQuery = useCallback(
@@ -247,14 +243,9 @@ export const AlertsTableComponent: React.FC<AlertsTableComponentProps> = ({
refetchQuery: inputsModel.Refetch,
{ status, selectedStatus }: UpdateAlertsStatusProps
) => {
// TODO: Once we are past experimental phase this code should be removed
const currentStatusFilter = ruleRegistryEnabled
? buildAlertStatusFilterRuleRegistry(status)
: buildAlertStatusFilter(status);

await updateAlertStatusAction({
query: showClearSelectionAction
? getGlobalQuery(currentStatusFilter)?.filterQuery
? getGlobalQuery(buildAlertStatusFilter(status))?.filterQuery
: undefined,
alertIds: Object.keys(selectedEventIds),
selectedStatus,
@@ -273,7 +264,6 @@ export const AlertsTableComponent: React.FC<AlertsTableComponentProps> = ({
showClearSelectionAction,
onAlertStatusUpdateSuccess,
onAlertStatusUpdateFailure,
ruleRegistryEnabled,
]
);

@@ -327,24 +317,16 @@ export const AlertsTableComponent: React.FC<AlertsTableComponentProps> = ({
);

const defaultFiltersMemo = useMemo(() => {
// TODO: Once we are past experimental phase this code should be removed
const alertStatusFilter = ruleRegistryEnabled
? buildAlertStatusFilterRuleRegistry(filterGroup)
: buildAlertStatusFilter(filterGroup);
const alertStatusFilter = buildAlertStatusFilter(filterGroup);

if (isEmpty(defaultFilters)) {
return alertStatusFilter;
} else if (defaultFilters != null && !isEmpty(defaultFilters)) {
return [...defaultFilters, ...alertStatusFilter];
}
}, [defaultFilters, filterGroup, ruleRegistryEnabled]);
}, [defaultFilters, filterGroup]);
const { filterManager } = useKibana().services.data.query;

// TODO: Once we are past experimental phase this code should be removed
const defaultTimelineModel = ruleRegistryEnabled
? alertsDefaultModelRuleRegistry
: alertsDefaultModel;

const tGridEnabled = useIsExperimentalFeatureEnabled('tGridEnabled');

useEffect(() => {
@@ -359,7 +341,7 @@ export const AlertsTableComponent: React.FC<AlertsTableComponentProps> = ({
: c
),
documentType: i18n.ALERTS_DOCUMENT_TYPE,
excludedRowRendererIds: defaultTimelineModel.excludedRowRendererIds as RowRendererId[],
excludedRowRendererIds: alertsDefaultModel.excludedRowRendererIds as RowRendererId[],
filterManager,
footerText: i18n.TOTAL_COUNT_OF_ALERTS,
id: timelineId,
@@ -370,7 +352,7 @@ export const AlertsTableComponent: React.FC<AlertsTableComponentProps> = ({
showCheckboxes: true,
})
);
}, [dispatch, defaultTimelineModel, filterManager, tGridEnabled, timelineId]);
}, [dispatch, filterManager, tGridEnabled, timelineId]);

const leadingControlColumns = useMemo(() => getDefaultControlColumn(ACTION_BUTTON_COUNT), []);

@@ -383,7 +365,7 @@ export const AlertsTableComponent: React.FC<AlertsTableComponentProps> = ({
additionalFilters={additionalFiltersComponent}
currentFilter={filterGroup}
defaultCellActions={defaultCellActions}
defaultModel={defaultTimelineModel}
defaultModel={alertsDefaultModel}
end={to}
entityType="events"
hasAlertsCrud={hasIndexWrite && hasIndexMaintenance}
Loading