diff --git a/x-pack/plugins/encrypted_saved_objects/server/crypto/encryption_key_rotation_service.ts b/x-pack/plugins/encrypted_saved_objects/server/crypto/encryption_key_rotation_service.ts index d8fa12a3e4973..80275ce3a91e4 100644 --- a/x-pack/plugins/encrypted_saved_objects/server/crypto/encryption_key_rotation_service.ts +++ b/x-pack/plugins/encrypted_saved_objects/server/crypto/encryption_key_rotation_service.ts @@ -205,7 +205,7 @@ export class EncryptionKeyRotationService { } this.options.logger.info( - `Encryption key rotation is completed. ${result.successful} objects out ouf ${result.total} were successfully re-encrypted with the primary encryption key and ${result.failed} objects failed.` + `Encryption key rotation is completed. ${result.successful} objects out of ${result.total} were successfully re-encrypted with the primary encryption key and ${result.failed} objects failed.` ); return result; diff --git a/x-pack/plugins/encrypted_saved_objects/server/plugin.ts b/x-pack/plugins/encrypted_saved_objects/server/plugin.ts index 7aecb03868fa8..e7e0a7b482c03 100644 --- a/x-pack/plugins/encrypted_saved_objects/server/plugin.ts +++ b/x-pack/plugins/encrypted_saved_objects/server/plugin.ts @@ -110,22 +110,21 @@ export class EncryptedSavedObjectsPlugin getStartServices: core.getStartServices, }); - // In the serverless environment, the encryption keys for saved objects is managed internally and never - // exposed to users and administrators, eliminating the need for any public Encrypted Saved Objects HTTP APIs - if (this.initializerContext.env.packageInfo.buildFlavor !== 'serverless') { - defineRoutes({ - router: core.http.createRouter(), - logger: this.initializerContext.logger.get('routes'), - encryptionKeyRotationService: Object.freeze( - new EncryptionKeyRotationService({ - logger: this.logger.get('key-rotation-service'), - service, - getStartServices: core.getStartServices, - }) - ), - config, - }); - } + // Expose the key rotation route for both stateful and serverless environments + // The endpoint requires admin privileges, and is internal only in serverless + defineRoutes({ + router: core.http.createRouter(), + logger: this.initializerContext.logger.get('routes'), + encryptionKeyRotationService: Object.freeze( + new EncryptionKeyRotationService({ + logger: this.logger.get('key-rotation-service'), + service, + getStartServices: core.getStartServices, + }) + ), + config, + buildFlavor: this.initializerContext.env.packageInfo.buildFlavor, + }); return { canEncrypt, diff --git a/x-pack/plugins/encrypted_saved_objects/server/routes/index.mock.ts b/x-pack/plugins/encrypted_saved_objects/server/routes/index.mock.ts index 822427687759f..e6263521f690d 100644 --- a/x-pack/plugins/encrypted_saved_objects/server/routes/index.mock.ts +++ b/x-pack/plugins/encrypted_saved_objects/server/routes/index.mock.ts @@ -5,6 +5,7 @@ * 2.0. */ +import { BuildFlavor } from '@kbn/config'; import { httpServiceMock, loggingSystemMock } from '@kbn/core/server/mocks'; import type { ConfigType } from '../config'; @@ -17,5 +18,6 @@ export const routeDefinitionParamsMock = { logger: loggingSystemMock.create().get(), config: ConfigSchema.validate(config) as ConfigType, encryptionKeyRotationService: encryptionKeyRotationServiceMock.create(), + buildFlavor: 'traditional' as BuildFlavor, }), }; diff --git a/x-pack/plugins/encrypted_saved_objects/server/routes/index.ts b/x-pack/plugins/encrypted_saved_objects/server/routes/index.ts index 28f8dde589c75..14d1933e8b765 100644 --- a/x-pack/plugins/encrypted_saved_objects/server/routes/index.ts +++ b/x-pack/plugins/encrypted_saved_objects/server/routes/index.ts @@ -5,6 +5,7 @@ * 2.0. */ +import { BuildFlavor } from '@kbn/config'; import type { IRouter, Logger } from '@kbn/core/server'; import type { PublicMethodsOf } from '@kbn/utility-types'; @@ -20,6 +21,7 @@ export interface RouteDefinitionParams { logger: Logger; config: ConfigType; encryptionKeyRotationService: PublicMethodsOf; + buildFlavor: BuildFlavor; } export function defineRoutes(params: RouteDefinitionParams) { diff --git a/x-pack/plugins/encrypted_saved_objects/server/routes/key_rotation.ts b/x-pack/plugins/encrypted_saved_objects/server/routes/key_rotation.ts index c9c452cf9a031..e26efbb2a93b3 100644 --- a/x-pack/plugins/encrypted_saved_objects/server/routes/key_rotation.ts +++ b/x-pack/plugins/encrypted_saved_objects/server/routes/key_rotation.ts @@ -23,6 +23,7 @@ export function defineKeyRotationRoutes({ router, logger, config, + buildFlavor, }: RouteDefinitionParams) { let rotationInProgress = false; router.post( @@ -41,6 +42,7 @@ export function defineKeyRotationRoutes({ options: { tags: ['access:rotateEncryptionKey', 'oas-tag:saved objects'], description: `Rotate a key for encrypted saved objects`, + access: buildFlavor === 'serverless' ? 'internal' : undefined, }, }, async (context, request, response) => { diff --git a/x-pack/plugins/encrypted_saved_objects/tsconfig.json b/x-pack/plugins/encrypted_saved_objects/tsconfig.json index 83cdcd6225850..d2115146a4a42 100644 --- a/x-pack/plugins/encrypted_saved_objects/tsconfig.json +++ b/x-pack/plugins/encrypted_saved_objects/tsconfig.json @@ -14,6 +14,7 @@ "@kbn/core-saved-objects-api-server-mocks", "@kbn/core-security-common", "@kbn/test-jest-helpers", + "@kbn/config", ], "exclude": [ "target/**/*", diff --git a/x-pack/test_serverless/api_integration/test_suites/common/platform_security/encrypted_saved_objects.ts b/x-pack/test_serverless/api_integration/test_suites/common/platform_security/encrypted_saved_objects.ts index ad873c2bc9ce4..51a0d3f03be8f 100644 --- a/x-pack/test_serverless/api_integration/test_suites/common/platform_security/encrypted_saved_objects.ts +++ b/x-pack/test_serverless/api_integration/test_suites/common/platform_security/encrypted_saved_objects.ts @@ -5,6 +5,7 @@ * 2.0. */ +import expect from 'expect'; import { FtrProviderContext } from '../../../ftr_provider_context'; import { InternalRequestHeader, RoleCredentials } from '../../../../shared/services'; @@ -24,13 +25,40 @@ export default function ({ getService }: FtrProviderContext) { await svlUserManager.invalidateM2mApiKeyWithRoleScope(roleAuthc); }); describe('route access', () => { - describe('disabled', () => { + describe('internal', () => { it('rotate key', async () => { - const { body, status } = await supertestWithoutAuth + let body: unknown; + let status: number; + + ({ body, status } = await supertestWithoutAuth + .post('/api/encrypted_saved_objects/_rotate_key') + // .set(internalReqHeader) + .set(roleAuthc.apiKeyHeader)); + // svlCommonApi.assertApiNotFound(body, status); + // expect a rejection because we're not using the internal header + expect(body).toEqual({ + statusCode: 400, + error: 'Bad Request', + message: expect.stringContaining('Request must contain a kbn-xsrf header.'), + }); + expect(status).toBe(400); + + ({ body, status } = await supertestWithoutAuth .post('/api/encrypted_saved_objects/_rotate_key') .set(internalReqHeader) - .set(roleAuthc.apiKeyHeader); - svlCommonApi.assertApiNotFound(body, status); + .set(roleAuthc.apiKeyHeader)); + // expect a different, legitimate error when we use the internal header + // the config does not contain decryptionOnlyKeys, so when the API is + // called successfully, it will error for this reason, and not for an + // access or or missing header reason + expect(body).toEqual({ + statusCode: 400, + error: 'Bad Request', + message: expect.stringContaining( + 'Kibana is not configured to support encryption key rotation. Update `kibana.yml` to include `xpack.encryptedSavedObjects.keyRotation.decryptionOnlyKeys` to rotate your encryption keys.' + ), + }); + expect(status).toBe(400); }); }); });