Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[7.x] [actions] add rule saved object reference to action execution event log doc (#101526) #102994

Merged
merged 1 commit into from
Jun 22, 2021
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
64 changes: 64 additions & 0 deletions x-pack/plugins/actions/server/actions_client.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1676,6 +1676,70 @@ describe('execute()', () => {
name: 'my name',
},
});

await expect(
actionsClient.execute({
actionId,
params: {
name: 'my name',
},
relatedSavedObjects: [
{
id: 'some-id',
typeId: 'some-type-id',
type: 'some-type',
},
],
})
).resolves.toMatchObject({ status: 'ok', actionId });

expect(actionExecutor.execute).toHaveBeenCalledWith({
actionId,
request,
params: {
name: 'my name',
},
relatedSavedObjects: [
{
id: 'some-id',
typeId: 'some-type-id',
type: 'some-type',
},
],
});

await expect(
actionsClient.execute({
actionId,
params: {
name: 'my name',
},
relatedSavedObjects: [
{
id: 'some-id',
typeId: 'some-type-id',
type: 'some-type',
namespace: 'some-namespace',
},
],
})
).resolves.toMatchObject({ status: 'ok', actionId });

expect(actionExecutor.execute).toHaveBeenCalledWith({
actionId,
request,
params: {
name: 'my name',
},
relatedSavedObjects: [
{
id: 'some-id',
typeId: 'some-type-id',
type: 'some-type',
namespace: 'some-namespace',
},
],
});
});
});

Expand Down
9 changes: 8 additions & 1 deletion x-pack/plugins/actions/server/actions_client.ts
Original file line number Diff line number Diff line change
Expand Up @@ -469,14 +469,21 @@ export class ActionsClient {
actionId,
params,
source,
relatedSavedObjects,
}: Omit<ExecuteOptions, 'request'>): Promise<ActionTypeExecutorResult<unknown>> {
if (
(await getAuthorizationModeBySource(this.unsecuredSavedObjectsClient, source)) ===
AuthorizationMode.RBAC
) {
await this.authorization.ensureAuthorized('execute');
}
return this.actionExecutor.execute({ actionId, params, source, request: this.request });
return this.actionExecutor.execute({
actionId,
params,
source,
request: this.request,
relatedSavedObjects,
});
}

public async enqueueExecution(options: EnqueueExecutionOptions): Promise<void> {
Expand Down
56 changes: 56 additions & 0 deletions x-pack/plugins/actions/server/create_execute_function.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -83,6 +83,62 @@ describe('execute()', () => {
});
});

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,
Expand Down
5 changes: 4 additions & 1 deletion x-pack/plugins/actions/server/create_execute_function.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ import { RawAction, ActionTypeRegistryContract, PreConfiguredAction } from './ty
import { ACTION_TASK_PARAMS_SAVED_OBJECT_TYPE } from './constants/saved_objects';
import { ExecuteOptions as ActionExecutorOptions } from './lib/action_executor';
import { isSavedObjectExecutionSource } from './lib';
import { RelatedSavedObjects } from './lib/related_saved_objects';

interface CreateExecuteFunctionOptions {
taskManager: TaskManagerStartContract;
Expand All @@ -23,6 +24,7 @@ export interface ExecuteOptions extends Pick<ActionExecutorOptions, 'params' | '
id: string;
spaceId: string;
apiKey: string | null;
relatedSavedObjects?: RelatedSavedObjects;
}

export type ExecutionEnqueuer = (
Expand All @@ -38,7 +40,7 @@ export function createExecutionEnqueuerFunction({
}: CreateExecuteFunctionOptions) {
return async function execute(
unsecuredSavedObjectsClient: SavedObjectsClientContract,
{ id, params, spaceId, source, apiKey }: ExecuteOptions
{ id, params, spaceId, source, apiKey, relatedSavedObjects }: ExecuteOptions
) {
if (!isESOCanEncrypt) {
throw new Error(
Expand Down Expand Up @@ -68,6 +70,7 @@ export function createExecutionEnqueuerFunction({
actionId: id,
params,
apiKey,
relatedSavedObjects,
},
executionSourceAsSavedObjectReferences(source)
);
Expand Down
13 changes: 13 additions & 0 deletions x-pack/plugins/actions/server/lib/action_executor.ts
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@ import { EVENT_LOG_ACTIONS } from '../constants/event_log';
import { IEvent, IEventLogger, SAVED_OBJECT_REL_PRIMARY } from '../../../event_log/server';
import { ActionsClient } from '../actions_client';
import { ActionExecutionSource } from './action_execution_source';
import { RelatedSavedObjects } from './related_saved_objects';

export interface ActionExecutorContext {
logger: Logger;
Expand All @@ -42,6 +43,7 @@ export interface ExecuteOptions<Source = unknown> {
request: KibanaRequest;
params: Record<string, unknown>;
source?: ActionExecutionSource<Source>;
relatedSavedObjects?: RelatedSavedObjects;
}

export type ActionExecutorContract = PublicMethodsOf<ActionExecutor>;
Expand All @@ -68,6 +70,7 @@ export class ActionExecutor {
params,
request,
source,
relatedSavedObjects,
}: ExecuteOptions): Promise<ActionTypeExecutorResult<unknown>> {
if (!this.isInitialized) {
throw new Error('ActionExecutor not initialized');
Expand Down Expand Up @@ -154,6 +157,16 @@ export class ActionExecutor {
},
};

for (const relatedSavedObject of relatedSavedObjects || []) {
event.kibana?.saved_objects?.push({
rel: SAVED_OBJECT_REL_PRIMARY,
type: relatedSavedObject.type,
id: relatedSavedObject.id,
type_id: relatedSavedObject.typeId,
namespace: relatedSavedObject.namespace,
});
}

eventLogger.startTiming(event);
let rawResult: ActionTypeExecutorResult<unknown>;
try {
Expand Down
86 changes: 86 additions & 0 deletions x-pack/plugins/actions/server/lib/related_saved_objects.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,86 @@
/*
* 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 { validatedRelatedSavedObjects } from './related_saved_objects';
import { loggingSystemMock } from '../../../../../src/core/server/mocks';
import { Logger } from '../../../../../src/core/server';

const loggerMock = loggingSystemMock.createLogger();

describe('related_saved_objects', () => {
beforeEach(() => {
jest.resetAllMocks();
});

it('validates valid objects', () => {
ensureValid(loggerMock, undefined);
ensureValid(loggerMock, []);
ensureValid(loggerMock, [
{
id: 'some-id',
type: 'some-type',
},
]);
ensureValid(loggerMock, [
{
id: 'some-id',
type: 'some-type',
typeId: 'some-type-id',
},
]);
ensureValid(loggerMock, [
{
id: 'some-id',
type: 'some-type',
namespace: 'some-namespace',
},
]);
ensureValid(loggerMock, [
{
id: 'some-id',
type: 'some-type',
typeId: 'some-type-id',
namespace: 'some-namespace',
},
]);
ensureValid(loggerMock, [
{
id: 'some-id',
type: 'some-type',
},
{
id: 'some-id-2',
type: 'some-type-2',
},
]);
});
});

it('handles invalid objects', () => {
ensureInvalid(loggerMock, 42);
ensureInvalid(loggerMock, {});
ensureInvalid(loggerMock, [{}]);
ensureInvalid(loggerMock, [{ id: 'some-id' }]);
ensureInvalid(loggerMock, [{ id: 42 }]);
ensureInvalid(loggerMock, [{ id: 'some-id', type: 'some-type', x: 42 }]);
});

function ensureValid(logger: Logger, savedObjects: unknown) {
const result = validatedRelatedSavedObjects(logger, savedObjects);
expect(result).toEqual(savedObjects === undefined ? [] : savedObjects);
expect(loggerMock.warn).not.toHaveBeenCalled();
}

function ensureInvalid(logger: Logger, savedObjects: unknown) {
const result = validatedRelatedSavedObjects(logger, savedObjects);
expect(result).toEqual([]);

const message = loggerMock.warn.mock.calls[0][0];
expect(message).toMatch(
/ignoring invalid related saved objects: expected value of type \[array\] but got/
);
}
31 changes: 31 additions & 0 deletions x-pack/plugins/actions/server/lib/related_saved_objects.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
/*
* 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 { schema, TypeOf } from '@kbn/config-schema';
import { Logger } from '../../../../../src/core/server';

export type RelatedSavedObjects = TypeOf<typeof RelatedSavedObjectsSchema>;

const RelatedSavedObjectsSchema = schema.arrayOf(
schema.object({
namespace: schema.maybe(schema.string({ minLength: 1 })),
id: schema.string({ minLength: 1 }),
type: schema.string({ minLength: 1 }),
// optional; for SO types like action/alert that have type id's
typeId: schema.maybe(schema.string({ minLength: 1 })),
}),
{ defaultValue: [] }
);

export function validatedRelatedSavedObjects(logger: Logger, data: unknown): RelatedSavedObjects {
try {
return RelatedSavedObjectsSchema.validate(data);
} catch (err) {
logger.warn(`ignoring invalid related saved objects: ${err.message}`);
return [];
}
}
Loading