Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add license check for FIPS #181187

Merged
merged 53 commits into from
Jul 2, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
53 commits
Select commit Hold shift + click to select a range
fd898bc
Adding check when getting the config for the sec plugin to verify FIP…
kc13greiner Jan 9, 2024
392bee9
Merge branch 'main' into feature/add_fips_config
kc13greiner Jan 9, 2024
eff7830
changing fips_mode -> fipsMode
kc13greiner Jan 11, 2024
be473ba
Merge branch 'main' into feature/add_fips_config
kc13greiner Jan 12, 2024
d6f8f5c
Merge branch 'main' into feature/add_fips_config
kc13greiner Jan 16, 2024
eccc9e9
Changing expect to log statement to clarify that it shouldnt throw an…
kc13greiner Jan 22, 2024
ea425ec
Merge branch 'main' into feature/add_fips_config
kc13greiner Jan 24, 2024
d2cc083
Merge branch 'main' into feature/add_fips_config
kc13greiner Feb 2, 2024
b65471c
Merge branch 'main' into feature/add_fips_config
kc13greiner Mar 12, 2024
a0b72e6
Merge branch 'feature/add_fips_config' into feature/fips_license_check
kc13greiner Apr 16, 2024
683531c
Adding license check for FIPS
kc13greiner Apr 18, 2024
03f4b9e
Removing unused line in snapshot
kc13greiner Apr 18, 2024
ff51670
Merge branch 'main' into feature/fips_license_check
kc13greiner Apr 18, 2024
8959fbb
Adding new property
kc13greiner Apr 18, 2024
d50f7c5
Refactoring usage of License in favor of just using SecurityLicense
kc13greiner Apr 23, 2024
b1fa762
Merge branch 'main' into feature/fips_license_check
kc13greiner Apr 23, 2024
9d4d035
Removing console logs
kc13greiner Apr 23, 2024
fd58057
Merge branch 'main' into feature/fips_license_check
kc13greiner Apr 23, 2024
16d7ad8
Fixing jest tests
kc13greiner Apr 23, 2024
2bee3ea
Merge branch 'main' into feature/fips_license_check
kc13greiner Apr 23, 2024
b2fc622
snapshot update
kc13greiner Apr 23, 2024
eb5d11f
Updating snapshots and expected licenses
kc13greiner Apr 23, 2024
b1cab38
Merge branch 'main' into feature/fips_license_check
kc13greiner Apr 23, 2024
abed490
Need to add logic that waits for the license to load, run the start c…
kc13greiner Apr 24, 2024
15ab04b
Merge branch 'main' into feature/fips_license_check
kc13greiner Apr 24, 2024
955cf08
Throwing error instead of exiting
kc13greiner Apr 24, 2024
bc31503
Merge branch 'main' into feature/fips_license_check
kc13greiner Apr 24, 2024
80a3867
Merge branch 'main' into feature/fips_license_check
kc13greiner May 30, 2024
cb93a25
Changing config to experimental and adding updated FIPS docs
kc13greiner May 30, 2024
4816c26
Merge branch 'main' into feature/fips_license_check
kc13greiner May 30, 2024
4daab8c
Removing merge conflict code
kc13greiner May 30, 2024
6672ff9
Merge branch 'main' into feature/fips_license_check
kc13greiner May 30, 2024
27c9d15
Updating jest test snapshot
kc13greiner May 30, 2024
58a9e1b
Merge branch 'main' into feature/fips_license_check
kc13greiner Jun 10, 2024
2126400
Exposing fips setting from security setup
kc13greiner Jun 13, 2024
1b91334
Merge branch 'main' into feature/fips_license_check
kc13greiner Jun 13, 2024
d871efc
[CI] Auto-commit changed files from 'node scripts/lint_ts_projects --…
kibanamachine Jun 13, 2024
c37004a
[DOCS] Minor doc edits
lcawl Jun 18, 2024
90050f1
Merge branch 'main' into feature/fips_license_check
lcawl Jun 18, 2024
57c2f36
Exposing fips flag from core
kc13greiner Jun 20, 2024
ff3586a
[CI] Auto-commit changed files from 'node scripts/lint_ts_projects --…
kibanamachine Jun 20, 2024
31b7546
Fixing config type and adding tests
kc13greiner Jun 20, 2024
7b1a16a
Merge branch 'main' into feature/fips_license_check
kc13greiner Jun 20, 2024
d5cd54e
[CI] Auto-commit changed files from 'node scripts/lint_ts_projects --…
kibanamachine Jun 20, 2024
19244c4
fixing mocks
kc13greiner Jun 20, 2024
d468deb
Merge branch 'main' into feature/fips_license_check
kc13greiner Jun 20, 2024
b7f1de0
Removing tests
kc13greiner Jun 20, 2024
7b7ed01
PR feedback from Pierre
kc13greiner Jun 25, 2024
50015b0
Merge branch 'main' into feature/fips_license_check
kc13greiner Jun 25, 2024
2c11610
Moving FIPS config comparison check to Core > Security Service setup
kc13greiner Jun 26, 2024
fccc836
Merge branch 'main' into feature/fips_license_check
kc13greiner Jun 26, 2024
b82345d
[CI] Auto-commit changed files from 'node scripts/lint_ts_projects --…
kibanamachine Jun 26, 2024
33db0d6
Merge branch 'main' into feature/fips_license_check
kc13greiner Jul 2, 2024
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
63 changes: 63 additions & 0 deletions docs/user/security/fips-140-2.asciidoc
Original file line number Diff line number Diff line change
@@ -0,0 +1,63 @@
[[xpack-security-fips-140-2]]
=== FIPS 140-2
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Heya @georgewallace ! Would you or someone on you team be able to take a look at this documentation?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Heya @elastic/kibana-docs Would someone mind taking a look?

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

To make this page appear in the output, you'll need to add include::fips-140-2.asciidoc[] at the end of the docs/user/security/index.asciidoc file (or in some other navigation file, depending on where it ought to appear in the navigation tree).

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I've added the page to the navigation in c37004a. I think it could honestly go in either of these sections: https://www.elastic.co/guide/en/kibana/current/using-kibana-with-security.html or https://www.elastic.co/guide/en/kibana/current/xpack-security.html. I've added it to the latter for now but can easily move if the former makes more sense for this content.


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, 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. 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
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Do we need to do anything about

## enable OpenSSL 3 legacy provider
--openssl-legacy-provider

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Good question 😅 Im not sure how that conflicts in a FIPS setup. I can do some investigation.

Is there anyway the KB admin could override this currently?

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It can be removed, Kibana archives will have this at $KIBANA_HOME/config/node.options. or for deb/rpm /etc/kibana/node.options.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think it's ok to leave. You can use multiple providers alongside the FIPS provider. In #183777 I ran the FIPS agent pipeline appending FIPS options to the existing node.options without issue. That said, not sure it's "FIPS compliant" and still worth double checking @kc13greiner.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ok, we should be able to leave it:

When I configure node with:

[provider_sect]
default = default_sect
# The fips section name should match the section name inside the
# included fipsmodule.cnf.
# fips = fips_sect
legacy = legacy_sect

It runs with the legacy provider (w/o FIPS) and allows md4/md5

Screenshot 2024-06-11 at 5 25 10 PM

If I include fips as a provider, unsupported algorithms are not allowed and it doesn't even mention legacy mode anymore

[provider_sect]
default = default_sect
# The fips section name should match the section name inside the
# included fipsmodule.cnf.
fips = fips_sect
legacy = legacy_sect
Screenshot 2024-06-11 at 5 26 56 PM


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, refer to the <<change-password,keystore documentation>>.

===== 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.
1 change: 1 addition & 0 deletions docs/user/security/index.asciidoc
Original file line number Diff line number Diff line change
Expand Up @@ -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[]
Original file line number Diff line number Diff line change
Expand Up @@ -284,6 +284,7 @@ export function createPluginSetupContext<TPlugin, TPluginDependencies>({
},
security: {
registerSecurityDelegate: (api) => deps.security.registerSecurityDelegate(api),
fips: deps.security.fips,
},
userProfile: {
registerUserProfileDelegate: (delegate) =>
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,119 @@
/*
* 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.
*/

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, checkFipsConfig } from './fips';
import { loggingSystemMock } from '@kbn/core-logging-server-mocks';

describe('fips', () => {
let config: SecurityServiceConfigType;
describe('#isFipsEnabled', () => {
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);
});
});

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.",
],
]
`);
});
});
});
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
/*
* 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 { 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.');
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -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', () => {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -9,30 +9,59 @@
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, checkFipsConfig } from './fips/fips';
import type {
InternalSecurityServiceSetup,
InternalSecurityServiceStart,
} from './internal_contracts';
import { getDefaultSecurityImplementation, convertSecurityApi } from './utils';
import {
getDefaultSecurityImplementation,
convertSecurityApi,
SecurityServiceConfigType,
} from './utils';

export class SecurityService
implements CoreService<InternalSecurityServiceSetup, InternalSecurityServiceStart>
{
private readonly log: Logger;
private securityApi?: CoreSecurityDelegateContract;
private config$: Observable<Config>;
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: SecurityServiceConfigType = config.get(['xpack', 'security']);
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Oh, yeah... I overlooked we would need to access the security plugin's config from our core service for that feature 🙈.

With our security-in-core initiative, I think we'll want to move the security config from xpack.security.* to core.security.* at some point. It shouldn't be that much work (moving the schema registration + adding config deprecation + exposing the config type from core for the security plugin to be able to use them)

Without doing so, I agree that hacking around and accessing the raw config is the most pragmatic approach. It's absolutely against a dozen Core principles though, so would you mind opening a follow-up issue, just to keep track (and discuss) of moving the security config schema/registration to Core?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Follow up issue: #186863

Ill make a note to update this code once we move the config over


checkFipsConfig(securityConfig, this.log);

return {
registerSecurityDelegate: (api) => {
if (this.securityApi) {
throw new Error('security API can only be registered once');
}
this.securityApi = api;
},
fips: {
isEnabled: () => isFipsEnabled(securityConfig),
},
};
}

Expand All @@ -44,5 +73,10 @@ export class SecurityService
return convertSecurityApi(apiContract);
}

public stop() {}
public stop() {
if (this.configSubscription) {
this.configSubscription.unsubscribe();
this.configSubscription = undefined;
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -8,3 +8,11 @@

export { convertSecurityApi } from './convert_security_api';
export { getDefaultSecurityImplementation } from './default_implementation';

export interface SecurityServiceConfigType {
experimental?: {
fipsMode?: {
enabled: boolean;
};
};
}
Original file line number Diff line number Diff line change
Expand Up @@ -20,5 +20,7 @@
"@kbn/core-http-server",
"@kbn/logging-mocks",
"@kbn/core-base-server-mocks",
"@kbn/config",
"@kbn/core-logging-server-mocks",
]
}
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ import { auditServiceMock, type MockedAuditService } from './audit.mock';
const createSetupMock = () => {
const mock: jest.Mocked<SecurityServiceSetup> = {
registerSecurityDelegate: jest.fn(),
fips: { isEnabled: jest.fn() },
};

return mock;
Expand All @@ -43,6 +44,7 @@ const createStartMock = (): SecurityStartMock => {
const createInternalSetupMock = () => {
const mock: jest.Mocked<InternalSecurityServiceSetup> = {
registerSecurityDelegate: jest.fn(),
fips: { isEnabled: jest.fn() },
};

return mock;
Expand Down
1 change: 1 addition & 0 deletions packages/core/security/core-security-server/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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';
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@
* Side Public License, v 1.
*/

import type { CoreFipsService } from './fips';
import type { CoreAuthenticationService } from './authc';
import type { CoreSecurityDelegateContract } from './api_provider';
import type { CoreAuditService } from './audit';
Expand All @@ -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;
}

/**
Expand Down
Loading