From 5f367e79fbef93301cf14f7f32e4cfc7f8edfe98 Mon Sep 17 00:00:00 2001 From: Shaunak Kashyap Date: Fri, 11 May 2018 06:42:46 -0700 Subject: [PATCH 01/24] WIP checkin --- .../check_license/__tests__/check_license.js | 180 ++++++++++++++++++ .../server/lib/check_license/check_license.js | 69 +++++++ .../beats/server/lib/check_license/index.js | 7 + .../__tests__/wrap_custom_error.js | 21 ++ .../error_wrappers/__tests__/wrap_es_error.js | 41 ++++ .../__tests__/wrap_unknown_error.js | 19 ++ .../lib/error_wrappers/wrap_custom_error.js | 18 ++ .../lib/error_wrappers/wrap_unknown_error.js | 17 ++ .../__tests__/license_pre_routing_factory.js | 72 +++++++ .../lib/license_pre_routing_factory/index.js | 7 + .../license_pre_routing_factory.js | 28 +++ .../lib/register_license_checker/index.js | 7 + .../register_license_checker.js | 21 ++ .../api/register_disenroll_beats_route.js | 0 .../routes/api/register_enroll_beats_route.js | 0 15 files changed, 507 insertions(+) create mode 100644 x-pack/plugins/beats/server/lib/check_license/__tests__/check_license.js create mode 100644 x-pack/plugins/beats/server/lib/check_license/check_license.js create mode 100644 x-pack/plugins/beats/server/lib/check_license/index.js create mode 100644 x-pack/plugins/beats/server/lib/error_wrappers/__tests__/wrap_custom_error.js create mode 100644 x-pack/plugins/beats/server/lib/error_wrappers/__tests__/wrap_es_error.js create mode 100644 x-pack/plugins/beats/server/lib/error_wrappers/__tests__/wrap_unknown_error.js create mode 100644 x-pack/plugins/beats/server/lib/error_wrappers/wrap_custom_error.js create mode 100644 x-pack/plugins/beats/server/lib/error_wrappers/wrap_unknown_error.js create mode 100644 x-pack/plugins/beats/server/lib/license_pre_routing_factory/__tests__/license_pre_routing_factory.js create mode 100644 x-pack/plugins/beats/server/lib/license_pre_routing_factory/index.js create mode 100644 x-pack/plugins/beats/server/lib/license_pre_routing_factory/license_pre_routing_factory.js create mode 100644 x-pack/plugins/beats/server/lib/register_license_checker/index.js create mode 100644 x-pack/plugins/beats/server/lib/register_license_checker/register_license_checker.js create mode 100644 x-pack/plugins/beats/server/routes/api/register_disenroll_beats_route.js create mode 100644 x-pack/plugins/beats/server/routes/api/register_enroll_beats_route.js diff --git a/x-pack/plugins/beats/server/lib/check_license/__tests__/check_license.js b/x-pack/plugins/beats/server/lib/check_license/__tests__/check_license.js new file mode 100644 index 0000000000000..449ff3a60b9e7 --- /dev/null +++ b/x-pack/plugins/beats/server/lib/check_license/__tests__/check_license.js @@ -0,0 +1,180 @@ +/* + * 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 { set } from 'lodash'; +import { checkLicense } from '../check_license'; + +describe('check_license', function () { + + let mockLicenseInfo; + beforeEach(() => mockLicenseInfo = {}); + + describe('license information is undefined', () => { + beforeEach(() => mockLicenseInfo = undefined); + + it('should set isAvailable to false', () => { + expect(checkLicense(mockLicenseInfo).isAvailable).to.be(false); + }); + + it('should set enableLinks to false', () => { + expect(checkLicense(mockLicenseInfo).enableLinks).to.be(false); + }); + + it('should set isReadOnly to false', () => { + expect(checkLicense(mockLicenseInfo).isReadOnly).to.be(false); + }); + + it('should set a message', () => { + expect(checkLicense(mockLicenseInfo).message).to.not.be(undefined); + }); + }); + + describe('license information is not available', () => { + beforeEach(() => mockLicenseInfo.isAvailable = () => false); + + it('should set isAvailable to false', () => { + expect(checkLicense(mockLicenseInfo).isAvailable).to.be(false); + }); + + it('should set enableLinks to false', () => { + expect(checkLicense(mockLicenseInfo).enableLinks).to.be(false); + }); + + it('should set isReadOnly to false', () => { + expect(checkLicense(mockLicenseInfo).isReadOnly).to.be(false); + }); + + it('should set a message', () => { + expect(checkLicense(mockLicenseInfo).message).to.not.be(undefined); + }); + }); + + describe('license information is available', () => { + beforeEach(() => { + mockLicenseInfo.isAvailable = () => true; + set(mockLicenseInfo, 'license.getType', () => 'basic'); + }); + + describe('& license is trial, standard, gold, platinum', () => { + beforeEach(() => { + set(mockLicenseInfo, 'license.isOneOf', () => true); + mockLicenseInfo.feature = () => ({ isEnabled: () => true }); // Security feature is enabled + }); + + describe('& license is active', () => { + beforeEach(() => set(mockLicenseInfo, 'license.isActive', () => true)); + + it('should set isAvailable to true', () => { + expect(checkLicense(mockLicenseInfo).isAvailable).to.be(true); + }); + + it ('should set enableLinks to true', () => { + expect(checkLicense(mockLicenseInfo).enableLinks).to.be(true); + }); + + it ('should set isReadOnly to false', () => { + expect(checkLicense(mockLicenseInfo).isReadOnly).to.be(false); + }); + + it('should not set a message', () => { + expect(checkLicense(mockLicenseInfo).message).to.be(undefined); + }); + }); + + describe('& license is expired', () => { + beforeEach(() => set(mockLicenseInfo, 'license.isActive', () => false)); + + it('should set isAvailable to true', () => { + expect(checkLicense(mockLicenseInfo).isAvailable).to.be(true); + }); + + it ('should set enableLinks to true', () => { + expect(checkLicense(mockLicenseInfo).enableLinks).to.be(true); + }); + + it ('should set isReadOnly to true', () => { + expect(checkLicense(mockLicenseInfo).isReadOnly).to.be(true); + }); + + it('should set a message', () => { + expect(checkLicense(mockLicenseInfo).message).to.not.be(undefined); + }); + }); + }); + + describe('& license is basic', () => { + beforeEach(() => { + set(mockLicenseInfo, 'license.isOneOf', () => false); + mockLicenseInfo.feature = () => ({ isEnabled: () => true }); // Security feature is enabled + }); + + describe('& license is active', () => { + beforeEach(() => set(mockLicenseInfo, 'license.isActive', () => true)); + + it('should set isAvailable to false', () => { + expect(checkLicense(mockLicenseInfo).isAvailable).to.be(false); + }); + + it ('should set enableLinks to false', () => { + expect(checkLicense(mockLicenseInfo).enableLinks).to.be(false); + }); + + it ('should set isReadOnly to false', () => { + expect(checkLicense(mockLicenseInfo).isReadOnly).to.be(false); + }); + + it('should set a message', () => { + expect(checkLicense(mockLicenseInfo).message).to.not.be(undefined); + }); + }); + + describe('& license is expired', () => { + beforeEach(() => set(mockLicenseInfo, 'license.isActive', () => false)); + + it('should set isAvailable to false', () => { + expect(checkLicense(mockLicenseInfo).isAvailable).to.be(false); + }); + + it ('should set enableLinks to false', () => { + expect(checkLicense(mockLicenseInfo).enableLinks).to.be(false); + }); + + it ('should set isReadOnly to false', () => { + expect(checkLicense(mockLicenseInfo).isReadOnly).to.be(false); + }); + + it('should set a message', () => { + expect(checkLicense(mockLicenseInfo).message).to.not.be(undefined); + }); + }); + }); + + describe('& security is disabled', () => { + beforeEach(() => { + mockLicenseInfo.feature = () => ({ isEnabled: () => false }); // Security feature is disabled + set(mockLicenseInfo, 'license.isOneOf', () => true); + set(mockLicenseInfo, 'license.isActive', () => true); + }); + + it('should set isAvailable to false', () => { + expect(checkLicense(mockLicenseInfo).isAvailable).to.be(false); + }); + + it ('should set enableLinks to false', () => { + expect(checkLicense(mockLicenseInfo).enableLinks).to.be(false); + }); + + it ('should set isReadOnly to false', () => { + expect(checkLicense(mockLicenseInfo).isReadOnly).to.be(false); + }); + + it('should set a message', () => { + expect(checkLicense(mockLicenseInfo).message).to.not.be(undefined); + }); + }); + }); +}); diff --git a/x-pack/plugins/beats/server/lib/check_license/check_license.js b/x-pack/plugins/beats/server/lib/check_license/check_license.js new file mode 100644 index 0000000000000..aa1704cc02730 --- /dev/null +++ b/x-pack/plugins/beats/server/lib/check_license/check_license.js @@ -0,0 +1,69 @@ +/* + * 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. + */ + +export function checkLicense(xpackLicenseInfo) { + // If, for some reason, we cannot get the license information + // from Elasticsearch, assume worst case and disable the Logstash pipeline UI + if (!xpackLicenseInfo || !xpackLicenseInfo.isAvailable()) { + return { + isAvailable: false, + enableLinks: false, + isReadOnly: false, + message: 'You cannot manage Logstash pipelines because license information is not available at this time.' + }; + } + + const VALID_LICENSE_MODES = [ + 'trial', + 'standard', + 'gold', + 'platinum' + ]; + + const isLicenseModeValid = xpackLicenseInfo.license.isOneOf(VALID_LICENSE_MODES); + const isLicenseActive = xpackLicenseInfo.license.isActive(); + const licenseType = xpackLicenseInfo.license.getType(); + const isSecurityEnabled = xpackLicenseInfo.feature('security').isEnabled(); + + // Security is not enabled in ES + if (!isSecurityEnabled) { + const message = 'Security must be enabled in order to use Logstash pipeline management features.' + + ' Please set xpack.security.enabled: true in your elasticsearch.yml.'; + return { + isAvailable: false, + enableLinks: false, + isReadOnly: false, + message + }; + } + + // License is not valid + if (!isLicenseModeValid) { + return { + isAvailable: false, + enableLinks: false, + isReadOnly: false, + message: `Your ${licenseType} license does not support Logstash pipeline management features. Please upgrade your license.` + }; + } + + // License is valid but not active, we go into a read-only mode. + if (!isLicenseActive) { + return { + isAvailable: true, + enableLinks: true, + isReadOnly: true, + message: `You cannot edit, create, or delete your Logstash pipelines because your ${licenseType} license has expired.` + }; + } + + // License is valid and active + return { + isAvailable: true, + enableLinks: true, + isReadOnly: false + }; +} diff --git a/x-pack/plugins/beats/server/lib/check_license/index.js b/x-pack/plugins/beats/server/lib/check_license/index.js new file mode 100644 index 0000000000000..f2c070fd44b6e --- /dev/null +++ b/x-pack/plugins/beats/server/lib/check_license/index.js @@ -0,0 +1,7 @@ +/* + * 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. + */ + +export { checkLicense } from './check_license'; diff --git a/x-pack/plugins/beats/server/lib/error_wrappers/__tests__/wrap_custom_error.js b/x-pack/plugins/beats/server/lib/error_wrappers/__tests__/wrap_custom_error.js new file mode 100644 index 0000000000000..443744ccb0cc8 --- /dev/null +++ b/x-pack/plugins/beats/server/lib/error_wrappers/__tests__/wrap_custom_error.js @@ -0,0 +1,21 @@ +/* + * 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 { wrapCustomError } from '../wrap_custom_error'; + +describe('wrap_custom_error', () => { + describe('#wrapCustomError', () => { + it('should return a Boom object', () => { + const originalError = new Error('I am an error'); + const statusCode = 404; + const wrappedError = wrapCustomError(originalError, statusCode); + + expect(wrappedError.isBoom).to.be(true); + expect(wrappedError.output.statusCode).to.equal(statusCode); + }); + }); +}); diff --git a/x-pack/plugins/beats/server/lib/error_wrappers/__tests__/wrap_es_error.js b/x-pack/plugins/beats/server/lib/error_wrappers/__tests__/wrap_es_error.js new file mode 100644 index 0000000000000..f1b956bdcc3bb --- /dev/null +++ b/x-pack/plugins/beats/server/lib/error_wrappers/__tests__/wrap_es_error.js @@ -0,0 +1,41 @@ +/* + * 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/plugins/beats/server/lib/error_wrappers/__tests__/wrap_unknown_error.js b/x-pack/plugins/beats/server/lib/error_wrappers/__tests__/wrap_unknown_error.js new file mode 100644 index 0000000000000..6d6a336417bef --- /dev/null +++ b/x-pack/plugins/beats/server/lib/error_wrappers/__tests__/wrap_unknown_error.js @@ -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 expect from 'expect.js'; +import { wrapUnknownError } from '../wrap_unknown_error'; + +describe('wrap_unknown_error', () => { + describe('#wrapUnknownError', () => { + it('should return a Boom object', () => { + const originalError = new Error('I am an error'); + const wrappedError = wrapUnknownError(originalError); + + expect(wrappedError.isBoom).to.be(true); + }); + }); +}); diff --git a/x-pack/plugins/beats/server/lib/error_wrappers/wrap_custom_error.js b/x-pack/plugins/beats/server/lib/error_wrappers/wrap_custom_error.js new file mode 100644 index 0000000000000..890a366ac65c1 --- /dev/null +++ b/x-pack/plugins/beats/server/lib/error_wrappers/wrap_custom_error.js @@ -0,0 +1,18 @@ +/* + * 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 Boom from 'boom'; + +/** + * Wraps a custom error into a Boom error response and returns it + * + * @param err Object error + * @param statusCode Error status code + * @return Object Boom error response + */ +export function wrapCustomError(err, statusCode) { + return Boom.wrap(err, statusCode); +} diff --git a/x-pack/plugins/beats/server/lib/error_wrappers/wrap_unknown_error.js b/x-pack/plugins/beats/server/lib/error_wrappers/wrap_unknown_error.js new file mode 100644 index 0000000000000..b0cdced7adbef --- /dev/null +++ b/x-pack/plugins/beats/server/lib/error_wrappers/wrap_unknown_error.js @@ -0,0 +1,17 @@ +/* + * 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 Boom from 'boom'; + +/** + * Wraps an unknown error into a Boom error response and returns it + * + * @param err Object Unknown error + * @return Object Boom error response + */ +export function wrapUnknownError(err) { + return Boom.wrap(err); +} diff --git a/x-pack/plugins/beats/server/lib/license_pre_routing_factory/__tests__/license_pre_routing_factory.js b/x-pack/plugins/beats/server/lib/license_pre_routing_factory/__tests__/license_pre_routing_factory.js new file mode 100644 index 0000000000000..c543d79814dd3 --- /dev/null +++ b/x-pack/plugins/beats/server/lib/license_pre_routing_factory/__tests__/license_pre_routing_factory.js @@ -0,0 +1,72 @@ +/* + * 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 { licensePreRoutingFactory } from '../license_pre_routing_factory'; + +describe('license_pre_routing_factory', () => { + describe('#logstashFeaturePreRoutingFactory', () => { + let mockServer; + let mockLicenseCheckResults; + + beforeEach(() => { + mockServer = { + plugins: { + xpack_main: { + info: { + feature: () => ({ + getLicenseCheckResults: () => mockLicenseCheckResults + }) + } + } + } + }; + }); + + it('only instantiates one instance per server', () => { + const firstInstance = licensePreRoutingFactory(mockServer); + const secondInstance = licensePreRoutingFactory(mockServer); + + expect(firstInstance).to.be(secondInstance); + }); + + describe('isAvailable is false', () => { + beforeEach(() => { + mockLicenseCheckResults = { + isAvailable: false + }; + }); + + it ('replies with 403', (done) => { + const licensePreRouting = licensePreRoutingFactory(mockServer); + const stubRequest = {}; + licensePreRouting(stubRequest, (response) => { + expect(response).to.be.an(Error); + expect(response.isBoom).to.be(true); + expect(response.output.statusCode).to.be(403); + done(); + }); + }); + }); + + describe('isAvailable is true', () => { + beforeEach(() => { + mockLicenseCheckResults = { + isAvailable: true + }; + }); + + it ('replies with nothing', (done) => { + const licensePreRouting = licensePreRoutingFactory(mockServer); + const stubRequest = {}; + licensePreRouting(stubRequest, (response) => { + expect(response).to.be(undefined); + done(); + }); + }); + }); + }); +}); diff --git a/x-pack/plugins/beats/server/lib/license_pre_routing_factory/index.js b/x-pack/plugins/beats/server/lib/license_pre_routing_factory/index.js new file mode 100644 index 0000000000000..0743e443955f4 --- /dev/null +++ b/x-pack/plugins/beats/server/lib/license_pre_routing_factory/index.js @@ -0,0 +1,7 @@ +/* + * 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. + */ + +export { licensePreRoutingFactory } from './license_pre_routing_factory'; diff --git a/x-pack/plugins/beats/server/lib/license_pre_routing_factory/license_pre_routing_factory.js b/x-pack/plugins/beats/server/lib/license_pre_routing_factory/license_pre_routing_factory.js new file mode 100644 index 0000000000000..4ae31f692bfd7 --- /dev/null +++ b/x-pack/plugins/beats/server/lib/license_pre_routing_factory/license_pre_routing_factory.js @@ -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 { once } from 'lodash'; +import { wrapCustomError } from '../error_wrappers'; +import { PLUGIN } from '../../../common/constants'; + +export const licensePreRoutingFactory = once((server) => { + const xpackMainPlugin = server.plugins.xpack_main; + + // License checking and enable/disable logic + function licensePreRouting(request, reply) { + const licenseCheckResults = xpackMainPlugin.info.feature(PLUGIN.ID).getLicenseCheckResults(); + if (!licenseCheckResults.isAvailable) { + const error = new Error(licenseCheckResults.message); + const statusCode = 403; + const wrappedError = wrapCustomError(error, statusCode); + reply(wrappedError); + } else { + reply(); + } + } + + return licensePreRouting; +}); diff --git a/x-pack/plugins/beats/server/lib/register_license_checker/index.js b/x-pack/plugins/beats/server/lib/register_license_checker/index.js new file mode 100644 index 0000000000000..7b0f97c38d129 --- /dev/null +++ b/x-pack/plugins/beats/server/lib/register_license_checker/index.js @@ -0,0 +1,7 @@ +/* + * 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. + */ + +export { registerLicenseChecker } from './register_license_checker'; diff --git a/x-pack/plugins/beats/server/lib/register_license_checker/register_license_checker.js b/x-pack/plugins/beats/server/lib/register_license_checker/register_license_checker.js new file mode 100644 index 0000000000000..8a17fb2eea497 --- /dev/null +++ b/x-pack/plugins/beats/server/lib/register_license_checker/register_license_checker.js @@ -0,0 +1,21 @@ +/* + * 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 { mirrorPluginStatus } from '../../../../../server/lib/mirror_plugin_status'; +import { checkLicense } from '../check_license'; +import { PLUGIN } from '../../../common/constants'; + +export function registerLicenseChecker(server) { + const xpackMainPlugin = server.plugins.xpack_main; + const logstashPlugin = server.plugins.logstash; + + mirrorPluginStatus(xpackMainPlugin, logstashPlugin); + xpackMainPlugin.status.once('green', () => { + // Register a function that is called whenever the xpack info changes, + // to re-compute the license check results for this plugin + xpackMainPlugin.info.feature(PLUGIN.ID).registerLicenseCheckResultsGenerator(checkLicense); + }); +} diff --git a/x-pack/plugins/beats/server/routes/api/register_disenroll_beats_route.js b/x-pack/plugins/beats/server/routes/api/register_disenroll_beats_route.js new file mode 100644 index 0000000000000..e69de29bb2d1d diff --git a/x-pack/plugins/beats/server/routes/api/register_enroll_beats_route.js b/x-pack/plugins/beats/server/routes/api/register_enroll_beats_route.js new file mode 100644 index 0000000000000..e69de29bb2d1d From 60cfd96fb89a4901e518270d66a62776afe57e1d Mon Sep 17 00:00:00 2001 From: Shaunak Kashyap Date: Sat, 12 May 2018 06:55:32 -0700 Subject: [PATCH 02/24] Add API integration test --- .../check_license/__tests__/check_license.js | 180 ------------------ .../server/lib/check_license/check_license.js | 69 ------- .../beats/server/lib/check_license/index.js | 7 - .../__tests__/wrap_custom_error.js | 21 -- .../__tests__/wrap_unknown_error.js | 19 -- .../lib/error_wrappers/wrap_custom_error.js | 18 -- .../lib/error_wrappers/wrap_unknown_error.js | 17 -- .../__tests__/license_pre_routing_factory.js | 72 ------- .../lib/license_pre_routing_factory/index.js | 7 - .../license_pre_routing_factory.js | 28 --- .../lib/register_license_checker/index.js | 7 - .../register_license_checker.js | 21 -- .../api/register_disenroll_beats_route.js | 0 .../routes/api/register_enroll_beats_route.js | 0 14 files changed, 466 deletions(-) delete mode 100644 x-pack/plugins/beats/server/lib/check_license/__tests__/check_license.js delete mode 100644 x-pack/plugins/beats/server/lib/check_license/check_license.js delete mode 100644 x-pack/plugins/beats/server/lib/check_license/index.js delete mode 100644 x-pack/plugins/beats/server/lib/error_wrappers/__tests__/wrap_custom_error.js delete mode 100644 x-pack/plugins/beats/server/lib/error_wrappers/__tests__/wrap_unknown_error.js delete mode 100644 x-pack/plugins/beats/server/lib/error_wrappers/wrap_custom_error.js delete mode 100644 x-pack/plugins/beats/server/lib/error_wrappers/wrap_unknown_error.js delete mode 100644 x-pack/plugins/beats/server/lib/license_pre_routing_factory/__tests__/license_pre_routing_factory.js delete mode 100644 x-pack/plugins/beats/server/lib/license_pre_routing_factory/index.js delete mode 100644 x-pack/plugins/beats/server/lib/license_pre_routing_factory/license_pre_routing_factory.js delete mode 100644 x-pack/plugins/beats/server/lib/register_license_checker/index.js delete mode 100644 x-pack/plugins/beats/server/lib/register_license_checker/register_license_checker.js delete mode 100644 x-pack/plugins/beats/server/routes/api/register_disenroll_beats_route.js delete mode 100644 x-pack/plugins/beats/server/routes/api/register_enroll_beats_route.js diff --git a/x-pack/plugins/beats/server/lib/check_license/__tests__/check_license.js b/x-pack/plugins/beats/server/lib/check_license/__tests__/check_license.js deleted file mode 100644 index 449ff3a60b9e7..0000000000000 --- a/x-pack/plugins/beats/server/lib/check_license/__tests__/check_license.js +++ /dev/null @@ -1,180 +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 { set } from 'lodash'; -import { checkLicense } from '../check_license'; - -describe('check_license', function () { - - let mockLicenseInfo; - beforeEach(() => mockLicenseInfo = {}); - - describe('license information is undefined', () => { - beforeEach(() => mockLicenseInfo = undefined); - - it('should set isAvailable to false', () => { - expect(checkLicense(mockLicenseInfo).isAvailable).to.be(false); - }); - - it('should set enableLinks to false', () => { - expect(checkLicense(mockLicenseInfo).enableLinks).to.be(false); - }); - - it('should set isReadOnly to false', () => { - expect(checkLicense(mockLicenseInfo).isReadOnly).to.be(false); - }); - - it('should set a message', () => { - expect(checkLicense(mockLicenseInfo).message).to.not.be(undefined); - }); - }); - - describe('license information is not available', () => { - beforeEach(() => mockLicenseInfo.isAvailable = () => false); - - it('should set isAvailable to false', () => { - expect(checkLicense(mockLicenseInfo).isAvailable).to.be(false); - }); - - it('should set enableLinks to false', () => { - expect(checkLicense(mockLicenseInfo).enableLinks).to.be(false); - }); - - it('should set isReadOnly to false', () => { - expect(checkLicense(mockLicenseInfo).isReadOnly).to.be(false); - }); - - it('should set a message', () => { - expect(checkLicense(mockLicenseInfo).message).to.not.be(undefined); - }); - }); - - describe('license information is available', () => { - beforeEach(() => { - mockLicenseInfo.isAvailable = () => true; - set(mockLicenseInfo, 'license.getType', () => 'basic'); - }); - - describe('& license is trial, standard, gold, platinum', () => { - beforeEach(() => { - set(mockLicenseInfo, 'license.isOneOf', () => true); - mockLicenseInfo.feature = () => ({ isEnabled: () => true }); // Security feature is enabled - }); - - describe('& license is active', () => { - beforeEach(() => set(mockLicenseInfo, 'license.isActive', () => true)); - - it('should set isAvailable to true', () => { - expect(checkLicense(mockLicenseInfo).isAvailable).to.be(true); - }); - - it ('should set enableLinks to true', () => { - expect(checkLicense(mockLicenseInfo).enableLinks).to.be(true); - }); - - it ('should set isReadOnly to false', () => { - expect(checkLicense(mockLicenseInfo).isReadOnly).to.be(false); - }); - - it('should not set a message', () => { - expect(checkLicense(mockLicenseInfo).message).to.be(undefined); - }); - }); - - describe('& license is expired', () => { - beforeEach(() => set(mockLicenseInfo, 'license.isActive', () => false)); - - it('should set isAvailable to true', () => { - expect(checkLicense(mockLicenseInfo).isAvailable).to.be(true); - }); - - it ('should set enableLinks to true', () => { - expect(checkLicense(mockLicenseInfo).enableLinks).to.be(true); - }); - - it ('should set isReadOnly to true', () => { - expect(checkLicense(mockLicenseInfo).isReadOnly).to.be(true); - }); - - it('should set a message', () => { - expect(checkLicense(mockLicenseInfo).message).to.not.be(undefined); - }); - }); - }); - - describe('& license is basic', () => { - beforeEach(() => { - set(mockLicenseInfo, 'license.isOneOf', () => false); - mockLicenseInfo.feature = () => ({ isEnabled: () => true }); // Security feature is enabled - }); - - describe('& license is active', () => { - beforeEach(() => set(mockLicenseInfo, 'license.isActive', () => true)); - - it('should set isAvailable to false', () => { - expect(checkLicense(mockLicenseInfo).isAvailable).to.be(false); - }); - - it ('should set enableLinks to false', () => { - expect(checkLicense(mockLicenseInfo).enableLinks).to.be(false); - }); - - it ('should set isReadOnly to false', () => { - expect(checkLicense(mockLicenseInfo).isReadOnly).to.be(false); - }); - - it('should set a message', () => { - expect(checkLicense(mockLicenseInfo).message).to.not.be(undefined); - }); - }); - - describe('& license is expired', () => { - beforeEach(() => set(mockLicenseInfo, 'license.isActive', () => false)); - - it('should set isAvailable to false', () => { - expect(checkLicense(mockLicenseInfo).isAvailable).to.be(false); - }); - - it ('should set enableLinks to false', () => { - expect(checkLicense(mockLicenseInfo).enableLinks).to.be(false); - }); - - it ('should set isReadOnly to false', () => { - expect(checkLicense(mockLicenseInfo).isReadOnly).to.be(false); - }); - - it('should set a message', () => { - expect(checkLicense(mockLicenseInfo).message).to.not.be(undefined); - }); - }); - }); - - describe('& security is disabled', () => { - beforeEach(() => { - mockLicenseInfo.feature = () => ({ isEnabled: () => false }); // Security feature is disabled - set(mockLicenseInfo, 'license.isOneOf', () => true); - set(mockLicenseInfo, 'license.isActive', () => true); - }); - - it('should set isAvailable to false', () => { - expect(checkLicense(mockLicenseInfo).isAvailable).to.be(false); - }); - - it ('should set enableLinks to false', () => { - expect(checkLicense(mockLicenseInfo).enableLinks).to.be(false); - }); - - it ('should set isReadOnly to false', () => { - expect(checkLicense(mockLicenseInfo).isReadOnly).to.be(false); - }); - - it('should set a message', () => { - expect(checkLicense(mockLicenseInfo).message).to.not.be(undefined); - }); - }); - }); -}); diff --git a/x-pack/plugins/beats/server/lib/check_license/check_license.js b/x-pack/plugins/beats/server/lib/check_license/check_license.js deleted file mode 100644 index aa1704cc02730..0000000000000 --- a/x-pack/plugins/beats/server/lib/check_license/check_license.js +++ /dev/null @@ -1,69 +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. - */ - -export function checkLicense(xpackLicenseInfo) { - // If, for some reason, we cannot get the license information - // from Elasticsearch, assume worst case and disable the Logstash pipeline UI - if (!xpackLicenseInfo || !xpackLicenseInfo.isAvailable()) { - return { - isAvailable: false, - enableLinks: false, - isReadOnly: false, - message: 'You cannot manage Logstash pipelines because license information is not available at this time.' - }; - } - - const VALID_LICENSE_MODES = [ - 'trial', - 'standard', - 'gold', - 'platinum' - ]; - - const isLicenseModeValid = xpackLicenseInfo.license.isOneOf(VALID_LICENSE_MODES); - const isLicenseActive = xpackLicenseInfo.license.isActive(); - const licenseType = xpackLicenseInfo.license.getType(); - const isSecurityEnabled = xpackLicenseInfo.feature('security').isEnabled(); - - // Security is not enabled in ES - if (!isSecurityEnabled) { - const message = 'Security must be enabled in order to use Logstash pipeline management features.' - + ' Please set xpack.security.enabled: true in your elasticsearch.yml.'; - return { - isAvailable: false, - enableLinks: false, - isReadOnly: false, - message - }; - } - - // License is not valid - if (!isLicenseModeValid) { - return { - isAvailable: false, - enableLinks: false, - isReadOnly: false, - message: `Your ${licenseType} license does not support Logstash pipeline management features. Please upgrade your license.` - }; - } - - // License is valid but not active, we go into a read-only mode. - if (!isLicenseActive) { - return { - isAvailable: true, - enableLinks: true, - isReadOnly: true, - message: `You cannot edit, create, or delete your Logstash pipelines because your ${licenseType} license has expired.` - }; - } - - // License is valid and active - return { - isAvailable: true, - enableLinks: true, - isReadOnly: false - }; -} diff --git a/x-pack/plugins/beats/server/lib/check_license/index.js b/x-pack/plugins/beats/server/lib/check_license/index.js deleted file mode 100644 index f2c070fd44b6e..0000000000000 --- a/x-pack/plugins/beats/server/lib/check_license/index.js +++ /dev/null @@ -1,7 +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. - */ - -export { checkLicense } from './check_license'; diff --git a/x-pack/plugins/beats/server/lib/error_wrappers/__tests__/wrap_custom_error.js b/x-pack/plugins/beats/server/lib/error_wrappers/__tests__/wrap_custom_error.js deleted file mode 100644 index 443744ccb0cc8..0000000000000 --- a/x-pack/plugins/beats/server/lib/error_wrappers/__tests__/wrap_custom_error.js +++ /dev/null @@ -1,21 +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 { wrapCustomError } from '../wrap_custom_error'; - -describe('wrap_custom_error', () => { - describe('#wrapCustomError', () => { - it('should return a Boom object', () => { - const originalError = new Error('I am an error'); - const statusCode = 404; - const wrappedError = wrapCustomError(originalError, statusCode); - - expect(wrappedError.isBoom).to.be(true); - expect(wrappedError.output.statusCode).to.equal(statusCode); - }); - }); -}); diff --git a/x-pack/plugins/beats/server/lib/error_wrappers/__tests__/wrap_unknown_error.js b/x-pack/plugins/beats/server/lib/error_wrappers/__tests__/wrap_unknown_error.js deleted file mode 100644 index 6d6a336417bef..0000000000000 --- a/x-pack/plugins/beats/server/lib/error_wrappers/__tests__/wrap_unknown_error.js +++ /dev/null @@ -1,19 +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 { wrapUnknownError } from '../wrap_unknown_error'; - -describe('wrap_unknown_error', () => { - describe('#wrapUnknownError', () => { - it('should return a Boom object', () => { - const originalError = new Error('I am an error'); - const wrappedError = wrapUnknownError(originalError); - - expect(wrappedError.isBoom).to.be(true); - }); - }); -}); diff --git a/x-pack/plugins/beats/server/lib/error_wrappers/wrap_custom_error.js b/x-pack/plugins/beats/server/lib/error_wrappers/wrap_custom_error.js deleted file mode 100644 index 890a366ac65c1..0000000000000 --- a/x-pack/plugins/beats/server/lib/error_wrappers/wrap_custom_error.js +++ /dev/null @@ -1,18 +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 Boom from 'boom'; - -/** - * Wraps a custom error into a Boom error response and returns it - * - * @param err Object error - * @param statusCode Error status code - * @return Object Boom error response - */ -export function wrapCustomError(err, statusCode) { - return Boom.wrap(err, statusCode); -} diff --git a/x-pack/plugins/beats/server/lib/error_wrappers/wrap_unknown_error.js b/x-pack/plugins/beats/server/lib/error_wrappers/wrap_unknown_error.js deleted file mode 100644 index b0cdced7adbef..0000000000000 --- a/x-pack/plugins/beats/server/lib/error_wrappers/wrap_unknown_error.js +++ /dev/null @@ -1,17 +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 Boom from 'boom'; - -/** - * Wraps an unknown error into a Boom error response and returns it - * - * @param err Object Unknown error - * @return Object Boom error response - */ -export function wrapUnknownError(err) { - return Boom.wrap(err); -} diff --git a/x-pack/plugins/beats/server/lib/license_pre_routing_factory/__tests__/license_pre_routing_factory.js b/x-pack/plugins/beats/server/lib/license_pre_routing_factory/__tests__/license_pre_routing_factory.js deleted file mode 100644 index c543d79814dd3..0000000000000 --- a/x-pack/plugins/beats/server/lib/license_pre_routing_factory/__tests__/license_pre_routing_factory.js +++ /dev/null @@ -1,72 +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 { licensePreRoutingFactory } from '../license_pre_routing_factory'; - -describe('license_pre_routing_factory', () => { - describe('#logstashFeaturePreRoutingFactory', () => { - let mockServer; - let mockLicenseCheckResults; - - beforeEach(() => { - mockServer = { - plugins: { - xpack_main: { - info: { - feature: () => ({ - getLicenseCheckResults: () => mockLicenseCheckResults - }) - } - } - } - }; - }); - - it('only instantiates one instance per server', () => { - const firstInstance = licensePreRoutingFactory(mockServer); - const secondInstance = licensePreRoutingFactory(mockServer); - - expect(firstInstance).to.be(secondInstance); - }); - - describe('isAvailable is false', () => { - beforeEach(() => { - mockLicenseCheckResults = { - isAvailable: false - }; - }); - - it ('replies with 403', (done) => { - const licensePreRouting = licensePreRoutingFactory(mockServer); - const stubRequest = {}; - licensePreRouting(stubRequest, (response) => { - expect(response).to.be.an(Error); - expect(response.isBoom).to.be(true); - expect(response.output.statusCode).to.be(403); - done(); - }); - }); - }); - - describe('isAvailable is true', () => { - beforeEach(() => { - mockLicenseCheckResults = { - isAvailable: true - }; - }); - - it ('replies with nothing', (done) => { - const licensePreRouting = licensePreRoutingFactory(mockServer); - const stubRequest = {}; - licensePreRouting(stubRequest, (response) => { - expect(response).to.be(undefined); - done(); - }); - }); - }); - }); -}); diff --git a/x-pack/plugins/beats/server/lib/license_pre_routing_factory/index.js b/x-pack/plugins/beats/server/lib/license_pre_routing_factory/index.js deleted file mode 100644 index 0743e443955f4..0000000000000 --- a/x-pack/plugins/beats/server/lib/license_pre_routing_factory/index.js +++ /dev/null @@ -1,7 +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. - */ - -export { licensePreRoutingFactory } from './license_pre_routing_factory'; diff --git a/x-pack/plugins/beats/server/lib/license_pre_routing_factory/license_pre_routing_factory.js b/x-pack/plugins/beats/server/lib/license_pre_routing_factory/license_pre_routing_factory.js deleted file mode 100644 index 4ae31f692bfd7..0000000000000 --- a/x-pack/plugins/beats/server/lib/license_pre_routing_factory/license_pre_routing_factory.js +++ /dev/null @@ -1,28 +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 { once } from 'lodash'; -import { wrapCustomError } from '../error_wrappers'; -import { PLUGIN } from '../../../common/constants'; - -export const licensePreRoutingFactory = once((server) => { - const xpackMainPlugin = server.plugins.xpack_main; - - // License checking and enable/disable logic - function licensePreRouting(request, reply) { - const licenseCheckResults = xpackMainPlugin.info.feature(PLUGIN.ID).getLicenseCheckResults(); - if (!licenseCheckResults.isAvailable) { - const error = new Error(licenseCheckResults.message); - const statusCode = 403; - const wrappedError = wrapCustomError(error, statusCode); - reply(wrappedError); - } else { - reply(); - } - } - - return licensePreRouting; -}); diff --git a/x-pack/plugins/beats/server/lib/register_license_checker/index.js b/x-pack/plugins/beats/server/lib/register_license_checker/index.js deleted file mode 100644 index 7b0f97c38d129..0000000000000 --- a/x-pack/plugins/beats/server/lib/register_license_checker/index.js +++ /dev/null @@ -1,7 +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. - */ - -export { registerLicenseChecker } from './register_license_checker'; diff --git a/x-pack/plugins/beats/server/lib/register_license_checker/register_license_checker.js b/x-pack/plugins/beats/server/lib/register_license_checker/register_license_checker.js deleted file mode 100644 index 8a17fb2eea497..0000000000000 --- a/x-pack/plugins/beats/server/lib/register_license_checker/register_license_checker.js +++ /dev/null @@ -1,21 +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 { mirrorPluginStatus } from '../../../../../server/lib/mirror_plugin_status'; -import { checkLicense } from '../check_license'; -import { PLUGIN } from '../../../common/constants'; - -export function registerLicenseChecker(server) { - const xpackMainPlugin = server.plugins.xpack_main; - const logstashPlugin = server.plugins.logstash; - - mirrorPluginStatus(xpackMainPlugin, logstashPlugin); - xpackMainPlugin.status.once('green', () => { - // Register a function that is called whenever the xpack info changes, - // to re-compute the license check results for this plugin - xpackMainPlugin.info.feature(PLUGIN.ID).registerLicenseCheckResultsGenerator(checkLicense); - }); -} diff --git a/x-pack/plugins/beats/server/routes/api/register_disenroll_beats_route.js b/x-pack/plugins/beats/server/routes/api/register_disenroll_beats_route.js deleted file mode 100644 index e69de29bb2d1d..0000000000000 diff --git a/x-pack/plugins/beats/server/routes/api/register_enroll_beats_route.js b/x-pack/plugins/beats/server/routes/api/register_enroll_beats_route.js deleted file mode 100644 index e69de29bb2d1d..0000000000000 From d40042a5480125f1e80d58bd0e649e5fe7a4bad6 Mon Sep 17 00:00:00 2001 From: Shaunak Kashyap Date: Sat, 12 May 2018 09:22:39 -0700 Subject: [PATCH 03/24] Converting to Jest test --- .../error_wrappers/__tests__/wrap_es_error.js | 41 ------------------- 1 file changed, 41 deletions(-) delete mode 100644 x-pack/plugins/beats/server/lib/error_wrappers/__tests__/wrap_es_error.js diff --git a/x-pack/plugins/beats/server/lib/error_wrappers/__tests__/wrap_es_error.js b/x-pack/plugins/beats/server/lib/error_wrappers/__tests__/wrap_es_error.js deleted file mode 100644 index f1b956bdcc3bb..0000000000000 --- a/x-pack/plugins/beats/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'); - }); - }); -}); From ed3e29ddf56847877389767fe1c830563253fc24 Mon Sep 17 00:00:00 2001 From: Shaunak Kashyap Date: Fri, 11 May 2018 06:42:46 -0700 Subject: [PATCH 04/24] WIP checkin --- .../call_with_request_factory.js | 19 ++ .../lib/call_with_request_factory/index.js | 7 + .../check_license/__tests__/check_license.js | 180 ++++++++++++++++++ .../server/lib/check_license/check_license.js | 69 +++++++ .../beats/server/lib/check_license/index.js | 7 + .../__tests__/wrap_custom_error.js | 21 ++ .../error_wrappers/__tests__/wrap_es_error.js | 41 ++++ .../__tests__/wrap_unknown_error.js | 19 ++ .../lib/error_wrappers/wrap_custom_error.js | 18 ++ .../lib/error_wrappers/wrap_unknown_error.js | 17 ++ .../__tests__/license_pre_routing_factory.js | 72 +++++++ .../lib/license_pre_routing_factory/index.js | 7 + .../license_pre_routing_factory.js | 28 +++ .../lib/register_license_checker/index.js | 7 + .../register_license_checker.js | 21 ++ .../api/register_disenroll_beats_route.js | 0 .../routes/api/register_enroll_beats_route.js | 0 17 files changed, 533 insertions(+) create mode 100644 x-pack/plugins/beats/server/lib/call_with_request_factory/call_with_request_factory.js create mode 100644 x-pack/plugins/beats/server/lib/call_with_request_factory/index.js create mode 100644 x-pack/plugins/beats/server/lib/check_license/__tests__/check_license.js create mode 100644 x-pack/plugins/beats/server/lib/check_license/check_license.js create mode 100644 x-pack/plugins/beats/server/lib/check_license/index.js create mode 100644 x-pack/plugins/beats/server/lib/error_wrappers/__tests__/wrap_custom_error.js create mode 100644 x-pack/plugins/beats/server/lib/error_wrappers/__tests__/wrap_es_error.js create mode 100644 x-pack/plugins/beats/server/lib/error_wrappers/__tests__/wrap_unknown_error.js create mode 100644 x-pack/plugins/beats/server/lib/error_wrappers/wrap_custom_error.js create mode 100644 x-pack/plugins/beats/server/lib/error_wrappers/wrap_unknown_error.js create mode 100644 x-pack/plugins/beats/server/lib/license_pre_routing_factory/__tests__/license_pre_routing_factory.js create mode 100644 x-pack/plugins/beats/server/lib/license_pre_routing_factory/index.js create mode 100644 x-pack/plugins/beats/server/lib/license_pre_routing_factory/license_pre_routing_factory.js create mode 100644 x-pack/plugins/beats/server/lib/register_license_checker/index.js create mode 100644 x-pack/plugins/beats/server/lib/register_license_checker/register_license_checker.js create mode 100644 x-pack/plugins/beats/server/routes/api/register_disenroll_beats_route.js create mode 100644 x-pack/plugins/beats/server/routes/api/register_enroll_beats_route.js diff --git a/x-pack/plugins/beats/server/lib/call_with_request_factory/call_with_request_factory.js b/x-pack/plugins/beats/server/lib/call_with_request_factory/call_with_request_factory.js new file mode 100644 index 0000000000000..f772b81850f71 --- /dev/null +++ b/x-pack/plugins/beats/server/lib/call_with_request_factory/call_with_request_factory.js @@ -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 { once } from 'lodash'; + +const callWithRequest = once((server) => { + const pipeline = server.config().get('elasticsearch'); + const cluster = server.plugins.elasticsearch.createCluster('logstash', pipeline); + return cluster.callWithRequest; +}); + +export const callWithRequestFactory = (server, request) => { + return (...args) => { + return callWithRequest(server)(request, ...args); + }; +}; diff --git a/x-pack/plugins/beats/server/lib/call_with_request_factory/index.js b/x-pack/plugins/beats/server/lib/call_with_request_factory/index.js new file mode 100644 index 0000000000000..787814d87dff9 --- /dev/null +++ b/x-pack/plugins/beats/server/lib/call_with_request_factory/index.js @@ -0,0 +1,7 @@ +/* + * 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. + */ + +export { callWithRequestFactory } from './call_with_request_factory'; diff --git a/x-pack/plugins/beats/server/lib/check_license/__tests__/check_license.js b/x-pack/plugins/beats/server/lib/check_license/__tests__/check_license.js new file mode 100644 index 0000000000000..449ff3a60b9e7 --- /dev/null +++ b/x-pack/plugins/beats/server/lib/check_license/__tests__/check_license.js @@ -0,0 +1,180 @@ +/* + * 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 { set } from 'lodash'; +import { checkLicense } from '../check_license'; + +describe('check_license', function () { + + let mockLicenseInfo; + beforeEach(() => mockLicenseInfo = {}); + + describe('license information is undefined', () => { + beforeEach(() => mockLicenseInfo = undefined); + + it('should set isAvailable to false', () => { + expect(checkLicense(mockLicenseInfo).isAvailable).to.be(false); + }); + + it('should set enableLinks to false', () => { + expect(checkLicense(mockLicenseInfo).enableLinks).to.be(false); + }); + + it('should set isReadOnly to false', () => { + expect(checkLicense(mockLicenseInfo).isReadOnly).to.be(false); + }); + + it('should set a message', () => { + expect(checkLicense(mockLicenseInfo).message).to.not.be(undefined); + }); + }); + + describe('license information is not available', () => { + beforeEach(() => mockLicenseInfo.isAvailable = () => false); + + it('should set isAvailable to false', () => { + expect(checkLicense(mockLicenseInfo).isAvailable).to.be(false); + }); + + it('should set enableLinks to false', () => { + expect(checkLicense(mockLicenseInfo).enableLinks).to.be(false); + }); + + it('should set isReadOnly to false', () => { + expect(checkLicense(mockLicenseInfo).isReadOnly).to.be(false); + }); + + it('should set a message', () => { + expect(checkLicense(mockLicenseInfo).message).to.not.be(undefined); + }); + }); + + describe('license information is available', () => { + beforeEach(() => { + mockLicenseInfo.isAvailable = () => true; + set(mockLicenseInfo, 'license.getType', () => 'basic'); + }); + + describe('& license is trial, standard, gold, platinum', () => { + beforeEach(() => { + set(mockLicenseInfo, 'license.isOneOf', () => true); + mockLicenseInfo.feature = () => ({ isEnabled: () => true }); // Security feature is enabled + }); + + describe('& license is active', () => { + beforeEach(() => set(mockLicenseInfo, 'license.isActive', () => true)); + + it('should set isAvailable to true', () => { + expect(checkLicense(mockLicenseInfo).isAvailable).to.be(true); + }); + + it ('should set enableLinks to true', () => { + expect(checkLicense(mockLicenseInfo).enableLinks).to.be(true); + }); + + it ('should set isReadOnly to false', () => { + expect(checkLicense(mockLicenseInfo).isReadOnly).to.be(false); + }); + + it('should not set a message', () => { + expect(checkLicense(mockLicenseInfo).message).to.be(undefined); + }); + }); + + describe('& license is expired', () => { + beforeEach(() => set(mockLicenseInfo, 'license.isActive', () => false)); + + it('should set isAvailable to true', () => { + expect(checkLicense(mockLicenseInfo).isAvailable).to.be(true); + }); + + it ('should set enableLinks to true', () => { + expect(checkLicense(mockLicenseInfo).enableLinks).to.be(true); + }); + + it ('should set isReadOnly to true', () => { + expect(checkLicense(mockLicenseInfo).isReadOnly).to.be(true); + }); + + it('should set a message', () => { + expect(checkLicense(mockLicenseInfo).message).to.not.be(undefined); + }); + }); + }); + + describe('& license is basic', () => { + beforeEach(() => { + set(mockLicenseInfo, 'license.isOneOf', () => false); + mockLicenseInfo.feature = () => ({ isEnabled: () => true }); // Security feature is enabled + }); + + describe('& license is active', () => { + beforeEach(() => set(mockLicenseInfo, 'license.isActive', () => true)); + + it('should set isAvailable to false', () => { + expect(checkLicense(mockLicenseInfo).isAvailable).to.be(false); + }); + + it ('should set enableLinks to false', () => { + expect(checkLicense(mockLicenseInfo).enableLinks).to.be(false); + }); + + it ('should set isReadOnly to false', () => { + expect(checkLicense(mockLicenseInfo).isReadOnly).to.be(false); + }); + + it('should set a message', () => { + expect(checkLicense(mockLicenseInfo).message).to.not.be(undefined); + }); + }); + + describe('& license is expired', () => { + beforeEach(() => set(mockLicenseInfo, 'license.isActive', () => false)); + + it('should set isAvailable to false', () => { + expect(checkLicense(mockLicenseInfo).isAvailable).to.be(false); + }); + + it ('should set enableLinks to false', () => { + expect(checkLicense(mockLicenseInfo).enableLinks).to.be(false); + }); + + it ('should set isReadOnly to false', () => { + expect(checkLicense(mockLicenseInfo).isReadOnly).to.be(false); + }); + + it('should set a message', () => { + expect(checkLicense(mockLicenseInfo).message).to.not.be(undefined); + }); + }); + }); + + describe('& security is disabled', () => { + beforeEach(() => { + mockLicenseInfo.feature = () => ({ isEnabled: () => false }); // Security feature is disabled + set(mockLicenseInfo, 'license.isOneOf', () => true); + set(mockLicenseInfo, 'license.isActive', () => true); + }); + + it('should set isAvailable to false', () => { + expect(checkLicense(mockLicenseInfo).isAvailable).to.be(false); + }); + + it ('should set enableLinks to false', () => { + expect(checkLicense(mockLicenseInfo).enableLinks).to.be(false); + }); + + it ('should set isReadOnly to false', () => { + expect(checkLicense(mockLicenseInfo).isReadOnly).to.be(false); + }); + + it('should set a message', () => { + expect(checkLicense(mockLicenseInfo).message).to.not.be(undefined); + }); + }); + }); +}); diff --git a/x-pack/plugins/beats/server/lib/check_license/check_license.js b/x-pack/plugins/beats/server/lib/check_license/check_license.js new file mode 100644 index 0000000000000..aa1704cc02730 --- /dev/null +++ b/x-pack/plugins/beats/server/lib/check_license/check_license.js @@ -0,0 +1,69 @@ +/* + * 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. + */ + +export function checkLicense(xpackLicenseInfo) { + // If, for some reason, we cannot get the license information + // from Elasticsearch, assume worst case and disable the Logstash pipeline UI + if (!xpackLicenseInfo || !xpackLicenseInfo.isAvailable()) { + return { + isAvailable: false, + enableLinks: false, + isReadOnly: false, + message: 'You cannot manage Logstash pipelines because license information is not available at this time.' + }; + } + + const VALID_LICENSE_MODES = [ + 'trial', + 'standard', + 'gold', + 'platinum' + ]; + + const isLicenseModeValid = xpackLicenseInfo.license.isOneOf(VALID_LICENSE_MODES); + const isLicenseActive = xpackLicenseInfo.license.isActive(); + const licenseType = xpackLicenseInfo.license.getType(); + const isSecurityEnabled = xpackLicenseInfo.feature('security').isEnabled(); + + // Security is not enabled in ES + if (!isSecurityEnabled) { + const message = 'Security must be enabled in order to use Logstash pipeline management features.' + + ' Please set xpack.security.enabled: true in your elasticsearch.yml.'; + return { + isAvailable: false, + enableLinks: false, + isReadOnly: false, + message + }; + } + + // License is not valid + if (!isLicenseModeValid) { + return { + isAvailable: false, + enableLinks: false, + isReadOnly: false, + message: `Your ${licenseType} license does not support Logstash pipeline management features. Please upgrade your license.` + }; + } + + // License is valid but not active, we go into a read-only mode. + if (!isLicenseActive) { + return { + isAvailable: true, + enableLinks: true, + isReadOnly: true, + message: `You cannot edit, create, or delete your Logstash pipelines because your ${licenseType} license has expired.` + }; + } + + // License is valid and active + return { + isAvailable: true, + enableLinks: true, + isReadOnly: false + }; +} diff --git a/x-pack/plugins/beats/server/lib/check_license/index.js b/x-pack/plugins/beats/server/lib/check_license/index.js new file mode 100644 index 0000000000000..f2c070fd44b6e --- /dev/null +++ b/x-pack/plugins/beats/server/lib/check_license/index.js @@ -0,0 +1,7 @@ +/* + * 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. + */ + +export { checkLicense } from './check_license'; diff --git a/x-pack/plugins/beats/server/lib/error_wrappers/__tests__/wrap_custom_error.js b/x-pack/plugins/beats/server/lib/error_wrappers/__tests__/wrap_custom_error.js new file mode 100644 index 0000000000000..443744ccb0cc8 --- /dev/null +++ b/x-pack/plugins/beats/server/lib/error_wrappers/__tests__/wrap_custom_error.js @@ -0,0 +1,21 @@ +/* + * 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 { wrapCustomError } from '../wrap_custom_error'; + +describe('wrap_custom_error', () => { + describe('#wrapCustomError', () => { + it('should return a Boom object', () => { + const originalError = new Error('I am an error'); + const statusCode = 404; + const wrappedError = wrapCustomError(originalError, statusCode); + + expect(wrappedError.isBoom).to.be(true); + expect(wrappedError.output.statusCode).to.equal(statusCode); + }); + }); +}); diff --git a/x-pack/plugins/beats/server/lib/error_wrappers/__tests__/wrap_es_error.js b/x-pack/plugins/beats/server/lib/error_wrappers/__tests__/wrap_es_error.js new file mode 100644 index 0000000000000..f1b956bdcc3bb --- /dev/null +++ b/x-pack/plugins/beats/server/lib/error_wrappers/__tests__/wrap_es_error.js @@ -0,0 +1,41 @@ +/* + * 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/plugins/beats/server/lib/error_wrappers/__tests__/wrap_unknown_error.js b/x-pack/plugins/beats/server/lib/error_wrappers/__tests__/wrap_unknown_error.js new file mode 100644 index 0000000000000..6d6a336417bef --- /dev/null +++ b/x-pack/plugins/beats/server/lib/error_wrappers/__tests__/wrap_unknown_error.js @@ -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 expect from 'expect.js'; +import { wrapUnknownError } from '../wrap_unknown_error'; + +describe('wrap_unknown_error', () => { + describe('#wrapUnknownError', () => { + it('should return a Boom object', () => { + const originalError = new Error('I am an error'); + const wrappedError = wrapUnknownError(originalError); + + expect(wrappedError.isBoom).to.be(true); + }); + }); +}); diff --git a/x-pack/plugins/beats/server/lib/error_wrappers/wrap_custom_error.js b/x-pack/plugins/beats/server/lib/error_wrappers/wrap_custom_error.js new file mode 100644 index 0000000000000..890a366ac65c1 --- /dev/null +++ b/x-pack/plugins/beats/server/lib/error_wrappers/wrap_custom_error.js @@ -0,0 +1,18 @@ +/* + * 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 Boom from 'boom'; + +/** + * Wraps a custom error into a Boom error response and returns it + * + * @param err Object error + * @param statusCode Error status code + * @return Object Boom error response + */ +export function wrapCustomError(err, statusCode) { + return Boom.wrap(err, statusCode); +} diff --git a/x-pack/plugins/beats/server/lib/error_wrappers/wrap_unknown_error.js b/x-pack/plugins/beats/server/lib/error_wrappers/wrap_unknown_error.js new file mode 100644 index 0000000000000..b0cdced7adbef --- /dev/null +++ b/x-pack/plugins/beats/server/lib/error_wrappers/wrap_unknown_error.js @@ -0,0 +1,17 @@ +/* + * 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 Boom from 'boom'; + +/** + * Wraps an unknown error into a Boom error response and returns it + * + * @param err Object Unknown error + * @return Object Boom error response + */ +export function wrapUnknownError(err) { + return Boom.wrap(err); +} diff --git a/x-pack/plugins/beats/server/lib/license_pre_routing_factory/__tests__/license_pre_routing_factory.js b/x-pack/plugins/beats/server/lib/license_pre_routing_factory/__tests__/license_pre_routing_factory.js new file mode 100644 index 0000000000000..c543d79814dd3 --- /dev/null +++ b/x-pack/plugins/beats/server/lib/license_pre_routing_factory/__tests__/license_pre_routing_factory.js @@ -0,0 +1,72 @@ +/* + * 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 { licensePreRoutingFactory } from '../license_pre_routing_factory'; + +describe('license_pre_routing_factory', () => { + describe('#logstashFeaturePreRoutingFactory', () => { + let mockServer; + let mockLicenseCheckResults; + + beforeEach(() => { + mockServer = { + plugins: { + xpack_main: { + info: { + feature: () => ({ + getLicenseCheckResults: () => mockLicenseCheckResults + }) + } + } + } + }; + }); + + it('only instantiates one instance per server', () => { + const firstInstance = licensePreRoutingFactory(mockServer); + const secondInstance = licensePreRoutingFactory(mockServer); + + expect(firstInstance).to.be(secondInstance); + }); + + describe('isAvailable is false', () => { + beforeEach(() => { + mockLicenseCheckResults = { + isAvailable: false + }; + }); + + it ('replies with 403', (done) => { + const licensePreRouting = licensePreRoutingFactory(mockServer); + const stubRequest = {}; + licensePreRouting(stubRequest, (response) => { + expect(response).to.be.an(Error); + expect(response.isBoom).to.be(true); + expect(response.output.statusCode).to.be(403); + done(); + }); + }); + }); + + describe('isAvailable is true', () => { + beforeEach(() => { + mockLicenseCheckResults = { + isAvailable: true + }; + }); + + it ('replies with nothing', (done) => { + const licensePreRouting = licensePreRoutingFactory(mockServer); + const stubRequest = {}; + licensePreRouting(stubRequest, (response) => { + expect(response).to.be(undefined); + done(); + }); + }); + }); + }); +}); diff --git a/x-pack/plugins/beats/server/lib/license_pre_routing_factory/index.js b/x-pack/plugins/beats/server/lib/license_pre_routing_factory/index.js new file mode 100644 index 0000000000000..0743e443955f4 --- /dev/null +++ b/x-pack/plugins/beats/server/lib/license_pre_routing_factory/index.js @@ -0,0 +1,7 @@ +/* + * 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. + */ + +export { licensePreRoutingFactory } from './license_pre_routing_factory'; diff --git a/x-pack/plugins/beats/server/lib/license_pre_routing_factory/license_pre_routing_factory.js b/x-pack/plugins/beats/server/lib/license_pre_routing_factory/license_pre_routing_factory.js new file mode 100644 index 0000000000000..4ae31f692bfd7 --- /dev/null +++ b/x-pack/plugins/beats/server/lib/license_pre_routing_factory/license_pre_routing_factory.js @@ -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 { once } from 'lodash'; +import { wrapCustomError } from '../error_wrappers'; +import { PLUGIN } from '../../../common/constants'; + +export const licensePreRoutingFactory = once((server) => { + const xpackMainPlugin = server.plugins.xpack_main; + + // License checking and enable/disable logic + function licensePreRouting(request, reply) { + const licenseCheckResults = xpackMainPlugin.info.feature(PLUGIN.ID).getLicenseCheckResults(); + if (!licenseCheckResults.isAvailable) { + const error = new Error(licenseCheckResults.message); + const statusCode = 403; + const wrappedError = wrapCustomError(error, statusCode); + reply(wrappedError); + } else { + reply(); + } + } + + return licensePreRouting; +}); diff --git a/x-pack/plugins/beats/server/lib/register_license_checker/index.js b/x-pack/plugins/beats/server/lib/register_license_checker/index.js new file mode 100644 index 0000000000000..7b0f97c38d129 --- /dev/null +++ b/x-pack/plugins/beats/server/lib/register_license_checker/index.js @@ -0,0 +1,7 @@ +/* + * 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. + */ + +export { registerLicenseChecker } from './register_license_checker'; diff --git a/x-pack/plugins/beats/server/lib/register_license_checker/register_license_checker.js b/x-pack/plugins/beats/server/lib/register_license_checker/register_license_checker.js new file mode 100644 index 0000000000000..8a17fb2eea497 --- /dev/null +++ b/x-pack/plugins/beats/server/lib/register_license_checker/register_license_checker.js @@ -0,0 +1,21 @@ +/* + * 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 { mirrorPluginStatus } from '../../../../../server/lib/mirror_plugin_status'; +import { checkLicense } from '../check_license'; +import { PLUGIN } from '../../../common/constants'; + +export function registerLicenseChecker(server) { + const xpackMainPlugin = server.plugins.xpack_main; + const logstashPlugin = server.plugins.logstash; + + mirrorPluginStatus(xpackMainPlugin, logstashPlugin); + xpackMainPlugin.status.once('green', () => { + // Register a function that is called whenever the xpack info changes, + // to re-compute the license check results for this plugin + xpackMainPlugin.info.feature(PLUGIN.ID).registerLicenseCheckResultsGenerator(checkLicense); + }); +} diff --git a/x-pack/plugins/beats/server/routes/api/register_disenroll_beats_route.js b/x-pack/plugins/beats/server/routes/api/register_disenroll_beats_route.js new file mode 100644 index 0000000000000..e69de29bb2d1d diff --git a/x-pack/plugins/beats/server/routes/api/register_enroll_beats_route.js b/x-pack/plugins/beats/server/routes/api/register_enroll_beats_route.js new file mode 100644 index 0000000000000..e69de29bb2d1d From 19d893d6aa689d1adedf61ad8de41fc7bde71616 Mon Sep 17 00:00:00 2001 From: Shaunak Kashyap Date: Sat, 12 May 2018 12:33:56 -0700 Subject: [PATCH 05/24] Fixing API for default case + adding test for it --- .../apis/beats/create_enrollment_tokens.js | 24 +++++++++++++++++++ 1 file changed, 24 insertions(+) diff --git a/x-pack/test/api_integration/apis/beats/create_enrollment_tokens.js b/x-pack/test/api_integration/apis/beats/create_enrollment_tokens.js index 86b80323773b4..953508ebfadb3 100644 --- a/x-pack/test/api_integration/apis/beats/create_enrollment_tokens.js +++ b/x-pack/test/api_integration/apis/beats/create_enrollment_tokens.js @@ -41,6 +41,30 @@ export default function ({ getService }) { expect(tokensFromApi).to.eql(tokensInEs); }); + it('should create one token by default', async () => { + const { body: apiResponse } = await supertest + .post( + '/api/beats/enrollment_tokens' + ) + .set('kbn-xsrf', 'xxx') + .send() + .expect(200); + + const tokensFromApi = apiResponse.tokens; + + const esResponse = await es.search({ + index: ES_ADMIN_INDEX_NAME, + type: ES_TYPE_NAME, + q: 'type:enrollment_token' + }); + + const tokensInEs = esResponse.hits.hits + .map(hit => hit._source.enrollment_token.token); + + expect(tokensFromApi.length).to.eql(1); + expect(tokensFromApi).to.eql(tokensInEs); + }); + it('should create the specified number of tokens', async () => { const numTokens = chance.integer({ min: 1, max: 2000 }); From ccc117b3e7ec842e089ac9b546ca9356cd94ff89 Mon Sep 17 00:00:00 2001 From: Shaunak Kashyap Date: Mon, 14 May 2018 07:01:21 -0700 Subject: [PATCH 06/24] Fixing copy pasta typos --- .../lib/call_with_request_factory/call_with_request_factory.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/x-pack/plugins/beats/server/lib/call_with_request_factory/call_with_request_factory.js b/x-pack/plugins/beats/server/lib/call_with_request_factory/call_with_request_factory.js index f772b81850f71..b9afd6a47c618 100644 --- a/x-pack/plugins/beats/server/lib/call_with_request_factory/call_with_request_factory.js +++ b/x-pack/plugins/beats/server/lib/call_with_request_factory/call_with_request_factory.js @@ -8,7 +8,7 @@ import { once } from 'lodash'; const callWithRequest = once((server) => { const pipeline = server.config().get('elasticsearch'); - const cluster = server.plugins.elasticsearch.createCluster('logstash', pipeline); + const cluster = server.plugins.elasticsearch.createCluster('beats', pipeline); return cluster.callWithRequest; }); From f73bf3bce365ba513a61438a0c0fc6f34f3a7717 Mon Sep 17 00:00:00 2001 From: Shaunak Kashyap Date: Mon, 14 May 2018 11:11:34 -0700 Subject: [PATCH 07/24] Fixing variable name --- .../call_with_request_factory/call_with_request_factory.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/x-pack/plugins/beats/server/lib/call_with_request_factory/call_with_request_factory.js b/x-pack/plugins/beats/server/lib/call_with_request_factory/call_with_request_factory.js index b9afd6a47c618..0c4f909d12f61 100644 --- a/x-pack/plugins/beats/server/lib/call_with_request_factory/call_with_request_factory.js +++ b/x-pack/plugins/beats/server/lib/call_with_request_factory/call_with_request_factory.js @@ -7,8 +7,8 @@ import { once } from 'lodash'; const callWithRequest = once((server) => { - const pipeline = server.config().get('elasticsearch'); - const cluster = server.plugins.elasticsearch.createCluster('beats', pipeline); + const config = server.config().get('elasticsearch'); + const cluster = server.plugins.elasticsearch.createCluster('beats', config); return cluster.callWithRequest; }); From ff35c6f5259e73043dcedc96ca836f5c36112cdd Mon Sep 17 00:00:00 2001 From: Shaunak Kashyap Date: Mon, 14 May 2018 13:02:53 -0700 Subject: [PATCH 08/24] Using a single index --- .../test/api_integration/apis/beats/create_enrollment_tokens.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/x-pack/test/api_integration/apis/beats/create_enrollment_tokens.js b/x-pack/test/api_integration/apis/beats/create_enrollment_tokens.js index 953508ebfadb3..129b6b405571c 100644 --- a/x-pack/test/api_integration/apis/beats/create_enrollment_tokens.js +++ b/x-pack/test/api_integration/apis/beats/create_enrollment_tokens.js @@ -53,7 +53,7 @@ export default function ({ getService }) { const tokensFromApi = apiResponse.tokens; const esResponse = await es.search({ - index: ES_ADMIN_INDEX_NAME, + index: ES_INDEX_NAME, type: ES_TYPE_NAME, q: 'type:enrollment_token' }); From be53b05bf6b40c92955c1a631bc47e94b8fca3bc Mon Sep 17 00:00:00 2001 From: Shaunak Kashyap Date: Tue, 15 May 2018 11:57:43 -0700 Subject: [PATCH 09/24] Implementing GET /api/beats/agents API --- .../call_with_request_factory.js | 19 -- .../lib/call_with_request_factory/index.js | 7 - .../check_license/__tests__/check_license.js | 180 ------------------ .../server/lib/check_license/check_license.js | 69 ------- .../beats/server/lib/check_license/index.js | 7 - .../__tests__/wrap_custom_error.js | 21 -- .../error_wrappers/__tests__/wrap_es_error.js | 41 ---- .../__tests__/wrap_unknown_error.js | 19 -- .../lib/error_wrappers/wrap_custom_error.js | 18 -- .../lib/error_wrappers/wrap_unknown_error.js | 17 -- .../__tests__/license_pre_routing_factory.js | 72 ------- .../lib/license_pre_routing_factory/index.js | 7 - .../license_pre_routing_factory.js | 28 --- .../lib/register_license_checker/index.js | 7 - .../register_license_checker.js | 21 -- .../api/register_disenroll_beats_route.js | 0 .../routes/api/register_enroll_beats_route.js | 0 17 files changed, 533 deletions(-) delete mode 100644 x-pack/plugins/beats/server/lib/call_with_request_factory/call_with_request_factory.js delete mode 100644 x-pack/plugins/beats/server/lib/call_with_request_factory/index.js delete mode 100644 x-pack/plugins/beats/server/lib/check_license/__tests__/check_license.js delete mode 100644 x-pack/plugins/beats/server/lib/check_license/check_license.js delete mode 100644 x-pack/plugins/beats/server/lib/check_license/index.js delete mode 100644 x-pack/plugins/beats/server/lib/error_wrappers/__tests__/wrap_custom_error.js delete mode 100644 x-pack/plugins/beats/server/lib/error_wrappers/__tests__/wrap_es_error.js delete mode 100644 x-pack/plugins/beats/server/lib/error_wrappers/__tests__/wrap_unknown_error.js delete mode 100644 x-pack/plugins/beats/server/lib/error_wrappers/wrap_custom_error.js delete mode 100644 x-pack/plugins/beats/server/lib/error_wrappers/wrap_unknown_error.js delete mode 100644 x-pack/plugins/beats/server/lib/license_pre_routing_factory/__tests__/license_pre_routing_factory.js delete mode 100644 x-pack/plugins/beats/server/lib/license_pre_routing_factory/index.js delete mode 100644 x-pack/plugins/beats/server/lib/license_pre_routing_factory/license_pre_routing_factory.js delete mode 100644 x-pack/plugins/beats/server/lib/register_license_checker/index.js delete mode 100644 x-pack/plugins/beats/server/lib/register_license_checker/register_license_checker.js delete mode 100644 x-pack/plugins/beats/server/routes/api/register_disenroll_beats_route.js delete mode 100644 x-pack/plugins/beats/server/routes/api/register_enroll_beats_route.js diff --git a/x-pack/plugins/beats/server/lib/call_with_request_factory/call_with_request_factory.js b/x-pack/plugins/beats/server/lib/call_with_request_factory/call_with_request_factory.js deleted file mode 100644 index 0c4f909d12f61..0000000000000 --- a/x-pack/plugins/beats/server/lib/call_with_request_factory/call_with_request_factory.js +++ /dev/null @@ -1,19 +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 { once } from 'lodash'; - -const callWithRequest = once((server) => { - const config = server.config().get('elasticsearch'); - const cluster = server.plugins.elasticsearch.createCluster('beats', config); - return cluster.callWithRequest; -}); - -export const callWithRequestFactory = (server, request) => { - return (...args) => { - return callWithRequest(server)(request, ...args); - }; -}; diff --git a/x-pack/plugins/beats/server/lib/call_with_request_factory/index.js b/x-pack/plugins/beats/server/lib/call_with_request_factory/index.js deleted file mode 100644 index 787814d87dff9..0000000000000 --- a/x-pack/plugins/beats/server/lib/call_with_request_factory/index.js +++ /dev/null @@ -1,7 +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. - */ - -export { callWithRequestFactory } from './call_with_request_factory'; diff --git a/x-pack/plugins/beats/server/lib/check_license/__tests__/check_license.js b/x-pack/plugins/beats/server/lib/check_license/__tests__/check_license.js deleted file mode 100644 index 449ff3a60b9e7..0000000000000 --- a/x-pack/plugins/beats/server/lib/check_license/__tests__/check_license.js +++ /dev/null @@ -1,180 +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 { set } from 'lodash'; -import { checkLicense } from '../check_license'; - -describe('check_license', function () { - - let mockLicenseInfo; - beforeEach(() => mockLicenseInfo = {}); - - describe('license information is undefined', () => { - beforeEach(() => mockLicenseInfo = undefined); - - it('should set isAvailable to false', () => { - expect(checkLicense(mockLicenseInfo).isAvailable).to.be(false); - }); - - it('should set enableLinks to false', () => { - expect(checkLicense(mockLicenseInfo).enableLinks).to.be(false); - }); - - it('should set isReadOnly to false', () => { - expect(checkLicense(mockLicenseInfo).isReadOnly).to.be(false); - }); - - it('should set a message', () => { - expect(checkLicense(mockLicenseInfo).message).to.not.be(undefined); - }); - }); - - describe('license information is not available', () => { - beforeEach(() => mockLicenseInfo.isAvailable = () => false); - - it('should set isAvailable to false', () => { - expect(checkLicense(mockLicenseInfo).isAvailable).to.be(false); - }); - - it('should set enableLinks to false', () => { - expect(checkLicense(mockLicenseInfo).enableLinks).to.be(false); - }); - - it('should set isReadOnly to false', () => { - expect(checkLicense(mockLicenseInfo).isReadOnly).to.be(false); - }); - - it('should set a message', () => { - expect(checkLicense(mockLicenseInfo).message).to.not.be(undefined); - }); - }); - - describe('license information is available', () => { - beforeEach(() => { - mockLicenseInfo.isAvailable = () => true; - set(mockLicenseInfo, 'license.getType', () => 'basic'); - }); - - describe('& license is trial, standard, gold, platinum', () => { - beforeEach(() => { - set(mockLicenseInfo, 'license.isOneOf', () => true); - mockLicenseInfo.feature = () => ({ isEnabled: () => true }); // Security feature is enabled - }); - - describe('& license is active', () => { - beforeEach(() => set(mockLicenseInfo, 'license.isActive', () => true)); - - it('should set isAvailable to true', () => { - expect(checkLicense(mockLicenseInfo).isAvailable).to.be(true); - }); - - it ('should set enableLinks to true', () => { - expect(checkLicense(mockLicenseInfo).enableLinks).to.be(true); - }); - - it ('should set isReadOnly to false', () => { - expect(checkLicense(mockLicenseInfo).isReadOnly).to.be(false); - }); - - it('should not set a message', () => { - expect(checkLicense(mockLicenseInfo).message).to.be(undefined); - }); - }); - - describe('& license is expired', () => { - beforeEach(() => set(mockLicenseInfo, 'license.isActive', () => false)); - - it('should set isAvailable to true', () => { - expect(checkLicense(mockLicenseInfo).isAvailable).to.be(true); - }); - - it ('should set enableLinks to true', () => { - expect(checkLicense(mockLicenseInfo).enableLinks).to.be(true); - }); - - it ('should set isReadOnly to true', () => { - expect(checkLicense(mockLicenseInfo).isReadOnly).to.be(true); - }); - - it('should set a message', () => { - expect(checkLicense(mockLicenseInfo).message).to.not.be(undefined); - }); - }); - }); - - describe('& license is basic', () => { - beforeEach(() => { - set(mockLicenseInfo, 'license.isOneOf', () => false); - mockLicenseInfo.feature = () => ({ isEnabled: () => true }); // Security feature is enabled - }); - - describe('& license is active', () => { - beforeEach(() => set(mockLicenseInfo, 'license.isActive', () => true)); - - it('should set isAvailable to false', () => { - expect(checkLicense(mockLicenseInfo).isAvailable).to.be(false); - }); - - it ('should set enableLinks to false', () => { - expect(checkLicense(mockLicenseInfo).enableLinks).to.be(false); - }); - - it ('should set isReadOnly to false', () => { - expect(checkLicense(mockLicenseInfo).isReadOnly).to.be(false); - }); - - it('should set a message', () => { - expect(checkLicense(mockLicenseInfo).message).to.not.be(undefined); - }); - }); - - describe('& license is expired', () => { - beforeEach(() => set(mockLicenseInfo, 'license.isActive', () => false)); - - it('should set isAvailable to false', () => { - expect(checkLicense(mockLicenseInfo).isAvailable).to.be(false); - }); - - it ('should set enableLinks to false', () => { - expect(checkLicense(mockLicenseInfo).enableLinks).to.be(false); - }); - - it ('should set isReadOnly to false', () => { - expect(checkLicense(mockLicenseInfo).isReadOnly).to.be(false); - }); - - it('should set a message', () => { - expect(checkLicense(mockLicenseInfo).message).to.not.be(undefined); - }); - }); - }); - - describe('& security is disabled', () => { - beforeEach(() => { - mockLicenseInfo.feature = () => ({ isEnabled: () => false }); // Security feature is disabled - set(mockLicenseInfo, 'license.isOneOf', () => true); - set(mockLicenseInfo, 'license.isActive', () => true); - }); - - it('should set isAvailable to false', () => { - expect(checkLicense(mockLicenseInfo).isAvailable).to.be(false); - }); - - it ('should set enableLinks to false', () => { - expect(checkLicense(mockLicenseInfo).enableLinks).to.be(false); - }); - - it ('should set isReadOnly to false', () => { - expect(checkLicense(mockLicenseInfo).isReadOnly).to.be(false); - }); - - it('should set a message', () => { - expect(checkLicense(mockLicenseInfo).message).to.not.be(undefined); - }); - }); - }); -}); diff --git a/x-pack/plugins/beats/server/lib/check_license/check_license.js b/x-pack/plugins/beats/server/lib/check_license/check_license.js deleted file mode 100644 index aa1704cc02730..0000000000000 --- a/x-pack/plugins/beats/server/lib/check_license/check_license.js +++ /dev/null @@ -1,69 +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. - */ - -export function checkLicense(xpackLicenseInfo) { - // If, for some reason, we cannot get the license information - // from Elasticsearch, assume worst case and disable the Logstash pipeline UI - if (!xpackLicenseInfo || !xpackLicenseInfo.isAvailable()) { - return { - isAvailable: false, - enableLinks: false, - isReadOnly: false, - message: 'You cannot manage Logstash pipelines because license information is not available at this time.' - }; - } - - const VALID_LICENSE_MODES = [ - 'trial', - 'standard', - 'gold', - 'platinum' - ]; - - const isLicenseModeValid = xpackLicenseInfo.license.isOneOf(VALID_LICENSE_MODES); - const isLicenseActive = xpackLicenseInfo.license.isActive(); - const licenseType = xpackLicenseInfo.license.getType(); - const isSecurityEnabled = xpackLicenseInfo.feature('security').isEnabled(); - - // Security is not enabled in ES - if (!isSecurityEnabled) { - const message = 'Security must be enabled in order to use Logstash pipeline management features.' - + ' Please set xpack.security.enabled: true in your elasticsearch.yml.'; - return { - isAvailable: false, - enableLinks: false, - isReadOnly: false, - message - }; - } - - // License is not valid - if (!isLicenseModeValid) { - return { - isAvailable: false, - enableLinks: false, - isReadOnly: false, - message: `Your ${licenseType} license does not support Logstash pipeline management features. Please upgrade your license.` - }; - } - - // License is valid but not active, we go into a read-only mode. - if (!isLicenseActive) { - return { - isAvailable: true, - enableLinks: true, - isReadOnly: true, - message: `You cannot edit, create, or delete your Logstash pipelines because your ${licenseType} license has expired.` - }; - } - - // License is valid and active - return { - isAvailable: true, - enableLinks: true, - isReadOnly: false - }; -} diff --git a/x-pack/plugins/beats/server/lib/check_license/index.js b/x-pack/plugins/beats/server/lib/check_license/index.js deleted file mode 100644 index f2c070fd44b6e..0000000000000 --- a/x-pack/plugins/beats/server/lib/check_license/index.js +++ /dev/null @@ -1,7 +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. - */ - -export { checkLicense } from './check_license'; diff --git a/x-pack/plugins/beats/server/lib/error_wrappers/__tests__/wrap_custom_error.js b/x-pack/plugins/beats/server/lib/error_wrappers/__tests__/wrap_custom_error.js deleted file mode 100644 index 443744ccb0cc8..0000000000000 --- a/x-pack/plugins/beats/server/lib/error_wrappers/__tests__/wrap_custom_error.js +++ /dev/null @@ -1,21 +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 { wrapCustomError } from '../wrap_custom_error'; - -describe('wrap_custom_error', () => { - describe('#wrapCustomError', () => { - it('should return a Boom object', () => { - const originalError = new Error('I am an error'); - const statusCode = 404; - const wrappedError = wrapCustomError(originalError, statusCode); - - expect(wrappedError.isBoom).to.be(true); - expect(wrappedError.output.statusCode).to.equal(statusCode); - }); - }); -}); diff --git a/x-pack/plugins/beats/server/lib/error_wrappers/__tests__/wrap_es_error.js b/x-pack/plugins/beats/server/lib/error_wrappers/__tests__/wrap_es_error.js deleted file mode 100644 index f1b956bdcc3bb..0000000000000 --- a/x-pack/plugins/beats/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/plugins/beats/server/lib/error_wrappers/__tests__/wrap_unknown_error.js b/x-pack/plugins/beats/server/lib/error_wrappers/__tests__/wrap_unknown_error.js deleted file mode 100644 index 6d6a336417bef..0000000000000 --- a/x-pack/plugins/beats/server/lib/error_wrappers/__tests__/wrap_unknown_error.js +++ /dev/null @@ -1,19 +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 { wrapUnknownError } from '../wrap_unknown_error'; - -describe('wrap_unknown_error', () => { - describe('#wrapUnknownError', () => { - it('should return a Boom object', () => { - const originalError = new Error('I am an error'); - const wrappedError = wrapUnknownError(originalError); - - expect(wrappedError.isBoom).to.be(true); - }); - }); -}); diff --git a/x-pack/plugins/beats/server/lib/error_wrappers/wrap_custom_error.js b/x-pack/plugins/beats/server/lib/error_wrappers/wrap_custom_error.js deleted file mode 100644 index 890a366ac65c1..0000000000000 --- a/x-pack/plugins/beats/server/lib/error_wrappers/wrap_custom_error.js +++ /dev/null @@ -1,18 +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 Boom from 'boom'; - -/** - * Wraps a custom error into a Boom error response and returns it - * - * @param err Object error - * @param statusCode Error status code - * @return Object Boom error response - */ -export function wrapCustomError(err, statusCode) { - return Boom.wrap(err, statusCode); -} diff --git a/x-pack/plugins/beats/server/lib/error_wrappers/wrap_unknown_error.js b/x-pack/plugins/beats/server/lib/error_wrappers/wrap_unknown_error.js deleted file mode 100644 index b0cdced7adbef..0000000000000 --- a/x-pack/plugins/beats/server/lib/error_wrappers/wrap_unknown_error.js +++ /dev/null @@ -1,17 +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 Boom from 'boom'; - -/** - * Wraps an unknown error into a Boom error response and returns it - * - * @param err Object Unknown error - * @return Object Boom error response - */ -export function wrapUnknownError(err) { - return Boom.wrap(err); -} diff --git a/x-pack/plugins/beats/server/lib/license_pre_routing_factory/__tests__/license_pre_routing_factory.js b/x-pack/plugins/beats/server/lib/license_pre_routing_factory/__tests__/license_pre_routing_factory.js deleted file mode 100644 index c543d79814dd3..0000000000000 --- a/x-pack/plugins/beats/server/lib/license_pre_routing_factory/__tests__/license_pre_routing_factory.js +++ /dev/null @@ -1,72 +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 { licensePreRoutingFactory } from '../license_pre_routing_factory'; - -describe('license_pre_routing_factory', () => { - describe('#logstashFeaturePreRoutingFactory', () => { - let mockServer; - let mockLicenseCheckResults; - - beforeEach(() => { - mockServer = { - plugins: { - xpack_main: { - info: { - feature: () => ({ - getLicenseCheckResults: () => mockLicenseCheckResults - }) - } - } - } - }; - }); - - it('only instantiates one instance per server', () => { - const firstInstance = licensePreRoutingFactory(mockServer); - const secondInstance = licensePreRoutingFactory(mockServer); - - expect(firstInstance).to.be(secondInstance); - }); - - describe('isAvailable is false', () => { - beforeEach(() => { - mockLicenseCheckResults = { - isAvailable: false - }; - }); - - it ('replies with 403', (done) => { - const licensePreRouting = licensePreRoutingFactory(mockServer); - const stubRequest = {}; - licensePreRouting(stubRequest, (response) => { - expect(response).to.be.an(Error); - expect(response.isBoom).to.be(true); - expect(response.output.statusCode).to.be(403); - done(); - }); - }); - }); - - describe('isAvailable is true', () => { - beforeEach(() => { - mockLicenseCheckResults = { - isAvailable: true - }; - }); - - it ('replies with nothing', (done) => { - const licensePreRouting = licensePreRoutingFactory(mockServer); - const stubRequest = {}; - licensePreRouting(stubRequest, (response) => { - expect(response).to.be(undefined); - done(); - }); - }); - }); - }); -}); diff --git a/x-pack/plugins/beats/server/lib/license_pre_routing_factory/index.js b/x-pack/plugins/beats/server/lib/license_pre_routing_factory/index.js deleted file mode 100644 index 0743e443955f4..0000000000000 --- a/x-pack/plugins/beats/server/lib/license_pre_routing_factory/index.js +++ /dev/null @@ -1,7 +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. - */ - -export { licensePreRoutingFactory } from './license_pre_routing_factory'; diff --git a/x-pack/plugins/beats/server/lib/license_pre_routing_factory/license_pre_routing_factory.js b/x-pack/plugins/beats/server/lib/license_pre_routing_factory/license_pre_routing_factory.js deleted file mode 100644 index 4ae31f692bfd7..0000000000000 --- a/x-pack/plugins/beats/server/lib/license_pre_routing_factory/license_pre_routing_factory.js +++ /dev/null @@ -1,28 +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 { once } from 'lodash'; -import { wrapCustomError } from '../error_wrappers'; -import { PLUGIN } from '../../../common/constants'; - -export const licensePreRoutingFactory = once((server) => { - const xpackMainPlugin = server.plugins.xpack_main; - - // License checking and enable/disable logic - function licensePreRouting(request, reply) { - const licenseCheckResults = xpackMainPlugin.info.feature(PLUGIN.ID).getLicenseCheckResults(); - if (!licenseCheckResults.isAvailable) { - const error = new Error(licenseCheckResults.message); - const statusCode = 403; - const wrappedError = wrapCustomError(error, statusCode); - reply(wrappedError); - } else { - reply(); - } - } - - return licensePreRouting; -}); diff --git a/x-pack/plugins/beats/server/lib/register_license_checker/index.js b/x-pack/plugins/beats/server/lib/register_license_checker/index.js deleted file mode 100644 index 7b0f97c38d129..0000000000000 --- a/x-pack/plugins/beats/server/lib/register_license_checker/index.js +++ /dev/null @@ -1,7 +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. - */ - -export { registerLicenseChecker } from './register_license_checker'; diff --git a/x-pack/plugins/beats/server/lib/register_license_checker/register_license_checker.js b/x-pack/plugins/beats/server/lib/register_license_checker/register_license_checker.js deleted file mode 100644 index 8a17fb2eea497..0000000000000 --- a/x-pack/plugins/beats/server/lib/register_license_checker/register_license_checker.js +++ /dev/null @@ -1,21 +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 { mirrorPluginStatus } from '../../../../../server/lib/mirror_plugin_status'; -import { checkLicense } from '../check_license'; -import { PLUGIN } from '../../../common/constants'; - -export function registerLicenseChecker(server) { - const xpackMainPlugin = server.plugins.xpack_main; - const logstashPlugin = server.plugins.logstash; - - mirrorPluginStatus(xpackMainPlugin, logstashPlugin); - xpackMainPlugin.status.once('green', () => { - // Register a function that is called whenever the xpack info changes, - // to re-compute the license check results for this plugin - xpackMainPlugin.info.feature(PLUGIN.ID).registerLicenseCheckResultsGenerator(checkLicense); - }); -} diff --git a/x-pack/plugins/beats/server/routes/api/register_disenroll_beats_route.js b/x-pack/plugins/beats/server/routes/api/register_disenroll_beats_route.js deleted file mode 100644 index e69de29bb2d1d..0000000000000 diff --git a/x-pack/plugins/beats/server/routes/api/register_enroll_beats_route.js b/x-pack/plugins/beats/server/routes/api/register_enroll_beats_route.js deleted file mode 100644 index e69de29bb2d1d..0000000000000 From 965a4e3ece22956f4fdb7b0f7340f0161a8fc923 Mon Sep 17 00:00:00 2001 From: Shaunak Kashyap Date: Wed, 16 May 2018 09:23:24 -0700 Subject: [PATCH 10/24] Updating mapping --- .../apis/beats/create_enrollment_tokens.js | 24 ------------------- 1 file changed, 24 deletions(-) diff --git a/x-pack/test/api_integration/apis/beats/create_enrollment_tokens.js b/x-pack/test/api_integration/apis/beats/create_enrollment_tokens.js index 129b6b405571c..86b80323773b4 100644 --- a/x-pack/test/api_integration/apis/beats/create_enrollment_tokens.js +++ b/x-pack/test/api_integration/apis/beats/create_enrollment_tokens.js @@ -41,30 +41,6 @@ export default function ({ getService }) { expect(tokensFromApi).to.eql(tokensInEs); }); - it('should create one token by default', async () => { - const { body: apiResponse } = await supertest - .post( - '/api/beats/enrollment_tokens' - ) - .set('kbn-xsrf', 'xxx') - .send() - .expect(200); - - const tokensFromApi = apiResponse.tokens; - - const esResponse = await es.search({ - index: ES_INDEX_NAME, - type: ES_TYPE_NAME, - q: 'type:enrollment_token' - }); - - const tokensInEs = esResponse.hits.hits - .map(hit => hit._source.enrollment_token.token); - - expect(tokensFromApi.length).to.eql(1); - expect(tokensFromApi).to.eql(tokensInEs); - }); - it('should create the specified number of tokens', async () => { const numTokens = chance.integer({ min: 1, max: 2000 }); From 481f96ff9b78b2ea0260ea41299a7837a8159dfd Mon Sep 17 00:00:00 2001 From: Shaunak Kashyap Date: Tue, 15 May 2018 18:36:17 -0700 Subject: [PATCH 11/24] Creating POST /api/beats/agents/verify API --- .../api/register_verify_beats_routes.js | 132 ++++++++++++++++++ 1 file changed, 132 insertions(+) create mode 100644 x-pack/plugins/beats/server/routes/api/register_verify_beats_routes.js diff --git a/x-pack/plugins/beats/server/routes/api/register_verify_beats_routes.js b/x-pack/plugins/beats/server/routes/api/register_verify_beats_routes.js new file mode 100644 index 0000000000000..c57c35859aba9 --- /dev/null +++ b/x-pack/plugins/beats/server/routes/api/register_verify_beats_routes.js @@ -0,0 +1,132 @@ +/* + * 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 moment from 'moment'; +import { + get, + flatten +} from 'lodash'; +import { INDEX_NAMES } from '../../../common/constants'; +import { callWithRequestFactory } from '../../lib/client'; +import { wrapEsError } from '../../lib/error_wrappers'; + +async function getBeats(callWithRequest, beatIds) { + const ids = beatIds.map(beatId => `beat:${beatId}`); + const params = { + index: INDEX_NAMES.BEATS, + type: '_doc', + body: { ids }, + _sourceInclude: [ 'beat.id', 'beat.verified_on' ] + }; + + const response = await callWithRequest('mget', params); + return get(response, 'docs', []); +} + +async function verifyBeats(callWithRequest, beatIds) { + if (!Array.isArray(beatIds) || (beatIds.length === 0)) { + return []; + } + + const verifiedOn = moment().toJSON(); + const body = flatten(beatIds.map(beatId => [ + { update: { _id: `beat:${beatId}` } }, + { doc: { beat: { verified_on: verifiedOn } } } + ])); + + const params = { + index: INDEX_NAMES.BEATS, + type: '_doc', + body, + refresh: 'wait_for' + }; + + const response = await callWithRequest('bulk', params); + return get(response, 'items', []); +} + +// TODO: add license check pre-hook +// TODO: write to Kibana audit log file +export function registerVerifyBeatsRoute(server) { + server.route({ + method: 'POST', + path: '/api/beats/agents/verify', + config: { + validate: { + payload: Joi.object({ + beats: Joi.array({ + id: Joi.string().required() + }).min(1) + }).required() + } + }, + handler: async (request, reply) => { + const callWithRequest = callWithRequestFactory(server, request); + + const beats = [...request.payload.beats]; + const beatIds = beats.map(beat => beat.id); + + let nonExistentBeatIds; + let alreadyVerifiedBeatIds; + let verifiedBeatIds; + + try { + const beatsFromEs = await getBeats(callWithRequest, beatIds); + + // TODO: extract into helper function + nonExistentBeatIds = beatsFromEs.reduce((beatIdsSoFar, beatFromEs, idx) => { + if (!beatFromEs.found) { + beatIdsSoFar.push(beatIds[idx]); + } + return beatIdsSoFar; + }, []); + + alreadyVerifiedBeatIds = beatsFromEs + .filter(beat => beat.found) + .filter(beat => beat._source.beat.hasOwnProperty('verified_on')) + .map(beat => beat._source.beat.id); + + const beatIdsToVerify = beatsFromEs + .filter(beat => beat.found) + .filter(beat => !beat._source.beat.hasOwnProperty('verified_on')) + .map(beat => beat._source.beat.id); + + const verifications = await verifyBeats(callWithRequest, beatIdsToVerify); + + // TODO: extract into helper function + verifiedBeatIds = verifications.reduce((beatIdsSoFar, verification, idx) => { + if (verification.update.status === 200) { + beatIdsSoFar.push(beatIdsToVerify[idx]); + } + return beatIdsSoFar; + }, []); + + } catch (err) { + return reply(wrapEsError(err)); + } + + 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); + } + }); +} From 71a4d83691da97c54292284a39593eed055060f5 Mon Sep 17 00:00:00 2001 From: Shaunak Kashyap Date: Wed, 16 May 2018 05:21:38 -0700 Subject: [PATCH 12/24] Refactoring: extracting out helper functions --- .../api/register_verify_beats_routes.js | 65 +++++++++++-------- 1 file changed, 38 insertions(+), 27 deletions(-) diff --git a/x-pack/plugins/beats/server/routes/api/register_verify_beats_routes.js b/x-pack/plugins/beats/server/routes/api/register_verify_beats_routes.js index c57c35859aba9..6aaa61b07c5f8 100644 --- a/x-pack/plugins/beats/server/routes/api/register_verify_beats_routes.js +++ b/x-pack/plugins/beats/server/routes/api/register_verify_beats_routes.js @@ -49,6 +49,38 @@ async function verifyBeats(callWithRequest, beatIds) { return get(response, 'items', []); } +function determineNonExistentBeatIds(beatsFromEs, beatIdsFromRequest) { + return beatsFromEs.reduce((nonExistentBeatIds, beatFromEs, idx) => { + if (!beatFromEs.found) { + nonExistentBeatIds.push(beatIdsFromRequest[idx]); + } + return nonExistentBeatIds; + }, []); +} + +function determineAlreadyVerifiedBeatIds(beatsFromEs) { + return beatsFromEs + .filter(beat => beat.found) + .filter(beat => beat._source.beat.hasOwnProperty('verified_on')) + .map(beat => beat._source.beat.id); +} + +function determineToBeVerifiedBeatIds(beatsFromEs) { + return beatsFromEs + .filter(beat => beat.found) + .filter(beat => !beat._source.beat.hasOwnProperty('verified_on')) + .map(beat => beat._source.beat.id); +} + +function determineVerifiedBeatIds(verifications, toBeVerifiedBeatIds) { + return verifications.reduce((verifiedBeatIds, verification, idx) => { + if (verification.update.status === 200) { + verifiedBeatIds.push(toBeVerifiedBeatIds[idx]); + } + return verifiedBeatIds; + }, []); +} + // TODO: add license check pre-hook // TODO: write to Kibana audit log file export function registerVerifyBeatsRoute(server) { @@ -77,33 +109,12 @@ export function registerVerifyBeatsRoute(server) { try { const beatsFromEs = await getBeats(callWithRequest, beatIds); - // TODO: extract into helper function - nonExistentBeatIds = beatsFromEs.reduce((beatIdsSoFar, beatFromEs, idx) => { - if (!beatFromEs.found) { - beatIdsSoFar.push(beatIds[idx]); - } - return beatIdsSoFar; - }, []); - - alreadyVerifiedBeatIds = beatsFromEs - .filter(beat => beat.found) - .filter(beat => beat._source.beat.hasOwnProperty('verified_on')) - .map(beat => beat._source.beat.id); - - const beatIdsToVerify = beatsFromEs - .filter(beat => beat.found) - .filter(beat => !beat._source.beat.hasOwnProperty('verified_on')) - .map(beat => beat._source.beat.id); - - const verifications = await verifyBeats(callWithRequest, beatIdsToVerify); - - // TODO: extract into helper function - verifiedBeatIds = verifications.reduce((beatIdsSoFar, verification, idx) => { - if (verification.update.status === 200) { - beatIdsSoFar.push(beatIdsToVerify[idx]); - } - return beatIdsSoFar; - }, []); + nonExistentBeatIds = determineNonExistentBeatIds(beatsFromEs, beatIds); + alreadyVerifiedBeatIds = determineAlreadyVerifiedBeatIds(beatsFromEs); + const toBeVerifiedBeatIds = determineToBeVerifiedBeatIds(beatsFromEs); + + const verifications = await verifyBeats(callWithRequest, toBeVerifiedBeatIds); + verifiedBeatIds = determineVerifiedBeatIds(verifications, toBeVerifiedBeatIds); } catch (err) { return reply(wrapEsError(err)); From 12d908c10f04965279dae7d5d5c1a149f7a69c27 Mon Sep 17 00:00:00 2001 From: Shaunak Kashyap Date: Wed, 16 May 2018 05:59:12 -0700 Subject: [PATCH 13/24] Expanding TODO note so I won't forget :) --- .../beats/server/routes/api/register_enroll_beat_route.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/x-pack/plugins/beats/server/routes/api/register_enroll_beat_route.js b/x-pack/plugins/beats/server/routes/api/register_enroll_beat_route.js index fb004fbb79e12..6374bf53ba8e6 100644 --- a/x-pack/plugins/beats/server/routes/api/register_enroll_beat_route.js +++ b/x-pack/plugins/beats/server/routes/api/register_enroll_beat_route.js @@ -59,7 +59,7 @@ function persistBeat(callWithInternalUser, beat, beatId, accessToken, remoteAddr } // TODO: add license check pre-hook -// TODO: write to Kibana audit log file +// TODO: write to Kibana audit log file (include who did the verification as well) export function registerEnrollBeatRoute(server) { server.route({ method: 'POST', From ab2a0ec446c8a032f9dcd81370b8f98992c070bf Mon Sep 17 00:00:00 2001 From: Shaunak Kashyap Date: Wed, 16 May 2018 06:38:31 -0700 Subject: [PATCH 14/24] Fixing file name --- .../routes/api/register_verify_beats_route.js | 18 +-- .../api/register_verify_beats_routes.js | 143 ------------------ 2 files changed, 9 insertions(+), 152 deletions(-) delete mode 100644 x-pack/plugins/beats/server/routes/api/register_verify_beats_routes.js diff --git a/x-pack/plugins/beats/server/routes/api/register_verify_beats_route.js b/x-pack/plugins/beats/server/routes/api/register_verify_beats_route.js index 11a4aff1204dc..6aaa61b07c5f8 100644 --- a/x-pack/plugins/beats/server/routes/api/register_verify_beats_route.js +++ b/x-pack/plugins/beats/server/routes/api/register_verify_beats_route.js @@ -49,7 +49,7 @@ async function verifyBeats(callWithRequest, beatIds) { return get(response, 'items', []); } -function findNonExistentBeatIds(beatsFromEs, beatIdsFromRequest) { +function determineNonExistentBeatIds(beatsFromEs, beatIdsFromRequest) { return beatsFromEs.reduce((nonExistentBeatIds, beatFromEs, idx) => { if (!beatFromEs.found) { nonExistentBeatIds.push(beatIdsFromRequest[idx]); @@ -58,21 +58,21 @@ function findNonExistentBeatIds(beatsFromEs, beatIdsFromRequest) { }, []); } -function findAlreadyVerifiedBeatIds(beatsFromEs) { +function determineAlreadyVerifiedBeatIds(beatsFromEs) { return beatsFromEs .filter(beat => beat.found) .filter(beat => beat._source.beat.hasOwnProperty('verified_on')) .map(beat => beat._source.beat.id); } -function findToBeVerifiedBeatIds(beatsFromEs) { +function determineToBeVerifiedBeatIds(beatsFromEs) { return beatsFromEs .filter(beat => beat.found) .filter(beat => !beat._source.beat.hasOwnProperty('verified_on')) .map(beat => beat._source.beat.id); } -function findVerifiedBeatIds(verifications, toBeVerifiedBeatIds) { +function determineVerifiedBeatIds(verifications, toBeVerifiedBeatIds) { return verifications.reduce((verifiedBeatIds, verification, idx) => { if (verification.update.status === 200) { verifiedBeatIds.push(toBeVerifiedBeatIds[idx]); @@ -82,7 +82,7 @@ function findVerifiedBeatIds(verifications, toBeVerifiedBeatIds) { } // TODO: add license check pre-hook -// TODO: write to Kibana audit log file (include who did the verification as well) +// TODO: write to Kibana audit log file export function registerVerifyBeatsRoute(server) { server.route({ method: 'POST', @@ -109,12 +109,12 @@ export function registerVerifyBeatsRoute(server) { try { const beatsFromEs = await getBeats(callWithRequest, beatIds); - nonExistentBeatIds = findNonExistentBeatIds(beatsFromEs, beatIds); - alreadyVerifiedBeatIds = findAlreadyVerifiedBeatIds(beatsFromEs); - const toBeVerifiedBeatIds = findToBeVerifiedBeatIds(beatsFromEs); + nonExistentBeatIds = determineNonExistentBeatIds(beatsFromEs, beatIds); + alreadyVerifiedBeatIds = determineAlreadyVerifiedBeatIds(beatsFromEs); + const toBeVerifiedBeatIds = determineToBeVerifiedBeatIds(beatsFromEs); const verifications = await verifyBeats(callWithRequest, toBeVerifiedBeatIds); - verifiedBeatIds = findVerifiedBeatIds(verifications, toBeVerifiedBeatIds); + verifiedBeatIds = determineVerifiedBeatIds(verifications, toBeVerifiedBeatIds); } catch (err) { return reply(wrapEsError(err)); diff --git a/x-pack/plugins/beats/server/routes/api/register_verify_beats_routes.js b/x-pack/plugins/beats/server/routes/api/register_verify_beats_routes.js deleted file mode 100644 index 6aaa61b07c5f8..0000000000000 --- a/x-pack/plugins/beats/server/routes/api/register_verify_beats_routes.js +++ /dev/null @@ -1,143 +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 moment from 'moment'; -import { - get, - flatten -} from 'lodash'; -import { INDEX_NAMES } from '../../../common/constants'; -import { callWithRequestFactory } from '../../lib/client'; -import { wrapEsError } from '../../lib/error_wrappers'; - -async function getBeats(callWithRequest, beatIds) { - const ids = beatIds.map(beatId => `beat:${beatId}`); - const params = { - index: INDEX_NAMES.BEATS, - type: '_doc', - body: { ids }, - _sourceInclude: [ 'beat.id', 'beat.verified_on' ] - }; - - const response = await callWithRequest('mget', params); - return get(response, 'docs', []); -} - -async function verifyBeats(callWithRequest, beatIds) { - if (!Array.isArray(beatIds) || (beatIds.length === 0)) { - return []; - } - - const verifiedOn = moment().toJSON(); - const body = flatten(beatIds.map(beatId => [ - { update: { _id: `beat:${beatId}` } }, - { doc: { beat: { verified_on: verifiedOn } } } - ])); - - const params = { - index: INDEX_NAMES.BEATS, - type: '_doc', - body, - refresh: 'wait_for' - }; - - const response = await callWithRequest('bulk', params); - return get(response, 'items', []); -} - -function determineNonExistentBeatIds(beatsFromEs, beatIdsFromRequest) { - return beatsFromEs.reduce((nonExistentBeatIds, beatFromEs, idx) => { - if (!beatFromEs.found) { - nonExistentBeatIds.push(beatIdsFromRequest[idx]); - } - return nonExistentBeatIds; - }, []); -} - -function determineAlreadyVerifiedBeatIds(beatsFromEs) { - return beatsFromEs - .filter(beat => beat.found) - .filter(beat => beat._source.beat.hasOwnProperty('verified_on')) - .map(beat => beat._source.beat.id); -} - -function determineToBeVerifiedBeatIds(beatsFromEs) { - return beatsFromEs - .filter(beat => beat.found) - .filter(beat => !beat._source.beat.hasOwnProperty('verified_on')) - .map(beat => beat._source.beat.id); -} - -function determineVerifiedBeatIds(verifications, toBeVerifiedBeatIds) { - return verifications.reduce((verifiedBeatIds, verification, idx) => { - if (verification.update.status === 200) { - verifiedBeatIds.push(toBeVerifiedBeatIds[idx]); - } - return verifiedBeatIds; - }, []); -} - -// TODO: add license check pre-hook -// TODO: write to Kibana audit log file -export function registerVerifyBeatsRoute(server) { - server.route({ - method: 'POST', - path: '/api/beats/agents/verify', - config: { - validate: { - payload: Joi.object({ - beats: Joi.array({ - id: Joi.string().required() - }).min(1) - }).required() - } - }, - handler: async (request, reply) => { - const callWithRequest = callWithRequestFactory(server, request); - - const beats = [...request.payload.beats]; - const beatIds = beats.map(beat => beat.id); - - let nonExistentBeatIds; - let alreadyVerifiedBeatIds; - let verifiedBeatIds; - - try { - const beatsFromEs = await getBeats(callWithRequest, beatIds); - - nonExistentBeatIds = determineNonExistentBeatIds(beatsFromEs, beatIds); - alreadyVerifiedBeatIds = determineAlreadyVerifiedBeatIds(beatsFromEs); - const toBeVerifiedBeatIds = determineToBeVerifiedBeatIds(beatsFromEs); - - const verifications = await verifyBeats(callWithRequest, toBeVerifiedBeatIds); - verifiedBeatIds = determineVerifiedBeatIds(verifications, toBeVerifiedBeatIds); - - } catch (err) { - return reply(wrapEsError(err)); - } - - 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); - } - }); -} From d14642c534f5752a66ac360761f3df5fbc77fef1 Mon Sep 17 00:00:00 2001 From: Shaunak Kashyap Date: Wed, 16 May 2018 13:00:28 -0700 Subject: [PATCH 15/24] Add API tests --- .../test/api_integration/apis/beats/index.js | 1 + .../api_integration/apis/beats/update_beat.js | 96 +++++++++++++++++++ .../es_archives/beats/list/mappings.json | 3 + 3 files changed, 100 insertions(+) create mode 100644 x-pack/test/api_integration/apis/beats/update_beat.js diff --git a/x-pack/test/api_integration/apis/beats/index.js b/x-pack/test/api_integration/apis/beats/index.js index abb97b3daed91..b41f17ed749b3 100644 --- a/x-pack/test/api_integration/apis/beats/index.js +++ b/x-pack/test/api_integration/apis/beats/index.js @@ -21,5 +21,6 @@ export default function ({ getService, loadTestFile }) { loadTestFile(require.resolve('./enroll_beat')); loadTestFile(require.resolve('./list_beats')); loadTestFile(require.resolve('./verify_beats')); + loadTestFile(require.resolve('./update_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 new file mode 100644 index 0000000000000..6d8891db8c587 --- /dev/null +++ b/x-pack/test/api_integration/apis/beats/update_beat.js @@ -0,0 +1,96 @@ +/* + * 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 { + ES_INDEX_NAME, + ES_TYPE_NAME +} from './constants'; + +export default function ({ getService }) { + const supertest = getService('supertest'); + const chance = getService('chance'); + const es = getService('es'); + const esArchiver = getService('esArchiver'); + + describe('update_beat', () => { + let beat; + const archive = 'beats/list'; + + beforeEach('load beats archive', () => esArchiver.load(archive)); + beforeEach(() => { + beat = { + access_token: '93c4a4dd08564c189a7ec4e4f046b975', + type: 'filebeat', + host_name: 'foo.bar.com' + }; + }); + + afterEach('unload beats archive', () => esArchiver.unload(archive)); + + it('should update an existing verified beat', async () => { + const beatId = 'foo'; + await supertest + .put( + `/api/beats/agent/${beatId}` + ) + .set('kbn-xsrf', 'xxx') + .send(beat) + .expect(204); + + 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.be(beat.type); + expect(beatInEs._source.beat.host_name).to.be(beat.host_name); + expect(beatInEs._source.beat.version).to.be(beat.version); + expect(beatInEs._source.beat.ephemeral_id).to.be(beat.ephemeral_id); + }); + + it('should return an error for an invalid access token', async () => { + beat.access_token = chance.word(); + const { body } = await supertest + .put( + `/api/beats/agent/foo` + ) + .set('kbn-xsrf', 'xxx') + .send(beat) + .expect(401); + + expect(body.message).to.be('Invalid access token'); + }); + + it('should return an error for an existing but unverified beat', async () => { + beat.access_token = '3c4a4dd08564c189a7ec4e4f046b9759'; + const { body } = await supertest + .put( + '/api/beats/agent/bar' + ) + .set('kbn-xsrf', 'xxx') + .send(beat) + .expect(400); + + expect(body.message).to.be('Beat has not been verified'); + }); + + it('should return an error for a non-existent beat', async () => { + const beatId = chance.word(); + const { body } = await supertest + .put( + `/api/beats/agent/${beatId}` + ) + .set('kbn-xsrf', 'xxx') + .send(beat) + .expect(404); + + expect(body.message).to.be('Beat not found'); + }); + }); +} 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 92d89fb159733..24aceed8a5b61 100644 --- a/x-pack/test/functional/es_archives/beats/list/mappings.json +++ b/x-pack/test/functional/es_archives/beats/list/mappings.json @@ -54,6 +54,9 @@ "type": { "type": "keyword" }, + "version": { + "type": "keyword" + }, "host_ip": { "type": "ip" }, From 35d44d1bebedf0807fd1f49dcd87924a813fd2b0 Mon Sep 17 00:00:00 2001 From: Shaunak Kashyap Date: Wed, 16 May 2018 13:00:51 -0700 Subject: [PATCH 16/24] Update template to allow version field for beat --- .../beats/server/lib/index_template/beats_template.json | 3 +++ 1 file changed, 3 insertions(+) diff --git a/x-pack/plugins/beats/server/lib/index_template/beats_template.json b/x-pack/plugins/beats/server/lib/index_template/beats_template.json index 9b37b7e816bf8..e293845f9a3a8 100644 --- a/x-pack/plugins/beats/server/lib/index_template/beats_template.json +++ b/x-pack/plugins/beats/server/lib/index_template/beats_template.json @@ -54,6 +54,9 @@ "type": { "type": "keyword" }, + "version": { + "type": "keyword" + }, "host_ip": { "type": "ip" }, From e09f587c746703bb0a63c2b981e43627c11d6912 Mon Sep 17 00:00:00 2001 From: Shaunak Kashyap Date: Wed, 16 May 2018 13:01:43 -0700 Subject: [PATCH 17/24] Implement PUT /api/beats/agent/{beat ID} API --- .../plugins/beats/server/routes/api/index.js | 2 + .../routes/api/register_update_beat_route.js | 97 +++++++++++++++++++ 2 files changed, 99 insertions(+) create mode 100644 x-pack/plugins/beats/server/routes/api/register_update_beat_route.js diff --git a/x-pack/plugins/beats/server/routes/api/index.js b/x-pack/plugins/beats/server/routes/api/index.js index def322f0e94eb..4e6ee318668bf 100644 --- a/x-pack/plugins/beats/server/routes/api/index.js +++ b/x-pack/plugins/beats/server/routes/api/index.js @@ -8,10 +8,12 @@ import { registerCreateEnrollmentTokensRoute } from './register_create_enrollmen import { registerEnrollBeatRoute } from './register_enroll_beat_route'; import { registerListBeatsRoute } from './register_list_beats_route'; import { registerVerifyBeatsRoute } from './register_verify_beats_route'; +import { registerUpdateBeatRoute } from './register_update_beat_route'; export function registerApiRoutes(server) { registerCreateEnrollmentTokensRoute(server); registerEnrollBeatRoute(server); registerListBeatsRoute(server); registerVerifyBeatsRoute(server); + registerUpdateBeatRoute(server); } diff --git a/x-pack/plugins/beats/server/routes/api/register_update_beat_route.js b/x-pack/plugins/beats/server/routes/api/register_update_beat_route.js new file mode 100644 index 0000000000000..51728719880c9 --- /dev/null +++ b/x-pack/plugins/beats/server/routes/api/register_update_beat_route.js @@ -0,0 +1,97 @@ +/* + * 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 { get } from 'lodash'; +import { INDEX_NAMES } from '../../../common/constants'; +import { callWithInternalUserFactory } from '../../lib/client'; +import { wrapEsError } from '../../lib/error_wrappers'; + +async function getBeat(callWithInternalUser, beatId) { + const params = { + index: INDEX_NAMES.BEATS, + type: '_doc', + id: `beat:${beatId}`, + ignore: [ 404 ] + }; + + const response = await callWithInternalUser('get', params); + if (!response.found) { + return null; + } + + return get(response, '_source.beat'); +} + +function persistBeat(callWithInternalUser, beat) { + const body = { + type: 'beat', + beat + }; + + const params = { + index: INDEX_NAMES.BEATS, + type: '_doc', + id: `beat:${beat.id}`, + body, + refresh: 'wait_for' + }; + return callWithInternalUser('index', params); +} + +// TODO: add license check pre-hook +// TODO: write to Kibana audit log file (include who did the verification as well) +export function registerUpdateBeatRoute(server) { + server.route({ + method: 'PUT', + path: '/api/beats/agent/{beatId}', + config: { + validate: { + payload: Joi.object({ + access_token: Joi.string().required(), + type: Joi.string(), + host_name: Joi.string(), + ephemeral_id: Joi.string(), + local_configuration_yml: Joi.string(), + metadata: Joi.object() + }).required() + }, + auth: false + }, + handler: async (request, reply) => { + const callWithInternalUser = callWithInternalUserFactory(server); + const beatId = request.params.beatId; + + try { + const beat = await getBeat(callWithInternalUser, beatId); + if (beat === null) { + return reply({ message: 'Beat not found' }).code(404); + } + + const isAccessTokenValid = beat.access_token === request.payload.access_token; + if (!isAccessTokenValid) { + return reply({ message: 'Invalid access token' }).code(401); + } + + const isBeatVerified = beat.hasOwnProperty('verified_on'); + if (!isBeatVerified) { + return reply({ message: 'Beat has not been verified' }).code(400); + } + + const remoteAddress = request.info.remoteAddress; + await persistBeat(callWithInternalUser, { + ...beat, + ...request.payload, + host_ip: remoteAddress + }); + } catch (err) { + return reply(wrapEsError(err)); + } + + reply().code(204); + } + }); +} From 63747a395820be49553301dc16106b576e983a14 Mon Sep 17 00:00:00 2001 From: Shaunak Kashyap Date: Wed, 16 May 2018 13:02:32 -0700 Subject: [PATCH 18/24] Make enroll beat code consistent with update beat code --- .../routes/api/register_enroll_beat_route.js | 19 ++++++++++--------- 1 file changed, 10 insertions(+), 9 deletions(-) diff --git a/x-pack/plugins/beats/server/routes/api/register_enroll_beat_route.js b/x-pack/plugins/beats/server/routes/api/register_enroll_beat_route.js index 6374bf53ba8e6..dcc495785ec22 100644 --- a/x-pack/plugins/beats/server/routes/api/register_enroll_beat_route.js +++ b/x-pack/plugins/beats/server/routes/api/register_enroll_beat_route.js @@ -37,21 +37,16 @@ function deleteUsedEnrollmentToken(callWithInternalUser, enrollmentToken) { return callWithInternalUser('delete', params); } -function persistBeat(callWithInternalUser, beat, beatId, accessToken, remoteAddress) { +function persistBeat(callWithInternalUser, beat) { const body = { type: 'beat', - beat: { - ...omit(beat, 'enrollment_token'), - id: beatId, - access_token: accessToken, - host_ip: remoteAddress - } + beat }; const params = { index: INDEX_NAMES.BEATS, type: '_doc', - id: `beat:${beatId}`, + id: `beat:${beat.id}`, body, refresh: 'wait_for' }; @@ -76,6 +71,7 @@ export function registerEnrollBeatRoute(server) { }, handler: async (request, reply) => { const callWithInternalUser = callWithInternalUserFactory(server); + const beatId = request.params.beatId; let accessToken; try { @@ -90,7 +86,12 @@ export function registerEnrollBeatRoute(server) { accessToken = uuid.v4().replace(/-/g, ""); const remoteAddress = request.info.remoteAddress; - await persistBeat(callWithInternalUser, request.payload, request.params.beatId, accessToken, remoteAddress); + await persistBeat(callWithInternalUser, { + ...omit(request.payload, 'enrollment_token'), + id: beatId, + access_token: accessToken, + host_ip: remoteAddress + }); await deleteUsedEnrollmentToken(callWithInternalUser, enrollmentToken); } catch (err) { From 6940d3de5ae06c2b309ed00767bd59fd63cc04fa Mon Sep 17 00:00:00 2001 From: Shaunak Kashyap Date: Wed, 16 May 2018 13:02:48 -0700 Subject: [PATCH 19/24] Fixing minor typo in TODO comment --- .../beats/server/routes/api/register_enroll_beat_route.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/x-pack/plugins/beats/server/routes/api/register_enroll_beat_route.js b/x-pack/plugins/beats/server/routes/api/register_enroll_beat_route.js index dcc495785ec22..d0ebb53be0c0f 100644 --- a/x-pack/plugins/beats/server/routes/api/register_enroll_beat_route.js +++ b/x-pack/plugins/beats/server/routes/api/register_enroll_beat_route.js @@ -54,7 +54,7 @@ function persistBeat(callWithInternalUser, beat) { } // TODO: add license check pre-hook -// TODO: write to Kibana audit log file (include who did the verification as well) +// TODO: write to Kibana audit log file export function registerEnrollBeatRoute(server) { server.route({ method: 'POST', From b565aec3593cb53053ea5b02d74b408d7b0162b4 Mon Sep 17 00:00:00 2001 From: Shaunak Kashyap Date: Wed, 16 May 2018 13:25:05 -0700 Subject: [PATCH 20/24] Allow version in request payload --- .../beats/server/routes/api/register_update_beat_route.js | 1 + 1 file changed, 1 insertion(+) diff --git a/x-pack/plugins/beats/server/routes/api/register_update_beat_route.js b/x-pack/plugins/beats/server/routes/api/register_update_beat_route.js index 51728719880c9..31cc495829b35 100644 --- a/x-pack/plugins/beats/server/routes/api/register_update_beat_route.js +++ b/x-pack/plugins/beats/server/routes/api/register_update_beat_route.js @@ -53,6 +53,7 @@ export function registerUpdateBeatRoute(server) { payload: Joi.object({ access_token: Joi.string().required(), type: Joi.string(), + version: Joi.string(), host_name: Joi.string(), ephemeral_id: Joi.string(), local_configuration_yml: Joi.string(), From dcbeec6f54f28d67c6f5e7964ff52168ab51e6a6 Mon Sep 17 00:00:00 2001 From: Shaunak Kashyap Date: Wed, 16 May 2018 13:25:25 -0700 Subject: [PATCH 21/24] Make sure beat is not updated in ES in error scenarios --- .../api_integration/apis/beats/update_beat.js | 42 +++++++++++++++++-- 1 file changed, 38 insertions(+), 4 deletions(-) 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 6d8891db8c587..09d286be0fcf8 100644 --- a/x-pack/test/api_integration/apis/beats/update_beat.js +++ b/x-pack/test/api_integration/apis/beats/update_beat.js @@ -22,10 +22,18 @@ export default function ({ getService }) { beforeEach('load beats archive', () => esArchiver.load(archive)); beforeEach(() => { + const version = chance.integer({ min: 1, max: 10 }) + + '.' + + chance.integer({ min: 1, max: 10 }) + + '.' + + chance.integer({ min: 1, max: 10 }); + beat = { access_token: '93c4a4dd08564c189a7ec4e4f046b975', - type: 'filebeat', - host_name: 'foo.bar.com' + type: `${chance.word()}beat`, + host_name: `www.${chance.word()}.net`, + version, + ephemeral_id: chance.word() }; }); @@ -55,29 +63,55 @@ export default function ({ getService }) { }); it('should return an error for an invalid access token', async () => { + const beatId = 'foo'; beat.access_token = chance.word(); const { body } = await supertest .put( - `/api/beats/agent/foo` + `/api/beats/agent/${beatId}` ) .set('kbn-xsrf', 'xxx') .send(beat) .expect(401); expect(body.message).to.be('Invalid access token'); + + 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 an existing but unverified beat', async () => { + const beatId = 'bar'; beat.access_token = '3c4a4dd08564c189a7ec4e4f046b9759'; const { body } = await supertest .put( - '/api/beats/agent/bar' + `/api/beats/agent/${beatId}` ) .set('kbn-xsrf', 'xxx') .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 () => { From 08a46b63e31f1a76cd875b6e6a8a36d16d74db68 Mon Sep 17 00:00:00 2001 From: Shaunak Kashyap Date: Fri, 18 May 2018 05:25:15 -0700 Subject: [PATCH 22/24] Adding version as required field in Enroll Beat API payload --- .../beats/server/routes/api/register_enroll_beat_route.js | 1 + x-pack/test/api_integration/apis/beats/enroll_beat.js | 7 +++++++ 2 files changed, 8 insertions(+) diff --git a/x-pack/plugins/beats/server/routes/api/register_enroll_beat_route.js b/x-pack/plugins/beats/server/routes/api/register_enroll_beat_route.js index d0ebb53be0c0f..720835cb19b80 100644 --- a/x-pack/plugins/beats/server/routes/api/register_enroll_beat_route.js +++ b/x-pack/plugins/beats/server/routes/api/register_enroll_beat_route.js @@ -64,6 +64,7 @@ export function registerEnrollBeatRoute(server) { payload: Joi.object({ enrollment_token: Joi.string().required(), type: Joi.string().required(), + version: Joi.string().required(), host_name: Joi.string().required() }).required() }, 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 ec3785f8eb35d..388987f6d6d22 100644 --- a/x-pack/test/api_integration/apis/beats/enroll_beat.js +++ b/x-pack/test/api_integration/apis/beats/enroll_beat.js @@ -24,10 +24,17 @@ export default function ({ getService }) { beforeEach(async () => { validEnrollmentToken = chance.word(); beatId = chance.word(); + const version = chance.integer({ min: 1, max: 10 }) + + '.' + + chance.integer({ min: 1, max: 10 }) + + '.' + + chance.integer({ min: 1, max: 10 }); + beat = { enrollment_token: validEnrollmentToken, type: 'filebeat', host_name: 'foo.bar.com', + version }; await es.index({ From 1b549842d88d651bd9673cc0294368b86cf80be9 Mon Sep 17 00:00:00 2001 From: Shaunak Kashyap Date: Fri, 18 May 2018 12:37:20 -0700 Subject: [PATCH 23/24] Using destructuring --- .../beats/server/routes/api/register_enroll_beat_route.js | 2 +- .../beats/server/routes/api/register_update_beat_route.js | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/x-pack/plugins/beats/server/routes/api/register_enroll_beat_route.js b/x-pack/plugins/beats/server/routes/api/register_enroll_beat_route.js index 720835cb19b80..2a86f33b0d28f 100644 --- a/x-pack/plugins/beats/server/routes/api/register_enroll_beat_route.js +++ b/x-pack/plugins/beats/server/routes/api/register_enroll_beat_route.js @@ -72,7 +72,7 @@ export function registerEnrollBeatRoute(server) { }, handler: async (request, reply) => { const callWithInternalUser = callWithInternalUserFactory(server); - const beatId = request.params.beatId; + const { beatId } = request.params; let accessToken; try { diff --git a/x-pack/plugins/beats/server/routes/api/register_update_beat_route.js b/x-pack/plugins/beats/server/routes/api/register_update_beat_route.js index 31cc495829b35..c93eca7590454 100644 --- a/x-pack/plugins/beats/server/routes/api/register_update_beat_route.js +++ b/x-pack/plugins/beats/server/routes/api/register_update_beat_route.js @@ -64,7 +64,7 @@ export function registerUpdateBeatRoute(server) { }, handler: async (request, reply) => { const callWithInternalUser = callWithInternalUserFactory(server); - const beatId = request.params.beatId; + const { beatId } = request.params; try { const beat = await getBeat(callWithInternalUser, beatId); From add6da3ab68eb8eb5077be308c3d8567531833cc Mon Sep 17 00:00:00 2001 From: Shaunak Kashyap Date: Fri, 18 May 2018 12:37:55 -0700 Subject: [PATCH 24/24] Fixing rename that was accidentally reversed in conflict fixing --- .../routes/api/register_verify_beats_route.js | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/x-pack/plugins/beats/server/routes/api/register_verify_beats_route.js b/x-pack/plugins/beats/server/routes/api/register_verify_beats_route.js index 6aaa61b07c5f8..b2113029224a5 100644 --- a/x-pack/plugins/beats/server/routes/api/register_verify_beats_route.js +++ b/x-pack/plugins/beats/server/routes/api/register_verify_beats_route.js @@ -49,7 +49,7 @@ async function verifyBeats(callWithRequest, beatIds) { return get(response, 'items', []); } -function determineNonExistentBeatIds(beatsFromEs, beatIdsFromRequest) { +function findNonExistentBeatIds(beatsFromEs, beatIdsFromRequest) { return beatsFromEs.reduce((nonExistentBeatIds, beatFromEs, idx) => { if (!beatFromEs.found) { nonExistentBeatIds.push(beatIdsFromRequest[idx]); @@ -58,21 +58,21 @@ function determineNonExistentBeatIds(beatsFromEs, beatIdsFromRequest) { }, []); } -function determineAlreadyVerifiedBeatIds(beatsFromEs) { +function findAlreadyVerifiedBeatIds(beatsFromEs) { return beatsFromEs .filter(beat => beat.found) .filter(beat => beat._source.beat.hasOwnProperty('verified_on')) .map(beat => beat._source.beat.id); } -function determineToBeVerifiedBeatIds(beatsFromEs) { +function findToBeVerifiedBeatIds(beatsFromEs) { return beatsFromEs .filter(beat => beat.found) .filter(beat => !beat._source.beat.hasOwnProperty('verified_on')) .map(beat => beat._source.beat.id); } -function determineVerifiedBeatIds(verifications, toBeVerifiedBeatIds) { +function findVerifiedBeatIds(verifications, toBeVerifiedBeatIds) { return verifications.reduce((verifiedBeatIds, verification, idx) => { if (verification.update.status === 200) { verifiedBeatIds.push(toBeVerifiedBeatIds[idx]); @@ -109,12 +109,12 @@ export function registerVerifyBeatsRoute(server) { try { const beatsFromEs = await getBeats(callWithRequest, beatIds); - nonExistentBeatIds = determineNonExistentBeatIds(beatsFromEs, beatIds); - alreadyVerifiedBeatIds = determineAlreadyVerifiedBeatIds(beatsFromEs); - const toBeVerifiedBeatIds = determineToBeVerifiedBeatIds(beatsFromEs); + nonExistentBeatIds = findNonExistentBeatIds(beatsFromEs, beatIds); + alreadyVerifiedBeatIds = findAlreadyVerifiedBeatIds(beatsFromEs); + const toBeVerifiedBeatIds = findToBeVerifiedBeatIds(beatsFromEs); const verifications = await verifyBeats(callWithRequest, toBeVerifiedBeatIds); - verifiedBeatIds = determineVerifiedBeatIds(verifications, toBeVerifiedBeatIds); + verifiedBeatIds = findVerifiedBeatIds(verifications, toBeVerifiedBeatIds); } catch (err) { return reply(wrapEsError(err));