Skip to content

Commit

Permalink
[ResponseOps][Alerting] Optimize the scheduling of rule actions so it…
Browse files Browse the repository at this point in the history
… can happen in bulk (#137781)

* Adding bulk scheduler

* Updating bulk schedule

* Fixing failing tests

* Fixing test

* Adding bulk schedule tests

* Cleaning up enqueue function

* Using bulk getConnectors

* Removing empty line

* Update x-pack/plugins/actions/server/create_execute_function.ts

Co-authored-by: Mike Côté <mikecote@users.noreply.github.com>

* Update x-pack/plugins/actions/server/create_execute_function.ts

Co-authored-by: Mike Côté <mikecote@users.noreply.github.com>

* Cleaning up auth

* Fixing test failure

* Updating bulk auth changes

* Fixed track change

* Fixing test failures

* Addressing pr comments

Co-authored-by: Kibana Machine <42973632+kibanamachine@users.noreply.github.com>
Co-authored-by: Mike Côté <mikecote@users.noreply.github.com>
  • Loading branch information
3 people authored Aug 12, 2022
1 parent c868daa commit 4ddc26d
Show file tree
Hide file tree
Showing 21 changed files with 1,535 additions and 164 deletions.
1 change: 1 addition & 0 deletions x-pack/plugins/actions/server/actions_client.mock.ts
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@ const createActionsClientMock = () => {
execute: jest.fn(),
enqueueExecution: jest.fn(),
ephemeralEnqueuedExecution: jest.fn(),
bulkEnqueueExecution: jest.fn(),
listTypes: jest.fn(),
isActionTypeEnabled: jest.fn(),
isPreconfigured: jest.fn(),
Expand Down
130 changes: 130 additions & 0 deletions x-pack/plugins/actions/server/actions_client.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,7 @@ import { ActionsAuthorization } from './authorization/actions_authorization';
import {
getAuthorizationModeBySource,
AuthorizationMode,
getBulkAuthorizationModeBySource,
} from './authorization/get_authorization_mode_by_source';
import { actionsAuthorizationMock } from './authorization/actions_authorization.mock';
import { trackLegacyRBACExemption } from './lib/track_legacy_rbac_exemption';
Expand Down Expand Up @@ -59,6 +60,9 @@ jest.mock('./authorization/get_authorization_mode_by_source', () => {
getAuthorizationModeBySource: jest.fn(() => {
return 1;
}),
getBulkAuthorizationModeBySource: jest.fn(() => {
return 1;
}),
AuthorizationMode: {
Legacy: 0,
RBAC: 1,
Expand All @@ -80,6 +84,7 @@ const actionExecutor = actionExecutorMock.create();
const authorization = actionsAuthorizationMock.create();
const executionEnqueuer = jest.fn();
const ephemeralExecutionEnqueuer = jest.fn();
const bulkExecutionEnqueuer = jest.fn();
const request = httpServerMock.createKibanaRequest();
const auditLogger = auditLoggerMock.create();
const mockUsageCountersSetup = usageCountersServiceMock.createSetupContract();
Expand Down Expand Up @@ -124,6 +129,7 @@ beforeEach(() => {
actionExecutor,
executionEnqueuer,
ephemeralExecutionEnqueuer,
bulkExecutionEnqueuer,
request,
authorization: authorization as unknown as ActionsAuthorization,
auditLogger,
Expand Down Expand Up @@ -550,6 +556,7 @@ describe('create()', () => {
actionExecutor,
executionEnqueuer,
ephemeralExecutionEnqueuer,
bulkExecutionEnqueuer,
request,
authorization: authorization as unknown as ActionsAuthorization,
connectorTokenClient: connectorTokenClientMock.create(),
Expand Down Expand Up @@ -655,6 +662,7 @@ describe('get()', () => {
actionExecutor,
executionEnqueuer,
ephemeralExecutionEnqueuer,
bulkExecutionEnqueuer,
request,
authorization: authorization as unknown as ActionsAuthorization,
preconfiguredActions: [
Expand Down Expand Up @@ -714,6 +722,7 @@ describe('get()', () => {
actionExecutor,
executionEnqueuer,
ephemeralExecutionEnqueuer,
bulkExecutionEnqueuer,
request,
authorization: authorization as unknown as ActionsAuthorization,
preconfiguredActions: [
Expand Down Expand Up @@ -835,6 +844,7 @@ describe('get()', () => {
actionExecutor,
executionEnqueuer,
ephemeralExecutionEnqueuer,
bulkExecutionEnqueuer,
request,
authorization: authorization as unknown as ActionsAuthorization,
preconfiguredActions: [
Expand Down Expand Up @@ -909,6 +919,7 @@ describe('getAll()', () => {
actionExecutor,
executionEnqueuer,
ephemeralExecutionEnqueuer,
bulkExecutionEnqueuer,
request,
authorization: authorization as unknown as ActionsAuthorization,
preconfiguredActions: [
Expand Down Expand Up @@ -1050,6 +1061,7 @@ describe('getAll()', () => {
actionExecutor,
executionEnqueuer,
ephemeralExecutionEnqueuer,
bulkExecutionEnqueuer,
request,
authorization: authorization as unknown as ActionsAuthorization,
preconfiguredActions: [
Expand Down Expand Up @@ -1131,6 +1143,7 @@ describe('getBulk()', () => {
actionExecutor,
executionEnqueuer,
ephemeralExecutionEnqueuer,
bulkExecutionEnqueuer,
request,
authorization: authorization as unknown as ActionsAuthorization,
preconfiguredActions: [
Expand Down Expand Up @@ -1266,6 +1279,7 @@ describe('getBulk()', () => {
actionExecutor,
executionEnqueuer,
ephemeralExecutionEnqueuer,
bulkExecutionEnqueuer,
request,
authorization: authorization as unknown as ActionsAuthorization,
preconfiguredActions: [
Expand Down Expand Up @@ -1324,6 +1338,7 @@ describe('getOAuthAccessToken()', () => {
actionExecutor,
executionEnqueuer,
ephemeralExecutionEnqueuer,
bulkExecutionEnqueuer,
request,
authorization: authorization as unknown as ActionsAuthorization,
preconfiguredActions: [
Expand Down Expand Up @@ -2326,6 +2341,119 @@ describe('enqueueExecution()', () => {
});
});

describe('bulkEnqueueExecution()', () => {
describe('authorization', () => {
test('ensures user is authorised to excecute actions', async () => {
(getBulkAuthorizationModeBySource as jest.Mock).mockImplementationOnce(() => {
return { [AuthorizationMode.RBAC]: 1, [AuthorizationMode.Legacy]: 0 };
});
await actionsClient.bulkEnqueueExecution([
{
id: uuid.v4(),
params: {},
spaceId: 'default',
executionId: '123abc',
apiKey: null,
},
{
id: uuid.v4(),
params: {},
spaceId: 'default',
executionId: '456def',
apiKey: null,
},
]);
expect(authorization.ensureAuthorized).toHaveBeenCalledWith('execute');
});

test('throws when user is not authorised to create the type of action', async () => {
(getBulkAuthorizationModeBySource as jest.Mock).mockImplementationOnce(() => {
return { [AuthorizationMode.RBAC]: 1, [AuthorizationMode.Legacy]: 0 };
});
authorization.ensureAuthorized.mockRejectedValue(
new Error(`Unauthorized to execute all actions`)
);

await expect(
actionsClient.bulkEnqueueExecution([
{
id: uuid.v4(),
params: {},
spaceId: 'default',
executionId: '123abc',
apiKey: null,
},
{
id: uuid.v4(),
params: {},
spaceId: 'default',
executionId: '456def',
apiKey: null,
},
])
).rejects.toMatchInlineSnapshot(`[Error: Unauthorized to execute all actions]`);

expect(authorization.ensureAuthorized).toHaveBeenCalledWith('execute');
});

test('tracks legacy RBAC', async () => {
(getBulkAuthorizationModeBySource as jest.Mock).mockImplementationOnce(() => {
return { [AuthorizationMode.RBAC]: 0, [AuthorizationMode.Legacy]: 2 };
});

await actionsClient.bulkEnqueueExecution([
{
id: uuid.v4(),
params: {},
spaceId: 'default',
executionId: '123abc',
apiKey: null,
},
{
id: uuid.v4(),
params: {},
spaceId: 'default',
executionId: '456def',
apiKey: null,
},
]);

expect(trackLegacyRBACExemption as jest.Mock).toBeCalledWith(
'bulkEnqueueExecution',
mockUsageCounter,
2
);
});
});

test('calls the bulkExecutionEnqueuer with the appropriate parameters', async () => {
(getBulkAuthorizationModeBySource as jest.Mock).mockImplementationOnce(() => {
return { [AuthorizationMode.RBAC]: 0, [AuthorizationMode.Legacy]: 0 };
});
const opts = [
{
id: uuid.v4(),
params: {},
spaceId: 'default',
executionId: '123abc',
apiKey: null,
},
{
id: uuid.v4(),
params: {},
spaceId: 'default',
executionId: '456def',
apiKey: null,
},
];
await expect(actionsClient.bulkEnqueueExecution(opts)).resolves.toMatchInlineSnapshot(
`undefined`
);

expect(bulkExecutionEnqueuer).toHaveBeenCalledWith(unsecuredSavedObjectsClient, opts);
});
});

describe('isActionTypeEnabled()', () => {
const fooActionType: ActionType = {
id: 'foo',
Expand Down Expand Up @@ -2366,6 +2494,7 @@ describe('isPreconfigured()', () => {
actionExecutor,
executionEnqueuer,
ephemeralExecutionEnqueuer,
bulkExecutionEnqueuer,
request,
authorization: authorization as unknown as ActionsAuthorization,
preconfiguredActions: [
Expand Down Expand Up @@ -2403,6 +2532,7 @@ describe('isPreconfigured()', () => {
actionExecutor,
executionEnqueuer,
ephemeralExecutionEnqueuer,
bulkExecutionEnqueuer,
request,
authorization: authorization as unknown as ActionsAuthorization,
preconfiguredActions: [
Expand Down
38 changes: 37 additions & 1 deletion x-pack/plugins/actions/server/actions_client.ts
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,13 @@ import { AuditLogger } from '@kbn/security-plugin/server';
import { RunNowResult } from '@kbn/task-manager-plugin/server';
import { ActionType } from '../common';
import { ActionTypeRegistry } from './action_type_registry';
import { validateConfig, validateSecrets, ActionExecutorContract, validateConnector } from './lib';
import {
validateConfig,
validateSecrets,
ActionExecutorContract,
validateConnector,
ActionExecutionSource,
} from './lib';
import {
ActionResult,
FindActionResult,
Expand All @@ -39,10 +45,12 @@ import { ExecuteOptions } from './lib/action_executor';
import {
ExecutionEnqueuer,
ExecuteOptions as EnqueueExecutionOptions,
BulkExecutionEnqueuer,
} from './create_execute_function';
import { ActionsAuthorization } from './authorization/actions_authorization';
import {
getAuthorizationModeBySource,
getBulkAuthorizationModeBySource,
AuthorizationMode,
} from './authorization/get_authorization_mode_by_source';
import { connectorAuditEvent, ConnectorAuditAction } from './lib/audit_events';
Expand Down Expand Up @@ -94,6 +102,7 @@ interface ConstructorOptions {
actionExecutor: ActionExecutorContract;
executionEnqueuer: ExecutionEnqueuer<void>;
ephemeralExecutionEnqueuer: ExecutionEnqueuer<RunNowResult>;
bulkExecutionEnqueuer: BulkExecutionEnqueuer<void>;
request: KibanaRequest;
authorization: ActionsAuthorization;
auditLogger?: AuditLogger;
Expand All @@ -118,6 +127,7 @@ export class ActionsClient {
private readonly authorization: ActionsAuthorization;
private readonly executionEnqueuer: ExecutionEnqueuer<void>;
private readonly ephemeralExecutionEnqueuer: ExecutionEnqueuer<RunNowResult>;
private readonly bulkExecutionEnqueuer: BulkExecutionEnqueuer<void>;
private readonly auditLogger?: AuditLogger;
private readonly usageCounter?: UsageCounter;
private readonly connectorTokenClient: ConnectorTokenClientContract;
Expand All @@ -132,6 +142,7 @@ export class ActionsClient {
actionExecutor,
executionEnqueuer,
ephemeralExecutionEnqueuer,
bulkExecutionEnqueuer,
request,
authorization,
auditLogger,
Expand All @@ -147,6 +158,7 @@ export class ActionsClient {
this.actionExecutor = actionExecutor;
this.executionEnqueuer = executionEnqueuer;
this.ephemeralExecutionEnqueuer = ephemeralExecutionEnqueuer;
this.bulkExecutionEnqueuer = bulkExecutionEnqueuer;
this.request = request;
this.authorization = authorization;
this.auditLogger = auditLogger;
Expand Down Expand Up @@ -656,6 +668,30 @@ export class ActionsClient {
return this.executionEnqueuer(this.unsecuredSavedObjectsClient, options);
}

public async bulkEnqueueExecution(options: EnqueueExecutionOptions[]): Promise<void> {
const sources: Array<ActionExecutionSource<unknown>> = [];
options.forEach((option) => {
if (option.source) {
sources.push(option.source);
}
});
const authCounts = await getBulkAuthorizationModeBySource(
this.unsecuredSavedObjectsClient,
sources
);
if (authCounts[AuthorizationMode.RBAC] > 0) {
await this.authorization.ensureAuthorized('execute');
}
if (authCounts[AuthorizationMode.Legacy] > 0) {
trackLegacyRBACExemption(
'bulkEnqueueExecution',
this.usageCounter,
authCounts[AuthorizationMode.Legacy]
);
}
return this.bulkExecutionEnqueuer(this.unsecuredSavedObjectsClient, options);
}

public async ephemeralEnqueuedExecution(options: EnqueueExecutionOptions): Promise<RunNowResult> {
const { source } = options;
if (
Expand Down
Loading

0 comments on commit 4ddc26d

Please sign in to comment.