[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 <elasticmachine@users.noreply.github.com>
This commit is contained in:
James Gowdy 2020-07-01 17:45:36 +01:00 committed by GitHub
parent b7f33b94a8
commit eafd2af6aa
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
20 changed files with 229 additions and 137 deletions

View file

@ -115,7 +115,7 @@ function getMlSetup(context: APMRequestHandlerContext, request: KibanaRequest) {
const mlClient = ml.mlClient.asScoped(request).callAsCurrentUser; const mlClient = ml.mlClient.asScoped(request).callAsCurrentUser;
return { return {
mlSystem: ml.mlSystemProvider(mlClient, request), mlSystem: ml.mlSystemProvider(mlClient, request),
anomalyDetectors: ml.anomalyDetectorsProvider(mlClient), anomalyDetectors: ml.anomalyDetectorsProvider(mlClient, request),
mlClient, mlClient,
}; };
} }

View file

@ -157,7 +157,7 @@ export class InfraServerPlugin {
plugins.ml?.mlSystemProvider(context.ml?.mlClient.callAsCurrentUser, request); plugins.ml?.mlSystemProvider(context.ml?.mlClient.callAsCurrentUser, request);
const mlAnomalyDetectors = const mlAnomalyDetectors =
context.ml && 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'; const spaceId = plugins.spaces?.spacesService.getSpaceId(request) || 'default';
return { return {

View file

@ -53,6 +53,7 @@ export const adminMlCapabilities = {
export type UserMlCapabilities = typeof userMlCapabilities; export type UserMlCapabilities = typeof userMlCapabilities;
export type AdminMlCapabilities = typeof adminMlCapabilities; export type AdminMlCapabilities = typeof adminMlCapabilities;
export type MlCapabilities = UserMlCapabilities & AdminMlCapabilities; export type MlCapabilities = UserMlCapabilities & AdminMlCapabilities;
export type MlCapabilitiesKey = keyof MlCapabilities;
export const basicLicenseMlCapabilities = ['canAccessML', 'canFindFileStructure'] as Array< export const basicLicenseMlCapabilities = ['canAccessML', 'canFindFileStructure'] as Array<
keyof MlCapabilities keyof MlCapabilities

View file

@ -4,6 +4,7 @@
* you may not use this file except in compliance with the Elastic License. * 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 { getAdminCapabilities, getUserCapabilities } from './__mocks__/ml_capabilities';
import { capabilitiesProvider } from './check_capabilities'; import { capabilitiesProvider } from './check_capabilities';
import { MlLicense } from '../../../common/license'; import { MlLicense } from '../../../common/license';
@ -22,8 +23,12 @@ const mlLicenseBasic = {
const mlIsEnabled = async () => true; const mlIsEnabled = async () => true;
const mlIsNotEnabled = async () => false; const mlIsNotEnabled = async () => false;
const callWithRequestNonUpgrade = async () => ({ upgrade_mode: false }); const callWithRequestNonUpgrade = ((async () => ({
const callWithRequestUpgrade = async () => ({ upgrade_mode: true }); upgrade_mode: false,
})) as unknown) as LegacyAPICaller;
const callWithRequestUpgrade = ((async () => ({
upgrade_mode: true,
})) as unknown) as LegacyAPICaller;
describe('check_capabilities', () => { describe('check_capabilities', () => {
describe('getCapabilities() - right number of capabilities', () => { describe('getCapabilities() - right number of capabilities', () => {

View file

@ -4,17 +4,25 @@
* you may not use this file except in compliance with the Elastic License. * 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 { import {
MlCapabilities, MlCapabilities,
adminMlCapabilities, adminMlCapabilities,
MlCapabilitiesResponse, MlCapabilitiesResponse,
ResolveMlCapabilities,
MlCapabilitiesKey,
} from '../../../common/types/capabilities'; } from '../../../common/types/capabilities';
import { upgradeCheckProvider } from './upgrade'; import { upgradeCheckProvider } from './upgrade';
import { MlLicense } from '../../../common/license'; import { MlLicense } from '../../../common/license';
import {
InsufficientMLCapabilities,
UnknownMLCapabilitiesError,
MLPrivilegesUninitialized,
} from './errors';
export function capabilitiesProvider( export function capabilitiesProvider(
callAsCurrentUser: ILegacyScopedClusterClient['callAsCurrentUser'], callAsCurrentUser: LegacyAPICaller,
capabilities: MlCapabilities, capabilities: MlCapabilities,
mlLicense: MlLicense, mlLicense: MlLicense,
isMlEnabledInSpace: () => Promise<boolean> isMlEnabledInSpace: () => Promise<boolean>
@ -47,3 +55,27 @@ function disableAdminPrivileges(capabilities: MlCapabilities) {
capabilities.canCreateAnnotation = false; capabilities.canCreateAnnotation = false;
capabilities.canDeleteAnnotation = false; capabilities.canDeleteAnnotation = false;
} }
export type HasMlCapabilities = (capabilities: MlCapabilitiesKey[]) => Promise<void>;
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');
}
};
};
}

View file

@ -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);
}
}

View file

@ -4,5 +4,9 @@
* you may not use this file except in compliance with the Elastic License. * 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'; export { setupCapabilitiesSwitcher } from './capabilities_switcher';

View file

@ -4,12 +4,10 @@
* you may not use this file except in compliance with the Elastic License. * 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'; import { mlLog } from '../../client/log';
export function upgradeCheckProvider( export function upgradeCheckProvider(callAsCurrentUser: LegacyAPICaller) {
callAsCurrentUser: ILegacyScopedClusterClient['callAsCurrentUser']
) {
async function isUpgradeInProgress(): Promise<boolean> { async function isUpgradeInProgress(): Promise<boolean> {
let upgradeInProgress = false; let upgradeInProgress = false;
try { try {

View file

@ -4,7 +4,7 @@
* you may not use this file except in compliance with the Elastic License. * 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 _ from 'lodash';
import { ML_JOB_FIELD_TYPES } from '../../../common/constants/field_types'; import { ML_JOB_FIELD_TYPES } from '../../../common/constants/field_types';
import { getSafeAggregationName } from '../../../common/util/job_utils'; import { getSafeAggregationName } from '../../../common/util/job_utils';
@ -113,7 +113,7 @@ export class DataVisualizer {
options?: LegacyCallAPIOptions options?: LegacyCallAPIOptions
) => Promise<any>; ) => Promise<any>;
constructor(callAsCurrentUser: ILegacyScopedClusterClient['callAsCurrentUser']) { constructor(callAsCurrentUser: LegacyAPICaller) {
this.callAsCurrentUser = callAsCurrentUser; this.callAsCurrentUser = callAsCurrentUser;
} }

View file

@ -5,7 +5,7 @@
*/ */
import Boom from 'boom'; import Boom from 'boom';
import { ILegacyScopedClusterClient } from 'kibana/server'; import { LegacyAPICaller } from 'kibana/server';
import { DetectorRule, DetectorRuleScope } from '../../../common/types/detector_rules'; import { DetectorRule, DetectorRuleScope } from '../../../common/types/detector_rules';
@ -58,18 +58,14 @@ interface PartialJob {
} }
export class FilterManager { export class FilterManager {
private _client: ILegacyScopedClusterClient['callAsCurrentUser']; constructor(private callAsCurrentUser: LegacyAPICaller) {}
constructor(client: ILegacyScopedClusterClient['callAsCurrentUser']) {
this._client = client;
}
async getFilter(filterId: string) { async getFilter(filterId: string) {
try { try {
const [JOBS, FILTERS] = [0, 1]; const [JOBS, FILTERS] = [0, 1];
const results = await Promise.all([ const results = await Promise.all([
this._client('ml.jobs'), this.callAsCurrentUser('ml.jobs'),
this._client('ml.filters', { filterId }), this.callAsCurrentUser('ml.filters', { filterId }),
]); ]);
if (results[FILTERS] && results[FILTERS].filters.length) { if (results[FILTERS] && results[FILTERS].filters.length) {
@ -91,7 +87,7 @@ export class FilterManager {
async getAllFilters() { async getAllFilters() {
try { try {
const filtersResp = await this._client('ml.filters'); const filtersResp = await this.callAsCurrentUser('ml.filters');
return filtersResp.filters; return filtersResp.filters;
} catch (error) { } catch (error) {
throw Boom.badRequest(error); throw Boom.badRequest(error);
@ -101,7 +97,10 @@ export class FilterManager {
async getAllFilterStats() { async getAllFilterStats() {
try { try {
const [JOBS, FILTERS] = [0, 1]; 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. // Build a map of filter_ids against jobs and detectors using that filter.
let filtersInUse: FiltersInUse = {}; let filtersInUse: FiltersInUse = {};
@ -138,7 +137,7 @@ export class FilterManager {
delete filter.filterId; delete filter.filterId;
try { try {
// Returns the newly created filter. // 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) { } catch (error) {
throw Boom.badRequest(error); throw Boom.badRequest(error);
} }
@ -158,7 +157,7 @@ export class FilterManager {
} }
// Returns the newly updated filter. // Returns the newly updated filter.
return await this._client('ml.updateFilter', { return await this.callAsCurrentUser('ml.updateFilter', {
filterId, filterId,
body, body,
}); });
@ -168,7 +167,7 @@ export class FilterManager {
} }
async deleteFilter(filterId: string) { async deleteFilter(filterId: string) {
return this._client('ml.deleteFilter', { filterId }); return this.callAsCurrentUser('ml.deleteFilter', { filterId });
} }
buildFiltersInUse(jobsList: PartialJob[]) { buildFiltersInUse(jobsList: PartialJob[]) {

View file

@ -51,7 +51,7 @@ import { registerKibanaSettings } from './lib/register_settings';
declare module 'kibana/server' { declare module 'kibana/server' {
interface RequestHandlerContext { interface RequestHandlerContext {
ml?: { [PLUGIN_ID]?: {
mlClient: ILegacyScopedClusterClient; mlClient: ILegacyScopedClusterClient;
}; };
} }

View file

@ -113,9 +113,6 @@ export function systemRoutes(
{ {
path: '/api/ml/ml_capabilities', path: '/api/ml/ml_capabilities',
validate: false, validate: false,
options: {
tags: ['access:ml:canAccessML'],
},
}, },
mlLicense.basicLicenseAPIGuard(async (context, request, response) => { mlLicense.basicLicenseAPIGuard(async (context, request, response) => {
try { try {

View file

@ -6,3 +6,4 @@
export * from '../common/types/anomalies'; export * from '../common/types/anomalies';
export * from '../common/types/anomaly_detection_jobs'; export * from '../common/types/anomaly_detection_jobs';
export * from './lib/capabilities/errors';

View file

@ -4,24 +4,30 @@
* you may not use this file except in compliance with the Elastic License. * you may not use this file except in compliance with the Elastic License.
*/ */
import { LegacyAPICaller } from 'kibana/server'; import { LegacyAPICaller, KibanaRequest } from 'kibana/server';
import { LicenseCheck } from '../license_checks';
import { Job } from '../../../common/types/anomaly_detection_jobs'; import { Job } from '../../../common/types/anomaly_detection_jobs';
import { SharedServicesChecks } from '../shared_services';
export interface AnomalyDetectorsProvider { export interface AnomalyDetectorsProvider {
anomalyDetectorsProvider( anomalyDetectorsProvider(
callAsCurrentUser: LegacyAPICaller callAsCurrentUser: LegacyAPICaller,
request: KibanaRequest
): { ): {
jobs(jobId?: string): Promise<{ count: number; jobs: Job[] }>; jobs(jobId?: string): Promise<{ count: number; jobs: Job[] }>;
}; };
} }
export function getAnomalyDetectorsProvider(isFullLicense: LicenseCheck): AnomalyDetectorsProvider { export function getAnomalyDetectorsProvider({
isFullLicense,
getHasMlCapabilities,
}: SharedServicesChecks): AnomalyDetectorsProvider {
return { return {
anomalyDetectorsProvider(callAsCurrentUser: LegacyAPICaller) { anomalyDetectorsProvider(callAsCurrentUser: LegacyAPICaller, request: KibanaRequest) {
const hasMlCapabilities = getHasMlCapabilities(request);
return { return {
jobs(jobId?: string) { async jobs(jobId?: string) {
isFullLicense(); isFullLicense();
await hasMlCapabilities(['canGetJobs']);
return callAsCurrentUser('ml.jobs', jobId !== undefined ? { jobId } : {}); return callAsCurrentUser('ml.jobs', jobId !== undefined ? { jobId } : {});
}, },
}; };

View file

@ -4,19 +4,40 @@
* you may not use this file except in compliance with the Elastic License. * you may not use this file except in compliance with the Elastic License.
*/ */
import { LegacyAPICaller } from 'kibana/server'; import { LegacyAPICaller, KibanaRequest } from 'kibana/server';
import { LicenseCheck } from '../license_checks';
import { jobServiceProvider } from '../../models/job_service'; import { jobServiceProvider } from '../../models/job_service';
import { SharedServicesChecks } from '../shared_services';
type OrigJobServiceProvider = ReturnType<typeof jobServiceProvider>;
export interface JobServiceProvider { export interface JobServiceProvider {
jobServiceProvider(callAsCurrentUser: LegacyAPICaller): ReturnType<typeof jobServiceProvider>; jobServiceProvider(
callAsCurrentUser: LegacyAPICaller,
request: KibanaRequest
): {
jobsSummary: OrigJobServiceProvider['jobsSummary'];
};
} }
export function getJobServiceProvider(isFullLicense: LicenseCheck): JobServiceProvider { export function getJobServiceProvider({
isFullLicense,
getHasMlCapabilities,
}: SharedServicesChecks): JobServiceProvider {
return { return {
jobServiceProvider(callAsCurrentUser: LegacyAPICaller) { jobServiceProvider(callAsCurrentUser: LegacyAPICaller, request: KibanaRequest) {
isFullLicense(); // const hasMlCapabilities = getHasMlCapabilities(request);
return jobServiceProvider(callAsCurrentUser); 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);
},
};
}, },
}; };
} }

View file

@ -4,89 +4,59 @@
* you may not use this file except in compliance with the Elastic License. * you may not use this file except in compliance with the Elastic License.
*/ */
import { LegacyAPICaller, SavedObjectsClientContract } from 'kibana/server'; import { LegacyAPICaller, KibanaRequest, SavedObjectsClientContract } from 'kibana/server';
import { LicenseCheck } from '../license_checks'; import { DataRecognizer } from '../../models/data_recognizer';
import { DataRecognizer, RecognizeResult } from '../../models/data_recognizer'; import { SharedServicesChecks } from '../shared_services';
import {
Module,
DatafeedOverride,
JobOverride,
DataRecognizerConfigResponse,
} from '../../../common/types/modules';
export interface ModulesProvider { export interface ModulesProvider {
modulesProvider( modulesProvider(
callAsCurrentUser: LegacyAPICaller, callAsCurrentUser: LegacyAPICaller,
request: KibanaRequest,
savedObjectsClient: SavedObjectsClientContract savedObjectsClient: SavedObjectsClientContract
): { ): {
recognize(indexPatternTitle: string): Promise<RecognizeResult[]>; recognize: DataRecognizer['findMatches'];
getModule(moduleId?: string): Promise<Module | Module[]>; getModule: DataRecognizer['getModule'];
saveModuleItems( listModules: DataRecognizer['listModules'];
moduleId: string, setupModuleItems: DataRecognizer['setupModuleItems'];
prefix: string,
groups: string[],
indexPatternName: string,
query: any,
useDedicatedIndex: boolean,
startDatafeed: boolean,
start: number,
end: number,
jobOverrides: JobOverride[],
datafeedOverrides: DatafeedOverride[],
estimateModelMemory?: boolean
): Promise<DataRecognizerConfigResponse>;
}; };
} }
export function getModulesProvider(isFullLicense: LicenseCheck): ModulesProvider { export function getModulesProvider({
isFullLicense,
getHasMlCapabilities,
}: SharedServicesChecks): ModulesProvider {
return { return {
modulesProvider( modulesProvider(
callAsCurrentUser: LegacyAPICaller, callAsCurrentUser: LegacyAPICaller,
request: KibanaRequest,
savedObjectsClient: SavedObjectsClientContract savedObjectsClient: SavedObjectsClientContract
) { ) {
isFullLicense(); const hasMlCapabilities = getHasMlCapabilities(request);
const dr = dataRecognizerFactory(callAsCurrentUser, savedObjectsClient);
return { return {
recognize(indexPatternTitle: string) { async recognize(...args) {
const dr = dataRecognizerFactory(callAsCurrentUser, savedObjectsClient); isFullLicense();
return dr.findMatches(indexPatternTitle); await hasMlCapabilities(['canCreateJob']);
return dr.findMatches(...args);
}, },
getModule(moduleId?: string) { async getModule(moduleId: string) {
const dr = dataRecognizerFactory(callAsCurrentUser, savedObjectsClient); isFullLicense();
if (moduleId === undefined) { await hasMlCapabilities(['canGetJobs']);
return dr.listModules();
} else { return dr.getModule(moduleId);
return dr.getModule(moduleId);
}
}, },
saveModuleItems( async listModules() {
moduleId: string, isFullLicense();
prefix: string, await hasMlCapabilities(['canGetJobs']);
groups: string[],
indexPatternName: string, return dr.listModules();
query: any, },
useDedicatedIndex: boolean, async setupModuleItems(...args) {
startDatafeed: boolean, isFullLicense();
start: number, await hasMlCapabilities(['canCreateJob']);
end: number,
jobOverrides: JobOverride[], return dr.setupModuleItems(...args);
datafeedOverrides: DatafeedOverride[],
estimateModelMemory?: boolean
) {
const dr = dataRecognizerFactory(callAsCurrentUser, savedObjectsClient);
return dr.setupModuleItems(
moduleId,
prefix,
groups,
indexPatternName,
query,
useDedicatedIndex,
startDatafeed,
start,
end,
jobOverrides,
datafeedOverrides,
estimateModelMemory
);
}, },
}; };
}, },

View file

@ -4,21 +4,36 @@
* you may not use this file except in compliance with the Elastic License. * you may not use this file except in compliance with the Elastic License.
*/ */
import { LegacyAPICaller } from 'kibana/server'; import { LegacyAPICaller, KibanaRequest } from 'kibana/server';
import { LicenseCheck } from '../license_checks';
import { resultsServiceProvider } from '../../models/results_service'; import { resultsServiceProvider } from '../../models/results_service';
import { SharedServicesChecks } from '../shared_services';
type OrigResultsServiceProvider = ReturnType<typeof resultsServiceProvider>;
export interface ResultsServiceProvider { export interface ResultsServiceProvider {
resultsServiceProvider( resultsServiceProvider(
callAsCurrentUser: LegacyAPICaller callAsCurrentUser: LegacyAPICaller,
): ReturnType<typeof resultsServiceProvider>; request: KibanaRequest
): {
getAnomaliesTableData: OrigResultsServiceProvider['getAnomaliesTableData'];
};
} }
export function getResultsServiceProvider(isFullLicense: LicenseCheck): ResultsServiceProvider { export function getResultsServiceProvider({
isFullLicense,
getHasMlCapabilities,
}: SharedServicesChecks): ResultsServiceProvider {
return { return {
resultsServiceProvider(callAsCurrentUser: LegacyAPICaller) { resultsServiceProvider(callAsCurrentUser: LegacyAPICaller, request: KibanaRequest) {
isFullLicense(); const hasMlCapabilities = getHasMlCapabilities(request);
return resultsServiceProvider(callAsCurrentUser); const { getAnomaliesTableData } = resultsServiceProvider(callAsCurrentUser);
return {
async getAnomaliesTableData(...args) {
isFullLicense();
await hasMlCapabilities(['canGetJobs']);
return getAnomaliesTableData(...args);
},
};
}, },
}; };
} }

View file

@ -8,13 +8,13 @@ import { LegacyAPICaller, KibanaRequest } from 'kibana/server';
import { SearchResponse, SearchParams } from 'elasticsearch'; import { SearchResponse, SearchParams } from 'elasticsearch';
import { MlServerLicense } from '../../lib/license'; import { MlServerLicense } from '../../lib/license';
import { CloudSetup } from '../../../../cloud/server'; import { CloudSetup } from '../../../../cloud/server';
import { LicenseCheck } from '../license_checks';
import { spacesUtilsProvider } from '../../lib/spaces_utils'; import { spacesUtilsProvider } from '../../lib/spaces_utils';
import { SpacesPluginSetup } from '../../../../spaces/server'; import { SpacesPluginSetup } from '../../../../spaces/server';
import { capabilitiesProvider } from '../../lib/capabilities'; import { capabilitiesProvider } from '../../lib/capabilities';
import { MlInfoResponse } from '../../../common/types/ml_server_info'; import { MlInfoResponse } from '../../../common/types/ml_server_info';
import { ML_RESULTS_INDEX_PATTERN } from '../../../common/constants/index_patterns'; import { ML_RESULTS_INDEX_PATTERN } from '../../../common/constants/index_patterns';
import { MlCapabilitiesResponse, ResolveMlCapabilities } from '../../../common/types/capabilities'; import { MlCapabilitiesResponse, ResolveMlCapabilities } from '../../../common/types/capabilities';
import { SharedServicesChecks } from '../shared_services';
export interface MlSystemProvider { export interface MlSystemProvider {
mlSystemProvider( mlSystemProvider(
@ -28,8 +28,7 @@ export interface MlSystemProvider {
} }
export function getMlSystemProvider( export function getMlSystemProvider(
isMinimumLicense: LicenseCheck, { isMinimumLicense, isFullLicense, getHasMlCapabilities }: SharedServicesChecks,
isFullLicense: LicenseCheck,
mlLicense: MlServerLicense, mlLicense: MlServerLicense,
spaces: SpacesPluginSetup | undefined, spaces: SpacesPluginSetup | undefined,
cloud: CloudSetup | undefined, cloud: CloudSetup | undefined,
@ -37,6 +36,7 @@ export function getMlSystemProvider(
): MlSystemProvider { ): MlSystemProvider {
return { return {
mlSystemProvider(callAsCurrentUser: LegacyAPICaller, request: KibanaRequest) { mlSystemProvider(callAsCurrentUser: LegacyAPICaller, request: KibanaRequest) {
// const hasMlCapabilities = getHasMlCapabilities(request);
return { return {
async mlCapabilities() { async mlCapabilities() {
isMinimumLicense(); isMinimumLicense();
@ -48,7 +48,7 @@ export function getMlSystemProvider(
const mlCapabilities = await resolveMlCapabilities(request); const mlCapabilities = await resolveMlCapabilities(request);
if (mlCapabilities === null) { if (mlCapabilities === null) {
throw new Error('resolveMlCapabilities is not defined'); throw new Error('mlCapabilities is not defined');
} }
const { getCapabilities } = capabilitiesProvider( const { getCapabilities } = capabilitiesProvider(
@ -61,6 +61,7 @@ export function getMlSystemProvider(
}, },
async mlInfo(): Promise<MlInfoResponse> { async mlInfo(): Promise<MlInfoResponse> {
isMinimumLicense(); isMinimumLicense();
const info = await callAsCurrentUser('ml.info'); const info = await callAsCurrentUser('ml.info');
const cloudId = cloud && cloud.cloudId; const cloudId = cloud && cloud.cloudId;
return { return {
@ -70,6 +71,11 @@ export function getMlSystemProvider(
}, },
async mlAnomalySearch<T>(searchParams: SearchParams): Promise<SearchResponse<T>> { async mlAnomalySearch<T>(searchParams: SearchParams): Promise<SearchResponse<T>> {
isFullLicense(); 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', { return callAsCurrentUser('search', {
...searchParams, ...searchParams,
index: ML_RESULTS_INDEX_PATTERN, index: ML_RESULTS_INDEX_PATTERN,

View file

@ -4,6 +4,7 @@
* you may not use this file except in compliance with the Elastic License. * you may not use this file except in compliance with the Elastic License.
*/ */
import { KibanaRequest } from 'kibana/server';
import { MlServerLicense } from '../lib/license'; import { MlServerLicense } from '../lib/license';
import { SpacesPluginSetup } from '../../../spaces/server'; import { SpacesPluginSetup } from '../../../spaces/server';
@ -18,6 +19,7 @@ import {
getAnomalyDetectorsProvider, getAnomalyDetectorsProvider,
} from './providers/anomaly_detectors'; } from './providers/anomaly_detectors';
import { ResolveMlCapabilities } from '../../common/types/capabilities'; import { ResolveMlCapabilities } from '../../common/types/capabilities';
import { hasMlCapabilitiesProvider, HasMlCapabilities } from '../lib/capabilities';
export type SharedServices = JobServiceProvider & export type SharedServices = JobServiceProvider &
AnomalyDetectorsProvider & AnomalyDetectorsProvider &
@ -25,6 +27,12 @@ export type SharedServices = JobServiceProvider &
ModulesProvider & ModulesProvider &
ResultsServiceProvider; ResultsServiceProvider;
export interface SharedServicesChecks {
isFullLicense(): void;
isMinimumLicense(): void;
getHasMlCapabilities(request: KibanaRequest): HasMlCapabilities;
}
export function createSharedServices( export function createSharedServices(
mlLicense: MlServerLicense, mlLicense: MlServerLicense,
spaces: SpacesPluginSetup | undefined, spaces: SpacesPluginSetup | undefined,
@ -32,19 +40,18 @@ export function createSharedServices(
resolveMlCapabilities: ResolveMlCapabilities resolveMlCapabilities: ResolveMlCapabilities
): SharedServices { ): SharedServices {
const { isFullLicense, isMinimumLicense } = licenseChecks(mlLicense); const { isFullLicense, isMinimumLicense } = licenseChecks(mlLicense);
const getHasMlCapabilities = hasMlCapabilitiesProvider(resolveMlCapabilities);
const checks: SharedServicesChecks = {
isFullLicense,
isMinimumLicense,
getHasMlCapabilities,
};
return { return {
...getJobServiceProvider(isFullLicense), ...getJobServiceProvider(checks),
...getAnomalyDetectorsProvider(isFullLicense), ...getAnomalyDetectorsProvider(checks),
...getMlSystemProvider( ...getModulesProvider(checks),
isMinimumLicense, ...getResultsServiceProvider(checks),
isFullLicense, ...getMlSystemProvider(checks, mlLicense, spaces, cloud, resolveMlCapabilities),
mlLicense,
spaces,
cloud,
resolveMlCapabilities
),
...getModulesProvider(isFullLicense),
...getResultsServiceProvider(isFullLicense),
}; };
} }

View file

@ -164,9 +164,11 @@ export const signalRulesAlertType = ({
} }
const scopedMlCallCluster = services.getScopedCallCluster(ml.mlClient); const scopedMlCallCluster = services.getScopedCallCluster(ml.mlClient);
const summaryJobs = await ml // Using fake KibanaRequest as it is needed to satisfy the ML Services API, but can be empty as it is
.jobServiceProvider(scopedMlCallCluster) // currently unused by the jobsSummary function.
.jobsSummary([machineLearningJobId]); const summaryJobs = await (
await ml.jobServiceProvider(scopedMlCallCluster, ({} as unknown) as KibanaRequest)
).jobsSummary([machineLearningJobId]);
const jobSummary = summaryJobs.find((job) => job.id === machineLearningJobId); const jobSummary = summaryJobs.find((job) => job.id === machineLearningJobId);
if (jobSummary == null || !isJobStarted(jobSummary.jobState, jobSummary.datafeedState)) { if (jobSummary == null || !isJobStarted(jobSummary.jobState, jobSummary.datafeedState)) {
@ -184,7 +186,7 @@ export const signalRulesAlertType = ({
const anomalyResults = await findMlSignals({ const anomalyResults = await findMlSignals({
ml, ml,
callCluster: scopedMlCallCluster, 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. // currently unused by the mlAnomalySearch function.
request: ({} as unknown) as KibanaRequest, request: ({} as unknown) as KibanaRequest,
jobId: machineLearningJobId, jobId: machineLearningJobId,