Do not generate an ephemeral encryption key in production. (#81511)
This commit is contained in:
parent
634c0b3424
commit
03a53b9f39
30 changed files with 543 additions and 238 deletions
|
@ -26,9 +26,7 @@ beforeEach(() => {
|
||||||
actionTypeRegistryParams = {
|
actionTypeRegistryParams = {
|
||||||
licensing: licensingMock.createSetup(),
|
licensing: licensingMock.createSetup(),
|
||||||
taskManager: mockTaskManager,
|
taskManager: mockTaskManager,
|
||||||
taskRunnerFactory: new TaskRunnerFactory(
|
taskRunnerFactory: new TaskRunnerFactory(new ActionExecutor({ isESOCanEncrypt: true })),
|
||||||
new ActionExecutor({ isESOUsingEphemeralEncryptionKey: false })
|
|
||||||
),
|
|
||||||
actionsConfigUtils: mockedActionsConfig,
|
actionsConfigUtils: mockedActionsConfig,
|
||||||
licenseState: mockedLicenseState,
|
licenseState: mockedLicenseState,
|
||||||
preconfiguredActions: [
|
preconfiguredActions: [
|
||||||
|
|
|
@ -59,9 +59,7 @@ beforeEach(() => {
|
||||||
actionTypeRegistryParams = {
|
actionTypeRegistryParams = {
|
||||||
licensing: licensingMock.createSetup(),
|
licensing: licensingMock.createSetup(),
|
||||||
taskManager: mockTaskManager,
|
taskManager: mockTaskManager,
|
||||||
taskRunnerFactory: new TaskRunnerFactory(
|
taskRunnerFactory: new TaskRunnerFactory(new ActionExecutor({ isESOCanEncrypt: true })),
|
||||||
new ActionExecutor({ isESOUsingEphemeralEncryptionKey: false })
|
|
||||||
),
|
|
||||||
actionsConfigUtils: actionsConfigMock.create(),
|
actionsConfigUtils: actionsConfigMock.create(),
|
||||||
licenseState: mockedLicenseState,
|
licenseState: mockedLicenseState,
|
||||||
preconfiguredActions: [],
|
preconfiguredActions: [],
|
||||||
|
@ -411,9 +409,7 @@ describe('create()', () => {
|
||||||
const localActionTypeRegistryParams = {
|
const localActionTypeRegistryParams = {
|
||||||
licensing: licensingMock.createSetup(),
|
licensing: licensingMock.createSetup(),
|
||||||
taskManager: mockTaskManager,
|
taskManager: mockTaskManager,
|
||||||
taskRunnerFactory: new TaskRunnerFactory(
|
taskRunnerFactory: new TaskRunnerFactory(new ActionExecutor({ isESOCanEncrypt: true })),
|
||||||
new ActionExecutor({ isESOUsingEphemeralEncryptionKey: false })
|
|
||||||
),
|
|
||||||
actionsConfigUtils: localConfigUtils,
|
actionsConfigUtils: localConfigUtils,
|
||||||
licenseState: licenseStateMock.create(),
|
licenseState: licenseStateMock.create(),
|
||||||
preconfiguredActions: [],
|
preconfiguredActions: [],
|
||||||
|
|
|
@ -33,9 +33,7 @@ export function createActionTypeRegistry(): {
|
||||||
const actionTypeRegistry = new ActionTypeRegistry({
|
const actionTypeRegistry = new ActionTypeRegistry({
|
||||||
taskManager: taskManagerMock.createSetup(),
|
taskManager: taskManagerMock.createSetup(),
|
||||||
licensing: licensingMock.createSetup(),
|
licensing: licensingMock.createSetup(),
|
||||||
taskRunnerFactory: new TaskRunnerFactory(
|
taskRunnerFactory: new TaskRunnerFactory(new ActionExecutor({ isESOCanEncrypt: true })),
|
||||||
new ActionExecutor({ isESOUsingEphemeralEncryptionKey: false })
|
|
||||||
),
|
|
||||||
actionsConfigUtils: actionsConfigMock.create(),
|
actionsConfigUtils: actionsConfigMock.create(),
|
||||||
licenseState: licenseStateMock.create(),
|
licenseState: licenseStateMock.create(),
|
||||||
preconfiguredActions: [],
|
preconfiguredActions: [],
|
||||||
|
|
|
@ -28,7 +28,7 @@ describe('execute()', () => {
|
||||||
const executeFn = createExecutionEnqueuerFunction({
|
const executeFn = createExecutionEnqueuerFunction({
|
||||||
taskManager: mockTaskManager,
|
taskManager: mockTaskManager,
|
||||||
actionTypeRegistry,
|
actionTypeRegistry,
|
||||||
isESOUsingEphemeralEncryptionKey: false,
|
isESOCanEncrypt: true,
|
||||||
preconfiguredActions: [],
|
preconfiguredActions: [],
|
||||||
});
|
});
|
||||||
savedObjectsClient.get.mockResolvedValueOnce({
|
savedObjectsClient.get.mockResolvedValueOnce({
|
||||||
|
@ -87,7 +87,7 @@ describe('execute()', () => {
|
||||||
const executeFn = createExecutionEnqueuerFunction({
|
const executeFn = createExecutionEnqueuerFunction({
|
||||||
taskManager: mockTaskManager,
|
taskManager: mockTaskManager,
|
||||||
actionTypeRegistry: actionTypeRegistryMock.create(),
|
actionTypeRegistry: actionTypeRegistryMock.create(),
|
||||||
isESOUsingEphemeralEncryptionKey: false,
|
isESOCanEncrypt: true,
|
||||||
preconfiguredActions: [
|
preconfiguredActions: [
|
||||||
{
|
{
|
||||||
id: '123',
|
id: '123',
|
||||||
|
@ -158,10 +158,10 @@ describe('execute()', () => {
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
|
||||||
test('throws when passing isESOUsingEphemeralEncryptionKey with true as a value', async () => {
|
test('throws when passing isESOCanEncrypt with false as a value', async () => {
|
||||||
const executeFn = createExecutionEnqueuerFunction({
|
const executeFn = createExecutionEnqueuerFunction({
|
||||||
taskManager: mockTaskManager,
|
taskManager: mockTaskManager,
|
||||||
isESOUsingEphemeralEncryptionKey: true,
|
isESOCanEncrypt: false,
|
||||||
actionTypeRegistry: actionTypeRegistryMock.create(),
|
actionTypeRegistry: actionTypeRegistryMock.create(),
|
||||||
preconfiguredActions: [],
|
preconfiguredActions: [],
|
||||||
});
|
});
|
||||||
|
@ -173,7 +173,7 @@ describe('execute()', () => {
|
||||||
apiKey: null,
|
apiKey: null,
|
||||||
})
|
})
|
||||||
).rejects.toThrowErrorMatchingInlineSnapshot(
|
).rejects.toThrowErrorMatchingInlineSnapshot(
|
||||||
`"Unable to execute action because the Encrypted Saved Objects plugin uses an ephemeral encryption key. Please set xpack.encryptedSavedObjects.encryptionKey in the kibana.yml or use the bin/kibana-encryption-keys command."`
|
`"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."`
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
@ -181,7 +181,7 @@ describe('execute()', () => {
|
||||||
const mockedActionTypeRegistry = actionTypeRegistryMock.create();
|
const mockedActionTypeRegistry = actionTypeRegistryMock.create();
|
||||||
const executeFn = createExecutionEnqueuerFunction({
|
const executeFn = createExecutionEnqueuerFunction({
|
||||||
taskManager: mockTaskManager,
|
taskManager: mockTaskManager,
|
||||||
isESOUsingEphemeralEncryptionKey: false,
|
isESOCanEncrypt: true,
|
||||||
actionTypeRegistry: mockedActionTypeRegistry,
|
actionTypeRegistry: mockedActionTypeRegistry,
|
||||||
preconfiguredActions: [],
|
preconfiguredActions: [],
|
||||||
});
|
});
|
||||||
|
@ -211,7 +211,7 @@ describe('execute()', () => {
|
||||||
const mockedActionTypeRegistry = actionTypeRegistryMock.create();
|
const mockedActionTypeRegistry = actionTypeRegistryMock.create();
|
||||||
const executeFn = createExecutionEnqueuerFunction({
|
const executeFn = createExecutionEnqueuerFunction({
|
||||||
taskManager: mockTaskManager,
|
taskManager: mockTaskManager,
|
||||||
isESOUsingEphemeralEncryptionKey: false,
|
isESOCanEncrypt: true,
|
||||||
actionTypeRegistry: mockedActionTypeRegistry,
|
actionTypeRegistry: mockedActionTypeRegistry,
|
||||||
preconfiguredActions: [
|
preconfiguredActions: [
|
||||||
{
|
{
|
||||||
|
|
|
@ -14,7 +14,7 @@ import { isSavedObjectExecutionSource } from './lib';
|
||||||
|
|
||||||
interface CreateExecuteFunctionOptions {
|
interface CreateExecuteFunctionOptions {
|
||||||
taskManager: TaskManagerStartContract;
|
taskManager: TaskManagerStartContract;
|
||||||
isESOUsingEphemeralEncryptionKey: boolean;
|
isESOCanEncrypt: boolean;
|
||||||
actionTypeRegistry: ActionTypeRegistryContract;
|
actionTypeRegistry: ActionTypeRegistryContract;
|
||||||
preconfiguredActions: PreConfiguredAction[];
|
preconfiguredActions: PreConfiguredAction[];
|
||||||
}
|
}
|
||||||
|
@ -33,16 +33,16 @@ export type ExecutionEnqueuer = (
|
||||||
export function createExecutionEnqueuerFunction({
|
export function createExecutionEnqueuerFunction({
|
||||||
taskManager,
|
taskManager,
|
||||||
actionTypeRegistry,
|
actionTypeRegistry,
|
||||||
isESOUsingEphemeralEncryptionKey,
|
isESOCanEncrypt,
|
||||||
preconfiguredActions,
|
preconfiguredActions,
|
||||||
}: CreateExecuteFunctionOptions) {
|
}: CreateExecuteFunctionOptions) {
|
||||||
return async function execute(
|
return async function execute(
|
||||||
unsecuredSavedObjectsClient: SavedObjectsClientContract,
|
unsecuredSavedObjectsClient: SavedObjectsClientContract,
|
||||||
{ id, params, spaceId, source, apiKey }: ExecuteOptions
|
{ id, params, spaceId, source, apiKey }: ExecuteOptions
|
||||||
) {
|
) {
|
||||||
if (isESOUsingEphemeralEncryptionKey === true) {
|
if (!isESOCanEncrypt) {
|
||||||
throw new Error(
|
throw new Error(
|
||||||
`Unable to execute action because the Encrypted Saved Objects plugin uses an ephemeral encryption key. Please set xpack.encryptedSavedObjects.encryptionKey in the kibana.yml or use the bin/kibana-encryption-keys command.`
|
`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.`
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -17,7 +17,7 @@ import { ActionType } from '../types';
|
||||||
import { actionsMock, actionsClientMock } from '../mocks';
|
import { actionsMock, actionsClientMock } from '../mocks';
|
||||||
import { pick } from 'lodash';
|
import { pick } from 'lodash';
|
||||||
|
|
||||||
const actionExecutor = new ActionExecutor({ isESOUsingEphemeralEncryptionKey: false });
|
const actionExecutor = new ActionExecutor({ isESOCanEncrypt: true });
|
||||||
const services = actionsMock.createServices();
|
const services = actionsMock.createServices();
|
||||||
|
|
||||||
const actionsClient = actionsClientMock.create();
|
const actionsClient = actionsClientMock.create();
|
||||||
|
@ -310,8 +310,8 @@ test('should not throws an error if actionType is preconfigured', async () => {
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
test('throws an error when passing isESOUsingEphemeralEncryptionKey with value of true', async () => {
|
test('throws an error when passing isESOCanEncrypt with value of false', async () => {
|
||||||
const customActionExecutor = new ActionExecutor({ isESOUsingEphemeralEncryptionKey: true });
|
const customActionExecutor = new ActionExecutor({ isESOCanEncrypt: false });
|
||||||
customActionExecutor.initialize({
|
customActionExecutor.initialize({
|
||||||
logger: loggingSystemMock.create().get(),
|
logger: loggingSystemMock.create().get(),
|
||||||
spaces: spacesMock,
|
spaces: spacesMock,
|
||||||
|
@ -325,7 +325,7 @@ test('throws an error when passing isESOUsingEphemeralEncryptionKey with value o
|
||||||
await expect(
|
await expect(
|
||||||
customActionExecutor.execute(executeParams)
|
customActionExecutor.execute(executeParams)
|
||||||
).rejects.toThrowErrorMatchingInlineSnapshot(
|
).rejects.toThrowErrorMatchingInlineSnapshot(
|
||||||
`"Unable to execute action because the Encrypted Saved Objects plugin uses an ephemeral encryption key. Please set xpack.encryptedSavedObjects.encryptionKey in the kibana.yml or use the bin/kibana-encryption-keys command."`
|
`"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."`
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
|
@ -48,10 +48,10 @@ export type ActionExecutorContract = PublicMethodsOf<ActionExecutor>;
|
||||||
export class ActionExecutor {
|
export class ActionExecutor {
|
||||||
private isInitialized = false;
|
private isInitialized = false;
|
||||||
private actionExecutorContext?: ActionExecutorContext;
|
private actionExecutorContext?: ActionExecutorContext;
|
||||||
private readonly isESOUsingEphemeralEncryptionKey: boolean;
|
private readonly isESOCanEncrypt: boolean;
|
||||||
|
|
||||||
constructor({ isESOUsingEphemeralEncryptionKey }: { isESOUsingEphemeralEncryptionKey: boolean }) {
|
constructor({ isESOCanEncrypt }: { isESOCanEncrypt: boolean }) {
|
||||||
this.isESOUsingEphemeralEncryptionKey = isESOUsingEphemeralEncryptionKey;
|
this.isESOCanEncrypt = isESOCanEncrypt;
|
||||||
}
|
}
|
||||||
|
|
||||||
public initialize(actionExecutorContext: ActionExecutorContext) {
|
public initialize(actionExecutorContext: ActionExecutorContext) {
|
||||||
|
@ -72,9 +72,9 @@ export class ActionExecutor {
|
||||||
throw new Error('ActionExecutor not initialized');
|
throw new Error('ActionExecutor not initialized');
|
||||||
}
|
}
|
||||||
|
|
||||||
if (this.isESOUsingEphemeralEncryptionKey === true) {
|
if (!this.isESOCanEncrypt) {
|
||||||
throw new Error(
|
throw new Error(
|
||||||
`Unable to execute action because the Encrypted Saved Objects plugin uses an ephemeral encryption key. Please set xpack.encryptedSavedObjects.encryptionKey in the kibana.yml or use the bin/kibana-encryption-keys command.`
|
`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.`
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -84,18 +84,14 @@ beforeEach(() => {
|
||||||
});
|
});
|
||||||
|
|
||||||
test(`throws an error if factory isn't initialized`, () => {
|
test(`throws an error if factory isn't initialized`, () => {
|
||||||
const factory = new TaskRunnerFactory(
|
const factory = new TaskRunnerFactory(new ActionExecutor({ isESOCanEncrypt: true }));
|
||||||
new ActionExecutor({ isESOUsingEphemeralEncryptionKey: false })
|
|
||||||
);
|
|
||||||
expect(() =>
|
expect(() =>
|
||||||
factory.create({ taskInstance: mockedTaskInstance })
|
factory.create({ taskInstance: mockedTaskInstance })
|
||||||
).toThrowErrorMatchingInlineSnapshot(`"TaskRunnerFactory not initialized"`);
|
).toThrowErrorMatchingInlineSnapshot(`"TaskRunnerFactory not initialized"`);
|
||||||
});
|
});
|
||||||
|
|
||||||
test(`throws an error if factory is already initialized`, () => {
|
test(`throws an error if factory is already initialized`, () => {
|
||||||
const factory = new TaskRunnerFactory(
|
const factory = new TaskRunnerFactory(new ActionExecutor({ isESOCanEncrypt: true }));
|
||||||
new ActionExecutor({ isESOUsingEphemeralEncryptionKey: false })
|
|
||||||
);
|
|
||||||
factory.initialize(taskRunnerFactoryInitializerParams);
|
factory.initialize(taskRunnerFactoryInitializerParams);
|
||||||
expect(() =>
|
expect(() =>
|
||||||
factory.initialize(taskRunnerFactoryInitializerParams)
|
factory.initialize(taskRunnerFactoryInitializerParams)
|
||||||
|
|
|
@ -51,25 +51,21 @@ describe('Actions Plugin', () => {
|
||||||
};
|
};
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should log warning when Encrypted Saved Objects plugin is using an ephemeral encryption key', async () => {
|
it('should log warning when Encrypted Saved Objects plugin is missing encryption key', async () => {
|
||||||
// coreMock.createSetup doesn't support Plugin generics
|
await plugin.setup(coreSetup, pluginsSetup);
|
||||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
expect(pluginsSetup.encryptedSavedObjects.canEncrypt).toEqual(false);
|
||||||
await plugin.setup(coreSetup as any, pluginsSetup);
|
|
||||||
expect(pluginsSetup.encryptedSavedObjects.usingEphemeralEncryptionKey).toEqual(true);
|
|
||||||
expect(context.logger.get().warn).toHaveBeenCalledWith(
|
expect(context.logger.get().warn).toHaveBeenCalledWith(
|
||||||
'APIs are disabled because the Encrypted Saved Objects plugin uses an ephemeral encryption key. Please set xpack.encryptedSavedObjects.encryptionKey in the kibana.yml or use the bin/kibana-encryption-keys command.'
|
'APIs are disabled 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.'
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
|
||||||
describe('routeHandlerContext.getActionsClient()', () => {
|
describe('routeHandlerContext.getActionsClient()', () => {
|
||||||
it('should not throw error when ESO plugin not using a generated key', async () => {
|
it('should not throw error when ESO plugin has encryption key', async () => {
|
||||||
// coreMock.createSetup doesn't support Plugin generics
|
await plugin.setup(coreSetup, {
|
||||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
||||||
await plugin.setup(coreSetup as any, {
|
|
||||||
...pluginsSetup,
|
...pluginsSetup,
|
||||||
encryptedSavedObjects: {
|
encryptedSavedObjects: {
|
||||||
...pluginsSetup.encryptedSavedObjects,
|
...pluginsSetup.encryptedSavedObjects,
|
||||||
usingEphemeralEncryptionKey: false,
|
canEncrypt: true,
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
|
@ -99,10 +95,8 @@ describe('Actions Plugin', () => {
|
||||||
actionsContextHandler!.getActionsClient();
|
actionsContextHandler!.getActionsClient();
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should throw error when ESO plugin using a generated key', async () => {
|
it('should throw error when ESO plugin is missing encryption key', async () => {
|
||||||
// coreMock.createSetup doesn't support Plugin generics
|
await plugin.setup(coreSetup, pluginsSetup);
|
||||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
||||||
await plugin.setup(coreSetup as any, pluginsSetup);
|
|
||||||
|
|
||||||
expect(coreSetup.http.registerRouteHandlerContext).toHaveBeenCalledTimes(1);
|
expect(coreSetup.http.registerRouteHandlerContext).toHaveBeenCalledTimes(1);
|
||||||
const handler = coreSetup.http.registerRouteHandlerContext.mock.calls[0] as [
|
const handler = coreSetup.http.registerRouteHandlerContext.mock.calls[0] as [
|
||||||
|
@ -123,7 +117,7 @@ describe('Actions Plugin', () => {
|
||||||
httpServerMock.createResponseFactory()
|
httpServerMock.createResponseFactory()
|
||||||
)) as unknown) as ActionsApiRequestHandlerContext;
|
)) as unknown) as ActionsApiRequestHandlerContext;
|
||||||
expect(() => actionsContextHandler!.getActionsClient()).toThrowErrorMatchingInlineSnapshot(
|
expect(() => actionsContextHandler!.getActionsClient()).toThrowErrorMatchingInlineSnapshot(
|
||||||
`"Unable to create actions client because the Encrypted Saved Objects plugin uses an ephemeral encryption key. Please set xpack.encryptedSavedObjects.encryptionKey in the kibana.yml or use the bin/kibana-encryption-keys command."`
|
`"Unable to create actions client 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."`
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
@ -234,14 +228,12 @@ describe('Actions Plugin', () => {
|
||||||
expect(pluginStart.isActionExecutable('preconfiguredServerLog', '.server-log')).toBe(true);
|
expect(pluginStart.isActionExecutable('preconfiguredServerLog', '.server-log')).toBe(true);
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should not throw error when ESO plugin not using a generated key', async () => {
|
it('should not throw error when ESO plugin has encryption key', async () => {
|
||||||
// coreMock.createSetup doesn't support Plugin generics
|
await plugin.setup(coreSetup, {
|
||||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
||||||
await plugin.setup(coreSetup as any, {
|
|
||||||
...pluginsSetup,
|
...pluginsSetup,
|
||||||
encryptedSavedObjects: {
|
encryptedSavedObjects: {
|
||||||
...pluginsSetup.encryptedSavedObjects,
|
...pluginsSetup.encryptedSavedObjects,
|
||||||
usingEphemeralEncryptionKey: false,
|
canEncrypt: true,
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
const pluginStart = await plugin.start(coreStart, pluginsStart);
|
const pluginStart = await plugin.start(coreStart, pluginsStart);
|
||||||
|
@ -249,17 +241,15 @@ describe('Actions Plugin', () => {
|
||||||
await pluginStart.getActionsClientWithRequest(httpServerMock.createKibanaRequest());
|
await pluginStart.getActionsClientWithRequest(httpServerMock.createKibanaRequest());
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should throw error when ESO plugin using generated key', async () => {
|
it('should throw error when ESO plugin is missing encryption key', async () => {
|
||||||
// coreMock.createSetup doesn't support Plugin generics
|
await plugin.setup(coreSetup, pluginsSetup);
|
||||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
||||||
await plugin.setup(coreSetup as any, pluginsSetup);
|
|
||||||
const pluginStart = await plugin.start(coreStart, pluginsStart);
|
const pluginStart = await plugin.start(coreStart, pluginsStart);
|
||||||
|
|
||||||
expect(pluginsSetup.encryptedSavedObjects.usingEphemeralEncryptionKey).toEqual(true);
|
expect(pluginsSetup.encryptedSavedObjects.canEncrypt).toEqual(false);
|
||||||
await expect(
|
await expect(
|
||||||
pluginStart.getActionsClientWithRequest(httpServerMock.createKibanaRequest())
|
pluginStart.getActionsClientWithRequest(httpServerMock.createKibanaRequest())
|
||||||
).rejects.toThrowErrorMatchingInlineSnapshot(
|
).rejects.toThrowErrorMatchingInlineSnapshot(
|
||||||
`"Unable to create actions client because the Encrypted Saved Objects plugin uses an ephemeral encryption key. Please set xpack.encryptedSavedObjects.encryptionKey in the kibana.yml or use the bin/kibana-encryption-keys command."`
|
`"Unable to create actions client 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."`
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
|
@ -144,7 +144,7 @@ export class ActionsPlugin implements Plugin<PluginSetupContract, PluginStartCon
|
||||||
private security?: SecurityPluginSetup;
|
private security?: SecurityPluginSetup;
|
||||||
private eventLogService?: IEventLogService;
|
private eventLogService?: IEventLogService;
|
||||||
private eventLogger?: IEventLogger;
|
private eventLogger?: IEventLogger;
|
||||||
private isESOUsingEphemeralEncryptionKey?: boolean;
|
private isESOCanEncrypt?: boolean;
|
||||||
private readonly telemetryLogger: Logger;
|
private readonly telemetryLogger: Logger;
|
||||||
private readonly preconfiguredActions: PreConfiguredAction[];
|
private readonly preconfiguredActions: PreConfiguredAction[];
|
||||||
private readonly kibanaIndexConfig: { kibana: { index: string } };
|
private readonly kibanaIndexConfig: { kibana: { index: string } };
|
||||||
|
@ -162,12 +162,11 @@ export class ActionsPlugin implements Plugin<PluginSetupContract, PluginStartCon
|
||||||
plugins: ActionsPluginsSetup
|
plugins: ActionsPluginsSetup
|
||||||
): PluginSetupContract {
|
): PluginSetupContract {
|
||||||
this.licenseState = new LicenseState(plugins.licensing.license$);
|
this.licenseState = new LicenseState(plugins.licensing.license$);
|
||||||
this.isESOUsingEphemeralEncryptionKey =
|
this.isESOCanEncrypt = plugins.encryptedSavedObjects.canEncrypt;
|
||||||
plugins.encryptedSavedObjects.usingEphemeralEncryptionKey;
|
|
||||||
|
|
||||||
if (this.isESOUsingEphemeralEncryptionKey) {
|
if (!this.isESOCanEncrypt) {
|
||||||
this.logger.warn(
|
this.logger.warn(
|
||||||
'APIs are disabled because the Encrypted Saved Objects plugin uses an ephemeral encryption key. Please set xpack.encryptedSavedObjects.encryptionKey in the kibana.yml or use the bin/kibana-encryption-keys command.'
|
'APIs are disabled 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.'
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -181,7 +180,7 @@ export class ActionsPlugin implements Plugin<PluginSetupContract, PluginStartCon
|
||||||
});
|
});
|
||||||
|
|
||||||
const actionExecutor = new ActionExecutor({
|
const actionExecutor = new ActionExecutor({
|
||||||
isESOUsingEphemeralEncryptionKey: this.isESOUsingEphemeralEncryptionKey,
|
isESOCanEncrypt: this.isESOCanEncrypt,
|
||||||
});
|
});
|
||||||
|
|
||||||
// get executions count
|
// get executions count
|
||||||
|
@ -270,7 +269,7 @@ export class ActionsPlugin implements Plugin<PluginSetupContract, PluginStartCon
|
||||||
actionTypeRegistry,
|
actionTypeRegistry,
|
||||||
taskRunnerFactory,
|
taskRunnerFactory,
|
||||||
kibanaIndexConfig,
|
kibanaIndexConfig,
|
||||||
isESOUsingEphemeralEncryptionKey,
|
isESOCanEncrypt,
|
||||||
preconfiguredActions,
|
preconfiguredActions,
|
||||||
instantiateAuthorization,
|
instantiateAuthorization,
|
||||||
getUnsecuredSavedObjectsClient,
|
getUnsecuredSavedObjectsClient,
|
||||||
|
@ -286,9 +285,9 @@ export class ActionsPlugin implements Plugin<PluginSetupContract, PluginStartCon
|
||||||
request: KibanaRequest,
|
request: KibanaRequest,
|
||||||
authorizationContext?: ActionExecutionSource<unknown>
|
authorizationContext?: ActionExecutionSource<unknown>
|
||||||
) => {
|
) => {
|
||||||
if (isESOUsingEphemeralEncryptionKey === true) {
|
if (isESOCanEncrypt !== true) {
|
||||||
throw new Error(
|
throw new Error(
|
||||||
`Unable to create actions client because the Encrypted Saved Objects plugin uses an ephemeral encryption key. Please set xpack.encryptedSavedObjects.encryptionKey in the kibana.yml or use the bin/kibana-encryption-keys command.`
|
`Unable to create actions client 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.`
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -314,7 +313,7 @@ export class ActionsPlugin implements Plugin<PluginSetupContract, PluginStartCon
|
||||||
executionEnqueuer: createExecutionEnqueuerFunction({
|
executionEnqueuer: createExecutionEnqueuerFunction({
|
||||||
taskManager: plugins.taskManager,
|
taskManager: plugins.taskManager,
|
||||||
actionTypeRegistry: actionTypeRegistry!,
|
actionTypeRegistry: actionTypeRegistry!,
|
||||||
isESOUsingEphemeralEncryptionKey: isESOUsingEphemeralEncryptionKey!,
|
isESOCanEncrypt: isESOCanEncrypt!,
|
||||||
preconfiguredActions,
|
preconfiguredActions,
|
||||||
}),
|
}),
|
||||||
auditLogger: this.security?.audit.asScoped(request),
|
auditLogger: this.security?.audit.asScoped(request),
|
||||||
|
@ -437,7 +436,7 @@ export class ActionsPlugin implements Plugin<PluginSetupContract, PluginStartCon
|
||||||
): IContextProvider<ActionsRequestHandlerContext, 'actions'> => {
|
): IContextProvider<ActionsRequestHandlerContext, 'actions'> => {
|
||||||
const {
|
const {
|
||||||
actionTypeRegistry,
|
actionTypeRegistry,
|
||||||
isESOUsingEphemeralEncryptionKey,
|
isESOCanEncrypt,
|
||||||
preconfiguredActions,
|
preconfiguredActions,
|
||||||
actionExecutor,
|
actionExecutor,
|
||||||
instantiateAuthorization,
|
instantiateAuthorization,
|
||||||
|
@ -448,9 +447,9 @@ export class ActionsPlugin implements Plugin<PluginSetupContract, PluginStartCon
|
||||||
const [{ savedObjects }, { taskManager }] = await core.getStartServices();
|
const [{ savedObjects }, { taskManager }] = await core.getStartServices();
|
||||||
return {
|
return {
|
||||||
getActionsClient: () => {
|
getActionsClient: () => {
|
||||||
if (isESOUsingEphemeralEncryptionKey === true) {
|
if (isESOCanEncrypt !== true) {
|
||||||
throw new Error(
|
throw new Error(
|
||||||
`Unable to create actions client because the Encrypted Saved Objects plugin uses an ephemeral encryption key. Please set xpack.encryptedSavedObjects.encryptionKey in the kibana.yml or use the bin/kibana-encryption-keys command.`
|
`Unable to create actions client 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.`
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
return new ActionsClient({
|
return new ActionsClient({
|
||||||
|
@ -468,7 +467,7 @@ export class ActionsPlugin implements Plugin<PluginSetupContract, PluginStartCon
|
||||||
executionEnqueuer: createExecutionEnqueuerFunction({
|
executionEnqueuer: createExecutionEnqueuerFunction({
|
||||||
taskManager,
|
taskManager,
|
||||||
actionTypeRegistry: actionTypeRegistry!,
|
actionTypeRegistry: actionTypeRegistry!,
|
||||||
isESOUsingEphemeralEncryptionKey: isESOUsingEphemeralEncryptionKey!,
|
isESOCanEncrypt: isESOCanEncrypt!,
|
||||||
preconfiguredActions,
|
preconfiguredActions,
|
||||||
}),
|
}),
|
||||||
auditLogger: security?.audit.asScoped(request),
|
auditLogger: security?.audit.asScoped(request),
|
||||||
|
|
|
@ -25,7 +25,7 @@ describe('Alerting Plugin', () => {
|
||||||
let coreSetup: ReturnType<typeof coreMock.createSetup>;
|
let coreSetup: ReturnType<typeof coreMock.createSetup>;
|
||||||
let pluginsSetup: jest.Mocked<AlertingPluginsSetup>;
|
let pluginsSetup: jest.Mocked<AlertingPluginsSetup>;
|
||||||
|
|
||||||
it('should log warning when Encrypted Saved Objects plugin is using an ephemeral encryption key', async () => {
|
it('should log warning when Encrypted Saved Objects plugin is missing encryption key', async () => {
|
||||||
const context = coreMock.createPluginInitializerContext<AlertsConfig>({
|
const context = coreMock.createPluginInitializerContext<AlertsConfig>({
|
||||||
healthCheck: {
|
healthCheck: {
|
||||||
interval: '5m',
|
interval: '5m',
|
||||||
|
@ -40,7 +40,7 @@ describe('Alerting Plugin', () => {
|
||||||
const encryptedSavedObjectsSetup = encryptedSavedObjectsMock.createSetup();
|
const encryptedSavedObjectsSetup = encryptedSavedObjectsMock.createSetup();
|
||||||
|
|
||||||
const setupMocks = coreMock.createSetup();
|
const setupMocks = coreMock.createSetup();
|
||||||
// need await to test number of calls of setupMocks.status.set, becuase it is under async function which awaiting core.getStartServices()
|
// need await to test number of calls of setupMocks.status.set, because it is under async function which awaiting core.getStartServices()
|
||||||
await plugin.setup(setupMocks, {
|
await plugin.setup(setupMocks, {
|
||||||
licensing: licensingMock.createSetup(),
|
licensing: licensingMock.createSetup(),
|
||||||
encryptedSavedObjects: encryptedSavedObjectsSetup,
|
encryptedSavedObjects: encryptedSavedObjectsSetup,
|
||||||
|
@ -51,9 +51,9 @@ describe('Alerting Plugin', () => {
|
||||||
});
|
});
|
||||||
|
|
||||||
expect(setupMocks.status.set).toHaveBeenCalledTimes(1);
|
expect(setupMocks.status.set).toHaveBeenCalledTimes(1);
|
||||||
expect(encryptedSavedObjectsSetup.usingEphemeralEncryptionKey).toEqual(true);
|
expect(encryptedSavedObjectsSetup.canEncrypt).toEqual(false);
|
||||||
expect(context.logger.get().warn).toHaveBeenCalledWith(
|
expect(context.logger.get().warn).toHaveBeenCalledWith(
|
||||||
'APIs are disabled because the Encrypted Saved Objects plugin uses an ephemeral encryption key. Please set xpack.encryptedSavedObjects.encryptionKey in the kibana.yml or use the bin/kibana-encryption-keys command.'
|
'APIs are disabled 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.'
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
@ -110,7 +110,7 @@ describe('Alerting Plugin', () => {
|
||||||
|
|
||||||
describe('start()', () => {
|
describe('start()', () => {
|
||||||
describe('getAlertsClientWithRequest()', () => {
|
describe('getAlertsClientWithRequest()', () => {
|
||||||
it('throws error when encryptedSavedObjects plugin has usingEphemeralEncryptionKey set to true', async () => {
|
it('throws error when encryptedSavedObjects plugin is missing encryption key', async () => {
|
||||||
const context = coreMock.createPluginInitializerContext<AlertsConfig>({
|
const context = coreMock.createPluginInitializerContext<AlertsConfig>({
|
||||||
healthCheck: {
|
healthCheck: {
|
||||||
interval: '5m',
|
interval: '5m',
|
||||||
|
@ -141,15 +141,15 @@ describe('Alerting Plugin', () => {
|
||||||
taskManager: taskManagerMock.createStart(),
|
taskManager: taskManagerMock.createStart(),
|
||||||
});
|
});
|
||||||
|
|
||||||
expect(encryptedSavedObjectsSetup.usingEphemeralEncryptionKey).toEqual(true);
|
expect(encryptedSavedObjectsSetup.canEncrypt).toEqual(false);
|
||||||
expect(() =>
|
expect(() =>
|
||||||
startContract.getAlertsClientWithRequest({} as KibanaRequest)
|
startContract.getAlertsClientWithRequest({} as KibanaRequest)
|
||||||
).toThrowErrorMatchingInlineSnapshot(
|
).toThrowErrorMatchingInlineSnapshot(
|
||||||
`"Unable to create alerts client because the Encrypted Saved Objects plugin uses an ephemeral encryption key. Please set xpack.encryptedSavedObjects.encryptionKey in the kibana.yml or use the bin/kibana-encryption-keys command."`
|
`"Unable to create alerts client 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."`
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
|
||||||
it(`doesn't throw error when encryptedSavedObjects plugin has usingEphemeralEncryptionKey set to false`, async () => {
|
it(`doesn't throw error when encryptedSavedObjects plugin has encryption key`, async () => {
|
||||||
const context = coreMock.createPluginInitializerContext<AlertsConfig>({
|
const context = coreMock.createPluginInitializerContext<AlertsConfig>({
|
||||||
healthCheck: {
|
healthCheck: {
|
||||||
interval: '5m',
|
interval: '5m',
|
||||||
|
@ -163,7 +163,7 @@ describe('Alerting Plugin', () => {
|
||||||
|
|
||||||
const encryptedSavedObjectsSetup = {
|
const encryptedSavedObjectsSetup = {
|
||||||
...encryptedSavedObjectsMock.createSetup(),
|
...encryptedSavedObjectsMock.createSetup(),
|
||||||
usingEphemeralEncryptionKey: false,
|
canEncrypt: true,
|
||||||
};
|
};
|
||||||
plugin.setup(coreMock.createSetup(), {
|
plugin.setup(coreMock.createSetup(), {
|
||||||
licensing: licensingMock.createSetup(),
|
licensing: licensingMock.createSetup(),
|
||||||
|
|
|
@ -153,7 +153,7 @@ export class AlertingPlugin {
|
||||||
private alertTypeRegistry?: AlertTypeRegistry;
|
private alertTypeRegistry?: AlertTypeRegistry;
|
||||||
private readonly taskRunnerFactory: TaskRunnerFactory;
|
private readonly taskRunnerFactory: TaskRunnerFactory;
|
||||||
private licenseState: ILicenseState | null = null;
|
private licenseState: ILicenseState | null = null;
|
||||||
private isESOUsingEphemeralEncryptionKey?: boolean;
|
private isESOCanEncrypt?: boolean;
|
||||||
private security?: SecurityPluginSetup;
|
private security?: SecurityPluginSetup;
|
||||||
private readonly alertsClientFactory: AlertsClientFactory;
|
private readonly alertsClientFactory: AlertsClientFactory;
|
||||||
private readonly telemetryLogger: Logger;
|
private readonly telemetryLogger: Logger;
|
||||||
|
@ -189,12 +189,11 @@ export class AlertingPlugin {
|
||||||
};
|
};
|
||||||
});
|
});
|
||||||
|
|
||||||
this.isESOUsingEphemeralEncryptionKey =
|
this.isESOCanEncrypt = plugins.encryptedSavedObjects.canEncrypt;
|
||||||
plugins.encryptedSavedObjects.usingEphemeralEncryptionKey;
|
|
||||||
|
|
||||||
if (this.isESOUsingEphemeralEncryptionKey) {
|
if (!this.isESOCanEncrypt) {
|
||||||
this.logger.warn(
|
this.logger.warn(
|
||||||
'APIs are disabled because the Encrypted Saved Objects plugin uses an ephemeral encryption key. Please set xpack.encryptedSavedObjects.encryptionKey in the kibana.yml or use the bin/kibana-encryption-keys command.'
|
'APIs are disabled 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.'
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -311,7 +310,7 @@ export class AlertingPlugin {
|
||||||
|
|
||||||
public start(core: CoreStart, plugins: AlertingPluginsStart): PluginStartContract {
|
public start(core: CoreStart, plugins: AlertingPluginsStart): PluginStartContract {
|
||||||
const {
|
const {
|
||||||
isESOUsingEphemeralEncryptionKey,
|
isESOCanEncrypt,
|
||||||
logger,
|
logger,
|
||||||
taskRunnerFactory,
|
taskRunnerFactory,
|
||||||
alertTypeRegistry,
|
alertTypeRegistry,
|
||||||
|
@ -353,9 +352,9 @@ export class AlertingPlugin {
|
||||||
});
|
});
|
||||||
|
|
||||||
const getAlertsClientWithRequest = (request: KibanaRequest) => {
|
const getAlertsClientWithRequest = (request: KibanaRequest) => {
|
||||||
if (isESOUsingEphemeralEncryptionKey === true) {
|
if (isESOCanEncrypt !== true) {
|
||||||
throw new Error(
|
throw new Error(
|
||||||
`Unable to create alerts client because the Encrypted Saved Objects plugin uses an ephemeral encryption key. Please set xpack.encryptedSavedObjects.encryptionKey in the kibana.yml or use the bin/kibana-encryption-keys command.`
|
`Unable to create alerts client 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.`
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
return alertsClientFactory!.create(request, core.savedObjects);
|
return alertsClientFactory!.create(request, core.savedObjects);
|
||||||
|
|
|
@ -47,8 +47,7 @@ describe('healthRoute', () => {
|
||||||
const router = httpServiceMock.createRouter();
|
const router = httpServiceMock.createRouter();
|
||||||
|
|
||||||
const licenseState = licenseStateMock.create();
|
const licenseState = licenseStateMock.create();
|
||||||
const encryptedSavedObjects = encryptedSavedObjectsMock.createSetup();
|
const encryptedSavedObjects = encryptedSavedObjectsMock.createSetup({ canEncrypt: true });
|
||||||
encryptedSavedObjects.usingEphemeralEncryptionKey = false;
|
|
||||||
healthRoute(router, licenseState, encryptedSavedObjects);
|
healthRoute(router, licenseState, encryptedSavedObjects);
|
||||||
|
|
||||||
const [config] = router.get.mock.calls[0];
|
const [config] = router.get.mock.calls[0];
|
||||||
|
@ -60,8 +59,7 @@ describe('healthRoute', () => {
|
||||||
const router = httpServiceMock.createRouter();
|
const router = httpServiceMock.createRouter();
|
||||||
|
|
||||||
const licenseState = licenseStateMock.create();
|
const licenseState = licenseStateMock.create();
|
||||||
const encryptedSavedObjects = encryptedSavedObjectsMock.createSetup();
|
const encryptedSavedObjects = encryptedSavedObjectsMock.createSetup({ canEncrypt: true });
|
||||||
encryptedSavedObjects.usingEphemeralEncryptionKey = false;
|
|
||||||
healthRoute(router, licenseState, encryptedSavedObjects);
|
healthRoute(router, licenseState, encryptedSavedObjects);
|
||||||
const [, handler] = router.get.mock.calls[0];
|
const [, handler] = router.get.mock.calls[0];
|
||||||
|
|
||||||
|
@ -85,12 +83,11 @@ describe('healthRoute', () => {
|
||||||
`);
|
`);
|
||||||
});
|
});
|
||||||
|
|
||||||
it('evaluates whether Encrypted Saved Objects is using an ephemeral encryption key', async () => {
|
it('evaluates whether Encrypted Saved Objects is missing encryption key', async () => {
|
||||||
const router = httpServiceMock.createRouter();
|
const router = httpServiceMock.createRouter();
|
||||||
|
|
||||||
const licenseState = licenseStateMock.create();
|
const licenseState = licenseStateMock.create();
|
||||||
const encryptedSavedObjects = encryptedSavedObjectsMock.createSetup();
|
const encryptedSavedObjects = encryptedSavedObjectsMock.createSetup({ canEncrypt: false });
|
||||||
encryptedSavedObjects.usingEphemeralEncryptionKey = true;
|
|
||||||
healthRoute(router, licenseState, encryptedSavedObjects);
|
healthRoute(router, licenseState, encryptedSavedObjects);
|
||||||
const [, handler] = router.get.mock.calls[0];
|
const [, handler] = router.get.mock.calls[0];
|
||||||
|
|
||||||
|
@ -129,8 +126,7 @@ describe('healthRoute', () => {
|
||||||
const router = httpServiceMock.createRouter();
|
const router = httpServiceMock.createRouter();
|
||||||
|
|
||||||
const licenseState = licenseStateMock.create();
|
const licenseState = licenseStateMock.create();
|
||||||
const encryptedSavedObjects = encryptedSavedObjectsMock.createSetup();
|
const encryptedSavedObjects = encryptedSavedObjectsMock.createSetup({ canEncrypt: true });
|
||||||
encryptedSavedObjects.usingEphemeralEncryptionKey = false;
|
|
||||||
healthRoute(router, licenseState, encryptedSavedObjects);
|
healthRoute(router, licenseState, encryptedSavedObjects);
|
||||||
const [, handler] = router.get.mock.calls[0];
|
const [, handler] = router.get.mock.calls[0];
|
||||||
|
|
||||||
|
@ -169,8 +165,7 @@ describe('healthRoute', () => {
|
||||||
const router = httpServiceMock.createRouter();
|
const router = httpServiceMock.createRouter();
|
||||||
|
|
||||||
const licenseState = licenseStateMock.create();
|
const licenseState = licenseStateMock.create();
|
||||||
const encryptedSavedObjects = encryptedSavedObjectsMock.createSetup();
|
const encryptedSavedObjects = encryptedSavedObjectsMock.createSetup({ canEncrypt: true });
|
||||||
encryptedSavedObjects.usingEphemeralEncryptionKey = false;
|
|
||||||
healthRoute(router, licenseState, encryptedSavedObjects);
|
healthRoute(router, licenseState, encryptedSavedObjects);
|
||||||
const [, handler] = router.get.mock.calls[0];
|
const [, handler] = router.get.mock.calls[0];
|
||||||
|
|
||||||
|
@ -209,8 +204,7 @@ describe('healthRoute', () => {
|
||||||
const router = httpServiceMock.createRouter();
|
const router = httpServiceMock.createRouter();
|
||||||
|
|
||||||
const licenseState = licenseStateMock.create();
|
const licenseState = licenseStateMock.create();
|
||||||
const encryptedSavedObjects = encryptedSavedObjectsMock.createSetup();
|
const encryptedSavedObjects = encryptedSavedObjectsMock.createSetup({ canEncrypt: true });
|
||||||
encryptedSavedObjects.usingEphemeralEncryptionKey = false;
|
|
||||||
healthRoute(router, licenseState, encryptedSavedObjects);
|
healthRoute(router, licenseState, encryptedSavedObjects);
|
||||||
const [, handler] = router.get.mock.calls[0];
|
const [, handler] = router.get.mock.calls[0];
|
||||||
|
|
||||||
|
@ -249,8 +243,7 @@ describe('healthRoute', () => {
|
||||||
const router = httpServiceMock.createRouter();
|
const router = httpServiceMock.createRouter();
|
||||||
|
|
||||||
const licenseState = licenseStateMock.create();
|
const licenseState = licenseStateMock.create();
|
||||||
const encryptedSavedObjects = encryptedSavedObjectsMock.createSetup();
|
const encryptedSavedObjects = encryptedSavedObjectsMock.createSetup({ canEncrypt: true });
|
||||||
encryptedSavedObjects.usingEphemeralEncryptionKey = false;
|
|
||||||
healthRoute(router, licenseState, encryptedSavedObjects);
|
healthRoute(router, licenseState, encryptedSavedObjects);
|
||||||
const [, handler] = router.get.mock.calls[0];
|
const [, handler] = router.get.mock.calls[0];
|
||||||
|
|
||||||
|
@ -291,8 +284,7 @@ describe('healthRoute', () => {
|
||||||
const router = httpServiceMock.createRouter();
|
const router = httpServiceMock.createRouter();
|
||||||
|
|
||||||
const licenseState = licenseStateMock.create();
|
const licenseState = licenseStateMock.create();
|
||||||
const encryptedSavedObjects = encryptedSavedObjectsMock.createSetup();
|
const encryptedSavedObjects = encryptedSavedObjectsMock.createSetup({ canEncrypt: true });
|
||||||
encryptedSavedObjects.usingEphemeralEncryptionKey = false;
|
|
||||||
healthRoute(router, licenseState, encryptedSavedObjects);
|
healthRoute(router, licenseState, encryptedSavedObjects);
|
||||||
const [, handler] = router.get.mock.calls[0];
|
const [, handler] = router.get.mock.calls[0];
|
||||||
|
|
||||||
|
|
|
@ -55,7 +55,7 @@ export function healthRoute(
|
||||||
|
|
||||||
const frameworkHealth: AlertingFrameworkHealth = {
|
const frameworkHealth: AlertingFrameworkHealth = {
|
||||||
isSufficientlySecure: !isSecurityEnabled || (isSecurityEnabled && isTLSEnabled),
|
isSufficientlySecure: !isSecurityEnabled || (isSecurityEnabled && isTLSEnabled),
|
||||||
hasPermanentEncryptionKey: !encryptedSavedObjects.usingEphemeralEncryptionKey,
|
hasPermanentEncryptionKey: encryptedSavedObjects.canEncrypt,
|
||||||
alertingFrameworkHeath,
|
alertingFrameworkHeath,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
|
@ -5,10 +5,7 @@
|
||||||
* 2.0.
|
* 2.0.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
jest.mock('crypto', () => ({ randomBytes: jest.fn() }));
|
import { ConfigSchema } from './config';
|
||||||
|
|
||||||
import { loggingSystemMock } from 'src/core/server/mocks';
|
|
||||||
import { createConfig, ConfigSchema } from './config';
|
|
||||||
|
|
||||||
describe('config schema', () => {
|
describe('config schema', () => {
|
||||||
it('generates proper defaults', () => {
|
it('generates proper defaults', () => {
|
||||||
|
@ -32,6 +29,17 @@ describe('config schema', () => {
|
||||||
}
|
}
|
||||||
`);
|
`);
|
||||||
|
|
||||||
|
expect(ConfigSchema.validate({ encryptionKey: 'z'.repeat(32) }, { dist: true }))
|
||||||
|
.toMatchInlineSnapshot(`
|
||||||
|
Object {
|
||||||
|
"enabled": true,
|
||||||
|
"encryptionKey": "zzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzz",
|
||||||
|
"keyRotation": Object {
|
||||||
|
"decryptionOnlyKeys": Array [],
|
||||||
|
},
|
||||||
|
}
|
||||||
|
`);
|
||||||
|
|
||||||
expect(ConfigSchema.validate({}, { dist: true })).toMatchInlineSnapshot(`
|
expect(ConfigSchema.validate({}, { dist: true })).toMatchInlineSnapshot(`
|
||||||
Object {
|
Object {
|
||||||
"enabled": true,
|
"enabled": true,
|
||||||
|
@ -79,6 +87,18 @@ describe('config schema', () => {
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it('should not allow `null` value for the encryption key', () => {
|
||||||
|
expect(() => ConfigSchema.validate({ encryptionKey: null })).toThrowErrorMatchingInlineSnapshot(
|
||||||
|
`"[encryptionKey]: expected value of type [string] but got [null]"`
|
||||||
|
);
|
||||||
|
|
||||||
|
expect(() =>
|
||||||
|
ConfigSchema.validate({ encryptionKey: null }, { dist: true })
|
||||||
|
).toThrowErrorMatchingInlineSnapshot(
|
||||||
|
`"[encryptionKey]: expected value of type [string] but got [null]"`
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
it('should throw error if any of the xpack.encryptedSavedObjects.keyRotation.decryptionOnlyKeys is less than 32 characters', () => {
|
it('should throw error if any of the xpack.encryptedSavedObjects.keyRotation.decryptionOnlyKeys is less than 32 characters', () => {
|
||||||
expect(() =>
|
expect(() =>
|
||||||
ConfigSchema.validate({
|
ConfigSchema.validate({
|
||||||
|
@ -121,43 +141,3 @@ describe('config schema', () => {
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
describe('createConfig()', () => {
|
|
||||||
it('should log a warning, set xpack.encryptedSavedObjects.encryptionKey and usingEphemeralEncryptionKey=true when encryptionKey is not set', () => {
|
|
||||||
const mockRandomBytes = jest.requireMock('crypto').randomBytes;
|
|
||||||
mockRandomBytes.mockReturnValue('ab'.repeat(16));
|
|
||||||
|
|
||||||
const logger = loggingSystemMock.create().get();
|
|
||||||
const config = createConfig(ConfigSchema.validate({}, { dist: true }), logger);
|
|
||||||
expect(config).toEqual({
|
|
||||||
enabled: true,
|
|
||||||
encryptionKey: 'ab'.repeat(16),
|
|
||||||
keyRotation: { decryptionOnlyKeys: [] },
|
|
||||||
usingEphemeralEncryptionKey: true,
|
|
||||||
});
|
|
||||||
|
|
||||||
expect(loggingSystemMock.collect(logger).warn).toMatchInlineSnapshot(`
|
|
||||||
Array [
|
|
||||||
Array [
|
|
||||||
"Generating a random key for xpack.encryptedSavedObjects.encryptionKey. To decrypt encrypted saved objects attributes after restart, please set xpack.encryptedSavedObjects.encryptionKey in the kibana.yml or use the bin/kibana-encryption-keys command.",
|
|
||||||
],
|
|
||||||
]
|
|
||||||
`);
|
|
||||||
});
|
|
||||||
|
|
||||||
it('should not log a warning and set usingEphemeralEncryptionKey=false when encryptionKey is set', async () => {
|
|
||||||
const logger = loggingSystemMock.create().get();
|
|
||||||
const config = createConfig(
|
|
||||||
ConfigSchema.validate({ encryptionKey: 'supersecret'.repeat(3) }, { dist: true }),
|
|
||||||
logger
|
|
||||||
);
|
|
||||||
expect(config).toEqual({
|
|
||||||
enabled: true,
|
|
||||||
encryptionKey: 'supersecret'.repeat(3),
|
|
||||||
keyRotation: { decryptionOnlyKeys: [] },
|
|
||||||
usingEphemeralEncryptionKey: false,
|
|
||||||
});
|
|
||||||
|
|
||||||
expect(loggingSystemMock.collect(logger).warn).toEqual([]);
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
|
@ -5,11 +5,9 @@
|
||||||
* 2.0.
|
* 2.0.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import crypto from 'crypto';
|
|
||||||
import { schema, TypeOf } from '@kbn/config-schema';
|
import { schema, TypeOf } from '@kbn/config-schema';
|
||||||
import { Logger } from 'src/core/server';
|
|
||||||
|
|
||||||
export type ConfigType = ReturnType<typeof createConfig>;
|
export type ConfigType = TypeOf<typeof ConfigSchema>;
|
||||||
|
|
||||||
export const ConfigSchema = schema.object(
|
export const ConfigSchema = schema.object(
|
||||||
{
|
{
|
||||||
|
@ -33,23 +31,3 @@ export const ConfigSchema = schema.object(
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
|
|
||||||
export function createConfig(config: TypeOf<typeof ConfigSchema>, logger: Logger) {
|
|
||||||
let encryptionKey = config.encryptionKey;
|
|
||||||
const usingEphemeralEncryptionKey = encryptionKey === undefined;
|
|
||||||
if (encryptionKey === undefined) {
|
|
||||||
logger.warn(
|
|
||||||
'Generating a random key for xpack.encryptedSavedObjects.encryptionKey. ' +
|
|
||||||
'To decrypt encrypted saved objects attributes after restart, ' +
|
|
||||||
'please set xpack.encryptedSavedObjects.encryptionKey in the kibana.yml or use the bin/kibana-encryption-keys command.'
|
|
||||||
);
|
|
||||||
|
|
||||||
encryptionKey = crypto.randomBytes(16).toString('hex');
|
|
||||||
}
|
|
||||||
|
|
||||||
return {
|
|
||||||
...config,
|
|
||||||
encryptionKey,
|
|
||||||
usingEphemeralEncryptionKey,
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
|
@ -226,6 +226,72 @@ describe('#stripOrDecryptAttributes', () => {
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
describe('without encryption key', () => {
|
||||||
|
beforeEach(() => {
|
||||||
|
service = new EncryptedSavedObjectsService({
|
||||||
|
logger: loggingSystemMock.create().get(),
|
||||||
|
audit: mockAuditLogger,
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
it('does not fail if none of attributes are supposed to be encrypted', async () => {
|
||||||
|
const attributes = { attrOne: 'one', attrTwo: 'two', attrThree: 'three' };
|
||||||
|
|
||||||
|
service.registerType({ type: 'known-type-1', attributesToEncrypt: new Set(['attrFour']) });
|
||||||
|
|
||||||
|
await expect(
|
||||||
|
service.stripOrDecryptAttributes({ id: 'known-id', type: 'known-type-1' }, attributes)
|
||||||
|
).resolves.toEqual({ attributes: { attrOne: 'one', attrTwo: 'two', attrThree: 'three' } });
|
||||||
|
});
|
||||||
|
|
||||||
|
it('does not fail if there are attributes are supposed to be encrypted, but should be stripped', async () => {
|
||||||
|
const attributes = { attrOne: 'one', attrTwo: 'two', attrThree: 'three' };
|
||||||
|
|
||||||
|
service.registerType({
|
||||||
|
type: 'known-type-1',
|
||||||
|
attributesToEncrypt: new Set(['attrOne', 'attrThree']),
|
||||||
|
});
|
||||||
|
|
||||||
|
await expect(
|
||||||
|
service.stripOrDecryptAttributes({ id: 'known-id', type: 'known-type-1' }, attributes)
|
||||||
|
).resolves.toEqual({ attributes: { attrTwo: 'two' } });
|
||||||
|
});
|
||||||
|
|
||||||
|
it('fails if needs to decrypt any attribute', async () => {
|
||||||
|
service.registerType({
|
||||||
|
type: 'known-type-1',
|
||||||
|
attributesToEncrypt: new Set([
|
||||||
|
'attrOne',
|
||||||
|
{ key: 'attrThree', dangerouslyExposeValue: true },
|
||||||
|
]),
|
||||||
|
});
|
||||||
|
|
||||||
|
const mockUser = mockAuthenticatedUser();
|
||||||
|
const { attributes, error } = await service.stripOrDecryptAttributes(
|
||||||
|
{ type: 'known-type-1', id: 'object-id' },
|
||||||
|
{ attrOne: 'one', attrTwo: 'two', attrThree: 'three' },
|
||||||
|
undefined,
|
||||||
|
{ user: mockUser }
|
||||||
|
);
|
||||||
|
|
||||||
|
expect(attributes).toEqual({ attrTwo: 'two' });
|
||||||
|
|
||||||
|
const encryptionError = error as EncryptionError;
|
||||||
|
expect(encryptionError.attributeName).toBe('attrThree');
|
||||||
|
expect(encryptionError.message).toBe('Unable to decrypt attribute "attrThree"');
|
||||||
|
expect(encryptionError.cause).toEqual(
|
||||||
|
new Error('Decryption is disabled because of missing decryption keys.')
|
||||||
|
);
|
||||||
|
|
||||||
|
expect(mockAuditLogger.decryptAttributeFailure).toHaveBeenCalledTimes(1);
|
||||||
|
expect(mockAuditLogger.decryptAttributeFailure).toHaveBeenCalledWith(
|
||||||
|
'attrThree',
|
||||||
|
{ type: 'known-type-1', id: 'object-id' },
|
||||||
|
mockUser
|
||||||
|
);
|
||||||
|
});
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
describe('#encryptAttributes', () => {
|
describe('#encryptAttributes', () => {
|
||||||
|
@ -465,6 +531,58 @@ describe('#encryptAttributes', () => {
|
||||||
mockUser
|
mockUser
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
describe('without encryption key', () => {
|
||||||
|
beforeEach(() => {
|
||||||
|
service = new EncryptedSavedObjectsService({
|
||||||
|
logger: loggingSystemMock.create().get(),
|
||||||
|
audit: mockAuditLogger,
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
it('does not fail if none of attributes are supposed to be encrypted', async () => {
|
||||||
|
const attributes = { attrOne: 'one', attrTwo: 'two', attrThree: 'three' };
|
||||||
|
|
||||||
|
service.registerType({ type: 'known-type-1', attributesToEncrypt: new Set(['attrFour']) });
|
||||||
|
|
||||||
|
await expect(
|
||||||
|
service.encryptAttributes({ type: 'known-type-1', id: 'object-id' }, attributes)
|
||||||
|
).resolves.toEqual({
|
||||||
|
attrOne: 'one',
|
||||||
|
attrTwo: 'two',
|
||||||
|
attrThree: 'three',
|
||||||
|
});
|
||||||
|
expect(mockAuditLogger.encryptAttributesSuccess).not.toHaveBeenCalled();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('fails if needs to encrypt any attribute', async () => {
|
||||||
|
const attributes = { attrOne: 'one', attrTwo: 'two', attrThree: 'three' };
|
||||||
|
service.registerType({
|
||||||
|
type: 'known-type-1',
|
||||||
|
attributesToEncrypt: new Set(['attrOne', 'attrThree']),
|
||||||
|
});
|
||||||
|
|
||||||
|
const mockUser = mockAuthenticatedUser();
|
||||||
|
await expect(
|
||||||
|
service.encryptAttributes({ type: 'known-type-1', id: 'object-id' }, attributes, {
|
||||||
|
user: mockUser,
|
||||||
|
})
|
||||||
|
).rejects.toThrowError(EncryptionError);
|
||||||
|
|
||||||
|
expect(attributes).toEqual({
|
||||||
|
attrOne: 'one',
|
||||||
|
attrTwo: 'two',
|
||||||
|
attrThree: 'three',
|
||||||
|
});
|
||||||
|
expect(mockAuditLogger.encryptAttributesSuccess).not.toHaveBeenCalled();
|
||||||
|
expect(mockAuditLogger.encryptAttributeFailure).toHaveBeenCalledTimes(1);
|
||||||
|
expect(mockAuditLogger.encryptAttributeFailure).toHaveBeenCalledWith(
|
||||||
|
'attrOne',
|
||||||
|
{ type: 'known-type-1', id: 'object-id' },
|
||||||
|
mockUser
|
||||||
|
);
|
||||||
|
});
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
describe('#decryptAttributes', () => {
|
describe('#decryptAttributes', () => {
|
||||||
|
@ -1099,6 +1217,88 @@ describe('#decryptAttributes', () => {
|
||||||
expect(decryptionOnlyCryptoTwo.decrypt).not.toHaveBeenCalled();
|
expect(decryptionOnlyCryptoTwo.decrypt).not.toHaveBeenCalled();
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
describe('without encryption key', () => {
|
||||||
|
beforeEach(() => {
|
||||||
|
service = new EncryptedSavedObjectsService({
|
||||||
|
logger: loggingSystemMock.create().get(),
|
||||||
|
audit: mockAuditLogger,
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
it('does not fail if none of attributes are supposed to be decrypted', async () => {
|
||||||
|
const attributes = { attrOne: 'one', attrTwo: 'two', attrThree: 'three' };
|
||||||
|
|
||||||
|
service = new EncryptedSavedObjectsService({
|
||||||
|
decryptionOnlyCryptos: [],
|
||||||
|
logger: loggingSystemMock.create().get(),
|
||||||
|
audit: mockAuditLogger,
|
||||||
|
});
|
||||||
|
service.registerType({ type: 'known-type-1', attributesToEncrypt: new Set(['attrFour']) });
|
||||||
|
|
||||||
|
await expect(
|
||||||
|
service.decryptAttributes({ type: 'known-type-1', id: 'object-id' }, attributes)
|
||||||
|
).resolves.toEqual({
|
||||||
|
attrOne: 'one',
|
||||||
|
attrTwo: 'two',
|
||||||
|
attrThree: 'three',
|
||||||
|
});
|
||||||
|
expect(mockAuditLogger.decryptAttributesSuccess).not.toHaveBeenCalled();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('does not fail if can decrypt attributes with decryption only keys', async () => {
|
||||||
|
const decryptionOnlyCryptoOne = createNodeCryptMock('old-key-one');
|
||||||
|
decryptionOnlyCryptoOne.decrypt.mockImplementation(
|
||||||
|
async (encryptedOutput: string | Buffer, aad?: string) => `${encryptedOutput}||${aad}`
|
||||||
|
);
|
||||||
|
|
||||||
|
service = new EncryptedSavedObjectsService({
|
||||||
|
decryptionOnlyCryptos: [decryptionOnlyCryptoOne],
|
||||||
|
logger: loggingSystemMock.create().get(),
|
||||||
|
audit: mockAuditLogger,
|
||||||
|
});
|
||||||
|
service.registerType({
|
||||||
|
type: 'known-type-1',
|
||||||
|
attributesToEncrypt: new Set(['attrOne', 'attrThree', 'attrFour']),
|
||||||
|
});
|
||||||
|
|
||||||
|
const attributes = { attrOne: 'one', attrTwo: 'two', attrThree: 'three', attrFour: null };
|
||||||
|
await expect(
|
||||||
|
service.decryptAttributes({ type: 'known-type-1', id: 'object-id' }, attributes)
|
||||||
|
).resolves.toEqual({
|
||||||
|
attrOne: 'one||["known-type-1","object-id",{"attrTwo":"two"}]',
|
||||||
|
attrTwo: 'two',
|
||||||
|
attrThree: 'three||["known-type-1","object-id",{"attrTwo":"two"}]',
|
||||||
|
attrFour: null,
|
||||||
|
});
|
||||||
|
expect(mockAuditLogger.decryptAttributesSuccess).toHaveBeenCalledTimes(1);
|
||||||
|
expect(mockAuditLogger.decryptAttributesSuccess).toHaveBeenCalledWith(
|
||||||
|
['attrOne', 'attrThree'],
|
||||||
|
{ type: 'known-type-1', id: 'object-id' },
|
||||||
|
undefined
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('fails if needs to decrypt any attribute', async () => {
|
||||||
|
const attributes = { attrOne: 'one', attrTwo: 'two', attrThree: 'three' };
|
||||||
|
|
||||||
|
service.registerType({ type: 'known-type-1', attributesToEncrypt: new Set(['attrOne']) });
|
||||||
|
|
||||||
|
const mockUser = mockAuthenticatedUser();
|
||||||
|
await expect(
|
||||||
|
service.decryptAttributes({ type: 'known-type-1', id: 'object-id' }, attributes, {
|
||||||
|
user: mockUser,
|
||||||
|
})
|
||||||
|
).rejects.toThrowError(EncryptionError);
|
||||||
|
|
||||||
|
expect(mockAuditLogger.decryptAttributesSuccess).not.toHaveBeenCalled();
|
||||||
|
expect(mockAuditLogger.decryptAttributeFailure).toHaveBeenCalledWith(
|
||||||
|
'attrOne',
|
||||||
|
{ type: 'known-type-1', id: 'object-id' },
|
||||||
|
mockUser
|
||||||
|
);
|
||||||
|
});
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
describe('#encryptAttributesSync', () => {
|
describe('#encryptAttributesSync', () => {
|
||||||
|
@ -1283,6 +1483,58 @@ describe('#encryptAttributesSync', () => {
|
||||||
attrThree: 'three',
|
attrThree: 'three',
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
describe('without encryption key', () => {
|
||||||
|
beforeEach(() => {
|
||||||
|
service = new EncryptedSavedObjectsService({
|
||||||
|
logger: loggingSystemMock.create().get(),
|
||||||
|
audit: mockAuditLogger,
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
it('does not fail if none of attributes are supposed to be encrypted', () => {
|
||||||
|
const attributes = { attrOne: 'one', attrTwo: 'two', attrThree: 'three' };
|
||||||
|
|
||||||
|
service.registerType({ type: 'known-type-1', attributesToEncrypt: new Set(['attrFour']) });
|
||||||
|
|
||||||
|
expect(
|
||||||
|
service.encryptAttributesSync({ type: 'known-type-1', id: 'object-id' }, attributes)
|
||||||
|
).toEqual({
|
||||||
|
attrOne: 'one',
|
||||||
|
attrTwo: 'two',
|
||||||
|
attrThree: 'three',
|
||||||
|
});
|
||||||
|
expect(mockAuditLogger.encryptAttributesSuccess).not.toHaveBeenCalled();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('fails if needs to encrypt any attribute', () => {
|
||||||
|
const attributes = { attrOne: 'one', attrTwo: 'two', attrThree: 'three' };
|
||||||
|
service.registerType({
|
||||||
|
type: 'known-type-1',
|
||||||
|
attributesToEncrypt: new Set(['attrOne', 'attrThree']),
|
||||||
|
});
|
||||||
|
|
||||||
|
const mockUser = mockAuthenticatedUser();
|
||||||
|
expect(() =>
|
||||||
|
service.encryptAttributesSync({ type: 'known-type-1', id: 'object-id' }, attributes, {
|
||||||
|
user: mockUser,
|
||||||
|
})
|
||||||
|
).toThrowError(EncryptionError);
|
||||||
|
|
||||||
|
expect(attributes).toEqual({
|
||||||
|
attrOne: 'one',
|
||||||
|
attrTwo: 'two',
|
||||||
|
attrThree: 'three',
|
||||||
|
});
|
||||||
|
expect(mockAuditLogger.encryptAttributesSuccess).not.toHaveBeenCalled();
|
||||||
|
expect(mockAuditLogger.encryptAttributeFailure).toHaveBeenCalledTimes(1);
|
||||||
|
expect(mockAuditLogger.encryptAttributeFailure).toHaveBeenCalledWith(
|
||||||
|
'attrOne',
|
||||||
|
{ type: 'known-type-1', id: 'object-id' },
|
||||||
|
mockUser
|
||||||
|
);
|
||||||
|
});
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
describe('#decryptAttributesSync', () => {
|
describe('#decryptAttributesSync', () => {
|
||||||
|
@ -1784,4 +2036,86 @@ describe('#decryptAttributesSync', () => {
|
||||||
expect(decryptionOnlyCryptoTwo.decryptSync).not.toHaveBeenCalled();
|
expect(decryptionOnlyCryptoTwo.decryptSync).not.toHaveBeenCalled();
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
describe('without encryption key', () => {
|
||||||
|
beforeEach(() => {
|
||||||
|
service = new EncryptedSavedObjectsService({
|
||||||
|
logger: loggingSystemMock.create().get(),
|
||||||
|
audit: mockAuditLogger,
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
it('does not fail if none of attributes are supposed to be decrypted', () => {
|
||||||
|
const attributes = { attrOne: 'one', attrTwo: 'two', attrThree: 'three' };
|
||||||
|
|
||||||
|
service = new EncryptedSavedObjectsService({
|
||||||
|
decryptionOnlyCryptos: [],
|
||||||
|
logger: loggingSystemMock.create().get(),
|
||||||
|
audit: mockAuditLogger,
|
||||||
|
});
|
||||||
|
service.registerType({ type: 'known-type-1', attributesToEncrypt: new Set(['attrFour']) });
|
||||||
|
|
||||||
|
expect(
|
||||||
|
service.decryptAttributesSync({ type: 'known-type-1', id: 'object-id' }, attributes)
|
||||||
|
).toEqual({
|
||||||
|
attrOne: 'one',
|
||||||
|
attrTwo: 'two',
|
||||||
|
attrThree: 'three',
|
||||||
|
});
|
||||||
|
expect(mockAuditLogger.decryptAttributesSuccess).not.toHaveBeenCalled();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('does not fail if can decrypt attributes with decryption only keys', () => {
|
||||||
|
const decryptionOnlyCryptoOne = createNodeCryptMock('old-key-one');
|
||||||
|
decryptionOnlyCryptoOne.decryptSync.mockImplementation(
|
||||||
|
(encryptedOutput: string | Buffer, aad?: string) => `${encryptedOutput}||${aad}`
|
||||||
|
);
|
||||||
|
|
||||||
|
service = new EncryptedSavedObjectsService({
|
||||||
|
decryptionOnlyCryptos: [decryptionOnlyCryptoOne],
|
||||||
|
logger: loggingSystemMock.create().get(),
|
||||||
|
audit: mockAuditLogger,
|
||||||
|
});
|
||||||
|
service.registerType({
|
||||||
|
type: 'known-type-1',
|
||||||
|
attributesToEncrypt: new Set(['attrOne', 'attrThree', 'attrFour']),
|
||||||
|
});
|
||||||
|
|
||||||
|
const attributes = { attrOne: 'one', attrTwo: 'two', attrThree: 'three', attrFour: null };
|
||||||
|
expect(
|
||||||
|
service.decryptAttributesSync({ type: 'known-type-1', id: 'object-id' }, attributes)
|
||||||
|
).toEqual({
|
||||||
|
attrOne: 'one||["known-type-1","object-id",{"attrTwo":"two"}]',
|
||||||
|
attrTwo: 'two',
|
||||||
|
attrThree: 'three||["known-type-1","object-id",{"attrTwo":"two"}]',
|
||||||
|
attrFour: null,
|
||||||
|
});
|
||||||
|
expect(mockAuditLogger.decryptAttributesSuccess).toHaveBeenCalledTimes(1);
|
||||||
|
expect(mockAuditLogger.decryptAttributesSuccess).toHaveBeenCalledWith(
|
||||||
|
['attrOne', 'attrThree'],
|
||||||
|
{ type: 'known-type-1', id: 'object-id' },
|
||||||
|
undefined
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('fails if needs to decrypt any attribute', () => {
|
||||||
|
const attributes = { attrOne: 'one', attrTwo: 'two', attrThree: 'three' };
|
||||||
|
|
||||||
|
service.registerType({ type: 'known-type-1', attributesToEncrypt: new Set(['attrOne']) });
|
||||||
|
|
||||||
|
const mockUser = mockAuthenticatedUser();
|
||||||
|
expect(() =>
|
||||||
|
service.decryptAttributesSync({ type: 'known-type-1', id: 'object-id' }, attributes, {
|
||||||
|
user: mockUser,
|
||||||
|
})
|
||||||
|
).toThrowError(EncryptionError);
|
||||||
|
|
||||||
|
expect(mockAuditLogger.decryptAttributesSuccess).not.toHaveBeenCalled();
|
||||||
|
expect(mockAuditLogger.decryptAttributeFailure).toHaveBeenCalledWith(
|
||||||
|
'attrOne',
|
||||||
|
{ type: 'known-type-1', id: 'object-id' },
|
||||||
|
mockUser
|
||||||
|
);
|
||||||
|
});
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|
|
@ -77,7 +77,7 @@ interface EncryptedSavedObjectsServiceOptions {
|
||||||
/**
|
/**
|
||||||
* NodeCrypto instance used for both encryption and decryption.
|
* NodeCrypto instance used for both encryption and decryption.
|
||||||
*/
|
*/
|
||||||
primaryCrypto: Crypto;
|
primaryCrypto?: Crypto;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* NodeCrypto instances used ONLY for decryption (i.e. rotated encryption keys).
|
* NodeCrypto instances used ONLY for decryption (i.e. rotated encryption keys).
|
||||||
|
@ -293,12 +293,17 @@ export class EncryptedSavedObjectsService {
|
||||||
let iteratorResult = iterator.next();
|
let iteratorResult = iterator.next();
|
||||||
while (!iteratorResult.done) {
|
while (!iteratorResult.done) {
|
||||||
const [attributeValue, encryptionAAD] = iteratorResult.value;
|
const [attributeValue, encryptionAAD] = iteratorResult.value;
|
||||||
try {
|
// We check this inside of the iterator to throw only if we do need to encrypt anything.
|
||||||
iteratorResult = iterator.next(
|
if (this.options.primaryCrypto) {
|
||||||
await this.options.primaryCrypto.encrypt(attributeValue, encryptionAAD)
|
try {
|
||||||
);
|
iteratorResult = iterator.next(
|
||||||
} catch (err) {
|
await this.options.primaryCrypto.encrypt(attributeValue, encryptionAAD)
|
||||||
iterator.throw!(err);
|
);
|
||||||
|
} catch (err) {
|
||||||
|
iterator.throw!(err);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
iterator.throw!(new Error('Encryption is disabled because of missing encryption key.'));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -324,12 +329,17 @@ export class EncryptedSavedObjectsService {
|
||||||
let iteratorResult = iterator.next();
|
let iteratorResult = iterator.next();
|
||||||
while (!iteratorResult.done) {
|
while (!iteratorResult.done) {
|
||||||
const [attributeValue, encryptionAAD] = iteratorResult.value;
|
const [attributeValue, encryptionAAD] = iteratorResult.value;
|
||||||
try {
|
// We check this inside of the iterator to throw only if we do need to encrypt anything.
|
||||||
iteratorResult = iterator.next(
|
if (this.options.primaryCrypto) {
|
||||||
this.options.primaryCrypto.encryptSync(attributeValue, encryptionAAD)
|
try {
|
||||||
);
|
iteratorResult = iterator.next(
|
||||||
} catch (err) {
|
this.options.primaryCrypto.encryptSync(attributeValue, encryptionAAD)
|
||||||
iterator.throw!(err);
|
);
|
||||||
|
} catch (err) {
|
||||||
|
iterator.throw!(err);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
iterator.throw!(new Error('Encryption is disabled because of missing encryption key.'));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -358,7 +368,11 @@ export class EncryptedSavedObjectsService {
|
||||||
while (!iteratorResult.done) {
|
while (!iteratorResult.done) {
|
||||||
const [attributeValue, encryptionAAD] = iteratorResult.value;
|
const [attributeValue, encryptionAAD] = iteratorResult.value;
|
||||||
|
|
||||||
let decryptionError;
|
// We check this inside of the iterator to throw only if we do need to decrypt anything.
|
||||||
|
let decryptionError =
|
||||||
|
decrypters.length === 0
|
||||||
|
? new Error('Decryption is disabled because of missing decryption keys.')
|
||||||
|
: undefined;
|
||||||
for (const decrypter of decrypters) {
|
for (const decrypter of decrypters) {
|
||||||
try {
|
try {
|
||||||
iteratorResult = iterator.next(await decrypter.decrypt(attributeValue, encryptionAAD));
|
iteratorResult = iterator.next(await decrypter.decrypt(attributeValue, encryptionAAD));
|
||||||
|
@ -402,7 +416,11 @@ export class EncryptedSavedObjectsService {
|
||||||
while (!iteratorResult.done) {
|
while (!iteratorResult.done) {
|
||||||
const [attributeValue, encryptionAAD] = iteratorResult.value;
|
const [attributeValue, encryptionAAD] = iteratorResult.value;
|
||||||
|
|
||||||
let decryptionError;
|
// We check this inside of the iterator to throw only if we do need to decrypt anything.
|
||||||
|
let decryptionError =
|
||||||
|
decrypters.length === 0
|
||||||
|
? new Error('Decryption is disabled because of missing decryption keys.')
|
||||||
|
: undefined;
|
||||||
for (const decrypter of decrypters) {
|
for (const decrypter of decrypters) {
|
||||||
try {
|
try {
|
||||||
iteratorResult = iterator.next(decrypter.decryptSync(attributeValue, encryptionAAD));
|
iteratorResult = iterator.next(decrypter.decryptSync(attributeValue, encryptionAAD));
|
||||||
|
@ -541,6 +559,9 @@ export class EncryptedSavedObjectsService {
|
||||||
return this.options.decryptionOnlyCryptos;
|
return this.options.decryptionOnlyCryptos;
|
||||||
}
|
}
|
||||||
|
|
||||||
return [this.options.primaryCrypto, ...(this.options.decryptionOnlyCryptos ?? [])];
|
return [
|
||||||
|
...(this.options.primaryCrypto ? [this.options.primaryCrypto] : []),
|
||||||
|
...(this.options.decryptionOnlyCryptos ?? []),
|
||||||
|
];
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -8,11 +8,13 @@
|
||||||
import { EncryptedSavedObjectsPluginSetup, EncryptedSavedObjectsPluginStart } from './plugin';
|
import { EncryptedSavedObjectsPluginSetup, EncryptedSavedObjectsPluginStart } from './plugin';
|
||||||
import { EncryptedSavedObjectsClient, EncryptedSavedObjectsClientOptions } from './saved_objects';
|
import { EncryptedSavedObjectsClient, EncryptedSavedObjectsClientOptions } from './saved_objects';
|
||||||
|
|
||||||
function createEncryptedSavedObjectsSetupMock() {
|
function createEncryptedSavedObjectsSetupMock(
|
||||||
|
{ canEncrypt }: { canEncrypt: boolean } = { canEncrypt: false }
|
||||||
|
) {
|
||||||
return {
|
return {
|
||||||
registerType: jest.fn(),
|
registerType: jest.fn(),
|
||||||
__legacyCompat: { registerLegacyAPI: jest.fn() },
|
__legacyCompat: { registerLegacyAPI: jest.fn() },
|
||||||
usingEphemeralEncryptionKey: true,
|
canEncrypt,
|
||||||
createMigration: jest.fn(),
|
createMigration: jest.fn(),
|
||||||
} as jest.Mocked<EncryptedSavedObjectsPluginSetup>;
|
} as jest.Mocked<EncryptedSavedObjectsPluginSetup>;
|
||||||
}
|
}
|
||||||
|
|
|
@ -19,12 +19,28 @@ describe('EncryptedSavedObjects Plugin', () => {
|
||||||
);
|
);
|
||||||
expect(plugin.setup(coreMock.createSetup(), { security: securityMock.createSetup() }))
|
expect(plugin.setup(coreMock.createSetup(), { security: securityMock.createSetup() }))
|
||||||
.toMatchInlineSnapshot(`
|
.toMatchInlineSnapshot(`
|
||||||
Object {
|
Object {
|
||||||
"createMigration": [Function],
|
"canEncrypt": false,
|
||||||
"registerType": [Function],
|
"createMigration": [Function],
|
||||||
"usingEphemeralEncryptionKey": true,
|
"registerType": [Function],
|
||||||
}
|
}
|
||||||
`);
|
`);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('exposes proper contract when encryption key is set', () => {
|
||||||
|
const plugin = new EncryptedSavedObjectsPlugin(
|
||||||
|
coreMock.createPluginInitializerContext(
|
||||||
|
ConfigSchema.validate({ encryptionKey: 'z'.repeat(32) }, { dist: true })
|
||||||
|
)
|
||||||
|
);
|
||||||
|
expect(plugin.setup(coreMock.createSetup(), { security: securityMock.createSetup() }))
|
||||||
|
.toMatchInlineSnapshot(`
|
||||||
|
Object {
|
||||||
|
"canEncrypt": true,
|
||||||
|
"createMigration": [Function],
|
||||||
|
"registerType": [Function],
|
||||||
|
}
|
||||||
|
`);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
|
@ -6,10 +6,9 @@
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import nodeCrypto from '@elastic/node-crypto';
|
import nodeCrypto from '@elastic/node-crypto';
|
||||||
import { Logger, PluginInitializerContext, CoreSetup, Plugin } from 'src/core/server';
|
import type { Logger, PluginInitializerContext, CoreSetup, Plugin } from 'src/core/server';
|
||||||
import { TypeOf } from '@kbn/config-schema';
|
import type { SecurityPluginSetup } from '../../security/server';
|
||||||
import { SecurityPluginSetup } from '../../security/server';
|
import type { ConfigType } from './config';
|
||||||
import { createConfig, ConfigSchema } from './config';
|
|
||||||
import {
|
import {
|
||||||
EncryptedSavedObjectsService,
|
EncryptedSavedObjectsService,
|
||||||
EncryptedSavedObjectTypeRegistration,
|
EncryptedSavedObjectTypeRegistration,
|
||||||
|
@ -26,8 +25,11 @@ export interface PluginsSetup {
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface EncryptedSavedObjectsPluginSetup {
|
export interface EncryptedSavedObjectsPluginSetup {
|
||||||
|
/**
|
||||||
|
* Indicates if Saved Object encryption is possible. Requires an encryption key to be explicitly set via `xpack.encryptedSavedObjects.encryptionKey`.
|
||||||
|
*/
|
||||||
|
canEncrypt: boolean;
|
||||||
registerType: (typeRegistration: EncryptedSavedObjectTypeRegistration) => void;
|
registerType: (typeRegistration: EncryptedSavedObjectTypeRegistration) => void;
|
||||||
usingEphemeralEncryptionKey: boolean;
|
|
||||||
createMigration: CreateEncryptedSavedObjectsMigrationFn;
|
createMigration: CreateEncryptedSavedObjectsMigrationFn;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -50,19 +52,24 @@ export class EncryptedSavedObjectsPlugin
|
||||||
}
|
}
|
||||||
|
|
||||||
public setup(core: CoreSetup, deps: PluginsSetup): EncryptedSavedObjectsPluginSetup {
|
public setup(core: CoreSetup, deps: PluginsSetup): EncryptedSavedObjectsPluginSetup {
|
||||||
const config = createConfig(
|
const config = this.initializerContext.config.get<ConfigType>();
|
||||||
this.initializerContext.config.get<TypeOf<typeof ConfigSchema>>(),
|
const canEncrypt = config.encryptionKey !== undefined;
|
||||||
this.initializerContext.logger.get('config')
|
if (!canEncrypt) {
|
||||||
|
this.logger.warn(
|
||||||
|
'Saved objects encryption key is not set. This will severely limit Kibana functionality. ' +
|
||||||
|
'Please set xpack.encryptedSavedObjects.encryptionKey in the kibana.yml or use the bin/kibana-encryption-keys command.'
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
const primaryCrypto = config.encryptionKey
|
||||||
|
? nodeCrypto({ encryptionKey: config.encryptionKey })
|
||||||
|
: undefined;
|
||||||
|
const decryptionOnlyCryptos = config.keyRotation.decryptionOnlyKeys.map((decryptionKey) =>
|
||||||
|
nodeCrypto({ encryptionKey: decryptionKey })
|
||||||
);
|
);
|
||||||
const auditLogger = new EncryptedSavedObjectsAuditLogger(
|
const auditLogger = new EncryptedSavedObjectsAuditLogger(
|
||||||
deps.security?.audit.getLogger('encryptedSavedObjects')
|
deps.security?.audit.getLogger('encryptedSavedObjects')
|
||||||
);
|
);
|
||||||
|
|
||||||
const primaryCrypto = nodeCrypto({ encryptionKey: config.encryptionKey });
|
|
||||||
const decryptionOnlyCryptos = config.keyRotation.decryptionOnlyKeys.map((decryptionKey) =>
|
|
||||||
nodeCrypto({ encryptionKey: decryptionKey })
|
|
||||||
);
|
|
||||||
|
|
||||||
const service = Object.freeze(
|
const service = Object.freeze(
|
||||||
new EncryptedSavedObjectsService({
|
new EncryptedSavedObjectsService({
|
||||||
primaryCrypto,
|
primaryCrypto,
|
||||||
|
@ -94,9 +101,9 @@ export class EncryptedSavedObjectsPlugin
|
||||||
});
|
});
|
||||||
|
|
||||||
return {
|
return {
|
||||||
|
canEncrypt,
|
||||||
registerType: (typeRegistration: EncryptedSavedObjectTypeRegistration) =>
|
registerType: (typeRegistration: EncryptedSavedObjectTypeRegistration) =>
|
||||||
service.registerType(typeRegistration),
|
service.registerType(typeRegistration),
|
||||||
usingEphemeralEncryptionKey: config.usingEphemeralEncryptionKey,
|
|
||||||
createMigration: getCreateMigration(
|
createMigration: getCreateMigration(
|
||||||
service,
|
service,
|
||||||
(typeRegistration: EncryptedSavedObjectTypeRegistration) => {
|
(typeRegistration: EncryptedSavedObjectTypeRegistration) => {
|
||||||
|
|
|
@ -5,7 +5,7 @@
|
||||||
* 2.0.
|
* 2.0.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import { ConfigSchema, createConfig } from '../config';
|
import { ConfigSchema, ConfigType } from '../config';
|
||||||
|
|
||||||
import { httpServiceMock, loggingSystemMock } from '../../../../../src/core/server/mocks';
|
import { httpServiceMock, loggingSystemMock } from '../../../../../src/core/server/mocks';
|
||||||
import { encryptionKeyRotationServiceMock } from '../crypto/index.mock';
|
import { encryptionKeyRotationServiceMock } from '../crypto/index.mock';
|
||||||
|
@ -14,7 +14,7 @@ export const routeDefinitionParamsMock = {
|
||||||
create: (config: Record<string, unknown> = {}) => ({
|
create: (config: Record<string, unknown> = {}) => ({
|
||||||
router: httpServiceMock.createRouter(),
|
router: httpServiceMock.createRouter(),
|
||||||
logger: loggingSystemMock.create().get(),
|
logger: loggingSystemMock.create().get(),
|
||||||
config: createConfig(ConfigSchema.validate(config), loggingSystemMock.create().get()),
|
config: ConfigSchema.validate(config) as ConfigType,
|
||||||
encryptionKeyRotationService: encryptionKeyRotationServiceMock.create(),
|
encryptionKeyRotationService: encryptionKeyRotationServiceMock.create(),
|
||||||
}),
|
}),
|
||||||
};
|
};
|
||||||
|
|
|
@ -4,14 +4,13 @@
|
||||||
"server": true,
|
"server": true,
|
||||||
"ui": true,
|
"ui": true,
|
||||||
"configPath": ["xpack", "fleet"],
|
"configPath": ["xpack", "fleet"],
|
||||||
"requiredPlugins": ["licensing", "data"],
|
"requiredPlugins": ["licensing", "data", "encryptedSavedObjects"],
|
||||||
"optionalPlugins": [
|
"optionalPlugins": [
|
||||||
"security",
|
"security",
|
||||||
"features",
|
"features",
|
||||||
"cloud",
|
"cloud",
|
||||||
"usageCollection",
|
"usageCollection",
|
||||||
"home",
|
"home"
|
||||||
"encryptedSavedObjects"
|
|
||||||
],
|
],
|
||||||
"extraPublicDirs": ["common"],
|
"extraPublicDirs": ["common"],
|
||||||
"requiredBundles": ["kibanaReact", "esUiShared", "home", "infra", "kibanaUtils"]
|
"requiredBundles": ["kibanaReact", "esUiShared", "home", "infra", "kibanaUtils"]
|
||||||
|
|
|
@ -95,7 +95,7 @@ export interface FleetSetupDeps {
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface FleetStartDeps {
|
export interface FleetStartDeps {
|
||||||
encryptedSavedObjects?: EncryptedSavedObjectsPluginStart;
|
encryptedSavedObjects: EncryptedSavedObjectsPluginStart;
|
||||||
security?: SecurityPluginStart;
|
security?: SecurityPluginStart;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -255,11 +255,11 @@ export class FleetPlugin
|
||||||
|
|
||||||
// Conditional config routes
|
// Conditional config routes
|
||||||
if (config.agents.enabled) {
|
if (config.agents.enabled) {
|
||||||
const isESOUsingEphemeralEncryptionKey = !deps.encryptedSavedObjects;
|
const isESOCanEncrypt = deps.encryptedSavedObjects.canEncrypt;
|
||||||
if (isESOUsingEphemeralEncryptionKey) {
|
if (!isESOCanEncrypt) {
|
||||||
if (this.logger) {
|
if (this.logger) {
|
||||||
this.logger.warn(
|
this.logger.warn(
|
||||||
'Fleet APIs are disabled because the Encrypted Saved Objects plugin uses an ephemeral encryption key. Please set xpack.encryptedSavedObjects.encryptionKey in the kibana.yml or use the bin/kibana-encryption-keys command.'
|
'Fleet APIs are disabled 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.'
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
|
|
|
@ -24,7 +24,7 @@ export const getFleetStatusHandler: RequestHandler = async (context, request, re
|
||||||
const isProductionMode = appContextService.getIsProductionMode();
|
const isProductionMode = appContextService.getIsProductionMode();
|
||||||
const isCloud = appContextService.getCloud()?.isCloudEnabled ?? false;
|
const isCloud = appContextService.getCloud()?.isCloudEnabled ?? false;
|
||||||
const isTLSCheckDisabled = appContextService.getConfig()?.agents?.tlsCheckDisabled ?? false;
|
const isTLSCheckDisabled = appContextService.getConfig()?.agents?.tlsCheckDisabled ?? false;
|
||||||
const isUsingEphemeralEncryptionKey = !appContextService.getEncryptedSavedObjectsSetup();
|
const canEncrypt = appContextService.getEncryptedSavedObjectsSetup()?.canEncrypt === true;
|
||||||
|
|
||||||
const missingRequirements: GetFleetStatusResponse['missing_requirements'] = [];
|
const missingRequirements: GetFleetStatusResponse['missing_requirements'] = [];
|
||||||
if (!isAdminUserSetup) {
|
if (!isAdminUserSetup) {
|
||||||
|
@ -37,7 +37,7 @@ export const getFleetStatusHandler: RequestHandler = async (context, request, re
|
||||||
missingRequirements.push('tls_required');
|
missingRequirements.push('tls_required');
|
||||||
}
|
}
|
||||||
|
|
||||||
if (isUsingEphemeralEncryptionKey) {
|
if (!canEncrypt) {
|
||||||
missingRequirements.push('encrypted_saved_object_encryption_key_required');
|
missingRequirements.push('encrypted_saved_object_encryption_key_required');
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -44,7 +44,7 @@ export class AlertingSecurity {
|
||||||
|
|
||||||
return {
|
return {
|
||||||
isSufficientlySecure: !isSecurityEnabled || (isSecurityEnabled && isTLSEnabled),
|
isSufficientlySecure: !isSecurityEnabled || (isSecurityEnabled && isTLSEnabled),
|
||||||
hasPermanentEncryptionKey: Boolean(encryptedSavedObjects),
|
hasPermanentEncryptionKey: encryptedSavedObjects?.canEncrypt === true,
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
|
@ -18,7 +18,7 @@ describe('read_privileges route', () => {
|
||||||
({ clients, context } = requestContextMock.createTools());
|
({ clients, context } = requestContextMock.createTools());
|
||||||
|
|
||||||
clients.clusterClient.callAsCurrentUser.mockResolvedValue(getMockPrivilegesResult());
|
clients.clusterClient.callAsCurrentUser.mockResolvedValue(getMockPrivilegesResult());
|
||||||
readPrivilegesRoute(server.router, false);
|
readPrivilegesRoute(server.router, true);
|
||||||
});
|
});
|
||||||
|
|
||||||
describe('normal status codes', () => {
|
describe('normal status codes', () => {
|
||||||
|
|
|
@ -14,7 +14,7 @@ import { readPrivileges } from '../../privileges/read_privileges';
|
||||||
|
|
||||||
export const readPrivilegesRoute = (
|
export const readPrivilegesRoute = (
|
||||||
router: SecuritySolutionPluginRouter,
|
router: SecuritySolutionPluginRouter,
|
||||||
usingEphemeralEncryptionKey: boolean
|
hasEncryptionKey: boolean
|
||||||
) => {
|
) => {
|
||||||
router.get(
|
router.get(
|
||||||
{
|
{
|
||||||
|
@ -39,7 +39,7 @@ export const readPrivilegesRoute = (
|
||||||
const clusterPrivileges = await readPrivileges(clusterClient.callAsCurrentUser, index);
|
const clusterPrivileges = await readPrivileges(clusterClient.callAsCurrentUser, index);
|
||||||
const privileges = merge(clusterPrivileges, {
|
const privileges = merge(clusterPrivileges, {
|
||||||
is_authenticated: request.auth.isAuthenticated ?? false,
|
is_authenticated: request.auth.isAuthenticated ?? false,
|
||||||
has_encryption_key: !usingEphemeralEncryptionKey,
|
has_encryption_key: hasEncryptionKey,
|
||||||
});
|
});
|
||||||
|
|
||||||
return response.ok({ body: privileges });
|
return response.ok({ body: privileges });
|
||||||
|
|
|
@ -183,7 +183,7 @@ export class Plugin implements IPlugin<PluginSetup, PluginStart, SetupPlugins, S
|
||||||
initRoutes(
|
initRoutes(
|
||||||
router,
|
router,
|
||||||
config,
|
config,
|
||||||
plugins.encryptedSavedObjects?.usingEphemeralEncryptionKey ?? false,
|
plugins.encryptedSavedObjects?.canEncrypt === true,
|
||||||
plugins.security,
|
plugins.security,
|
||||||
plugins.ml
|
plugins.ml
|
||||||
);
|
);
|
||||||
|
|
|
@ -47,7 +47,7 @@ import { getTimelineRoute } from '../lib/timeline/routes/get_timeline_route';
|
||||||
export const initRoutes = (
|
export const initRoutes = (
|
||||||
router: SecuritySolutionPluginRouter,
|
router: SecuritySolutionPluginRouter,
|
||||||
config: ConfigType,
|
config: ConfigType,
|
||||||
usingEphemeralEncryptionKey: boolean,
|
hasEncryptionKey: boolean,
|
||||||
security: SetupPlugins['security'],
|
security: SetupPlugins['security'],
|
||||||
ml: SetupPlugins['ml']
|
ml: SetupPlugins['ml']
|
||||||
) => {
|
) => {
|
||||||
|
@ -102,5 +102,5 @@ export const initRoutes = (
|
||||||
readTagsRoute(router);
|
readTagsRoute(router);
|
||||||
|
|
||||||
// Privileges API to get the generic user privileges
|
// Privileges API to get the generic user privileges
|
||||||
readPrivilegesRoute(router, usingEphemeralEncryptionKey);
|
readPrivilegesRoute(router, hasEncryptionKey);
|
||||||
};
|
};
|
||||||
|
|
Loading…
Reference in a new issue