[Endpoint][EPM] Retrieve Index Pattern from Ingest Manager (#63016)

* Endpoint successfully depending on ingest manager to initialize

* Moving the endpoint functional tests to their own directory to avoid enabling ingest in the base tests

* Removing page objects and other endpoint fields from base functional

* Updating code owners with new functional location

* Adding index pattern functionality

* Missed a file

* Pointing resolver tests at endpoint functional tests

* Pointing space tests at the endpoint functional directory

* Adding ingest service to do setup and tests for 500s

* Correcting services path

* Adding jest test names

* Updating es archives with the correct mapping and index names

* Fixing import error

* Adding resolver tests to code owners

* enabling epm flag for functional tests

* adding correct tag to test

* Removing the version information and unneeded xsrf

* Addressing endpoint index pattern feedback

* Removing unused import

* Renaming index pattern to es index pattern

* Fixing missed index pattern calls

* Removing unused import

* Fixing type error

* Moving es_index_pattern outside of installed and fixing function name

* Keeping the event index the same for now

* Wrapping index pattern await in try catch

* Address PR feedback, adding comments
This commit is contained in:
Jonathan Buttner 2020-04-16 16:20:16 -04:00 committed by GitHub
parent cae0c964ac
commit 0a9e17b57f
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
55 changed files with 962 additions and 464 deletions

3
.github/CODEOWNERS vendored
View file

@ -209,9 +209,12 @@
# Endpoint
/x-pack/plugins/endpoint/ @elastic/endpoint-app-team
/x-pack/test/api_integration/apis/endpoint/ @elastic/endpoint-app-team
/x-pack/test/endpoint_api_integration_no_ingest/ @elastic/endpoint-app-team
/x-pack/test/functional_endpoint/ @elastic/endpoint-app-team
/x-pack/test/functional_endpoint_ingest_failure/ @elastic/endpoint-app-team
/x-pack/test/functional/es_archives/endpoint/ @elastic/endpoint-app-team
/x-pack/test/plugin_functional/plugins/resolver_test/ @elastic/endpoint-app-team
/x-pack/test/plugin_functional/test_suites/resolver/ @elastic/endpoint-app-team
# SIEM
/x-pack/legacy/plugins/siem/ @elastic/siem

View file

@ -0,0 +1,9 @@
/*
* 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 { schema } from '@kbn/config-schema';
export const indexPatternGetParamsSchema = schema.object({ datasetPath: schema.string() });

View file

@ -7,6 +7,7 @@
import { SearchResponse } from 'elasticsearch';
import { TypeOf } from '@kbn/config-schema';
import { alertingIndexGetQuerySchema } from './schema/alert_index';
import { indexPatternGetParamsSchema } from './schema/index_pattern';
import { Datasource, NewDatasource } from '../../ingest_manager/common';
/**
@ -33,9 +34,9 @@ export type Direction = 'asc' | 'desc';
export class EndpointAppConstants {
static BASE_API_URL = '/api/endpoint';
static ENDPOINT_INDEX_NAME = 'endpoint-agent*';
static INDEX_PATTERN_ROUTE = `${EndpointAppConstants.BASE_API_URL}/index_pattern`;
static ALERT_INDEX_NAME = 'events-endpoint-1';
static EVENT_INDEX_NAME = 'events-endpoint-*';
static EVENT_DATASET = 'events';
static DEFAULT_TOTAL_HITS = 10000;
/**
* Legacy events are stored in indices with endgame-* prefix
@ -446,6 +447,11 @@ export type AlertingIndexGetQueryInput = KbnConfigSchemaInputTypeOf<
*/
export type AlertingIndexGetQueryResult = TypeOf<typeof alertingIndexGetQuerySchema>;
/**
* Result of the validated params when handling an index pattern request.
*/
export type IndexPatternGetParamsResult = TypeOf<typeof indexPatternGetParamsSchema>;
/**
* Endpoint Policy configuration
*/

View file

@ -15,10 +15,14 @@ import { EndpointAppConstants } from '../../../../../common/types';
export const alertMiddlewareFactory: MiddlewareFactory<AlertListState> = (coreStart, depsStart) => {
async function fetchIndexPatterns(): Promise<IIndexPattern[]> {
const { indexPatterns } = depsStart.data;
const indexName = EndpointAppConstants.ALERT_INDEX_NAME;
const fields = await indexPatterns.getFieldsForWildcard({ pattern: indexName });
const eventsPattern: { indexPattern: string } = await coreStart.http.get(
`${EndpointAppConstants.INDEX_PATTERN_ROUTE}/${EndpointAppConstants.EVENT_DATASET}`
);
const fields = await indexPatterns.getFieldsForWildcard({
pattern: eventsPattern.indexPattern,
});
const indexPattern: IIndexPattern = {
title: indexName,
title: eventsPattern.indexPattern,
fields,
};

View file

@ -41,7 +41,7 @@ async function main() {
metadataIndex: {
alias: 'mi',
describe: 'index to store host metadata in',
default: 'endpoint-agent-1',
default: 'metrics-endpoint-default-1',
type: 'string',
},
auth: {

View file

@ -0,0 +1,77 @@
/*
* 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 { Logger, LoggerFactory, RequestHandlerContext } from 'kibana/server';
import { ESIndexPatternService } from '../../ingest_manager/server';
import { EndpointAppConstants } from '../common/types';
export interface IndexPatternRetriever {
getIndexPattern(ctx: RequestHandlerContext, datasetPath: string): Promise<string>;
getEventIndexPattern(ctx: RequestHandlerContext): Promise<string>;
getMetadataIndexPattern(ctx: RequestHandlerContext): Promise<string>;
}
/**
* This class is used to retrieve an index pattern. It should be used in the server side code whenever
* an index pattern is needed to query data within ES. The index pattern is constructed by the Ingest Manager
* based on the contents of the Endpoint Package in the Package Registry.
*/
export class IngestIndexPatternRetriever implements IndexPatternRetriever {
private static endpointPackageName = 'endpoint';
private static metadataDataset = 'metadata';
private readonly log: Logger;
constructor(private readonly service: ESIndexPatternService, loggerFactory: LoggerFactory) {
this.log = loggerFactory.get('index-pattern-retriever');
}
/**
* Retrieves the index pattern for querying events within elasticsearch.
*
* @param ctx a RequestHandlerContext from a route handler
* @returns a string representing the index pattern (e.g. `events-endpoint-*`)
*/
async getEventIndexPattern(ctx: RequestHandlerContext) {
return await this.getIndexPattern(ctx, EndpointAppConstants.EVENT_DATASET);
}
/**
* Retrieves the index pattern for querying endpoint metadata within elasticsearch.
*
* @param ctx a RequestHandlerContext from a route handler
* @returns a string representing the index pattern (e.g. `metrics-endpoint-*`)
*/
async getMetadataIndexPattern(ctx: RequestHandlerContext) {
return await this.getIndexPattern(ctx, IngestIndexPatternRetriever.metadataDataset);
}
/**
* Retrieves the index pattern for a specific dataset for querying endpoint data.
*
* @param ctx a RequestHandlerContext from a route handler
* @param datasetPath a string of the path being used for a dataset within the Endpoint Package
* (e.g. `events`, `metadata`)
* @returns a string representing the index pattern (e.g. `metrics-endpoint-*`)
*/
async getIndexPattern(ctx: RequestHandlerContext, datasetPath: string) {
try {
const pattern = await this.service.getESIndexPattern(
ctx.core.savedObjects.client,
IngestIndexPatternRetriever.endpointPackageName,
datasetPath
);
if (!pattern) {
const msg = `Unable to retrieve the index pattern for dataset: ${datasetPath}`;
this.log.warn(msg);
throw new Error(msg);
}
return pattern;
} catch (error) {
const errMsg = `Error occurred while retrieving pattern for: ${datasetPath} error: ${error}`;
this.log.warn(errMsg);
throw new Error(errMsg);
}
}
}

View file

@ -0,0 +1,44 @@
/*
* 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.
*/
/**
* Creates a mock IndexPatternRetriever for use in tests.
*
* @param indexPattern a string index pattern to return when any of the mock's public methods are called.
* @returns the same string passed in via `indexPattern`
*/
export const createMockIndexPatternRetriever = (indexPattern: string) => {
const mockGetFunc = jest.fn().mockResolvedValue(indexPattern);
return {
getIndexPattern: mockGetFunc,
getEventIndexPattern: mockGetFunc,
getMetadataIndexPattern: mockGetFunc,
};
};
export const MetadataIndexPattern = 'metrics-endpoint-*';
/**
* Creates a mock IndexPatternRetriever for use in tests that returns `metrics-endpoint-*`
*/
export const createMockMetadataIndexPatternRetriever = () => {
return createMockIndexPatternRetriever(MetadataIndexPattern);
};
/**
* Creates a mock IndexPatternService for use in tests that need to interact with the Ingest Manager's
* ESIndexPatternService.
*
* @param indexPattern a string index pattern to return when called by a test
* @returns the same value as `indexPattern` parameter
*/
export const createMockIndexPatternService = (indexPattern: string) => {
return {
esIndexPatternService: {
getESIndexPattern: jest.fn().mockResolvedValue(indexPattern),
},
};
};

View file

@ -7,6 +7,7 @@
import { EndpointPlugin, EndpointPluginSetupDependencies } from './plugin';
import { coreMock } from '../../../../src/core/server/mocks';
import { PluginSetupContract } from '../../features/server';
import { createMockIndexPatternService } from './mocks';
describe('test endpoint plugin', () => {
let plugin: EndpointPlugin;
@ -28,7 +29,10 @@ describe('test endpoint plugin', () => {
getFeaturesUICapabilities: jest.fn(),
registerLegacyAPI: jest.fn(),
};
mockedEndpointPluginSetupDependencies = { features: mockedPluginSetupContract };
mockedEndpointPluginSetupDependencies = {
features: mockedPluginSetupContract,
ingestManager: createMockIndexPatternService(''),
};
});
it('test properly setup plugin', async () => {

View file

@ -6,12 +6,15 @@
import { Plugin, CoreSetup, PluginInitializerContext, Logger } from 'kibana/server';
import { first } from 'rxjs/operators';
import { PluginSetupContract as FeaturesPluginSetupContract } from '../../features/server';
import { IngestManagerSetupContract } from '../../ingest_manager/server';
import { createConfig$, EndpointConfigType } from './config';
import { EndpointAppContext } from './types';
import { registerAlertRoutes } from './routes/alerts';
import { registerResolverRoutes } from './routes/resolver';
import { registerIndexPatternRoute } from './routes/index_pattern';
import { registerEndpointRoutes } from './routes/metadata';
import { IngestIndexPatternRetriever } from './index_pattern';
export type EndpointPluginStart = void;
export type EndpointPluginSetup = void;
@ -19,6 +22,7 @@ export interface EndpointPluginStartDependencies {} // eslint-disable-line @type
export interface EndpointPluginSetupDependencies {
features: FeaturesPluginSetupContract;
ingestManager: IngestManagerSetupContract;
}
export class EndpointPlugin
@ -62,6 +66,10 @@ export class EndpointPlugin
},
});
const endpointContext = {
indexPatternRetriever: new IngestIndexPatternRetriever(
plugins.ingestManager.esIndexPatternService,
this.initializerContext.logger
),
logFactory: this.initializerContext.logger,
config: (): Promise<EndpointConfigType> => {
return createConfig$(this.initializerContext)
@ -73,6 +81,7 @@ export class EndpointPlugin
registerEndpointRoutes(router, endpointContext);
registerResolverRoutes(router, endpointContext);
registerAlertRoutes(router, endpointContext);
registerIndexPatternRoute(router, endpointContext);
}
public start() {

View file

@ -12,6 +12,7 @@ import {
import { registerAlertRoutes } from './index';
import { EndpointConfigSchema } from '../../config';
import { alertingIndexGetQuerySchema } from '../../../common/schema/alert_index';
import { createMockIndexPatternRetriever } from '../../mocks';
describe('test alerts route', () => {
let routerMock: jest.Mocked<IRouter>;
@ -24,6 +25,7 @@ describe('test alerts route', () => {
mockClusterClient.asScoped.mockReturnValue(mockScopedClient);
routerMock = httpServiceMock.createRouter();
registerAlertRoutes(routerMock, {
indexPatternRetriever: createMockIndexPatternRetriever('events-endpoint-*'),
logFactory: loggingServiceMock.create(),
config: () => Promise.resolve(EndpointConfigSchema.validate({})),
});

View file

@ -26,15 +26,18 @@ export const alertDetailsHandlerWrapper = function(
id: alertId,
})) as GetResponse<AlertEvent>;
const indexPattern = await endpointAppContext.indexPatternRetriever.getEventIndexPattern(ctx);
const config = await endpointAppContext.config();
const pagination: AlertDetailsPagination = new AlertDetailsPagination(
config,
ctx,
req.params,
response
response,
indexPattern
);
const currentHostInfo = await getHostData(ctx, response._source.host.id);
const currentHostInfo = await getHostData(ctx, response._source.host.id, indexPattern);
return res.ok({
body: {

View file

@ -29,7 +29,8 @@ export class AlertDetailsPagination extends Pagination<
config: EndpointConfigType,
requestContext: RequestHandlerContext,
state: AlertDetailsRequestParams,
data: GetResponse<AlertEvent>
data: GetResponse<AlertEvent>,
private readonly indexPattern: string
) {
super(config, requestContext, state, data);
}
@ -54,7 +55,8 @@ export class AlertDetailsPagination extends Pagination<
const response = await searchESForAlerts(
this.requestContext.core.elasticsearch.dataClient,
reqData
reqData,
this.indexPattern
);
return response;
}

View file

@ -97,7 +97,8 @@ function buildSort(query: AlertSearchQuery): AlertSort {
* Builds a request body for Elasticsearch, given a set of query params.
**/
const buildAlertSearchQuery = async (
query: AlertSearchQuery
query: AlertSearchQuery,
indexPattern: string
): Promise<AlertSearchRequestWrapper> => {
let totalHitsMin: number = EndpointAppConstants.DEFAULT_TOTAL_HITS;
@ -125,7 +126,7 @@ const buildAlertSearchQuery = async (
const reqWrapper: AlertSearchRequestWrapper = {
size: query.pageSize,
index: EndpointAppConstants.ALERT_INDEX_NAME,
index: indexPattern,
body: reqBody,
};
@ -141,9 +142,10 @@ const buildAlertSearchQuery = async (
**/
export const searchESForAlerts = async (
dataClient: IScopedClusterClient,
query: AlertSearchQuery
query: AlertSearchQuery,
indexPattern: string
): Promise<SearchResponse<AlertEvent>> => {
const reqWrapper = await buildAlertSearchQuery(query);
const reqWrapper = await buildAlertSearchQuery(query, indexPattern);
const response = (await dataClient.callAsCurrentUser('search', reqWrapper)) as SearchResponse<
AlertEvent
>;

View file

@ -18,8 +18,13 @@ export const alertListHandlerWrapper = function(
res
) => {
try {
const indexPattern = await endpointAppContext.indexPatternRetriever.getEventIndexPattern(ctx);
const reqData = await getRequestData(req, endpointAppContext);
const response = await searchESForAlerts(ctx.core.elasticsearch.dataClient, reqData);
const response = await searchESForAlerts(
ctx.core.elasticsearch.dataClient,
reqData,
indexPattern
);
const mappedBody = await mapToAlertResultList(ctx, endpointAppContext, reqData, response);
return res.ok({ body: mappedBody });
} catch (err) {

View file

@ -0,0 +1,42 @@
/*
* 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 { IRouter, Logger, RequestHandler } from 'kibana/server';
import { EndpointAppContext } from '../types';
import { IndexPatternGetParamsResult, EndpointAppConstants } from '../../common/types';
import { indexPatternGetParamsSchema } from '../../common/schema/index_pattern';
import { IndexPatternRetriever } from '../index_pattern';
function handleIndexPattern(
log: Logger,
indexRetriever: IndexPatternRetriever
): RequestHandler<IndexPatternGetParamsResult> {
return async (context, req, res) => {
try {
return res.ok({
body: {
indexPattern: await indexRetriever.getIndexPattern(context, req.params.datasetPath),
},
});
} catch (error) {
log.warn(error);
return res.notFound({ body: error });
}
};
}
export function registerIndexPatternRoute(router: IRouter, endpointAppContext: EndpointAppContext) {
const log = endpointAppContext.logFactory.get('index_pattern');
router.get(
{
path: `${EndpointAppConstants.INDEX_PATTERN_ROUTE}/{datasetPath}`,
validate: { params: indexPatternGetParamsSchema },
options: { authRequired: true },
},
handleIndexPattern(log, endpointAppContext.indexPatternRetriever)
);
}

View file

@ -8,9 +8,9 @@ import { IRouter, RequestHandlerContext } from 'kibana/server';
import { SearchResponse } from 'elasticsearch';
import { schema } from '@kbn/config-schema';
import { kibanaRequestToMetadataListESQuery, getESQueryHostMetadataByID } from './query_builders';
import { HostInfo, HostMetadata, HostResultList, HostStatus } from '../../../common/types';
import { EndpointAppContext } from '../../types';
import { getESQueryHostMetadataByID, kibanaRequestToMetadataListESQuery } from './query_builders';
interface HitSource {
_source: HostMetadata;
@ -50,7 +50,14 @@ export function registerEndpointRoutes(router: IRouter, endpointAppContext: Endp
},
async (context, req, res) => {
try {
const queryParams = await kibanaRequestToMetadataListESQuery(req, endpointAppContext);
const index = await endpointAppContext.indexPatternRetriever.getMetadataIndexPattern(
context
);
const queryParams = await kibanaRequestToMetadataListESQuery(
req,
endpointAppContext,
index
);
const response = (await context.core.elasticsearch.dataClient.callAsCurrentUser(
'search',
queryParams
@ -72,7 +79,11 @@ export function registerEndpointRoutes(router: IRouter, endpointAppContext: Endp
},
async (context, req, res) => {
try {
const doc = await getHostData(context, req.params.id);
const index = await endpointAppContext.indexPatternRetriever.getMetadataIndexPattern(
context
);
const doc = await getHostData(context, req.params.id, index);
if (doc) {
return res.ok({ body: doc });
}
@ -86,9 +97,10 @@ export function registerEndpointRoutes(router: IRouter, endpointAppContext: Endp
export async function getHostData(
context: RequestHandlerContext,
id: string
id: string,
index: string
): Promise<HostInfo | undefined> {
const query = getESQueryHostMetadataByID(id);
const query = getESQueryHostMetadataByID(id, index);
const response = (await context.core.elasticsearch.dataClient.callAsCurrentUser(
'search',
query

View file

@ -11,24 +11,28 @@ import {
RequestHandler,
RequestHandlerContext,
RouteConfig,
SavedObjectsClientContract,
} from 'kibana/server';
import {
elasticsearchServiceMock,
httpServerMock,
httpServiceMock,
loggingServiceMock,
savedObjectsClientMock,
} from '../../../../../../src/core/server/mocks';
import { HostInfo, HostMetadata, HostResultList, HostStatus } from '../../../common/types';
import { SearchResponse } from 'elasticsearch';
import { registerEndpointRoutes } from './index';
import { EndpointConfigSchema } from '../../config';
import * as data from '../../test_data/all_metadata_data.json';
import { registerEndpointRoutes } from './index';
import { createMockMetadataIndexPatternRetriever } from '../../mocks';
describe('test endpoint route', () => {
let routerMock: jest.Mocked<IRouter>;
let mockResponse: jest.Mocked<KibanaResponseFactory>;
let mockClusterClient: jest.Mocked<IClusterClient>;
let mockScopedClient: jest.Mocked<IScopedClusterClient>;
let mockSavedObjectClient: jest.Mocked<SavedObjectsClientContract>;
let routeHandler: RequestHandler<any, any, any>;
let routeConfig: RouteConfig<any, any, any, any>;
@ -37,15 +41,38 @@ describe('test endpoint route', () => {
IClusterClient
>;
mockScopedClient = elasticsearchServiceMock.createScopedClusterClient();
mockSavedObjectClient = savedObjectsClientMock.create();
mockClusterClient.asScoped.mockReturnValue(mockScopedClient);
routerMock = httpServiceMock.createRouter();
mockResponse = httpServerMock.createResponseFactory();
registerEndpointRoutes(routerMock, {
indexPatternRetriever: createMockMetadataIndexPatternRetriever(),
logFactory: loggingServiceMock.create(),
config: () => Promise.resolve(EndpointConfigSchema.validate({})),
});
});
function createRouteHandlerContext(
dataClient: jest.Mocked<IScopedClusterClient>,
savedObjectsClient: jest.Mocked<SavedObjectsClientContract>
) {
return ({
core: {
elasticsearch: {
dataClient,
},
savedObjects: {
client: savedObjectsClient,
},
},
/**
* Using unknown here because the object defined is not a full `RequestHandlerContext`. We don't
* need all of the fields required to run the tests, but the `routeHandler` function requires a
* `RequestHandlerContext`.
*/
} as unknown) as RequestHandlerContext;
}
it('test find the latest of all endpoints', async () => {
const mockRequest = httpServerMock.createKibanaRequest({});
@ -58,13 +85,7 @@ describe('test endpoint route', () => {
)!;
await routeHandler(
({
core: {
elasticsearch: {
dataClient: mockScopedClient,
},
},
} as unknown) as RequestHandlerContext,
createRouteHandlerContext(mockScopedClient, mockSavedObjectClient),
mockRequest,
mockResponse
);
@ -100,13 +121,7 @@ describe('test endpoint route', () => {
)!;
await routeHandler(
({
core: {
elasticsearch: {
dataClient: mockScopedClient,
},
},
} as unknown) as RequestHandlerContext,
createRouteHandlerContext(mockScopedClient, mockSavedObjectClient),
mockRequest,
mockResponse
);
@ -147,13 +162,7 @@ describe('test endpoint route', () => {
)!;
await routeHandler(
({
core: {
elasticsearch: {
dataClient: mockScopedClient,
},
},
} as unknown) as RequestHandlerContext,
createRouteHandlerContext(mockScopedClient, mockSavedObjectClient),
mockRequest,
mockResponse
);
@ -212,13 +221,7 @@ describe('test endpoint route', () => {
)!;
await routeHandler(
({
core: {
elasticsearch: {
dataClient: mockScopedClient,
},
},
} as unknown) as RequestHandlerContext,
createRouteHandlerContext(mockScopedClient, mockSavedObjectClient),
mockRequest,
mockResponse
);
@ -243,13 +246,7 @@ describe('test endpoint route', () => {
)!;
await routeHandler(
({
core: {
elasticsearch: {
dataClient: mockScopedClient,
},
},
} as unknown) as RequestHandlerContext,
createRouteHandlerContext(mockScopedClient, mockSavedObjectClient),
mockRequest,
mockResponse
);

View file

@ -6,7 +6,7 @@
import { httpServerMock, loggingServiceMock } from '../../../../../../src/core/server/mocks';
import { EndpointConfigSchema } from '../../config';
import { kibanaRequestToMetadataListESQuery, getESQueryHostMetadataByID } from './query_builders';
import { EndpointAppConstants } from '../../../common/types';
import { createMockMetadataIndexPatternRetriever, MetadataIndexPattern } from '../../mocks';
describe('query builder', () => {
describe('MetadataListESQuery', () => {
@ -14,17 +14,22 @@ describe('query builder', () => {
const mockRequest = httpServerMock.createKibanaRequest({
body: {},
});
const query = await kibanaRequestToMetadataListESQuery(mockRequest, {
logFactory: loggingServiceMock.create(),
config: () => Promise.resolve(EndpointConfigSchema.validate({})),
});
const query = await kibanaRequestToMetadataListESQuery(
mockRequest,
{
indexPatternRetriever: createMockMetadataIndexPatternRetriever(),
logFactory: loggingServiceMock.create(),
config: () => Promise.resolve(EndpointConfigSchema.validate({})),
},
MetadataIndexPattern
);
expect(query).toEqual({
body: {
query: {
match_all: {},
},
collapse: {
field: 'host.id.keyword',
field: 'host.id',
inner_hits: {
name: 'most_recent',
size: 1,
@ -34,7 +39,7 @@ describe('query builder', () => {
aggs: {
total: {
cardinality: {
field: 'host.id.keyword',
field: 'host.id',
},
},
},
@ -48,7 +53,7 @@ describe('query builder', () => {
},
from: 0,
size: 10,
index: EndpointAppConstants.ENDPOINT_INDEX_NAME,
index: MetadataIndexPattern,
} as Record<string, any>);
});
});
@ -60,10 +65,15 @@ describe('query builder', () => {
filter: 'not host.ip:10.140.73.246',
},
});
const query = await kibanaRequestToMetadataListESQuery(mockRequest, {
logFactory: loggingServiceMock.create(),
config: () => Promise.resolve(EndpointConfigSchema.validate({})),
});
const query = await kibanaRequestToMetadataListESQuery(
mockRequest,
{
indexPatternRetriever: createMockMetadataIndexPatternRetriever(),
logFactory: loggingServiceMock.create(),
config: () => Promise.resolve(EndpointConfigSchema.validate({})),
},
MetadataIndexPattern
);
expect(query).toEqual({
body: {
query: {
@ -83,7 +93,7 @@ describe('query builder', () => {
},
},
collapse: {
field: 'host.id.keyword',
field: 'host.id',
inner_hits: {
name: 'most_recent',
size: 1,
@ -93,7 +103,7 @@ describe('query builder', () => {
aggs: {
total: {
cardinality: {
field: 'host.id.keyword',
field: 'host.id',
},
},
},
@ -107,7 +117,7 @@ describe('query builder', () => {
},
from: 0,
size: 10,
index: EndpointAppConstants.ENDPOINT_INDEX_NAME,
index: MetadataIndexPattern,
} as Record<string, any>);
});
});
@ -115,14 +125,15 @@ describe('query builder', () => {
describe('MetadataGetQuery', () => {
it('searches for the correct ID', () => {
const mockID = 'AABBCCDD-0011-2233-AA44-DEADBEEF8899';
const query = getESQueryHostMetadataByID(mockID);
const query = getESQueryHostMetadataByID(mockID, MetadataIndexPattern);
expect(query).toEqual({
body: {
query: { match: { 'host.id.keyword': mockID } },
query: { match: { 'host.id': mockID } },
sort: [{ 'event.created': { order: 'desc' } }],
size: 1,
},
index: EndpointAppConstants.ENDPOINT_INDEX_NAME,
index: MetadataIndexPattern,
});
});
});

View file

@ -6,18 +6,18 @@
import { KibanaRequest } from 'kibana/server';
import { esKuery } from '../../../../../../src/plugins/data/server';
import { EndpointAppContext } from '../../types';
import { EndpointAppConstants } from '../../../common/types';
export const kibanaRequestToMetadataListESQuery = async (
request: KibanaRequest<any, any, any>,
endpointAppContext: EndpointAppContext
endpointAppContext: EndpointAppContext,
index: string
): Promise<Record<string, any>> => {
const pagingProperties = await getPagingProperties(request, endpointAppContext);
return {
body: {
query: buildQueryBody(request),
collapse: {
field: 'host.id.keyword',
field: 'host.id',
inner_hits: {
name: 'most_recent',
size: 1,
@ -27,7 +27,7 @@ export const kibanaRequestToMetadataListESQuery = async (
aggs: {
total: {
cardinality: {
field: 'host.id.keyword',
field: 'host.id',
},
},
},
@ -41,7 +41,7 @@ export const kibanaRequestToMetadataListESQuery = async (
},
from: pagingProperties.pageIndex * pagingProperties.pageSize,
size: pagingProperties.pageSize,
index: EndpointAppConstants.ENDPOINT_INDEX_NAME,
index,
};
};
@ -74,12 +74,12 @@ function buildQueryBody(request: KibanaRequest<any, any, any>): Record<string, a
};
}
export function getESQueryHostMetadataByID(hostID: string) {
export function getESQueryHostMetadataByID(hostID: string, index: string) {
return {
body: {
query: {
match: {
'host.id.keyword': hostID,
'host.id': hostID,
},
},
sort: [
@ -91,6 +91,6 @@ export function getESQueryHostMetadataByID(hostID: string) {
],
size: 1,
},
index: EndpointAppConstants.ENDPOINT_INDEX_NAME,
index,
};
}

View file

@ -12,6 +12,7 @@ import { handleLifecycle, validateLifecycle } from './resolver/lifecycle';
export function registerResolverRoutes(router: IRouter, endpointAppContext: EndpointAppContext) {
const log = endpointAppContext.logFactory.get('resolver');
const indexPatternService = endpointAppContext.indexPatternRetriever;
router.get(
{
@ -19,7 +20,7 @@ export function registerResolverRoutes(router: IRouter, endpointAppContext: Endp
validate: validateRelatedEvents,
options: { authRequired: true },
},
handleRelatedEvents(log)
handleRelatedEvents(log, indexPatternService)
);
router.get(
@ -28,7 +29,7 @@ export function registerResolverRoutes(router: IRouter, endpointAppContext: Endp
validate: validateChildren,
options: { authRequired: true },
},
handleChildren(log)
handleChildren(log, indexPatternService)
);
router.get(
@ -37,6 +38,6 @@ export function registerResolverRoutes(router: IRouter, endpointAppContext: Endp
validate: validateLifecycle,
options: { authRequired: true },
},
handleLifecycle(log)
handleLifecycle(log, indexPatternService)
);
}

View file

@ -11,6 +11,7 @@ import { extractEntityID } from './utils/normalize';
import { getPaginationParams } from './utils/pagination';
import { LifecycleQuery } from './queries/lifecycle';
import { ChildrenQuery } from './queries/children';
import { IndexPatternRetriever } from '../../index_pattern';
interface ChildrenQueryParams {
after?: string;
@ -45,7 +46,8 @@ export const validateChildren = {
};
export function handleChildren(
log: Logger
log: Logger,
indexRetriever: IndexPatternRetriever
): RequestHandler<ChildrenPathParams, ChildrenQueryParams> {
return async (context, req, res) => {
const {
@ -54,10 +56,11 @@ export function handleChildren(
} = req;
try {
const pagination = getPaginationParams(limit, after);
const indexPattern = await indexRetriever.getEventIndexPattern(context);
const client = context.core.elasticsearch.dataClient;
const childrenQuery = new ChildrenQuery(legacyEndpointID, pagination);
const lifecycleQuery = new LifecycleQuery(legacyEndpointID);
const childrenQuery = new ChildrenQuery(indexPattern, legacyEndpointID, pagination);
const lifecycleQuery = new LifecycleQuery(indexPattern, legacyEndpointID);
// Retrieve the related child process events for a given process
const { total, results: events, nextCursor } = await childrenQuery.search(client, id);

View file

@ -10,6 +10,7 @@ import { RequestHandler, Logger } from 'kibana/server';
import { extractParentEntityID } from './utils/normalize';
import { LifecycleQuery } from './queries/lifecycle';
import { ResolverEvent } from '../../../common/types';
import { IndexPatternRetriever } from '../../index_pattern';
interface LifecycleQueryParams {
ancestors: number;
@ -46,7 +47,8 @@ function getParentEntityID(results: ResolverEvent[]) {
}
export function handleLifecycle(
log: Logger
log: Logger,
indexRetriever: IndexPatternRetriever
): RequestHandler<LifecyclePathParams, LifecycleQueryParams> {
return async (context, req, res) => {
const {
@ -56,8 +58,8 @@ export function handleLifecycle(
try {
const ancestorLifecycles = [];
const client = context.core.elasticsearch.dataClient;
const lifecycleQuery = new LifecycleQuery(legacyEndpointID);
const indexPattern = await indexRetriever.getEventIndexPattern(context);
const lifecycleQuery = new LifecycleQuery(indexPattern, legacyEndpointID);
const { results: processLifecycle } = await lifecycleQuery.search(client, id);
let nextParentID = getParentEntityID(processLifecycle);

View file

@ -11,6 +11,7 @@ import { JsonObject } from '../../../../../../../src/plugins/kibana_utils/public
export abstract class ResolverQuery {
constructor(
private readonly indexPattern: string,
private readonly endpointID?: string,
private readonly pagination?: PaginationParams
) {}
@ -26,7 +27,7 @@ export abstract class ResolverQuery {
if (this.endpointID) {
return this.legacyQuery(this.endpointID, ids, EndpointAppConstants.LEGACY_EVENT_INDEX_NAME);
}
return this.query(ids, EndpointAppConstants.EVENT_INDEX_NAME);
return this.query(ids, this.indexPattern);
}
async search(client: IScopedClusterClient, ...ids: string[]) {

View file

@ -6,11 +6,17 @@
import { ChildrenQuery } from './children';
import { EndpointAppConstants } from '../../../../common/types';
export const fakeEventIndexPattern = 'events-endpoint-*';
describe('children events query', () => {
it('generates the correct legacy queries', () => {
const timestamp = new Date().getTime();
expect(
new ChildrenQuery('awesome-id', { size: 1, timestamp, eventID: 'foo' }).build('5')
new ChildrenQuery(EndpointAppConstants.LEGACY_EVENT_INDEX_NAME, 'awesome-id', {
size: 1,
timestamp,
eventID: 'foo',
}).build('5')
).toStrictEqual({
body: {
query: {
@ -50,7 +56,11 @@ describe('children events query', () => {
const timestamp = new Date().getTime();
expect(
new ChildrenQuery(undefined, { size: 1, timestamp, eventID: 'bar' }).build('baz')
new ChildrenQuery(fakeEventIndexPattern, undefined, {
size: 1,
timestamp,
eventID: 'bar',
}).build('baz')
).toStrictEqual({
body: {
query: {
@ -88,7 +98,7 @@ describe('children events query', () => {
size: 1,
sort: [{ '@timestamp': 'asc' }, { 'event.id': 'asc' }],
},
index: EndpointAppConstants.EVENT_INDEX_NAME,
index: fakeEventIndexPattern,
});
});
});

View file

@ -5,10 +5,13 @@
*/
import { EndpointAppConstants } from '../../../../common/types';
import { LifecycleQuery } from './lifecycle';
import { fakeEventIndexPattern } from './children.test';
describe('lifecycle query', () => {
it('generates the correct legacy queries', () => {
expect(new LifecycleQuery('awesome-id').build('5')).toStrictEqual({
expect(
new LifecycleQuery(EndpointAppConstants.LEGACY_EVENT_INDEX_NAME, 'awesome-id').build('5')
).toStrictEqual({
body: {
query: {
bool: {
@ -32,7 +35,7 @@ describe('lifecycle query', () => {
});
it('generates the correct non-legacy queries', () => {
expect(new LifecycleQuery().build('baz')).toStrictEqual({
expect(new LifecycleQuery(fakeEventIndexPattern).build('baz')).toStrictEqual({
body: {
query: {
bool: {
@ -57,7 +60,7 @@ describe('lifecycle query', () => {
},
sort: [{ '@timestamp': 'asc' }],
},
index: EndpointAppConstants.EVENT_INDEX_NAME,
index: fakeEventIndexPattern,
});
});
});

View file

@ -5,12 +5,17 @@
*/
import { RelatedEventsQuery } from './related_events';
import { EndpointAppConstants } from '../../../../common/types';
import { fakeEventIndexPattern } from './children.test';
describe('related events query', () => {
it('generates the correct legacy queries', () => {
const timestamp = new Date().getTime();
expect(
new RelatedEventsQuery('awesome-id', { size: 1, timestamp, eventID: 'foo' }).build('5')
new RelatedEventsQuery(EndpointAppConstants.LEGACY_EVENT_INDEX_NAME, 'awesome-id', {
size: 1,
timestamp,
eventID: 'foo',
}).build('5')
).toStrictEqual({
body: {
query: {
@ -51,7 +56,11 @@ describe('related events query', () => {
const timestamp = new Date().getTime();
expect(
new RelatedEventsQuery(undefined, { size: 1, timestamp, eventID: 'bar' }).build('baz')
new RelatedEventsQuery(fakeEventIndexPattern, undefined, {
size: 1,
timestamp,
eventID: 'bar',
}).build('baz')
).toStrictEqual({
body: {
query: {
@ -90,7 +99,7 @@ describe('related events query', () => {
size: 1,
sort: [{ '@timestamp': 'asc' }, { 'event.id': 'asc' }],
},
index: EndpointAppConstants.EVENT_INDEX_NAME,
index: fakeEventIndexPattern,
});
});
});

View file

@ -8,6 +8,7 @@ import { schema } from '@kbn/config-schema';
import { RequestHandler, Logger } from 'kibana/server';
import { getPaginationParams } from './utils/pagination';
import { RelatedEventsQuery } from './queries/related_events';
import { IndexPatternRetriever } from '../../index_pattern';
interface RelatedEventsQueryParams {
after?: string;
@ -42,7 +43,8 @@ export const validateRelatedEvents = {
};
export function handleRelatedEvents(
log: Logger
log: Logger,
indexRetriever: IndexPatternRetriever
): RequestHandler<RelatedEventsPathParams, RelatedEventsQueryParams> {
return async (context, req, res) => {
const {
@ -53,8 +55,9 @@ export function handleRelatedEvents(
const pagination = getPaginationParams(limit, after);
const client = context.core.elasticsearch.dataClient;
const indexPattern = await indexRetriever.getEventIndexPattern(context);
// Retrieve the related non-process events for a given process
const relatedEventsQuery = new RelatedEventsQuery(legacyEndpointID, pagination);
const relatedEventsQuery = new RelatedEventsQuery(indexPattern, legacyEndpointID, pagination);
const relatedEvents = await relatedEventsQuery.search(client, id);
const { total, results: events, nextCursor } = relatedEvents;

View file

@ -1,115 +1,109 @@
{
"took" : 343,
"timed_out" : false,
"_shards" : {
"total" : 1,
"successful" : 1,
"skipped" : 0,
"failed" : 0
"took": 343,
"timed_out": false,
"_shards": {
"total": 1,
"successful": 1,
"skipped": 0,
"failed": 0
},
"hits" : {
"total" : {
"value" : 4,
"relation" : "eq"
"hits": {
"total": {
"value": 4,
"relation": "eq"
},
"max_score" : null,
"hits" : [
"max_score": null,
"hits": [
{
"_index" : "endpoint-agent",
"_id" : "WqVo1G8BYQH1gtPUgYkC",
"_score" : null,
"_source" : {
"@timestamp" : 1579816615336,
"event" : {
"created" : "2020-01-23T21:56:55.336Z"
"_index": "metadata-endpoint-default-1",
"_id": "WqVo1G8BYQH1gtPUgYkC",
"_score": null,
"_source": {
"@timestamp": 1579816615336,
"event": {
"created": "2020-01-23T21:56:55.336Z"
},
"elastic": {
"agent": {
"id": "56a75650-3c8a-4e4f-ac17-6dd729c650e2"
}
},
"endpoint" : {
"policy" : {
"id" : "C2A9093E-E289-4C0A-AA44-8C32A414FA7A"
"endpoint": {
"policy": {
"id": "C2A9093E-E289-4C0A-AA44-8C32A414FA7A"
}
},
"agent" : {
"version" : "6.8.3",
"id" : "56a75650-3c8a-4e4f-ac17-6dd729c650e2",
"agent": {
"version": "6.8.3",
"id": "56a75650-3c8a-4e4f-ac17-6dd729c650e2",
"name": "Elastic Endpoint"
},
"host" : {
"id" : "7141a48b-e19f-4ae3-89a0-6e7179a84265",
"hostname" : "larimer-0.example.com",
"ip" : "10.21.48.136",
"mac" : "77-be-30-f0-e8-d6",
"architecture" : "x86_64",
"os" : {
"name" : "windows 6.2",
"full" : "Windows Server 2012",
"version" : "6.2",
"variant" : "Windows Server"
"host": {
"id": "7141a48b-e19f-4ae3-89a0-6e7179a84265",
"hostname": "larimer-0.example.com",
"ip": "10.21.48.136",
"mac": "77-be-30-f0-e8-d6",
"architecture": "x86_64",
"os": {
"name": "windows 6.2",
"full": "Windows Server 2012",
"version": "6.2",
"variant": "Windows Server"
}
}
},
"fields" : {
"host.id.keyword" : [
"7141a48b-e19f-4ae3-89a0-6e7179a84265"
]
"fields": {
"host.id.keyword": ["7141a48b-e19f-4ae3-89a0-6e7179a84265"]
},
"sort" : [
1579816615336
],
"inner_hits" : {
"most_recent" : {
"hits" : {
"total" : {
"value" : 2,
"relation" : "eq"
"sort": [1579816615336],
"inner_hits": {
"most_recent": {
"hits": {
"total": {
"value": 2,
"relation": "eq"
},
"max_score" : null,
"hits" : [
"max_score": null,
"hits": [
{
"_index" : "endpoint-agent",
"_id" : "WqVo1G8BYQH1gtPUgYkC",
"_score" : null,
"_source" : {
"@timestamp" : 1579816615336,
"event" : {
"created" : "2020-01-23T21:56:55.336Z"
"_index": "metadata-endpoint-default-1",
"_id": "WqVo1G8BYQH1gtPUgYkC",
"_score": null,
"_source": {
"@timestamp": 1579816615336,
"event": {
"created": "2020-01-23T21:56:55.336Z"
},
"elastic": {
"agent": {
"id": "56a75650-3c8a-4e4f-ac17-6dd729c650e2"
}
},
"endpoint" : {
"policy" : {
"id" : "C2A9093E-E289-4C0A-AA44-8C32A414FA7A"
"endpoint": {
"policy": {
"id": "C2A9093E-E289-4C0A-AA44-8C32A414FA7A"
}
},
"agent" : {
"version" : "6.8.3",
"id" : "56a75650-3c8a-4e4f-ac17-6dd729c650e2",
"agent": {
"version": "6.8.3",
"id": "56a75650-3c8a-4e4f-ac17-6dd729c650e2",
"name": "Elastic Endpoint"
},
"host" : {
"id" : "7141a48b-e19f-4ae3-89a0-6e7179a84265",
"hostname" : "larimer-0.example.com",
"ip" : "10.21.48.136",
"mac" : "77-be-30-f0-e8-d6",
"architecture" : "x86_64",
"os" : {
"name" : "windows 6.2",
"full" : "Windows Server 2012",
"version" : "6.2",
"variant" : "Windows Server"
"host": {
"id": "7141a48b-e19f-4ae3-89a0-6e7179a84265",
"hostname": "larimer-0.example.com",
"ip": "10.21.48.136",
"mac": "77-be-30-f0-e8-d6",
"architecture": "x86_64",
"os": {
"name": "windows 6.2",
"full": "Windows Server 2012",
"version": "6.2",
"variant": "Windows Server"
}
}
},
"sort" : [
1579816615336
]
"sort": [1579816615336]
}
]
}
@ -117,101 +111,95 @@
}
},
{
"_index" : "endpoint-agent",
"_id" : "W6Vo1G8BYQH1gtPUgYkC",
"_score" : null,
"_source" : {
"@timestamp" : 1579816615336,
"event" : {
"created" : "2020-01-23T21:56:55.336Z"
"_index": "metadata-endpoint-default-1",
"_id": "W6Vo1G8BYQH1gtPUgYkC",
"_score": null,
"_source": {
"@timestamp": 1579816615336,
"event": {
"created": "2020-01-23T21:56:55.336Z"
},
"elastic": {
"agent": {
"id": "c2d84d8f-d355-40de-8b54-5d318d4d1312"
}
},
"endpoint" : {
"policy" : {
"id" : "C2A9093E-E289-4C0A-AA44-8C32A414FA7A"
"endpoint": {
"policy": {
"id": "C2A9093E-E289-4C0A-AA44-8C32A414FA7A"
}
},
"agent" : {
"version" : "6.4.3",
"id" : "c2d84d8f-d355-40de-8b54-5d318d4d1312",
"agent": {
"version": "6.4.3",
"id": "c2d84d8f-d355-40de-8b54-5d318d4d1312",
"name": "Elastic Endpoint"
},
"host" : {
"id" : "f35ec6c1-6562-45b1-818f-2f14c0854adf",
"hostname" : "hildebrandt-6.example.com",
"ip" : "10.53.92.84",
"mac" : "af-f1-8f-51-25-2a",
"architecture" : "x86_64",
"os" : {
"name" : "windows 10.0",
"full" : "Windows 10",
"version" : "10.0",
"variant" : "Windows Pro"
"host": {
"id": "f35ec6c1-6562-45b1-818f-2f14c0854adf",
"hostname": "hildebrandt-6.example.com",
"ip": "10.53.92.84",
"mac": "af-f1-8f-51-25-2a",
"architecture": "x86_64",
"os": {
"name": "windows 10.0",
"full": "Windows 10",
"version": "10.0",
"variant": "Windows Pro"
}
}
},
"fields" : {
"host.id.keyword" : [
"f35ec6c1-6562-45b1-818f-2f14c0854adf"
]
"fields": {
"host.id.keyword": ["f35ec6c1-6562-45b1-818f-2f14c0854adf"]
},
"sort" : [
1579816615336
],
"inner_hits" : {
"most_recent" : {
"hits" : {
"total" : {
"value" : 2,
"relation" : "eq"
"sort": [1579816615336],
"inner_hits": {
"most_recent": {
"hits": {
"total": {
"value": 2,
"relation": "eq"
},
"max_score" : null,
"hits" : [
"max_score": null,
"hits": [
{
"_index" : "endpoint-agent",
"_id" : "W6Vo1G8BYQH1gtPUgYkC",
"_score" : null,
"_source" : {
"@timestamp" : 1579816615336,
"event" : {
"created" : "2020-01-23T21:56:55.336Z"
"_index": "metadata-endpoint-default-1",
"_id": "W6Vo1G8BYQH1gtPUgYkC",
"_score": null,
"_source": {
"@timestamp": 1579816615336,
"event": {
"created": "2020-01-23T21:56:55.336Z"
},
"elastic": {
"agent": {
"id": "c2d84d8f-d355-40de-8b54-5d318d4d1312"
}
},
"endpoint" : {
"policy" : {
"id" : "C2A9093E-E289-4C0A-AA44-8C32A414FA7A"
"endpoint": {
"policy": {
"id": "C2A9093E-E289-4C0A-AA44-8C32A414FA7A"
}
},
"agent" : {
"version" : "6.4.3",
"id" : "c2d84d8f-d355-40de-8b54-5d318d4d1312",
"agent": {
"version": "6.4.3",
"id": "c2d84d8f-d355-40de-8b54-5d318d4d1312",
"name": "Elastic Endpoint"
},
"host" : {
"id" : "f35ec6c1-6562-45b1-818f-2f14c0854adf",
"hostname" : "hildebrandt-6.example.com",
"ip" : "10.53.92.84",
"mac" : "af-f1-8f-51-25-2a",
"architecture" : "x86_64",
"os" : {
"name" : "windows 10.0",
"full" : "Windows 10",
"version" : "10.0",
"variant" : "Windows Pro"
"host": {
"id": "f35ec6c1-6562-45b1-818f-2f14c0854adf",
"hostname": "hildebrandt-6.example.com",
"ip": "10.53.92.84",
"mac": "af-f1-8f-51-25-2a",
"architecture": "x86_64",
"os": {
"name": "windows 10.0",
"full": "Windows 10",
"version": "10.0",
"variant": "Windows Pro"
}
}
},
"sort" : [
1579816615336
]
"sort": [1579816615336]
}
]
}
@ -220,9 +208,9 @@
}
]
},
"aggregations" : {
"total" : {
"value" : 2
"aggregations": {
"total": {
"value": 2
}
}
}

View file

@ -5,11 +5,13 @@
*/
import { LoggerFactory } from 'kibana/server';
import { EndpointConfigType } from './config';
import { IndexPatternRetriever } from './index_pattern';
/**
* The context for Endpoint apps.
*/
export interface EndpointAppContext {
indexPatternRetriever: IndexPatternRetriever;
logFactory: LoggerFactory;
config(): Promise<EndpointConfigType>;
}

View file

@ -218,6 +218,7 @@ export type PackageInfo = Installable<
export interface Installation extends SavedObjectAttributes {
installed: AssetReference[];
es_index_patterns: Record<string, string>;
name: string;
version: string;
}

View file

@ -7,6 +7,9 @@ import { schema, TypeOf } from '@kbn/config-schema';
import { PluginInitializerContext } from 'src/core/server';
import { IngestManagerPlugin } from './plugin';
export { ESIndexPatternService } from './services';
export { IngestManagerSetupContract } from './plugin';
export const config = {
exposeToBrowser: {
epm: true,

View file

@ -11,7 +11,9 @@ import {
Plugin,
PluginInitializerContext,
SavedObjectsServiceStart,
} from 'src/core/server';
RecursiveReadonly,
} from 'kibana/server';
import { deepFreeze } from '../../../../src/core/utils';
import { LicensingPluginSetup } from '../../licensing/server';
import { EncryptedSavedObjectsPluginStart } from '../../encrypted_saved_objects/server';
import { SecurityPluginSetup } from '../../security/server';
@ -38,7 +40,18 @@ import {
} from './routes';
import { IngestManagerConfigType } from '../common';
import { appContextService } from './services';
import {
appContextService,
ESIndexPatternService,
ESIndexPatternSavedObjectService,
} from './services';
/**
* Describes public IngestManager plugin contract returned at the `setup` stage.
*/
export interface IngestManagerSetupContract {
esIndexPatternService: ESIndexPatternService;
}
export interface IngestManagerSetupDeps {
licensing: LicensingPluginSetup;
@ -63,7 +76,7 @@ const allSavedObjectTypes = [
ENROLLMENT_API_KEYS_SAVED_OBJECT_TYPE,
];
export class IngestManagerPlugin implements Plugin {
export class IngestManagerPlugin implements Plugin<IngestManagerSetupContract> {
private config$: Observable<IngestManagerConfigType>;
private security: SecurityPluginSetup | undefined;
@ -71,7 +84,10 @@ export class IngestManagerPlugin implements Plugin {
this.config$ = this.initializerContext.config.create<IngestManagerConfigType>();
}
public async setup(core: CoreSetup, deps: IngestManagerSetupDeps) {
public async setup(
core: CoreSetup,
deps: IngestManagerSetupDeps
): Promise<RecursiveReadonly<IngestManagerSetupContract>> {
if (deps.security) {
this.security = deps.security;
}
@ -130,6 +146,9 @@ export class IngestManagerPlugin implements Plugin {
basePath: core.http.basePath,
});
}
return deepFreeze({
esIndexPatternService: new ESIndexPatternSavedObjectService(),
});
}
public async start(

View file

@ -150,6 +150,10 @@ export const savedObjectMappings = {
name: { type: 'keyword' },
version: { type: 'keyword' },
internal: { type: 'boolean' },
es_index_patterns: {
dynamic: false,
type: 'object',
},
installed: {
type: 'nested',
properties: {

View file

@ -136,6 +136,22 @@ export function generateTemplateName(dataset: Dataset): string {
return getDatasetAssetBaseName(dataset);
}
/**
* Returns a map of the dataset path fields to elasticsearch index pattern.
* @param datasets an array of Dataset objects
*/
export function generateESIndexPatterns(datasets: Dataset[] | undefined): Record<string, string> {
if (!datasets) {
return {};
}
const patterns: Record<string, string> = {};
for (const dataset of datasets) {
patterns[dataset.path] = generateTemplateName(dataset) + '-*';
}
return patterns;
}
function getBaseTemplate(type: string, templateName: string, mappings: Mappings): IndexTemplate {
return {
// We need to decide which order we use for the templates

View file

@ -18,6 +18,7 @@ import * as Registry from '../registry';
import { getObject } from './get_objects';
import { getInstallation } from './index';
import { installTemplates } from '../elasticsearch/template/install';
import { generateESIndexPatterns } from '../elasticsearch/template/template';
import { installPipelines } from '../elasticsearch/ingest_pipeline/install';
import { installILMPolicy } from '../elasticsearch/ilm/install';
@ -117,17 +118,18 @@ export async function installPackage(options: {
installTemplatePromises,
]);
const toSave = res.flat();
const toSaveAssetRefs: AssetReference[] = res.flat();
const toSaveESIndexPatterns = generateESIndexPatterns(registryPackageInfo.datasets);
// Save those references in the package manager's state saved object
await saveInstallationReferences({
return await saveInstallationReferences({
savedObjectsClient,
pkgkey,
pkgName,
pkgVersion,
internal,
toSave,
toSaveAssetRefs,
toSaveESIndexPatterns,
});
return toSave;
}
// TODO: make it an exhaustive list
@ -156,25 +158,44 @@ export async function saveInstallationReferences(options: {
pkgName: string;
pkgVersion: string;
internal: boolean;
toSave: AssetReference[];
toSaveAssetRefs: AssetReference[];
toSaveESIndexPatterns: Record<string, string>;
}) {
const { savedObjectsClient, pkgName, pkgVersion, internal, toSave } = options;
const {
savedObjectsClient,
pkgName,
pkgVersion,
internal,
toSaveAssetRefs,
toSaveESIndexPatterns,
} = options;
const installation = await getInstallation({ savedObjectsClient, pkgName });
const savedRefs = installation?.installed || [];
const savedAssetRefs = installation?.installed || [];
const toInstallESIndexPatterns = Object.assign(
installation?.es_index_patterns || {},
toSaveESIndexPatterns
);
const mergeRefsReducer = (current: AssetReference[], pending: AssetReference) => {
const hasRef = current.find(c => c.id === pending.id && c.type === pending.type);
if (!hasRef) current.push(pending);
return current;
};
const toInstall = toSave.reduce(mergeRefsReducer, savedRefs);
const toInstallAssetsRefs = toSaveAssetRefs.reduce(mergeRefsReducer, savedAssetRefs);
await savedObjectsClient.create<Installation>(
PACKAGES_SAVED_OBJECT_TYPE,
{ installed: toInstall, name: pkgName, version: pkgVersion, internal },
{
installed: toInstallAssetsRefs,
es_index_patterns: toInstallESIndexPatterns,
name: pkgName,
version: pkgVersion,
internal,
},
{ id: pkgName, overwrite: true }
);
return toInstall;
return toInstallAssetsRefs;
}
async function installKibanaSavedObjects({

View file

@ -0,0 +1,26 @@
/*
* 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 { SavedObjectsClientContract } from 'kibana/server';
import { getInstallation } from './epm/packages/get';
export interface ESIndexPatternService {
getESIndexPattern(
savedObjectsClient: SavedObjectsClientContract,
pkgName: string,
datasetPath: string
): Promise<string | undefined>;
}
export class ESIndexPatternSavedObjectService implements ESIndexPatternService {
public async getESIndexPattern(
savedObjectsClient: SavedObjectsClientContract,
pkgName: string,
datasetPath: string
): Promise<string | undefined> {
const installation = await getInstallation({ savedObjectsClient, pkgName });
return installation?.es_index_patterns[datasetPath];
}
}

View file

@ -4,6 +4,7 @@
* you may not use this file except in compliance with the Elastic License.
*/
export { appContextService } from './app_context';
export { ESIndexPatternService, ESIndexPatternSavedObjectService } from './es_index_pattern';
// Saved object services
export { datasourceService } from './datasource';

View file

@ -47,6 +47,7 @@ const onlyNotInCoverageTests = [
require.resolve('../test/licensing_plugin/config.ts'),
require.resolve('../test/licensing_plugin/config.public.ts'),
require.resolve('../test/licensing_plugin/config.legacy.ts'),
require.resolve('../test/endpoint_api_integration_no_ingest/config.ts'),
];
require('@kbn/plugin-helpers').babelRegister();

View file

@ -6,9 +6,17 @@
import { FtrProviderContext } from '../../ftr_provider_context';
export default function endpointAPIIntegrationTests({ loadTestFile }: FtrProviderContext) {
export default function endpointAPIIntegrationTests({
loadTestFile,
getService,
}: FtrProviderContext) {
describe('Endpoint plugin', function() {
const ingestManager = getService('ingestManager');
this.tags(['endpoint']);
before(async () => {
await ingestManager.setup();
});
loadTestFile(require.resolve('./index_pattern'));
loadTestFile(require.resolve('./resolver'));
loadTestFile(require.resolve('./metadata'));
loadTestFile(require.resolve('./alerts'));

View file

@ -0,0 +1,27 @@
/*
* 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 expect from '@kbn/expect/expect.js';
import { FtrProviderContext } from '../../ftr_provider_context';
export default function({ getService }: FtrProviderContext) {
const supertest = getService('supertest');
describe('Endpoint index pattern API', () => {
it('should retrieve the index pattern for events', async () => {
const { body } = await supertest.get('/api/endpoint/index_pattern/events').expect(200);
expect(body.indexPattern).to.eql('events-endpoint-*');
});
it('should retrieve the index pattern for metadata', async () => {
const { body } = await supertest.get('/api/endpoint/index_pattern/metadata').expect(200);
expect(body.indexPattern).to.eql('metrics-endpoint-*');
});
it('should not retrieve the index pattern for an invalid key', async () => {
await supertest.get('/api/endpoint/index_pattern/blah').expect(404);
});
});
}

View file

@ -159,7 +159,7 @@ export default function({ getService }: FtrProviderContext) {
.post('/api/endpoint/metadata')
.set('kbn-xsrf', 'xxx')
.send({
filter: `host.os.variant.keyword:${variantValue}`,
filter: `host.os.variant:${variantValue}`,
})
.expect(200);
expect(body.total).to.eql(2);

View file

@ -17,8 +17,8 @@ export default function resolverAPIIntegrationTests({ getService }: FtrProviderC
const esArchiver = getService('esArchiver');
describe('Resolver', () => {
before(() => esArchiver.load('endpoint/resolver/api_feature'));
after(() => esArchiver.unload('endpoint/resolver/api_feature'));
before(async () => await esArchiver.load('endpoint/resolver/api_feature'));
after(async () => await esArchiver.unload('endpoint/resolver/api_feature'));
describe('related events endpoint', () => {
const endpointID = '5a0c957f-b8e7-4538-965e-57e8bb86ad3a';

View file

@ -22,6 +22,7 @@ import {
import { SiemGraphQLClientProvider, SiemGraphQLClientFactoryProvider } from './siem_graphql_client';
import { InfraOpsSourceConfigurationProvider } from './infraops_source_configuration';
import { MachineLearningProvider } from './ml';
import { IngestManagerProvider } from './ingest_manager';
export const services = {
...commonServices,
@ -39,4 +40,5 @@ export const services = {
supertestWithoutAuth: SupertestWithoutAuthProvider,
usageAPI: UsageAPIProvider,
ml: MachineLearningProvider,
ingestManager: IngestManagerProvider,
};

View file

@ -0,0 +1,28 @@
/*
* 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 { FtrProviderContext } from '../ftr_provider_context';
import { setupRouteService, fleetSetupRouteService } from '../../../plugins/ingest_manager/common';
export function IngestManagerProvider({ getService }: FtrProviderContext) {
const supertest = getService('supertest');
return {
async setup() {
const headers = { accept: 'application/json', 'kbn-xsrf': 'some-xsrf-token' };
const { body } = await supertest
.get(fleetSetupRouteService.getFleetSetupPath())
.set(headers)
.expect(200);
if (!body.isInitialized) {
await supertest
.post(setupRouteService.getSetupPath())
.set(headers)
.expect(200);
}
},
};
}

View file

@ -0,0 +1,27 @@
/*
* 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 { FtrProviderContext } from '../ftr_provider_context';
export default function({ getService }: FtrProviderContext) {
const esArchiver = getService('esArchiver');
const supertest = getService('supertest');
describe('Endpoint alert API without ingest manager initialized', () => {
before(async () => {
await esArchiver.load('endpoint/alerts/api_feature');
await esArchiver.load('endpoint/alerts/host_api_feature');
});
after(async () => {
await esArchiver.unload('endpoint/alerts/api_feature');
await esArchiver.unload('endpoint/alerts/host_api_feature');
});
it('should return a 500', async () => {
await supertest.get('/api/endpoint/alerts').expect(500);
});
});
}

View file

@ -0,0 +1,16 @@
/*
* 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 { FtrProviderContext } from '../ftr_provider_context';
export default function endpointAPIIntegrationTests({ loadTestFile }: FtrProviderContext) {
describe('Endpoint plugin', function() {
this.tags('ciGroup7');
loadTestFile(require.resolve('./index_pattern'));
loadTestFile(require.resolve('./metadata'));
loadTestFile(require.resolve('./alerts'));
});
}

View file

@ -0,0 +1,16 @@
/*
* 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 { FtrProviderContext } from '../ftr_provider_context';
export default function({ getService }: FtrProviderContext) {
const supertest = getService('supertest');
describe('Endpoint index pattern API without ingest manager initialized', () => {
it('should not retrieve the index pattern for events', async () => {
await supertest.get('/api/endpoint/index_pattern/events').expect(404);
});
});
}

View file

@ -0,0 +1,22 @@
/*
* 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 { FtrProviderContext } from '../ftr_provider_context';
export default function({ getService }: FtrProviderContext) {
const esArchiver = getService('esArchiver');
const supertest = getService('supertest');
describe('test metadata api when ingest manager is not initialized', () => {
before(async () => await esArchiver.load('endpoint/metadata/api_feature'));
after(async () => await esArchiver.unload('endpoint/metadata/api_feature'));
it('metadata api should return a 500', async () => {
await supertest
.post('/api/endpoint/metadata')
.set('kbn-xsrf', 'xxx')
.send()
.expect(500);
});
});
}

View file

@ -0,0 +1,19 @@
/*
* 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 { FtrConfigProviderContext } from '@kbn/test/types/ftr';
export default async function({ readConfigFile }: FtrConfigProviderContext) {
const xPackAPITestsConfig = await readConfigFile(require.resolve('../api_integration/config.js'));
return {
...xPackAPITestsConfig.getAll(),
testFiles: [require.resolve('./apis')],
junit: {
reportName: 'X-Pack Endpoint API Integration Without Ingest Tests',
},
};
}

View file

@ -0,0 +1,11 @@
/*
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
* or more contributor license agreements. Licensed under the Elastic License;
* you may not use this file except in compliance with the Elastic License.
*/
import { GenericFtrProviderContext } from '@kbn/test/types/ftr';
import { services } from '../api_integration/services';
export type FtrProviderContext = GenericFtrProviderContext<typeof services, {}>;

View file

@ -1,33 +1,62 @@
{
"type": "index",
"value": {
"aliases": {
},
"index": "endpoint-agent-1",
"aliases": {},
"index": "metrics-endpoint-default-1",
"mappings": {
"_meta": {
"version": "1.5.0-dev"
},
"date_detection": false,
"dynamic_templates": [
{
"strings_as_keyword": {
"mapping": {
"ignore_above": 1024,
"type": "keyword"
},
"match_mapping_type": "string"
}
}
],
"properties": {
"@timestamp": {
"type": "long"
"type": "date"
},
"elastic": {
"properties": {
"agent": {
"properties": {
"id": {
"ignore_above": 1024,
"type": "keyword"
}
},
"type": "object"
}
}
},
"agent": {
"properties": {
"id": {
"fields": {
"keyword": {
"ignore_above": 256,
"type": "keyword"
}
},
"type": "text"
"ignore_above": 1024,
"type": "keyword"
},
"name": {
"ignore_above": 1024,
"type": "keyword"
},
"version": {
"fields": {
"keyword": {
"ignore_above": 256,
"type": "keyword"
}
},
"type": "text"
"ignore_above": 1024,
"type": "keyword"
}
}
},
"ecs": {
"properties": {
"version": {
"ignore_above": 1024,
"type": "keyword"
}
}
},
@ -36,109 +65,71 @@
"policy": {
"properties": {
"id": {
"fields": {
"keyword": {
"ignore_above": 256,
"type": "keyword"
}
},
"type": "text"
},
"name": {
"fields": {
"keyword": {
"ignore_above": 256,
"type": "keyword"
}
},
"type": "text"
"ignore_above": 1024,
"type": "keyword"
}
}
},
"type": "object"
}
}
},
"event": {
"properties": {
"created": {
"type": "long"
"type": "date"
}
}
},
"host": {
"properties": {
"architecture": {
"ignore_above": 1024,
"type": "keyword"
},
"hostname": {
"fields": {
"keyword": {
"ignore_above": 256,
"type": "keyword"
}
},
"type": "text"
"ignore_above": 1024,
"type": "keyword"
},
"id": {
"fields": {
"keyword": {
"ignore_above": 256,
"type": "keyword"
}
},
"type": "text"
"ignore_above": 1024,
"type": "keyword"
},
"ip": {
"fields": {
"keyword": {
"ignore_above": 256,
"type": "keyword"
}
},
"type": "text"
"type": "ip"
},
"mac": {
"fields": {
"keyword": {
"ignore_above": 256,
"type": "keyword"
}
},
"type": "text"
"ignore_above": 1024,
"type": "keyword"
},
"os": {
"properties": {
"full": {
"fields": {
"keyword": {
"ignore_above": 256,
"type": "keyword"
"text": {
"norms": false,
"type": "text"
}
},
"type": "text"
"ignore_above": 1024,
"type": "keyword"
},
"name": {
"fields": {
"keyword": {
"ignore_above": 256,
"type": "keyword"
"text": {
"norms": false,
"type": "text"
}
},
"type": "text"
"ignore_above": 1024,
"type": "keyword"
},
"variant": {
"fields": {
"keyword": {
"ignore_above": 256,
"type": "keyword"
}
},
"type": "text"
"ignore_above": 1024,
"type": "keyword"
},
"version": {
"fields": {
"keyword": {
"ignore_above": 256,
"type": "keyword"
}
},
"type": "text"
"ignore_above": 1024,
"type": "keyword"
}
}
}
@ -146,11 +137,16 @@
}
}
},
"order": 1,
"settings": {
"index": {
"number_of_replicas": "1",
"number_of_shards": "1"
"mapping": {
"total_fields": {
"limit": 10000
}
},
"refresh_interval": "5s"
}
}
}
}
}

View file

@ -2,7 +2,7 @@
"type": "doc",
"value": {
"id": "3KVN2G8BYQH1gtPUuYk7",
"index": "endpoint-agent-1",
"index": "metrics-endpoint-default-1",
"source": {
"@timestamp": 1579881969541,
"agent": {
@ -51,7 +51,7 @@
"type": "doc",
"value": {
"id": "3aVN2G8BYQH1gtPUuYk7",
"index": "endpoint-agent-1",
"index": "metrics-endpoint-default-1",
"source": {
"@timestamp": 1579881969541,
"agent": {
@ -99,7 +99,7 @@
"type": "doc",
"value": {
"id": "3qVN2G8BYQH1gtPUuYk7",
"index": "endpoint-agent-1",
"index": "metrics-endpoint-default-1",
"source": {
"@timestamp": 1579881969541,
"agent": {
@ -145,7 +145,7 @@
"type": "doc",
"value": {
"id": "36VN2G8BYQH1gtPUuYk7",
"index": "endpoint-agent-1",
"index": "metrics-endpoint-default-1",
"source": {
"@timestamp": 1579878369541,
"agent": {
@ -194,7 +194,7 @@
"type": "doc",
"value": {
"id": "4KVN2G8BYQH1gtPUuYk7",
"index": "endpoint-agent-1",
"index": "metrics-endpoint-default-1",
"source": {
"@timestamp": 1579878369541,
"agent": {
@ -241,7 +241,7 @@
"type": "doc",
"value": {
"id": "4aVN2G8BYQH1gtPUuYk7",
"index": "endpoint-agent-1",
"index": "metrics-endpoint-default-1",
"source": {
"@timestamp": 1579878369541,
"agent": {
@ -288,7 +288,7 @@
"type": "doc",
"value": {
"id": "4qVN2G8BYQH1gtPUuYk7",
"index": "endpoint-agent-1",
"index": "metrics-endpoint-default-1",
"source": {
"@timestamp": 1579874769541,
"agent": {
@ -336,7 +336,7 @@
"type": "doc",
"value": {
"id": "46VN2G8BYQH1gtPUuYk7",
"index": "endpoint-agent-1",
"index": "metrics-endpoint-default-1",
"source": {
"@timestamp": 1579874769541,
"agent": {
@ -383,7 +383,7 @@
"type": "doc",
"value": {
"id": "5KVN2G8BYQH1gtPUuYk7",
"index": "endpoint-agent-1",
"index": "metrics-endpoint-default-1",
"source": {
"@timestamp": 1579874769541,
"agent": {

View file

@ -1,50 +1,62 @@
{
"type": "index",
"value": {
"aliases": {
},
"index": "endpoint-agent-1",
"aliases": {},
"index": "metrics-endpoint-default-1",
"mappings": {
"_meta": {
"version": "1.5.0-dev"
},
"date_detection": false,
"dynamic_templates": [
{
"strings_as_keyword": {
"mapping": {
"ignore_above": 1024,
"type": "keyword"
},
"match_mapping_type": "string"
}
}
],
"properties": {
"@timestamp": {
"type": "long"
"type": "date"
},
"elastic": {
"properties": {
"agent": {
"properties": {
"id": {
"fields": {
"keyword": {
"ignore_above": 256,
"type": "keyword"
}
},
"type": "text"
"ignore_above": 1024,
"type": "keyword"
}
}
},
"type": "object"
}
}
},
"agent": {
"properties": {
"id": {
"fields": {
"keyword": {
"ignore_above": 256,
"type": "keyword"
}
},
"type": "text"
"ignore_above": 1024,
"type": "keyword"
},
"name": {
"ignore_above": 1024,
"type": "keyword"
},
"version": {
"fields": {
"keyword": {
"ignore_above": 256,
"type": "keyword"
}
},
"type": "text"
"ignore_above": 1024,
"type": "keyword"
}
}
},
"ecs": {
"properties": {
"version": {
"ignore_above": 1024,
"type": "keyword"
}
}
},
@ -53,109 +65,71 @@
"policy": {
"properties": {
"id": {
"fields": {
"keyword": {
"ignore_above": 256,
"type": "keyword"
}
},
"type": "text"
},
"name": {
"fields": {
"keyword": {
"ignore_above": 256,
"type": "keyword"
}
},
"type": "text"
"ignore_above": 1024,
"type": "keyword"
}
}
},
"type": "object"
}
}
},
"event": {
"properties": {
"created": {
"type": "long"
"type": "date"
}
}
},
"host": {
"properties": {
"architecture": {
"ignore_above": 1024,
"type": "keyword"
},
"hostname": {
"fields": {
"keyword": {
"ignore_above": 256,
"type": "keyword"
}
},
"type": "text"
"ignore_above": 1024,
"type": "keyword"
},
"id": {
"fields": {
"keyword": {
"ignore_above": 256,
"type": "keyword"
}
},
"type": "text"
"ignore_above": 1024,
"type": "keyword"
},
"ip": {
"fields": {
"keyword": {
"ignore_above": 256,
"type": "keyword"
}
},
"type": "text"
"type": "ip"
},
"mac": {
"fields": {
"keyword": {
"ignore_above": 256,
"type": "keyword"
}
},
"type": "text"
"ignore_above": 1024,
"type": "keyword"
},
"os": {
"properties": {
"full": {
"fields": {
"keyword": {
"ignore_above": 256,
"type": "keyword"
"text": {
"norms": false,
"type": "text"
}
},
"type": "text"
"ignore_above": 1024,
"type": "keyword"
},
"name": {
"fields": {
"keyword": {
"ignore_above": 256,
"type": "keyword"
"text": {
"norms": false,
"type": "text"
}
},
"type": "text"
"ignore_above": 1024,
"type": "keyword"
},
"variant": {
"fields": {
"keyword": {
"ignore_above": 256,
"type": "keyword"
}
},
"type": "text"
"ignore_above": 1024,
"type": "keyword"
},
"version": {
"fields": {
"keyword": {
"ignore_above": 256,
"type": "keyword"
}
},
"type": "text"
"ignore_above": 1024,
"type": "keyword"
}
}
}
@ -163,10 +137,15 @@
}
}
},
"order": 1,
"settings": {
"index": {
"number_of_replicas": "1",
"number_of_shards": "1"
"mapping": {
"total_fields": {
"limit": 10000
}
},
"refresh_interval": "5s"
}
}
}

View file

@ -30,6 +30,7 @@ export default async function({ readConfigFile }: FtrConfigProviderContext) {
...xpackFunctionalConfig.get('kbnTestServer.serverArgs'),
'--xpack.endpoint.enabled=true',
'--xpack.ingestManager.enabled=true',
'--xpack.ingestManager.epm.enabled=true',
'--xpack.ingestManager.fleet.enabled=true',
],
},