kibana/x-pack/plugins/ml/server/plugin.ts
Kibana Machine d4f7bad3e7
[8.x] [ML] Adds ML tasks to the kibana audit log (#195120) (#196099)
# Backport

This will backport the following commits from `main` to `8.x`:
- [[ML] Adds ML tasks to the kibana audit log
(#195120)](https://github.com/elastic/kibana/pull/195120)

<!--- Backport version: 9.4.3 -->

### Questions ?
Please refer to the [Backport tool
documentation](https://github.com/sqren/backport)

<!--BACKPORT [{"author":{"name":"James
Gowdy","email":"jgowdy@elastic.co"},"sourceCommit":{"committedDate":"2024-10-14T10:37:56Z","message":"[ML]
Adds ML tasks to the kibana audit log (#195120)\n\nAdds a new
`MlAuditLogger` service for logging calls to elasticsearch
in\r\nkibana's audit log.\r\nNot all calls are logged, only ones which
make changes to ML jobs or\r\ntrained models, e.g. creating, deleting,
starting, stopping etc.\r\n\r\nCalls to the es client are wrapped in a
logging function so successes\r\nand failures can be caught and
logged.\r\n\r\nthe audit log can be enabed by adding this to the kibana
yml or dev.yml\r\nfile\r\n`xpack.security.audit.enabled: true`\r\n\r\nAn
example log entry (NDJSON formatted to make it
readable):\r\n```\r\n{\r\n \"event\": {\r\n \"action\":
\"ml_start_ad_datafeed\",\r\n \"type\": [\r\n \"change\"\r\n ],\r\n
\"category\": [\r\n \"database\"\r\n ],\r\n \"outcome\": \"success\"\r\n
},\r\n \"labels\": {\r\n \"application\": \"elastic/ml\"\r\n },\r\n
\"user\": {\r\n \"id\":
\"u_mGBROF_q5bmFCATbLXAcCwKa0k8JvONAwSruelyKA5E_0\",\r\n \"name\":
\"elastic\",\r\n \"roles\": [\r\n \"superuser\"\r\n ]\r\n },\r\n
\"kibana\": {\r\n \"space_id\": \"default\",\r\n \"session_id\":
\"U6HQCDkk+fAEUCXs7i4qM2/MZITPxE02pp8o7h09P68=\"\r\n },\r\n \"trace\":
{\r\n \"id\": \"4f1b616b-8535-43e1-8516-32ea9fe76d19\"\r\n },\r\n
\"client\": {\r\n \"ip\": \"127.0.0.1\"\r\n },\r\n \"http\": {\r\n
\"request\": {\r\n \"headers\": {\r\n \"x-forwarded-for\":
\"127.0.0.1\"\r\n }\r\n }\r\n },\r\n \"service\": {\r\n \"node\": {\r\n
\"roles\": [\r\n \"background_tasks\",\r\n \"ui\"\r\n ]\r\n }\r\n },\r\n
\"ecs\": {\r\n \"version\": \"8.11.0\"\r\n },\r\n \"@timestamp\":
\"2024-10-11T09:07:47.933+01:00\",\r\n \"message\": \"Starting anomaly
detection datafeed datafeed-11aaaa\",\r\n \"log\": {\r\n \"level\":
\"INFO\",\r\n \"logger\": \"plugins.security.audit.ecs\"\r\n },\r\n
\"process\": {\r\n \"pid\": 58305,\r\n \"uptime\": 100.982390291\r\n
},\r\n \"transaction\": {\r\n \"id\": \"77c14aadc6901324\"\r\n
}\r\n}\r\n```\r\n\r\n---------\r\n\r\nCo-authored-by: kibanamachine
<42973632+kibanamachine@users.noreply.github.com>","sha":"923c450c1b044a12dd938c0c5ea380a895eeaf88","branchLabelMapping":{"^v9.0.0$":"main","^v8.16.0$":"8.x","^v(\\d+).(\\d+).\\d+$":"$1.$2"}},"sourcePullRequest":{"labels":["release_note:enhancement",":ml","v9.0.0","v8.16.0","backport:version"],"title":"[ML]
Adds ML tasks to the kibana audit
log","number":195120,"url":"https://github.com/elastic/kibana/pull/195120","mergeCommit":{"message":"[ML]
Adds ML tasks to the kibana audit log (#195120)\n\nAdds a new
`MlAuditLogger` service for logging calls to elasticsearch
in\r\nkibana's audit log.\r\nNot all calls are logged, only ones which
make changes to ML jobs or\r\ntrained models, e.g. creating, deleting,
starting, stopping etc.\r\n\r\nCalls to the es client are wrapped in a
logging function so successes\r\nand failures can be caught and
logged.\r\n\r\nthe audit log can be enabed by adding this to the kibana
yml or dev.yml\r\nfile\r\n`xpack.security.audit.enabled: true`\r\n\r\nAn
example log entry (NDJSON formatted to make it
readable):\r\n```\r\n{\r\n \"event\": {\r\n \"action\":
\"ml_start_ad_datafeed\",\r\n \"type\": [\r\n \"change\"\r\n ],\r\n
\"category\": [\r\n \"database\"\r\n ],\r\n \"outcome\": \"success\"\r\n
},\r\n \"labels\": {\r\n \"application\": \"elastic/ml\"\r\n },\r\n
\"user\": {\r\n \"id\":
\"u_mGBROF_q5bmFCATbLXAcCwKa0k8JvONAwSruelyKA5E_0\",\r\n \"name\":
\"elastic\",\r\n \"roles\": [\r\n \"superuser\"\r\n ]\r\n },\r\n
\"kibana\": {\r\n \"space_id\": \"default\",\r\n \"session_id\":
\"U6HQCDkk+fAEUCXs7i4qM2/MZITPxE02pp8o7h09P68=\"\r\n },\r\n \"trace\":
{\r\n \"id\": \"4f1b616b-8535-43e1-8516-32ea9fe76d19\"\r\n },\r\n
\"client\": {\r\n \"ip\": \"127.0.0.1\"\r\n },\r\n \"http\": {\r\n
\"request\": {\r\n \"headers\": {\r\n \"x-forwarded-for\":
\"127.0.0.1\"\r\n }\r\n }\r\n },\r\n \"service\": {\r\n \"node\": {\r\n
\"roles\": [\r\n \"background_tasks\",\r\n \"ui\"\r\n ]\r\n }\r\n },\r\n
\"ecs\": {\r\n \"version\": \"8.11.0\"\r\n },\r\n \"@timestamp\":
\"2024-10-11T09:07:47.933+01:00\",\r\n \"message\": \"Starting anomaly
detection datafeed datafeed-11aaaa\",\r\n \"log\": {\r\n \"level\":
\"INFO\",\r\n \"logger\": \"plugins.security.audit.ecs\"\r\n },\r\n
\"process\": {\r\n \"pid\": 58305,\r\n \"uptime\": 100.982390291\r\n
},\r\n \"transaction\": {\r\n \"id\": \"77c14aadc6901324\"\r\n
}\r\n}\r\n```\r\n\r\n---------\r\n\r\nCo-authored-by: kibanamachine
<42973632+kibanamachine@users.noreply.github.com>","sha":"923c450c1b044a12dd938c0c5ea380a895eeaf88"}},"sourceBranch":"main","suggestedTargetBranches":["8.x"],"targetPullRequestStates":[{"branch":"main","label":"v9.0.0","branchLabelMappingKey":"^v9.0.0$","isSourceBranch":true,"state":"MERGED","url":"https://github.com/elastic/kibana/pull/195120","number":195120,"mergeCommit":{"message":"[ML]
Adds ML tasks to the kibana audit log (#195120)\n\nAdds a new
`MlAuditLogger` service for logging calls to elasticsearch
in\r\nkibana's audit log.\r\nNot all calls are logged, only ones which
make changes to ML jobs or\r\ntrained models, e.g. creating, deleting,
starting, stopping etc.\r\n\r\nCalls to the es client are wrapped in a
logging function so successes\r\nand failures can be caught and
logged.\r\n\r\nthe audit log can be enabed by adding this to the kibana
yml or dev.yml\r\nfile\r\n`xpack.security.audit.enabled: true`\r\n\r\nAn
example log entry (NDJSON formatted to make it
readable):\r\n```\r\n{\r\n \"event\": {\r\n \"action\":
\"ml_start_ad_datafeed\",\r\n \"type\": [\r\n \"change\"\r\n ],\r\n
\"category\": [\r\n \"database\"\r\n ],\r\n \"outcome\": \"success\"\r\n
},\r\n \"labels\": {\r\n \"application\": \"elastic/ml\"\r\n },\r\n
\"user\": {\r\n \"id\":
\"u_mGBROF_q5bmFCATbLXAcCwKa0k8JvONAwSruelyKA5E_0\",\r\n \"name\":
\"elastic\",\r\n \"roles\": [\r\n \"superuser\"\r\n ]\r\n },\r\n
\"kibana\": {\r\n \"space_id\": \"default\",\r\n \"session_id\":
\"U6HQCDkk+fAEUCXs7i4qM2/MZITPxE02pp8o7h09P68=\"\r\n },\r\n \"trace\":
{\r\n \"id\": \"4f1b616b-8535-43e1-8516-32ea9fe76d19\"\r\n },\r\n
\"client\": {\r\n \"ip\": \"127.0.0.1\"\r\n },\r\n \"http\": {\r\n
\"request\": {\r\n \"headers\": {\r\n \"x-forwarded-for\":
\"127.0.0.1\"\r\n }\r\n }\r\n },\r\n \"service\": {\r\n \"node\": {\r\n
\"roles\": [\r\n \"background_tasks\",\r\n \"ui\"\r\n ]\r\n }\r\n },\r\n
\"ecs\": {\r\n \"version\": \"8.11.0\"\r\n },\r\n \"@timestamp\":
\"2024-10-11T09:07:47.933+01:00\",\r\n \"message\": \"Starting anomaly
detection datafeed datafeed-11aaaa\",\r\n \"log\": {\r\n \"level\":
\"INFO\",\r\n \"logger\": \"plugins.security.audit.ecs\"\r\n },\r\n
\"process\": {\r\n \"pid\": 58305,\r\n \"uptime\": 100.982390291\r\n
},\r\n \"transaction\": {\r\n \"id\": \"77c14aadc6901324\"\r\n
}\r\n}\r\n```\r\n\r\n---------\r\n\r\nCo-authored-by: kibanamachine
<42973632+kibanamachine@users.noreply.github.com>","sha":"923c450c1b044a12dd938c0c5ea380a895eeaf88"}},{"branch":"8.x","label":"v8.16.0","branchLabelMappingKey":"^v8.16.0$","isSourceBranch":false,"state":"NOT_CREATED"}]}]
BACKPORT-->

Co-authored-by: James Gowdy <jgowdy@elastic.co>
2024-10-14 07:27:43 -05:00

363 lines
13 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
* 2.0; you may not use this file except in compliance with the Elastic License
* 2.0.
*/
import { i18n } from '@kbn/i18n';
import type {
CoreSetup,
CoreStart,
Plugin,
KibanaRequest,
Logger,
PluginInitializerContext,
CapabilitiesStart,
IClusterClient,
SavedObjectsServiceStart,
UiSettingsServiceStart,
CoreAuditService,
} from '@kbn/core/server';
import { DEFAULT_APP_CATEGORIES } from '@kbn/core/server';
import type { SecurityPluginSetup } from '@kbn/security-plugin/server';
import type { PluginStart as DataViewsPluginStart } from '@kbn/data-views-plugin/server';
import type { SpacesPluginSetup } from '@kbn/spaces-plugin/server';
import type { FieldFormatsStart } from '@kbn/field-formats-plugin/server';
import type { HomeServerPluginSetup } from '@kbn/home-plugin/server';
import type { CasesServerSetup } from '@kbn/cases-plugin/server';
import { KibanaFeatureScope } from '@kbn/features-plugin/common';
import type { PluginsSetup, PluginsStart, RouteInitialization } from './types';
import type { MlCapabilities } from '../common/types/capabilities';
import { notificationsRoutes } from './routes/notifications';
import {
type MlFeatures,
PLUGIN_ID,
type ConfigSchema,
initEnabledFeatures,
type CompatibleModule,
} from '../common/constants/app';
import { initMlServerLog } from './lib/log';
import { annotationRoutes } from './routes/annotations';
import { calendars } from './routes/calendars';
import { dataFeedRoutes } from './routes/datafeeds';
import { dataFrameAnalyticsRoutes } from './routes/data_frame_analytics';
import { dataRecognizer } from './routes/modules';
import { dataVisualizerRoutes } from './routes/data_visualizer';
import { fieldsService } from './routes/fields_service';
import { filtersRoutes } from './routes/filters';
import { jobAuditMessagesRoutes } from './routes/job_audit_messages';
import { jobRoutes } from './routes/anomaly_detectors';
import { jobServiceRoutes } from './routes/job_service';
import { savedObjectsRoutes } from './routes/saved_objects';
import { jobValidationRoutes } from './routes/job_validation';
import { resultsServiceRoutes } from './routes/results_service';
import { modelManagementRoutes } from './routes/model_management';
import { systemRoutes } from './routes/system';
import { MlLicense } from '../common/license';
import type { SharedServices } from './shared_services';
import { createSharedServices } from './shared_services';
import { getPluginPrivileges } from '../common/types/capabilities';
import { setupCapabilitiesSwitcher } from './lib/capabilities';
import { registerKibanaSettings } from './lib/register_settings';
import { trainedModelsRoutes } from './routes/trained_models';
import { managementRoutes } from './routes/management';
import {
setupSavedObjects,
jobSavedObjectsInitializationFactory,
savedObjectClientsFactory,
} from './saved_objects';
import { RouteGuard } from './lib/route_guard';
import { registerMlAlerts } from './lib/alerts/register_ml_alerts';
import { ML_ALERT_TYPES } from '../common/constants/alerts';
import { alertingRoutes } from './routes/alerting';
import { registerCollector } from './usage';
import { SavedObjectsSyncService } from './saved_objects/sync_task';
import { registerCasesPersistableState } from './lib/register_cases';
import { registerSampleDataSetLinks } from './lib/register_sample_data_set_links';
import { inferenceModelRoutes } from './routes/inference_models';
export type MlPluginSetup = SharedServices;
export type MlPluginStart = void;
export class MlServerPlugin
implements Plugin<MlPluginSetup, MlPluginStart, PluginsSetup, PluginsStart>
{
private log: Logger;
private mlLicense: MlLicense;
private capabilities: CapabilitiesStart | null = null;
private clusterClient: IClusterClient | null = null;
private fieldsFormat: FieldFormatsStart | null = null;
private uiSettings: UiSettingsServiceStart | null = null;
private savedObjectsStart: SavedObjectsServiceStart | null = null;
private spacesPlugin: SpacesPluginSetup | undefined;
private security: SecurityPluginSetup | undefined;
private home: HomeServerPluginSetup | null = null;
private cases: CasesServerSetup | null | undefined = null;
private dataViews: DataViewsPluginStart | null = null;
private auditService: CoreAuditService | null = null;
private isMlReady: Promise<void>;
private setMlReady: () => void = () => {};
private savedObjectsSyncService: SavedObjectsSyncService;
private enabledFeatures: MlFeatures = {
ad: true,
dfa: true,
nlp: true,
};
private compatibleModuleType: CompatibleModule | null = null;
constructor(ctx: PluginInitializerContext<ConfigSchema>) {
this.log = ctx.logger.get();
this.mlLicense = new MlLicense();
this.isMlReady = new Promise((resolve) => (this.setMlReady = resolve));
this.savedObjectsSyncService = new SavedObjectsSyncService(this.log);
const config = ctx.config.get();
initEnabledFeatures(this.enabledFeatures, config);
this.compatibleModuleType = config.compatibleModuleType ?? null;
this.enabledFeatures = Object.freeze(this.enabledFeatures);
}
public setup(coreSetup: CoreSetup<PluginsStart>, plugins: PluginsSetup): MlPluginSetup {
this.spacesPlugin = plugins.spaces;
this.security = plugins.security;
this.home = plugins.home;
this.cases = plugins.cases;
const { admin, user, apmUser } = getPluginPrivileges();
plugins.features.registerKibanaFeature({
id: PLUGIN_ID,
name: i18n.translate('xpack.ml.featureRegistry.mlFeatureName', {
defaultMessage: 'Machine Learning',
}),
order: 500,
category: DEFAULT_APP_CATEGORIES.kibana,
scope: [KibanaFeatureScope.Spaces, KibanaFeatureScope.Security],
app: [PLUGIN_ID, 'kibana'],
catalogue: [PLUGIN_ID, `${PLUGIN_ID}_file_data_visualizer`],
privilegesTooltip: i18n.translate('xpack.ml.featureRegistry.privilegesTooltip', {
defaultMessage:
'Granting All or Read feature privilege for Machine Learning will also grant the equivalent feature privileges to certain types of Kibana saved objects, namely index patterns, dashboards, saved searches and visualizations as well as machine learning job, trained model and module saved objects.',
}),
management: {
insightsAndAlerting: ['jobsListLink', 'triggersActions'],
},
alerting: Object.values(ML_ALERT_TYPES),
privileges: {
all: admin,
read: user,
},
reserved: {
description: i18n.translate('xpack.ml.feature.reserved.description', {
defaultMessage:
'To grant users access, you should also assign either the machine_learning_user or machine_learning_admin role.',
}),
privileges: [
{
id: 'ml_user',
privilege: user,
},
{
id: 'ml_admin',
privilege: admin,
},
{
id: 'ml_apm_user',
privilege: apmUser,
},
],
},
});
// initialize capabilities switcher to add license filter to ml capabilities
setupCapabilitiesSwitcher(
coreSetup,
plugins.licensing.license$,
this.enabledFeatures,
this.log
);
setupSavedObjects(coreSetup.savedObjects);
this.savedObjectsSyncService.registerSyncTask(
plugins.taskManager,
plugins.security,
this.spacesPlugin !== undefined,
() => this.isMlReady
);
const { getInternalSavedObjectsClient, getMlSavedObjectsClient } = savedObjectClientsFactory(
() => this.savedObjectsStart
);
const getSpaces = plugins.spaces
? () => coreSetup.getStartServices().then(([, { spaces }]) => spaces!)
: undefined;
const getDataViews = () => {
if (this.dataViews === null) {
throw Error('Data views plugin not initialized');
}
return this.dataViews;
};
const resolveMlCapabilities = async (request: KibanaRequest) => {
if (this.capabilities === null) {
return null;
}
const capabilities = await this.capabilities.resolveCapabilities(request, {
capabilityPath: 'ml.*',
});
return capabilities.ml as MlCapabilities;
};
const { internalServicesProviders, sharedServicesProviders } = createSharedServices(
this.mlLicense,
getSpaces,
plugins.cloud,
plugins.security?.authz,
resolveMlCapabilities,
() => this.clusterClient,
() => getInternalSavedObjectsClient(),
() => this.uiSettings,
() => this.fieldsFormat,
getDataViews,
() => this.auditService,
() => this.isMlReady,
this.compatibleModuleType
);
const routeInit: RouteInitialization = {
router: coreSetup.http.createRouter(),
routeGuard: new RouteGuard(
this.mlLicense,
getMlSavedObjectsClient,
getInternalSavedObjectsClient,
plugins.spaces,
plugins.security?.authz,
() => this.isMlReady,
() => this.dataViews,
coreSetup.getStartServices
),
mlLicense: this.mlLicense,
getEnabledFeatures: () => this.enabledFeatures,
};
// Register Anomaly Detection routes
if (this.enabledFeatures.ad) {
annotationRoutes(routeInit, plugins.security);
calendars(routeInit);
dataFeedRoutes(routeInit);
dataRecognizer(routeInit, this.compatibleModuleType);
filtersRoutes(routeInit);
jobAuditMessagesRoutes(routeInit);
jobRoutes(routeInit);
jobServiceRoutes(routeInit);
resultsServiceRoutes(routeInit);
jobValidationRoutes(routeInit);
}
// Register Data Frame Analytics routes
if (this.enabledFeatures.dfa) {
dataFrameAnalyticsRoutes(routeInit, plugins.cloud);
}
// Register Trained Model Management routes
if (this.enabledFeatures.dfa || this.enabledFeatures.nlp) {
trainedModelsRoutes(routeInit, plugins.cloud);
}
// Register Miscellaneous routes
inferenceModelRoutes(routeInit, plugins.cloud);
modelManagementRoutes(routeInit);
dataVisualizerRoutes(routeInit);
fieldsService(routeInit);
managementRoutes(routeInit);
savedObjectsRoutes(routeInit, {
getSpaces,
resolveMlCapabilities,
});
systemRoutes(routeInit, {
getSpaces,
cloud: plugins.cloud,
resolveMlCapabilities,
});
notificationsRoutes(routeInit);
alertingRoutes(routeInit, sharedServicesProviders);
initMlServerLog({ log: this.log });
if (plugins.alerting) {
registerMlAlerts(
{
alerting: plugins.alerting,
logger: this.log,
mlSharedServices: sharedServicesProviders,
mlServicesProviders: internalServicesProviders,
},
this.enabledFeatures
);
}
registerKibanaSettings(coreSetup);
if (plugins.usageCollection) {
const getIndexForType = (type: string) =>
coreSetup
.getStartServices()
.then(([coreStart]) => coreStart.savedObjects.getIndexForType(type));
registerCollector(plugins.usageCollection, getIndexForType);
}
return { ...sharedServicesProviders };
}
public start(coreStart: CoreStart, plugins: PluginsStart): MlPluginStart {
this.uiSettings = coreStart.uiSettings;
this.fieldsFormat = plugins.fieldFormats;
this.capabilities = coreStart.capabilities;
this.clusterClient = coreStart.elasticsearch.client;
this.savedObjectsStart = coreStart.savedObjects;
this.auditService = coreStart.security.audit;
this.dataViews = plugins.dataViews;
this.mlLicense.setup(plugins.licensing.license$, async (mlLicense: MlLicense) => {
if (mlLicense.isMlEnabled() === false || mlLicense.isFullLicense() === false) {
try {
await this.savedObjectsSyncService.unscheduleSyncTask(plugins.taskManager);
} catch (e) {
this.log.debug(`Error unscheduling saved objects sync task`, e);
}
return;
}
if (mlLicense.isMlEnabled() && mlLicense.isFullLicense()) {
if (this.cases) {
registerCasesPersistableState(this.cases, this.enabledFeatures, this.log);
}
if (this.home) {
registerSampleDataSetLinks(this.home, this.enabledFeatures, this.log);
}
}
// check whether the job saved objects exist
// and create them if needed.
const { initializeJobs } = jobSavedObjectsInitializationFactory(
coreStart,
this.security,
this.spacesPlugin !== undefined
);
initializeJobs()
.catch((err) => {
this.log.debug(`Error initializing jobs`, err);
})
.finally(() => {
this.setMlReady();
});
this.savedObjectsSyncService.scheduleSyncTask(plugins.taskManager, coreStart).catch((err) => {
this.log.debug(`Error scheduling saved objects sync task`, err);
});
});
}
public stop() {
this.mlLicense.unsubscribe();
}
}