mirror of
https://github.com/elastic/kibana.git
synced 2025-04-24 17:59:23 -04:00
[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:
parent
b7f33b94a8
commit
eafd2af6aa
20 changed files with 229 additions and 137 deletions
|
@ -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,
|
||||
};
|
||||
}
|
||||
|
|
|
@ -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 {
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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', () => {
|
||||
|
|
|
@ -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<boolean>
|
||||
|
@ -47,3 +55,27 @@ function disableAdminPrivileges(capabilities: MlCapabilities) {
|
|||
capabilities.canCreateAnnotation = 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');
|
||||
}
|
||||
};
|
||||
};
|
||||
}
|
||||
|
|
28
x-pack/plugins/ml/server/lib/capabilities/errors.ts
Normal file
28
x-pack/plugins/ml/server/lib/capabilities/errors.ts
Normal 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);
|
||||
}
|
||||
}
|
|
@ -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';
|
||||
|
|
|
@ -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<boolean> {
|
||||
let upgradeInProgress = false;
|
||||
try {
|
||||
|
|
|
@ -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<any>;
|
||||
|
||||
constructor(callAsCurrentUser: ILegacyScopedClusterClient['callAsCurrentUser']) {
|
||||
constructor(callAsCurrentUser: LegacyAPICaller) {
|
||||
this.callAsCurrentUser = callAsCurrentUser;
|
||||
}
|
||||
|
||||
|
|
|
@ -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[]) {
|
||||
|
|
|
@ -51,7 +51,7 @@ import { registerKibanaSettings } from './lib/register_settings';
|
|||
|
||||
declare module 'kibana/server' {
|
||||
interface RequestHandlerContext {
|
||||
ml?: {
|
||||
[PLUGIN_ID]?: {
|
||||
mlClient: ILegacyScopedClusterClient;
|
||||
};
|
||||
}
|
||||
|
|
|
@ -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 {
|
||||
|
|
|
@ -6,3 +6,4 @@
|
|||
|
||||
export * from '../common/types/anomalies';
|
||||
export * from '../common/types/anomaly_detection_jobs';
|
||||
export * from './lib/capabilities/errors';
|
||||
|
|
|
@ -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 } : {});
|
||||
},
|
||||
};
|
||||
|
|
|
@ -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<typeof 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 {
|
||||
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);
|
||||
},
|
||||
};
|
||||
},
|
||||
};
|
||||
}
|
||||
|
|
|
@ -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<RecognizeResult[]>;
|
||||
getModule(moduleId?: string): Promise<Module | Module[]>;
|
||||
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<DataRecognizerConfigResponse>;
|
||||
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);
|
||||
},
|
||||
};
|
||||
},
|
||||
|
|
|
@ -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<typeof resultsServiceProvider>;
|
||||
|
||||
export interface ResultsServiceProvider {
|
||||
resultsServiceProvider(
|
||||
callAsCurrentUser: LegacyAPICaller
|
||||
): ReturnType<typeof resultsServiceProvider>;
|
||||
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);
|
||||
},
|
||||
};
|
||||
},
|
||||
};
|
||||
}
|
||||
|
|
|
@ -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<MlInfoResponse> {
|
||||
isMinimumLicense();
|
||||
|
||||
const info = await callAsCurrentUser('ml.info');
|
||||
const cloudId = cloud && cloud.cloudId;
|
||||
return {
|
||||
|
@ -70,6 +71,11 @@ export function getMlSystemProvider(
|
|||
},
|
||||
async mlAnomalySearch<T>(searchParams: SearchParams): Promise<SearchResponse<T>> {
|
||||
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,
|
||||
|
|
|
@ -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),
|
||||
};
|
||||
}
|
||||
|
|
|
@ -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,
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue