From 2ea4bdfe0dc61e303fa555a12788a3868e7510db Mon Sep 17 00:00:00 2001 From: Nathan L Smith Date: Wed, 26 Feb 2020 07:14:08 -0600 Subject: [PATCH] Server-side license check for APM service map (#58547) * Factor our the license checking logic and messaging to common * Add licensing plugin as a dependency of the APM plugin * Throw a forbidden error on the server if trying to access the service map routes --- .../app/ServiceMap/PlatinumLicensePrompt.tsx | 18 ++++++------------ .../public/components/app/ServiceMap/index.tsx | 13 +++++++------ x-pack/plugins/apm/common/service_map.ts | 18 ++++++++++++++++++ x-pack/plugins/apm/kibana.json | 2 +- x-pack/plugins/apm/server/plugin.ts | 2 ++ .../plugins/apm/server/routes/service_map.ts | 17 ++++++++++++++--- 6 files changed, 48 insertions(+), 22 deletions(-) diff --git a/x-pack/legacy/plugins/apm/public/components/app/ServiceMap/PlatinumLicensePrompt.tsx b/x-pack/legacy/plugins/apm/public/components/app/ServiceMap/PlatinumLicensePrompt.tsx index c5771995daa24..9213349a1492b 100644 --- a/x-pack/legacy/plugins/apm/public/components/app/ServiceMap/PlatinumLicensePrompt.tsx +++ b/x-pack/legacy/plugins/apm/public/components/app/ServiceMap/PlatinumLicensePrompt.tsx @@ -4,15 +4,16 @@ * you may not use this file except in compliance with the Elastic License. */ -import React from 'react'; import { - EuiEmptyPrompt, EuiButton, - EuiPanel, + EuiEmptyPrompt, EuiFlexGroup, - EuiFlexItem + EuiFlexItem, + EuiPanel } from '@elastic/eui'; import { i18n } from '@kbn/i18n'; +import React from 'react'; +import { invalidLicenseMessage } from '../../../../../../../plugins/apm/common/service_map'; import { useKibanaUrl } from '../../../hooks/useKibanaUrl'; export function PlatinumLicensePrompt() { @@ -43,14 +44,7 @@ export function PlatinumLicensePrompt() { )} ]} - body={ -

- {i18n.translate('xpack.apm.serviceMap.licensePromptBody', { - defaultMessage: - "In order to access Service Maps, you must be subscribed to an Elastic Platinum license. With it, you'll have the ability to visualize your entire application stack along with your APM data." - })} -

- } + body={

{invalidLicenseMessage}

} title={

{i18n.translate('xpack.apm.serviceMap.licensePromptTitle', { diff --git a/x-pack/legacy/plugins/apm/public/components/app/ServiceMap/index.tsx b/x-pack/legacy/plugins/apm/public/components/app/ServiceMap/index.tsx index 5fea4be9ca0da..b14ecaa803f6d 100644 --- a/x-pack/legacy/plugins/apm/public/components/app/ServiceMap/index.tsx +++ b/x-pack/legacy/plugins/apm/public/components/app/ServiceMap/index.tsx @@ -17,12 +17,14 @@ import React, { useState } from 'react'; import { toMountPoint } from '../../../../../../../../src/plugins/kibana_react/public'; +import { isValidPlatinumLicense } from '../../../../../../../plugins/apm/common/service_map'; // eslint-disable-next-line @kbn/eslint/no-restricted-paths import { ServiceMapAPIResponse } from '../../../../../../../plugins/apm/server/lib/service_map/get_service_map'; import { useApmPluginContext } from '../../../hooks/useApmPluginContext'; import { useCallApmApi } from '../../../hooks/useCallApmApi'; import { useDeepObjectIdentity } from '../../../hooks/useDeepObjectIdentity'; import { useLicense } from '../../../hooks/useLicense'; +import { useLoadingIndicator } from '../../../hooks/useLoadingIndicator'; import { useLocation } from '../../../hooks/useLocation'; import { useUrlParams } from '../../../hooks/useUrlParams'; import { Controls } from './Controls'; @@ -31,7 +33,6 @@ import { getCytoscapeElements } from './get_cytoscape_elements'; import { PlatinumLicensePrompt } from './PlatinumLicensePrompt'; import { Popover } from './Popover'; import { useRefHeight } from './useRefHeight'; -import { useLoadingIndicator } from '../../../hooks/useLoadingIndicator'; interface ServiceMapProps { serviceName?: string; @@ -195,13 +196,13 @@ export function ServiceMap({ serviceName }: ServiceMapProps) { // eslint-disable-next-line react-hooks/exhaustive-deps }, [elements]); - const isValidPlatinumLicense = - license?.isActive && - (license?.type === 'platinum' || license?.type === 'trial'); - const [wrapperRef, height] = useRefHeight(); - return isValidPlatinumLicense ? ( + if (!license) { + return null; + } + + return isValidPlatinumLicense(license) ? (
{ plugins: { apm_oss: APMOSSPlugin extends Plugin ? TSetup : never; home: HomeServerPluginSetup; + licensing: LicensingPluginSetup; cloud?: CloudSetup; usageCollection?: UsageCollectionSetup; } diff --git a/x-pack/plugins/apm/server/routes/service_map.ts b/x-pack/plugins/apm/server/routes/service_map.ts index 584598805f8b3..bead0445d6ccc 100644 --- a/x-pack/plugins/apm/server/routes/service_map.ts +++ b/x-pack/plugins/apm/server/routes/service_map.ts @@ -4,13 +4,17 @@ * you may not use this file except in compliance with the Elastic License. */ -import * as t from 'io-ts'; import Boom from 'boom'; +import * as t from 'io-ts'; +import { + invalidLicenseMessage, + isValidPlatinumLicense +} from '../../common/service_map'; import { setupRequest } from '../lib/helpers/setup_request'; -import { createRoute } from './create_route'; -import { uiFiltersRt, rangeRt } from './default_api_types'; import { getServiceMap } from '../lib/service_map/get_service_map'; import { getServiceMapServiceNodeInfo } from '../lib/service_map/get_service_map_service_node_info'; +import { createRoute } from './create_route'; +import { rangeRt, uiFiltersRt } from './default_api_types'; export const serviceMapRoute = createRoute(() => ({ path: '/api/apm/service-map', @@ -26,6 +30,10 @@ export const serviceMapRoute = createRoute(() => ({ if (!context.config['xpack.apm.serviceMapEnabled']) { throw Boom.notFound(); } + if (!isValidPlatinumLicense(context.licensing.license)) { + throw Boom.forbidden(invalidLicenseMessage); + } + const setup = await setupRequest(context, request); const { query: { serviceName, environment, after } @@ -51,6 +59,9 @@ export const serviceMapServiceNodeRoute = createRoute(() => ({ if (!context.config['xpack.apm.serviceMapEnabled']) { throw Boom.notFound(); } + if (!isValidPlatinumLicense(context.licensing.license)) { + throw Boom.forbidden(invalidLicenseMessage); + } const setup = await setupRequest(context, request); const {