From 04e960fd992035fc25f629aa361eef585709d34f Mon Sep 17 00:00:00 2001 From: Kurt Date: Wed, 11 Sep 2024 14:17:21 -0400 Subject: [PATCH 1/2] adding check for pkcs12 to fips in security service --- .../src/fips/fips.test.ts | 91 ++++++++++++++++--- .../src/fips/fips.ts | 48 +++++++++- .../src/security_service.ts | 5 +- .../src/utils/index.ts | 11 +++ 4 files changed, 139 insertions(+), 16 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 8726e3b5a34ee..3f6328388287f 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 @@ -21,22 +21,22 @@ 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); }); }); @@ -54,10 +54,10 @@ describe('fips', () => { }); 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(); try { - checkFipsConfig(config, logger); + checkFipsConfig(securityConfig, {}, {}, logger); } catch (e) { expect(mockExit).toHaveBeenNthCalledWith(1, 78); } @@ -76,11 +76,11 @@ describe('fips', () => { return 1; }); - config = { experimental: { fipsMode: { enabled: false } } }; + securityConfig = { experimental: { fipsMode: { enabled: false } } }; const logger = loggingSystemMock.create().get(); try { - checkFipsConfig(config, logger); + checkFipsConfig(securityConfig, {}, {}, logger); } catch (e) { expect(mockExit).toHaveBeenNthCalledWith(1, 78); } @@ -99,11 +99,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 +116,72 @@ describe('fips', () => { ] `); }); + + describe('PKCS12 Config settings', function () { + let serverConfig = {}; + let elasticsearchConfig = {}; + + beforeAll(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(); + + try { + checkFipsConfig(securityConfig, elasticsearchConfig, serverConfig, logger); + } catch (e) { + expect(mockExit).toHaveBeenNthCalledWith(1, 78); + } + + expect(loggingSystemMock.collect(logger).error).toMatchInlineSnapshot(` + Array [ + Array [ + "Configuration mismatch error: elasticsearch.ssl.keystore.path is set, PKCS12 configurations are not allowed while running in FIPS mode.", + ], + Array [ + "Configuration mismatch error: elasticsearch.ssl.truststore.path is set, PKCS12 configurations are not allowed while running in FIPS mode.", + ], + Array [ + "Configuration mismatch error: server.ssl.keystore.path is set, PKCS12 configurations are not allowed while running in FIPS mode.", + ], + Array [ + "Configuration mismatch error: server.ssl.truststore.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..c21dbe01bc9f8 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 { 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( `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 if (definedPKCS12ConfigOptions.length > 0) { + definedPKCS12ConfigOptions.forEach((option) => { + logger.error( + `Configuration mismatch error: ${option} is set, PKCS12 configurations are not allowed while running in FIPS mode.` + ); + }); + 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; + }; + }; +} From 92aeabf477867dc1768f9048b159f01f2ab1fcc3 Mon Sep 17 00:00:00 2001 From: Kurt Date: Thu, 26 Sep 2024 12:13:39 -0400 Subject: [PATCH 2/2] Switching to use Core CriticalError interceptor --- .../src/fips/fips.test.ts | 98 ++++++++++--------- .../src/fips/fips.ts | 24 ++--- 2 files changed, 63 insertions(+), 59 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 3f6328388287f..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(), @@ -41,34 +43,21 @@ describe('fips', () => { }); 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 () => { securityConfig = { experimental: { fipsMode: { enabled: true } } }; const logger = loggingSystemMock.create().get(); + let fipsException: undefined | CriticalError; try { 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 () => { @@ -79,19 +68,17 @@ describe('fips', () => { securityConfig = { experimental: { fipsMode: { enabled: false } } }; const logger = loggingSystemMock.create().get(); + let fipsException: undefined | CriticalError; try { 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 () => { @@ -121,7 +108,7 @@ describe('fips', () => { let serverConfig = {}; let elasticsearchConfig = {}; - beforeAll(function () { + beforeEach(function () { mockGetFipsFn.mockImplementationOnce(() => { return 1; }); @@ -159,28 +146,45 @@ describe('fips', () => { const logger = loggingSystemMock.create().get(); + let fipsException: undefined | CriticalError; try { checkFipsConfig(securityConfig, elasticsearchConfig, serverConfig, logger); } catch (e) { - expect(mockExit).toHaveBeenNthCalledWith(1, 78); + fipsException = e; } - expect(loggingSystemMock.collect(logger).error).toMatchInlineSnapshot(` - Array [ - Array [ - "Configuration mismatch error: elasticsearch.ssl.keystore.path is set, PKCS12 configurations are not allowed while running in FIPS mode.", - ], - Array [ - "Configuration mismatch error: elasticsearch.ssl.truststore.path is set, PKCS12 configurations are not allowed while running in FIPS mode.", - ], - Array [ - "Configuration mismatch error: server.ssl.keystore.path is set, PKCS12 configurations are not allowed while running in FIPS mode.", - ], - Array [ - "Configuration mismatch error: server.ssl.truststore.path is set, PKCS12 configurations are not allowed while running in FIPS mode.", - ], - ] - `); + 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 c21dbe01bc9f8..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,8 +9,8 @@ import type { Logger } from '@kbn/logging'; import { getFips } from 'crypto'; +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; } @@ -32,21 +32,21 @@ export function checkFipsConfig( ); // 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 ); - - process.exit(78); } else if (definedPKCS12ConfigOptions.length > 0) { - definedPKCS12ConfigOptions.forEach((option) => { - logger.error( - `Configuration mismatch error: ${option} is set, PKCS12 configurations are not allowed while running in FIPS mode.` - ); - }); - - process.exit(78); + 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 + ); } else { logger.info('Kibana is running in FIPS mode.'); }