Skip to content

Commit

Permalink
Server-side license check for APM service map (#58547)
Browse files Browse the repository at this point in the history
* 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
  • Loading branch information
smith authored Feb 26, 2020
1 parent 342e501 commit 2ea4bdf
Show file tree
Hide file tree
Showing 6 changed files with 48 additions and 22 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -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() {
Expand Down Expand Up @@ -43,14 +44,7 @@ export function PlatinumLicensePrompt() {
)}
</EuiButton>
]}
body={
<p>
{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."
})}
</p>
}
body={<p>{invalidLicenseMessage}</p>}
title={
<h2>
{i18n.translate('xpack.apm.serviceMap.licensePromptTitle', {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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';
Expand All @@ -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;
Expand Down Expand Up @@ -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) ? (
<div
style={{ height: height - parseInt(theme.gutterTypes.gutterLarge, 10) }}
ref={wrapperRef}
Expand Down
18 changes: 18 additions & 0 deletions x-pack/plugins/apm/common/service_map.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,9 @@
* you may not use this file except in compliance with the Elastic License.
*/

import { i18n } from '@kbn/i18n';
import { ILicense } from '../../licensing/public';

export interface ServiceConnectionNode {
'service.name': string;
'service.environment': string | null;
Expand All @@ -30,3 +33,18 @@ export interface ServiceNodeMetrics {
avgRequestsPerMinute: number | null;
avgErrorsPerMinute: number | null;
}

export function isValidPlatinumLicense(license: ILicense) {
return (
license.isActive &&
(license.type === 'platinum' || license.type === 'trial')
);
}

export const invalidLicenseMessage = i18n.translate(
'xpack.apm.serviceMap.invalidLicenseMessage',
{
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."
}
);
2 changes: 1 addition & 1 deletion x-pack/plugins/apm/kibana.json
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,6 @@
"kibanaVersion": "kibana",
"configPath": ["xpack", "apm"],
"ui": false,
"requiredPlugins": ["apm_oss", "data", "home"],
"requiredPlugins": ["apm_oss", "data", "home", "licensing"],
"optionalPlugins": ["cloud", "usageCollection"]
}
2 changes: 2 additions & 0 deletions x-pack/plugins/apm/server/plugin.ts
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ import { HomeServerPluginSetup } from '../../../../src/plugins/home/server';
import { tutorialProvider } from './tutorial';
import { CloudSetup } from '../../cloud/server';
import { getInternalSavedObjectsClient } from './lib/helpers/get_internal_saved_objects_client';
import { LicensingPluginSetup } from '../../licensing/public';

export interface LegacySetup {
server: Server;
Expand All @@ -44,6 +45,7 @@ export class APMPlugin implements Plugin<APMPluginContract> {
plugins: {
apm_oss: APMOSSPlugin extends Plugin<infer TSetup> ? TSetup : never;
home: HomeServerPluginSetup;
licensing: LicensingPluginSetup;
cloud?: CloudSetup;
usageCollection?: UsageCollectionSetup;
}
Expand Down
17 changes: 14 additions & 3 deletions x-pack/plugins/apm/server/routes/service_map.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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',
Expand All @@ -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 }
Expand All @@ -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 {
Expand Down

0 comments on commit 2ea4bdf

Please sign in to comment.