From d2aa659860f4d868753517b8baa5da7da680e99f Mon Sep 17 00:00:00 2001 From: Matt Apperson Date: Fri, 13 Jul 2018 11:32:10 -0400 Subject: [PATCH] [Beats Management] add get beat endpoint (#20603) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * [Beats Management] Move tokens to use JWT, add more complete test suite (#20317) * inital effort to move to JWT and added jest based tests on libs * assign beats tests all passing * token tests now pass * add more tests * all tests now green * fix broken test, this is beats CM not logstash 😊 * added readme * move enrollment token back to a hash * remove un-needed comment * alias lodash get to avoid confusion * isolated hash creation * inital effort to move to JWT and added jest based tests on libs * assign beats tests all passing * token tests now pass * add more tests * all tests now green * move enrollment token back to a hash * remove un-needed comment * alias lodash get to avoid confusion * isolated hash creation * Add initial efforts for backend framework adapter testing * move ES code to a DatabaseAdapter from BackendAdapter and add a TON of types for ES * re-typed * renamed types to match pattern * aditional renames * adapter tests should always just use adapterSetup(); * database now uses InternalRequest * corrected spelling of framework * fix typings * remove CRUFT * RequestOrInternal * Dont pass around request objects everywhere, just pass the user. Also, removed hapi types as they were not compatible * fix tests, add test, removed extra comment * Moved critical path code from route, to more easeley tested domain * fix auth * remove beat verification, added get beat endpoint to return configs * fix type * update createGetBeatConfigurationRoute URL * rename method * update to match PR #20566 * updated lock file * fix bad merge * update TSLinting --- .../plugins/beats/common/constants/index.ts | 5 +- x-pack/plugins/beats/index.ts | 22 +-- x-pack/plugins/beats/readme.md | 10 +- .../beats/elasticsearch_beats_adapter.ts | 29 ++- .../adapters/beats/memory_beats_adapter.ts | 36 ++-- .../database/__tests__/kibana.test.ts | 3 +- .../lib/adapters/database/adapter_types.ts | 34 +--- .../database/kibana_database_adapter.ts | 15 +- .../kibana/kibana_framework_adapter.ts | 99 ++++++++++ .../kibana/testing_framework_adapter.ts | 79 ++++++++ .../framework/__tests__/kibana.test.ts | 5 +- .../lib/adapters/framework/adapter_types.ts | 8 +- .../framework/kibana_framework_adapter.ts | 10 +- .../framework/testing_framework_adapter.ts | 13 +- .../lib/adapters/tokens/adapter_types.ts | 5 +- .../tokens/elasticsearch_tokens_adapter.ts | 31 +-- .../adapters/tokens/memory_tokens_adapter.ts | 17 +- .../beats/server/lib/compose/kibana.ts | 22 +-- .../__tests__/beats/assign_tags.test.ts | 8 +- .../domains/__tests__/beats/enroll.test.ts | 13 +- .../domains/__tests__/beats/verify.test.ts | 183 ------------------ .../lib/domains/__tests__/tokens.test.ts | 14 +- .../plugins/beats/server/lib/domains/beats.ts | 85 +++----- .../plugins/beats/server/lib/domains/tags.ts | 15 +- .../beats/server/lib/domains/tokens.ts | 25 +-- x-pack/plugins/beats/server/lib/lib.ts | 6 + .../plugins/beats/server/management_server.ts | 5 +- .../server/rest_api/beats/configuration.ts | 54 ++++++ .../beats/server/rest_api/beats/enroll.ts | 32 +-- .../server/rest_api/beats/tag_assignment.ts | 5 +- .../server/rest_api/beats/tag_removal.ts | 5 +- .../beats/server/rest_api/beats/update.ts | 2 - .../beats/server/rest_api/beats/verify.ts | 63 ------ .../plugins/beats/server/rest_api/tags/set.ts | 6 +- .../beats/server/rest_api/tokens/create.ts | 5 +- .../error_wrappers/wrap_es_error.test.ts | 4 +- .../utils/error_wrappers/wrap_es_error.ts | 4 +- .../server/utils/find_non_existent_items.ts | 19 +- x-pack/plugins/beats/wallaby.js | 8 +- .../error_wrappers/__tests__/wrap_es_error.js | 41 ---- .../api_integration/apis/beats/enroll_beat.js | 2 +- .../api_integration/apis/beats/get_beat.js | 51 +++++ .../test/api_integration/apis/beats/index.js | 4 +- .../api_integration/apis/beats/update_beat.js | 30 --- .../apis/beats/verify_beats.js | 66 ------- .../es_archives/beats/list/data.json | 139 +++++++++++++ .../es_archives/beats/list/data.json.gz | Bin 521 -> 0 bytes .../es_archives/beats/list/mappings.json | 5 +- x-pack/yarn.lock | 4 + 49 files changed, 600 insertions(+), 746 deletions(-) create mode 100644 x-pack/plugins/beats/server/lib/adapters/famework/kibana/kibana_framework_adapter.ts create mode 100644 x-pack/plugins/beats/server/lib/adapters/famework/kibana/testing_framework_adapter.ts delete mode 100644 x-pack/plugins/beats/server/lib/domains/__tests__/beats/verify.test.ts create mode 100644 x-pack/plugins/beats/server/rest_api/beats/configuration.ts delete mode 100644 x-pack/plugins/beats/server/rest_api/beats/verify.ts delete mode 100644 x-pack/plugins/logstash/server/lib/error_wrappers/__tests__/wrap_es_error.js create mode 100644 x-pack/test/api_integration/apis/beats/get_beat.js delete mode 100644 x-pack/test/api_integration/apis/beats/verify_beats.js create mode 100644 x-pack/test/functional/es_archives/beats/list/data.json delete mode 100644 x-pack/test/functional/es_archives/beats/list/data.json.gz diff --git a/x-pack/plugins/beats/common/constants/index.ts b/x-pack/plugins/beats/common/constants/index.ts index 4662865e208a7..756ffcf07e3ea 100644 --- a/x-pack/plugins/beats/common/constants/index.ts +++ b/x-pack/plugins/beats/common/constants/index.ts @@ -6,7 +6,4 @@ export { PLUGIN } from './plugin'; export { INDEX_NAMES } from './index_names'; -export { - UNIQUENESS_ENFORCING_TYPES, - ConfigurationBlockTypes, -} from './configuration_blocks'; +export { UNIQUENESS_ENFORCING_TYPES, ConfigurationBlockTypes } from './configuration_blocks'; diff --git a/x-pack/plugins/beats/index.ts b/x-pack/plugins/beats/index.ts index 25be5728c93bb..ced89c186f73e 100644 --- a/x-pack/plugins/beats/index.ts +++ b/x-pack/plugins/beats/index.ts @@ -10,20 +10,18 @@ import { initServerWithKibana } from './server/kibana.index'; const DEFAULT_ENROLLMENT_TOKENS_TTL_S = 10 * 60; // 10 minutes -export const config = Joi.object({ - enabled: Joi.boolean().default(true), - encryptionKey: Joi.string(), - enrollmentTokensTtlInSeconds: Joi.number() - .integer() - .min(1) - .default(DEFAULT_ENROLLMENT_TOKENS_TTL_S), -}).default(); -export const configPrefix = 'xpack.beats'; - export function beats(kibana: any) { return new kibana.Plugin({ - config: () => config, - configPrefix, + config: () => + Joi.object({ + enabled: Joi.boolean().default(true), + encryptionKey: Joi.string(), + enrollmentTokensTtlInSeconds: Joi.number() + .integer() + .min(1) + .default(DEFAULT_ENROLLMENT_TOKENS_TTL_S), + }).default(), + configPrefix: 'xpack.beats', id: PLUGIN.ID, require: ['kibana', 'elasticsearch', 'xpack_main'], init(server: any) { diff --git a/x-pack/plugins/beats/readme.md b/x-pack/plugins/beats/readme.md index 725fe587c5aa8..fdd56a393e573 100644 --- a/x-pack/plugins/beats/readme.md +++ b/x-pack/plugins/beats/readme.md @@ -1,15 +1,7 @@ # Documentation for Beats CM in x-pack kibana -### Run tests (from x-pack dir) - -Functional tests +### Run tests ``` node scripts/jest.js plugins/beats --watch ``` - -Functional API tests - -``` -node scripts/functional_tests --config test/api_integration/config -``` diff --git a/x-pack/plugins/beats/server/lib/adapters/beats/elasticsearch_beats_adapter.ts b/x-pack/plugins/beats/server/lib/adapters/beats/elasticsearch_beats_adapter.ts index e2321ac6739a8..c0be74e1629f6 100644 --- a/x-pack/plugins/beats/server/lib/adapters/beats/elasticsearch_beats_adapter.ts +++ b/x-pack/plugins/beats/server/lib/adapters/beats/elasticsearch_beats_adapter.ts @@ -30,10 +30,7 @@ export class ElasticsearchBeatsAdapter implements CMBeatsAdapter { type: '_doc', }; - const response = await this.database.get( - this.framework.internalUser, - params - ); + const response = await this.database.get(this.framework.internalUser, params); if (!response.found) { return null; } @@ -155,13 +152,11 @@ export class ElasticsearchBeatsAdapter implements CMBeatsAdapter { refresh: 'wait_for', type: '_doc', }); - return _get(response, 'items', []).map( - (item: any, resultIdx: number) => ({ - idxInRequest: removals[resultIdx].idxInRequest, - result: item.update.result, - status: item.update.status, - }) - ); + return _get(response, 'items', []).map((item: any, resultIdx: number) => ({ + idxInRequest: removals[resultIdx].idxInRequest, + result: item.update.result, + status: item.update.status, + })); } public async assignTagsToBeats( @@ -193,12 +188,10 @@ export class ElasticsearchBeatsAdapter implements CMBeatsAdapter { refresh: 'wait_for', type: '_doc', }); - return _get(response, 'items', []).map( - (item: any, resultIdx: any) => ({ - idxInRequest: assignments[resultIdx].idxInRequest, - result: item.update.result, - status: item.update.status, - }) - ); + return _get(response, 'items', []).map((item: any, resultIdx: any) => ({ + idxInRequest: assignments[resultIdx].idxInRequest, + result: item.update.result, + status: item.update.status, + })); } } diff --git a/x-pack/plugins/beats/server/lib/adapters/beats/memory_beats_adapter.ts b/x-pack/plugins/beats/server/lib/adapters/beats/memory_beats_adapter.ts index a904b1d6831cd..041b34d29b49e 100644 --- a/x-pack/plugins/beats/server/lib/adapters/beats/memory_beats_adapter.ts +++ b/x-pack/plugins/beats/server/lib/adapters/beats/memory_beats_adapter.ts @@ -19,7 +19,7 @@ export class MemoryBeatsAdapter implements CMBeatsAdapter { } public async get(id: string) { - return this.beatsDB.find(beat => beat.id === id); + return this.beatsDB.find(beat => beat.id === id) || null; } public async insert(beat: CMBeat) { @@ -65,17 +65,15 @@ export class MemoryBeatsAdapter implements CMBeatsAdapter { ): Promise { const beatIds = removals.map(r => r.beatId); - const response = this.beatsDB - .filter(beat => beatIds.includes(beat.id)) - .map(beat => { - const tagData = removals.find(r => r.beatId === beat.id); - if (tagData) { - if (beat.tags) { - beat.tags = beat.tags.filter(tag => tag !== tagData.tag); - } + const response = this.beatsDB.filter(beat => beatIds.includes(beat.id)).map(beat => { + const tagData = removals.find(r => r.beatId === beat.id); + if (tagData) { + if (beat.tags) { + beat.tags = beat.tags.filter(tag => tag !== tagData.tag); } - return beat; - }); + } + return beat; + }); return response.map((item: CMBeat, resultIdx: number) => ({ idxInRequest: removals[resultIdx].idxInRequest, @@ -100,9 +98,7 @@ export class MemoryBeatsAdapter implements CMBeatsAdapter { if (!beat.tags) { beat.tags = []; } - const nonExistingTags = tags.filter( - (t: string) => beat.tags && !beat.tags.includes(t) - ); + const nonExistingTags = tags.filter((t: string) => beat.tags && !beat.tags.includes(t)); if (nonExistingTags.length > 0) { beat.tags = beat.tags.concat(nonExistingTags); @@ -111,12 +107,10 @@ export class MemoryBeatsAdapter implements CMBeatsAdapter { return beat; }); - return assignments.map( - (item: BeatsTagAssignment, resultIdx: number) => ({ - idxInRequest: assignments[resultIdx].idxInRequest, - result: 'updated', - status: 200, - }) - ); + return assignments.map((item: BeatsTagAssignment, resultIdx: number) => ({ + idxInRequest: assignments[resultIdx].idxInRequest, + result: 'updated', + status: 200, + })); } } diff --git a/x-pack/plugins/beats/server/lib/adapters/database/__tests__/kibana.test.ts b/x-pack/plugins/beats/server/lib/adapters/database/__tests__/kibana.test.ts index 19bf05c3c777e..c18c5b5bc4c76 100644 --- a/x-pack/plugins/beats/server/lib/adapters/database/__tests__/kibana.test.ts +++ b/x-pack/plugins/beats/server/lib/adapters/database/__tests__/kibana.test.ts @@ -28,7 +28,6 @@ contractTests('Kibana Database Adapter', { return await es.cleanup(); }, adapterSetup: () => { - return new KibanaDatabaseAdapter(kbnServer.server.plugins - .elasticsearch as DatabaseKbnESPlugin); + return new KibanaDatabaseAdapter(kbnServer.server.plugins.elasticsearch as DatabaseKbnESPlugin); }, }); diff --git a/x-pack/plugins/beats/server/lib/adapters/database/adapter_types.ts b/x-pack/plugins/beats/server/lib/adapters/database/adapter_types.ts index 36b5a35742bc9..22ee7a34066b3 100644 --- a/x-pack/plugins/beats/server/lib/adapters/database/adapter_types.ts +++ b/x-pack/plugins/beats/server/lib/adapters/database/adapter_types.ts @@ -5,10 +5,7 @@ */ import { FrameworkRequest, FrameworkUser } from '../framework/adapter_types'; export interface DatabaseAdapter { - putTemplate( - user: FrameworkUser, - params: DatabasePutTemplateParams - ): Promise; + putTemplate(user: FrameworkUser, params: DatabasePutTemplateParams): Promise; get( user: FrameworkUser, params: DatabaseGetParams @@ -25,27 +22,17 @@ export interface DatabaseAdapter { user: FrameworkUser, params: DatabaseDeleteDocumentParams ): Promise; - mget( - user: FrameworkUser, - params: DatabaseMGetParams - ): Promise>; + mget(user: FrameworkUser, params: DatabaseMGetParams): Promise>; bulk( user: FrameworkUser, params: DatabaseBulkIndexDocumentsParams ): Promise; - search( - user: FrameworkUser, - params: DatabaseSearchParams - ): Promise>; + search(user: FrameworkUser, params: DatabaseSearchParams): Promise>; } export interface DatabaseKbnESCluster { callWithInternalUser(esMethod: string, options: {}): Promise; - callWithRequest( - req: FrameworkRequest, - esMethod: string, - options: {} - ): Promise; + callWithRequest(req: FrameworkRequest, esMethod: string, options: {}): Promise; } export interface DatabaseKbnESPlugin { @@ -142,14 +129,11 @@ export interface DatabaseBulkResponse { took: number; errors: boolean; items: Array< - | DatabaseDeleteDocumentResponse - | DatabaseIndexDocumentResponse - | DatabaseUpdateDocumentResponse + DatabaseDeleteDocumentResponse | DatabaseIndexDocumentResponse | DatabaseUpdateDocumentResponse >; } -export interface DatabaseBulkIndexDocumentsParams - extends DatabaseGenericParams { +export interface DatabaseBulkIndexDocumentsParams extends DatabaseGenericParams { waitForActiveShards?: string; refresh?: DatabaseRefresh; routing?: string; @@ -299,11 +283,7 @@ export interface DatabaseGetParams extends DatabaseGenericParams { export type DatabaseNameList = string | string[] | boolean; export type DatabaseRefresh = boolean | 'true' | 'false' | 'wait_for' | ''; -export type DatabaseVersionType = - | 'internal' - | 'external' - | 'external_gte' - | 'force'; +export type DatabaseVersionType = 'internal' | 'external' | 'external_gte' | 'force'; export type ExpandWildcards = 'open' | 'closed' | 'none' | 'all'; export type DefaultOperator = 'AND' | 'OR'; export type DatabaseConflicts = 'abort' | 'proceed'; diff --git a/x-pack/plugins/beats/server/lib/adapters/database/kibana_database_adapter.ts b/x-pack/plugins/beats/server/lib/adapters/database/kibana_database_adapter.ts index 6c4d96446abd4..ba593a793e34b 100644 --- a/x-pack/plugins/beats/server/lib/adapters/database/kibana_database_adapter.ts +++ b/x-pack/plugins/beats/server/lib/adapters/database/kibana_database_adapter.ts @@ -30,10 +30,7 @@ export class KibanaDatabaseAdapter implements DatabaseAdapter { constructor(kbnElasticSearch: DatabaseKbnESPlugin) { this.es = kbnElasticSearch.getCluster('admin'); } - public async putTemplate( - user: FrameworkUser, - params: DatabasePutTemplateParams - ): Promise { + public async putTemplate(user: FrameworkUser, params: DatabasePutTemplateParams): Promise { const callES = this.getCallType(user); const result = await callES('indices.putTemplate', params); return result; @@ -59,10 +56,7 @@ export class KibanaDatabaseAdapter implements DatabaseAdapter { // todo } - public async bulk( - user: FrameworkUser, - params: DatabaseBulkIndexDocumentsParams - ): Promise { + public async bulk(user: FrameworkUser, params: DatabaseBulkIndexDocumentsParams): Promise { const callES = this.getCallType(user); const result = await callES('bulk', params); return result; @@ -76,10 +70,7 @@ export class KibanaDatabaseAdapter implements DatabaseAdapter { const result = await callES('create', params); return result; } - public async index( - user: FrameworkUser, - params: DatabaseIndexDocumentParams - ): Promise { + public async index(user: FrameworkUser, params: DatabaseIndexDocumentParams): Promise { const callES = this.getCallType(user); const result = await callES('index', params); return result; diff --git a/x-pack/plugins/beats/server/lib/adapters/famework/kibana/kibana_framework_adapter.ts b/x-pack/plugins/beats/server/lib/adapters/famework/kibana/kibana_framework_adapter.ts new file mode 100644 index 0000000000000..71703da8632ec --- /dev/null +++ b/x-pack/plugins/beats/server/lib/adapters/famework/kibana/kibana_framework_adapter.ts @@ -0,0 +1,99 @@ +/* + * 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 { + BackendFrameworkAdapter, + FrameworkRequest, + FrameworkRouteOptions, + WrappableRequest, +} from '../../../lib'; + +import { IStrictReply, Request, Server } from 'hapi'; +import { internalFrameworkRequest, wrapRequest } from '../../../../utils/wrap_request'; + +export class KibanaBackendFrameworkAdapter implements BackendFrameworkAdapter { + public version: string; + private server: Server; + private cryptoHash: string | null; + + constructor(hapiServer: Server) { + this.server = hapiServer; + this.version = hapiServer.plugins.kibana.status.plugin.version; + this.cryptoHash = null; + + this.validateConfig(); + } + + public getSetting(settingPath: string) { + // TODO type check server properly + if (settingPath === 'xpack.beats.encryptionKey') { + // @ts-ignore + return this.server.config().get(settingPath) || this.cryptoHash; + } + // @ts-ignore + return this.server.config().get(settingPath) || this.cryptoHash; + } + + public exposeStaticDir(urlPath: string, dir: string): void { + this.server.route({ + handler: { + directory: { + path: dir, + }, + }, + method: 'GET', + path: urlPath, + }); + } + + public registerRoute( + route: FrameworkRouteOptions + ) { + const wrappedHandler = (request: any, reply: IStrictReply) => + route.handler(wrapRequest(request), reply); + + this.server.route({ + config: route.config, + handler: wrappedHandler, + method: route.method, + path: route.path, + }); + } + + public installIndexTemplate(name: string, template: {}) { + return this.callWithInternalUser('indices.putTemplate', { + body: template, + name, + }); + } + + public async callWithInternalUser(esMethod: string, options: {}) { + const { elasticsearch } = this.server.plugins; + const { callWithInternalUser } = elasticsearch.getCluster('admin'); + return await callWithInternalUser(esMethod, options); + } + + public async callWithRequest(req: FrameworkRequest, ...rest: any[]) { + const internalRequest = req[internalFrameworkRequest]; + const { elasticsearch } = internalRequest.server.plugins; + const { callWithRequest } = elasticsearch.getCluster('data'); + const fields = await callWithRequest(internalRequest, ...rest); + return fields; + } + + private validateConfig() { + // @ts-ignore + const config = this.server.config(); + const encryptionKey = config.get('xpack.beats.encryptionKey'); + + if (!encryptionKey) { + this.server.log( + 'Using a default encryption key for xpack.beats.encryptionKey. It is recommended that you set xpack.beats.encryptionKey in kibana.yml with a unique token' + ); + this.cryptoHash = 'xpack_beats_default_encryptionKey'; + } + } +} diff --git a/x-pack/plugins/beats/server/lib/adapters/famework/kibana/testing_framework_adapter.ts b/x-pack/plugins/beats/server/lib/adapters/famework/kibana/testing_framework_adapter.ts new file mode 100644 index 0000000000000..757464fa6cdc7 --- /dev/null +++ b/x-pack/plugins/beats/server/lib/adapters/famework/kibana/testing_framework_adapter.ts @@ -0,0 +1,79 @@ +/* + * 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 { Client } from 'elasticsearch'; +import { Request } from 'hapi'; +import { get } from 'lodash'; +import { + BackendFrameworkAdapter, + FrameworkRequest, + FrameworkRouteOptions, + WrappableRequest, +} from '../../../lib'; + +interface TestSettings { + enrollmentTokensTtlInSeconds: number; + encryptionKey: string; +} + +export class TestingBackendFrameworkAdapter implements BackendFrameworkAdapter { + public version: string; + private client: Client | null; + private settings: TestSettings; + + constructor(client: Client | null, settings: TestSettings) { + this.client = client; + this.settings = settings || { + encryptionKey: 'something_who_cares', + enrollmentTokensTtlInSeconds: 10 * 60, // 10 minutes + }; + this.version = 'testing'; + } + + public getSetting(settingPath: string) { + switch (settingPath) { + case 'xpack.beats.enrollmentTokensTtlInSeconds': + return this.settings.enrollmentTokensTtlInSeconds; + case 'xpack.beats.encryptionKey': + return this.settings.encryptionKey; + } + } + + public exposeStaticDir(urlPath: string, dir: string): void { + // not yet testable + } + + public registerRoute( + route: FrameworkRouteOptions + ) { + // not yet testable + } + + public installIndexTemplate(name: string, template: {}) { + if (this.client) { + return this.client.indices.putTemplate({ + body: template, + name, + }); + } + } + + public async callWithInternalUser(esMethod: string, options: {}) { + const api = get(this.client, esMethod); + + api(options); + + return await api(options); + } + + public async callWithRequest(req: FrameworkRequest, esMethod: string, options: {}) { + const api = get(this.client, esMethod); + + api(options); + + return await api(options); + } +} diff --git a/x-pack/plugins/beats/server/lib/adapters/framework/__tests__/kibana.test.ts b/x-pack/plugins/beats/server/lib/adapters/framework/__tests__/kibana.test.ts index c87a7374837e3..5a539ebe6e5e7 100644 --- a/x-pack/plugins/beats/server/lib/adapters/framework/__tests__/kibana.test.ts +++ b/x-pack/plugins/beats/server/lib/adapters/framework/__tests__/kibana.test.ts @@ -7,12 +7,9 @@ // @ts-ignore import { createEsTestCluster } from '@kbn/test'; +import { config as beatsPluginConfig, configPrefix } from '../../../../..'; // @ts-ignore import * as kbnTestServer from '../../../../../../../../src/test_utils/kbn_server'; -import { - config as beatsPluginConfig, - configPrefix, -} from '../../../../../index'; import { KibanaBackendFrameworkAdapter } from '../kibana_framework_adapter'; import { contractTests } from './test_contract'; diff --git a/x-pack/plugins/beats/server/lib/adapters/framework/adapter_types.ts b/x-pack/plugins/beats/server/lib/adapters/framework/adapter_types.ts index 4be3589a6f043..014c041b11f0a 100644 --- a/x-pack/plugins/beats/server/lib/adapters/framework/adapter_types.ts +++ b/x-pack/plugins/beats/server/lib/adapters/framework/adapter_types.ts @@ -54,10 +54,10 @@ export interface FrameworkRouteOptions< config?: {}; } -export type FrameworkRouteHandler< - RouteRequest extends FrameworkWrappableRequest, - RouteResponse -> = (request: FrameworkRequest, reply: any) => void; +export type FrameworkRouteHandler = ( + request: FrameworkRequest, + reply: any +) => void; export interface FrameworkWrappableRequest< Payload = any, diff --git a/x-pack/plugins/beats/server/lib/adapters/framework/kibana_framework_adapter.ts b/x-pack/plugins/beats/server/lib/adapters/framework/kibana_framework_adapter.ts index 7113baf5c26e6..d07170acb7050 100644 --- a/x-pack/plugins/beats/server/lib/adapters/framework/kibana_framework_adapter.ts +++ b/x-pack/plugins/beats/server/lib/adapters/framework/kibana_framework_adapter.ts @@ -53,12 +53,10 @@ export class KibanaBackendFrameworkAdapter implements BackendFrameworkAdapter { }); } - public registerRoute< - RouteRequest extends FrameworkWrappableRequest, - RouteResponse - >(route: FrameworkRouteOptions) { - const wrappedHandler = (request: any, reply: any) => - route.handler(wrapRequest(request), reply); + public registerRoute( + route: FrameworkRouteOptions + ) { + const wrappedHandler = (request: any, reply: any) => route.handler(wrapRequest(request), reply); this.server.route({ handler: wrappedHandler, diff --git a/x-pack/plugins/beats/server/lib/adapters/framework/testing_framework_adapter.ts b/x-pack/plugins/beats/server/lib/adapters/framework/testing_framework_adapter.ts index 3bffe274fba8d..3280220893892 100644 --- a/x-pack/plugins/beats/server/lib/adapters/framework/testing_framework_adapter.ts +++ b/x-pack/plugins/beats/server/lib/adapters/framework/testing_framework_adapter.ts @@ -49,10 +49,9 @@ export class TestingBackendFrameworkAdapter implements BackendFrameworkAdapter { // not yet testable } - public registerRoute< - RouteRequest extends FrameworkWrappableRequest, - RouteResponse - >(route: FrameworkRouteOptions) { + public registerRoute( + route: FrameworkRouteOptions + ) { // not yet testable } @@ -68,11 +67,7 @@ export class TestingBackendFrameworkAdapter implements BackendFrameworkAdapter { return await api(options); } - public async callWithRequest( - req: FrameworkRequest, - esMethod: string, - options: {} - ) { + public async callWithRequest(req: FrameworkRequest, esMethod: string, options: {}) { const api = get(this.client, esMethod); api(options); diff --git a/x-pack/plugins/beats/server/lib/adapters/tokens/adapter_types.ts b/x-pack/plugins/beats/server/lib/adapters/tokens/adapter_types.ts index 71fb719d9952a..2fe8c811c396e 100644 --- a/x-pack/plugins/beats/server/lib/adapters/tokens/adapter_types.ts +++ b/x-pack/plugins/beats/server/lib/adapters/tokens/adapter_types.ts @@ -13,8 +13,5 @@ export interface TokenEnrollmentData { export interface CMTokensAdapter { deleteEnrollmentToken(enrollmentToken: string): Promise; getEnrollmentToken(enrollmentToken: string): Promise; - upsertTokens( - user: FrameworkUser, - tokens: TokenEnrollmentData[] - ): Promise; + upsertTokens(user: FrameworkUser, tokens: TokenEnrollmentData[]): Promise; } diff --git a/x-pack/plugins/beats/server/lib/adapters/tokens/elasticsearch_tokens_adapter.ts b/x-pack/plugins/beats/server/lib/adapters/tokens/elasticsearch_tokens_adapter.ts index 6dbefce514052..6aa9ceff46629 100644 --- a/x-pack/plugins/beats/server/lib/adapters/tokens/elasticsearch_tokens_adapter.ts +++ b/x-pack/plugins/beats/server/lib/adapters/tokens/elasticsearch_tokens_adapter.ts @@ -7,10 +7,7 @@ import { flatten, get } from 'lodash'; import { INDEX_NAMES } from '../../../../common/constants'; import { DatabaseAdapter } from '../database/adapter_types'; -import { - BackendFrameworkAdapter, - FrameworkUser, -} from '../framework/adapter_types'; +import { BackendFrameworkAdapter, FrameworkUser } from '../framework/adapter_types'; import { CMTokensAdapter, TokenEnrollmentData } from './adapter_types'; export class ElasticsearchTokensAdapter implements CMTokensAdapter { @@ -32,9 +29,7 @@ export class ElasticsearchTokensAdapter implements CMTokensAdapter { await this.database.delete(this.framework.internalUser, params); } - public async getEnrollmentToken( - tokenString: string - ): Promise { + public async getEnrollmentToken(tokenString: string): Promise { const params = { id: `enrollment_token:${tokenString}`, ignore: [404], @@ -42,18 +37,11 @@ export class ElasticsearchTokensAdapter implements CMTokensAdapter { type: '_doc', }; - const response = await this.database.get( - this.framework.internalUser, - params - ); - const tokenDetails = get( - response, - '_source.enrollment_token', - { - expires_on: '0', - token: null, - } - ); + const response = await this.database.get(this.framework.internalUser, params); + const tokenDetails = get(response, '_source.enrollment_token', { + expires_on: '0', + token: null, + }); // Elasticsearch might return fast if the token is not found. OR it might return fast // if the token *is* found. Either way, an attacker could using a timing attack to figure @@ -65,10 +53,7 @@ export class ElasticsearchTokensAdapter implements CMTokensAdapter { ); } - public async upsertTokens( - user: FrameworkUser, - tokens: TokenEnrollmentData[] - ) { + public async upsertTokens(user: FrameworkUser, tokens: TokenEnrollmentData[]) { const body = flatten( tokens.map(token => [ { index: { _id: `enrollment_token:${token.token}` } }, diff --git a/x-pack/plugins/beats/server/lib/adapters/tokens/memory_tokens_adapter.ts b/x-pack/plugins/beats/server/lib/adapters/tokens/memory_tokens_adapter.ts index 1eed188d3f2e9..7cf132bb6ba31 100644 --- a/x-pack/plugins/beats/server/lib/adapters/tokens/memory_tokens_adapter.ts +++ b/x-pack/plugins/beats/server/lib/adapters/tokens/memory_tokens_adapter.ts @@ -15,31 +15,22 @@ export class MemoryTokensAdapter implements CMTokensAdapter { } public async deleteEnrollmentToken(enrollmentToken: string) { - const index = this.tokenDB.findIndex( - token => token.token === enrollmentToken - ); + const index = this.tokenDB.findIndex(token => token.token === enrollmentToken); if (index > -1) { this.tokenDB.splice(index, 1); } } - public async getEnrollmentToken( - tokenString: string - ): Promise { + public async getEnrollmentToken(tokenString: string): Promise { return new Promise(resolve => { return resolve(this.tokenDB.find(token => token.token === tokenString)); }); } - public async upsertTokens( - user: FrameworkAuthenticatedUser, - tokens: TokenEnrollmentData[] - ) { + public async upsertTokens(user: FrameworkAuthenticatedUser, tokens: TokenEnrollmentData[]) { tokens.forEach(token => { - const existingIndex = this.tokenDB.findIndex( - t => t.token === token.token - ); + const existingIndex = this.tokenDB.findIndex(t => t.token === token.token); if (existingIndex !== -1) { this.tokenDB[existingIndex] = token; } else { diff --git a/x-pack/plugins/beats/server/lib/compose/kibana.ts b/x-pack/plugins/beats/server/lib/compose/kibana.ts index 7c46f82b84bf3..685171669a887 100644 --- a/x-pack/plugins/beats/server/lib/compose/kibana.ts +++ b/x-pack/plugins/beats/server/lib/compose/kibana.ts @@ -5,9 +5,9 @@ */ import { ElasticsearchBeatsAdapter } from '../adapters/beats/elasticsearch_beats_adapter'; +import { KibanaDatabaseAdapter } from '../adapters/database/kibana_database_adapter'; import { ElasticsearchTagsAdapter } from '../adapters/tags/elasticsearch_tags_adapter'; import { ElasticsearchTokensAdapter } from '../adapters/tokens/elasticsearch_tokens_adapter'; -import { KibanaDatabaseAdapter } from './../adapters/database/kibana_database_adapter'; import { KibanaBackendFrameworkAdapter } from '../adapters/framework/kibana_framework_adapter'; @@ -22,19 +22,13 @@ export function compose(server: any): CMServerLibs { const database = new KibanaDatabaseAdapter(server.plugins.elasticsearch); const tags = new CMTagsDomain(new ElasticsearchTagsAdapter(database)); - const tokens = new CMTokensDomain( - new ElasticsearchTokensAdapter(database, framework), - { - framework, - } - ); - const beats = new CMBeatsDomain( - new ElasticsearchBeatsAdapter(database, framework), - { - tags, - tokens, - } - ); + const tokens = new CMTokensDomain(new ElasticsearchTokensAdapter(database, framework), { + framework, + }); + const beats = new CMBeatsDomain(new ElasticsearchBeatsAdapter(database, framework), { + tags, + tokens, + }); const domainLibs: CMDomainLibs = { beats, diff --git a/x-pack/plugins/beats/server/lib/domains/__tests__/beats/assign_tags.test.ts b/x-pack/plugins/beats/server/lib/domains/__tests__/beats/assign_tags.test.ts index f28fcda8004e4..c9f9f15256d43 100644 --- a/x-pack/plugins/beats/server/lib/domains/__tests__/beats/assign_tags.test.ts +++ b/x-pack/plugins/beats/server/lib/domains/__tests__/beats/assign_tags.test.ts @@ -101,9 +101,7 @@ describe('Beats Domain Lib', () => { { beatId: 'bar', tag: 'production' }, ]); - expect(apiResponse.assignments).toEqual([ - { status: 200, result: 'updated' }, - ]); + expect(apiResponse.assignments).toEqual([{ status: 200, result: 'updated' }]); }); it('should not re-add an existing tag to a beat', async () => { @@ -117,9 +115,7 @@ describe('Beats Domain Lib', () => { { beatId: 'foo', tag: 'production' }, ]); - expect(apiResponse.assignments).toEqual([ - { status: 200, result: 'updated' }, - ]); + expect(apiResponse.assignments).toEqual([{ status: 200, result: 'updated' }]); beat = beatsDB.find(b => b.id === 'foo') as any; expect(beat.tags).toEqual([...tags, 'qa']); diff --git a/x-pack/plugins/beats/server/lib/domains/__tests__/beats/enroll.test.ts b/x-pack/plugins/beats/server/lib/domains/__tests__/beats/enroll.test.ts index 1466e9ed926c7..f60c1ed0e009e 100644 --- a/x-pack/plugins/beats/server/lib/domains/__tests__/beats/enroll.test.ts +++ b/x-pack/plugins/beats/server/lib/domains/__tests__/beats/enroll.test.ts @@ -8,6 +8,7 @@ import { MemoryBeatsAdapter } from '../../../adapters/beats/memory_beats_adapter import { TestingBackendFrameworkAdapter } from '../../../adapters/framework/testing_framework_adapter'; import { MemoryTagsAdapter } from '../../../adapters/tags/memory_tags_adapter'; import { MemoryTokensAdapter } from '../../../adapters/tokens/memory_tokens_adapter'; +import { BeatEnrollmentStatus } from '../../../lib'; import { BeatTag, CMBeat } from '../../../../../common/domain_types'; import { TokenEnrollmentData } from '../../../adapters/tokens/adapter_types'; @@ -84,16 +85,16 @@ describe('Beats Domain Lib', () => { }); it('should enroll beat, returning an access token', async () => { - const { token } = await tokensLib.getEnrollmentToken( - validEnrollmentToken - ); + const { token } = await tokensLib.getEnrollmentToken(validEnrollmentToken); expect(token).toEqual(validEnrollmentToken); - const { accessToken } = await beatsLib.enrollBeat( + const { accessToken, status } = await beatsLib.enrollBeat( + validEnrollmentToken, beatId, '192.168.1.1', omit(beat, 'enrollment_token') ); + expect(status).toEqual(BeatEnrollmentStatus.Success); expect(beatsDB.length).toEqual(1); expect(beatsDB[0]).toHaveProperty('host_ip'); @@ -126,9 +127,7 @@ describe('Beats Domain Lib', () => { await tokensLib.deleteEnrollmentToken(validEnrollmentToken); expect(tokensDB.length).toEqual(0); - const { token } = await tokensLib.getEnrollmentToken( - validEnrollmentToken - ); + const { token } = await tokensLib.getEnrollmentToken(validEnrollmentToken); expect(token).toEqual(null); }); diff --git a/x-pack/plugins/beats/server/lib/domains/__tests__/beats/verify.test.ts b/x-pack/plugins/beats/server/lib/domains/__tests__/beats/verify.test.ts deleted file mode 100644 index 140a17917684d..0000000000000 --- a/x-pack/plugins/beats/server/lib/domains/__tests__/beats/verify.test.ts +++ /dev/null @@ -1,183 +0,0 @@ -/* - * 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 { MemoryBeatsAdapter } from '../../../adapters/beats/memory_beats_adapter'; -import { TestingBackendFrameworkAdapter } from '../../../adapters/framework/testing_framework_adapter'; -import { MemoryTagsAdapter } from '../../../adapters/tags/memory_tags_adapter'; -import { MemoryTokensAdapter } from '../../../adapters/tokens/memory_tokens_adapter'; - -import { BeatTag, CMBeat } from '../../../../../common/domain_types'; -import { TokenEnrollmentData } from '../../../adapters/tokens/adapter_types'; - -import { CMBeatsDomain } from '../../beats'; -import { CMTagsDomain } from '../../tags'; -import { CMTokensDomain } from '../../tokens'; - -import Chance from 'chance'; - -const seed = Date.now(); -const chance = new Chance(seed); - -const settings = { - encryptionKey: 'something_who_cares', - enrollmentTokensTtlInSeconds: 10 * 60, // 10 minutes -}; - -describe('Beats Domain Lib', () => { - let beatsLib: CMBeatsDomain; - let tokensLib: CMTokensDomain; - - let beatsDB: CMBeat[] = []; - let tagsDB: BeatTag[] = []; - let tokensDB: TokenEnrollmentData[] = []; - - describe('verify_beat', () => { - beforeEach(async () => { - beatsDB = [ - { - access_token: '9a6c99ae0fd84b068819701169cd8a4b', - host_ip: '1.2.3.4', - host_name: 'foo.bar.com', - id: 'qux', - type: 'filebeat', - }, - { - access_token: '188255eb560a4448b72656c5e99cae6f', - host_ip: '22.33.11.44', - host_name: 'baz.bar.com', - id: 'baz', - type: 'metricbeat', - }, - { - access_token: '93c4a4dd08564c189a7ec4e4f046b975', - host_ip: '1.2.3.4', - host_name: 'foo.bar.com', - id: 'foo', - tags: ['production', 'qa'], - type: 'metricbeat', - verified_on: '2018-05-15T16:25:38.924Z', - }, - { - access_token: '3c4a4dd08564c189a7ec4e4f046b9759', - host_ip: '11.22.33.44', - host_name: 'foo.com', - id: 'bar', - type: 'filebeat', - }, - ]; - tagsDB = [ - { - configuration_blocks: [], - id: 'production', - }, - { - configuration_blocks: [], - id: 'development', - }, - { - configuration_blocks: [], - id: 'qa', - }, - ]; - tokensDB = []; - - const framework = new TestingBackendFrameworkAdapter(null, settings); - - tokensLib = new CMTokensDomain(new MemoryTokensAdapter(tokensDB), { - framework, - }); - - const tagsLib = new CMTagsDomain(new MemoryTagsAdapter(tagsDB)); - - beatsLib = new CMBeatsDomain(new MemoryBeatsAdapter(beatsDB), { - tags: tagsLib, - tokens: tokensLib, - }); - }); - - it('should return errors for non-existent beats', async () => { - const nonExistentBeatId = chance.word(); - - interface Beats { - id: string; - status?: number; - result?: string; - } - - const beats: Beats[] = [{ id: 'bar' }, { id: nonExistentBeatId }]; - const beatIds = beats.map(b => b.id); - - const { - verifiedBeatIds, - alreadyVerifiedBeatIds, - nonExistentBeatIds, - } = await beatsLib.verifyBeats({ kind: 'unauthenticated' }, beatIds); - - // TODO calculation of status should be done in-lib, w/switch statement here - beats.forEach(b => { - if (nonExistentBeatIds.includes(b.id)) { - b.status = 404; - b.result = 'not found'; - } else if (alreadyVerifiedBeatIds.includes(b.id)) { - b.status = 200; - b.result = 'already verified'; - } else if (verifiedBeatIds.includes(b.id)) { - b.status = 200; - b.result = 'verified'; - } else { - b.status = 400; - b.result = 'not verified'; - } - }); - - const response = { beats }; - expect(response.beats).toEqual([ - { id: 'bar', status: 200, result: 'verified' }, - { id: nonExistentBeatId, status: 404, result: 'not found' }, - ]); - }); - - it('should not re-verify already-verified beats', async () => { - interface Beats { - id: string; - status?: number; - result?: string; - } - - const beats: Beats[] = [{ id: 'foo' }, { id: 'bar' }]; - const beatIds = beats.map(b => b.id); - - const { - verifiedBeatIds, - alreadyVerifiedBeatIds, - nonExistentBeatIds, - } = await beatsLib.verifyBeats({ kind: 'unauthenticated' }, beatIds); - - // TODO calculation of status should be done in-lib, w/switch statement here - beats.forEach(beat => { - if (nonExistentBeatIds.includes(beat.id)) { - beat.status = 404; - beat.result = 'not found'; - } else if (alreadyVerifiedBeatIds.includes(beat.id)) { - beat.status = 200; - beat.result = 'already verified'; - } else if (verifiedBeatIds.includes(beat.id)) { - beat.status = 200; - beat.result = 'verified'; - } else { - beat.status = 400; - beat.result = 'not verified'; - } - }); - - const response = { beats }; - expect(response.beats).toEqual([ - { id: 'foo', status: 200, result: 'already verified' }, - { id: 'bar', status: 200, result: 'verified' }, - ]); - }); - }); -}); diff --git a/x-pack/plugins/beats/server/lib/domains/__tests__/tokens.test.ts b/x-pack/plugins/beats/server/lib/domains/__tests__/tokens.test.ts index 8002013fa4795..c89962dca7d3b 100644 --- a/x-pack/plugins/beats/server/lib/domains/__tests__/tokens.test.ts +++ b/x-pack/plugins/beats/server/lib/domains/__tests__/tokens.test.ts @@ -36,10 +36,7 @@ describe('Token Domain Lib', () => { }); it('should generate webtokens with a qty of 1', async () => { - const tokens = await tokensLib.createEnrollmentTokens( - framework.internalUser, - 1 - ); + const tokens = await tokensLib.createEnrollmentTokens(framework.internalUser, 1); expect(tokens.length).toBe(1); @@ -48,15 +45,10 @@ describe('Token Domain Lib', () => { it('should create the specified number of tokens', async () => { const numTokens = chance.integer({ min: 1, max: 20 }); - const tokensFromApi = await tokensLib.createEnrollmentTokens( - framework.internalUser, - numTokens - ); + const tokensFromApi = await tokensLib.createEnrollmentTokens(framework.internalUser, numTokens); expect(tokensFromApi.length).toEqual(numTokens); - expect(tokensFromApi).toEqual( - tokensDB.map((t: TokenEnrollmentData) => t.token) - ); + expect(tokensFromApi).toEqual(tokensDB.map((t: TokenEnrollmentData) => t.token)); }); it('should set token expiration to 10 minutes from now by default', async () => { diff --git a/x-pack/plugins/beats/server/lib/domains/beats.ts b/x-pack/plugins/beats/server/lib/domains/beats.ts index 9a580e2291ee7..07a084e54b53d 100644 --- a/x-pack/plugins/beats/server/lib/domains/beats.ts +++ b/x-pack/plugins/beats/server/lib/domains/beats.ts @@ -5,17 +5,15 @@ */ import { uniq } from 'lodash'; +import moment from 'moment'; import { findNonExistentItems } from '../../utils/find_non_existent_items'; import { CMBeat } from '../../../common/domain_types'; -import { - BeatsTagAssignment, - CMBeatsAdapter, -} from '../adapters/beats/adapter_types'; +import { BeatsTagAssignment, CMBeatsAdapter } from '../adapters/beats/adapter_types'; import { FrameworkUser } from '../adapters/framework/adapter_types'; import { CMAssignmentReturn } from '../adapters/beats/adapter_types'; -import { CMDomainLibs } from '../lib'; +import { BeatEnrollmentStatus, CMDomainLibs } from '../lib'; import { BeatsRemovalReturn } from './../adapters/beats/adapter_types'; export class CMBeatsDomain { @@ -32,11 +30,11 @@ export class CMBeatsDomain { this.tokens = libs.tokens; } - public async update( - beatId: string, - accessToken: string, - beatData: Partial - ) { + public async getById(beatId: string) { + return await this.adapter.get(beatId); + } + + public async update(beatId: string, accessToken: string, beatData: Partial) { const beat = await this.adapter.get(beatId); const { verified: isAccessTokenValid } = this.tokens.verifyToken( @@ -52,10 +50,6 @@ export class CMBeatsDomain { if (!isAccessTokenValid) { return 'invalid-access-token'; } - const isBeatVerified = beat.hasOwnProperty('verified_on'); - if (!isBeatVerified) { - return 'beat-not-verified'; - } await this.adapter.update({ ...beat, @@ -65,18 +59,34 @@ export class CMBeatsDomain { // TODO more strongly type this public async enrollBeat( + enrollmentToken: string, beatId: string, remoteAddress: string, beat: Partial - ) { + ): Promise<{ status: string; accessToken?: string }> { + const { token, expires_on } = await this.tokens.getEnrollmentToken(enrollmentToken); + + if (expires_on && moment(expires_on).isBefore(moment())) { + return { status: BeatEnrollmentStatus.ExpiredEnrollmentToken }; + } + if (!token) { + return { status: BeatEnrollmentStatus.InvalidEnrollmentToken }; + } + const accessToken = this.tokens.generateAccessToken(); + const verifiedOn = moment().toJSON(); + await this.adapter.insert({ ...beat, + verified_on: verifiedOn, access_token: accessToken, host_ip: remoteAddress, id: beatId, } as CMBeat); - return { accessToken }; + + await this.tokens.deleteEnrollmentToken(enrollmentToken); + + return { status: BeatEnrollmentStatus.Success, accessToken }; } public async removeTagsFromBeats( @@ -115,10 +125,7 @@ export class CMBeatsDomain { .filter((removal, idx) => response.removals[idx].status === null); if (validRemovals.length > 0) { - const removalResults = await this.adapter.removeTagsFromBeats( - user, - validRemovals - ); + const removalResults = await this.adapter.removeTagsFromBeats(user, validRemovals); return addToResultsToResponse('removals', response, removalResults); } return response; @@ -128,33 +135,6 @@ export class CMBeatsDomain { return await this.adapter.getAll(user); } - // TODO cleanup return value, should return a status enum - public async verifyBeats(user: FrameworkUser, beatIds: string[]) { - const beatsFromEs = await this.adapter.getWithIds(user, beatIds); - - const nonExistentBeatIds = findNonExistentItems(beatsFromEs, beatIds); - - const alreadyVerifiedBeatIds = beatsFromEs - .filter((beat: any) => beat.hasOwnProperty('verified_on')) - .map((beat: any) => beat.id); - - const toBeVerifiedBeatIds = beatsFromEs - .filter((beat: any) => !beat.hasOwnProperty('verified_on')) - .map((beat: any) => beat.id); - - const verifications = await this.adapter.verifyBeats( - user, - toBeVerifiedBeatIds - ); - - return { - alreadyVerifiedBeatIds, - nonExistentBeatIds, - toBeVerifiedBeatIds, - verifiedBeatIds: verifications.map((v: any) => v.id), - }; - } - public async assignTagsToBeats( user: FrameworkUser, assignments: BeatsTagAssignment[] @@ -191,10 +171,7 @@ export class CMBeatsDomain { .filter((assignment, idx) => response.assignments[idx].status === null); if (validAssignments.length > 0) { - const assignmentResults = await this.adapter.assignTagsToBeats( - user, - validAssignments - ); + const assignmentResults = await this.adapter.assignTagsToBeats(user, validAssignments); // TODO This should prob not mutate return addToResultsToResponse('assignments', response, assignmentResults); @@ -229,11 +206,7 @@ function addNonExistentItemToResponse( } // TODO dont mutate response -function addToResultsToResponse( - key: string, - response: any, - assignmentResults: any -) { +function addToResultsToResponse(key: string, response: any, assignmentResults: any) { assignmentResults.forEach((assignmentResult: any) => { const { idxInRequest, status, result } = assignmentResult; response[key][idxInRequest].status = status; diff --git a/x-pack/plugins/beats/server/lib/domains/tags.ts b/x-pack/plugins/beats/server/lib/domains/tags.ts index 194551721ab33..b6ce9f7e14821 100644 --- a/x-pack/plugins/beats/server/lib/domains/tags.ts +++ b/x-pack/plugins/beats/server/lib/domains/tags.ts @@ -22,14 +22,8 @@ export class CMTagsDomain { return await this.adapter.getTagsWithIds(user, tagIds); } - public async saveTag( - user: FrameworkUser, - tagId: string, - configs: ConfigurationBlock[] - ) { - const { isValid, message } = await this.validateConfigurationBlocks( - configs - ); + public async saveTag(user: FrameworkUser, tagId: string, configs: ConfigurationBlock[]) { + const { isValid, message } = await this.validateConfigurationBlocks(configs); if (!isValid) { return { isValid, result: message }; } @@ -49,10 +43,7 @@ export class CMTagsDomain { // If none of the types in the given configuration blocks are uniqueness-enforcing, // we don't need to perform any further validation checks. - const uniquenessEnforcingTypes = intersection( - types, - UNIQUENESS_ENFORCING_TYPES - ); + const uniquenessEnforcingTypes = intersection(types, UNIQUENESS_ENFORCING_TYPES); if (uniquenessEnforcingTypes.length === 0) { return { isValid: true }; } diff --git a/x-pack/plugins/beats/server/lib/domains/tokens.ts b/x-pack/plugins/beats/server/lib/domains/tokens.ts index 6cfb922c34d57..dc7ffa9a63356 100644 --- a/x-pack/plugins/beats/server/lib/domains/tokens.ts +++ b/x-pack/plugins/beats/server/lib/domains/tokens.ts @@ -18,10 +18,7 @@ export class CMTokensDomain { private adapter: CMTokensAdapter; private framework: BackendFrameworkAdapter; - constructor( - adapter: CMTokensAdapter, - libs: { framework: BackendFrameworkAdapter } - ) { + constructor(adapter: CMTokensAdapter, libs: { framework: BackendFrameworkAdapter }) { this.adapter = adapter; this.framework = libs.framework; } @@ -37,11 +34,7 @@ export class CMTokensDomain { }; } - const { verified, expired } = this.verifyToken( - enrollmentToken, - fullToken.token || '', - false - ); + const { verified, expired } = this.verifyToken(enrollmentToken, fullToken.token || '', false); if (!verified) { return { @@ -63,9 +56,7 @@ export class CMTokensDomain { let expired = false; if (decode) { - const enrollmentTokenSecret = this.framework.getSetting( - 'xpack.beats.encryptionKey' - ); + const enrollmentTokenSecret = this.framework.getSetting('xpack.beats.encryptionKey'); try { verifyToken(recivedToken, enrollmentTokenSecret); @@ -99,17 +90,13 @@ export class CMTokensDomain { return { expired, verified: - timingSafeEqual( - Buffer.from(recivedToken, 'utf8'), - Buffer.from(token2, 'utf8') - ) && tokenDecoded, + timingSafeEqual(Buffer.from(recivedToken, 'utf8'), Buffer.from(token2, 'utf8')) && + tokenDecoded, }; } public generateAccessToken() { - const enrollmentTokenSecret = this.framework.getSetting( - 'xpack.beats.encryptionKey' - ); + const enrollmentTokenSecret = this.framework.getSetting('xpack.beats.encryptionKey'); const tokenData = { created: moment().toJSON(), diff --git a/x-pack/plugins/beats/server/lib/lib.ts b/x-pack/plugins/beats/server/lib/lib.ts index d916c18aa4e4a..495093842b496 100644 --- a/x-pack/plugins/beats/server/lib/lib.ts +++ b/x-pack/plugins/beats/server/lib/lib.ts @@ -20,3 +20,9 @@ export interface CMServerLibs extends CMDomainLibs { framework: BackendFrameworkAdapter; database: DatabaseAdapter; } + +export enum BeatEnrollmentStatus { + Success = 'Success', + ExpiredEnrollmentToken = 'Expired enrollment token', + InvalidEnrollmentToken = 'Invalid enrollment token', +} diff --git a/x-pack/plugins/beats/server/management_server.ts b/x-pack/plugins/beats/server/management_server.ts index 637da2e37bd07..1c8a1d727175e 100644 --- a/x-pack/plugins/beats/server/management_server.ts +++ b/x-pack/plugins/beats/server/management_server.ts @@ -5,15 +5,14 @@ */ import { CMServerLibs } from './lib/lib'; +import { createGetBeatConfigurationRoute } from './rest_api/beats/configuration'; import { createBeatEnrollmentRoute } from './rest_api/beats/enroll'; import { createListAgentsRoute } from './rest_api/beats/list'; import { createTagAssignmentsRoute } from './rest_api/beats/tag_assignment'; import { createTagRemovalsRoute } from './rest_api/beats/tag_removal'; import { createBeatUpdateRoute } from './rest_api/beats/update'; -import { createBeatVerificationRoute } from './rest_api/beats/verify'; import { createSetTagRoute } from './rest_api/tags/set'; import { createTokensRoute } from './rest_api/tokens/create'; - import { beatsIndexTemplate } from './utils/index_templates'; export const initManagementServer = (libs: CMServerLibs) => { @@ -22,12 +21,12 @@ export const initManagementServer = (libs: CMServerLibs) => { body: beatsIndexTemplate, }); + libs.framework.registerRoute(createGetBeatConfigurationRoute(libs)); libs.framework.registerRoute(createTagAssignmentsRoute(libs)); libs.framework.registerRoute(createListAgentsRoute(libs)); libs.framework.registerRoute(createTagRemovalsRoute(libs)); libs.framework.registerRoute(createBeatEnrollmentRoute(libs)); libs.framework.registerRoute(createSetTagRoute(libs)); libs.framework.registerRoute(createTokensRoute(libs)); - libs.framework.registerRoute(createBeatVerificationRoute(libs)); libs.framework.registerRoute(createBeatUpdateRoute(libs)); }; diff --git a/x-pack/plugins/beats/server/rest_api/beats/configuration.ts b/x-pack/plugins/beats/server/rest_api/beats/configuration.ts new file mode 100644 index 0000000000000..81906ea9473e6 --- /dev/null +++ b/x-pack/plugins/beats/server/rest_api/beats/configuration.ts @@ -0,0 +1,54 @@ +/* + * 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 Joi from 'joi'; +import { BeatTag, ConfigurationBlock } from '../../../common/domain_types'; +import { CMServerLibs } from '../../lib/lib'; +import { wrapEsError } from '../../utils/error_wrappers'; + +export const createGetBeatConfigurationRoute = (libs: CMServerLibs) => ({ + method: 'GET', + path: '/api/beats/agent/{beatId}/configuration', + config: { + validate: { + headers: Joi.object({ + 'kbn-beats-access-token': Joi.string().required(), + }).options({ allowUnknown: true }), + }, + auth: false, + }, + handler: async (request: any, reply: any) => { + const beatId = request.params.beatId; + const accessToken = request.headers['kbn-beats-access-token']; + + let beat; + let tags; + try { + beat = await libs.beats.getById(beatId); + if (beat === null) { + return reply({ message: 'Beat not found' }).code(404); + } + + const isAccessTokenValid = beat.access_token === accessToken; + if (!isAccessTokenValid) { + return reply({ message: 'Invalid access token' }).code(401); + } + + tags = await libs.tags.getTagsWithIds(libs.framework.internalUser, beat.tags || []); + } catch (err) { + return reply(wrapEsError(err)); + } + + const configurationBlocks = tags.reduce((blocks: ConfigurationBlock[], tag: BeatTag) => { + blocks = blocks.concat(tag.configuration_blocks); + return blocks; + }, []); + + reply({ + configuration_blocks: configurationBlocks, + }); + }, +}); diff --git a/x-pack/plugins/beats/server/rest_api/beats/enroll.ts b/x-pack/plugins/beats/server/rest_api/beats/enroll.ts index d909b64810360..c5b01fdbd4cc3 100644 --- a/x-pack/plugins/beats/server/rest_api/beats/enroll.ts +++ b/x-pack/plugins/beats/server/rest_api/beats/enroll.ts @@ -3,13 +3,12 @@ * or more contributor license agreements. Licensed under the Elastic License; * you may not use this file except in compliance with the Elastic License. */ - import Joi from 'joi'; import { omit } from 'lodash'; -import moment from 'moment'; import { FrameworkRequest } from '../../lib/adapters/framework/adapter_types'; import { CMServerLibs } from '../../lib/lib'; import { wrapEsError } from '../../utils/error_wrappers'; +import { BeatEnrollmentStatus } from './../../lib/lib'; // TODO: add license check pre-hook // TODO: write to Kibana audit log file @@ -34,25 +33,26 @@ export const createBeatEnrollmentRoute = (libs: CMServerLibs) => ({ const enrollmentToken = request.headers['kbn-beats-enrollment-token']; try { - const { token, expires_on } = await libs.tokens.getEnrollmentToken( - enrollmentToken - ); - - if (expires_on && moment(expires_on).isBefore(moment())) { - return reply({ message: 'Expired enrollment token' }).code(400); - } - if (!token) { - return reply({ message: 'Invalid enrollment token' }).code(400); - } - const { accessToken } = await libs.beats.enrollBeat( + const { status, accessToken } = await libs.beats.enrollBeat( + enrollmentToken, beatId, request.info.remoteAddress, omit(request.payload, 'enrollment_token') ); - await libs.tokens.deleteEnrollmentToken(enrollmentToken); - - reply({ access_token: accessToken }).code(201); + switch (status) { + case BeatEnrollmentStatus.ExpiredEnrollmentToken: + return reply({ + message: BeatEnrollmentStatus.ExpiredEnrollmentToken, + }).code(400); + case BeatEnrollmentStatus.InvalidEnrollmentToken: + return reply({ + message: BeatEnrollmentStatus.InvalidEnrollmentToken, + }).code(400); + case BeatEnrollmentStatus.Success: + default: + return reply({ access_token: accessToken }).code(201); + } } catch (err) { // TODO move this to kibana route thing in adapter return reply(wrapEsError(err)); diff --git a/x-pack/plugins/beats/server/rest_api/beats/tag_assignment.ts b/x-pack/plugins/beats/server/rest_api/beats/tag_assignment.ts index a6654250d196f..9c832ee3226b8 100644 --- a/x-pack/plugins/beats/server/rest_api/beats/tag_assignment.ts +++ b/x-pack/plugins/beats/server/rest_api/beats/tag_assignment.ts @@ -34,10 +34,7 @@ export const createTagAssignmentsRoute = (libs: CMServerLibs) => ({ })); try { - const response = await libs.beats.assignTagsToBeats( - request.user, - tweakedAssignments - ); + const response = await libs.beats.assignTagsToBeats(request.user, tweakedAssignments); reply(response); } catch (err) { // TODO move this to kibana route thing in adapter diff --git a/x-pack/plugins/beats/server/rest_api/beats/tag_removal.ts b/x-pack/plugins/beats/server/rest_api/beats/tag_removal.ts index 1b495b906a364..6a7af3b42d407 100644 --- a/x-pack/plugins/beats/server/rest_api/beats/tag_removal.ts +++ b/x-pack/plugins/beats/server/rest_api/beats/tag_removal.ts @@ -34,10 +34,7 @@ export const createTagRemovalsRoute = (libs: CMServerLibs) => ({ })); try { - const response = await libs.beats.removeTagsFromBeats( - request.user, - tweakedRemovals - ); + const response = await libs.beats.removeTagsFromBeats(request.user, tweakedRemovals); reply(response); } catch (err) { // TODO move this to kibana route thing in adapter diff --git a/x-pack/plugins/beats/server/rest_api/beats/update.ts b/x-pack/plugins/beats/server/rest_api/beats/update.ts index c86cf74b0c744..60d7151731cd3 100644 --- a/x-pack/plugins/beats/server/rest_api/beats/update.ts +++ b/x-pack/plugins/beats/server/rest_api/beats/update.ts @@ -49,8 +49,6 @@ export const createBeatUpdateRoute = (libs: CMServerLibs) => ({ return reply({ message: 'Beat not found' }).code(404); case 'invalid-access-token': return reply({ message: 'Invalid access token' }).code(401); - case 'beat-not-verified': - return reply({ message: 'Beat has not been verified' }).code(400); } reply().code(204); diff --git a/x-pack/plugins/beats/server/rest_api/beats/verify.ts b/x-pack/plugins/beats/server/rest_api/beats/verify.ts deleted file mode 100644 index d15b0374f445f..0000000000000 --- a/x-pack/plugins/beats/server/rest_api/beats/verify.ts +++ /dev/null @@ -1,63 +0,0 @@ -/* - * 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 Joi from 'joi'; -import { FrameworkRequest } from '../../lib/adapters/framework/adapter_types'; -import { CMServerLibs } from '../../lib/lib'; -import { wrapEsError } from '../../utils/error_wrappers'; - -// TODO: add license check pre-hook -// TODO: write to Kibana audit log file -export const createBeatVerificationRoute = (libs: CMServerLibs) => ({ - config: { - auth: false, - validate: { - payload: Joi.object({ - beats: Joi.array() - .items({ - id: Joi.string().required(), - }) - .min(1), - }).required(), - }, - }, - handler: async (request: FrameworkRequest, reply: any) => { - const beats = [...request.payload.beats]; - const beatIds = beats.map(beat => beat.id); - - try { - const { - verifiedBeatIds, - alreadyVerifiedBeatIds, - nonExistentBeatIds, - } = await libs.beats.verifyBeats(request.user, beatIds); - - // TODO calculation of status should be done in-lib, w/switch statement here - beats.forEach(beat => { - if (nonExistentBeatIds.includes(beat.id)) { - beat.status = 404; - beat.result = 'not found'; - } else if (alreadyVerifiedBeatIds.includes(beat.id)) { - beat.status = 200; - beat.result = 'already verified'; - } else if (verifiedBeatIds.includes(beat.id)) { - beat.status = 200; - beat.result = 'verified'; - } else { - beat.status = 400; - beat.result = 'not verified'; - } - }); - - const response = { beats }; - reply(response); - } catch (err) { - return reply(wrapEsError(err)); - } - }, - method: 'POST', - path: '/api/beats/agents/verify', -}); diff --git a/x-pack/plugins/beats/server/rest_api/tags/set.ts b/x-pack/plugins/beats/server/rest_api/tags/set.ts index bdc49ce62a9a9..6c01959e75311 100644 --- a/x-pack/plugins/beats/server/rest_api/tags/set.ts +++ b/x-pack/plugins/beats/server/rest_api/tags/set.ts @@ -32,11 +32,7 @@ export const createSetTagRoute = (libs: CMServerLibs) => ({ }, }, handler: async (request: FrameworkRequest, reply: any) => { - const configurationBlocks = get( - request, - 'payload.configuration_blocks', - [] - ); + const configurationBlocks = get(request, 'payload.configuration_blocks', []); try { const { isValid, result } = await libs.tags.saveTag( request.user, diff --git a/x-pack/plugins/beats/server/rest_api/tokens/create.ts b/x-pack/plugins/beats/server/rest_api/tokens/create.ts index 9e20735d640e5..74278703347c3 100644 --- a/x-pack/plugins/beats/server/rest_api/tokens/create.ts +++ b/x-pack/plugins/beats/server/rest_api/tokens/create.ts @@ -27,10 +27,7 @@ export const createTokensRoute = (libs: CMServerLibs) => ({ const numTokens = get(request, 'payload.num_tokens', DEFAULT_NUM_TOKENS); try { - const tokens = await libs.tokens.createEnrollmentTokens( - request.user, - numTokens - ); + const tokens = await libs.tokens.createEnrollmentTokens(request.user, numTokens); reply({ tokens }); } catch (err) { // TODO move this to kibana route thing in adapter diff --git a/x-pack/plugins/beats/server/utils/error_wrappers/wrap_es_error.test.ts b/x-pack/plugins/beats/server/utils/error_wrappers/wrap_es_error.test.ts index 5087bf3224c42..e7fb1c2adda21 100644 --- a/x-pack/plugins/beats/server/utils/error_wrappers/wrap_es_error.test.ts +++ b/x-pack/plugins/beats/server/utils/error_wrappers/wrap_es_error.test.ts @@ -24,9 +24,7 @@ describe('wrap_es_error', () => { const wrappedError = wrapEsError(originalError); expect(wrappedError.output.statusCode).toEqual(originalError.statusCode); - expect(wrappedError.output.payload.message).toEqual( - originalError.message - ); + expect(wrappedError.output.payload.message).toEqual(originalError.message); }); it('should return invalid permissions message for 403 errors', () => { diff --git a/x-pack/plugins/beats/server/utils/error_wrappers/wrap_es_error.ts b/x-pack/plugins/beats/server/utils/error_wrappers/wrap_es_error.ts index 50ffbcb4a10c9..30328f2c6a833 100644 --- a/x-pack/plugins/beats/server/utils/error_wrappers/wrap_es_error.ts +++ b/x-pack/plugins/beats/server/utils/error_wrappers/wrap_es_error.ts @@ -16,9 +16,7 @@ import Boom from 'boom'; export function wrapEsError(err: any) { const statusCode = err.statusCode; if (statusCode === 403) { - return Boom.forbidden( - 'Insufficient user permissions for managing Beats configuration' - ); + return Boom.forbidden('Insufficient user permissions for managing Beats configuration'); } return Boom.wrap(err, err.statusCode); } diff --git a/x-pack/plugins/beats/server/utils/find_non_existent_items.ts b/x-pack/plugins/beats/server/utils/find_non_existent_items.ts index d6b2a0c9e143b..0e9b4f0b6fa5e 100644 --- a/x-pack/plugins/beats/server/utils/find_non_existent_items.ts +++ b/x-pack/plugins/beats/server/utils/find_non_existent_items.ts @@ -10,17 +10,10 @@ interface RandomItem { } export function findNonExistentItems(items: RandomItem[], requestedItems: any) { - return requestedItems.reduce( - (nonExistentItems: string[], requestedItem: string, idx: number) => { - if ( - items.findIndex( - (item: RandomItem) => item && item.id === requestedItem - ) === -1 - ) { - nonExistentItems.push(requestedItems[idx]); - } - return nonExistentItems; - }, - [] - ); + return requestedItems.reduce((nonExistentItems: string[], requestedItem: string, idx: number) => { + if (items.findIndex((item: RandomItem) => item && item.id === requestedItem) === -1) { + nonExistentItems.push(requestedItems[idx]); + } + return nonExistentItems; + }, []); } diff --git a/x-pack/plugins/beats/wallaby.js b/x-pack/plugins/beats/wallaby.js index 79b1438a5374d..8c0c4aa355925 100644 --- a/x-pack/plugins/beats/wallaby.js +++ b/x-pack/plugins/beats/wallaby.js @@ -4,14 +4,10 @@ * you may not use this file except in compliance with the Elastic License. */ const path = require('path'); -process.env.NODE_PATH += - path.delimiter + path.join(__dirname, '..', '..', '..', 'node_modules'); +process.env.NODE_PATH = path.join(__dirname, '..', '..', 'node_modules'); module.exports = function (wallaby) { return { - hints: { - commentAutoLog: 'testOutputWith:', - }, debug: true, files: [ './tsconfig.json', @@ -43,7 +39,6 @@ module.exports = function (wallaby) { '..', '..' ); - wallaby.testFramework.configure({ rootDir: wallaby.localProjectDir, moduleNameMapper: { @@ -61,6 +56,7 @@ module.exports = function (wallaby) { ], transform: { '^.+\\.js$': `${kibanaDirectory}/src/dev/jest/babel_transform.js`, + //"^.+\\.tsx?$": `${kibanaDirectory}/src/dev/jest/ts_transform.js`, }, }); }, diff --git a/x-pack/plugins/logstash/server/lib/error_wrappers/__tests__/wrap_es_error.js b/x-pack/plugins/logstash/server/lib/error_wrappers/__tests__/wrap_es_error.js deleted file mode 100644 index f1b956bdcc3bb..0000000000000 --- a/x-pack/plugins/logstash/server/lib/error_wrappers/__tests__/wrap_es_error.js +++ /dev/null @@ -1,41 +0,0 @@ -/* - * 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 'expect.js'; -import { wrapEsError } from '../wrap_es_error'; - -describe('wrap_es_error', () => { - describe('#wrapEsError', () => { - - let originalError; - beforeEach(() => { - originalError = new Error('I am an error'); - originalError.statusCode = 404; - }); - - it('should return a Boom object', () => { - const wrappedError = wrapEsError(originalError); - - expect(wrappedError.isBoom).to.be(true); - }); - - it('should return the correct Boom object', () => { - const wrappedError = wrapEsError(originalError); - - expect(wrappedError.output.statusCode).to.be(originalError.statusCode); - expect(wrappedError.output.payload.message).to.be(originalError.message); - }); - - it('should return invalid permissions message for 403 errors', () => { - const securityError = new Error('I am an error'); - securityError.statusCode = 403; - const wrappedError = wrapEsError(securityError); - - expect(wrappedError.isBoom).to.be(true); - expect(wrappedError.message).to.be('Insufficient user permissions for managing Logstash pipelines'); - }); - }); -}); diff --git a/x-pack/test/api_integration/apis/beats/enroll_beat.js b/x-pack/test/api_integration/apis/beats/enroll_beat.js index 1dde64c9ee1d8..0234359b4f9ff 100644 --- a/x-pack/test/api_integration/apis/beats/enroll_beat.js +++ b/x-pack/test/api_integration/apis/beats/enroll_beat.js @@ -66,7 +66,7 @@ export default function ({ getService }) { id: `beat:${beatId}`, }); - expect(esResponse._source.beat).to.not.have.property('verified_on'); + expect(esResponse._source.beat).to.have.property('verified_on'); expect(esResponse._source.beat).to.have.property('host_ip'); }); diff --git a/x-pack/test/api_integration/apis/beats/get_beat.js b/x-pack/test/api_integration/apis/beats/get_beat.js new file mode 100644 index 0000000000000..8d6a81d100eb4 --- /dev/null +++ b/x-pack/test/api_integration/apis/beats/get_beat.js @@ -0,0 +1,51 @@ +/* + * 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 'expect.js'; + +export default function ({ getService }) { + const supertest = getService('supertest'); + const esArchiver = getService('esArchiver'); + + describe('get_beat_configuration', () => { + const archive = 'beats/list'; + + beforeEach('load beats archive', () => esArchiver.load(archive)); + afterEach('unload beats archive', () => esArchiver.unload(archive)); + + it('should return merged configuration for the beat', async () => { + const { body: apiResponse } = await supertest + .get('/api/beats/agent/foo/configuration') + .set( + 'kbn-beats-access-token', + 'eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.' + + 'eyJjcmVhdGVkIjoiMjAxOC0wNi0zMFQwMzo0MjoxNS4yMzBaIiwiaWF0IjoxNTMwMzMwMTM1fQ.' + + 'SSsX2Byyo1B1bGxV8C3G4QldhE5iH87EY_1r21-bwbI' + ) + .expect(200); + + const configurationBlocks = apiResponse.configuration_blocks; + + expect(configurationBlocks).to.be.an(Array); + expect(configurationBlocks.length).to.be(3); + + expect(configurationBlocks[0].type).to.be('output'); + expect(configurationBlocks[0].block_yml).to.be( + 'elasticsearch:\n hosts: ["localhost:9200"]\n username: "..."' + ); + + expect(configurationBlocks[1].type).to.be('metricbeat.modules'); + expect(configurationBlocks[1].block_yml).to.be( + 'module: memcached\nhosts: ["localhost:11211"]' + ); + + expect(configurationBlocks[2].type).to.be('metricbeat.modules'); + expect(configurationBlocks[2].block_yml).to.be( + 'module: munin\nhosts: ["localhost:4949"]\nnode.namespace: node' + ); + }); + }); +} diff --git a/x-pack/test/api_integration/apis/beats/index.js b/x-pack/test/api_integration/apis/beats/index.js index f8956d3e498ba..a6552a383dbd8 100644 --- a/x-pack/test/api_integration/apis/beats/index.js +++ b/x-pack/test/api_integration/apis/beats/index.js @@ -12,7 +12,7 @@ export default function ({ getService, loadTestFile }) { describe('beats', () => { const cleanup = () => es.indices.delete({ index: ES_INDEX_NAME, - ignore: [ 404 ] + ignore: [404] }); beforeEach(cleanup); @@ -20,10 +20,10 @@ export default function ({ getService, loadTestFile }) { loadTestFile(require.resolve('./create_enrollment_tokens')); loadTestFile(require.resolve('./enroll_beat')); loadTestFile(require.resolve('./list_beats')); - loadTestFile(require.resolve('./verify_beats')); loadTestFile(require.resolve('./update_beat')); loadTestFile(require.resolve('./set_tag')); loadTestFile(require.resolve('./assign_tags_to_beats')); loadTestFile(require.resolve('./remove_tags_from_beats')); + loadTestFile(require.resolve('./get_beat')); }); } diff --git a/x-pack/test/api_integration/apis/beats/update_beat.js b/x-pack/test/api_integration/apis/beats/update_beat.js index fb30970d3cc7f..697e644cb2221 100644 --- a/x-pack/test/api_integration/apis/beats/update_beat.js +++ b/x-pack/test/api_integration/apis/beats/update_beat.js @@ -103,36 +103,6 @@ export default function ({ getService }) { expect(beatInEs._source.beat.ephemeral_id).to.not.be(beat.ephemeral_id); }); - it('should return an error for an existing but unverified beat', async () => { - const beatId = 'bar'; - - const { body } = await supertest - .put(`/api/beats/agent/${beatId}`) - .set('kbn-xsrf', 'xxx') - .set( - 'kbn-beats-access-token', - 'eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.' + - 'eyJjcmVhdGVkIjoiMjAxOC0wNi0zMFQwMzo0MjoxNS4yMzBaIiwiaWF0IjoxNTMwMzMwMTM1fQ.' + - 'SSsX2Byyo1B1bGxV8C3G4QldhE5iH87EY_1r21-bwbI' - ) - .send(beat) - .expect(400); - - expect(body.message).to.be('Beat has not been verified'); - - const beatInEs = await es.get({ - index: ES_INDEX_NAME, - type: ES_TYPE_NAME, - id: `beat:${beatId}`, - }); - - expect(beatInEs._source.beat.id).to.be(beatId); - expect(beatInEs._source.beat.type).to.not.be(beat.type); - expect(beatInEs._source.beat.host_name).to.not.be(beat.host_name); - expect(beatInEs._source.beat.version).to.not.be(beat.version); - expect(beatInEs._source.beat.ephemeral_id).to.not.be(beat.ephemeral_id); - }); - it('should return an error for a non-existent beat', async () => { const beatId = chance.word(); const { body } = await supertest diff --git a/x-pack/test/api_integration/apis/beats/verify_beats.js b/x-pack/test/api_integration/apis/beats/verify_beats.js deleted file mode 100644 index 30521d0483c2d..0000000000000 --- a/x-pack/test/api_integration/apis/beats/verify_beats.js +++ /dev/null @@ -1,66 +0,0 @@ -/* - * 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 'expect.js'; - -export default function ({ getService }) { - const supertest = getService('supertest'); - const esArchiver = getService('esArchiver'); - const chance = getService('chance'); - - describe('verify_beats', () => { - const archive = 'beats/list'; - - beforeEach('load beats archive', () => esArchiver.load(archive)); - afterEach('unload beats archive', () => esArchiver.unload(archive)); - - it('verify the given beats', async () => { - const { body: apiResponse } = await supertest - .post('/api/beats/agents/verify') - .set('kbn-xsrf', 'xxx') - .send({ - beats: [{ id: 'bar' }, { id: 'baz' }], - }) - .expect(200); - - expect(apiResponse.beats).to.eql([ - { id: 'bar', status: 200, result: 'verified' }, - { id: 'baz', status: 200, result: 'verified' }, - ]); - }); - - it('should not re-verify already-verified beats', async () => { - const { body: apiResponse } = await supertest - .post('/api/beats/agents/verify') - .set('kbn-xsrf', 'xxx') - .send({ - beats: [{ id: 'foo' }, { id: 'bar' }], - }) - .expect(200); - - expect(apiResponse.beats).to.eql([ - { id: 'foo', status: 200, result: 'already verified' }, - { id: 'bar', status: 200, result: 'verified' }, - ]); - }); - - it('should return errors for non-existent beats', async () => { - const nonExistentBeatId = chance.word(); - const { body: apiResponse } = await supertest - .post('/api/beats/agents/verify') - .set('kbn-xsrf', 'xxx') - .send({ - beats: [{ id: 'bar' }, { id: nonExistentBeatId }], - }) - .expect(200); - - expect(apiResponse.beats).to.eql([ - { id: 'bar', status: 200, result: 'verified' }, - { id: nonExistentBeatId, status: 404, result: 'not found' }, - ]); - }); - }); -} diff --git a/x-pack/test/functional/es_archives/beats/list/data.json b/x-pack/test/functional/es_archives/beats/list/data.json new file mode 100644 index 0000000000000..bbe5bc8dbe3e5 --- /dev/null +++ b/x-pack/test/functional/es_archives/beats/list/data.json @@ -0,0 +1,139 @@ +{ + "type": "doc", + "value": { + "index": ".management-beats", + "type": "_doc", + "id": "beat:qux", + "source": { + "type": "beat", + "beat": { + "type": "filebeat", + "host_ip": "1.2.3.4", + "host_name": "foo.bar.com", + "id": "qux", + "access_token": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJjcmVhdGVkIjoiMjAxOC0wNi0zMFQwMzo0MjoxNS4yMzBaIiwiaWF0IjoxNTMwMzMwMTM1fQ.SSsX2Byyo1B1bGxV8C3G4QldhE5iH87EY_1r21-bwbI" + } + } + } +} + +{ + "type": "doc", + "value": { + "index": ".management-beats", + "type": "_doc", + "id": "beat:baz", + "source": { + "type": "beat", + "beat": { + "type": "metricbeat", + "host_ip": "22.33.11.44", + "host_name": "baz.bar.com", + "id": "baz", + "access_token": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJjcmVhdGVkIjoiMjAxOC0wNi0zMFQwMzo0MjoxNS4yMzBaIiwiaWF0IjoxNTMwMzMwMTM1fQ.SSsX2Byyo1B1bGxV8C3G4QldhE5iH87EY_1r21-bwbI" + } + } + } +} + +{ + "type": "doc", + "value": { + "index": ".management-beats", + "type": "_doc", + "id": "beat:foo", + "source": { + "type": "beat", + "beat": { + "type": "metricbeat", + "host_ip": "1.2.3.4", + "host_name": "foo.bar.com", + "id": "foo", + "access_token": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJjcmVhdGVkIjoiMjAxOC0wNi0zMFQwMzo0MjoxNS4yMzBaIiwiaWF0IjoxNTMwMzMwMTM1fQ.SSsX2Byyo1B1bGxV8C3G4QldhE5iH87EY_1r21-bwbI", + "verified_on": "2018-05-15T16:25:38.924Z", + "tags": [ + "production", + "qa" + ] + } + } + } +} + +{ + "type": "doc", + "value": { + "index": ".management-beats", + "type": "_doc", + "id": "beat:bar", + "source": { + "type": "beat", + "beat": { + "type": "filebeat", + "host_ip": "11.22.33.44", + "host_name": "foo.com", + "id": "bar", + "access_token": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJjcmVhdGVkIjoiMjAxOC0wNi0zMFQwMzo0MjoxNS4yMzBaIiwiaWF0IjoxNTMwMzMwMTM1fQ.SSsX2Byyo1B1bGxV8C3G4QldhE5iH87EY_1r21-bwbI" + } + } + } +} + +{ + "type": "doc", + "value": { + "index": ".management-beats", + "type": "_doc", + "id": "tag:production", + "source": { + "type": "tag", + "tag": { + "configuration_blocks": [ + { + "type": "output", + "block_yml": "elasticsearch:\n hosts: [\"localhost:9200\"]\n username: \"...\"" + }, + { + "type": "metricbeat.modules", + "block_yml": "module: memcached\nhosts: [\"localhost:11211\"]" + } + ] + } + } + } +} + +{ + "type": "doc", + "value": { + "index": ".management-beats", + "type": "_doc", + "id": "tag:development", + "source": { + "type": "tag", + "tag": { + "configuration_blocks": [] + } + } + } +} + +{ + "type": "doc", + "value": { + "index": ".management-beats", + "type": "_doc", + "id": "tag:qa", + "source": { + "type": "tag", + "tag": { + "configuration_blocks": [ + { + "type": "metricbeat.modules", + "block_yml": "module: munin\nhosts: [\"localhost:4949\"]\nnode.namespace: node" + } + ] + } + } + } +} \ No newline at end of file diff --git a/x-pack/test/functional/es_archives/beats/list/data.json.gz b/x-pack/test/functional/es_archives/beats/list/data.json.gz deleted file mode 100644 index 6af0e1b8aeb47b3ac9c5bea7dba0abc92e3fe9e4..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 521 zcmV+k0`~nMiwFp){WeQOZ*BnXRb6k|Fcf{yuRwV&RveO!=53&s87Wi@ z#@bF(7IM;)v?a?%|>Z7)U=4GYBf}k6ZJ|0 zE9_?y*@!@dEc9qD2_V2Bp3#7YY15@RO?LEJ2j|d2R(TSUH0wFb4`{-(m{h%MwUW7K zHTF@(s_~}Gr*F6-H|I&}ut=sM&_N3r@3J8dUdlNKE{*}=L7nrWwi3DnF(EWboRlwV zDATm)&)ptj_pFb;l?VKRKBwcikmeIqc+rI&Vv>?G`?)4^1wBXEMe9rH?+IqmW z(!Lw6?UHNug6D&gQP^b%BerJv`<;d)Hnv4xd}A9X{SOE;}n8{Fu| zQ@td`vqW%zydnaNV(w)mWunVf9e>8^YxwiB>mxqj??KGfZhxFu9&)sfEROi2j@56N$h=!*J0l4bwm^~91m=dFGL LEy(|MkO}|*NeBrP diff --git a/x-pack/test/functional/es_archives/beats/list/mappings.json b/x-pack/test/functional/es_archives/beats/list/mappings.json index 0057b0b765773..caabcc13a9353 100644 --- a/x-pack/test/functional/es_archives/beats/list/mappings.json +++ b/x-pack/test/functional/es_archives/beats/list/mappings.json @@ -77,9 +77,6 @@ "tags": { "type": "keyword" }, - "central_configuration_yml": { - "type": "text" - }, "metadata": { "dynamic": "true", "type": "object" @@ -90,4 +87,4 @@ } } } -} +} \ No newline at end of file diff --git a/x-pack/yarn.lock b/x-pack/yarn.lock index 3fc10b7dd6909..0987c469e25a8 100644 --- a/x-pack/yarn.lock +++ b/x-pack/yarn.lock @@ -131,6 +131,10 @@ version "1.2.0" resolved "https://registry.yarnpkg.com/@types/events/-/events-1.2.0.tgz#81a6731ce4df43619e5c8c945383b3e62a89ea86" +"@types/expect.js@^0.3.29": + version "0.3.29" + resolved "https://registry.yarnpkg.com/@types/expect.js/-/expect.js-0.3.29.tgz#28dd359155b84b8ecb094afc3f4b74c3222dca3b" + "@types/form-data@^2.2.1": version "2.2.1" resolved "https://registry.yarnpkg.com/@types/form-data/-/form-data-2.2.1.tgz#ee2b3b8eaa11c0938289953606b745b738c54b1e"