diff --git a/x-pack/plugins/actions/server/create_execute_function.test.ts b/x-pack/plugins/actions/server/create_execute_function.test.ts index ee8064d2aadc5..f31916458e59c 100644 --- a/x-pack/plugins/actions/server/create_execute_function.test.ts +++ b/x-pack/plugins/actions/server/create_execute_function.test.ts @@ -76,7 +76,15 @@ describe('execute()', () => { params: { baz: false }, apiKey: Buffer.from('123:abc').toString('base64'), }, - {} + { + references: [ + { + id: '123', + name: 'actionRef', + type: 'action', + }, + ], + } ); expect(actionTypeRegistry.isActionExecutable).toHaveBeenCalledWith('123', 'mock-action', { notifyUsage: true, @@ -128,14 +136,27 @@ describe('execute()', () => { apiKey: Buffer.from('123:abc').toString('base64'), relatedSavedObjects: [ { - id: 'some-id', + id: 'related_some-type_0', namespace: 'some-namespace', type: 'some-type', typeId: 'some-typeId', }, ], }, - {} + { + references: [ + { + id: '123', + name: 'actionRef', + type: 'action', + }, + { + id: 'some-id', + name: 'related_some-type_0', + type: 'some-type', + }, + ], + } ); }); @@ -214,6 +235,102 @@ describe('execute()', () => { ); }); + test('schedules the action with all given parameters with a preconfigured action and relatedSavedObjects', async () => { + const executeFn = createExecutionEnqueuerFunction({ + taskManager: mockTaskManager, + actionTypeRegistry: actionTypeRegistryMock.create(), + isESOCanEncrypt: true, + preconfiguredActions: [ + { + id: '123', + actionTypeId: 'mock-action-preconfigured', + config: {}, + isPreconfigured: true, + name: 'x', + secrets: {}, + }, + ], + }); + const source = { type: 'alert', id: uuid.v4() }; + + savedObjectsClient.get.mockResolvedValueOnce({ + id: '123', + type: 'action', + attributes: { + actionTypeId: 'mock-action', + }, + references: [], + }); + savedObjectsClient.create.mockResolvedValueOnce({ + id: '234', + type: 'action_task_params', + attributes: {}, + references: [], + }); + await executeFn(savedObjectsClient, { + id: '123', + params: { baz: false }, + spaceId: 'default', + apiKey: Buffer.from('123:abc').toString('base64'), + source: asSavedObjectExecutionSource(source), + relatedSavedObjects: [ + { + id: 'some-id', + namespace: 'some-namespace', + type: 'some-type', + typeId: 'some-typeId', + }, + ], + }); + expect(mockTaskManager.schedule).toHaveBeenCalledTimes(1); + expect(mockTaskManager.schedule.mock.calls[0]).toMatchInlineSnapshot(` + Array [ + Object { + "params": Object { + "actionTaskParamsId": "234", + "spaceId": "default", + }, + "scope": Array [ + "actions", + ], + "state": Object {}, + "taskType": "actions:mock-action-preconfigured", + }, + ] + `); + expect(savedObjectsClient.get).not.toHaveBeenCalled(); + expect(savedObjectsClient.create).toHaveBeenCalledWith( + 'action_task_params', + { + actionId: '123', + params: { baz: false }, + apiKey: Buffer.from('123:abc').toString('base64'), + relatedSavedObjects: [ + { + id: 'related_some-type_0', + namespace: 'some-namespace', + type: 'some-type', + typeId: 'some-typeId', + }, + ], + }, + { + references: [ + { + id: source.id, + name: 'source', + type: source.type, + }, + { + id: 'some-id', + name: 'related_some-type_0', + type: 'some-type', + }, + ], + } + ); + }); + test('throws when passing isESOCanEncrypt with false as a value', async () => { const executeFn = createExecutionEnqueuerFunction({ taskManager: mockTaskManager, diff --git a/x-pack/plugins/actions/server/create_execute_function.ts b/x-pack/plugins/actions/server/create_execute_function.ts index bcad5f20d9ba7..de15a1e0ca446 100644 --- a/x-pack/plugins/actions/server/create_execute_function.ts +++ b/x-pack/plugins/actions/server/create_execute_function.ts @@ -15,7 +15,7 @@ import { } from './types'; import { ACTION_TASK_PARAMS_SAVED_OBJECT_TYPE } from './constants/saved_objects'; import { ExecuteOptions as ActionExecutorOptions } from './lib/action_executor'; -import { isSavedObjectExecutionSource } from './lib'; +import { extractSavedObjectReferences, isSavedObjectExecutionSource } from './lib'; import { RelatedSavedObjects } from './lib/related_saved_objects'; interface CreateExecuteFunctionOptions { @@ -53,7 +53,11 @@ export function createExecutionEnqueuerFunction({ ); } - const action = await getAction(unsecuredSavedObjectsClient, preconfiguredActions, id); + const { action, isPreconfigured } = await getAction( + unsecuredSavedObjectsClient, + preconfiguredActions, + id + ); validateCanActionBeUsed(action); const { actionTypeId } = action; @@ -61,15 +65,33 @@ export function createExecutionEnqueuerFunction({ actionTypeRegistry.ensureActionTypeEnabled(actionTypeId); } + // Get saved object references from action ID and relatedSavedObjects + const { references, relatedSavedObjectWithRefs } = extractSavedObjectReferences( + id, + isPreconfigured, + relatedSavedObjects + ); + const executionSourceReference = executionSourceAsSavedObjectReferences(source); + + const taskReferences = []; + if (executionSourceReference.references) { + taskReferences.push(...executionSourceReference.references); + } + if (references) { + taskReferences.push(...references); + } + const actionTaskParamsRecord = await unsecuredSavedObjectsClient.create( ACTION_TASK_PARAMS_SAVED_OBJECT_TYPE, { actionId: id, params, apiKey, - relatedSavedObjects, + relatedSavedObjects: relatedSavedObjectWithRefs, }, - executionSourceAsSavedObjectReferences(source) + { + references: taskReferences, + } ); await taskManager.schedule({ @@ -93,7 +115,7 @@ export function createEphemeralExecutionEnqueuerFunction({ unsecuredSavedObjectsClient: SavedObjectsClientContract, { id, params, spaceId, source, apiKey }: ExecuteOptions ): Promise { - const action = await getAction(unsecuredSavedObjectsClient, preconfiguredActions, id); + const { action } = await getAction(unsecuredSavedObjectsClient, preconfiguredActions, id); validateCanActionBeUsed(action); const { actionTypeId } = action; @@ -148,12 +170,12 @@ async function getAction( unsecuredSavedObjectsClient: SavedObjectsClientContract, preconfiguredActions: PreConfiguredAction[], actionId: string -): Promise { +): Promise<{ action: PreConfiguredAction | RawAction; isPreconfigured: boolean }> { const pcAction = preconfiguredActions.find((action) => action.id === actionId); if (pcAction) { - return pcAction; + return { action: pcAction, isPreconfigured: true }; } const { attributes } = await unsecuredSavedObjectsClient.get('action', actionId); - return attributes; + return { action: attributes, isPreconfigured: false }; } diff --git a/x-pack/plugins/actions/server/lib/action_task_params_utils.test.ts b/x-pack/plugins/actions/server/lib/action_task_params_utils.test.ts new file mode 100644 index 0000000000000..98a425ff6fd39 --- /dev/null +++ b/x-pack/plugins/actions/server/lib/action_task_params_utils.test.ts @@ -0,0 +1,402 @@ +/* + * 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 { + extractSavedObjectReferences, + injectSavedObjectReferences, +} from './action_task_params_utils'; + +describe('extractSavedObjectReferences()', () => { + test('correctly extracts action id into references array', () => { + expect(extractSavedObjectReferences('my-action-id', false)).toEqual({ + references: [ + { + id: 'my-action-id', + name: 'actionRef', + type: 'action', + }, + ], + }); + }); + + test('correctly extracts related saved object into references array', () => { + const relatedSavedObjects = [ + { + id: 'abc', + type: 'alert', + typeId: 'ruleTypeA', + }, + { + id: 'def', + type: 'action', + typeId: 'connectorTypeB', + }, + { + id: 'xyz', + type: 'alert', + typeId: 'ruleTypeB', + namespace: 'custom', + }, + ]; + + expect(extractSavedObjectReferences('my-action-id', false, relatedSavedObjects)).toEqual({ + references: [ + { + id: 'my-action-id', + name: 'actionRef', + type: 'action', + }, + { + id: 'abc', + name: 'related_alert_0', + type: 'alert', + }, + { + id: 'def', + name: 'related_action_1', + type: 'action', + }, + { + id: 'xyz', + name: 'related_alert_2', + type: 'alert', + }, + ], + relatedSavedObjectWithRefs: [ + { + id: 'related_alert_0', + type: 'alert', + typeId: 'ruleTypeA', + }, + { + id: 'related_action_1', + type: 'action', + typeId: 'connectorTypeB', + }, + { + id: 'related_alert_2', + type: 'alert', + typeId: 'ruleTypeB', + namespace: 'custom', + }, + ], + }); + }); + + test('correctly skips extracting action id if action is preconfigured', () => { + expect(extractSavedObjectReferences('my-action-id', true)).toEqual({ + references: [], + }); + }); + + test('correctly extracts related saved object into references array if isPreconfigured is true', () => { + const relatedSavedObjects = [ + { + id: 'abc', + type: 'alert', + typeId: 'ruleTypeA', + }, + { + id: 'def', + type: 'action', + typeId: 'connectorTypeB', + }, + { + id: 'xyz', + type: 'alert', + typeId: 'ruleTypeB', + namespace: 'custom', + }, + ]; + + expect(extractSavedObjectReferences('my-action-id', true, relatedSavedObjects)).toEqual({ + references: [ + { + id: 'abc', + name: 'related_alert_0', + type: 'alert', + }, + { + id: 'def', + name: 'related_action_1', + type: 'action', + }, + { + id: 'xyz', + name: 'related_alert_2', + type: 'alert', + }, + ], + relatedSavedObjectWithRefs: [ + { + id: 'related_alert_0', + type: 'alert', + typeId: 'ruleTypeA', + }, + { + id: 'related_action_1', + type: 'action', + typeId: 'connectorTypeB', + }, + { + id: 'related_alert_2', + type: 'alert', + typeId: 'ruleTypeB', + namespace: 'custom', + }, + ], + }); + }); +}); + +describe('injectSavedObjectReferences()', () => { + test('correctly returns action id from references array', () => { + expect( + injectSavedObjectReferences([ + { + id: 'my-action-id', + name: 'actionRef', + type: 'action', + }, + ]) + ).toEqual({ actionId: 'my-action-id' }); + }); + + test('correctly returns undefined if no action id in references array', () => { + expect(injectSavedObjectReferences([])).toEqual({}); + }); + + test('correctly injects related saved object ids from references array', () => { + expect( + injectSavedObjectReferences( + [ + { + id: 'my-action-id', + name: 'actionRef', + type: 'action', + }, + { + id: 'abc', + name: 'related_alert_0', + type: 'alert', + }, + { + id: 'def', + name: 'related_action_1', + type: 'action', + }, + { + id: 'xyz', + name: 'related_alert_2', + type: 'alert', + }, + ], + [ + { + id: 'related_alert_0', + type: 'alert', + typeId: 'ruleTypeA', + }, + { + id: 'related_action_1', + type: 'action', + typeId: 'connectorTypeB', + }, + { + id: 'related_alert_2', + type: 'alert', + typeId: 'ruleTypeB', + namespace: 'custom', + }, + ] + ) + ).toEqual({ + actionId: 'my-action-id', + relatedSavedObjects: [ + { + id: 'abc', + type: 'alert', + typeId: 'ruleTypeA', + }, + { + id: 'def', + type: 'action', + typeId: 'connectorTypeB', + }, + { + id: 'xyz', + type: 'alert', + typeId: 'ruleTypeB', + namespace: 'custom', + }, + ], + }); + }); + + test('correctly injects related saved object ids from references array if no actionRef', () => { + expect( + injectSavedObjectReferences( + [ + { + id: 'abc', + name: 'related_alert_0', + type: 'alert', + }, + { + id: 'def', + name: 'related_action_1', + type: 'action', + }, + { + id: 'xyz', + name: 'related_alert_2', + type: 'alert', + }, + ], + [ + { + id: 'related_alert_0', + type: 'alert', + typeId: 'ruleTypeA', + }, + { + id: 'related_action_1', + type: 'action', + typeId: 'connectorTypeB', + }, + { + id: 'related_alert_2', + type: 'alert', + typeId: 'ruleTypeB', + namespace: 'custom', + }, + ] + ) + ).toEqual({ + relatedSavedObjects: [ + { + id: 'abc', + type: 'alert', + typeId: 'ruleTypeA', + }, + { + id: 'def', + type: 'action', + typeId: 'connectorTypeB', + }, + { + id: 'xyz', + type: 'alert', + typeId: 'ruleTypeB', + namespace: 'custom', + }, + ], + }); + }); + + test('correctly keeps related saved object ids if references array is empty', () => { + expect( + injectSavedObjectReferences( + [], + [ + { + id: 'abc', + type: 'alert', + typeId: 'ruleTypeA', + }, + { + id: 'def', + type: 'action', + typeId: 'connectorTypeB', + }, + { + id: 'xyz', + type: 'alert', + typeId: 'ruleTypeB', + namespace: 'custom', + }, + ] + ) + ).toEqual({ + relatedSavedObjects: [ + { + id: 'abc', + type: 'alert', + typeId: 'ruleTypeA', + }, + { + id: 'def', + type: 'action', + typeId: 'connectorTypeB', + }, + { + id: 'xyz', + type: 'alert', + typeId: 'ruleTypeB', + namespace: 'custom', + }, + ], + }); + }); + + test('correctly skips injecting missing related saved object ids in references array', () => { + expect( + injectSavedObjectReferences( + [ + { + id: 'my-action-id', + name: 'actionRef', + type: 'action', + }, + { + id: 'abc', + name: 'related_alert_0', + type: 'alert', + }, + ], + [ + { + id: 'related_alert_0', + type: 'alert', + typeId: 'ruleTypeA', + }, + { + id: 'def', + type: 'action', + typeId: 'connectorTypeB', + }, + { + id: 'xyz', + type: 'alert', + typeId: 'ruleTypeB', + namespace: 'custom', + }, + ] + ) + ).toEqual({ + actionId: 'my-action-id', + relatedSavedObjects: [ + { + id: 'abc', + type: 'alert', + typeId: 'ruleTypeA', + }, + { + id: 'def', + type: 'action', + typeId: 'connectorTypeB', + }, + { + id: 'xyz', + type: 'alert', + typeId: 'ruleTypeB', + namespace: 'custom', + }, + ], + }); + }); +}); diff --git a/x-pack/plugins/actions/server/lib/action_task_params_utils.ts b/x-pack/plugins/actions/server/lib/action_task_params_utils.ts new file mode 100644 index 0000000000000..a4b1afb9497dc --- /dev/null +++ b/x-pack/plugins/actions/server/lib/action_task_params_utils.ts @@ -0,0 +1,80 @@ +/* + * 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 { SavedObjectAttribute, SavedObjectReference } from 'src/core/server/types'; +import { RelatedSavedObjects } from './related_saved_objects'; + +export const ACTION_REF_NAME = `actionRef`; + +export function extractSavedObjectReferences( + actionId: string, + isPreconfigured: boolean, + relatedSavedObjects?: RelatedSavedObjects +): { + references: SavedObjectReference[]; + relatedSavedObjectWithRefs?: RelatedSavedObjects; +} { + const references: SavedObjectReference[] = []; + const relatedSavedObjectWithRefs: RelatedSavedObjects = []; + + // Add action saved object to reference if it is not preconfigured + if (!isPreconfigured) { + references.push({ + id: actionId, + name: ACTION_REF_NAME, + type: 'action', + }); + } + + // Add related saved objects, if any + (relatedSavedObjects ?? []).forEach((relatedSavedObject, index) => { + relatedSavedObjectWithRefs.push({ + ...relatedSavedObject, + id: `related_${relatedSavedObject.type}_${index}`, + }); + references.push({ + id: relatedSavedObject.id, + name: `related_${relatedSavedObject.type}_${index}`, + type: relatedSavedObject.type, + }); + }); + + return { + references, + ...(relatedSavedObjects ? { relatedSavedObjectWithRefs } : {}), + }; +} + +export function injectSavedObjectReferences( + references: SavedObjectReference[], + relatedSavedObjects?: RelatedSavedObjects +): { actionId?: string; relatedSavedObjects?: SavedObjectAttribute } { + references = references ?? []; + + // Look for for the action id + const action = references.find((ref) => ref.name === ACTION_REF_NAME); + + const injectedRelatedSavedObjects = (relatedSavedObjects ?? []).flatMap((relatedSavedObject) => { + const reference = references.find((ref) => ref.name === relatedSavedObject.id); + + // relatedSavedObjects are used only in the event log document that is written during + // action execution. Because they are not critical to the actual execution of the action + // we will not throw an error if no reference is found matching this related saved object + return reference ? [{ ...relatedSavedObject, id: reference.id }] : [relatedSavedObject]; + }); + + const result: { actionId?: string; relatedSavedObjects?: SavedObjectAttribute } = {}; + if (action) { + result.actionId = action.id; + } + + if (relatedSavedObjects) { + result.relatedSavedObjects = injectedRelatedSavedObjects; + } + + return result; +} diff --git a/x-pack/plugins/actions/server/lib/index.ts b/x-pack/plugins/actions/server/lib/index.ts index fba47f9a0f995..c47325c19fad9 100644 --- a/x-pack/plugins/actions/server/lib/index.ts +++ b/x-pack/plugins/actions/server/lib/index.ts @@ -13,6 +13,10 @@ export { ILicenseState, LicenseState } from './license_state'; export { verifyApiAccess } from './verify_api_access'; export { getActionTypeFeatureUsageName } from './get_action_type_feature_usage_name'; export { spaceIdToNamespace } from './space_id_to_namespace'; +export { + extractSavedObjectReferences, + injectSavedObjectReferences, +} from './action_task_params_utils'; export { ActionTypeDisabledError, ActionTypeDisabledReason, diff --git a/x-pack/plugins/actions/server/lib/task_runner_factory.test.ts b/x-pack/plugins/actions/server/lib/task_runner_factory.test.ts index 722ba08a26258..cff92f874e0ef 100644 --- a/x-pack/plugins/actions/server/lib/task_runner_factory.test.ts +++ b/x-pack/plugins/actions/server/lib/task_runner_factory.test.ts @@ -97,7 +97,7 @@ test(`throws an error if factory is already initialized`, () => { ).toThrowErrorMatchingInlineSnapshot(`"TaskRunnerFactory already initialized"`); }); -test('executes the task by calling the executor with proper parameters', async () => { +test('executes the task by calling the executor with proper parameters, using given actionId when no actionRef in references', async () => { const taskRunner = taskRunnerFactory.create({ taskInstance: mockedTaskInstance, }); @@ -146,6 +146,61 @@ test('executes the task by calling the executor with proper parameters', async ( ); }); +test('executes the task by calling the executor with proper parameters, using stored actionId when actionRef is in references', async () => { + const taskRunner = taskRunnerFactory.create({ + taskInstance: mockedTaskInstance, + }); + + mockedActionExecutor.execute.mockResolvedValueOnce({ status: 'ok', actionId: '2' }); + spaceIdToNamespace.mockReturnValueOnce('namespace-test'); + mockedEncryptedSavedObjectsClient.getDecryptedAsInternalUser.mockResolvedValueOnce({ + id: '3', + type: 'action_task_params', + attributes: { + actionId: '2', + params: { baz: true }, + apiKey: Buffer.from('123:abc').toString('base64'), + }, + references: [ + { + id: '9', + name: 'actionRef', + type: 'action', + }, + ], + }); + + const runnerResult = await taskRunner.run(); + + expect(runnerResult).toBeUndefined(); + expect(spaceIdToNamespace).toHaveBeenCalledWith('test'); + expect( + mockedEncryptedSavedObjectsClient.getDecryptedAsInternalUser + ).toHaveBeenCalledWith('action_task_params', '3', { namespace: 'namespace-test' }); + + expect(mockedActionExecutor.execute).toHaveBeenCalledWith({ + actionId: '9', + isEphemeral: false, + params: { baz: true }, + relatedSavedObjects: [], + request: expect.objectContaining({ + headers: { + // base64 encoded "123:abc" + authorization: 'ApiKey MTIzOmFiYw==', + }, + }), + taskInfo: { + scheduled: new Date(), + }, + }); + + const [executeParams] = mockedActionExecutor.execute.mock.calls[0]; + expect(taskRunnerFactoryInitializerParams.basePathService.set).toHaveBeenCalledWith( + executeParams.request, + '/s/test' + ); +}); + test('cleans up action_task_params object', async () => { const taskRunner = taskRunnerFactory.create({ taskInstance: mockedTaskInstance, @@ -161,7 +216,13 @@ test('cleans up action_task_params object', async () => { params: { baz: true }, apiKey: Buffer.from('123:abc').toString('base64'), }, - references: [], + references: [ + { + id: '2', + name: 'actionRef', + type: 'action', + }, + ], }); await taskRunner.run(); @@ -184,7 +245,13 @@ test('runs successfully when cleanup fails and logs the error', async () => { params: { baz: true }, apiKey: Buffer.from('123:abc').toString('base64'), }, - references: [], + references: [ + { + id: '2', + name: 'actionRef', + type: 'action', + }, + ], }); services.savedObjectsClient.delete.mockRejectedValueOnce(new Error('Fail')); @@ -209,7 +276,13 @@ test('throws an error with suggested retry logic when return status is error', a params: { baz: true }, apiKey: Buffer.from('123:abc').toString('base64'), }, - references: [], + references: [ + { + id: '2', + name: 'actionRef', + type: 'action', + }, + ], }); mockedActionExecutor.execute.mockResolvedValueOnce({ status: 'error', @@ -244,7 +317,13 @@ test('uses API key when provided', async () => { params: { baz: true }, apiKey: Buffer.from('123:abc').toString('base64'), }, - references: [], + references: [ + { + id: '2', + name: 'actionRef', + type: 'action', + }, + ], }); await taskRunner.run(); @@ -272,7 +351,7 @@ test('uses API key when provided', async () => { ); }); -test('uses relatedSavedObjects when provided', async () => { +test('uses relatedSavedObjects merged with references when provided', async () => { const taskRunner = taskRunnerFactory.create({ taskInstance: mockedTaskInstance, }); @@ -286,9 +365,20 @@ test('uses relatedSavedObjects when provided', async () => { actionId: '2', params: { baz: true }, apiKey: Buffer.from('123:abc').toString('base64'), - relatedSavedObjects: [{ id: 'some-id', type: 'some-type' }], + relatedSavedObjects: [{ id: 'related_some-type_0', type: 'some-type' }], }, - references: [], + references: [ + { + id: '2', + name: 'actionRef', + type: 'action', + }, + { + id: 'some-id', + name: 'related_some-type_0', + type: 'some-type', + }, + ], }); await taskRunner.run(); @@ -315,6 +405,56 @@ test('uses relatedSavedObjects when provided', async () => { }); }); +test('uses relatedSavedObjects as is when references are empty', async () => { + const taskRunner = taskRunnerFactory.create({ + taskInstance: mockedTaskInstance, + }); + + mockedActionExecutor.execute.mockResolvedValueOnce({ status: 'ok', actionId: '2' }); + spaceIdToNamespace.mockReturnValueOnce('namespace-test'); + mockedEncryptedSavedObjectsClient.getDecryptedAsInternalUser.mockResolvedValueOnce({ + id: '3', + type: 'action_task_params', + attributes: { + actionId: '2', + params: { baz: true }, + apiKey: Buffer.from('123:abc').toString('base64'), + relatedSavedObjects: [{ id: 'abc', type: 'some-type', namespace: 'yo' }], + }, + references: [ + { + id: '2', + name: 'actionRef', + type: 'action', + }, + ], + }); + + await taskRunner.run(); + + expect(mockedActionExecutor.execute).toHaveBeenCalledWith({ + actionId: '2', + isEphemeral: false, + params: { baz: true }, + relatedSavedObjects: [ + { + id: 'abc', + type: 'some-type', + namespace: 'yo', + }, + ], + request: expect.objectContaining({ + headers: { + // base64 encoded "123:abc" + authorization: 'ApiKey MTIzOmFiYw==', + }, + }), + taskInfo: { + scheduled: new Date(), + }, + }); +}); + test('sanitizes invalid relatedSavedObjects when provided', async () => { const taskRunner = taskRunnerFactory.create({ taskInstance: mockedTaskInstance, @@ -329,9 +469,20 @@ test('sanitizes invalid relatedSavedObjects when provided', async () => { actionId: '2', params: { baz: true }, apiKey: Buffer.from('123:abc').toString('base64'), - relatedSavedObjects: [{ Xid: 'some-id', type: 'some-type' }], + relatedSavedObjects: [{ Xid: 'related_some-type_0', type: 'some-type' }], }, - references: [], + references: [ + { + id: '2', + name: 'actionRef', + type: 'action', + }, + { + id: 'some-id', + name: 'related_some-type_0', + type: 'some-type', + }, + ], }); await taskRunner.run(); @@ -366,7 +517,13 @@ test(`doesn't use API key when not provided`, async () => { actionId: '2', params: { baz: true }, }, - references: [], + references: [ + { + id: '2', + name: 'actionRef', + type: 'action', + }, + ], }); await taskRunner.run(); @@ -404,7 +561,13 @@ test(`throws an error when license doesn't support the action type`, async () => params: { baz: true }, apiKey: Buffer.from('123:abc').toString('base64'), }, - references: [], + references: [ + { + id: '2', + name: 'actionRef', + type: 'action', + }, + ], }); mockedActionExecutor.execute.mockImplementation(() => { throw new ActionTypeDisabledError('Fail', 'license_invalid'); diff --git a/x-pack/plugins/actions/server/lib/task_runner_factory.ts b/x-pack/plugins/actions/server/lib/task_runner_factory.ts index 2354ea55eded6..45ae6c1d5fae9 100644 --- a/x-pack/plugins/actions/server/lib/task_runner_factory.ts +++ b/x-pack/plugins/actions/server/lib/task_runner_factory.ts @@ -33,7 +33,8 @@ import { } from '../types'; import { ACTION_TASK_PARAMS_SAVED_OBJECT_TYPE } from '../constants/saved_objects'; import { asSavedObjectExecutionSource } from './action_execution_source'; -import { validatedRelatedSavedObjects } from './related_saved_objects'; +import { RelatedSavedObjects, validatedRelatedSavedObjects } from './related_saved_objects'; +import { injectSavedObjectReferences } from './action_task_params_utils'; export interface TaskRunnerContext { logger: Logger; @@ -178,11 +179,30 @@ async function getActionTaskParams( const { spaceId } = executorParams; const namespace = spaceIdToNamespace(spaceId); if (isPersistedActionTask(executorParams)) { - return encryptedSavedObjectsClient.getDecryptedAsInternalUser( + const actionTask = await encryptedSavedObjectsClient.getDecryptedAsInternalUser( ACTION_TASK_PARAMS_SAVED_OBJECT_TYPE, executorParams.actionTaskParamsId, { namespace } ); + + const { + attributes: { relatedSavedObjects }, + references, + } = actionTask; + + const { + actionId, + relatedSavedObjects: injectedRelatedSavedObjects, + } = injectSavedObjectReferences(references, relatedSavedObjects as RelatedSavedObjects); + + return { + ...actionTask, + attributes: { + ...actionTask.attributes, + ...(actionId ? { actionId } : {}), + ...(relatedSavedObjects ? { relatedSavedObjects: injectedRelatedSavedObjects } : {}), + }, + }; } else { return { attributes: executorParams.taskParams, references: executorParams.references ?? [] }; } diff --git a/x-pack/plugins/actions/server/plugin.ts b/x-pack/plugins/actions/server/plugin.ts index 9e22816056618..e5c81f6320f51 100644 --- a/x-pack/plugins/actions/server/plugin.ts +++ b/x-pack/plugins/actions/server/plugin.ts @@ -229,7 +229,8 @@ export class ActionsPlugin implements Plugin { + beforeEach(() => { + jest.resetAllMocks(); + encryptedSavedObjectsSetup.createMigration.mockImplementation(({ migration }) => migration); + }); + + describe('7.16.0', () => { + test('adds actionId to references array if actionId is not preconfigured', () => { + const migration716 = getActionTaskParamsMigrations( + encryptedSavedObjectsSetup, + preconfiguredActions + )['7.16.0']; + const actionTaskParam = getMockData(); + const migratedActionTaskParam = migration716(actionTaskParam, context); + expect(migratedActionTaskParam).toEqual({ + ...actionTaskParam, + references: [ + { + id: actionTaskParam.attributes.actionId, + name: 'actionRef', + type: 'action', + }, + ], + }); + }); + + test('does not add actionId to references array if actionId is preconfigured', () => { + const migration716 = getActionTaskParamsMigrations( + encryptedSavedObjectsSetup, + preconfiguredActions + )['7.16.0']; + const actionTaskParam = getMockData({ actionId: 'my-slack1' }); + const migratedActionTaskParam = migration716(actionTaskParam, context); + expect(migratedActionTaskParam).toEqual({ + ...actionTaskParam, + references: [], + }); + }); + + test('handles empty relatedSavedObjects array', () => { + const migration716 = getActionTaskParamsMigrations( + encryptedSavedObjectsSetup, + preconfiguredActions + )['7.16.0']; + const actionTaskParam = getMockData({ relatedSavedObjects: [] }); + const migratedActionTaskParam = migration716(actionTaskParam, context); + expect(migratedActionTaskParam).toEqual({ + ...actionTaskParam, + attributes: { + ...actionTaskParam.attributes, + relatedSavedObjects: [], + }, + references: [ + { + id: actionTaskParam.attributes.actionId, + name: 'actionRef', + type: 'action', + }, + ], + }); + }); + + test('adds actionId and relatedSavedObjects to references array', () => { + const migration716 = getActionTaskParamsMigrations( + encryptedSavedObjectsSetup, + preconfiguredActions + )['7.16.0']; + const actionTaskParam = getMockData({ + relatedSavedObjects: [ + { + id: 'some-id', + namespace: 'some-namespace', + type: 'some-type', + typeId: 'some-typeId', + }, + ], + }); + const migratedActionTaskParam = migration716(actionTaskParam, context); + expect(migratedActionTaskParam).toEqual({ + ...actionTaskParam, + attributes: { + ...actionTaskParam.attributes, + relatedSavedObjects: [ + { + id: 'related_some-type_0', + namespace: 'some-namespace', + type: 'some-type', + typeId: 'some-typeId', + }, + ], + }, + references: [ + { + id: actionTaskParam.attributes.actionId, + name: 'actionRef', + type: 'action', + }, + { + id: 'some-id', + name: 'related_some-type_0', + type: 'some-type', + }, + ], + }); + }); + + test('only adds relatedSavedObjects to references array if action is preconfigured', () => { + const migration716 = getActionTaskParamsMigrations( + encryptedSavedObjectsSetup, + preconfiguredActions + )['7.16.0']; + const actionTaskParam = getMockData({ + actionId: 'my-slack1', + relatedSavedObjects: [ + { + id: 'some-id', + namespace: 'some-namespace', + type: 'some-type', + typeId: 'some-typeId', + }, + ], + }); + const migratedActionTaskParam = migration716(actionTaskParam, context); + expect(migratedActionTaskParam).toEqual({ + ...actionTaskParam, + attributes: { + ...actionTaskParam.attributes, + relatedSavedObjects: [ + { + id: 'related_some-type_0', + namespace: 'some-namespace', + type: 'some-type', + typeId: 'some-typeId', + }, + ], + }, + references: [ + { + id: 'some-id', + name: 'related_some-type_0', + type: 'some-type', + }, + ], + }); + }); + + test('adds actionId and multiple relatedSavedObjects to references array', () => { + const migration716 = getActionTaskParamsMigrations( + encryptedSavedObjectsSetup, + preconfiguredActions + )['7.16.0']; + const actionTaskParam = getMockData({ + relatedSavedObjects: [ + { + id: 'some-id', + namespace: 'some-namespace', + type: 'some-type', + typeId: 'some-typeId', + }, + { + id: 'another-id', + type: 'another-type', + typeId: 'another-typeId', + }, + ], + }); + const migratedActionTaskParam = migration716(actionTaskParam, context); + expect(migratedActionTaskParam).toEqual({ + ...actionTaskParam, + attributes: { + ...actionTaskParam.attributes, + relatedSavedObjects: [ + { + id: 'related_some-type_0', + namespace: 'some-namespace', + type: 'some-type', + typeId: 'some-typeId', + }, + { + id: 'related_another-type_1', + type: 'another-type', + typeId: 'another-typeId', + }, + ], + }, + references: [ + { + id: actionTaskParam.attributes.actionId, + name: 'actionRef', + type: 'action', + }, + { + id: 'some-id', + name: 'related_some-type_0', + type: 'some-type', + }, + { + id: 'another-id', + name: 'related_another-type_1', + type: 'another-type', + }, + ], + }); + }); + + test('does not overwrite existing references', () => { + const migration716 = getActionTaskParamsMigrations( + encryptedSavedObjectsSetup, + preconfiguredActions + )['7.16.0']; + const actionTaskParam = getMockData( + { + relatedSavedObjects: [ + { + id: 'some-id', + namespace: 'some-namespace', + type: 'some-type', + typeId: 'some-typeId', + }, + ], + }, + [ + { + id: 'existing-ref-id', + name: 'existingRef', + type: 'existing-ref-type', + }, + ] + ); + const migratedActionTaskParam = migration716(actionTaskParam, context); + expect(migratedActionTaskParam).toEqual({ + ...actionTaskParam, + attributes: { + ...actionTaskParam.attributes, + relatedSavedObjects: [ + { + id: 'related_some-type_0', + namespace: 'some-namespace', + type: 'some-type', + typeId: 'some-typeId', + }, + ], + }, + references: [ + { + id: 'existing-ref-id', + name: 'existingRef', + type: 'existing-ref-type', + }, + { + id: actionTaskParam.attributes.actionId, + name: 'actionRef', + type: 'action', + }, + { + id: 'some-id', + name: 'related_some-type_0', + type: 'some-type', + }, + ], + }); + }); + + test('does not overwrite existing references if relatedSavedObjects is undefined', () => { + const migration716 = getActionTaskParamsMigrations( + encryptedSavedObjectsSetup, + preconfiguredActions + )['7.16.0']; + const actionTaskParam = getMockData({}, [ + { + id: 'existing-ref-id', + name: 'existingRef', + type: 'existing-ref-type', + }, + ]); + const migratedActionTaskParam = migration716(actionTaskParam, context); + expect(migratedActionTaskParam).toEqual({ + ...actionTaskParam, + references: [ + { + id: 'existing-ref-id', + name: 'existingRef', + type: 'existing-ref-type', + }, + { + id: actionTaskParam.attributes.actionId, + name: 'actionRef', + type: 'action', + }, + ], + }); + }); + + test('does not overwrite existing references if relatedSavedObjects is empty', () => { + const migration716 = getActionTaskParamsMigrations( + encryptedSavedObjectsSetup, + preconfiguredActions + )['7.16.0']; + const actionTaskParam = getMockData({ relatedSavedObjects: [] }, [ + { + id: 'existing-ref-id', + name: 'existingRef', + type: 'existing-ref-type', + }, + ]); + const migratedActionTaskParam = migration716(actionTaskParam, context); + expect(migratedActionTaskParam).toEqual({ + ...actionTaskParam, + attributes: { + ...actionTaskParam.attributes, + relatedSavedObjects: [], + }, + references: [ + { + id: 'existing-ref-id', + name: 'existingRef', + type: 'existing-ref-type', + }, + { + id: actionTaskParam.attributes.actionId, + name: 'actionRef', + type: 'action', + }, + ], + }); + }); + }); +}); + +describe('handles errors during migrations', () => { + beforeEach(() => { + jest.resetAllMocks(); + encryptedSavedObjectsSetup.createMigration.mockImplementation(() => () => { + throw new Error(`Can't migrate!`); + }); + }); + + describe('7.16.0 throws if migration fails', () => { + test('should show the proper exception', () => { + const migration716 = getActionTaskParamsMigrations( + encryptedSavedObjectsSetup, + preconfiguredActions + )['7.16.0']; + const actionTaskParam = getMockData(); + expect(() => { + migration716(actionTaskParam, context); + }).toThrowError(`Can't migrate!`); + expect(context.log.error).toHaveBeenCalledWith( + `encryptedSavedObject 7.16.0 migration failed for action task param ${actionTaskParam.id} with error: Can't migrate!`, + { + migrations: { + actionTaskParamDocument: actionTaskParam, + }, + } + ); + }); + }); +}); + +describe('isPreconfiguredAction()', () => { + test('returns true if actionId is preconfigured action', () => { + expect( + isPreconfiguredAction(getMockData({ actionId: 'my-slack1' }), preconfiguredActions) + ).toEqual(true); + }); + + test('returns false if actionId is not preconfigured action', () => { + expect(isPreconfiguredAction(getMockData(), preconfiguredActions)).toEqual(false); + }); +}); + +function getMockData( + overwrites: Record = {}, + referencesOverwrites: SavedObjectReference[] = [] +): SavedObjectUnsanitizedDoc { + return { + attributes: { + actionId: uuid.v4(), + params: {}, + ...overwrites, + }, + references: [...referencesOverwrites], + id: uuid.v4(), + type: 'action_task_param', + }; +} diff --git a/x-pack/plugins/actions/server/saved_objects/action_task_params_migrations.ts b/x-pack/plugins/actions/server/saved_objects/action_task_params_migrations.ts new file mode 100644 index 0000000000000..3612642160443 --- /dev/null +++ b/x-pack/plugins/actions/server/saved_objects/action_task_params_migrations.ts @@ -0,0 +1,139 @@ +/* + * 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 { + LogMeta, + SavedObjectMigrationMap, + SavedObjectUnsanitizedDoc, + SavedObjectMigrationFn, + SavedObjectMigrationContext, + SavedObjectReference, +} from '../../../../../src/core/server'; +import { ActionTaskParams, PreConfiguredAction } from '../types'; +import { EncryptedSavedObjectsPluginSetup } from '../../../encrypted_saved_objects/server'; +import type { IsMigrationNeededPredicate } from '../../../encrypted_saved_objects/server'; +import { RelatedSavedObjects } from '../lib/related_saved_objects'; + +interface ActionTaskParamsLogMeta extends LogMeta { + migrations: { actionTaskParamDocument: SavedObjectUnsanitizedDoc }; +} + +type ActionTaskParamMigration = ( + doc: SavedObjectUnsanitizedDoc +) => SavedObjectUnsanitizedDoc; + +function createEsoMigration( + encryptedSavedObjects: EncryptedSavedObjectsPluginSetup, + isMigrationNeededPredicate: IsMigrationNeededPredicate, + migrationFunc: ActionTaskParamMigration +) { + return encryptedSavedObjects.createMigration({ + isMigrationNeededPredicate, + migration: migrationFunc, + shouldMigrateIfDecryptionFails: true, // shouldMigrateIfDecryptionFails flag that applies the migration to undecrypted document if decryption fails + }); +} + +export function getActionTaskParamsMigrations( + encryptedSavedObjects: EncryptedSavedObjectsPluginSetup, + preconfiguredActions: PreConfiguredAction[] +): SavedObjectMigrationMap { + const migrationActionTaskParamsSixteen = createEsoMigration( + encryptedSavedObjects, + (doc): doc is SavedObjectUnsanitizedDoc => true, + pipeMigrations(getUseSavedObjectReferencesFn(preconfiguredActions)) + ); + + return { + '7.16.0': executeMigrationWithErrorHandling(migrationActionTaskParamsSixteen, '7.16.0'), + }; +} + +function executeMigrationWithErrorHandling( + migrationFunc: SavedObjectMigrationFn, + version: string +) { + return ( + doc: SavedObjectUnsanitizedDoc, + context: SavedObjectMigrationContext + ) => { + try { + return migrationFunc(doc, context); + } catch (ex) { + context.log.error( + `encryptedSavedObject ${version} migration failed for action task param ${doc.id} with error: ${ex.message}`, + { + migrations: { + actionTaskParamDocument: doc, + }, + } + ); + throw ex; + } + }; +} + +export function isPreconfiguredAction( + doc: SavedObjectUnsanitizedDoc, + preconfiguredActions: PreConfiguredAction[] +): boolean { + return !!preconfiguredActions.find((action) => action.id === doc.attributes.actionId); +} + +function getUseSavedObjectReferencesFn(preconfiguredActions: PreConfiguredAction[]) { + return (doc: SavedObjectUnsanitizedDoc) => { + return useSavedObjectReferences(doc, preconfiguredActions); + }; +} + +function useSavedObjectReferences( + doc: SavedObjectUnsanitizedDoc, + preconfiguredActions: PreConfiguredAction[] +): SavedObjectUnsanitizedDoc { + const { + attributes: { actionId, relatedSavedObjects }, + references, + } = doc; + + const newReferences: SavedObjectReference[] = []; + const relatedSavedObjectRefs: RelatedSavedObjects = []; + + if (!isPreconfiguredAction(doc, preconfiguredActions)) { + newReferences.push({ + id: actionId, + name: 'actionRef', + type: 'action', + }); + } + + // Add related saved objects, if any + ((relatedSavedObjects as RelatedSavedObjects) ?? []).forEach((relatedSavedObject, index) => { + relatedSavedObjectRefs.push({ + ...relatedSavedObject, + id: `related_${relatedSavedObject.type}_${index}`, + }); + newReferences.push({ + id: relatedSavedObject.id, + name: `related_${relatedSavedObject.type}_${index}`, + type: relatedSavedObject.type, + }); + }); + + return { + ...doc, + attributes: { + ...doc.attributes, + ...(relatedSavedObjects ? { relatedSavedObjects: relatedSavedObjectRefs } : {}), + }, + references: [...(references ?? []), ...(newReferences ?? [])], + }; +} + +function pipeMigrations(...migrations: ActionTaskParamMigration[]): ActionTaskParamMigration { + return (doc: SavedObjectUnsanitizedDoc) => + migrations.reduce((migratedDoc, nextMigration) => nextMigration(migratedDoc), doc); +} diff --git a/x-pack/plugins/actions/server/saved_objects/migrations.test.ts b/x-pack/plugins/actions/server/saved_objects/actions_migrations.test.ts similarity index 88% rename from x-pack/plugins/actions/server/saved_objects/migrations.test.ts rename to x-pack/plugins/actions/server/saved_objects/actions_migrations.test.ts index beaea76756113..bc0e59279abc1 100644 --- a/x-pack/plugins/actions/server/saved_objects/migrations.test.ts +++ b/x-pack/plugins/actions/server/saved_objects/actions_migrations.test.ts @@ -6,7 +6,7 @@ */ import uuid from 'uuid'; -import { getMigrations } from './migrations'; +import { getActionsMigrations } from './actions_migrations'; import { RawAction } from '../types'; import { SavedObjectUnsanitizedDoc } from 'kibana/server'; import { encryptedSavedObjectsMock } from '../../../encrypted_saved_objects/server/mocks'; @@ -23,7 +23,7 @@ describe('successful migrations', () => { describe('7.10.0', () => { test('add hasAuth config property for .email actions', () => { - const migration710 = getMigrations(encryptedSavedObjectsSetup)['7.10.0']; + const migration710 = getActionsMigrations(encryptedSavedObjectsSetup)['7.10.0']; const action = getMockDataForEmail({}); const migratedAction = migration710(action, context); expect(migratedAction.attributes.config).toEqual({ @@ -41,7 +41,7 @@ describe('successful migrations', () => { }); test('rename cases configuration object', () => { - const migration710 = getMigrations(encryptedSavedObjectsSetup)['7.10.0']; + const migration710 = getActionsMigrations(encryptedSavedObjectsSetup)['7.10.0']; const action = getCasesMockData({}); const migratedAction = migration710(action, context); expect(migratedAction.attributes.config).toEqual({ @@ -61,7 +61,7 @@ describe('successful migrations', () => { describe('7.11.0', () => { test('add hasAuth = true for .webhook actions with user and password', () => { - const migration711 = getMigrations(encryptedSavedObjectsSetup)['7.11.0']; + const migration711 = getActionsMigrations(encryptedSavedObjectsSetup)['7.11.0']; const action = getMockDataForWebhook({}, true); expect(migration711(action, context)).toMatchObject({ ...action, @@ -75,7 +75,7 @@ describe('successful migrations', () => { }); test('add hasAuth = false for .webhook actions without user and password', () => { - const migration711 = getMigrations(encryptedSavedObjectsSetup)['7.11.0']; + const migration711 = getActionsMigrations(encryptedSavedObjectsSetup)['7.11.0']; const action = getMockDataForWebhook({}, false); expect(migration711(action, context)).toMatchObject({ ...action, @@ -88,7 +88,7 @@ describe('successful migrations', () => { }); }); test('remove cases mapping object', () => { - const migration711 = getMigrations(encryptedSavedObjectsSetup)['7.11.0']; + const migration711 = getActionsMigrations(encryptedSavedObjectsSetup)['7.11.0']; const action = getMockData({ config: { incidentConfiguration: { mapping: [] }, isCaseOwned: true, another: 'value' }, }); @@ -106,7 +106,7 @@ describe('successful migrations', () => { describe('7.14.0', () => { test('add isMissingSecrets property for actions', () => { - const migration714 = getMigrations(encryptedSavedObjectsSetup)['7.14.0']; + const migration714 = getActionsMigrations(encryptedSavedObjectsSetup)['7.14.0']; const action = getMockData({ isMissingSecrets: undefined }); const migratedAction = migration714(action, context); expect(migratedAction).toEqual({ @@ -130,7 +130,7 @@ describe('handles errors during migrations', () => { describe('7.10.0 throws if migration fails', () => { test('should show the proper exception', () => { - const migration710 = getMigrations(encryptedSavedObjectsSetup)['7.10.0']; + const migration710 = getActionsMigrations(encryptedSavedObjectsSetup)['7.10.0']; const action = getMockDataForEmail({}); expect(() => { migration710(action, context); @@ -148,7 +148,7 @@ describe('handles errors during migrations', () => { describe('7.11.0 throws if migration fails', () => { test('should show the proper exception', () => { - const migration711 = getMigrations(encryptedSavedObjectsSetup)['7.11.0']; + const migration711 = getActionsMigrations(encryptedSavedObjectsSetup)['7.11.0']; const action = getMockDataForEmail({}); expect(() => { migration711(action, context); @@ -166,7 +166,7 @@ describe('handles errors during migrations', () => { describe('7.14.0 throws if migration fails', () => { test('should show the proper exception', () => { - const migration714 = getMigrations(encryptedSavedObjectsSetup)['7.14.0']; + const migration714 = getActionsMigrations(encryptedSavedObjectsSetup)['7.14.0']; const action = getMockDataForEmail({}); expect(() => { migration714(action, context); diff --git a/x-pack/plugins/actions/server/saved_objects/migrations.ts b/x-pack/plugins/actions/server/saved_objects/actions_migrations.ts similarity index 99% rename from x-pack/plugins/actions/server/saved_objects/migrations.ts rename to x-pack/plugins/actions/server/saved_objects/actions_migrations.ts index de15de7b15e23..a72565e00ef7b 100644 --- a/x-pack/plugins/actions/server/saved_objects/migrations.ts +++ b/x-pack/plugins/actions/server/saved_objects/actions_migrations.ts @@ -36,7 +36,7 @@ function createEsoMigration( }); } -export function getMigrations( +export function getActionsMigrations( encryptedSavedObjects: EncryptedSavedObjectsPluginSetup ): SavedObjectMigrationMap { const migrationActionsTen = createEsoMigration( diff --git a/x-pack/plugins/actions/server/saved_objects/index.ts b/x-pack/plugins/actions/server/saved_objects/index.ts index c4ce38c857151..71ec92645b249 100644 --- a/x-pack/plugins/actions/server/saved_objects/index.ts +++ b/x-pack/plugins/actions/server/saved_objects/index.ts @@ -13,8 +13,9 @@ import type { } from 'kibana/server'; import { EncryptedSavedObjectsPluginSetup } from '../../../encrypted_saved_objects/server'; import mappings from './mappings.json'; -import { getMigrations } from './migrations'; -import { RawAction } from '../types'; +import { getActionsMigrations } from './actions_migrations'; +import { getActionTaskParamsMigrations } from './action_task_params_migrations'; +import { PreConfiguredAction, RawAction } from '../types'; import { getImportWarnings } from './get_import_warnings'; import { transformConnectorsForExport } from './transform_connectors_for_export'; import { ActionTypeRegistry } from '../action_type_registry'; @@ -28,14 +29,15 @@ export function setupSavedObjects( savedObjects: SavedObjectsServiceSetup, encryptedSavedObjects: EncryptedSavedObjectsPluginSetup, actionTypeRegistry: ActionTypeRegistry, - taskManagerIndex: string + taskManagerIndex: string, + preconfiguredActions: PreConfiguredAction[] ) { savedObjects.registerType({ name: ACTION_SAVED_OBJECT_TYPE, hidden: true, namespaceType: 'single', mappings: mappings.action as SavedObjectsTypeMappingDefinition, - migrations: getMigrations(encryptedSavedObjects), + migrations: getActionsMigrations(encryptedSavedObjects), management: { defaultSearchField: 'name', importableAndExportable: true, @@ -71,6 +73,7 @@ export function setupSavedObjects( hidden: true, namespaceType: 'single', mappings: mappings.action_task_params as SavedObjectsTypeMappingDefinition, + migrations: getActionTaskParamsMigrations(encryptedSavedObjects, preconfiguredActions), excludeOnUpgrade: async ({ readonlyEsClient }) => { const oldestIdleActionTask = await getOldestIdleActionTask( readonlyEsClient, diff --git a/x-pack/test/alerting_api_integration/spaces_only/tests/action_task_params/index.ts b/x-pack/test/alerting_api_integration/spaces_only/tests/action_task_params/index.ts new file mode 100644 index 0000000000000..115358c4bce3a --- /dev/null +++ b/x-pack/test/alerting_api_integration/spaces_only/tests/action_task_params/index.ts @@ -0,0 +1,19 @@ +/* + * 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 { FtrProviderContext } from '../../../common/ftr_provider_context'; +import { buildUp, tearDown } from '..'; + +// eslint-disable-next-line import/no-default-export +export default function actionTaskParamsTests({ loadTestFile, getService }: FtrProviderContext) { + describe('Action Task Params', () => { + before(async () => buildUp(getService)); + after(async () => tearDown(getService)); + + loadTestFile(require.resolve('./migrations')); + }); +} diff --git a/x-pack/test/alerting_api_integration/spaces_only/tests/action_task_params/migrations.ts b/x-pack/test/alerting_api_integration/spaces_only/tests/action_task_params/migrations.ts new file mode 100644 index 0000000000000..0c0b62b6cb529 --- /dev/null +++ b/x-pack/test/alerting_api_integration/spaces_only/tests/action_task_params/migrations.ts @@ -0,0 +1,90 @@ +/* + * 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 expect from '@kbn/expect'; +import { SavedObject, SavedObjectReference } from 'src/core/server'; +import { ActionTaskParams } from '../../../../../plugins/actions/server/types'; +import { FtrProviderContext } from '../../../common/ftr_provider_context'; + +// eslint-disable-next-line import/no-default-export +export default function createGetTests({ getService }: FtrProviderContext) { + const es = getService('es'); + const esArchiver = getService('esArchiver'); + + describe('migrations', () => { + before(async () => { + await esArchiver.load('x-pack/test/functional/es_archives/action_task_params'); + }); + + after(async () => { + await esArchiver.unload('x-pack/test/functional/es_archives/action_task_params'); + }); + + it('7.16.0 migrates action_task_params to use references array', async () => { + // Inspect migration of non-preconfigured connector ID + const response = await es.get>({ + index: '.kibana', + id: 'action_task_params:b9af6280-0052-11ec-917b-f7aa317691ed', + }); + expect(response.statusCode).to.eql(200); + const { actionId, relatedSavedObjects, references } = getActionIdAndRelatedSavedObjects( + response.body._source + ); + + expect(references.find((ref: SavedObjectReference) => ref.name === 'actionRef')).to.eql({ + name: 'actionRef', + id: actionId, + type: 'action', + }); + + // Should have reference entry for each relatedSavedObject entry + (relatedSavedObjects ?? []).forEach((relatedSavedObject: any) => { + expect( + references.find((ref: SavedObjectReference) => ref.name === relatedSavedObject.id) + ).not.to.be(undefined); + }); + + // Inspect migration of preconfigured connector ID + const preconfiguredConnectorResponse = await es.get>({ + index: '.kibana', + id: 'action_task_params:0205a520-0054-11ec-917b-f7aa317691ed', + }); + expect(preconfiguredConnectorResponse.statusCode).to.eql(200); + + const { + relatedSavedObjects: preconfiguredRelatedSavedObjects, + references: preconfiguredReferences, + } = getActionIdAndRelatedSavedObjects(preconfiguredConnectorResponse.body._source); + + expect( + preconfiguredReferences.find((ref: SavedObjectReference) => ref.name === 'actionRef') + ).to.eql(undefined); + + // Should have reference entry for each relatedSavedObject entry + (preconfiguredRelatedSavedObjects ?? []).forEach((relatedSavedObject: any) => { + expect( + preconfiguredReferences.find( + (ref: SavedObjectReference) => ref.name === relatedSavedObject.id + ) + ).not.to.be(undefined); + }); + }); + }); + + function getActionIdAndRelatedSavedObjects(responseSource: any) { + if (!responseSource) { + return {}; + } + + const actionTaskParams = (responseSource as any)?.action_task_params as ActionTaskParams; + const actionId = actionTaskParams.actionId; + const relatedSavedObjects = actionTaskParams.relatedSavedObjects as unknown[]; + const references = responseSource?.references ?? []; + + return { actionId, relatedSavedObjects, references }; + } +} diff --git a/x-pack/test/alerting_api_integration/spaces_only/tests/index.ts b/x-pack/test/alerting_api_integration/spaces_only/tests/index.ts index 8b0addcba26e9..88e5e0740789f 100644 --- a/x-pack/test/alerting_api_integration/spaces_only/tests/index.ts +++ b/x-pack/test/alerting_api_integration/spaces_only/tests/index.ts @@ -15,6 +15,7 @@ export default function alertingApiIntegrationTests({ loadTestFile }: FtrProvide loadTestFile(require.resolve('./actions')); loadTestFile(require.resolve('./alerting')); + loadTestFile(require.resolve('./action_task_params')); }); } diff --git a/x-pack/test/functional/es_archives/action_task_params/data.json b/x-pack/test/functional/es_archives/action_task_params/data.json new file mode 100644 index 0000000000000..158faa429d888 --- /dev/null +++ b/x-pack/test/functional/es_archives/action_task_params/data.json @@ -0,0 +1,63 @@ +{ + "type": "doc", + "value": { + "index": ".kibana_1", + "id": "action_task_params:b9af6280-0052-11ec-917b-f7aa317691ed", + "source": { + "type": "action_task_params", + "action_task_params" : { + "actionId" : "918da460-0052-11ec-917b-f7aa317691ed", + "params" : { + "level" : "info", + "message" : "yo yo" + }, + "relatedSavedObjects" : [ + { + "type" : "alert", + "typeId" : "example.always-firing", + "id" : "b6db0cd0-0052-11ec-917b-f7aa317691ed" + } + ] + }, + "references" : [ + { + "name" : "source", + "id" : "b6db0cd0-0052-11ec-917b-f7aa317691ed", + "type" : "alert" + } + ] + } + } +} + +{ + "type": "doc", + "value": { + "index": ".kibana_1", + "id": "action_task_params:0205a520-0054-11ec-917b-f7aa317691ed", + "source": { + "type": "action_task_params", + "action_task_params" : { + "actionId" : "my-slack1", + "params" : { + "level" : "info", + "message" : "hi hi" + }, + "relatedSavedObjects" : [ + { + "type" : "alert", + "typeId" : "example.always-firing", + "id" : "b50bfcb0-0053-11ec-917b-f7aa317691ed" + } + ] + }, + "references" : [ + { + "name" : "source", + "id" : "b50bfcb0-0053-11ec-917b-f7aa317691ed", + "type" : "alert" + } + ] + } + } +} \ No newline at end of file diff --git a/x-pack/test/functional/es_archives/action_task_params/mappings.json b/x-pack/test/functional/es_archives/action_task_params/mappings.json new file mode 100644 index 0000000000000..d0eb35fa3b157 --- /dev/null +++ b/x-pack/test/functional/es_archives/action_task_params/mappings.json @@ -0,0 +1,2572 @@ +{ + "type": "index", + "value": { + "aliases": { + ".kibana": { + } + }, + "index": ".kibana_1", + "mappings": { + "_meta": { + "migrationMappingPropertyHashes": { + "action": "6e96ac5e648f57523879661ea72525b7", + "action_task_params": "a9d49f184ee89641044be0ca2950fa3a", + "alert": "7b44fba6773e37c806ce290ea9b7024e", + "apm-indices": "9bb9b2bf1fa636ed8619cbab5ce6a1dd", + "apm-telemetry": "3d1b76c39bfb2cc8296b024d73854724", + "app_search_telemetry": "3d1b76c39bfb2cc8296b024d73854724", + "application_usage_daily": "43b8830d5d0df85a6823d290885fc9fd", + "application_usage_totals": "3d1b76c39bfb2cc8296b024d73854724", + "application_usage_transactional": "3d1b76c39bfb2cc8296b024d73854724", + "canvas-element": "7390014e1091044523666d97247392fc", + "canvas-workpad": "b0a1706d356228dbdcb4a17e6b9eb231", + "canvas-workpad-template": "ae2673f678281e2c055d764b153e9715", + "cases": "32aa96a6d3855ddda53010ae2048ac22", + "cases-comments": "c2061fb929f585df57425102fa928b4b", + "cases-configure": "42711cbb311976c0687853f4c1354572", + "cases-user-actions": "32277330ec6b721abe3b846cfd939a71", + "config": "c63748b75f39d0c54de12d12c1ccbc20", + "dashboard": "d00f614b29a80360e1190193fd333bab", + "endpoint:user-artifact": "4a11183eee21e6fbad864f7a30b39ad0", + "endpoint:user-artifact-manifest": "a0d7b04ad405eed54d76e279c3727862", + "epm-packages": "8f6e0b09ea0374c4ffe98c3755373cff", + "exception-list": "497afa2f881a675d72d58e20057f3d8b", + "exception-list-agnostic": "497afa2f881a675d72d58e20057f3d8b", + "file-upload-telemetry": "0ed4d3e1983d1217a30982630897092e", + "fleet-agent-actions": "e520c855577170c24481be05c3ae14ec", + "fleet-agent-events": "3231653fafe4ef3196fe3b32ab774bf2", + "fleet-agents": "034346488514b7058a79140b19ddf631", + "fleet-enrollment-api-keys": "28b91e20b105b6f928e2012600085d8f", + "graph-workspace": "cd7ba1330e6682e9cc00b78850874be1", + "index-pattern": "66eccb05066c5a89924f48a9e9736499", + "infrastructure-ui-source": "2b2809653635caf490c93f090502d04c", + "ingest-agent-policies": "9326f99c977fd2ef5ab24b6336a0675c", + "ingest-outputs": "8aa988c376e65443fefc26f1075e93a3", + "ingest-package-policies": "8545e51d7bc8286d6dace3d41240d749", + "ingest_manager_settings": "012cf278ec84579495110bb827d1ed09", + "inventory-view": "88fc7e12fd1b45b6f0787323ce4f18d2", + "kql-telemetry": "d12a98a6f19a2d273696597547e064ee", + "lens": "d33c68a69ff1e78c9888dedd2164ac22", + "lens-ui-telemetry": "509bfa5978586998e05f9e303c07a327", + "map": "4a05b35c3a3a58fbc72dd0202dc3487f", + "maps-telemetry": "5ef305b18111b77789afefbd36b66171", + "metrics-explorer-view": "a8df1d270ee48c969d22d23812d08187", + "migrationVersion": "4a1746014a75ade3a714e1db5763276f", + "ml-telemetry": "257fd1d4b4fdbb9cb4b8a3b27da201e9", + "namespace": "2f4316de49999235636386fe51dc06c1", + "namespaces": "2f4316de49999235636386fe51dc06c1", + "query": "11aaeb7f5f7fa5bb43f25e18ce26e7d9", + "references": "7997cf5a56cc02bdc9c93361bde732b0", + "sample-data-telemetry": "7d3cfeb915303c9641c59681967ffeb4", + "search": "5c4b9a6effceb17ae8a0ab22d0c49767", + "search-telemetry": "3d1b76c39bfb2cc8296b024d73854724", + "siem-detection-engine-rule-actions": "6569b288c169539db10cb262bf79de18", + "siem-detection-engine-rule-status": "ae783f41c6937db6b7a2ef5c93a9e9b0", + "siem-ui-timeline": "94bc38c7a421d15fbfe8ea565370a421", + "siem-ui-timeline-note": "8874706eedc49059d4cf0f5094559084", + "siem-ui-timeline-pinned-event": "20638091112f0e14f0e443d512301c29", + "space": "c5ca8acafa0beaa4d08d014a97b6bc6b", + "telemetry": "36a616f7026dfa617d6655df850fe16d", + "timelion-sheet": "9a2a2748877c7a7b582fef201ab1d4cf", + "tsvb-validation-telemetry": "3a37ef6c8700ae6fc97d5c7da00e9215", + "type": "2f4316de49999235636386fe51dc06c1", + "ui-metric": "0d409297dc5ebe1e3a1da691c6ee32e3", + "updated_at": "00da57df13e94e9d98437d13ace4bfe0", + "upgrade-assistant-reindex-operation": "215107c281839ea9b3ad5f6419819763", + "upgrade-assistant-telemetry": "56702cec857e0a9dacfb696655b4ff7b", + "uptime-dynamic-settings": "fcdb453a30092f022f2642db29523d80", + "url": "c7f66a0df8b1b52f17c28c4adb111105", + "visualization": "52d7a13ad68a150c4525b292d23e12cc", + "workplace_search_telemetry": "3d1b76c39bfb2cc8296b024d73854724" + } + }, + "dynamic": "strict", + "properties": { + "action": { + "properties": { + "actionTypeId": { + "type": "keyword" + }, + "isMissingSecrets": { + "type": "boolean" + }, + "config": { + "enabled": false, + "type": "object" + }, + "name": { + "fields": { + "keyword": { + "type": "keyword" + } + }, + "type": "text" + }, + "secrets": { + "type": "binary" + } + } + }, + "action_task_params": { + "properties": { + "actionId": { + "type": "keyword" + }, + "apiKey": { + "type": "binary" + }, + "params": { + "enabled": false, + "type": "object" + }, + "relatedSavedObjects": { + "enabled": false, + "type": "object" + } + } + }, + "alert": { + "properties": { + "actions": { + "properties": { + "actionRef": { + "type": "keyword" + }, + "actionTypeId": { + "type": "keyword" + }, + "group": { + "type": "keyword" + }, + "params": { + "enabled": false, + "type": "object" + } + }, + "type": "nested" + }, + "alertTypeId": { + "type": "keyword" + }, + "apiKey": { + "type": "binary" + }, + "apiKeyOwner": { + "type": "keyword" + }, + "consumer": { + "type": "keyword" + }, + "createdAt": { + "type": "date" + }, + "createdBy": { + "type": "keyword" + }, + "enabled": { + "type": "boolean" + }, + "muteAll": { + "type": "boolean" + }, + "mutedInstanceIds": { + "type": "keyword" + }, + "name": { + "fields": { + "keyword": { + "type": "keyword" + } + }, + "type": "text" + }, + "params": { + "enabled": false, + "type": "object" + }, + "schedule": { + "properties": { + "interval": { + "type": "keyword" + } + } + }, + "scheduledTaskId": { + "type": "keyword" + }, + "tags": { + "type": "keyword" + }, + "throttle": { + "type": "keyword" + }, + "updatedBy": { + "type": "keyword" + } + } + }, + "apm-indices": { + "properties": { + "apm_oss": { + "properties": { + "errorIndices": { + "type": "keyword" + }, + "metricsIndices": { + "type": "keyword" + }, + "onboardingIndices": { + "type": "keyword" + }, + "sourcemapIndices": { + "type": "keyword" + }, + "spanIndices": { + "type": "keyword" + }, + "transactionIndices": { + "type": "keyword" + } + } + } + } + }, + "apm-telemetry": { + "dynamic": "false", + "type": "object" + }, + "app_search_telemetry": { + "dynamic": "false", + "type": "object" + }, + "application_usage_daily": { + "dynamic": "false", + "properties": { + "timestamp": { + "type": "date" + } + } + }, + "application_usage_totals": { + "dynamic": "false", + "type": "object" + }, + "application_usage_transactional": { + "dynamic": "false", + "type": "object" + }, + "canvas-element": { + "dynamic": "false", + "properties": { + "@created": { + "type": "date" + }, + "@timestamp": { + "type": "date" + }, + "content": { + "type": "text" + }, + "help": { + "type": "text" + }, + "image": { + "type": "text" + }, + "name": { + "fields": { + "keyword": { + "type": "keyword" + } + }, + "type": "text" + } + } + }, + "canvas-workpad": { + "dynamic": "false", + "properties": { + "@created": { + "type": "date" + }, + "@timestamp": { + "type": "date" + }, + "name": { + "fields": { + "keyword": { + "type": "keyword" + } + }, + "type": "text" + } + } + }, + "canvas-workpad-template": { + "dynamic": "false", + "properties": { + "help": { + "fields": { + "keyword": { + "type": "keyword" + } + }, + "type": "text" + }, + "name": { + "fields": { + "keyword": { + "type": "keyword" + } + }, + "type": "text" + }, + "tags": { + "fields": { + "keyword": { + "type": "keyword" + } + }, + "type": "text" + }, + "template_key": { + "type": "keyword" + } + } + }, + "cases": { + "properties": { + "closed_at": { + "type": "date" + }, + "closed_by": { + "properties": { + "email": { + "type": "keyword" + }, + "full_name": { + "type": "keyword" + }, + "username": { + "type": "keyword" + } + } + }, + "connector_id": { + "type": "keyword" + }, + "created_at": { + "type": "date" + }, + "created_by": { + "properties": { + "email": { + "type": "keyword" + }, + "full_name": { + "type": "keyword" + }, + "username": { + "type": "keyword" + } + } + }, + "description": { + "type": "text" + }, + "external_service": { + "properties": { + "connector_id": { + "type": "keyword" + }, + "connector_name": { + "type": "keyword" + }, + "external_id": { + "type": "keyword" + }, + "external_title": { + "type": "text" + }, + "external_url": { + "type": "text" + }, + "pushed_at": { + "type": "date" + }, + "pushed_by": { + "properties": { + "email": { + "type": "keyword" + }, + "full_name": { + "type": "keyword" + }, + "username": { + "type": "keyword" + } + } + } + } + }, + "status": { + "type": "keyword" + }, + "tags": { + "type": "keyword" + }, + "title": { + "type": "keyword" + }, + "updated_at": { + "type": "date" + }, + "updated_by": { + "properties": { + "email": { + "type": "keyword" + }, + "full_name": { + "type": "keyword" + }, + "username": { + "type": "keyword" + } + } + } + } + }, + "cases-comments": { + "properties": { + "comment": { + "type": "text" + }, + "created_at": { + "type": "date" + }, + "created_by": { + "properties": { + "email": { + "type": "keyword" + }, + "full_name": { + "type": "keyword" + }, + "username": { + "type": "keyword" + } + } + }, + "pushed_at": { + "type": "date" + }, + "pushed_by": { + "properties": { + "email": { + "type": "keyword" + }, + "full_name": { + "type": "keyword" + }, + "username": { + "type": "keyword" + } + } + }, + "updated_at": { + "type": "date" + }, + "updated_by": { + "properties": { + "email": { + "type": "keyword" + }, + "full_name": { + "type": "keyword" + }, + "username": { + "type": "keyword" + } + } + } + } + }, + "cases-configure": { + "properties": { + "closure_type": { + "type": "keyword" + }, + "connector_id": { + "type": "keyword" + }, + "connector_name": { + "type": "keyword" + }, + "created_at": { + "type": "date" + }, + "created_by": { + "properties": { + "email": { + "type": "keyword" + }, + "full_name": { + "type": "keyword" + }, + "username": { + "type": "keyword" + } + } + }, + "updated_at": { + "type": "date" + }, + "updated_by": { + "properties": { + "email": { + "type": "keyword" + }, + "full_name": { + "type": "keyword" + }, + "username": { + "type": "keyword" + } + } + } + } + }, + "cases-user-actions": { + "properties": { + "action": { + "type": "keyword" + }, + "action_at": { + "type": "date" + }, + "action_by": { + "properties": { + "email": { + "type": "keyword" + }, + "full_name": { + "type": "keyword" + }, + "username": { + "type": "keyword" + } + } + }, + "action_field": { + "type": "keyword" + }, + "new_value": { + "type": "text" + }, + "old_value": { + "type": "text" + } + } + }, + "config": { + "dynamic": "false", + "properties": { + "buildNum": { + "type": "keyword" + } + } + }, + "dashboard": { + "properties": { + "description": { + "type": "text" + }, + "hits": { + "type": "integer" + }, + "kibanaSavedObjectMeta": { + "properties": { + "searchSourceJSON": { + "type": "text" + } + } + }, + "optionsJSON": { + "type": "text" + }, + "panelsJSON": { + "type": "text" + }, + "refreshInterval": { + "properties": { + "display": { + "type": "keyword" + }, + "pause": { + "type": "boolean" + }, + "section": { + "type": "integer" + }, + "value": { + "type": "integer" + } + } + }, + "timeFrom": { + "type": "keyword" + }, + "timeRestore": { + "type": "boolean" + }, + "timeTo": { + "type": "keyword" + }, + "title": { + "type": "text" + }, + "version": { + "type": "integer" + } + } + }, + "endpoint:user-artifact": { + "properties": { + "body": { + "type": "binary" + }, + "compressionAlgorithm": { + "index": false, + "type": "keyword" + }, + "created": { + "index": false, + "type": "date" + }, + "decodedSha256": { + "index": false, + "type": "keyword" + }, + "decodedSize": { + "index": false, + "type": "long" + }, + "encodedSha256": { + "type": "keyword" + }, + "encodedSize": { + "index": false, + "type": "long" + }, + "encryptionAlgorithm": { + "index": false, + "type": "keyword" + }, + "identifier": { + "type": "keyword" + } + } + }, + "endpoint:user-artifact-manifest": { + "properties": { + "created": { + "index": false, + "type": "date" + }, + "schemaVersion": { + "type": "keyword" + }, + "semanticVersion": { + "index": false, + "type": "keyword" + }, + "artifacts": { + "type": "nested", + "properties": { + "policyId": { + "type": "keyword", + "index": false + }, + "artifactId": { + "type": "keyword", + "index": false + } + } + } + } + }, + "epm-packages": { + "properties": { + "es_index_patterns": { + "enabled": false, + "type": "object" + }, + "installed_es": { + "properties": { + "id": { + "type": "keyword" + }, + "type": { + "type": "keyword" + } + }, + "type": "nested" + }, + "installed_kibana": { + "properties": { + "id": { + "type": "keyword" + }, + "type": { + "type": "keyword" + } + }, + "type": "nested" + }, + "internal": { + "type": "boolean" + }, + "name": { + "type": "keyword" + }, + "removable": { + "type": "boolean" + }, + "version": { + "type": "keyword" + } + } + }, + "exception-list": { + "properties": { + "_tags": { + "type": "keyword" + }, + "comments": { + "properties": { + "comment": { + "type": "keyword" + }, + "created_at": { + "type": "keyword" + }, + "created_by": { + "type": "keyword" + }, + "id": { + "type": "keyword" + }, + "updated_at": { + "type": "keyword" + }, + "updated_by": { + "type": "keyword" + } + } + }, + "created_at": { + "type": "keyword" + }, + "created_by": { + "type": "keyword" + }, + "description": { + "type": "keyword" + }, + "entries": { + "properties": { + "entries": { + "properties": { + "field": { + "type": "keyword" + }, + "operator": { + "type": "keyword" + }, + "type": { + "type": "keyword" + }, + "value": { + "fields": { + "text": { + "type": "text" + } + }, + "type": "keyword" + } + } + }, + "field": { + "type": "keyword" + }, + "list": { + "properties": { + "id": { + "type": "keyword" + }, + "type": { + "type": "keyword" + } + } + }, + "operator": { + "type": "keyword" + }, + "type": { + "type": "keyword" + }, + "value": { + "fields": { + "text": { + "type": "text" + } + }, + "type": "keyword" + } + } + }, + "immutable": { + "type": "boolean" + }, + "item_id": { + "type": "keyword" + }, + "list_id": { + "type": "keyword" + }, + "list_type": { + "type": "keyword" + }, + "meta": { + "type": "keyword" + }, + "name": { + "type": "keyword" + }, + "tags": { + "type": "keyword" + }, + "tie_breaker_id": { + "type": "keyword" + }, + "type": { + "type": "keyword" + }, + "updated_by": { + "type": "keyword" + }, + "version": { + "type": "keyword" + } + } + }, + "exception-list-agnostic": { + "properties": { + "_tags": { + "type": "keyword" + }, + "comments": { + "properties": { + "comment": { + "type": "keyword" + }, + "created_at": { + "type": "keyword" + }, + "created_by": { + "type": "keyword" + }, + "id": { + "type": "keyword" + }, + "updated_at": { + "type": "keyword" + }, + "updated_by": { + "type": "keyword" + } + } + }, + "created_at": { + "type": "keyword" + }, + "created_by": { + "type": "keyword" + }, + "description": { + "type": "keyword" + }, + "entries": { + "properties": { + "entries": { + "properties": { + "field": { + "type": "keyword" + }, + "operator": { + "type": "keyword" + }, + "type": { + "type": "keyword" + }, + "value": { + "fields": { + "text": { + "type": "text" + } + }, + "type": "keyword" + } + } + }, + "field": { + "type": "keyword" + }, + "list": { + "properties": { + "id": { + "type": "keyword" + }, + "type": { + "type": "keyword" + } + } + }, + "operator": { + "type": "keyword" + }, + "type": { + "type": "keyword" + }, + "value": { + "fields": { + "text": { + "type": "text" + } + }, + "type": "keyword" + } + } + }, + "immutable": { + "type": "boolean" + }, + "item_id": { + "type": "keyword" + }, + "list_id": { + "type": "keyword" + }, + "list_type": { + "type": "keyword" + }, + "meta": { + "type": "keyword" + }, + "name": { + "type": "keyword" + }, + "tags": { + "type": "keyword" + }, + "tie_breaker_id": { + "type": "keyword" + }, + "type": { + "type": "keyword" + }, + "updated_by": { + "type": "keyword" + }, + "version": { + "type": "keyword" + } + } + }, + "file-upload-telemetry": { + "properties": { + "filesUploadedTotalCount": { + "type": "long" + } + } + }, + "fleet-agent-actions": { + "properties": { + "agent_id": { + "type": "keyword" + }, + "created_at": { + "type": "date" + }, + "data": { + "type": "binary" + }, + "sent_at": { + "type": "date" + }, + "type": { + "type": "keyword" + } + } + }, + "fleet-agent-events": { + "properties": { + "action_id": { + "type": "keyword" + }, + "agent_id": { + "type": "keyword" + }, + "config_id": { + "type": "keyword" + }, + "data": { + "type": "text" + }, + "message": { + "type": "text" + }, + "payload": { + "type": "text" + }, + "stream_id": { + "type": "keyword" + }, + "subtype": { + "type": "keyword" + }, + "timestamp": { + "type": "date" + }, + "type": { + "type": "keyword" + } + } + }, + "fleet-agents": { + "properties": { + "access_api_key_id": { + "type": "keyword" + }, + "active": { + "type": "boolean" + }, + "config_id": { + "type": "keyword" + }, + "config_revision": { + "type": "integer" + }, + "current_error_events": { + "index": false, + "type": "text" + }, + "default_api_key": { + "type": "binary" + }, + "default_api_key_id": { + "type": "keyword" + }, + "enrolled_at": { + "type": "date" + }, + "last_checkin": { + "type": "date" + }, + "last_checkin_status": { + "type": "keyword" + }, + "last_updated": { + "type": "date" + }, + "local_metadata": { + "type": "flattened" + }, + "packages": { + "type": "keyword" + }, + "shared_id": { + "type": "keyword" + }, + "type": { + "type": "keyword" + }, + "unenrolled_at": { + "type": "date" + }, + "unenrollment_started_at": { + "type": "date" + }, + "updated_at": { + "type": "date" + }, + "user_provided_metadata": { + "type": "flattened" + }, + "version": { + "type": "keyword" + } + } + }, + "fleet-enrollment-api-keys": { + "properties": { + "active": { + "type": "boolean" + }, + "api_key": { + "type": "binary" + }, + "api_key_id": { + "type": "keyword" + }, + "config_id": { + "type": "keyword" + }, + "created_at": { + "type": "date" + }, + "expire_at": { + "type": "date" + }, + "name": { + "type": "keyword" + }, + "type": { + "type": "keyword" + }, + "updated_at": { + "type": "date" + } + } + }, + "graph-workspace": { + "properties": { + "description": { + "type": "text" + }, + "kibanaSavedObjectMeta": { + "properties": { + "searchSourceJSON": { + "type": "text" + } + } + }, + "numLinks": { + "type": "integer" + }, + "numVertices": { + "type": "integer" + }, + "title": { + "type": "text" + }, + "version": { + "type": "integer" + }, + "wsState": { + "type": "text" + } + } + }, + "index-pattern": { + "properties": { + "fieldFormatMap": { + "type": "text" + }, + "fields": { + "type": "text" + }, + "intervalName": { + "type": "keyword" + }, + "notExpandable": { + "type": "boolean" + }, + "sourceFilters": { + "type": "text" + }, + "timeFieldName": { + "type": "keyword" + }, + "title": { + "type": "text" + }, + "type": { + "type": "keyword" + }, + "typeMeta": { + "type": "keyword" + } + } + }, + "infrastructure-ui-source": { + "properties": { + "description": { + "type": "text" + }, + "fields": { + "properties": { + "container": { + "type": "keyword" + }, + "host": { + "type": "keyword" + }, + "pod": { + "type": "keyword" + }, + "tiebreaker": { + "type": "keyword" + }, + "timestamp": { + "type": "keyword" + } + } + }, + "inventoryDefaultView": { + "type": "keyword" + }, + "logAlias": { + "type": "keyword" + }, + "logColumns": { + "properties": { + "fieldColumn": { + "properties": { + "field": { + "type": "keyword" + }, + "id": { + "type": "keyword" + } + } + }, + "messageColumn": { + "properties": { + "id": { + "type": "keyword" + } + } + }, + "timestampColumn": { + "properties": { + "id": { + "type": "keyword" + } + } + } + }, + "type": "nested" + }, + "metricAlias": { + "type": "keyword" + }, + "metricsExplorerDefaultView": { + "type": "keyword" + }, + "name": { + "type": "text" + } + } + }, + "ingest-agent-policies": { + "properties": { + "description": { + "type": "text" + }, + "is_default": { + "type": "boolean" + }, + "monitoring_enabled": { + "index": false, + "type": "keyword" + }, + "name": { + "type": "keyword" + }, + "namespace": { + "type": "keyword" + }, + "package_configs": { + "type": "keyword" + }, + "revision": { + "type": "integer" + }, + "status": { + "type": "keyword" + }, + "updated_at": { + "type": "date" + }, + "updated_by": { + "type": "keyword" + } + } + }, + "ingest-outputs": { + "properties": { + "ca_sha256": { + "index": false, + "type": "keyword" + }, + "config": { + "type": "flattened" + }, + "fleet_enroll_password": { + "type": "binary" + }, + "fleet_enroll_username": { + "type": "binary" + }, + "hosts": { + "type": "keyword" + }, + "is_default": { + "type": "boolean" + }, + "name": { + "type": "keyword" + }, + "type": { + "type": "keyword" + } + } + }, + "ingest-package-policies": { + "properties": { + "config_id": { + "type": "keyword" + }, + "created_at": { + "type": "date" + }, + "created_by": { + "type": "keyword" + }, + "description": { + "type": "text" + }, + "enabled": { + "type": "boolean" + }, + "inputs": { + "enabled": false, + "properties": { + "config": { + "type": "flattened" + }, + "enabled": { + "type": "boolean" + }, + "streams": { + "properties": { + "compiled_stream": { + "type": "flattened" + }, + "config": { + "type": "flattened" + }, + "data_stream": { + "properties": { + "dataset": { + "type": "keyword" + }, + "type": { + "type": "keyword" + } + } + }, + "enabled": { + "type": "boolean" + }, + "id": { + "type": "keyword" + }, + "vars": { + "type": "flattened" + } + }, + "type": "nested" + }, + "type": { + "type": "keyword" + }, + "vars": { + "type": "flattened" + } + }, + "type": "nested" + }, + "name": { + "type": "keyword" + }, + "namespace": { + "type": "keyword" + }, + "output_id": { + "type": "keyword" + }, + "package": { + "properties": { + "name": { + "type": "keyword" + }, + "title": { + "type": "keyword" + }, + "version": { + "type": "keyword" + } + } + }, + "revision": { + "type": "integer" + }, + "updated_at": { + "type": "date" + }, + "updated_by": { + "type": "keyword" + } + } + }, + "ingest_manager_settings": { + "properties": { + "agent_auto_upgrade": { + "type": "keyword" + }, + "has_seen_add_data_notice": { + "index": false, + "type": "boolean" + }, + "kibana_ca_sha256": { + "type": "keyword" + }, + "kibana_url": { + "type": "keyword" + }, + "package_auto_upgrade": { + "type": "keyword" + } + } + }, + "inventory-view": { + "properties": { + "accountId": { + "type": "keyword" + }, + "autoBounds": { + "type": "boolean" + }, + "autoReload": { + "type": "boolean" + }, + "boundsOverride": { + "properties": { + "max": { + "type": "integer" + }, + "min": { + "type": "integer" + } + } + }, + "customMetrics": { + "properties": { + "aggregation": { + "type": "keyword" + }, + "field": { + "type": "keyword" + }, + "id": { + "type": "keyword" + }, + "label": { + "type": "keyword" + }, + "type": { + "type": "keyword" + } + }, + "type": "nested" + }, + "customOptions": { + "properties": { + "field": { + "type": "keyword" + }, + "text": { + "type": "keyword" + } + }, + "type": "nested" + }, + "filterQuery": { + "properties": { + "expression": { + "type": "keyword" + }, + "kind": { + "type": "keyword" + } + } + }, + "groupBy": { + "properties": { + "field": { + "type": "keyword" + }, + "label": { + "type": "keyword" + } + }, + "type": "nested" + }, + "legend": { + "properties": { + "palette": { + "type": "keyword" + }, + "reverseColors": { + "type": "boolean" + }, + "steps": { + "type": "long" + } + } + }, + "metric": { + "properties": { + "aggregation": { + "type": "keyword" + }, + "field": { + "type": "keyword" + }, + "id": { + "type": "keyword" + }, + "label": { + "type": "keyword" + }, + "type": { + "type": "keyword" + } + } + }, + "name": { + "type": "keyword" + }, + "nodeType": { + "type": "keyword" + }, + "region": { + "type": "keyword" + }, + "sort": { + "properties": { + "by": { + "type": "keyword" + }, + "direction": { + "type": "keyword" + } + } + }, + "time": { + "type": "long" + }, + "view": { + "type": "keyword" + } + } + }, + "kql-telemetry": { + "properties": { + "optInCount": { + "type": "long" + }, + "optOutCount": { + "type": "long" + } + } + }, + "lens": { + "properties": { + "description": { + "type": "text" + }, + "expression": { + "index": false, + "type": "keyword" + }, + "state": { + "type": "flattened" + }, + "title": { + "type": "text" + }, + "visualizationType": { + "type": "keyword" + } + } + }, + "lens-ui-telemetry": { + "properties": { + "count": { + "type": "integer" + }, + "date": { + "type": "date" + }, + "name": { + "type": "keyword" + }, + "type": { + "type": "keyword" + } + } + }, + "map": { + "properties": { + "description": { + "type": "text" + }, + "layerListJSON": { + "type": "text" + }, + "mapStateJSON": { + "type": "text" + }, + "title": { + "type": "text" + }, + "uiStateJSON": { + "type": "text" + }, + "version": { + "type": "integer" + } + } + }, + "maps-telemetry": { + "enabled": false, + "type": "object" + }, + "metrics-explorer-view": { + "properties": { + "chartOptions": { + "properties": { + "stack": { + "type": "boolean" + }, + "type": { + "type": "keyword" + }, + "yAxisMode": { + "type": "keyword" + } + } + }, + "currentTimerange": { + "properties": { + "from": { + "type": "keyword" + }, + "interval": { + "type": "keyword" + }, + "to": { + "type": "keyword" + } + } + }, + "name": { + "type": "keyword" + }, + "options": { + "properties": { + "aggregation": { + "type": "keyword" + }, + "filterQuery": { + "type": "keyword" + }, + "forceInterval": { + "type": "boolean" + }, + "groupBy": { + "type": "keyword" + }, + "limit": { + "type": "integer" + }, + "metrics": { + "properties": { + "aggregation": { + "type": "keyword" + }, + "color": { + "type": "keyword" + }, + "field": { + "type": "keyword" + }, + "label": { + "type": "keyword" + } + }, + "type": "nested" + }, + "source": { + "type": "keyword" + } + } + } + } + }, + "migrationVersion": { + "dynamic": "true", + "properties": { + "config": { + "fields": { + "keyword": { + "ignore_above": 256, + "type": "keyword" + } + }, + "type": "text" + }, + "space": { + "fields": { + "keyword": { + "ignore_above": 256, + "type": "keyword" + } + }, + "type": "text" + } + } + }, + "ml-telemetry": { + "properties": { + "file_data_visualizer": { + "properties": { + "index_creation_count": { + "type": "long" + } + } + } + } + }, + "namespace": { + "type": "keyword" + }, + "namespaces": { + "type": "keyword" + }, + "query": { + "properties": { + "description": { + "type": "text" + }, + "filters": { + "enabled": false, + "type": "object" + }, + "query": { + "properties": { + "language": { + "type": "keyword" + }, + "query": { + "index": false, + "type": "keyword" + } + } + }, + "timefilter": { + "enabled": false, + "type": "object" + }, + "title": { + "type": "text" + } + } + }, + "references": { + "properties": { + "id": { + "type": "keyword" + }, + "name": { + "type": "keyword" + }, + "type": { + "type": "keyword" + } + }, + "type": "nested" + }, + "sample-data-telemetry": { + "properties": { + "installCount": { + "type": "long" + }, + "unInstallCount": { + "type": "long" + } + } + }, + "search": { + "properties": { + "columns": { + "index": false, + "type": "keyword" + }, + "description": { + "type": "text" + }, + "hits": { + "index": false, + "type": "integer" + }, + "kibanaSavedObjectMeta": { + "properties": { + "searchSourceJSON": { + "index": false, + "type": "text" + } + } + }, + "sort": { + "index": false, + "type": "keyword" + }, + "title": { + "type": "text" + }, + "version": { + "type": "integer" + } + } + }, + "search-telemetry": { + "dynamic": "false", + "type": "object" + }, + "siem-detection-engine-rule-actions": { + "properties": { + "actions": { + "properties": { + "action_type_id": { + "type": "keyword" + }, + "group": { + "type": "keyword" + }, + "id": { + "type": "keyword" + }, + "params": { + "enabled": false, + "type": "object" + } + } + }, + "alertThrottle": { + "type": "keyword" + }, + "ruleAlertId": { + "type": "keyword" + }, + "ruleThrottle": { + "type": "keyword" + } + } + }, + "siem-detection-engine-rule-status": { + "properties": { + "alertId": { + "type": "keyword" + }, + "bulkCreateTimeDurations": { + "type": "float" + }, + "gap": { + "type": "text" + }, + "lastFailureAt": { + "type": "date" + }, + "lastFailureMessage": { + "type": "text" + }, + "lastLookBackDate": { + "type": "date" + }, + "lastSuccessAt": { + "type": "date" + }, + "lastSuccessMessage": { + "type": "text" + }, + "searchAfterTimeDurations": { + "type": "float" + }, + "status": { + "type": "keyword" + }, + "statusDate": { + "type": "date" + } + } + }, + "siem-ui-timeline": { + "properties": { + "columns": { + "properties": { + "aggregatable": { + "type": "boolean" + }, + "category": { + "type": "keyword" + }, + "columnHeaderType": { + "type": "keyword" + }, + "description": { + "type": "text" + }, + "example": { + "type": "text" + }, + "id": { + "type": "keyword" + }, + "indexes": { + "type": "keyword" + }, + "name": { + "type": "text" + }, + "placeholder": { + "type": "text" + }, + "searchable": { + "type": "boolean" + }, + "type": { + "type": "keyword" + } + } + }, + "created": { + "type": "date" + }, + "createdBy": { + "type": "text" + }, + "dataProviders": { + "properties": { + "and": { + "properties": { + "enabled": { + "type": "boolean" + }, + "excluded": { + "type": "boolean" + }, + "id": { + "type": "keyword" + }, + "kqlQuery": { + "type": "text" + }, + "name": { + "type": "text" + }, + "queryMatch": { + "properties": { + "displayField": { + "type": "text" + }, + "displayValue": { + "type": "text" + }, + "field": { + "type": "text" + }, + "operator": { + "type": "text" + }, + "value": { + "type": "text" + } + } + }, + "type": { + "type": "text" + } + } + }, + "enabled": { + "type": "boolean" + }, + "excluded": { + "type": "boolean" + }, + "id": { + "type": "keyword" + }, + "kqlQuery": { + "type": "text" + }, + "name": { + "type": "text" + }, + "queryMatch": { + "properties": { + "displayField": { + "type": "text" + }, + "displayValue": { + "type": "text" + }, + "field": { + "type": "text" + }, + "operator": { + "type": "text" + }, + "value": { + "type": "text" + } + } + }, + "type": { + "type": "text" + } + } + }, + "dateRange": { + "properties": { + "end": { + "type": "date" + }, + "start": { + "type": "date" + } + } + }, + "description": { + "type": "text" + }, + "eventType": { + "type": "keyword" + }, + "excludedRowRendererIds": { + "type": "text" + }, + "favorite": { + "properties": { + "favoriteDate": { + "type": "date" + }, + "fullName": { + "type": "text" + }, + "keySearch": { + "type": "text" + }, + "userName": { + "type": "text" + } + } + }, + "filters": { + "properties": { + "exists": { + "type": "text" + }, + "match_all": { + "type": "text" + }, + "meta": { + "properties": { + "alias": { + "type": "text" + }, + "controlledBy": { + "type": "text" + }, + "disabled": { + "type": "boolean" + }, + "field": { + "type": "text" + }, + "formattedValue": { + "type": "text" + }, + "index": { + "type": "keyword" + }, + "key": { + "type": "keyword" + }, + "negate": { + "type": "boolean" + }, + "params": { + "type": "text" + }, + "type": { + "type": "keyword" + }, + "value": { + "type": "text" + } + } + }, + "missing": { + "type": "text" + }, + "query": { + "type": "text" + }, + "range": { + "type": "text" + }, + "script": { + "type": "text" + } + } + }, + "kqlMode": { + "type": "keyword" + }, + "kqlQuery": { + "properties": { + "filterQuery": { + "properties": { + "kuery": { + "properties": { + "expression": { + "type": "text" + }, + "kind": { + "type": "keyword" + } + } + }, + "serializedQuery": { + "type": "text" + } + } + } + } + }, + "savedQueryId": { + "type": "keyword" + }, + "sort": { + "properties": { + "columnId": { + "type": "keyword" + }, + "sortDirection": { + "type": "keyword" + } + } + }, + "status": { + "type": "keyword" + }, + "templateTimelineId": { + "type": "text" + }, + "templateTimelineVersion": { + "type": "integer" + }, + "timelineType": { + "type": "keyword" + }, + "title": { + "type": "text" + }, + "updated": { + "type": "date" + }, + "updatedBy": { + "type": "text" + } + } + }, + "siem-ui-timeline-note": { + "properties": { + "created": { + "type": "date" + }, + "createdBy": { + "type": "text" + }, + "eventId": { + "type": "keyword" + }, + "note": { + "type": "text" + }, + "timelineId": { + "type": "keyword" + }, + "updated": { + "type": "date" + }, + "updatedBy": { + "type": "text" + } + } + }, + "siem-ui-timeline-pinned-event": { + "properties": { + "created": { + "type": "date" + }, + "createdBy": { + "type": "text" + }, + "eventId": { + "type": "keyword" + }, + "timelineId": { + "type": "keyword" + }, + "updated": { + "type": "date" + }, + "updatedBy": { + "type": "text" + } + } + }, + "space": { + "properties": { + "_reserved": { + "type": "boolean" + }, + "color": { + "type": "keyword" + }, + "description": { + "type": "text" + }, + "disabledFeatures": { + "type": "keyword" + }, + "imageUrl": { + "index": false, + "type": "text" + }, + "initials": { + "type": "keyword" + }, + "name": { + "fields": { + "keyword": { + "ignore_above": 2048, + "type": "keyword" + } + }, + "type": "text" + } + } + }, + "telemetry": { + "properties": { + "allowChangingOptInStatus": { + "type": "boolean" + }, + "enabled": { + "type": "boolean" + }, + "lastReported": { + "type": "date" + }, + "lastVersionChecked": { + "type": "keyword" + }, + "reportFailureCount": { + "type": "integer" + }, + "reportFailureVersion": { + "type": "keyword" + }, + "sendUsageFrom": { + "type": "keyword" + }, + "userHasSeenNotice": { + "type": "boolean" + } + } + }, + "timelion-sheet": { + "properties": { + "description": { + "type": "text" + }, + "hits": { + "type": "integer" + }, + "kibanaSavedObjectMeta": { + "properties": { + "searchSourceJSON": { + "type": "text" + } + } + }, + "timelion_chart_height": { + "type": "integer" + }, + "timelion_columns": { + "type": "integer" + }, + "timelion_interval": { + "type": "keyword" + }, + "timelion_other_interval": { + "type": "keyword" + }, + "timelion_rows": { + "type": "integer" + }, + "timelion_sheet": { + "type": "text" + }, + "title": { + "type": "text" + }, + "version": { + "type": "integer" + } + } + }, + "tsvb-validation-telemetry": { + "properties": { + "failedRequests": { + "type": "long" + } + } + }, + "type": { + "type": "keyword" + }, + "ui-metric": { + "properties": { + "count": { + "type": "integer" + } + } + }, + "updated_at": { + "type": "date" + }, + "upgrade-assistant-reindex-operation": { + "properties": { + "errorMessage": { + "fields": { + "keyword": { + "ignore_above": 256, + "type": "keyword" + } + }, + "type": "text" + }, + "indexName": { + "type": "keyword" + }, + "lastCompletedStep": { + "type": "long" + }, + "locked": { + "type": "date" + }, + "newIndexName": { + "fields": { + "keyword": { + "ignore_above": 256, + "type": "keyword" + } + }, + "type": "text" + }, + "reindexOptions": { + "properties": { + "openAndClose": { + "type": "boolean" + }, + "queueSettings": { + "properties": { + "queuedAt": { + "type": "long" + }, + "startedAt": { + "type": "long" + } + } + } + } + }, + "reindexTaskId": { + "fields": { + "keyword": { + "ignore_above": 256, + "type": "keyword" + } + }, + "type": "text" + }, + "reindexTaskPercComplete": { + "type": "float" + }, + "runningReindexCount": { + "type": "integer" + }, + "status": { + "type": "integer" + } + } + }, + "upgrade-assistant-telemetry": { + "properties": { + "features": { + "properties": { + "deprecation_logging": { + "properties": { + "enabled": { + "null_value": true, + "type": "boolean" + } + } + } + } + }, + "ui_open": { + "properties": { + "cluster": { + "null_value": 0, + "type": "long" + }, + "indices": { + "null_value": 0, + "type": "long" + }, + "overview": { + "null_value": 0, + "type": "long" + } + } + }, + "ui_reindex": { + "properties": { + "close": { + "null_value": 0, + "type": "long" + }, + "open": { + "null_value": 0, + "type": "long" + }, + "start": { + "null_value": 0, + "type": "long" + }, + "stop": { + "null_value": 0, + "type": "long" + } + } + } + } + }, + "uptime-dynamic-settings": { + "properties": { + "certAgeThreshold": { + "type": "long" + }, + "certExpirationThreshold": { + "type": "long" + }, + "heartbeatIndices": { + "type": "keyword" + } + } + }, + "url": { + "properties": { + "accessCount": { + "type": "long" + }, + "accessDate": { + "type": "date" + }, + "createDate": { + "type": "date" + }, + "url": { + "fields": { + "keyword": { + "ignore_above": 2048, + "type": "keyword" + } + }, + "type": "text" + } + } + }, + "visualization": { + "properties": { + "description": { + "type": "text" + }, + "kibanaSavedObjectMeta": { + "properties": { + "searchSourceJSON": { + "type": "text" + } + } + }, + "savedSearchRefName": { + "type": "keyword" + }, + "title": { + "type": "text" + }, + "uiStateJSON": { + "type": "text" + }, + "version": { + "type": "integer" + }, + "visState": { + "type": "text" + } + } + }, + "workplace_search_telemetry": { + "dynamic": "false", + "type": "object" + } + } + }, + "settings": { + "index": { + "auto_expand_replicas": "0-1", + "number_of_replicas": "0", + "number_of_shards": "1" + } + } + } +}