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

[Security Solution][Serverless] AppFeatures improvements #158935

Merged
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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
2 changes: 1 addition & 1 deletion config/serverless.security.yml
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ xpack.uptime.enabled: false

## Enable the Serverless Security plugin
xpack.serverless.security.enabled: true
xpack.serverless.security.productLineIds: ['securityComplete']
xpack.serverless.security.productTypes: [{ product_line: 'security', product_tier: 'complete' }]

## Set the home route
uiSettings.overrides.defaultRoute: /app/security/get_started
Expand Down
2 changes: 1 addition & 1 deletion packages/kbn-optimizer/limits.yml
Original file line number Diff line number Diff line change
Expand Up @@ -119,7 +119,7 @@ pageLoadAssetSize:
serverless: 16573
serverlessObservability: 30000
serverlessSearch: 30000
serverlessSecurity: 41807
serverlessSecurity: 66019
sessionView: 77750
share: 71239
snapshotRestore: 79032
Expand Down
9 changes: 4 additions & 5 deletions x-pack/plugins/ess_security/server/constants.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,9 +5,8 @@
* 2.0.
*/

import { AppFeatureKey, AppFeatureKeys } from '@kbn/security-solution-plugin/common';
import { ALL_APP_FEATURE_KEYS } from '@kbn/security-solution-plugin/common';

export const DEFAULT_APP_FEATURES: AppFeatureKeys = {
[AppFeatureKey.advancedInsights]: true,
[AppFeatureKey.casesConnectors]: true,
};
// Just copying all feature keys for now.
// We may need a different set of keys in the future if we create serverless-specific appFeatures
export const DEFAULT_APP_FEATURES = [...ALL_APP_FEATURE_KEYS];
2 changes: 1 addition & 1 deletion x-pack/plugins/security_solution/common/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ export { APP_UI_ID, APP_ID, CASES_FEATURE_ID, SERVER_APP_ID, SecurityPageName }
export { ELASTIC_SECURITY_RULE_ID } from './detection_engine/constants';
export { allowedExperimentalValues, type ExperimentalFeatures } from './experimental_features';
export type { AppFeatureKeys } from './types/app_features';
export { AppFeatureKey } from './types/app_features';
export { AppFeatureKey, ALL_APP_FEATURE_KEYS } from './types/app_features';

// Careful of exporting anything from this file as any file(s) you export here will cause your page bundle size to increase.
// If you're using functions/types/etc... internally it's best to import directly from their paths than expose the functions/types/etc... here.
Expand Down
9 changes: 4 additions & 5 deletions x-pack/plugins/security_solution/common/types/app_features.ts
Original file line number Diff line number Diff line change
Expand Up @@ -20,10 +20,9 @@ export enum AppFeatureCasesKey {
}

// Merges the two enums.
// We need to merge the value and the type and export both to replicate how enum works.
export const AppFeatureKey = { ...AppFeatureSecurityKey, ...AppFeatureCasesKey };
export type AppFeatureKey = AppFeatureSecurityKey | AppFeatureCasesKey;
export type AppFeatureKeys = AppFeatureKey[];

type AppFeatureSecurityKeys = { [key in AppFeatureSecurityKey]: boolean };
type AppFeatureCasesKeys = { [key in AppFeatureCasesKey]: boolean };
export type AppFeatureKeys = AppFeatureSecurityKeys & AppFeatureCasesKeys;
// We need to merge the value and the type and export both to replicate how enum works.
export const AppFeatureKey = { ...AppFeatureSecurityKey, ...AppFeatureCasesKey };
export const ALL_APP_FEATURE_KEYS = Object.freeze(Object.values(AppFeatureKey));
Original file line number Diff line number Diff line change
Expand Up @@ -50,15 +50,28 @@ const CASES_APP_FEATURE_CONFIG = {

jest.mock('./security_kibana_features', () => {
return {
getSecurityBaseKibanaFeature: jest.fn().mockReturnValue(SECURITY_BASE_CONFIG),
getSecurityAppFeaturesConfig: jest.fn().mockReturnValue(SECURITY_APP_FEATURE_CONFIG),
getSecurityBaseKibanaFeature: jest.fn(() => SECURITY_BASE_CONFIG),
getSecurityBaseKibanaSubFeatureIds: jest.fn(() => ['subFeature1']),
getSecurityAppFeaturesConfig: jest.fn(() => SECURITY_APP_FEATURE_CONFIG),
};
});
jest.mock('./security_kibana_sub_features', () => {
return {
securitySubFeaturesMap: new Map([['subFeature1', { baz: 'baz' }]]),
};
});

jest.mock('./security_cases_kibana_features', () => {
return {
getCasesBaseKibanaFeature: jest.fn().mockReturnValue(CASES_BASE_CONFIG),
getCasesAppFeaturesConfig: jest.fn().mockReturnValue(CASES_APP_FEATURE_CONFIG),
getCasesBaseKibanaFeature: jest.fn(() => CASES_BASE_CONFIG),
getCasesBaseKibanaSubFeatureIds: jest.fn(() => ['subFeature1']),
getCasesAppFeaturesConfig: jest.fn(() => CASES_APP_FEATURE_CONFIG),
};
});

jest.mock('./security_cases_kibana_sub_features', () => {
return {
casesSubFeaturesMap: new Map([['subFeature1', { baz: 'baz' }]]),
};
});

Expand All @@ -69,9 +82,7 @@ describe('AppFeatures', () => {
getKibanaFeatures: jest.fn(),
} as unknown as PluginSetupContract;

const appFeatureKeys = {
'test-base-feature': true,
} as unknown as AppFeatureKeys;
const appFeatureKeys = ['test-base-feature'] as unknown as AppFeatureKeys;

const appFeatures = new AppFeatures(
{} as unknown as Logger,
Expand All @@ -83,6 +94,7 @@ describe('AppFeatures', () => {
expect(featuresSetup.registerKibanaFeature).toHaveBeenCalledWith({
...SECURITY_BASE_CONFIG,
...SECURITY_APP_FEATURE_CONFIG['test-base-feature'],
subFeatures: [{ baz: 'baz' }],
});
});

Expand All @@ -91,9 +103,7 @@ describe('AppFeatures', () => {
registerKibanaFeature: jest.fn(),
} as unknown as PluginSetupContract;

const appFeatureKeys = {
'test-cases-feature': true,
} as unknown as AppFeatureKeys;
const appFeatureKeys = ['test-cases-feature'] as unknown as AppFeatureKeys;

const appFeatures = new AppFeatures(
{} as unknown as Logger,
Expand All @@ -105,6 +115,7 @@ describe('AppFeatures', () => {
expect(featuresSetup.registerKibanaFeature).toHaveBeenCalledWith({
...CASES_BASE_CONFIG,
...CASES_APP_FEATURE_CONFIG['test-cases-feature'],
subFeatures: [{ baz: 'baz' }],
});
});
});
Original file line number Diff line number Diff line change
Expand Up @@ -12,25 +12,32 @@ import type { AppFeatureKibanaConfig, AppFeaturesConfig } from './types';
import {
getSecurityAppFeaturesConfig,
getSecurityBaseKibanaFeature,
getSecurityBaseKibanaSubFeatureIds,
} from './security_kibana_features';
import {
getCasesBaseKibanaFeature,
getCasesAppFeaturesConfig,
getCasesBaseKibanaSubFeatureIds,
} from './security_cases_kibana_features';
import { AppFeaturesConfigMerger } from './app_features_config_merger';

type AppFeaturesMap = Map<AppFeatureKey, boolean>;
import { casesSubFeaturesMap } from './security_cases_kibana_sub_features';
import { securitySubFeaturesMap } from './security_kibana_sub_features';

export class AppFeatures {
private merger: AppFeaturesConfigMerger;
private appFeatures?: AppFeaturesMap;
private securityFeatureConfigMerger: AppFeaturesConfigMerger;
private casesFeatureConfigMerger: AppFeaturesConfigMerger;
private appFeatures?: Set<AppFeatureKey>;
private featuresSetup?: FeaturesPluginSetup;

constructor(
private readonly logger: Logger,
private readonly experimentalFeatures: ExperimentalFeatures
) {
this.merger = new AppFeaturesConfigMerger(this.logger);
this.securityFeatureConfigMerger = new AppFeaturesConfigMerger(
this.logger,
securitySubFeaturesMap
);
this.casesFeatureConfigMerger = new AppFeaturesConfigMerger(this.logger, casesSubFeaturesMap);
}

public init(featuresSetup: FeaturesPluginSetup) {
Expand All @@ -41,15 +48,15 @@ export class AppFeatures {
if (this.appFeatures) {
throw new Error('AppFeatures has already been initialized');
}
this.appFeatures = new Map(Object.entries(appFeatureKeys) as Array<[AppFeatureKey, boolean]>);
this.appFeatures = new Set(appFeatureKeys);
this.registerEnabledKibanaFeatures();
}

public isEnabled(appFeatureKey: AppFeatureKey): boolean {
if (!this.appFeatures) {
throw new Error('AppFeatures has not been initialized');
}
return this.appFeatures.get(appFeatureKey) ?? false;
return this.appFeatures.has(appFeatureKey);
}

private registerEnabledKibanaFeatures() {
Expand All @@ -59,25 +66,31 @@ export class AppFeatures {
);
}
// register main security Kibana features
const securityBaseKibanaFeature = getSecurityBaseKibanaFeature(this.experimentalFeatures);
const securityBaseKibanaFeature = getSecurityBaseKibanaFeature();
const securityBaseKibanaSubFeatureIds = getSecurityBaseKibanaSubFeatureIds(
this.experimentalFeatures
);
const enabledSecurityAppFeaturesConfigs = this.getEnabledAppFeaturesConfigs(
getSecurityAppFeaturesConfig()
);
this.featuresSetup.registerKibanaFeature(
this.merger.mergeAppFeatureConfigs(
this.securityFeatureConfigMerger.mergeAppFeatureConfigs(
securityBaseKibanaFeature,
securityBaseKibanaSubFeatureIds,
enabledSecurityAppFeaturesConfigs
)
);

// register security cases Kibana features
const securityCasesBaseKibanaFeature = getCasesBaseKibanaFeature();
const securityCasesBaseKibanaSubFeatureIds = getCasesBaseKibanaSubFeatureIds();
const enabledCasesAppFeaturesConfigs = this.getEnabledAppFeaturesConfigs(
getCasesAppFeaturesConfig()
);
this.featuresSetup.registerKibanaFeature(
this.merger.mergeAppFeatureConfigs(
this.casesFeatureConfigMerger.mergeAppFeatureConfigs(
securityCasesBaseKibanaFeature,
securityCasesBaseKibanaSubFeatureIds,
enabledCasesAppFeaturesConfigs
)
);
Expand Down
Loading