Skip to content

Commit 654d4da

Browse files
authored
[Security_Solution][Bug] Handle non-ecs categories in events (#71714)
* Make resolver related event categories permissive
1 parent 1f34096 commit 654d4da

File tree

6 files changed

+69
-199
lines changed

6 files changed

+69
-199
lines changed

x-pack/plugins/security_solution/public/resolver/store/data/reducer.test.ts

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -166,6 +166,15 @@ describe('Resolver Data Middleware', () => {
166166

167167
expect(selectedEventsForFirstChildNode).toBe(firstChildNodeInTree.relatedEvents);
168168
});
169+
it('should return related events for the category equal to the number of events of that type provided', () => {
170+
const relatedEventsByCategory = selectors.relatedEventsByCategory(store.getState());
171+
const relatedEventsForOvercountedCategory = relatedEventsByCategory(
172+
firstChildNodeInTree.id
173+
)(categoryToOverCount);
174+
expect(relatedEventsForOvercountedCategory.length).toBe(
175+
eventStatsForFirstChildNode.byCategory[categoryToOverCount] - 1
176+
);
177+
});
169178
it('should indicate the limit has been exceeded because the number of related events received for the category is less than what the stats count said it would be', () => {
170179
const selectedRelatedInfo = selectors.relatedEventInfoByEntityId(store.getState());
171180
const shouldShowLimit = selectedRelatedInfo(firstChildNodeInTree.id)

x-pack/plugins/security_solution/public/resolver/store/data/selectors.ts

Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -130,6 +130,38 @@ export function relatedEventsByEntityId(data: DataState): Map<string, ResolverRe
130130
return data.relatedEvents;
131131
}
132132

133+
/**
134+
* Returns a function that returns a function (when supplied with an entity id for a node)
135+
* that returns related events for a node that match an event.category (when supplied with the category)
136+
*/
137+
export const relatedEventsByCategory = createSelector(
138+
relatedEventsByEntityId,
139+
function provideGettersByCategory(
140+
/* eslint-disable no-shadow */
141+
relatedEventsByEntityId
142+
/* eslint-enable no-shadow */
143+
) {
144+
return defaultMemoize((entityId: string) => {
145+
return defaultMemoize((ecsCategory: string) => {
146+
const relatedById = relatedEventsByEntityId.get(entityId);
147+
// With no related events, we can't return related by category
148+
if (!relatedById) {
149+
return [];
150+
}
151+
return relatedById.events.reduce(
152+
(eventsByCategory: ResolverEvent[], candidate: ResolverEvent) => {
153+
if ([candidate && allEventCategories(candidate)].flat().includes(ecsCategory)) {
154+
eventsByCategory.push(candidate);
155+
}
156+
return eventsByCategory;
157+
},
158+
[]
159+
);
160+
});
161+
});
162+
}
163+
);
164+
133165
/**
134166
* returns a map of entity_ids to booleans indicating if it is waiting on related event
135167
* A value of `undefined` can be interpreted as `not yet requested`

x-pack/plugins/security_solution/public/resolver/store/selectors.ts

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -100,6 +100,15 @@ export const relatedEventsByEntityId = composeSelectors(
100100
dataSelectors.relatedEventsByEntityId
101101
);
102102

103+
/**
104+
* Returns a function that returns a function (when supplied with an entity id for a node)
105+
* that returns related events for a node that match an event.category (when supplied with the category)
106+
*/
107+
export const relatedEventsByCategory = composeSelectors(
108+
dataStateSelector,
109+
dataSelectors.relatedEventsByCategory
110+
);
111+
103112
/**
104113
* Entity ids to booleans for waiting status
105114
*/

x-pack/plugins/security_solution/public/resolver/view/panel.tsx

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,6 @@
77
import React, { memo, useMemo, useContext, useLayoutEffect, useState } from 'react';
88
import { useSelector } from 'react-redux';
99
import { EuiPanel } from '@elastic/eui';
10-
import { displayNameRecord } from './process_event_dot';
1110
import * as selectors from '../store/selectors';
1211
import { useResolverDispatch } from './use_resolver_dispatch';
1312
import * as event from '../../../common/endpoint/models/event';
@@ -144,7 +143,7 @@ const PanelContent = memo(function PanelContent() {
144143
* | relateds list 1 type | entity_id of process | valid related event type |
145144
*/
146145

147-
if (crumbEvent in displayNameRecord && uiSelectedEvent) {
146+
if (crumbEvent && crumbEvent.length && uiSelectedEvent) {
148147
return 'processEventListNarrowedByType';
149148
}
150149
}

x-pack/plugins/security_solution/public/resolver/view/panels/panel_content_related_list.tsx

Lines changed: 17 additions & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -164,9 +164,6 @@ export const ProcessEventListNarrowedByType = memo(function ProcessEventListNarr
164164
const relatedsReadyMap = useSelector(selectors.relatedEventsReady);
165165
const relatedsReady = relatedsReadyMap.get(processEntityId);
166166

167-
const relatedEventsForThisProcess = useSelector(selectors.relatedEventsByEntityId).get(
168-
processEntityId
169-
);
170167
const dispatch = useResolverDispatch();
171168

172169
useEffect(() => {
@@ -189,39 +186,30 @@ export const ProcessEventListNarrowedByType = memo(function ProcessEventListNarr
189186
];
190187
}, [pushToQueryParams, eventsString]);
191188

192-
const relatedEventsToDisplay = useMemo(() => {
193-
return relatedEventsForThisProcess?.events || [];
194-
}, [relatedEventsForThisProcess?.events]);
189+
const relatedByCategory = useSelector(selectors.relatedEventsByCategory);
195190

196191
/**
197192
* A list entry will be displayed for each of these
198193
*/
199194
const matchingEventEntries: MatchingEventEntry[] = useMemo(() => {
200-
const relateds = relatedEventsToDisplay
201-
.reduce((a: ResolverEvent[], candidate) => {
202-
if (event.primaryEventCategory(candidate) === eventType) {
203-
a.push(candidate);
204-
}
205-
return a;
206-
}, [])
207-
.map((resolverEvent) => {
208-
const eventTime = event.eventTimestamp(resolverEvent);
209-
const formattedDate = typeof eventTime === 'undefined' ? '' : formatDate(eventTime);
210-
const entityId = event.eventId(resolverEvent);
195+
const relateds = relatedByCategory(processEntityId)(eventType).map((resolverEvent) => {
196+
const eventTime = event.eventTimestamp(resolverEvent);
197+
const formattedDate = typeof eventTime === 'undefined' ? '' : formatDate(eventTime);
198+
const entityId = event.eventId(resolverEvent);
211199

212-
return {
213-
formattedDate,
214-
eventCategory: `${eventType}`,
215-
eventType: `${event.ecsEventType(resolverEvent)}`,
216-
name: event.descriptiveName(resolverEvent),
217-
entityId,
218-
setQueryParams: () => {
219-
pushToQueryParams({ crumbId: entityId, crumbEvent: processEntityId });
220-
},
221-
};
222-
});
200+
return {
201+
formattedDate,
202+
eventCategory: `${eventType}`,
203+
eventType: `${event.ecsEventType(resolverEvent)}`,
204+
name: event.descriptiveName(resolverEvent),
205+
entityId,
206+
setQueryParams: () => {
207+
pushToQueryParams({ crumbId: entityId, crumbEvent: processEntityId });
208+
},
209+
};
210+
});
223211
return relateds;
224-
}, [relatedEventsToDisplay, eventType, processEntityId, pushToQueryParams]);
212+
}, [relatedByCategory, eventType, processEntityId, pushToQueryParams]);
225213

226214
const crumbs = useMemo(() => {
227215
return [

x-pack/plugins/security_solution/public/resolver/view/process_event_dot.tsx

Lines changed: 1 addition & 168 deletions
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,6 @@
88

99
import React, { useCallback, useMemo } from 'react';
1010
import styled from 'styled-components';
11-
import { i18n } from '@kbn/i18n';
1211
import { htmlIdGenerator, EuiButton, EuiI18nNumber, EuiFlexGroup, EuiFlexItem } from '@elastic/eui';
1312
import { useSelector } from 'react-redux';
1413
import { NodeSubMenu, subMenuAssets } from './submenu';
@@ -21,172 +20,6 @@ import * as eventModel from '../../../common/endpoint/models/event';
2120
import * as selectors from '../store/selectors';
2221
import { useResolverQueryParams } from './use_resolver_query_params';
2322

24-
/**
25-
* A record of all known event types (in schema format) to translations
26-
*/
27-
export const displayNameRecord = {
28-
application: i18n.translate(
29-
'xpack.securitySolution.endpoint.resolver.applicationEventTypeDisplayName',
30-
{
31-
defaultMessage: 'Application',
32-
}
33-
),
34-
apm: i18n.translate('xpack.securitySolution.endpoint.resolver.apmEventTypeDisplayName', {
35-
defaultMessage: 'APM',
36-
}),
37-
audit: i18n.translate('xpack.securitySolution.endpoint.resolver.auditEventTypeDisplayName', {
38-
defaultMessage: 'Audit',
39-
}),
40-
authentication: i18n.translate(
41-
'xpack.securitySolution.endpoint.resolver.authenticationEventTypeDisplayName',
42-
{
43-
defaultMessage: 'Authentication',
44-
}
45-
),
46-
certificate: i18n.translate(
47-
'xpack.securitySolution.endpoint.resolver.certificateEventTypeDisplayName',
48-
{
49-
defaultMessage: 'Certificate',
50-
}
51-
),
52-
cloud: i18n.translate('xpack.securitySolution.endpoint.resolver.cloudEventTypeDisplayName', {
53-
defaultMessage: 'Cloud',
54-
}),
55-
database: i18n.translate(
56-
'xpack.securitySolution.endpoint.resolver.databaseEventTypeDisplayName',
57-
{
58-
defaultMessage: 'Database',
59-
}
60-
),
61-
driver: i18n.translate('xpack.securitySolution.endpoint.resolver.driverEventTypeDisplayName', {
62-
defaultMessage: 'Driver',
63-
}),
64-
email: i18n.translate('xpack.securitySolution.endpoint.resolver.emailEventTypeDisplayName', {
65-
defaultMessage: 'Email',
66-
}),
67-
file: i18n.translate('xpack.securitySolution.endpoint.resolver.fileEventTypeDisplayName', {
68-
defaultMessage: 'File',
69-
}),
70-
host: i18n.translate('xpack.securitySolution.endpoint.resolver.hostEventTypeDisplayName', {
71-
defaultMessage: 'Host',
72-
}),
73-
iam: i18n.translate('xpack.securitySolution.endpoint.resolver.iamEventTypeDisplayName', {
74-
defaultMessage: 'IAM',
75-
}),
76-
iam_group: i18n.translate(
77-
'xpack.securitySolution.endpoint.resolver.iam_groupEventTypeDisplayName',
78-
{
79-
defaultMessage: 'IAM Group',
80-
}
81-
),
82-
intrusion_detection: i18n.translate(
83-
'xpack.securitySolution.endpoint.resolver.intrusion_detectionEventTypeDisplayName',
84-
{
85-
defaultMessage: 'Intrusion Detection',
86-
}
87-
),
88-
malware: i18n.translate('xpack.securitySolution.endpoint.resolver.malwareEventTypeDisplayName', {
89-
defaultMessage: 'Malware',
90-
}),
91-
network_flow: i18n.translate(
92-
'xpack.securitySolution.endpoint.resolver.network_flowEventTypeDisplayName',
93-
{
94-
defaultMessage: 'Network Flow',
95-
}
96-
),
97-
network: i18n.translate('xpack.securitySolution.endpoint.resolver.networkEventTypeDisplayName', {
98-
defaultMessage: 'Network',
99-
}),
100-
package: i18n.translate('xpack.securitySolution.endpoint.resolver.packageEventTypeDisplayName', {
101-
defaultMessage: 'Package',
102-
}),
103-
process: i18n.translate('xpack.securitySolution.endpoint.resolver.processEventTypeDisplayName', {
104-
defaultMessage: 'Process',
105-
}),
106-
registry: i18n.translate(
107-
'xpack.securitySolution.endpoint.resolver.registryEventTypeDisplayName',
108-
{
109-
defaultMessage: 'Registry',
110-
}
111-
),
112-
session: i18n.translate('xpack.securitySolution.endpoint.resolver.sessionEventTypeDisplayName', {
113-
defaultMessage: 'Session',
114-
}),
115-
service: i18n.translate('xpack.securitySolution.endpoint.resolver.serviceEventTypeDisplayName', {
116-
defaultMessage: 'Service',
117-
}),
118-
socket: i18n.translate('xpack.securitySolution.endpoint.resolver.socketEventTypeDisplayName', {
119-
defaultMessage: 'Socket',
120-
}),
121-
vulnerability: i18n.translate(
122-
'xpack.securitySolution.endpoint.resolver.vulnerabilityEventTypeDisplayName',
123-
{
124-
defaultMessage: 'Vulnerability',
125-
}
126-
),
127-
web: i18n.translate('xpack.securitySolution.endpoint.resolver.webEventTypeDisplayName', {
128-
defaultMessage: 'Web',
129-
}),
130-
alert: i18n.translate('xpack.securitySolution.endpoint.resolver.alertEventTypeDisplayName', {
131-
defaultMessage: 'Alert',
132-
}),
133-
security: i18n.translate(
134-
'xpack.securitySolution.endpoint.resolver.securityEventTypeDisplayName',
135-
{
136-
defaultMessage: 'Security',
137-
}
138-
),
139-
dns: i18n.translate('xpack.securitySolution.endpoint.resolver.dnsEventTypeDisplayName', {
140-
defaultMessage: 'DNS',
141-
}),
142-
clr: i18n.translate('xpack.securitySolution.endpoint.resolver.clrEventTypeDisplayName', {
143-
defaultMessage: 'CLR',
144-
}),
145-
image_load: i18n.translate(
146-
'xpack.securitySolution.endpoint.resolver.image_loadEventTypeDisplayName',
147-
{
148-
defaultMessage: 'Image Load',
149-
}
150-
),
151-
powershell: i18n.translate(
152-
'xpack.securitySolution.endpoint.resolver.powershellEventTypeDisplayName',
153-
{
154-
defaultMessage: 'Powershell',
155-
}
156-
),
157-
wmi: i18n.translate('xpack.securitySolution.endpoint.resolver.wmiEventTypeDisplayName', {
158-
defaultMessage: 'WMI',
159-
}),
160-
api: i18n.translate('xpack.securitySolution.endpoint.resolver.apiEventTypeDisplayName', {
161-
defaultMessage: 'API',
162-
}),
163-
user: i18n.translate('xpack.securitySolution.endpoint.resolver.userEventTypeDisplayName', {
164-
defaultMessage: 'User',
165-
}),
166-
} as const;
167-
168-
const unknownEventTypeMessage = i18n.translate(
169-
'xpack.securitySolution.endpoint.resolver.userEventTypeDisplayUnknown',
170-
{
171-
defaultMessage: 'Unknown',
172-
}
173-
);
174-
175-
type EventDisplayName = typeof displayNameRecord[keyof typeof displayNameRecord] &
176-
typeof unknownEventTypeMessage;
177-
178-
/**
179-
* Take a `schemaName` and return a translation.
180-
*/
181-
const schemaNameTranslation: (
182-
schemaName: string
183-
) => EventDisplayName = function nameInSchemaToDisplayName(schemaName) {
184-
if (schemaName in displayNameRecord) {
185-
return displayNameRecord[schemaName as keyof typeof displayNameRecord];
186-
}
187-
return unknownEventTypeMessage;
188-
};
189-
19023
interface StyledActionsContainer {
19124
readonly color: string;
19225
readonly fontSize: number;
@@ -437,7 +270,7 @@ const UnstyledProcessEventDot = React.memo(
437270
)) {
438271
relatedStatsList.push({
439272
prefix: <EuiI18nNumber value={total || 0} />,
440-
optionTitle: schemaNameTranslation(category),
273+
optionTitle: category,
441274
action: () => {
442275
dispatch({
443276
type: 'userSelectedRelatedEventCategory',

0 commit comments

Comments
 (0)