resolves https://github.com/elastic/kibana/issues/99225 Prior to this PR, when an alerting connection action was executed, the event log document generated did not contain a reference to the originating rule. This makes it difficult to diagnose problems with connector errors, since the error is often in the parameters specified in the actions in the alert. In this PR, a reference to the alerting rule is added to the saved_objects field in the event document for these events.
338 lines
10 KiB
TypeScript
338 lines
10 KiB
TypeScript
/*
|
|
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
|
|
* or more contributor license agreements. Licensed under the Elastic License
|
|
* 2.0; you may not use this file except in compliance with the Elastic License
|
|
* 2.0.
|
|
*/
|
|
|
|
import { KibanaRequest } from 'src/core/server';
|
|
import uuid from 'uuid';
|
|
import { taskManagerMock } from '../../task_manager/server/mocks';
|
|
import { createExecutionEnqueuerFunction } from './create_execute_function';
|
|
import { savedObjectsClientMock } from '../../../../src/core/server/mocks';
|
|
import { actionTypeRegistryMock } from './action_type_registry.mock';
|
|
import {
|
|
asHttpRequestExecutionSource,
|
|
asSavedObjectExecutionSource,
|
|
} from './lib/action_execution_source';
|
|
|
|
const mockTaskManager = taskManagerMock.createStart();
|
|
const savedObjectsClient = savedObjectsClientMock.create();
|
|
const request = {} as KibanaRequest;
|
|
|
|
beforeEach(() => jest.resetAllMocks());
|
|
|
|
describe('execute()', () => {
|
|
test('schedules the action with all given parameters', async () => {
|
|
const actionTypeRegistry = actionTypeRegistryMock.create();
|
|
const executeFn = createExecutionEnqueuerFunction({
|
|
taskManager: mockTaskManager,
|
|
actionTypeRegistry,
|
|
isESOCanEncrypt: true,
|
|
preconfiguredActions: [],
|
|
});
|
|
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: asHttpRequestExecutionSource(request),
|
|
});
|
|
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",
|
|
},
|
|
]
|
|
`);
|
|
expect(savedObjectsClient.get).toHaveBeenCalledWith('action', '123');
|
|
expect(savedObjectsClient.create).toHaveBeenCalledWith(
|
|
'action_task_params',
|
|
{
|
|
actionId: '123',
|
|
params: { baz: false },
|
|
apiKey: Buffer.from('123:abc').toString('base64'),
|
|
},
|
|
{}
|
|
);
|
|
expect(actionTypeRegistry.isActionExecutable).toHaveBeenCalledWith('123', 'mock-action', {
|
|
notifyUsage: true,
|
|
});
|
|
});
|
|
|
|
test('schedules the action with all given parameters and relatedSavedObjects', async () => {
|
|
const actionTypeRegistry = actionTypeRegistryMock.create();
|
|
const executeFn = createExecutionEnqueuerFunction({
|
|
taskManager: mockTaskManager,
|
|
actionTypeRegistry,
|
|
isESOCanEncrypt: true,
|
|
preconfiguredActions: [],
|
|
});
|
|
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: asHttpRequestExecutionSource(request),
|
|
relatedSavedObjects: [
|
|
{
|
|
id: 'some-id',
|
|
namespace: 'some-namespace',
|
|
type: 'some-type',
|
|
typeId: 'some-typeId',
|
|
},
|
|
],
|
|
});
|
|
expect(savedObjectsClient.create).toHaveBeenCalledWith(
|
|
'action_task_params',
|
|
{
|
|
actionId: '123',
|
|
params: { baz: false },
|
|
apiKey: Buffer.from('123:abc').toString('base64'),
|
|
relatedSavedObjects: [
|
|
{
|
|
id: 'some-id',
|
|
namespace: 'some-namespace',
|
|
type: 'some-type',
|
|
typeId: 'some-typeId',
|
|
},
|
|
],
|
|
},
|
|
{}
|
|
);
|
|
});
|
|
|
|
test('schedules the action with all given parameters with a preconfigured action', 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),
|
|
});
|
|
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'),
|
|
},
|
|
{
|
|
references: [
|
|
{
|
|
id: source.id,
|
|
name: 'source',
|
|
type: source.type,
|
|
},
|
|
],
|
|
}
|
|
);
|
|
});
|
|
|
|
test('throws when passing isESOCanEncrypt with false as a value', async () => {
|
|
const executeFn = createExecutionEnqueuerFunction({
|
|
taskManager: mockTaskManager,
|
|
isESOCanEncrypt: false,
|
|
actionTypeRegistry: actionTypeRegistryMock.create(),
|
|
preconfiguredActions: [],
|
|
});
|
|
await expect(
|
|
executeFn(savedObjectsClient, {
|
|
id: '123',
|
|
params: { baz: false },
|
|
spaceId: 'default',
|
|
apiKey: null,
|
|
})
|
|
).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."`
|
|
);
|
|
});
|
|
|
|
test('throws when isMissingSecrets is true for connector', async () => {
|
|
const executeFn = createExecutionEnqueuerFunction({
|
|
taskManager: mockTaskManager,
|
|
isESOCanEncrypt: true,
|
|
actionTypeRegistry: actionTypeRegistryMock.create(),
|
|
preconfiguredActions: [],
|
|
});
|
|
savedObjectsClient.get.mockResolvedValueOnce({
|
|
id: '123',
|
|
type: 'action',
|
|
attributes: {
|
|
name: 'mock-action',
|
|
isMissingSecrets: true,
|
|
actionTypeId: 'mock-action',
|
|
},
|
|
references: [],
|
|
});
|
|
await expect(
|
|
executeFn(savedObjectsClient, {
|
|
id: '123',
|
|
params: { baz: false },
|
|
spaceId: 'default',
|
|
apiKey: null,
|
|
})
|
|
).rejects.toThrowErrorMatchingInlineSnapshot(
|
|
`"Unable to execute action because no secrets are defined for the \\"mock-action\\" connector."`
|
|
);
|
|
});
|
|
|
|
test('should ensure action type is enabled', async () => {
|
|
const mockedActionTypeRegistry = actionTypeRegistryMock.create();
|
|
const executeFn = createExecutionEnqueuerFunction({
|
|
taskManager: mockTaskManager,
|
|
isESOCanEncrypt: true,
|
|
actionTypeRegistry: mockedActionTypeRegistry,
|
|
preconfiguredActions: [],
|
|
});
|
|
mockedActionTypeRegistry.ensureActionTypeEnabled.mockImplementation(() => {
|
|
throw new Error('Fail');
|
|
});
|
|
savedObjectsClient.get.mockResolvedValueOnce({
|
|
id: '123',
|
|
type: 'action',
|
|
attributes: {
|
|
actionTypeId: 'mock-action',
|
|
},
|
|
references: [],
|
|
});
|
|
|
|
await expect(
|
|
executeFn(savedObjectsClient, {
|
|
id: '123',
|
|
params: { baz: false },
|
|
spaceId: 'default',
|
|
apiKey: null,
|
|
})
|
|
).rejects.toThrowErrorMatchingInlineSnapshot(`"Fail"`);
|
|
});
|
|
|
|
test('should skip ensure action type if action type is preconfigured and license is valid', async () => {
|
|
const mockedActionTypeRegistry = actionTypeRegistryMock.create();
|
|
const executeFn = createExecutionEnqueuerFunction({
|
|
taskManager: mockTaskManager,
|
|
isESOCanEncrypt: true,
|
|
actionTypeRegistry: mockedActionTypeRegistry,
|
|
preconfiguredActions: [
|
|
{
|
|
actionTypeId: 'mock-action',
|
|
config: {},
|
|
id: 'my-slack1',
|
|
name: 'Slack #xyz',
|
|
secrets: {},
|
|
isPreconfigured: true,
|
|
},
|
|
],
|
|
});
|
|
mockedActionTypeRegistry.isActionExecutable.mockImplementation(() => true);
|
|
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: null,
|
|
});
|
|
|
|
expect(mockedActionTypeRegistry.ensureActionTypeEnabled).not.toHaveBeenCalled();
|
|
});
|
|
});
|