[Security Solution] [Timeline] Bugfix to include unmapped fields in the timeline event details JSON (#92025)

This commit is contained in:
Steph Milovic 2021-03-01 20:40:54 -07:00 committed by GitHub
parent 7a1944a5a0
commit 90976ee119
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
35 changed files with 1248 additions and 403 deletions

View file

@ -16,6 +16,7 @@ export interface TimelineEventsDetailsItem {
values?: Maybe<string[]>;
// eslint-disable-next-line @typescript-eslint/no-explicit-any
originalValue?: Maybe<any>;
isObjectArray: boolean;
}
export interface TimelineEventsDetailsStrategyResponse extends IEsSearchResponse {

View file

@ -15,8 +15,8 @@ exports[`JSON View rendering should match snapshot 1`] = `
value="{
\\"_id\\": \\"pEMaMmkBUV60JmNWmWVi\\",
\\"_index\\": \\"filebeat-8.0.0-2019.02.19-000001\\",
\\"_type\\": \\"_doc\\",
\\"_score\\": 1,
\\"_type\\": \\"_doc\\",
\\"@timestamp\\": \\"2019-02-28T16:50:54.621Z\\",
\\"agent\\": {
\\"ephemeral_id\\": \\"9d391ef2-a734-4787-8891-67031178c641\\",

View file

@ -85,24 +85,28 @@ export const getColumns = ({
sortable: false,
truncateText: false,
width: '30px',
render: (field: string) => (
<EuiToolTip content={i18n.VIEW_COLUMN(field)}>
<EuiCheckbox
aria-label={i18n.VIEW_COLUMN(field)}
checked={columnHeaders.findIndex((c) => c.id === field) !== -1}
data-test-subj={`toggle-field-${field}`}
data-colindex={1}
id={field}
onChange={() =>
toggleColumn({
columnHeaderType: defaultColumnHeaderType,
id: field,
width: DEFAULT_COLUMN_MIN_WIDTH,
})
}
/>
</EuiToolTip>
),
render: (field: string, data: EventFieldsData) => {
const label = data.isObjectArray ? i18n.NESTED_COLUMN(field) : i18n.VIEW_COLUMN(field);
return (
<EuiToolTip content={label}>
<EuiCheckbox
aria-label={label}
checked={columnHeaders.findIndex((c) => c.id === field) !== -1}
data-test-subj={`toggle-field-${field}`}
data-colindex={1}
id={field}
onChange={() =>
toggleColumn({
columnHeaderType: defaultColumnHeaderType,
id: field,
width: DEFAULT_COLUMN_MIN_WIDTH,
})
}
disabled={data.isObjectArray && data.type !== 'geo_point'}
/>
</EuiToolTip>
);
},
},
{
field: 'field',
@ -118,38 +122,42 @@ export const getColumns = ({
</EuiFlexItem>
<EuiFlexItem grow={false}>
<DroppableWrapper
droppableId={getDroppableId(
`event-details-field-droppable-wrapper-${contextId}-${eventId}-${data.category}-${field}`
)}
key={getDroppableId(
`event-details-field-droppable-wrapper-${contextId}-${eventId}-${data.category}-${field}`
)}
isDropDisabled={true}
type={DRAG_TYPE_FIELD}
renderClone={(provided) => (
<div
{...provided.draggableProps}
{...provided.dragHandleProps}
ref={provided.innerRef}
tabIndex={-1}
>
<DragEffects>
<DraggableFieldBadge fieldId={field} />
</DragEffects>
</div>
)}
>
<DraggableFieldsBrowserField
browserFields={browserFields}
categoryId={data.category}
fieldName={field}
fieldCategory={data.category}
onUpdateColumns={onUpdateColumns}
timelineId={timelineId}
toggleColumn={toggleColumn}
/>
</DroppableWrapper>
{data.isObjectArray && data.type !== 'geo_point' ? (
<>{field}</>
) : (
<DroppableWrapper
droppableId={getDroppableId(
`event-details-field-droppable-wrapper-${contextId}-${eventId}-${data.category}-${field}`
)}
key={getDroppableId(
`event-details-field-droppable-wrapper-${contextId}-${eventId}-${data.category}-${field}`
)}
isDropDisabled={true}
type={DRAG_TYPE_FIELD}
renderClone={(provided) => (
<div
{...provided.draggableProps}
{...provided.dragHandleProps}
ref={provided.innerRef}
tabIndex={-1}
>
<DragEffects>
<DraggableFieldBadge fieldId={field} />
</DragEffects>
</div>
)}
>
<DraggableFieldsBrowserField
browserFields={browserFields}
categoryId={data.category}
fieldName={field}
fieldCategory={data.category}
onUpdateColumns={onUpdateColumns}
timelineId={timelineId}
toggleColumn={toggleColumn}
/>
</DroppableWrapper>
)}
</EuiFlexItem>
<EuiFlexItem grow={false}>
<EuiIconTip
@ -191,6 +199,7 @@ export const getColumns = ({
fieldFormat={data.format}
fieldName={data.field}
fieldType={data.type}
isObjectArray={data.isObjectArray}
value={value}
linkValue={getLinkValue(data.field)}
/>

View file

@ -108,7 +108,6 @@ export const EventFieldsBrowser = React.memo<Props>(
const columnHeaders = useDeepEqualSelector((state) => {
const { columns } = getTimeline(state, timelineId) ?? timelineDefaults;
return getColumnHeaders(columns, browserFields);
});

View file

@ -112,6 +112,7 @@ export const getIconFromType = (type: string | null) => {
case 'date':
return 'clock';
case 'ip':
case 'geo_point':
return 'globe';
case 'object':
return 'questionInCircle';

View file

@ -54,12 +54,14 @@ export const JsonView = React.memo<Props>(({ data }) => {
JsonView.displayName = 'JsonView';
export const buildJsonView = (data: TimelineEventsDetailsItem[]) =>
data.reduce(
(accumulator, item) =>
set(
item.field,
Array.isArray(item.originalValue) ? item.originalValue.join() : item.originalValue,
accumulator
),
{}
);
data
.sort((a, b) => a.field.localeCompare(b.field))
.reduce(
(accumulator, item) =>
set(
item.field,
Array.isArray(item.originalValue) ? item.originalValue.join() : item.originalValue,
accumulator
),
{}
);

View file

@ -61,3 +61,10 @@ export const VIEW_COLUMN = (field: string) =>
values: { field },
defaultMessage: 'View {field} column',
});
export const NESTED_COLUMN = (field: string) =>
i18n.translate('xpack.securitySolution.eventDetails.nestedColumnCheckboxAriaLabel', {
values: { field },
defaultMessage:
'The {field} field is an object, and is broken down into nested fields which can be added as column',
});

View file

@ -14,105 +14,126 @@ export const mockDetailItemData: TimelineEventsDetailsItem[] = [
field: '_id',
originalValue: 'pEMaMmkBUV60JmNWmWVi',
values: ['pEMaMmkBUV60JmNWmWVi'],
isObjectArray: false,
},
{
field: '_index',
originalValue: 'filebeat-8.0.0-2019.02.19-000001',
values: ['filebeat-8.0.0-2019.02.19-000001'],
isObjectArray: false,
},
{
field: '_type',
originalValue: '_doc',
values: ['_doc'],
isObjectArray: false,
},
{
field: '_score',
originalValue: 1,
values: ['1'],
isObjectArray: false,
},
{
field: '@timestamp',
originalValue: '2019-02-28T16:50:54.621Z',
values: ['2019-02-28T16:50:54.621Z'],
isObjectArray: false,
},
{
field: 'agent.ephemeral_id',
originalValue: '9d391ef2-a734-4787-8891-67031178c641',
values: ['9d391ef2-a734-4787-8891-67031178c641'],
isObjectArray: false,
},
{
field: 'agent.hostname',
originalValue: 'siem-kibana',
values: ['siem-kibana'],
},
{
field: 'agent.id',
originalValue: '5de03d5f-52f3-482e-91d4-853c7de073c3',
values: ['5de03d5f-52f3-482e-91d4-853c7de073c3'],
},
{
field: 'agent.type',
originalValue: 'filebeat',
values: ['filebeat'],
},
{
field: 'agent.version',
originalValue: '8.0.0',
values: ['8.0.0'],
},
{
field: 'cloud.availability_zone',
originalValue: 'projects/189716325846/zones/us-east1-b',
values: ['projects/189716325846/zones/us-east1-b'],
},
{
field: 'cloud.instance.id',
originalValue: '5412578377715150143',
values: ['5412578377715150143'],
},
{
field: 'cloud.instance.name',
originalValue: 'siem-kibana',
values: ['siem-kibana'],
},
{
field: 'cloud.machine.type',
originalValue: 'projects/189716325846/machineTypes/n1-standard-1',
values: ['projects/189716325846/machineTypes/n1-standard-1'],
isObjectArray: false,
},
{
field: 'cloud.project.id',
originalValue: 'elastic-beats',
values: ['elastic-beats'],
isObjectArray: false,
},
{
field: 'cloud.provider',
originalValue: 'gce',
values: ['gce'],
isObjectArray: false,
},
{
field: 'destination.bytes',
originalValue: 584,
values: ['584'],
isObjectArray: false,
},
{
field: 'destination.ip',
originalValue: '10.47.8.200',
values: ['10.47.8.200'],
isObjectArray: false,
},
{
field: 'agent.id',
originalValue: '5de03d5f-52f3-482e-91d4-853c7de073c3',
values: ['5de03d5f-52f3-482e-91d4-853c7de073c3'],
isObjectArray: false,
},
{
field: 'cloud.instance.name',
originalValue: 'siem-kibana',
values: ['siem-kibana'],
isObjectArray: false,
},
{
field: 'cloud.machine.type',
originalValue: 'projects/189716325846/machineTypes/n1-standard-1',
values: ['projects/189716325846/machineTypes/n1-standard-1'],
isObjectArray: false,
},
{
field: 'agent.type',
originalValue: 'filebeat',
values: ['filebeat'],
isObjectArray: false,
},
{
field: 'destination.packets',
originalValue: 4,
values: ['4'],
isObjectArray: false,
},
{
field: 'destination.port',
originalValue: 902,
values: ['902'],
isObjectArray: false,
},
{
field: 'event.kind',
originalValue: 'event',
values: ['event'],
isObjectArray: false,
},
{
field: 'agent.version',
originalValue: '8.0.0',
values: ['8.0.0'],
isObjectArray: false,
},
{
field: 'cloud.availability_zone',
originalValue: 'projects/189716325846/zones/us-east1-b',
values: ['projects/189716325846/zones/us-east1-b'],
isObjectArray: false,
},
{
field: 'cloud.instance.id',
originalValue: '5412578377715150143',
values: ['5412578377715150143'],
isObjectArray: false,
},
];

View file

@ -2287,11 +2287,13 @@ export const mockTimelineDetails: TimelineEventsDetailsItem[] = [
field: 'host.name',
values: ['apache'],
originalValue: 'apache',
isObjectArray: false,
},
{
field: 'user.id',
values: ['1'],
originalValue: 1,
isObjectArray: false,
},
];

View file

@ -29,6 +29,7 @@ describe('helpers', () => {
{
field: 'x',
values: ['The nickname of the developer we all :heart:'],
isObjectArray: false,
originalValue: 'The nickname of the developer we all :heart:',
},
]);
@ -40,6 +41,7 @@ describe('helpers', () => {
{
field: 'x',
values: ['The nickname of the developer we all :heart:'],
isObjectArray: false,
originalValue: 'The nickname of the developer we all :heart:',
},
]);
@ -51,6 +53,7 @@ describe('helpers', () => {
{
field: 'x',
values: ['The nickname of the developer we all :heart:', 'We are all made of stars'],
isObjectArray: false,
originalValue: 'The nickname of the developer we all :heart:',
},
]);
@ -65,6 +68,7 @@ describe('helpers', () => {
{
field: 'x.y.z',
values: ['zed'],
isObjectArray: false,
originalValue: 'zed',
},
]);
@ -76,6 +80,7 @@ describe('helpers', () => {
{
field: 'x.y.z',
values: ['zed'],
isObjectArray: false,
originalValue: 'zed',
},
]);
@ -90,6 +95,7 @@ describe('helpers', () => {
{
field: 'a',
values: (5 as unknown) as string[],
isObjectArray: false,
originalValue: 'zed',
},
],
@ -104,7 +110,7 @@ describe('helpers', () => {
'when trying to access field:',
'a',
'from data object of:',
[{ field: 'a', originalValue: 'zed', values: 5 }]
[{ field: 'a', isObjectArray: false, originalValue: 'zed', values: 5 }]
);
});
@ -116,6 +122,7 @@ describe('helpers', () => {
{
field: 'a',
values: (['hi', 5] as unknown) as string[],
isObjectArray: false,
originalValue: 'zed',
},
],
@ -130,7 +137,7 @@ describe('helpers', () => {
'when trying to access field:',
'a',
'from data object of:',
[{ field: 'a', originalValue: 'zed', values: ['hi', 5] }]
[{ field: 'a', isObjectArray: false, originalValue: 'zed', values: ['hi', 5] }]
);
});
});

View file

@ -86,6 +86,7 @@ export const HeaderComponent: React.FC<Props> = ({
getManageTimelineById,
timelineId,
]);
const showSortingCapability = !isEqlOn && !(header.subType && header.subType.nested);
return (
<>
@ -94,7 +95,7 @@ export const HeaderComponent: React.FC<Props> = ({
isLoading={isLoading}
isResizing={false}
onClick={onColumnSort}
showSortingCapability={!isEqlOn}
showSortingCapability={showSortingCapability}
sort={sort}
>
<Actions

View file

@ -69,7 +69,7 @@ exports[`Columns it renders the expected columns 1`] = `
fieldFormat=""
fieldName="event.category"
fieldType=""
key="plain-column-renderer-formatted-field-value-test-event.category-1-event.category-Access"
key="plain-column-renderer-formatted-field-value-test-event.category-1-event.category-Access-0"
truncate={true}
value="Access"
/>
@ -99,7 +99,7 @@ exports[`Columns it renders the expected columns 1`] = `
fieldFormat=""
fieldName="event.action"
fieldType=""
key="plain-column-renderer-formatted-field-value-test-event.action-1-event.action-Action"
key="plain-column-renderer-formatted-field-value-test-event.action-1-event.action-Action-0"
truncate={true}
value="Action"
/>
@ -129,7 +129,7 @@ exports[`Columns it renders the expected columns 1`] = `
fieldFormat=""
fieldName="host.name"
fieldType=""
key="plain-column-renderer-formatted-field-value-test-host.name-1-host.name-apache"
key="plain-column-renderer-formatted-field-value-test-host.name-1-host.name-apache-0"
truncate={true}
value="apache"
/>
@ -159,7 +159,7 @@ exports[`Columns it renders the expected columns 1`] = `
fieldFormat=""
fieldName="source.ip"
fieldType=""
key="plain-column-renderer-formatted-field-value-test-source.ip-1-source.ip-192.168.0.1"
key="plain-column-renderer-formatted-field-value-test-source.ip-1-source.ip-192.168.0.1-0"
truncate={true}
value="192.168.0.1"
/>
@ -189,7 +189,7 @@ exports[`Columns it renders the expected columns 1`] = `
fieldFormat=""
fieldName="destination.ip"
fieldType=""
key="plain-column-renderer-formatted-field-value-test-destination.ip-1-destination.ip-192.168.0.3"
key="plain-column-renderer-formatted-field-value-test-destination.ip-1-destination.ip-192.168.0.3-0"
truncate={true}
value="192.168.0.3"
/>
@ -219,7 +219,7 @@ exports[`Columns it renders the expected columns 1`] = `
fieldFormat=""
fieldName="user.name"
fieldType=""
key="plain-column-renderer-formatted-field-value-test-user.name-1-user.name-john.dee"
key="plain-column-renderer-formatted-field-value-test-user.name-1-user.name-john.dee-0"
truncate={true}
value="john.dee"
/>

View file

@ -8,7 +8,7 @@ exports[`get_column_renderer renders correctly against snapshot 1`] = `
fieldFormat=""
fieldName="event.severity"
fieldType=""
key="plain-column-renderer-formatted-field-value-test-event.severity-1-message-3"
key="plain-column-renderer-formatted-field-value-test-event.severity-1-message-3-0"
value="3"
/>
</span>

View file

@ -8,7 +8,7 @@ exports[`plain_column_renderer rendering renders correctly against snapshot 1`]
fieldFormat=""
fieldName="event.category"
fieldType="keyword"
key="plain-column-renderer-formatted-field-value-test-event.category-1-event.category-Access"
key="plain-column-renderer-formatted-field-value-test-event.category-1-event.category-Access-0"
value="Access"
/>
</span>

View file

@ -41,14 +41,27 @@ const columnNamesNotDraggable = [MESSAGE_FIELD_NAME];
const FormattedFieldValueComponent: React.FC<{
contextId: string;
eventId: string;
isObjectArray?: boolean;
fieldFormat?: string;
fieldName: string;
fieldType: string;
truncate?: boolean;
value: string | number | undefined | null;
linkValue?: string | null | undefined;
}> = ({ contextId, eventId, fieldFormat, fieldName, fieldType, truncate, value, linkValue }) => {
if (fieldType === IP_FIELD_TYPE) {
}> = ({
contextId,
eventId,
fieldFormat,
fieldName,
fieldType,
isObjectArray = false,
truncate,
value,
linkValue,
}) => {
if (isObjectArray) {
return <>{value}</>;
} else if (fieldType === IP_FIELD_TYPE) {
return (
<FormattedIp
eventId={eventId}

View file

@ -40,9 +40,9 @@ export const plainColumnRenderer: ColumnRenderer = {
linkValues?: string[] | null | undefined;
}) =>
values != null
? values.map((value) => (
? values.map((value, i) => (
<FormattedFieldValue
key={`plain-column-renderer-formatted-field-value-${timelineId}-${columnName}-${eventId}-${field.id}-${value}`}
key={`plain-column-renderer-formatted-field-value-${timelineId}-${columnName}-${eventId}-${field.id}-${value}-${i}`}
contextId={`plain-column-renderer-formatted-field-value-${timelineId}`}
eventId={eventId}
fieldFormat={field.format || ''}

View file

@ -5,7 +5,7 @@
* 2.0.
*/
import { Filter } from '../../../../../../../src/plugins/data/public';
import { Filter, IFieldSubType } from '../../../../../../../src/plugins/data/public';
import { DataProvider } from '../../components/timeline/data_providers/data_provider';
import { Sort } from '../../components/timeline/body/sort';
@ -43,6 +43,7 @@ export interface ColumnHeaderOptions {
label?: string;
linkField?: string;
placeholder?: string;
subType?: IFieldSubType;
type?: string;
width: number;
}

View file

@ -7,7 +7,6 @@
export const toArray = <T = string>(value: T | T[] | null): T[] =>
Array.isArray(value) ? value : value == null ? [] : [value];
export const toStringArray = <T = string>(value: T | T[] | null): string[] => {
if (Array.isArray(value)) {
return value.reduce<string[]>((acc, v) => {
@ -42,3 +41,47 @@ export const toStringArray = <T = string>(value: T | T[] | null): string[] => {
return [`${value}`];
}
};
export const toObjectArrayOfStrings = <T = string>(
value: T | T[] | null
): Array<{
str: string;
isObjectArray?: boolean;
}> => {
if (Array.isArray(value)) {
return value.reduce<
Array<{
str: string;
isObjectArray?: boolean;
}>
>((acc, v) => {
if (v != null) {
switch (typeof v) {
case 'number':
case 'boolean':
return [...acc, { str: v.toString() }];
case 'object':
try {
return [...acc, { str: JSON.stringify(v), isObjectArray: true }]; // need to track when string is not a simple value
} catch {
return [...acc, { str: 'Invalid Object' }];
}
case 'string':
return [...acc, { str: v }];
default:
return [...acc, { str: `${v}` }];
}
}
return acc;
}, []);
} else if (value == null) {
return [];
} else if (!Array.isArray(value) && typeof value === 'object') {
try {
return [{ str: JSON.stringify(value), isObjectArray: true }];
} catch {
return [{ str: 'Invalid Object' }];
}
} else {
return [{ str: `${value}` }];
}
};

View file

@ -11,7 +11,7 @@ import { hostFieldsMap } from '../../../../../../common/ecs/ecs_fields';
import { HostsEdges } from '../../../../../../common/search_strategy/security_solution/hosts';
import { HostAggEsItem, HostBuckets, HostValue } from '../../../../../lib/hosts/types';
import { toStringArray } from '../../../../helpers/to_array';
import { toObjectArrayOfStrings } from '../../../../helpers/to_array';
export const HOSTS_FIELDS: readonly string[] = [
'_id',
@ -33,7 +33,11 @@ export const formatHostEdgesData = (
flattenedFields.cursor.value = hostId || '';
const fieldValue = getHostFieldValue(fieldName, bucket);
if (fieldValue != null) {
return set(`node.${fieldName}`, toStringArray(fieldValue), flattenedFields);
return set(
`node.${fieldName}`,
toObjectArrayOfStrings(fieldValue).map(({ str }) => str),
flattenedFields
);
}
return flattenedFields;
},

View file

@ -8,7 +8,7 @@
import { get, getOr, isEmpty } from 'lodash/fp';
import { set } from '@elastic/safer-lodash-set/fp';
import { mergeFieldsWithHit } from '../../../../../utils/build_query';
import { toStringArray } from '../../../../helpers/to_array';
import { toObjectArrayOfStrings } from '../../../../helpers/to_array';
import {
AuthenticationsEdges,
AuthenticationHit,
@ -55,7 +55,11 @@ export const formatAuthenticationData = (
const fieldPath = `node.${fieldName}`;
const fieldValue = get(fieldPath, mergedResult);
if (!isEmpty(fieldValue)) {
return set(fieldPath, toStringArray(fieldValue), mergedResult);
return set(
fieldPath,
toObjectArrayOfStrings(fieldValue).map(({ str }) => str),
mergedResult
);
} else {
return mergedResult;
}

View file

@ -9,7 +9,7 @@ import { set } from '@elastic/safer-lodash-set/fp';
import { get, has, head } from 'lodash/fp';
import { hostFieldsMap } from '../../../../../../common/ecs/ecs_fields';
import { HostItem } from '../../../../../../common/search_strategy/security_solution/hosts';
import { toStringArray } from '../../../../helpers/to_array';
import { toObjectArrayOfStrings } from '../../../../helpers/to_array';
import { HostAggEsItem, HostBuckets, HostValue } from '../../../../../lib/hosts/types';
@ -42,7 +42,11 @@ export const formatHostItem = (bucket: HostAggEsItem): HostItem =>
if (fieldName === '_id') {
return set('_id', fieldValue, flattenedFields);
}
return set(fieldName, toStringArray(fieldValue), flattenedFields);
return set(
fieldName,
toObjectArrayOfStrings(fieldValue).map(({ str }) => str),
flattenedFields
);
}
return flattenedFields;
}, {});

View file

@ -14,7 +14,7 @@ import {
HostsUncommonProcessesEdges,
HostsUncommonProcessHit,
} from '../../../../../../common/search_strategy/security_solution/hosts/uncommon_processes';
import { toStringArray } from '../../../../helpers/to_array';
import { toObjectArrayOfStrings } from '../../../../helpers/to_array';
import { HostHits } from '../../../../../../common/search_strategy';
export const uncommonProcessesFields = [
@ -82,7 +82,11 @@ export const formatUncommonProcessesData = (
fieldPath = `node.hosts.0.name`;
fieldValue = get(fieldPath, mergedResult);
}
return set(fieldPath, toStringArray(fieldValue), mergedResult);
return set(
fieldPath,
toObjectArrayOfStrings(fieldValue).map(({ str }) => str),
mergedResult
);
},
{
node: {

View file

@ -13,7 +13,7 @@ import {
NetworkDetailsHostHit,
NetworkHit,
} from '../../../../../../common/search_strategy/security_solution/network';
import { toStringArray } from '../../../../helpers/to_array';
import { toObjectArrayOfStrings } from '../../../../helpers/to_array';
export const getNetworkDetailsAgg = (type: string, networkHit: NetworkHit | {}) => {
const firstSeen = getOr(null, `firstSeen.value_as_string`, networkHit);
@ -53,7 +53,7 @@ const formatHostEcs = (data: Record<string, unknown> | null): HostEcs | null =>
}
return {
...acc,
[key]: toStringArray(value),
[key]: toObjectArrayOfStrings(value).map(({ str }) => str),
};
}, {});
};

View file

@ -7,7 +7,7 @@
import { EqlSearchStrategyResponse } from '../../../../../data_enhanced/common';
import { DEFAULT_MAX_TABLE_QUERY_SIZE } from '../../../../common/constants';
import { EqlSearchResponse } from '../../../../common/detection_engine/types';
import { EqlSearchResponse, EqlSequence } from '../../../../common/detection_engine/types';
import { EventHit, TimelineEdges } from '../../../../common/search_strategy';
import {
TimelineEqlRequestOptions,
@ -56,51 +56,53 @@ export const buildEqlDsl = (options: TimelineEqlRequestOptions): Record<string,
},
};
};
const parseSequences = async (sequences: Array<EqlSequence<unknown>>, fieldRequested: string[]) =>
sequences.reduce<Promise<TimelineEdges[]>>(async (acc, sequence, sequenceIndex) => {
const sequenceParentId = sequence.events[0]?._id ?? null;
const data = await acc;
const allData = await Promise.all(
sequence.events.map(async (event, eventIndex) => {
const item = await formatTimelineData(
fieldRequested,
TIMELINE_EVENTS_FIELDS,
event as EventHit
);
return Promise.resolve({
...item,
node: {
...item.node,
ecs: {
...item.node.ecs,
...(sequenceParentId != null
? {
eql: {
parentId: sequenceParentId,
sequenceNumber: `${sequenceIndex}-${eventIndex}`,
},
}
: {}),
},
},
});
})
);
return Promise.resolve([...data, ...allData]);
}, Promise.resolve([]));
export const parseEqlResponse = (
export const parseEqlResponse = async (
options: TimelineEqlRequestOptions,
response: EqlSearchStrategyResponse<EqlSearchResponse<unknown>>
): Promise<TimelineEqlResponse> => {
const { activePage, querySize } = options.pagination;
// const totalCount = response.rawResponse?.body?.hits?.total?.value ?? 0;
let edges: TimelineEdges[] = [];
if (response.rawResponse.body.hits.sequences !== undefined) {
edges = response.rawResponse.body.hits.sequences.reduce<TimelineEdges[]>(
(data, sequence, sequenceIndex) => {
const sequenceParentId = sequence.events[0]?._id ?? null;
return [
...data,
...sequence.events.map((event, eventIndex) => {
const item = formatTimelineData(
options.fieldRequested,
TIMELINE_EVENTS_FIELDS,
event as EventHit
);
return {
...item,
node: {
...item.node,
ecs: {
...item.node.ecs,
...(sequenceParentId != null
? {
eql: {
parentId: sequenceParentId,
sequenceNumber: `${sequenceIndex}-${eventIndex}`,
},
}
: {}),
},
},
};
}),
];
},
[]
);
edges = await parseSequences(response.rawResponse.body.hits.sequences, options.fieldRequested);
} else if (response.rawResponse.body.hits.events !== undefined) {
edges = response.rawResponse.body.hits.events.map((event) =>
formatTimelineData(options.fieldRequested, TIMELINE_EVENTS_FIELDS, event as EventHit)
edges = await Promise.all(
response.rawResponse.body.hits.events.map(async (event) =>
formatTimelineData(options.fieldRequested, TIMELINE_EVENTS_FIELDS, event as EventHit)
)
);
}

View file

@ -38,7 +38,7 @@ export const securitySolutionTimelineEqlSearchStrategyProvider = (
},
};
}),
mergeMap((esSearchRes) =>
mergeMap(async (esSearchRes) =>
parseEqlResponse(
request,
(esSearchRes as unknown) as EqlSearchStrategyResponse<EqlSearchResponse<unknown>>

View file

@ -8,54 +8,23 @@
import { EventHit } from '../../../../../../common/search_strategy';
import { TIMELINE_EVENTS_FIELDS } from './constants';
import { formatTimelineData } from './helpers';
import { eventHit } from '../mocks';
describe('#formatTimelineData', () => {
it('happy path', () => {
const response: EventHit = {
_index: 'auditbeat-7.8.0-2020.11.05-000003',
_id: 'tkCt1nUBaEgqnrVSZ8R_',
_score: 0,
_type: '',
fields: {
'event.category': ['process'],
'process.ppid': [3977],
'user.name': ['jenkins'],
'process.args': ['go', 'vet', './...'],
message: ['Process go (PID: 4313) by user jenkins STARTED'],
'process.pid': [4313],
'process.working_directory': [
'/var/lib/jenkins/workspace/Beats_beats_PR-22624/src/github.com/elastic/beats/libbeat',
],
'process.entity_id': ['Z59cIkAAIw8ZoK0H'],
'host.ip': ['10.224.1.237', 'fe80::4001:aff:fee0:1ed', '172.17.0.1'],
'process.name': ['go'],
'event.action': ['process_started'],
'agent.type': ['auditbeat'],
'@timestamp': ['2020-11-17T14:48:08.922Z'],
'event.module': ['system'],
'event.type': ['start'],
'host.name': ['beats-ci-immutable-ubuntu-1804-1605624279743236239'],
'process.hash.sha1': ['1eac22336a41e0660fb302add9d97daa2bcc7040'],
'host.os.family': ['debian'],
'event.kind': ['event'],
'host.id': ['e59991e835905c65ed3e455b33e13bd6'],
'event.dataset': ['process'],
'process.executable': [
'/var/lib/jenkins/workspace/Beats_beats_PR-22624/.gvm/versions/go1.14.7.linux.amd64/bin/go',
],
},
_source: {},
sort: ['1605624488922', 'beats-ci-immutable-ubuntu-1804-1605624279743236239'],
aggregations: {},
};
expect(
formatTimelineData(
['@timestamp', 'host.name', 'destination.ip', 'source.ip'],
TIMELINE_EVENTS_FIELDS,
response
)
).toEqual({
it('happy path', async () => {
const res = await formatTimelineData(
[
'@timestamp',
'host.name',
'destination.ip',
'source.ip',
'source.geo.location',
'threat.indicator.matched.field',
],
TIMELINE_EVENTS_FIELDS,
eventHit
);
expect(res).toEqual({
cursor: {
tiebreaker: 'beats-ci-immutable-ubuntu-1804-1605624279743236239',
value: '1605624488922',
@ -72,6 +41,14 @@ describe('#formatTimelineData', () => {
field: 'host.name',
value: ['beats-ci-immutable-ubuntu-1804-1605624279743236239'],
},
{
field: 'source.geo.location',
value: [`{"lon":118.7778,"lat":32.0617}`],
},
{
field: 'threat.indicator.matched.field',
value: ['matched_field', 'matched_field_2'],
},
],
ecs: {
'@timestamp': ['2020-11-17T14:48:08.922Z'],
@ -122,7 +99,7 @@ describe('#formatTimelineData', () => {
});
});
it('rule signal results', () => {
it('rule signal results', async () => {
const response: EventHit = {
_index: '.siem-signals-patrykkopycinski-default-000007',
_id: 'a77040f198355793c35bf22b900902371309be615381f0a2ec92c208b6132562',
@ -290,7 +267,7 @@ describe('#formatTimelineData', () => {
};
expect(
formatTimelineData(
await formatTimelineData(
['@timestamp', 'host.name', 'destination.ip', 'source.ip'],
TIMELINE_EVENTS_FIELDS,
response

View file

@ -6,9 +6,13 @@
*/
import { get, has, merge, uniq } from 'lodash/fp';
import { EventHit, TimelineEdges } from '../../../../../../common/search_strategy';
import {
EventHit,
TimelineEdges,
TimelineNonEcsData,
} from '../../../../../../common/search_strategy';
import { toStringArray } from '../../../../helpers/to_array';
import { formatGeoLocation, isGeoField } from '../details/helpers';
import { getDataSafety, getDataFromFieldsHits } from '../details/helpers';
const getTimestamp = (hit: EventHit): string => {
if (hit.fields && hit.fields['@timestamp']) {
@ -19,13 +23,14 @@ const getTimestamp = (hit: EventHit): string => {
return '';
};
export const formatTimelineData = (
export const formatTimelineData = async (
dataFields: readonly string[],
ecsFields: readonly string[],
hit: EventHit
) =>
uniq([...ecsFields, ...dataFields]).reduce<TimelineEdges>(
(flattenedFields, fieldName) => {
uniq([...ecsFields, ...dataFields]).reduce<Promise<TimelineEdges>>(
async (acc, fieldName) => {
const flattenedFields: TimelineEdges = await acc;
flattenedFields.node._id = hit._id;
flattenedFields.node._index = hit._index;
flattenedFields.node.ecs._id = hit._id;
@ -35,30 +40,81 @@ export const formatTimelineData = (
flattenedFields.cursor.value = hit.sort[0];
flattenedFields.cursor.tiebreaker = hit.sort[1];
}
return mergeTimelineFieldsWithHit(fieldName, flattenedFields, hit, dataFields, ecsFields);
const waitForIt = await mergeTimelineFieldsWithHit(
fieldName,
flattenedFields,
hit,
dataFields,
ecsFields
);
return Promise.resolve(waitForIt);
},
{
Promise.resolve({
node: { ecs: { _id: '' }, data: [], _id: '', _index: '' },
cursor: {
value: '',
tiebreaker: null,
},
}
})
);
const specialFields = ['_id', '_index', '_type', '_score'];
const mergeTimelineFieldsWithHit = <T>(
const getValuesFromFields = async (
fieldName: string,
hit: EventHit,
nestedParentFieldName?: string
): Promise<TimelineNonEcsData[]> => {
if (specialFields.includes(fieldName)) {
return [{ field: fieldName, value: toStringArray(get(fieldName, hit)) }];
}
let fieldToEval;
if (has(fieldName, hit._source)) {
fieldToEval = {
[fieldName]: get(fieldName, hit._source),
};
} else {
if (nestedParentFieldName == null || nestedParentFieldName === fieldName) {
fieldToEval = {
[fieldName]: hit.fields[fieldName],
};
} else if (nestedParentFieldName != null) {
fieldToEval = {
[nestedParentFieldName]: hit.fields[nestedParentFieldName],
};
} else {
// fallback, should never hit
fieldToEval = {
[fieldName]: [],
};
}
}
const formattedData = await getDataSafety(getDataFromFieldsHits, fieldToEval);
return formattedData.reduce(
(acc: TimelineNonEcsData[], { field, values }) =>
// nested fields return all field values, pick only the one we asked for
field.includes(fieldName) ? [...acc, { field, value: values }] : acc,
[]
);
};
const mergeTimelineFieldsWithHit = async <T>(
fieldName: string,
flattenedFields: T,
hit: { _source: {}; fields: Record<string, unknown[]> },
hit: EventHit,
dataFields: readonly string[],
ecsFields: readonly string[]
) => {
if (fieldName != null || dataFields.includes(fieldName)) {
const fieldNameAsArray = fieldName.split('.');
const nestedParentFieldName = Object.keys(hit.fields ?? []).find((f) => {
return f === fieldNameAsArray.slice(0, f.split('.').length).join('.');
});
if (
has(fieldName, hit._source) ||
has(fieldName, hit.fields) ||
nestedParentFieldName != null ||
specialFields.includes(fieldName)
) {
const objectWithProperty = {
@ -67,16 +123,7 @@ const mergeTimelineFieldsWithHit = <T>(
data: dataFields.includes(fieldName)
? [
...get('node.data', flattenedFields),
{
field: fieldName,
value: specialFields.includes(fieldName)
? toStringArray(get(fieldName, hit))
: isGeoField(fieldName)
? formatGeoLocation(hit.fields[fieldName])
: has(fieldName, hit._source)
? toStringArray(get(fieldName, hit._source))
: toStringArray(hit.fields[fieldName]),
},
...(await getValuesFromFields(fieldName, hit, nestedParentFieldName)),
]
: get('node.data', flattenedFields),
ecs: ecsFields.includes(fieldName)

View file

@ -40,8 +40,10 @@ export const timelineEventsAll: SecuritySolutionTimelineFactory<TimelineEventsQu
const { activePage, querySize } = options.pagination;
const totalCount = response.rawResponse.hits.total || 0;
const hits = response.rawResponse.hits.hits;
const edges: TimelineEdges[] = hits.map((hit) =>
formatTimelineData(options.fieldRequested, TIMELINE_EVENTS_FIELDS, hit as EventHit)
const edges: TimelineEdges[] = await Promise.all(
hits.map((hit) =>
formatTimelineData(options.fieldRequested, TIMELINE_EVENTS_FIELDS, hit as EventHit)
)
);
const inspect = {
dsl: [inspectStringifyObject(buildTimelineEventsAllQuery(queryOptions))],

View file

@ -5,150 +5,192 @@
* 2.0.
*/
import { EventHit } from '../../../../../../common/search_strategy';
import { getDataFromFieldsHits } from './helpers';
import { EventHit, EventSource } from '../../../../../../common/search_strategy';
import { getDataFromFieldsHits, getDataFromSourceHits, getDataSafety } from './helpers';
import { eventDetailsFormattedFields, eventHit } from '../mocks';
describe('#getDataFromFieldsHits', () => {
it('happy path', () => {
const response: EventHit = {
_index: 'auditbeat-7.8.0-2020.11.05-000003',
_id: 'tkCt1nUBaEgqnrVSZ8R_',
_score: 0,
_type: '',
fields: {
'event.category': ['process'],
'process.ppid': [3977],
'user.name': ['jenkins'],
'process.args': ['go', 'vet', './...'],
message: ['Process go (PID: 4313) by user jenkins STARTED'],
'process.pid': [4313],
'process.working_directory': [
'/var/lib/jenkins/workspace/Beats_beats_PR-22624/src/github.com/elastic/beats/libbeat',
describe('Events Details Helpers', () => {
const fields: EventHit['fields'] = eventHit.fields;
const resultFields = eventDetailsFormattedFields;
describe('#getDataFromFieldsHits', () => {
it('happy path', () => {
const result = getDataFromFieldsHits(fields);
expect(result).toEqual(resultFields);
});
it('lets get weird', () => {
const whackFields = {
'crazy.pants': [
{
'matched.field': ['matched_field'],
first_seen: ['2021-02-22T17:29:25.195Z'],
provider: ['yourself'],
type: ['custom'],
'matched.atomic': ['matched_atomic'],
lazer: [
{
'great.field': ['grrrrr'],
lazer: [
{
lazer: [
{
cool: true,
lazer: [
{
lazer: [
{
lazer: [
{
lazer: [
{
whoa: false,
},
],
},
],
},
],
},
],
},
],
},
{
lazer: [
{
cool: false,
},
],
},
],
},
{
'great.field': ['grrrrr_2'],
},
],
},
],
'process.entity_id': ['Z59cIkAAIw8ZoK0H'],
'host.ip': ['10.224.1.237', 'fe80::4001:aff:fee0:1ed', '172.17.0.1'],
'process.name': ['go'],
'event.action': ['process_started'],
'agent.type': ['auditbeat'],
'@timestamp': ['2020-11-17T14:48:08.922Z'],
'event.module': ['system'],
'event.type': ['start'],
'host.name': ['beats-ci-immutable-ubuntu-1804-1605624279743236239'],
'process.hash.sha1': ['1eac22336a41e0660fb302add9d97daa2bcc7040'],
'host.os.family': ['debian'],
'event.kind': ['event'],
'host.id': ['e59991e835905c65ed3e455b33e13bd6'],
'event.dataset': ['process'],
'process.executable': [
'/var/lib/jenkins/workspace/Beats_beats_PR-22624/.gvm/versions/go1.14.7.linux.amd64/bin/go',
],
},
_source: {},
sort: ['1605624488922', 'beats-ci-immutable-ubuntu-1804-1605624279743236239'],
aggregations: {},
};
const whackResultFields = [
{
category: 'crazy',
field: 'crazy.pants.matched.field',
values: ['matched_field'],
originalValue: ['matched_field'],
isObjectArray: false,
},
{
category: 'crazy',
field: 'crazy.pants.first_seen',
values: ['2021-02-22T17:29:25.195Z'],
originalValue: ['2021-02-22T17:29:25.195Z'],
isObjectArray: false,
},
{
category: 'crazy',
field: 'crazy.pants.provider',
values: ['yourself'],
originalValue: ['yourself'],
isObjectArray: false,
},
{
category: 'crazy',
field: 'crazy.pants.type',
values: ['custom'],
originalValue: ['custom'],
isObjectArray: false,
},
{
category: 'crazy',
field: 'crazy.pants.matched.atomic',
values: ['matched_atomic'],
originalValue: ['matched_atomic'],
isObjectArray: false,
},
{
category: 'crazy',
field: 'crazy.pants.lazer.great.field',
values: ['grrrrr', 'grrrrr_2'],
originalValue: ['grrrrr', 'grrrrr_2'],
isObjectArray: false,
},
{
category: 'crazy',
field: 'crazy.pants.lazer.lazer.lazer.cool',
values: ['true', 'false'],
originalValue: ['true', 'false'],
isObjectArray: false,
},
{
category: 'crazy',
field: 'crazy.pants.lazer.lazer.lazer.lazer.lazer.lazer.lazer.whoa',
values: ['false'],
originalValue: ['false'],
isObjectArray: false,
},
];
const result = getDataFromFieldsHits(whackFields);
expect(result).toEqual(whackResultFields);
});
});
it('#getDataFromSourceHits', () => {
const _source: EventSource = {
'@timestamp': '2021-02-24T00:41:06.527Z',
'signal.status': 'open',
'signal.rule.name': 'Rawr',
'threat.indicator': [
{
provider: 'yourself',
type: 'custom',
first_seen: ['2021-02-22T17:29:25.195Z'],
matched: { atomic: 'atom', field: 'field', type: 'type' },
},
{
provider: 'other_you',
type: 'custom',
first_seen: '2021-02-22T17:29:25.195Z',
matched: { atomic: 'atom', field: 'field', type: 'type' },
},
],
};
expect(getDataFromFieldsHits(response.fields)).toEqual([
{
category: 'event',
field: 'event.category',
originalValue: ['process'],
values: ['process'],
},
{ category: 'process', field: 'process.ppid', originalValue: ['3977'], values: ['3977'] },
{ category: 'user', field: 'user.name', originalValue: ['jenkins'], values: ['jenkins'] },
{
category: 'process',
field: 'process.args',
originalValue: ['go', 'vet', './...'],
values: ['go', 'vet', './...'],
},
{
category: 'base',
field: 'message',
originalValue: ['Process go (PID: 4313) by user jenkins STARTED'],
values: ['Process go (PID: 4313) by user jenkins STARTED'],
},
{ category: 'process', field: 'process.pid', originalValue: ['4313'], values: ['4313'] },
{
category: 'process',
field: 'process.working_directory',
originalValue: [
'/var/lib/jenkins/workspace/Beats_beats_PR-22624/src/github.com/elastic/beats/libbeat',
],
values: [
'/var/lib/jenkins/workspace/Beats_beats_PR-22624/src/github.com/elastic/beats/libbeat',
],
},
{
category: 'process',
field: 'process.entity_id',
originalValue: ['Z59cIkAAIw8ZoK0H'],
values: ['Z59cIkAAIw8ZoK0H'],
},
{
category: 'host',
field: 'host.ip',
originalValue: ['10.224.1.237', 'fe80::4001:aff:fee0:1ed', '172.17.0.1'],
values: ['10.224.1.237', 'fe80::4001:aff:fee0:1ed', '172.17.0.1'],
},
{ category: 'process', field: 'process.name', originalValue: ['go'], values: ['go'] },
{
category: 'event',
field: 'event.action',
originalValue: ['process_started'],
values: ['process_started'],
},
{
category: 'agent',
field: 'agent.type',
originalValue: ['auditbeat'],
values: ['auditbeat'],
},
expect(getDataFromSourceHits(_source)).toEqual([
{
category: 'base',
field: '@timestamp',
originalValue: ['2020-11-17T14:48:08.922Z'],
values: ['2020-11-17T14:48:08.922Z'],
},
{ category: 'event', field: 'event.module', originalValue: ['system'], values: ['system'] },
{ category: 'event', field: 'event.type', originalValue: ['start'], values: ['start'] },
{
category: 'host',
field: 'host.name',
originalValue: ['beats-ci-immutable-ubuntu-1804-1605624279743236239'],
values: ['beats-ci-immutable-ubuntu-1804-1605624279743236239'],
values: ['2021-02-24T00:41:06.527Z'],
originalValue: ['2021-02-24T00:41:06.527Z'],
isObjectArray: false,
},
{
category: 'process',
field: 'process.hash.sha1',
originalValue: ['1eac22336a41e0660fb302add9d97daa2bcc7040'],
values: ['1eac22336a41e0660fb302add9d97daa2bcc7040'],
},
{ category: 'host', field: 'host.os.family', originalValue: ['debian'], values: ['debian'] },
{ category: 'event', field: 'event.kind', originalValue: ['event'], values: ['event'] },
{
category: 'host',
field: 'host.id',
originalValue: ['e59991e835905c65ed3e455b33e13bd6'],
values: ['e59991e835905c65ed3e455b33e13bd6'],
category: 'signal',
field: 'signal.status',
values: ['open'],
originalValue: ['open'],
isObjectArray: false,
},
{
category: 'event',
field: 'event.dataset',
originalValue: ['process'],
values: ['process'],
category: 'signal',
field: 'signal.rule.name',
values: ['Rawr'],
originalValue: ['Rawr'],
isObjectArray: false,
},
{
category: 'process',
field: 'process.executable',
originalValue: [
'/var/lib/jenkins/workspace/Beats_beats_PR-22624/.gvm/versions/go1.14.7.linux.amd64/bin/go',
],
category: 'threat',
field: 'threat.indicator',
values: [
'/var/lib/jenkins/workspace/Beats_beats_PR-22624/.gvm/versions/go1.14.7.linux.amd64/bin/go',
'{"provider":"yourself","type":"custom","first_seen":["2021-02-22T17:29:25.195Z"],"matched":{"atomic":"atom","field":"field","type":"type"}}',
'{"provider":"other_you","type":"custom","first_seen":"2021-02-22T17:29:25.195Z","matched":{"atomic":"atom","field":"field","type":"type"}}',
],
originalValue: [
'{"provider":"yourself","type":"custom","first_seen":["2021-02-22T17:29:25.195Z"],"matched":{"atomic":"atom","field":"field","type":"type"}}',
'{"provider":"other_you","type":"custom","first_seen":"2021-02-22T17:29:25.195Z","matched":{"atomic":"atom","field":"field","type":"type"}}',
],
isObjectArray: true,
},
]);
});
it('#getDataSafety', async () => {
const result = await getDataSafety(getDataFromFieldsHits, fields);
expect(result).toEqual(resultFields);
});
});

View file

@ -7,8 +7,12 @@
import { get, isEmpty, isNumber, isObject, isString } from 'lodash/fp';
import { EventSource, TimelineEventsDetailsItem } from '../../../../../../common/search_strategy';
import { toStringArray } from '../../../../helpers/to_array';
import {
EventHit,
EventSource,
TimelineEventsDetailsItem,
} from '../../../../../../common/search_strategy';
import { toObjectArrayOfStrings, toStringArray } from '../../../../helpers/to_array';
export const baseCategoryFields = ['@timestamp', 'labels', 'message', 'tags'];
@ -24,7 +28,10 @@ export const formatGeoLocation = (item: unknown[]) => {
const itemGeo = item.length > 0 ? (item[0] as { coordinates: number[] }) : null;
if (itemGeo != null && !isEmpty(itemGeo.coordinates)) {
try {
return toStringArray({ long: itemGeo.coordinates[0], lat: itemGeo.coordinates[1] });
return toStringArray({
lon: itemGeo.coordinates[0],
lat: itemGeo.coordinates[1],
});
} catch {
return toStringArray(item);
}
@ -46,13 +53,18 @@ export const getDataFromSourceHits = (
const field = path ? `${path}.${source}` : source;
const fieldCategory = getFieldCategory(field);
const objArrStr = toObjectArrayOfStrings(item);
const strArr = objArrStr.map(({ str }) => str);
const isObjectArray = objArrStr.some((o) => o.isObjectArray);
return [
...accumulator,
{
category: fieldCategory,
field,
values: toStringArray(item),
originalValue: toStringArray(item),
values: strArr,
originalValue: strArr,
isObjectArray,
} as TimelineEventsDetailsItem,
];
} else if (isObject(item)) {
@ -65,18 +77,81 @@ export const getDataFromSourceHits = (
}, []);
export const getDataFromFieldsHits = (
fields: Record<string, unknown[]>
fields: EventHit['fields'],
prependField?: string,
prependFieldCategory?: string
): TimelineEventsDetailsItem[] =>
Object.keys(fields).reduce<TimelineEventsDetailsItem[]>((accumulator, field) => {
const item: unknown[] = fields[field];
const fieldCategory = getFieldCategory(field);
return [
const fieldCategory =
prependFieldCategory != null ? prependFieldCategory : getFieldCategory(field);
if (isGeoField(field)) {
return [
...accumulator,
{
category: fieldCategory,
field,
values: formatGeoLocation(item),
originalValue: formatGeoLocation(item),
isObjectArray: true, // important for UI
},
];
}
const objArrStr = toObjectArrayOfStrings(item);
const strArr = objArrStr.map(({ str }) => str);
const isObjectArray = objArrStr.some((o) => o.isObjectArray);
const dotField = prependField ? `${prependField}.${field}` : field;
// return simple field value (non-object, non-array)
if (!isObjectArray) {
return [
...accumulator,
{
category: fieldCategory,
field: dotField,
values: strArr,
originalValue: strArr,
isObjectArray,
},
];
}
// format nested fields
const nestedFields = Array.isArray(item)
? item
.reduce((acc, i) => [...acc, getDataFromFieldsHits(i, dotField, fieldCategory)], [])
.flat()
: getDataFromFieldsHits(item, prependField, fieldCategory);
// combine duplicate fields
const flat: Record<string, TimelineEventsDetailsItem> = [
...accumulator,
{
category: fieldCategory,
field,
values: isGeoField(field) ? formatGeoLocation(item) : toStringArray(item),
originalValue: toStringArray(item),
} as TimelineEventsDetailsItem,
];
...nestedFields,
].reduce(
(acc, f) => ({
...acc,
// acc/flat is hashmap to determine if we already have the field or not without an array iteration
// its converted back to array in return with Object.values
...(acc[f.field] != null
? {
[f.field]: {
...f,
originalValue: acc[f.field].originalValue.includes(f.originalValue[0])
? acc[f.field].originalValue
: [...acc[f.field].originalValue, ...f.originalValue],
values: acc[f.field].values.includes(f.values[0])
? acc[f.field].values
: [...acc[f.field].values, ...f.values],
},
}
: { [f.field]: f }),
}),
{}
);
return Object.values(flat);
}, []);
export const getDataSafety = <A, T>(fn: (args: A) => T, args: A): Promise<T> =>
new Promise((resolve) => setTimeout(() => resolve(fn(args))));

View file

@ -5,7 +5,7 @@
* 2.0.
*/
import { cloneDeep, merge } from 'lodash/fp';
import { cloneDeep, merge, unionBy } from 'lodash/fp';
import { IEsSearchResponse } from '../../../../../../../../../src/plugins/data/common';
import {
@ -13,11 +13,13 @@ import {
TimelineEventsQueries,
TimelineEventsDetailsStrategyResponse,
TimelineEventsDetailsRequestOptions,
TimelineEventsDetailsItem,
EventSource,
} from '../../../../../../common/search_strategy';
import { inspectStringifyObject } from '../../../../../utils/build_query';
import { SecuritySolutionTimelineFactory } from '../../types';
import { buildTimelineDetailsQuery } from './query.events_details.dsl';
import { getDataFromSourceHits } from './helpers';
import { getDataFromFieldsHits, getDataFromSourceHits, getDataSafety } from './helpers';
export const timelineEventsDetails: SecuritySolutionTimelineFactory<TimelineEventsQueries.details> = {
buildDsl: (options: TimelineEventsDetailsRequestOptions) => {
@ -29,11 +31,10 @@ export const timelineEventsDetails: SecuritySolutionTimelineFactory<TimelineEven
response: IEsSearchResponse<EventHit>
): Promise<TimelineEventsDetailsStrategyResponse> => {
const { indexName, eventId, docValueFields = [] } = options;
const { _source, ...hitsData } = cloneDeep(response.rawResponse.hits.hits[0] ?? {});
const { _source, fields, ...hitsData } = cloneDeep(response.rawResponse.hits.hits[0] ?? {});
const inspect = {
dsl: [inspectStringifyObject(buildTimelineDetailsQuery(indexName, eventId, docValueFields))],
};
if (response.isRunning) {
return {
...response,
@ -41,12 +42,19 @@ export const timelineEventsDetails: SecuritySolutionTimelineFactory<TimelineEven
inspect,
};
}
const sourceData = await getDataSafety<EventSource, TimelineEventsDetailsItem[]>(
getDataFromSourceHits,
_source
);
const fieldsData = await getDataSafety<EventHit['fields'], TimelineEventsDetailsItem[]>(
getDataFromFieldsHits,
merge(fields, hitsData)
);
const sourceData = getDataFromSourceHits(merge(_source, hitsData));
const data = unionBy('field', fieldsData, sourceData);
return {
...response,
data: sourceData,
data,
inspect,
};
},

View file

@ -24,6 +24,7 @@ describe('buildTimelineDetailsQuery', () => {
Object {
"allowNoIndices": true,
"body": Object {
"_source": true,
"docvalue_fields": Array [
Object {
"field": "@timestamp",
@ -38,6 +39,9 @@ describe('buildTimelineDetailsQuery', () => {
"field": "agent.name",
},
],
"fields": Array [
"*",
],
"query": Object {
"terms": Object {
"_id": Array [

View file

@ -22,6 +22,8 @@ export const buildTimelineDetailsQuery = (
_id: [id],
},
},
fields: ['*'],
_source: true,
},
size: 1,
});

View file

@ -0,0 +1,329 @@
/*
* 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.
*/
export const eventHit = {
_index: 'auditbeat-7.8.0-2020.11.05-000003',
_id: 'tkCt1nUBaEgqnrVSZ8R_',
_score: 0,
_type: '',
fields: {
'event.category': ['process'],
'process.ppid': [3977],
'user.name': ['jenkins'],
'process.args': ['go', 'vet', './...'],
message: ['Process go (PID: 4313) by user jenkins STARTED'],
'process.pid': [4313],
'process.working_directory': [
'/var/lib/jenkins/workspace/Beats_beats_PR-22624/src/github.com/elastic/beats/libbeat',
],
'process.entity_id': ['Z59cIkAAIw8ZoK0H'],
'host.ip': ['10.224.1.237', 'fe80::4001:aff:fee0:1ed', '172.17.0.1'],
'process.name': ['go'],
'event.action': ['process_started'],
'agent.type': ['auditbeat'],
'@timestamp': ['2020-11-17T14:48:08.922Z'],
'event.module': ['system'],
'event.type': ['start'],
'host.name': ['beats-ci-immutable-ubuntu-1804-1605624279743236239'],
'process.hash.sha1': ['1eac22336a41e0660fb302add9d97daa2bcc7040'],
'host.os.family': ['debian'],
'event.kind': ['event'],
'host.id': ['e59991e835905c65ed3e455b33e13bd6'],
'event.dataset': ['process'],
'process.executable': [
'/var/lib/jenkins/workspace/Beats_beats_PR-22624/.gvm/versions/go1.14.7.linux.amd64/bin/go',
],
'source.geo.location': [{ coordinates: [118.7778, 32.0617], type: 'Point' }],
'threat.indicator': [
{
'matched.field': ['matched_field'],
first_seen: ['2021-02-22T17:29:25.195Z'],
provider: ['yourself'],
type: ['custom'],
'matched.atomic': ['matched_atomic'],
lazer: [
{
'great.field': ['grrrrr'],
},
{
'great.field': ['grrrrr_2'],
},
],
},
{
'matched.field': ['matched_field_2'],
first_seen: ['2021-02-22T17:29:25.195Z'],
provider: ['other_you'],
type: ['custom'],
'matched.atomic': ['matched_atomic_2'],
lazer: [
{
'great.field': [
{
wowoe: [
{
fooooo: ['grrrrr'],
},
],
astring: 'cool',
aNumber: 1,
anObject: {
neat: true,
},
},
],
},
],
},
],
},
_source: {},
sort: ['1605624488922', 'beats-ci-immutable-ubuntu-1804-1605624279743236239'],
aggregations: {},
};
export const eventDetailsFormattedFields = [
{
category: 'event',
field: 'event.category',
isObjectArray: false,
originalValue: ['process'],
values: ['process'],
},
{
category: 'process',
field: 'process.ppid',
isObjectArray: false,
originalValue: ['3977'],
values: ['3977'],
},
{
category: 'user',
field: 'user.name',
isObjectArray: false,
originalValue: ['jenkins'],
values: ['jenkins'],
},
{
category: 'process',
field: 'process.args',
isObjectArray: false,
originalValue: ['go', 'vet', './...'],
values: ['go', 'vet', './...'],
},
{
category: 'base',
field: 'message',
isObjectArray: false,
originalValue: ['Process go (PID: 4313) by user jenkins STARTED'],
values: ['Process go (PID: 4313) by user jenkins STARTED'],
},
{
category: 'process',
field: 'process.pid',
isObjectArray: false,
originalValue: ['4313'],
values: ['4313'],
},
{
category: 'process',
field: 'process.working_directory',
isObjectArray: false,
originalValue: [
'/var/lib/jenkins/workspace/Beats_beats_PR-22624/src/github.com/elastic/beats/libbeat',
],
values: [
'/var/lib/jenkins/workspace/Beats_beats_PR-22624/src/github.com/elastic/beats/libbeat',
],
},
{
category: 'process',
field: 'process.entity_id',
isObjectArray: false,
originalValue: ['Z59cIkAAIw8ZoK0H'],
values: ['Z59cIkAAIw8ZoK0H'],
},
{
category: 'host',
field: 'host.ip',
isObjectArray: false,
originalValue: ['10.224.1.237', 'fe80::4001:aff:fee0:1ed', '172.17.0.1'],
values: ['10.224.1.237', 'fe80::4001:aff:fee0:1ed', '172.17.0.1'],
},
{
category: 'process',
field: 'process.name',
isObjectArray: false,
originalValue: ['go'],
values: ['go'],
},
{
category: 'event',
field: 'event.action',
isObjectArray: false,
originalValue: ['process_started'],
values: ['process_started'],
},
{
category: 'agent',
field: 'agent.type',
isObjectArray: false,
originalValue: ['auditbeat'],
values: ['auditbeat'],
},
{
category: 'base',
field: '@timestamp',
isObjectArray: false,
originalValue: ['2020-11-17T14:48:08.922Z'],
values: ['2020-11-17T14:48:08.922Z'],
},
{
category: 'event',
field: 'event.module',
isObjectArray: false,
originalValue: ['system'],
values: ['system'],
},
{
category: 'event',
field: 'event.type',
isObjectArray: false,
originalValue: ['start'],
values: ['start'],
},
{
category: 'host',
field: 'host.name',
isObjectArray: false,
originalValue: ['beats-ci-immutable-ubuntu-1804-1605624279743236239'],
values: ['beats-ci-immutable-ubuntu-1804-1605624279743236239'],
},
{
category: 'process',
field: 'process.hash.sha1',
isObjectArray: false,
originalValue: ['1eac22336a41e0660fb302add9d97daa2bcc7040'],
values: ['1eac22336a41e0660fb302add9d97daa2bcc7040'],
},
{
category: 'host',
field: 'host.os.family',
isObjectArray: false,
originalValue: ['debian'],
values: ['debian'],
},
{
category: 'event',
field: 'event.kind',
isObjectArray: false,
originalValue: ['event'],
values: ['event'],
},
{
category: 'host',
field: 'host.id',
isObjectArray: false,
originalValue: ['e59991e835905c65ed3e455b33e13bd6'],
values: ['e59991e835905c65ed3e455b33e13bd6'],
},
{
category: 'event',
field: 'event.dataset',
isObjectArray: false,
originalValue: ['process'],
values: ['process'],
},
{
category: 'process',
field: 'process.executable',
isObjectArray: false,
originalValue: [
'/var/lib/jenkins/workspace/Beats_beats_PR-22624/.gvm/versions/go1.14.7.linux.amd64/bin/go',
],
values: [
'/var/lib/jenkins/workspace/Beats_beats_PR-22624/.gvm/versions/go1.14.7.linux.amd64/bin/go',
],
},
{
category: 'source',
field: 'source.geo.location',
isObjectArray: true,
originalValue: [`{"lon":118.7778,"lat":32.0617}`],
values: [`{"lon":118.7778,"lat":32.0617}`],
},
{
category: 'threat',
field: 'threat.indicator.matched.field',
values: ['matched_field', 'matched_field_2'],
originalValue: ['matched_field', 'matched_field_2'],
isObjectArray: false,
},
{
category: 'threat',
field: 'threat.indicator.first_seen',
values: ['2021-02-22T17:29:25.195Z'],
originalValue: ['2021-02-22T17:29:25.195Z'],
isObjectArray: false,
},
{
category: 'threat',
field: 'threat.indicator.provider',
values: ['yourself', 'other_you'],
originalValue: ['yourself', 'other_you'],
isObjectArray: false,
},
{
category: 'threat',
field: 'threat.indicator.type',
values: ['custom'],
originalValue: ['custom'],
isObjectArray: false,
},
{
category: 'threat',
field: 'threat.indicator.matched.atomic',
values: ['matched_atomic', 'matched_atomic_2'],
originalValue: ['matched_atomic', 'matched_atomic_2'],
isObjectArray: false,
},
{
category: 'threat',
field: 'threat.indicator.lazer.great.field',
values: ['grrrrr', 'grrrrr_2'],
originalValue: ['grrrrr', 'grrrrr_2'],
isObjectArray: false,
},
{
category: 'threat',
field: 'threat.indicator.lazer.great.field.wowoe.fooooo',
values: ['grrrrr'],
originalValue: ['grrrrr'],
isObjectArray: false,
},
{
category: 'threat',
field: 'threat.indicator.lazer.great.field.astring',
values: ['cool'],
originalValue: ['cool'],
isObjectArray: false,
},
{
category: 'threat',
field: 'threat.indicator.lazer.great.field.aNumber',
values: ['1'],
originalValue: ['1'],
isObjectArray: false,
},
{
category: 'threat',
field: 'threat.indicator.lazer.great.field.neat',
values: ['true'],
originalValue: ['true'],
isObjectArray: false,
},
];

View file

@ -20,96 +20,133 @@ const EXPECTED_DATA = [
field: '@timestamp',
values: ['2019-02-10T02:39:44.107Z'],
originalValue: ['2019-02-10T02:39:44.107Z'],
isObjectArray: false,
},
{
category: '@version',
field: '@version',
values: ['1'],
originalValue: ['1'],
isObjectArray: false,
},
{
category: '_id',
field: '_id',
values: ['QRhG1WgBqd-n62SwZYDT'],
originalValue: ['QRhG1WgBqd-n62SwZYDT'],
isObjectArray: false,
},
{
category: '_index',
field: '_index',
values: ['filebeat-7.0.0-iot-2019.06'],
originalValue: ['filebeat-7.0.0-iot-2019.06'],
isObjectArray: false,
},
{
category: '_score',
field: '_score',
values: ['1'],
originalValue: ['1'],
isObjectArray: false,
},
{
category: 'agent',
field: 'agent.ephemeral_id',
values: ['909cd6a1-527d-41a5-9585-a7fb5386f851'],
originalValue: ['909cd6a1-527d-41a5-9585-a7fb5386f851'],
isObjectArray: false,
},
{
category: 'agent',
field: 'agent.hostname',
values: ['raspberrypi'],
originalValue: ['raspberrypi'],
isObjectArray: false,
},
{
category: 'agent',
field: 'agent.id',
values: ['4d3ea604-27e5-4ec7-ab64-44f82285d776'],
originalValue: ['4d3ea604-27e5-4ec7-ab64-44f82285d776'],
isObjectArray: false,
},
{
category: 'agent',
field: 'agent.type',
values: ['filebeat'],
originalValue: ['filebeat'],
isObjectArray: false,
},
{
category: 'agent',
field: 'agent.version',
values: ['7.0.0'],
originalValue: ['7.0.0'],
isObjectArray: false,
},
{
category: 'destination',
field: 'destination.domain',
values: ['s3-iad-2.cf.dash.row.aiv-cdn.net'],
originalValue: ['s3-iad-2.cf.dash.row.aiv-cdn.net'],
isObjectArray: false,
},
{
category: 'destination',
field: 'destination.ip',
values: ['10.100.7.196'],
originalValue: ['10.100.7.196'],
isObjectArray: false,
},
{
category: 'destination',
field: 'destination.port',
values: ['40684'],
originalValue: ['40684'],
isObjectArray: false,
},
{
category: 'ecs',
field: 'ecs.version',
values: ['1.0.0-beta2'],
originalValue: ['1.0.0-beta2'],
isObjectArray: false,
},
{
category: 'event',
field: 'event.dataset',
values: ['suricata.eve'],
originalValue: ['suricata.eve'],
isObjectArray: false,
},
{
category: 'event',
field: 'event.end',
values: ['2019-02-10T02:39:44.107Z'],
originalValue: ['2019-02-10T02:39:44.107Z'],
isObjectArray: false,
},
{
category: 'event',
field: 'event.kind',
values: ['event'],
originalValue: ['event'],
isObjectArray: false,
},
{
category: 'event',
field: 'event.module',
values: ['suricata'],
originalValue: ['suricata'],
isObjectArray: false,
},
{
category: 'event',
field: 'event.type',
values: ['fileinfo'],
originalValue: ['fileinfo'],
isObjectArray: false,
},
{
category: 'file',
@ -120,270 +157,484 @@ const EXPECTED_DATA = [
originalValue: [
'/dm/2$XTMWANo0Q2RZKlH-95UoAahZrOg~/0a9a/bf72/e1da/4c20-919e-0cbabcf7bfe8/75f50c57-d25f-4e97-9e37-01b9f5caa293_audio_13.mp4',
],
isObjectArray: false,
},
{
category: 'file',
field: 'file.size',
values: ['48277'],
originalValue: ['48277'],
isObjectArray: false,
},
{
category: 'fileset',
field: 'fileset.name',
values: ['eve'],
originalValue: ['eve'],
isObjectArray: false,
},
{
category: 'flow',
field: 'flow.locality',
values: ['public'],
originalValue: ['public'],
isObjectArray: false,
},
{
category: 'host',
field: 'host.architecture',
values: ['armv7l'],
originalValue: ['armv7l'],
isObjectArray: false,
},
{
category: 'host',
field: 'host.containerized',
values: ['false'],
originalValue: ['false'],
isObjectArray: false,
},
{
category: 'host',
field: 'host.hostname',
values: ['raspberrypi'],
originalValue: ['raspberrypi'],
isObjectArray: false,
},
{
category: 'host',
field: 'host.id',
values: ['b19a781f683541a7a25ee345133aa399'],
originalValue: ['b19a781f683541a7a25ee345133aa399'],
isObjectArray: false,
},
{
category: 'host',
field: 'host.name',
values: ['raspberrypi'],
originalValue: ['raspberrypi'],
isObjectArray: false,
},
{
category: 'host',
field: 'host.os.codename',
values: ['stretch'],
originalValue: ['stretch'],
isObjectArray: false,
},
{
category: 'host',
field: 'host.os.family',
values: [''],
originalValue: [''],
isObjectArray: false,
},
{
category: 'host',
field: 'host.os.kernel',
values: ['4.14.50-v7+'],
originalValue: ['4.14.50-v7+'],
isObjectArray: false,
},
{
category: 'host',
field: 'host.os.name',
values: ['Raspbian GNU/Linux'],
originalValue: ['Raspbian GNU/Linux'],
isObjectArray: false,
},
{
category: 'host',
field: 'host.os.platform',
values: ['raspbian'],
originalValue: ['raspbian'],
isObjectArray: false,
},
{
category: 'host',
field: 'host.os.version',
values: ['9 (stretch)'],
originalValue: ['9 (stretch)'],
isObjectArray: false,
},
{
category: 'http',
field: 'http.request.method',
values: ['get'],
originalValue: ['get'],
isObjectArray: false,
},
{
category: 'http',
field: 'http.response.body.bytes',
values: ['48277'],
originalValue: ['48277'],
isObjectArray: false,
},
{
category: 'http',
field: 'http.response.status_code',
values: ['206'],
originalValue: ['206'],
isObjectArray: false,
},
{
category: 'input',
field: 'input.type',
values: ['log'],
originalValue: ['log'],
isObjectArray: false,
},
{
category: 'base',
field: 'labels.pipeline',
values: ['filebeat-7.0.0-suricata-eve-pipeline'],
originalValue: ['filebeat-7.0.0-suricata-eve-pipeline'],
isObjectArray: false,
},
{
category: 'log',
field: 'log.file.path',
values: ['/var/log/suricata/eve.json'],
originalValue: ['/var/log/suricata/eve.json'],
isObjectArray: false,
},
{
category: 'log',
field: 'log.offset',
values: ['1856288115'],
originalValue: ['1856288115'],
isObjectArray: false,
},
{
category: 'network',
field: 'network.name',
values: ['iot'],
originalValue: ['iot'],
isObjectArray: false,
},
{
category: 'network',
field: 'network.protocol',
values: ['http'],
originalValue: ['http'],
isObjectArray: false,
},
{
category: 'network',
field: 'network.transport',
values: ['tcp'],
originalValue: ['tcp'],
isObjectArray: false,
},
{
category: 'service',
field: 'service.type',
values: ['suricata'],
originalValue: ['suricata'],
isObjectArray: false,
},
{
category: 'source',
field: 'source.as.num',
values: ['16509'],
originalValue: ['16509'],
isObjectArray: false,
},
{
category: 'source',
field: 'source.as.org',
values: ['Amazon.com, Inc.'],
originalValue: ['Amazon.com, Inc.'],
isObjectArray: false,
},
{
category: 'source',
field: 'source.domain',
values: ['server-54-239-219-210.jfk51.r.cloudfront.net'],
originalValue: ['server-54-239-219-210.jfk51.r.cloudfront.net'],
isObjectArray: false,
},
{
category: 'source',
field: 'source.geo.city_name',
values: ['Seattle'],
originalValue: ['Seattle'],
isObjectArray: false,
},
{
category: 'source',
field: 'source.geo.continent_name',
values: ['North America'],
originalValue: ['North America'],
isObjectArray: false,
},
{
category: 'source',
field: 'source.geo.country_iso_code',
values: ['US'],
originalValue: ['US'],
isObjectArray: false,
},
{
category: 'source',
field: 'source.geo.location',
values: ['{"lon":-122.3341,"lat":47.6103}'],
originalValue: ['{"lon":-122.3341,"lat":47.6103}'],
isObjectArray: true,
},
{
category: 'source',
field: 'source.geo.location.lat',
values: ['47.6103'],
originalValue: ['47.6103'],
isObjectArray: false,
},
{
category: 'source',
field: 'source.geo.location.lon',
values: ['-122.3341'],
originalValue: ['-122.3341'],
isObjectArray: false,
},
{
category: 'source',
field: 'source.geo.region_iso_code',
values: ['US-WA'],
originalValue: ['US-WA'],
isObjectArray: false,
},
{
category: 'source',
field: 'source.geo.region_name',
values: ['Washington'],
originalValue: ['Washington'],
isObjectArray: false,
},
{
category: 'source',
field: 'source.ip',
values: ['54.239.219.210'],
originalValue: ['54.239.219.210'],
isObjectArray: false,
},
{
category: 'source',
field: 'source.port',
values: ['80'],
originalValue: ['80'],
isObjectArray: false,
},
{
category: 'suricata',
field: 'suricata.eve.app_proto',
values: ['http'],
originalValue: ['http'],
isObjectArray: false,
},
{
category: 'suricata',
field: 'suricata.eve.dest_ip',
values: ['10.100.7.196'],
originalValue: ['10.100.7.196'],
isObjectArray: false,
},
{
category: 'suricata',
field: 'suricata.eve.dest_port',
values: ['40684'],
originalValue: ['40684'],
isObjectArray: false,
},
{
category: 'suricata',
field: 'suricata.eve.fileinfo.filename',
values: [
'/dm/2$XTMWANo0Q2RZKlH-95UoAahZrOg~/0a9a/bf72/e1da/4c20-919e-0cbabcf7bfe8/75f50c57-d25f-4e97-9e37-01b9f5caa293_audio_13.mp4',
],
originalValue: [
'/dm/2$XTMWANo0Q2RZKlH-95UoAahZrOg~/0a9a/bf72/e1da/4c20-919e-0cbabcf7bfe8/75f50c57-d25f-4e97-9e37-01b9f5caa293_audio_13.mp4',
],
isObjectArray: false,
},
{
category: 'suricata',
field: 'suricata.eve.fileinfo.size',
values: ['48277'],
originalValue: ['48277'],
isObjectArray: false,
},
{
category: 'suricata',
field: 'suricata.eve.fileinfo.state',
values: ['CLOSED'],
originalValue: ['CLOSED'],
isObjectArray: false,
},
{
category: 'suricata',
field: 'suricata.eve.fileinfo.stored',
values: ['false'],
originalValue: ['false'],
isObjectArray: false,
},
{
category: 'suricata',
field: 'suricata.eve.fileinfo.tx_id',
values: ['301'],
originalValue: ['301'],
isObjectArray: false,
},
{
category: 'suricata',
field: 'suricata.eve.flow_id',
values: ['196625917175466'],
originalValue: ['196625917175466'],
isObjectArray: false,
},
{
category: 'suricata',
field: 'suricata.eve.http.hostname',
values: ['s3-iad-2.cf.dash.row.aiv-cdn.net'],
originalValue: ['s3-iad-2.cf.dash.row.aiv-cdn.net'],
isObjectArray: false,
},
{
category: 'suricata',
field: 'suricata.eve.http.http_content_type',
values: ['video/mp4'],
originalValue: ['video/mp4'],
isObjectArray: false,
},
{
category: 'suricata',
field: 'suricata.eve.http.http_method',
values: ['get'],
originalValue: ['get'],
isObjectArray: false,
},
{
category: 'suricata',
field: 'suricata.eve.http.length',
values: ['48277'],
originalValue: ['48277'],
isObjectArray: false,
},
{
category: 'suricata',
field: 'suricata.eve.http.protocol',
values: ['HTTP/1.1'],
originalValue: ['HTTP/1.1'],
isObjectArray: false,
},
{
category: 'suricata',
field: 'suricata.eve.http.status',
values: ['206'],
originalValue: ['206'],
isObjectArray: false,
},
{
category: 'suricata',
field: 'suricata.eve.http.url',
values: [
'/dm/2$XTMWANo0Q2RZKlH-95UoAahZrOg~/0a9a/bf72/e1da/4c20-919e-0cbabcf7bfe8/75f50c57-d25f-4e97-9e37-01b9f5caa293_audio_13.mp4',
],
originalValue: [
'/dm/2$XTMWANo0Q2RZKlH-95UoAahZrOg~/0a9a/bf72/e1da/4c20-919e-0cbabcf7bfe8/75f50c57-d25f-4e97-9e37-01b9f5caa293_audio_13.mp4',
],
isObjectArray: false,
},
{
category: 'suricata',
field: 'suricata.eve.in_iface',
values: ['eth0'],
originalValue: ['eth0'],
isObjectArray: false,
},
{
category: 'suricata',
field: 'suricata.eve.proto',
values: ['tcp'],
originalValue: ['tcp'],
isObjectArray: false,
},
{
category: 'suricata',
field: 'suricata.eve.src_ip',
values: ['54.239.219.210'],
originalValue: ['54.239.219.210'],
isObjectArray: false,
},
{
category: 'suricata',
field: 'suricata.eve.src_port',
values: ['80'],
originalValue: ['80'],
isObjectArray: false,
},
{
category: 'suricata',
field: 'suricata.eve.timestamp',
values: ['2019-02-10T02:39:44.107Z'],
originalValue: ['2019-02-10T02:39:44.107Z'],
isObjectArray: false,
},
{
category: 'base',
field: 'tags',
values: ['suricata'],
originalValue: ['suricata'],
isObjectArray: false,
},
{
category: 'traefik',
field: 'traefik.access.geoip.city_name',
values: ['Seattle'],
originalValue: ['Seattle'],
isObjectArray: false,
},
{
category: 'traefik',
field: 'traefik.access.geoip.continent_name',
values: ['North America'],
originalValue: ['North America'],
isObjectArray: false,
},
{
category: 'traefik',
field: 'traefik.access.geoip.country_iso_code',
values: ['US'],
originalValue: ['US'],
isObjectArray: false,
},
{
category: 'traefik',
field: 'traefik.access.geoip.location',
values: ['{"lon":-122.3341,"lat":47.6103}'],
originalValue: ['{"lon":-122.3341,"lat":47.6103}'],
isObjectArray: true,
},
{
category: 'traefik',
field: 'traefik.access.geoip.region_iso_code',
values: ['US-WA'],
originalValue: ['US-WA'],
isObjectArray: false,
},
{
category: 'traefik',
field: 'traefik.access.geoip.region_name',
values: ['Washington'],
originalValue: ['Washington'],
isObjectArray: false,
},
{
category: 'url',
field: 'url.domain',
values: ['s3-iad-2.cf.dash.row.aiv-cdn.net'],
originalValue: ['s3-iad-2.cf.dash.row.aiv-cdn.net'],
isObjectArray: false,
},
{
category: 'url',
@ -394,6 +645,7 @@ const EXPECTED_DATA = [
originalValue: [
'/dm/2$XTMWANo0Q2RZKlH-95UoAahZrOg~/0a9a/bf72/e1da/4c20-919e-0cbabcf7bfe8/75f50c57-d25f-4e97-9e37-01b9f5caa293_audio_13.mp4',
],
isObjectArray: false,
},
{
category: 'url',
@ -404,27 +656,9 @@ const EXPECTED_DATA = [
originalValue: [
'/dm/2$XTMWANo0Q2RZKlH-95UoAahZrOg~/0a9a/bf72/e1da/4c20-919e-0cbabcf7bfe8/75f50c57-d25f-4e97-9e37-01b9f5caa293_audio_13.mp4',
],
},
{
category: '_index',
field: '_index',
values: ['filebeat-7.0.0-iot-2019.06'],
originalValue: ['filebeat-7.0.0-iot-2019.06'],
},
{
category: '_id',
field: '_id',
values: ['QRhG1WgBqd-n62SwZYDT'],
originalValue: ['QRhG1WgBqd-n62SwZYDT'],
},
{
category: '_score',
field: '_score',
values: ['1'],
originalValue: ['1'],
isObjectArray: false,
},
];
const EXPECTED_KPI_COUNTS = {
destinationIpCount: 154,
hostCount: 1,
@ -456,7 +690,7 @@ export default function ({ getService }: FtrProviderContext) {
wait_for_completion_timeout: '10s',
})
.expect(200);
expect(sortBy(detailsData, 'name')).to.eql(sortBy(EXPECTED_DATA, 'name'));
expect(sortBy(detailsData, 'field')).to.eql(sortBy(EXPECTED_DATA, 'field'));
});
it('Make sure that we get kpi data', async () => {