[Security_Solution] Split up indices (#69589)

* Fixing resolver alert generation

* Splitting indices up

* Removing tests that could randomly fail because of the generation code

* Adding support for multiple indices

* Updating archives with the new index names

* Removing alerts data stream

* Switching to process instead of fake

Co-authored-by: Elastic Machine <elasticmachine@users.noreply.github.com>
This commit is contained in:
Jonathan Buttner 2020-06-23 12:35:02 -04:00 committed by GitHub
parent 98a897736b
commit 5fb206f67f
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
24 changed files with 130 additions and 74 deletions

View file

@ -4,7 +4,8 @@
* you may not use this file except in compliance with the Elastic License.
*/
export const eventsIndexPattern = 'events-endpoint-*';
export const eventsIndexPattern = 'logs-endpoint.events.*';
export const alertsIndexPattern = 'logs-endpoint.alerts-*';
export const metadataIndexPattern = 'metrics-endpoint.metadata-*';
export const policyIndexPattern = 'metrics-endpoint.policy-*';
export const telemetryIndexPattern = 'metrics-endpoint.telemetry-*';

View file

@ -16,6 +16,7 @@ export async function indexHostsAndAlerts(
metadataIndex: string,
policyIndex: string,
eventIndex: string,
alertIndex: string,
alertsPerHost: number,
options: TreeOptions = {}
) {
@ -23,7 +24,7 @@ export async function indexHostsAndAlerts(
for (let i = 0; i < numHosts; i++) {
const generator = new EndpointDocGenerator(random);
await indexHostDocs(numDocs, client, metadataIndex, policyIndex, generator);
await indexAlerts(client, eventIndex, generator, alertsPerHost, options);
await indexAlerts(client, eventIndex, alertIndex, generator, alertsPerHost, options);
}
await client.indices.refresh({
index: eventIndex,
@ -65,7 +66,8 @@ async function indexHostDocs(
async function indexAlerts(
client: Client,
index: string,
eventIndex: string,
alertIndex: string,
generator: EndpointDocGenerator,
numAlerts: number,
options: TreeOptions = {}
@ -82,9 +84,14 @@ async function indexAlerts(
}
const body = resolverDocs.reduce(
// eslint-disable-next-line @typescript-eslint/no-explicit-any
(array: Array<Record<string, any>>, doc) => (
array.push({ create: { _index: index } }, doc), array
),
(array: Array<Record<string, any>>, doc) => {
let index = eventIndex;
if (doc.event.kind === 'alert') {
index = alertIndex;
}
array.push({ create: { _index: index } }, doc);
return array;
},
[]
);
await client.bulk({ body, refresh: 'true' });

View file

@ -4,7 +4,7 @@
* you may not use this file except in compliance with the Elastic License.
*/
import { eventsIndexPattern } from '../../../common/endpoint/constants';
import { alertsIndexPattern } from '../../../common/endpoint/constants';
import { IIndexPattern } from '../../../../../../src/plugins/data/public';
import {
AlertResultList,
@ -49,10 +49,10 @@ export const alertMiddlewareFactory: ImmutableMiddlewareFactory<AlertListState>
async function fetchIndexPatterns(): Promise<IIndexPattern[]> {
const { indexPatterns } = depsStart.data;
const fields = await indexPatterns.getFieldsForWildcard({
pattern: eventsIndexPattern,
pattern: alertsIndexPattern,
});
const indexPattern: IIndexPattern = {
title: eventsIndexPattern,
title: alertsIndexPattern,
fields,
};

View file

@ -95,19 +95,25 @@ async function main() {
eventIndex: {
alias: 'ei',
describe: 'index to store events in',
default: 'events-endpoint-1',
default: 'logs-endpoint.events.process-default',
type: 'string',
},
alertIndex: {
alias: 'ai',
describe: 'index to store alerts in',
default: 'logs-endpoint.alerts-default',
type: 'string',
},
metadataIndex: {
alias: 'mi',
describe: 'index to store host metadata in',
default: 'metrics-endpoint.metadata-default-1',
default: 'metrics-endpoint.metadata-default',
type: 'string',
},
policyIndex: {
alias: 'pi',
describe: 'index to store host policy in',
default: 'metrics-endpoint.policy-default-1',
default: 'metrics-endpoint.policy-default',
type: 'string',
},
ancestors: {
@ -192,7 +198,10 @@ async function main() {
const client = new Client(clientOptions);
if (argv.delete) {
await deleteIndices([argv.eventIndex, argv.metadataIndex, argv.policyIndex], client);
await deleteIndices(
[argv.eventIndex, argv.metadataIndex, argv.policyIndex, argv.alertIndex],
client
);
}
let seed = argv.seed;
@ -209,6 +218,7 @@ async function main() {
argv.metadataIndex,
argv.policyIndex,
argv.eventIndex,
argv.alertIndex,
argv.alertsPerHost,
{
ancestors: argv.ancestors,

View file

@ -5,7 +5,7 @@
*/
import { GetResponse } from 'elasticsearch';
import { KibanaRequest, RequestHandler } from 'kibana/server';
import { eventsIndexPattern } from '../../../../../common/endpoint/constants';
import { alertsIndexPattern } from '../../../../../common/endpoint/constants';
import { AlertEvent } from '../../../../../common/endpoint/types';
import { EndpointAppContext } from '../../../types';
import { AlertDetailsRequestParams } from '../../../../../common/endpoint_alerts/types';
@ -34,7 +34,7 @@ export const alertDetailsHandlerWrapper = function (
ctx,
req.params,
response,
eventsIndexPattern
alertsIndexPattern
);
const currentHostInfo = await getHostData(

View file

@ -4,7 +4,7 @@
* you may not use this file except in compliance with the Elastic License.
*/
import { RequestHandler } from 'kibana/server';
import { eventsIndexPattern } from '../../../../../common/endpoint/constants';
import { alertsIndexPattern } from '../../../../../common/endpoint/constants';
import { EndpointAppContext } from '../../../types';
import { searchESForAlerts } from '../lib';
import { getRequestData, mapToAlertResultList } from './lib';
@ -23,7 +23,7 @@ export const alertListHandlerWrapper = function (
const response = await searchESForAlerts(
ctx.core.elasticsearch.legacy.client,
reqData,
eventsIndexPattern
alertsIndexPattern
);
const mappedBody = await mapToAlertResultList(ctx, endpointAppContext, reqData, response);
return res.ok({ body: mappedBody });

View file

@ -7,7 +7,7 @@
import { TypeOf } from '@kbn/config-schema';
import { RequestHandler, Logger } from 'kibana/server';
import { validateAlerts } from '../../../../common/endpoint/schema/resolver';
import { eventsIndexPattern } from '../../../../common/endpoint/constants';
import { alertsIndexPattern, eventsIndexPattern } from '../../../../common/endpoint/constants';
import { Fetcher } from './utils/fetch';
import { EndpointAppContext } from '../../types';
@ -23,7 +23,7 @@ export function handleAlerts(
try {
const client = context.core.elasticsearch.legacy.client;
const fetcher = new Fetcher(client, id, eventsIndexPattern, endpointID);
const fetcher = new Fetcher(client, id, eventsIndexPattern, alertsIndexPattern, endpointID);
return res.ok({
body: await fetcher.alerts(alerts, afterAlert),

View file

@ -6,7 +6,7 @@
import { RequestHandler, Logger } from 'kibana/server';
import { TypeOf } from '@kbn/config-schema';
import { eventsIndexPattern } from '../../../../common/endpoint/constants';
import { eventsIndexPattern, alertsIndexPattern } from '../../../../common/endpoint/constants';
import { validateAncestry } from '../../../../common/endpoint/schema/resolver';
import { Fetcher } from './utils/fetch';
import { EndpointAppContext } from '../../types';
@ -23,7 +23,7 @@ export function handleAncestry(
try {
const client = context.core.elasticsearch.legacy.client;
const fetcher = new Fetcher(client, id, eventsIndexPattern, endpointID);
const fetcher = new Fetcher(client, id, eventsIndexPattern, alertsIndexPattern, endpointID);
const ancestorInfo = await fetcher.ancestors(ancestors);
return res.ok({

View file

@ -6,7 +6,7 @@
import { RequestHandler, Logger } from 'kibana/server';
import { TypeOf } from '@kbn/config-schema';
import { eventsIndexPattern } from '../../../../common/endpoint/constants';
import { eventsIndexPattern, alertsIndexPattern } from '../../../../common/endpoint/constants';
import { validateChildren } from '../../../../common/endpoint/schema/resolver';
import { Fetcher } from './utils/fetch';
import { EndpointAppContext } from '../../types';
@ -22,7 +22,7 @@ export function handleChildren(
} = req;
try {
const client = context.core.elasticsearch.legacy.client;
const fetcher = new Fetcher(client, id, eventsIndexPattern, endpointID);
const fetcher = new Fetcher(client, id, eventsIndexPattern, alertsIndexPattern, endpointID);
return res.ok({
body: await fetcher.children(children, generations, afterChild),

View file

@ -6,7 +6,7 @@
import { TypeOf } from '@kbn/config-schema';
import { RequestHandler, Logger } from 'kibana/server';
import { eventsIndexPattern } from '../../../../common/endpoint/constants';
import { eventsIndexPattern, alertsIndexPattern } from '../../../../common/endpoint/constants';
import { validateEvents } from '../../../../common/endpoint/schema/resolver';
import { Fetcher } from './utils/fetch';
import { EndpointAppContext } from '../../types';
@ -23,7 +23,7 @@ export function handleEvents(
try {
const client = context.core.elasticsearch.legacy.client;
const fetcher = new Fetcher(client, id, eventsIndexPattern, endpointID);
const fetcher = new Fetcher(client, id, eventsIndexPattern, alertsIndexPattern, endpointID);
return res.ok({
body: await fetcher.events(events, afterEvent),

View file

@ -15,7 +15,7 @@ import { JsonObject } from '../../../../../../../../src/plugins/kibana_utils/com
export class AlertsQuery extends ResolverQuery<PaginatedResults> {
constructor(
private readonly pagination: PaginationBuilder,
indexPattern: string,
indexPattern: string | string[],
endpointID?: string
) {
super(indexPattern, endpointID);

View file

@ -25,13 +25,16 @@ export abstract class ResolverQuery<T> implements MSearchQuery {
* we need `endpointID` for legacy data is because we don't have a cross endpoint unique identifier for process
* events. Instead we use `unique_pid/ppid` and `endpointID` to uniquely identify a process event.
*/
constructor(private readonly indexPattern: string, private readonly endpointID?: string) {}
constructor(
private readonly indexPattern: string | string[],
private readonly endpointID?: string
) {}
private static createIdsArray(ids: string | string[]): string[] {
return Array.isArray(ids) ? ids : [ids];
}
private buildQuery(ids: string | string[]): { query: JsonObject; index: string } {
private buildQuery(ids: string | string[]): { query: JsonObject; index: string | string[] } {
const idsArray = ResolverQuery.createIdsArray(ids);
if (this.endpointID) {
return { query: this.legacyQuery(this.endpointID, idsArray), index: legacyEventIndexPattern };

View file

@ -15,7 +15,7 @@ import { JsonObject } from '../../../../../../../../src/plugins/kibana_utils/com
export class ChildrenQuery extends ResolverQuery<PaginatedResults> {
constructor(
private readonly pagination: PaginationBuilder,
indexPattern: string,
indexPattern: string | string[],
endpointID?: string
) {
super(indexPattern, endpointID);

View file

@ -15,7 +15,7 @@ import { JsonObject } from '../../../../../../../../src/plugins/kibana_utils/com
export class EventsQuery extends ResolverQuery<PaginatedResults> {
constructor(
private readonly pagination: PaginationBuilder,
indexPattern: string,
indexPattern: string | string[],
endpointID?: string
) {
super(indexPattern, endpointID);

View file

@ -6,7 +6,7 @@
import { RequestHandler, Logger } from 'kibana/server';
import { TypeOf } from '@kbn/config-schema';
import { eventsIndexPattern } from '../../../../common/endpoint/constants';
import { eventsIndexPattern, alertsIndexPattern } from '../../../../common/endpoint/constants';
import { validateTree } from '../../../../common/endpoint/schema/resolver';
import { Fetcher } from './utils/fetch';
import { Tree } from './utils/tree';
@ -34,7 +34,7 @@ export function handleTree(
try {
const client = context.core.elasticsearch.legacy.client;
const fetcher = new Fetcher(client, id, eventsIndexPattern, endpointID);
const fetcher = new Fetcher(client, id, eventsIndexPattern, alertsIndexPattern, endpointID);
const [childrenNodes, ancestry, relatedEvents, relatedAlerts] = await Promise.all([
fetcher.children(children, generations, afterChild),

View file

@ -39,9 +39,13 @@ export class Fetcher {
*/
private readonly id: string,
/**
* Index pattern for searching ES
* Index pattern for searching ES for events
*/
private readonly indexPattern: string,
private readonly eventsIndexPattern: string,
/**
* Index pattern for searching ES for alerts
*/
private readonly alertsIndexPattern: string,
/**
* This is used for searching legacy events
*/
@ -109,7 +113,7 @@ export class Fetcher {
public async alerts(limit: number, after?: string): Promise<ResolverRelatedAlerts> {
const query = new AlertsQuery(
PaginationBuilder.createBuilder(limit, after),
this.indexPattern,
this.alertsIndexPattern,
this.endpointID
);
@ -140,7 +144,7 @@ export class Fetcher {
}
private async getNode(entityID: string): Promise<LifecycleNode | undefined> {
const query = new LifecycleQuery(this.indexPattern, this.endpointID);
const query = new LifecycleQuery(this.eventsIndexPattern, this.endpointID);
const results = await query.search(this.client, entityID);
if (results.length === 0) {
return;
@ -172,7 +176,7 @@ export class Fetcher {
return;
}
const query = new LifecycleQuery(this.indexPattern, this.endpointID);
const query = new LifecycleQuery(this.eventsIndexPattern, this.endpointID);
const results = await query.search(this.client, ancestors);
if (results.length === 0) {
@ -210,7 +214,7 @@ export class Fetcher {
private async doEvents(limit: number, after?: string) {
const query = new EventsQuery(
PaginationBuilder.createBuilder(limit, after),
this.indexPattern,
this.eventsIndexPattern,
this.endpointID
);
@ -243,10 +247,10 @@ export class Fetcher {
const childrenQuery = new ChildrenQuery(
PaginationBuilder.createBuilder(limit, after),
this.indexPattern,
this.eventsIndexPattern,
this.endpointID
);
const lifecycleQuery = new LifecycleQuery(this.indexPattern, this.endpointID);
const lifecycleQuery = new LifecycleQuery(this.eventsIndexPattern, this.endpointID);
const { totals, results } = await childrenQuery.search(this.client, ids);
if (results.length === 0) {
@ -262,7 +266,10 @@ export class Fetcher {
}
private async doStats(tree: Tree) {
const statsQuery = new StatsQuery(this.indexPattern, this.endpointID);
const statsQuery = new StatsQuery(
[this.eventsIndexPattern, this.alertsIndexPattern],
this.endpointID
);
const ids = tree.ids();
const res = await statsQuery.search(this.client, ids);
const alerts = res.alerts;

View file

@ -9,6 +9,7 @@ import {
deleteEventsStream,
deleteMetadataStream,
deletePolicyStream,
deleteAlertsStream,
} from '../data_stream_helper';
import { indexHostsAndAlerts } from '../../../../../plugins/security_solution/common/endpoint/index_data';
@ -42,9 +43,10 @@ export default function ({ getService }: FtrProviderContext) {
'alerts-seed',
numberOfHosts,
1,
'metrics-endpoint.metadata-default-1',
'metrics-endpoint.policy-default-1',
'events-endpoint-1',
'metrics-endpoint.metadata-default',
'metrics-endpoint.policy-default',
'logs-endpoint.events.process-default',
'logs-endpoint.alerts-default',
numberOfAlertsPerHost
);
});
@ -54,6 +56,7 @@ export default function ({ getService }: FtrProviderContext) {
// to do it manually
await Promise.all([
deleteEventsStream(getService),
deleteAlertsStream(getService),
deleteMetadataStream(getService),
deletePolicyStream(getService),
]);

View file

@ -8,6 +8,7 @@ import { Client } from '@elastic/elasticsearch';
import {
metadataIndexPattern,
eventsIndexPattern,
alertsIndexPattern,
policyIndexPattern,
} from '../../../../plugins/security_solution/common/endpoint/constants';
@ -32,6 +33,10 @@ export async function deleteEventsStream(getService: (serviceName: 'es') => Clie
await deleteDataStream(getService, eventsIndexPattern);
}
export async function deleteAlertsStream(getService: (serviceName: 'es') => Client) {
await deleteDataStream(getService, alertsIndexPattern);
}
export async function deletePolicyStream(getService: (serviceName: 'es') => Client) {
await deleteDataStream(getService, policyIndexPattern);
}

View file

@ -178,7 +178,11 @@ const compareArrays = (
* @param relatedEvents the related events received for a particular node
* @param categories the related event info used when generating the resolver tree
*/
const verifyStats = (stats: ResolverNodeStats | undefined, categories: RelatedEventInfo[]) => {
const verifyStats = (
stats: ResolverNodeStats | undefined,
categories: RelatedEventInfo[],
relatedAlerts: number
) => {
expect(stats).to.not.be(undefined);
let totalExpEvents = 0;
for (const cat of categories) {
@ -196,6 +200,7 @@ const verifyStats = (stats: ResolverNodeStats | undefined, categories: RelatedEv
totalExpEvents += cat.count;
}
expect(stats?.events.total).to.be(totalExpEvents);
expect(stats?.totalAlerts);
};
/**
@ -204,9 +209,13 @@ const verifyStats = (stats: ResolverNodeStats | undefined, categories: RelatedEv
* @param nodes an array of lifecycle nodes that should have a stats field defined
* @param categories the related event info used when generating the resolver tree
*/
const verifyLifecycleStats = (nodes: LifecycleNode[], categories: RelatedEventInfo[]) => {
const verifyLifecycleStats = (
nodes: LifecycleNode[],
categories: RelatedEventInfo[],
relatedAlerts: number
) => {
for (const node of nodes) {
verifyStats(node.stats, categories);
verifyStats(node.stats, categories, relatedAlerts);
}
};
@ -220,13 +229,13 @@ export default function resolverAPIIntegrationTests({ getService }: FtrProviderC
{ category: RelatedEventCategory.File, count: 1 },
{ category: RelatedEventCategory.Registry, count: 1 },
];
const relatedAlerts = 4;
let resolverTrees: GeneratedTrees;
let tree: Tree;
const treeOptions: Options = {
ancestors: 5,
relatedEvents: relatedEventsToGen,
relatedAlerts: 4,
relatedAlerts,
children: 3,
generations: 2,
percentTerminated: 100,
@ -697,11 +706,11 @@ export default function resolverAPIIntegrationTests({ getService }: FtrProviderC
expect(body.children.nextChild).to.equal(null);
expect(body.children.childNodes.length).to.equal(12);
verifyChildren(body.children.childNodes, tree, 4, 3);
verifyLifecycleStats(body.children.childNodes, relatedEventsToGen);
verifyLifecycleStats(body.children.childNodes, relatedEventsToGen, relatedAlerts);
expect(body.ancestry.nextAncestor).to.equal(null);
verifyAncestry(body.ancestry.ancestors, tree, true);
verifyLifecycleStats(body.ancestry.ancestors, relatedEventsToGen);
verifyLifecycleStats(body.ancestry.ancestors, relatedEventsToGen, relatedAlerts);
expect(body.relatedEvents.nextEvent).to.equal(null);
compareArrays(tree.origin.relatedEvents, body.relatedEvents.events, true);
@ -710,7 +719,7 @@ export default function resolverAPIIntegrationTests({ getService }: FtrProviderC
compareArrays(tree.origin.relatedAlerts, body.relatedAlerts.alerts, true);
compareArrays(tree.origin.lifecycle, body.lifecycle, true);
verifyStats(body.stats, relatedEventsToGen);
verifyStats(body.stats, relatedEventsToGen, relatedAlerts);
});
});
});

View file

@ -25,7 +25,8 @@ export interface Options extends TreeOptions {
*/
export interface GeneratedTrees {
trees: Tree[];
index: string;
eventsIndex: string;
alertsIndex: string;
}
export function ResolverGeneratorProvider({ getService }: FtrProviderContext) {
@ -34,27 +35,30 @@ export function ResolverGeneratorProvider({ getService }: FtrProviderContext) {
return {
async createTrees(
options: Options,
eventsIndex: string = 'events-endpoint-1'
eventsIndex: string = 'logs-endpoint.events.process-default',
alertsIndex: string = 'logs-endpoint.alerts-default'
): Promise<GeneratedTrees> {
const allTrees: Tree[] = [];
const generator = new EndpointDocGenerator();
const numTrees = options.numTrees ?? 1;
for (let j = 0; j < numTrees; j++) {
const tree = generator.generateTree(options);
const body = tree.allEvents.reduce(
(array: Array<Record<string, any>>, doc) => (
/**
* We're using data streams which require that a bulk use `create` instead of `index`.
*/
array.push({ create: { _index: eventsIndex } }, doc), array
),
[]
);
const body = tree.allEvents.reduce((array: Array<Record<string, any>>, doc) => {
let index = eventsIndex;
if (doc.event.kind === 'alert') {
index = alertsIndex;
}
/**
* We're using data streams which require that a bulk use `create` instead of `index`.
*/
array.push({ create: { _index: index } }, doc);
return array;
}, []);
// force a refresh here otherwise the documents might not be available when the tests search for them
await client.bulk({ body, refresh: 'true' });
allTrees.push(tree);
}
return { trees: allTrees, index: eventsIndex };
return { trees: allTrees, eventsIndex, alertsIndex };
},
async deleteTrees(trees: GeneratedTrees) {
/**
@ -63,7 +67,14 @@ export function ResolverGeneratorProvider({ getService }: FtrProviderContext) {
* need to do raw requests here. Delete a data stream is slightly different than that of a regular index which
* is why we're using _data_stream here.
*/
await client.transport.request({ method: 'DELETE', path: `_data_stream/${trees.index}` });
await client.transport.request({
method: 'DELETE',
path: `_data_stream/${trees.eventsIndex}`,
});
await client.transport.request({
method: 'DELETE',
path: `_data_stream/${trees.alertsIndex}`,
});
},
};
}

View file

@ -2,7 +2,7 @@
"type": "doc",
"value": {
"id": "3KVN2G8BYQH1gtPUuYk7",
"index": "metrics-endpoint.metadata-default-1",
"index": "metrics-endpoint.metadata-default",
"source": {
"@timestamp": 1579881969541,
"agent": {
@ -57,7 +57,7 @@
"type": "doc",
"value": {
"id": "3aVN2G8BYQH1gtPUuYk7",
"index": "metrics-endpoint.metadata-default-1",
"index": "metrics-endpoint.metadata-default",
"source": {
"@timestamp": 1579881969541,
"agent": {
@ -111,7 +111,7 @@
"type": "doc",
"value": {
"id": "3qVN2G8BYQH1gtPUuYk7",
"index": "metrics-endpoint.metadata-default-1",
"index": "metrics-endpoint.metadata-default",
"source": {
"@timestamp": 1579881969541,
"agent": {
@ -163,7 +163,7 @@
"type": "doc",
"value": {
"id": "36VN2G8BYQH1gtPUuYk7",
"index": "metrics-endpoint.metadata-default-1",
"index": "metrics-endpoint.metadata-default",
"source": {
"@timestamp": 1579878369541,
"agent": {
@ -218,7 +218,7 @@
"type": "doc",
"value": {
"id": "4KVN2G8BYQH1gtPUuYk7",
"index": "metrics-endpoint.metadata-default-1",
"index": "metrics-endpoint.metadata-default",
"source": {
"@timestamp": 1579878369541,
"agent": {
@ -271,7 +271,7 @@
"type": "doc",
"value": {
"id": "4aVN2G8BYQH1gtPUuYk7",
"index": "metrics-endpoint.metadata-default-1",
"index": "metrics-endpoint.metadata-default",
"source": {
"@timestamp": 1579878369541,
"agent": {
@ -324,7 +324,7 @@
"type": "doc",
"value": {
"id": "4qVN2G8BYQH1gtPUuYk7",
"index": "metrics-endpoint.metadata-default-1",
"index": "metrics-endpoint.metadata-default",
"source": {
"@timestamp": 1579874769541,
"agent": {
@ -378,7 +378,7 @@
"type": "doc",
"value": {
"id": "46VN2G8BYQH1gtPUuYk7",
"index": "metrics-endpoint.metadata-default-1",
"index": "metrics-endpoint.metadata-default",
"source": {
"@timestamp": 1579874769541,
"agent": {
@ -431,7 +431,7 @@
"type": "doc",
"value": {
"id": "5KVN2G8BYQH1gtPUuYk7",
"index": "metrics-endpoint.metadata-default-1",
"index": "metrics-endpoint.metadata-default",
"source": {
"@timestamp": 1579874769541,
"agent": {