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

Asset criticality alert enrichment #171241

Merged
merged 46 commits into from
Dec 22, 2023
Merged
Show file tree
Hide file tree
Changes from 36 commits
Commits
Show all changes
46 commits
Select commit Hold shift + click to select a range
ee2fac5
Add new fields to alertsFieldMap
Nov 14, 2023
5f13e85
Add new fields to alerts model including BaseFieldsLatest, etc
Nov 14, 2023
37e1dbf
Add new alert columns to table
Nov 14, 2023
243c117
refactoring alert enrichment code.
Nov 14, 2023
8fd21de
fix makeSingleFieldMatchQuery snapshot
Nov 28, 2023
c692d33
restore create_single_field_match_enrichment to `main` version
Nov 28, 2023
887764a
remove references to should_minimum_match
Nov 29, 2023
0f6d337
add new extraFilter option to createSingleMatchEnrichment
Nov 29, 2023
29f7513
put back all the stuff abount minimum_should_match
Nov 29, 2023
12aa184
work in progress
Nov 29, 2023
e3b7020
Add alert enrichment
nkhristinin Nov 30, 2023
c9b2cf1
Add integrations tests
nkhristinin Nov 30, 2023
2111edf
remove owners.csv
nkhristinin Nov 30, 2023
219ba82
Merge branch 'main' into asset-criticality-alert-enrichment
kibanamachine Dec 1, 2023
070b43d
Schema changes
nkhristinin Dec 1, 2023
0b77a96
fix unit tests
nkhristinin Dec 1, 2023
b31beb6
Add more unit tests
nkhristinin Dec 1, 2023
f862201
Merge branch 'main' into asset-criticality-alert-enrichment
nkhristinin Dec 4, 2023
78a3694
Merge branch 'main' into asset-criticality-alert-enrichment
kibanamachine Dec 4, 2023
c4a461e
[CI] Auto-commit changed files from 'node scripts/eslint --no-cache -…
kibanamachine Dec 4, 2023
c97c330
Merge branch 'main' into asset-criticality-alert-enrichment
kibanamachine Dec 6, 2023
d3c5122
Change alert schema
nkhristinin Dec 6, 2023
fe33f17
change how to set field to alert
nkhristinin Dec 7, 2023
10ee114
add more tests
nkhristinin Dec 7, 2023
c58cf48
Remove some comments
nkhristinin Dec 7, 2023
498e965
fix tests
nkhristinin Dec 7, 2023
bd872e1
update alert schema
nkhristinin Dec 11, 2023
78a5b2b
Merge branch 'main' into asset-criticality-alert-enrichment
kibanamachine Dec 11, 2023
d7d2c6c
change alert schema
nkhristinin Dec 11, 2023
4cf765e
remoe comments
nkhristinin Dec 11, 2023
744aace
Fix types
nkhristinin Dec 11, 2023
7b2e1ea
Merge branch 'main' into asset-criticality-alert-enrichment
kibanamachine Dec 12, 2023
00fdef5
Merge branch 'main' into asset-criticality-alert-enrichment
kibanamachine Dec 12, 2023
fa8df5c
Fix path
nkhristinin Dec 12, 2023
0695181
Move feature flag
nkhristinin Dec 12, 2023
9ca2fa7
Merge branch 'main' into asset-criticality-alert-enrichment
kibanamachine Dec 13, 2023
81142da
Remove type annotation
nkhristinin Dec 13, 2023
08611bf
Merge branch 'main' into asset-criticality-alert-enrichment
kibanamachine Dec 19, 2023
81ca783
fix tests
nkhristinin Dec 19, 2023
ffeb96e
PR fixes
nkhristinin Dec 21, 2023
35a9be3
Clean tests
nkhristinin Dec 21, 2023
c296a4c
Add utility function to check if index exist
nkhristinin Dec 21, 2023
b44541a
Rename file
nkhristinin Dec 21, 2023
ed77987
Merge branch 'main' into asset-criticality-alert-enrichment
kibanamachine Dec 21, 2023
84c8da9
specify test parameters
nkhristinin Dec 21, 2023
5c1de12
Merge branch 'main' into asset-criticality-alert-enrichment
nkhristinin Dec 21, 2023
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -131,6 +131,7 @@ const SecurityAlertOptional = rt.partial({
'kibana.alert.flapping_history': schemaBooleanArray,
'kibana.alert.group.id': schemaString,
'kibana.alert.group.index': schemaNumber,
'kibana.alert.host.criticality_level': schemaString,
Copy link
Contributor

Choose a reason for hiding this comment

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

@elastic/actionable-observability since these two fields are being added at the kibana.alert.* level, we'd like approval from you all that this will not conflict with any future fields you all plan to add.

Copy link
Contributor

Choose a reason for hiding this comment

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

@nkhristinin unless we want different fields for risk enrichment than we do for risk score docs themselves (which we hadn't up until this point), this seems like a placeholder field added in the absence of an official ECS one.

If it helps, I've just added those fields to the Risk Score Extensions RFC; in that case I think these would be host.risk.criticality_level and user.risk.criticality_level.

Copy link
Member

Choose a reason for hiding this comment

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

@kobelb We discussed adding severity to our rules, but we don't have a plan for it atm.
@simianhacker Do you have any input?

Just a question about the naming: How does criticality differ from severity?

Copy link
Member

Choose a reason for hiding this comment

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

@kobelb We don't have any immediate plans so from a conflict perspective, I think we are good if you're good.
@maryam-saeidi I think when we (ResponseOps and Observability) work on alert severity levels, we'll probably need something similar.

Copy link
Contributor

Choose a reason for hiding this comment

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

kibana.alert.host.criticality_level and kibana.alert.user.criticality_level - are used to store the Asset Criticality level which can be assigned by the user in UI for entities.

Values can be very important, not important, normal

Copy link
Contributor

Choose a reason for hiding this comment

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

Thanks, @simianhacker and @maryam-saeidi. While I think the severity that we've discussed is similar to the asset criticality fields that are being added, I currently think they're two unique aspects, so I don't think we should have any conflicts here.

'kibana.alert.last_detected': schemaDate,
'kibana.alert.maintenance_window_ids': schemaStringArray,
'kibana.alert.new_terms': schemaStringArray,
Expand Down Expand Up @@ -193,6 +194,7 @@ const SecurityAlertOptional = rt.partial({
),
'kibana.alert.time_range': schemaDateRange,
'kibana.alert.url': schemaString,
'kibana.alert.user.criticality_level': schemaString,
'kibana.alert.workflow_assignee_ids': schemaStringArray,
'kibana.alert.workflow_reason': schemaString,
'kibana.alert.workflow_status': schemaString,
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,60 @@
/*
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
* or more contributor license agreements. Licensed under the Elastic License
* 2.0; you may not use this file except in compliance with the Elastic License
* 2.0.
*/

import type { AlertWithCommonFields800 } from '@kbn/rule-registry-plugin/common/schemas/8.0.0';
import type {
ALERT_HOST_CRITICALITY,
ALERT_USER_CRITICALITY,
} from '../../../../../field_maps/field_names';
import type {
Ancestor8120,
BaseFields8120,
EqlBuildingBlockFields8120,
EqlShellFields8120,
NewTermsFields8120,
} from '../8.12.0';

/* DO NOT MODIFY THIS SCHEMA TO ADD NEW FIELDS. These types represent the alerts that shipped in 8.13.0.
Any changes to these types should be bug fixes so the types more accurately represent the alerts from 8.13.0.
If you are adding new fields for a new release of Kibana, create a new sibling folder to this one
for the version to be released and add the field(s) to the schema in that folder.
Then, update `../index.ts` to import from the new folder that has the latest schemas, add the
new schemas to the union of all alert schemas, and re-export the new schemas as the `*Latest` schemas.
*/

export type { Ancestor8120 as Ancestor8130 };

export interface BaseFields8130 extends BaseFields8120 {
[ALERT_HOST_CRITICALITY]: string | undefined;
[ALERT_USER_CRITICALITY]: string | undefined;
}

export interface WrappedFields8130<T extends BaseFields8130> {
_id: string;
_index: string;
_source: T;
}

export type GenericAlert8130 = AlertWithCommonFields800<BaseFields8130>;

export type EqlShellFields8130 = EqlShellFields8120 & BaseFields8130;

export type EqlBuildingBlockFields8130 = EqlBuildingBlockFields8120 & BaseFields8130;

export type NewTermsFields8130 = NewTermsFields8120 & BaseFields8130;

export type NewTermsAlert8130 = NewTermsFields8120 & BaseFields8130;

export type EqlBuildingBlockAlert8130 = AlertWithCommonFields800<EqlBuildingBlockFields8120>;

export type EqlShellAlert8130 = AlertWithCommonFields800<EqlShellFields8130>;

export type DetectionAlert8130 =
| GenericAlert8130
| EqlShellAlert8130
| EqlBuildingBlockAlert8130
| NewTermsAlert8130;
Original file line number Diff line number Diff line change
Expand Up @@ -12,15 +12,16 @@ import type { DetectionAlert860 } from './8.6.0';
import type { DetectionAlert870 } from './8.7.0';
import type { DetectionAlert880 } from './8.8.0';
import type { DetectionAlert890 } from './8.9.0';
import type { DetectionAlert8120 } from './8.12.0';
import type {
Ancestor8120,
BaseFields8120,
DetectionAlert8120,
EqlBuildingBlockFields8120,
EqlShellFields8120,
NewTermsFields8120,
WrappedFields8120,
} from './8.12.0';
Ancestor8130,
BaseFields8130,
DetectionAlert8130,
EqlBuildingBlockFields8130,
EqlShellFields8130,
NewTermsFields8130,
WrappedFields8130,
} from './8.13.0';

// When new Alert schemas are created for new Kibana versions, add the DetectionAlert type from the new version
// here, e.g. `export type DetectionAlert = DetectionAlert800 | DetectionAlert820` if a new schema is created in 8.2.0
Expand All @@ -31,14 +32,15 @@ export type DetectionAlert =
| DetectionAlert870
| DetectionAlert880
| DetectionAlert890
| DetectionAlert8120;
| DetectionAlert8120
| DetectionAlert8130;

export type {
Ancestor8120 as AncestorLatest,
BaseFields8120 as BaseFieldsLatest,
DetectionAlert8120 as DetectionAlertLatest,
WrappedFields8120 as WrappedFieldsLatest,
EqlBuildingBlockFields8120 as EqlBuildingBlockFieldsLatest,
EqlShellFields8120 as EqlShellFieldsLatest,
NewTermsFields8120 as NewTermsFieldsLatest,
Ancestor8130 as AncestorLatest,
BaseFields8130 as BaseFieldsLatest,
DetectionAlert8130 as DetectionAlertLatest,
WrappedFields8130 as WrappedFieldsLatest,
EqlBuildingBlockFields8130 as EqlBuildingBlockFieldsLatest,
EqlShellFields8130 as EqlShellFieldsLatest,
NewTermsFields8130 as NewTermsFieldsLatest,
};
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
/*
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
* or more contributor license agreements. Licensed under the Elastic License
* 2.0; you may not use this file except in compliance with the Elastic License
* 2.0.
*/

import { alertsFieldMap840 } from '../8.4.0';
import { ALERT_HOST_CRITICALITY, ALERT_USER_CRITICALITY } from '../field_names';

export const alertsFieldMap8130 = {
...alertsFieldMap840,
/**
* Stores the criticality level for the host, as determined by analysts, in relation to the alert.
* The Criticality level is copied from the asset criticality index.
*/
[ALERT_HOST_CRITICALITY]: {
type: 'keyword',
array: false,
required: false,
},
/**
* Stores the criticality level for the user, as determined by analysts, in relation to the alert.
* The Criticality level is copied from the asset criticality index.
*/
[ALERT_USER_CRITICALITY]: {
type: 'keyword',
array: false,
required: false,
},
} as const;

export type AlertsFieldMap8130 = typeof alertsFieldMap8130;
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
/*
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
* or more contributor license agreements. Licensed under the Elastic License
* 2.0; you may not use this file except in compliance with the Elastic License
* 2.0.
*/

import type { AlertsFieldMap8130 } from './alerts';
import { alertsFieldMap8130 } from './alerts';
export type { AlertsFieldMap8130 };
export { alertsFieldMap8130 };
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,8 @@ export const ALERT_THRESHOLD_RESULT = `${ALERT_NAMESPACE}.threshold_result` as c
export const ALERT_THRESHOLD_RESULT_COUNT = `${ALERT_THRESHOLD_RESULT}.count` as const;
export const ALERT_NEW_TERMS = `${ALERT_NAMESPACE}.new_terms` as const;
export const ALERT_NEW_TERMS_FIELDS = `${ALERT_RULE_PARAMETERS}.new_terms_fields` as const;
export const ALERT_HOST_CRITICALITY = `${ALERT_NAMESPACE}.host.criticality_level` as const;
export const ALERT_USER_CRITICALITY = `${ALERT_NAMESPACE}.user.criticality_level` as const;

export const ALERT_ORIGINAL_EVENT = `${ALERT_NAMESPACE}.original_event` as const;
export const ALERT_ORIGINAL_EVENT_ACTION = `${ALERT_ORIGINAL_EVENT}.action` as const;
Expand Down
8 changes: 4 additions & 4 deletions x-pack/plugins/security_solution/common/field_maps/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,9 +5,9 @@
* 2.0.
*/

import type { AlertsFieldMap840 } from './8.4.0';
import { alertsFieldMap840 } from './8.4.0';
import type { AlertsFieldMap8130 } from './8.13.0';
import { alertsFieldMap8130 } from './8.13.0';
import type { RulesFieldMap } from './8.0.0/rules';
import { rulesFieldMap } from './8.0.0/rules';
export type { AlertsFieldMap840 as AlertsFieldMap, RulesFieldMap };
export { alertsFieldMap840 as alertsFieldMap, rulesFieldMap };
export type { AlertsFieldMap8130 as AlertsFieldMap, RulesFieldMap };
export { alertsFieldMap8130 as alertsFieldMap, rulesFieldMap };
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,10 @@
*/

import type { EuiDataGridColumn } from '@elastic/eui';
import {
ALERT_HOST_CRITICALITY,
ALERT_USER_CRITICALITY,
} from '../../../../common/field_maps/field_names';
import type { LicenseService } from '../../../../common/license';
import type { ColumnHeaderOptions } from '../../../../common/types';

Expand Down Expand Up @@ -72,6 +76,18 @@ const getBaseColumns = (
id: 'user.risk.calculated_level',
}
: null,
isPlatinumPlus
? {
columnHeaderType: defaultColumnHeaderType,
id: ALERT_HOST_CRITICALITY,
}
: null,
isPlatinumPlus
? {
columnHeaderType: defaultColumnHeaderType,
id: ALERT_USER_CRITICALITY,
}
: null,
{
columnHeaderType: defaultColumnHeaderType,
id: 'process.name',
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -80,6 +80,8 @@ import {
ALERT_RULE_THREAT,
ALERT_RULE_EXCEPTIONS_LIST,
ALERT_RULE_IMMUTABLE,
ALERT_HOST_CRITICALITY,
ALERT_USER_CRITICALITY,
} from '../../../../../../common/field_maps/field_names';
import type { CompleteRule, RuleParams } from '../../../rule_schema';
import { commonParamsCamelToSnake, typeSpecificCamelToSnake } from '../../../rule_management';
Expand Down Expand Up @@ -256,6 +258,9 @@ export const buildAlert = (
'kibana.alert.rule.risk_score': params.riskScore,
'kibana.alert.rule.severity': params.severity,
'kibana.alert.rule.building_block_type': params.buildingBlockType,
// asset criticality fields will be enriched before ingestion
[ALERT_HOST_CRITICALITY]: undefined,
[ALERT_USER_CRITICALITY]: undefined,
};
};

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -68,6 +68,8 @@ import {
ALERT_RULE_TIMELINE_TITLE,
ALERT_RULE_INDICES,
ALERT_RULE_TIMESTAMP_OVERRIDE,
ALERT_HOST_CRITICALITY,
ALERT_USER_CRITICALITY,
} from '../../../../../../../common/field_maps/field_names';

export const createAlert = (
Expand Down Expand Up @@ -194,6 +196,8 @@ export const createAlert = (
rule_name_override: undefined,
timestamp_override: undefined,
},
[ALERT_HOST_CRITICALITY]: undefined,
[ALERT_USER_CRITICALITY]: undefined,
...data,
},
});
Original file line number Diff line number Diff line change
Expand Up @@ -22,11 +22,15 @@ export const createSingleFieldMatchEnrichment: CreateFieldsMatchEnrichment = asy
createEnrichmentFunction,
name,
enrichmentResponseFields,
extraFilters,
}) => {
try {
logger.debug(`Enrichment ${name}: started`);

// gets just the events we will enrich
const eventsWithField = events.filter((event) => getEventValue(event, mappingField.eventField));
Copy link
Contributor

Choose a reason for hiding this comment

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

@oatkiller were these meant to stick around, or were these just notes for you? Typically when I see comments like this in the code, it's making up for an unhelpful variable name. IMO these variables are good at describing what they are, but not why they are. Perhaps the comments wouldn't be needed if the intention was more clear?

Suggested change
// gets just the events we will enrich
const eventsWithField = events.filter((event) => getEventValue(event, mappingField.eventField));
const eventsToEnrich = events.filter((event) => getEventValue(event, mappingField.eventField));


// gets the values for that field, and makes a map of field_value to event
const eventsMapByFieldValue = eventsWithField.reduce((acc, event) => {
const eventFieldValue = getEventValue(event, mappingField.eventField);

Expand All @@ -38,14 +42,18 @@ export const createSingleFieldMatchEnrichment: CreateFieldsMatchEnrichment = asy
return acc;
}, {} as { [key: string]: typeof events });

// list of e.g. user.name's of host.names
const uniqueEventsValuesToSearchBy = Object.keys(eventsMapByFieldValue);

// array of arrays of e.g. user.name's of host.names
const chunksUniqueEventsValuesToSearchBy = chunk(uniqueEventsValuesToSearchBy, MAX_CLAUSES);

const getAllEnrichment = chunksUniqueEventsValuesToSearchBy
.map((enrichmentValuesChunk) =>
makeSingleFieldMatchQuery({
values: enrichmentValuesChunk,
searchByField: mappingField.enrichmentField,
extraFilters,
})
)
.filter((query) => query.query?.bool?.should?.length > 0)
Expand All @@ -63,6 +71,7 @@ export const createSingleFieldMatchEnrichment: CreateFieldsMatchEnrichment = asy
.filter((result) => result.status === 'fulfilled')
.map((result) => (result as PromiseFulfilledResult<EnrichmentType[]>)?.value);

// search hits.
const enrichments = flatten(enrichmentsResults);
Copy link
Contributor

Choose a reason for hiding this comment

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

Suggested change
.map((result) => (result as PromiseFulfilledResult<EnrichmentType[]>)?.value);
// search hits.
const enrichments = flatten(enrichmentsResults);
.flatMap((result) => (result as PromiseFulfilledResult<EnrichmentType[]>)?.value);

(you'd also need to rename enrichmentResults to just enrichments, but I couldn't include that line in this comment)


if (enrichments.length === 0) {
Expand Down
Loading