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 = {
|
||||
licensing: licensingMock.createSetup(),
|
||||
taskManager: mockTaskManager,
|
||||
taskRunnerFactory: new TaskRunnerFactory(
|
||||
new ActionExecutor({ isESOUsingEphemeralEncryptionKey: false })
|
||||
),
|
||||
taskRunnerFactory: new TaskRunnerFactory(new ActionExecutor({ isESOCanEncrypt: true })),
|
||||
actionsConfigUtils: mockedActionsConfig,
|
||||
licenseState: mockedLicenseState,
|
||||
preconfiguredActions: [
|
||||
|
|
|
@ -59,9 +59,7 @@ beforeEach(() => {
|
|||
actionTypeRegistryParams = {
|
||||
licensing: licensingMock.createSetup(),
|
||||
taskManager: mockTaskManager,
|
||||
taskRunnerFactory: new TaskRunnerFactory(
|
||||
new ActionExecutor({ isESOUsingEphemeralEncryptionKey: false })
|
||||
),
|
||||
taskRunnerFactory: new TaskRunnerFactory(new ActionExecutor({ isESOCanEncrypt: true })),
|
||||
actionsConfigUtils: actionsConfigMock.create(),
|
||||
licenseState: mockedLicenseState,
|
||||
preconfiguredActions: [],
|
||||
|
@ -411,9 +409,7 @@ describe('create()', () => {
|
|||
const localActionTypeRegistryParams = {
|
||||
licensing: licensingMock.createSetup(),
|
||||
taskManager: mockTaskManager,
|
||||
taskRunnerFactory: new TaskRunnerFactory(
|
||||
new ActionExecutor({ isESOUsingEphemeralEncryptionKey: false })
|
||||
),
|
||||
taskRunnerFactory: new TaskRunnerFactory(new ActionExecutor({ isESOCanEncrypt: true })),
|
||||
actionsConfigUtils: localConfigUtils,
|
||||
licenseState: licenseStateMock.create(),
|
||||
preconfiguredActions: [],
|
||||
|
|
|
@ -33,9 +33,7 @@ export function createActionTypeRegistry(): {
|
|||
const actionTypeRegistry = new ActionTypeRegistry({
|
||||
taskManager: taskManagerMock.createSetup(),
|
||||
licensing: licensingMock.createSetup(),
|
||||
taskRunnerFactory: new TaskRunnerFactory(
|
||||
new ActionExecutor({ isESOUsingEphemeralEncryptionKey: false })
|
||||
),
|
||||
taskRunnerFactory: new TaskRunnerFactory(new ActionExecutor({ isESOCanEncrypt: true })),
|
||||
actionsConfigUtils: actionsConfigMock.create(),
|
||||
licenseState: licenseStateMock.create(),
|
||||
preconfiguredActions: [],
|
||||
|
|
|
@ -28,7 +28,7 @@ describe('execute()', () => {
|
|||
const executeFn = createExecutionEnqueuerFunction({
|
||||
taskManager: mockTaskManager,
|
||||
actionTypeRegistry,
|
||||
isESOUsingEphemeralEncryptionKey: false,
|
||||
isESOCanEncrypt: true,
|
||||
preconfiguredActions: [],
|
||||
});
|
||||
savedObjectsClient.get.mockResolvedValueOnce({
|
||||
|
@ -87,7 +87,7 @@ describe('execute()', () => {
|
|||
const executeFn = createExecutionEnqueuerFunction({
|
||||
taskManager: mockTaskManager,
|
||||
actionTypeRegistry: actionTypeRegistryMock.create(),
|
||||
isESOUsingEphemeralEncryptionKey: false,
|
||||
isESOCanEncrypt: true,
|
||||
preconfiguredActions: [
|
||||
{
|
||||
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({
|
||||
taskManager: mockTaskManager,
|
||||
isESOUsingEphemeralEncryptionKey: true,
|
||||
isESOCanEncrypt: false,
|
||||
actionTypeRegistry: actionTypeRegistryMock.create(),
|
||||
preconfiguredActions: [],
|
||||
});
|
||||
|
@ -173,7 +173,7 @@ describe('execute()', () => {
|
|||
apiKey: null,
|
||||
})
|
||||
).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 executeFn = createExecutionEnqueuerFunction({
|
||||
taskManager: mockTaskManager,
|
||||
isESOUsingEphemeralEncryptionKey: false,
|
||||
isESOCanEncrypt: true,
|
||||
actionTypeRegistry: mockedActionTypeRegistry,
|
||||
preconfiguredActions: [],
|
||||
});
|
||||
|
@ -211,7 +211,7 @@ describe('execute()', () => {
|
|||
const mockedActionTypeRegistry = actionTypeRegistryMock.create();
|
||||
const executeFn = createExecutionEnqueuerFunction({
|
||||
taskManager: mockTaskManager,
|
||||
isESOUsingEphemeralEncryptionKey: false,
|
||||
isESOCanEncrypt: true,
|
||||
actionTypeRegistry: mockedActionTypeRegistry,
|
||||
preconfiguredActions: [
|
||||
{
|
||||
|
|
|
@ -14,7 +14,7 @@ import { isSavedObjectExecutionSource } from './lib';
|
|||
|
||||
interface CreateExecuteFunctionOptions {
|
||||
taskManager: TaskManagerStartContract;
|
||||
isESOUsingEphemeralEncryptionKey: boolean;
|
||||
isESOCanEncrypt: boolean;
|
||||
actionTypeRegistry: ActionTypeRegistryContract;
|
||||
preconfiguredActions: PreConfiguredAction[];
|
||||
}
|
||||
|
@ -33,16 +33,16 @@ export type ExecutionEnqueuer = (
|
|||
export function createExecutionEnqueuerFunction({
|
||||
taskManager,
|
||||
actionTypeRegistry,
|
||||
isESOUsingEphemeralEncryptionKey,
|
||||
isESOCanEncrypt,
|
||||
preconfiguredActions,
|
||||
}: CreateExecuteFunctionOptions) {
|
||||
return async function execute(
|
||||
unsecuredSavedObjectsClient: SavedObjectsClientContract,
|
||||
{ id, params, spaceId, source, apiKey }: ExecuteOptions
|
||||
) {
|
||||
if (isESOUsingEphemeralEncryptionKey === true) {
|
||||
if (!isESOCanEncrypt) {
|
||||
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 { pick } from 'lodash';
|
||||
|
||||
const actionExecutor = new ActionExecutor({ isESOUsingEphemeralEncryptionKey: false });
|
||||
const actionExecutor = new ActionExecutor({ isESOCanEncrypt: true });
|
||||
const services = actionsMock.createServices();
|
||||
|
||||
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 () => {
|
||||
const customActionExecutor = new ActionExecutor({ isESOUsingEphemeralEncryptionKey: true });
|
||||
test('throws an error when passing isESOCanEncrypt with value of false', async () => {
|
||||
const customActionExecutor = new ActionExecutor({ isESOCanEncrypt: false });
|
||||
customActionExecutor.initialize({
|
||||
logger: loggingSystemMock.create().get(),
|
||||
spaces: spacesMock,
|
||||
|
@ -325,7 +325,7 @@ test('throws an error when passing isESOUsingEphemeralEncryptionKey with value o
|
|||
await expect(
|
||||
customActionExecutor.execute(executeParams)
|
||||
).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 {
|
||||
private isInitialized = false;
|
||||
private actionExecutorContext?: ActionExecutorContext;
|
||||
private readonly isESOUsingEphemeralEncryptionKey: boolean;
|
||||
private readonly isESOCanEncrypt: boolean;
|
||||
|
||||
constructor({ isESOUsingEphemeralEncryptionKey }: { isESOUsingEphemeralEncryptionKey: boolean }) {
|
||||
this.isESOUsingEphemeralEncryptionKey = isESOUsingEphemeralEncryptionKey;
|
||||
constructor({ isESOCanEncrypt }: { isESOCanEncrypt: boolean }) {
|
||||
this.isESOCanEncrypt = isESOCanEncrypt;
|
||||
}
|
||||
|
||||
public initialize(actionExecutorContext: ActionExecutorContext) {
|
||||
|
@ -72,9 +72,9 @@ export class ActionExecutor {
|
|||
throw new Error('ActionExecutor not initialized');
|
||||
}
|
||||
|
||||
if (this.isESOUsingEphemeralEncryptionKey === true) {
|
||||
if (!this.isESOCanEncrypt) {
|
||||
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`, () => {
|
||||
const factory = new TaskRunnerFactory(
|
||||
new ActionExecutor({ isESOUsingEphemeralEncryptionKey: false })
|
||||
);
|
||||
const factory = new TaskRunnerFactory(new ActionExecutor({ isESOCanEncrypt: true }));
|
||||
expect(() =>
|
||||
factory.create({ taskInstance: mockedTaskInstance })
|
||||
).toThrowErrorMatchingInlineSnapshot(`"TaskRunnerFactory not initialized"`);
|
||||
});
|
||||
|
||||
test(`throws an error if factory is already initialized`, () => {
|
||||
const factory = new TaskRunnerFactory(
|
||||
new ActionExecutor({ isESOUsingEphemeralEncryptionKey: false })
|
||||
);
|
||||
const factory = new TaskRunnerFactory(new ActionExecutor({ isESOCanEncrypt: true }));
|
||||
factory.initialize(taskRunnerFactoryInitializerParams);
|
||||
expect(() =>
|
||||
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 () => {
|
||||
// coreMock.createSetup doesn't support Plugin generics
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||
await plugin.setup(coreSetup as any, pluginsSetup);
|
||||
expect(pluginsSetup.encryptedSavedObjects.usingEphemeralEncryptionKey).toEqual(true);
|
||||
it('should log warning when Encrypted Saved Objects plugin is missing encryption key', async () => {
|
||||
await plugin.setup(coreSetup, pluginsSetup);
|
||||
expect(pluginsSetup.encryptedSavedObjects.canEncrypt).toEqual(false);
|
||||
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()', () => {
|
||||
it('should not throw error when ESO plugin not using a generated key', async () => {
|
||||
// coreMock.createSetup doesn't support Plugin generics
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||
await plugin.setup(coreSetup as any, {
|
||||
it('should not throw error when ESO plugin has encryption key', async () => {
|
||||
await plugin.setup(coreSetup, {
|
||||
...pluginsSetup,
|
||||
encryptedSavedObjects: {
|
||||
...pluginsSetup.encryptedSavedObjects,
|
||||
usingEphemeralEncryptionKey: false,
|
||||
canEncrypt: true,
|
||||
},
|
||||
});
|
||||
|
||||
|
@ -99,10 +95,8 @@ describe('Actions Plugin', () => {
|
|||
actionsContextHandler!.getActionsClient();
|
||||
});
|
||||
|
||||
it('should throw error when ESO plugin using a generated key', async () => {
|
||||
// coreMock.createSetup doesn't support Plugin generics
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||
await plugin.setup(coreSetup as any, pluginsSetup);
|
||||
it('should throw error when ESO plugin is missing encryption key', async () => {
|
||||
await plugin.setup(coreSetup, pluginsSetup);
|
||||
|
||||
expect(coreSetup.http.registerRouteHandlerContext).toHaveBeenCalledTimes(1);
|
||||
const handler = coreSetup.http.registerRouteHandlerContext.mock.calls[0] as [
|
||||
|
@ -123,7 +117,7 @@ describe('Actions Plugin', () => {
|
|||
httpServerMock.createResponseFactory()
|
||||
)) as unknown) as ActionsApiRequestHandlerContext;
|
||||
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);
|
||||
});
|
||||
|
||||
it('should not throw error when ESO plugin not using a generated key', async () => {
|
||||
// coreMock.createSetup doesn't support Plugin generics
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||
await plugin.setup(coreSetup as any, {
|
||||
it('should not throw error when ESO plugin has encryption key', async () => {
|
||||
await plugin.setup(coreSetup, {
|
||||
...pluginsSetup,
|
||||
encryptedSavedObjects: {
|
||||
...pluginsSetup.encryptedSavedObjects,
|
||||
usingEphemeralEncryptionKey: false,
|
||||
canEncrypt: true,
|
||||
},
|
||||
});
|
||||
const pluginStart = await plugin.start(coreStart, pluginsStart);
|
||||
|
@ -249,17 +241,15 @@ describe('Actions Plugin', () => {
|
|||
await pluginStart.getActionsClientWithRequest(httpServerMock.createKibanaRequest());
|
||||
});
|
||||
|
||||
it('should throw error when ESO plugin using generated key', async () => {
|
||||
// coreMock.createSetup doesn't support Plugin generics
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||
await plugin.setup(coreSetup as any, pluginsSetup);
|
||||
it('should throw error when ESO plugin is missing encryption key', async () => {
|
||||
await plugin.setup(coreSetup, pluginsSetup);
|
||||
const pluginStart = await plugin.start(coreStart, pluginsStart);
|
||||
|
||||
expect(pluginsSetup.encryptedSavedObjects.usingEphemeralEncryptionKey).toEqual(true);
|
||||
expect(pluginsSetup.encryptedSavedObjects.canEncrypt).toEqual(false);
|
||||
await expect(
|
||||
pluginStart.getActionsClientWithRequest(httpServerMock.createKibanaRequest())
|
||||
).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 eventLogService?: IEventLogService;
|
||||
private eventLogger?: IEventLogger;
|
||||
private isESOUsingEphemeralEncryptionKey?: boolean;
|
||||
private isESOCanEncrypt?: boolean;
|
||||
private readonly telemetryLogger: Logger;
|
||||
private readonly preconfiguredActions: PreConfiguredAction[];
|
||||
private readonly kibanaIndexConfig: { kibana: { index: string } };
|
||||
|
@ -162,12 +162,11 @@ export class ActionsPlugin implements Plugin<PluginSetupContract, PluginStartCon
|
|||
plugins: ActionsPluginsSetup
|
||||
): PluginSetupContract {
|
||||
this.licenseState = new LicenseState(plugins.licensing.license$);
|
||||
this.isESOUsingEphemeralEncryptionKey =
|
||||
plugins.encryptedSavedObjects.usingEphemeralEncryptionKey;
|
||||
this.isESOCanEncrypt = plugins.encryptedSavedObjects.canEncrypt;
|
||||
|
||||
if (this.isESOUsingEphemeralEncryptionKey) {
|
||||
if (!this.isESOCanEncrypt) {
|
||||
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({
|
||||
isESOUsingEphemeralEncryptionKey: this.isESOUsingEphemeralEncryptionKey,
|
||||
isESOCanEncrypt: this.isESOCanEncrypt,
|
||||
});
|
||||
|
||||
// get executions count
|
||||
|
@ -270,7 +269,7 @@ export class ActionsPlugin implements Plugin<PluginSetupContract, PluginStartCon
|
|||
actionTypeRegistry,
|
||||
taskRunnerFactory,
|
||||
kibanaIndexConfig,
|
||||
isESOUsingEphemeralEncryptionKey,
|
||||
isESOCanEncrypt,
|
||||
preconfiguredActions,
|
||||
instantiateAuthorization,
|
||||
getUnsecuredSavedObjectsClient,
|
||||
|
@ -286,9 +285,9 @@ export class ActionsPlugin implements Plugin<PluginSetupContract, PluginStartCon
|
|||
request: KibanaRequest,
|
||||
authorizationContext?: ActionExecutionSource<unknown>
|
||||
) => {
|
||||
if (isESOUsingEphemeralEncryptionKey === true) {
|
||||
if (isESOCanEncrypt !== true) {
|
||||
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({
|
||||
taskManager: plugins.taskManager,
|
||||
actionTypeRegistry: actionTypeRegistry!,
|
||||
isESOUsingEphemeralEncryptionKey: isESOUsingEphemeralEncryptionKey!,
|
||||
isESOCanEncrypt: isESOCanEncrypt!,
|
||||
preconfiguredActions,
|
||||
}),
|
||||
auditLogger: this.security?.audit.asScoped(request),
|
||||
|
@ -437,7 +436,7 @@ export class ActionsPlugin implements Plugin<PluginSetupContract, PluginStartCon
|
|||
): IContextProvider<ActionsRequestHandlerContext, 'actions'> => {
|
||||
const {
|
||||
actionTypeRegistry,
|
||||
isESOUsingEphemeralEncryptionKey,
|
||||
isESOCanEncrypt,
|
||||
preconfiguredActions,
|
||||
actionExecutor,
|
||||
instantiateAuthorization,
|
||||
|
@ -448,9 +447,9 @@ export class ActionsPlugin implements Plugin<PluginSetupContract, PluginStartCon
|
|||
const [{ savedObjects }, { taskManager }] = await core.getStartServices();
|
||||
return {
|
||||
getActionsClient: () => {
|
||||
if (isESOUsingEphemeralEncryptionKey === true) {
|
||||
if (isESOCanEncrypt !== true) {
|
||||
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({
|
||||
|
@ -468,7 +467,7 @@ export class ActionsPlugin implements Plugin<PluginSetupContract, PluginStartCon
|
|||
executionEnqueuer: createExecutionEnqueuerFunction({
|
||||
taskManager,
|
||||
actionTypeRegistry: actionTypeRegistry!,
|
||||
isESOUsingEphemeralEncryptionKey: isESOUsingEphemeralEncryptionKey!,
|
||||
isESOCanEncrypt: isESOCanEncrypt!,
|
||||
preconfiguredActions,
|
||||
}),
|
||||
auditLogger: security?.audit.asScoped(request),
|
||||
|
|
|
@ -25,7 +25,7 @@ describe('Alerting Plugin', () => {
|
|||
let coreSetup: ReturnType<typeof coreMock.createSetup>;
|
||||
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>({
|
||||
healthCheck: {
|
||||
interval: '5m',
|
||||
|
@ -40,7 +40,7 @@ describe('Alerting Plugin', () => {
|
|||
const encryptedSavedObjectsSetup = encryptedSavedObjectsMock.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, {
|
||||
licensing: licensingMock.createSetup(),
|
||||
encryptedSavedObjects: encryptedSavedObjectsSetup,
|
||||
|
@ -51,9 +51,9 @@ describe('Alerting Plugin', () => {
|
|||
});
|
||||
|
||||
expect(setupMocks.status.set).toHaveBeenCalledTimes(1);
|
||||
expect(encryptedSavedObjectsSetup.usingEphemeralEncryptionKey).toEqual(true);
|
||||
expect(encryptedSavedObjectsSetup.canEncrypt).toEqual(false);
|
||||
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('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>({
|
||||
healthCheck: {
|
||||
interval: '5m',
|
||||
|
@ -141,15 +141,15 @@ describe('Alerting Plugin', () => {
|
|||
taskManager: taskManagerMock.createStart(),
|
||||
});
|
||||
|
||||
expect(encryptedSavedObjectsSetup.usingEphemeralEncryptionKey).toEqual(true);
|
||||
expect(encryptedSavedObjectsSetup.canEncrypt).toEqual(false);
|
||||
expect(() =>
|
||||
startContract.getAlertsClientWithRequest({} as KibanaRequest)
|
||||
).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>({
|
||||
healthCheck: {
|
||||
interval: '5m',
|
||||
|
@ -163,7 +163,7 @@ describe('Alerting Plugin', () => {
|
|||
|
||||
const encryptedSavedObjectsSetup = {
|
||||
...encryptedSavedObjectsMock.createSetup(),
|
||||
usingEphemeralEncryptionKey: false,
|
||||
canEncrypt: true,
|
||||
};
|
||||
plugin.setup(coreMock.createSetup(), {
|
||||
licensing: licensingMock.createSetup(),
|
||||
|
|
|
@ -153,7 +153,7 @@ export class AlertingPlugin {
|
|||
private alertTypeRegistry?: AlertTypeRegistry;
|
||||
private readonly taskRunnerFactory: TaskRunnerFactory;
|
||||
private licenseState: ILicenseState | null = null;
|
||||
private isESOUsingEphemeralEncryptionKey?: boolean;
|
||||
private isESOCanEncrypt?: boolean;
|
||||
private security?: SecurityPluginSetup;
|
||||
private readonly alertsClientFactory: AlertsClientFactory;
|
||||
private readonly telemetryLogger: Logger;
|
||||
|
@ -189,12 +189,11 @@ export class AlertingPlugin {
|
|||
};
|
||||
});
|
||||
|
||||
this.isESOUsingEphemeralEncryptionKey =
|
||||
plugins.encryptedSavedObjects.usingEphemeralEncryptionKey;
|
||||
this.isESOCanEncrypt = plugins.encryptedSavedObjects.canEncrypt;
|
||||
|
||||
if (this.isESOUsingEphemeralEncryptionKey) {
|
||||
if (!this.isESOCanEncrypt) {
|
||||
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 {
|
||||
const {
|
||||
isESOUsingEphemeralEncryptionKey,
|
||||
isESOCanEncrypt,
|
||||
logger,
|
||||
taskRunnerFactory,
|
||||
alertTypeRegistry,
|
||||
|
@ -353,9 +352,9 @@ export class AlertingPlugin {
|
|||
});
|
||||
|
||||
const getAlertsClientWithRequest = (request: KibanaRequest) => {
|
||||
if (isESOUsingEphemeralEncryptionKey === true) {
|
||||
if (isESOCanEncrypt !== true) {
|
||||
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);
|
||||
|
|
|
@ -47,8 +47,7 @@ describe('healthRoute', () => {
|
|||
const router = httpServiceMock.createRouter();
|
||||
|
||||
const licenseState = licenseStateMock.create();
|
||||
const encryptedSavedObjects = encryptedSavedObjectsMock.createSetup();
|
||||
encryptedSavedObjects.usingEphemeralEncryptionKey = false;
|
||||
const encryptedSavedObjects = encryptedSavedObjectsMock.createSetup({ canEncrypt: true });
|
||||
healthRoute(router, licenseState, encryptedSavedObjects);
|
||||
|
||||
const [config] = router.get.mock.calls[0];
|
||||
|
@ -60,8 +59,7 @@ describe('healthRoute', () => {
|
|||
const router = httpServiceMock.createRouter();
|
||||
|
||||
const licenseState = licenseStateMock.create();
|
||||
const encryptedSavedObjects = encryptedSavedObjectsMock.createSetup();
|
||||
encryptedSavedObjects.usingEphemeralEncryptionKey = false;
|
||||
const encryptedSavedObjects = encryptedSavedObjectsMock.createSetup({ canEncrypt: true });
|
||||
healthRoute(router, licenseState, encryptedSavedObjects);
|
||||
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 licenseState = licenseStateMock.create();
|
||||
const encryptedSavedObjects = encryptedSavedObjectsMock.createSetup();
|
||||
encryptedSavedObjects.usingEphemeralEncryptionKey = true;
|
||||
const encryptedSavedObjects = encryptedSavedObjectsMock.createSetup({ canEncrypt: false });
|
||||
healthRoute(router, licenseState, encryptedSavedObjects);
|
||||
const [, handler] = router.get.mock.calls[0];
|
||||
|
||||
|
@ -129,8 +126,7 @@ describe('healthRoute', () => {
|
|||
const router = httpServiceMock.createRouter();
|
||||
|
||||
const licenseState = licenseStateMock.create();
|
||||
const encryptedSavedObjects = encryptedSavedObjectsMock.createSetup();
|
||||
encryptedSavedObjects.usingEphemeralEncryptionKey = false;
|
||||
const encryptedSavedObjects = encryptedSavedObjectsMock.createSetup({ canEncrypt: true });
|
||||
healthRoute(router, licenseState, encryptedSavedObjects);
|
||||
const [, handler] = router.get.mock.calls[0];
|
||||
|
||||
|
@ -169,8 +165,7 @@ describe('healthRoute', () => {
|
|||
const router = httpServiceMock.createRouter();
|
||||
|
||||
const licenseState = licenseStateMock.create();
|
||||
const encryptedSavedObjects = encryptedSavedObjectsMock.createSetup();
|
||||
encryptedSavedObjects.usingEphemeralEncryptionKey = false;
|
||||
const encryptedSavedObjects = encryptedSavedObjectsMock.createSetup({ canEncrypt: true });
|
||||
healthRoute(router, licenseState, encryptedSavedObjects);
|
||||
const [, handler] = router.get.mock.calls[0];
|
||||
|
||||
|
@ -209,8 +204,7 @@ describe('healthRoute', () => {
|
|||
const router = httpServiceMock.createRouter();
|
||||
|
||||
const licenseState = licenseStateMock.create();
|
||||
const encryptedSavedObjects = encryptedSavedObjectsMock.createSetup();
|
||||
encryptedSavedObjects.usingEphemeralEncryptionKey = false;
|
||||
const encryptedSavedObjects = encryptedSavedObjectsMock.createSetup({ canEncrypt: true });
|
||||
healthRoute(router, licenseState, encryptedSavedObjects);
|
||||
const [, handler] = router.get.mock.calls[0];
|
||||
|
||||
|
@ -249,8 +243,7 @@ describe('healthRoute', () => {
|
|||
const router = httpServiceMock.createRouter();
|
||||
|
||||
const licenseState = licenseStateMock.create();
|
||||
const encryptedSavedObjects = encryptedSavedObjectsMock.createSetup();
|
||||
encryptedSavedObjects.usingEphemeralEncryptionKey = false;
|
||||
const encryptedSavedObjects = encryptedSavedObjectsMock.createSetup({ canEncrypt: true });
|
||||
healthRoute(router, licenseState, encryptedSavedObjects);
|
||||
const [, handler] = router.get.mock.calls[0];
|
||||
|
||||
|
@ -291,8 +284,7 @@ describe('healthRoute', () => {
|
|||
const router = httpServiceMock.createRouter();
|
||||
|
||||
const licenseState = licenseStateMock.create();
|
||||
const encryptedSavedObjects = encryptedSavedObjectsMock.createSetup();
|
||||
encryptedSavedObjects.usingEphemeralEncryptionKey = false;
|
||||
const encryptedSavedObjects = encryptedSavedObjectsMock.createSetup({ canEncrypt: true });
|
||||
healthRoute(router, licenseState, encryptedSavedObjects);
|
||||
const [, handler] = router.get.mock.calls[0];
|
||||
|
||||
|
|
|
@ -55,7 +55,7 @@ export function healthRoute(
|
|||
|
||||
const frameworkHealth: AlertingFrameworkHealth = {
|
||||
isSufficientlySecure: !isSecurityEnabled || (isSecurityEnabled && isTLSEnabled),
|
||||
hasPermanentEncryptionKey: !encryptedSavedObjects.usingEphemeralEncryptionKey,
|
||||
hasPermanentEncryptionKey: encryptedSavedObjects.canEncrypt,
|
||||
alertingFrameworkHeath,
|
||||
};
|
||||
|
||||
|
|
|
@ -5,10 +5,7 @@
|
|||
* 2.0.
|
||||
*/
|
||||
|
||||
jest.mock('crypto', () => ({ randomBytes: jest.fn() }));
|
||||
|
||||
import { loggingSystemMock } from 'src/core/server/mocks';
|
||||
import { createConfig, ConfigSchema } from './config';
|
||||
import { ConfigSchema } from './config';
|
||||
|
||||
describe('config schema', () => {
|
||||
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(`
|
||||
Object {
|
||||
"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', () => {
|
||||
expect(() =>
|
||||
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.
|
||||
*/
|
||||
|
||||
import crypto from 'crypto';
|
||||
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(
|
||||
{
|
||||
|
@ -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', () => {
|
||||
|
@ -465,6 +531,58 @@ describe('#encryptAttributes', () => {
|
|||
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', () => {
|
||||
|
@ -1099,6 +1217,88 @@ describe('#decryptAttributes', () => {
|
|||
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', () => {
|
||||
|
@ -1283,6 +1483,58 @@ describe('#encryptAttributesSync', () => {
|
|||
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', () => {
|
||||
|
@ -1784,4 +2036,86 @@ describe('#decryptAttributesSync', () => {
|
|||
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.
|
||||
*/
|
||||
primaryCrypto: Crypto;
|
||||
primaryCrypto?: Crypto;
|
||||
|
||||
/**
|
||||
* NodeCrypto instances used ONLY for decryption (i.e. rotated encryption keys).
|
||||
|
@ -293,6 +293,8 @@ export class EncryptedSavedObjectsService {
|
|||
let iteratorResult = iterator.next();
|
||||
while (!iteratorResult.done) {
|
||||
const [attributeValue, encryptionAAD] = iteratorResult.value;
|
||||
// We check this inside of the iterator to throw only if we do need to encrypt anything.
|
||||
if (this.options.primaryCrypto) {
|
||||
try {
|
||||
iteratorResult = iterator.next(
|
||||
await this.options.primaryCrypto.encrypt(attributeValue, encryptionAAD)
|
||||
|
@ -300,6 +302,9 @@ export class EncryptedSavedObjectsService {
|
|||
} catch (err) {
|
||||
iterator.throw!(err);
|
||||
}
|
||||
} else {
|
||||
iterator.throw!(new Error('Encryption is disabled because of missing encryption key.'));
|
||||
}
|
||||
}
|
||||
|
||||
return iteratorResult.value;
|
||||
|
@ -324,6 +329,8 @@ export class EncryptedSavedObjectsService {
|
|||
let iteratorResult = iterator.next();
|
||||
while (!iteratorResult.done) {
|
||||
const [attributeValue, encryptionAAD] = iteratorResult.value;
|
||||
// We check this inside of the iterator to throw only if we do need to encrypt anything.
|
||||
if (this.options.primaryCrypto) {
|
||||
try {
|
||||
iteratorResult = iterator.next(
|
||||
this.options.primaryCrypto.encryptSync(attributeValue, encryptionAAD)
|
||||
|
@ -331,6 +338,9 @@ export class EncryptedSavedObjectsService {
|
|||
} catch (err) {
|
||||
iterator.throw!(err);
|
||||
}
|
||||
} else {
|
||||
iterator.throw!(new Error('Encryption is disabled because of missing encryption key.'));
|
||||
}
|
||||
}
|
||||
|
||||
return iteratorResult.value;
|
||||
|
@ -358,7 +368,11 @@ export class EncryptedSavedObjectsService {
|
|||
while (!iteratorResult.done) {
|
||||
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) {
|
||||
try {
|
||||
iteratorResult = iterator.next(await decrypter.decrypt(attributeValue, encryptionAAD));
|
||||
|
@ -402,7 +416,11 @@ export class EncryptedSavedObjectsService {
|
|||
while (!iteratorResult.done) {
|
||||
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) {
|
||||
try {
|
||||
iteratorResult = iterator.next(decrypter.decryptSync(attributeValue, encryptionAAD));
|
||||
|
@ -541,6 +559,9 @@ export class EncryptedSavedObjectsService {
|
|||
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 { EncryptedSavedObjectsClient, EncryptedSavedObjectsClientOptions } from './saved_objects';
|
||||
|
||||
function createEncryptedSavedObjectsSetupMock() {
|
||||
function createEncryptedSavedObjectsSetupMock(
|
||||
{ canEncrypt }: { canEncrypt: boolean } = { canEncrypt: false }
|
||||
) {
|
||||
return {
|
||||
registerType: jest.fn(),
|
||||
__legacyCompat: { registerLegacyAPI: jest.fn() },
|
||||
usingEphemeralEncryptionKey: true,
|
||||
canEncrypt,
|
||||
createMigration: jest.fn(),
|
||||
} as jest.Mocked<EncryptedSavedObjectsPluginSetup>;
|
||||
}
|
||||
|
|
|
@ -20,9 +20,25 @@ describe('EncryptedSavedObjects Plugin', () => {
|
|||
expect(plugin.setup(coreMock.createSetup(), { security: securityMock.createSetup() }))
|
||||
.toMatchInlineSnapshot(`
|
||||
Object {
|
||||
"canEncrypt": false,
|
||||
"createMigration": [Function],
|
||||
"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],
|
||||
"usingEphemeralEncryptionKey": true,
|
||||
}
|
||||
`);
|
||||
});
|
||||
|
|
|
@ -6,10 +6,9 @@
|
|||
*/
|
||||
|
||||
import nodeCrypto from '@elastic/node-crypto';
|
||||
import { Logger, PluginInitializerContext, CoreSetup, Plugin } from 'src/core/server';
|
||||
import { TypeOf } from '@kbn/config-schema';
|
||||
import { SecurityPluginSetup } from '../../security/server';
|
||||
import { createConfig, ConfigSchema } from './config';
|
||||
import type { Logger, PluginInitializerContext, CoreSetup, Plugin } from 'src/core/server';
|
||||
import type { SecurityPluginSetup } from '../../security/server';
|
||||
import type { ConfigType } from './config';
|
||||
import {
|
||||
EncryptedSavedObjectsService,
|
||||
EncryptedSavedObjectTypeRegistration,
|
||||
|
@ -26,8 +25,11 @@ export interface PluginsSetup {
|
|||
}
|
||||
|
||||
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;
|
||||
usingEphemeralEncryptionKey: boolean;
|
||||
createMigration: CreateEncryptedSavedObjectsMigrationFn;
|
||||
}
|
||||
|
||||
|
@ -50,19 +52,24 @@ export class EncryptedSavedObjectsPlugin
|
|||
}
|
||||
|
||||
public setup(core: CoreSetup, deps: PluginsSetup): EncryptedSavedObjectsPluginSetup {
|
||||
const config = createConfig(
|
||||
this.initializerContext.config.get<TypeOf<typeof ConfigSchema>>(),
|
||||
this.initializerContext.logger.get('config')
|
||||
const config = this.initializerContext.config.get<ConfigType>();
|
||||
const canEncrypt = config.encryptionKey !== undefined;
|
||||
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(
|
||||
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(
|
||||
new EncryptedSavedObjectsService({
|
||||
primaryCrypto,
|
||||
|
@ -94,9 +101,9 @@ export class EncryptedSavedObjectsPlugin
|
|||
});
|
||||
|
||||
return {
|
||||
canEncrypt,
|
||||
registerType: (typeRegistration: EncryptedSavedObjectTypeRegistration) =>
|
||||
service.registerType(typeRegistration),
|
||||
usingEphemeralEncryptionKey: config.usingEphemeralEncryptionKey,
|
||||
createMigration: getCreateMigration(
|
||||
service,
|
||||
(typeRegistration: EncryptedSavedObjectTypeRegistration) => {
|
||||
|
|
|
@ -5,7 +5,7 @@
|
|||
* 2.0.
|
||||
*/
|
||||
|
||||
import { ConfigSchema, createConfig } from '../config';
|
||||
import { ConfigSchema, ConfigType } from '../config';
|
||||
|
||||
import { httpServiceMock, loggingSystemMock } from '../../../../../src/core/server/mocks';
|
||||
import { encryptionKeyRotationServiceMock } from '../crypto/index.mock';
|
||||
|
@ -14,7 +14,7 @@ export const routeDefinitionParamsMock = {
|
|||
create: (config: Record<string, unknown> = {}) => ({
|
||||
router: httpServiceMock.createRouter(),
|
||||
logger: loggingSystemMock.create().get(),
|
||||
config: createConfig(ConfigSchema.validate(config), loggingSystemMock.create().get()),
|
||||
config: ConfigSchema.validate(config) as ConfigType,
|
||||
encryptionKeyRotationService: encryptionKeyRotationServiceMock.create(),
|
||||
}),
|
||||
};
|
||||
|
|
|
@ -4,14 +4,13 @@
|
|||
"server": true,
|
||||
"ui": true,
|
||||
"configPath": ["xpack", "fleet"],
|
||||
"requiredPlugins": ["licensing", "data"],
|
||||
"requiredPlugins": ["licensing", "data", "encryptedSavedObjects"],
|
||||
"optionalPlugins": [
|
||||
"security",
|
||||
"features",
|
||||
"cloud",
|
||||
"usageCollection",
|
||||
"home",
|
||||
"encryptedSavedObjects"
|
||||
"home"
|
||||
],
|
||||
"extraPublicDirs": ["common"],
|
||||
"requiredBundles": ["kibanaReact", "esUiShared", "home", "infra", "kibanaUtils"]
|
||||
|
|
|
@ -95,7 +95,7 @@ export interface FleetSetupDeps {
|
|||
}
|
||||
|
||||
export interface FleetStartDeps {
|
||||
encryptedSavedObjects?: EncryptedSavedObjectsPluginStart;
|
||||
encryptedSavedObjects: EncryptedSavedObjectsPluginStart;
|
||||
security?: SecurityPluginStart;
|
||||
}
|
||||
|
||||
|
@ -255,11 +255,11 @@ export class FleetPlugin
|
|||
|
||||
// Conditional config routes
|
||||
if (config.agents.enabled) {
|
||||
const isESOUsingEphemeralEncryptionKey = !deps.encryptedSavedObjects;
|
||||
if (isESOUsingEphemeralEncryptionKey) {
|
||||
const isESOCanEncrypt = deps.encryptedSavedObjects.canEncrypt;
|
||||
if (!isESOCanEncrypt) {
|
||||
if (this.logger) {
|
||||
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 {
|
||||
|
|
|
@ -24,7 +24,7 @@ export const getFleetStatusHandler: RequestHandler = async (context, request, re
|
|||
const isProductionMode = appContextService.getIsProductionMode();
|
||||
const isCloud = appContextService.getCloud()?.isCloudEnabled ?? false;
|
||||
const isTLSCheckDisabled = appContextService.getConfig()?.agents?.tlsCheckDisabled ?? false;
|
||||
const isUsingEphemeralEncryptionKey = !appContextService.getEncryptedSavedObjectsSetup();
|
||||
const canEncrypt = appContextService.getEncryptedSavedObjectsSetup()?.canEncrypt === true;
|
||||
|
||||
const missingRequirements: GetFleetStatusResponse['missing_requirements'] = [];
|
||||
if (!isAdminUserSetup) {
|
||||
|
@ -37,7 +37,7 @@ export const getFleetStatusHandler: RequestHandler = async (context, request, re
|
|||
missingRequirements.push('tls_required');
|
||||
}
|
||||
|
||||
if (isUsingEphemeralEncryptionKey) {
|
||||
if (!canEncrypt) {
|
||||
missingRequirements.push('encrypted_saved_object_encryption_key_required');
|
||||
}
|
||||
|
||||
|
|
|
@ -44,7 +44,7 @@ export class AlertingSecurity {
|
|||
|
||||
return {
|
||||
isSufficientlySecure: !isSecurityEnabled || (isSecurityEnabled && isTLSEnabled),
|
||||
hasPermanentEncryptionKey: Boolean(encryptedSavedObjects),
|
||||
hasPermanentEncryptionKey: encryptedSavedObjects?.canEncrypt === true,
|
||||
};
|
||||
};
|
||||
}
|
||||
|
|
|
@ -18,7 +18,7 @@ describe('read_privileges route', () => {
|
|||
({ clients, context } = requestContextMock.createTools());
|
||||
|
||||
clients.clusterClient.callAsCurrentUser.mockResolvedValue(getMockPrivilegesResult());
|
||||
readPrivilegesRoute(server.router, false);
|
||||
readPrivilegesRoute(server.router, true);
|
||||
});
|
||||
|
||||
describe('normal status codes', () => {
|
||||
|
|
|
@ -14,7 +14,7 @@ import { readPrivileges } from '../../privileges/read_privileges';
|
|||
|
||||
export const readPrivilegesRoute = (
|
||||
router: SecuritySolutionPluginRouter,
|
||||
usingEphemeralEncryptionKey: boolean
|
||||
hasEncryptionKey: boolean
|
||||
) => {
|
||||
router.get(
|
||||
{
|
||||
|
@ -39,7 +39,7 @@ export const readPrivilegesRoute = (
|
|||
const clusterPrivileges = await readPrivileges(clusterClient.callAsCurrentUser, index);
|
||||
const privileges = merge(clusterPrivileges, {
|
||||
is_authenticated: request.auth.isAuthenticated ?? false,
|
||||
has_encryption_key: !usingEphemeralEncryptionKey,
|
||||
has_encryption_key: hasEncryptionKey,
|
||||
});
|
||||
|
||||
return response.ok({ body: privileges });
|
||||
|
|
|
@ -183,7 +183,7 @@ export class Plugin implements IPlugin<PluginSetup, PluginStart, SetupPlugins, S
|
|||
initRoutes(
|
||||
router,
|
||||
config,
|
||||
plugins.encryptedSavedObjects?.usingEphemeralEncryptionKey ?? false,
|
||||
plugins.encryptedSavedObjects?.canEncrypt === true,
|
||||
plugins.security,
|
||||
plugins.ml
|
||||
);
|
||||
|
|
|
@ -47,7 +47,7 @@ import { getTimelineRoute } from '../lib/timeline/routes/get_timeline_route';
|
|||
export const initRoutes = (
|
||||
router: SecuritySolutionPluginRouter,
|
||||
config: ConfigType,
|
||||
usingEphemeralEncryptionKey: boolean,
|
||||
hasEncryptionKey: boolean,
|
||||
security: SetupPlugins['security'],
|
||||
ml: SetupPlugins['ml']
|
||||
) => {
|
||||
|
@ -102,5 +102,5 @@ export const initRoutes = (
|
|||
readTagsRoute(router);
|
||||
|
||||
// Privileges API to get the generic user privileges
|
||||
readPrivilegesRoute(router, usingEphemeralEncryptionKey);
|
||||
readPrivilegesRoute(router, hasEncryptionKey);
|
||||
};
|
||||
|
|
Loading…
Reference in a new issue