[SIEM] Timeline NOT working with unauthorized (#41767) (#41884)

* Allow error to show in the application

* Allow unauthorized user to use timeline api with redux storage

* add callout to timeline to show + fix event details

* Build fixes

* fix pinned event

* review I

* fix details timeline test on api integration
This commit is contained in:
Xavier Mouligneau 2019-07-24 16:10:41 +02:00 committed by GitHub
parent 91915690b6
commit ffd4de9b9f
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
39 changed files with 1016 additions and 1599 deletions

View file

@ -43,8 +43,8 @@
# the username and password that the Kibana server uses to perform maintenance on the Kibana
# index at startup. Your Kibana users still need to authenticate with Elasticsearch, which
# is proxied through the Kibana server.
#elasticsearch.username: "user"
#elasticsearch.password: "pass"
elasticsearch.username: "elastic"
elasticsearch.password: "yDaYCXL6KYgNligMpSwd"
# Enables SSL and paths to the PEM-format SSL certificate and SSL key files, respectively.
# These settings enable SSL for outgoing requests from the Kibana server to the browser.

View file

@ -391,221 +391,141 @@ exports[`EventDetails rendering should match snapshot 1`] = `
data={
Array [
Object {
"category": "_id",
"description": "Each document has an _id that uniquely identifies it",
"example": "Y-6TfmcB0WOhS6qyMv3s",
"field": "_id",
"originalValue": "pEMaMmkBUV60JmNWmWVi",
"type": "keyword",
"values": Array [
"pEMaMmkBUV60JmNWmWVi",
],
},
Object {
"category": "_index",
"description": "An index is like a database in a relational database. It has a mapping which defines multiple types. An index is a logical namespace which maps to one or more primary shards and can have zero or more replica shards.",
"example": "auditbeat-8.0.0-2019.02.19-000001",
"field": "_index",
"originalValue": "filebeat-8.0.0-2019.02.19-000001",
"type": "keyword",
"values": Array [
"filebeat-8.0.0-2019.02.19-000001",
],
},
Object {
"category": "_type",
"description": null,
"example": null,
"field": "_type",
"originalValue": "_doc",
"type": "keyword",
"values": Array [
"_doc",
],
},
Object {
"category": "_score",
"description": null,
"example": null,
"field": "_score",
"originalValue": 1,
"type": "long",
"values": Array [
"1",
],
},
Object {
"category": "@timestamp",
"description": "Date/time when the event originated.For log events this is the date/time when the event was generated, and not when it was read.Required field for all events.",
"example": "2016-05-23T08:05:34.853Z",
"field": "@timestamp",
"originalValue": "2019-02-28T16:50:54.621Z",
"type": "date",
"values": Array [
"2019-02-28T16:50:54.621Z",
],
},
Object {
"category": "agent",
"description": "Ephemeral identifier of this agent (if one exists).This id normally changes across restarts, but \`agent.id\` does not.",
"example": "8a4f500f",
"field": "agent.ephemeral_id",
"originalValue": "9d391ef2-a734-4787-8891-67031178c641",
"type": "keyword",
"values": Array [
"9d391ef2-a734-4787-8891-67031178c641",
],
},
Object {
"category": "agent",
"description": null,
"example": null,
"field": "agent.hostname",
"originalValue": "siem-kibana",
"type": "keyword",
"values": Array [
"siem-kibana",
],
},
Object {
"category": "agent",
"description": "Unique identifier of this agent (if one exists).Example: For Beats this would be beat.id.",
"example": "8a4f500d",
"field": "agent.id",
"originalValue": "5de03d5f-52f3-482e-91d4-853c7de073c3",
"type": "keyword",
"values": Array [
"5de03d5f-52f3-482e-91d4-853c7de073c3",
],
},
Object {
"category": "agent",
"description": "Type of the agent.The agent type stays always the same and should be given by the agent used. In case of Filebeat the agent would always be Filebeat also if two Filebeat instances are run on the same machine.",
"example": "filebeat",
"field": "agent.type",
"originalValue": "filebeat",
"type": "keyword",
"values": Array [
"filebeat",
],
},
Object {
"category": "agent",
"description": "Version of the agent.",
"example": "6.0.0-rc2",
"field": "agent.version",
"originalValue": "8.0.0",
"type": "keyword",
"values": Array [
"8.0.0",
],
},
Object {
"category": "cloud",
"description": "Availability zone in which this host is running.",
"example": "us-east-1c",
"field": "cloud.availability_zone",
"originalValue": "projects/189716325846/zones/us-east1-b",
"type": "keyword",
"values": Array [
"projects/189716325846/zones/us-east1-b",
],
},
Object {
"category": "cloud",
"description": "Instance ID of the host machine.",
"example": "i-1234567890abcdef0",
"field": "cloud.instance.id",
"originalValue": "5412578377715150143",
"type": "keyword",
"values": Array [
"5412578377715150143",
],
},
Object {
"category": "cloud",
"description": "Instance name of the host machine.",
"example": null,
"field": "cloud.instance.name",
"originalValue": "siem-kibana",
"type": "keyword",
"values": Array [
"siem-kibana",
],
},
Object {
"category": "cloud",
"description": "Machine type of the host machine.",
"example": "t2.medium",
"field": "cloud.machine.type",
"originalValue": "projects/189716325846/machineTypes/n1-standard-1",
"type": "keyword",
"values": Array [
"projects/189716325846/machineTypes/n1-standard-1",
],
},
Object {
"category": "cloud",
"description": null,
"example": null,
"field": "cloud.project.id",
"originalValue": "elastic-beats",
"type": "keyword",
"values": Array [
"elastic-beats",
],
},
Object {
"category": "cloud",
"description": "Name of the cloud provider. Example values are ec2, gce, or digitalocean.",
"example": "ec2",
"field": "cloud.provider",
"originalValue": "gce",
"type": "keyword",
"values": Array [
"gce",
],
},
Object {
"category": "destination",
"description": "Bytes sent from the destination to the source.",
"example": "184",
"field": "destination.bytes",
"originalValue": 584,
"type": "long",
"values": Array [
"584",
],
},
Object {
"category": "destination",
"description": "IP address of the destination.Can be one or multiple IPv4 or IPv6 addresses.",
"example": null,
"field": "destination.ip",
"originalValue": "10.47.8.200",
"type": "ip",
"values": Array [
"10.47.8.200",
],
},
Object {
"category": "destination",
"description": "Packets sent from the destination to the source.",
"example": "12",
"field": "destination.packets",
"originalValue": 4,
"type": "long",
"values": Array [
"4",
],
},
Object {
"category": "destination",
"description": "Port of the destination.",
"example": null,
"field": "destination.port",
"originalValue": 902,
"type": "long",
"values": Array [
"902",
],

View file

@ -5,221 +5,141 @@ exports[`JSON View rendering should match snapshot 1`] = `
data={
Array [
Object {
"category": "_id",
"description": "Each document has an _id that uniquely identifies it",
"example": "Y-6TfmcB0WOhS6qyMv3s",
"field": "_id",
"originalValue": "pEMaMmkBUV60JmNWmWVi",
"type": "keyword",
"values": Array [
"pEMaMmkBUV60JmNWmWVi",
],
},
Object {
"category": "_index",
"description": "An index is like a database in a relational database. It has a mapping which defines multiple types. An index is a logical namespace which maps to one or more primary shards and can have zero or more replica shards.",
"example": "auditbeat-8.0.0-2019.02.19-000001",
"field": "_index",
"originalValue": "filebeat-8.0.0-2019.02.19-000001",
"type": "keyword",
"values": Array [
"filebeat-8.0.0-2019.02.19-000001",
],
},
Object {
"category": "_type",
"description": null,
"example": null,
"field": "_type",
"originalValue": "_doc",
"type": "keyword",
"values": Array [
"_doc",
],
},
Object {
"category": "_score",
"description": null,
"example": null,
"field": "_score",
"originalValue": 1,
"type": "long",
"values": Array [
"1",
],
},
Object {
"category": "@timestamp",
"description": "Date/time when the event originated.For log events this is the date/time when the event was generated, and not when it was read.Required field for all events.",
"example": "2016-05-23T08:05:34.853Z",
"field": "@timestamp",
"originalValue": "2019-02-28T16:50:54.621Z",
"type": "date",
"values": Array [
"2019-02-28T16:50:54.621Z",
],
},
Object {
"category": "agent",
"description": "Ephemeral identifier of this agent (if one exists).This id normally changes across restarts, but \`agent.id\` does not.",
"example": "8a4f500f",
"field": "agent.ephemeral_id",
"originalValue": "9d391ef2-a734-4787-8891-67031178c641",
"type": "keyword",
"values": Array [
"9d391ef2-a734-4787-8891-67031178c641",
],
},
Object {
"category": "agent",
"description": null,
"example": null,
"field": "agent.hostname",
"originalValue": "siem-kibana",
"type": "keyword",
"values": Array [
"siem-kibana",
],
},
Object {
"category": "agent",
"description": "Unique identifier of this agent (if one exists).Example: For Beats this would be beat.id.",
"example": "8a4f500d",
"field": "agent.id",
"originalValue": "5de03d5f-52f3-482e-91d4-853c7de073c3",
"type": "keyword",
"values": Array [
"5de03d5f-52f3-482e-91d4-853c7de073c3",
],
},
Object {
"category": "agent",
"description": "Type of the agent.The agent type stays always the same and should be given by the agent used. In case of Filebeat the agent would always be Filebeat also if two Filebeat instances are run on the same machine.",
"example": "filebeat",
"field": "agent.type",
"originalValue": "filebeat",
"type": "keyword",
"values": Array [
"filebeat",
],
},
Object {
"category": "agent",
"description": "Version of the agent.",
"example": "6.0.0-rc2",
"field": "agent.version",
"originalValue": "8.0.0",
"type": "keyword",
"values": Array [
"8.0.0",
],
},
Object {
"category": "cloud",
"description": "Availability zone in which this host is running.",
"example": "us-east-1c",
"field": "cloud.availability_zone",
"originalValue": "projects/189716325846/zones/us-east1-b",
"type": "keyword",
"values": Array [
"projects/189716325846/zones/us-east1-b",
],
},
Object {
"category": "cloud",
"description": "Instance ID of the host machine.",
"example": "i-1234567890abcdef0",
"field": "cloud.instance.id",
"originalValue": "5412578377715150143",
"type": "keyword",
"values": Array [
"5412578377715150143",
],
},
Object {
"category": "cloud",
"description": "Instance name of the host machine.",
"example": null,
"field": "cloud.instance.name",
"originalValue": "siem-kibana",
"type": "keyword",
"values": Array [
"siem-kibana",
],
},
Object {
"category": "cloud",
"description": "Machine type of the host machine.",
"example": "t2.medium",
"field": "cloud.machine.type",
"originalValue": "projects/189716325846/machineTypes/n1-standard-1",
"type": "keyword",
"values": Array [
"projects/189716325846/machineTypes/n1-standard-1",
],
},
Object {
"category": "cloud",
"description": null,
"example": null,
"field": "cloud.project.id",
"originalValue": "elastic-beats",
"type": "keyword",
"values": Array [
"elastic-beats",
],
},
Object {
"category": "cloud",
"description": "Name of the cloud provider. Example values are ec2, gce, or digitalocean.",
"example": "ec2",
"field": "cloud.provider",
"originalValue": "gce",
"type": "keyword",
"values": Array [
"gce",
],
},
Object {
"category": "destination",
"description": "Bytes sent from the destination to the source.",
"example": "184",
"field": "destination.bytes",
"originalValue": 584,
"type": "long",
"values": Array [
"584",
],
},
Object {
"category": "destination",
"description": "IP address of the destination.Can be one or multiple IPv4 or IPv6 addresses.",
"example": null,
"field": "destination.ip",
"originalValue": "10.47.8.200",
"type": "ip",
"values": Array [
"10.47.8.200",
],
},
Object {
"category": "destination",
"description": "Packets sent from the destination to the source.",
"example": "12",
"field": "destination.packets",
"originalValue": 4,
"type": "long",
"values": Array [
"4",
],
},
Object {
"category": "destination",
"description": "Port of the destination.",
"example": null,
"field": "destination.port",
"originalValue": 902,
"type": "long",
"values": Array [
"902",
],

View file

@ -12,7 +12,7 @@ import styled from 'styled-components';
import { BrowserFields } from '../../containers/source';
import { DragEffects } from '../drag_and_drop/draggable_wrapper';
import { DefaultDraggable } from '../draggables';
import { DetailItem, ToStringArray } from '../../graphql/types';
import { ToStringArray } from '../../graphql/types';
import { DroppableWrapper } from '../drag_and_drop/droppable_wrapper';
import { DraggableFieldBadge } from '../draggables/field_badge';
import { FormattedFieldValue } from '../timeline/body/renderers/formatted_field';
@ -28,6 +28,7 @@ import * as i18n from './translations';
import { OverflowField } from '../tables/helpers';
import { DATE_FIELD_TYPE, MESSAGE_FIELD_NAME } from '../timeline/body/renderers/constants';
import { EVENT_DURATION_FIELD_NAME } from '../duration';
import { EventFieldsData } from './types';
const HoverActionsContainer = styled(EuiPanel)`
align-items: center;
@ -76,7 +77,7 @@ export const getColumns = ({
name: i18n.FIELD,
sortable: true,
truncateText: false,
render: (field: string, data: DetailItem) => (
render: (field: string, data: EventFieldsData) => (
<DroppableWrapper
droppableId={getDroppableId(
`event-details-${eventId}-${data.category}-${field}-${timelineId}`
@ -125,7 +126,7 @@ export const getColumns = ({
name: i18n.VALUE,
sortable: true,
truncateText: false,
render: (values: ToStringArray | null | undefined, data: DetailItem) => (
render: (values: ToStringArray | null | undefined, data: EventFieldsData) => (
<EuiFlexGroup direction="column" alignItems="flexStart" component="span" gutterSize="none">
{values != null &&
values.map((value, i) => (
@ -176,7 +177,7 @@ export const getColumns = ({
{
field: 'description',
name: i18n.DESCRIPTION,
render: (description: string | null | undefined, data: DetailItem) => (
render: (description: string | null | undefined, data: EventFieldsData) => (
<SelectableText>{`${description || ''} ${getExampleText(data.example)}`}</SelectableText>
),
sortable: true,
@ -185,9 +186,9 @@ export const getColumns = ({
},
{
field: 'valuesConcatenated',
render: () => null,
sortable: false,
truncateText: true,
render: () => null,
width: '1px',
},
];

View file

@ -101,7 +101,7 @@ describe('EventFieldsBrowser', () => {
.find('[data-test-subj="field-name"]')
.at(0)
.text()
).toEqual('_id');
).toEqual('@timestamp');
});
});
@ -124,7 +124,7 @@ describe('EventFieldsBrowser', () => {
.find('[data-test-subj="draggable-content"]')
.at(0)
.text()
).toEqual('pEMaMmkBUV60JmNWmWVi');
).toEqual('Feb 28, 2019 @ 16:50:54.621');
});
});
@ -149,7 +149,9 @@ describe('EventFieldsBrowser', () => {
.find('.euiTableRowCell')
.at(3)
.text()
).toContain('Each document has an _id that uniquely identifies it');
).toContain(
'DescriptionDate/time when the event originated. For log events this is the date/time when the event was generated, and not when it was read. Required field for all events. Example: 2016-05-23T08:05:34.853Z'
);
});
});
});

View file

@ -11,7 +11,8 @@ import {
import * as React from 'react';
import { pure } from 'recompose';
import { BrowserFields } from '../../containers/source';
import { sortBy } from 'lodash';
import { BrowserFields, getAllFieldsByName } from '../../containers/source';
import { DetailItem } from '../../graphql/types';
import { OnUpdateColumns } from '../timeline/events';
@ -29,22 +30,28 @@ interface Props {
/** Renders a table view or JSON view of the `ECS` `data` */
export const EventFieldsBrowser = pure<Props>(
({ browserFields, data, eventId, isLoading, onUpdateColumns, timelineId }) => (
<EuiInMemoryTable
items={data.map(item => ({
...item,
valuesConcatenated: item.values != null ? item.values.join() : '',
}))}
columns={getColumns({
browserFields,
eventId,
isLoading,
onUpdateColumns,
timelineId,
})}
pagination={false}
search={search}
sorting={true}
/>
)
({ browserFields, data, eventId, isLoading, onUpdateColumns, timelineId }) => {
const fieldsByName = getAllFieldsByName(browserFields);
return (
<EuiInMemoryTable
items={sortBy(data, ['field']).map(item => {
return {
...item,
...fieldsByName[item.field],
valuesConcatenated: item.values != null ? item.values.join() : '',
};
})}
columns={getColumns({
browserFields,
eventId,
isLoading,
onUpdateColumns,
timelineId,
})}
pagination={false}
search={search}
sorting={true}
/>
);
}
);

View file

@ -7,13 +7,17 @@
import { mockDetailItemData } from '../../mock/mock_detail_item';
import { getExampleText, getIconFromType } from './helpers';
import { mockBrowserFields } from '../../containers/source/mock';
const aField = mockDetailItemData[0];
const aField = {
...mockDetailItemData[4],
...mockBrowserFields.base.fields!['@timestamp'],
};
describe('helpers', () => {
describe('getExampleText', () => {
test('it returns the expected example text when the field contains an example', () => {
expect(getExampleText(aField.example)).toEqual('Example: Y-6TfmcB0WOhS6qyMv3s');
expect(getExampleText(aField.example)).toEqual('Example: 2016-05-23T08:05:34.853Z');
});
test(`it returns an empty string when the field's example is an empty string`, () => {

View file

@ -0,0 +1,10 @@
/*
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
* or more contributor license agreements. Licensed under the Elastic License;
* you may not use this file except in compliance with the Elastic License.
*/
import { BrowserField } from '../../containers/source';
import { DetailItem } from '../../graphql/types';
export type EventFieldsData = BrowserField & DetailItem;

View file

@ -741,6 +741,7 @@ In other use cases the message field can be used to concatenate different values
onToggleDataProviderEnabled={[MockFunction]}
onToggleDataProviderExcluded={[MockFunction]}
show={true}
showCallOutUnauthorizedMsg={false}
sort={
Object {
"columnId": "@timestamp",

View file

@ -230,6 +230,7 @@ exports[`Header rendering renders correctly against snapshot 1`] = `
onToggleDataProviderEnabled={[MockFunction]}
onToggleDataProviderExcluded={[MockFunction]}
show={true}
showCallOutUnauthorizedMsg={false}
sort={
Object {
"columnId": "@timestamp",

View file

@ -33,6 +33,7 @@ describe('Header', () => {
onToggleDataProviderEnabled={jest.fn()}
onToggleDataProviderExcluded={jest.fn()}
show={true}
showCallOutUnauthorizedMsg={false}
sort={{
columnId: '@timestamp',
sortDirection: Direction.desc,
@ -57,6 +58,7 @@ describe('Header', () => {
onToggleDataProviderEnabled={jest.fn()}
onToggleDataProviderExcluded={jest.fn()}
show={true}
showCallOutUnauthorizedMsg={false}
sort={{
columnId: '@timestamp',
sortDirection: Direction.desc,
@ -67,5 +69,32 @@ describe('Header', () => {
expect(wrapper.find('[data-test-subj="dataProviders"]').exists()).toEqual(true);
});
test('it renders the unauthorized call out providers', () => {
const wrapper = mount(
<TestProviders>
<TimelineHeader
browserFields={{}}
dataProviders={mockDataProviders}
id="foo"
indexPattern={indexPattern}
onChangeDataProviderKqlQuery={jest.fn()}
onChangeDroppableAndProvider={jest.fn()}
onDataProviderEdited={jest.fn()}
onDataProviderRemoved={jest.fn()}
onToggleDataProviderEnabled={jest.fn()}
onToggleDataProviderExcluded={jest.fn()}
show={true}
showCallOutUnauthorizedMsg={true}
sort={{
columnId: '@timestamp',
sortDirection: Direction.desc,
}}
/>
</TestProviders>
);
expect(wrapper.find('[data-test-subj="timelineCallOutUnauthorized"]').exists()).toEqual(true);
});
});
});

View file

@ -4,6 +4,7 @@
* you may not use this file except in compliance with the Elastic License.
*/
import { EuiCallOut } from '@elastic/eui';
import * as React from 'react';
import { pure } from 'recompose';
import styled from 'styled-components';
@ -23,6 +24,8 @@ import {
import { StatefulSearchOrFilter } from '../search_or_filter';
import { BrowserFields } from '../../../containers/source';
import * as i18n from './translations';
interface Props {
browserFields: BrowserFields;
dataProviders: DataProvider[];
@ -35,6 +38,7 @@ interface Props {
onToggleDataProviderEnabled: OnToggleDataProviderEnabled;
onToggleDataProviderExcluded: OnToggleDataProviderExcluded;
show: boolean;
showCallOutUnauthorizedMsg: boolean;
sort: Sort;
}
@ -55,8 +59,18 @@ export const TimelineHeader = pure<Props>(
onToggleDataProviderEnabled,
onToggleDataProviderExcluded,
show,
showCallOutUnauthorizedMsg,
}) => (
<TimelineHeaderContainer data-test-subj="timelineHeader">
{showCallOutUnauthorizedMsg && (
<EuiCallOut
data-test-subj="timelineCallOutUnauthorized"
title={i18n.CALL_OUT_UNAUTHORIZED_MSG}
color="warning"
iconType="alert"
size="s"
/>
)}
<DataProviders
browserFields={browserFields}
id={id}

View file

@ -0,0 +1,15 @@
/*
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
* or more contributor license agreements. Licensed under the Elastic License;
* you may not use this file except in compliance with the Elastic License.
*/
import { i18n } from '@kbn/i18n';
export const CALL_OUT_UNAUTHORIZED_MSG = i18n.translate(
'xpack.siem.timeline.callOut.unauthorized.message.description',
{
defaultMessage:
'You require permission to auto-save timelines within the SIEM application, though you may continue to use the timeline to search and filter security events',
}
);

View file

@ -49,6 +49,7 @@ interface StateReduxProps {
sort?: Sort;
start: number;
show?: boolean;
showCallOutUnauthorizedMsg: boolean;
}
interface DispatchProps {
@ -139,6 +140,7 @@ class StatefulTimelineComponent extends React.Component<Props> {
sort,
start,
show,
showCallOutUnauthorizedMsg,
}: Props) =>
id !== this.props.id ||
flyoutHeaderHeight !== this.props.flyoutHeaderHeight ||
@ -155,7 +157,8 @@ class StatefulTimelineComponent extends React.Component<Props> {
pageCount !== this.props.pageCount ||
!isEqual(sort, this.props.sort) ||
start !== this.props.start ||
show !== this.props.show;
show !== this.props.show ||
showCallOutUnauthorizedMsg !== this.props.showCallOutUnauthorizedMsg;
public componentDidMount() {
const { createTimeline, id } = this.props;
@ -179,6 +182,7 @@ class StatefulTimelineComponent extends React.Component<Props> {
kqlMode,
kqlQueryExpression,
show,
showCallOutUnauthorizedMsg,
start,
sort,
} = this.props;
@ -208,6 +212,7 @@ class StatefulTimelineComponent extends React.Component<Props> {
onToggleDataProviderEnabled={this.onToggleDataProviderEnabled}
onToggleDataProviderExcluded={this.onToggleDataProviderExcluded}
show={show!}
showCallOutUnauthorizedMsg={showCallOutUnauthorizedMsg}
start={start}
sort={sort!}
/>
@ -274,6 +279,7 @@ class StatefulTimelineComponent extends React.Component<Props> {
}
const makeMapStateToProps = () => {
const getShowCallOutUnauthorizedMsg = timelineSelectors.getShowCallOutUnauthorizedMsg();
const getTimeline = timelineSelectors.getTimelineByIdSelector();
const getKqlQueryTimeline = timelineSelectors.getKqlFilterQuerySelector();
const getInputsTimeline = inputsSelectors.getTimelineSelector();
@ -303,6 +309,7 @@ const makeMapStateToProps = () => {
kqlQueryExpression,
sort,
start: input.timerange.from,
showCallOutUnauthorizedMsg: getShowCallOutUnauthorizedMsg(state),
show,
};
};

View file

@ -67,6 +67,7 @@ describe('Timeline', () => {
onToggleDataProviderEnabled={jest.fn()}
onToggleDataProviderExcluded={jest.fn()}
show={true}
showCallOutUnauthorizedMsg={false}
start={startDate}
sort={sort}
/>
@ -100,6 +101,7 @@ describe('Timeline', () => {
onToggleDataProviderEnabled={jest.fn()}
onToggleDataProviderExcluded={jest.fn()}
show={true}
showCallOutUnauthorizedMsg={false}
start={startDate}
sort={sort}
/>
@ -136,6 +138,7 @@ describe('Timeline', () => {
onToggleDataProviderEnabled={jest.fn()}
onToggleDataProviderExcluded={jest.fn()}
show={true}
showCallOutUnauthorizedMsg={false}
start={startDate}
sort={sort}
/>
@ -172,6 +175,7 @@ describe('Timeline', () => {
onToggleDataProviderEnabled={jest.fn()}
onToggleDataProviderExcluded={jest.fn()}
show={true}
showCallOutUnauthorizedMsg={false}
start={startDate}
sort={sort}
/>
@ -213,6 +217,7 @@ describe('Timeline', () => {
onToggleDataProviderEnabled={jest.fn()}
onToggleDataProviderExcluded={jest.fn()}
show={true}
showCallOutUnauthorizedMsg={false}
start={startDate}
sort={sort}
/>
@ -256,6 +261,7 @@ describe('Timeline', () => {
onToggleDataProviderEnabled={jest.fn()}
onToggleDataProviderExcluded={jest.fn()}
show={true}
showCallOutUnauthorizedMsg={false}
start={startDate}
sort={sort}
/>
@ -307,6 +313,7 @@ describe('Timeline', () => {
onToggleDataProviderEnabled={mockOnToggleDataProviderEnabled}
onToggleDataProviderExcluded={jest.fn()}
show={true}
showCallOutUnauthorizedMsg={false}
start={startDate}
sort={sort}
/>
@ -362,6 +369,7 @@ describe('Timeline', () => {
onToggleDataProviderEnabled={jest.fn()}
onToggleDataProviderExcluded={mockOnToggleDataProviderExcluded}
show={true}
showCallOutUnauthorizedMsg={false}
start={startDate}
sort={sort}
/>
@ -418,6 +426,7 @@ describe('Timeline', () => {
onToggleDataProviderEnabled={jest.fn()}
onToggleDataProviderExcluded={jest.fn()}
show={true}
showCallOutUnauthorizedMsg={false}
start={startDate}
sort={sort}
/>
@ -466,6 +475,7 @@ describe('Timeline', () => {
onToggleDataProviderEnabled={jest.fn()}
onToggleDataProviderExcluded={jest.fn()}
show={true}
showCallOutUnauthorizedMsg={false}
start={startDate}
sort={sort}
/>
@ -518,6 +528,7 @@ describe('Timeline', () => {
onToggleDataProviderEnabled={mockOnToggleDataProviderEnabled}
onToggleDataProviderExcluded={jest.fn()}
show={true}
showCallOutUnauthorizedMsg={false}
start={startDate}
sort={sort}
/>
@ -574,6 +585,7 @@ describe('Timeline', () => {
onToggleDataProviderEnabled={jest.fn()}
onToggleDataProviderExcluded={mockOnToggleDataProviderExcluded}
show={true}
showCallOutUnauthorizedMsg={false}
start={startDate}
sort={sort}
/>

View file

@ -71,6 +71,7 @@ interface Props {
onToggleDataProviderEnabled: OnToggleDataProviderEnabled;
onToggleDataProviderExcluded: OnToggleDataProviderExcluded;
show: boolean;
showCallOutUnauthorizedMsg: boolean;
start: number;
sort: Sort;
}
@ -99,6 +100,7 @@ export const Timeline = pure<Props>(
onToggleDataProviderEnabled,
onToggleDataProviderExcluded,
show,
showCallOutUnauthorizedMsg,
start,
sort,
}) => {
@ -134,6 +136,7 @@ export const Timeline = pure<Props>(
onToggleDataProviderEnabled={onToggleDataProviderEnabled}
onToggleDataProviderExcluded={onToggleDataProviderExcluded}
show={show}
showCallOutUnauthorizedMsg={showCallOutUnauthorizedMsg}
sort={sort}
/>
</WrappedByAutoSizer>

View file

@ -17,11 +17,7 @@ export const timelineDetailsQuery = gql`
id
TimelineDetails(eventId: $eventId, indexName: $indexName, defaultIndex: $defaultIndex) {
data {
category
description
example
field
type
values
originalValue
}

View file

@ -527,6 +527,22 @@
"name": "PinnedEvent",
"description": "",
"fields": [
{
"name": "code",
"description": "",
"args": [],
"type": { "kind": "SCALAR", "name": "Float", "ofType": null },
"isDeprecated": false,
"deprecationReason": null
},
{
"name": "message",
"description": "",
"args": [],
"type": { "kind": "SCALAR", "name": "String", "ofType": null },
"isDeprecated": false,
"deprecationReason": null
},
{
"name": "pinnedEventId",
"description": "",
@ -5327,34 +5343,6 @@
"name": "DetailItem",
"description": "",
"fields": [
{
"name": "category",
"description": "",
"args": [],
"type": {
"kind": "NON_NULL",
"name": null,
"ofType": { "kind": "SCALAR", "name": "String", "ofType": null }
},
"isDeprecated": false,
"deprecationReason": null
},
{
"name": "description",
"description": "",
"args": [],
"type": { "kind": "SCALAR", "name": "String", "ofType": null },
"isDeprecated": false,
"deprecationReason": null
},
{
"name": "example",
"description": "",
"args": [],
"type": { "kind": "SCALAR", "name": "String", "ofType": null },
"isDeprecated": false,
"deprecationReason": null
},
{
"name": "field",
"description": "",
@ -5367,18 +5355,6 @@
"isDeprecated": false,
"deprecationReason": null
},
{
"name": "type",
"description": "",
"args": [],
"type": {
"kind": "NON_NULL",
"name": null,
"ofType": { "kind": "SCALAR", "name": "String", "ofType": null }
},
"isDeprecated": false,
"deprecationReason": null
},
{
"name": "values",
"description": "",
@ -8429,6 +8405,14 @@
"type": { "kind": "SCALAR", "name": "String", "ofType": null },
"isDeprecated": false,
"deprecationReason": null
},
{
"name": "operator",
"description": "",
"args": [],
"type": { "kind": "SCALAR", "name": "String", "ofType": null },
"isDeprecated": false,
"deprecationReason": null
}
],
"inputFields": null,
@ -9316,6 +9300,12 @@
"description": "",
"type": { "kind": "SCALAR", "name": "String", "ofType": null },
"defaultValue": null
},
{
"name": "operator",
"description": "",
"type": { "kind": "SCALAR", "name": "String", "ofType": null },
"defaultValue": null
}
],
"interfaces": null,
@ -9475,6 +9465,22 @@
"name": "ResponseFavoriteTimeline",
"description": "",
"fields": [
{
"name": "code",
"description": "",
"args": [],
"type": { "kind": "SCALAR", "name": "Float", "ofType": null },
"isDeprecated": false,
"deprecationReason": null
},
{
"name": "message",
"description": "",
"args": [],
"type": { "kind": "SCALAR", "name": "String", "ofType": null },
"isDeprecated": false,
"deprecationReason": null
},
{
"name": "savedObjectId",
"description": "",

View file

@ -79,6 +79,10 @@ export interface ResponseNotes {
}
export interface PinnedEvent {
code?: number | null;
message?: string | null;
pinnedEventId: string;
eventId?: string | null;
@ -832,16 +836,8 @@ export interface TimelineDetailsData {
}
export interface DetailItem {
category: string;
description?: string | null;
example?: string | null;
field: string;
type: string;
values?: ToStringArray | null;
originalValue?: EsValue | null;
@ -1338,6 +1334,8 @@ export interface QueryMatchResult {
value?: string | null;
displayValue?: string | null;
operator?: string | null;
}
export interface DateRangePickerResult {
@ -1420,6 +1418,10 @@ export interface ResponseTimeline {
}
export interface ResponseFavoriteTimeline {
code?: number | null;
message?: string | null;
savedObjectId: string;
version: string;
@ -1625,6 +1627,8 @@ export interface QueryMatchInput {
value?: string | null;
displayValue?: string | null;
operator?: string | null;
}
export interface SerializedFilterQueryInput {
@ -3504,16 +3508,8 @@ export namespace GetTimelineDetailsQuery {
export type Data = {
__typename?: 'DetailItem';
category: string;
description?: string | null;
example?: string | null;
field: string;
type: string;
values?: ToStringArray | null;
originalValue?: EsValue | null;
@ -4384,6 +4380,8 @@ export namespace GetOneTimeline {
value?: string | null;
displayValue?: string | null;
operator?: string | null;
};
export type And = {
@ -4412,6 +4410,8 @@ export namespace GetOneTimeline {
value?: string | null;
displayValue?: string | null;
operator?: string | null;
};
export type DateRange = {
@ -4640,6 +4640,8 @@ export namespace PersistTimelineMutation {
value?: string | null;
displayValue?: string | null;
operator?: string | null;
};
export type And = {
@ -4668,6 +4670,8 @@ export namespace PersistTimelineMutation {
value?: string | null;
displayValue?: string | null;
operator?: string | null;
};
export type Favorite = {

View file

@ -113,6 +113,7 @@ export const mockGlobalState: State = {
},
dragAndDrop: { dataProviders: {} },
timeline: {
showCallOutUnauthorizedMsg: false,
autoSavedWarningMsg: {
timelineId: null,
newTimelineModel: null,

View file

@ -10,187 +10,102 @@ export const mockDetailItemDataId = 'Y-6TfmcB0WOhS6qyMv3s';
export const mockDetailItemData: DetailItem[] = [
{
category: '_id',
description: 'Each document has an _id that uniquely identifies it',
example: 'Y-6TfmcB0WOhS6qyMv3s',
field: '_id',
type: 'keyword',
originalValue: 'pEMaMmkBUV60JmNWmWVi',
values: ['pEMaMmkBUV60JmNWmWVi'],
},
{
category: '_index',
description:
'An index is like a database in a relational database. It has a mapping which defines multiple types. An index is a logical namespace which maps to one or more primary shards and can have zero or more replica shards.',
example: 'auditbeat-8.0.0-2019.02.19-000001',
field: '_index',
type: 'keyword',
originalValue: 'filebeat-8.0.0-2019.02.19-000001',
values: ['filebeat-8.0.0-2019.02.19-000001'],
},
{
category: '_type',
description: null,
example: null,
field: '_type',
type: 'keyword',
originalValue: '_doc',
values: ['_doc'],
},
{
category: '_score',
description: null,
example: null,
field: '_score',
type: 'long',
originalValue: 1,
values: ['1'],
},
{
category: '@timestamp',
description:
'Date/time when the event originated.For log events this is the date/time when the event was generated, and not when it was read.Required field for all events.',
example: '2016-05-23T08:05:34.853Z',
field: '@timestamp',
type: 'date',
originalValue: '2019-02-28T16:50:54.621Z',
values: ['2019-02-28T16:50:54.621Z'],
},
{
category: 'agent',
description:
'Ephemeral identifier of this agent (if one exists).This id normally changes across restarts, but `agent.id` does not.',
example: '8a4f500f',
field: 'agent.ephemeral_id',
type: 'keyword',
originalValue: '9d391ef2-a734-4787-8891-67031178c641',
values: ['9d391ef2-a734-4787-8891-67031178c641'],
},
{
category: 'agent',
description: null,
example: null,
field: 'agent.hostname',
type: 'keyword',
originalValue: 'siem-kibana',
values: ['siem-kibana'],
},
{
category: 'agent',
description:
'Unique identifier of this agent (if one exists).Example: For Beats this would be beat.id.',
example: '8a4f500d',
field: 'agent.id',
type: 'keyword',
originalValue: '5de03d5f-52f3-482e-91d4-853c7de073c3',
values: ['5de03d5f-52f3-482e-91d4-853c7de073c3'],
},
{
category: 'agent',
description:
'Type of the agent.The agent type stays always the same and should be given by the agent used. In case of Filebeat the agent would always be Filebeat also if two Filebeat instances are run on the same machine.',
example: 'filebeat',
field: 'agent.type',
type: 'keyword',
originalValue: 'filebeat',
values: ['filebeat'],
},
{
category: 'agent',
description: 'Version of the agent.',
example: '6.0.0-rc2',
field: 'agent.version',
type: 'keyword',
originalValue: '8.0.0',
values: ['8.0.0'],
},
{
category: 'cloud',
description: 'Availability zone in which this host is running.',
example: 'us-east-1c',
field: 'cloud.availability_zone',
type: 'keyword',
originalValue: 'projects/189716325846/zones/us-east1-b',
values: ['projects/189716325846/zones/us-east1-b'],
},
{
category: 'cloud',
description: 'Instance ID of the host machine.',
example: 'i-1234567890abcdef0',
field: 'cloud.instance.id',
type: 'keyword',
originalValue: '5412578377715150143',
values: ['5412578377715150143'],
},
{
category: 'cloud',
description: 'Instance name of the host machine.',
example: null,
field: 'cloud.instance.name',
type: 'keyword',
originalValue: 'siem-kibana',
values: ['siem-kibana'],
},
{
category: 'cloud',
description: 'Machine type of the host machine.',
example: 't2.medium',
field: 'cloud.machine.type',
type: 'keyword',
originalValue: 'projects/189716325846/machineTypes/n1-standard-1',
values: ['projects/189716325846/machineTypes/n1-standard-1'],
},
{
category: 'cloud',
description: null,
example: null,
field: 'cloud.project.id',
type: 'keyword',
originalValue: 'elastic-beats',
values: ['elastic-beats'],
},
{
category: 'cloud',
description: 'Name of the cloud provider. Example values are ec2, gce, or digitalocean.',
example: 'ec2',
field: 'cloud.provider',
type: 'keyword',
originalValue: 'gce',
values: ['gce'],
},
{
category: 'destination',
description: 'Bytes sent from the destination to the source.',
example: '184',
field: 'destination.bytes',
type: 'long',
originalValue: 584,
values: ['584'],
},
{
category: 'destination',
description: 'IP address of the destination.Can be one or multiple IPv4 or IPv6 addresses.',
example: null,
field: 'destination.ip',
type: 'ip',
originalValue: '10.47.8.200',
values: ['10.47.8.200'],
},
{
category: 'destination',
description: 'Packets sent from the destination to the source.',
example: '12',
field: 'destination.packets',
type: 'long',
originalValue: 4,
values: ['4'],
},
{
category: 'destination',
description: 'Port of the destination.',
example: null,
field: 'destination.port',
type: 'long',
originalValue: 902,
values: ['902'],
},

View file

@ -183,3 +183,5 @@ export const updateAutoSaveMsg = actionCreator<{
timelineId: string | null;
newTimelineModel: TimelineModel | null;
}>('UPDATE_AUTO_SAVE');
export const showCallOutUnauthorizedMsg = actionCreator('SHOW_CALL_OUT_UNAUTHORIZED_MSG');

View file

@ -29,6 +29,7 @@ import {
TimelineResult,
} from '../../graphql/types';
import { AppApolloClient } from '../../lib/lib';
import { addError } from '../app/actions';
import { NotesById } from '../app/model';
import { TimeRange } from '../inputs/model';
@ -55,6 +56,7 @@ import {
endTimelineSaving,
createTimeline,
addTimeline,
showCallOutUnauthorizedMsg,
} from './actions';
import { TimelineModel } from './model';
import { TimelineById } from './reducer';
@ -131,6 +133,9 @@ export const createTimelineEpic = <State>(): Epic<
withLatestFrom(timeline$),
filter(([action, timeline]) => {
const timelineId: TimelineModel = timeline[get('payload.id', action)];
if (action.type === addError.type) {
return true;
}
if (action.type === createTimeline.type) {
myEpicTimelineId.setTimelineId(null);
myEpicTimelineId.setTimelineVersion(null);
@ -186,6 +191,7 @@ export const createTimelineEpic = <State>(): Epic<
mergeMap(([result, recentTimeline]) => {
const savedTimeline = recentTimeline[get('payload.id', action)];
const response: ResponseTimeline = get('data.persistTimeline', result);
const callOutMsg = response.code === 403 ? [showCallOutUnauthorizedMsg()] : [];
return [
response.code === 409
@ -202,6 +208,7 @@ export const createTimelineEpic = <State>(): Epic<
isSaving: false,
},
}),
...callOutMsg,
endTimelineSaving({
id: get('payload.id', action),
}),

View file

@ -14,12 +14,13 @@ import { filter, mergeMap, withLatestFrom, startWith, takeUntil } from 'rxjs/ope
import { persistTimelineFavoriteMutation } from '../../containers/timeline/favorite/persist.gql_query';
import { PersistTimelineFavoriteMutation, ResponseFavoriteTimeline } from '../../graphql/types';
import { addError } from '../app/actions';
import {
endTimelineSaving,
updateIsFavorite,
updateTimeline,
startTimelineSaving,
showCallOutUnauthorizedMsg,
} from './actions';
import { TimelineById } from './reducer';
import { dispatcherTimelinePersistQueue } from './epic_dispatcher_timeline_persistence_queue';
@ -53,7 +54,10 @@ export const epicPersistTimelineFavorite = (
mergeMap(([result, recentTimelines]) => {
const savedTimeline = recentTimelines[get('payload.id', action)];
const response: ResponseFavoriteTimeline = get('data.persistFavorite', result);
const callOutMsg = response.code === 403 ? [showCallOutUnauthorizedMsg()] : [];
return [
...callOutMsg,
updateTimeline({
id: get('payload.id', action),
timeline: {
@ -73,6 +77,9 @@ export const epicPersistTimelineFavorite = (
action$.pipe(
withLatestFrom(timeline$),
filter(([checkAction, updatedTimeline]) => {
if (checkAction.type === addError.type) {
return true;
}
if (
checkAction.type === endTimelineSaving.type &&
updatedTimeline[get('payload.id', checkAction)].savedObjectId != null

View file

@ -14,7 +14,7 @@ import { filter, mergeMap, switchMap, withLatestFrom, startWith, takeUntil } fro
import { persistTimelineNoteMutation } from '../../containers/timeline/notes/persist.gql_query';
import { PersistTimelineNoteMutation, ResponseNote } from '../../graphql/types';
import { updateNote } from '../app/actions';
import { updateNote, addError } from '../app/actions';
import { NotesById } from '../app/model';
import {
@ -23,6 +23,7 @@ import {
endTimelineSaving,
updateTimeline,
startTimelineSaving,
showCallOutUnauthorizedMsg,
} from './actions';
import { TimelineById } from './reducer';
import { myEpicTimelineId } from './my_epic_timeline_id';
@ -63,8 +64,10 @@ export const epicPersistNote = (
mergeMap(([result, recentTimeline, recentNotes]) => {
const noteIdRedux = get('payload.noteId', action);
const response: ResponseNote = get('data.persistNote', result);
const callOutMsg = response.code === 403 ? [showCallOutUnauthorizedMsg()] : [];
return [
...callOutMsg,
recentTimeline[get('payload.id', action)].savedObjectId == null
? updateTimeline({
id: get('payload.id', action),
@ -100,6 +103,9 @@ export const epicPersistNote = (
action$.pipe(
withLatestFrom(timeline$),
filter(([checkAction, updatedTimeline]) => {
if (checkAction.type === addError.type) {
return true;
}
if (
checkAction.type === endTimelineSaving.type &&
updatedTimeline[get('payload.id', checkAction)].savedObjectId != null

View file

@ -14,13 +14,14 @@ import { filter, mergeMap, startWith, withLatestFrom, takeUntil } from 'rxjs/ope
import { persistTimelinePinnedEventMutation } from '../../containers/timeline/pinned_event/persist.gql_query';
import { PersistTimelinePinnedEventMutation, PinnedEvent } from '../../graphql/types';
import { addError } from '../app/actions';
import {
pinEvent,
endTimelineSaving,
unPinEvent,
updateTimeline,
startTimelineSaving,
showCallOutUnauthorizedMsg,
} from './actions';
import { TimelineById } from './reducer';
import { myEpicTimelineId } from './my_epic_timeline_id';
@ -63,6 +64,7 @@ export const epicPersistPinnedEvent = (
mergeMap(([result, recentTimeline]) => {
const savedTimeline = recentTimeline[get('payload.id', action)];
const response: PinnedEvent = get('data.persistPinnedEventOnTimeline', result);
const callOutMsg = response && response.code === 403 ? [showCallOutUnauthorizedMsg()] : [];
return [
response != null
@ -94,6 +96,7 @@ export const epicPersistPinnedEvent = (
),
},
}),
...callOutMsg,
endTimelineSaving({
id: get('payload.id', action),
}),
@ -104,6 +107,9 @@ export const epicPersistPinnedEvent = (
action$.pipe(
withLatestFrom(timeline$),
filter(([checkAction, updatedTimeline]) => {
if (checkAction.type === addError.type) {
return true;
}
if (
checkAction.type === endTimelineSaving.type &&
updatedTimeline[get('payload.id', checkAction)].savedObjectId != null

View file

@ -28,6 +28,7 @@ export const initialTimelineState: TimelineState = {
timelineId: null,
newTimelineModel: null,
},
showCallOutUnauthorizedMsg: false,
};
interface AddTimelineHistoryParams {

View file

@ -23,6 +23,7 @@ import {
removeColumn,
removeProvider,
setKqlFilterQueryDraft,
showCallOutUnauthorizedMsg,
showTimeline,
startTimelineSaving,
unPinEvent,
@ -97,6 +98,7 @@ export interface AutoSavedWarningMsg {
export interface TimelineState {
timelineById: TimelineById;
autoSavedWarningMsg: AutoSavedWarningMsg;
showCallOutUnauthorizedMsg: boolean;
}
const EMPTY_TIMELINE_BY_ID: TimelineById = {}; // stable reference
@ -107,6 +109,7 @@ export const initialTimelineState: TimelineState = {
timelineId: null,
newTimelineModel: null,
},
showCallOutUnauthorizedMsg: false,
};
/** The reducer for all timeline actions */
@ -387,4 +390,8 @@ export const timelineReducer = reducerWithInitialState(initialTimelineState)
newTimelineModel,
},
}))
.case(showCallOutUnauthorizedMsg, state => ({
...state,
showCallOutUnauthorizedMsg: true,
}))
.build();

View file

@ -16,6 +16,9 @@ const selectTimelineById = (state: State): TimelineById => state.timeline.timeli
const selectAutoSaveMsg = (state: State): AutoSavedWarningMsg => state.timeline.autoSavedWarningMsg;
const selectCallOutUnauthorizedMsg = (state: State): boolean =>
state.timeline.showCallOutUnauthorizedMsg;
export const selectTimeline = (state: State, timelineId: string): TimelineModel =>
state.timeline.timelineById[timelineId];
@ -29,6 +32,12 @@ export const timelineByIdSelector = createSelector(
timelineById => timelineById
);
export const getShowCallOutUnauthorizedMsg = () =>
createSelector(
selectCallOutUnauthorizedMsg,
showCallOutUnauthorizedMsg => showCallOutUnauthorizedMsg
);
export const getTimelineByIdSelector = () =>
createSelector(
selectTimeline,

View file

@ -45,11 +45,7 @@ export const eventsSchema = gql`
}
type DetailItem {
category: String!
description: String
example: String
field: String!
type: String!
values: ToStringArray
originalValue: EsValue
}

View file

@ -12,6 +12,8 @@ export const pinnedEventSchema = gql`
#########################
type PinnedEvent {
code: Float
message: String
pinnedEventId: ID!
eventId: ID
timelineId: ID

View file

@ -201,6 +201,8 @@ export const timelineSchema = gql`
}
type ResponseFavoriteTimeline {
code: Float
message: String
savedObjectId: String!
version: String!
favorite: [FavoriteTimelineResult!]

View file

@ -108,6 +108,10 @@ export interface ResponseNotes {
}
export interface PinnedEvent {
code?: number | null;
message?: string | null;
pinnedEventId: string;
eventId?: string | null;
@ -861,16 +865,8 @@ export interface TimelineDetailsData {
}
export interface DetailItem {
category: string;
description?: string | null;
example?: string | null;
field: string;
type: string;
values?: ToStringArray | null;
originalValue?: EsValue | null;
@ -1367,6 +1363,8 @@ export interface QueryMatchResult {
value?: string | null;
displayValue?: string | null;
operator?: string | null;
}
export interface DateRangePickerResult {
@ -1449,6 +1447,10 @@ export interface ResponseTimeline {
}
export interface ResponseFavoriteTimeline {
code?: number | null;
message?: string | null;
savedObjectId: string;
version: string;
@ -1654,6 +1656,8 @@ export interface QueryMatchInput {
value?: string | null;
displayValue?: string | null;
operator?: string | null;
}
export interface SerializedFilterQueryInput {
@ -2316,6 +2320,10 @@ export namespace ResponseNotesResolvers {
export namespace PinnedEventResolvers {
export interface Resolvers<Context = SiemContext, TypeParent = PinnedEvent> {
code?: CodeResolver<number | null, TypeParent, Context>;
message?: MessageResolver<string | null, TypeParent, Context>;
pinnedEventId?: PinnedEventIdResolver<string, TypeParent, Context>;
eventId?: EventIdResolver<string | null, TypeParent, Context>;
@ -2335,6 +2343,16 @@ export namespace PinnedEventResolvers {
version?: VersionResolver<string | null, TypeParent, Context>;
}
export type CodeResolver<
R = number | null,
Parent = PinnedEvent,
Context = SiemContext
> = Resolver<R, Parent, Context>;
export type MessageResolver<
R = string | null,
Parent = PinnedEvent,
Context = SiemContext
> = Resolver<R, Parent, Context>;
export type PinnedEventIdResolver<
R = string,
Parent = PinnedEvent,
@ -5084,46 +5102,18 @@ export namespace TimelineDetailsDataResolvers {
export namespace DetailItemResolvers {
export interface Resolvers<Context = SiemContext, TypeParent = DetailItem> {
category?: CategoryResolver<string, TypeParent, Context>;
description?: DescriptionResolver<string | null, TypeParent, Context>;
example?: ExampleResolver<string | null, TypeParent, Context>;
field?: FieldResolver<string, TypeParent, Context>;
type?: TypeResolver<string, TypeParent, Context>;
values?: ValuesResolver<ToStringArray | null, TypeParent, Context>;
originalValue?: OriginalValueResolver<EsValue | null, TypeParent, Context>;
}
export type CategoryResolver<R = string, Parent = DetailItem, Context = SiemContext> = Resolver<
R,
Parent,
Context
>;
export type DescriptionResolver<
R = string | null,
Parent = DetailItem,
Context = SiemContext
> = Resolver<R, Parent, Context>;
export type ExampleResolver<
R = string | null,
Parent = DetailItem,
Context = SiemContext
> = Resolver<R, Parent, Context>;
export type FieldResolver<R = string, Parent = DetailItem, Context = SiemContext> = Resolver<
R,
Parent,
Context
>;
export type TypeResolver<R = string, Parent = DetailItem, Context = SiemContext> = Resolver<
R,
Parent,
Context
>;
export type ValuesResolver<
R = ToStringArray | null,
Parent = DetailItem,
@ -6767,6 +6757,8 @@ export namespace QueryMatchResultResolvers {
value?: ValueResolver<string | null, TypeParent, Context>;
displayValue?: DisplayValueResolver<string | null, TypeParent, Context>;
operator?: OperatorResolver<string | null, TypeParent, Context>;
}
export type FieldResolver<
@ -6789,6 +6781,11 @@ export namespace QueryMatchResultResolvers {
Parent = QueryMatchResult,
Context = SiemContext
> = Resolver<R, Parent, Context>;
export type OperatorResolver<
R = string | null,
Parent = QueryMatchResult,
Context = SiemContext
> = Resolver<R, Parent, Context>;
}
export namespace DateRangePickerResultResolvers {
@ -7107,6 +7104,10 @@ export namespace ResponseTimelineResolvers {
export namespace ResponseFavoriteTimelineResolvers {
export interface Resolvers<Context = SiemContext, TypeParent = ResponseFavoriteTimeline> {
code?: CodeResolver<number | null, TypeParent, Context>;
message?: MessageResolver<string | null, TypeParent, Context>;
savedObjectId?: SavedObjectIdResolver<string, TypeParent, Context>;
version?: VersionResolver<string, TypeParent, Context>;
@ -7114,6 +7115,16 @@ export namespace ResponseFavoriteTimelineResolvers {
favorite?: FavoriteResolver<FavoriteTimelineResult[] | null, TypeParent, Context>;
}
export type CodeResolver<
R = number | null,
Parent = ResponseFavoriteTimeline,
Context = SiemContext
> = Resolver<R, Parent, Context>;
export type MessageResolver<
R = string | null,
Parent = ResponseFavoriteTimeline,
Context = SiemContext
> = Resolver<R, Parent, Context>;
export type SavedObjectIdResolver<
R = string,
Parent = ResponseFavoriteTimeline,

View file

@ -28,17 +28,11 @@ import {
TimelineDetailsData,
TimelineEdges,
} from '../../graphql/types';
import { getDocumentation, getIndexAlias, hasDocumentation } from '../../utils/beat_schema';
import { baseCategoryFields } from '../../utils/beat_schema/8.0.0';
import { reduceFields } from '../../utils/build_query/reduce_fields';
import { mergeFieldsWithHit } from '../../utils/build_query';
import { eventFieldsMap } from '../ecs_fields';
import {
FrameworkAdapter,
FrameworkRequest,
MappingProperties,
RequestOptions,
} from '../framework';
import { FrameworkAdapter, FrameworkRequest, RequestOptions } from '../framework';
import { TermAggregation } from '../types';
import { buildDetailsQuery, buildQuery } from './query.dsl';
@ -114,32 +108,18 @@ export class ElasticsearchEventsAdapter implements EventsAdapter {
request: FrameworkRequest,
options: RequestDetailsOptions
): Promise<TimelineDetailsData> {
const [mapResponse, searchResponse] = await Promise.all([
this.framework.callWithRequest(request, 'indices.getMapping', {
allowNoIndices: true,
ignoreUnavailable: true,
index: options.indexName,
}),
this.framework.callWithRequest<EventHit, TermAggregation>(
request,
'search',
buildDetailsQuery(options.indexName, options.eventId)
),
]);
const searchResponse = await this.framework.callWithRequest<EventHit, TermAggregation>(
request,
'search',
buildDetailsQuery(options.indexName, options.eventId)
);
const sourceData = getOr({}, 'hits.hits.0._source', searchResponse);
const hitsData = getOr({}, 'hits.hits.0', searchResponse);
delete hitsData._source;
return {
data: getSchemaFromData(
{
...addBasicElasticSearchProperties(),
...getOr({}, [options.indexName, 'mappings', 'properties'], mapResponse),
},
getDataFromHits(merge(sourceData, hitsData)),
getIndexAlias(options.defaultIndex, options.indexName)
),
data: getDataFromHits(merge(sourceData, hitsData)),
};
}
@ -289,50 +269,3 @@ const getDataFromHits = (sources: EventSource, category?: string, path?: string)
}
return accumulator;
}, []);
const getSchemaFromData = (
properties: MappingProperties,
data: DetailItem[],
index: string,
path?: string
): DetailItem[] =>
!isEmpty(properties)
? Object.keys(properties).reduce<DetailItem[]>((accumulator, property) => {
const item = get(property, properties);
const field = path ? `${path}.${property}` : property;
const dataFilterItem = data.filter(dataItem => dataItem.field === field);
if (item.properties == null && dataFilterItem.length === 1) {
const dataItem = dataFilterItem[0];
const dataFromMapping = {
type: get([property, 'type'], properties),
};
return [
...accumulator,
{
...dataItem,
...(hasDocumentation(index, field)
? merge(getDocumentation(index, field), dataFromMapping)
: dataFromMapping),
},
];
} else if (item.properties != null) {
return [...accumulator, ...getSchemaFromData(item.properties, data, index, field)];
}
return accumulator;
}, [])
: data;
const addBasicElasticSearchProperties = () => ({
_id: {
type: 'keyword',
},
_index: {
type: 'keyword',
},
_type: {
type: 'keyword',
},
_score: {
type: 'long',
},
});

File diff suppressed because it is too large Load diff

View file

@ -8,11 +8,18 @@ import { failure } from 'io-ts/lib/PathReporter';
import { RequestAuth } from 'hapi';
import { Legacy } from 'kibana';
import { getOr } from 'lodash/fp';
import uuid from 'uuid';
import { FindOptions } from 'src/legacy/server/saved_objects/service';
import { Pick3 } from '../../../common/utility_types';
import { PageInfoNote, ResponseNote, ResponseNotes, SortNote } from '../../graphql/types';
import {
PageInfoNote,
ResponseNote,
ResponseNotes,
SortNote,
NoteResult,
} from '../../graphql/types';
import { FrameworkRequest, internalFrameworkRequest } from '../framework';
import { SavedNote, NoteSavedObjectRuntimeType, NoteSavedObject } from './types';
import { noteSavedObjectType } from './saved_object_mappings';
@ -104,21 +111,24 @@ export class Note {
version: string | null,
note: SavedNote
): Promise<ResponseNote> {
let timelineVersionSavedObject = null;
try {
if (note.timelineId == null) {
const timelineResult = convertSavedObjectToSavedTimeline(
await this.libs.savedObjects
.getScopedSavedObjectsClient(request[internalFrameworkRequest])
.create(
timelineSavedObjectType,
pickSavedTimeline(null, {}, request[internalFrameworkRequest].auth || null)
)
);
note.timelineId = timelineResult.savedObjectId;
timelineVersionSavedObject = timelineResult.version;
}
if (noteId == null) {
const timelineVersionSavedObject =
note.timelineId == null
? await (async () => {
const timelineResult = convertSavedObjectToSavedTimeline(
await this.libs.savedObjects
.getScopedSavedObjectsClient(request[internalFrameworkRequest])
.create(
timelineSavedObjectType,
pickSavedTimeline(null, {}, request[internalFrameworkRequest].auth || null)
)
);
note.timelineId = timelineResult.savedObjectId;
return timelineResult.version;
})()
: null;
// Create new note
return {
code: 200,
@ -134,6 +144,7 @@ export class Note {
),
};
}
// Update new note
return {
code: 200,
@ -152,6 +163,20 @@ export class Note {
),
};
} catch (err) {
if (getOr(null, 'output.statusCode', err) === 403) {
const noteToReturn: NoteResult = {
...note,
noteId: uuid.v1(),
version: '',
timelineId: '',
timelineVersion: '',
};
return {
code: 403,
message: err.message,
note: noteToReturn,
};
}
throw err;
}
}

View file

@ -18,7 +18,7 @@ import {
PinnedEventSavedObjectRuntimeType,
SavedPinnedEvent,
} from './types';
import { PageInfoNote, SortNote } from '../../graphql/types';
import { PageInfoNote, SortNote, PinnedEvent as PinnedEventResponse } from '../../graphql/types';
import { pinnedEventSavedObjectType, timelineSavedObjectType } from '../../saved_objects';
import { pickSavedTimeline } from '../timeline/pick_saved_timeline';
import { convertSavedObjectToSavedTimeline } from '../timeline/convert_saved_object_to_savedtimeline';
@ -96,52 +96,69 @@ export class PinnedEvent {
pinnedEventId: string | null,
eventId: string,
timelineId: string | null
): Promise<PinnedEventSavedObject | null> {
let timelineVersionSavedObject = null;
): Promise<PinnedEventResponse | null> {
try {
if (timelineId == null) {
const timelineResult = convertSavedObjectToSavedTimeline(
await this.libs.savedObjects
.getScopedSavedObjectsClient(request[internalFrameworkRequest])
.create(
timelineSavedObjectType,
pickSavedTimeline(null, {}, request[internalFrameworkRequest].auth || null)
)
);
timelineId = timelineResult.savedObjectId;
timelineVersionSavedObject = timelineResult.version;
}
if (pinnedEventId == null) {
const allPinnedEventId = await this.getAllPinnedEventsByTimelineId(request, timelineId);
const isPinnedAlreadyExisting = allPinnedEventId.filter(
pinnedEvent => pinnedEvent.eventId === eventId
);
if (isPinnedAlreadyExisting.length === 0) {
const savedPinnedEvent: SavedPinnedEvent = {
eventId,
timelineId,
};
// create Pinned Event on Timeline
return convertSavedObjectToSavedPinnedEvent(
await this.libs.savedObjects
.getScopedSavedObjectsClient(request[internalFrameworkRequest])
.create(
pinnedEventSavedObjectType,
pickSavedPinnedEvent(
pinnedEventId,
savedPinnedEvent,
request[internalFrameworkRequest].auth || null
)
),
timelineVersionSavedObject != null ? timelineVersionSavedObject : undefined
const timelineVersionSavedObject =
timelineId == null
? await (async () => {
const timelineResult = convertSavedObjectToSavedTimeline(
await this.libs.savedObjects
.getScopedSavedObjectsClient(request[internalFrameworkRequest])
.create(
timelineSavedObjectType,
pickSavedTimeline(null, {}, request[internalFrameworkRequest].auth || null)
)
);
timelineId = timelineResult.savedObjectId;
return timelineResult.version;
})()
: null;
if (timelineId != null) {
const allPinnedEventId = await this.getAllPinnedEventsByTimelineId(request, timelineId);
const isPinnedAlreadyExisting = allPinnedEventId.filter(
pinnedEvent => pinnedEvent.eventId === eventId
);
if (isPinnedAlreadyExisting.length === 0) {
const savedPinnedEvent: SavedPinnedEvent = {
eventId,
timelineId,
};
// create Pinned Event on Timeline
return convertSavedObjectToSavedPinnedEvent(
await this.libs.savedObjects
.getScopedSavedObjectsClient(request[internalFrameworkRequest])
.create(
pinnedEventSavedObjectType,
pickSavedPinnedEvent(
pinnedEventId,
savedPinnedEvent,
request[internalFrameworkRequest].auth || null
)
),
timelineVersionSavedObject != null ? timelineVersionSavedObject : undefined
);
}
return isPinnedAlreadyExisting[0];
}
return isPinnedAlreadyExisting[0];
throw new Error('You can NOT pinned event without a timelineID');
}
// Delete Pinned Event on Timeline
await this.deletePinnedEventOnTimeline(request, [pinnedEventId]);
return null;
} catch (err) {
if (getOr(null, 'output.statusCode', err) === 403) {
return pinnedEventId != null
? {
code: 403,
message: err.message,
pinnedEventId: eventId,
timelineId: '',
timelineVersion: '',
}
: null;
}
throw err;
}
}

View file

@ -15,6 +15,7 @@ import {
PageInfoTimeline,
SortTimeline,
ResponseFavoriteTimeline,
TimelineResult,
} from '../../graphql/types';
import { FrameworkRequest, internalFrameworkRequest } from '../framework';
import { NoteSavedObject } from '../note/types';
@ -77,54 +78,68 @@ export class Timeline {
request: FrameworkRequest,
timelineId: string | null
): Promise<ResponseFavoriteTimeline> {
let timeline: SavedTimeline = {};
if (timelineId != null) {
const {
eventIdToNoteIds,
notes,
noteIds,
pinnedEventIds,
pinnedEventsSaveObject,
savedObjectId,
version,
...savedTimeline
} = await this.getBasicSavedTimeline(request, timelineId);
timelineId = savedObjectId;
timeline = savedTimeline;
}
const userName = getOr(null, 'credentials.username', request[internalFrameworkRequest].auth);
const fullName = getOr(null, 'credentials.fullname', request[internalFrameworkRequest].auth);
const userFavoriteTimeline = {
keySearch: userName != null ? convertStringToBase64(userName) : null,
favoriteDate: new Date().valueOf(),
fullName,
userName,
};
if (timeline.favorite != null) {
const alreadyExistsTimelineFavoriteByUser = timeline.favorite.findIndex(
user => user.userName === userName
);
try {
let timeline: SavedTimeline = {};
if (timelineId != null) {
const {
eventIdToNoteIds,
notes,
noteIds,
pinnedEventIds,
pinnedEventsSaveObject,
savedObjectId,
version,
...savedTimeline
} = await this.getBasicSavedTimeline(request, timelineId);
timelineId = savedObjectId;
timeline = savedTimeline;
}
timeline.favorite =
alreadyExistsTimelineFavoriteByUser > -1
? [
...timeline.favorite.slice(0, alreadyExistsTimelineFavoriteByUser),
...timeline.favorite.slice(alreadyExistsTimelineFavoriteByUser + 1),
]
: [...timeline.favorite, userFavoriteTimeline];
} else if (timeline.favorite == null) {
timeline.favorite = [userFavoriteTimeline];
const userFavoriteTimeline = {
keySearch: userName != null ? convertStringToBase64(userName) : null,
favoriteDate: new Date().valueOf(),
fullName,
userName,
};
if (timeline.favorite != null) {
const alreadyExistsTimelineFavoriteByUser = timeline.favorite.findIndex(
user => user.userName === userName
);
timeline.favorite =
alreadyExistsTimelineFavoriteByUser > -1
? [
...timeline.favorite.slice(0, alreadyExistsTimelineFavoriteByUser),
...timeline.favorite.slice(alreadyExistsTimelineFavoriteByUser + 1),
]
: [...timeline.favorite, userFavoriteTimeline];
} else if (timeline.favorite == null) {
timeline.favorite = [userFavoriteTimeline];
}
const persistResponse = await this.persistTimeline(request, timelineId, null, timeline);
return {
savedObjectId: persistResponse.timeline.savedObjectId,
version: persistResponse.timeline.version,
favorite:
persistResponse.timeline.favorite != null
? persistResponse.timeline.favorite.filter(fav => fav.userName === userName)
: [],
};
} catch (err) {
if (getOr(null, 'output.statusCode', err) === 403) {
return {
savedObjectId: '',
version: '',
favorite: [],
code: 403,
message: err.message,
};
}
throw err;
}
const persistResponse = await this.persistTimeline(request, timelineId, null, timeline);
return {
savedObjectId: persistResponse.timeline.savedObjectId,
version: persistResponse.timeline.version,
favorite:
persistResponse.timeline.favorite != null
? persistResponse.timeline.favorite.filter(fav => fav.userName === userName)
: [],
};
}
public async persistTimeline(
@ -133,27 +148,26 @@ export class Timeline {
version: string | null,
timeline: SavedTimeline
): Promise<ResponseTimeline> {
if (timelineId == null) {
// Create new timeline
return {
code: 200,
message: 'success',
timeline: convertSavedObjectToSavedTimeline(
await this.libs.savedObjects
.getScopedSavedObjectsClient(request[internalFrameworkRequest])
.create(
timelineSavedObjectType,
pickSavedTimeline(
timelineId,
timeline,
request[internalFrameworkRequest].auth || null
)
)
),
};
}
try {
if (timelineId == null) {
// Create new timeline
return {
code: 200,
message: 'success',
timeline: convertSavedObjectToSavedTimeline(
await this.libs.savedObjects
.getScopedSavedObjectsClient(request[internalFrameworkRequest])
.create(
timelineSavedObjectType,
pickSavedTimeline(
timelineId,
timeline,
request[internalFrameworkRequest].auth || null
)
)
),
};
}
// Update Timeline
await this.libs.savedObjects
.getScopedSavedObjectsClient(request[internalFrameworkRequest])
@ -171,12 +185,26 @@ export class Timeline {
timeline: await this.getSavedTimeline(request, timelineId),
};
} catch (err) {
if (this.libs.savedObjects.SavedObjectsClient.errors.isConflictError(err)) {
if (
timelineId != null &&
this.libs.savedObjects.SavedObjectsClient.errors.isConflictError(err)
) {
return {
code: 409,
message: err.message,
timeline: await this.getSavedTimeline(request, timelineId),
};
} else if (getOr(null, 'output.statusCode', err) === 403) {
const timelineToReturn: TimelineResult = {
...timeline,
savedObjectId: '',
version: '',
};
return {
code: 403,
message: err.message,
timeline: timelineToReturn,
};
}
throw err;
}

View file

@ -12,10 +12,7 @@ import { DetailItem, GetTimelineDetailsQuery } from '../../../../plugins/siem/pu
import { KbnTestProvider } from './types';
type DetailsData = Array<
Pick<
DetailItem,
'category' | 'description' | 'example' | 'field' | 'type' | 'values' | 'originalValue'
> & {
Pick<DetailItem, 'field' | 'values' | 'originalValue'> & {
__typename: string;
}
>;
@ -25,618 +22,277 @@ const INDEX_NAME = 'filebeat-7.0.0-iot-2019.06';
const ID = 'QRhG1WgBqd-n62SwZYDT';
const EXPECTED_DATA: DetailItem[] = [
{
category: '_id',
description: 'Each document has an _id that uniquely identifies it',
example: 'Y-6TfmcB0WOhS6qyMv3s',
field: '_id',
type: 'keyword',
originalValue: 'QRhG1WgBqd-n62SwZYDT',
values: ['QRhG1WgBqd-n62SwZYDT'],
},
{
category: '_index',
description:
'An index is like a database in a relational database. It has a mapping which defines multiple types. An index is a logical namespace which maps to one or more primary shards and can have zero or more replica shards.',
example: 'auditbeat-8.0.0-2019.02.19-000001',
field: '_index',
type: 'keyword',
originalValue: 'filebeat-7.0.0-iot-2019.06',
values: ['filebeat-7.0.0-iot-2019.06'],
},
{
category: '_type',
description: null,
example: null,
field: '_type',
type: 'keyword',
originalValue: '_doc',
values: ['_doc'],
},
{
category: '_score',
description: null,
example: null,
field: '_score',
type: 'long',
originalValue: 1,
values: ['1'],
},
{
category: '@timestamp',
description:
'Date/time when the event originated. For log events this is the date/time when the event was generated, and not when it was read. Required field for all events.',
example: '2016-05-23T08:05:34.853Z',
field: '@timestamp',
type: 'date',
originalValue: '2019-02-10T02:39:44.107Z',
values: ['2019-02-10T02:39:44.107Z'],
originalValue: '2019-02-10T02:39:44.107Z',
},
{ field: '@version', values: ['1'], originalValue: '1' },
{
category: '@version',
description: null,
example: null,
field: '@version',
type: 'keyword',
originalValue: '1',
values: ['1'],
},
{
category: 'agent',
description:
'Ephemeral identifier of this agent (if one exists). This id normally changes across restarts, but `agent.id` does not.',
example: '8a4f500f',
field: 'agent.ephemeral_id',
type: 'keyword',
originalValue: '909cd6a1-527d-41a5-9585-a7fb5386f851',
values: ['909cd6a1-527d-41a5-9585-a7fb5386f851'],
originalValue: '909cd6a1-527d-41a5-9585-a7fb5386f851',
},
{
category: 'agent',
description: null,
example: null,
field: 'agent.hostname',
type: 'keyword',
originalValue: 'raspberrypi',
values: ['raspberrypi'],
originalValue: 'raspberrypi',
},
{
category: 'agent',
description:
'Unique identifier of this agent (if one exists). Example: For Beats this would be beat.id.',
example: '8a4f500d',
field: 'agent.id',
type: 'keyword',
originalValue: '4d3ea604-27e5-4ec7-ab64-44f82285d776',
values: ['4d3ea604-27e5-4ec7-ab64-44f82285d776'],
originalValue: '4d3ea604-27e5-4ec7-ab64-44f82285d776',
},
{
category: 'agent',
description:
'Type of the agent. The agent type stays always the same and should be given by the agent used. In case of Filebeat the agent would always be Filebeat also if two Filebeat instances are run on the same machine.',
example: 'filebeat',
field: 'agent.type',
type: 'keyword',
originalValue: 'filebeat',
values: ['filebeat'],
originalValue: 'filebeat',
},
{ field: 'agent.version', values: ['7.0.0'], originalValue: '7.0.0' },
{
category: 'agent',
description: 'Version of the agent.',
example: '6.0.0-rc2',
field: 'agent.version',
type: 'keyword',
originalValue: '7.0.0',
values: ['7.0.0'],
},
{
category: 'destination',
description: 'Destination domain.',
example: null,
field: 'destination.domain',
type: 'keyword',
originalValue: 's3-iad-2.cf.dash.row.aiv-cdn.net',
values: ['s3-iad-2.cf.dash.row.aiv-cdn.net'],
originalValue: 's3-iad-2.cf.dash.row.aiv-cdn.net',
},
{
category: 'destination',
description: 'IP address of the destination. Can be one or multiple IPv4 or IPv6 addresses.',
example: null,
field: 'destination.ip',
type: 'ip',
originalValue: '10.100.7.196',
values: ['10.100.7.196'],
originalValue: '10.100.7.196',
},
{ field: 'destination.port', values: ['40684'], originalValue: 40684 },
{
category: 'destination',
description: 'Port of the destination.',
example: null,
field: 'destination.port',
type: 'long',
originalValue: 40684,
values: ['40684'],
},
{
category: 'ecs',
description:
'ECS version this event conforms to. `ecs.version` is a required field and must exist in all events. When querying across multiple indices -- which may conform to slightly different ECS versions -- this field lets integrations adjust to the schema version of the events. The current version is 1.0.0-beta2 .',
example: '1.0.0-beta2',
field: 'ecs.version',
type: 'keyword',
originalValue: '1.0.0-beta2',
values: ['1.0.0-beta2'],
originalValue: '1.0.0-beta2',
},
{
category: 'event',
description:
'Name of the dataset. The concept of a `dataset` (fileset / metricset) is used in Beats as a subset of modules. It contains the information which is currently stored in metricset.name and metricset.module or fileset.name.',
example: 'stats',
field: 'event.dataset',
type: 'keyword',
originalValue: 'suricata.eve',
values: ['suricata.eve'],
originalValue: 'suricata.eve',
},
{
category: 'event',
description:
'event.end contains the date when the event ended or when the activity was last observed.',
example: null,
field: 'event.end',
type: 'date',
originalValue: '2019-02-10T02:39:44.107Z',
values: ['2019-02-10T02:39:44.107Z'],
originalValue: '2019-02-10T02:39:44.107Z',
},
{ field: 'event.kind', values: ['event'], originalValue: 'event' },
{
category: 'event',
description:
'The kind of the event. This gives information about what type of information the event contains, without being specific to the contents of the event. Examples are `event`, `state`, `alarm`. Warning: In future versions of ECS, we plan to provide a list of acceptable values for this field, please use with caution.',
example: 'state',
field: 'event.kind',
type: 'keyword',
originalValue: 'event',
values: ['event'],
},
{
category: 'event',
description:
'Name of the module this data is coming from. This information is coming from the modules used in Beats or Logstash.',
example: 'mysql',
field: 'event.module',
type: 'keyword',
originalValue: 'suricata',
values: ['suricata'],
originalValue: 'suricata',
},
{
category: 'event',
description: 'Reserved for future usage. Please avoid using this field for user data.',
example: null,
field: 'event.type',
type: 'keyword',
originalValue: 'fileinfo',
values: ['fileinfo'],
originalValue: 'fileinfo',
},
{
category: 'file',
description: 'Path to the file.',
example: null,
field: 'file.path',
type: 'keyword',
originalValue:
'/dm/2$XTMWANo0Q2RZKlH-95UoAahZrOg~/0a9a/bf72/e1da/4c20-919e-0cbabcf7bfe8/75f50c57-d25f-4e97-9e37-01b9f5caa293_audio_13.mp4',
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',
},
{ field: 'file.size', values: ['48277'], originalValue: 48277 },
{ field: 'fileset.name', values: ['eve'], originalValue: 'eve' },
{ field: 'flow.locality', values: ['public'], originalValue: 'public' },
{
category: 'file',
description: 'File size in bytes (field is only added when `type` is `file`).',
example: null,
field: 'file.size',
type: 'long',
originalValue: 48277,
values: ['48277'],
},
{
category: 'fileset',
description: null,
example: null,
field: 'fileset.name',
type: 'keyword',
originalValue: 'eve',
values: ['eve'],
},
{
category: 'flow',
description: null,
example: null,
field: 'flow.locality',
type: 'keyword',
originalValue: 'public',
values: ['public'],
},
{
category: 'host',
description: 'Operating system architecture.',
example: 'x86_64',
field: 'host.architecture',
type: 'keyword',
originalValue: 'armv7l',
values: ['armv7l'],
originalValue: 'armv7l',
},
{
category: 'host',
description:
'Hostname of the host. It normally contains what the `hostname` command returns on the host machine.',
example: null,
field: 'host.hostname',
type: 'keyword',
originalValue: 'raspberrypi',
values: ['raspberrypi'],
originalValue: 'raspberrypi',
},
{
category: 'host',
description:
'Unique host id. As hostname is not always unique, use values that are meaningful in your environment. Example: The current usage of `beat.name`.',
example: null,
field: 'host.id',
type: 'keyword',
originalValue: 'b19a781f683541a7a25ee345133aa399',
values: ['b19a781f683541a7a25ee345133aa399'],
originalValue: 'b19a781f683541a7a25ee345133aa399',
},
{
category: 'host',
description:
'Name of the host. It can contain what `hostname` returns on Unix systems, the fully qualified domain name, or a name specified by the user. The sender decides which value to use.',
example: null,
field: 'host.name',
type: 'keyword',
originalValue: 'raspberrypi',
values: ['raspberrypi'],
originalValue: 'raspberrypi',
},
{
category: 'host',
description: null,
example: null,
field: 'host.os.codename',
type: 'keyword',
originalValue: 'stretch',
values: ['stretch'],
originalValue: 'stretch',
},
{ field: 'host.os.family', values: [''], originalValue: '' },
{
category: 'host',
description: 'OS family (such as redhat, debian, freebsd, windows).',
example: 'debian',
field: 'host.os.family',
type: 'keyword',
originalValue: '',
values: [''],
},
{
category: 'host',
description: 'Operating system kernel version as a raw string.',
example: '4.4.0-112-generic',
field: 'host.os.kernel',
type: 'keyword',
originalValue: '4.14.50-v7+',
values: ['4.14.50-v7+'],
originalValue: '4.14.50-v7+',
},
{
category: 'host',
description: 'Operating system name, without the version.',
example: 'Mac OS X',
field: 'host.os.name',
type: 'keyword',
originalValue: 'Raspbian GNU/Linux',
values: ['Raspbian GNU/Linux'],
originalValue: 'Raspbian GNU/Linux',
},
{
category: 'host',
description: 'Operating system platform (such centos, ubuntu, windows).',
example: 'darwin',
field: 'host.os.platform',
type: 'keyword',
originalValue: 'raspbian',
values: ['raspbian'],
originalValue: 'raspbian',
},
{
category: 'host',
description: 'Operating system version as a raw string.',
example: '10.14.1',
field: 'host.os.version',
type: 'keyword',
originalValue: '9 (stretch)',
values: ['9 (stretch)'],
originalValue: '9 (stretch)',
},
{ field: 'http.request.method', values: ['get'], originalValue: 'get' },
{
category: 'http',
description:
'Http request method. The field value must be normalized to lowercase for querying. See "Lowercase Capitalization" in the "Implementing ECS" section.',
example: 'get, post, put',
field: 'http.request.method',
type: 'keyword',
originalValue: 'get',
values: ['get'],
},
{
category: 'http',
description: 'Size in bytes of the response body.',
example: '887',
field: 'http.response.body.bytes',
type: 'long',
originalValue: 48277,
values: ['48277'],
originalValue: 48277,
},
{
category: 'http',
description: 'Http response status code.',
example: '404',
field: 'http.response.status_code',
type: 'long',
originalValue: 206,
values: ['206'],
originalValue: 206,
},
{ field: 'input.type', values: ['log'], originalValue: 'log' },
{
category: 'input',
description: null,
example: null,
field: 'input.type',
type: 'keyword',
originalValue: 'log',
values: ['log'],
},
{
category: 'labels',
description: null,
example: null,
field: 'labels.pipeline',
type: 'keyword',
originalValue: 'filebeat-7.0.0-suricata-eve-pipeline',
values: ['filebeat-7.0.0-suricata-eve-pipeline'],
originalValue: 'filebeat-7.0.0-suricata-eve-pipeline',
},
{
category: 'log',
description: null,
example: null,
field: 'log.file.path',
type: 'keyword',
originalValue: '/var/log/suricata/eve.json',
values: ['/var/log/suricata/eve.json'],
originalValue: '/var/log/suricata/eve.json',
},
{
category: 'log',
description: null,
example: null,
field: 'log.offset',
type: 'long',
originalValue: 1856288115,
values: ['1856288115'],
originalValue: 1856288115,
},
{ field: 'network.name', values: ['iot'], originalValue: 'iot' },
{ field: 'network.protocol', values: ['http'], originalValue: 'http' },
{ field: 'network.transport', values: ['tcp'], originalValue: 'tcp' },
{
category: 'network',
description: 'Name given by operators to sections of their network.',
example: 'Guest Wifi',
field: 'network.name',
type: 'keyword',
originalValue: 'iot',
values: ['iot'],
},
{
category: 'network',
description:
'L7 Network protocol name. ex. http, lumberjack, transport protocol. The field value must be normalized to lowercase for querying. See "Lowercase Capitalization" in the "Implementing ECS" section.',
example: 'http',
field: 'network.protocol',
type: 'keyword',
originalValue: 'http',
values: ['http'],
},
{
category: 'network',
description:
'Same as network.iana_number, but instead using the Keyword name of the transport layer (udp, tcp, ipv6-icmp, etc.) The field value must be normalized to lowercase for querying. See "Lowercase Capitalization" in the "Implementing ECS" section.',
example: 'tcp',
field: 'network.transport',
type: 'keyword',
originalValue: 'tcp',
values: ['tcp'],
},
{
category: 'service',
description:
'The type of the service data is collected from. The type can be used to group and correlate logs and metrics from one service type. Example: If logs or metrics are collected from Elasticsearch, `service.type` would be `elasticsearch`.',
example: 'elasticsearch',
field: 'service.type',
type: 'keyword',
values: ['suricata'],
originalValue: 'suricata',
values: ['suricata'],
},
{ field: 'source.as.num', values: ['16509'], originalValue: 16509 },
{
category: 'source',
description: null,
example: null,
field: 'source.as.num',
type: 'long',
originalValue: 16509,
values: ['16509'],
},
{
category: 'source',
description: null,
example: null,
field: 'source.as.org',
type: 'keyword',
originalValue: 'Amazon.com, Inc.',
values: ['Amazon.com, Inc.'],
originalValue: 'Amazon.com, Inc.',
},
{
category: 'source',
description: 'Source domain.',
example: null,
field: 'source.domain',
type: 'keyword',
originalValue: 'server-54-239-219-210.jfk51.r.cloudfront.net',
values: ['server-54-239-219-210.jfk51.r.cloudfront.net'],
originalValue: 'server-54-239-219-210.jfk51.r.cloudfront.net',
},
{
category: 'source',
description: 'City name.',
example: 'Montreal',
field: 'source.geo.city_name',
type: 'keyword',
originalValue: 'Seattle',
values: ['Seattle'],
originalValue: 'Seattle',
},
{
category: 'source',
description: 'Name of the continent.',
example: 'North America',
field: 'source.geo.continent_name',
type: 'keyword',
originalValue: 'North America',
values: ['North America'],
originalValue: 'North America',
},
{
category: 'source',
description: 'Country ISO code.',
example: 'CA',
field: 'source.geo.country_iso_code',
type: 'keyword',
originalValue: 'US',
values: ['US'],
originalValue: 'US',
},
{
field: 'source.geo.location.lat',
values: ['47.6103'],
originalValue: 47.6103,
},
{
field: 'source.geo.location.lon',
values: ['-122.3341'],
originalValue: -122.3341,
},
{
category: 'source',
description: 'Region ISO code.',
example: 'CA-QC',
field: 'source.geo.region_iso_code',
type: 'keyword',
originalValue: 'US-WA',
values: ['US-WA'],
originalValue: 'US-WA',
},
{
category: 'source',
description: 'Region name.',
example: 'Quebec',
field: 'source.geo.region_name',
type: 'keyword',
originalValue: 'Washington',
values: ['Washington'],
originalValue: 'Washington',
},
{
category: 'source',
description: 'IP address of the source. Can be one or multiple IPv4 or IPv6 addresses.',
example: null,
field: 'source.ip',
type: 'ip',
originalValue: '54.239.219.210',
values: ['54.239.219.210'],
originalValue: '54.239.219.210',
},
{ field: 'source.port', values: ['80'], originalValue: 80 },
{
category: 'source',
description: 'Port of the source.',
example: null,
field: 'source.port',
type: 'long',
originalValue: 80,
values: ['80'],
},
{
category: 'suricata',
description: null,
example: null,
field: 'suricata.eve.fileinfo.state',
type: 'keyword',
originalValue: 'CLOSED',
values: ['CLOSED'],
originalValue: 'CLOSED',
},
{
category: 'suricata',
description: null,
example: null,
field: 'suricata.eve.fileinfo.tx_id',
type: 'long',
originalValue: 301,
values: ['301'],
originalValue: 301,
},
{
category: 'suricata',
description: null,
example: null,
field: 'suricata.eve.flow_id',
type: 'keyword',
originalValue: 196625917175466,
values: ['196625917175466'],
originalValue: 196625917175466,
},
{
category: 'suricata',
description: null,
example: null,
field: 'suricata.eve.http.http_content_type',
type: 'keyword',
originalValue: 'video/mp4',
values: ['video/mp4'],
originalValue: 'video/mp4',
},
{
category: 'suricata',
description: null,
example: null,
field: 'suricata.eve.http.protocol',
type: 'keyword',
originalValue: 'HTTP/1.1',
values: ['HTTP/1.1'],
originalValue: 'HTTP/1.1',
},
{
category: 'suricata',
description: null,
example: null,
field: 'suricata.eve.in_iface',
type: 'keyword',
originalValue: 'eth0',
values: ['eth0'],
originalValue: 'eth0',
},
{ field: 'tags', values: ['suricata'], originalValue: ['suricata'] },
{
category: 'tags',
description: 'List of keywords used to tag each event.',
example: '["production", "env2"]',
field: 'tags',
type: 'keyword',
originalValue: ['suricata'],
values: ['suricata'],
},
{
category: 'url',
description:
'Domain of the request, such as "www.elastic.co". In some cases a URL may refer to an IP and/or port directly, without a domain name. In this case, the IP address would go to the `domain` field.',
example: 'www.elastic.co',
field: 'url.domain',
type: 'keyword',
originalValue: 's3-iad-2.cf.dash.row.aiv-cdn.net',
values: ['s3-iad-2.cf.dash.row.aiv-cdn.net'],
originalValue: 's3-iad-2.cf.dash.row.aiv-cdn.net',
},
{
category: 'url',
description:
'Unmodified original url as seen in the event source. Note that in network monitoring, the observed URL may be a full URL, whereas in access logs, the URL is often just represented as a path. This field is meant to represent the URL as it was observed, complete or not.',
example: 'https://www.elastic.co:443/search?q=elasticsearch#top or /search?q=elasticsearch',
field: 'url.original',
type: 'keyword',
originalValue:
'/dm/2$XTMWANo0Q2RZKlH-95UoAahZrOg~/0a9a/bf72/e1da/4c20-919e-0cbabcf7bfe8/75f50c57-d25f-4e97-9e37-01b9f5caa293_audio_13.mp4',
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',
},
{
category: 'url',
description: 'Path of the request, such as "/search".',
example: null,
field: 'url.path',
type: 'keyword',
originalValue:
'/dm/2$XTMWANo0Q2RZKlH-95UoAahZrOg~/0a9a/bf72/e1da/4c20-919e-0cbabcf7bfe8/75f50c57-d25f-4e97-9e37-01b9f5caa293_audio_13.mp4',
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',
},
{
field: '_index',
values: ['filebeat-7.0.0-iot-2019.06'],
originalValue: 'filebeat-7.0.0-iot-2019.06',
},
{ field: '_type', values: ['_doc'], originalValue: '_doc' },
{
field: '_id',
values: ['QRhG1WgBqd-n62SwZYDT'],
originalValue: 'QRhG1WgBqd-n62SwZYDT',
},
{ field: '_score', values: ['1'], originalValue: 1 },
];
const timelineDetailsTests: KbnTestProvider = ({ getService }) => {