diff --git a/src/core/server/integration_tests/saved_objects/migrations/group2/check_registered_types.test.ts b/src/core/server/integration_tests/saved_objects/migrations/group2/check_registered_types.test.ts index d3f1bb72142dc..0285dc8e98ba8 100644 --- a/src/core/server/integration_tests/saved_objects/migrations/group2/check_registered_types.test.ts +++ b/src/core/server/integration_tests/saved_objects/migrations/group2/check_registered_types.test.ts @@ -56,7 +56,7 @@ describe('checking migration metadata changes on all registered SO types', () => expect(hashMap).toMatchInlineSnapshot(` Object { "action": "6cfc277ed3211639e37546ac625f4a68f2494215", - "action_task_params": "db2afea7d78e00e725486b791554d0d4e81956ef", + "action_task_params": "5f419caba96dd8c77d0f94013e71d43890e3d5d6", "alert": "785240e3137f5eb1a0f8986e5b8eff99780fc04f", "api_key_pending_invalidation": "16e7bcf8e78764102d7f525542d5b616809a21ee", "apm-indices": "d19dd7fb51f2d2cbc1f8769481721e0953f9a6d2", diff --git a/x-pack/plugins/actions/common/execution_log_types.ts b/x-pack/plugins/actions/common/execution_log_types.ts index 7fed846f081ca..d28ee5cf50481 100644 --- a/x-pack/plugins/actions/common/execution_log_types.ts +++ b/x-pack/plugins/actions/common/execution_log_types.ts @@ -19,6 +19,7 @@ export interface IExecutionLog { connector_name: string; connector_id: string; timed_out: boolean; + source: string; } export interface IExecutionLogResult { diff --git a/x-pack/plugins/actions/server/actions_client.test.ts b/x-pack/plugins/actions/server/actions_client.test.ts index a164058d3b87d..3c2d91939e688 100644 --- a/x-pack/plugins/actions/server/actions_client.test.ts +++ b/x-pack/plugins/actions/server/actions_client.test.ts @@ -12,7 +12,12 @@ import { ByteSizeValue } from '@kbn/config-schema'; import { ActionTypeRegistry, ActionTypeRegistryOpts } from './action_type_registry'; import { ActionsClient } from './actions_client'; import { ExecutorType, ActionType } from './types'; -import { ActionExecutor, TaskRunnerFactory, ILicenseState } from './lib'; +import { + ActionExecutor, + TaskRunnerFactory, + ILicenseState, + asHttpRequestExecutionSource, +} from './lib'; import { taskManagerMock } from '@kbn/task-manager-plugin/server/mocks'; import { actionsConfigMock } from './actions_config.mock'; import { getActionsConfigurationUtilities } from './actions_config'; @@ -2171,6 +2176,7 @@ describe('execute()', () => { params: { name: 'my name', }, + source: asHttpRequestExecutionSource(request), }); expect(authorization.ensureAuthorized).toHaveBeenCalledWith('execute'); }); @@ -2189,6 +2195,7 @@ describe('execute()', () => { params: { name: 'my name', }, + source: asHttpRequestExecutionSource(request), }) ).rejects.toMatchInlineSnapshot(`[Error: Unauthorized to execute all actions]`); @@ -2205,6 +2212,7 @@ describe('execute()', () => { params: { name: 'my name', }, + source: asHttpRequestExecutionSource(request), }); expect(trackLegacyRBACExemption as jest.Mock).toBeCalledWith('execute', mockUsageCounter); @@ -2221,6 +2229,7 @@ describe('execute()', () => { params: { name: 'my name', }, + source: asHttpRequestExecutionSource(request), }) ).resolves.toMatchObject({ status: 'ok', actionId }); @@ -2231,6 +2240,7 @@ describe('execute()', () => { name: 'my name', }, actionExecutionId, + source: asHttpRequestExecutionSource(request), }); await expect( @@ -2239,6 +2249,7 @@ describe('execute()', () => { params: { name: 'my name', }, + source: asHttpRequestExecutionSource(request), relatedSavedObjects: [ { id: 'some-id', @@ -2263,6 +2274,7 @@ describe('execute()', () => { }, ], actionExecutionId, + source: asHttpRequestExecutionSource(request), }); await expect( @@ -2271,6 +2283,7 @@ describe('execute()', () => { params: { name: 'my name', }, + source: asHttpRequestExecutionSource(request), relatedSavedObjects: [ { id: 'some-id', @@ -2288,6 +2301,7 @@ describe('execute()', () => { params: { name: 'my name', }, + source: asHttpRequestExecutionSource(request), relatedSavedObjects: [ { id: 'some-id', @@ -2313,6 +2327,7 @@ describe('enqueueExecution()', () => { spaceId: 'default', executionId: '123abc', apiKey: null, + source: asHttpRequestExecutionSource(request), }); expect(authorization.ensureAuthorized).toHaveBeenCalledWith('execute'); }); @@ -2332,6 +2347,7 @@ describe('enqueueExecution()', () => { spaceId: 'default', executionId: '123abc', apiKey: null, + source: asHttpRequestExecutionSource(request), }) ).rejects.toMatchInlineSnapshot(`[Error: Unauthorized to execute all actions]`); @@ -2349,6 +2365,7 @@ describe('enqueueExecution()', () => { spaceId: 'default', executionId: '123abc', apiKey: null, + source: asHttpRequestExecutionSource(request), }); expect(trackLegacyRBACExemption as jest.Mock).toBeCalledWith( @@ -2365,6 +2382,7 @@ describe('enqueueExecution()', () => { spaceId: 'default', executionId: '123abc', apiKey: Buffer.from('123:abc').toString('base64'), + source: asHttpRequestExecutionSource(request), }; await expect(actionsClient.enqueueExecution(opts)).resolves.toMatchInlineSnapshot(`undefined`); @@ -2385,6 +2403,7 @@ describe('bulkEnqueueExecution()', () => { spaceId: 'default', executionId: '123abc', apiKey: null, + source: asHttpRequestExecutionSource(request), }, { id: uuidv4(), @@ -2392,6 +2411,7 @@ describe('bulkEnqueueExecution()', () => { spaceId: 'default', executionId: '456def', apiKey: null, + source: asHttpRequestExecutionSource(request), }, ]); expect(authorization.ensureAuthorized).toHaveBeenCalledWith('execute'); @@ -2413,6 +2433,7 @@ describe('bulkEnqueueExecution()', () => { spaceId: 'default', executionId: '123abc', apiKey: null, + source: asHttpRequestExecutionSource(request), }, { id: uuidv4(), @@ -2420,6 +2441,7 @@ describe('bulkEnqueueExecution()', () => { spaceId: 'default', executionId: '456def', apiKey: null, + source: asHttpRequestExecutionSource(request), }, ]) ).rejects.toMatchInlineSnapshot(`[Error: Unauthorized to execute all actions]`); @@ -2439,6 +2461,7 @@ describe('bulkEnqueueExecution()', () => { spaceId: 'default', executionId: '123abc', apiKey: null, + source: asHttpRequestExecutionSource(request), }, { id: uuidv4(), @@ -2446,6 +2469,7 @@ describe('bulkEnqueueExecution()', () => { spaceId: 'default', executionId: '456def', apiKey: null, + source: asHttpRequestExecutionSource(request), }, ]); @@ -2468,6 +2492,7 @@ describe('bulkEnqueueExecution()', () => { spaceId: 'default', executionId: '123abc', apiKey: null, + source: asHttpRequestExecutionSource(request), }, { id: uuidv4(), @@ -2475,6 +2500,7 @@ describe('bulkEnqueueExecution()', () => { spaceId: 'default', executionId: '456def', apiKey: null, + source: asHttpRequestExecutionSource(request), }, ]; await expect(actionsClient.bulkEnqueueExecution(opts)).resolves.toMatchInlineSnapshot( diff --git a/x-pack/plugins/actions/server/constants/event_log.ts b/x-pack/plugins/actions/server/constants/event_log.ts index 9dba72462f317..e3ae4e8c5b855 100644 --- a/x-pack/plugins/actions/server/constants/event_log.ts +++ b/x-pack/plugins/actions/server/constants/event_log.ts @@ -9,6 +9,5 @@ export const EVENT_LOG_PROVIDER = 'actions'; export const EVENT_LOG_ACTIONS = { execute: 'execute', executeStart: 'execute-start', - executeViaHttp: 'execute-via-http', executeTimeout: 'execute-timeout', }; 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 8147e14d2a943..e4787101b1cde 100644 --- a/x-pack/plugins/actions/server/create_execute_function.test.ts +++ b/x-pack/plugins/actions/server/create_execute_function.test.ts @@ -79,6 +79,7 @@ describe('execute()', () => { actionId: '123', params: { baz: false }, executionId: '123abc', + source: 'HTTP_REQUEST', apiKey: Buffer.from('123:abc').toString('base64'), }, { @@ -151,6 +152,7 @@ describe('execute()', () => { params: { baz: false }, executionId: '123abc', consumer: 'test-consumer', + source: 'HTTP_REQUEST', apiKey: Buffer.from('123:abc').toString('base64'), }, { @@ -213,6 +215,7 @@ describe('execute()', () => { params: { baz: false }, apiKey: Buffer.from('123:abc').toString('base64'), executionId: '123abc', + source: 'HTTP_REQUEST', relatedSavedObjects: [ { id: 'related_some-type_0', @@ -303,6 +306,7 @@ describe('execute()', () => { actionId: '123', params: { baz: false }, executionId: '123abc', + source: 'SAVED_OBJECT', apiKey: Buffer.from('123:abc').toString('base64'), }, { @@ -390,6 +394,7 @@ describe('execute()', () => { params: { baz: false }, apiKey: Buffer.from('123:abc').toString('base64'), executionId: '123abc', + source: 'SAVED_OBJECT', relatedSavedObjects: [ { id: 'related_some-type_0', @@ -430,6 +435,7 @@ describe('execute()', () => { spaceId: 'default', executionId: '123abc', apiKey: null, + source: asHttpRequestExecutionSource(request), }) ).rejects.toThrowErrorMatchingInlineSnapshot( `"Unable to execute action because the Encrypted Saved Objects plugin is missing encryption key. Please set xpack.encryptedSavedObjects.encryptionKey in the kibana.yml or use the bin/kibana-encryption-keys command."` @@ -460,6 +466,7 @@ describe('execute()', () => { spaceId: 'default', executionId: '123abc', apiKey: null, + source: asHttpRequestExecutionSource(request), }) ).rejects.toThrowErrorMatchingInlineSnapshot( `"Unable to execute action because no secrets are defined for the \\"mock-action\\" connector."` @@ -493,6 +500,7 @@ describe('execute()', () => { spaceId: 'default', executionId: '123abc', apiKey: null, + source: asHttpRequestExecutionSource(request), }) ).rejects.toThrowErrorMatchingInlineSnapshot(`"Fail"`); }); @@ -537,6 +545,7 @@ describe('execute()', () => { spaceId: 'default', executionId: '123abc', apiKey: null, + source: asHttpRequestExecutionSource(request), }); expect(mockedActionTypeRegistry.ensureActionTypeEnabled).not.toHaveBeenCalled(); @@ -613,6 +622,7 @@ describe('bulkExecute()', () => { actionId: '123', params: { baz: false }, executionId: '123abc', + source: 'HTTP_REQUEST', apiKey: Buffer.from('123:abc').toString('base64'), }, references: [ @@ -703,6 +713,7 @@ describe('bulkExecute()', () => { params: { baz: false }, executionId: '123abc', consumer: 'test-consumer', + source: 'HTTP_REQUEST', apiKey: Buffer.from('123:abc').toString('base64'), }, references: [ @@ -778,6 +789,7 @@ describe('bulkExecute()', () => { params: { baz: false }, apiKey: Buffer.from('123:abc').toString('base64'), executionId: '123abc', + source: 'HTTP_REQUEST', relatedSavedObjects: [ { id: 'related_some-type_0', @@ -885,6 +897,7 @@ describe('bulkExecute()', () => { actionId: '123', params: { baz: false }, executionId: '123abc', + source: 'SAVED_OBJECT', apiKey: Buffer.from('123:abc').toString('base64'), }, references: [ @@ -989,6 +1002,7 @@ describe('bulkExecute()', () => { params: { baz: false }, apiKey: Buffer.from('123:abc').toString('base64'), executionId: '123abc', + source: 'SAVED_OBJECT', relatedSavedObjects: [ { id: 'related_some-type_0', @@ -1031,6 +1045,7 @@ describe('bulkExecute()', () => { spaceId: 'default', executionId: '123abc', apiKey: null, + source: asHttpRequestExecutionSource(request), }, ]) ).rejects.toThrowErrorMatchingInlineSnapshot( @@ -1067,6 +1082,7 @@ describe('bulkExecute()', () => { spaceId: 'default', executionId: '123abc', apiKey: null, + source: asHttpRequestExecutionSource(request), }, ]) ).rejects.toThrowErrorMatchingInlineSnapshot( @@ -1106,6 +1122,7 @@ describe('bulkExecute()', () => { spaceId: 'default', executionId: '123abc', apiKey: null, + source: asHttpRequestExecutionSource(request), }, ]) ).rejects.toThrowErrorMatchingInlineSnapshot(`"Fail"`); @@ -1162,6 +1179,7 @@ describe('bulkExecute()', () => { spaceId: 'default', executionId: '123abc', apiKey: null, + source: asHttpRequestExecutionSource(request), }, ]); diff --git a/x-pack/plugins/actions/server/create_execute_function.ts b/x-pack/plugins/actions/server/create_execute_function.ts index c050cf34c9fca..c33bcc6923d8a 100644 --- a/x-pack/plugins/actions/server/create_execute_function.ts +++ b/x-pack/plugins/actions/server/create_execute_function.ts @@ -116,6 +116,7 @@ export function createExecutionEnqueuerFunction({ executionId, consumer, relatedSavedObjects: relatedSavedObjectWithRefs, + ...(source ? { source: source.type } : {}), }, { references: taskReferences, @@ -202,6 +203,7 @@ export function createBulkExecutionEnqueuerFunction({ executionId: actionToExecute.executionId, consumer: actionToExecute.consumer, relatedSavedObjects: relatedSavedObjectWithRefs, + ...(actionToExecute.source ? { source: actionToExecute.source.type } : {}), }, references: taskReferences, }; diff --git a/x-pack/plugins/actions/server/create_unsecured_execute_function.test.ts b/x-pack/plugins/actions/server/create_unsecured_execute_function.test.ts index a61d6193a9ab5..cd50be7558fea 100644 --- a/x-pack/plugins/actions/server/create_unsecured_execute_function.test.ts +++ b/x-pack/plugins/actions/server/create_unsecured_execute_function.test.ts @@ -10,7 +10,10 @@ import { savedObjectsRepositoryMock } from '@kbn/core/server/mocks'; import { taskManagerMock } from '@kbn/task-manager-plugin/server/mocks'; import { createBulkUnsecuredExecutionEnqueuerFunction } from './create_unsecured_execute_function'; import { actionTypeRegistryMock } from './action_type_registry.mock'; -import { asSavedObjectExecutionSource } from './lib/action_execution_source'; +import { + asNotificationExecutionSource, + asSavedObjectExecutionSource, +} from './lib/action_execution_source'; const mockTaskManager = taskManagerMock.createStart(); const internalSavedObjectsRepository = savedObjectsRepositoryMock.create(); @@ -59,10 +62,12 @@ describe('bulkExecute()', () => { { id: '123', params: { baz: false }, + source: asNotificationExecutionSource({ connectorId: 'abc', requesterId: 'foo' }), }, { id: '123', params: { baz: true }, + source: asNotificationExecutionSource({ connectorId: 'abc', requesterId: 'foo' }), }, ]); expect(mockTaskManager.bulkSchedule).toHaveBeenCalledTimes(1); @@ -102,6 +107,7 @@ describe('bulkExecute()', () => { actionId: '123', params: { baz: false }, apiKey: null, + source: 'NOTIFICATION', }, references: [], }, @@ -111,6 +117,7 @@ describe('bulkExecute()', () => { actionId: '123', params: { baz: true }, apiKey: null, + source: 'NOTIFICATION', }, references: [], }, @@ -171,6 +178,7 @@ describe('bulkExecute()', () => { { id: '123', params: { baz: true }, + source: asNotificationExecutionSource({ connectorId: 'abc', requesterId: 'foo' }), }, ]); expect(mockTaskManager.bulkSchedule).toHaveBeenCalledTimes(1); @@ -210,6 +218,7 @@ describe('bulkExecute()', () => { actionId: '123', params: { baz: false }, apiKey: null, + source: 'SAVED_OBJECT', }, references: [ { @@ -225,6 +234,7 @@ describe('bulkExecute()', () => { actionId: '123', params: { baz: true }, apiKey: null, + source: 'NOTIFICATION', }, references: [], }, @@ -291,6 +301,7 @@ describe('bulkExecute()', () => { { id: '123', params: { baz: true }, + source: asNotificationExecutionSource({ connectorId: 'abc', requesterId: 'foo' }), relatedSavedObjects: [ { id: 'some-id', @@ -337,6 +348,7 @@ describe('bulkExecute()', () => { actionId: '123', params: { baz: false }, apiKey: null, + source: 'SAVED_OBJECT', }, references: [ { @@ -352,6 +364,7 @@ describe('bulkExecute()', () => { actionId: '123', params: { baz: true }, apiKey: null, + source: 'NOTIFICATION', relatedSavedObjects: [ { id: 'related_some-type_0', @@ -392,10 +405,12 @@ describe('bulkExecute()', () => { { id: '123', params: { baz: false }, + source: asNotificationExecutionSource({ connectorId: 'abc', requesterId: 'foo' }), }, { id: 'not-preconfigured', params: { baz: true }, + source: asNotificationExecutionSource({ connectorId: 'abc', requesterId: 'foo' }), }, ]) ).rejects.toThrowErrorMatchingInlineSnapshot( @@ -429,10 +444,12 @@ describe('bulkExecute()', () => { { id: '123', params: { baz: false }, + source: asNotificationExecutionSource({ connectorId: 'abc', requesterId: 'foo' }), }, { id: '123', params: { baz: true }, + source: asNotificationExecutionSource({ connectorId: 'abc', requesterId: 'foo' }), }, ]) ).rejects.toThrowErrorMatchingInlineSnapshot(`"Fail"`); @@ -468,10 +485,12 @@ describe('bulkExecute()', () => { { id: '123', params: { baz: false }, + source: asNotificationExecutionSource({ connectorId: 'abc', requesterId: 'foo' }), }, { id: '456', params: { baz: true }, + source: asNotificationExecutionSource({ connectorId: 'abc', requesterId: 'foo' }), }, ]) ).rejects.toThrowErrorMatchingInlineSnapshot( diff --git a/x-pack/plugins/actions/server/create_unsecured_execute_function.ts b/x-pack/plugins/actions/server/create_unsecured_execute_function.ts index 4670601ecff83..b1ad90d9093eb 100644 --- a/x-pack/plugins/actions/server/create_unsecured_execute_function.ts +++ b/x-pack/plugins/actions/server/create_unsecured_execute_function.ts @@ -111,6 +111,7 @@ export function createBulkUnsecuredExecutionEnqueuerFunction({ params: actionToExecute.params, apiKey: null, relatedSavedObjects: relatedSavedObjectWithRefs, + ...(actionToExecute.source ? { source: actionToExecute.source.type } : {}), }, references: taskReferences, }; diff --git a/x-pack/plugins/actions/server/index.ts b/x-pack/plugins/actions/server/index.ts index 2713ee17463e4..15f3c2fa7e20a 100644 --- a/x-pack/plugins/actions/server/index.ts +++ b/x-pack/plugins/actions/server/index.ts @@ -29,7 +29,11 @@ export type { export type { PluginSetupContract, PluginStartContract } from './plugin'; -export { asSavedObjectExecutionSource, asHttpRequestExecutionSource } from './lib'; +export { + asSavedObjectExecutionSource, + asHttpRequestExecutionSource, + asNotificationExecutionSource, +} from './lib'; export { ACTION_SAVED_OBJECT_TYPE } from './constants/saved_objects'; export const plugin = (initContext: PluginInitializerContext) => new ActionsPlugin(initContext); diff --git a/x-pack/plugins/actions/server/lib/action_execution_source.ts b/x-pack/plugins/actions/server/lib/action_execution_source.ts index f5e1570dfa4c5..9701b759f981e 100644 --- a/x-pack/plugins/actions/server/lib/action_execution_source.ts +++ b/x-pack/plugins/actions/server/lib/action_execution_source.ts @@ -10,6 +10,7 @@ import { KibanaRequest, SavedObjectReference } from '@kbn/core/server'; export enum ActionExecutionSourceType { SAVED_OBJECT = 'SAVED_OBJECT', HTTP_REQUEST = 'HTTP_REQUEST', + NOTIFICATION = 'NOTIFICATION', } export interface ActionExecutionSource { @@ -19,6 +20,12 @@ export interface ActionExecutionSource { export type HttpRequestExecutionSource = ActionExecutionSource; export type SavedObjectExecutionSource = ActionExecutionSource>; +export interface NotificationSource { + requesterId: string; + connectorId: string; +} +export type NotificationExecutionSource = ActionExecutionSource; + export function asHttpRequestExecutionSource(source: KibanaRequest): HttpRequestExecutionSource { return { type: ActionExecutionSourceType.HTTP_REQUEST, @@ -26,6 +33,13 @@ export function asHttpRequestExecutionSource(source: KibanaRequest): HttpRequest }; } +export function asEmptySource(type: ActionExecutionSourceType): ActionExecutionSource<{}> { + return { + type, + source: {}, + }; +} + export function asSavedObjectExecutionSource( source: Omit ): SavedObjectExecutionSource { @@ -35,6 +49,15 @@ export function asSavedObjectExecutionSource( }; } +export function asNotificationExecutionSource( + source: NotificationSource +): NotificationExecutionSource { + return { + type: ActionExecutionSourceType.NOTIFICATION, + source, + }; +} + export function isHttpRequestExecutionSource( executionSource?: ActionExecutionSource ): executionSource is HttpRequestExecutionSource { @@ -46,3 +69,9 @@ export function isSavedObjectExecutionSource( ): executionSource is SavedObjectExecutionSource { return executionSource?.type === ActionExecutionSourceType.SAVED_OBJECT; } + +export function isNotificationExecutionSource( + executionSource?: ActionExecutionSource +): executionSource is NotificationExecutionSource { + return executionSource?.type === ActionExecutionSourceType.NOTIFICATION; +} diff --git a/x-pack/plugins/actions/server/lib/action_executor.test.ts b/x-pack/plugins/actions/server/lib/action_executor.test.ts index dd7cdbcb45124..a6acfc25a923a 100644 --- a/x-pack/plugins/actions/server/lib/action_executor.test.ts +++ b/x-pack/plugins/actions/server/lib/action_executor.test.ts @@ -10,11 +10,15 @@ import { schema } from '@kbn/config-schema'; import { ActionExecutor } from './action_executor'; import { actionTypeRegistryMock } from '../action_type_registry.mock'; import { encryptedSavedObjectsMock } from '@kbn/encrypted-saved-objects-plugin/server/mocks'; -import { loggingSystemMock } from '@kbn/core/server/mocks'; +import { httpServerMock, loggingSystemMock } from '@kbn/core/server/mocks'; import { eventLoggerMock } from '@kbn/event-log-plugin/server/mocks'; import { spacesServiceMock } from '@kbn/spaces-plugin/server/spaces_service/spaces_service.mock'; import { ActionType } from '../types'; import { actionsMock } from '../mocks'; +import { + asHttpRequestExecutionSource, + asSavedObjectExecutionSource, +} from './action_execution_source'; const actionExecutor = new ActionExecutor({ isESOCanEncrypt: true }); const services = actionsMock.createServices(); @@ -200,6 +204,285 @@ test('successfully executes', async () => { `); }); +test('successfully executes when http_request source is specified', async () => { + const actionType: jest.Mocked = { + id: 'test', + name: 'Test', + minimumLicenseRequired: 'basic', + supportedFeatureIds: ['alerting'], + executor: jest.fn(), + }; + const actionSavedObject = { + id: '1', + type: 'action', + attributes: { + name: '1', + actionTypeId: 'test', + config: { + bar: true, + }, + secrets: { + baz: true, + }, + }, + references: [], + }; + encryptedSavedObjectsClient.getDecryptedAsInternalUser.mockResolvedValueOnce(actionSavedObject); + actionTypeRegistry.get.mockReturnValueOnce(actionType); + await actionExecutor.execute({ + ...executeParams, + source: asHttpRequestExecutionSource(httpServerMock.createKibanaRequest()), + }); + + expect(encryptedSavedObjectsClient.getDecryptedAsInternalUser).toHaveBeenCalledWith( + 'action', + '1', + { namespace: 'some-namespace' } + ); + + expect(actionTypeRegistry.get).toHaveBeenCalledWith('test'); + expect(actionTypeRegistry.isActionExecutable).toHaveBeenCalledWith('1', 'test', { + notifyUsage: true, + }); + + expect(actionType.executor).toHaveBeenCalledWith({ + actionId: '1', + services: expect.anything(), + config: { + bar: true, + }, + secrets: { + baz: true, + }, + params: { foo: true }, + logger: loggerMock, + }); + + expect(loggerMock.debug).toBeCalledWith('executing action test:1: 1'); + expect(eventLogger.logEvent.mock.calls).toMatchInlineSnapshot(` + Array [ + Array [ + Object { + "event": Object { + "action": "execute-start", + "kind": "action", + }, + "kibana": Object { + "action": Object { + "execution": Object { + "source": "http_request", + "uuid": "2", + }, + "id": "1", + "name": "1", + }, + "alert": Object { + "rule": Object { + "execution": Object { + "uuid": "123abc", + }, + }, + }, + "saved_objects": Array [ + Object { + "id": "1", + "namespace": "some-namespace", + "rel": "primary", + "type": "action", + "type_id": "test", + }, + ], + "space_ids": Array [ + "some-namespace", + ], + }, + "message": "action started: test:1: 1", + }, + ], + Array [ + Object { + "event": Object { + "action": "execute", + "kind": "action", + "outcome": "success", + }, + "kibana": Object { + "action": Object { + "execution": Object { + "source": "http_request", + "uuid": "2", + }, + "id": "1", + "name": "1", + }, + "alert": Object { + "rule": Object { + "execution": Object { + "uuid": "123abc", + }, + }, + }, + "saved_objects": Array [ + Object { + "id": "1", + "namespace": "some-namespace", + "rel": "primary", + "type": "action", + "type_id": "test", + }, + ], + "space_ids": Array [ + "some-namespace", + ], + }, + "message": "action executed: test:1: 1", + }, + ], + ] + `); +}); + +test('successfully executes when saved_object source is specified', async () => { + const actionType: jest.Mocked = { + id: 'test', + name: 'Test', + minimumLicenseRequired: 'basic', + supportedFeatureIds: ['alerting'], + executor: jest.fn(), + }; + const actionSavedObject = { + id: '1', + type: 'action', + attributes: { + name: '1', + actionTypeId: 'test', + config: { + bar: true, + }, + secrets: { + baz: true, + }, + }, + references: [], + }; + encryptedSavedObjectsClient.getDecryptedAsInternalUser.mockResolvedValueOnce(actionSavedObject); + actionTypeRegistry.get.mockReturnValueOnce(actionType); + await actionExecutor.execute({ + ...executeParams, + source: asSavedObjectExecutionSource({ + id: '573891ae-8c48-49cb-a197-0cd5ec34a88b', + type: 'alert', + }), + }); + + expect(encryptedSavedObjectsClient.getDecryptedAsInternalUser).toHaveBeenCalledWith( + 'action', + '1', + { namespace: 'some-namespace' } + ); + + expect(actionTypeRegistry.get).toHaveBeenCalledWith('test'); + expect(actionTypeRegistry.isActionExecutable).toHaveBeenCalledWith('1', 'test', { + notifyUsage: true, + }); + + expect(actionType.executor).toHaveBeenCalledWith({ + actionId: '1', + services: expect.anything(), + config: { + bar: true, + }, + secrets: { + baz: true, + }, + params: { foo: true }, + logger: loggerMock, + }); + + expect(loggerMock.debug).toBeCalledWith('executing action test:1: 1'); + expect(eventLogger.logEvent.mock.calls).toMatchInlineSnapshot(` + Array [ + Array [ + Object { + "event": Object { + "action": "execute-start", + "kind": "action", + }, + "kibana": Object { + "action": Object { + "execution": Object { + "source": "alert", + "uuid": "2", + }, + "id": "1", + "name": "1", + }, + "alert": Object { + "rule": Object { + "execution": Object { + "uuid": "123abc", + }, + }, + }, + "saved_objects": Array [ + Object { + "id": "1", + "namespace": "some-namespace", + "rel": "primary", + "type": "action", + "type_id": "test", + }, + ], + "space_ids": Array [ + "some-namespace", + ], + }, + "message": "action started: test:1: 1", + }, + ], + Array [ + Object { + "event": Object { + "action": "execute", + "kind": "action", + "outcome": "success", + }, + "kibana": Object { + "action": Object { + "execution": Object { + "source": "alert", + "uuid": "2", + }, + "id": "1", + "name": "1", + }, + "alert": Object { + "rule": Object { + "execution": Object { + "uuid": "123abc", + }, + }, + }, + "saved_objects": Array [ + Object { + "id": "1", + "namespace": "some-namespace", + "rel": "primary", + "type": "action", + "type_id": "test", + }, + ], + "space_ids": Array [ + "some-namespace", + ], + }, + "message": "action executed: test:1: 1", + }, + ], + ] + `); +}); + test('successfully executes with preconfigured connector', async () => { const actionType: jest.Mocked = { id: 'test', diff --git a/x-pack/plugins/actions/server/lib/action_executor.ts b/x-pack/plugins/actions/server/lib/action_executor.ts index d42951947f93d..d9b2f7abc73cb 100644 --- a/x-pack/plugins/actions/server/lib/action_executor.ts +++ b/x-pack/plugins/actions/server/lib/action_executor.ts @@ -90,6 +90,7 @@ export class ActionExecutor { actionId, params, request, + source, isEphemeral, taskInfo, executionId, @@ -184,6 +185,7 @@ export class ActionExecutor { name, actionExecutionId, isPreconfigured: this.actionInfo.isPreconfigured, + ...(source ? { source } : {}), }); eventLogger.startTiming(event); @@ -348,6 +350,7 @@ export class ActionExecutor { relatedSavedObjects, actionExecutionId, isPreconfigured: this.actionInfo.isPreconfigured, + ...(source ? { source } : {}), }); eventLogger.logEvent(event); @@ -363,7 +366,7 @@ interface ActionInfo { isPreconfigured?: boolean; } -async function getActionInfoInternal( +async function getActionInfoInternal( isESOCanEncrypt: boolean, encryptedSavedObjectsClient: EncryptedSavedObjectsClient, preconfiguredActions: PreConfiguredAction[], diff --git a/x-pack/plugins/actions/server/lib/create_action_event_log_record_object.test.ts b/x-pack/plugins/actions/server/lib/create_action_event_log_record_object.test.ts index 58a21c911cf34..28c3a96e14507 100644 --- a/x-pack/plugins/actions/server/lib/create_action_event_log_record_object.test.ts +++ b/x-pack/plugins/actions/server/lib/create_action_event_log_record_object.test.ts @@ -5,6 +5,8 @@ * 2.0. */ +import { httpServerMock } from '@kbn/core-http-server-mocks'; +import { asHttpRequestExecutionSource } from './action_execution_source'; import { createActionEventLogRecordObject } from './create_action_event_log_record_object'; describe('createActionEventLogRecordObject', () => { @@ -297,6 +299,120 @@ describe('createActionEventLogRecordObject', () => { }); }); + test('created action event "execute" with http_request source', async () => { + expect( + createActionEventLogRecordObject({ + actionId: '1', + name: 'test name', + action: 'execute', + message: 'action execution start', + namespace: 'default', + executionId: '123abc', + consumer: 'test-consumer', + savedObjects: [ + { + id: '2', + type: 'action', + typeId: '.email', + relation: 'primary', + }, + ], + actionExecutionId: '123abc', + source: asHttpRequestExecutionSource(httpServerMock.createKibanaRequest()), + }) + ).toStrictEqual({ + event: { + action: 'execute', + kind: 'action', + }, + kibana: { + alert: { + rule: { + consumer: 'test-consumer', + execution: { + uuid: '123abc', + }, + }, + }, + saved_objects: [ + { + id: '2', + namespace: 'default', + rel: 'primary', + type: 'action', + type_id: '.email', + }, + ], + action: { + name: 'test name', + id: '1', + execution: { + source: 'http_request', + uuid: '123abc', + }, + }, + }, + message: 'action execution start', + }); + }); + + test('created action event "execute" with saved_object source', async () => { + expect( + createActionEventLogRecordObject({ + actionId: '1', + name: 'test name', + action: 'execute', + message: 'action execution start', + namespace: 'default', + executionId: '123abc', + consumer: 'test-consumer', + savedObjects: [ + { + id: '2', + type: 'action', + typeId: '.email', + relation: 'primary', + }, + ], + actionExecutionId: '123abc', + source: asHttpRequestExecutionSource(httpServerMock.createKibanaRequest()), + }) + ).toStrictEqual({ + event: { + action: 'execute', + kind: 'action', + }, + kibana: { + alert: { + rule: { + consumer: 'test-consumer', + execution: { + uuid: '123abc', + }, + }, + }, + saved_objects: [ + { + id: '2', + namespace: 'default', + rel: 'primary', + type: 'action', + type_id: '.email', + }, + ], + action: { + name: 'test name', + id: '1', + execution: { + source: 'http_request', + uuid: '123abc', + }, + }, + }, + message: 'action execution start', + }); + }); + test('created action event "execute" for preconfigured connector with space_agnostic true', async () => { expect( createActionEventLogRecordObject({ diff --git a/x-pack/plugins/actions/server/lib/create_action_event_log_record_object.ts b/x-pack/plugins/actions/server/lib/create_action_event_log_record_object.ts index 95a6101d684f5..46dcddd9b55c5 100644 --- a/x-pack/plugins/actions/server/lib/create_action_event_log_record_object.ts +++ b/x-pack/plugins/actions/server/lib/create_action_event_log_record_object.ts @@ -9,6 +9,7 @@ import { set } from '@kbn/safer-lodash-set'; import { isEmpty } from 'lodash'; import { IEvent, SAVED_OBJECT_REL_PRIMARY } from '@kbn/event-log-plugin/server'; import { RelatedSavedObjects } from './related_saved_objects'; +import { ActionExecutionSource, isSavedObjectExecutionSource } from './action_execution_source'; export type Event = Exclude; @@ -35,6 +36,7 @@ interface CreateActionEventLogRecordParams { }>; relatedSavedObjects?: RelatedSavedObjects; isPreconfigured?: boolean; + source?: ActionExecutionSource; } export function createActionEventLogRecordObject(params: CreateActionEventLogRecordParams): Event { @@ -51,6 +53,7 @@ export function createActionEventLogRecordObject(params: CreateActionEventLogRec actionExecutionId, isPreconfigured, actionId, + source, } = params; const kibanaAlertRule = { @@ -94,6 +97,14 @@ export function createActionEventLogRecordObject(params: CreateActionEventLogRec ...(message ? { message } : {}), }; + if (source) { + if (isSavedObjectExecutionSource(source)) { + set(event, 'kibana.action.execution.source', source.source.type); + } else { + set(event, 'kibana.action.execution.source', source.type?.toLowerCase()); + } + } + for (const relatedSavedObject of relatedSavedObjects || []) { const ruleTypeId = relatedSavedObject.type === 'alert' ? relatedSavedObject.typeId : null; if (ruleTypeId) { diff --git a/x-pack/plugins/actions/server/lib/get_execution_log_aggregation.test.ts b/x-pack/plugins/actions/server/lib/get_execution_log_aggregation.test.ts index c92f4be981795..f85d51b5ae2c3 100644 --- a/x-pack/plugins/actions/server/lib/get_execution_log_aggregation.test.ts +++ b/x-pack/plugins/actions/server/lib/get_execution_log_aggregation.test.ts @@ -96,6 +96,7 @@ describe('getExecutionLogAggregation', () => { 'kibana.space_ids', 'kibana.action.name', 'kibana.action.id', + 'kibana.action.execution.source', ], }, }, @@ -225,6 +226,7 @@ describe('getExecutionLogAggregation', () => { 'kibana.space_ids', 'kibana.action.name', 'kibana.action.id', + 'kibana.action.execution.source', ], }, }, @@ -370,6 +372,7 @@ describe('getExecutionLogAggregation', () => { 'kibana.space_ids', 'kibana.action.name', 'kibana.action.id', + 'kibana.action.execution.source', ], }, }, @@ -526,7 +529,11 @@ describe('formatExecutionLogResult', () => { kibana: { space_ids: ['default'], version: '8.7.0', - action: { name: 'test connector', id: '1' }, + action: { + name: 'test connector', + id: '1', + execution: { source: 'SAVED_OBJECT' }, + }, }, message: 'action executed: .server-log:6709f660-8d11-11ed-bae5-bd32cbc9eaaa: test connector', @@ -563,6 +570,7 @@ describe('formatExecutionLogResult', () => { timestamp: '2023-01-05T15:55:50.495Z', version: '8.7.0', timed_out: false, + source: 'SAVED_OBJECT', }, ], total: 1, @@ -598,7 +606,11 @@ describe('formatExecutionLogResult', () => { kibana: { space_ids: ['default'], version: '8.7.0', - action: { name: 'test', id: '1' }, + action: { + name: 'test', + id: '1', + execution: { source: 'SAVED_OBJECT' }, + }, }, message: 'action execution failure: .email:e020c620-8d14-11ed-bae5-bd32cbc9eaaa: test', @@ -638,7 +650,11 @@ describe('formatExecutionLogResult', () => { kibana: { space_ids: ['default'], version: '8.7.0', - action: { name: 'test connector', id: '1' }, + action: { + name: 'test connector', + id: '1', + execution: { source: 'SAVED_OBJECT' }, + }, }, message: 'action executed: .server-log:6709f660-8d11-11ed-bae5-bd32cbc9eaaa: test connector', @@ -675,6 +691,7 @@ describe('formatExecutionLogResult', () => { timestamp: '2023-01-05T16:23:53.813Z', version: '8.7.0', timed_out: false, + source: 'SAVED_OBJECT', }, { connector_name: 'test connector', @@ -689,6 +706,7 @@ describe('formatExecutionLogResult', () => { timestamp: '2023-01-05T15:55:50.495Z', version: '8.7.0', timed_out: false, + source: 'SAVED_OBJECT', }, ], total: 2, diff --git a/x-pack/plugins/actions/server/lib/get_execution_log_aggregation.ts b/x-pack/plugins/actions/server/lib/get_execution_log_aggregation.ts index e2f3940716317..ddbb864d4c06a 100644 --- a/x-pack/plugins/actions/server/lib/get_execution_log_aggregation.ts +++ b/x-pack/plugins/actions/server/lib/get_execution_log_aggregation.ts @@ -28,6 +28,7 @@ const VERSION_FIELD = 'kibana.version'; const ERROR_MESSAGE_FIELD = 'error.message'; const SCHEDULE_DELAY_FIELD = 'kibana.task.schedule_delay'; const EXECUTION_UUID_FIELD = 'kibana.action.execution.uuid'; +const EXECUTION_SOURCE_FIELD = 'kibana.action.execution.source'; const Millis2Nanos = 1000 * 1000; @@ -257,6 +258,7 @@ export function getExecutionLogAggregation({ SPACE_ID_FIELD, ACTION_NAME_FIELD, ACTION_ID_FIELD, + EXECUTION_SOURCE_FIELD, ], }, }, @@ -312,6 +314,7 @@ function formatExecutionLogAggBucket(bucket: IExecutionUuidAggBucket): IExecutio const message = status === 'failure' ? `${outcomeMessage} - ${outcomeErrorMessage}` : outcomeMessage; const version = outcomeAndMessage.kibana?.version ?? ''; + const source = outcomeAndMessage.kibana?.action?.execution?.source ?? ''; const spaceIds = outcomeAndMessage?.kibana?.space_ids ?? []; const connectorName = outcomeAndMessage?.kibana?.action?.name ?? ''; @@ -324,6 +327,7 @@ function formatExecutionLogAggBucket(bucket: IExecutionUuidAggBucket): IExecutio status, message, version, + source, schedule_delay_ms: scheduleDelayUs / Millis2Nanos, space_ids: spaceIds, connector_name: connectorName, diff --git a/x-pack/plugins/actions/server/lib/index.ts b/x-pack/plugins/actions/server/lib/index.ts index ef1a925ca72cb..6d2c3b8c109c5 100644 --- a/x-pack/plugins/actions/server/lib/index.ts +++ b/x-pack/plugins/actions/server/lib/index.ts @@ -31,6 +31,8 @@ export { isSavedObjectExecutionSource, asHttpRequestExecutionSource, isHttpRequestExecutionSource, + asNotificationExecutionSource, + isNotificationExecutionSource, } from './action_execution_source'; export { validateEmptyStrings } from './validate_empty_strings'; export { parseDate } from './parse_date'; 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 e5873169ceddb..404c3ba452086 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 @@ -29,6 +29,7 @@ const executeParamsFields = [ 'executionId', 'request.headers', 'taskInfo', + 'source', ]; const spaceIdToNamespace = jest.fn(); const actionTypeRegistry = actionTypeRegistryMock.create(); @@ -287,6 +288,128 @@ test('executes the task by calling the executor with proper parameters when cons ); }); +test('executes the task by calling the executor with proper parameters when saved_object source is provided', 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', + consumer: 'test-consumer', + params: { baz: true }, + executionId: '123abc', + source: 'SAVED_OBJECT', + apiKey: Buffer.from('123:abc').toString('base64'), + }, + references: [{ name: 'source', id: 'abc', type: 'alert' }], + }); + + const runnerResult = await taskRunner.run(); + + expect(runnerResult).toBeUndefined(); + expect(spaceIdToNamespace).toHaveBeenCalledWith('test'); + expect(mockedEncryptedSavedObjectsClient.getDecryptedAsInternalUser).toHaveBeenCalledWith( + 'action_task_params', + '3', + { namespace: 'namespace-test' } + ); + + const [executeParams] = mockedActionExecutor.execute.mock.calls[0]; + expect(pick(executeParams, [...executeParamsFields, 'consumer'])).toEqual({ + actionId: '2', + consumer: 'test-consumer', + isEphemeral: false, + params: { baz: true }, + relatedSavedObjects: [], + executionId: '123abc', + request: { + headers: { + // base64 encoded "123:abc" + authorization: 'ApiKey MTIzOmFiYw==', + }, + }, + source: { + type: 'SAVED_OBJECT', + source: { id: 'abc', type: 'alert' }, + }, + taskInfo: { + scheduled: new Date(), + attempts: 0, + }, + }); + + expect(taskRunnerFactoryInitializerParams.basePathService.set).toHaveBeenCalledWith( + executeParams.request, + '/s/test' + ); +}); + +test('executes the task by calling the executor with proper parameters when notification source is provided', 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', + consumer: 'test-consumer', + params: { baz: true }, + executionId: '123abc', + source: 'NOTIFICATION', + apiKey: Buffer.from('123:abc').toString('base64'), + }, + references: [], + }); + + const runnerResult = await taskRunner.run(); + + expect(runnerResult).toBeUndefined(); + expect(spaceIdToNamespace).toHaveBeenCalledWith('test'); + expect(mockedEncryptedSavedObjectsClient.getDecryptedAsInternalUser).toHaveBeenCalledWith( + 'action_task_params', + '3', + { namespace: 'namespace-test' } + ); + + const [executeParams] = mockedActionExecutor.execute.mock.calls[0]; + expect(pick(executeParams, [...executeParamsFields, 'consumer'])).toEqual({ + actionId: '2', + consumer: 'test-consumer', + isEphemeral: false, + params: { baz: true }, + relatedSavedObjects: [], + executionId: '123abc', + request: { + headers: { + // base64 encoded "123:abc" + authorization: 'ApiKey MTIzOmFiYw==', + }, + }, + source: { + type: 'NOTIFICATION', + source: {}, + }, + taskInfo: { + scheduled: new Date(), + attempts: 0, + }, + }); + + expect(taskRunnerFactoryInitializerParams.basePathService.set).toHaveBeenCalledWith( + executeParams.request, + '/s/test' + ); +}); + test('cleans up action_task_params object', async () => { const taskRunner = taskRunnerFactory.create({ taskInstance: mockedTaskInstance, 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 8fbf68ca09f98..de347637f5cf4 100644 --- a/x-pack/plugins/actions/server/lib/task_runner_factory.ts +++ b/x-pack/plugins/actions/server/lib/task_runner_factory.ts @@ -7,19 +7,17 @@ import { v4 as uuidv4 } from 'uuid'; import { pick } from 'lodash'; -import { pipe } from 'fp-ts/lib/pipeable'; -import { map, fromNullable, getOrElse } from 'fp-ts/lib/Option'; import { addSpaceIdToPath } from '@kbn/spaces-plugin/server'; import { Logger, SavedObjectsClientContract, KibanaRequest, CoreKibanaRequest, - SavedObjectReference, IBasePath, SavedObject, Headers, FakeRawRequest, + SavedObjectReference, } from '@kbn/core/server'; import { RunContext } from '@kbn/task-manager-plugin/server'; import { EncryptedSavedObjectsClient } from '@kbn/encrypted-saved-objects-plugin/server'; @@ -34,7 +32,11 @@ import { isPersistedActionTask, } from '../types'; import { ACTION_TASK_PARAMS_SAVED_OBJECT_TYPE } from '../constants/saved_objects'; -import { asSavedObjectExecutionSource } from './action_execution_source'; +import { + ActionExecutionSourceType, + asEmptySource, + asSavedObjectExecutionSource, +} from './action_execution_source'; import { RelatedSavedObjects, validatedRelatedSavedObjects } from './related_saved_objects'; import { injectSavedObjectReferences } from './action_task_params_utils'; import { InMemoryMetrics, IN_MEMORY_METRICS } from '../monitoring'; @@ -93,7 +95,15 @@ export class TaskRunnerFactory { const { spaceId } = actionTaskExecutorParams; const { - attributes: { actionId, params, apiKey, executionId, consumer, relatedSavedObjects }, + attributes: { + actionId, + params, + apiKey, + executionId, + consumer, + source, + relatedSavedObjects, + }, references, } = await getActionTaskParams( actionTaskExecutorParams, @@ -118,12 +128,12 @@ export class TaskRunnerFactory { actionId: actionId as string, isEphemeral: !isPersistedActionTask(actionTaskExecutorParams), request, - ...getSourceFromReferences(references), taskInfo, executionId, consumer, relatedSavedObjects: validatedRelatedSavedObjects(logger, relatedSavedObjects), actionExecutionId, + ...getSource(references, source), }); } catch (e) { logger.error( @@ -188,7 +198,7 @@ export class TaskRunnerFactory { const { spaceId } = actionTaskExecutorParams; const { - attributes: { actionId, apiKey, executionId, consumer, relatedSavedObjects }, + attributes: { actionId, apiKey, executionId, consumer, source, relatedSavedObjects }, references, } = await getActionTaskParams( actionTaskExecutorParams, @@ -206,8 +216,8 @@ export class TaskRunnerFactory { consumer, executionId, relatedSavedObjects: (relatedSavedObjects || []) as RelatedSavedObjects, - ...getSourceFromReferences(references), actionExecutionId, + ...getSource(references, source), }); inMemoryMetrics.increment(IN_MEMORY_METRICS.ACTION_TIMEOUTS); @@ -275,12 +285,11 @@ async function getActionTaskParams( } } -function getSourceFromReferences(references: SavedObjectReference[]) { - return pipe( - fromNullable(references.find((ref) => ref.name === 'source')), - map((source) => ({ - source: asSavedObjectExecutionSource(pick(source, 'id', 'type')), - })), - getOrElse(() => ({})) - ); +function getSource(references: SavedObjectReference[], sourceType?: string) { + const sourceInReferences = references.find((ref) => ref.name === 'source'); + if (sourceInReferences) { + return { source: asSavedObjectExecutionSource(pick(sourceInReferences, 'id', 'type')) }; + } + + return sourceType ? { source: asEmptySource(sourceType as ActionExecutionSourceType) } : {}; } diff --git a/x-pack/plugins/actions/server/routes/get_global_execution_logs.test.ts b/x-pack/plugins/actions/server/routes/get_global_execution_logs.test.ts index e91ab9268bac5..1c309e14dae09 100644 --- a/x-pack/plugins/actions/server/routes/get_global_execution_logs.test.ts +++ b/x-pack/plugins/actions/server/routes/get_global_execution_logs.test.ts @@ -39,6 +39,7 @@ describe('getRuleExecutionLogRoute', () => { timestamp: '2023-01-05T15:55:50.495Z', version: '8.7.0', timed_out: false, + source: 'SAVED_OBJECT', }, ], total: 1, diff --git a/x-pack/plugins/actions/server/saved_objects/mappings.json b/x-pack/plugins/actions/server/saved_objects/mappings.json index 80646579f86db..be0a691f852b2 100644 --- a/x-pack/plugins/actions/server/saved_objects/mappings.json +++ b/x-pack/plugins/actions/server/saved_objects/mappings.json @@ -25,6 +25,7 @@ } }, "action_task_params": { + "dynamic": false, "properties": { "actionId": { "type": "keyword" diff --git a/x-pack/plugins/actions/server/types.ts b/x-pack/plugins/actions/server/types.ts index 147a71da0c960..72e7eeddce1c1 100644 --- a/x-pack/plugins/actions/server/types.ts +++ b/x-pack/plugins/actions/server/types.ts @@ -155,6 +155,7 @@ export interface ActionTaskParams extends SavedObjectAttributes { apiKey?: string; executionId?: string; consumer?: string; + source?: string; } interface PersistedActionTaskExecutorParams { diff --git a/x-pack/plugins/actions/server/unsecured_actions_client/unsecured_actions_client.test.ts b/x-pack/plugins/actions/server/unsecured_actions_client/unsecured_actions_client.test.ts index c863e943b8dc0..7d0d4d687a71a 100644 --- a/x-pack/plugins/actions/server/unsecured_actions_client/unsecured_actions_client.test.ts +++ b/x-pack/plugins/actions/server/unsecured_actions_client/unsecured_actions_client.test.ts @@ -7,6 +7,7 @@ import { UnsecuredActionsClient } from './unsecured_actions_client'; import { savedObjectsRepositoryMock } from '@kbn/core/server/mocks'; +import { asNotificationExecutionSource } from '../lib'; const internalSavedObjectsRepository = savedObjectsRepositoryMock.create(); const executionEnqueuer = jest.fn(); @@ -56,9 +57,33 @@ describe('bulkEnqueueExecution()', () => { }, ]; await expect( - unsecuredActionsClient.bulkEnqueueExecution('notifications', opts) + unsecuredActionsClient.bulkEnqueueExecution('functional_tester', opts) ).resolves.toMatchInlineSnapshot(`undefined`); expect(executionEnqueuer).toHaveBeenCalledWith(internalSavedObjectsRepository, opts); }); + + test('injects source and calls the executionEnqueuer with the appropriate parameters when requester is "notifications"', async () => { + const opts = [ + { + id: 'preconfigured1', + params: {}, + executionId: '123abc', + }, + { + id: 'preconfigured2', + params: {}, + executionId: '456def', + }, + ]; + await expect( + unsecuredActionsClient.bulkEnqueueExecution('notifications', opts) + ).resolves.toMatchInlineSnapshot(`undefined`); + + const optsWithSource = opts.map((opt) => ({ + ...opt, + source: asNotificationExecutionSource({ connectorId: opt.id, requesterId: 'notifications' }), + })); + expect(executionEnqueuer).toHaveBeenCalledWith(internalSavedObjectsRepository, optsWithSource); + }); }); diff --git a/x-pack/plugins/actions/server/unsecured_actions_client/unsecured_actions_client.ts b/x-pack/plugins/actions/server/unsecured_actions_client/unsecured_actions_client.ts index 333490389013a..a2d87c8a5db4a 100644 --- a/x-pack/plugins/actions/server/unsecured_actions_client/unsecured_actions_client.ts +++ b/x-pack/plugins/actions/server/unsecured_actions_client/unsecured_actions_client.ts @@ -10,11 +10,14 @@ import { BulkUnsecuredExecutionEnqueuer, ExecuteOptions, } from '../create_unsecured_execute_function'; +import { asNotificationExecutionSource } from '../lib'; + +const NOTIFICATION_REQUESTER_ID = 'notifications'; // allowlist for features wanting access to the unsecured actions client // which allows actions to be enqueued for execution without a user request const ALLOWED_REQUESTER_IDS = [ - 'notifications', + NOTIFICATION_REQUESTER_ID, // For functional testing 'functional_tester', ]; @@ -47,6 +50,25 @@ export class UnsecuredActionsClient { `"${requesterId}" feature is not allow-listed for UnsecuredActionsClient access.` ); } - return this.executionEnqueuer(this.internalSavedObjectsRepository, actionsToExecute); + // Inject source based on requesterId + return this.executionEnqueuer( + this.internalSavedObjectsRepository, + this.injectSource(requesterId, actionsToExecute) + ); + } + + private injectSource(requesterId: string, actionsToExecute: ExecuteOptions[]): ExecuteOptions[] { + switch (requesterId) { + case NOTIFICATION_REQUESTER_ID: + return actionsToExecute.map((actionToExecute) => ({ + ...actionToExecute, + source: asNotificationExecutionSource({ + requesterId, + connectorId: actionToExecute.id, + }), + })); + default: + return actionsToExecute; + } } } diff --git a/x-pack/plugins/actions/tsconfig.json b/x-pack/plugins/actions/tsconfig.json index 72ba9c7c92e09..77ef11e88bfe3 100644 --- a/x-pack/plugins/actions/tsconfig.json +++ b/x-pack/plugins/actions/tsconfig.json @@ -32,7 +32,8 @@ "@kbn/logging", "@kbn/logging-mocks", "@kbn/core-elasticsearch-client-server-mocks", - "@kbn/safer-lodash-set" + "@kbn/safer-lodash-set", + "@kbn/core-http-server-mocks" ], "exclude": [ "target/**/*", diff --git a/x-pack/plugins/cases/server/client/cases/push.ts b/x-pack/plugins/cases/server/client/cases/push.ts index 177f6f2ec6ce6..4f3ab74557278 100644 --- a/x-pack/plugins/cases/server/client/cases/push.ts +++ b/x-pack/plugins/cases/server/client/cases/push.ts @@ -11,6 +11,7 @@ import type { SavedObjectsFindResponse } from '@kbn/core/server'; import type { UserProfile } from '@kbn/security-plugin/common'; import type { SecurityPluginStart } from '@kbn/security-plugin/server'; +import { asSavedObjectExecutionSource } from '@kbn/actions-plugin/server'; import type { ActionConnector, CaseResponse, @@ -26,7 +27,7 @@ import { OWNER_FIELD, CommentType, } from '../../../common/api'; -import { CASE_COMMENT_SAVED_OBJECT } from '../../../common/constants'; +import { CASE_COMMENT_SAVED_OBJECT, CASE_SAVED_OBJECT } from '../../../common/constants'; import { createIncident, getDurationInSeconds, getUserProfiles } from './utils'; import { createCaseError } from '../../common/error'; @@ -164,6 +165,7 @@ export const push = async ( subAction: 'pushToService', subActionParams: externalServiceIncident, }, + source: asSavedObjectExecutionSource({ id: caseId, type: CASE_SAVED_OBJECT }), }); if (pushRes.status === 'error') { diff --git a/x-pack/plugins/event_log/generated/mappings.json b/x-pack/plugins/event_log/generated/mappings.json index a5212917393f8..8c61d6385d80b 100644 --- a/x-pack/plugins/event_log/generated/mappings.json +++ b/x-pack/plugins/event_log/generated/mappings.json @@ -445,6 +445,10 @@ }, "execution": { "properties": { + "source": { + "ignore_above": 1024, + "type": "keyword" + }, "uuid": { "ignore_above": 1024, "type": "keyword" diff --git a/x-pack/plugins/event_log/generated/schemas.ts b/x-pack/plugins/event_log/generated/schemas.ts index 93ee9f2977a36..27c3813dae658 100644 --- a/x-pack/plugins/event_log/generated/schemas.ts +++ b/x-pack/plugins/event_log/generated/schemas.ts @@ -201,6 +201,7 @@ export const EventSchema = schema.maybe( id: ecsString(), execution: schema.maybe( schema.object({ + source: ecsString(), uuid: ecsString(), }) ), diff --git a/x-pack/plugins/event_log/scripts/mappings.js b/x-pack/plugins/event_log/scripts/mappings.js index 36838a4208a87..ecc6a5fc30792 100644 --- a/x-pack/plugins/event_log/scripts/mappings.js +++ b/x-pack/plugins/event_log/scripts/mappings.js @@ -227,6 +227,10 @@ exports.EcsCustomPropertyMappings = { }, execution: { properties: { + source: { + ignore_above: 1024, + type: 'keyword', + }, uuid: { ignore_above: 1024, type: 'keyword', diff --git a/x-pack/plugins/triggers_actions_ui/public/application/sections/actions_connectors_list/components/actions_connectors_event_log_list_table.tsx b/x-pack/plugins/triggers_actions_ui/public/application/sections/actions_connectors_list/components/actions_connectors_event_log_list_table.tsx index a2a674ecd73ee..ec8d0d305ba69 100644 --- a/x-pack/plugins/triggers_actions_ui/public/application/sections/actions_connectors_list/components/actions_connectors_event_log_list_table.tsx +++ b/x-pack/plugins/triggers_actions_ui/public/application/sections/actions_connectors_list/components/actions_connectors_event_log_list_table.tsx @@ -377,6 +377,21 @@ export const ConnectorEventLogListTable = { - const { spaceId, connectorId, actionTypeId, outcome, message, errorMessage } = params; + const { spaceId, connectorId, actionTypeId, outcome, message, errorMessage, source } = params; const events: IValidatedEvent[] = await retry.try(async () => { return await getEventLog({ @@ -559,6 +562,10 @@ export default function ({ getService }: FtrProviderContext) { expect(executeEvent?.message).to.eql(message); expect(startExecuteEvent?.message).to.eql(message.replace('executed', 'started')); + if (source) { + expect(executeEvent?.kibana?.action?.execution?.source).to.eql(source.toLowerCase()); + } + if (errorMessage) { expect(executeEvent?.error?.message).to.eql(errorMessage); } diff --git a/x-pack/test/alerting_api_integration/spaces_only/tests/actions/execute.ts b/x-pack/test/alerting_api_integration/spaces_only/tests/actions/execute.ts index f66570fc2655f..cb10066a50653 100644 --- a/x-pack/test/alerting_api_integration/spaces_only/tests/actions/execute.ts +++ b/x-pack/test/alerting_api_integration/spaces_only/tests/actions/execute.ts @@ -8,6 +8,7 @@ import type { Client } from '@elastic/elasticsearch'; import expect from '@kbn/expect'; import { IValidatedEvent, nanosToMillis } from '@kbn/event-log-plugin/server'; import { ESTestIndexTool, ES_TEST_INDEX_NAME } from '@kbn/alerting-api-integration-helpers'; +import { ActionExecutionSourceType } from '@kbn/actions-plugin/server/lib/action_execution_source'; import { Spaces } from '../../scenarios'; import { getUrlPrefix, ObjectRemover, getEventLog } from '../../../common/lib'; import { FtrProviderContext } from '../../../common/ftr_provider_context'; @@ -95,6 +96,7 @@ export default function ({ getService }: FtrProviderContext) { outcome: 'success', message: `action executed: test.index-record:${createdAction.id}: My action`, startMessage: `action started: test.index-record:${createdAction.id}: My action`, + source: ActionExecutionSourceType.HTTP_REQUEST, }); }); @@ -138,6 +140,7 @@ export default function ({ getService }: FtrProviderContext) { outcome: 'failure', message: `action execution failure: test.failing:${createdAction.id}: failing action`, errorMessage: `an error occurred while running the action: expected failure for .kibana-alerting-test-data actions-failure-1:space1; retry: true`, + source: ActionExecutionSourceType.HTTP_REQUEST, }); }); @@ -336,11 +339,20 @@ export default function ({ getService }: FtrProviderContext) { message: string; errorMessage?: string; startMessage?: string; + source?: string; } async function validateEventLog(params: ValidateEventLogParams): Promise { - const { spaceId, actionId, actionTypeId, outcome, message, startMessage, errorMessage } = - params; + const { + spaceId, + actionId, + actionTypeId, + outcome, + message, + startMessage, + errorMessage, + source, + } = params; const events: IValidatedEvent[] = await retry.try(async () => { return await getEventLog({ @@ -397,6 +409,10 @@ export default function ({ getService }: FtrProviderContext) { expect(executeEvent?.kibana?.task).to.eql(undefined); + if (source) { + expect(executeEvent?.kibana?.action?.execution?.source).to.eql(source.toLowerCase()); + } + if (errorMessage) { expect(executeEvent?.error?.message).to.eql(errorMessage); } diff --git a/x-pack/test/alerting_api_integration/spaces_only/tests/alerting/group1/event_log.ts b/x-pack/test/alerting_api_integration/spaces_only/tests/alerting/group1/event_log.ts index ac862b55aaadc..27d4fc59d239a 100644 --- a/x-pack/test/alerting_api_integration/spaces_only/tests/alerting/group1/event_log.ts +++ b/x-pack/test/alerting_api_integration/spaces_only/tests/alerting/group1/event_log.ts @@ -319,6 +319,7 @@ export default function eventLogTests({ getService }: FtrProviderContext) { ruleTypeId: response.body.rule_type_id, rule: undefined, consumer: 'alertsFixture', + source: 'alert', }); break; } @@ -1138,6 +1139,7 @@ interface ValidateEventLogParams { namespace?: string; }; flapping?: boolean; + source?: string; } export function validateEvent(event: IValidatedEvent, params: ValidateEventLogParams): void { @@ -1157,6 +1159,7 @@ export function validateEvent(event: IValidatedEvent, params: ValidateEventLogPa consumer, ruleTypeId, flapping, + source, } = params; const { status, actionGroupId, instanceId, reason, shouldHaveEventEnd } = params; @@ -1210,6 +1213,10 @@ export function validateEvent(event: IValidatedEvent, params: ValidateEventLogPa expect(event?.kibana?.alert?.flapping).to.be(flapping); } + if (source) { + expect(event?.kibana?.action?.execution?.source).to.be(source); + } + expect(event?.kibana?.alert?.rule?.rule_type_id).to.be(ruleTypeId); expect(event?.kibana?.space_ids?.[0]).to.equal(spaceId);