Disable action plugin functionality when ESO plugin is using an ephemeral encryption key (#56906) (#57486)

* Disable actions client when ESO using generated key

* Add test for getActionsClientWithRequest

* Add other part to plugin.test.ts

* Cleanup tests a bit

* Cleanup tests

* plugin.test.ts cleanup

* Add warning logs on setup

Co-authored-by: Elastic Machine <elasticmachine@users.noreply.github.com>

Co-authored-by: Elastic Machine <elasticmachine@users.noreply.github.com>
This commit is contained in:
Mike Côté 2020-02-12 15:44:32 -05:00 committed by GitHub
parent 4e75d5b694
commit 3194cd0602
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
13 changed files with 294 additions and 15 deletions

View file

@ -10,6 +10,34 @@ import { licensingMock } from '../../../../plugins/licensing/server/mocks';
import { encryptedSavedObjectsMock } from '../../../../plugins/encrypted_saved_objects/server/mocks';
describe('Alerting Plugin', () => {
describe('setup()', () => {
it('should log warning when Encrypted Saved Objects plugin is using an ephemeral encryption key', async () => {
const context = coreMock.createPluginInitializerContext();
const plugin = new Plugin(context);
const coreSetup = coreMock.createSetup();
const encryptedSavedObjectsSetup = encryptedSavedObjectsMock.createSetup();
await plugin.setup(
{
...coreSetup,
http: {
...coreSetup.http,
route: jest.fn(),
},
} as any,
{
licensing: licensingMock.createSetup(),
encryptedSavedObjects: encryptedSavedObjectsSetup,
} as any
);
expect(encryptedSavedObjectsSetup.usingEphemeralEncryptionKey).toEqual(true);
expect(context.logger.get().warn).toHaveBeenCalledWith(
'APIs are disabled due to the Encrypted Saved Objects plugin using an ephemeral encryption key. Please set xpack.encryptedSavedObjects.encryptionKey in kibana.yml.'
);
});
});
describe('start()', () => {
/**
* HACK: This test has put together to ensuire the function "getAlertsClientWithRequest"

View file

@ -69,6 +69,12 @@ export class Plugin {
this.isESOUsingEphemeralEncryptionKey =
plugins.encryptedSavedObjects.usingEphemeralEncryptionKey;
if (this.isESOUsingEphemeralEncryptionKey) {
this.logger.warn(
'APIs are disabled due to the Encrypted Saved Objects plugin using an ephemeral encryption key. Please set xpack.encryptedSavedObjects.encryptionKey in kibana.yml.'
);
}
// Encrypted attributes
plugins.encryptedSavedObjects.registerType({
type: 'alert',

View file

@ -13,7 +13,9 @@ import { configUtilsMock } from './actions_config.mock';
const mockTaskManager = taskManagerMock.setup();
const actionTypeRegistryParams = {
taskManager: mockTaskManager,
taskRunnerFactory: new TaskRunnerFactory(new ActionExecutor()),
taskRunnerFactory: new TaskRunnerFactory(
new ActionExecutor({ isESOUsingEphemeralEncryptionKey: false })
),
actionsConfigUtils: configUtilsMock,
};

View file

@ -27,7 +27,9 @@ const mockTaskManager = taskManagerMock.setup();
const actionTypeRegistryParams = {
taskManager: mockTaskManager,
taskRunnerFactory: new TaskRunnerFactory(new ActionExecutor()),
taskRunnerFactory: new TaskRunnerFactory(
new ActionExecutor({ isESOUsingEphemeralEncryptionKey: false })
),
actionsConfigUtils: configUtilsMock,
};
@ -204,7 +206,9 @@ describe('create()', () => {
const localActionTypeRegistryParams = {
taskManager: mockTaskManager,
taskRunnerFactory: new TaskRunnerFactory(new ActionExecutor()),
taskRunnerFactory: new TaskRunnerFactory(
new ActionExecutor({ isESOUsingEphemeralEncryptionKey: false })
),
actionsConfigUtils: localConfigUtils,
};

View file

@ -21,7 +21,9 @@ export function createActionTypeRegistry(): {
const logger = loggingServiceMock.create().get() as jest.Mocked<Logger>;
const actionTypeRegistry = new ActionTypeRegistry({
taskManager: taskManagerMock.setup(),
taskRunnerFactory: new TaskRunnerFactory(new ActionExecutor()),
taskRunnerFactory: new TaskRunnerFactory(
new ActionExecutor({ isESOUsingEphemeralEncryptionKey: false })
),
actionsConfigUtils: configUtilsMock,
});
registerBuiltInActionTypes({

View file

@ -20,6 +20,7 @@ describe('execute()', () => {
getBasePath,
taskManager: mockTaskManager,
getScopedSavedObjectsClient: jest.fn().mockReturnValueOnce(savedObjectsClient),
isESOUsingEphemeralEncryptionKey: false,
});
savedObjectsClient.get.mockResolvedValueOnce({
id: '123',
@ -71,6 +72,7 @@ describe('execute()', () => {
getBasePath,
taskManager: mockTaskManager,
getScopedSavedObjectsClient,
isESOUsingEphemeralEncryptionKey: false,
});
savedObjectsClient.get.mockResolvedValueOnce({
id: '123',
@ -118,6 +120,7 @@ describe('execute()', () => {
getBasePath,
taskManager: mockTaskManager,
getScopedSavedObjectsClient,
isESOUsingEphemeralEncryptionKey: false,
});
savedObjectsClient.get.mockResolvedValueOnce({
id: '123',
@ -155,4 +158,24 @@ describe('execute()', () => {
},
});
});
test('throws when passing isESOUsingEphemeralEncryptionKey with true as a value', async () => {
const getScopedSavedObjectsClient = jest.fn().mockReturnValueOnce(savedObjectsClient);
const executeFn = createExecuteFunction({
getBasePath,
taskManager: mockTaskManager,
getScopedSavedObjectsClient,
isESOUsingEphemeralEncryptionKey: true,
});
await expect(
executeFn({
id: '123',
params: { baz: false },
spaceId: 'default',
apiKey: null,
})
).rejects.toThrowErrorMatchingInlineSnapshot(
`"Unable to execute action due to the Encrypted Saved Objects plugin using an ephemeral encryption key. Please set xpack.encryptedSavedObjects.encryptionKey in kibana.yml"`
);
});
});

View file

@ -12,6 +12,7 @@ interface CreateExecuteFunctionOptions {
taskManager: TaskManagerStartContract;
getScopedSavedObjectsClient: (request: any) => SavedObjectsClientContract;
getBasePath: GetBasePathFunction;
isESOUsingEphemeralEncryptionKey: boolean;
}
export interface ExecuteOptions {
@ -25,8 +26,15 @@ export function createExecuteFunction({
getBasePath,
taskManager,
getScopedSavedObjectsClient,
isESOUsingEphemeralEncryptionKey,
}: CreateExecuteFunctionOptions) {
return async function execute({ id, params, spaceId, apiKey }: ExecuteOptions) {
if (isESOUsingEphemeralEncryptionKey === true) {
throw new Error(
`Unable to execute action due to the Encrypted Saved Objects plugin using an ephemeral encryption key. Please set xpack.encryptedSavedObjects.encryptionKey in kibana.yml`
);
}
const requestHeaders: Record<string, string> = {};
if (apiKey) {

View file

@ -7,9 +7,11 @@
import { PluginInitializerContext } from '../../../../src/core/server';
import { ActionsPlugin } from './plugin';
import { configSchema } from './config';
import { ActionsClient as ActionsClientClass } from './actions_client';
export type ActionsClient = PublicMethodsOf<ActionsClientClass>;
export { ActionsPlugin, ActionResult, ActionTypeExecutorOptions, ActionType } from './types';
export { ActionsClient } from './actions_client';
export { PluginSetupContract, PluginStartContract } from './plugin';
export const plugin = (initContext: PluginInitializerContext) => new ActionsPlugin(initContext);

View file

@ -11,8 +11,9 @@ import { actionTypeRegistryMock } from '../action_type_registry.mock';
import { encryptedSavedObjectsMock } from '../../../encrypted_saved_objects/server/mocks';
import { savedObjectsClientMock, loggingServiceMock } from '../../../../../src/core/server/mocks';
import { eventLoggerMock } from '../../../event_log/server/mocks';
import { spacesServiceMock } from '../../../spaces/server/spaces_service/spaces_service.mock';
const actionExecutor = new ActionExecutor();
const actionExecutor = new ActionExecutor({ isESOUsingEphemeralEncryptionKey: false });
const savedObjectsClient = savedObjectsClientMock.create();
function getServices() {
@ -33,18 +34,20 @@ const executeParams = {
request: {} as KibanaRequest,
};
const spacesMock = spacesServiceMock.createSetupContract();
actionExecutor.initialize({
logger: loggingServiceMock.create().get(),
spaces: {
getSpaceId: () => 'some-namespace',
} as any,
spaces: spacesMock,
getServices,
actionTypeRegistry,
encryptedSavedObjectsPlugin,
eventLogger: eventLoggerMock.create(),
});
beforeEach(() => jest.resetAllMocks());
beforeEach(() => {
jest.resetAllMocks();
spacesMock.getSpaceId.mockReturnValue('some-namespace');
});
test('successfully executes', async () => {
const actionType = {
@ -219,3 +222,20 @@ test('returns an error if actionType is not enabled', async () => {
}
`);
});
test('throws an error when passing isESOUsingEphemeralEncryptionKey with value of true', async () => {
const customActionExecutor = new ActionExecutor({ isESOUsingEphemeralEncryptionKey: true });
customActionExecutor.initialize({
logger: loggingServiceMock.create().get(),
spaces: spacesMock,
getServices,
actionTypeRegistry,
encryptedSavedObjectsPlugin,
eventLogger: eventLoggerMock.create(),
});
await expect(
customActionExecutor.execute(executeParams)
).rejects.toThrowErrorMatchingInlineSnapshot(
`"Unable to execute action due to the Encrypted Saved Objects plugin using an ephemeral encryption key. Please set xpack.encryptedSavedObjects.encryptionKey in kibana.yml"`
);
});

View file

@ -37,6 +37,11 @@ export type ActionExecutorContract = PublicMethodsOf<ActionExecutor>;
export class ActionExecutor {
private isInitialized = false;
private actionExecutorContext?: ActionExecutorContext;
private readonly isESOUsingEphemeralEncryptionKey: boolean;
constructor({ isESOUsingEphemeralEncryptionKey }: { isESOUsingEphemeralEncryptionKey: boolean }) {
this.isESOUsingEphemeralEncryptionKey = isESOUsingEphemeralEncryptionKey;
}
public initialize(actionExecutorContext: ActionExecutorContext) {
if (this.isInitialized) {
@ -55,6 +60,12 @@ export class ActionExecutor {
throw new Error('ActionExecutor not initialized');
}
if (this.isESOUsingEphemeralEncryptionKey === true) {
throw new Error(
`Unable to execute action due to the Encrypted Saved Objects plugin using an ephemeral encryption key. Please set xpack.encryptedSavedObjects.encryptionKey in kibana.yml`
);
}
const {
spaces,
getServices,

View file

@ -78,14 +78,18 @@ beforeEach(() => {
});
test(`throws an error if factory isn't initialized`, () => {
const factory = new TaskRunnerFactory(new ActionExecutor());
const factory = new TaskRunnerFactory(
new ActionExecutor({ isESOUsingEphemeralEncryptionKey: false })
);
expect(() =>
factory.create({ taskInstance: mockedTaskInstance })
).toThrowErrorMatchingInlineSnapshot(`"TaskRunnerFactory not initialized"`);
});
test(`throws an error if factory is already initialized`, () => {
const factory = new TaskRunnerFactory(new ActionExecutor());
const factory = new TaskRunnerFactory(
new ActionExecutor({ isESOUsingEphemeralEncryptionKey: false })
);
factory.initialize(taskRunnerFactoryInitializerParams);
expect(() =>
factory.initialize(taskRunnerFactoryInitializerParams)

View file

@ -0,0 +1,145 @@
/*
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
* or more contributor license agreements. Licensed under the Elastic License;
* you may not use this file except in compliance with the Elastic License.
*/
import { ActionsPlugin, ActionsPluginsSetup, ActionsPluginsStart } from './plugin';
import { PluginInitializerContext } from '../../../../src/core/server';
import { coreMock, httpServerMock } from '../../../../src/core/server/mocks';
import { licensingMock } from '../../licensing/server/mocks';
import { encryptedSavedObjectsMock } from '../../encrypted_saved_objects/server/mocks';
import { taskManagerMock } from '../../task_manager/server/mocks';
import { eventLogMock } from '../../event_log/server/mocks';
describe('Actions Plugin', () => {
describe('setup()', () => {
let context: PluginInitializerContext;
let plugin: ActionsPlugin;
let coreSetup: ReturnType<typeof coreMock.createSetup>;
let pluginsSetup: jest.Mocked<ActionsPluginsSetup>;
beforeEach(() => {
context = coreMock.createPluginInitializerContext();
plugin = new ActionsPlugin(context);
coreSetup = coreMock.createSetup();
pluginsSetup = {
taskManager: taskManagerMock.createSetup(),
encryptedSavedObjects: encryptedSavedObjectsMock.createSetup(),
licensing: licensingMock.createSetup(),
event_log: eventLogMock.createSetup(),
};
});
it('should log warning when Encrypted Saved Objects plugin is using an ephemeral encryption key', async () => {
await plugin.setup(coreSetup, pluginsSetup);
expect(pluginsSetup.encryptedSavedObjects.usingEphemeralEncryptionKey).toEqual(true);
expect(context.logger.get().warn).toHaveBeenCalledWith(
'APIs are disabled due to the Encrypted Saved Objects plugin using an ephemeral encryption key. Please set xpack.encryptedSavedObjects.encryptionKey in kibana.yml.'
);
});
describe('routeHandlerContext.getActionsClient()', () => {
it('should not throw error when ESO plugin not using a generated key', async () => {
await plugin.setup(coreSetup, {
...pluginsSetup,
encryptedSavedObjects: {
...pluginsSetup.encryptedSavedObjects,
usingEphemeralEncryptionKey: false,
},
});
expect(coreSetup.http.registerRouteHandlerContext).toHaveBeenCalledTimes(1);
const handler = coreSetup.http.registerRouteHandlerContext.mock.calls[0];
expect(handler[0]).toEqual('actions');
const actionsContextHandler = (await handler[1](
{
core: {
savedObjects: {
client: {},
},
},
} as any,
httpServerMock.createKibanaRequest(),
httpServerMock.createResponseFactory()
)) as any;
actionsContextHandler.getActionsClient();
});
it('should throw error when ESO plugin using a generated key', async () => {
await plugin.setup(coreSetup, pluginsSetup);
expect(coreSetup.http.registerRouteHandlerContext).toHaveBeenCalledTimes(1);
const handler = coreSetup.http.registerRouteHandlerContext.mock.calls[0];
expect(handler[0]).toEqual('actions');
const actionsContextHandler = (await handler[1](
{
core: {
savedObjects: {
client: {},
},
},
} as any,
httpServerMock.createKibanaRequest(),
httpServerMock.createResponseFactory()
)) as any;
expect(() => actionsContextHandler.getActionsClient()).toThrowErrorMatchingInlineSnapshot(
`"Unable to create actions client due to the Encrypted Saved Objects plugin using an ephemeral encryption key. Please set xpack.encryptedSavedObjects.encryptionKey in kibana.yml"`
);
});
});
});
describe('start()', () => {
let plugin: ActionsPlugin;
let coreSetup: ReturnType<typeof coreMock.createSetup>;
let coreStart: ReturnType<typeof coreMock.createStart>;
let pluginsSetup: jest.Mocked<ActionsPluginsSetup>;
let pluginsStart: jest.Mocked<ActionsPluginsStart>;
beforeEach(() => {
const context = coreMock.createPluginInitializerContext();
plugin = new ActionsPlugin(context);
coreSetup = coreMock.createSetup();
coreStart = coreMock.createStart();
pluginsSetup = {
taskManager: taskManagerMock.createSetup(),
encryptedSavedObjects: encryptedSavedObjectsMock.createSetup(),
licensing: licensingMock.createSetup(),
event_log: eventLogMock.createSetup(),
};
pluginsStart = {
taskManager: taskManagerMock.createStart(),
encryptedSavedObjects: encryptedSavedObjectsMock.createStart(),
};
});
describe('getActionsClientWithRequest()', () => {
it('should not throw error when ESO plugin not using a generated key', async () => {
await plugin.setup(coreSetup, {
...pluginsSetup,
encryptedSavedObjects: {
...pluginsSetup.encryptedSavedObjects,
usingEphemeralEncryptionKey: false,
},
});
const pluginStart = plugin.start(coreStart, pluginsStart);
await pluginStart.getActionsClientWithRequest(httpServerMock.createKibanaRequest());
});
it('should throw error when ESO plugin using generated key', async () => {
await plugin.setup(coreSetup, pluginsSetup);
const pluginStart = plugin.start(coreStart, pluginsStart);
expect(pluginsSetup.encryptedSavedObjects.usingEphemeralEncryptionKey).toEqual(true);
await expect(
pluginStart.getActionsClientWithRequest(httpServerMock.createKibanaRequest())
).rejects.toThrowErrorMatchingInlineSnapshot(
`"Unable to create actions client due to the Encrypted Saved Objects plugin using an ephemeral encryption key. Please set xpack.encryptedSavedObjects.encryptionKey in kibana.yml"`
);
});
});
});
});

View file

@ -62,7 +62,7 @@ export interface PluginSetupContract {
export interface PluginStartContract {
execute(options: ExecuteOptions): Promise<void>;
getActionsClientWithRequest(request: KibanaRequest): Promise<ActionsClient>;
getActionsClientWithRequest(request: KibanaRequest): Promise<PublicMethodsOf<ActionsClient>>;
}
export interface ActionsPluginsSetup {
@ -90,6 +90,7 @@ export class ActionsPlugin implements Plugin<Promise<PluginSetupContract>, Plugi
private licenseState: LicenseState | null = null;
private spaces?: SpacesServiceSetup;
private eventLogger?: IEventLogger;
private isESOUsingEphemeralEncryptionKey?: boolean;
constructor(initContext: PluginInitializerContext) {
this.config = initContext.config
@ -108,6 +109,15 @@ export class ActionsPlugin implements Plugin<Promise<PluginSetupContract>, Plugi
}
public async setup(core: CoreSetup, plugins: ActionsPluginsSetup): Promise<PluginSetupContract> {
this.isESOUsingEphemeralEncryptionKey =
plugins.encryptedSavedObjects.usingEphemeralEncryptionKey;
if (this.isESOUsingEphemeralEncryptionKey) {
this.logger.warn(
'APIs are disabled due to the Encrypted Saved Objects plugin using an ephemeral encryption key. Please set xpack.encryptedSavedObjects.encryptionKey in kibana.yml.'
);
}
// Encrypted attributes
// - `secrets` properties will be encrypted
// - `config` will be included in AAD
@ -127,7 +137,9 @@ export class ActionsPlugin implements Plugin<Promise<PluginSetupContract>, Plugi
event: { provider: EVENT_LOG_PROVIDER },
});
const actionExecutor = new ActionExecutor();
const actionExecutor = new ActionExecutor({
isESOUsingEphemeralEncryptionKey: this.isESOUsingEphemeralEncryptionKey,
});
const taskRunnerFactory = new TaskRunnerFactory(actionExecutor);
const actionsConfigUtils = getActionsConfigurationUtilities(
(await this.config) as ActionsConfig
@ -179,6 +191,7 @@ export class ActionsPlugin implements Plugin<Promise<PluginSetupContract>, Plugi
taskRunnerFactory,
kibanaIndex,
adminClient,
isESOUsingEphemeralEncryptionKey,
} = this;
actionExecutor!.initialize({
@ -203,9 +216,15 @@ export class ActionsPlugin implements Plugin<Promise<PluginSetupContract>, Plugi
taskManager: plugins.taskManager,
getScopedSavedObjectsClient: core.savedObjects.getScopedClient,
getBasePath: this.getBasePath,
isESOUsingEphemeralEncryptionKey: isESOUsingEphemeralEncryptionKey!,
}),
// Ability to get an actions client from legacy code
async getActionsClientWithRequest(request: KibanaRequest) {
if (isESOUsingEphemeralEncryptionKey === true) {
throw new Error(
`Unable to create actions client due to the Encrypted Saved Objects plugin using an ephemeral encryption key. Please set xpack.encryptedSavedObjects.encryptionKey in kibana.yml`
);
}
return new ActionsClient({
savedObjectsClient: core.savedObjects.getScopedClient(request),
actionTypeRegistry: actionTypeRegistry!,
@ -229,10 +248,15 @@ export class ActionsPlugin implements Plugin<Promise<PluginSetupContract>, Plugi
private createRouteHandlerContext = (
defaultKibanaIndex: string
): IContextProvider<RequestHandler<any, any, any>, 'actions'> => {
const { actionTypeRegistry, adminClient } = this;
const { actionTypeRegistry, adminClient, isESOUsingEphemeralEncryptionKey } = this;
return async function actionsRouteHandlerContext(context, request) {
return {
getActionsClient: () => {
if (isESOUsingEphemeralEncryptionKey === true) {
throw new Error(
`Unable to create actions client due to the Encrypted Saved Objects plugin using an ephemeral encryption key. Please set xpack.encryptedSavedObjects.encryptionKey in kibana.yml`
);
}
return new ActionsClient({
savedObjectsClient: context.core!.savedObjects.client,
actionTypeRegistry: actionTypeRegistry!,