From eafd2af6aa496e7e857b9f3070ab9687465ac30d Mon Sep 17 00:00:00 2001 From: James Gowdy Date: Wed, 1 Jul 2020 17:45:36 +0100 Subject: [PATCH] [ML] Adding capabilities checks to shared functions (#70069) * [ML] Adding capabilities checks to shared functions * small refactor * disabling capabilities checks for functions called by SIEM alerting * testing git * removing comment * using constant for ml app id * tiny type clean up * removing check in ml_capabilities * fixing types * removing capabilities checks from ml_capabilities endpoint * updating types * better error handling * improving capabilities check * adding custom errors Co-authored-by: Elastic Machine --- .../apm/server/lib/helpers/setup_request.ts | 2 +- x-pack/plugins/infra/server/plugin.ts | 2 +- .../plugins/ml/common/types/capabilities.ts | 1 + .../capabilities/check_capabilities.test.ts | 9 +- .../lib/capabilities/check_capabilities.ts | 36 ++++++- .../ml/server/lib/capabilities/errors.ts | 28 +++++ .../ml/server/lib/capabilities/index.ts | 6 +- .../ml/server/lib/capabilities/upgrade.ts | 6 +- .../models/data_visualizer/data_visualizer.ts | 4 +- .../ml/server/models/filter/filter_manager.ts | 25 +++-- x-pack/plugins/ml/server/plugin.ts | 2 +- x-pack/plugins/ml/server/routes/system.ts | 3 - x-pack/plugins/ml/server/shared.ts | 1 + .../providers/anomaly_detectors.ts | 18 ++-- .../shared_services/providers/job_service.ts | 35 ++++-- .../shared_services/providers/modules.ts | 102 +++++++----------- .../providers/results_service.ts | 31 ++++-- .../shared_services/providers/system.ts | 14 ++- .../server/shared_services/shared_services.ts | 31 +++--- .../signals/signal_rule_alert_type.ts | 10 +- 20 files changed, 229 insertions(+), 137 deletions(-) create mode 100644 x-pack/plugins/ml/server/lib/capabilities/errors.ts diff --git a/x-pack/plugins/apm/server/lib/helpers/setup_request.ts b/x-pack/plugins/apm/server/lib/helpers/setup_request.ts index c41dff79a916..2dd8ed01082f 100644 --- a/x-pack/plugins/apm/server/lib/helpers/setup_request.ts +++ b/x-pack/plugins/apm/server/lib/helpers/setup_request.ts @@ -115,7 +115,7 @@ function getMlSetup(context: APMRequestHandlerContext, request: KibanaRequest) { const mlClient = ml.mlClient.asScoped(request).callAsCurrentUser; return { mlSystem: ml.mlSystemProvider(mlClient, request), - anomalyDetectors: ml.anomalyDetectorsProvider(mlClient), + anomalyDetectors: ml.anomalyDetectorsProvider(mlClient, request), mlClient, }; } diff --git a/x-pack/plugins/infra/server/plugin.ts b/x-pack/plugins/infra/server/plugin.ts index 8062c48d9861..5b9fbc2829c7 100644 --- a/x-pack/plugins/infra/server/plugin.ts +++ b/x-pack/plugins/infra/server/plugin.ts @@ -157,7 +157,7 @@ export class InfraServerPlugin { plugins.ml?.mlSystemProvider(context.ml?.mlClient.callAsCurrentUser, request); const mlAnomalyDetectors = context.ml && - plugins.ml?.anomalyDetectorsProvider(context.ml?.mlClient.callAsCurrentUser); + plugins.ml?.anomalyDetectorsProvider(context.ml?.mlClient.callAsCurrentUser, request); const spaceId = plugins.spaces?.spacesService.getSpaceId(request) || 'default'; return { diff --git a/x-pack/plugins/ml/common/types/capabilities.ts b/x-pack/plugins/ml/common/types/capabilities.ts index 9216430ab783..f2b8159b6b83 100644 --- a/x-pack/plugins/ml/common/types/capabilities.ts +++ b/x-pack/plugins/ml/common/types/capabilities.ts @@ -53,6 +53,7 @@ export const adminMlCapabilities = { export type UserMlCapabilities = typeof userMlCapabilities; export type AdminMlCapabilities = typeof adminMlCapabilities; export type MlCapabilities = UserMlCapabilities & AdminMlCapabilities; +export type MlCapabilitiesKey = keyof MlCapabilities; export const basicLicenseMlCapabilities = ['canAccessML', 'canFindFileStructure'] as Array< keyof MlCapabilities diff --git a/x-pack/plugins/ml/server/lib/capabilities/check_capabilities.test.ts b/x-pack/plugins/ml/server/lib/capabilities/check_capabilities.test.ts index 3354523b1718..8e18b57ac92a 100644 --- a/x-pack/plugins/ml/server/lib/capabilities/check_capabilities.test.ts +++ b/x-pack/plugins/ml/server/lib/capabilities/check_capabilities.test.ts @@ -4,6 +4,7 @@ * you may not use this file except in compliance with the Elastic License. */ +import { LegacyAPICaller } from 'kibana/server'; import { getAdminCapabilities, getUserCapabilities } from './__mocks__/ml_capabilities'; import { capabilitiesProvider } from './check_capabilities'; import { MlLicense } from '../../../common/license'; @@ -22,8 +23,12 @@ const mlLicenseBasic = { const mlIsEnabled = async () => true; const mlIsNotEnabled = async () => false; -const callWithRequestNonUpgrade = async () => ({ upgrade_mode: false }); -const callWithRequestUpgrade = async () => ({ upgrade_mode: true }); +const callWithRequestNonUpgrade = ((async () => ({ + upgrade_mode: false, +})) as unknown) as LegacyAPICaller; +const callWithRequestUpgrade = ((async () => ({ + upgrade_mode: true, +})) as unknown) as LegacyAPICaller; describe('check_capabilities', () => { describe('getCapabilities() - right number of capabilities', () => { diff --git a/x-pack/plugins/ml/server/lib/capabilities/check_capabilities.ts b/x-pack/plugins/ml/server/lib/capabilities/check_capabilities.ts index d4218d8e55c3..bdcdf50b983f 100644 --- a/x-pack/plugins/ml/server/lib/capabilities/check_capabilities.ts +++ b/x-pack/plugins/ml/server/lib/capabilities/check_capabilities.ts @@ -4,17 +4,25 @@ * you may not use this file except in compliance with the Elastic License. */ -import { ILegacyScopedClusterClient } from 'kibana/server'; +import { LegacyAPICaller, KibanaRequest } from 'kibana/server'; +import { mlLog } from '../../client/log'; import { MlCapabilities, adminMlCapabilities, MlCapabilitiesResponse, + ResolveMlCapabilities, + MlCapabilitiesKey, } from '../../../common/types/capabilities'; import { upgradeCheckProvider } from './upgrade'; import { MlLicense } from '../../../common/license'; +import { + InsufficientMLCapabilities, + UnknownMLCapabilitiesError, + MLPrivilegesUninitialized, +} from './errors'; export function capabilitiesProvider( - callAsCurrentUser: ILegacyScopedClusterClient['callAsCurrentUser'], + callAsCurrentUser: LegacyAPICaller, capabilities: MlCapabilities, mlLicense: MlLicense, isMlEnabledInSpace: () => Promise @@ -47,3 +55,27 @@ function disableAdminPrivileges(capabilities: MlCapabilities) { capabilities.canCreateAnnotation = false; capabilities.canDeleteAnnotation = false; } + +export type HasMlCapabilities = (capabilities: MlCapabilitiesKey[]) => Promise; + +export function hasMlCapabilitiesProvider(resolveMlCapabilities: ResolveMlCapabilities) { + return (request: KibanaRequest): HasMlCapabilities => { + let mlCapabilities: MlCapabilities | null = null; + return async (capabilities: MlCapabilitiesKey[]) => { + try { + mlCapabilities = await resolveMlCapabilities(request); + } catch (e) { + mlLog.error(e); + throw new UnknownMLCapabilitiesError(`Unable to perform ML capabilities check ${e}`); + } + + if (mlCapabilities === null) { + throw new MLPrivilegesUninitialized('ML capabilities have not been initialized'); + } + + if (capabilities.every((c) => mlCapabilities![c] === true) === false) { + throw new InsufficientMLCapabilities('Insufficient privileges to access feature'); + } + }; + }; +} diff --git a/x-pack/plugins/ml/server/lib/capabilities/errors.ts b/x-pack/plugins/ml/server/lib/capabilities/errors.ts new file mode 100644 index 000000000000..1695e0cd3b6c --- /dev/null +++ b/x-pack/plugins/ml/server/lib/capabilities/errors.ts @@ -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. + */ + +/* eslint-disable max-classes-per-file */ + +export class UnknownMLCapabilitiesError extends Error { + constructor(message?: string) { + super(message); + Object.setPrototypeOf(this, new.target.prototype); + } +} + +export class InsufficientMLCapabilities extends Error { + constructor(message?: string) { + super(message); + Object.setPrototypeOf(this, new.target.prototype); + } +} + +export class MLPrivilegesUninitialized extends Error { + constructor(message?: string) { + super(message); + Object.setPrototypeOf(this, new.target.prototype); + } +} diff --git a/x-pack/plugins/ml/server/lib/capabilities/index.ts b/x-pack/plugins/ml/server/lib/capabilities/index.ts index b73c6b87f683..4e4e3c31ff16 100644 --- a/x-pack/plugins/ml/server/lib/capabilities/index.ts +++ b/x-pack/plugins/ml/server/lib/capabilities/index.ts @@ -4,5 +4,9 @@ * you may not use this file except in compliance with the Elastic License. */ -export { capabilitiesProvider } from './check_capabilities'; +export { + capabilitiesProvider, + hasMlCapabilitiesProvider, + HasMlCapabilities, +} from './check_capabilities'; export { setupCapabilitiesSwitcher } from './capabilities_switcher'; diff --git a/x-pack/plugins/ml/server/lib/capabilities/upgrade.ts b/x-pack/plugins/ml/server/lib/capabilities/upgrade.ts index 259606ba8c7e..45f3f3da20c2 100644 --- a/x-pack/plugins/ml/server/lib/capabilities/upgrade.ts +++ b/x-pack/plugins/ml/server/lib/capabilities/upgrade.ts @@ -4,12 +4,10 @@ * you may not use this file except in compliance with the Elastic License. */ -import { ILegacyScopedClusterClient } from 'kibana/server'; +import { LegacyAPICaller } from 'kibana/server'; import { mlLog } from '../../client/log'; -export function upgradeCheckProvider( - callAsCurrentUser: ILegacyScopedClusterClient['callAsCurrentUser'] -) { +export function upgradeCheckProvider(callAsCurrentUser: LegacyAPICaller) { async function isUpgradeInProgress(): Promise { let upgradeInProgress = false; try { diff --git a/x-pack/plugins/ml/server/models/data_visualizer/data_visualizer.ts b/x-pack/plugins/ml/server/models/data_visualizer/data_visualizer.ts index dc1eef8edd0b..78cc7901363c 100644 --- a/x-pack/plugins/ml/server/models/data_visualizer/data_visualizer.ts +++ b/x-pack/plugins/ml/server/models/data_visualizer/data_visualizer.ts @@ -4,7 +4,7 @@ * you may not use this file except in compliance with the Elastic License. */ -import { LegacyCallAPIOptions, ILegacyScopedClusterClient } from 'kibana/server'; +import { LegacyCallAPIOptions, LegacyAPICaller } from 'kibana/server'; import _ from 'lodash'; import { ML_JOB_FIELD_TYPES } from '../../../common/constants/field_types'; import { getSafeAggregationName } from '../../../common/util/job_utils'; @@ -113,7 +113,7 @@ export class DataVisualizer { options?: LegacyCallAPIOptions ) => Promise; - constructor(callAsCurrentUser: ILegacyScopedClusterClient['callAsCurrentUser']) { + constructor(callAsCurrentUser: LegacyAPICaller) { this.callAsCurrentUser = callAsCurrentUser; } diff --git a/x-pack/plugins/ml/server/models/filter/filter_manager.ts b/x-pack/plugins/ml/server/models/filter/filter_manager.ts index 22d3df3cc8a6..40a20030cb63 100644 --- a/x-pack/plugins/ml/server/models/filter/filter_manager.ts +++ b/x-pack/plugins/ml/server/models/filter/filter_manager.ts @@ -5,7 +5,7 @@ */ import Boom from 'boom'; -import { ILegacyScopedClusterClient } from 'kibana/server'; +import { LegacyAPICaller } from 'kibana/server'; import { DetectorRule, DetectorRuleScope } from '../../../common/types/detector_rules'; @@ -58,18 +58,14 @@ interface PartialJob { } export class FilterManager { - private _client: ILegacyScopedClusterClient['callAsCurrentUser']; - - constructor(client: ILegacyScopedClusterClient['callAsCurrentUser']) { - this._client = client; - } + constructor(private callAsCurrentUser: LegacyAPICaller) {} async getFilter(filterId: string) { try { const [JOBS, FILTERS] = [0, 1]; const results = await Promise.all([ - this._client('ml.jobs'), - this._client('ml.filters', { filterId }), + this.callAsCurrentUser('ml.jobs'), + this.callAsCurrentUser('ml.filters', { filterId }), ]); if (results[FILTERS] && results[FILTERS].filters.length) { @@ -91,7 +87,7 @@ export class FilterManager { async getAllFilters() { try { - const filtersResp = await this._client('ml.filters'); + const filtersResp = await this.callAsCurrentUser('ml.filters'); return filtersResp.filters; } catch (error) { throw Boom.badRequest(error); @@ -101,7 +97,10 @@ export class FilterManager { async getAllFilterStats() { try { const [JOBS, FILTERS] = [0, 1]; - const results = await Promise.all([this._client('ml.jobs'), this._client('ml.filters')]); + const results = await Promise.all([ + this.callAsCurrentUser('ml.jobs'), + this.callAsCurrentUser('ml.filters'), + ]); // Build a map of filter_ids against jobs and detectors using that filter. let filtersInUse: FiltersInUse = {}; @@ -138,7 +137,7 @@ export class FilterManager { delete filter.filterId; try { // Returns the newly created filter. - return await this._client('ml.addFilter', { filterId, body: filter }); + return await this.callAsCurrentUser('ml.addFilter', { filterId, body: filter }); } catch (error) { throw Boom.badRequest(error); } @@ -158,7 +157,7 @@ export class FilterManager { } // Returns the newly updated filter. - return await this._client('ml.updateFilter', { + return await this.callAsCurrentUser('ml.updateFilter', { filterId, body, }); @@ -168,7 +167,7 @@ export class FilterManager { } async deleteFilter(filterId: string) { - return this._client('ml.deleteFilter', { filterId }); + return this.callAsCurrentUser('ml.deleteFilter', { filterId }); } buildFiltersInUse(jobsList: PartialJob[]) { diff --git a/x-pack/plugins/ml/server/plugin.ts b/x-pack/plugins/ml/server/plugin.ts index 3e753627ead9..83b14d60fb41 100644 --- a/x-pack/plugins/ml/server/plugin.ts +++ b/x-pack/plugins/ml/server/plugin.ts @@ -51,7 +51,7 @@ import { registerKibanaSettings } from './lib/register_settings'; declare module 'kibana/server' { interface RequestHandlerContext { - ml?: { + [PLUGIN_ID]?: { mlClient: ILegacyScopedClusterClient; }; } diff --git a/x-pack/plugins/ml/server/routes/system.ts b/x-pack/plugins/ml/server/routes/system.ts index 99c7805a62e7..d78c1cf3aa6a 100644 --- a/x-pack/plugins/ml/server/routes/system.ts +++ b/x-pack/plugins/ml/server/routes/system.ts @@ -113,9 +113,6 @@ export function systemRoutes( { path: '/api/ml/ml_capabilities', validate: false, - options: { - tags: ['access:ml:canAccessML'], - }, }, mlLicense.basicLicenseAPIGuard(async (context, request, response) => { try { diff --git a/x-pack/plugins/ml/server/shared.ts b/x-pack/plugins/ml/server/shared.ts index be27ee2d44a8..7b4b2a55c29f 100644 --- a/x-pack/plugins/ml/server/shared.ts +++ b/x-pack/plugins/ml/server/shared.ts @@ -6,3 +6,4 @@ export * from '../common/types/anomalies'; export * from '../common/types/anomaly_detection_jobs'; +export * from './lib/capabilities/errors'; diff --git a/x-pack/plugins/ml/server/shared_services/providers/anomaly_detectors.ts b/x-pack/plugins/ml/server/shared_services/providers/anomaly_detectors.ts index a2e3ce6569e6..3ae05152ae63 100644 --- a/x-pack/plugins/ml/server/shared_services/providers/anomaly_detectors.ts +++ b/x-pack/plugins/ml/server/shared_services/providers/anomaly_detectors.ts @@ -4,24 +4,30 @@ * you may not use this file except in compliance with the Elastic License. */ -import { LegacyAPICaller } from 'kibana/server'; -import { LicenseCheck } from '../license_checks'; +import { LegacyAPICaller, KibanaRequest } from 'kibana/server'; import { Job } from '../../../common/types/anomaly_detection_jobs'; +import { SharedServicesChecks } from '../shared_services'; export interface AnomalyDetectorsProvider { anomalyDetectorsProvider( - callAsCurrentUser: LegacyAPICaller + callAsCurrentUser: LegacyAPICaller, + request: KibanaRequest ): { jobs(jobId?: string): Promise<{ count: number; jobs: Job[] }>; }; } -export function getAnomalyDetectorsProvider(isFullLicense: LicenseCheck): AnomalyDetectorsProvider { +export function getAnomalyDetectorsProvider({ + isFullLicense, + getHasMlCapabilities, +}: SharedServicesChecks): AnomalyDetectorsProvider { return { - anomalyDetectorsProvider(callAsCurrentUser: LegacyAPICaller) { + anomalyDetectorsProvider(callAsCurrentUser: LegacyAPICaller, request: KibanaRequest) { + const hasMlCapabilities = getHasMlCapabilities(request); return { - jobs(jobId?: string) { + async jobs(jobId?: string) { isFullLicense(); + await hasMlCapabilities(['canGetJobs']); return callAsCurrentUser('ml.jobs', jobId !== undefined ? { jobId } : {}); }, }; diff --git a/x-pack/plugins/ml/server/shared_services/providers/job_service.ts b/x-pack/plugins/ml/server/shared_services/providers/job_service.ts index 5deb7c3cb756..e5a42090163f 100644 --- a/x-pack/plugins/ml/server/shared_services/providers/job_service.ts +++ b/x-pack/plugins/ml/server/shared_services/providers/job_service.ts @@ -4,19 +4,40 @@ * you may not use this file except in compliance with the Elastic License. */ -import { LegacyAPICaller } from 'kibana/server'; -import { LicenseCheck } from '../license_checks'; +import { LegacyAPICaller, KibanaRequest } from 'kibana/server'; import { jobServiceProvider } from '../../models/job_service'; +import { SharedServicesChecks } from '../shared_services'; + +type OrigJobServiceProvider = ReturnType; export interface JobServiceProvider { - jobServiceProvider(callAsCurrentUser: LegacyAPICaller): ReturnType; + jobServiceProvider( + callAsCurrentUser: LegacyAPICaller, + request: KibanaRequest + ): { + jobsSummary: OrigJobServiceProvider['jobsSummary']; + }; } -export function getJobServiceProvider(isFullLicense: LicenseCheck): JobServiceProvider { +export function getJobServiceProvider({ + isFullLicense, + getHasMlCapabilities, +}: SharedServicesChecks): JobServiceProvider { return { - jobServiceProvider(callAsCurrentUser: LegacyAPICaller) { - isFullLicense(); - return jobServiceProvider(callAsCurrentUser); + jobServiceProvider(callAsCurrentUser: LegacyAPICaller, request: KibanaRequest) { + // const hasMlCapabilities = getHasMlCapabilities(request); + const { jobsSummary } = jobServiceProvider(callAsCurrentUser); + return { + async jobsSummary(...args) { + isFullLicense(); + // Removed while https://github.com/elastic/kibana/issues/64588 exists. + // SIEM are calling this endpoint with a dummy request object from their alerting + // integration and currently alerting does not supply a request object. + // await hasMlCapabilities(['canGetJobs']); + + return jobsSummary(...args); + }, + }; }, }; } diff --git a/x-pack/plugins/ml/server/shared_services/providers/modules.ts b/x-pack/plugins/ml/server/shared_services/providers/modules.ts index e5359a0b0f80..d1666c5c1bf7 100644 --- a/x-pack/plugins/ml/server/shared_services/providers/modules.ts +++ b/x-pack/plugins/ml/server/shared_services/providers/modules.ts @@ -4,89 +4,59 @@ * you may not use this file except in compliance with the Elastic License. */ -import { LegacyAPICaller, SavedObjectsClientContract } from 'kibana/server'; -import { LicenseCheck } from '../license_checks'; -import { DataRecognizer, RecognizeResult } from '../../models/data_recognizer'; -import { - Module, - DatafeedOverride, - JobOverride, - DataRecognizerConfigResponse, -} from '../../../common/types/modules'; +import { LegacyAPICaller, KibanaRequest, SavedObjectsClientContract } from 'kibana/server'; +import { DataRecognizer } from '../../models/data_recognizer'; +import { SharedServicesChecks } from '../shared_services'; export interface ModulesProvider { modulesProvider( callAsCurrentUser: LegacyAPICaller, + request: KibanaRequest, savedObjectsClient: SavedObjectsClientContract ): { - recognize(indexPatternTitle: string): Promise; - getModule(moduleId?: string): Promise; - saveModuleItems( - moduleId: string, - prefix: string, - groups: string[], - indexPatternName: string, - query: any, - useDedicatedIndex: boolean, - startDatafeed: boolean, - start: number, - end: number, - jobOverrides: JobOverride[], - datafeedOverrides: DatafeedOverride[], - estimateModelMemory?: boolean - ): Promise; + recognize: DataRecognizer['findMatches']; + getModule: DataRecognizer['getModule']; + listModules: DataRecognizer['listModules']; + setupModuleItems: DataRecognizer['setupModuleItems']; }; } -export function getModulesProvider(isFullLicense: LicenseCheck): ModulesProvider { +export function getModulesProvider({ + isFullLicense, + getHasMlCapabilities, +}: SharedServicesChecks): ModulesProvider { return { modulesProvider( callAsCurrentUser: LegacyAPICaller, + request: KibanaRequest, savedObjectsClient: SavedObjectsClientContract ) { - isFullLicense(); + const hasMlCapabilities = getHasMlCapabilities(request); + const dr = dataRecognizerFactory(callAsCurrentUser, savedObjectsClient); return { - recognize(indexPatternTitle: string) { - const dr = dataRecognizerFactory(callAsCurrentUser, savedObjectsClient); - return dr.findMatches(indexPatternTitle); + async recognize(...args) { + isFullLicense(); + await hasMlCapabilities(['canCreateJob']); + + return dr.findMatches(...args); }, - getModule(moduleId?: string) { - const dr = dataRecognizerFactory(callAsCurrentUser, savedObjectsClient); - if (moduleId === undefined) { - return dr.listModules(); - } else { - return dr.getModule(moduleId); - } + async getModule(moduleId: string) { + isFullLicense(); + await hasMlCapabilities(['canGetJobs']); + + return dr.getModule(moduleId); }, - saveModuleItems( - moduleId: string, - prefix: string, - groups: string[], - indexPatternName: string, - query: any, - useDedicatedIndex: boolean, - startDatafeed: boolean, - start: number, - end: number, - jobOverrides: JobOverride[], - datafeedOverrides: DatafeedOverride[], - estimateModelMemory?: boolean - ) { - const dr = dataRecognizerFactory(callAsCurrentUser, savedObjectsClient); - return dr.setupModuleItems( - moduleId, - prefix, - groups, - indexPatternName, - query, - useDedicatedIndex, - startDatafeed, - start, - end, - jobOverrides, - datafeedOverrides, - estimateModelMemory - ); + async listModules() { + isFullLicense(); + await hasMlCapabilities(['canGetJobs']); + + return dr.listModules(); + }, + async setupModuleItems(...args) { + isFullLicense(); + await hasMlCapabilities(['canCreateJob']); + + return dr.setupModuleItems(...args); }, }; }, diff --git a/x-pack/plugins/ml/server/shared_services/providers/results_service.ts b/x-pack/plugins/ml/server/shared_services/providers/results_service.ts index 8da25a02278e..e9448a67cd98 100644 --- a/x-pack/plugins/ml/server/shared_services/providers/results_service.ts +++ b/x-pack/plugins/ml/server/shared_services/providers/results_service.ts @@ -4,21 +4,36 @@ * you may not use this file except in compliance with the Elastic License. */ -import { LegacyAPICaller } from 'kibana/server'; -import { LicenseCheck } from '../license_checks'; +import { LegacyAPICaller, KibanaRequest } from 'kibana/server'; import { resultsServiceProvider } from '../../models/results_service'; +import { SharedServicesChecks } from '../shared_services'; + +type OrigResultsServiceProvider = ReturnType; export interface ResultsServiceProvider { resultsServiceProvider( - callAsCurrentUser: LegacyAPICaller - ): ReturnType; + callAsCurrentUser: LegacyAPICaller, + request: KibanaRequest + ): { + getAnomaliesTableData: OrigResultsServiceProvider['getAnomaliesTableData']; + }; } -export function getResultsServiceProvider(isFullLicense: LicenseCheck): ResultsServiceProvider { +export function getResultsServiceProvider({ + isFullLicense, + getHasMlCapabilities, +}: SharedServicesChecks): ResultsServiceProvider { return { - resultsServiceProvider(callAsCurrentUser: LegacyAPICaller) { - isFullLicense(); - return resultsServiceProvider(callAsCurrentUser); + resultsServiceProvider(callAsCurrentUser: LegacyAPICaller, request: KibanaRequest) { + const hasMlCapabilities = getHasMlCapabilities(request); + const { getAnomaliesTableData } = resultsServiceProvider(callAsCurrentUser); + return { + async getAnomaliesTableData(...args) { + isFullLicense(); + await hasMlCapabilities(['canGetJobs']); + return getAnomaliesTableData(...args); + }, + }; }, }; } diff --git a/x-pack/plugins/ml/server/shared_services/providers/system.ts b/x-pack/plugins/ml/server/shared_services/providers/system.ts index 8f1cfbc5c1b6..00124a67e523 100644 --- a/x-pack/plugins/ml/server/shared_services/providers/system.ts +++ b/x-pack/plugins/ml/server/shared_services/providers/system.ts @@ -8,13 +8,13 @@ import { LegacyAPICaller, KibanaRequest } from 'kibana/server'; import { SearchResponse, SearchParams } from 'elasticsearch'; import { MlServerLicense } from '../../lib/license'; import { CloudSetup } from '../../../../cloud/server'; -import { LicenseCheck } from '../license_checks'; import { spacesUtilsProvider } from '../../lib/spaces_utils'; import { SpacesPluginSetup } from '../../../../spaces/server'; import { capabilitiesProvider } from '../../lib/capabilities'; import { MlInfoResponse } from '../../../common/types/ml_server_info'; import { ML_RESULTS_INDEX_PATTERN } from '../../../common/constants/index_patterns'; import { MlCapabilitiesResponse, ResolveMlCapabilities } from '../../../common/types/capabilities'; +import { SharedServicesChecks } from '../shared_services'; export interface MlSystemProvider { mlSystemProvider( @@ -28,8 +28,7 @@ export interface MlSystemProvider { } export function getMlSystemProvider( - isMinimumLicense: LicenseCheck, - isFullLicense: LicenseCheck, + { isMinimumLicense, isFullLicense, getHasMlCapabilities }: SharedServicesChecks, mlLicense: MlServerLicense, spaces: SpacesPluginSetup | undefined, cloud: CloudSetup | undefined, @@ -37,6 +36,7 @@ export function getMlSystemProvider( ): MlSystemProvider { return { mlSystemProvider(callAsCurrentUser: LegacyAPICaller, request: KibanaRequest) { + // const hasMlCapabilities = getHasMlCapabilities(request); return { async mlCapabilities() { isMinimumLicense(); @@ -48,7 +48,7 @@ export function getMlSystemProvider( const mlCapabilities = await resolveMlCapabilities(request); if (mlCapabilities === null) { - throw new Error('resolveMlCapabilities is not defined'); + throw new Error('mlCapabilities is not defined'); } const { getCapabilities } = capabilitiesProvider( @@ -61,6 +61,7 @@ export function getMlSystemProvider( }, async mlInfo(): Promise { isMinimumLicense(); + const info = await callAsCurrentUser('ml.info'); const cloudId = cloud && cloud.cloudId; return { @@ -70,6 +71,11 @@ export function getMlSystemProvider( }, async mlAnomalySearch(searchParams: SearchParams): Promise> { isFullLicense(); + // Removed while https://github.com/elastic/kibana/issues/64588 exists. + // SIEM are calling this endpoint with a dummy request object from their alerting + // integration and currently alerting does not supply a request object. + // await hasMlCapabilities(['canAccessML']); + return callAsCurrentUser('search', { ...searchParams, index: ML_RESULTS_INDEX_PATTERN, diff --git a/x-pack/plugins/ml/server/shared_services/shared_services.ts b/x-pack/plugins/ml/server/shared_services/shared_services.ts index f2d20a72444b..3345111fad4a 100644 --- a/x-pack/plugins/ml/server/shared_services/shared_services.ts +++ b/x-pack/plugins/ml/server/shared_services/shared_services.ts @@ -4,6 +4,7 @@ * you may not use this file except in compliance with the Elastic License. */ +import { KibanaRequest } from 'kibana/server'; import { MlServerLicense } from '../lib/license'; import { SpacesPluginSetup } from '../../../spaces/server'; @@ -18,6 +19,7 @@ import { getAnomalyDetectorsProvider, } from './providers/anomaly_detectors'; import { ResolveMlCapabilities } from '../../common/types/capabilities'; +import { hasMlCapabilitiesProvider, HasMlCapabilities } from '../lib/capabilities'; export type SharedServices = JobServiceProvider & AnomalyDetectorsProvider & @@ -25,6 +27,12 @@ export type SharedServices = JobServiceProvider & ModulesProvider & ResultsServiceProvider; +export interface SharedServicesChecks { + isFullLicense(): void; + isMinimumLicense(): void; + getHasMlCapabilities(request: KibanaRequest): HasMlCapabilities; +} + export function createSharedServices( mlLicense: MlServerLicense, spaces: SpacesPluginSetup | undefined, @@ -32,19 +40,18 @@ export function createSharedServices( resolveMlCapabilities: ResolveMlCapabilities ): SharedServices { const { isFullLicense, isMinimumLicense } = licenseChecks(mlLicense); + const getHasMlCapabilities = hasMlCapabilitiesProvider(resolveMlCapabilities); + const checks: SharedServicesChecks = { + isFullLicense, + isMinimumLicense, + getHasMlCapabilities, + }; return { - ...getJobServiceProvider(isFullLicense), - ...getAnomalyDetectorsProvider(isFullLicense), - ...getMlSystemProvider( - isMinimumLicense, - isFullLicense, - mlLicense, - spaces, - cloud, - resolveMlCapabilities - ), - ...getModulesProvider(isFullLicense), - ...getResultsServiceProvider(isFullLicense), + ...getJobServiceProvider(checks), + ...getAnomalyDetectorsProvider(checks), + ...getModulesProvider(checks), + ...getResultsServiceProvider(checks), + ...getMlSystemProvider(checks, mlLicense, spaces, cloud, resolveMlCapabilities), }; } diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/signals/signal_rule_alert_type.ts b/x-pack/plugins/security_solution/server/lib/detection_engine/signals/signal_rule_alert_type.ts index 143c300698b5..a33af1e48585 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/signals/signal_rule_alert_type.ts +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/signals/signal_rule_alert_type.ts @@ -164,9 +164,11 @@ export const signalRulesAlertType = ({ } const scopedMlCallCluster = services.getScopedCallCluster(ml.mlClient); - const summaryJobs = await ml - .jobServiceProvider(scopedMlCallCluster) - .jobsSummary([machineLearningJobId]); + // Using fake KibanaRequest as it is needed to satisfy the ML Services API, but can be empty as it is + // currently unused by the jobsSummary function. + const summaryJobs = await ( + await ml.jobServiceProvider(scopedMlCallCluster, ({} as unknown) as KibanaRequest) + ).jobsSummary([machineLearningJobId]); const jobSummary = summaryJobs.find((job) => job.id === machineLearningJobId); if (jobSummary == null || !isJobStarted(jobSummary.jobState, jobSummary.datafeedState)) { @@ -184,7 +186,7 @@ export const signalRulesAlertType = ({ const anomalyResults = await findMlSignals({ ml, callCluster: scopedMlCallCluster, - // This is needed to satisfy the ML Services API, but can be empty as it is + // Using fake KibanaRequest as it is needed to satisfy the ML Services API, but can be empty as it is // currently unused by the mlAnomalySearch function. request: ({} as unknown) as KibanaRequest, jobId: machineLearningJobId,