[Security Solution] Refactor AppFeatures to ProductFeatures (#177005)

## Summary

This PR renames `AppFeatures` -> `ProductFeatures`.

This module is responsible for managing the Security _features_ that are
enabled according to the _product_ type used. After talking with
different teams we agreed it would be more intuitive and easier to
understand if we named it `ProductFeatures`, since `AppFeatures` is too
vague and generic.

This refactoring does not introduce any change in the application
behavior.

Internal docs will also be updated.

---------

Co-authored-by: kibanamachine <42973632+kibanamachine@users.noreply.github.com>
This commit is contained in:
Sergi Massaneda 2024-02-19 12:55:05 +01:00 committed by GitHub
parent 5ae321f8f5
commit 872929bd60
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
72 changed files with 1063 additions and 962 deletions

View file

@ -5,8 +5,8 @@
* 2.0.
*/
export { securityDefaultAppFeaturesConfig } from './src/security/app_feature_config';
export { getCasesDefaultAppFeaturesConfig } from './src/cases/app_feature_config';
export { assistantDefaultAppFeaturesConfig } from './src/assistant/app_feature_config';
export { securityDefaultProductFeaturesConfig } from './src/security/product_feature_config';
export { getCasesDefaultProductFeaturesConfig } from './src/cases/product_feature_config';
export { assistantDefaultProductFeaturesConfig } from './src/assistant/product_feature_config';
export { createEnabledAppFeaturesConfigMap } from './src/helpers';
export { createEnabledProductFeaturesConfigMap } from './src/helpers';

View file

@ -5,4 +5,4 @@
* 2.0.
*/
export * from './src/app_features_keys';
export * from './src/product_features_keys';

View file

@ -4,4 +4,7 @@
* 2.0; you may not use this file except in compliance with the Elastic License
* 2.0.
*/
export { AppFeaturesPrivilegeId, AppFeaturesPrivileges } from './src/app_features_privileges';
export {
ProductFeaturesPrivilegeId,
ProductFeaturesPrivileges,
} from './src/product_features_privileges';

View file

@ -4,15 +4,15 @@
* 2.0; you may not use this file except in compliance with the Elastic License
* 2.0.
*/
import type { AssistantSubFeatureId } from '../app_features_keys';
import type { AppFeatureParams } from '../types';
import type { AssistantSubFeatureId } from '../product_features_keys';
import type { ProductFeatureParams } from '../types';
import { getAssistantBaseKibanaFeature } from './kibana_features';
import {
getAssistantBaseKibanaSubFeatureIds,
assistantSubFeaturesMap,
} from './kibana_sub_features';
export const getAssistantFeature = (): AppFeatureParams<AssistantSubFeatureId> => ({
export const getAssistantFeature = (): ProductFeatureParams<AssistantSubFeatureId> => ({
baseKibanaFeature: getAssistantBaseKibanaFeature(),
baseKibanaSubFeatureIds: getAssistantBaseKibanaSubFeatureIds(),
subFeaturesMap: assistantSubFeaturesMap,

View file

@ -5,9 +5,9 @@
* 2.0.
*/
import type { AssistantSubFeatureId } from '../app_features_keys';
import { AppFeatureAssistantKey } from '../app_features_keys';
import type { AppFeatureKibanaConfig } from '../types';
import type { AssistantSubFeatureId } from '../product_features_keys';
import { ProductFeatureAssistantKey } from '../product_features_keys';
import type { ProductFeatureKibanaConfig } from '../types';
/**
* App features privileges configuration for the Security Assistant Kibana Feature app.
@ -19,11 +19,11 @@ import type { AppFeatureKibanaConfig } from '../types';
* - `subFeatureIds`: the ids of the sub-features that will be added into the Security subFeatures entry.
* - `subFeaturesPrivileges`: the privileges that will be added into the existing Security subFeature with the privilege `id` specified.
*/
export const assistantDefaultAppFeaturesConfig: Record<
AppFeatureAssistantKey,
AppFeatureKibanaConfig<AssistantSubFeatureId>
export const assistantDefaultProductFeaturesConfig: Record<
ProductFeatureAssistantKey,
ProductFeatureKibanaConfig<AssistantSubFeatureId>
> = {
[AppFeatureAssistantKey.assistant]: {
[ProductFeatureAssistantKey.assistant]: {
privileges: {
all: {
ui: ['ai-assistant'],

View file

@ -4,15 +4,15 @@
* 2.0; you may not use this file except in compliance with the Elastic License
* 2.0.
*/
import type { CasesSubFeatureId } from '../app_features_keys';
import type { AppFeatureParams } from '../types';
import type { CasesSubFeatureId } from '../product_features_keys';
import type { ProductFeatureParams } from '../types';
import { getCasesBaseKibanaFeature } from './kibana_features';
import { getCasesBaseKibanaSubFeatureIds, getCasesSubFeaturesMap } from './kibana_sub_features';
import type { CasesFeatureParams } from './types';
export const getCasesFeature = (
params: CasesFeatureParams
): AppFeatureParams<CasesSubFeatureId> => ({
): ProductFeatureParams<CasesSubFeatureId> => ({
baseKibanaFeature: getCasesBaseKibanaFeature(params),
baseKibanaSubFeatureIds: getCasesBaseKibanaSubFeatureIds(),
subFeaturesMap: getCasesSubFeaturesMap(params),

View file

@ -7,7 +7,7 @@
import { i18n } from '@kbn/i18n';
import type { SubFeatureConfig } from '@kbn/features-plugin/common';
import { CasesSubFeatureId } from '../app_features_keys';
import { CasesSubFeatureId } from '../product_features_keys';
import { APP_ID } from '../constants';
import type { CasesFeatureParams } from './types';

View file

@ -5,9 +5,9 @@
* 2.0.
*/
import { AppFeatureCasesKey } from '../app_features_keys';
import { ProductFeatureCasesKey } from '../product_features_keys';
import { APP_ID } from '../constants';
import type { DefaultCasesAppFeaturesConfig } from './types';
import type { DefaultCasesProductFeaturesConfig } from './types';
/**
* App features privileges configuration for the Security Cases Kibana Feature app.
@ -19,14 +19,14 @@ import type { DefaultCasesAppFeaturesConfig } from './types';
* - `subFeatureIds`: the ids of the sub-features that will be added into the Security subFeatures entry.
* - `subFeaturesPrivileges`: the privileges that will be added into the existing Security subFeature with the privilege `id` specified.
*/
export const getCasesDefaultAppFeaturesConfig = ({
export const getCasesDefaultProductFeaturesConfig = ({
apiTags,
uiCapabilities,
}: {
apiTags: { connectors: string };
uiCapabilities: { connectors: string };
}): DefaultCasesAppFeaturesConfig => ({
[AppFeatureCasesKey.casesConnectors]: {
}): DefaultCasesProductFeaturesConfig => ({
[ProductFeatureCasesKey.casesConnectors]: {
privileges: {
all: {
api: [apiTags.connectors],

View file

@ -6,8 +6,8 @@
*/
import type { CasesUiCapabilities, CasesApiTags } from '@kbn/cases-plugin/common';
import type { AppFeatureCasesKey, CasesSubFeatureId } from '../app_features_keys';
import type { AppFeatureKibanaConfig } from '../types';
import type { ProductFeatureCasesKey, CasesSubFeatureId } from '../product_features_keys';
import type { ProductFeatureKibanaConfig } from '../types';
export interface CasesFeatureParams {
uiCapabilities: CasesUiCapabilities;
@ -15,7 +15,7 @@ export interface CasesFeatureParams {
savedObjects: { files: string[] };
}
export type DefaultCasesAppFeaturesConfig = Record<
AppFeatureCasesKey,
AppFeatureKibanaConfig<CasesSubFeatureId>
export type DefaultCasesProductFeaturesConfig = Record<
ProductFeatureCasesKey,
ProductFeatureKibanaConfig<CasesSubFeatureId>
>;

View file

@ -5,26 +5,29 @@
* 2.0.
*/
import type { AppFeatureKeys, AppFeatureKeyType, AppFeatureKibanaConfig } from './types';
import type {
ProductFeatureKeys,
ProductFeatureKeyType,
ProductFeatureKibanaConfig,
} from './types';
/**
* Creates the AppFeaturesConfig Map from the given appFeatures object and a set of enabled appFeatures keys.
* Creates the ProductFeaturesConfig Map from the given productFeatures object and a set of enabled productFeatures keys.
*/
export const createEnabledAppFeaturesConfigMap = <
K extends AppFeatureKeyType,
export const createEnabledProductFeaturesConfigMap = <
K extends ProductFeatureKeyType,
T extends string = string
>(
appFeatures: Record<K, AppFeatureKibanaConfig<T>>,
enabledAppFeaturesKeys: AppFeatureKeys
) => {
return new Map(
Object.entries<AppFeatureKibanaConfig<T>>(appFeatures).reduce<
Array<[K, AppFeatureKibanaConfig<T>]>
productFeatures: Record<K, ProductFeatureKibanaConfig<T>>,
enabledProductFeaturesKeys: ProductFeatureKeys
) =>
new Map(
Object.entries<ProductFeatureKibanaConfig<T>>(productFeatures).reduce<
Array<[K, ProductFeatureKibanaConfig<T>]>
>((acc, [key, value]) => {
if (enabledAppFeaturesKeys.includes(key as K)) {
if (enabledProductFeaturesKeys.includes(key as K)) {
acc.push([key as K, value]);
}
return acc;
}, [])
);
};

View file

@ -5,7 +5,7 @@
* 2.0.
*/
export enum AppFeatureSecurityKey {
export enum ProductFeatureSecurityKey {
/** Enables Advanced Insights (Entity Risk, GenAI) */
advancedInsights = 'advanced_insights',
/**
@ -65,14 +65,14 @@ export enum AppFeatureSecurityKey {
externalRuleActions = 'external_rule_actions',
}
export enum AppFeatureCasesKey {
export enum ProductFeatureCasesKey {
/**
* Enables Cases Connectors
*/
casesConnectors = 'cases_connectors',
}
export enum AppFeatureAssistantKey {
export enum ProductFeatureAssistantKey {
/**
* Enables Elastic AI Assistant
*/
@ -80,15 +80,18 @@ export enum AppFeatureAssistantKey {
}
// Merges the two enums.
export const AppFeatureKey = {
...AppFeatureSecurityKey,
...AppFeatureCasesKey,
...AppFeatureAssistantKey,
export const ProductFeatureKey = {
...ProductFeatureSecurityKey,
...ProductFeatureCasesKey,
...ProductFeatureAssistantKey,
};
// We need to merge the value and the type and export both to replicate how enum works.
export type AppFeatureKeyType = AppFeatureSecurityKey | AppFeatureCasesKey | AppFeatureAssistantKey;
export type ProductFeatureKeyType =
| ProductFeatureSecurityKey
| ProductFeatureCasesKey
| ProductFeatureAssistantKey;
export const ALL_APP_FEATURE_KEYS = Object.freeze(Object.values(AppFeatureKey));
export const ALL_PRODUCT_FEATURE_KEYS = Object.freeze(Object.values(ProductFeatureKey));
/** Sub-features IDs for Security */
export enum SecuritySubFeatureId {

View file

@ -7,7 +7,7 @@
import { APP_ID } from './constants';
export enum AppFeaturesPrivilegeId {
export enum ProductFeaturesPrivilegeId {
endpointExceptions = 'endpoint_exceptions',
}
@ -16,8 +16,8 @@ export enum AppFeaturesPrivilegeId {
* using a different Kibana feature configuration (sub-feature, main feature privilege, etc)
* in each offering type (ess, serverless)
*/
export const AppFeaturesPrivileges = {
[AppFeaturesPrivilegeId.endpointExceptions]: {
export const ProductFeaturesPrivileges = {
[ProductFeaturesPrivilegeId.endpointExceptions]: {
all: {
ui: ['showEndpointExceptions', 'crudEndpointExceptions'],
api: [`${APP_ID}-showEndpointExceptions`, `${APP_ID}-crudEndpointExceptions`],

View file

@ -4,15 +4,15 @@
* 2.0; you may not use this file except in compliance with the Elastic License
* 2.0.
*/
import type { SecuritySubFeatureId } from '../app_features_keys';
import type { AppFeatureParams } from '../types';
import type { SecuritySubFeatureId } from '../product_features_keys';
import type { ProductFeatureParams } from '../types';
import { getSecurityBaseKibanaFeature } from './kibana_features';
import { securitySubFeaturesMap, getSecurityBaseKibanaSubFeatureIds } from './kibana_sub_features';
import type { SecurityFeatureParams } from './types';
export const getSecurityFeature = (
params: SecurityFeatureParams
): AppFeatureParams<SecuritySubFeatureId> => ({
): ProductFeatureParams<SecuritySubFeatureId> => ({
baseKibanaFeature: getSecurityBaseKibanaFeature(params),
baseKibanaSubFeatureIds: getSecurityBaseKibanaSubFeatureIds(params),
subFeaturesMap: securitySubFeaturesMap,

View file

@ -8,9 +8,12 @@
import { i18n } from '@kbn/i18n';
import type { SubFeatureConfig } from '@kbn/features-plugin/common';
import { EXCEPTION_LIST_NAMESPACE_AGNOSTIC } from '@kbn/securitysolution-list-constants';
import { AppFeaturesPrivilegeId, AppFeaturesPrivileges } from '../app_features_privileges';
import {
ProductFeaturesPrivilegeId,
ProductFeaturesPrivileges,
} from '../product_features_privileges';
import { SecuritySubFeatureId } from '../app_features_keys';
import { SecuritySubFeatureId } from '../product_features_keys';
import { APP_ID } from '../constants';
import type { SecurityFeatureParams } from './types';
@ -583,7 +586,7 @@ const endpointExceptionsSubFeature: SubFeatureConfig = {
all: [],
read: [],
},
...AppFeaturesPrivileges[AppFeaturesPrivilegeId.endpointExceptions].all,
...ProductFeaturesPrivileges[ProductFeaturesPrivilegeId.endpointExceptions].all,
},
{
id: 'endpoint_exceptions_read',
@ -593,7 +596,7 @@ const endpointExceptionsSubFeature: SubFeatureConfig = {
all: [],
read: [],
},
...AppFeaturesPrivileges[AppFeaturesPrivilegeId.endpointExceptions].read,
...ProductFeaturesPrivileges[ProductFeaturesPrivilegeId.endpointExceptions].read,
},
],
},

View file

@ -5,9 +5,9 @@
* 2.0.
*/
import { AppFeatureSecurityKey, SecuritySubFeatureId } from '../app_features_keys';
import { ProductFeatureSecurityKey, SecuritySubFeatureId } from '../product_features_keys';
import { APP_ID } from '../constants';
import type { DefaultSecurityAppFeaturesConfig } from './types';
import type { DefaultSecurityProductFeaturesConfig } from './types';
/**
* App features privileges configuration for the Security Solution Kibana Feature app.
@ -19,8 +19,8 @@ import type { DefaultSecurityAppFeaturesConfig } from './types';
* - `subFeatureIds`: the ids of the sub-features that will be added into the Security subFeatures entry.
* - `subFeaturesPrivileges`: the privileges that will be added into the existing Security subFeature with the privilege `id` specified.
*/
export const securityDefaultAppFeaturesConfig: DefaultSecurityAppFeaturesConfig = {
[AppFeatureSecurityKey.advancedInsights]: {
export const securityDefaultProductFeaturesConfig: DefaultSecurityProductFeaturesConfig = {
[ProductFeatureSecurityKey.advancedInsights]: {
privileges: {
all: {
ui: ['entity-analytics'],
@ -32,7 +32,7 @@ export const securityDefaultAppFeaturesConfig: DefaultSecurityAppFeaturesConfig
},
},
},
[AppFeatureSecurityKey.investigationGuide]: {
[ProductFeatureSecurityKey.investigationGuide]: {
privileges: {
all: {
ui: ['investigation-guide'],
@ -43,7 +43,7 @@ export const securityDefaultAppFeaturesConfig: DefaultSecurityAppFeaturesConfig
},
},
[AppFeatureSecurityKey.threatIntelligence]: {
[ProductFeatureSecurityKey.threatIntelligence]: {
privileges: {
all: {
ui: ['threat-intelligence'],
@ -56,18 +56,18 @@ export const securityDefaultAppFeaturesConfig: DefaultSecurityAppFeaturesConfig
},
},
[AppFeatureSecurityKey.endpointHostManagement]: {
[ProductFeatureSecurityKey.endpointHostManagement]: {
subFeatureIds: [SecuritySubFeatureId.endpointList],
},
[AppFeatureSecurityKey.endpointPolicyManagement]: {
[ProductFeatureSecurityKey.endpointPolicyManagement]: {
subFeatureIds: [SecuritySubFeatureId.policyManagement],
},
// Adds no additional kibana feature controls
[AppFeatureSecurityKey.endpointPolicyProtections]: {},
[ProductFeatureSecurityKey.endpointPolicyProtections]: {},
[AppFeatureSecurityKey.endpointArtifactManagement]: {
[ProductFeatureSecurityKey.endpointArtifactManagement]: {
subFeatureIds: [
SecuritySubFeatureId.trustedApplications,
SecuritySubFeatureId.blocklist,
@ -87,7 +87,7 @@ export const securityDefaultAppFeaturesConfig: DefaultSecurityAppFeaturesConfig
],
},
[AppFeatureSecurityKey.endpointResponseActions]: {
[ProductFeatureSecurityKey.endpointResponseActions]: {
subFeatureIds: [
SecuritySubFeatureId.hostIsolationExceptions,
SecuritySubFeatureId.responseActionsHistory,
@ -105,8 +105,9 @@ export const securityDefaultAppFeaturesConfig: DefaultSecurityAppFeaturesConfig
],
},
[AppFeatureSecurityKey.osqueryAutomatedResponseActions]: {},
[AppFeatureSecurityKey.endpointProtectionUpdates]: {},
[AppFeatureSecurityKey.endpointAgentTamperProtection]: {},
[AppFeatureSecurityKey.externalRuleActions]: {},
// Product features without RBAC
[ProductFeatureSecurityKey.osqueryAutomatedResponseActions]: {},
[ProductFeatureSecurityKey.endpointProtectionUpdates]: {},
[ProductFeatureSecurityKey.endpointAgentTamperProtection]: {},
[ProductFeatureSecurityKey.externalRuleActions]: {},
};

View file

@ -5,16 +5,16 @@
* 2.0.
*/
import type { AppFeatureSecurityKey, SecuritySubFeatureId } from '../app_features_keys';
import type { AppFeatureKibanaConfig } from '../types';
import type { ProductFeatureSecurityKey, SecuritySubFeatureId } from '../product_features_keys';
import type { ProductFeatureKibanaConfig } from '../types';
export interface SecurityFeatureParams {
experimentalFeatures: Record<string, boolean>;
savedObjects: string[];
}
export type DefaultSecurityAppFeaturesConfig = Omit<
Record<AppFeatureSecurityKey, AppFeatureKibanaConfig<SecuritySubFeatureId>>,
AppFeatureSecurityKey.endpointExceptions
// | add not default security app features here
export type DefaultSecurityProductFeaturesConfig = Omit<
Record<ProductFeatureSecurityKey, ProductFeatureKibanaConfig<SecuritySubFeatureId>>,
ProductFeatureSecurityKey.endpointExceptions
// | add not generic security app features here
>;

View file

@ -12,48 +12,48 @@ import type {
} from '@kbn/features-plugin/common';
import type { RecursivePartial } from '@kbn/utility-types';
import type {
AppFeatureAssistantKey,
AppFeatureCasesKey,
AppFeatureKeyType,
AppFeatureSecurityKey,
ProductFeatureAssistantKey,
ProductFeatureCasesKey,
ProductFeatureKeyType,
ProductFeatureSecurityKey,
AssistantSubFeatureId,
CasesSubFeatureId,
SecuritySubFeatureId,
} from './app_features_keys';
} from './product_features_keys';
export type { AppFeatureKeyType };
export type AppFeatureKeys = AppFeatureKeyType[];
export type { ProductFeatureKeyType };
export type ProductFeatureKeys = ProductFeatureKeyType[];
// Features types
export type BaseKibanaFeatureConfig = Omit<KibanaFeatureConfig, 'subFeatures'>;
export type SubFeaturesPrivileges = RecursivePartial<SubFeaturePrivilegeConfig>;
export type AppFeatureKibanaConfig<T extends string = string> =
export type ProductFeatureKibanaConfig<T extends string = string> =
RecursivePartial<BaseKibanaFeatureConfig> & {
subFeatureIds?: T[];
subFeaturesPrivileges?: SubFeaturesPrivileges[];
};
export type AppFeaturesConfig<T extends string = string> = Map<
AppFeatureKeyType,
AppFeatureKibanaConfig<T>
export type ProductFeaturesConfig<T extends string = string> = Map<
ProductFeatureKeyType,
ProductFeatureKibanaConfig<T>
>;
export type AppFeaturesSecurityConfig = Map<
AppFeatureSecurityKey,
AppFeatureKibanaConfig<SecuritySubFeatureId>
export type ProductFeaturesSecurityConfig = Map<
ProductFeatureSecurityKey,
ProductFeatureKibanaConfig<SecuritySubFeatureId>
>;
export type AppFeaturesCasesConfig = Map<
AppFeatureCasesKey,
AppFeatureKibanaConfig<CasesSubFeatureId>
export type ProductFeaturesCasesConfig = Map<
ProductFeatureCasesKey,
ProductFeatureKibanaConfig<CasesSubFeatureId>
>;
export type AppFeaturesAssistantConfig = Map<
AppFeatureAssistantKey,
AppFeatureKibanaConfig<AssistantSubFeatureId>
export type ProductFeaturesAssistantConfig = Map<
ProductFeatureAssistantKey,
ProductFeatureKibanaConfig<AssistantSubFeatureId>
>;
export type AppSubFeaturesMap<T extends string = string> = Map<T, SubFeatureConfig>;
export interface AppFeatureParams<T extends string = string> {
export interface ProductFeatureParams<T extends string = string> {
baseKibanaFeature: BaseKibanaFeatureConfig;
baseKibanaSubFeatureIds: T[];
subFeaturesMap: AppSubFeaturesMap<T>;

View file

@ -8,7 +8,7 @@
import type { ENDPOINT_PRIVILEGES, FleetAuthz } from '@kbn/fleet-plugin/common';
import { omit } from 'lodash';
import type { AppFeaturesService } from '../../../../server/lib/app_features_service';
import type { ProductFeaturesService } from '../../../../server/lib/product_features_service';
import { RESPONSE_CONSOLE_ACTION_COMMANDS_TO_REQUIRED_AUTHZ } from '../response_actions/constants';
import type { LicenseService } from '../../../license';
import type { EndpointAuthz } from '../../types/authz';
@ -22,19 +22,19 @@ import type { MaybeImmutable } from '../../types';
* level, use `calculateEndpointAuthz()`
*
* @param fleetAuthz
* @param appFeatureService
* @param productFeatureService
*/
function hasAuthFactory(fleetAuthz: FleetAuthz, appFeatureService?: AppFeaturesService) {
function hasAuthFactory(fleetAuthz: FleetAuthz, productFeatureService?: ProductFeaturesService) {
return function hasAuth(
privilege: keyof typeof ENDPOINT_PRIVILEGES,
{ action }: { action?: string } = {}
): boolean {
// Product features control
if (appFeatureService) {
if (productFeatureService) {
// Only server side has to check this, to prevent "superuser" role from being allowed to use product gated APIs.
// UI side does not need to check this. Capabilities list is correct for superuser.
const actionToCheck = action ?? appFeatureService.getApiActionName(privilege);
if (!appFeatureService.isActionRegistered(actionToCheck)) {
const actionToCheck = action ?? productFeatureService.getApiActionName(privilege);
if (!productFeatureService.isActionRegistered(actionToCheck)) {
return false;
}
}
@ -58,9 +58,9 @@ export const calculateEndpointAuthz = (
licenseService: LicenseService,
fleetAuthz: FleetAuthz,
userRoles: MaybeImmutable<string[]> = [],
appFeaturesService?: AppFeaturesService // only exists on the server side
productFeaturesService?: ProductFeaturesService // only exists on the server side
): EndpointAuthz => {
const hasAuth = hasAuthFactory(fleetAuthz, appFeaturesService);
const hasAuth = hasAuthFactory(fleetAuthz, productFeaturesService);
const isPlatinumPlusLicense = licenseService.isPlatinumPlus();
const isEnterpriseLicense = licenseService.isEnterprise();

View file

@ -9,7 +9,7 @@ import React from 'react';
import { EuiCode, EuiEmptyPrompt } from '@elastic/eui';
import { FormattedMessage } from '@kbn/i18n-react';
import { useIsMounted } from '@kbn/securitysolution-hook-utils';
import { AppFeatureKey } from '@kbn/security-solution-features/keys';
import { ProductFeatureKey } from '@kbn/security-solution-features/keys';
import { useUpsellingComponent } from '../../../common/hooks/use_upselling';
import { ResponseActionFormField } from './osquery_response_action_form_field';
import type { ArrayItem } from '../../../shared_imports';
@ -28,7 +28,9 @@ export const OsqueryResponseAction = React.memo((props: OsqueryResponseActionPro
const isMounted = useIsMounted();
// serverless component that is returned when users do not have Endpoint.Complete tier
const UpsellingComponent = useUpsellingComponent(AppFeatureKey.osqueryAutomatedResponseActions);
const UpsellingComponent = useUpsellingComponent(
ProductFeatureKey.osqueryAutomatedResponseActions
);
if (osquery) {
const { disabled, permissionDenied } = osquery.fetchInstallationStatus();

View file

@ -50,7 +50,7 @@ import { calculateEndpointAuthz } from '../../common/endpoint/service/authz';
import type { FeatureUsageService } from './services/feature_usage/service';
import type { ExperimentalFeatures } from '../../common/experimental_features';
import type { ActionCreateService } from './services/actions/create/types';
import type { AppFeaturesService } from '../lib/app_features_service/app_features_service';
import type { ProductFeaturesService } from '../lib/product_features_service/product_features_service';
export interface EndpointAppContextServiceSetupContract {
securitySolutionRequestContextFactory: IRequestContextFactory;
@ -79,7 +79,7 @@ export interface EndpointAppContextServiceStartContract {
messageSigningService: MessageSigningServiceInterface | undefined;
actionCreateService: ActionCreateService | undefined;
esClient: ElasticsearchClient;
appFeaturesService: AppFeaturesService;
productFeaturesService: ProductFeaturesService;
savedObjectsClient: SavedObjectsClientContract;
}
@ -117,17 +117,17 @@ export class EndpointAppContextService {
featureUsageService,
endpointMetadataService,
esClient,
appFeaturesService,
productFeaturesService,
savedObjectsClient,
} = dependencies;
registerIngestCallback(
'agentPolicyCreate',
getAgentPolicyCreateCallback(logger, appFeaturesService)
getAgentPolicyCreateCallback(logger, productFeaturesService)
);
registerIngestCallback(
'agentPolicyUpdate',
getAgentPolicyUpdateCallback(logger, appFeaturesService)
getAgentPolicyUpdateCallback(logger, productFeaturesService)
);
registerIngestCallback(
@ -140,7 +140,7 @@ export class EndpointAppContextService {
licenseService,
exceptionListsClient,
this.setupDependencies.cloud,
appFeaturesService
productFeaturesService
)
);
@ -158,7 +158,7 @@ export class EndpointAppContextService {
endpointMetadataService,
this.setupDependencies.cloud,
esClient,
appFeaturesService
productFeaturesService
)
);
@ -194,7 +194,7 @@ export class EndpointAppContextService {
}
public async getEndpointAuthz(request: KibanaRequest): Promise<EndpointAuthz> {
if (!this.startDependencies?.appFeaturesService) {
if (!this.startDependencies?.productFeaturesService) {
throw new EndpointAppContentServicesNotStartedError();
}
const fleetAuthz = await this.getFleetAuthzService().fromRequest(request);
@ -203,7 +203,7 @@ export class EndpointAppContextService {
this.getLicenseService(),
fleetAuthz,
userRoles,
this.startDependencies.appFeaturesService
this.startDependencies.productFeaturesService
);
}

View file

@ -9,25 +9,25 @@ import { createMockEndpointAppContextServiceStartContract } from '../mocks';
import type { Logger } from '@kbn/logging';
import type { EndpointInternalFleetServicesInterface } from '../services/fleet';
import { ALL_APP_FEATURE_KEYS } from '@kbn/security-solution-features/keys';
import type { AppFeaturesService } from '../../lib/app_features_service/app_features_service';
import { createAppFeaturesServiceMock } from '../../lib/app_features_service/mocks';
import { ALL_PRODUCT_FEATURE_KEYS } from '@kbn/security-solution-features/keys';
import type { ProductFeaturesService } from '../../lib/product_features_service/product_features_service';
import { createProductFeaturesServiceMock } from '../../lib/product_features_service/mocks';
import { turnOffAgentPolicyFeatures } from './turn_off_agent_policy_features';
describe('Turn Off Agent Policy Features Migration', () => {
let fleetServices: EndpointInternalFleetServicesInterface;
let appFeatureService: AppFeaturesService;
let productFeatureService: ProductFeaturesService;
let logger: Logger;
const callTurnOffAgentPolicyFeatures = () =>
turnOffAgentPolicyFeatures(fleetServices, appFeatureService, logger);
turnOffAgentPolicyFeatures(fleetServices, productFeatureService, logger);
beforeEach(() => {
const endpointContextStartContract = createMockEndpointAppContextServiceStartContract();
({ logger } = endpointContextStartContract);
appFeatureService = endpointContextStartContract.appFeaturesService;
productFeatureService = endpointContextStartContract.productFeaturesService;
fleetServices = endpointContextStartContract.endpointFleetServicesFactory.asInternalUser();
});
@ -46,8 +46,8 @@ describe('Turn Off Agent Policy Features Migration', () => {
describe('and `agentTamperProtection` is disabled', () => {
beforeEach(() => {
appFeatureService = createAppFeaturesServiceMock(
ALL_APP_FEATURE_KEYS.filter((key) => key !== 'endpoint_agent_tamper_protection')
productFeatureService = createProductFeaturesServiceMock(
ALL_PRODUCT_FEATURE_KEYS.filter((key) => key !== 'endpoint_agent_tamper_protection')
);
});

View file

@ -6,27 +6,27 @@
*/
import type { Logger } from '@kbn/core/server';
import { AppFeatureSecurityKey } from '@kbn/security-solution-features/keys';
import { ProductFeatureSecurityKey } from '@kbn/security-solution-features/keys';
import type { EndpointInternalFleetServicesInterface } from '../services/fleet';
import type { AppFeaturesService } from '../../lib/app_features_service/app_features_service';
import type { ProductFeaturesService } from '../../lib/product_features_service/product_features_service';
export const turnOffAgentPolicyFeatures = async (
fleetServices: EndpointInternalFleetServicesInterface,
appFeaturesService: AppFeaturesService,
productFeaturesService: ProductFeaturesService,
logger: Logger
): Promise<void> => {
const log = logger.get('endpoint', 'agentPolicyFeatures');
if (appFeaturesService.isEnabled(AppFeatureSecurityKey.endpointAgentTamperProtection)) {
if (productFeaturesService.isEnabled(ProductFeatureSecurityKey.endpointAgentTamperProtection)) {
log.info(
`App feature [${AppFeatureSecurityKey.endpointAgentTamperProtection}] is enabled. Nothing to do!`
`App feature [${ProductFeatureSecurityKey.endpointAgentTamperProtection}] is enabled. Nothing to do!`
);
return;
}
log.info(
`App feature [${AppFeatureSecurityKey.endpointAgentTamperProtection}] is disabled. Checking fleet agent policies for compliance`
`App feature [${ProductFeatureSecurityKey.endpointAgentTamperProtection}] is disabled. Checking fleet agent policies for compliance`
);
const { agentPolicy: agentPolicyService, internalSoClient } = fleetServices;

View file

@ -10,24 +10,24 @@ import type { Logger } from '@kbn/logging';
import type { ElasticsearchClient } from '@kbn/core-elasticsearch-server';
import type { EndpointInternalFleetServicesInterface } from '../services/fleet';
import { ALL_APP_FEATURE_KEYS } from '@kbn/security-solution-features/keys';
import { ALL_PRODUCT_FEATURE_KEYS } from '@kbn/security-solution-features/keys';
import { turnOffPolicyProtectionsIfNotSupported } from './turn_off_policy_protections';
import { FleetPackagePolicyGenerator } from '../../../common/endpoint/data_generators/fleet_package_policy_generator';
import type { PolicyData } from '../../../common/endpoint/types';
import type { PackagePolicyClient } from '@kbn/fleet-plugin/server';
import type { PromiseResolvedValue } from '../../../common/endpoint/types/utility_types';
import { ensureOnlyEventCollectionIsAllowed } from '../../../common/endpoint/models/policy_config_helpers';
import type { AppFeaturesService } from '../../lib/app_features_service/app_features_service';
import { createAppFeaturesServiceMock } from '../../lib/app_features_service/mocks';
import type { ProductFeaturesService } from '../../lib/product_features_service/product_features_service';
import { createProductFeaturesServiceMock } from '../../lib/product_features_service/mocks';
describe('Turn Off Policy Protections Migration', () => {
let esClient: ElasticsearchClient;
let fleetServices: EndpointInternalFleetServicesInterface;
let appFeatureService: AppFeaturesService;
let productFeatureService: ProductFeaturesService;
let logger: Logger;
const callTurnOffPolicyProtections = () =>
turnOffPolicyProtectionsIfNotSupported(esClient, fleetServices, appFeatureService, logger);
turnOffPolicyProtectionsIfNotSupported(esClient, fleetServices, productFeatureService, logger);
const generatePolicyMock = (
policyGenerator: FleetPackagePolicyGenerator,
@ -60,7 +60,7 @@ describe('Turn Off Policy Protections Migration', () => {
({ esClient, logger } = endpointContextStartContract);
appFeatureService = endpointContextStartContract.appFeaturesService;
productFeatureService = endpointContextStartContract.productFeaturesService;
fleetServices = endpointContextStartContract.endpointFleetServicesFactory.asInternalUser();
});
@ -89,8 +89,8 @@ describe('Turn Off Policy Protections Migration', () => {
policyGenerator = new FleetPackagePolicyGenerator('seed');
const packagePolicyListSrv = fleetServices.packagePolicy.list as jest.Mock;
appFeatureService = createAppFeaturesServiceMock(
ALL_APP_FEATURE_KEYS.filter((key) => key !== 'endpoint_protection_updates')
productFeatureService = createProductFeaturesServiceMock(
ALL_PRODUCT_FEATURE_KEYS.filter((key) => key !== 'endpoint_protection_updates')
);
page1Items = [
@ -160,8 +160,8 @@ describe('Turn Off Policy Protections Migration', () => {
policyGenerator = new FleetPackagePolicyGenerator('seed');
const packagePolicyListSrv = fleetServices.packagePolicy.list as jest.Mock;
appFeatureService = createAppFeaturesServiceMock(
ALL_APP_FEATURE_KEYS.filter((key) => key !== 'endpoint_policy_protections')
productFeatureService = createProductFeaturesServiceMock(
ALL_PRODUCT_FEATURE_KEYS.filter((key) => key !== 'endpoint_policy_protections')
);
page1Items = [
@ -253,8 +253,8 @@ describe('Turn Off Policy Protections Migration', () => {
policyGenerator = new FleetPackagePolicyGenerator('seed');
const packagePolicyListSrv = fleetServices.packagePolicy.list as jest.Mock;
appFeatureService = createAppFeaturesServiceMock(
ALL_APP_FEATURE_KEYS.filter(
productFeatureService = createProductFeaturesServiceMock(
ALL_PRODUCT_FEATURE_KEYS.filter(
(key) => key !== 'endpoint_policy_protections' && key !== 'endpoint_protection_updates'
)
);

View file

@ -8,7 +8,7 @@
import type { ElasticsearchClient, Logger } from '@kbn/core/server';
import type { UpdatePackagePolicy } from '@kbn/fleet-plugin/common';
import type { AuthenticatedUser } from '@kbn/security-plugin/common';
import { AppFeatureSecurityKey } from '@kbn/security-solution-features/keys';
import { ProductFeatureSecurityKey } from '@kbn/security-solution-features/keys';
import {
ensureOnlyEventCollectionIsAllowed,
isPolicySetToEventCollectionOnly,
@ -16,33 +16,33 @@ import {
import type { PolicyData } from '../../../common/endpoint/types';
import type { EndpointInternalFleetServicesInterface } from '../services/fleet';
import { getPolicyDataForUpdate } from '../../../common/endpoint/service/policy';
import type { AppFeaturesService } from '../../lib/app_features_service/app_features_service';
import type { ProductFeaturesService } from '../../lib/product_features_service/product_features_service';
export const turnOffPolicyProtectionsIfNotSupported = async (
esClient: ElasticsearchClient,
fleetServices: EndpointInternalFleetServicesInterface,
appFeaturesService: AppFeaturesService,
productFeaturesService: ProductFeaturesService,
logger: Logger
): Promise<void> => {
const log = logger.get('endpoint', 'policyProtections');
const isProtectionUpdatesFeatureEnabled = appFeaturesService.isEnabled(
AppFeatureSecurityKey.endpointProtectionUpdates
const isProtectionUpdatesFeatureEnabled = productFeaturesService.isEnabled(
ProductFeatureSecurityKey.endpointProtectionUpdates
);
const isPolicyProtectionsEnabled = appFeaturesService.isEnabled(
AppFeatureSecurityKey.endpointPolicyProtections
const isPolicyProtectionsEnabled = productFeaturesService.isEnabled(
ProductFeatureSecurityKey.endpointPolicyProtections
);
if (isPolicyProtectionsEnabled) {
log.info(
`App feature [${AppFeatureSecurityKey.endpointPolicyProtections}] is enabled. Nothing to do!`
`App feature [${ProductFeatureSecurityKey.endpointPolicyProtections}] is enabled. Nothing to do!`
);
}
if (isProtectionUpdatesFeatureEnabled) {
log.info(
`App feature [${AppFeatureSecurityKey.endpointProtectionUpdates}] is enabled. Nothing to do!`
`App feature [${ProductFeatureSecurityKey.endpointProtectionUpdates}] is enabled. Nothing to do!`
);
}
@ -52,13 +52,13 @@ export const turnOffPolicyProtectionsIfNotSupported = async (
if (!isPolicyProtectionsEnabled) {
log.info(
`App feature [${AppFeatureSecurityKey.endpointPolicyProtections}] is disabled. Checking endpoint integration policies for compliance`
`App feature [${ProductFeatureSecurityKey.endpointPolicyProtections}] is disabled. Checking endpoint integration policies for compliance`
);
}
if (!isProtectionUpdatesFeatureEnabled) {
log.info(
`App feature [${AppFeatureSecurityKey.endpointProtectionUpdates}] is disabled. Checking endpoint integration policies for compliance`
`App feature [${ProductFeatureSecurityKey.endpointProtectionUpdates}] is disabled. Checking endpoint integration policies for compliance`
);
}

View file

@ -71,7 +71,7 @@ import type { EndpointAuthz } from '../../common/endpoint/types/authz';
import { EndpointFleetServicesFactory } from './services/fleet';
import { createLicenseServiceMock } from '../../common/license/mocks';
import { createFeatureUsageServiceMock } from './services/feature_usage/mocks';
import { createAppFeaturesServiceMock } from '../lib/app_features_service/mocks';
import { createProductFeaturesServiceMock } from '../lib/product_features_service/mocks';
/**
* Creates a mocked EndpointAppContext.
@ -170,7 +170,7 @@ export const createMockEndpointAppContextServiceStartContract =
savedObjectsStart
);
const experimentalFeatures = config.experimentalFeatures;
const appFeaturesService = createAppFeaturesServiceMock(
const productFeaturesService = createProductFeaturesServiceMock(
undefined,
experimentalFeatures,
undefined,
@ -224,7 +224,7 @@ export const createMockEndpointAppContextServiceStartContract =
actionCreateService: undefined,
createFleetActionsClient: jest.fn((_) => fleetActionsClientMock),
esClient: elasticsearchClientMock.createElasticsearchClient(),
appFeaturesService,
productFeaturesService,
savedObjectsClient: savedObjectsClientMock.create(),
};
};

View file

@ -16,7 +16,7 @@ import { createPackagePolicyServiceMock } from '@kbn/fleet-plugin/server/mocks';
import type { ExceptionListClient } from '@kbn/lists-plugin/server';
import { listMock } from '@kbn/lists-plugin/server/mocks';
import type { ExceptionListItemSchema } from '@kbn/securitysolution-io-ts-list-types';
import type { AppFeatureKeys } from '@kbn/security-solution-features';
import type { ProductFeatureKeys } from '@kbn/security-solution-features';
import {
createPackagePolicyWithInitialManifestMock,
createPackagePolicyWithManifestMock,
@ -28,8 +28,8 @@ import { createEndpointArtifactClientMock, getManifestClientMock } from '../mock
import type { ManifestManagerContext } from './manifest_manager';
import { ManifestManager } from './manifest_manager';
import { parseExperimentalConfigValue } from '../../../../../common/experimental_features';
import { createAppFeaturesServiceMock } from '../../../../lib/app_features_service/mocks';
import type { AppFeaturesService } from '../../../../lib/app_features_service/app_features_service';
import { createProductFeaturesServiceMock } from '../../../../lib/product_features_service/mocks';
import type { ProductFeaturesService } from '../../../../lib/product_features_service/product_features_service';
export const createExceptionListResponse = (data: ExceptionListItemSchema[], total?: number) => ({
data,
@ -71,28 +71,28 @@ export interface ManifestManagerMockOptions {
exceptionListClient: ExceptionListClient;
packagePolicyService: jest.Mocked<PackagePolicyClient>;
savedObjectsClient: ReturnType<typeof savedObjectsClientMock.create>;
appFeaturesService: AppFeaturesService;
productFeaturesService: ProductFeaturesService;
}
export const buildManifestManagerMockOptions = (
opts: Partial<ManifestManagerMockOptions>,
customAppFeatures?: AppFeatureKeys
customProductFeatures?: ProductFeatureKeys
): ManifestManagerMockOptions => {
const savedObjectMock = savedObjectsClientMock.create();
return {
exceptionListClient: listMock.getExceptionListClient(savedObjectMock),
packagePolicyService: createPackagePolicyServiceMock(),
savedObjectsClient: savedObjectMock,
appFeaturesService: createAppFeaturesServiceMock(customAppFeatures),
productFeaturesService: createProductFeaturesServiceMock(customProductFeatures),
...opts,
};
};
export const buildManifestManagerContextMock = (
opts: Partial<ManifestManagerMockOptions>,
customAppFeatures?: AppFeatureKeys
customProductFeatures?: ProductFeatureKeys
): ManifestManagerContext => {
const fullOpts = buildManifestManagerMockOptions(opts, customAppFeatures);
const fullOpts = buildManifestManagerMockOptions(opts, customProductFeatures);
return {
...fullOpts,

View file

@ -37,7 +37,7 @@ import type { EndpointArtifactClientInterface } from '../artifact_client';
import { InvalidInternalManifestError } from '../errors';
import { EndpointError } from '../../../../../common/endpoint/errors';
import type { Artifact } from '@kbn/fleet-plugin/server';
import { AppFeatureSecurityKey } from '@kbn/security-solution-features/keys';
import { ProductFeatureSecurityKey } from '@kbn/security-solution-features/keys';
import type { ExceptionListItemSchema } from '@kbn/securitysolution-io-ts-list-types/src/response/exception_list_item_schema';
import {
createFetchAllArtifactsIterableMock,
@ -775,7 +775,7 @@ describe('ManifestManager', () => {
tags: ['policy:all'],
});
const context = buildManifestManagerContextMock({}, [
AppFeatureSecurityKey.endpointArtifactManagement,
ProductFeatureSecurityKey.endpointArtifactManagement,
]);
const manifestManager = new ManifestManager(context);
@ -859,8 +859,8 @@ describe('ManifestManager', () => {
tags: ['policy:all'],
});
const context = buildManifestManagerContextMock({}, [
AppFeatureSecurityKey.endpointArtifactManagement,
AppFeatureSecurityKey.endpointResponseActions,
ProductFeatureSecurityKey.endpointArtifactManagement,
ProductFeatureSecurityKey.endpointResponseActions,
]);
const manifestManager = new ManifestManager(context);

View file

@ -14,10 +14,10 @@ import type { PackagePolicy } from '@kbn/fleet-plugin/common';
import type { Artifact, PackagePolicyClient } from '@kbn/fleet-plugin/server';
import type { ExceptionListClient } from '@kbn/lists-plugin/server';
import type { ExceptionListItemSchema } from '@kbn/securitysolution-io-ts-list-types';
import { AppFeatureKey } from '@kbn/security-solution-features/keys';
import { ProductFeatureKey } from '@kbn/security-solution-features/keys';
import { stringify } from '../../../utils/stringify';
import { QueueProcessor } from '../../../utils/queue_processor';
import type { AppFeaturesService } from '../../../../lib/app_features_service/app_features_service';
import type { ProductFeaturesService } from '../../../../lib/product_features_service/product_features_service';
import type { ExperimentalFeatures } from '../../../../../common';
import type { ManifestSchemaVersion } from '../../../../../common/endpoint/schema/common';
import {
@ -81,7 +81,7 @@ export interface ManifestManagerContext {
experimentalFeatures: ExperimentalFeatures;
packagerTaskPackagePolicyUpdateBatchSize: number;
esClient: ElasticsearchClient;
appFeaturesService: AppFeaturesService;
productFeaturesService: ProductFeaturesService;
}
const getArtifactIds = (manifest: ManifestSchema) =>
@ -103,7 +103,7 @@ export class ManifestManager {
protected cachedExceptionsListsByOs: Map<string, ExceptionListItemSchema[]>;
protected packagerTaskPackagePolicyUpdateBatchSize: number;
protected esClient: ElasticsearchClient;
protected appFeaturesService: AppFeaturesService;
protected productFeaturesService: ProductFeaturesService;
constructor(context: ManifestManagerContext) {
this.artifactClient = context.artifactClient;
@ -117,7 +117,7 @@ export class ManifestManager {
this.packagerTaskPackagePolicyUpdateBatchSize =
context.packagerTaskPackagePolicyUpdateBatchSize;
this.esClient = context.esClient;
this.appFeaturesService = context.appFeaturesService;
this.productFeaturesService = context.productFeaturesService;
}
/**
@ -151,9 +151,9 @@ export class ManifestManager {
let itemsByListId: ExceptionListItemSchema[] = [];
if (
(listId === ENDPOINT_ARTIFACT_LISTS.hostIsolationExceptions.id &&
this.appFeaturesService.isEnabled(AppFeatureKey.endpointResponseActions)) ||
this.productFeaturesService.isEnabled(ProductFeatureKey.endpointResponseActions)) ||
(listId !== ENDPOINT_ARTIFACT_LISTS.hostIsolationExceptions.id &&
this.appFeaturesService.isEnabled(AppFeatureKey.endpointArtifactManagement))
this.productFeaturesService.isEnabled(ProductFeatureKey.endpointArtifactManagement))
) {
itemsByListId = await getAllItemsFromEndpointExceptionList({
elClient,

View file

@ -32,7 +32,10 @@ import {
getPackagePolicyUpdateCallback,
} from './fleet_integration';
import type { KibanaRequest, Logger } from '@kbn/core/server';
import { ALL_APP_FEATURE_KEYS, AppFeatureSecurityKey } from '@kbn/security-solution-features/keys';
import {
ALL_PRODUCT_FEATURE_KEYS,
ProductFeatureSecurityKey,
} from '@kbn/security-solution-features/keys';
import { requestContextMock } from '../lib/detection_engine/routes/__mocks__';
import { requestContextFactoryMock } from '../request_context_factory.mock';
import type { EndpointAppContextServiceStartContract } from '../endpoint/endpoint_app_context_services';
@ -60,8 +63,8 @@ import { createMockPolicyData } from '../endpoint/services/feature_usage/mocks';
import { ALL_ENDPOINT_ARTIFACT_LIST_IDS } from '../../common/endpoint/service/artifacts/constants';
import { ENDPOINT_EVENT_FILTERS_LIST_ID } from '@kbn/securitysolution-list-constants';
import { disableProtections } from '../../common/endpoint/models/policy_config_helpers';
import type { AppFeaturesService } from '../lib/app_features_service/app_features_service';
import { createAppFeaturesServiceMock } from '../lib/app_features_service/mocks';
import type { ProductFeaturesService } from '../lib/product_features_service/product_features_service';
import { createProductFeaturesServiceMock } from '../lib/product_features_service/mocks';
import * as moment from 'moment';
import type { PostAgentPolicyCreateCallback } from '@kbn/fleet-plugin/server/types';
@ -87,7 +90,7 @@ describe('ingest_integration tests ', () => {
});
const generator = new EndpointDocGenerator();
const cloudService = cloudMock.createSetup();
let appFeaturesService: AppFeaturesService;
let productFeaturesService: ProductFeaturesService;
beforeEach(() => {
endpointAppContextMock = createMockEndpointAppContextServiceStartContract();
@ -96,7 +99,7 @@ describe('ingest_integration tests ', () => {
licenseEmitter = new Subject();
licenseService = new LicenseService();
licenseService.start(licenseEmitter);
appFeaturesService = endpointAppContextMock.appFeaturesService;
productFeaturesService = endpointAppContextMock.productFeaturesService;
jest
.spyOn(endpointAppContextMock.endpointMetadataService, 'getFleetEndpointPackagePolicy')
@ -153,7 +156,7 @@ describe('ingest_integration tests ', () => {
licenseService,
exceptionListClient,
cloudService,
appFeaturesService
productFeaturesService
);
return callback(
@ -389,15 +392,15 @@ describe('ingest_integration tests ', () => {
});
describe('agent policy update callback', () => {
it('AppFeature disabled - returns an error if higher tier features are turned on in the policy', async () => {
it('ProductFeature disabled - returns an error if higher tier features are turned on in the policy', async () => {
const logger = loggingSystemMock.create().get('ingest_integration.test');
appFeaturesService = createAppFeaturesServiceMock(
ALL_APP_FEATURE_KEYS.filter(
(key) => key !== AppFeatureSecurityKey.endpointAgentTamperProtection
productFeaturesService = createProductFeaturesServiceMock(
ALL_PRODUCT_FEATURE_KEYS.filter(
(key) => key !== ProductFeatureSecurityKey.endpointAgentTamperProtection
)
);
const callback = getAgentPolicyUpdateCallback(logger, appFeaturesService);
const callback = getAgentPolicyUpdateCallback(logger, productFeaturesService);
const policyConfig = generator.generateAgentPolicy();
policyConfig.is_protected = true;
@ -406,15 +409,15 @@ describe('ingest_integration tests ', () => {
'Agent Tamper Protection is not allowed in current environment'
);
});
it('AppFeature disabled - returns agent policy if higher tier features are turned off in the policy', async () => {
it('ProductFeature disabled - returns agent policy if higher tier features are turned off in the policy', async () => {
const logger = loggingSystemMock.create().get('ingest_integration.test');
appFeaturesService = createAppFeaturesServiceMock(
ALL_APP_FEATURE_KEYS.filter(
(key) => key !== AppFeatureSecurityKey.endpointAgentTamperProtection
productFeaturesService = createProductFeaturesServiceMock(
ALL_PRODUCT_FEATURE_KEYS.filter(
(key) => key !== ProductFeatureSecurityKey.endpointAgentTamperProtection
)
);
const callback = getAgentPolicyUpdateCallback(logger, appFeaturesService);
const callback = getAgentPolicyUpdateCallback(logger, productFeaturesService);
const policyConfig = generator.generateAgentPolicy();
@ -422,10 +425,10 @@ describe('ingest_integration tests ', () => {
expect(updatedPolicyConfig).toEqual(policyConfig);
});
it('AppFeature enabled - returns agent policy if higher tier features are turned on in the policy', async () => {
it('ProductFeature enabled - returns agent policy if higher tier features are turned on in the policy', async () => {
const logger = loggingSystemMock.create().get('ingest_integration.test');
const callback = getAgentPolicyUpdateCallback(logger, appFeaturesService);
const callback = getAgentPolicyUpdateCallback(logger, productFeaturesService);
const policyConfig = generator.generateAgentPolicy();
policyConfig.is_protected = true;
@ -434,10 +437,10 @@ describe('ingest_integration tests ', () => {
expect(updatedPolicyConfig).toEqual(policyConfig);
});
it('AppFeature enabled - returns agent policy if higher tier features are turned off in the policy', async () => {
it('ProductFeature enabled - returns agent policy if higher tier features are turned off in the policy', async () => {
const logger = loggingSystemMock.create().get('ingest_integration.test');
const callback = getAgentPolicyUpdateCallback(logger, appFeaturesService);
const callback = getAgentPolicyUpdateCallback(logger, productFeaturesService);
const policyConfig = generator.generateAgentPolicy();
const updatedPolicyConfig = await callback(policyConfig);
@ -453,17 +456,17 @@ describe('ingest_integration tests ', () => {
beforeEach(() => {
logger = loggingSystemMock.create().get('ingest_integration.test');
callback = getAgentPolicyCreateCallback(logger, appFeaturesService);
callback = getAgentPolicyCreateCallback(logger, productFeaturesService);
policyConfig = generator.generateAgentPolicy();
});
it('AppFeature disabled - returns an error if higher tier features are turned on in the policy', async () => {
appFeaturesService = createAppFeaturesServiceMock(
ALL_APP_FEATURE_KEYS.filter(
(key) => key !== AppFeatureSecurityKey.endpointAgentTamperProtection
it('ProductFeature disabled - returns an error if higher tier features are turned on in the policy', async () => {
productFeaturesService = createProductFeaturesServiceMock(
ALL_PRODUCT_FEATURE_KEYS.filter(
(key) => key !== ProductFeatureSecurityKey.endpointAgentTamperProtection
)
);
callback = getAgentPolicyCreateCallback(logger, appFeaturesService);
callback = getAgentPolicyCreateCallback(logger, productFeaturesService);
policyConfig.is_protected = true;
await expect(() => callback(policyConfig)).rejects.toThrow(
@ -471,26 +474,26 @@ describe('ingest_integration tests ', () => {
);
});
it('AppFeature disabled - returns agent policy if higher tier features are turned off in the policy', async () => {
appFeaturesService = createAppFeaturesServiceMock(
ALL_APP_FEATURE_KEYS.filter(
(key) => key !== AppFeatureSecurityKey.endpointAgentTamperProtection
it('ProductFeature disabled - returns agent policy if higher tier features are turned off in the policy', async () => {
productFeaturesService = createProductFeaturesServiceMock(
ALL_PRODUCT_FEATURE_KEYS.filter(
(key) => key !== ProductFeatureSecurityKey.endpointAgentTamperProtection
)
);
callback = getAgentPolicyCreateCallback(logger, appFeaturesService);
callback = getAgentPolicyCreateCallback(logger, productFeaturesService);
const updatedPolicyConfig = await callback(policyConfig);
expect(updatedPolicyConfig).toEqual(policyConfig);
});
it('AppFeature enabled - returns agent policy if higher tier features are turned on in the policy', async () => {
it('ProductFeature enabled - returns agent policy if higher tier features are turned on in the policy', async () => {
policyConfig.is_protected = true;
const updatedPolicyConfig = await callback(policyConfig);
expect(updatedPolicyConfig).toEqual(policyConfig);
});
it('AppFeature enabled - returns agent policy if higher tier features are turned off in the policy', async () => {
it('ProductFeature enabled - returns agent policy if higher tier features are turned off in the policy', async () => {
const updatedPolicyConfig = await callback(policyConfig);
expect(updatedPolicyConfig).toEqual(policyConfig);
@ -514,7 +517,7 @@ describe('ingest_integration tests ', () => {
endpointAppContextMock.endpointMetadataService,
cloudService,
esClient,
appFeaturesService
productFeaturesService
);
const policyConfig = generator.generatePolicyPackagePolicy();
policyConfig.inputs[0]!.config!.policy.value = mockPolicy;
@ -533,7 +536,7 @@ describe('ingest_integration tests ', () => {
endpointAppContextMock.endpointMetadataService,
cloudService,
esClient,
appFeaturesService
productFeaturesService
);
const policyConfig = generator.generatePolicyPackagePolicy();
policyConfig.inputs[0]!.config!.policy.value = mockPolicy;
@ -558,9 +561,9 @@ describe('ingest_integration tests ', () => {
const validDateYesterday = moment.utc().subtract(1, 'day');
it('should throw if endpointProtectionUpdates appFeature is disabled and user modifies global_manifest_version', () => {
appFeaturesService = createAppFeaturesServiceMock(
ALL_APP_FEATURE_KEYS.filter((key) => key !== 'endpoint_protection_updates')
it('should throw if endpointProtectionUpdates productFeature is disabled and user modifies global_manifest_version', () => {
productFeaturesService = createProductFeaturesServiceMock(
ALL_PRODUCT_FEATURE_KEYS.filter((key) => key !== 'endpoint_protection_updates')
);
const callback = getPackagePolicyUpdateCallback(
endpointAppContextMock.logger,
@ -569,7 +572,7 @@ describe('ingest_integration tests ', () => {
endpointAppContextMock.endpointMetadataService,
cloudService,
esClient,
appFeaturesService
productFeaturesService
);
const policyConfig = generator.generatePolicyPackagePolicy();
policyConfig.inputs[0]!.config!.policy.value.global_manifest_version = '2023-01-01';
@ -624,7 +627,7 @@ describe('ingest_integration tests ', () => {
endpointAppContextMock.endpointMetadataService,
cloudService,
esClient,
appFeaturesService
productFeaturesService
);
const policyConfig = generator.generatePolicyPackagePolicy();
policyConfig.inputs[0]!.config!.policy.value = {
@ -684,7 +687,7 @@ describe('ingest_integration tests ', () => {
endpointAppContextMock.endpointMetadataService,
cloudService,
esClient,
appFeaturesService
productFeaturesService
);
const policyConfig = generator.generatePolicyPackagePolicy();
policyConfig.inputs[0]!.config!.policy.value = {
@ -722,7 +725,7 @@ describe('ingest_integration tests ', () => {
endpointAppContextMock.endpointMetadataService,
cloudService,
esClient,
appFeaturesService
productFeaturesService
);
const policyConfig = generator.generatePolicyPackagePolicy();
policyConfig.inputs[0]!.config!.policy.value = mockPolicy;
@ -736,9 +739,9 @@ describe('ingest_integration tests ', () => {
expect(updatedPolicyConfig.inputs[0]!.config!.policy.value).toEqual(mockPolicy);
});
it('should turn off protections if endpointPolicyProtections appFeature is disabled', async () => {
appFeaturesService = createAppFeaturesServiceMock(
ALL_APP_FEATURE_KEYS.filter((key) => key !== 'endpoint_policy_protections')
it('should turn off protections if endpointPolicyProtections productFeature is disabled', async () => {
productFeaturesService = createProductFeaturesServiceMock(
ALL_PRODUCT_FEATURE_KEYS.filter((key) => key !== 'endpoint_policy_protections')
);
const callback = getPackagePolicyUpdateCallback(
endpointAppContextMock.logger,
@ -747,7 +750,7 @@ describe('ingest_integration tests ', () => {
endpointAppContextMock.endpointMetadataService,
cloudService,
esClient,
appFeaturesService
productFeaturesService
);
const updatedPolicy = await callback(
@ -826,7 +829,7 @@ describe('ingest_integration tests ', () => {
endpointAppContextMock.endpointMetadataService,
cloudService,
esClient,
appFeaturesService
productFeaturesService
);
const policyConfig = generator.generatePolicyPackagePolicy();
@ -863,7 +866,7 @@ describe('ingest_integration tests ', () => {
endpointAppContextMock.endpointMetadataService,
cloudService,
esClient,
appFeaturesService
productFeaturesService
);
const policyConfig = generator.generatePolicyPackagePolicy();
// values should be updated

View file

@ -24,12 +24,12 @@ import type {
} from '@kbn/fleet-plugin/common';
import type { CloudSetup } from '@kbn/cloud-plugin/server';
import type { InfoResponse } from '@elastic/elasticsearch/lib/api/types';
import { AppFeatureSecurityKey } from '@kbn/security-solution-features/keys';
import { ProductFeatureSecurityKey } from '@kbn/security-solution-features/keys';
import type {
PostAgentPolicyCreateCallback,
PostAgentPolicyUpdateCallback,
} from '@kbn/fleet-plugin/server/types';
import { validatePolicyAgainstAppFeatures } from './handlers/validate_policy_against_app_features';
import { validatePolicyAgainstProductFeatures } from './handlers/validate_policy_against_product_features';
import { validateEndpointPackagePolicy } from './handlers/validate_endpoint_package_policy';
import {
isPolicySetToEventCollectionOnly,
@ -51,7 +51,7 @@ import { notifyProtectionFeatureUsage } from './notify_protection_feature_usage'
import type { AnyPolicyCreateConfig } from './types';
import { ENDPOINT_INTEGRATION_CONFIG_KEY } from './constants';
import { createEventFilters } from './handlers/create_event_filters';
import type { AppFeaturesService } from '../lib/app_features_service/app_features_service';
import type { ProductFeaturesService } from '../lib/product_features_service/product_features_service';
import { removeProtectionUpdatesNote } from './handlers/remove_protection_updates_note';
const isEndpointPackagePolicy = <T extends { package?: { name: string } }>(
@ -90,7 +90,7 @@ export const getPackagePolicyCreateCallback = (
licenseService: LicenseService,
exceptionsClient: ExceptionListClient | undefined,
cloud: CloudSetup,
appFeatures: AppFeaturesService
productFeatures: ProductFeaturesService
): PostPackagePolicyCreateCallback => {
return async (
newPackagePolicy,
@ -111,7 +111,7 @@ export const getPackagePolicyCreateCallback = (
}
if (newPackagePolicy?.inputs) {
validatePolicyAgainstAppFeatures(newPackagePolicy.inputs, appFeatures);
validatePolicyAgainstProductFeatures(newPackagePolicy.inputs, productFeatures);
validateEndpointPackagePolicy(newPackagePolicy.inputs);
}
// Optional endpoint integration configuration
@ -163,7 +163,7 @@ export const getPackagePolicyCreateCallback = (
endpointIntegrationConfig,
cloud,
esClientInfo,
appFeatures
productFeatures
);
return {
@ -199,7 +199,7 @@ export const getPackagePolicyUpdateCallback = (
endpointMetadataService: EndpointMetadataService,
cloud: CloudSetup,
esClient: ElasticsearchClient,
appFeatures: AppFeaturesService
productFeatures: ProductFeaturesService
): PutPackagePolicyUpdateCallback => {
return async (newPackagePolicy: NewPackagePolicy): Promise<UpdatePackagePolicy> => {
if (!isEndpointPackagePolicy(newPackagePolicy)) {
@ -218,7 +218,7 @@ export const getPackagePolicyUpdateCallback = (
);
// Validate that Endpoint Security policy uses only enabled App Features
validatePolicyAgainstAppFeatures(endpointIntegrationData.inputs, appFeatures);
validatePolicyAgainstProductFeatures(endpointIntegrationData.inputs, productFeatures);
validateEndpointPackagePolicy(endpointIntegrationData.inputs);
@ -258,11 +258,11 @@ export const getPackagePolicyUpdateCallback = (
// If no Policy Protection allowed (ex. serverless)
const eventsOnlyPolicy = isPolicySetToEventCollectionOnly(newEndpointPackagePolicy);
if (
!appFeatures.isEnabled(AppFeatureSecurityKey.endpointPolicyProtections) &&
!productFeatures.isEnabled(ProductFeatureSecurityKey.endpointPolicyProtections) &&
!eventsOnlyPolicy.isOnlyCollectingEvents
) {
logger.warn(
`Endpoint integration policy [${endpointIntegrationData.id}][${endpointIntegrationData.name}] adjusted due to [endpointPolicyProtections] appFeature not being enabled. Trigger [${eventsOnlyPolicy.message}]`
`Endpoint integration policy [${endpointIntegrationData.id}][${endpointIntegrationData.name}] adjusted due to [endpointPolicyProtections] productFeature not being enabled. Trigger [${eventsOnlyPolicy.message}]`
);
endpointIntegrationData.inputs[0].config.policy.value =
@ -317,12 +317,12 @@ const throwAgentTamperProtectionUnavailableError = (
export const getAgentPolicyCreateCallback = (
logger: Logger,
appFeatures: AppFeaturesService
productFeatures: ProductFeaturesService
): PostAgentPolicyCreateCallback => {
return async (agentPolicy: NewAgentPolicy): Promise<NewAgentPolicy> => {
if (
agentPolicy.is_protected &&
!appFeatures.isEnabled(AppFeatureSecurityKey.endpointAgentTamperProtection)
!productFeatures.isEnabled(ProductFeatureSecurityKey.endpointAgentTamperProtection)
) {
throwAgentTamperProtectionUnavailableError(logger, agentPolicy.name, agentPolicy.id);
}
@ -332,12 +332,12 @@ export const getAgentPolicyCreateCallback = (
export const getAgentPolicyUpdateCallback = (
logger: Logger,
appFeatures: AppFeaturesService
productFeatures: ProductFeaturesService
): PostAgentPolicyUpdateCallback => {
return async (agentPolicy: Partial<AgentPolicy>): Promise<Partial<AgentPolicy>> => {
if (
agentPolicy.is_protected &&
!appFeatures.isEnabled(AppFeatureSecurityKey.endpointAgentTamperProtection)
!productFeatures.isEnabled(ProductFeatureSecurityKey.endpointAgentTamperProtection)
) {
throwAgentTamperProtectionUnavailableError(logger, agentPolicy.name, agentPolicy.id);
}

View file

@ -8,7 +8,7 @@ import { Subject } from 'rxjs';
import type { ILicense } from '@kbn/licensing-plugin/common/types';
import { licenseMock } from '@kbn/licensing-plugin/common/licensing.mock';
import { cloudMock } from '@kbn/cloud-plugin/server/mocks';
import { ALL_APP_FEATURE_KEYS } from '@kbn/security-solution-features/keys';
import { ALL_PRODUCT_FEATURE_KEYS } from '@kbn/security-solution-features/keys';
import { LicenseService } from '../../../common/license';
import { createDefaultPolicy } from './create_default_policy';
import { ProtectionModes } from '../../../common/endpoint/types';
@ -20,8 +20,8 @@ import type {
PolicyCreateCloudConfig,
PolicyCreateEndpointConfig,
} from '../types';
import type { AppFeaturesService } from '../../lib/app_features_service/app_features_service';
import { createAppFeaturesServiceMock } from '../../lib/app_features_service/mocks';
import type { ProductFeaturesService } from '../../lib/product_features_service/product_features_service';
import { createProductFeaturesServiceMock } from '../../lib/product_features_service/mocks';
describe('Create Default Policy tests ', () => {
const cloud = cloudMock.createSetup();
@ -31,7 +31,7 @@ describe('Create Default Policy tests ', () => {
const Gold = licenseMock.createLicense({ license: { type: 'gold', mode: 'gold', uid: '' } });
let licenseEmitter: Subject<ILicense>;
let licenseService: LicenseService;
let appFeaturesService: AppFeaturesService;
let productFeaturesService: ProductFeaturesService;
const createDefaultPolicyCallback = async (
config: AnyPolicyCreateConfig | undefined
@ -39,7 +39,7 @@ describe('Create Default Policy tests ', () => {
const esClientInfo = await elasticsearchServiceMock.createClusterClient().asInternalUser.info();
esClientInfo.cluster_name = '';
esClientInfo.cluster_uuid = '';
return createDefaultPolicy(licenseService, config, cloud, esClientInfo, appFeaturesService);
return createDefaultPolicy(licenseService, config, cloud, esClientInfo, productFeaturesService);
};
beforeEach(() => {
@ -47,7 +47,7 @@ describe('Create Default Policy tests ', () => {
licenseService = new LicenseService();
licenseService.start(licenseEmitter);
licenseEmitter.next(Platinum); // set license level to platinum
appFeaturesService = createAppFeaturesServiceMock();
productFeaturesService = createProductFeaturesServiceMock();
});
describe('When no config is set', () => {
@ -210,9 +210,9 @@ describe('Create Default Policy tests ', () => {
expect(policy).toMatchObject(defaultPolicy);
});
it('should set policy to event collection only if endpointPolicyProtections appFeature is disabled', async () => {
appFeaturesService = createAppFeaturesServiceMock(
ALL_APP_FEATURE_KEYS.filter((key) => key !== 'endpoint_policy_protections')
it('should set policy to event collection only if endpointPolicyProtections productFeature is disabled', async () => {
productFeaturesService = createProductFeaturesServiceMock(
ALL_PRODUCT_FEATURE_KEYS.filter((key) => key !== 'endpoint_policy_protections')
);
await expect(

View file

@ -7,7 +7,7 @@
import type { CloudSetup } from '@kbn/cloud-plugin/server';
import type { InfoResponse } from '@elastic/elasticsearch/lib/api/types';
import { AppFeatureSecurityKey } from '@kbn/security-solution-features/keys';
import { ProductFeatureSecurityKey } from '@kbn/security-solution-features/keys';
import {
policyFactory as policyConfigFactory,
policyFactoryWithoutPaidFeatures as policyConfigFactoryWithoutPaidFeatures,
@ -25,7 +25,7 @@ import {
disableProtections,
ensureOnlyEventCollectionIsAllowed,
} from '../../../common/endpoint/models/policy_config_helpers';
import type { AppFeaturesService } from '../../lib/app_features_service/app_features_service';
import type { ProductFeaturesService } from '../../lib/product_features_service/product_features_service';
/**
* Create the default endpoint policy based on the current license and configuration type
@ -35,7 +35,7 @@ export const createDefaultPolicy = (
config: AnyPolicyCreateConfig | undefined,
cloud: CloudSetup,
esClientInfo: InfoResponse,
appFeatures: AppFeaturesService
productFeatures: ProductFeaturesService
): PolicyConfig => {
// Pass license and cloud information to use in Policy creation
const factoryPolicy = policyConfigFactory(
@ -57,7 +57,7 @@ export const createDefaultPolicy = (
}
// If no Policy Protection allowed (ex. serverless)
if (!appFeatures.isEnabled(AppFeatureSecurityKey.endpointPolicyProtections)) {
if (!productFeatures.isEnabled(ProductFeatureSecurityKey.endpointPolicyProtections)) {
defaultPolicyPerType = ensureOnlyEventCollectionIsAllowed(defaultPolicyPerType);
}

View file

@ -1,31 +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 { NewPackagePolicyInput } from '@kbn/fleet-plugin/common';
import { AppFeatureSecurityKey } from '@kbn/security-solution-features/src/app_features_keys';
import type { AppFeaturesService } from '../../lib/app_features_service';
export const validatePolicyAgainstAppFeatures = (
inputs: NewPackagePolicyInput[],
appFeaturesService: AppFeaturesService
): void => {
const input = inputs.find((i) => i.type === 'endpoint');
if (input?.config?.policy?.value?.global_manifest_version) {
const globalManifestVersion = input.config.policy.value.global_manifest_version;
if (
globalManifestVersion !== 'latest' &&
!appFeaturesService.isEnabled(AppFeatureSecurityKey.endpointProtectionUpdates)
) {
const appFeatureError: Error & { statusCode?: number; apiPassThrough?: boolean } = new Error(
'To modify protection updates, you must add at least Endpoint Complete to your project.'
);
appFeatureError.statusCode = 403;
appFeatureError.apiPassThrough = true;
throw appFeatureError;
}
}
};

View file

@ -0,0 +1,32 @@
/*
* 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 { NewPackagePolicyInput } from '@kbn/fleet-plugin/common';
import { ProductFeatureSecurityKey } from '@kbn/security-solution-features/keys';
import type { ProductFeaturesService } from '../../lib/product_features_service';
export const validatePolicyAgainstProductFeatures = (
inputs: NewPackagePolicyInput[],
productFeaturesService: ProductFeaturesService
): void => {
const input = inputs.find((i) => i.type === 'endpoint');
if (input?.config?.policy?.value?.global_manifest_version) {
const globalManifestVersion = input.config.policy.value.global_manifest_version;
if (
globalManifestVersion !== 'latest' &&
!productFeaturesService.isEnabled(ProductFeatureSecurityKey.endpointProtectionUpdates)
) {
const productFeatureError: Error & { statusCode?: number; apiPassThrough?: boolean } =
new Error(
'To modify protection updates, you must add at least Endpoint Complete to your project.'
);
productFeatureError.statusCode = 403;
productFeatureError.apiPassThrough = true;
throw productFeatureError;
}
}
};

View file

@ -1,245 +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 { AppFeaturesService } from './app_features_service';
import { AppFeatures } from './app_features';
import type { AppFeaturesConfig, BaseKibanaFeatureConfig } from '@kbn/security-solution-features';
import { loggerMock } from '@kbn/logging-mocks';
import type { ExperimentalFeatures } from '../../../common';
import { featuresPluginMock } from '@kbn/features-plugin/server/mocks';
import type { AppFeaturesConfigurator } from './types';
import type {
AssistantSubFeatureId,
CasesSubFeatureId,
SecuritySubFeatureId,
} from '@kbn/security-solution-features/keys';
import { AppFeatureKey } from '@kbn/security-solution-features/keys';
import { httpServiceMock } from '@kbn/core-http-server-mocks';
import type {
KibanaRequest,
LifecycleResponseFactory,
OnPostAuthHandler,
} from '@kbn/core-http-server';
jest.mock('./app_features');
const MockedAppFeatures = AppFeatures as unknown as jest.MockedClass<typeof AppFeatures>;
const appFeature = {
subFeaturesMap: new Map(),
baseKibanaFeature: {} as BaseKibanaFeatureConfig,
baseKibanaSubFeatureIds: [],
};
const mockGetFeature = jest.fn().mockReturnValue(appFeature);
jest.mock('@kbn/security-solution-features/app_features', () => ({
getAssistantFeature: () => mockGetFeature(),
getCasesFeature: () => mockGetFeature(),
getSecurityFeature: () => mockGetFeature(),
}));
describe('AppFeaturesService', () => {
beforeEach(() => {
jest.clearAllMocks();
});
it('should create AppFeatureService instance', () => {
const experimentalFeatures = {} as ExperimentalFeatures;
new AppFeaturesService(loggerMock.create(), experimentalFeatures);
expect(mockGetFeature).toHaveBeenCalledTimes(3);
expect(MockedAppFeatures).toHaveBeenCalledTimes(3);
});
it('should init all AppFeatures when initialized', () => {
const experimentalFeatures = {} as ExperimentalFeatures;
const appFeaturesService = new AppFeaturesService(loggerMock.create(), experimentalFeatures);
const featuresSetup = featuresPluginMock.createSetup();
appFeaturesService.init(featuresSetup);
expect(MockedAppFeatures.mock.instances[0].init).toHaveBeenCalledWith(featuresSetup);
expect(MockedAppFeatures.mock.instances[1].init).toHaveBeenCalledWith(featuresSetup);
expect(MockedAppFeatures.mock.instances[2].init).toHaveBeenCalledWith(featuresSetup);
});
it('should configure AppFeatures', () => {
const experimentalFeatures = {} as ExperimentalFeatures;
const appFeaturesService = new AppFeaturesService(loggerMock.create(), experimentalFeatures);
const featuresSetup = featuresPluginMock.createSetup();
appFeaturesService.init(featuresSetup);
const mockSecurityConfig = new Map() as AppFeaturesConfig<SecuritySubFeatureId>;
const mockCasesConfig = new Map() as AppFeaturesConfig<CasesSubFeatureId>;
const mockAssistantConfig = new Map() as AppFeaturesConfig<AssistantSubFeatureId>;
const configurator: AppFeaturesConfigurator = {
security: jest.fn(() => mockSecurityConfig),
cases: jest.fn(() => mockCasesConfig),
securityAssistant: jest.fn(() => mockAssistantConfig),
};
appFeaturesService.setAppFeaturesConfigurator(configurator);
expect(configurator.security).toHaveBeenCalled();
expect(configurator.cases).toHaveBeenCalled();
expect(configurator.securityAssistant).toHaveBeenCalled();
expect(MockedAppFeatures.mock.instances[0].setConfig).toHaveBeenCalledWith(mockSecurityConfig);
expect(MockedAppFeatures.mock.instances[1].setConfig).toHaveBeenCalledWith(mockCasesConfig);
expect(MockedAppFeatures.mock.instances[2].setConfig).toHaveBeenCalledWith(mockAssistantConfig);
});
it('should return isEnabled for enabled features', () => {
const experimentalFeatures = {} as ExperimentalFeatures;
const appFeaturesService = new AppFeaturesService(loggerMock.create(), experimentalFeatures);
const featuresSetup = featuresPluginMock.createSetup();
appFeaturesService.init(featuresSetup);
const mockSecurityConfig = new Map([
[AppFeatureKey.advancedInsights, {}],
[AppFeatureKey.endpointExceptions, {}],
]) as AppFeaturesConfig<SecuritySubFeatureId>;
const mockCasesConfig = new Map([
[AppFeatureKey.casesConnectors, {}],
]) as AppFeaturesConfig<CasesSubFeatureId>;
const mockAssistantConfig = new Map([
[AppFeatureKey.assistant, {}],
]) as AppFeaturesConfig<AssistantSubFeatureId>;
const configurator: AppFeaturesConfigurator = {
security: jest.fn(() => mockSecurityConfig),
cases: jest.fn(() => mockCasesConfig),
securityAssistant: jest.fn(() => mockAssistantConfig),
};
appFeaturesService.setAppFeaturesConfigurator(configurator);
expect(appFeaturesService.isEnabled(AppFeatureKey.advancedInsights)).toEqual(true);
expect(appFeaturesService.isEnabled(AppFeatureKey.endpointExceptions)).toEqual(true);
expect(appFeaturesService.isEnabled(AppFeatureKey.casesConnectors)).toEqual(true);
expect(appFeaturesService.isEnabled(AppFeatureKey.assistant)).toEqual(true);
expect(appFeaturesService.isEnabled(AppFeatureKey.externalRuleActions)).toEqual(false);
});
it('should call isApiPrivilegeEnabled for api actions', () => {
const experimentalFeatures = {} as ExperimentalFeatures;
const appFeaturesService = new AppFeaturesService(loggerMock.create(), experimentalFeatures);
appFeaturesService.isApiPrivilegeEnabled('writeEndpointExceptions');
expect(MockedAppFeatures.mock.instances[0].isActionRegistered).toHaveBeenCalledWith(
'api:securitySolution-writeEndpointExceptions'
);
expect(MockedAppFeatures.mock.instances[1].isActionRegistered).toHaveBeenCalledWith(
'api:securitySolution-writeEndpointExceptions'
);
expect(MockedAppFeatures.mock.instances[2].isActionRegistered).toHaveBeenCalledWith(
'api:securitySolution-writeEndpointExceptions'
);
});
describe('registerApiAccessControl', () => {
const mockHttpSetup = httpServiceMock.createSetupContract();
let lastRegisteredFn: OnPostAuthHandler;
mockHttpSetup.registerOnPostAuth.mockImplementation((fn) => {
lastRegisteredFn = fn;
});
const getReq = (tags: string[] = []) =>
({
route: { options: { tags } },
url: { pathname: '', search: '' },
} as unknown as KibanaRequest);
const res = { notFound: jest.fn() } as unknown as LifecycleResponseFactory;
const toolkit = { next: jest.fn() };
beforeEach(() => {
jest.clearAllMocks();
});
it('should register api authorization http route interceptor', () => {
const experimentalFeatures = {} as ExperimentalFeatures;
const appFeaturesService = new AppFeaturesService(loggerMock.create(), experimentalFeatures);
appFeaturesService.registerApiAccessControl(mockHttpSetup);
expect(mockHttpSetup.registerOnPostAuth).toHaveBeenCalledTimes(1);
});
it('should authorize when no tag matches', () => {
const experimentalFeatures = {} as ExperimentalFeatures;
const appFeaturesService = new AppFeaturesService(loggerMock.create(), experimentalFeatures);
appFeaturesService.registerApiAccessControl(mockHttpSetup);
lastRegisteredFn(getReq(['access:something', 'access:securitySolution']), res, toolkit);
expect(MockedAppFeatures.mock.instances[0].isActionRegistered).not.toHaveBeenCalled();
expect(res.notFound).not.toHaveBeenCalled();
expect(toolkit.next).toHaveBeenCalledTimes(1);
});
it('should check when tag matches and return not found when not action registered', () => {
const experimentalFeatures = {} as ExperimentalFeatures;
const appFeaturesService = new AppFeaturesService(loggerMock.create(), experimentalFeatures);
appFeaturesService.registerApiAccessControl(mockHttpSetup);
(MockedAppFeatures.mock.instances[0].isActionRegistered as jest.Mock).mockReturnValueOnce(
false
);
lastRegisteredFn(getReq(['access:securitySolution-foo']), res, toolkit);
expect(MockedAppFeatures.mock.instances[0].isActionRegistered).toHaveBeenCalledWith(
'api:securitySolution-foo'
);
expect(res.notFound).toHaveBeenCalledTimes(1);
expect(toolkit.next).not.toHaveBeenCalled();
});
it('should check when tag matches and continue when action registered', () => {
const experimentalFeatures = {} as ExperimentalFeatures;
const appFeaturesService = new AppFeaturesService(loggerMock.create(), experimentalFeatures);
appFeaturesService.registerApiAccessControl(mockHttpSetup);
(MockedAppFeatures.mock.instances[0].isActionRegistered as jest.Mock).mockReturnValueOnce(
true
);
lastRegisteredFn(getReq(['access:securitySolution-foo']), res, toolkit);
expect(MockedAppFeatures.mock.instances[0].isActionRegistered).toHaveBeenCalledWith(
'api:securitySolution-foo'
);
expect(res.notFound).not.toHaveBeenCalled();
expect(toolkit.next).toHaveBeenCalledTimes(1);
});
it('should check when appFeature tag when it matches and return not found when not enabled', () => {
const experimentalFeatures = {} as ExperimentalFeatures;
const appFeaturesService = new AppFeaturesService(loggerMock.create(), experimentalFeatures);
appFeaturesService.registerApiAccessControl(mockHttpSetup);
appFeaturesService.isEnabled = jest.fn().mockReturnValueOnce(false);
lastRegisteredFn(getReq(['securitySolutionAppFeature:foo']), res, toolkit);
expect(appFeaturesService.isEnabled).toHaveBeenCalledWith('foo');
expect(res.notFound).toHaveBeenCalledTimes(1);
expect(toolkit.next).not.toHaveBeenCalled();
});
it('should check when appFeature tag when it matches and continue when enabled', () => {
const experimentalFeatures = {} as ExperimentalFeatures;
const appFeaturesService = new AppFeaturesService(loggerMock.create(), experimentalFeatures);
appFeaturesService.registerApiAccessControl(mockHttpSetup);
appFeaturesService.isEnabled = jest.fn().mockReturnValueOnce(true);
lastRegisteredFn(getReq(['securitySolutionAppFeature:foo']), res, toolkit);
expect(appFeaturesService.isEnabled).toHaveBeenCalledWith('foo');
expect(res.notFound).not.toHaveBeenCalled();
expect(toolkit.next).toHaveBeenCalledTimes(1);
});
});
});

View file

@ -4,4 +4,4 @@
* 2.0; you may not use this file except in compliance with the Elastic License
* 2.0.
*/
export { AppFeaturesService } from './app_features_service';
export { ProductFeaturesService } from './product_features_service';

View file

@ -10,12 +10,12 @@ import type { PluginSetupContract as FeaturesPluginSetup } from '@kbn/features-p
import { loggingSystemMock } from '@kbn/core-logging-server-mocks';
import { featuresPluginMock } from '@kbn/features-plugin/server/mocks';
import type { AppFeatureKeys } from '@kbn/security-solution-features';
import { ALL_APP_FEATURE_KEYS } from '@kbn/security-solution-features/keys';
import type { ProductFeatureKeys } from '@kbn/security-solution-features';
import { ALL_PRODUCT_FEATURE_KEYS } from '@kbn/security-solution-features/keys';
import { allowedExperimentalValues, type ExperimentalFeatures } from '../../../common';
import { AppFeaturesService } from './app_features_service';
import { ProductFeaturesService } from './product_features_service';
jest.mock('@kbn/security-solution-features/app_features', () => ({
jest.mock('@kbn/security-solution-features/product_features', () => ({
getSecurityFeature: jest.fn(() => ({
baseKibanaFeature: {},
baseKibanaSubFeatureIds: [],
@ -33,19 +33,19 @@ jest.mock('@kbn/security-solution-features/app_features', () => ({
})),
}));
export const createAppFeaturesServiceMock = (
export const createProductFeaturesServiceMock = (
/** What features keys should be enabled. Default is all */
enabledFeatureKeys: AppFeatureKeys = [...ALL_APP_FEATURE_KEYS],
enabledFeatureKeys: ProductFeatureKeys = [...ALL_PRODUCT_FEATURE_KEYS],
experimentalFeatures: ExperimentalFeatures = { ...allowedExperimentalValues },
featuresPluginSetupContract: FeaturesPluginSetup = featuresPluginMock.createSetup(),
logger: Logger = loggingSystemMock.create().get('appFeatureMock')
logger: Logger = loggingSystemMock.create().get('productFeatureMock')
) => {
const appFeaturesService = new AppFeaturesService(logger, experimentalFeatures);
const productFeaturesService = new ProductFeaturesService(logger, experimentalFeatures);
appFeaturesService.init(featuresPluginSetupContract);
productFeaturesService.init(featuresPluginSetupContract);
if (enabledFeatureKeys) {
appFeaturesService.setAppFeaturesConfigurator({
productFeaturesService.setProductFeaturesConfigurator({
security: jest.fn().mockReturnValue(
new Map(
enabledFeatureKeys.map((key) => [
@ -106,5 +106,5 @@ export const createAppFeaturesServiceMock = (
});
}
return appFeaturesService;
return productFeaturesService;
};

View file

@ -7,10 +7,10 @@
import type { PluginSetupContract } from '@kbn/features-plugin/server';
import { loggingSystemMock } from '@kbn/core-logging-server-mocks';
import { AppFeatures } from './app_features';
import { ProductFeatures } from './product_features';
import type {
AppFeatureKeyType,
AppFeatureKibanaConfig,
ProductFeatureKeyType,
ProductFeatureKibanaConfig,
BaseKibanaFeatureConfig,
} from '@kbn/security-solution-features';
import type { SubFeatureConfig } from '@kbn/features-plugin/common';
@ -102,11 +102,11 @@ const subFeaturesMap = new Map([
]);
// app features configs
const testSubFeaturePrivilegeConfig: AppFeatureKibanaConfig = {
const testSubFeaturePrivilegeConfig: ProductFeatureKibanaConfig = {
subFeatureIds: [SUB_FEATURE.name],
};
const testFeaturePrivilegeConfig: AppFeatureKibanaConfig = {
const testFeaturePrivilegeConfig: ProductFeatureKibanaConfig = {
privileges: {
all: {
ui: ['test-action'],
@ -144,7 +144,7 @@ const expectedBaseWithTestConfigPrivileges = {
},
};
describe('AppFeatures', () => {
describe('ProductFeatures', () => {
describe('setConfig', () => {
it('should register base kibana features', () => {
const featuresSetup = {
@ -152,14 +152,14 @@ describe('AppFeatures', () => {
getKibanaFeatures: jest.fn(),
} as unknown as PluginSetupContract;
const appFeatures = new AppFeatures(
const productFeatures = new ProductFeatures(
loggingSystemMock.create().get('mock'),
subFeaturesMap,
baseKibanaFeature,
[]
);
appFeatures.init(featuresSetup);
appFeatures.setConfig(new Map());
productFeatures.init(featuresSetup);
productFeatures.setConfig(new Map());
expect(featuresSetup.registerKibanaFeature).toHaveBeenCalledWith({
...baseKibanaFeature,
@ -173,15 +173,15 @@ describe('AppFeatures', () => {
getKibanaFeatures: jest.fn(),
} as unknown as PluginSetupContract;
const appFeatures = new AppFeatures(
const productFeatures = new ProductFeatures(
loggingSystemMock.create().get('mock'),
subFeaturesMap,
baseKibanaFeature,
[]
);
appFeatures.init(featuresSetup);
appFeatures.setConfig(
new Map([['test-feature' as AppFeatureKeyType, testFeaturePrivilegeConfig]])
productFeatures.init(featuresSetup);
productFeatures.setConfig(
new Map([['test-feature' as ProductFeatureKeyType, testFeaturePrivilegeConfig]])
);
expect(featuresSetup.registerKibanaFeature).toHaveBeenCalledWith({
@ -197,17 +197,17 @@ describe('AppFeatures', () => {
getKibanaFeatures: jest.fn(),
} as unknown as PluginSetupContract;
const appFeatures = new AppFeatures(
const productFeatures = new ProductFeatures(
loggingSystemMock.create().get('mock'),
subFeaturesMap,
baseKibanaFeature,
[]
);
appFeatures.init(featuresSetup);
appFeatures.setConfig(
productFeatures.init(featuresSetup);
productFeatures.setConfig(
new Map([
['test-feature' as AppFeatureKeyType, testFeaturePrivilegeConfig],
['test-sub-feature' as AppFeatureKeyType, testSubFeaturePrivilegeConfig],
['test-feature' as ProductFeatureKeyType, testFeaturePrivilegeConfig],
['test-sub-feature' as ProductFeatureKeyType, testSubFeaturePrivilegeConfig],
])
);
@ -224,17 +224,17 @@ describe('AppFeatures', () => {
getKibanaFeatures: jest.fn(),
} as unknown as PluginSetupContract;
const appFeatures = new AppFeatures(
const productFeatures = new ProductFeatures(
loggingSystemMock.create().get('mock'),
subFeaturesMap,
baseKibanaFeature,
[SUB_FEATURE_2.name]
);
appFeatures.init(featuresSetup);
appFeatures.setConfig(
productFeatures.init(featuresSetup);
productFeatures.setConfig(
new Map([
['test-feature' as AppFeatureKeyType, testFeaturePrivilegeConfig],
['test-sub-feature' as AppFeatureKeyType, testSubFeaturePrivilegeConfig],
['test-feature' as ProductFeatureKeyType, testFeaturePrivilegeConfig],
['test-sub-feature' as ProductFeatureKeyType, testSubFeaturePrivilegeConfig],
])
);
@ -252,25 +252,25 @@ describe('AppFeatures', () => {
registerKibanaFeature: jest.fn(),
} as unknown as PluginSetupContract;
const appFeatures = new AppFeatures(
const productFeatures = new ProductFeatures(
loggingSystemMock.create().get('mock'),
subFeaturesMap,
baseKibanaFeature,
[]
);
appFeatures.init(featuresSetup);
appFeatures.setConfig(new Map());
productFeatures.init(featuresSetup);
productFeatures.setConfig(new Map());
expect(appFeatures.isActionRegistered('api:api-read')).toEqual(true);
expect(appFeatures.isActionRegistered('ui:read')).toEqual(true);
expect(appFeatures.isActionRegistered('api:api-write')).toEqual(true);
expect(appFeatures.isActionRegistered('ui:write')).toEqual(true);
expect(appFeatures.isActionRegistered('api:test-action')).toEqual(false);
expect(appFeatures.isActionRegistered('ui:test-action')).toEqual(false);
expect(appFeatures.isActionRegistered('api:subFeature1-action')).toEqual(false);
expect(appFeatures.isActionRegistered('ui:subFeature1-action')).toEqual(false);
expect(appFeatures.isActionRegistered('api:subFeature2-action')).toEqual(false);
expect(appFeatures.isActionRegistered('ui:subFeature2-action')).toEqual(false);
expect(productFeatures.isActionRegistered('api:api-read')).toEqual(true);
expect(productFeatures.isActionRegistered('ui:read')).toEqual(true);
expect(productFeatures.isActionRegistered('api:api-write')).toEqual(true);
expect(productFeatures.isActionRegistered('ui:write')).toEqual(true);
expect(productFeatures.isActionRegistered('api:test-action')).toEqual(false);
expect(productFeatures.isActionRegistered('ui:test-action')).toEqual(false);
expect(productFeatures.isActionRegistered('api:subFeature1-action')).toEqual(false);
expect(productFeatures.isActionRegistered('ui:subFeature1-action')).toEqual(false);
expect(productFeatures.isActionRegistered('api:subFeature2-action')).toEqual(false);
expect(productFeatures.isActionRegistered('ui:subFeature2-action')).toEqual(false);
});
it('should register config privilege actions', () => {
@ -278,27 +278,27 @@ describe('AppFeatures', () => {
registerKibanaFeature: jest.fn(),
} as unknown as PluginSetupContract;
const appFeatures = new AppFeatures(
const productFeatures = new ProductFeatures(
loggingSystemMock.create().get('mock'),
subFeaturesMap,
baseKibanaFeature,
[]
);
appFeatures.init(featuresSetup);
appFeatures.setConfig(
new Map([['test-feature' as AppFeatureKeyType, testFeaturePrivilegeConfig]])
productFeatures.init(featuresSetup);
productFeatures.setConfig(
new Map([['test-feature' as ProductFeatureKeyType, testFeaturePrivilegeConfig]])
);
expect(appFeatures.isActionRegistered('api:api-read')).toEqual(true);
expect(appFeatures.isActionRegistered('ui:read')).toEqual(true);
expect(appFeatures.isActionRegistered('api:api-write')).toEqual(true);
expect(appFeatures.isActionRegistered('ui:write')).toEqual(true);
expect(appFeatures.isActionRegistered('api:test-action')).toEqual(true);
expect(appFeatures.isActionRegistered('ui:test-action')).toEqual(true);
expect(appFeatures.isActionRegistered('api:subFeature1-action')).toEqual(false);
expect(appFeatures.isActionRegistered('ui:subFeature1-action')).toEqual(false);
expect(appFeatures.isActionRegistered('api:subFeature2-action')).toEqual(false);
expect(appFeatures.isActionRegistered('ui:subFeature2-action')).toEqual(false);
expect(productFeatures.isActionRegistered('api:api-read')).toEqual(true);
expect(productFeatures.isActionRegistered('ui:read')).toEqual(true);
expect(productFeatures.isActionRegistered('api:api-write')).toEqual(true);
expect(productFeatures.isActionRegistered('ui:write')).toEqual(true);
expect(productFeatures.isActionRegistered('api:test-action')).toEqual(true);
expect(productFeatures.isActionRegistered('ui:test-action')).toEqual(true);
expect(productFeatures.isActionRegistered('api:subFeature1-action')).toEqual(false);
expect(productFeatures.isActionRegistered('ui:subFeature1-action')).toEqual(false);
expect(productFeatures.isActionRegistered('api:subFeature2-action')).toEqual(false);
expect(productFeatures.isActionRegistered('ui:subFeature2-action')).toEqual(false);
});
it('should register config sub-feature privilege actions', () => {
@ -306,30 +306,30 @@ describe('AppFeatures', () => {
registerKibanaFeature: jest.fn(),
} as unknown as PluginSetupContract;
const appFeatures = new AppFeatures(
const productFeatures = new ProductFeatures(
loggingSystemMock.create().get('mock'),
subFeaturesMap,
baseKibanaFeature,
[]
);
appFeatures.init(featuresSetup);
appFeatures.setConfig(
productFeatures.init(featuresSetup);
productFeatures.setConfig(
new Map([
['test-feature' as AppFeatureKeyType, testFeaturePrivilegeConfig],
['test-sub-feature' as AppFeatureKeyType, testSubFeaturePrivilegeConfig],
['test-feature' as ProductFeatureKeyType, testFeaturePrivilegeConfig],
['test-sub-feature' as ProductFeatureKeyType, testSubFeaturePrivilegeConfig],
])
);
expect(appFeatures.isActionRegistered('api:api-read')).toEqual(true);
expect(appFeatures.isActionRegistered('ui:read')).toEqual(true);
expect(appFeatures.isActionRegistered('api:api-write')).toEqual(true);
expect(appFeatures.isActionRegistered('ui:write')).toEqual(true);
expect(appFeatures.isActionRegistered('api:test-action')).toEqual(true);
expect(appFeatures.isActionRegistered('ui:test-action')).toEqual(true);
expect(appFeatures.isActionRegistered('api:subFeature1-action')).toEqual(true);
expect(appFeatures.isActionRegistered('ui:subFeature1-action')).toEqual(true);
expect(appFeatures.isActionRegistered('api:subFeature2-action')).toEqual(false);
expect(appFeatures.isActionRegistered('ui:subFeature2-action')).toEqual(false);
expect(productFeatures.isActionRegistered('api:api-read')).toEqual(true);
expect(productFeatures.isActionRegistered('ui:read')).toEqual(true);
expect(productFeatures.isActionRegistered('api:api-write')).toEqual(true);
expect(productFeatures.isActionRegistered('ui:write')).toEqual(true);
expect(productFeatures.isActionRegistered('api:test-action')).toEqual(true);
expect(productFeatures.isActionRegistered('ui:test-action')).toEqual(true);
expect(productFeatures.isActionRegistered('api:subFeature1-action')).toEqual(true);
expect(productFeatures.isActionRegistered('ui:subFeature1-action')).toEqual(true);
expect(productFeatures.isActionRegistered('api:subFeature2-action')).toEqual(false);
expect(productFeatures.isActionRegistered('ui:subFeature2-action')).toEqual(false);
});
it('should register default and config sub-feature privilege actions', () => {
@ -337,30 +337,30 @@ describe('AppFeatures', () => {
registerKibanaFeature: jest.fn(),
} as unknown as PluginSetupContract;
const appFeatures = new AppFeatures(
const productFeatures = new ProductFeatures(
loggingSystemMock.create().get('mock'),
subFeaturesMap,
baseKibanaFeature,
[SUB_FEATURE_2.name]
);
appFeatures.init(featuresSetup);
appFeatures.setConfig(
productFeatures.init(featuresSetup);
productFeatures.setConfig(
new Map([
['test-feature' as AppFeatureKeyType, testFeaturePrivilegeConfig],
['test-sub-feature' as AppFeatureKeyType, testSubFeaturePrivilegeConfig],
['test-feature' as ProductFeatureKeyType, testFeaturePrivilegeConfig],
['test-sub-feature' as ProductFeatureKeyType, testSubFeaturePrivilegeConfig],
])
);
expect(appFeatures.isActionRegistered('api:api-read')).toEqual(true);
expect(appFeatures.isActionRegistered('ui:read')).toEqual(true);
expect(appFeatures.isActionRegistered('api:api-write')).toEqual(true);
expect(appFeatures.isActionRegistered('ui:write')).toEqual(true);
expect(appFeatures.isActionRegistered('api:test-action')).toEqual(true);
expect(appFeatures.isActionRegistered('ui:test-action')).toEqual(true);
expect(appFeatures.isActionRegistered('api:subFeature1-action')).toEqual(true);
expect(appFeatures.isActionRegistered('ui:subFeature1-action')).toEqual(true);
expect(appFeatures.isActionRegistered('api:subFeature2-action')).toEqual(true);
expect(appFeatures.isActionRegistered('ui:subFeature2-action')).toEqual(true);
expect(productFeatures.isActionRegistered('api:api-read')).toEqual(true);
expect(productFeatures.isActionRegistered('ui:read')).toEqual(true);
expect(productFeatures.isActionRegistered('api:api-write')).toEqual(true);
expect(productFeatures.isActionRegistered('ui:write')).toEqual(true);
expect(productFeatures.isActionRegistered('api:test-action')).toEqual(true);
expect(productFeatures.isActionRegistered('ui:test-action')).toEqual(true);
expect(productFeatures.isActionRegistered('api:subFeature1-action')).toEqual(true);
expect(productFeatures.isActionRegistered('ui:subFeature1-action')).toEqual(true);
expect(productFeatures.isActionRegistered('api:subFeature2-action')).toEqual(true);
expect(productFeatures.isActionRegistered('ui:subFeature2-action')).toEqual(true);
});
});
});

View file

@ -12,14 +12,14 @@ import type {
PluginSetupContract as FeaturesPluginSetup,
} from '@kbn/features-plugin/server';
import type {
AppFeaturesConfig,
ProductFeaturesConfig,
AppSubFeaturesMap,
BaseKibanaFeatureConfig,
} from '@kbn/security-solution-features';
import { AppFeaturesConfigMerger } from './app_features_config_merger';
import { ProductFeaturesConfigMerger } from './product_features_config_merger';
export class AppFeatures<T extends string = string, S extends string = string> {
private featureConfigMerger: AppFeaturesConfigMerger;
export class ProductFeatures<T extends string = string, S extends string = string> {
private featureConfigMerger: ProductFeaturesConfigMerger;
private featuresSetup?: FeaturesPluginSetup;
private readonly registeredActions: Set<string>;
@ -29,7 +29,7 @@ export class AppFeatures<T extends string = string, S extends string = string> {
private readonly baseKibanaFeature: BaseKibanaFeatureConfig,
private readonly baseKibanaSubFeatureIds: T[]
) {
this.featureConfigMerger = new AppFeaturesConfigMerger(this.logger, subFeaturesMap);
this.featureConfigMerger = new ProductFeaturesConfigMerger(this.logger, subFeaturesMap);
this.registeredActions = new Set();
}
@ -37,22 +37,22 @@ export class AppFeatures<T extends string = string, S extends string = string> {
this.featuresSetup = featuresSetup;
}
public setConfig(appFeatureConfig: AppFeaturesConfig<S>) {
public setConfig(productFeatureConfig: ProductFeaturesConfig<S>) {
if (this.featuresSetup == null) {
throw new Error(
'Cannot sync kibana features as featuresSetup is not present. Did you call init?'
);
}
const completeAppFeatureConfig = this.featureConfigMerger.mergeAppFeatureConfigs(
const completeProductFeatureConfig = this.featureConfigMerger.mergeProductFeatureConfigs(
this.baseKibanaFeature,
this.baseKibanaSubFeatureIds,
Array.from(appFeatureConfig.values())
Array.from(productFeatureConfig.values())
);
this.logger.debug(JSON.stringify(completeAppFeatureConfig));
this.featuresSetup.registerKibanaFeature(completeAppFeatureConfig);
this.addRegisteredActions(completeAppFeatureConfig);
this.logger.debug(JSON.stringify(completeProductFeatureConfig));
this.featuresSetup.registerKibanaFeature(completeProductFeatureConfig);
this.addRegisteredActions(completeProductFeatureConfig);
}
private addRegisteredActions(config: KibanaFeatureConfig) {

View file

@ -6,9 +6,9 @@
*/
import { loggingSystemMock } from '@kbn/core/server/mocks';
import { AppFeaturesConfigMerger } from './app_features_config_merger';
import { ProductFeaturesConfigMerger } from './product_features_config_merger';
import type { Logger } from '@kbn/core/server';
import type { AppFeatureKibanaConfig } from '@kbn/security-solution-features';
import type { ProductFeatureKibanaConfig } from '@kbn/security-solution-features';
import type { KibanaFeatureConfig, SubFeatureConfig } from '@kbn/features-plugin/common';
const category = {
@ -138,8 +138,8 @@ export const subFeaturesMap = Object.freeze(
const mockLogger = loggingSystemMock.create().get() as jest.Mocked<Logger>;
describe('AppFeaturesConfigMerger', () => {
const merger = new AppFeaturesConfigMerger(mockLogger, subFeaturesMap);
describe('ProductFeaturesConfigMerger', () => {
const merger = new ProductFeaturesConfigMerger(mockLogger, subFeaturesMap);
beforeEach(() => {
jest.clearAllMocks();
@ -147,7 +147,7 @@ describe('AppFeaturesConfigMerger', () => {
describe('main privileges', () => {
it('should merge enabled main privileges into base config', () => {
const enabledAppFeaturesConfigs: AppFeatureKibanaConfig[] = [
const enabledProductFeaturesConfigs: ProductFeatureKibanaConfig[] = [
{
privileges: {
all: {
@ -179,10 +179,10 @@ describe('AppFeaturesConfigMerger', () => {
},
];
const merged = merger.mergeAppFeatureConfigs(
const merged = merger.mergeProductFeatureConfigs(
baseKibanaFeature,
[],
enabledAppFeaturesConfigs
enabledProductFeaturesConfigs
);
expect(merged).toEqual({
@ -227,21 +227,25 @@ describe('AppFeaturesConfigMerger', () => {
it('adds base subFeatures in the correct order', () => {
const baseKibanaSubFeatureIds = ['subFeature2', 'subFeature3', 'subFeature1'];
const merged = merger.mergeAppFeatureConfigs(baseKibanaFeature, baseKibanaSubFeatureIds, []);
const merged = merger.mergeProductFeatureConfigs(
baseKibanaFeature,
baseKibanaSubFeatureIds,
[]
);
expect(merged.subFeatures).toEqual([subFeature1, subFeature2, subFeature3]);
});
it('should merge enabled subFeatures into base config in the correct order', () => {
const enabledAppFeaturesConfigs: AppFeatureKibanaConfig[] = [
const enabledProductFeaturesConfigs: ProductFeatureKibanaConfig[] = [
{
subFeatureIds: ['subFeature3', 'subFeature1'],
},
];
const merged = merger.mergeAppFeatureConfigs(
const merged = merger.mergeProductFeatureConfigs(
baseKibanaFeature,
['subFeature2'],
enabledAppFeaturesConfigs
enabledProductFeaturesConfigs
);
expect(merged).toEqual({
@ -253,7 +257,7 @@ describe('AppFeaturesConfigMerger', () => {
describe('subFeaturePrivileges', () => {
it('should merge enabled subFeatures with extra subFeaturePrivileges into base config in the correct order', () => {
const enabledAppFeaturesConfigs: AppFeatureKibanaConfig[] = [
const enabledProductFeaturesConfigs: ProductFeatureKibanaConfig[] = [
{
subFeaturesPrivileges: [
{
@ -273,10 +277,10 @@ describe('AppFeaturesConfigMerger', () => {
},
];
const merged = merger.mergeAppFeatureConfigs(
const merged = merger.mergeProductFeatureConfigs(
baseKibanaFeature,
['subFeature2'],
enabledAppFeaturesConfigs
enabledProductFeaturesConfigs
);
expect(merged).toEqual({
...baseKibanaFeature,
@ -323,7 +327,7 @@ describe('AppFeaturesConfigMerger', () => {
it('should warn if there are subFeaturesPrivileges for a subFeature id that is not found', () => {
const subFeaturesPrivilegesId = 'sub-feature-1_all';
const enabledAppFeaturesConfigs: AppFeatureKibanaConfig[] = [
const enabledProductFeaturesConfigs: ProductFeatureKibanaConfig[] = [
{
subFeaturesPrivileges: [
{
@ -335,10 +339,10 @@ describe('AppFeaturesConfigMerger', () => {
},
];
const merged = merger.mergeAppFeatureConfigs(
const merged = merger.mergeProductFeatureConfigs(
baseKibanaFeature,
['subFeature2', 'subFeature3'],
enabledAppFeaturesConfigs
enabledProductFeaturesConfigs
);
expect(mockLogger.warn).toHaveBeenCalledWith(
@ -349,7 +353,7 @@ describe('AppFeaturesConfigMerger', () => {
});
it('should merge everything at the same time', () => {
const enabledAppFeaturesConfigs: AppFeatureKibanaConfig[] = [
const enabledProductFeaturesConfigs: ProductFeatureKibanaConfig[] = [
{
privileges: {
all: {
@ -395,10 +399,10 @@ describe('AppFeaturesConfigMerger', () => {
];
const baseKibanaSubFeatureIds = ['subFeature2'];
const merged = merger.mergeAppFeatureConfigs(
const merged = merger.mergeProductFeatureConfigs(
baseKibanaFeature,
baseKibanaSubFeatureIds,
enabledAppFeaturesConfigs
enabledProductFeaturesConfigs
);
expect(merged).toEqual({

View file

@ -9,28 +9,28 @@ import { cloneDeep, isArray, mergeWith, uniq } from 'lodash';
import type { Logger } from '@kbn/core/server';
import type { KibanaFeatureConfig, SubFeatureConfig } from '@kbn/features-plugin/common';
import type {
AppFeatureKibanaConfig,
ProductFeatureKibanaConfig,
BaseKibanaFeatureConfig,
SubFeaturesPrivileges,
} from '@kbn/security-solution-features';
export class AppFeaturesConfigMerger<T extends string = string> {
export class ProductFeaturesConfigMerger<T extends string = string> {
constructor(
private readonly logger: Logger,
private readonly subFeaturesMap: Map<T, SubFeatureConfig>
) {}
/**
* Merges `appFeaturesConfigs` into `kibanaFeatureConfig`.
* Merges `productFeaturesConfigs` into `kibanaFeatureConfig`.
* @param kibanaFeatureConfig the KibanaFeatureConfig to merge into
* @param kibanaSubFeatureIds
* @param appFeaturesConfigs the AppFeatureKibanaConfig to merge
* @param productFeaturesConfigs the ProductFeatureKibanaConfig to merge
* @returns mergedKibanaFeatureConfig the merged KibanaFeatureConfig
* */
public mergeAppFeatureConfigs(
public mergeProductFeatureConfigs(
kibanaFeatureConfig: BaseKibanaFeatureConfig,
kibanaSubFeatureIds: T[],
appFeaturesConfigs: AppFeatureKibanaConfig[]
productFeaturesConfigs: ProductFeatureKibanaConfig[]
): KibanaFeatureConfig {
const mergedKibanaFeatureConfig = cloneDeep(kibanaFeatureConfig) as KibanaFeatureConfig;
const subFeaturesPrivilegesToMerge: SubFeaturesPrivileges[] = [];
@ -38,9 +38,9 @@ export class AppFeaturesConfigMerger<T extends string = string> {
kibanaSubFeatureIds.map((id) => [id, true])
);
appFeaturesConfigs.forEach((appFeatureConfig) => {
const { subFeaturesPrivileges, subFeatureIds, ...appFeatureConfigToMerge } =
cloneDeep(appFeatureConfig);
productFeaturesConfigs.forEach((productFeatureConfig) => {
const { subFeaturesPrivileges, subFeatureIds, ...productFeatureConfigToMerge } =
cloneDeep(productFeatureConfig);
subFeatureIds?.forEach((subFeatureId) => {
enabledSubFeaturesIndexed[subFeatureId] = true;
@ -49,7 +49,7 @@ export class AppFeaturesConfigMerger<T extends string = string> {
if (subFeaturesPrivileges) {
subFeaturesPrivilegesToMerge.push(...subFeaturesPrivileges);
}
mergeWith(mergedKibanaFeatureConfig, appFeatureConfigToMerge, featureConfigMerger);
mergeWith(mergedKibanaFeatureConfig, productFeatureConfigToMerge, featureConfigMerger);
});
// generate sub features configs from enabled sub feature ids, preserving map order

View file

@ -0,0 +1,284 @@
/*
* 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 { ProductFeaturesService } from './product_features_service';
import { ProductFeatures } from './product_features';
import type {
ProductFeaturesConfig,
BaseKibanaFeatureConfig,
} from '@kbn/security-solution-features';
import { loggerMock } from '@kbn/logging-mocks';
import type { ExperimentalFeatures } from '../../../common';
import { featuresPluginMock } from '@kbn/features-plugin/server/mocks';
import type { ProductFeaturesConfigurator } from './types';
import type {
AssistantSubFeatureId,
CasesSubFeatureId,
SecuritySubFeatureId,
} from '@kbn/security-solution-features/keys';
import { ProductFeatureKey } from '@kbn/security-solution-features/keys';
import { httpServiceMock } from '@kbn/core-http-server-mocks';
import type {
KibanaRequest,
LifecycleResponseFactory,
OnPostAuthHandler,
} from '@kbn/core-http-server';
jest.mock('./product_features');
const MockedProductFeatures = ProductFeatures as unknown as jest.MockedClass<
typeof ProductFeatures
>;
const productFeature = {
subFeaturesMap: new Map(),
baseKibanaFeature: {} as BaseKibanaFeatureConfig,
baseKibanaSubFeatureIds: [],
};
const mockGetFeature = jest.fn().mockReturnValue(productFeature);
jest.mock('@kbn/security-solution-features/product_features', () => ({
getAssistantFeature: () => mockGetFeature(),
getCasesFeature: () => mockGetFeature(),
getSecurityFeature: () => mockGetFeature(),
}));
describe('ProductFeaturesService', () => {
beforeEach(() => {
jest.clearAllMocks();
});
it('should create ProductFeatureService instance', () => {
const experimentalFeatures = {} as ExperimentalFeatures;
new ProductFeaturesService(loggerMock.create(), experimentalFeatures);
expect(mockGetFeature).toHaveBeenCalledTimes(3);
expect(MockedProductFeatures).toHaveBeenCalledTimes(3);
});
it('should init all ProductFeatures when initialized', () => {
const experimentalFeatures = {} as ExperimentalFeatures;
const productFeaturesService = new ProductFeaturesService(
loggerMock.create(),
experimentalFeatures
);
const featuresSetup = featuresPluginMock.createSetup();
productFeaturesService.init(featuresSetup);
expect(MockedProductFeatures.mock.instances[0].init).toHaveBeenCalledWith(featuresSetup);
expect(MockedProductFeatures.mock.instances[1].init).toHaveBeenCalledWith(featuresSetup);
expect(MockedProductFeatures.mock.instances[2].init).toHaveBeenCalledWith(featuresSetup);
});
it('should configure ProductFeatures', () => {
const experimentalFeatures = {} as ExperimentalFeatures;
const productFeaturesService = new ProductFeaturesService(
loggerMock.create(),
experimentalFeatures
);
const featuresSetup = featuresPluginMock.createSetup();
productFeaturesService.init(featuresSetup);
const mockSecurityConfig = new Map() as ProductFeaturesConfig<SecuritySubFeatureId>;
const mockCasesConfig = new Map() as ProductFeaturesConfig<CasesSubFeatureId>;
const mockAssistantConfig = new Map() as ProductFeaturesConfig<AssistantSubFeatureId>;
const configurator: ProductFeaturesConfigurator = {
security: jest.fn(() => mockSecurityConfig),
cases: jest.fn(() => mockCasesConfig),
securityAssistant: jest.fn(() => mockAssistantConfig),
};
productFeaturesService.setProductFeaturesConfigurator(configurator);
expect(configurator.security).toHaveBeenCalled();
expect(configurator.cases).toHaveBeenCalled();
expect(configurator.securityAssistant).toHaveBeenCalled();
expect(MockedProductFeatures.mock.instances[0].setConfig).toHaveBeenCalledWith(
mockSecurityConfig
);
expect(MockedProductFeatures.mock.instances[1].setConfig).toHaveBeenCalledWith(mockCasesConfig);
expect(MockedProductFeatures.mock.instances[2].setConfig).toHaveBeenCalledWith(
mockAssistantConfig
);
});
it('should return isEnabled for enabled features', () => {
const experimentalFeatures = {} as ExperimentalFeatures;
const productFeaturesService = new ProductFeaturesService(
loggerMock.create(),
experimentalFeatures
);
const featuresSetup = featuresPluginMock.createSetup();
productFeaturesService.init(featuresSetup);
const mockSecurityConfig = new Map([
[ProductFeatureKey.advancedInsights, {}],
[ProductFeatureKey.endpointExceptions, {}],
]) as ProductFeaturesConfig<SecuritySubFeatureId>;
const mockCasesConfig = new Map([
[ProductFeatureKey.casesConnectors, {}],
]) as ProductFeaturesConfig<CasesSubFeatureId>;
const mockAssistantConfig = new Map([
[ProductFeatureKey.assistant, {}],
]) as ProductFeaturesConfig<AssistantSubFeatureId>;
const configurator: ProductFeaturesConfigurator = {
security: jest.fn(() => mockSecurityConfig),
cases: jest.fn(() => mockCasesConfig),
securityAssistant: jest.fn(() => mockAssistantConfig),
};
productFeaturesService.setProductFeaturesConfigurator(configurator);
expect(productFeaturesService.isEnabled(ProductFeatureKey.advancedInsights)).toEqual(true);
expect(productFeaturesService.isEnabled(ProductFeatureKey.endpointExceptions)).toEqual(true);
expect(productFeaturesService.isEnabled(ProductFeatureKey.casesConnectors)).toEqual(true);
expect(productFeaturesService.isEnabled(ProductFeatureKey.assistant)).toEqual(true);
expect(productFeaturesService.isEnabled(ProductFeatureKey.externalRuleActions)).toEqual(false);
});
it('should call isApiPrivilegeEnabled for api actions', () => {
const experimentalFeatures = {} as ExperimentalFeatures;
const productFeaturesService = new ProductFeaturesService(
loggerMock.create(),
experimentalFeatures
);
productFeaturesService.isApiPrivilegeEnabled('writeEndpointExceptions');
expect(MockedProductFeatures.mock.instances[0].isActionRegistered).toHaveBeenCalledWith(
'api:securitySolution-writeEndpointExceptions'
);
expect(MockedProductFeatures.mock.instances[1].isActionRegistered).toHaveBeenCalledWith(
'api:securitySolution-writeEndpointExceptions'
);
expect(MockedProductFeatures.mock.instances[2].isActionRegistered).toHaveBeenCalledWith(
'api:securitySolution-writeEndpointExceptions'
);
});
describe('registerApiAccessControl', () => {
const mockHttpSetup = httpServiceMock.createSetupContract();
let lastRegisteredFn: OnPostAuthHandler;
mockHttpSetup.registerOnPostAuth.mockImplementation((fn) => {
lastRegisteredFn = fn;
});
const getReq = (tags: string[] = []) =>
({
route: { options: { tags } },
url: { pathname: '', search: '' },
} as unknown as KibanaRequest);
const res = { notFound: jest.fn() } as unknown as LifecycleResponseFactory;
const toolkit = { next: jest.fn() };
beforeEach(() => {
jest.clearAllMocks();
});
it('should register api authorization http route interceptor', () => {
const experimentalFeatures = {} as ExperimentalFeatures;
const productFeaturesService = new ProductFeaturesService(
loggerMock.create(),
experimentalFeatures
);
productFeaturesService.registerApiAccessControl(mockHttpSetup);
expect(mockHttpSetup.registerOnPostAuth).toHaveBeenCalledTimes(1);
});
it('should authorize when no tag matches', () => {
const experimentalFeatures = {} as ExperimentalFeatures;
const productFeaturesService = new ProductFeaturesService(
loggerMock.create(),
experimentalFeatures
);
productFeaturesService.registerApiAccessControl(mockHttpSetup);
lastRegisteredFn(getReq(['access:something', 'access:securitySolution']), res, toolkit);
expect(MockedProductFeatures.mock.instances[0].isActionRegistered).not.toHaveBeenCalled();
expect(res.notFound).not.toHaveBeenCalled();
expect(toolkit.next).toHaveBeenCalledTimes(1);
});
it('should check when tag matches and return not found when not action registered', () => {
const experimentalFeatures = {} as ExperimentalFeatures;
const productFeaturesService = new ProductFeaturesService(
loggerMock.create(),
experimentalFeatures
);
productFeaturesService.registerApiAccessControl(mockHttpSetup);
(MockedProductFeatures.mock.instances[0].isActionRegistered as jest.Mock).mockReturnValueOnce(
false
);
lastRegisteredFn(getReq(['access:securitySolution-foo']), res, toolkit);
expect(MockedProductFeatures.mock.instances[0].isActionRegistered).toHaveBeenCalledWith(
'api:securitySolution-foo'
);
expect(res.notFound).toHaveBeenCalledTimes(1);
expect(toolkit.next).not.toHaveBeenCalled();
});
it('should check when tag matches and continue when action registered', () => {
const experimentalFeatures = {} as ExperimentalFeatures;
const productFeaturesService = new ProductFeaturesService(
loggerMock.create(),
experimentalFeatures
);
productFeaturesService.registerApiAccessControl(mockHttpSetup);
(MockedProductFeatures.mock.instances[0].isActionRegistered as jest.Mock).mockReturnValueOnce(
true
);
lastRegisteredFn(getReq(['access:securitySolution-foo']), res, toolkit);
expect(MockedProductFeatures.mock.instances[0].isActionRegistered).toHaveBeenCalledWith(
'api:securitySolution-foo'
);
expect(res.notFound).not.toHaveBeenCalled();
expect(toolkit.next).toHaveBeenCalledTimes(1);
});
it('should check when productFeature tag when it matches and return not found when not enabled', () => {
const experimentalFeatures = {} as ExperimentalFeatures;
const productFeaturesService = new ProductFeaturesService(
loggerMock.create(),
experimentalFeatures
);
productFeaturesService.registerApiAccessControl(mockHttpSetup);
productFeaturesService.isEnabled = jest.fn().mockReturnValueOnce(false);
lastRegisteredFn(getReq(['securitySolutionProductFeature:foo']), res, toolkit);
expect(productFeaturesService.isEnabled).toHaveBeenCalledWith('foo');
expect(res.notFound).toHaveBeenCalledTimes(1);
expect(toolkit.next).not.toHaveBeenCalled();
});
it('should check when productFeature tag when it matches and continue when enabled', () => {
const experimentalFeatures = {} as ExperimentalFeatures;
const productFeaturesService = new ProductFeaturesService(
loggerMock.create(),
experimentalFeatures
);
productFeaturesService.registerApiAccessControl(mockHttpSetup);
productFeaturesService.isEnabled = jest.fn().mockReturnValueOnce(true);
lastRegisteredFn(getReq(['securitySolutionProductFeature:foo']), res, toolkit);
expect(productFeaturesService.isEnabled).toHaveBeenCalledWith('foo');
expect(res.notFound).not.toHaveBeenCalled();
expect(toolkit.next).toHaveBeenCalledTimes(1);
});
});
});

View file

@ -14,24 +14,24 @@
import type { HttpServiceSetup, Logger } from '@kbn/core/server';
import { hiddenTypes as filesSavedObjectTypes } from '@kbn/files-plugin/server/saved_objects';
import type { PluginSetupContract as FeaturesPluginSetup } from '@kbn/features-plugin/server';
import type { AppFeatureKeyType } from '@kbn/security-solution-features';
import type { ProductFeatureKeyType } from '@kbn/security-solution-features';
import {
getAssistantFeature,
getCasesFeature,
getSecurityFeature,
} from '@kbn/security-solution-features/app_features';
} from '@kbn/security-solution-features/product_features';
import type { ExperimentalFeatures } from '../../../common';
import { APP_ID } from '../../../common';
import { AppFeatures } from './app_features';
import type { AppFeaturesConfigurator } from './types';
import { ProductFeatures } from './product_features';
import type { ProductFeaturesConfigurator } from './types';
import { securityDefaultSavedObjects } from './security_saved_objects';
import { casesApiTags, casesUiCapabilities } from './cases_privileges';
export class AppFeaturesService {
private securityAppFeatures: AppFeatures;
private casesAppFeatures: AppFeatures;
private securityAssistantAppFeatures: AppFeatures;
private appFeatures?: Set<AppFeatureKeyType>;
export class ProductFeaturesService {
private securityProductFeatures: ProductFeatures;
private casesProductFeatures: ProductFeatures;
private securityAssistantProductFeatures: ProductFeatures;
private productFeatures?: Set<ProductFeatureKeyType>;
constructor(
private readonly logger: Logger,
@ -41,7 +41,7 @@ export class AppFeaturesService {
savedObjects: securityDefaultSavedObjects,
experimentalFeatures: this.experimentalFeatures,
});
this.securityAppFeatures = new AppFeatures(
this.securityProductFeatures = new ProductFeatures(
this.logger,
securityFeature.subFeaturesMap,
securityFeature.baseKibanaFeature,
@ -53,7 +53,7 @@ export class AppFeaturesService {
apiTags: casesApiTags,
savedObjects: { files: filesSavedObjectTypes },
});
this.casesAppFeatures = new AppFeatures(
this.casesProductFeatures = new ProductFeatures(
this.logger,
casesFeature.subFeaturesMap,
casesFeature.baseKibanaFeature,
@ -61,7 +61,7 @@ export class AppFeaturesService {
);
const assistantFeature = getAssistantFeature();
this.securityAssistantAppFeatures = new AppFeatures(
this.securityAssistantProductFeatures = new ProductFeatures(
this.logger,
assistantFeature.subFeaturesMap,
assistantFeature.baseKibanaFeature,
@ -70,44 +70,44 @@ export class AppFeaturesService {
}
public init(featuresSetup: FeaturesPluginSetup) {
this.securityAppFeatures.init(featuresSetup);
this.casesAppFeatures.init(featuresSetup);
this.securityAssistantAppFeatures.init(featuresSetup);
this.securityProductFeatures.init(featuresSetup);
this.casesProductFeatures.init(featuresSetup);
this.securityAssistantProductFeatures.init(featuresSetup);
}
public setAppFeaturesConfigurator(configurator: AppFeaturesConfigurator) {
const securityAppFeaturesConfig = configurator.security();
this.securityAppFeatures.setConfig(securityAppFeaturesConfig);
public setProductFeaturesConfigurator(configurator: ProductFeaturesConfigurator) {
const securityProductFeaturesConfig = configurator.security();
this.securityProductFeatures.setConfig(securityProductFeaturesConfig);
const casesAppFeaturesConfig = configurator.cases();
this.casesAppFeatures.setConfig(casesAppFeaturesConfig);
const casesProductFeaturesConfig = configurator.cases();
this.casesProductFeatures.setConfig(casesProductFeaturesConfig);
const securityAssistantAppFeaturesConfig = configurator.securityAssistant();
this.securityAssistantAppFeatures.setConfig(securityAssistantAppFeaturesConfig);
const securityAssistantProductFeaturesConfig = configurator.securityAssistant();
this.securityAssistantProductFeatures.setConfig(securityAssistantProductFeaturesConfig);
this.appFeatures = new Set<AppFeatureKeyType>(
this.productFeatures = new Set<ProductFeatureKeyType>(
Object.freeze([
...securityAppFeaturesConfig.keys(),
...casesAppFeaturesConfig.keys(),
...securityAssistantAppFeaturesConfig.keys(),
]) as readonly AppFeatureKeyType[]
...securityProductFeaturesConfig.keys(),
...casesProductFeaturesConfig.keys(),
...securityAssistantProductFeaturesConfig.keys(),
]) as readonly ProductFeatureKeyType[]
);
}
public isEnabled(appFeatureKey: AppFeatureKeyType): boolean {
if (!this.appFeatures) {
throw new Error('AppFeatures has not yet been configured');
public isEnabled(productFeatureKey: ProductFeatureKeyType): boolean {
if (!this.productFeatures) {
throw new Error('ProductFeatures has not yet been configured');
}
return this.appFeatures.has(appFeatureKey);
return this.productFeatures.has(productFeatureKey);
}
public getApiActionName = (apiPrivilege: string) => `api:${APP_ID}-${apiPrivilege}`;
public isActionRegistered(action: string) {
return (
this.securityAppFeatures.isActionRegistered(action) ||
this.casesAppFeatures.isActionRegistered(action) ||
this.securityAssistantAppFeatures.isActionRegistered(action)
this.securityProductFeatures.isActionRegistered(action) ||
this.casesProductFeatures.isActionRegistered(action) ||
this.securityAssistantProductFeatures.isActionRegistered(action)
);
}
@ -116,13 +116,13 @@ export class AppFeaturesService {
}
public registerApiAccessControl(http: HttpServiceSetup) {
// The `securitySolutionAppFeature:` prefix is used for AppFeature based control.
// Should be used only by routes that do not need RBAC, only direct appFeature control.
const APP_FEATURE_TAG_PREFIX = 'securitySolutionAppFeature:';
// The `securitySolutionProductFeature:` prefix is used for ProductFeature based control.
// Should be used only by routes that do not need RBAC, only direct productFeature control.
const APP_FEATURE_TAG_PREFIX = 'securitySolutionProductFeature:';
// The "access:securitySolution-" prefix is used for API action based control.
// Should be used by routes that need RBAC, extending the `access:` role privilege check from the security plugin.
// An additional check is performed to ensure the privilege has been registered by the appFeature service,
// preventing full access (`*`) roles, such as superuser, from bypassing appFeature controls.
// An additional check is performed to ensure the privilege has been registered by the productFeature service,
// preventing full access (`*`) roles, such as superuser, from bypassing productFeature controls.
const API_ACTION_TAG_PREFIX = `access:${APP_ID}-`;
http.registerOnPostAuth((request, response, toolkit) => {
@ -130,7 +130,7 @@ export class AppFeaturesService {
let isEnabled = true;
if (tag.startsWith(APP_FEATURE_TAG_PREFIX)) {
isEnabled = this.isEnabled(
tag.substring(APP_FEATURE_TAG_PREFIX.length) as AppFeatureKeyType
tag.substring(APP_FEATURE_TAG_PREFIX.length) as ProductFeatureKeyType
);
} else if (tag.startsWith(API_ACTION_TAG_PREFIX)) {
isEnabled = this.isApiPrivilegeEnabled(tag.substring(API_ACTION_TAG_PREFIX.length));

View file

@ -5,15 +5,15 @@
* 2.0.
*/
import type { AppFeaturesConfig } from '@kbn/security-solution-features';
import type { ProductFeaturesConfig } from '@kbn/security-solution-features';
import type {
SecuritySubFeatureId,
CasesSubFeatureId,
AssistantSubFeatureId,
} from '@kbn/security-solution-features/keys';
export interface AppFeaturesConfigurator {
security: () => AppFeaturesConfig<SecuritySubFeatureId>;
cases: () => AppFeaturesConfig<CasesSubFeatureId>;
securityAssistant: () => AppFeaturesConfig<AssistantSubFeatureId>;
export interface ProductFeaturesConfigurator {
security: () => ProductFeaturesConfig<SecuritySubFeatureId>;
cases: () => ProductFeaturesConfig<CasesSubFeatureId>;
securityAssistant: () => ProductFeaturesConfig<AssistantSubFeatureId>;
}

View file

@ -120,7 +120,7 @@ import {
ENDPOINT_SEARCH_STRATEGY,
} from '../common/endpoint/constants';
import { AppFeaturesService } from './lib/app_features_service/app_features_service';
import { ProductFeaturesService } from './lib/product_features_service/product_features_service';
import { registerRiskScoringTask } from './lib/entity_analytics/risk_score/tasks/risk_scoring_task';
import { registerProtectionUpdatesNoteRoutes } from './endpoint/routes/protection_updates_note';
import {
@ -139,7 +139,7 @@ export class Plugin implements ISecuritySolutionPlugin {
private readonly config: ConfigType;
private readonly logger: Logger;
private readonly appClientFactory: AppClientFactory;
private readonly appFeaturesService: AppFeaturesService;
private readonly productFeaturesService: ProductFeaturesService;
private readonly ruleMonitoringService: IRuleMonitoringService;
private readonly endpointAppContextService = new EndpointAppContextService();
@ -163,7 +163,10 @@ export class Plugin implements ISecuritySolutionPlugin {
this.config = serverConfig;
this.logger = context.logger.get();
this.appClientFactory = new AppClientFactory();
this.appFeaturesService = new AppFeaturesService(this.logger, this.config.experimentalFeatures);
this.productFeaturesService = new ProductFeaturesService(
this.logger,
this.config.experimentalFeatures
);
this.ruleMonitoringService = createRuleMonitoringService(this.config, this.logger);
this.telemetryEventsSender = new TelemetryEventsSender(this.logger);
@ -188,12 +191,12 @@ export class Plugin implements ISecuritySolutionPlugin {
): SecuritySolutionPluginSetup {
this.logger.debug('plugin setup');
const { appClientFactory, appFeaturesService, pluginContext, config, logger } = this;
const { appClientFactory, productFeaturesService, pluginContext, config, logger } = this;
const experimentalFeatures = config.experimentalFeatures;
initSavedObjects(core.savedObjects);
initUiSettings(core.uiSettings, experimentalFeatures, config.enableUiSettingsValidations);
appFeaturesService.init(plugins.features);
productFeaturesService.init(plugins.features);
events.forEach((eventConfig) => core.analytics.registerEventType(eventConfig));
@ -220,7 +223,7 @@ export class Plugin implements ISecuritySolutionPlugin {
kibanaBranch: pluginContext.env.packageInfo.branch,
});
appFeaturesService.registerApiAccessControl(core.http);
productFeaturesService.registerApiAccessControl(core.http);
const router = core.http.createRouter<SecuritySolutionRequestHandlerContext>();
core.http.registerRouteHandlerContext<SecuritySolutionRequestHandlerContext, typeof APP_ID>(
APP_ID,
@ -494,8 +497,8 @@ export class Plugin implements ISecuritySolutionPlugin {
featureUsageService.setup(plugins.licensing);
return {
setAppFeaturesConfigurator:
appFeaturesService.setAppFeaturesConfigurator.bind(appFeaturesService),
setProductFeaturesConfigurator:
productFeaturesService.setProductFeaturesConfigurator.bind(productFeaturesService),
experimentalFeatures: { ...config.experimentalFeatures },
};
}
@ -504,7 +507,7 @@ export class Plugin implements ISecuritySolutionPlugin {
core: SecuritySolutionPluginCoreStartDependencies,
plugins: SecuritySolutionPluginStartDependencies
): SecuritySolutionPluginStart {
const { config, logger, appFeaturesService } = this;
const { config, logger, productFeaturesService } = this;
this.ruleMonitoringService.start(core, plugins);
@ -567,7 +570,7 @@ export class Plugin implements ISecuritySolutionPlugin {
experimentalFeatures: config.experimentalFeatures,
packagerTaskPackagePolicyUpdateBatchSize: config.packagerTaskPackagePolicyUpdateBatchSize,
esClient: core.elasticsearch.client.asInternalUser,
appFeaturesService,
productFeaturesService,
});
// Migrate artifacts to fleet and then start the manifest task after that is done
@ -584,13 +587,13 @@ export class Plugin implements ISecuritySolutionPlugin {
turnOffPolicyProtectionsIfNotSupported(
core.elasticsearch.client.asInternalUser,
endpointFleetServicesFactory.asInternalUser(),
appFeaturesService,
productFeaturesService,
logger
);
turnOffAgentPolicyFeatures(
endpointFleetServicesFactory.asInternalUser(),
appFeaturesService,
productFeaturesService,
logger
);
});
@ -636,7 +639,7 @@ export class Plugin implements ISecuritySolutionPlugin {
),
createFleetActionsClient,
esClient: core.elasticsearch.client.asInternalUser,
appFeaturesService,
productFeaturesService,
savedObjectsClient,
});

View file

@ -42,7 +42,7 @@ import type { SharePluginStart } from '@kbn/share-plugin/server';
import type { GuidedOnboardingPluginSetup } from '@kbn/guided-onboarding-plugin/server';
import type { PluginSetup as UnifiedSearchServerPluginSetup } from '@kbn/unified-search-plugin/server';
import type { ElasticAssistantPluginStart } from '@kbn/elastic-assistant-plugin/server';
import type { AppFeaturesService } from './lib/app_features_service/app_features_service';
import type { ProductFeaturesService } from './lib/product_features_service/product_features_service';
import type { ExperimentalFeatures } from '../common';
export interface SecuritySolutionPluginSetupDependencies {
@ -90,7 +90,7 @@ export interface SecuritySolutionPluginSetup {
/**
* Sets the configurations for app features that are available to the Security Solution
*/
setAppFeaturesConfigurator: AppFeaturesService['setAppFeaturesConfigurator'];
setProductFeaturesConfigurator: ProductFeaturesService['setProductFeaturesConfigurator'];
/**
* The security solution generic experimental features
*/

View file

@ -1,22 +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 { AppFeatureKeys } from '@kbn/security-solution-features';
import type { AppFeaturesConfigurator } from '@kbn/security-solution-plugin/server/lib/app_features_service/types';
import { getCasesAppFeaturesConfigurator } from './cases_app_features_config';
import { getSecurityAppFeaturesConfigurator } from './security_app_features_config';
import { getSecurityAssistantAppFeaturesConfigurator } from './security_assistant_app_features_config';
export const getProductAppFeaturesConfigurator = (
enabledAppFeatureKeys: AppFeatureKeys
): AppFeaturesConfigurator => {
return {
security: getSecurityAppFeaturesConfigurator(enabledAppFeatureKeys),
cases: getCasesAppFeaturesConfigurator(enabledAppFeatureKeys),
securityAssistant: getSecurityAssistantAppFeaturesConfigurator(enabledAppFeatureKeys),
};
};

View file

@ -5,8 +5,8 @@
* 2.0.
*/
import { ALL_APP_FEATURE_KEYS } from '@kbn/security-solution-features/keys';
import { ALL_PRODUCT_FEATURE_KEYS } from '@kbn/security-solution-features/keys';
// 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];
// We may need a different set of keys in the future if we create serverless-specific productFeatures
export const DEFAULT_PRODUCT_FEATURES = [...ALL_PRODUCT_FEATURE_KEYS];

View file

@ -6,8 +6,8 @@
*/
import type { Plugin, CoreSetup } from '@kbn/core/server';
import { getProductAppFeaturesConfigurator } from './app_features';
import { DEFAULT_APP_FEATURES } from './constants';
import { getProductProductFeaturesConfigurator } from './product_features';
import { DEFAULT_PRODUCT_FEATURES } from './constants';
import type {
SecuritySolutionEssPluginSetup,
@ -26,8 +26,9 @@ export class SecuritySolutionEssPlugin
>
{
public setup(_coreSetup: CoreSetup, pluginsSetup: SecuritySolutionEssPluginSetupDeps) {
const appFeaturesConfigurator = getProductAppFeaturesConfigurator(DEFAULT_APP_FEATURES);
pluginsSetup.securitySolution.setAppFeaturesConfigurator(appFeaturesConfigurator);
const productFeaturesConfigurator =
getProductProductFeaturesConfigurator(DEFAULT_PRODUCT_FEATURES);
pluginsSetup.securitySolution.setProductFeaturesConfigurator(productFeaturesConfigurator);
return {};
}

View file

@ -5,26 +5,29 @@
* 2.0.
*/
import type {
AppFeatureKeys,
AppFeatureKibanaConfig,
AppFeaturesAssistantConfig,
ProductFeatureKeys,
ProductFeatureKibanaConfig,
ProductFeaturesAssistantConfig,
} from '@kbn/security-solution-features';
import {
assistantDefaultAppFeaturesConfig,
createEnabledAppFeaturesConfigMap,
assistantDefaultProductFeaturesConfig,
createEnabledProductFeaturesConfigMap,
} from '@kbn/security-solution-features/config';
import type {
AppFeatureAssistantKey,
ProductFeatureAssistantKey,
AssistantSubFeatureId,
} from '@kbn/security-solution-features/keys';
export const getSecurityAssistantAppFeaturesConfigurator =
(enabledAppFeatureKeys: AppFeatureKeys) => (): AppFeaturesAssistantConfig => {
return createEnabledAppFeaturesConfigMap(assistantAppFeaturesConfig, enabledAppFeatureKeys);
export const getSecurityAssistantProductFeaturesConfigurator =
(enabledProductFeatureKeys: ProductFeatureKeys) => (): ProductFeaturesAssistantConfig => {
return createEnabledProductFeaturesConfigMap(
assistantProductFeaturesConfig,
enabledProductFeatureKeys
);
};
/**
* Maps the AppFeatures keys to Kibana privileges that will be merged
* Maps the ProductFeatures keys to Kibana privileges that will be merged
* into the base privileges config for the Security Assistant app.
*
* Privileges can be added in different ways:
@ -32,10 +35,10 @@ export const getSecurityAssistantAppFeaturesConfigurator =
* - `subFeatureIds`: the ids of the sub-features that will be added into the Assistant subFeatures entry.
* - `subFeaturesPrivileges`: the privileges that will be added into the existing Assistant subFeature with the privilege `id` specified.
*/
const assistantAppFeaturesConfig: Record<
AppFeatureAssistantKey,
AppFeatureKibanaConfig<AssistantSubFeatureId>
const assistantProductFeaturesConfig: Record<
ProductFeatureAssistantKey,
ProductFeatureKibanaConfig<AssistantSubFeatureId>
> = {
...assistantDefaultAppFeaturesConfig,
...assistantDefaultProductFeaturesConfig,
// ess-specific app features configs here
};

View file

@ -5,14 +5,17 @@
* 2.0.
*/
import type {
AppFeatureKibanaConfig,
AppFeaturesCasesConfig,
AppFeatureKeys,
ProductFeatureKibanaConfig,
ProductFeaturesCasesConfig,
ProductFeatureKeys,
} from '@kbn/security-solution-features';
import type { AppFeatureCasesKey, CasesSubFeatureId } from '@kbn/security-solution-features/keys';
import type {
ProductFeatureCasesKey,
CasesSubFeatureId,
} from '@kbn/security-solution-features/keys';
import {
getCasesDefaultAppFeaturesConfig,
createEnabledAppFeaturesConfigMap,
getCasesDefaultProductFeaturesConfig,
createEnabledProductFeaturesConfigMap,
} from '@kbn/security-solution-features/config';
import {
@ -20,13 +23,16 @@ import {
GET_CONNECTORS_CONFIGURE_API_TAG,
} from '@kbn/cases-plugin/common/constants';
export const getCasesAppFeaturesConfigurator =
(enabledAppFeatureKeys: AppFeatureKeys) => (): AppFeaturesCasesConfig => {
return createEnabledAppFeaturesConfigMap(casesAppFeaturesConfig, enabledAppFeatureKeys);
export const getCasesProductFeaturesConfigurator =
(enabledProductFeatureKeys: ProductFeatureKeys) => (): ProductFeaturesCasesConfig => {
return createEnabledProductFeaturesConfigMap(
casesProductFeaturesConfig,
enabledProductFeatureKeys
);
};
/**
* Maps the AppFeatures keys to Kibana privileges that will be merged
* Maps the ProductFeatures keys to Kibana privileges that will be merged
* into the base privileges config for the Security Cases app.
*
* Privileges can be added in different ways:
@ -34,11 +40,11 @@ export const getCasesAppFeaturesConfigurator =
* - `subFeatureIds`: the ids of the sub-features that will be added into the Cases subFeatures entry.
* - `subFeaturesPrivileges`: the privileges that will be added into the existing Cases subFeature with the privilege `id` specified.
*/
const casesAppFeaturesConfig: Record<
AppFeatureCasesKey,
AppFeatureKibanaConfig<CasesSubFeatureId>
const casesProductFeaturesConfig: Record<
ProductFeatureCasesKey,
ProductFeatureKibanaConfig<CasesSubFeatureId>
> = {
...getCasesDefaultAppFeaturesConfig({
...getCasesDefaultProductFeaturesConfig({
apiTags: { connectors: GET_CONNECTORS_CONFIGURE_API_TAG },
uiCapabilities: { connectors: CASES_CONNECTORS_CAPABILITY },
}),

View file

@ -0,0 +1,22 @@
/*
* 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 { ProductFeatureKeys } from '@kbn/security-solution-features';
import type { ProductFeaturesConfigurator } from '@kbn/security-solution-plugin/server/lib/product_features_service/types';
import { getCasesProductFeaturesConfigurator } from './cases_product_features_config';
import { getSecurityProductFeaturesConfigurator } from './security_product_features_config';
import { getSecurityAssistantProductFeaturesConfigurator } from './assistant_product_features_config';
export const getProductProductFeaturesConfigurator = (
enabledProductFeatureKeys: ProductFeatureKeys
): ProductFeaturesConfigurator => {
return {
security: getSecurityProductFeaturesConfigurator(enabledProductFeatureKeys),
cases: getCasesProductFeaturesConfigurator(enabledProductFeatureKeys),
securityAssistant: getSecurityAssistantProductFeaturesConfigurator(enabledProductFeatureKeys),
};
};

View file

@ -5,30 +5,33 @@
* 2.0.
*/
import type {
AppFeatureKeys,
AppFeatureKibanaConfig,
AppFeaturesSecurityConfig,
ProductFeatureKeys,
ProductFeatureKibanaConfig,
ProductFeaturesSecurityConfig,
} from '@kbn/security-solution-features';
import {
AppFeatureSecurityKey,
ProductFeatureSecurityKey,
type SecuritySubFeatureId,
} from '@kbn/security-solution-features/keys';
import {
securityDefaultAppFeaturesConfig,
createEnabledAppFeaturesConfigMap,
securityDefaultProductFeaturesConfig,
createEnabledProductFeaturesConfigMap,
} from '@kbn/security-solution-features/config';
import {
AppFeaturesPrivilegeId,
AppFeaturesPrivileges,
ProductFeaturesPrivilegeId,
ProductFeaturesPrivileges,
} from '@kbn/security-solution-features/privileges';
export const getSecurityAppFeaturesConfigurator =
(enabledAppFeatureKeys: AppFeatureKeys) => (): AppFeaturesSecurityConfig => {
return createEnabledAppFeaturesConfigMap(securityAppFeaturesConfig, enabledAppFeatureKeys);
export const getSecurityProductFeaturesConfigurator =
(enabledProductFeatureKeys: ProductFeatureKeys) => (): ProductFeaturesSecurityConfig => {
return createEnabledProductFeaturesConfigMap(
securityProductFeaturesConfig,
enabledProductFeatureKeys
);
};
/**
* Maps the AppFeatures keys to Kibana privileges that will be merged
* Maps the ProductFeatures keys to Kibana privileges that will be merged
* into the base privileges config for the Security app.
*
* Privileges can be added in different ways:
@ -36,12 +39,12 @@ export const getSecurityAppFeaturesConfigurator =
* - `subFeatureIds`: the ids of the sub-features that will be added into the Security subFeatures entry.
* - `subFeaturesPrivileges`: the privileges that will be added into the existing Security subFeature with the privilege `id` specified.
*/
const securityAppFeaturesConfig: Record<
AppFeatureSecurityKey,
AppFeatureKibanaConfig<SecuritySubFeatureId>
const securityProductFeaturesConfig: Record<
ProductFeatureSecurityKey,
ProductFeatureKibanaConfig<SecuritySubFeatureId>
> = {
...securityDefaultAppFeaturesConfig,
[AppFeatureSecurityKey.endpointExceptions]: {
privileges: AppFeaturesPrivileges[AppFeaturesPrivilegeId.endpointExceptions],
...securityDefaultProductFeaturesConfig,
[ProductFeatureSecurityKey.endpointExceptions]: {
privileges: ProductFeaturesPrivileges[ProductFeaturesPrivilegeId.endpointExceptions],
},
};

View file

@ -5,38 +5,41 @@
* 2.0.
*/
import type { AppFeatureKeys } from '@kbn/security-solution-features';
import { AppFeatureKey } from '@kbn/security-solution-features/keys';
import type { ProductFeatureKeys } from '@kbn/security-solution-features';
import { ProductFeatureKey } from '@kbn/security-solution-features/keys';
import type { SecurityProductLine, SecurityProductTier } from '../config';
type PliAppFeatures = Readonly<
Record<SecurityProductLine, Readonly<Record<SecurityProductTier, Readonly<AppFeatureKeys>>>>
type PliProductFeatures = Readonly<
Record<SecurityProductLine, Readonly<Record<SecurityProductTier, Readonly<ProductFeatureKeys>>>>
>;
export const PLI_APP_FEATURES: PliAppFeatures = {
export const PLI_PRODUCT_FEATURES: PliProductFeatures = {
security: {
essentials: [AppFeatureKey.endpointHostManagement, AppFeatureKey.endpointPolicyManagement],
essentials: [
ProductFeatureKey.endpointHostManagement,
ProductFeatureKey.endpointPolicyManagement,
],
complete: [
AppFeatureKey.advancedInsights,
AppFeatureKey.assistant,
AppFeatureKey.investigationGuide,
AppFeatureKey.threatIntelligence,
AppFeatureKey.casesConnectors,
AppFeatureKey.externalRuleActions,
ProductFeatureKey.advancedInsights,
ProductFeatureKey.assistant,
ProductFeatureKey.investigationGuide,
ProductFeatureKey.threatIntelligence,
ProductFeatureKey.casesConnectors,
ProductFeatureKey.externalRuleActions,
],
},
endpoint: {
essentials: [
AppFeatureKey.endpointPolicyProtections,
AppFeatureKey.endpointArtifactManagement,
AppFeatureKey.endpointExceptions,
ProductFeatureKey.endpointPolicyProtections,
ProductFeatureKey.endpointArtifactManagement,
ProductFeatureKey.endpointExceptions,
],
complete: [
AppFeatureKey.endpointResponseActions,
AppFeatureKey.osqueryAutomatedResponseActions,
AppFeatureKey.endpointAgentTamperProtection,
AppFeatureKey.endpointExceptions,
AppFeatureKey.endpointProtectionUpdates,
ProductFeatureKey.endpointResponseActions,
ProductFeatureKey.osqueryAutomatedResponseActions,
ProductFeatureKey.endpointAgentTamperProtection,
ProductFeatureKey.endpointExceptions,
ProductFeatureKey.endpointProtectionUpdates,
],
},
cloud: {

View file

@ -4,44 +4,44 @@
* 2.0; you may not use this file except in compliance with the Elastic License
* 2.0.
*/
import { getProductAppFeatures } from './pli_features';
import { getProductProductFeatures } from './pli_features';
import * as pliConfig from './pli_config';
import { ProductLine, ProductTier } from '../product';
describe('getProductAppFeatures', () => {
describe('getProductProductFeatures', () => {
it('should return the essentials PLIs features', () => {
// @ts-ignore reassigning readonly value for testing
pliConfig.PLI_APP_FEATURES = {
pliConfig.PLI_PRODUCT_FEATURES = {
security: {
essentials: ['foo'],
complete: ['baz'],
},
};
const appFeatureKeys = getProductAppFeatures([
const productFeatureKeys = getProductProductFeatures([
{ product_line: ProductLine.security, product_tier: ProductTier.essentials },
]);
expect(appFeatureKeys).toEqual(['foo']);
expect(productFeatureKeys).toEqual(['foo']);
});
it('should return the complete PLIs features, which includes essentials', () => {
// @ts-ignore reassigning readonly value for testing
pliConfig.PLI_APP_FEATURES = {
pliConfig.PLI_PRODUCT_FEATURES = {
security: {
essentials: ['foo'],
complete: ['baz'],
},
};
const appFeatureKeys = getProductAppFeatures([
const productFeatureKeys = getProductProductFeatures([
{ product_line: ProductLine.security, product_tier: ProductTier.complete },
]);
expect(appFeatureKeys).toEqual(['foo', 'baz']);
expect(productFeatureKeys).toEqual(['foo', 'baz']);
});
it('returns an empty object if no PLIs are enabled', () => {
expect(getProductAppFeatures([])).toEqual([]);
expect(getProductProductFeatures([])).toEqual([]);
});
});

View file

@ -5,24 +5,26 @@
* 2.0.
*/
import type { AppFeatureKeys } from '@kbn/security-solution-features/src/types';
import type { ProductFeatureKeys } from '@kbn/security-solution-features';
import type { SecurityProductTypes } from '../config';
import { ProductTier } from '../product';
import { PLI_APP_FEATURES } from './pli_config';
import { PLI_PRODUCT_FEATURES } from './pli_config';
/**
* Returns the U (union) of all PLIs from the enabled productTypes in a single array.
*/
export const getProductAppFeatures = (productTypes: SecurityProductTypes): AppFeatureKeys => {
const appFeatureKeys = productTypes.reduce<AppFeatureKeys>(
(appFeatures, { product_line: line, product_tier: tier }) => {
export const getProductProductFeatures = (
productTypes: SecurityProductTypes
): ProductFeatureKeys => {
const productFeatureKeys = productTypes.reduce<ProductFeatureKeys>(
(productFeatures, { product_line: line, product_tier: tier }) => {
if (tier === ProductTier.complete) {
appFeatures.push(...PLI_APP_FEATURES[line][ProductTier.essentials]);
productFeatures.push(...PLI_PRODUCT_FEATURES[line][ProductTier.essentials]);
}
appFeatures.push(...PLI_APP_FEATURES[line][tier]);
return appFeatures;
productFeatures.push(...PLI_PRODUCT_FEATURES[line][tier]);
return productFeatures;
},
[]
);
return appFeatureKeys;
return productFeatureKeys;
};

View file

@ -5,26 +5,26 @@
* 2.0.
*/
import type { AppFeatureKeyType } from '@kbn/security-solution-features';
import { PLI_APP_FEATURES } from '../../../common/pli/pli_config';
import type { ProductFeatureKeyType } from '@kbn/security-solution-features';
import { PLI_PRODUCT_FEATURES } from '../../../common/pli/pli_config';
export const getProductTypeByPLI = (requiredPLI: AppFeatureKeyType): string | null => {
if (PLI_APP_FEATURES.security.essentials.includes(requiredPLI)) {
export const getProductTypeByPLI = (requiredPLI: ProductFeatureKeyType): string | null => {
if (PLI_PRODUCT_FEATURES.security.essentials.includes(requiredPLI)) {
return 'Security Essentials';
}
if (PLI_APP_FEATURES.security.complete.includes(requiredPLI)) {
if (PLI_PRODUCT_FEATURES.security.complete.includes(requiredPLI)) {
return 'Security Complete';
}
if (PLI_APP_FEATURES.endpoint.essentials.includes(requiredPLI)) {
if (PLI_PRODUCT_FEATURES.endpoint.essentials.includes(requiredPLI)) {
return 'Endpoint Essentials';
}
if (PLI_APP_FEATURES.endpoint.complete.includes(requiredPLI)) {
if (PLI_PRODUCT_FEATURES.endpoint.complete.includes(requiredPLI)) {
return 'Endpoint Complete';
}
if (PLI_APP_FEATURES.cloud.essentials.includes(requiredPLI)) {
if (PLI_PRODUCT_FEATURES.cloud.essentials.includes(requiredPLI)) {
return 'Cloud Essentials';
}
if (PLI_APP_FEATURES.cloud.complete.includes(requiredPLI)) {
if (PLI_PRODUCT_FEATURES.cloud.complete.includes(requiredPLI)) {
return 'Cloud Complete';
}
return null;

View file

@ -8,10 +8,10 @@
import { EuiEmptyPrompt, EuiIcon } from '@elastic/eui';
import { FormattedMessage } from '@kbn/i18n-react';
import React, { memo } from 'react';
import type { AppFeatureKeyType } from '@kbn/security-solution-features/keys';
import type { ProductFeatureKeyType } from '@kbn/security-solution-features/keys';
import { getProductTypeByPLI } from '../../hooks/use_product_type_by_pli';
const EndpointExceptionsDetailsUpselling: React.FC<{ requiredPLI: AppFeatureKeyType }> = memo(
const EndpointExceptionsDetailsUpselling: React.FC<{ requiredPLI: ProductFeatureKeyType }> = memo(
({ requiredPLI }) => {
const productTypeRequired = getProductTypeByPLI(requiredPLI);

View file

@ -9,10 +9,10 @@ import { EuiEmptyPrompt, EuiIcon } from '@elastic/eui';
import { FormattedMessage } from '@kbn/i18n-react';
import React from 'react';
import type { AppFeatureKeyType } from '@kbn/security-solution-features';
import type { ProductFeatureKeyType } from '@kbn/security-solution-features';
import { getProductTypeByPLI } from '../hooks/use_product_type_by_pli';
const OsqueryResponseActionsUpsellingSection: React.FC<{ requiredPLI: AppFeatureKeyType }> =
const OsqueryResponseActionsUpsellingSection: React.FC<{ requiredPLI: ProductFeatureKeyType }> =
React.memo(({ requiredPLI }) => {
const productTypeRequired = getProductTypeByPLI(requiredPLI);

View file

@ -8,11 +8,11 @@
import React from 'react';
import { EuiEmptyPrompt, EuiIcon } from '@elastic/eui';
import { FormattedMessage } from '@kbn/i18n-react';
import type { AppFeatureKeyType } from '@kbn/security-solution-features';
import type { ProductFeatureKeyType } from '@kbn/security-solution-features';
import { KibanaPageTemplate } from '@kbn/shared-ux-page-kibana-template';
import { getProductTypeByPLI } from '../hooks/use_product_type_by_pli';
const ThreatIntelligencePaywall: React.FC<{ requiredPLI: AppFeatureKeyType }> = React.memo(
const ThreatIntelligencePaywall: React.FC<{ requiredPLI: ProductFeatureKeyType }> = React.memo(
function PaywallComponent({ requiredPLI }) {
const productTypeRequired = getProductTypeByPLI(requiredPLI);

View file

@ -13,13 +13,13 @@ import {
} from './register_upsellings';
import { ProductLine, ProductTier } from '../../common/product';
import type { SecurityProductTypes } from '../../common/config';
import { ALL_APP_FEATURE_KEYS } from '@kbn/security-solution-features/keys';
import { ALL_PRODUCT_FEATURE_KEYS } from '@kbn/security-solution-features/keys';
import type { UpsellingService } from '@kbn/security-solution-upselling/service';
import { mockServices } from '../common/services/__mocks__/services.mock';
const mockGetProductAppFeatures = jest.fn();
const mockGetProductProductFeatures = jest.fn();
jest.mock('../../common/pli/pli_features', () => ({
getProductAppFeatures: () => mockGetProductAppFeatures(),
getProductProductFeatures: () => mockGetProductProductFeatures(),
}));
const allProductTypes: SecurityProductTypes = [
@ -30,7 +30,7 @@ const allProductTypes: SecurityProductTypes = [
describe('registerUpsellings', () => {
it('should not register anything when all PLIs features are enabled', () => {
mockGetProductAppFeatures.mockReturnValue(ALL_APP_FEATURE_KEYS);
mockGetProductProductFeatures.mockReturnValue(ALL_PRODUCT_FEATURE_KEYS);
const setPages = jest.fn();
const setSections = jest.fn();
@ -54,7 +54,7 @@ describe('registerUpsellings', () => {
});
it('should register all upsellings pages, sections and messages when PLIs features are disabled', () => {
mockGetProductAppFeatures.mockReturnValue([]);
mockGetProductProductFeatures.mockReturnValue([]);
const setPages = jest.fn();
const setSections = jest.fn();

View file

@ -15,8 +15,8 @@ import type {
import type { UpsellingService } from '@kbn/security-solution-upselling/service';
import React from 'react';
import { UPGRADE_INVESTIGATION_GUIDE } from '@kbn/security-solution-upselling/messages';
import { AppFeatureKey } from '@kbn/security-solution-features/keys';
import type { AppFeatureKeyType } from '@kbn/security-solution-features';
import { ProductFeatureKey } from '@kbn/security-solution-features/keys';
import type { ProductFeatureKeyType } from '@kbn/security-solution-features';
import {
EndpointAgentTamperProtectionLazy,
EndpointPolicyProtectionsLazy,
@ -24,7 +24,7 @@ import {
RuleDetailsEndpointExceptionsLazy,
} from './sections/endpoint_management';
import type { SecurityProductTypes } from '../../common/config';
import { getProductAppFeatures } from '../../common/pli/pli_features';
import { getProductProductFeatures } from '../../common/pli/pli_features';
import {
EndpointExceptionsDetailsUpsellingLazy,
EntityAnalyticsUpsellingLazy,
@ -36,12 +36,12 @@ import type { Services } from '../common/services';
import { withServicesProvider } from '../common/services';
interface UpsellingsConfig {
pli: AppFeatureKeyType;
pli: ProductFeatureKeyType;
component: React.ComponentType;
}
interface UpsellingsMessageConfig {
pli: AppFeatureKeyType;
pli: ProductFeatureKeyType;
message: string;
id: UpsellingMessageId;
}
@ -55,7 +55,7 @@ export const registerUpsellings = (
productTypes: SecurityProductTypes,
services: Services
) => {
const enabledPLIsSet = new Set(getProductAppFeatures(productTypes));
const enabledPLIsSet = new Set(getProductProductFeatures(productTypes));
const upsellingPagesToRegister = upsellingPages.reduce<PageUpsellings>(
(pageUpsellings, { pageName, pli, component }) => {
@ -97,25 +97,25 @@ export const upsellingPages: UpsellingPages = [
// It is highly advisable to make use of lazy loaded components to minimize bundle size.
{
pageName: SecurityPageName.entityAnalytics,
pli: AppFeatureKey.advancedInsights,
pli: ProductFeatureKey.advancedInsights,
component: () => (
<EntityAnalyticsUpsellingLazy
requiredProduct={getProductTypeByPLI(AppFeatureKey.advancedInsights) ?? undefined}
requiredProduct={getProductTypeByPLI(ProductFeatureKey.advancedInsights) ?? undefined}
/>
),
},
{
pageName: SecurityPageName.threatIntelligence,
pli: AppFeatureKey.threatIntelligence,
pli: ProductFeatureKey.threatIntelligence,
component: () => (
<ThreatIntelligencePaywallLazy requiredPLI={AppFeatureKey.threatIntelligence} />
<ThreatIntelligencePaywallLazy requiredPLI={ProductFeatureKey.threatIntelligence} />
),
},
{
pageName: SecurityPageName.exceptions,
pli: AppFeatureKey.endpointExceptions,
pli: ProductFeatureKey.endpointExceptions,
component: () => (
<EndpointExceptionsDetailsUpsellingLazy requiredPLI={AppFeatureKey.endpointExceptions} />
<EndpointExceptionsDetailsUpsellingLazy requiredPLI={ProductFeatureKey.endpointExceptions} />
),
},
];
@ -125,31 +125,31 @@ export const upsellingSections: UpsellingSections = [
// It is highly advisable to make use of lazy loaded components to minimize bundle size.
{
id: 'osquery_automated_response_actions',
pli: AppFeatureKey.osqueryAutomatedResponseActions,
pli: ProductFeatureKey.osqueryAutomatedResponseActions,
component: () => (
<OsqueryResponseActionsUpsellingSectionLazy
requiredPLI={AppFeatureKey.osqueryAutomatedResponseActions}
requiredPLI={ProductFeatureKey.osqueryAutomatedResponseActions}
/>
),
},
{
id: 'endpoint_agent_tamper_protection',
pli: AppFeatureKey.endpointAgentTamperProtection,
pli: ProductFeatureKey.endpointAgentTamperProtection,
component: EndpointAgentTamperProtectionLazy,
},
{
id: 'endpointPolicyProtections',
pli: AppFeatureKey.endpointPolicyProtections,
pli: ProductFeatureKey.endpointPolicyProtections,
component: EndpointPolicyProtectionsLazy,
},
{
id: 'ruleDetailsEndpointExceptions',
pli: AppFeatureKey.endpointExceptions,
pli: ProductFeatureKey.endpointExceptions,
component: RuleDetailsEndpointExceptionsLazy,
},
{
id: 'endpoint_protection_updates',
pli: AppFeatureKey.endpointProtectionUpdates,
pli: ProductFeatureKey.endpointProtectionUpdates,
component: EndpointProtectionUpdatesLazy,
},
];
@ -158,9 +158,9 @@ export const upsellingSections: UpsellingSections = [
export const upsellingMessages: UpsellingMessages = [
{
id: 'investigation_guide',
pli: AppFeatureKey.investigationGuide,
pli: ProductFeatureKey.investigationGuide,
message: UPGRADE_INVESTIGATION_GUIDE(
getProductTypeByPLI(AppFeatureKey.investigationGuide) ?? ''
getProductTypeByPLI(ProductFeatureKey.investigationGuide) ?? ''
),
},
];

View file

@ -1,27 +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 { AppFeatureKeys } from '@kbn/security-solution-features';
import type { AppFeaturesConfigurator } from '@kbn/security-solution-plugin/server/lib/app_features_service/types';
import type { ServerlessSecurityConfig } from '../config';
import { getCasesAppFeaturesConfigurator } from './cases_app_features_config';
import { getSecurityAppFeaturesConfigurator } from './security_app_features_config';
import { getSecurityAssistantAppFeaturesConfigurator } from './security_assistant_app_features_config';
export const getProductAppFeaturesConfigurator = (
enabledAppFeatureKeys: AppFeatureKeys,
config: ServerlessSecurityConfig
): AppFeaturesConfigurator => {
return {
security: getSecurityAppFeaturesConfigurator(
enabledAppFeatureKeys,
config.experimentalFeatures
),
cases: getCasesAppFeaturesConfigurator(enabledAppFeatureKeys),
securityAssistant: getSecurityAssistantAppFeaturesConfigurator(enabledAppFeatureKeys),
};
};

View file

@ -14,7 +14,7 @@ import type {
} from '@kbn/core/server';
import { SECURITY_PROJECT_SETTINGS } from '@kbn/serverless-security-settings';
import { getProductAppFeatures } from '../common/pli/pli_features';
import { getProductProductFeatures } from '../common/pli/pli_features';
import type { ServerlessSecurityConfig } from './config';
import { createConfig } from './config';
@ -26,7 +26,7 @@ import type {
} from './types';
import { SecurityUsageReportingTask } from './task_manager/usage_reporting_task';
import { cloudSecurityMetringTaskProperties } from './cloud_security/cloud_security_metering_task_config';
import { getProductAppFeaturesConfigurator } from './app_features';
import { getProductProductFeaturesConfigurator } from './product_features';
import { METERING_TASK as ENDPOINT_METERING_TASK } from './endpoint/constants/metering';
import {
endpointMeteringService,
@ -55,7 +55,7 @@ export class SecuritySolutionServerlessPlugin
public setup(coreSetup: CoreSetup, pluginsSetup: SecuritySolutionServerlessPluginSetupDeps) {
this.config = createConfig(this.initializerContext, pluginsSetup.securitySolution);
const enabledAppFeatures = getProductAppFeatures(this.config.productTypes);
const enabledProductFeatures = getProductProductFeatures(this.config.productTypes);
// securitySolutionEss plugin should always be disabled when securitySolutionServerless is enabled.
// This check is an additional layer of security to prevent double registrations when
@ -64,14 +64,17 @@ export class SecuritySolutionServerlessPlugin
if (shouldRegister) {
const productTypesStr = JSON.stringify(this.config.productTypes, null, 2);
this.logger.info(`Security Solution running with product types:\n${productTypesStr}`);
const appFeaturesConfigurator = getProductAppFeaturesConfigurator(
enabledAppFeatures,
const productFeaturesConfigurator = getProductProductFeaturesConfigurator(
enabledProductFeatures,
this.config
);
pluginsSetup.securitySolution.setAppFeaturesConfigurator(appFeaturesConfigurator);
pluginsSetup.securitySolution.setProductFeaturesConfigurator(productFeaturesConfigurator);
}
enableRuleActions({ actions: pluginsSetup.actions, appFeatureKeys: enabledAppFeatures });
enableRuleActions({
actions: pluginsSetup.actions,
productFeatureKeys: enabledProductFeatures,
});
this.cloudSecurityUsageReportingTask = new SecurityUsageReportingTask({
core: coreSetup,

View file

@ -5,26 +5,29 @@
* 2.0.
*/
import type {
AppFeatureKeys,
AppFeatureKibanaConfig,
AppFeaturesAssistantConfig,
ProductFeatureKeys,
ProductFeatureKibanaConfig,
ProductFeaturesAssistantConfig,
} from '@kbn/security-solution-features';
import {
assistantDefaultAppFeaturesConfig,
createEnabledAppFeaturesConfigMap,
assistantDefaultProductFeaturesConfig,
createEnabledProductFeaturesConfigMap,
} from '@kbn/security-solution-features/config';
import type {
AppFeatureAssistantKey,
ProductFeatureAssistantKey,
AssistantSubFeatureId,
} from '@kbn/security-solution-features/keys';
export const getSecurityAssistantAppFeaturesConfigurator =
(enabledAppFeatureKeys: AppFeatureKeys) => (): AppFeaturesAssistantConfig => {
return createEnabledAppFeaturesConfigMap(assistantAppFeaturesConfig, enabledAppFeatureKeys);
export const getSecurityAssistantProductFeaturesConfigurator =
(enabledProductFeatureKeys: ProductFeatureKeys) => (): ProductFeaturesAssistantConfig => {
return createEnabledProductFeaturesConfigMap(
assistantProductFeaturesConfig,
enabledProductFeatureKeys
);
};
/**
* Maps the AppFeatures keys to Kibana privileges that will be merged
* Maps the ProductFeatures keys to Kibana privileges that will be merged
* into the base privileges config for the Security Assistant app.
*
* Privileges can be added in different ways:
@ -32,10 +35,10 @@ export const getSecurityAssistantAppFeaturesConfigurator =
* - `subFeatureIds`: the ids of the sub-features that will be added into the Assistant subFeatures entry.
* - `subFeaturesPrivileges`: the privileges that will be added into the existing Assistant subFeature with the privilege `id` specified.
*/
const assistantAppFeaturesConfig: Record<
AppFeatureAssistantKey,
AppFeatureKibanaConfig<AssistantSubFeatureId>
const assistantProductFeaturesConfig: Record<
ProductFeatureAssistantKey,
ProductFeatureKibanaConfig<AssistantSubFeatureId>
> = {
...assistantDefaultAppFeaturesConfig,
...assistantDefaultProductFeaturesConfig,
// serverless-specific app features configs here
};

View file

@ -5,27 +5,33 @@
* 2.0.
*/
import type {
AppFeatureKibanaConfig,
AppFeaturesCasesConfig,
AppFeatureKeys,
ProductFeatureKibanaConfig,
ProductFeaturesCasesConfig,
ProductFeatureKeys,
} from '@kbn/security-solution-features';
import {
getCasesDefaultAppFeaturesConfig,
createEnabledAppFeaturesConfigMap,
getCasesDefaultProductFeaturesConfig,
createEnabledProductFeaturesConfigMap,
} from '@kbn/security-solution-features/config';
import type { AppFeatureCasesKey, CasesSubFeatureId } from '@kbn/security-solution-features/keys';
import type {
ProductFeatureCasesKey,
CasesSubFeatureId,
} from '@kbn/security-solution-features/keys';
import {
CASES_CONNECTORS_CAPABILITY,
GET_CONNECTORS_CONFIGURE_API_TAG,
} from '@kbn/cases-plugin/common/constants';
export const getCasesAppFeaturesConfigurator =
(enabledAppFeatureKeys: AppFeatureKeys) => (): AppFeaturesCasesConfig => {
return createEnabledAppFeaturesConfigMap(casesAppFeaturesConfig, enabledAppFeatureKeys);
export const getCasesProductFeaturesConfigurator =
(enabledProductFeatureKeys: ProductFeatureKeys) => (): ProductFeaturesCasesConfig => {
return createEnabledProductFeaturesConfigMap(
casesProductFeaturesConfig,
enabledProductFeatureKeys
);
};
/**
* Maps the AppFeatures keys to Kibana privileges that will be merged
* Maps the ProductFeatures keys to Kibana privileges that will be merged
* into the base privileges config for the Security Cases app.
*
* Privileges can be added in different ways:
@ -33,11 +39,11 @@ export const getCasesAppFeaturesConfigurator =
* - `subFeatureIds`: the ids of the sub-features that will be added into the Cases subFeatures entry.
* - `subFeaturesPrivileges`: the privileges that will be added into the existing Cases subFeature with the privilege `id` specified.
*/
const casesAppFeaturesConfig: Record<
AppFeatureCasesKey,
AppFeatureKibanaConfig<CasesSubFeatureId>
const casesProductFeaturesConfig: Record<
ProductFeatureCasesKey,
ProductFeatureKibanaConfig<CasesSubFeatureId>
> = {
...getCasesDefaultAppFeaturesConfig({
...getCasesDefaultProductFeaturesConfig({
apiTags: { connectors: GET_CONNECTORS_CONFIGURE_API_TAG },
uiCapabilities: { connectors: CASES_CONNECTORS_CAPABILITY },
}),

View file

@ -0,0 +1,27 @@
/*
* 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 { ProductFeatureKeys } from '@kbn/security-solution-features';
import type { ProductFeaturesConfigurator } from '@kbn/security-solution-plugin/server/lib/product_features_service/types';
import type { ServerlessSecurityConfig } from '../config';
import { getCasesProductFeaturesConfigurator } from './cases_product_features_config';
import { getSecurityProductFeaturesConfigurator } from './security_product_features_config';
import { getSecurityAssistantProductFeaturesConfigurator } from './assistant_product_features_config';
export const getProductProductFeaturesConfigurator = (
enabledProductFeatureKeys: ProductFeatureKeys,
config: ServerlessSecurityConfig
): ProductFeaturesConfigurator => {
return {
security: getSecurityProductFeaturesConfigurator(
enabledProductFeatureKeys,
config.experimentalFeatures
),
cases: getCasesProductFeaturesConfigurator(enabledProductFeatureKeys),
securityAssistant: getSecurityAssistantProductFeaturesConfigurator(enabledProductFeatureKeys),
};
};

View file

@ -5,28 +5,34 @@
* 2.0.
*/
import type {
AppFeatureKeys,
AppFeatureKibanaConfig,
AppFeaturesSecurityConfig,
ProductFeatureKeys,
ProductFeatureKibanaConfig,
ProductFeaturesSecurityConfig,
} from '@kbn/security-solution-features';
import {
securityDefaultAppFeaturesConfig,
createEnabledAppFeaturesConfigMap,
securityDefaultProductFeaturesConfig,
createEnabledProductFeaturesConfigMap,
} from '@kbn/security-solution-features/config';
import { AppFeatureSecurityKey, SecuritySubFeatureId } from '@kbn/security-solution-features/keys';
import {
ProductFeatureSecurityKey,
SecuritySubFeatureId,
} from '@kbn/security-solution-features/keys';
import type { ExperimentalFeatures } from '../../common/experimental_features';
export const getSecurityAppFeaturesConfigurator =
export const getSecurityProductFeaturesConfigurator =
(
enabledAppFeatureKeys: AppFeatureKeys,
enabledProductFeatureKeys: ProductFeatureKeys,
_: ExperimentalFeatures // currently un-used, but left here as a convenience for possible future use
) =>
(): AppFeaturesSecurityConfig => {
return createEnabledAppFeaturesConfigMap(securityAppFeaturesConfig, enabledAppFeatureKeys);
(): ProductFeaturesSecurityConfig => {
return createEnabledProductFeaturesConfigMap(
securityProductFeaturesConfig,
enabledProductFeatureKeys
);
};
/**
* Maps the AppFeatures keys to Kibana privileges that will be merged
* Maps the ProductFeatures keys to Kibana privileges that will be merged
* into the base privileges config for the Security app.
*
* Privileges can be added in different ways:
@ -34,12 +40,12 @@ export const getSecurityAppFeaturesConfigurator =
* - `subFeatureIds`: the ids of the sub-features that will be added into the Security subFeatures entry.
* - `subFeaturesPrivileges`: the privileges that will be added into the existing Security subFeature with the privilege `id` specified.
*/
const securityAppFeaturesConfig: Record<
AppFeatureSecurityKey,
AppFeatureKibanaConfig<SecuritySubFeatureId>
const securityProductFeaturesConfig: Record<
ProductFeatureSecurityKey,
ProductFeatureKibanaConfig<SecuritySubFeatureId>
> = {
...securityDefaultAppFeaturesConfig,
[AppFeatureSecurityKey.endpointExceptions]: {
...securityDefaultProductFeaturesConfig,
[ProductFeatureSecurityKey.endpointExceptions]: {
subFeatureIds: [SecuritySubFeatureId.endpointExceptions],
},
};

View file

@ -4,14 +4,14 @@
* 2.0; you may not use this file except in compliance with the Elastic License
* 2.0.
*/
import { AppFeatureSecurityKey } from '@kbn/security-solution-features/keys';
import { ProductFeatureSecurityKey } from '@kbn/security-solution-features/keys';
import {
IndexConnectorTypeId,
SlackWebhookConnectorTypeId,
EmailConnectorTypeId,
} from '@kbn/stack-connectors-plugin/server/connector_types';
import { EnabledActionTypes } from '@kbn/actions-plugin/server/config';
import type { AppFeatureKeys } from '@kbn/security-solution-features/src/types';
import type { ProductFeatureKeys } from '@kbn/security-solution-features';
import type { PluginSetupContract as ActionsPluginSetupContract } from '@kbn/actions-plugin/server';
@ -22,16 +22,16 @@ const INTERNAL_RULE_ACTIONS = [
];
/**
* enable rule actions based on AppFeature Config
* enable rule actions based on ProductFeature Config
*/
export const enableRuleActions = ({
actions,
appFeatureKeys,
productFeatureKeys,
}: {
actions: ActionsPluginSetupContract;
appFeatureKeys: AppFeatureKeys;
productFeatureKeys: ProductFeatureKeys;
}) => {
if (appFeatureKeys.includes(AppFeatureSecurityKey.externalRuleActions)) {
if (productFeatureKeys.includes(ProductFeatureSecurityKey.externalRuleActions)) {
// enables all rule actions
actions.setEnabledConnectorTypes([EnabledActionTypes.Any]);
} else {