a1e511a727
* [ML] Changing all calls to ML endpoints to use internal user * updating alerting * updating documentation * [ML] Changing all calls to ML endpoints to use internal user * updating alerting * updating documentation * fixing missed types * adding authorization headers to endpoint calls * correcting has privileges call * updating security tests * odd eslint error * adding auth header to module setup * fixing missing auth argument * fixing delete DFA job permission checks * removing debug test tag * removing additional ml privilege checks * adding authorization header to _evaluate * updating alerting cluster client name * code clean up * changing authorizationHeader name * updating alterting documentation * fixing secondary credentials * adding management links * updating SIEM telemetry * fixing merge conflicts * granting access to index patterns Co-authored-by: Elastic Machine <elasticmachine@users.noreply.github.com>
565 lines
17 KiB
TypeScript
565 lines
17 KiB
TypeScript
/*
|
|
* 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.
|
|
*/
|
|
|
|
import { RequestHandlerContext } from 'kibana/server';
|
|
import { wrapError } from '../client/error_wrapper';
|
|
import { analyticsAuditMessagesProvider } from '../models/data_frame_analytics/analytics_audit_messages';
|
|
import { RouteInitialization } from '../types';
|
|
import {
|
|
dataAnalyticsJobConfigSchema,
|
|
dataAnalyticsJobUpdateSchema,
|
|
dataAnalyticsEvaluateSchema,
|
|
dataAnalyticsExplainSchema,
|
|
analyticsIdSchema,
|
|
stopsDataFrameAnalyticsJobQuerySchema,
|
|
deleteDataFrameAnalyticsJobSchema,
|
|
} from './schemas/data_analytics_schema';
|
|
import { IndexPatternHandler } from '../models/data_frame_analytics/index_patterns';
|
|
import { DeleteDataFrameAnalyticsWithIndexStatus } from '../../common/types/data_frame_analytics';
|
|
import { getAuthorizationHeader } from '../lib/request_authorization';
|
|
|
|
function getIndexPatternId(context: RequestHandlerContext, patternName: string) {
|
|
const iph = new IndexPatternHandler(context.core.savedObjects.client);
|
|
return iph.getIndexPatternId(patternName);
|
|
}
|
|
|
|
function deleteDestIndexPatternById(context: RequestHandlerContext, indexPatternId: string) {
|
|
const iph = new IndexPatternHandler(context.core.savedObjects.client);
|
|
return iph.deleteIndexPatternById(indexPatternId);
|
|
}
|
|
|
|
/**
|
|
* Routes for the data frame analytics
|
|
*/
|
|
export function dataFrameAnalyticsRoutes({ router, mlLicense }: RouteInitialization) {
|
|
async function userCanDeleteIndex(
|
|
context: RequestHandlerContext,
|
|
destinationIndex: string
|
|
): Promise<boolean> {
|
|
if (!mlLicense.isSecurityEnabled()) {
|
|
return true;
|
|
}
|
|
const privilege = await context.ml!.mlClient.callAsCurrentUser('ml.privilegeCheck', {
|
|
body: {
|
|
index: [
|
|
{
|
|
names: [destinationIndex], // uses wildcard
|
|
privileges: ['delete_index'],
|
|
},
|
|
],
|
|
},
|
|
});
|
|
if (!privilege) {
|
|
return false;
|
|
}
|
|
return privilege.has_all_requested === true;
|
|
}
|
|
|
|
/**
|
|
* @apiGroup DataFrameAnalytics
|
|
*
|
|
* @api {get} /api/ml/data_frame/analytics Get analytics data
|
|
* @apiName GetDataFrameAnalytics
|
|
* @apiDescription Returns the list of data frame analytics jobs.
|
|
*
|
|
* @apiSuccess {Number} count
|
|
* @apiSuccess {Object[]} data_frame_analytics
|
|
*/
|
|
router.get(
|
|
{
|
|
path: '/api/ml/data_frame/analytics',
|
|
validate: false,
|
|
options: {
|
|
tags: ['access:ml:canGetDataFrameAnalytics'],
|
|
},
|
|
},
|
|
mlLicense.fullLicenseAPIGuard(async (context, request, response) => {
|
|
try {
|
|
const results = await context.ml!.mlClient.callAsInternalUser('ml.getDataFrameAnalytics');
|
|
return response.ok({
|
|
body: results,
|
|
});
|
|
} catch (e) {
|
|
return response.customError(wrapError(e));
|
|
}
|
|
})
|
|
);
|
|
|
|
/**
|
|
* @apiGroup DataFrameAnalytics
|
|
*
|
|
* @api {get} /api/ml/data_frame/analytics/:analyticsId Get analytics data by id
|
|
* @apiName GetDataFrameAnalyticsById
|
|
* @apiDescription Returns the data frame analytics job.
|
|
*
|
|
* @apiSchema (params) analyticsIdSchema
|
|
*/
|
|
router.get(
|
|
{
|
|
path: '/api/ml/data_frame/analytics/{analyticsId}',
|
|
validate: {
|
|
params: analyticsIdSchema,
|
|
},
|
|
options: {
|
|
tags: ['access:ml:canGetDataFrameAnalytics'],
|
|
},
|
|
},
|
|
mlLicense.fullLicenseAPIGuard(async (context, request, response) => {
|
|
try {
|
|
const { analyticsId } = request.params;
|
|
const results = await context.ml!.mlClient.callAsInternalUser('ml.getDataFrameAnalytics', {
|
|
analyticsId,
|
|
});
|
|
return response.ok({
|
|
body: results,
|
|
});
|
|
} catch (e) {
|
|
return response.customError(wrapError(e));
|
|
}
|
|
})
|
|
);
|
|
|
|
/**
|
|
* @apiGroup DataFrameAnalytics
|
|
*
|
|
* @api {get} /api/ml/data_frame/analytics/_stats Get analytics stats
|
|
* @apiName GetDataFrameAnalyticsStats
|
|
* @apiDescription Returns data frame analytics jobs statistics.
|
|
*/
|
|
router.get(
|
|
{
|
|
path: '/api/ml/data_frame/analytics/_stats',
|
|
validate: false,
|
|
options: {
|
|
tags: ['access:ml:canGetDataFrameAnalytics'],
|
|
},
|
|
},
|
|
mlLicense.fullLicenseAPIGuard(async (context, request, response) => {
|
|
try {
|
|
const results = await context.ml!.mlClient.callAsInternalUser(
|
|
'ml.getDataFrameAnalyticsStats'
|
|
);
|
|
return response.ok({
|
|
body: results,
|
|
});
|
|
} catch (e) {
|
|
return response.customError(wrapError(e));
|
|
}
|
|
})
|
|
);
|
|
|
|
/**
|
|
* @apiGroup DataFrameAnalytics
|
|
*
|
|
* @api {get} /api/ml/data_frame/analytics/:analyticsId/_stats Get stats for requested analytics job
|
|
* @apiName GetDataFrameAnalyticsStatsById
|
|
* @apiDescription Returns data frame analytics job statistics.
|
|
*
|
|
* @apiSchema (params) analyticsIdSchema
|
|
*/
|
|
router.get(
|
|
{
|
|
path: '/api/ml/data_frame/analytics/{analyticsId}/_stats',
|
|
validate: {
|
|
params: analyticsIdSchema,
|
|
},
|
|
options: {
|
|
tags: ['access:ml:canGetDataFrameAnalytics'],
|
|
},
|
|
},
|
|
mlLicense.fullLicenseAPIGuard(async (context, request, response) => {
|
|
try {
|
|
const { analyticsId } = request.params;
|
|
const results = await context.ml!.mlClient.callAsInternalUser(
|
|
'ml.getDataFrameAnalyticsStats',
|
|
{
|
|
analyticsId,
|
|
}
|
|
);
|
|
return response.ok({
|
|
body: results,
|
|
});
|
|
} catch (e) {
|
|
return response.customError(wrapError(e));
|
|
}
|
|
})
|
|
);
|
|
|
|
/**
|
|
* @apiGroup DataFrameAnalytics
|
|
*
|
|
* @api {put} /api/ml/data_frame/analytics/:analyticsId Instantiate a data frame analytics job
|
|
* @apiName UpdateDataFrameAnalytics
|
|
* @apiDescription This API creates a data frame analytics job that performs an analysis
|
|
* on the source index and stores the outcome in a destination index.
|
|
*
|
|
* @apiSchema (params) analyticsIdSchema
|
|
* @apiSchema (body) dataAnalyticsJobConfigSchema
|
|
*/
|
|
router.put(
|
|
{
|
|
path: '/api/ml/data_frame/analytics/{analyticsId}',
|
|
validate: {
|
|
params: analyticsIdSchema,
|
|
body: dataAnalyticsJobConfigSchema,
|
|
},
|
|
options: {
|
|
tags: ['access:ml:canCreateDataFrameAnalytics'],
|
|
},
|
|
},
|
|
mlLicense.fullLicenseAPIGuard(async (context, request, response) => {
|
|
try {
|
|
const { analyticsId } = request.params;
|
|
const results = await context.ml!.mlClient.callAsInternalUser(
|
|
'ml.createDataFrameAnalytics',
|
|
{
|
|
body: request.body,
|
|
analyticsId,
|
|
...getAuthorizationHeader(request),
|
|
}
|
|
);
|
|
return response.ok({
|
|
body: results,
|
|
});
|
|
} catch (e) {
|
|
return response.customError(wrapError(e));
|
|
}
|
|
})
|
|
);
|
|
|
|
/**
|
|
* @apiGroup DataFrameAnalytics
|
|
*
|
|
* @api {post} /api/ml/data_frame/_evaluate Evaluate the data frame analytics for an annotated index
|
|
* @apiName EvaluateDataFrameAnalytics
|
|
* @apiDescription Evaluates the data frame analytics for an annotated index.
|
|
*
|
|
* @apiSchema (body) dataAnalyticsEvaluateSchema
|
|
*/
|
|
router.post(
|
|
{
|
|
path: '/api/ml/data_frame/_evaluate',
|
|
validate: {
|
|
body: dataAnalyticsEvaluateSchema,
|
|
},
|
|
options: {
|
|
tags: ['access:ml:canGetDataFrameAnalytics'],
|
|
},
|
|
},
|
|
mlLicense.fullLicenseAPIGuard(async (context, request, response) => {
|
|
try {
|
|
const results = await context.ml!.mlClient.callAsInternalUser(
|
|
'ml.evaluateDataFrameAnalytics',
|
|
{
|
|
body: request.body,
|
|
...getAuthorizationHeader(request),
|
|
}
|
|
);
|
|
return response.ok({
|
|
body: results,
|
|
});
|
|
} catch (e) {
|
|
return response.customError(wrapError(e));
|
|
}
|
|
})
|
|
);
|
|
|
|
/**
|
|
* @apiGroup DataFrameAnalytics
|
|
*
|
|
* @api {post} /api/ml/data_frame/_explain Explain a data frame analytics config
|
|
* @apiName ExplainDataFrameAnalytics
|
|
* @apiDescription This API provides explanations for a data frame analytics config
|
|
* that either exists already or one that has not been created yet.
|
|
*
|
|
* @apiSchema (body) dataAnalyticsExplainSchema
|
|
*/
|
|
router.post(
|
|
{
|
|
path: '/api/ml/data_frame/analytics/_explain',
|
|
validate: {
|
|
body: dataAnalyticsExplainSchema,
|
|
},
|
|
options: {
|
|
tags: ['access:ml:canCreateDataFrameAnalytics'],
|
|
},
|
|
},
|
|
mlLicense.fullLicenseAPIGuard(async (context, request, response) => {
|
|
try {
|
|
const results = await context.ml!.mlClient.callAsInternalUser(
|
|
'ml.explainDataFrameAnalytics',
|
|
{
|
|
body: request.body,
|
|
}
|
|
);
|
|
return response.ok({
|
|
body: results,
|
|
});
|
|
} catch (e) {
|
|
return response.customError(wrapError(e));
|
|
}
|
|
})
|
|
);
|
|
|
|
/**
|
|
* @apiGroup DataFrameAnalytics
|
|
*
|
|
* @api {delete} /api/ml/data_frame/analytics/:analyticsId Delete specified analytics job
|
|
* @apiName DeleteDataFrameAnalytics
|
|
* @apiDescription Deletes specified data frame analytics job.
|
|
*
|
|
* @apiSchema (params) analyticsIdSchema
|
|
*/
|
|
router.delete(
|
|
{
|
|
path: '/api/ml/data_frame/analytics/{analyticsId}',
|
|
validate: {
|
|
params: analyticsIdSchema,
|
|
query: deleteDataFrameAnalyticsJobSchema,
|
|
},
|
|
options: {
|
|
tags: ['access:ml:canDeleteDataFrameAnalytics'],
|
|
},
|
|
},
|
|
mlLicense.fullLicenseAPIGuard(async (context, request, response) => {
|
|
try {
|
|
const { analyticsId } = request.params;
|
|
const { deleteDestIndex, deleteDestIndexPattern } = request.query;
|
|
let destinationIndex: string | undefined;
|
|
const analyticsJobDeleted: DeleteDataFrameAnalyticsWithIndexStatus = { success: false };
|
|
const destIndexDeleted: DeleteDataFrameAnalyticsWithIndexStatus = { success: false };
|
|
const destIndexPatternDeleted: DeleteDataFrameAnalyticsWithIndexStatus = {
|
|
success: false,
|
|
};
|
|
|
|
// Check if analyticsId is valid and get destination index
|
|
if (deleteDestIndex || deleteDestIndexPattern) {
|
|
try {
|
|
const dfa = await context.ml!.mlClient.callAsInternalUser('ml.getDataFrameAnalytics', {
|
|
analyticsId,
|
|
});
|
|
if (Array.isArray(dfa.data_frame_analytics) && dfa.data_frame_analytics.length > 0) {
|
|
destinationIndex = dfa.data_frame_analytics[0].dest.index;
|
|
}
|
|
} catch (e) {
|
|
return response.customError(wrapError(e));
|
|
}
|
|
|
|
// If user checks box to delete the destinationIndex associated with the job
|
|
if (destinationIndex && deleteDestIndex) {
|
|
// Verify if user has privilege to delete the destination index
|
|
const userCanDeleteDestIndex = await userCanDeleteIndex(context, destinationIndex);
|
|
// If user does have privilege to delete the index, then delete the index
|
|
if (userCanDeleteDestIndex) {
|
|
try {
|
|
await context.ml!.mlClient.callAsCurrentUser('indices.delete', {
|
|
index: destinationIndex,
|
|
});
|
|
destIndexDeleted.success = true;
|
|
} catch (deleteIndexError) {
|
|
destIndexDeleted.error = wrapError(deleteIndexError);
|
|
}
|
|
} else {
|
|
return response.forbidden();
|
|
}
|
|
}
|
|
|
|
// Delete the index pattern if there's an index pattern that matches the name of dest index
|
|
if (destinationIndex && deleteDestIndexPattern) {
|
|
try {
|
|
const indexPatternId = await getIndexPatternId(context, destinationIndex);
|
|
if (indexPatternId) {
|
|
await deleteDestIndexPatternById(context, indexPatternId);
|
|
}
|
|
destIndexPatternDeleted.success = true;
|
|
} catch (deleteDestIndexPatternError) {
|
|
destIndexPatternDeleted.error = wrapError(deleteDestIndexPatternError);
|
|
}
|
|
}
|
|
}
|
|
// Grab the target index from the data frame analytics job id
|
|
// Delete the data frame analytics
|
|
|
|
try {
|
|
await context.ml!.mlClient.callAsInternalUser('ml.deleteDataFrameAnalytics', {
|
|
analyticsId,
|
|
});
|
|
analyticsJobDeleted.success = true;
|
|
} catch (deleteDFAError) {
|
|
analyticsJobDeleted.error = wrapError(deleteDFAError);
|
|
if (analyticsJobDeleted.error.statusCode === 404) {
|
|
return response.notFound();
|
|
}
|
|
}
|
|
const results = {
|
|
analyticsJobDeleted,
|
|
destIndexDeleted,
|
|
destIndexPatternDeleted,
|
|
};
|
|
return response.ok({
|
|
body: results,
|
|
});
|
|
} catch (e) {
|
|
return response.customError(wrapError(e));
|
|
}
|
|
})
|
|
);
|
|
|
|
/**
|
|
* @apiGroup DataFrameAnalytics
|
|
*
|
|
* @api {post} /api/ml/data_frame/analytics/:analyticsId/_start Start specified analytics job
|
|
* @apiName StartDataFrameAnalyticsJob
|
|
* @apiDescription Starts a data frame analytics job.
|
|
*
|
|
* @apiSchema (params) analyticsIdSchema
|
|
*/
|
|
router.post(
|
|
{
|
|
path: '/api/ml/data_frame/analytics/{analyticsId}/_start',
|
|
validate: {
|
|
params: analyticsIdSchema,
|
|
},
|
|
options: {
|
|
tags: ['access:ml:canStartStopDataFrameAnalytics'],
|
|
},
|
|
},
|
|
mlLicense.fullLicenseAPIGuard(async (context, request, response) => {
|
|
try {
|
|
const { analyticsId } = request.params;
|
|
const results = await context.ml!.mlClient.callAsInternalUser(
|
|
'ml.startDataFrameAnalytics',
|
|
{
|
|
analyticsId,
|
|
}
|
|
);
|
|
return response.ok({
|
|
body: results,
|
|
});
|
|
} catch (e) {
|
|
return response.customError(wrapError(e));
|
|
}
|
|
})
|
|
);
|
|
|
|
/**
|
|
* @apiGroup DataFrameAnalytics
|
|
*
|
|
* @api {post} /api/ml/data_frame/analytics/:analyticsId/_stop Stop specified analytics job
|
|
* @apiName StopsDataFrameAnalyticsJob
|
|
* @apiDescription Stops a data frame analytics job.
|
|
*
|
|
* @apiSchema (params) analyticsIdSchema
|
|
* @apiSchema (query) stopsDataFrameAnalyticsJobQuerySchema
|
|
*/
|
|
router.post(
|
|
{
|
|
path: '/api/ml/data_frame/analytics/{analyticsId}/_stop',
|
|
validate: {
|
|
params: analyticsIdSchema,
|
|
query: stopsDataFrameAnalyticsJobQuerySchema,
|
|
},
|
|
options: {
|
|
tags: ['access:ml:canStartStopDataFrameAnalytics'],
|
|
},
|
|
},
|
|
mlLicense.fullLicenseAPIGuard(async (context, request, response) => {
|
|
try {
|
|
const options: { analyticsId: string; force?: boolean | undefined } = {
|
|
analyticsId: request.params.analyticsId,
|
|
};
|
|
// @ts-expect-error TODO: update types
|
|
if (request.url?.query?.force !== undefined) {
|
|
// @ts-expect-error TODO: update types
|
|
options.force = request.url.query.force;
|
|
}
|
|
|
|
const results = await context.ml!.mlClient.callAsInternalUser(
|
|
'ml.stopDataFrameAnalytics',
|
|
options
|
|
);
|
|
return response.ok({
|
|
body: results,
|
|
});
|
|
} catch (e) {
|
|
return response.customError(wrapError(e));
|
|
}
|
|
})
|
|
);
|
|
|
|
/**
|
|
* @apiGroup DataFrameAnalytics
|
|
*
|
|
* @api {post} /api/ml/data_frame/analytics/:analyticsId/_update Update specified analytics job
|
|
* @apiName UpdateDataFrameAnalyticsJob
|
|
* @apiDescription Updates a data frame analytics job.
|
|
*
|
|
* @apiSchema (params) analyticsIdSchema
|
|
*/
|
|
router.post(
|
|
{
|
|
path: '/api/ml/data_frame/analytics/{analyticsId}/_update',
|
|
validate: {
|
|
params: analyticsIdSchema,
|
|
body: dataAnalyticsJobUpdateSchema,
|
|
},
|
|
options: {
|
|
tags: ['access:ml:canCreateDataFrameAnalytics'],
|
|
},
|
|
},
|
|
mlLicense.fullLicenseAPIGuard(async (context, request, response) => {
|
|
try {
|
|
const { analyticsId } = request.params;
|
|
const results = await context.ml!.mlClient.callAsCurrentUser(
|
|
'ml.updateDataFrameAnalytics',
|
|
{
|
|
body: request.body,
|
|
analyticsId,
|
|
}
|
|
);
|
|
return response.ok({
|
|
body: results,
|
|
});
|
|
} catch (e) {
|
|
return response.customError(wrapError(e));
|
|
}
|
|
})
|
|
);
|
|
|
|
/**
|
|
* @apiGroup DataFrameAnalytics
|
|
*
|
|
* @api {get} /api/ml/data_frame/analytics/:analyticsId/messages Get analytics job messages
|
|
* @apiName GetDataFrameAnalyticsMessages
|
|
* @apiDescription Returns the list of audit messages for data frame analytics jobs.
|
|
*
|
|
* @apiSchema (params) analyticsIdSchema
|
|
*/
|
|
router.get(
|
|
{
|
|
path: '/api/ml/data_frame/analytics/{analyticsId}/messages',
|
|
validate: {
|
|
params: analyticsIdSchema,
|
|
},
|
|
options: {
|
|
tags: ['access:ml:canGetDataFrameAnalytics'],
|
|
},
|
|
},
|
|
mlLicense.fullLicenseAPIGuard(async (context, request, response) => {
|
|
try {
|
|
const { analyticsId } = request.params;
|
|
const { getAnalyticsAuditMessages } = analyticsAuditMessagesProvider(context.ml!.mlClient);
|
|
|
|
const results = await getAnalyticsAuditMessages(analyticsId);
|
|
return response.ok({
|
|
body: results,
|
|
});
|
|
} catch (e) {
|
|
return response.customError(wrapError(e));
|
|
}
|
|
})
|
|
);
|
|
}
|