[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>
This commit is contained in:
Kibana Machine 2024-10-14 23:27:43 +11:00 committed by GitHub
parent 759501133e
commit d4f7bad3e7
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
8 changed files with 743 additions and 148 deletions

View file

@ -17,20 +17,20 @@ by cluster-wide privileges. For more information on enabling audit logging in
Audit logs are **disabled** by default. To enable this functionality, you must
set `xpack.security.audit.enabled` to `true` in `kibana.yml`.
You can optionally configure audit logs location, file/rolling file appenders and
You can optionally configure audit logs location, file/rolling file appenders and
ignore filters using <<audit-logging-settings>>.
============================================================================
[[xpack-security-ecs-audit-logging]]
==== Audit events
Refer to the table of events that can be logged for auditing purposes.
Refer to the table of events that can be logged for auditing purposes.
Each event is broken down into <<field-event-category, category>>, <<field-event-type, type>>, <<field-event-action, action>> and
<<field-event-outcome, outcome>> fields to make it easy to filter, query and aggregate the resulting logs. The <<field-trace-id, trace.id>>
field can be used to correlate multiple events that originate from the same request.
Refer to <<xpack-security-ecs-audit-schema>> for a table of fields that get logged with audit event.
Refer to <<xpack-security-ecs-audit-schema>> for a table of fields that get logged with audit event.
[NOTE]
============================================================================
@ -116,6 +116,38 @@ Refer to the corresponding {es} logs for potential write errors.
.1+| `case_user_action_create_case`
| `success` | User has created a case.
.2+| `ml_put_ad_job`
| `success` | Creating anomaly detection job.
| `failure` | Failed to create anomaly detection job.
.2+| `ml_put_ad_datafeed`
| `success` | Creating anomaly detection datafeed.
| `failure` | Failed to create anomaly detection datafeed.
.2+| `ml_put_calendar`
| `success` | Creating calendar.
| `failure` | Failed to create calendar.
.2+| `ml_post_calendar_events`
| `success` | Adding events to calendar.
| `failure` | Failed to add events to calendar.
.2+| `ml_forecast`
| `success` | Creating anomaly detection forecast.
| `failure` | Failed to create anomaly detection forecast.
.2+| `ml_put_filter`
| `success` | Creating filter.
| `failure` | Failed to create filter.
.2+| `ml_put_dfa_job`
| `success` | Creating data frame analytics job.
| `failure` | Failed to create data frame analytics job.
.2+| `ml_put_trained_model`
| `success` | Creating trained model.
| `failure` | Failed to create trained model.
3+a|
====== Type: change
@ -234,6 +266,74 @@ Refer to the corresponding {es} logs for potential write errors.
.1+| `case_user_action_update_case_title`
| `success` | User has updated the case title.
.2+| `ml_open_ad_job`
| `success` | Opening anomaly detection job.
| `failure` | Failed to open anomaly detection job.
.2+| `ml_close_ad_job`
| `success` | Closing anomaly detection job.
| `failure` | Failed to close anomaly detection job.
.2+| `ml_start_ad_datafeed`
| `success` | Starting anomaly detection datafeed.
| `failure` | Failed to start anomaly detection datafeed.
.2+| `ml_stop_ad_datafeed`
| `success` | Stopping anomaly detection datafeed.
| `failure` | Failed to stop anomaly detection datafeed.
.2+| `ml_update_ad_job`
| `success` | Updating anomaly detection job.
| `failure` | Failed to update anomaly detection job.
.2+| `ml_reset_ad_job`
| `success` | Resetting anomaly detection job.
| `failure` | Failed to reset anomaly detection job.
.2+| `ml_revert_ad_snapshot`
| `success` | Reverting anomaly detection snapshot.
| `failure` | Failed to revert anomaly detection snapshot.
.2+| `ml_update_ad_datafeed`
| `success` | Updating anomaly detection datafeed.
| `failure` | Failed to update anomaly detection datafeed.
.2+| `ml_put_calendar_job`
| `success` | Adding job to calendar.
| `failure` | Failed to add job to calendar.
.2+| `ml_delete_calendar_job`
| `success` | Removing job from calendar.
| `failure` | Failed to remove job from calendar.
.2+| `ml_update_filter`
| `success` | Updating filter.
| `failure` | Failed to update filter.
.2+| `ml_start_dfa_job`
| `success` | Starting data frame analytics job.
| `failure` | Failed to start data frame analytics job.
.2+| `ml_stop_dfa_job`
| `success` | Stopping data frame analytics job.
| `failure` | Failed to stop data frame analytics job.
.2+| `ml_update_dfa_job`
| `success` | Updating data frame analytics job.
| `failure` | Failed to update data frame analytics job.
.2+| `ml_start_trained_model_deployment`
| `success` | Starting trained model deployment.
| `failure` | Failed to start trained model deployment.
.2+| `ml_stop_trained_model_deployment`
| `success` | Stopping trained model deployment.
| `failure` | Failed to stop trained model deployment.
.2+| `ml_update_trained_model_deployment`
| `success` | Updating trained model deployment.
| `failure` | Failed to update trained model deployment.
3+a|
====== Type: deletion
@ -289,6 +389,42 @@ Refer to the corresponding {es} logs for potential write errors.
.1+| `case_user_action_delete_case_tags`
| `success` | User has removed tags from a case.
.2+| `ml_delete_ad_job`
| `success` | Deleting anomaly detection job.
| `failure` | Failed to delete anomaly detection job.
.2+| `ml_delete_model_snapshot`
| `success` | Deleting model snapshot.
| `failure` | Failed to delete model snapshot.
.2+| `ml_delete_ad_datafeed`
| `success` | Deleting anomaly detection datafeed.
| `failure` | Failed to delete anomaly detection datafeed.
.2+| `ml_delete_calendar`
| `success` | Deleting calendar.
| `failure` | Failed to delete calendar.
.2+| `ml_delete_calendar_event`
| `success` | Deleting calendar event.
| `failure` | Failed to delete calendar event.
.2+| `ml_delete_filter`
| `success` | Deleting filter.
| `failure` | Failed to delete filter.
.2+| `ml_delete_forecast`
| `success` | Deleting forecast.
| `failure` | Failed to delete forecast.
.2+| `ml_delete_dfa_job`
| `success` | Deleting data frame analytics job.
| `failure` | Failed to delete data frame analytics job.
.2+| `ml_delete_trained_model`
| `success` | Deleting trained model.
| `failure` | Failed to delete trained model.
3+a|
====== Type: access
@ -448,6 +584,10 @@ Refer to the corresponding {es} logs for potential write errors.
| `success` | User has accessed the connectors of a case.
| `failure` | User is not authorized to access the connectors of a case.
.2+| `ml_infer_trained_model`
| `success` | Inferring using trained model.
| `failure` | Failed to infer using trained model.
3+a|
===== Category: web
@ -474,12 +614,12 @@ Audit logs are written in JSON using https://www.elastic.co/guide/en/ecs/1.6/ind
| *Description*
| `@timestamp`
| Time when the event was generated.
| Time when the event was generated.
Example: `2016-05-23T08:05:34.853Z`
| `message`
| Human readable description of the event.
| Human readable description of the event.
2+a| ===== Event Fields
@ -489,7 +629,7 @@ Example: `2016-05-23T08:05:34.853Z`
| [[field-event-action]] `event.action`
| The action captured by the event.
Refer to <<xpack-security-ecs-audit-logging>> for a table of possible actions.
Refer to <<xpack-security-ecs-audit-logging>> for a table of possible actions.
| [[field-event-category]] `event.category`
| High level category associated with the event.
@ -513,7 +653,7 @@ Possible values:
`deletion`
| [[field-event-outcome]] `event.outcome`
a| Denotes whether the event represents a success or failure:
a| Denotes whether the event represents a success or failure:
* Any actions that the user is not authorized to perform are logged with outcome: `failure`
* Authorized read operations are only logged after successfully fetching the data from {es} with outcome: `success`
@ -553,7 +693,7 @@ Example: `[kibana_admin, reporting_user]`
Example: `default`
| `kibana.session_id`
| ID of the user session associated with the event.
| ID of the user session associated with the event.
Each login attempt results in a unique session id.
@ -604,7 +744,7 @@ Example: `[marketing]`
| Error code describing the error.
| `error.message`
| Error message.
| Error message.
2+a| ===== HTTP and URL Fields

View file

@ -8,3 +8,4 @@
export { getMlClient } from './ml_client';
export { MLJobNotFound, MLModelNotFound } from './errors';
export type { MlClient } from './types';
export { MlAuditLogger } from './ml_audit_logger';

View file

@ -0,0 +1,383 @@
/*
* 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 type { KibanaRequest } from '@kbn/core-http-server';
import type { AuditLogger, CoreAuditService, EcsEvent } from '@kbn/core/server';
import type { ArrayElement } from '@kbn/utility-types';
import type { MlClient, MlClientParams } from './types';
import {
getADJobIdsFromRequest,
getDFAJobIdsFromRequest,
getDatafeedIdsFromRequest,
getModelIdsFromRequest,
} from './ml_client';
type TaskTypeAD =
| 'ml_put_ad_job'
| 'ml_delete_ad_job'
| 'ml_delete_model_snapshot'
| 'ml_open_ad_job'
| 'ml_close_ad_job'
| 'ml_update_ad_job'
| 'ml_reset_ad_job'
| 'ml_revert_ad_snapshot'
| 'ml_put_ad_datafeed'
| 'ml_delete_ad_datafeed'
| 'ml_start_ad_datafeed'
| 'ml_stop_ad_datafeed'
| 'ml_update_ad_datafeed'
| 'ml_put_calendar'
| 'ml_delete_calendar'
| 'ml_put_calendar_job'
| 'ml_delete_calendar_job'
| 'ml_post_calendar_events'
| 'ml_delete_calendar_event'
| 'ml_put_filter'
| 'ml_update_filter'
| 'ml_delete_filter'
| 'ml_forecast'
| 'ml_delete_forecast';
type TaskTypeDFA =
| 'ml_put_dfa_job'
| 'ml_delete_dfa_job'
| 'ml_start_dfa_job'
| 'ml_stop_dfa_job'
| 'ml_update_dfa_job';
type TaskTypeNLP =
| 'ml_put_trained_model'
| 'ml_delete_trained_model'
| 'ml_start_trained_model_deployment'
| 'ml_stop_trained_model_deployment'
| 'ml_update_trained_model_deployment'
| 'ml_infer_trained_model';
type TaskType = TaskTypeAD | TaskTypeDFA | TaskTypeNLP;
const APPLICATION = 'elastic/ml';
const CATEGORY = 'database';
const EVENT_TYPES: Record<string, ArrayElement<EcsEvent['type']>> = {
creation: 'creation',
deletion: 'deletion',
change: 'change',
access: 'access',
} as const;
type EventTypes = keyof typeof EVENT_TYPES;
interface MlLogEntry {
event: EcsEvent;
message: string;
labels: { application: typeof APPLICATION };
}
export class MlAuditLogger {
private auditLogger: AuditLogger;
constructor(audit: CoreAuditService, request?: KibanaRequest) {
this.auditLogger = request ? audit.asScoped(request) : audit.withoutRequest;
}
public async wrapTask<T, P extends MlClientParams>(task: () => T, taskType: TaskType, p: P) {
try {
const resp = await task();
this.logSuccess(taskType, p);
return resp;
} catch (error) {
this.logFailure(taskType, p);
throw error;
}
}
public logMessage(message: string) {
this.auditLogger.log({
message,
labels: {
application: APPLICATION,
},
});
}
private logSuccess(taskType: TaskType, p: MlClientParams) {
const entry = this.createLogEntry(taskType, p, true);
if (entry) {
this.auditLogger.log(entry);
}
}
private logFailure(taskType: TaskType, p: MlClientParams) {
const entry = this.createLogEntry(taskType, p, false);
if (entry) {
this.auditLogger.log(entry);
}
}
private createLogEntry(
taskType: TaskType,
p: MlClientParams,
success: boolean
): MlLogEntry | undefined {
try {
const { message, type } = this.createPartialLogEntry(taskType, p);
return {
event: {
action: taskType,
type,
category: [CATEGORY],
outcome: success ? 'success' : 'failure',
},
message,
labels: {
application: APPLICATION,
},
};
} catch (error) {
// if an unknown task type is passed, we won't log anything
}
}
private createPartialLogEntry(
taskType: TaskType,
p: MlClientParams
): { message: string; type: EventTypes[] } {
switch (taskType) {
/* Anomaly Detection */
case 'ml_put_ad_job': {
const [jobId] = getADJobIdsFromRequest(p);
return { message: `Creating anomaly detection job ${jobId}`, type: [EVENT_TYPES.creation] };
}
case 'ml_delete_ad_job': {
const [jobId] = getADJobIdsFromRequest(p);
return { message: `Deleting anomaly detection job ${jobId}`, type: [EVENT_TYPES.deletion] };
}
case 'ml_delete_model_snapshot': {
const [jobId] = getADJobIdsFromRequest(p);
const [params] = p as Parameters<MlClient['deleteModelSnapshot']>;
const snapshotId = params.snapshot_id;
return {
message: `Deleting model snapshot ${snapshotId} from job ${jobId}`,
type: [EVENT_TYPES.deletion],
};
}
case 'ml_open_ad_job': {
const [jobId] = getADJobIdsFromRequest(p);
return { message: `Opening anomaly detection job ${jobId}`, type: [EVENT_TYPES.change] };
}
case 'ml_close_ad_job': {
const [jobId] = getADJobIdsFromRequest(p);
return { message: `Closing anomaly detection job ${jobId}`, type: [EVENT_TYPES.change] };
}
case 'ml_update_ad_job': {
const [jobId] = getADJobIdsFromRequest(p);
return { message: `Updating anomaly detection job ${jobId}`, type: [EVENT_TYPES.change] };
}
case 'ml_reset_ad_job': {
const [jobId] = getADJobIdsFromRequest(p);
return { message: `Resetting anomaly detection job ${jobId}`, type: [EVENT_TYPES.change] };
}
case 'ml_revert_ad_snapshot': {
const [jobId] = getADJobIdsFromRequest(p);
const [params] = p as Parameters<MlClient['revertModelSnapshot']>;
const snapshotId = params.snapshot_id;
return {
message: `Reverting anomaly detection snapshot ${snapshotId} in job ${jobId}`,
type: [EVENT_TYPES.change],
};
}
case 'ml_put_ad_datafeed': {
const [datafeedId] = getDatafeedIdsFromRequest(p);
const [jobId] = getADJobIdsFromRequest(p);
return {
message: `Creating anomaly detection datafeed ${datafeedId} for job ${jobId}`,
type: [EVENT_TYPES.creation],
};
}
case 'ml_delete_ad_datafeed': {
const [datafeedId] = getDatafeedIdsFromRequest(p);
return {
message: `Deleting anomaly detection datafeed ${datafeedId}`,
type: [EVENT_TYPES.deletion],
};
}
case 'ml_start_ad_datafeed': {
const [datafeedId] = getDatafeedIdsFromRequest(p);
return {
message: `Starting anomaly detection datafeed ${datafeedId}`,
type: [EVENT_TYPES.change],
};
}
case 'ml_stop_ad_datafeed': {
const [datafeedId] = getDatafeedIdsFromRequest(p);
return {
message: `Stopping anomaly detection datafeed ${datafeedId}`,
type: [EVENT_TYPES.change],
};
}
case 'ml_update_ad_datafeed': {
const [datafeedId] = getDatafeedIdsFromRequest(p);
return {
message: `Updating anomaly detection datafeed ${datafeedId}`,
type: [EVENT_TYPES.change],
};
}
case 'ml_put_calendar': {
const [params] = p as Parameters<MlClient['putCalendar']>;
const calendarId = params.calendar_id;
// @ts-expect-error body is optional
const jobIds = (params.body ?? params).job_ids;
return {
message: `Creating calendar ${calendarId} ${jobIds ? `with job(s) ${jobIds}` : ''}`,
type: [EVENT_TYPES.creation],
};
}
case 'ml_delete_calendar': {
const [params] = p as Parameters<MlClient['deleteCalendar']>;
const calendarId = params.calendar_id;
return { message: `Deleting calendar ${calendarId}`, type: [EVENT_TYPES.deletion] };
}
case 'ml_put_calendar_job': {
const [params] = p as Parameters<MlClient['putCalendarJob']>;
const calendarId = params.calendar_id;
const jobIds = params.job_id;
return {
message: `Adding job(s) ${jobIds} to calendar ${calendarId}`,
type: [EVENT_TYPES.change],
};
}
case 'ml_delete_calendar_job': {
const [params] = p as Parameters<MlClient['deleteCalendarJob']>;
const calendarId = params.calendar_id;
const jobIds = params.job_id;
return {
message: `Removing job(s) ${jobIds} from calendar ${calendarId}`,
type: [EVENT_TYPES.change],
};
}
case 'ml_post_calendar_events': {
const [params] = p as Parameters<MlClient['postCalendarEvents']>;
const calendarId = params.calendar_id;
// @ts-expect-error body is optional
const eventsCount = (params.body ?? params).events;
return {
message: `Adding ${eventsCount} event(s) to calendar ${calendarId}`,
type: [EVENT_TYPES.creation],
};
}
case 'ml_delete_calendar_event': {
const [params] = p as Parameters<MlClient['deleteCalendarEvent']>;
const calendarId = params.calendar_id;
const eventId = params.event_id;
return {
message: `Removing event(s) ${eventId} from calendar ${calendarId}`,
type: [EVENT_TYPES.deletion],
};
}
case 'ml_put_filter': {
const [params] = p as Parameters<MlClient['putFilter']>;
const filterId = params.filter_id;
return { message: `Creating filter ${filterId}`, type: [EVENT_TYPES.creation] };
}
case 'ml_update_filter': {
const [params] = p as Parameters<MlClient['updateFilter']>;
const filterId = params.filter_id;
return { message: `Updating filter ${filterId}`, type: [EVENT_TYPES.change] };
}
case 'ml_delete_filter': {
const [params] = p as Parameters<MlClient['deleteFilter']>;
const filterId = params.filter_id;
return { message: `Deleting filter ${filterId}`, type: [EVENT_TYPES.deletion] };
}
case 'ml_forecast': {
const [jobId] = getADJobIdsFromRequest(p);
return { message: `Forecasting for job ${jobId}`, type: [EVENT_TYPES.creation] };
}
case 'ml_delete_forecast': {
const [params] = p as Parameters<MlClient['deleteForecast']>;
const forecastId = params.forecast_id;
const [jobId] = getADJobIdsFromRequest(p);
return {
message: `Deleting forecast ${forecastId} for job ${jobId}`,
type: [EVENT_TYPES.deletion],
};
}
/* Data Frame Analytics */
case 'ml_put_dfa_job': {
const [analyticsId] = getDFAJobIdsFromRequest(p);
return {
message: `Creating data frame analytics job ${analyticsId}`,
type: [EVENT_TYPES.creation],
};
}
case 'ml_delete_dfa_job': {
const [analyticsId] = getDFAJobIdsFromRequest(p);
return {
message: `Deleting data frame analytics job ${analyticsId}`,
type: [EVENT_TYPES.deletion],
};
}
case 'ml_start_dfa_job': {
const [analyticsId] = getDFAJobIdsFromRequest(p);
return {
message: `Starting data frame analytics job ${analyticsId}`,
type: [EVENT_TYPES.change],
};
}
case 'ml_stop_dfa_job': {
const [analyticsId] = getDFAJobIdsFromRequest(p);
return {
message: `Stopping data frame analytics job ${analyticsId}`,
type: [EVENT_TYPES.change],
};
}
case 'ml_update_dfa_job': {
const [analyticsId] = getDFAJobIdsFromRequest(p);
return {
message: `Updating data frame analytics job ${analyticsId}`,
type: [EVENT_TYPES.change],
};
}
/* Trained Models */
case 'ml_put_trained_model': {
const [modelId] = getModelIdsFromRequest(p);
return { message: `Creating trained model ${modelId}`, type: [EVENT_TYPES.creation] };
}
case 'ml_delete_trained_model': {
const [modelId] = getModelIdsFromRequest(p);
return { message: `Deleting trained model ${modelId}`, type: [EVENT_TYPES.deletion] };
}
case 'ml_start_trained_model_deployment': {
const [modelId] = getModelIdsFromRequest(p);
return {
message: `Starting trained model deployment for model ${modelId}`,
type: [EVENT_TYPES.change],
};
}
case 'ml_stop_trained_model_deployment': {
const [modelId] = getModelIdsFromRequest(p);
return {
message: `Stopping trained model deployment for model ${modelId}`,
type: [EVENT_TYPES.change],
};
}
case 'ml_update_trained_model_deployment': {
const [modelId] = getModelIdsFromRequest(p);
return {
message: `Updating trained model deployment for model ${modelId}`,
type: [EVENT_TYPES.change],
};
}
case 'ml_infer_trained_model': {
const [modelId] = getModelIdsFromRequest(p);
return { message: `Inferring trained model ${modelId}`, type: [EVENT_TYPES.access] };
}
default:
throw new Error(`Unsupported task type: ${taskType}`);
}
}
}

View file

@ -25,10 +25,12 @@ import type {
MlGetDatafeedParams,
MlGetTrainedModelParams,
} from './types';
import type { MlAuditLogger } from './ml_audit_logger';
export function getMlClient(
client: IScopedClusterClient,
mlSavedObjectService: MLSavedObjectService
mlSavedObjectService: MLSavedObjectService,
auditLogger: MlAuditLogger
): MlClient {
const mlClient = client.asInternalUser.ml;
@ -160,28 +162,44 @@ export function getMlClient(
return {
async closeJob(...p: Parameters<MlClient['closeJob']>) {
await jobIdsCheck('anomaly-detector', p);
return mlClient.closeJob(...p);
return auditLogger.wrapTask(() => mlClient.closeJob(...p), 'ml_close_ad_job', p);
},
async deleteCalendar(...p: Parameters<MlClient['deleteCalendar']>) {
return mlClient.deleteCalendar(...p);
return auditLogger.wrapTask(() => mlClient.deleteCalendar(...p), 'ml_delete_calendar', p);
},
async deleteCalendarEvent(...p: Parameters<MlClient['deleteCalendarEvent']>) {
return mlClient.deleteCalendarEvent(...p);
return auditLogger.wrapTask(
() => mlClient.deleteCalendarEvent(...p),
'ml_delete_calendar_event',
p
);
},
async deleteCalendarJob(...p: Parameters<MlClient['deleteCalendarJob']>) {
return mlClient.deleteCalendarJob(...p);
return auditLogger.wrapTask(
() => mlClient.deleteCalendarJob(...p),
'ml_delete_calendar_job',
p
);
},
async deleteDataFrameAnalytics(...p: Parameters<MlClient['deleteDataFrameAnalytics']>) {
await jobIdsCheck('data-frame-analytics', p);
const resp = await mlClient.deleteDataFrameAnalytics(...p);
const resp = await auditLogger.wrapTask(
() => mlClient.deleteDataFrameAnalytics(...p),
'ml_delete_dfa_job',
p
);
// don't delete the job saved object as the real job will not be
// deleted initially and could still fail.
return resp;
},
async deleteDatafeed(...p: Parameters<MlClient['deleteDatafeed']>) {
await datafeedIdsCheck(p);
const resp = await mlClient.deleteDatafeed(...p);
const [datafeedId] = getDatafeedIdsFromRequest(p);
const resp = await auditLogger.wrapTask(
() => mlClient.deleteDatafeed(...p),
'ml_delete_ad_datafeed',
p
);
if (datafeedId !== undefined) {
await mlSavedObjectService.deleteDatafeed(datafeedId);
}
@ -192,26 +210,33 @@ export function getMlClient(
return mlClient.deleteExpiredData(...p);
},
async deleteFilter(...p: Parameters<MlClient['deleteFilter']>) {
return mlClient.deleteFilter(...p);
return auditLogger.wrapTask(() => mlClient.deleteFilter(...p), 'ml_delete_filter', p);
},
async deleteForecast(...p: Parameters<MlClient['deleteForecast']>) {
await jobIdsCheck('anomaly-detector', p);
return mlClient.deleteForecast(...p);
return auditLogger.wrapTask(() => mlClient.deleteForecast(...p), 'ml_delete_forecast', p);
},
async deleteJob(...p: Parameters<MlClient['deleteJob']>) {
await jobIdsCheck('anomaly-detector', p);
const resp = await mlClient.deleteJob(...p);
return auditLogger.wrapTask(() => mlClient.deleteJob(...p), 'ml_delete_ad_job', p);
// don't delete the job saved object as the real job will not be
// deleted initially and could still fail.
return resp;
},
async deleteModelSnapshot(...p: Parameters<MlClient['deleteModelSnapshot']>) {
await jobIdsCheck('anomaly-detector', p);
return mlClient.deleteModelSnapshot(...p);
return auditLogger.wrapTask(
() => mlClient.deleteModelSnapshot(...p),
'ml_delete_model_snapshot',
p
);
},
async deleteTrainedModel(...p: Parameters<MlClient['deleteTrainedModel']>) {
await modelIdsCheck(p);
return mlClient.deleteTrainedModel(...p);
return auditLogger.wrapTask(
() => mlClient.deleteTrainedModel(...p),
'ml_delete_trained_model',
p
);
},
async estimateModelMemory(...p: Parameters<MlClient['estimateModelMemory']>) {
return mlClient.estimateModelMemory(...p);
@ -229,7 +254,7 @@ export function getMlClient(
},
async forecast(...p: Parameters<MlClient['forecast']>) {
await jobIdsCheck('anomaly-detector', p);
return mlClient.forecast(...p);
return auditLogger.wrapTask(() => mlClient.forecast(...p), 'ml_forecast', p);
},
async getBuckets(...p: Parameters<MlClient['getBuckets']>) {
await jobIdsCheck('anomaly-detector', p);
@ -502,16 +527,21 @@ export function getMlClient(
await modelIdsCheck(p);
// TODO use mlClient.startTrainedModelDeployment when esClient is updated
const { model_id: modelId, adaptive_allocations: adaptiveAllocations, ...queryParams } = p[0];
return client.asInternalUser.transport.request<estypes.MlStartTrainedModelDeploymentResponse>(
{
method: 'POST',
path: `_ml/trained_models/${modelId}/deployment/_start`,
...(isPopulatedObject(queryParams) ? { querystring: queryParams } : {}),
...(isPopulatedObject(adaptiveAllocations)
? { body: { adaptive_allocations: adaptiveAllocations } }
: {}),
},
p[1]
return auditLogger.wrapTask(
() =>
client.asInternalUser.transport.request<estypes.MlStartTrainedModelDeploymentResponse>(
{
method: 'POST',
path: `_ml/trained_models/${modelId}/deployment/_start`,
...(isPopulatedObject(queryParams) ? { querystring: queryParams } : {}),
...(isPopulatedObject(adaptiveAllocations)
? { body: { adaptive_allocations: adaptiveAllocations } }
: {}),
},
p[1]
),
'ml_start_trained_model_deployment',
p
);
},
async updateTrainedModelDeployment(...p: Parameters<MlClient['updateTrainedModelDeployment']>) {
@ -519,17 +549,26 @@ export function getMlClient(
const { deployment_id: deploymentId, model_id: modelId, ...bodyParams } = p[0];
// TODO use mlClient.updateTrainedModelDeployment when esClient is updated
return client.asInternalUser.transport.request({
method: 'POST',
path: `/_ml/trained_models/${deploymentId}/deployment/_update`,
body: bodyParams,
});
return auditLogger.wrapTask(
() =>
client.asInternalUser.transport.request({
method: 'POST',
path: `/_ml/trained_models/${deploymentId}/deployment/_update`,
body: bodyParams,
}),
'ml_update_trained_model_deployment',
p
);
},
async stopTrainedModelDeployment(...p: Parameters<MlClient['stopTrainedModelDeployment']>) {
await modelIdsCheck(p);
switchDeploymentId(p);
return mlClient.stopTrainedModelDeployment(...p);
return auditLogger.wrapTask(
() => mlClient.stopTrainedModelDeployment(...p),
'ml_stop_trained_model_deployment',
p
);
},
async inferTrainedModel(...p: Parameters<MlClient['inferTrainedModel']>) {
await modelIdsCheck(p);
@ -547,14 +586,19 @@ export function getMlClient(
// @ts-expect-error body doesn't exist in the type
const { model_id: id, body, query: querystring } = p[0];
return client.asInternalUser.transport.request(
{
method: 'POST',
path: `/_ml/trained_models/${id}/_infer`,
body,
querystring,
},
p[1]
return auditLogger.wrapTask(
() =>
client.asInternalUser.transport.request(
{
method: 'POST',
path: `/_ml/trained_models/${id}/_infer`,
body,
querystring,
},
p[1]
),
'ml_infer_trained_model',
p
);
},
async info(...p: Parameters<MlClient['info']>) {
@ -562,10 +606,14 @@ export function getMlClient(
},
async openJob(...p: Parameters<MlClient['openJob']>) {
await jobIdsCheck('anomaly-detector', p);
return mlClient.openJob(...p);
return auditLogger.wrapTask(() => mlClient.openJob(...p), 'ml_open_ad_job', p);
},
async postCalendarEvents(...p: Parameters<MlClient['postCalendarEvents']>) {
return mlClient.postCalendarEvents(...p);
return auditLogger.wrapTask(
() => mlClient.postCalendarEvents(...p),
'ml_post_calendar_events',
p
);
},
async postData(...p: Parameters<MlClient['postData']>) {
await jobIdsCheck('anomaly-detector', p);
@ -576,22 +624,30 @@ export function getMlClient(
return mlClient.previewDatafeed(...p);
},
async putCalendar(...p: Parameters<MlClient['putCalendar']>) {
return mlClient.putCalendar(...p);
return auditLogger.wrapTask(() => mlClient.putCalendar(...p), 'ml_put_calendar', p);
},
async putCalendarJob(...p: Parameters<MlClient['putCalendarJob']>) {
return mlClient.putCalendarJob(...p);
return auditLogger.wrapTask(() => mlClient.putCalendarJob(...p), 'ml_put_calendar_job', p);
},
async putDataFrameAnalytics(...p: Parameters<MlClient['putDataFrameAnalytics']>) {
const resp = await mlClient.putDataFrameAnalytics(...p);
const [analyticsId] = getDFAJobIdsFromRequest(p);
const resp = await auditLogger.wrapTask(
() => mlClient.putDataFrameAnalytics(...p),
'ml_put_dfa_job',
p
);
if (analyticsId !== undefined) {
await mlSavedObjectService.createDataFrameAnalyticsJob(analyticsId);
}
return resp;
},
async putDatafeed(...p: Parameters<MlClient['putDatafeed']>) {
const resp = await mlClient.putDatafeed(...p);
const [datafeedId] = getDatafeedIdsFromRequest(p);
const resp = await auditLogger.wrapTask(
() => mlClient.putDatafeed(...p),
'ml_put_ad_datafeed',
p
);
const jobId = getJobIdFromBody(p);
if (datafeedId !== undefined && jobId !== undefined) {
await mlSavedObjectService.addDatafeed(datafeedId, jobId);
@ -600,18 +656,22 @@ export function getMlClient(
return resp;
},
async putFilter(...p: Parameters<MlClient['putFilter']>) {
return mlClient.putFilter(...p);
return auditLogger.wrapTask(() => mlClient.putFilter(...p), 'ml_put_filter', p);
},
async putJob(...p: Parameters<MlClient['putJob']>) {
const resp = await mlClient.putJob(...p);
const [jobId] = getADJobIdsFromRequest(p);
const resp = await auditLogger.wrapTask(() => mlClient.putJob(...p), 'ml_put_ad_job', p);
if (jobId !== undefined) {
await mlSavedObjectService.createAnomalyDetectionJob(jobId);
}
return resp;
},
async putTrainedModel(...p: Parameters<MlClient['putTrainedModel']>) {
const resp = await mlClient.putTrainedModel(...p);
const resp = await auditLogger.wrapTask(
() => mlClient.putTrainedModel(...p),
'ml_put_trained_model',
p
);
const [modelId] = getModelIdsFromRequest(p);
if (modelId !== undefined) {
const model = (p[0] as estypes.MlPutTrainedModelRequest).body;
@ -622,70 +682,61 @@ export function getMlClient(
},
async revertModelSnapshot(...p: Parameters<MlClient['revertModelSnapshot']>) {
await jobIdsCheck('anomaly-detector', p);
return mlClient.revertModelSnapshot(...p);
return auditLogger.wrapTask(
() => mlClient.revertModelSnapshot(...p),
'ml_revert_ad_snapshot',
p
);
},
async setUpgradeMode(...p: Parameters<MlClient['setUpgradeMode']>) {
return mlClient.setUpgradeMode(...p);
},
async startDataFrameAnalytics(...p: Parameters<MlClient['startDataFrameAnalytics']>) {
await jobIdsCheck('data-frame-analytics', p);
return mlClient.startDataFrameAnalytics(...p);
return auditLogger.wrapTask(
() => mlClient.startDataFrameAnalytics(...p),
'ml_start_dfa_job',
p
);
},
async startDatafeed(...p: Parameters<MlClient['startDatafeed']>) {
await datafeedIdsCheck(p);
return mlClient.startDatafeed(...p);
return auditLogger.wrapTask(() => mlClient.startDatafeed(...p), 'ml_start_ad_datafeed', p);
},
async stopDataFrameAnalytics(...p: Parameters<MlClient['stopDataFrameAnalytics']>) {
await jobIdsCheck('data-frame-analytics', p);
return mlClient.stopDataFrameAnalytics(...p);
return auditLogger.wrapTask(
() => mlClient.stopDataFrameAnalytics(...p),
'ml_stop_dfa_job',
p
);
},
async stopDatafeed(...p: Parameters<MlClient['stopDatafeed']>) {
await datafeedIdsCheck(p);
return mlClient.stopDatafeed(...p);
return auditLogger.wrapTask(() => mlClient.stopDatafeed(...p), 'ml_stop_ad_datafeed', p);
},
async updateDataFrameAnalytics(...p: Parameters<MlClient['updateDataFrameAnalytics']>) {
await jobIdsCheck('data-frame-analytics', p);
return mlClient.updateDataFrameAnalytics(...p);
return auditLogger.wrapTask(
() => mlClient.updateDataFrameAnalytics(...p),
'ml_update_dfa_job',
p
);
},
async updateDatafeed(...p: Parameters<MlClient['updateDatafeed']>) {
await datafeedIdsCheck(p);
// Temporary workaround for the incorrect updateDatafeed function in the esclient
if (
// @ts-expect-error TS complains it's always false
p.length === 0 ||
p[0] === undefined
) {
// Temporary generic error message. This should never be triggered
// but is added for type correctness below
throw new Error('Incorrect arguments supplied');
}
// @ts-expect-error body doesn't exist in the type
const { datafeed_id: id, body } = p[0];
return client.asInternalUser.transport.request(
{
method: 'POST',
path: `/_ml/datafeeds/${id}/_update`,
body,
},
p[1]
);
// this should be reinstated once https://github.com/elastic/elasticsearch-js/issues/1601
// is fixed
// return mlClient.updateDatafeed(...p);
return auditLogger.wrapTask(() => mlClient.updateDatafeed(...p), 'ml_update_ad_datafeed', p);
},
async updateFilter(...p: Parameters<MlClient['updateFilter']>) {
return mlClient.updateFilter(...p);
return auditLogger.wrapTask(() => mlClient.updateFilter(...p), 'ml_update_filter', p);
},
async updateJob(...p: Parameters<MlClient['updateJob']>) {
await jobIdsCheck('anomaly-detector', p);
return mlClient.updateJob(...p);
return auditLogger.wrapTask(() => mlClient.updateJob(...p), 'ml_update_ad_job', p);
},
async resetJob(...p: Parameters<MlClient['resetJob']>) {
await jobIdsCheck('anomaly-detector', p);
return mlClient.resetJob(...p);
return auditLogger.wrapTask(() => mlClient.resetJob(...p), 'ml_reset_ad_job', p);
},
async updateModelSnapshot(...p: Parameters<MlClient['updateModelSnapshot']>) {
await jobIdsCheck('anomaly-detector', p);
@ -705,29 +756,29 @@ export function getMlClient(
} as MlClient;
}
function getDFAJobIdsFromRequest([params]: MlGetDFAParams): string[] {
export function getDFAJobIdsFromRequest([params]: MlGetDFAParams): string[] {
const ids = params?.id?.split(',');
return ids || [];
}
function getModelIdsFromRequest([params]: MlGetTrainedModelParams): string[] {
export function getModelIdsFromRequest([params]: MlGetTrainedModelParams): string[] {
const id = params?.model_id;
const ids = Array.isArray(id) ? id : id?.split(',');
return ids || [];
}
function getADJobIdsFromRequest([params]: MlGetADParams): string[] {
export function getADJobIdsFromRequest([params]: MlGetADParams): string[] {
const ids = typeof params?.job_id === 'string' ? params?.job_id.split(',') : params?.job_id;
return ids || [];
}
function getDatafeedIdsFromRequest([params]: MlGetDatafeedParams): string[] {
export function getDatafeedIdsFromRequest([params]: MlGetDatafeedParams): string[] {
const ids =
typeof params?.datafeed_id === 'string' ? params?.datafeed_id.split(',') : params?.datafeed_id;
return ids || [];
}
function getJobIdFromBody(p: any): string | undefined {
export function getJobIdFromBody(p: any): string | undefined {
const [params] = p;
return params?.body?.job_id;
}

View file

@ -27,7 +27,7 @@ import { mlSavedObjectServiceFactory } from '../saved_objects';
import type { MlLicense } from '../../common/license';
import type { MlClient } from './ml_client';
import { getMlClient } from './ml_client';
import { MlAuditLogger, getMlClient } from './ml_client';
import { getDataViewsServiceFactory } from './data_views_utils';
type MLRequestHandlerContext = CustomRequestHandlerContext<{
@ -42,6 +42,7 @@ type Handler<P = unknown, Q = unknown, B = unknown> = (handlerParams: {
mlSavedObjectService: MLSavedObjectService;
mlClient: MlClient;
getDataViewsService(): Promise<DataViewsService>;
auditLogger: MlAuditLogger;
}) => ReturnType<RequestHandler<P, Q, B>>;
type GetMlSavedObjectClient = (request: KibanaRequest) => SavedObjectsClientContract | null;
@ -121,6 +122,8 @@ export class RouteGuard {
const [coreStart] = await this._getStartServices();
const executionContext = createExecutionContext(coreStart, PLUGIN_ID, request.route.path);
const auditLogger = new MlAuditLogger(coreStart.security.audit, request);
return await coreStart.executionContext.withContext(executionContext, () =>
handler({
client,
@ -128,13 +131,14 @@ export class RouteGuard {
response,
context,
mlSavedObjectService,
mlClient: getMlClient(client, mlSavedObjectService),
mlClient: getMlClient(client, mlSavedObjectService, auditLogger),
getDataViewsService: getDataViewsServiceFactory(
this._getDataViews,
savedObjectClient,
client,
request
),
auditLogger,
})
);
};

View file

@ -17,6 +17,7 @@ import type {
IClusterClient,
SavedObjectsServiceStart,
UiSettingsServiceStart,
CoreAuditService,
} from '@kbn/core/server';
import { DEFAULT_APP_CATEGORIES } from '@kbn/core/server';
import type { SecurityPluginSetup } from '@kbn/security-plugin/server';
@ -94,6 +95,7 @@ export class MlServerPlugin
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;
@ -218,6 +220,7 @@ export class MlServerPlugin
() => this.uiSettings,
() => this.fieldsFormat,
getDataViews,
() => this.auditService,
() => this.isMlReady,
this.compatibleModuleType
);
@ -313,6 +316,7 @@ export class MlServerPlugin
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) => {

View file

@ -11,6 +11,7 @@ import type {
SavedObjectsClientContract,
UiSettingsServiceStart,
KibanaRequest,
CoreAuditService,
} from '@kbn/core/server';
import type { SpacesPluginStart } from '@kbn/spaces-plugin/server';
import { CoreKibanaRequest } from '@kbn/core/server';
@ -49,7 +50,7 @@ import {
MLUISettingsClientUninitialized,
} from './errors';
import type { MlClient } from '../lib/ml_client';
import { getMlClient } from '../lib/ml_client';
import { getMlClient, MlAuditLogger } from '../lib/ml_client';
import type { MLSavedObjectService } from '../saved_objects';
import { mlSavedObjectServiceFactory } from '../saved_objects';
import type { MlAlertingServiceProvider } from './providers/alerting_service';
@ -110,6 +111,7 @@ export function createSharedServices(
getUiSettings: () => UiSettingsServiceStart | null,
getFieldsFormat: () => FieldFormatsStart | null,
getDataViews: () => DataViewsPluginStart,
getAuditService: () => CoreAuditService | null,
isMlReady: () => Promise<void>,
compatibleModuleType: CompatibleModule | null
): {
@ -137,7 +139,8 @@ export function createSharedServices(
isMlReady,
getUiSettings,
getFieldsFormat,
getDataViews
getDataViews,
getAuditService
);
const {
@ -209,7 +212,8 @@ function getRequestItemsProvider(
isMlReady: () => Promise<void>,
getUiSettings: () => UiSettingsServiceStart | null,
getFieldsFormat: () => FieldFormatsStart | null,
getDataViews: () => DataViewsPluginStart
getDataViews: () => DataViewsPluginStart,
getAuditService: () => CoreAuditService | null
) {
return (request: KibanaRequest) => {
let hasMlCapabilities: HasMlCapabilities = hasMlCapabilitiesProvider(
@ -237,6 +241,11 @@ function getRequestItemsProvider(
throw new MLClusterClientUninitialized(`ML's cluster client has not been initialized`);
}
const auditService = getAuditService();
if (!auditService) {
throw new Error('Audit service not initialized');
}
const uiSettingsClient = getUiSettings()?.asScopedToClient(savedObjectsClient);
if (!uiSettingsClient) {
throw new MLUISettingsClientUninitialized(`ML's UI settings client has not been initialized`);
@ -263,7 +272,8 @@ function getRequestItemsProvider(
if (request instanceof CoreKibanaRequest) {
scopedClient = clusterClient.asScoped(request);
mlSavedObjectService = getSobSavedObjectService(scopedClient);
mlClient = getMlClient(scopedClient, mlSavedObjectService);
const auditLogger = new MlAuditLogger(auditService, request);
mlClient = getMlClient(scopedClient, mlSavedObjectService, auditLogger);
} else {
hasMlCapabilities = () => Promise.resolve();
const { asInternalUser } = clusterClient;
@ -273,7 +283,8 @@ function getRequestItemsProvider(
asSecondaryAuthUser: asInternalUser,
};
mlSavedObjectService = getSobSavedObjectService(scopedClient);
mlClient = getMlClient(scopedClient, mlSavedObjectService);
const auditLogger = new MlAuditLogger(auditService);
mlClient = getMlClient(scopedClient, mlSavedObjectService, auditLogger);
}
const getDataViewsService = getDataViewsServiceFactory(

View file

@ -25,28 +25,46 @@
},
// add references to other TypeScript projects the plugin depends on
"@kbn/actions-plugin",
"@kbn/aiops-change-point-detection",
"@kbn/aiops-common",
"@kbn/aiops-plugin",
"@kbn/alerting-plugin",
"@kbn/alerts-as-data-utils",
"@kbn/analytics",
"@kbn/cases-plugin",
"@kbn/charts-plugin",
"@kbn/cloud-plugin",
"@kbn/code-editor",
"@kbn/config-schema",
"@kbn/content-management-plugin",
"@kbn/core-elasticsearch-client-server-mocks",
"@kbn/core-elasticsearch-server",
"@kbn/core-http-browser",
"@kbn/core-http-server",
"@kbn/core-lifecycle-browser",
"@kbn/core-notifications-browser-mocks",
"@kbn/core-ui-settings-browser",
"@kbn/dashboard-plugin",
"@kbn/data-plugin",
"@kbn/data-view-editor-plugin",
"@kbn/data-views-plugin",
"@kbn/data-visualizer-plugin",
"@kbn/deeplinks-management",
"@kbn/deeplinks-ml",
"@kbn/embeddable-plugin",
"@kbn/es-query",
"@kbn/es-types",
"@kbn/es-ui-shared-plugin",
"@kbn/esql-utils",
"@kbn/features-plugin",
"@kbn/field-formats-plugin",
"@kbn/field-types",
"@kbn/home-plugin",
"@kbn/i18n-react",
"@kbn/i18n",
"@kbn/inference_integration_flyout",
"@kbn/inspector-plugin",
"@kbn/json-schemas",
"@kbn/kibana-react-plugin",
"@kbn/kibana-utils-plugin",
"@kbn/lens-plugin",
@ -55,28 +73,55 @@
"@kbn/management-plugin",
"@kbn/maps-plugin",
"@kbn/ml-agg-utils",
"@kbn/ml-anomaly-utils",
"@kbn/ml-category-validator",
"@kbn/ml-creation-wizard-utils",
"@kbn/ml-data-frame-analytics-utils",
"@kbn/ml-data-grid",
"@kbn/ml-data-view-utils",
"@kbn/ml-date-picker",
"@kbn/ml-date-utils",
"@kbn/ml-error-utils",
"@kbn/ml-field-stats-flyout",
"@kbn/ml-in-memory-table",
"@kbn/ml-is-defined",
"@kbn/ml-is-populated-object",
"@kbn/ml-kibana-theme",
"@kbn/ml-local-storage",
"@kbn/ml-nested-property",
"@kbn/ml-number-utils",
"@kbn/ml-parse-interval",
"@kbn/ml-query-utils",
"@kbn/ml-route-utils",
"@kbn/ml-runtime-field-utils",
"@kbn/ml-string-hash",
"@kbn/ml-time-buckets",
"@kbn/ml-trained-models-utils",
"@kbn/ml-trained-models-utils",
"@kbn/ml-ui-actions",
"@kbn/ml-url-state",
"@kbn/ml-validators",
"@kbn/monaco",
"@kbn/observability-ai-assistant-plugin",
"@kbn/presentation-containers",
"@kbn/presentation-panel-plugin",
"@kbn/presentation-publishing",
"@kbn/presentation-util-plugin",
"@kbn/react-kibana-context-render",
"@kbn/react-kibana-mount",
"@kbn/rison",
"@kbn/rule-data-utils",
"@kbn/rule-registry-plugin",
"@kbn/saved-objects-finder-plugin",
"@kbn/saved-objects-management-plugin",
"@kbn/saved-search-plugin",
"@kbn/security-plugin",
"@kbn/securitysolution-ecs",
"@kbn/share-plugin",
"@kbn/shared-ux-link-redirect-app",
"@kbn/shared-ux-page-kibana-template",
"@kbn/shared-ux-router",
"@kbn/shared-ux-utility",
"@kbn/spaces-plugin",
"@kbn/std",
"@kbn/task-manager-plugin",
@ -84,53 +129,9 @@
"@kbn/triggers-actions-ui-plugin",
"@kbn/ui-actions-plugin",
"@kbn/ui-theme",
"@kbn/unified-field-list",
"@kbn/unified-search-plugin",
"@kbn/usage-collection-plugin",
"@kbn/utility-types",
"@kbn/ml-error-utils",
"@kbn/ml-anomaly-utils",
"@kbn/ml-data-frame-analytics-utils",
"@kbn/ml-data-grid",
"@kbn/ml-kibana-theme",
"@kbn/ml-runtime-field-utils",
"@kbn/ml-date-utils",
"@kbn/ml-category-validator",
"@kbn/ml-ui-actions",
"@kbn/deeplinks-ml",
"@kbn/core-notifications-browser-mocks",
"@kbn/unified-field-list",
"@kbn/core-ui-settings-browser",
"@kbn/content-management-plugin",
"@kbn/ml-in-memory-table",
"@kbn/presentation-util-plugin",
"@kbn/react-kibana-mount",
"@kbn/core-http-browser",
"@kbn/data-view-editor-plugin",
"@kbn/rule-data-utils",
"@kbn/alerts-as-data-utils",
"@kbn/rule-registry-plugin",
"@kbn/securitysolution-ecs",
"@kbn/ml-data-view-utils",
"@kbn/ml-creation-wizard-utils",
"@kbn/deeplinks-management",
"@kbn/code-editor",
"@kbn/presentation-publishing",
"@kbn/core-elasticsearch-server",
"@kbn/core-elasticsearch-client-server-mocks",
"@kbn/ml-time-buckets",
"@kbn/aiops-change-point-detection",
"@kbn/inference_integration_flyout",
"@kbn/presentation-containers",
"@kbn/presentation-panel-plugin",
"@kbn/shared-ux-utility",
"@kbn/react-kibana-context-render",
"@kbn/esql-utils",
"@kbn/core-lifecycle-browser",
"@kbn/observability-ai-assistant-plugin",
"@kbn/json-schemas",
"@kbn/ml-field-stats-flyout",
"@kbn/ml-parse-interval",
"@kbn/ml-validators",
"@kbn/aiops-common"
]
}