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 8726e3b5a34ee..ff610493e1322 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 @@ -7,6 +7,8 @@ * License v3.0 only", or the "Server Side Public License, v 1". */ +import { CriticalError } from '@kbn/core-base-server-internal'; + const mockGetFipsFn = jest.fn(); jest.mock('crypto', () => ({ randomBytes: jest.fn(), @@ -21,54 +23,41 @@ import { isFipsEnabled, checkFipsConfig } from './fips'; import { loggingSystemMock } from '@kbn/core-logging-server-mocks'; describe('fips', () => { - let config: SecurityServiceConfigType; + let securityConfig: SecurityServiceConfigType; describe('#isFipsEnabled', () => { it('should return `true` if config.experimental.fipsMode.enabled is `true`', () => { - config = { experimental: { fipsMode: { enabled: true } } }; + securityConfig = { experimental: { fipsMode: { enabled: true } } }; - expect(isFipsEnabled(config)).toBe(true); + expect(isFipsEnabled(securityConfig)).toBe(true); }); it('should return `false` if config.experimental.fipsMode.enabled is `false`', () => { - config = { experimental: { fipsMode: { enabled: false } } }; + securityConfig = { experimental: { fipsMode: { enabled: false } } }; - expect(isFipsEnabled(config)).toBe(false); + expect(isFipsEnabled(securityConfig)).toBe(false); }); it('should return `false` if config.experimental.fipsMode.enabled is `undefined`', () => { - expect(isFipsEnabled(config)).toBe(false); + expect(isFipsEnabled(securityConfig)).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 } } }; + securityConfig = { experimental: { fipsMode: { enabled: true } } }; const logger = loggingSystemMock.create().get(); + let fipsException: undefined | CriticalError; try { - checkFipsConfig(config, logger); + checkFipsConfig(securityConfig, {}, {}, logger); } catch (e) { - expect(mockExit).toHaveBeenNthCalledWith(1, 78); + fipsException = e; } - 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", - ], - ] - `); + expect(fipsException).toBeInstanceOf(CriticalError); + expect(fipsException!.processExitCode).toBe(78); + expect(fipsException!.message).toEqual( + '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 () => { @@ -76,22 +65,20 @@ describe('fips', () => { return 1; }); - config = { experimental: { fipsMode: { enabled: false } } }; + securityConfig = { experimental: { fipsMode: { enabled: false } } }; const logger = loggingSystemMock.create().get(); + let fipsException: undefined | CriticalError; try { - checkFipsConfig(config, logger); + checkFipsConfig(securityConfig, {}, {}, logger); } catch (e) { - expect(mockExit).toHaveBeenNthCalledWith(1, 78); + fipsException = e; } - - 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", - ], - ] - `); + expect(fipsException).toBeInstanceOf(CriticalError); + expect(fipsException!.processExitCode).toBe(78); + expect(fipsException!.message).toEqual( + '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 () => { @@ -99,11 +86,11 @@ describe('fips', () => { return 1; }); - config = { experimental: { fipsMode: { enabled: true } } }; + securityConfig = { experimental: { fipsMode: { enabled: true } } }; const logger = loggingSystemMock.create().get(); try { - checkFipsConfig(config, logger); + checkFipsConfig(securityConfig, {}, {}, logger); } catch (e) { logger.error('Should not throw error!'); } @@ -116,5 +103,89 @@ describe('fips', () => { ] `); }); + + describe('PKCS12 Config settings', function () { + let serverConfig = {}; + let elasticsearchConfig = {}; + + beforeEach(function () { + mockGetFipsFn.mockImplementationOnce(() => { + return 1; + }); + + securityConfig = { experimental: { fipsMode: { enabled: true } } }; + }); + + afterEach(function () { + serverConfig = {}; + elasticsearchConfig = {}; + }); + + it('should log an error message for each PKCS12 configuration option that is set', async () => { + elasticsearchConfig = { + ssl: { + keystore: { + path: '/test', + }, + truststore: { + path: '/test', + }, + }, + }; + + serverConfig = { + ssl: { + keystore: { + path: '/test', + }, + truststore: { + path: '/test', + }, + }, + }; + + const logger = loggingSystemMock.create().get(); + + let fipsException: undefined | CriticalError; + try { + checkFipsConfig(securityConfig, elasticsearchConfig, serverConfig, logger); + } catch (e) { + fipsException = e; + } + + expect(fipsException).toBeInstanceOf(CriticalError); + expect(fipsException!.processExitCode).toBe(78); + expect(fipsException!.message).toEqual( + 'Configuration mismatch error: elasticsearch.ssl.keystore.path, elasticsearch.ssl.truststore.path, server.ssl.keystore.path, server.ssl.truststore.path are set, PKCS12 configurations are not allowed while running in FIPS mode.' + ); + }); + + it('should log an error message for one PKCS12 configuration option that is set', async () => { + elasticsearchConfig = { + ssl: { + keystore: { + path: '/test', + }, + }, + }; + + serverConfig = {}; + + const logger = loggingSystemMock.create().get(); + + let fipsException: undefined | CriticalError; + try { + checkFipsConfig(securityConfig, elasticsearchConfig, serverConfig, logger); + } catch (e) { + fipsException = e; + } + + expect(fipsException).toBeInstanceOf(CriticalError); + expect(fipsException!.processExitCode).toBe(78); + expect(fipsException!.message).toEqual( + 'Configuration mismatch error: elasticsearch.ssl.keystore.path is set, PKCS12 configurations are not allowed while 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 8f09facb554b5..0d9dea9e467fe 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,28 +9,70 @@ import type { Logger } from '@kbn/logging'; import { getFips } from 'crypto'; -import { SecurityServiceConfigType } from '../utils'; - +import { CriticalError } from '@kbn/core-base-server-internal'; +import { PKCS12ConfigType, SecurityServiceConfigType } from '../utils'; export function isFipsEnabled(config: SecurityServiceConfigType): boolean { return config?.experimental?.fipsMode?.enabled ?? false; } -export function checkFipsConfig(config: SecurityServiceConfigType, logger: Logger) { +export function checkFipsConfig( + config: SecurityServiceConfigType, + elasticsearchConfig: PKCS12ConfigType, + serverConfig: PKCS12ConfigType, + 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 + const definedPKCS12ConfigOptions = findDefinedPKCS12ConfigOptions( + elasticsearchConfig, + serverConfig + ); + // FIPS must be enabled on both, or, log/error an exit Kibana if (isFipsConfigEnabled !== isNodeRunningWithFipsEnabled) { - logger.error( + throw new CriticalError( `Configuration mismatch error. xpack.security.experimental.fipsMode.enabled is set to ${isFipsConfigEnabled} and the configured Node.js environment has FIPS ${ isNodeRunningWithFipsEnabled ? 'enabled' : 'disabled' - }` + }`, + 'invalidConfig', + 78 + ); + } else if (definedPKCS12ConfigOptions.length > 0) { + throw new CriticalError( + `Configuration mismatch error: ${definedPKCS12ConfigOptions.join(', ')} ${ + definedPKCS12ConfigOptions.length > 1 ? 'are' : 'is' + } set, PKCS12 configurations are not allowed while running in FIPS mode.`, + 'invalidConfig', + 78 ); - process.exit(78); } else { logger.info('Kibana is running in FIPS mode.'); } } } + +function findDefinedPKCS12ConfigOptions( + elasticsearchConfig: PKCS12ConfigType, + serverConfig: PKCS12ConfigType +): string[] { + const result = []; + if (elasticsearchConfig?.ssl?.keystore?.path) { + result.push('elasticsearch.ssl.keystore.path'); + } + + if (elasticsearchConfig?.ssl?.truststore?.path) { + result.push('elasticsearch.ssl.truststore.path'); + } + + if (serverConfig?.ssl?.keystore?.path) { + result.push('server.ssl.keystore.path'); + } + + if (serverConfig?.ssl?.truststore?.path) { + result.push('server.ssl.truststore.path'); + } + + return result; +} 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 cf39664bd46a0..81a337db47569 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 @@ -21,6 +21,7 @@ import { getDefaultSecurityImplementation, convertSecurityApi, SecurityServiceConfigType, + PKCS12ConfigType, } from './utils'; export class SecurityService @@ -50,8 +51,10 @@ export class SecurityService public setup(): InternalSecurityServiceSetup { const config = this.getConfig(); const securityConfig: SecurityServiceConfigType = config.get(['xpack', 'security']); + const elasticsearchConfig: PKCS12ConfigType = config.get(['elasticsearch']); + const serverConfig: PKCS12ConfigType = config.get(['server']); - checkFipsConfig(securityConfig, this.log); + checkFipsConfig(securityConfig, elasticsearchConfig, serverConfig, this.log); 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 1e3a370057135..666afcce38afd 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 @@ -17,3 +17,14 @@ export interface SecurityServiceConfigType { }; }; } + +export interface PKCS12ConfigType { + ssl?: { + keystore?: { + path?: string; + }; + truststore?: { + path?: string; + }; + }; +}