From fd898bc0cbfa22183dd08127d5f2a0a9814b0573 Mon Sep 17 00:00:00 2001 From: Kurt Date: Tue, 9 Jan 2024 18:29:55 -0500 Subject: [PATCH 01/28] Adding check when getting the config for the sec plugin to verify FIPS is enabled in KB and Node --- .../resources/base/bin/kibana-docker | 1 + x-pack/plugins/security/server/config.test.ts | 97 +++++++++++++++++++ x-pack/plugins/security/server/config.ts | 27 +++++- 3 files changed, 124 insertions(+), 1 deletion(-) diff --git a/src/dev/build/tasks/os_packages/docker_generator/resources/base/bin/kibana-docker b/src/dev/build/tasks/os_packages/docker_generator/resources/base/bin/kibana-docker index 3aa057ac1bd37..e0909e4a0fc7f 100755 --- a/src/dev/build/tasks/os_packages/docker_generator/resources/base/bin/kibana-docker +++ b/src/dev/build/tasks/os_packages/docker_generator/resources/base/bin/kibana-docker @@ -382,6 +382,7 @@ kibana_vars=( xpack.security.authc.selector.enabled xpack.security.cookieName xpack.security.encryptionKey + xpack.security.fips_mode.enabled xpack.security.loginAssistanceMessage xpack.security.loginHelp xpack.security.sameSiteCookies diff --git a/x-pack/plugins/security/server/config.test.ts b/x-pack/plugins/security/server/config.test.ts index 04b16aebab9cc..c2edb87c318cf 100644 --- a/x-pack/plugins/security/server/config.test.ts +++ b/x-pack/plugins/security/server/config.test.ts @@ -5,9 +5,13 @@ * 2.0. */ +const mockGetFipsFn = jest.fn(); jest.mock('crypto', () => ({ randomBytes: jest.fn(), constants: jest.requireActual('crypto').constants, + get getFips() { + return mockGetFipsFn; + }, })); jest.mock('@kbn/utils', () => ({ @@ -62,6 +66,9 @@ describe('config schema', () => { "cookieName": "sid", "enabled": true, "encryptionKey": "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa", + "fips_mode": Object { + "enabled": false, + }, "loginAssistanceMessage": "", "public": Object {}, "secureCookies": false, @@ -117,6 +124,9 @@ describe('config schema', () => { "cookieName": "sid", "enabled": true, "encryptionKey": "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa", + "fips_mode": Object { + "enabled": false, + }, "loginAssistanceMessage": "", "public": Object {}, "secureCookies": false, @@ -171,6 +181,9 @@ describe('config schema', () => { }, "cookieName": "sid", "enabled": true, + "fips_mode": Object { + "enabled": false, + }, "loginAssistanceMessage": "", "public": Object {}, "secureCookies": false, @@ -228,6 +241,9 @@ describe('config schema', () => { }, "cookieName": "sid", "enabled": true, + "fips_mode": Object { + "enabled": false, + }, "loginAssistanceMessage": "", "public": Object {}, "secureCookies": false, @@ -2433,3 +2449,84 @@ describe('createConfig()', () => { }); }); }); + +describe('checkFipsConfig', () => { + let mockExit: jest.SpyInstance; + beforeAll(() => { + mockExit = jest.spyOn(process, 'exit').mockImplementation((exitCode) => { + throw new Error(`Fake Exit: ${exitCode}`); + }); + }); + + afterAll(() => { + mockExit.mockRestore(); + }); + + it('should log an error message if FIPS mode is misconfigured - xpack.security.fips_mode.enabled true, Nodejs FIPS mode false', async () => { + const logger = loggingSystemMock.create().get(); + + try { + createConfig(ConfigSchema.validate({ fips_mode: { enabled: true } }), logger, { + isTLSEnabled: true, + }); + } catch (e) { + expect(mockExit).toHaveBeenNthCalledWith(1, 78); + } + + expect(loggingSystemMock.collect(logger).error).toMatchInlineSnapshot(` + Array [ + Array [ + "Configuration mismatch error. xpack.security.fips_mode.enabled is set to true and the configured Node.js environment has FIPS disabled", + ], + ] + `); + }); + + it('should log an error message if FIPS mode is misconfigured - xpack.security.fips_mode.enabled false, Nodejs FIPS mode true', async () => { + mockGetFipsFn.mockImplementationOnce(() => { + return 1; + }); + + const logger = loggingSystemMock.create().get(); + + try { + createConfig(ConfigSchema.validate({ fips_mode: { enabled: false } }), logger, { + isTLSEnabled: true, + }); + } catch (e) { + expect(mockExit).toHaveBeenNthCalledWith(1, 78); + } + + expect(loggingSystemMock.collect(logger).error).toMatchInlineSnapshot(` + Array [ + Array [ + "Configuration mismatch error. xpack.security.fips_mode.enabled is set to false and the configured Node.js environment has FIPS enabled", + ], + ] + `); + }); + + it('should log an info message if FIPS mode is properly configured - xpack.security.fips_mode.enabled true, Nodejs FIPS mode true', async () => { + mockGetFipsFn.mockImplementationOnce(() => { + return 1; + }); + + const logger = loggingSystemMock.create().get(); + + try { + createConfig(ConfigSchema.validate({ fips_mode: { enabled: true } }), logger, { + isTLSEnabled: true, + }); + } catch (e) { + expect(mockExit).toHaveBeenNthCalledWith(1, 78); + } + + expect(loggingSystemMock.collect(logger).info).toMatchInlineSnapshot(` + Array [ + Array [ + "Kibana is running in FIPS mode.", + ], + ] + `); + }); +}); diff --git a/x-pack/plugins/security/server/config.ts b/x-pack/plugins/security/server/config.ts index 68e1c7c2a0964..46814b13de71e 100644 --- a/x-pack/plugins/security/server/config.ts +++ b/x-pack/plugins/security/server/config.ts @@ -5,7 +5,7 @@ * 2.0. */ -import crypto from 'crypto'; +import crypto, { getFips } from 'crypto'; import type { Duration } from 'moment'; import path from 'path'; @@ -311,13 +311,38 @@ export const ConfigSchema = schema.object({ roleMappingManagementEnabled: schema.boolean({ defaultValue: true }), }), }), + fips_mode: schema.object({ + enabled: schema.boolean({ defaultValue: false }), + }), }); +function checkFipsConfig(config: RawConfigType, logger: Logger) { + const isFipsEnabled = config.fips_mode.enabled; + const isNodeRunningWithFipsEnabled = getFips() === 1; + + // Check if FIPS is enabled in either setting + if (isFipsEnabled || isNodeRunningWithFipsEnabled) { + // FIPS must be enabled on both or log and error an exit Kibana + if (isFipsEnabled !== isNodeRunningWithFipsEnabled) { + logger.error( + `Configuration mismatch error. xpack.security.fips_mode.enabled is set to ${isFipsEnabled} and the configured Node.js environment has FIPS ${ + isNodeRunningWithFipsEnabled ? 'enabled' : 'disabled' + }` + ); + process.exit(78); + } else { + logger.info('Kibana is running in FIPS mode.'); + } + } +} + export function createConfig( config: RawConfigType, logger: Logger, { isTLSEnabled }: { isTLSEnabled: boolean } ) { + checkFipsConfig(config, logger); + let encryptionKey = config.encryptionKey; if (encryptionKey === undefined) { logger.warn( From eff7830dc58280604c5d53dee6b6b556c2d70583 Mon Sep 17 00:00:00 2001 From: Kurt Date: Thu, 11 Jan 2024 12:43:11 -0500 Subject: [PATCH 02/28] changing fips_mode -> fipsMode --- .../resources/base/bin/kibana-docker | 2 +- x-pack/plugins/security/server/config.test.ts | 24 +++++++++---------- x-pack/plugins/security/server/config.ts | 6 ++--- 3 files changed, 16 insertions(+), 16 deletions(-) diff --git a/src/dev/build/tasks/os_packages/docker_generator/resources/base/bin/kibana-docker b/src/dev/build/tasks/os_packages/docker_generator/resources/base/bin/kibana-docker index e0909e4a0fc7f..035303fd73ef8 100755 --- a/src/dev/build/tasks/os_packages/docker_generator/resources/base/bin/kibana-docker +++ b/src/dev/build/tasks/os_packages/docker_generator/resources/base/bin/kibana-docker @@ -382,7 +382,7 @@ kibana_vars=( xpack.security.authc.selector.enabled xpack.security.cookieName xpack.security.encryptionKey - xpack.security.fips_mode.enabled + xpack.security.fipsMode.enabled xpack.security.loginAssistanceMessage xpack.security.loginHelp xpack.security.sameSiteCookies diff --git a/x-pack/plugins/security/server/config.test.ts b/x-pack/plugins/security/server/config.test.ts index c2edb87c318cf..1df55905f663b 100644 --- a/x-pack/plugins/security/server/config.test.ts +++ b/x-pack/plugins/security/server/config.test.ts @@ -66,7 +66,7 @@ describe('config schema', () => { "cookieName": "sid", "enabled": true, "encryptionKey": "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa", - "fips_mode": Object { + "fipsMode": Object { "enabled": false, }, "loginAssistanceMessage": "", @@ -124,7 +124,7 @@ describe('config schema', () => { "cookieName": "sid", "enabled": true, "encryptionKey": "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa", - "fips_mode": Object { + "fipsMode": Object { "enabled": false, }, "loginAssistanceMessage": "", @@ -181,7 +181,7 @@ describe('config schema', () => { }, "cookieName": "sid", "enabled": true, - "fips_mode": Object { + "fipsMode": Object { "enabled": false, }, "loginAssistanceMessage": "", @@ -241,7 +241,7 @@ describe('config schema', () => { }, "cookieName": "sid", "enabled": true, - "fips_mode": Object { + "fipsMode": Object { "enabled": false, }, "loginAssistanceMessage": "", @@ -2462,11 +2462,11 @@ describe('checkFipsConfig', () => { mockExit.mockRestore(); }); - it('should log an error message if FIPS mode is misconfigured - xpack.security.fips_mode.enabled true, Nodejs FIPS mode false', async () => { + it('should log an error message if FIPS mode is misconfigured - xpack.security.fipsMode.enabled true, Nodejs FIPS mode false', async () => { const logger = loggingSystemMock.create().get(); try { - createConfig(ConfigSchema.validate({ fips_mode: { enabled: true } }), logger, { + createConfig(ConfigSchema.validate({ fipsMode: { enabled: true } }), logger, { isTLSEnabled: true, }); } catch (e) { @@ -2476,13 +2476,13 @@ describe('checkFipsConfig', () => { expect(loggingSystemMock.collect(logger).error).toMatchInlineSnapshot(` Array [ Array [ - "Configuration mismatch error. xpack.security.fips_mode.enabled is set to true and the configured Node.js environment has FIPS disabled", + "Configuration mismatch error. xpack.security.fipsMode.enabled is set to true and the configured Node.js environment has FIPS disabled", ], ] `); }); - it('should log an error message if FIPS mode is misconfigured - xpack.security.fips_mode.enabled false, Nodejs FIPS mode true', async () => { + it('should log an error message if FIPS mode is misconfigured - xpack.security.fipsMode.enabled false, Nodejs FIPS mode true', async () => { mockGetFipsFn.mockImplementationOnce(() => { return 1; }); @@ -2490,7 +2490,7 @@ describe('checkFipsConfig', () => { const logger = loggingSystemMock.create().get(); try { - createConfig(ConfigSchema.validate({ fips_mode: { enabled: false } }), logger, { + createConfig(ConfigSchema.validate({ fipsMode: { enabled: false } }), logger, { isTLSEnabled: true, }); } catch (e) { @@ -2500,13 +2500,13 @@ describe('checkFipsConfig', () => { expect(loggingSystemMock.collect(logger).error).toMatchInlineSnapshot(` Array [ Array [ - "Configuration mismatch error. xpack.security.fips_mode.enabled is set to false and the configured Node.js environment has FIPS enabled", + "Configuration mismatch error. xpack.security.fipsMode.enabled is set to false and the configured Node.js environment has FIPS enabled", ], ] `); }); - it('should log an info message if FIPS mode is properly configured - xpack.security.fips_mode.enabled true, Nodejs FIPS mode true', async () => { + it('should log an info message if FIPS mode is properly configured - xpack.security.fipsMode.enabled true, Nodejs FIPS mode true', async () => { mockGetFipsFn.mockImplementationOnce(() => { return 1; }); @@ -2514,7 +2514,7 @@ describe('checkFipsConfig', () => { const logger = loggingSystemMock.create().get(); try { - createConfig(ConfigSchema.validate({ fips_mode: { enabled: true } }), logger, { + createConfig(ConfigSchema.validate({ fipsMode: { enabled: true } }), logger, { isTLSEnabled: true, }); } catch (e) { diff --git a/x-pack/plugins/security/server/config.ts b/x-pack/plugins/security/server/config.ts index 46814b13de71e..6a76bb54f7e81 100644 --- a/x-pack/plugins/security/server/config.ts +++ b/x-pack/plugins/security/server/config.ts @@ -311,13 +311,13 @@ export const ConfigSchema = schema.object({ roleMappingManagementEnabled: schema.boolean({ defaultValue: true }), }), }), - fips_mode: schema.object({ + fipsMode: schema.object({ enabled: schema.boolean({ defaultValue: false }), }), }); function checkFipsConfig(config: RawConfigType, logger: Logger) { - const isFipsEnabled = config.fips_mode.enabled; + const isFipsEnabled = config.fipsMode.enabled; const isNodeRunningWithFipsEnabled = getFips() === 1; // Check if FIPS is enabled in either setting @@ -325,7 +325,7 @@ function checkFipsConfig(config: RawConfigType, logger: Logger) { // FIPS must be enabled on both or log and error an exit Kibana if (isFipsEnabled !== isNodeRunningWithFipsEnabled) { logger.error( - `Configuration mismatch error. xpack.security.fips_mode.enabled is set to ${isFipsEnabled} and the configured Node.js environment has FIPS ${ + `Configuration mismatch error. xpack.security.fipsMode.enabled is set to ${isFipsEnabled} and the configured Node.js environment has FIPS ${ isNodeRunningWithFipsEnabled ? 'enabled' : 'disabled' }` ); From eccc9e9bc472ed38d37ac5e8b910dc2f14e2d02e Mon Sep 17 00:00:00 2001 From: Kurt Date: Mon, 22 Jan 2024 14:18:15 -0500 Subject: [PATCH 03/28] Changing expect to log statement to clarify that it shouldnt throw an error for this test --- x-pack/plugins/security/server/config.test.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/x-pack/plugins/security/server/config.test.ts b/x-pack/plugins/security/server/config.test.ts index 1df55905f663b..2aa19fab4cb6c 100644 --- a/x-pack/plugins/security/server/config.test.ts +++ b/x-pack/plugins/security/server/config.test.ts @@ -2518,7 +2518,7 @@ describe('checkFipsConfig', () => { isTLSEnabled: true, }); } catch (e) { - expect(mockExit).toHaveBeenNthCalledWith(1, 78); + logger.error('Should not throw error!'); } expect(loggingSystemMock.collect(logger).info).toMatchInlineSnapshot(` From 683531c11218336517b45f7135a67fb3d530cb0c Mon Sep 17 00:00:00 2001 From: Kurt Date: Thu, 18 Apr 2024 13:34:18 -0400 Subject: [PATCH 04/28] Adding license check for FIPS --- .../src/licensing/license_features.ts | 6 + .../common/licensing/license_service.test.ts | 9 + .../common/licensing/license_service.ts | 3 + .../security/server/fips/fips_service.test.ts | 248 ++++++++++++++++++ .../security/server/fips/fips_service.ts | 84 ++++++ x-pack/plugins/security/server/fips/index.ts | 15 ++ x-pack/plugins/security/server/plugin.ts | 14 + 7 files changed, 379 insertions(+) create mode 100644 x-pack/plugins/security/server/fips/fips_service.test.ts create mode 100644 x-pack/plugins/security/server/fips/fips_service.ts create mode 100644 x-pack/plugins/security/server/fips/index.ts diff --git a/x-pack/packages/security/plugin_types_common/src/licensing/license_features.ts b/x-pack/packages/security/plugin_types_common/src/licensing/license_features.ts index c5f05c83c8e48..30c399b7513a1 100644 --- a/x-pack/packages/security/plugin_types_common/src/licensing/license_features.ts +++ b/x-pack/packages/security/plugin_types_common/src/licensing/license_features.ts @@ -78,4 +78,10 @@ export interface SecurityLicenseFeatures { * Describes the layout of the login form if it's displayed. */ readonly layout?: LoginLayout; + + /** + * Indicates whether we allow FIPS mode + */ + + readonly allowFips: boolean; } diff --git a/x-pack/plugins/security/common/licensing/license_service.test.ts b/x-pack/plugins/security/common/licensing/license_service.test.ts index 8bf9f4a030051..790b00640a088 100644 --- a/x-pack/plugins/security/common/licensing/license_service.test.ts +++ b/x-pack/plugins/security/common/licensing/license_service.test.ts @@ -31,6 +31,7 @@ describe('license features', function () { allowSubFeaturePrivileges: false, allowAuditLogging: false, allowUserProfileCollaboration: false, + allowFips: false, }); }); @@ -55,6 +56,7 @@ describe('license features', function () { allowSubFeaturePrivileges: false, allowAuditLogging: false, allowUserProfileCollaboration: false, + allowFips: false, }); }); @@ -76,6 +78,7 @@ describe('license features', function () { Object { "allowAccessAgreement": false, "allowAuditLogging": false, + "allowFips": false, "allowLogin": false, "allowRbac": false, "allowRoleDocumentLevelSecurity": false, @@ -99,6 +102,7 @@ describe('license features', function () { Object { "allowAccessAgreement": true, "allowAuditLogging": true, + "allowFips": true, "allowLogin": true, "allowRbac": true, "allowRoleDocumentLevelSecurity": true, @@ -141,6 +145,7 @@ describe('license features', function () { allowSubFeaturePrivileges: false, allowAuditLogging: false, allowUserProfileCollaboration: false, + allowFips: false, }); expect(getFeatureSpy).toHaveBeenCalledTimes(1); expect(getFeatureSpy).toHaveBeenCalledWith('security'); @@ -168,6 +173,7 @@ describe('license features', function () { allowSubFeaturePrivileges: false, allowAuditLogging: false, allowUserProfileCollaboration: false, + allowFips: false, }); }); @@ -194,6 +200,7 @@ describe('license features', function () { allowSubFeaturePrivileges: false, allowAuditLogging: false, allowUserProfileCollaboration: true, + allowFips: false, }); }); @@ -220,6 +227,7 @@ describe('license features', function () { allowSubFeaturePrivileges: true, allowAuditLogging: true, allowUserProfileCollaboration: true, + allowFips: false, }); }); @@ -246,6 +254,7 @@ describe('license features', function () { allowSubFeaturePrivileges: true, allowAuditLogging: true, allowUserProfileCollaboration: true, + allowFips: true, }); }); }); diff --git a/x-pack/plugins/security/common/licensing/license_service.ts b/x-pack/plugins/security/common/licensing/license_service.ts index 9605e4262347a..5b2ca931b2bf5 100644 --- a/x-pack/plugins/security/common/licensing/license_service.ts +++ b/x-pack/plugins/security/common/licensing/license_service.ts @@ -80,6 +80,7 @@ export class SecurityLicenseService { allowRbac: false, allowSubFeaturePrivileges: false, allowUserProfileCollaboration: false, + allowFips: false, layout: rawLicense !== undefined && !rawLicense?.isAvailable ? 'error-xpack-unavailable' @@ -101,6 +102,7 @@ export class SecurityLicenseService { allowRbac: false, allowSubFeaturePrivileges: false, allowUserProfileCollaboration: false, + allowFips: false, }; } @@ -121,6 +123,7 @@ export class SecurityLicenseService { allowRoleRemoteIndexPrivileges: isLicensePlatinumOrBetter, allowRbac: true, allowUserProfileCollaboration: isLicenseStandardOrBetter, + allowFips: isLicensePlatinumOrBetter, }; } } diff --git a/x-pack/plugins/security/server/fips/fips_service.test.ts b/x-pack/plugins/security/server/fips/fips_service.test.ts new file mode 100644 index 0000000000000..54f7727cabe99 --- /dev/null +++ b/x-pack/plugins/security/server/fips/fips_service.test.ts @@ -0,0 +1,248 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import { BehaviorSubject } from 'rxjs'; + +import { loggingSystemMock } from '@kbn/core/server/mocks'; +import { License } from '@kbn/licensing-plugin/common/license'; +import { licenseMock as licensingMock } from '@kbn/licensing-plugin/common/licensing.mock'; +import type { ILicense } from '@kbn/licensing-plugin/common/types'; + +import type { + FipsServiceSetupInternal, + FipsServiceSetupParams, + FipsServiceStartInternal, + FipsServiceStartParams, +} from './fips_service'; +import { FipsService } from './fips_service'; +import { licenseMock } from '../../common/licensing/index.mock'; +import { ConfigSchema, createConfig } from '../config'; + +const logger = loggingSystemMock.createLogger(); + +describe('FipsService', () => { + const mockFipsSetupParams: FipsServiceSetupParams = { + license: licenseMock.create(), + config: createConfig( + ConfigSchema.validate({ fipsMode: { enabled: true } }), + loggingSystemMock.createLogger(), + { + isTLSEnabled: false, + } + ), + }; + + const license = licensingMock.createLicenseMock(); + const licenseSubject = new BehaviorSubject(license); + const license$ = licenseSubject.asObservable(); + + const mockFipsStartParams: FipsServiceStartParams = { + license$, + }; + + let fipsService: FipsService; + let fipsServiceSetup: FipsServiceSetupInternal; + let fipsServiceStart: FipsServiceStartInternal; + + beforeEach(() => { + fipsService = new FipsService(logger); + }); + + afterEach(() => { + logger.fatal.mockClear(); + }); + + // describe('', () => {}); + + describe('setup()', () => { + it('should expose correct setup contract', () => { + fipsServiceSetup = fipsService.setup(mockFipsSetupParams); + + expect(fipsServiceSetup).toMatchInlineSnapshot(` + Object { + "canStartInFipsMode": [Function], + } + `); + }); + }); + + describe('start()', () => { + it('should expose correct start contract', () => { + fipsService.setup(mockFipsSetupParams); + fipsServiceStart = fipsService.start(mockFipsStartParams); + + expect(fipsServiceStart).toMatchInlineSnapshot(` + Object { + "monitorForLicenseDowngradeToLogFipsRestartError": [Function], + } + `); + }); + }); + + describe('#canStartInFipsMode', () => { + it('should not throw Error and log `fatal` if `fipsMode.enabled` is not `true` and license < platinum', () => { + fipsServiceSetup = fipsService.setup({ + license: licenseMock.create({}, 'basic'), + config: createConfig( + ConfigSchema.validate({ fipsMode: { enabled: false } }), + loggingSystemMock.createLogger(), + { + isTLSEnabled: false, + } + ), + }); + + fipsServiceSetup.canStartInFipsMode(); + + expect(logger.fatal).not.toHaveBeenCalled(); + }); + + it('should not throw Error and log `fatal` if `fipsMode.enabled` is not `true` and license >= platinum', () => { + fipsServiceSetup = fipsService.setup({ + license: licenseMock.create({ allowFips: true }, 'platinum'), + config: createConfig( + ConfigSchema.validate({ fipsMode: { enabled: false } }), + loggingSystemMock.createLogger(), + { + isTLSEnabled: false, + } + ), + }); + + fipsServiceSetup.canStartInFipsMode(); + + expect(logger.fatal).not.toHaveBeenCalled(); + }); + + it('should not throw Error and log `fatal` if `fipsMode.enabled` is `true` and license >= platinum', () => { + fipsServiceSetup = fipsService.setup({ + license: licenseMock.create({ allowFips: true }, 'platinum'), + config: createConfig( + ConfigSchema.validate({ fipsMode: { enabled: true } }), + loggingSystemMock.createLogger(), + { + isTLSEnabled: false, + } + ), + }); + + fipsServiceSetup.canStartInFipsMode(); + + expect(logger.fatal).not.toHaveBeenCalled(); + }); + + it('should throw Error and log `fatal` if `fipsMode.enabled` is `true` and license < platinum', () => { + fipsServiceSetup = fipsService.setup({ + license: licenseMock.create({}), + config: createConfig( + ConfigSchema.validate({ fipsMode: { enabled: true } }), + loggingSystemMock.createLogger(), + { + isTLSEnabled: false, + } + ), + }); + + expect(() => fipsServiceSetup.canStartInFipsMode()).toThrowError(); + expect(logger.fatal).toHaveBeenCalled(); + }); + }); + + describe('#monitorForLicenseDowngradeToLogFipsRestartError', () => { + afterEach(() => { + licenseSubject.next(license); + }); + + it('should not log `fatal` if license is not available', () => { + fipsService.setup({ + license: licenseMock.create({ allowFips: true }, 'platinum'), + config: createConfig( + ConfigSchema.validate({ fipsMode: { enabled: true } }), + loggingSystemMock.createLogger(), + { + isTLSEnabled: false, + } + ), + }); + + fipsServiceStart = fipsService.start({ license$ }); + + const nextLicense = License.fromJSON({ signature: 'xyz' }); + licenseSubject.next(nextLicense); + + fipsServiceStart.monitorForLicenseDowngradeToLogFipsRestartError(); + + expect(logger.fatal).not.toHaveBeenCalled(); + }); + + it('should not log `fatal` if `fipsMode.enabled` is `false`', () => { + fipsService.setup({ + license: licenseMock.create({}, 'basic'), + config: createConfig( + ConfigSchema.validate({ fipsMode: { enabled: false } }), + loggingSystemMock.createLogger(), + { + isTLSEnabled: false, + } + ), + }); + + fipsServiceStart = fipsService.start({ license$ }); + + const nextLicense = licensingMock.createLicense(); + licenseSubject.next(nextLicense); + + fipsServiceStart.monitorForLicenseDowngradeToLogFipsRestartError(); + + expect(logger.fatal).not.toHaveBeenCalled(); + }); + + it('should not log `fatal` if `fipsMode.enabled` is `true` and license >= platinum', () => { + fipsService.setup({ + license: licenseMock.create({ allowFips: true }, 'platinum'), + config: createConfig( + ConfigSchema.validate({ fipsMode: { enabled: true } }), + loggingSystemMock.createLogger(), + { + isTLSEnabled: false, + } + ), + }); + + fipsServiceStart = fipsService.start({ license$ }); + + const nextLicense = licensingMock.createLicense({ license: { type: 'platinum' } }); + licenseSubject.next(nextLicense); + + fipsServiceStart.monitorForLicenseDowngradeToLogFipsRestartError(); + + expect(logger.fatal).not.toHaveBeenCalled(); + }); + + it('should log `fatal` if `fipsMode.enabled` is `true` and license < platinum', () => { + fipsService.setup({ + license: licenseMock.create({ allowFips: false }, 'basic'), + config: createConfig( + ConfigSchema.validate({ fipsMode: { enabled: true } }), + loggingSystemMock.createLogger(), + { + isTLSEnabled: false, + } + ), + }); + + fipsServiceStart = fipsService.start({ license$ }); + + const nextLicense = licensingMock.createLicense({ license: { type: 'basic' } }); + licenseSubject.next(nextLicense); + + fipsServiceStart.monitorForLicenseDowngradeToLogFipsRestartError(); + + expect(logger.fatal).toHaveBeenCalledTimes(1); + }); + }); +}); diff --git a/x-pack/plugins/security/server/fips/fips_service.ts b/x-pack/plugins/security/server/fips/fips_service.ts new file mode 100644 index 0000000000000..246c950d970ac --- /dev/null +++ b/x-pack/plugins/security/server/fips/fips_service.ts @@ -0,0 +1,84 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import type { Observable } from 'rxjs'; + +import type { ILicense } from '@kbn/licensing-plugin/server'; +import type { Logger } from '@kbn/logging'; +import type { SecurityLicense } from '@kbn/security-plugin-types-common'; + +import type { ConfigType } from '../config'; + +export interface FipsServiceSetupParams { + config: ConfigType; + license: SecurityLicense; +} + +export interface FipsServiceStartParams { + license$: Observable; +} + +export interface FipsServiceSetupInternal { + canStartInFipsMode: () => void; +} + +export interface FipsServiceStartInternal { + monitorForLicenseDowngradeToLogFipsRestartError: () => void; +} + +export class FipsService { + private readonly logger: Logger; + private license: SecurityLicense | undefined; + private config: ConfigType | undefined; + + constructor(logger: Logger) { + this.logger = logger; + } + + setup({ config, license }: FipsServiceSetupParams): FipsServiceSetupInternal { + this.license = license; + this.config = config; + + return { canStartInFipsMode: () => this.canStartInFipsMode() }; + } + + start({ license$ }: FipsServiceStartParams): FipsServiceStartInternal { + return { + monitorForLicenseDowngradeToLogFipsRestartError: () => + this.monitorForLicenseDowngradeToLogFipsRestartError(license$), + }; + } + + private monitorForLicenseDowngradeToLogFipsRestartError(license$: Observable) { + license$.subscribe({ + next: (license) => { + if (license.isAvailable && this.isRunningInFipsModeWithUnsupportedLicense()) { + this.logger.fatal( + `Your current license level is ${license.type} and does not support running in FIPS mode, Kibana will not be able to restart. Please upgrade your license to Platinum or higher.` + ); + } + }, + error: (error) => { + this.logger.debug(`Unable to check license: ${error}`); + }, + }); + } + + private canStartInFipsMode() { + if (this.isRunningInFipsModeWithUnsupportedLicense()) { + this.logger.fatal('Current license level does not support running in FIPS mode'); + throw new Error('Current license level does not support running in FIPS mode'); + } + } + + private isRunningInFipsModeWithUnsupportedLicense(): boolean { + const isLicenseCompatibleWithFips = this.license?.getFeatures().allowFips || false; + const isConfiguredToRunInFipsMode = this.config?.fipsMode.enabled || false; + + return !isLicenseCompatibleWithFips && isConfiguredToRunInFipsMode; + } +} diff --git a/x-pack/plugins/security/server/fips/index.ts b/x-pack/plugins/security/server/fips/index.ts new file mode 100644 index 0000000000000..e0ead2fde30fd --- /dev/null +++ b/x-pack/plugins/security/server/fips/index.ts @@ -0,0 +1,15 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +export { FipsService } from './fips_service'; + +export type { + FipsServiceSetupInternal, + FipsServiceStartInternal, + FipsServiceSetupParams, + FipsServiceStartParams, +} from './fips_service'; diff --git a/x-pack/plugins/security/server/plugin.ts b/x-pack/plugins/security/server/plugin.ts index 71ae6c6436063..8a0e937bd4fd7 100644 --- a/x-pack/plugins/security/server/plugin.ts +++ b/x-pack/plugins/security/server/plugin.ts @@ -52,6 +52,8 @@ import { ElasticsearchService } from './elasticsearch'; import type { SecurityFeatureUsageServiceStart } from './feature_usage'; import { SecurityFeatureUsageService } from './feature_usage'; import { securityFeatures } from './features'; +import type { FipsServiceSetupInternal, FipsServiceStartInternal } from './fips'; +import { FipsService } from './fips'; import { defineRoutes } from './routes'; import { setupSavedObjects } from './saved_objects'; import type { Session } from './session_management'; @@ -182,6 +184,10 @@ export class SecurityPlugin return this.userProfileStart; }; + private readonly fipsService: FipsService; + private fipsServiceSetup?: FipsServiceSetupInternal; + private fipsServiceStart?: FipsServiceStartInternal; + constructor(private readonly initializerContext: PluginInitializerContext) { this.logger = this.initializerContext.logger.get(); @@ -211,6 +217,8 @@ export class SecurityPlugin this.userProfileSettingsClient = new UserProfileSettingsClient( this.initializerContext.logger.get('user-settings-client') ); + + this.fipsService = new FipsService(this.initializerContext.logger.get('fips')); } public setup( @@ -291,6 +299,9 @@ export class SecurityPlugin this.userProfileService.setup({ authz: this.authorizationSetup, license }); + this.fipsServiceSetup = this.fipsService.setup({ config, license }); + this.fipsServiceSetup.canStartInFipsMode(); + setupSpacesClient({ spaces, audit: this.auditSetup, @@ -414,6 +425,9 @@ export class SecurityPlugin spaces: spaces?.spacesService, }); + this.fipsServiceStart = this.fipsService.start({ license$: licensing.license$ }); + this.fipsServiceStart.monitorForLicenseDowngradeToLogFipsRestartError(); + return Object.freeze({ authc: { apiKeys: this.authenticationStart.apiKeys, From 03f4b9e2da2b207cdd69cafed92f47c546c7e9ed Mon Sep 17 00:00:00 2001 From: Kurt Date: Thu, 18 Apr 2024 15:40:21 -0400 Subject: [PATCH 05/28] Removing unused line in snapshot --- x-pack/plugins/security/server/config.test.ts | 2 -- 1 file changed, 2 deletions(-) diff --git a/x-pack/plugins/security/server/config.test.ts b/x-pack/plugins/security/server/config.test.ts index 2f302a0340dd7..f2a0ccb7287eb 100644 --- a/x-pack/plugins/security/server/config.test.ts +++ b/x-pack/plugins/security/server/config.test.ts @@ -178,7 +178,6 @@ describe('config schema', () => { "selector": Object {}, }, "cookieName": "sid", - "enabled": true, "fipsMode": Object { "enabled": false, }, @@ -238,7 +237,6 @@ describe('config schema', () => { "selector": Object {}, }, "cookieName": "sid", - "enabled": true, "fipsMode": Object { "enabled": false, }, From 8959fbb01f5d5bc5b6cf4623a3b3cfce26729c7a Mon Sep 17 00:00:00 2001 From: Kurt Date: Thu, 18 Apr 2024 16:26:07 -0400 Subject: [PATCH 06/28] Adding new property --- x-pack/plugins/security/server/routes/views/login.test.ts | 1 + 1 file changed, 1 insertion(+) diff --git a/x-pack/plugins/security/server/routes/views/login.test.ts b/x-pack/plugins/security/server/routes/views/login.test.ts index 086c0c785e6bc..98113397ae6ba 100644 --- a/x-pack/plugins/security/server/routes/views/login.test.ts +++ b/x-pack/plugins/security/server/routes/views/login.test.ts @@ -174,6 +174,7 @@ describe('Login view routes', () => { allowAuditLogging: true, showLogin: true, allowUserProfileCollaboration: true, + allowFips: false, }); const request = httpServerMock.createKibanaRequest(); From d50f7c508ad8f5ef3e5e87a3bf3f194972eb4e86 Mon Sep 17 00:00:00 2001 From: Kurt Date: Tue, 23 Apr 2024 07:41:23 -0400 Subject: [PATCH 07/28] Refactoring usage of License in favor of just using SecurityLicense --- .../src/licensing/license.ts | 1 + .../security/common/licensing/index.mock.ts | 27 +- .../common/licensing/license_service.ts | 2 + .../security/server/fips/fips_service.test.ts | 346 ++++++++---------- .../security/server/fips/fips_service.ts | 59 +-- x-pack/plugins/security/server/fips/index.ts | 7 +- x-pack/plugins/security/server/plugin.ts | 8 +- 7 files changed, 203 insertions(+), 247 deletions(-) diff --git a/x-pack/packages/security/plugin_types_common/src/licensing/license.ts b/x-pack/packages/security/plugin_types_common/src/licensing/license.ts index 0a7e8e3b87c67..349395ee63fdc 100644 --- a/x-pack/packages/security/plugin_types_common/src/licensing/license.ts +++ b/x-pack/packages/security/plugin_types_common/src/licensing/license.ts @@ -13,6 +13,7 @@ import type { SecurityLicenseFeatures } from './license_features'; export interface SecurityLicense { isLicenseAvailable(): boolean; + getLicenseType(): string | undefined; getUnavailableReason: () => string | undefined; isEnabled(): boolean; getFeatures(): SecurityLicenseFeatures; diff --git a/x-pack/plugins/security/common/licensing/index.mock.ts b/x-pack/plugins/security/common/licensing/index.mock.ts index 9d2fef049de82..6ee9910b768bd 100644 --- a/x-pack/plugins/security/common/licensing/index.mock.ts +++ b/x-pack/plugins/security/common/licensing/index.mock.ts @@ -14,12 +14,33 @@ import type { SecurityLicense, SecurityLicenseFeatures } from '@kbn/security-plu export const licenseMock = { create: ( features: Partial | Observable> = {}, - licenseType: LicenseType = 'basic' // default to basic if this is not specified + licenseType: LicenseType = 'basic', // default to basic if this is not specified, + isAvailable: Observable = of(true) ): jest.Mocked => ({ - isLicenseAvailable: jest.fn().mockReturnValue(true), + isLicenseAvailable: jest.fn().mockImplementation(() => { + let result = true; + + isAvailable.subscribe((next) => { + result = next; + }); + + return result; + }), + getLicenseType: jest.fn().mockReturnValue(licenseType), getUnavailableReason: jest.fn(), isEnabled: jest.fn().mockReturnValue(true), - getFeatures: jest.fn().mockReturnValue(features), + getFeatures: + features instanceof Observable + ? jest.fn().mockImplementation(() => { + let subbedFeatures: Partial = {}; + + features.subscribe((next) => { + subbedFeatures = next; + }); + + return subbedFeatures; + }) + : jest.fn().mockReturnValue(features), hasAtLeast: jest .fn() .mockImplementation( diff --git a/x-pack/plugins/security/common/licensing/license_service.ts b/x-pack/plugins/security/common/licensing/license_service.ts index 09d2702eef58e..6d9cc08f9099b 100644 --- a/x-pack/plugins/security/common/licensing/license_service.ts +++ b/x-pack/plugins/security/common/licensing/license_service.ts @@ -28,6 +28,8 @@ export class SecurityLicenseService { license: Object.freeze({ isLicenseAvailable: () => rawLicense?.isAvailable ?? false, + getLicenseType: () => rawLicense?.type ?? undefined, + getUnavailableReason: () => rawLicense?.getUnavailableReason(), isEnabled: () => this.isSecurityEnabledFromRawLicense(rawLicense), diff --git a/x-pack/plugins/security/server/fips/fips_service.test.ts b/x-pack/plugins/security/server/fips/fips_service.test.ts index 54f7727cabe99..ca1805480641e 100644 --- a/x-pack/plugins/security/server/fips/fips_service.test.ts +++ b/x-pack/plugins/security/server/fips/fips_service.test.ts @@ -5,244 +5,210 @@ * 2.0. */ -import { BehaviorSubject } from 'rxjs'; +const mockGetFipsFn = jest.fn(); +jest.mock('crypto', () => ({ + randomBytes: jest.fn(), + constants: jest.requireActual('crypto').constants, + get getFips() { + return mockGetFipsFn; + }, +})); + +import type { Observable } from 'rxjs'; +import { BehaviorSubject, of } from 'rxjs'; import { loggingSystemMock } from '@kbn/core/server/mocks'; -import { License } from '@kbn/licensing-plugin/common/license'; -import { licenseMock as licensingMock } from '@kbn/licensing-plugin/common/licensing.mock'; -import type { ILicense } from '@kbn/licensing-plugin/common/types'; - -import type { - FipsServiceSetupInternal, - FipsServiceSetupParams, - FipsServiceStartInternal, - FipsServiceStartParams, -} from './fips_service'; +import type { LicenseType } from '@kbn/licensing-plugin/common/types'; +import type { SecurityLicenseFeatures } from '@kbn/security-plugin-types-common'; + +import type { FipsServiceSetupInternal, FipsServiceSetupParams } from './fips_service'; import { FipsService } from './fips_service'; import { licenseMock } from '../../common/licensing/index.mock'; import { ConfigSchema, createConfig } from '../config'; const logger = loggingSystemMock.createLogger(); -describe('FipsService', () => { - const mockFipsSetupParams: FipsServiceSetupParams = { - license: licenseMock.create(), - config: createConfig( - ConfigSchema.validate({ fipsMode: { enabled: true } }), - loggingSystemMock.createLogger(), - { - isTLSEnabled: false, - } - ), - }; +function buildMockFipsServiceSetupParams( + licenseType: LicenseType, + isFipsConfigured: boolean, + features$: Observable>, + isAvailable: Observable = of(true) +): FipsServiceSetupParams { + mockGetFipsFn.mockImplementationOnce(() => { + return isFipsConfigured ? 1 : 0; + }); + + const license = licenseMock.create(features$, licenseType, isAvailable); - const license = licensingMock.createLicenseMock(); - const licenseSubject = new BehaviorSubject(license); - const license$ = licenseSubject.asObservable(); + let mockConfig = {}; + if (isFipsConfigured) { + mockConfig = { fipsMode: { enabled: true } }; + } - const mockFipsStartParams: FipsServiceStartParams = { - license$, + return { + license, + config: createConfig(ConfigSchema.validate(mockConfig), loggingSystemMock.createLogger(), { + isTLSEnabled: false, + }), }; +} +describe('FipsService', () => { let fipsService: FipsService; let fipsServiceSetup: FipsServiceSetupInternal; - let fipsServiceStart: FipsServiceStartInternal; - beforeEach(() => { + beforeAll(() => { fipsService = new FipsService(logger); }); - afterEach(() => { + beforeEach(() => { logger.fatal.mockClear(); }); - // describe('', () => {}); + afterEach(() => { + logger.fatal.mockClear(); + }); describe('setup()', () => { it('should expose correct setup contract', () => { - fipsServiceSetup = fipsService.setup(mockFipsSetupParams); + fipsService = new FipsService(logger); + fipsServiceSetup = fipsService.setup( + buildMockFipsServiceSetupParams('platinum', true, of({ allowFips: true })) + ); expect(fipsServiceSetup).toMatchInlineSnapshot(` Object { - "canStartInFipsMode": [Function], + "validateLicenseForFips": [Function], } `); }); }); - describe('start()', () => { - it('should expose correct start contract', () => { - fipsService.setup(mockFipsSetupParams); - fipsServiceStart = fipsService.start(mockFipsStartParams); - - expect(fipsServiceStart).toMatchInlineSnapshot(` - Object { - "monitorForLicenseDowngradeToLogFipsRestartError": [Function], - } - `); - }); - }); - - describe('#canStartInFipsMode', () => { - it('should not throw Error and log `fatal` if `fipsMode.enabled` is not `true` and license < platinum', () => { - fipsServiceSetup = fipsService.setup({ - license: licenseMock.create({}, 'basic'), - config: createConfig( - ConfigSchema.validate({ fipsMode: { enabled: false } }), - loggingSystemMock.createLogger(), - { - isTLSEnabled: false, - } - ), - }); - - fipsServiceSetup.canStartInFipsMode(); - - expect(logger.fatal).not.toHaveBeenCalled(); - }); - - it('should not throw Error and log `fatal` if `fipsMode.enabled` is not `true` and license >= platinum', () => { - fipsServiceSetup = fipsService.setup({ - license: licenseMock.create({ allowFips: true }, 'platinum'), - config: createConfig( - ConfigSchema.validate({ fipsMode: { enabled: false } }), - loggingSystemMock.createLogger(), - { - isTLSEnabled: false, - } - ), - }); - - fipsServiceSetup.canStartInFipsMode(); - - expect(logger.fatal).not.toHaveBeenCalled(); - }); + describe('#validateLicenseForFips', () => { + describe('start-up check', () => { + it('should not throw Error/log.fatal if license features allowFips and `fipsMode.enabled` is `false`', () => { + fipsServiceSetup = fipsService.setup( + buildMockFipsServiceSetupParams('platinum', false, of({ allowFips: true })) + ); + fipsServiceSetup.validateLicenseForFips(); - it('should not throw Error and log `fatal` if `fipsMode.enabled` is `true` and license >= platinum', () => { - fipsServiceSetup = fipsService.setup({ - license: licenseMock.create({ allowFips: true }, 'platinum'), - config: createConfig( - ConfigSchema.validate({ fipsMode: { enabled: true } }), - loggingSystemMock.createLogger(), - { - isTLSEnabled: false, - } - ), + expect(logger.fatal).not.toHaveBeenCalled(); }); - fipsServiceSetup.canStartInFipsMode(); - - expect(logger.fatal).not.toHaveBeenCalled(); - }); + it('should not throw Error/log.fatal if license features allowFips and `fipsMode.enabled` is `true`', () => { + fipsServiceSetup = fipsService.setup( + buildMockFipsServiceSetupParams('platinum', true, of({ allowFips: true })) + ); + fipsServiceSetup.validateLicenseForFips(); - it('should throw Error and log `fatal` if `fipsMode.enabled` is `true` and license < platinum', () => { - fipsServiceSetup = fipsService.setup({ - license: licenseMock.create({}), - config: createConfig( - ConfigSchema.validate({ fipsMode: { enabled: true } }), - loggingSystemMock.createLogger(), - { - isTLSEnabled: false, - } - ), + expect(logger.fatal).not.toHaveBeenCalled(); }); - expect(() => fipsServiceSetup.canStartInFipsMode()).toThrowError(); - expect(logger.fatal).toHaveBeenCalled(); - }); - }); - - describe('#monitorForLicenseDowngradeToLogFipsRestartError', () => { - afterEach(() => { - licenseSubject.next(license); - }); + it('should not throw Error/log.fatal if license features do not allowFips and `fipsMode.enabled` is `false`', () => { + fipsServiceSetup = fipsService.setup( + buildMockFipsServiceSetupParams('basic', false, of({})) + ); + fipsServiceSetup.validateLicenseForFips(); - it('should not log `fatal` if license is not available', () => { - fipsService.setup({ - license: licenseMock.create({ allowFips: true }, 'platinum'), - config: createConfig( - ConfigSchema.validate({ fipsMode: { enabled: true } }), - loggingSystemMock.createLogger(), - { - isTLSEnabled: false, - } - ), + expect(logger.fatal).not.toHaveBeenCalled(); }); - fipsServiceStart = fipsService.start({ license$ }); - - const nextLicense = License.fromJSON({ signature: 'xyz' }); - licenseSubject.next(nextLicense); - - fipsServiceStart.monitorForLicenseDowngradeToLogFipsRestartError(); + it('should throw Error/log.fatal if license features do not allowFips and `fipsMode.enabled` is `true`', () => { + fipsServiceSetup = fipsService.setup( + buildMockFipsServiceSetupParams('basic', true, of({})) + ); - expect(logger.fatal).not.toHaveBeenCalled(); - }); - - it('should not log `fatal` if `fipsMode.enabled` is `false`', () => { - fipsService.setup({ - license: licenseMock.create({}, 'basic'), - config: createConfig( - ConfigSchema.validate({ fipsMode: { enabled: false } }), - loggingSystemMock.createLogger(), - { - isTLSEnabled: false, - } - ), + expect(() => { + fipsServiceSetup.validateLicenseForFips(); + }).toThrowError(); + expect(logger.fatal).toHaveBeenCalledTimes(1); }); - - fipsServiceStart = fipsService.start({ license$ }); - - const nextLicense = licensingMock.createLicense(); - licenseSubject.next(nextLicense); - - fipsServiceStart.monitorForLicenseDowngradeToLogFipsRestartError(); - - expect(logger.fatal).not.toHaveBeenCalled(); }); - it('should not log `fatal` if `fipsMode.enabled` is `true` and license >= platinum', () => { - fipsService.setup({ - license: licenseMock.create({ allowFips: true }, 'platinum'), - config: createConfig( - ConfigSchema.validate({ fipsMode: { enabled: true } }), - loggingSystemMock.createLogger(), - { - isTLSEnabled: false, - } - ), + describe('monitoring check', () => { + describe('with fipsMode.enabled', () => { + let mockFeaturesSubject: BehaviorSubject>; + let mockIsAvailableSubject: BehaviorSubject; + let mockFeatures$: Observable>; + let mockIsAvailable$: Observable; + + beforeAll(() => { + mockFeaturesSubject = new BehaviorSubject>({ + allowFips: true, + }); + mockIsAvailableSubject = new BehaviorSubject(true); + mockFeatures$ = mockFeaturesSubject.asObservable(); + mockIsAvailable$ = mockIsAvailableSubject.asObservable(); + fipsServiceSetup = fipsService.setup( + buildMockFipsServiceSetupParams('platinum', true, mockFeatures$, mockIsAvailable$) + ); + + fipsServiceSetup.validateLicenseForFips(); + }); + + beforeEach(() => { + mockFeaturesSubject.next({ allowFips: true }); + mockIsAvailableSubject.next(true); + }); + + it('should not log.fatal if license changes to unavailable and `fipsMode.enabled` is `true`', () => { + mockIsAvailableSubject.next(false); + expect(logger.fatal).not.toHaveBeenCalled(); + }); + + it('should not log.fatal if license features continue to allowFips and `fipsMode.enabled` is `true`', () => { + mockFeaturesSubject.next({ allowFips: true }); + expect(logger.fatal).not.toHaveBeenCalled(); + }); + + it('should log.fatal if license features change to not allowFips and `fipsMode.enabled` is `true`', () => { + mockFeaturesSubject.next({}); + expect(logger.fatal).toHaveBeenCalledTimes(1); + }); }); - fipsServiceStart = fipsService.start({ license$ }); - - const nextLicense = licensingMock.createLicense({ license: { type: 'platinum' } }); - licenseSubject.next(nextLicense); - - fipsServiceStart.monitorForLicenseDowngradeToLogFipsRestartError(); - - expect(logger.fatal).not.toHaveBeenCalled(); - }); - - it('should log `fatal` if `fipsMode.enabled` is `true` and license < platinum', () => { - fipsService.setup({ - license: licenseMock.create({ allowFips: false }, 'basic'), - config: createConfig( - ConfigSchema.validate({ fipsMode: { enabled: true } }), - loggingSystemMock.createLogger(), - { - isTLSEnabled: false, - } - ), + describe('with not fipsMode.enabled', () => { + let mockFeaturesSubject: BehaviorSubject>; + let mockIsAvailableSubject: BehaviorSubject; + let mockFeatures$: Observable>; + let mockIsAvailable$: Observable; + + beforeAll(() => { + mockFeaturesSubject = new BehaviorSubject>({ + allowFips: true, + }); + mockIsAvailableSubject = new BehaviorSubject(true); + mockFeatures$ = mockFeaturesSubject.asObservable(); + mockIsAvailable$ = mockIsAvailableSubject.asObservable(); + + fipsServiceSetup = fipsService.setup( + buildMockFipsServiceSetupParams('platinum', false, mockFeatures$, mockIsAvailable$) + ); + + fipsServiceSetup.validateLicenseForFips(); + }); + + beforeEach(() => { + mockFeaturesSubject.next({ allowFips: true }); + mockIsAvailableSubject.next(true); + }); + + it('should not log.fatal if license changes to unavailable and `fipsMode.enabled` is `false`', () => { + mockIsAvailableSubject.next(false); + expect(logger.fatal).not.toHaveBeenCalled(); + }); + + it('should not log.fatal if license features continue to allowFips and `fipsMode.enabled` is `false`', () => { + mockFeaturesSubject.next({ allowFips: true }); + expect(logger.fatal).not.toHaveBeenCalled(); + }); + + it('should not log.fatal if license change to not allowFips and `fipsMode.enabled` is `false`', () => { + console.log('Test'); + mockFeaturesSubject.next({}); + expect(logger.fatal).not.toHaveBeenCalled(); + }); }); - - fipsServiceStart = fipsService.start({ license$ }); - - const nextLicense = licensingMock.createLicense({ license: { type: 'basic' } }); - licenseSubject.next(nextLicense); - - fipsServiceStart.monitorForLicenseDowngradeToLogFipsRestartError(); - - expect(logger.fatal).toHaveBeenCalledTimes(1); }); }); }); diff --git a/x-pack/plugins/security/server/fips/fips_service.ts b/x-pack/plugins/security/server/fips/fips_service.ts index 246c950d970ac..2979060ccbefc 100644 --- a/x-pack/plugins/security/server/fips/fips_service.ts +++ b/x-pack/plugins/security/server/fips/fips_service.ts @@ -5,9 +5,6 @@ * 2.0. */ -import type { Observable } from 'rxjs'; - -import type { ILicense } from '@kbn/licensing-plugin/server'; import type { Logger } from '@kbn/logging'; import type { SecurityLicense } from '@kbn/security-plugin-types-common'; @@ -18,47 +15,39 @@ export interface FipsServiceSetupParams { license: SecurityLicense; } -export interface FipsServiceStartParams { - license$: Observable; -} - export interface FipsServiceSetupInternal { - canStartInFipsMode: () => void; -} - -export interface FipsServiceStartInternal { - monitorForLicenseDowngradeToLogFipsRestartError: () => void; + validateLicenseForFips: () => void; } export class FipsService { private readonly logger: Logger; - private license: SecurityLicense | undefined; - private config: ConfigType | undefined; constructor(logger: Logger) { this.logger = logger; } setup({ config, license }: FipsServiceSetupParams): FipsServiceSetupInternal { - this.license = license; - this.config = config; - - return { canStartInFipsMode: () => this.canStartInFipsMode() }; - } - - start({ license$ }: FipsServiceStartParams): FipsServiceStartInternal { return { - monitorForLicenseDowngradeToLogFipsRestartError: () => - this.monitorForLicenseDowngradeToLogFipsRestartError(license$), + validateLicenseForFips: () => this.validateLicenseForFips(config, license), }; } - private monitorForLicenseDowngradeToLogFipsRestartError(license$: Observable) { - license$.subscribe({ - next: (license) => { - if (license.isAvailable && this.isRunningInFipsModeWithUnsupportedLicense()) { + private validateLicenseForFips(config: ConfigType, license: SecurityLicense) { + const errorMessage = `Your current license level is ${license.getLicenseType()} and does not support running in FIPS mode.`; + + if (config?.fipsMode.enabled && !license.getFeatures().allowFips) { + this.logger.fatal(errorMessage); + throw new Error(errorMessage); + } + + license.features$.subscribe({ + next: (features) => { + console.log(config?.fipsMode.enabled); + console.log(!features.allowFips); + + if (license.isLicenseAvailable() && config?.fipsMode.enabled && !features.allowFips) { this.logger.fatal( - `Your current license level is ${license.type} and does not support running in FIPS mode, Kibana will not be able to restart. Please upgrade your license to Platinum or higher.` + `${errorMessage} Kibana will not be able to restart. Please upgrade your license to Platinum or higher.` ); } }, @@ -67,18 +56,4 @@ export class FipsService { }, }); } - - private canStartInFipsMode() { - if (this.isRunningInFipsModeWithUnsupportedLicense()) { - this.logger.fatal('Current license level does not support running in FIPS mode'); - throw new Error('Current license level does not support running in FIPS mode'); - } - } - - private isRunningInFipsModeWithUnsupportedLicense(): boolean { - const isLicenseCompatibleWithFips = this.license?.getFeatures().allowFips || false; - const isConfiguredToRunInFipsMode = this.config?.fipsMode.enabled || false; - - return !isLicenseCompatibleWithFips && isConfiguredToRunInFipsMode; - } } diff --git a/x-pack/plugins/security/server/fips/index.ts b/x-pack/plugins/security/server/fips/index.ts index e0ead2fde30fd..3af4435169348 100644 --- a/x-pack/plugins/security/server/fips/index.ts +++ b/x-pack/plugins/security/server/fips/index.ts @@ -7,9 +7,4 @@ export { FipsService } from './fips_service'; -export type { - FipsServiceSetupInternal, - FipsServiceStartInternal, - FipsServiceSetupParams, - FipsServiceStartParams, -} from './fips_service'; +export type { FipsServiceSetupInternal, FipsServiceSetupParams } from './fips_service'; diff --git a/x-pack/plugins/security/server/plugin.ts b/x-pack/plugins/security/server/plugin.ts index 1c6c15e43f6bd..51c845ee1e898 100644 --- a/x-pack/plugins/security/server/plugin.ts +++ b/x-pack/plugins/security/server/plugin.ts @@ -52,7 +52,7 @@ import { ElasticsearchService } from './elasticsearch'; import type { SecurityFeatureUsageServiceStart } from './feature_usage'; import { SecurityFeatureUsageService } from './feature_usage'; import { securityFeatures } from './features'; -import type { FipsServiceSetupInternal, FipsServiceStartInternal } from './fips'; +import type { FipsServiceSetupInternal } from './fips'; import { FipsService } from './fips'; import { defineRoutes } from './routes'; import { setupSavedObjects } from './saved_objects'; @@ -186,7 +186,6 @@ export class SecurityPlugin private readonly fipsService: FipsService; private fipsServiceSetup?: FipsServiceSetupInternal; - private fipsServiceStart?: FipsServiceStartInternal; constructor(private readonly initializerContext: PluginInitializerContext) { this.logger = this.initializerContext.logger.get(); @@ -303,7 +302,7 @@ export class SecurityPlugin this.userProfileService.setup({ authz: this.authorizationSetup, license }); this.fipsServiceSetup = this.fipsService.setup({ config, license }); - this.fipsServiceSetup.canStartInFipsMode(); + this.fipsServiceSetup.validateLicenseForFips(); setupSpacesClient({ spaces, @@ -430,9 +429,6 @@ export class SecurityPlugin spaces: spaces?.spacesService, }); - this.fipsServiceStart = this.fipsService.start({ license$: licensing.license$ }); - this.fipsServiceStart.monitorForLicenseDowngradeToLogFipsRestartError(); - return Object.freeze({ authc: { apiKeys: this.authenticationStart.apiKeys, From 9d4d0357a37d73c0df8abda62e8087aec09c42ae Mon Sep 17 00:00:00 2001 From: Kurt Date: Tue, 23 Apr 2024 08:41:42 -0400 Subject: [PATCH 08/28] Removing console logs --- x-pack/plugins/security/server/fips/fips_service.test.ts | 1 - x-pack/plugins/security/server/fips/fips_service.ts | 3 --- 2 files changed, 4 deletions(-) diff --git a/x-pack/plugins/security/server/fips/fips_service.test.ts b/x-pack/plugins/security/server/fips/fips_service.test.ts index ca1805480641e..11d7925831e06 100644 --- a/x-pack/plugins/security/server/fips/fips_service.test.ts +++ b/x-pack/plugins/security/server/fips/fips_service.test.ts @@ -204,7 +204,6 @@ describe('FipsService', () => { }); it('should not log.fatal if license change to not allowFips and `fipsMode.enabled` is `false`', () => { - console.log('Test'); mockFeaturesSubject.next({}); expect(logger.fatal).not.toHaveBeenCalled(); }); diff --git a/x-pack/plugins/security/server/fips/fips_service.ts b/x-pack/plugins/security/server/fips/fips_service.ts index 2979060ccbefc..f6d607d3b7587 100644 --- a/x-pack/plugins/security/server/fips/fips_service.ts +++ b/x-pack/plugins/security/server/fips/fips_service.ts @@ -42,9 +42,6 @@ export class FipsService { license.features$.subscribe({ next: (features) => { - console.log(config?.fipsMode.enabled); - console.log(!features.allowFips); - if (license.isLicenseAvailable() && config?.fipsMode.enabled && !features.allowFips) { this.logger.fatal( `${errorMessage} Kibana will not be able to restart. Please upgrade your license to Platinum or higher.` From 16d7ad8e7dbef0ac1dfc1e34f063366cd0d7f4ea Mon Sep 17 00:00:00 2001 From: Kurt Date: Tue, 23 Apr 2024 14:47:12 -0400 Subject: [PATCH 09/28] Fixing jest tests --- x-pack/plugins/security/public/plugin.test.tsx | 2 ++ 1 file changed, 2 insertions(+) diff --git a/x-pack/plugins/security/public/plugin.test.tsx b/x-pack/plugins/security/public/plugin.test.tsx index d3dadbfb87665..c388989c11cd9 100644 --- a/x-pack/plugins/security/public/plugin.test.tsx +++ b/x-pack/plugins/security/public/plugin.test.tsx @@ -43,6 +43,7 @@ describe('Security Plugin', () => { authz: { isRoleManagementEnabled: expect.any(Function) }, license: { isLicenseAvailable: expect.any(Function), + getLicenseType: expect.any(Function), isEnabled: expect.any(Function), getUnavailableReason: expect.any(Function), getFeatures: expect.any(Function), @@ -71,6 +72,7 @@ describe('Security Plugin', () => { authc: { getCurrentUser: expect.any(Function), areAPIKeysEnabled: expect.any(Function) }, license: { isLicenseAvailable: expect.any(Function), + getLicenseType: expect.any(Function), isEnabled: expect.any(Function), getUnavailableReason: expect.any(Function), getFeatures: expect.any(Function), From b2fc622d077b60befe757ced0f4602a39f88bf0d Mon Sep 17 00:00:00 2001 From: Kurt Date: Tue, 23 Apr 2024 16:43:57 -0400 Subject: [PATCH 10/28] snapshot update --- .../privileges/es/__snapshots__/index_privileges.test.tsx.snap | 1 + 1 file changed, 1 insertion(+) diff --git a/x-pack/plugins/security/public/management/roles/edit_role/privileges/es/__snapshots__/index_privileges.test.tsx.snap b/x-pack/plugins/security/public/management/roles/edit_role/privileges/es/__snapshots__/index_privileges.test.tsx.snap index d99dac15df58f..3c1247bb69013 100644 --- a/x-pack/plugins/security/public/management/roles/edit_role/privileges/es/__snapshots__/index_privileges.test.tsx.snap +++ b/x-pack/plugins/security/public/management/roles/edit_role/privileges/es/__snapshots__/index_privileges.test.tsx.snap @@ -24,6 +24,7 @@ exports[`it renders without crashing 1`] = ` "_subscribe": [Function], }, "getFeatures": [MockFunction], + "getLicenseType": [MockFunction], "getUnavailableReason": [MockFunction], "hasAtLeast": [MockFunction], "isEnabled": [MockFunction], From eb5d11f1b098b9759ccf41bfc27714379d55e65d Mon Sep 17 00:00:00 2001 From: Kurt Date: Tue, 23 Apr 2024 16:57:35 -0400 Subject: [PATCH 11/28] Updating snapshots and expected licenses --- .../es/__snapshots__/elasticsearch_privileges.test.tsx.snap | 2 ++ x-pack/plugins/security/server/plugin.test.ts | 1 + 2 files changed, 3 insertions(+) diff --git a/x-pack/plugins/security/public/management/roles/edit_role/privileges/es/__snapshots__/elasticsearch_privileges.test.tsx.snap b/x-pack/plugins/security/public/management/roles/edit_role/privileges/es/__snapshots__/elasticsearch_privileges.test.tsx.snap index 74013db24d9e4..76b2ac41157e3 100644 --- a/x-pack/plugins/security/public/management/roles/edit_role/privileges/es/__snapshots__/elasticsearch_privileges.test.tsx.snap +++ b/x-pack/plugins/security/public/management/roles/edit_role/privileges/es/__snapshots__/elasticsearch_privileges.test.tsx.snap @@ -129,6 +129,7 @@ exports[`it renders correctly in serverless mode 1`] = ` "_subscribe": [Function], }, "getFeatures": [MockFunction], + "getLicenseType": [MockFunction], "getUnavailableReason": [MockFunction], "hasAtLeast": [MockFunction], "isEnabled": [MockFunction], @@ -333,6 +334,7 @@ exports[`it renders without crashing 1`] = ` "_subscribe": [Function], }, "getFeatures": [MockFunction], + "getLicenseType": [MockFunction], "getUnavailableReason": [MockFunction], "hasAtLeast": [MockFunction], "isEnabled": [MockFunction], diff --git a/x-pack/plugins/security/server/plugin.test.ts b/x-pack/plugins/security/server/plugin.test.ts index 78da2544922af..a6b0d0866b468 100644 --- a/x-pack/plugins/security/server/plugin.test.ts +++ b/x-pack/plugins/security/server/plugin.test.ts @@ -121,6 +121,7 @@ describe('Security Plugin', () => { }, }, "getFeatures": [Function], + "getLicenseType": [Function], "getUnavailableReason": [Function], "hasAtLeast": [Function], "isEnabled": [Function], From abed49077ab7171a7d7bf4ae6473c114c11d7643 Mon Sep 17 00:00:00 2001 From: Kurt Date: Wed, 24 Apr 2024 14:00:12 -0400 Subject: [PATCH 12/28] Need to add logic that waits for the license to load, run the start check the first time, then monitor --- .../security/server/fips/fips_service.test.ts | 79 +++++++++++-------- .../security/server/fips/fips_service.ts | 31 +++++--- 2 files changed, 68 insertions(+), 42 deletions(-) diff --git a/x-pack/plugins/security/server/fips/fips_service.test.ts b/x-pack/plugins/security/server/fips/fips_service.test.ts index 11d7925831e06..82b7f264908ed 100644 --- a/x-pack/plugins/security/server/fips/fips_service.test.ts +++ b/x-pack/plugins/security/server/fips/fips_service.test.ts @@ -57,16 +57,13 @@ describe('FipsService', () => { let fipsService: FipsService; let fipsServiceSetup: FipsServiceSetupInternal; - beforeAll(() => { - fipsService = new FipsService(logger); - }); - beforeEach(() => { - logger.fatal.mockClear(); + fipsService = new FipsService(logger); + logger.error.mockClear(); }); afterEach(() => { - logger.fatal.mockClear(); + logger.error.mockClear(); }); describe('setup()', () => { @@ -86,42 +83,60 @@ describe('FipsService', () => { describe('#validateLicenseForFips', () => { describe('start-up check', () => { - it('should not throw Error/log.fatal if license features allowFips and `fipsMode.enabled` is `false`', () => { + it('should not throw Error/log.error if license features allowFips and `fipsMode.enabled` is `false`', () => { fipsServiceSetup = fipsService.setup( buildMockFipsServiceSetupParams('platinum', false, of({ allowFips: true })) ); fipsServiceSetup.validateLicenseForFips(); - expect(logger.fatal).not.toHaveBeenCalled(); + expect(() => { + fipsServiceSetup.validateLicenseForFips(); + }).not.toThrowError(); + expect(logger.error).not.toHaveBeenCalled(); }); - it('should not throw Error/log.fatal if license features allowFips and `fipsMode.enabled` is `true`', () => { + it('should not throw Error/log.error if license features allowFips and `fipsMode.enabled` is `true`', () => { fipsServiceSetup = fipsService.setup( buildMockFipsServiceSetupParams('platinum', true, of({ allowFips: true })) ); fipsServiceSetup.validateLicenseForFips(); - expect(logger.fatal).not.toHaveBeenCalled(); + expect(() => { + fipsServiceSetup.validateLicenseForFips(); + }).not.toThrowError(); + expect(logger.error).not.toHaveBeenCalled(); }); - it('should not throw Error/log.fatal if license features do not allowFips and `fipsMode.enabled` is `false`', () => { + it('should not throw Error/log.error if license features do not allowFips and `fipsMode.enabled` is `false`', () => { fipsServiceSetup = fipsService.setup( - buildMockFipsServiceSetupParams('basic', false, of({})) + buildMockFipsServiceSetupParams('basic', false, of({ allowFips: false })) ); fipsServiceSetup.validateLicenseForFips(); - expect(logger.fatal).not.toHaveBeenCalled(); + expect(() => { + fipsServiceSetup.validateLicenseForFips(); + }).not.toThrowError(); + expect(logger.error).not.toHaveBeenCalled(); }); - it('should throw Error/log.fatal if license features do not allowFips and `fipsMode.enabled` is `true`', () => { + it('should throw Error/log.error if license features do not allowFips and `fipsMode.enabled` is `true`', () => { + const mockExit: jest.SpyInstance = jest + .spyOn(process, 'exit') + .mockImplementation((exitCode) => { + throw new Error(`Fake Exit: ${exitCode}`); + }); + fipsServiceSetup = fipsService.setup( - buildMockFipsServiceSetupParams('basic', true, of({})) + buildMockFipsServiceSetupParams('basic', true, of({ allowFips: false })) ); - expect(() => { + try { fipsServiceSetup.validateLicenseForFips(); - }).toThrowError(); - expect(logger.fatal).toHaveBeenCalledTimes(1); + } catch (e) { + expect(mockExit).toHaveBeenNthCalledWith(1, 78); + } + + expect(logger.error).toHaveBeenCalled(); }); }); @@ -151,19 +166,19 @@ describe('FipsService', () => { mockIsAvailableSubject.next(true); }); - it('should not log.fatal if license changes to unavailable and `fipsMode.enabled` is `true`', () => { + it('should not log.error if license changes to unavailable and `fipsMode.enabled` is `true`', () => { mockIsAvailableSubject.next(false); - expect(logger.fatal).not.toHaveBeenCalled(); + expect(logger.error).not.toHaveBeenCalled(); }); - it('should not log.fatal if license features continue to allowFips and `fipsMode.enabled` is `true`', () => { + it('should not log.error if license features continue to allowFips and `fipsMode.enabled` is `true`', () => { mockFeaturesSubject.next({ allowFips: true }); - expect(logger.fatal).not.toHaveBeenCalled(); + expect(logger.error).not.toHaveBeenCalled(); }); - it('should log.fatal if license features change to not allowFips and `fipsMode.enabled` is `true`', () => { - mockFeaturesSubject.next({}); - expect(logger.fatal).toHaveBeenCalledTimes(1); + it('should log.error if license features change to not allowFips and `fipsMode.enabled` is `true`', () => { + mockFeaturesSubject.next({ allowFips: false }); + expect(logger.error).toHaveBeenCalledTimes(1); }); }); @@ -193,19 +208,19 @@ describe('FipsService', () => { mockIsAvailableSubject.next(true); }); - it('should not log.fatal if license changes to unavailable and `fipsMode.enabled` is `false`', () => { + it('should not log.error if license changes to unavailable and `fipsMode.enabled` is `false`', () => { mockIsAvailableSubject.next(false); - expect(logger.fatal).not.toHaveBeenCalled(); + expect(logger.error).not.toHaveBeenCalled(); }); - it('should not log.fatal if license features continue to allowFips and `fipsMode.enabled` is `false`', () => { + it('should not log.error if license features continue to allowFips and `fipsMode.enabled` is `false`', () => { mockFeaturesSubject.next({ allowFips: true }); - expect(logger.fatal).not.toHaveBeenCalled(); + expect(logger.error).not.toHaveBeenCalled(); }); - it('should not log.fatal if license change to not allowFips and `fipsMode.enabled` is `false`', () => { - mockFeaturesSubject.next({}); - expect(logger.fatal).not.toHaveBeenCalled(); + it('should not log.error if license change to not allowFips and `fipsMode.enabled` is `false`', () => { + mockFeaturesSubject.next({ allowFips: false }); + expect(logger.error).not.toHaveBeenCalled(); }); }); }); diff --git a/x-pack/plugins/security/server/fips/fips_service.ts b/x-pack/plugins/security/server/fips/fips_service.ts index f6d607d3b7587..d52ba7d3b62a2 100644 --- a/x-pack/plugins/security/server/fips/fips_service.ts +++ b/x-pack/plugins/security/server/fips/fips_service.ts @@ -21,9 +21,11 @@ export interface FipsServiceSetupInternal { export class FipsService { private readonly logger: Logger; + private isInitialLicenseLoaded: boolean; constructor(logger: Logger) { this.logger = logger; + this.isInitialLicenseLoaded = false; } setup({ config, license }: FipsServiceSetupParams): FipsServiceSetupInternal { @@ -33,18 +35,27 @@ export class FipsService { } private validateLicenseForFips(config: ConfigType, license: SecurityLicense) { - const errorMessage = `Your current license level is ${license.getLicenseType()} and does not support running in FIPS mode.`; - - if (config?.fipsMode.enabled && !license.getFeatures().allowFips) { - this.logger.fatal(errorMessage); - throw new Error(errorMessage); - } - license.features$.subscribe({ next: (features) => { - if (license.isLicenseAvailable() && config?.fipsMode.enabled && !features.allowFips) { - this.logger.fatal( - `${errorMessage} Kibana will not be able to restart. Please upgrade your license to Platinum or higher.` + const errorMessage = `Your current license level is ${license.getLicenseType()} and does not support running in FIPS mode.`; + + if (license.isLicenseAvailable() && !this.isInitialLicenseLoaded) { + if (config?.fipsMode.enabled && !license.getFeatures().allowFips) { + this.logger.error(errorMessage); + process.exit(78); + } + + this.isInitialLicenseLoaded = true; + } + + if ( + this.isInitialLicenseLoaded && + license.isLicenseAvailable() && + config?.fipsMode.enabled && + !features.allowFips + ) { + this.logger.error( + `${errorMessage} Kibana will not be able to restart. Please upgrade your license to platinum or higher.` ); } }, From 955cf08a80baa8c62a0b973f48f367db71957535 Mon Sep 17 00:00:00 2001 From: Kurt Date: Wed, 24 Apr 2024 17:17:49 -0400 Subject: [PATCH 13/28] Throwing error instead of exiting --- .../security/server/fips/fips_service.test.ts | 22 ++----------------- .../security/server/fips/fips_service.ts | 2 +- 2 files changed, 3 insertions(+), 21 deletions(-) diff --git a/x-pack/plugins/security/server/fips/fips_service.test.ts b/x-pack/plugins/security/server/fips/fips_service.test.ts index 82b7f264908ed..3ff652ca33399 100644 --- a/x-pack/plugins/security/server/fips/fips_service.test.ts +++ b/x-pack/plugins/security/server/fips/fips_service.test.ts @@ -89,9 +89,6 @@ describe('FipsService', () => { ); fipsServiceSetup.validateLicenseForFips(); - expect(() => { - fipsServiceSetup.validateLicenseForFips(); - }).not.toThrowError(); expect(logger.error).not.toHaveBeenCalled(); }); @@ -101,9 +98,6 @@ describe('FipsService', () => { ); fipsServiceSetup.validateLicenseForFips(); - expect(() => { - fipsServiceSetup.validateLicenseForFips(); - }).not.toThrowError(); expect(logger.error).not.toHaveBeenCalled(); }); @@ -113,28 +107,16 @@ describe('FipsService', () => { ); fipsServiceSetup.validateLicenseForFips(); - expect(() => { - fipsServiceSetup.validateLicenseForFips(); - }).not.toThrowError(); expect(logger.error).not.toHaveBeenCalled(); }); it('should throw Error/log.error if license features do not allowFips and `fipsMode.enabled` is `true`', () => { - const mockExit: jest.SpyInstance = jest - .spyOn(process, 'exit') - .mockImplementation((exitCode) => { - throw new Error(`Fake Exit: ${exitCode}`); - }); - fipsServiceSetup = fipsService.setup( buildMockFipsServiceSetupParams('basic', true, of({ allowFips: false })) ); - try { - fipsServiceSetup.validateLicenseForFips(); - } catch (e) { - expect(mockExit).toHaveBeenNthCalledWith(1, 78); - } + // Because the Error is thrown from within a SafeSubscriber and cannot be hooked into + fipsServiceSetup.validateLicenseForFips(); expect(logger.error).toHaveBeenCalled(); }); diff --git a/x-pack/plugins/security/server/fips/fips_service.ts b/x-pack/plugins/security/server/fips/fips_service.ts index d52ba7d3b62a2..9f9c01254bca2 100644 --- a/x-pack/plugins/security/server/fips/fips_service.ts +++ b/x-pack/plugins/security/server/fips/fips_service.ts @@ -42,7 +42,7 @@ export class FipsService { if (license.isLicenseAvailable() && !this.isInitialLicenseLoaded) { if (config?.fipsMode.enabled && !license.getFeatures().allowFips) { this.logger.error(errorMessage); - process.exit(78); + throw new Error(errorMessage); } this.isInitialLicenseLoaded = true; From cb93a257eda85018ec7b7ffd409efb9553284e28 Mon Sep 17 00:00:00 2001 From: Kurt Date: Wed, 29 May 2024 23:56:48 -0400 Subject: [PATCH 14/28] Changing config to experimental and adding updated FIPS docs --- docs/user/security/fips-140-2.asciidoc | 62 ++++++++++++++++++ .../resources/base/bin/kibana-docker | 2 +- x-pack/plugins/security/server/config.test.ts | 64 ++++++++++++------- x-pack/plugins/security/server/config.ts | 10 +-- .../security/server/fips/fips_service.test.ts | 26 ++++---- .../security/server/fips/fips_service.ts | 4 +- 6 files changed, 126 insertions(+), 42 deletions(-) create mode 100644 docs/user/security/fips-140-2.asciidoc diff --git a/docs/user/security/fips-140-2.asciidoc b/docs/user/security/fips-140-2.asciidoc new file mode 100644 index 0000000000000..aabf5f3f721b1 --- /dev/null +++ b/docs/user/security/fips-140-2.asciidoc @@ -0,0 +1,62 @@ +[role="xpack"] +[[xpack-security-fips-140-2]] +=== FIPS 140-2 + +experimental[] The Federal Information Processing Standard (FIPS) Publication 140-2, (FIPS PUB 140-2), +titled "Security Requirements for Cryptographic Modules" is a U.S. government computer security standard +used to approve cryptographic modules. + +{kib} offers a FIPS 140-2 compliant mode and as such can run in a Node.js environment configured with a FIPS +140-2 compliant OpenSSL3 provider. + +To run {kib} in FIPS mode, your cluster must be have a Platinum or Enterprise license. + +[IMPORTANT] +============================================================================ +The Node bundled with {kib} is not configured for FIPS 140-2. You must configure a FIPS 140-2 compliant OpenSSL3 +provider. Please consult the Node.js documentation to learn how to configure your environment. +============================================================================ + +For {kib}, adherence to FIPS 140-2 is ensured by: + +* Using FIPS approved / NIST recommended cryptographic algorithms. + +* Delegating the implementation of these cryptographic algorithms to a NIST validated cryptographic module +(available via Node.js configured with an OpenSSL3 provider). + +* Allowing the configuration of {kib} in a FIPS 140-2 compliant manner, as documented below. + +==== Configuring {kib} for FIPS 140-2 + +Apart from setting `xpack.security.experimental.fipsMode.enabled` to `true` in your {kib} config, a number of security related +settings need to be reviewed and configured in order to run {kib} successfully in a FIPS 140-2 compliant Node.js +environment. + +===== Kibana keystore + +FIPS 140-2 (via NIST Special Publication 800-132) dictates that encryption keys should at least have an effective +strength of 112 bits. As such, the Kibana keystore that stores the application’s secure settings needs to be +password protected with a password that satisfies this requirement. This means that the password needs to be 14 bytes +long which is equivalent to a 14 character ASCII encoded password, or a 7 character UTF-8 encoded password. + +For more information on how to set this password, please see the <>. + +===== TLS keystore and keys + +Keystores can be used in a number of General TLS settings in order to conveniently store key and trust material. +PKCS#12 keystores cannot be used in a FIPS 140-2 compliant Node.js environment. Avoid using these types of keystores. +Your FIPS 140-2 provider may provide a compliant keystore implementation that can be used, or you can use PEM encoded +files. To use PEM encoded key material, you can use the relevant `\*.key` and `*.certificate` configuration options, +and for trust material you can use `*.certificate_authorities`. + +As an example, avoid PKCS#12 specific settings such as: + +* `server.ssl.keystore.path` +* `server.ssl.truststore.path` +* `elasticsearch.ssl.keystore.path` +* `elasticsearch.ssl.truststore.path` + +===== Limitations + +Configuring {kib} to run in FIPS mode is still considered to be experimental. Not all features are guaranteed to +function as expected. diff --git a/src/dev/build/tasks/os_packages/docker_generator/resources/base/bin/kibana-docker b/src/dev/build/tasks/os_packages/docker_generator/resources/base/bin/kibana-docker index 1bae3dfff35aa..430d8c2f3fb18 100755 --- a/src/dev/build/tasks/os_packages/docker_generator/resources/base/bin/kibana-docker +++ b/src/dev/build/tasks/os_packages/docker_generator/resources/base/bin/kibana-docker @@ -386,7 +386,7 @@ kibana_vars=( xpack.security.authc.selector.enabled xpack.security.cookieName xpack.security.encryptionKey - xpack.security.fipsMode.enabled + xpack.security.experimental.fipsMode.enabled xpack.security.loginAssistanceMessage xpack.security.loginHelp xpack.security.sameSiteCookies diff --git a/x-pack/plugins/security/server/config.test.ts b/x-pack/plugins/security/server/config.test.ts index 9890557bbbc9e..fe7a7fbf22ac1 100644 --- a/x-pack/plugins/security/server/config.test.ts +++ b/x-pack/plugins/security/server/config.test.ts @@ -65,8 +65,10 @@ describe('config schema', () => { }, "cookieName": "sid", "encryptionKey": "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa", - "fipsMode": Object { - "enabled": false, + "experimental": Object { + "fipsMode": Object { + "enabled": false, + }, }, "loginAssistanceMessage": "", "public": Object {}, @@ -122,8 +124,10 @@ describe('config schema', () => { }, "cookieName": "sid", "encryptionKey": "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa", - "fipsMode": Object { - "enabled": false, + "experimental": Object { + "fipsMode": Object { + "enabled": false, + }, }, "loginAssistanceMessage": "", "public": Object {}, @@ -178,8 +182,10 @@ describe('config schema', () => { "selector": Object {}, }, "cookieName": "sid", - "fipsMode": Object { - "enabled": false, + "experimental": Object { + "fipsMode": Object { + "enabled": false, + }, }, "loginAssistanceMessage": "", "public": Object {}, @@ -237,8 +243,10 @@ describe('config schema', () => { "selector": Object {}, }, "cookieName": "sid", - "fipsMode": Object { - "enabled": false, + "experimental": Object { + "fipsMode": Object { + "enabled": false, + }, }, "loginAssistanceMessage": "", "public": Object {}, @@ -2526,13 +2534,17 @@ describe('checkFipsConfig', () => { mockExit.mockRestore(); }); - it('should log an error message if FIPS mode is misconfigured - xpack.security.fipsMode.enabled true, Nodejs FIPS mode false', async () => { + it('should log an error message if FIPS mode is misconfigured - xpack.security.experimental.fipsMode.enabled true, Nodejs FIPS mode false', async () => { const logger = loggingSystemMock.create().get(); try { - createConfig(ConfigSchema.validate({ fipsMode: { enabled: true } }), logger, { - isTLSEnabled: true, - }); + createConfig( + ConfigSchema.validate({ experimental: { fipsMode: { enabled: true } } }), + logger, + { + isTLSEnabled: true, + } + ); } catch (e) { expect(mockExit).toHaveBeenNthCalledWith(1, 78); } @@ -2540,13 +2552,13 @@ describe('checkFipsConfig', () => { expect(loggingSystemMock.collect(logger).error).toMatchInlineSnapshot(` Array [ Array [ - "Configuration mismatch error. xpack.security.fipsMode.enabled is set to true and the configured Node.js environment has FIPS disabled", + "Configuration mismatch error. xpack.security.experimental.fipsMode.enabled is set to true and the configured Node.js environment has FIPS disabled", ], ] `); }); - it('should log an error message if FIPS mode is misconfigured - xpack.security.fipsMode.enabled false, Nodejs FIPS mode true', async () => { + it('should log an error message if FIPS mode is misconfigured - xpack.security.experimental.fipsMode.enabled false, Nodejs FIPS mode true', async () => { mockGetFipsFn.mockImplementationOnce(() => { return 1; }); @@ -2554,9 +2566,13 @@ describe('checkFipsConfig', () => { const logger = loggingSystemMock.create().get(); try { - createConfig(ConfigSchema.validate({ fipsMode: { enabled: false } }), logger, { - isTLSEnabled: true, - }); + createConfig( + ConfigSchema.validate({ experimental: { fipsMode: { enabled: false } } }), + logger, + { + isTLSEnabled: true, + } + ); } catch (e) { expect(mockExit).toHaveBeenNthCalledWith(1, 78); } @@ -2564,13 +2580,13 @@ describe('checkFipsConfig', () => { expect(loggingSystemMock.collect(logger).error).toMatchInlineSnapshot(` Array [ Array [ - "Configuration mismatch error. xpack.security.fipsMode.enabled is set to false and the configured Node.js environment has FIPS enabled", + "Configuration mismatch error. xpack.security.experimental.fipsMode.enabled is set to false and the configured Node.js environment has FIPS enabled", ], ] `); }); - it('should log an info message if FIPS mode is properly configured - xpack.security.fipsMode.enabled true, Nodejs FIPS mode true', async () => { + it('should log an info message if FIPS mode is properly configured - xpack.security.experimental.fipsMode.enabled true, Nodejs FIPS mode true', async () => { mockGetFipsFn.mockImplementationOnce(() => { return 1; }); @@ -2578,9 +2594,13 @@ describe('checkFipsConfig', () => { const logger = loggingSystemMock.create().get(); try { - createConfig(ConfigSchema.validate({ fipsMode: { enabled: true } }), logger, { - isTLSEnabled: true, - }); + createConfig( + ConfigSchema.validate({ experimental: { fipsMode: { enabled: true } } }), + logger, + { + isTLSEnabled: true, + } + ); } catch (e) { logger.error('Should not throw error!'); } diff --git a/x-pack/plugins/security/server/config.ts b/x-pack/plugins/security/server/config.ts index 2f700c9341fc1..4e28de37c853f 100644 --- a/x-pack/plugins/security/server/config.ts +++ b/x-pack/plugins/security/server/config.ts @@ -314,13 +314,15 @@ export const ConfigSchema = schema.object({ roleMappingManagementEnabled: schema.boolean({ defaultValue: true }), }), }), - fipsMode: schema.object({ - enabled: schema.boolean({ defaultValue: false }), + experimental: schema.object({ + fipsMode: schema.object({ + enabled: schema.boolean({ defaultValue: false }), + }), }), }); function checkFipsConfig(config: RawConfigType, logger: Logger) { - const isFipsEnabled = config.fipsMode.enabled; + const isFipsEnabled = config.experimental.fipsMode.enabled; const isNodeRunningWithFipsEnabled = getFips() === 1; // Check if FIPS is enabled in either setting @@ -328,7 +330,7 @@ function checkFipsConfig(config: RawConfigType, logger: Logger) { // FIPS must be enabled on both or log and error an exit Kibana if (isFipsEnabled !== isNodeRunningWithFipsEnabled) { logger.error( - `Configuration mismatch error. xpack.security.fipsMode.enabled is set to ${isFipsEnabled} and the configured Node.js environment has FIPS ${ + `Configuration mismatch error. xpack.security.experimental.fipsMode.enabled is set to ${isFipsEnabled} and the configured Node.js environment has FIPS ${ isNodeRunningWithFipsEnabled ? 'enabled' : 'disabled' }` ); diff --git a/x-pack/plugins/security/server/fips/fips_service.test.ts b/x-pack/plugins/security/server/fips/fips_service.test.ts index 3ff652ca33399..aba86633c281f 100644 --- a/x-pack/plugins/security/server/fips/fips_service.test.ts +++ b/x-pack/plugins/security/server/fips/fips_service.test.ts @@ -42,7 +42,7 @@ function buildMockFipsServiceSetupParams( let mockConfig = {}; if (isFipsConfigured) { - mockConfig = { fipsMode: { enabled: true } }; + mockConfig = { experimental: { fipsMode: { enabled: true } } }; } return { @@ -83,7 +83,7 @@ describe('FipsService', () => { describe('#validateLicenseForFips', () => { describe('start-up check', () => { - it('should not throw Error/log.error if license features allowFips and `fipsMode.enabled` is `false`', () => { + it('should not throw Error/log.error if license features allowFips and `experimental.fipsMode.enabled` is `false`', () => { fipsServiceSetup = fipsService.setup( buildMockFipsServiceSetupParams('platinum', false, of({ allowFips: true })) ); @@ -92,7 +92,7 @@ describe('FipsService', () => { expect(logger.error).not.toHaveBeenCalled(); }); - it('should not throw Error/log.error if license features allowFips and `fipsMode.enabled` is `true`', () => { + it('should not throw Error/log.error if license features allowFips and `experimental.fipsMode.enabled` is `true`', () => { fipsServiceSetup = fipsService.setup( buildMockFipsServiceSetupParams('platinum', true, of({ allowFips: true })) ); @@ -101,7 +101,7 @@ describe('FipsService', () => { expect(logger.error).not.toHaveBeenCalled(); }); - it('should not throw Error/log.error if license features do not allowFips and `fipsMode.enabled` is `false`', () => { + it('should not throw Error/log.error if license features do not allowFips and `experimental.fipsMode.enabled` is `false`', () => { fipsServiceSetup = fipsService.setup( buildMockFipsServiceSetupParams('basic', false, of({ allowFips: false })) ); @@ -110,7 +110,7 @@ describe('FipsService', () => { expect(logger.error).not.toHaveBeenCalled(); }); - it('should throw Error/log.error if license features do not allowFips and `fipsMode.enabled` is `true`', () => { + it('should throw Error/log.error if license features do not allowFips and `experimental.fipsMode.enabled` is `true`', () => { fipsServiceSetup = fipsService.setup( buildMockFipsServiceSetupParams('basic', true, of({ allowFips: false })) ); @@ -123,7 +123,7 @@ describe('FipsService', () => { }); describe('monitoring check', () => { - describe('with fipsMode.enabled', () => { + describe('with experimental.fipsMode.enabled', () => { let mockFeaturesSubject: BehaviorSubject>; let mockIsAvailableSubject: BehaviorSubject; let mockFeatures$: Observable>; @@ -148,23 +148,23 @@ describe('FipsService', () => { mockIsAvailableSubject.next(true); }); - it('should not log.error if license changes to unavailable and `fipsMode.enabled` is `true`', () => { + it('should not log.error if license changes to unavailable and `experimental.fipsMode.enabled` is `true`', () => { mockIsAvailableSubject.next(false); expect(logger.error).not.toHaveBeenCalled(); }); - it('should not log.error if license features continue to allowFips and `fipsMode.enabled` is `true`', () => { + it('should not log.error if license features continue to allowFips and `experimental.fipsMode.enabled` is `true`', () => { mockFeaturesSubject.next({ allowFips: true }); expect(logger.error).not.toHaveBeenCalled(); }); - it('should log.error if license features change to not allowFips and `fipsMode.enabled` is `true`', () => { + it('should log.error if license features change to not allowFips and `experimental.fipsMode.enabled` is `true`', () => { mockFeaturesSubject.next({ allowFips: false }); expect(logger.error).toHaveBeenCalledTimes(1); }); }); - describe('with not fipsMode.enabled', () => { + describe('with not experimental.fipsMode.enabled', () => { let mockFeaturesSubject: BehaviorSubject>; let mockIsAvailableSubject: BehaviorSubject; let mockFeatures$: Observable>; @@ -190,17 +190,17 @@ describe('FipsService', () => { mockIsAvailableSubject.next(true); }); - it('should not log.error if license changes to unavailable and `fipsMode.enabled` is `false`', () => { + it('should not log.error if license changes to unavailable and `experimental.fipsMode.enabled` is `false`', () => { mockIsAvailableSubject.next(false); expect(logger.error).not.toHaveBeenCalled(); }); - it('should not log.error if license features continue to allowFips and `fipsMode.enabled` is `false`', () => { + it('should not log.error if license features continue to allowFips and `experimental.fipsMode.enabled` is `false`', () => { mockFeaturesSubject.next({ allowFips: true }); expect(logger.error).not.toHaveBeenCalled(); }); - it('should not log.error if license change to not allowFips and `fipsMode.enabled` is `false`', () => { + it('should not log.error if license change to not allowFips and `experimental.fipsMode.enabled` is `false`', () => { mockFeaturesSubject.next({ allowFips: false }); expect(logger.error).not.toHaveBeenCalled(); }); diff --git a/x-pack/plugins/security/server/fips/fips_service.ts b/x-pack/plugins/security/server/fips/fips_service.ts index 9f9c01254bca2..aa351ab48828d 100644 --- a/x-pack/plugins/security/server/fips/fips_service.ts +++ b/x-pack/plugins/security/server/fips/fips_service.ts @@ -40,7 +40,7 @@ export class FipsService { const errorMessage = `Your current license level is ${license.getLicenseType()} and does not support running in FIPS mode.`; if (license.isLicenseAvailable() && !this.isInitialLicenseLoaded) { - if (config?.fipsMode.enabled && !license.getFeatures().allowFips) { + if (config?.experimental.fipsMode.enabled && !license.getFeatures().allowFips) { this.logger.error(errorMessage); throw new Error(errorMessage); } @@ -51,7 +51,7 @@ export class FipsService { if ( this.isInitialLicenseLoaded && license.isLicenseAvailable() && - config?.fipsMode.enabled && + config?.experimental.fipsMode.enabled && !features.allowFips ) { this.logger.error( From 4daab8c67876f841e3ce673514a16ac737c5a05b Mon Sep 17 00:00:00 2001 From: Kurt Date: Thu, 30 May 2024 09:14:14 -0400 Subject: [PATCH 15/28] Removing merge conflict code --- x-pack/plugins/security/server/plugin.ts | 4 ---- 1 file changed, 4 deletions(-) diff --git a/x-pack/plugins/security/server/plugin.ts b/x-pack/plugins/security/server/plugin.ts index 59eb4cb347c8d..49e6d3cb571ae 100644 --- a/x-pack/plugins/security/server/plugin.ts +++ b/x-pack/plugins/security/server/plugin.ts @@ -204,10 +204,6 @@ export class SecurityPlugin this.analyticsService = new AnalyticsService(this.initializerContext.logger.get('analytics')); - this.userProfileSettingsClient = new UserProfileSettingsClient( - this.initializerContext.logger.get('user-settings-client') - ); - this.fipsService = new FipsService(this.initializerContext.logger.get('fips')); } From 27c9d153d142bb00385ef38bc736d4e409e267f5 Mon Sep 17 00:00:00 2001 From: Kurt Date: Thu, 30 May 2024 11:32:08 -0400 Subject: [PATCH 16/28] Updating jest test snapshot --- .../es/__snapshots__/remote_cluster_privileges.test.tsx.snap | 1 + 1 file changed, 1 insertion(+) diff --git a/x-pack/plugins/security/public/management/roles/edit_role/privileges/es/__snapshots__/remote_cluster_privileges.test.tsx.snap b/x-pack/plugins/security/public/management/roles/edit_role/privileges/es/__snapshots__/remote_cluster_privileges.test.tsx.snap index e0939f7f55e02..7cc4e67ece4fc 100644 --- a/x-pack/plugins/security/public/management/roles/edit_role/privileges/es/__snapshots__/remote_cluster_privileges.test.tsx.snap +++ b/x-pack/plugins/security/public/management/roles/edit_role/privileges/es/__snapshots__/remote_cluster_privileges.test.tsx.snap @@ -14,6 +14,7 @@ exports[`it renders without crashing 1`] = ` "_subscribe": [Function], }, "getFeatures": [MockFunction], + "getLicenseType": [MockFunction], "getUnavailableReason": [MockFunction], "hasAtLeast": [MockFunction], "isEnabled": [MockFunction], From 2126400e68754360cb978c3214319e88d37b79eb Mon Sep 17 00:00:00 2001 From: Kurt Date: Wed, 12 Jun 2024 21:55:45 -0400 Subject: [PATCH 17/28] Exposing fips setting from security setup --- .../plugin_types_server/src/plugin.ts | 6 ++ .../security/server/experimental/index.ts | 11 ++++ .../security/server/fips/fips_service.test.ts | 65 ++++++++++++++++++- .../security/server/fips/fips_service.ts | 6 ++ x-pack/plugins/security/server/plugin.test.ts | 3 + x-pack/plugins/security/server/plugin.ts | 3 + 6 files changed, 93 insertions(+), 1 deletion(-) create mode 100644 x-pack/plugins/security/server/experimental/index.ts diff --git a/x-pack/packages/security/plugin_types_server/src/plugin.ts b/x-pack/packages/security/plugin_types_server/src/plugin.ts index c8222163785bf..7ef64023bcad4 100644 --- a/x-pack/packages/security/plugin_types_server/src/plugin.ts +++ b/x-pack/packages/security/plugin_types_server/src/plugin.ts @@ -6,6 +6,7 @@ */ import type { SecurityLicense } from '@kbn/security-plugin-types-common'; +import { Experimental } from '@kbn/security-plugin/server/experimental'; import type { AuditServiceSetup } from './audit'; import type { PrivilegeDeprecationsService, AuthorizationServiceSetup } from './authorization'; import type { AuthenticationServiceStart } from './authentication'; @@ -29,6 +30,11 @@ export interface SecurityPluginSetup { * Exposes services to access kibana roles per feature id with the GetDeprecationsContext */ privilegeDeprecationsService: PrivilegeDeprecationsService; + + /** + * Exposes experimental features + */ + experimental: Experimental; } /** diff --git a/x-pack/plugins/security/server/experimental/index.ts b/x-pack/plugins/security/server/experimental/index.ts new file mode 100644 index 0000000000000..aaef0d8de9cd4 --- /dev/null +++ b/x-pack/plugins/security/server/experimental/index.ts @@ -0,0 +1,11 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ +import type { FipsServiceSetupInternal } from '../fips'; + +export interface Experimental { + isFipsEnabled: () => ReturnType; +} diff --git a/x-pack/plugins/security/server/fips/fips_service.test.ts b/x-pack/plugins/security/server/fips/fips_service.test.ts index aba86633c281f..d1facd19ab4a4 100644 --- a/x-pack/plugins/security/server/fips/fips_service.test.ts +++ b/x-pack/plugins/security/server/fips/fips_service.test.ts @@ -19,7 +19,7 @@ import { BehaviorSubject, of } from 'rxjs'; import { loggingSystemMock } from '@kbn/core/server/mocks'; import type { LicenseType } from '@kbn/licensing-plugin/common/types'; -import type { SecurityLicenseFeatures } from '@kbn/security-plugin-types-common'; +import type { SecurityLicense, SecurityLicenseFeatures } from '@kbn/security-plugin-types-common'; import type { FipsServiceSetupInternal, FipsServiceSetupParams } from './fips_service'; import { FipsService } from './fips_service'; @@ -75,12 +75,75 @@ describe('FipsService', () => { expect(fipsServiceSetup).toMatchInlineSnapshot(` Object { + "isKibanaFipsModeEnabled": [Function], "validateLicenseForFips": [Function], } `); }); }); + describe('#isKibanaFipsModeEnabled', () => { + let license: SecurityLicense; + beforeEach(() => { + license = licenseMock.create(of({ allowFips: true }), 'platinum'); + + fipsService = new FipsService(logger); + }); + + it('should return `true` when config `xpack.security.experimental.fipsMode.enabled` is `true`', () => { + mockGetFipsFn.mockImplementationOnce(() => { + return 1; + }); + + fipsServiceSetup = fipsService.setup({ + license, + config: createConfig( + ConfigSchema.validate({ experimental: { fipsMode: { enabled: true } } }), + loggingSystemMock.createLogger(), + { + isTLSEnabled: false, + } + ), + }); + + expect(fipsServiceSetup.isKibanaFipsModeEnabled()).toBe(true); + }); + + it('should return `false` when config `xpack.security.experimental.fipsMode.enabled` is `false`', () => { + mockGetFipsFn.mockImplementationOnce(() => { + return 0; + }); + + fipsServiceSetup = fipsService.setup({ + license, + config: createConfig( + ConfigSchema.validate({ experimental: { fipsMode: { enabled: false } } }), + loggingSystemMock.createLogger(), + { + isTLSEnabled: false, + } + ), + }); + + expect(fipsServiceSetup.isKibanaFipsModeEnabled()).toBe(false); + }); + + it('should return `false` when config `xpack.security.experimental.fipsMode.enabled` is `undefined`', () => { + mockGetFipsFn.mockImplementationOnce(() => { + return 0; + }); + + fipsServiceSetup = fipsService.setup({ + license, + config: createConfig(ConfigSchema.validate({}), loggingSystemMock.createLogger(), { + isTLSEnabled: false, + }), + }); + + expect(fipsServiceSetup.isKibanaFipsModeEnabled()).toBe(false); + }); + }); + describe('#validateLicenseForFips', () => { describe('start-up check', () => { it('should not throw Error/log.error if license features allowFips and `experimental.fipsMode.enabled` is `false`', () => { diff --git a/x-pack/plugins/security/server/fips/fips_service.ts b/x-pack/plugins/security/server/fips/fips_service.ts index aa351ab48828d..03446dc12a30d 100644 --- a/x-pack/plugins/security/server/fips/fips_service.ts +++ b/x-pack/plugins/security/server/fips/fips_service.ts @@ -17,6 +17,7 @@ export interface FipsServiceSetupParams { export interface FipsServiceSetupInternal { validateLicenseForFips: () => void; + isKibanaFipsModeEnabled: () => boolean; } export class FipsService { @@ -31,9 +32,14 @@ export class FipsService { setup({ config, license }: FipsServiceSetupParams): FipsServiceSetupInternal { return { validateLicenseForFips: () => this.validateLicenseForFips(config, license), + isKibanaFipsModeEnabled: () => this.isKibanaFipsModeEnabled(config), }; } + private isKibanaFipsModeEnabled(config: ConfigType): boolean { + return config?.experimental.fipsMode.enabled; + } + private validateLicenseForFips(config: ConfigType, license: SecurityLicense) { license.features$.subscribe({ next: (features) => { diff --git a/x-pack/plugins/security/server/plugin.test.ts b/x-pack/plugins/security/server/plugin.test.ts index a82b45753845b..6aa3722ba93f7 100644 --- a/x-pack/plugins/security/server/plugin.test.ts +++ b/x-pack/plugins/security/server/plugin.test.ts @@ -113,6 +113,9 @@ describe('Security Plugin', () => { "useRbacForRequest": [Function], }, }, + "experimental": Object { + "isFipsEnabled": [Function], + }, "license": Object { "features$": Observable { "operator": [Function], diff --git a/x-pack/plugins/security/server/plugin.ts b/x-pack/plugins/security/server/plugin.ts index 49e6d3cb571ae..fe3d3c27b0181 100644 --- a/x-pack/plugins/security/server/plugin.ts +++ b/x-pack/plugins/security/server/plugin.ts @@ -357,6 +357,9 @@ export class SecurityPlugin license, logger: this.logger.get('deprecations'), }), + experimental: { + isFipsEnabled: this.fipsServiceSetup.isKibanaFipsModeEnabled, + }, }); } From d871efcb5b68e382d8d5695caf796216c19f70bb Mon Sep 17 00:00:00 2001 From: kibanamachine <42973632+kibanamachine@users.noreply.github.com> Date: Thu, 13 Jun 2024 02:08:30 +0000 Subject: [PATCH 18/28] [CI] Auto-commit changed files from 'node scripts/lint_ts_projects --fix' --- x-pack/packages/security/plugin_types_server/tsconfig.json | 1 + 1 file changed, 1 insertion(+) diff --git a/x-pack/packages/security/plugin_types_server/tsconfig.json b/x-pack/packages/security/plugin_types_server/tsconfig.json index 51a1cf53c62bf..b1d61f46107e8 100644 --- a/x-pack/packages/security/plugin_types_server/tsconfig.json +++ b/x-pack/packages/security/plugin_types_server/tsconfig.json @@ -15,5 +15,6 @@ "@kbn/security-plugin-types-common", "@kbn/core-user-profile-server", "@kbn/core-security-server", + "@kbn/security-plugin", ] } From c37004ac14ef151b52f138a1fe649f93176fdee1 Mon Sep 17 00:00:00 2001 From: lcawl Date: Tue, 18 Jun 2024 08:45:29 -0700 Subject: [PATCH 19/28] [DOCS] Minor doc edits --- docs/user/security/fips-140-2.asciidoc | 11 ++++++----- docs/user/security/index.asciidoc | 1 + 2 files changed, 7 insertions(+), 5 deletions(-) diff --git a/docs/user/security/fips-140-2.asciidoc b/docs/user/security/fips-140-2.asciidoc index aabf5f3f721b1..2b4b195f38b05 100644 --- a/docs/user/security/fips-140-2.asciidoc +++ b/docs/user/security/fips-140-2.asciidoc @@ -1,20 +1,21 @@ -[role="xpack"] [[xpack-security-fips-140-2]] === FIPS 140-2 -experimental[] The Federal Information Processing Standard (FIPS) Publication 140-2, (FIPS PUB 140-2), +experimental::[] + +The Federal Information Processing Standard (FIPS) Publication 140-2, (FIPS PUB 140-2), titled "Security Requirements for Cryptographic Modules" is a U.S. government computer security standard used to approve cryptographic modules. {kib} offers a FIPS 140-2 compliant mode and as such can run in a Node.js environment configured with a FIPS 140-2 compliant OpenSSL3 provider. -To run {kib} in FIPS mode, your cluster must be have a Platinum or Enterprise license. +To run {kib} in FIPS mode, you must have the appropriate {subscriptions}[subscription]. [IMPORTANT] ============================================================================ The Node bundled with {kib} is not configured for FIPS 140-2. You must configure a FIPS 140-2 compliant OpenSSL3 -provider. Please consult the Node.js documentation to learn how to configure your environment. +provider. Consult the Node.js documentation to learn how to configure your environment. ============================================================================ For {kib}, adherence to FIPS 140-2 is ensured by: @@ -39,7 +40,7 @@ strength of 112 bits. As such, the Kibana keystore that stores the application password protected with a password that satisfies this requirement. This means that the password needs to be 14 bytes long which is equivalent to a 14 character ASCII encoded password, or a 7 character UTF-8 encoded password. -For more information on how to set this password, please see the <>. +For more information on how to set this password, refer to the <>. ===== TLS keystore and keys diff --git a/docs/user/security/index.asciidoc b/docs/user/security/index.asciidoc index f4678700d5e77..906aee3d76d5a 100644 --- a/docs/user/security/index.asciidoc +++ b/docs/user/security/index.asciidoc @@ -46,3 +46,4 @@ include::authorization/index.asciidoc[] include::authorization/kibana-privileges.asciidoc[] include::api-keys/index.asciidoc[] include::role-mappings/index.asciidoc[] +include::fips-140-2.asciidoc[] From 57c2f3654acfc312b0945079840bae6e1f2151b8 Mon Sep 17 00:00:00 2001 From: Kurt Date: Thu, 20 Jun 2024 13:58:11 -0400 Subject: [PATCH 20/28] Exposing fips flag from core --- .../src/plugin_context.ts | 1 + .../src/fips/fips.ts | 13 ++++++++ .../src/security_service.ts | 30 ++++++++++++++++++- .../src/security_service.mock.ts | 6 ++++ .../security/core-security-server/index.ts | 1 + .../core-security-server/src/contracts.ts | 6 ++++ .../security/core-security-server/src/fips.ts | 19 ++++++++++++ .../plugin_types_server/src/plugin.ts | 6 ---- .../security/server/experimental/index.ts | 11 ------- x-pack/plugins/security/server/plugin.ts | 3 -- 10 files changed, 75 insertions(+), 21 deletions(-) create mode 100644 packages/core/security/core-security-server-internal/src/fips/fips.ts create mode 100644 packages/core/security/core-security-server/src/fips.ts delete mode 100644 x-pack/plugins/security/server/experimental/index.ts diff --git a/packages/core/plugins/core-plugins-server-internal/src/plugin_context.ts b/packages/core/plugins/core-plugins-server-internal/src/plugin_context.ts index f1469fa57ced6..70551c1e27504 100644 --- a/packages/core/plugins/core-plugins-server-internal/src/plugin_context.ts +++ b/packages/core/plugins/core-plugins-server-internal/src/plugin_context.ts @@ -284,6 +284,7 @@ export function createPluginSetupContext({ }, security: { registerSecurityDelegate: (api) => deps.security.registerSecurityDelegate(api), + fips: deps.security.fips, }, userProfile: { registerUserProfileDelegate: (delegate) => diff --git a/packages/core/security/core-security-server-internal/src/fips/fips.ts b/packages/core/security/core-security-server-internal/src/fips/fips.ts new file mode 100644 index 0000000000000..c25b4bd2841ef --- /dev/null +++ b/packages/core/security/core-security-server-internal/src/fips/fips.ts @@ -0,0 +1,13 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0 and the Server Side Public License, v 1; you may not use this file except + * in compliance with, at your election, the Elastic License 2.0 or the Server + * Side Public License, v 1. + */ + +import type { ConfigType } from '@kbn/security-plugin/server/config'; + +export function isFipsEnabled(config: ConfigType): boolean { + return config?.experimental?.fipsMode?.enabled || false; +} diff --git a/packages/core/security/core-security-server-internal/src/security_service.ts b/packages/core/security/core-security-server-internal/src/security_service.ts index 826019f773b93..378d7eb546f91 100644 --- a/packages/core/security/core-security-server-internal/src/security_service.ts +++ b/packages/core/security/core-security-server-internal/src/security_service.ts @@ -9,6 +9,9 @@ import type { Logger } from '@kbn/logging'; import type { CoreContext, CoreService } from '@kbn/core-base-server-internal'; import type { CoreSecurityDelegateContract } from '@kbn/core-security-server'; +import { Observable, Subscription } from 'rxjs'; +import { Config } from '@kbn/config'; +import { isFipsEnabled } from './fips/fips'; import type { InternalSecurityServiceSetup, InternalSecurityServiceStart, @@ -20,12 +23,29 @@ export class SecurityService { private readonly log: Logger; private securityApi?: CoreSecurityDelegateContract; + private config$: Observable; + private configSubscription?: Subscription; + private config: Config | undefined; + private readonly getConfig = () => { + if (!this.config) { + throw new Error('Config is not available.'); + } + return this.config; + }; constructor(coreContext: CoreContext) { this.log = coreContext.logger.get('security-service'); + + this.config$ = coreContext.configService.getConfig$(); + this.configSubscription = this.config$.subscribe((config) => { + this.config = config; + }); } public setup(): InternalSecurityServiceSetup { + const config = this.getConfig(); + const securityConfig = config.get(['xpack', 'security']); + return { registerSecurityDelegate: (api) => { if (this.securityApi) { @@ -33,6 +53,9 @@ export class SecurityService } this.securityApi = api; }, + fips: { + isEnabled: () => isFipsEnabled(securityConfig), + }, }; } @@ -44,5 +67,10 @@ export class SecurityService return convertSecurityApi(apiContract); } - public stop() {} + public stop() { + if (this.configSubscription) { + this.configSubscription.unsubscribe(); + this.configSubscription = undefined; + } + } } diff --git a/packages/core/security/core-security-server-mocks/src/security_service.mock.ts b/packages/core/security/core-security-server-mocks/src/security_service.mock.ts index b19539fd862c0..192f6cf5c38df 100644 --- a/packages/core/security/core-security-server-mocks/src/security_service.mock.ts +++ b/packages/core/security/core-security-server-mocks/src/security_service.mock.ts @@ -35,6 +35,9 @@ const createStartMock = (): SecurityStartMock => { getCurrentUser: jest.fn(), }, audit: auditServiceMock.create(), + experimental: { + isFipsEnabled: jest.fn(), + }, }; return mock; @@ -60,6 +63,9 @@ const createInternalStartMock = (): InternalSecurityStartMock => { getCurrentUser: jest.fn(), }, audit: auditServiceMock.create(), + experimental: { + isFipsEnabled: jest.fn(), + }, }; return mock; diff --git a/packages/core/security/core-security-server/index.ts b/packages/core/security/core-security-server/index.ts index a4d3027c97fdb..6a111ab6e27ab 100644 --- a/packages/core/security/core-security-server/index.ts +++ b/packages/core/security/core-security-server/index.ts @@ -26,3 +26,4 @@ export type { AuditRequest, } from './src/audit_logging/audit_events'; export type { AuditLogger } from './src/audit_logging/audit_logger'; +export type { CoreFipsService } from './src/fips'; diff --git a/packages/core/security/core-security-server/src/contracts.ts b/packages/core/security/core-security-server/src/contracts.ts index ed25737823f7b..75b4174af1217 100644 --- a/packages/core/security/core-security-server/src/contracts.ts +++ b/packages/core/security/core-security-server/src/contracts.ts @@ -6,6 +6,7 @@ * Side Public License, v 1. */ +import { CoreFipsService } from './fips'; import type { CoreAuthenticationService } from './authc'; import type { CoreSecurityDelegateContract } from './api_provider'; import type { CoreAuditService } from './audit'; @@ -21,6 +22,11 @@ export interface SecurityServiceSetup { * @remark this should **exclusively** be used by the security plugin. */ registerSecurityDelegate(api: CoreSecurityDelegateContract): void; + + /** + * The {{@link CoreFipsService | FIPS service}} + */ + fips: CoreFipsService; } /** diff --git a/packages/core/security/core-security-server/src/fips.ts b/packages/core/security/core-security-server/src/fips.ts new file mode 100644 index 0000000000000..239903caba3bc --- /dev/null +++ b/packages/core/security/core-security-server/src/fips.ts @@ -0,0 +1,19 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0 and the Server Side Public License, v 1; you may not use this file except + * in compliance with, at your election, the Elastic License 2.0 or the Server + * Side Public License, v 1. + */ + +/** + * Core's FIPS service + * + * @public + */ +export interface CoreFipsService { + /** + * Check if Kibana is configured to run in FIPS mode + */ + isEnabled(): boolean; +} diff --git a/x-pack/packages/security/plugin_types_server/src/plugin.ts b/x-pack/packages/security/plugin_types_server/src/plugin.ts index 7ef64023bcad4..c8222163785bf 100644 --- a/x-pack/packages/security/plugin_types_server/src/plugin.ts +++ b/x-pack/packages/security/plugin_types_server/src/plugin.ts @@ -6,7 +6,6 @@ */ import type { SecurityLicense } from '@kbn/security-plugin-types-common'; -import { Experimental } from '@kbn/security-plugin/server/experimental'; import type { AuditServiceSetup } from './audit'; import type { PrivilegeDeprecationsService, AuthorizationServiceSetup } from './authorization'; import type { AuthenticationServiceStart } from './authentication'; @@ -30,11 +29,6 @@ export interface SecurityPluginSetup { * Exposes services to access kibana roles per feature id with the GetDeprecationsContext */ privilegeDeprecationsService: PrivilegeDeprecationsService; - - /** - * Exposes experimental features - */ - experimental: Experimental; } /** diff --git a/x-pack/plugins/security/server/experimental/index.ts b/x-pack/plugins/security/server/experimental/index.ts deleted file mode 100644 index aaef0d8de9cd4..0000000000000 --- a/x-pack/plugins/security/server/experimental/index.ts +++ /dev/null @@ -1,11 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License - * 2.0; you may not use this file except in compliance with the Elastic License - * 2.0. - */ -import type { FipsServiceSetupInternal } from '../fips'; - -export interface Experimental { - isFipsEnabled: () => ReturnType; -} diff --git a/x-pack/plugins/security/server/plugin.ts b/x-pack/plugins/security/server/plugin.ts index fc78fd85c3947..9d5ffde67b1d7 100644 --- a/x-pack/plugins/security/server/plugin.ts +++ b/x-pack/plugins/security/server/plugin.ts @@ -358,9 +358,6 @@ export class SecurityPlugin license, logger: this.logger.get('deprecations'), }), - experimental: { - isFipsEnabled: this.fipsServiceSetup.isKibanaFipsModeEnabled, - }, }); } From ff3586a951e7ad7dc0f5f76ad73874ed3d282afa Mon Sep 17 00:00:00 2001 From: kibanamachine <42973632+kibanamachine@users.noreply.github.com> Date: Thu, 20 Jun 2024 18:11:36 +0000 Subject: [PATCH 21/28] [CI] Auto-commit changed files from 'node scripts/lint_ts_projects --fix' --- .../core/security/core-security-server-internal/tsconfig.json | 2 ++ x-pack/packages/security/plugin_types_server/tsconfig.json | 1 - 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/packages/core/security/core-security-server-internal/tsconfig.json b/packages/core/security/core-security-server-internal/tsconfig.json index ad66b66deeeeb..7229df5cbff63 100644 --- a/packages/core/security/core-security-server-internal/tsconfig.json +++ b/packages/core/security/core-security-server-internal/tsconfig.json @@ -20,5 +20,7 @@ "@kbn/core-http-server", "@kbn/logging-mocks", "@kbn/core-base-server-mocks", + "@kbn/security-plugin", + "@kbn/config", ] } diff --git a/x-pack/packages/security/plugin_types_server/tsconfig.json b/x-pack/packages/security/plugin_types_server/tsconfig.json index b1d61f46107e8..51a1cf53c62bf 100644 --- a/x-pack/packages/security/plugin_types_server/tsconfig.json +++ b/x-pack/packages/security/plugin_types_server/tsconfig.json @@ -15,6 +15,5 @@ "@kbn/security-plugin-types-common", "@kbn/core-user-profile-server", "@kbn/core-security-server", - "@kbn/security-plugin", ] } From 31b75463ba733987580e0fe0f376db512d89d283 Mon Sep 17 00:00:00 2001 From: Kurt Date: Thu, 20 Jun 2024 16:31:50 -0400 Subject: [PATCH 22/28] Fixing config type and adding tests --- .../src/fips/fips.test.ts | 40 +++++++++++++++++++ .../src/fips/fips.ts | 4 +- .../src/security_service.test.ts | 10 +++++ .../src/security_service.ts | 8 +++- .../src/utils/index.ts | 8 ++++ .../security/server/fips/fips_service.ts | 6 --- x-pack/plugins/security/server/plugin.test.ts | 3 -- 7 files changed, 66 insertions(+), 13 deletions(-) create mode 100644 packages/core/security/core-security-server-internal/src/fips/fips.test.ts diff --git a/packages/core/security/core-security-server-internal/src/fips/fips.test.ts b/packages/core/security/core-security-server-internal/src/fips/fips.test.ts new file mode 100644 index 0000000000000..2e09bf2aa65b4 --- /dev/null +++ b/packages/core/security/core-security-server-internal/src/fips/fips.test.ts @@ -0,0 +1,40 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0 and the Server Side Public License, v 1; you may not use this file except + * in compliance with, at your election, the Elastic License 2.0 or the Server + * Side Public License, v 1. + */ + +import { SecurityServiceConfigType } from '../utils'; +import { isFipsEnabled } from './fips'; + +describe('fips', () => { + describe('#isFipsEnabled', () => { + let config: SecurityServiceConfigType; + + beforeAll(() => { + config = {}; + }); + + afterEach(() => { + config = {}; + }); + + it('should return `true` if config.experimental.fipsMode.enabled is `true`', () => { + config = { experimental: { fipsMode: { enabled: true } } }; + + expect(isFipsEnabled(config)).toBe(true); + }); + + it('should return `false` if config.experimental.fipsMode.enabled is `false`', () => { + config = { experimental: { fipsMode: { enabled: false } } }; + + expect(isFipsEnabled(config)).toBe(false); + }); + + it('should return `false` if config.experimental.fipsMode.enabled is `undefined`', () => { + expect(isFipsEnabled(config)).toBe(false); + }); + }); +}); diff --git a/packages/core/security/core-security-server-internal/src/fips/fips.ts b/packages/core/security/core-security-server-internal/src/fips/fips.ts index c25b4bd2841ef..fa24ac83bb880 100644 --- a/packages/core/security/core-security-server-internal/src/fips/fips.ts +++ b/packages/core/security/core-security-server-internal/src/fips/fips.ts @@ -6,8 +6,8 @@ * Side Public License, v 1. */ -import type { ConfigType } from '@kbn/security-plugin/server/config'; +import { SecurityServiceConfigType } from '../utils'; -export function isFipsEnabled(config: ConfigType): boolean { +export function isFipsEnabled(config: SecurityServiceConfigType): boolean { return config?.experimental?.fipsMode?.enabled || false; } diff --git a/packages/core/security/core-security-server-internal/src/security_service.test.ts b/packages/core/security/core-security-server-internal/src/security_service.test.ts index 4f5ae5e86cbab..5fb6b46f6dc63 100644 --- a/packages/core/security/core-security-server-internal/src/security_service.test.ts +++ b/packages/core/security/core-security-server-internal/src/security_service.test.ts @@ -45,6 +45,16 @@ describe('SecurityService', () => { ); }); }); + + describe('#fips', () => { + describe('#isEnabled', () => { + it('should return boolean', () => { + const { fips } = service.setup(); + + expect(fips.isEnabled()).toBe(false); + }); + }); + }); }); describe('#start', () => { diff --git a/packages/core/security/core-security-server-internal/src/security_service.ts b/packages/core/security/core-security-server-internal/src/security_service.ts index 378d7eb546f91..509774ab6a5fb 100644 --- a/packages/core/security/core-security-server-internal/src/security_service.ts +++ b/packages/core/security/core-security-server-internal/src/security_service.ts @@ -16,7 +16,11 @@ import type { InternalSecurityServiceSetup, InternalSecurityServiceStart, } from './internal_contracts'; -import { getDefaultSecurityImplementation, convertSecurityApi } from './utils'; +import { + getDefaultSecurityImplementation, + convertSecurityApi, + SecurityServiceConfigType, +} from './utils'; export class SecurityService implements CoreService @@ -44,7 +48,7 @@ export class SecurityService public setup(): InternalSecurityServiceSetup { const config = this.getConfig(); - const securityConfig = config.get(['xpack', 'security']); + const securityConfig: SecurityServiceConfigType = config.get(['xpack', 'security']); return { registerSecurityDelegate: (api) => { diff --git a/packages/core/security/core-security-server-internal/src/utils/index.ts b/packages/core/security/core-security-server-internal/src/utils/index.ts index e43884f204ece..6ce85739b44f6 100644 --- a/packages/core/security/core-security-server-internal/src/utils/index.ts +++ b/packages/core/security/core-security-server-internal/src/utils/index.ts @@ -8,3 +8,11 @@ export { convertSecurityApi } from './convert_security_api'; export { getDefaultSecurityImplementation } from './default_implementation'; + +export interface SecurityServiceConfigType { + experimental?: { + fipsMode?: { + enabled: boolean; + }; + }; +} diff --git a/x-pack/plugins/security/server/fips/fips_service.ts b/x-pack/plugins/security/server/fips/fips_service.ts index 03446dc12a30d..aa351ab48828d 100644 --- a/x-pack/plugins/security/server/fips/fips_service.ts +++ b/x-pack/plugins/security/server/fips/fips_service.ts @@ -17,7 +17,6 @@ export interface FipsServiceSetupParams { export interface FipsServiceSetupInternal { validateLicenseForFips: () => void; - isKibanaFipsModeEnabled: () => boolean; } export class FipsService { @@ -32,14 +31,9 @@ export class FipsService { setup({ config, license }: FipsServiceSetupParams): FipsServiceSetupInternal { return { validateLicenseForFips: () => this.validateLicenseForFips(config, license), - isKibanaFipsModeEnabled: () => this.isKibanaFipsModeEnabled(config), }; } - private isKibanaFipsModeEnabled(config: ConfigType): boolean { - return config?.experimental.fipsMode.enabled; - } - private validateLicenseForFips(config: ConfigType, license: SecurityLicense) { license.features$.subscribe({ next: (features) => { diff --git a/x-pack/plugins/security/server/plugin.test.ts b/x-pack/plugins/security/server/plugin.test.ts index 6aa3722ba93f7..a82b45753845b 100644 --- a/x-pack/plugins/security/server/plugin.test.ts +++ b/x-pack/plugins/security/server/plugin.test.ts @@ -113,9 +113,6 @@ describe('Security Plugin', () => { "useRbacForRequest": [Function], }, }, - "experimental": Object { - "isFipsEnabled": [Function], - }, "license": Object { "features$": Observable { "operator": [Function], From d5cd54ef7dec5364fa93ba6eedf17278916c1bd3 Mon Sep 17 00:00:00 2001 From: kibanamachine <42973632+kibanamachine@users.noreply.github.com> Date: Thu, 20 Jun 2024 20:44:09 +0000 Subject: [PATCH 23/28] [CI] Auto-commit changed files from 'node scripts/lint_ts_projects --fix' --- .../core/security/core-security-server-internal/tsconfig.json | 1 - 1 file changed, 1 deletion(-) diff --git a/packages/core/security/core-security-server-internal/tsconfig.json b/packages/core/security/core-security-server-internal/tsconfig.json index 7229df5cbff63..258b3853d686f 100644 --- a/packages/core/security/core-security-server-internal/tsconfig.json +++ b/packages/core/security/core-security-server-internal/tsconfig.json @@ -20,7 +20,6 @@ "@kbn/core-http-server", "@kbn/logging-mocks", "@kbn/core-base-server-mocks", - "@kbn/security-plugin", "@kbn/config", ] } From 19244c4e50916cc695657d957aa0b30d545c5562 Mon Sep 17 00:00:00 2001 From: Kurt Date: Thu, 20 Jun 2024 18:15:06 -0400 Subject: [PATCH 24/28] fixing mocks --- .../src/security_service.mock.ts | 8 ++------ 1 file changed, 2 insertions(+), 6 deletions(-) diff --git a/packages/core/security/core-security-server-mocks/src/security_service.mock.ts b/packages/core/security/core-security-server-mocks/src/security_service.mock.ts index 192f6cf5c38df..59a560d562e06 100644 --- a/packages/core/security/core-security-server-mocks/src/security_service.mock.ts +++ b/packages/core/security/core-security-server-mocks/src/security_service.mock.ts @@ -20,6 +20,7 @@ import { auditServiceMock, type MockedAuditService } from './audit.mock'; const createSetupMock = () => { const mock: jest.Mocked = { registerSecurityDelegate: jest.fn(), + fips: { isEnabled: jest.fn() }, }; return mock; @@ -35,9 +36,6 @@ const createStartMock = (): SecurityStartMock => { getCurrentUser: jest.fn(), }, audit: auditServiceMock.create(), - experimental: { - isFipsEnabled: jest.fn(), - }, }; return mock; @@ -46,6 +44,7 @@ const createStartMock = (): SecurityStartMock => { const createInternalSetupMock = () => { const mock: jest.Mocked = { registerSecurityDelegate: jest.fn(), + fips: { isEnabled: jest.fn() }, }; return mock; @@ -63,9 +62,6 @@ const createInternalStartMock = (): InternalSecurityStartMock => { getCurrentUser: jest.fn(), }, audit: auditServiceMock.create(), - experimental: { - isFipsEnabled: jest.fn(), - }, }; return mock; From b7f1de082b43d3e748ad6590fa9723a947b48932 Mon Sep 17 00:00:00 2001 From: Kurt Date: Thu, 20 Jun 2024 19:02:22 -0400 Subject: [PATCH 25/28] Removing tests --- .../security/server/fips/fips_service.test.ts | 65 +------------------ 1 file changed, 1 insertion(+), 64 deletions(-) diff --git a/x-pack/plugins/security/server/fips/fips_service.test.ts b/x-pack/plugins/security/server/fips/fips_service.test.ts index d1facd19ab4a4..aba86633c281f 100644 --- a/x-pack/plugins/security/server/fips/fips_service.test.ts +++ b/x-pack/plugins/security/server/fips/fips_service.test.ts @@ -19,7 +19,7 @@ import { BehaviorSubject, of } from 'rxjs'; import { loggingSystemMock } from '@kbn/core/server/mocks'; import type { LicenseType } from '@kbn/licensing-plugin/common/types'; -import type { SecurityLicense, SecurityLicenseFeatures } from '@kbn/security-plugin-types-common'; +import type { SecurityLicenseFeatures } from '@kbn/security-plugin-types-common'; import type { FipsServiceSetupInternal, FipsServiceSetupParams } from './fips_service'; import { FipsService } from './fips_service'; @@ -75,75 +75,12 @@ describe('FipsService', () => { expect(fipsServiceSetup).toMatchInlineSnapshot(` Object { - "isKibanaFipsModeEnabled": [Function], "validateLicenseForFips": [Function], } `); }); }); - describe('#isKibanaFipsModeEnabled', () => { - let license: SecurityLicense; - beforeEach(() => { - license = licenseMock.create(of({ allowFips: true }), 'platinum'); - - fipsService = new FipsService(logger); - }); - - it('should return `true` when config `xpack.security.experimental.fipsMode.enabled` is `true`', () => { - mockGetFipsFn.mockImplementationOnce(() => { - return 1; - }); - - fipsServiceSetup = fipsService.setup({ - license, - config: createConfig( - ConfigSchema.validate({ experimental: { fipsMode: { enabled: true } } }), - loggingSystemMock.createLogger(), - { - isTLSEnabled: false, - } - ), - }); - - expect(fipsServiceSetup.isKibanaFipsModeEnabled()).toBe(true); - }); - - it('should return `false` when config `xpack.security.experimental.fipsMode.enabled` is `false`', () => { - mockGetFipsFn.mockImplementationOnce(() => { - return 0; - }); - - fipsServiceSetup = fipsService.setup({ - license, - config: createConfig( - ConfigSchema.validate({ experimental: { fipsMode: { enabled: false } } }), - loggingSystemMock.createLogger(), - { - isTLSEnabled: false, - } - ), - }); - - expect(fipsServiceSetup.isKibanaFipsModeEnabled()).toBe(false); - }); - - it('should return `false` when config `xpack.security.experimental.fipsMode.enabled` is `undefined`', () => { - mockGetFipsFn.mockImplementationOnce(() => { - return 0; - }); - - fipsServiceSetup = fipsService.setup({ - license, - config: createConfig(ConfigSchema.validate({}), loggingSystemMock.createLogger(), { - isTLSEnabled: false, - }), - }); - - expect(fipsServiceSetup.isKibanaFipsModeEnabled()).toBe(false); - }); - }); - describe('#validateLicenseForFips', () => { describe('start-up check', () => { it('should not throw Error/log.error if license features allowFips and `experimental.fipsMode.enabled` is `false`', () => { From 7b7ed010889b5072d430622be372d4af9bfee487 Mon Sep 17 00:00:00 2001 From: Kurt Date: Mon, 24 Jun 2024 23:09:07 -0400 Subject: [PATCH 26/28] PR feedback from Pierre --- .../core-security-server-internal/src/fips/fips.test.ts | 8 -------- .../core-security-server-internal/src/fips/fips.ts | 2 +- .../core/security/core-security-server/src/contracts.ts | 4 ++-- 3 files changed, 3 insertions(+), 11 deletions(-) diff --git a/packages/core/security/core-security-server-internal/src/fips/fips.test.ts b/packages/core/security/core-security-server-internal/src/fips/fips.test.ts index 2e09bf2aa65b4..80874c112619d 100644 --- a/packages/core/security/core-security-server-internal/src/fips/fips.test.ts +++ b/packages/core/security/core-security-server-internal/src/fips/fips.test.ts @@ -13,14 +13,6 @@ describe('fips', () => { describe('#isFipsEnabled', () => { let config: SecurityServiceConfigType; - beforeAll(() => { - config = {}; - }); - - afterEach(() => { - config = {}; - }); - it('should return `true` if config.experimental.fipsMode.enabled is `true`', () => { config = { experimental: { fipsMode: { enabled: true } } }; diff --git a/packages/core/security/core-security-server-internal/src/fips/fips.ts b/packages/core/security/core-security-server-internal/src/fips/fips.ts index fa24ac83bb880..3e6ba2e99c706 100644 --- a/packages/core/security/core-security-server-internal/src/fips/fips.ts +++ b/packages/core/security/core-security-server-internal/src/fips/fips.ts @@ -9,5 +9,5 @@ import { SecurityServiceConfigType } from '../utils'; export function isFipsEnabled(config: SecurityServiceConfigType): boolean { - return config?.experimental?.fipsMode?.enabled || false; + return config?.experimental?.fipsMode?.enabled ?? false; } diff --git a/packages/core/security/core-security-server/src/contracts.ts b/packages/core/security/core-security-server/src/contracts.ts index 75b4174af1217..d2bf7d97e9472 100644 --- a/packages/core/security/core-security-server/src/contracts.ts +++ b/packages/core/security/core-security-server/src/contracts.ts @@ -6,7 +6,7 @@ * Side Public License, v 1. */ -import { CoreFipsService } from './fips'; +import type { CoreFipsService } from './fips'; import type { CoreAuthenticationService } from './authc'; import type { CoreSecurityDelegateContract } from './api_provider'; import type { CoreAuditService } from './audit'; @@ -24,7 +24,7 @@ export interface SecurityServiceSetup { registerSecurityDelegate(api: CoreSecurityDelegateContract): void; /** - * The {{@link CoreFipsService | FIPS service}} + * The {@link CoreFipsService | FIPS service} */ fips: CoreFipsService; } From 2c116105b50bce6079b893ed0029b8808607d534 Mon Sep 17 00:00:00 2001 From: Kurt Date: Tue, 25 Jun 2024 23:31:50 -0400 Subject: [PATCH 27/28] Moving FIPS config comparison check to Core > Security Service setup --- .../src/fips/fips.test.ts | 93 +++++++++++++++++- .../src/fips/fips.ts | 22 +++++ .../src/security_service.ts | 4 +- x-pack/plugins/security/server/config.test.ts | 97 ------------------- x-pack/plugins/security/server/config.ts | 24 +---- 5 files changed, 116 insertions(+), 124 deletions(-) diff --git a/packages/core/security/core-security-server-internal/src/fips/fips.test.ts b/packages/core/security/core-security-server-internal/src/fips/fips.test.ts index 80874c112619d..65f95aa7da691 100644 --- a/packages/core/security/core-security-server-internal/src/fips/fips.test.ts +++ b/packages/core/security/core-security-server-internal/src/fips/fips.test.ts @@ -6,13 +6,22 @@ * Side Public License, v 1. */ +const mockGetFipsFn = jest.fn(); +jest.mock('crypto', () => ({ + randomBytes: jest.fn(), + constants: jest.requireActual('crypto').constants, + get getFips() { + return mockGetFipsFn; + }, +})); + import { SecurityServiceConfigType } from '../utils'; -import { isFipsEnabled } from './fips'; +import { isFipsEnabled, checkFipsConfig } from './fips'; +import { loggingSystemMock } from '@kbn/core-logging-server-mocks'; describe('fips', () => { + let config: SecurityServiceConfigType; describe('#isFipsEnabled', () => { - let config: SecurityServiceConfigType; - it('should return `true` if config.experimental.fipsMode.enabled is `true`', () => { config = { experimental: { fipsMode: { enabled: true } } }; @@ -29,4 +38,82 @@ describe('fips', () => { expect(isFipsEnabled(config)).toBe(false); }); }); + + describe('checkFipsConfig', () => { + let mockExit: jest.SpyInstance; + + beforeAll(() => { + mockExit = jest.spyOn(process, 'exit').mockImplementation((exitCode) => { + throw new Error(`Fake Exit: ${exitCode}`); + }); + }); + + afterAll(() => { + mockExit.mockRestore(); + }); + + it('should log an error message if FIPS mode is misconfigured - xpack.security.experimental.fipsMode.enabled true, Nodejs FIPS mode false', async () => { + config = { experimental: { fipsMode: { enabled: true } } }; + const logger = loggingSystemMock.create().get(); + try { + checkFipsConfig(config, logger); + } catch (e) { + expect(mockExit).toHaveBeenNthCalledWith(1, 78); + } + + expect(loggingSystemMock.collect(logger).error).toMatchInlineSnapshot(` + Array [ + Array [ + "Configuration mismatch error. xpack.security.experimental.fipsMode.enabled is set to true and the configured Node.js environment has FIPS disabled", + ], + ] + `); + }); + + it('should log an error message if FIPS mode is misconfigured - xpack.security.experimental.fipsMode.enabled false, Nodejs FIPS mode true', async () => { + mockGetFipsFn.mockImplementationOnce(() => { + return 1; + }); + + config = { experimental: { fipsMode: { enabled: false } } }; + const logger = loggingSystemMock.create().get(); + + try { + checkFipsConfig(config, logger); + } catch (e) { + expect(mockExit).toHaveBeenNthCalledWith(1, 78); + } + + expect(loggingSystemMock.collect(logger).error).toMatchInlineSnapshot(` + Array [ + Array [ + "Configuration mismatch error. xpack.security.experimental.fipsMode.enabled is set to false and the configured Node.js environment has FIPS enabled", + ], + ] + `); + }); + + it('should log an info message if FIPS mode is properly configured - xpack.security.experimental.fipsMode.enabled true, Nodejs FIPS mode true', async () => { + mockGetFipsFn.mockImplementationOnce(() => { + return 1; + }); + + config = { experimental: { fipsMode: { enabled: true } } }; + const logger = loggingSystemMock.create().get(); + + try { + checkFipsConfig(config, logger); + } catch (e) { + logger.error('Should not throw error!'); + } + + expect(loggingSystemMock.collect(logger).info).toMatchInlineSnapshot(` + Array [ + Array [ + "Kibana is running in FIPS mode.", + ], + ] + `); + }); + }); }); diff --git a/packages/core/security/core-security-server-internal/src/fips/fips.ts b/packages/core/security/core-security-server-internal/src/fips/fips.ts index 3e6ba2e99c706..2b48fb68ff607 100644 --- a/packages/core/security/core-security-server-internal/src/fips/fips.ts +++ b/packages/core/security/core-security-server-internal/src/fips/fips.ts @@ -6,8 +6,30 @@ * Side Public License, v 1. */ +import type { Logger } from '@kbn/logging'; +import { getFips } from 'crypto'; import { SecurityServiceConfigType } from '../utils'; export function isFipsEnabled(config: SecurityServiceConfigType): boolean { return config?.experimental?.fipsMode?.enabled ?? false; } + +export function checkFipsConfig(config: SecurityServiceConfigType, logger: Logger) { + const isFipsConfigEnabled = isFipsEnabled(config); + const isNodeRunningWithFipsEnabled = getFips() === 1; + + // Check if FIPS is enabled in either setting + if (isFipsConfigEnabled || isNodeRunningWithFipsEnabled) { + // FIPS must be enabled on both or log and error an exit Kibana + if (isFipsConfigEnabled !== isNodeRunningWithFipsEnabled) { + logger.error( + `Configuration mismatch error. xpack.security.experimental.fipsMode.enabled is set to ${isFipsConfigEnabled} and the configured Node.js environment has FIPS ${ + isNodeRunningWithFipsEnabled ? 'enabled' : 'disabled' + }` + ); + process.exit(78); + } else { + logger.info('Kibana is running in FIPS mode.'); + } + } +} diff --git a/packages/core/security/core-security-server-internal/src/security_service.ts b/packages/core/security/core-security-server-internal/src/security_service.ts index 509774ab6a5fb..215e7ef376285 100644 --- a/packages/core/security/core-security-server-internal/src/security_service.ts +++ b/packages/core/security/core-security-server-internal/src/security_service.ts @@ -11,7 +11,7 @@ import type { CoreContext, CoreService } from '@kbn/core-base-server-internal'; import type { CoreSecurityDelegateContract } from '@kbn/core-security-server'; import { Observable, Subscription } from 'rxjs'; import { Config } from '@kbn/config'; -import { isFipsEnabled } from './fips/fips'; +import { isFipsEnabled, checkFipsConfig } from './fips/fips'; import type { InternalSecurityServiceSetup, InternalSecurityServiceStart, @@ -50,6 +50,8 @@ export class SecurityService const config = this.getConfig(); const securityConfig: SecurityServiceConfigType = config.get(['xpack', 'security']); + checkFipsConfig(securityConfig, this.log); + return { registerSecurityDelegate: (api) => { if (this.securityApi) { diff --git a/x-pack/plugins/security/server/config.test.ts b/x-pack/plugins/security/server/config.test.ts index fe7a7fbf22ac1..5e6c59aee4668 100644 --- a/x-pack/plugins/security/server/config.test.ts +++ b/x-pack/plugins/security/server/config.test.ts @@ -5,13 +5,9 @@ * 2.0. */ -const mockGetFipsFn = jest.fn(); jest.mock('crypto', () => ({ randomBytes: jest.fn(), constants: jest.requireActual('crypto').constants, - get getFips() { - return mockGetFipsFn; - }, })); jest.mock('@kbn/utils', () => ({ @@ -2521,96 +2517,3 @@ describe('createConfig()', () => { }); }); }); - -describe('checkFipsConfig', () => { - let mockExit: jest.SpyInstance; - beforeAll(() => { - mockExit = jest.spyOn(process, 'exit').mockImplementation((exitCode) => { - throw new Error(`Fake Exit: ${exitCode}`); - }); - }); - - afterAll(() => { - mockExit.mockRestore(); - }); - - it('should log an error message if FIPS mode is misconfigured - xpack.security.experimental.fipsMode.enabled true, Nodejs FIPS mode false', async () => { - const logger = loggingSystemMock.create().get(); - - try { - createConfig( - ConfigSchema.validate({ experimental: { fipsMode: { enabled: true } } }), - logger, - { - isTLSEnabled: true, - } - ); - } catch (e) { - expect(mockExit).toHaveBeenNthCalledWith(1, 78); - } - - expect(loggingSystemMock.collect(logger).error).toMatchInlineSnapshot(` - Array [ - Array [ - "Configuration mismatch error. xpack.security.experimental.fipsMode.enabled is set to true and the configured Node.js environment has FIPS disabled", - ], - ] - `); - }); - - it('should log an error message if FIPS mode is misconfigured - xpack.security.experimental.fipsMode.enabled false, Nodejs FIPS mode true', async () => { - mockGetFipsFn.mockImplementationOnce(() => { - return 1; - }); - - const logger = loggingSystemMock.create().get(); - - try { - createConfig( - ConfigSchema.validate({ experimental: { fipsMode: { enabled: false } } }), - logger, - { - isTLSEnabled: true, - } - ); - } catch (e) { - expect(mockExit).toHaveBeenNthCalledWith(1, 78); - } - - expect(loggingSystemMock.collect(logger).error).toMatchInlineSnapshot(` - Array [ - Array [ - "Configuration mismatch error. xpack.security.experimental.fipsMode.enabled is set to false and the configured Node.js environment has FIPS enabled", - ], - ] - `); - }); - - it('should log an info message if FIPS mode is properly configured - xpack.security.experimental.fipsMode.enabled true, Nodejs FIPS mode true', async () => { - mockGetFipsFn.mockImplementationOnce(() => { - return 1; - }); - - const logger = loggingSystemMock.create().get(); - - try { - createConfig( - ConfigSchema.validate({ experimental: { fipsMode: { enabled: true } } }), - logger, - { - isTLSEnabled: true, - } - ); - } catch (e) { - logger.error('Should not throw error!'); - } - - expect(loggingSystemMock.collect(logger).info).toMatchInlineSnapshot(` - Array [ - Array [ - "Kibana is running in FIPS mode.", - ], - ] - `); - }); -}); diff --git a/x-pack/plugins/security/server/config.ts b/x-pack/plugins/security/server/config.ts index 4e28de37c853f..e12f1462b39b4 100644 --- a/x-pack/plugins/security/server/config.ts +++ b/x-pack/plugins/security/server/config.ts @@ -5,7 +5,7 @@ * 2.0. */ -import crypto, { getFips } from 'crypto'; +import crypto from 'crypto'; import type { Duration } from 'moment'; import path from 'path'; @@ -321,33 +321,11 @@ export const ConfigSchema = schema.object({ }), }); -function checkFipsConfig(config: RawConfigType, logger: Logger) { - const isFipsEnabled = config.experimental.fipsMode.enabled; - const isNodeRunningWithFipsEnabled = getFips() === 1; - - // Check if FIPS is enabled in either setting - if (isFipsEnabled || isNodeRunningWithFipsEnabled) { - // FIPS must be enabled on both or log and error an exit Kibana - if (isFipsEnabled !== isNodeRunningWithFipsEnabled) { - logger.error( - `Configuration mismatch error. xpack.security.experimental.fipsMode.enabled is set to ${isFipsEnabled} and the configured Node.js environment has FIPS ${ - isNodeRunningWithFipsEnabled ? 'enabled' : 'disabled' - }` - ); - process.exit(78); - } else { - logger.info('Kibana is running in FIPS mode.'); - } - } -} - export function createConfig( config: RawConfigType, logger: Logger, { isTLSEnabled }: { isTLSEnabled: boolean } ) { - checkFipsConfig(config, logger); - let encryptionKey = config.encryptionKey; if (encryptionKey === undefined) { logger.warn( From b82345d27e099d831bc3fc28a4408f3f72eff2ac Mon Sep 17 00:00:00 2001 From: kibanamachine <42973632+kibanamachine@users.noreply.github.com> Date: Wed, 26 Jun 2024 03:46:11 +0000 Subject: [PATCH 28/28] [CI] Auto-commit changed files from 'node scripts/lint_ts_projects --fix' --- .../core/security/core-security-server-internal/tsconfig.json | 1 + 1 file changed, 1 insertion(+) diff --git a/packages/core/security/core-security-server-internal/tsconfig.json b/packages/core/security/core-security-server-internal/tsconfig.json index 258b3853d686f..e1812dc77cf49 100644 --- a/packages/core/security/core-security-server-internal/tsconfig.json +++ b/packages/core/security/core-security-server-internal/tsconfig.json @@ -21,5 +21,6 @@ "@kbn/logging-mocks", "@kbn/core-base-server-mocks", "@kbn/config", + "@kbn/core-logging-server-mocks", ] }