diff --git a/x-pack/legacy/plugins/logstash/index.js b/x-pack/legacy/plugins/logstash/index.js index fcabcd78705b1..29f01032f3413 100755 --- a/x-pack/legacy/plugins/logstash/index.js +++ b/x-pack/legacy/plugins/logstash/index.js @@ -5,10 +5,6 @@ */ import { resolve } from 'path'; -import { registerLogstashPipelinesRoutes } from './server/routes/api/pipelines'; -import { registerLogstashPipelineRoutes } from './server/routes/api/pipeline'; -import { registerLogstashUpgradeRoutes } from './server/routes/api/upgrade'; -import { registerLogstashClusterRoutes } from './server/routes/api/cluster'; import { registerLicenseChecker } from './server/lib/register_license_checker'; import { PLUGIN } from '../../../plugins/logstash/common/constants'; @@ -32,9 +28,5 @@ export const logstash = kibana => }, init: server => { registerLicenseChecker(server); - registerLogstashPipelinesRoutes(server); - registerLogstashPipelineRoutes(server); - registerLogstashUpgradeRoutes(server); - registerLogstashClusterRoutes(server); }, }); diff --git a/x-pack/plugins/logstash/kibana.json b/x-pack/plugins/logstash/kibana.json index 54fc57625f53b..bcc926535d3c2 100644 --- a/x-pack/plugins/logstash/kibana.json +++ b/x-pack/plugins/logstash/kibana.json @@ -6,6 +6,7 @@ "requiredPlugins": [ "licensing" ], + "optionalPlugins": ["security"], "server": true, "ui": false } diff --git a/x-pack/plugins/logstash/server/lib/check_license/check_license.test.ts b/x-pack/plugins/logstash/server/lib/check_license/check_license.test.ts new file mode 100755 index 0000000000000..6e88b485c471d --- /dev/null +++ b/x-pack/plugins/logstash/server/lib/check_license/check_license.test.ts @@ -0,0 +1,59 @@ +/* + * 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 { licensingMock } from '../../../../licensing/server/mocks'; +import { checkLicense } from './check_license'; + +describe('check_license', function() { + describe('returns "valid": false & message', () => { + it('license information is not available', () => { + const license = licensingMock.createLicenseMock(); + license.isAvailable = false; + + const { valid, message } = checkLicense(license); + + expect(valid).toBe(false); + expect(message).toStrictEqual(expect.any(String)); + }); + + it('license level is not enough', () => { + const license = licensingMock.createLicenseMock(); + license.hasAtLeast.mockReturnValue(false); + + const { valid, message } = checkLicense(license); + + expect(valid).toBe(false); + expect(message).toStrictEqual(expect.any(String)); + }); + + it('license is expired', () => { + const license = licensingMock.createLicenseMock(); + license.isActive = false; + + const { valid, message } = checkLicense(license); + + expect(valid).toBe(false); + expect(message).toStrictEqual(expect.any(String)); + }); + + it('elasticsearch security is disabled', () => { + const license = licensingMock.createLicenseMock(); + license.getFeature.mockReturnValue({ isEnabled: false, isAvailable: false }); + + const { valid, message } = checkLicense(license); + + expect(valid).toBe(false); + expect(message).toStrictEqual(expect.any(String)); + }); + }); + it('returns "valid": true without message otherwise', () => { + const license = licensingMock.createLicenseMock(); + + const { valid, message } = checkLicense(license); + + expect(valid).toBe(true); + expect(message).toBe(null); + }); +}); diff --git a/x-pack/plugins/logstash/server/lib/check_license/check_license.ts b/x-pack/plugins/logstash/server/lib/check_license/check_license.ts new file mode 100644 index 0000000000000..4eef2eb9b0681 --- /dev/null +++ b/x-pack/plugins/logstash/server/lib/check_license/check_license.ts @@ -0,0 +1,65 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import { i18n } from '@kbn/i18n'; + +import { CheckLicense } from '../../../../licensing/server'; + +export const checkLicense: CheckLicense = license => { + if (!license.isAvailable) { + return { + valid: false, + message: i18n.translate( + 'xpack.logstash.managementSection.notPossibleToManagePipelinesMessage', + { + defaultMessage: + 'You cannot manage Logstash pipelines because license information is not available at this time.', + } + ), + }; + } + + if (!license.hasAtLeast('standard')) { + return { + valid: false, + message: i18n.translate('xpack.logstash.managementSection.licenseDoesNotSupportDescription', { + defaultMessage: + 'Your {licenseType} license does not support Logstash pipeline management features. Please upgrade your license.', + values: { licenseType: license.type }, + }), + }; + } + + if (!license.isActive) { + return { + valid: false, + message: i18n.translate( + 'xpack.logstash.managementSection.pipelineCrudOperationsNotAllowedDescription', + { + defaultMessage: + 'You cannot edit, create, or delete your Logstash pipelines because your {licenseType} license has expired.', + values: { licenseType: license.type }, + } + ), + }; + } + + if (!license.getFeature('security').isEnabled) { + return { + valid: false, + message: i18n.translate('xpack.logstash.managementSection.enableSecurityDescription', { + defaultMessage: + 'Security must be enabled in order to use Logstash pipeline management features.' + + ' Please set xpack.security.enabled: true in your elasticsearch.yml.', + }), + }; + } + + return { + valid: true, + message: null, + }; +}; diff --git a/x-pack/plugins/logstash/server/lib/check_license/index.ts b/x-pack/plugins/logstash/server/lib/check_license/index.ts new file mode 100644 index 0000000000000..f2c070fd44b6e --- /dev/null +++ b/x-pack/plugins/logstash/server/lib/check_license/index.ts @@ -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/logstash/server/lib/fetch_all_from_scroll/fetch_all_from_scroll.ts b/x-pack/plugins/logstash/server/lib/fetch_all_from_scroll/fetch_all_from_scroll.ts index 57411d7900719..060cf188a4c60 100755 --- a/x-pack/plugins/logstash/server/lib/fetch_all_from_scroll/fetch_all_from_scroll.ts +++ b/x-pack/plugins/logstash/server/lib/fetch_all_from_scroll/fetch_all_from_scroll.ts @@ -7,14 +7,14 @@ import { APICaller } from 'src/core/server'; import { SearchResponse } from 'elasticsearch'; import { ES_SCROLL_SETTINGS } from '../../../common/constants'; -type Hits = SearchResponse['hits']['hits']; +import { Hits } from '../../types'; export async function fetchAllFromScroll( response: SearchResponse, callWithRequest: APICaller, hits: Hits = [] ): Promise { - const newHits = response.hits.hits; + const newHits = response.hits?.hits || []; const scrollId = response._scroll_id; if (newHits.length > 0) { diff --git a/x-pack/plugins/logstash/server/models/pipeline/pipeline.ts b/x-pack/plugins/logstash/server/models/pipeline/pipeline.ts index f28af33597e64..3f2debeebeb46 100755 --- a/x-pack/plugins/logstash/server/models/pipeline/pipeline.ts +++ b/x-pack/plugins/logstash/server/models/pipeline/pipeline.ts @@ -12,15 +12,15 @@ import { i18n } from '@kbn/i18n'; interface PipelineOptions { id: string; description: string; - username: string; pipeline: string; + username?: string; settings?: Record; } interface DownstreamPipeline { description: string; pipeline: string; - settings: Record; + settings?: Record; } /** * This model deals with a pipeline object from ES and converts it to Kibana downstream @@ -28,9 +28,10 @@ interface DownstreamPipeline { export class Pipeline { public readonly id: string; public readonly description: string; - public readonly username: string; + public readonly username?: string; public readonly pipeline: string; private readonly settings: Record; + constructor(options: PipelineOptions) { this.id = options.id; this.description = options.description; @@ -77,7 +78,7 @@ export class Pipeline { static fromDownstreamJSON( downstreamPipeline: DownstreamPipeline, pipelineId: string, - username: string + username?: string ) { const opts = { id: pipelineId, diff --git a/x-pack/plugins/logstash/server/models/pipeline_list_item/pipeline_list_item.test.ts b/x-pack/plugins/logstash/server/models/pipeline_list_item/pipeline_list_item.test.ts index f909e90ce43ca..c557e84443b02 100755 --- a/x-pack/plugins/logstash/server/models/pipeline_list_item/pipeline_list_item.test.ts +++ b/x-pack/plugins/logstash/server/models/pipeline_list_item/pipeline_list_item.test.ts @@ -20,6 +20,9 @@ describe('pipeline_list_item', () => { username: 'elastic', pipeline: 'input {} filter { grok {} }\n output {}', }, + _index: 'index', + _type: 'type', + _score: 100, }; describe('fromUpstreamJSON factory method', () => { diff --git a/x-pack/plugins/logstash/server/models/pipeline_list_item/pipeline_list_item.ts b/x-pack/plugins/logstash/server/models/pipeline_list_item/pipeline_list_item.ts index 68f91934adf43..98c91fca1fcca 100755 --- a/x-pack/plugins/logstash/server/models/pipeline_list_item/pipeline_list_item.ts +++ b/x-pack/plugins/logstash/server/models/pipeline_list_item/pipeline_list_item.ts @@ -5,18 +5,13 @@ */ import { get } from 'lodash'; +import { Hit, PipelineListItemOptions } from '../../types'; -interface PipelineListItemOptions { - id: string; - description: string; - last_modified: string; - username: string; -} export class PipelineListItem { - private readonly id: string; - private readonly description: string; - private readonly last_modified: string; - private readonly username: string; + public readonly id: string; + public readonly description: string; + public readonly last_modified: string; + public readonly username: string; constructor(options: PipelineListItemOptions) { this.id = options.id; this.description = options.description; @@ -39,7 +34,7 @@ export class PipelineListItem { * Takes the json GET response from ES and constructs a pipeline model to be used * in Kibana downstream */ - static fromUpstreamJSON(pipeline: Record) { + static fromUpstreamJSON(pipeline: Hit) { const opts = { id: pipeline._id, description: get(pipeline, '_source.description'), diff --git a/x-pack/plugins/logstash/server/plugin.ts b/x-pack/plugins/logstash/server/plugin.ts index 903d885c4ecd9..be039427505e9 100644 --- a/x-pack/plugins/logstash/server/plugin.ts +++ b/x-pack/plugins/logstash/server/plugin.ts @@ -3,26 +3,41 @@ * or more contributor license agreements. Licensed under the Elastic License; * you may not use this file except in compliance with the Elastic License. */ -import { CoreSetup, Logger, Plugin, PluginInitializerContext } from 'src/core/server'; +import { + CoreSetup, + ICustomClusterClient, + Logger, + Plugin, + PluginInitializerContext, +} from 'src/core/server'; import { LicensingPluginSetup } from '../../licensing/server'; +import { SecurityPluginSetup } from '../../security/server'; import { registerRoutes } from './routes'; interface SetupDeps { licensing: LicensingPluginSetup; + security?: SecurityPluginSetup; } export class LogstashPlugin implements Plugin { private readonly logger: Logger; + private esClient?: ICustomClusterClient; constructor(context: PluginInitializerContext) { this.logger = context.logger.get(); } setup(core: CoreSetup, deps: SetupDeps) { this.logger.debug('Setting up Logstash plugin'); - registerRoutes(core.http.createRouter()); + const esClient = core.elasticsearch.createClient('logstash'); + + registerRoutes(core.http.createRouter(), esClient, deps.security); } start() {} - stop() {} + stop() { + if (this.esClient) { + this.esClient.close(); + } + } } diff --git a/x-pack/plugins/logstash/server/types.ts b/x-pack/plugins/logstash/server/types.ts new file mode 100644 index 0000000000000..63647a07826d3 --- /dev/null +++ b/x-pack/plugins/logstash/server/types.ts @@ -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 { SearchResponse } from 'elasticsearch'; + +type UnwrapArray = T extends Array ? U : T; + +export type Hits = SearchResponse['hits']['hits']; +export type Hit = UnwrapArray; + +export interface PipelineListItemOptions { + id: string; + description: string; + last_modified: string; + username: string; +}