diff --git a/.eslintrc.js b/.eslintrc.js index e1153546e8154..5b5ef73ed3806 100644 --- a/.eslintrc.js +++ b/.eslintrc.js @@ -1180,6 +1180,7 @@ module.exports = { overrides: [ { files: [ + 'x-pack/packages/security-solution/features/**/*.{js,mjs,ts,tsx}', 'x-pack/packages/security-solution/navigation/**/*.{js,mjs,ts,tsx}', 'x-pack/plugins/security_solution/**/*.{js,mjs,ts,tsx}', 'x-pack/plugins/security_solution_ess/**/*.{js,mjs,ts,tsx}', diff --git a/.github/CODEOWNERS b/.github/CODEOWNERS index 4f050e3bf422f..847c11ea9c6fb 100644 --- a/.github/CODEOWNERS +++ b/.github/CODEOWNERS @@ -600,6 +600,7 @@ x-pack/plugins/searchprofiler @elastic/platform-deployment-management x-pack/test/security_api_integration/packages/helpers @elastic/kibana-security x-pack/plugins/security @elastic/kibana-security x-pack/plugins/security_solution_ess @elastic/security-solution +x-pack/packages/security-solution/features @elastic/security-threat-hunting-explore x-pack/test/cases_api_integration/common/plugins/security_solution @elastic/response-ops x-pack/packages/security-solution/navigation @elastic/security-threat-hunting-explore x-pack/plugins/security_solution @elastic/security-solution diff --git a/package.json b/package.json index acfeec966f0a0..28e41434a567c 100644 --- a/package.json +++ b/package.json @@ -605,6 +605,7 @@ "@kbn/searchprofiler-plugin": "link:x-pack/plugins/searchprofiler", "@kbn/security-plugin": "link:x-pack/plugins/security", "@kbn/security-solution-ess": "link:x-pack/plugins/security_solution_ess", + "@kbn/security-solution-features": "link:x-pack/packages/security-solution/features", "@kbn/security-solution-fixtures-plugin": "link:x-pack/test/cases_api_integration/common/plugins/security_solution", "@kbn/security-solution-navigation": "link:x-pack/packages/security-solution/navigation", "@kbn/security-solution-plugin": "link:x-pack/plugins/security_solution", diff --git a/packages/kbn-optimizer/limits.yml b/packages/kbn-optimizer/limits.yml index dc07f316c5244..d68f1f8a8caed 100644 --- a/packages/kbn-optimizer/limits.yml +++ b/packages/kbn-optimizer/limits.yml @@ -121,7 +121,7 @@ pageLoadAssetSize: security: 81771 securitySolution: 66738 securitySolutionEss: 16573 - securitySolutionServerless: 40000 + securitySolutionServerless: 45000 serverless: 16573 serverlessObservability: 68747 serverlessSearch: 71995 diff --git a/packages/kbn-utility-types/index.ts b/packages/kbn-utility-types/index.ts index 3aeaf04083c66..21ed1425a1cb2 100644 --- a/packages/kbn-utility-types/index.ts +++ b/packages/kbn-utility-types/index.ts @@ -151,3 +151,23 @@ export type ArrayElement = A extends ReadonlyArray ? T : never; export type WithRequiredProperty = Omit & { [Property in Key]-?: Type[Property]; }; + +// Recursive partial object type. inspired by EUI RecursivePartial +export type RecursivePartial = { + [P in keyof T]?: T[P] extends NonAny[] + ? T[P] + : T[P] extends readonly NonAny[] + ? T[P] + : T[P] extends Array + ? Array> + : T[P] extends ReadonlyArray + ? ReadonlyArray> + : T[P] extends Set + ? Set> + : T[P] extends Map + ? Map> + : T[P] extends NonAny + ? T[P] + : RecursivePartial; +}; +type NonAny = number | boolean | string | symbol | null; diff --git a/tsconfig.base.json b/tsconfig.base.json index a347a249b68ca..56c849d8611e7 100644 --- a/tsconfig.base.json +++ b/tsconfig.base.json @@ -1194,6 +1194,8 @@ "@kbn/security-plugin/*": ["x-pack/plugins/security/*"], "@kbn/security-solution-ess": ["x-pack/plugins/security_solution_ess"], "@kbn/security-solution-ess/*": ["x-pack/plugins/security_solution_ess/*"], + "@kbn/security-solution-features": ["x-pack/packages/security-solution/features"], + "@kbn/security-solution-features/*": ["x-pack/packages/security-solution/features/*"], "@kbn/security-solution-fixtures-plugin": ["x-pack/test/cases_api_integration/common/plugins/security_solution"], "@kbn/security-solution-fixtures-plugin/*": ["x-pack/test/cases_api_integration/common/plugins/security_solution/*"], "@kbn/security-solution-navigation": ["x-pack/packages/security-solution/navigation"], diff --git a/x-pack/packages/security-solution/features/README.mdx b/x-pack/packages/security-solution/features/README.mdx new file mode 100644 index 0000000000000..e87fe71c4fac9 --- /dev/null +++ b/x-pack/packages/security-solution/features/README.mdx @@ -0,0 +1,4 @@ +## Security Solution App Features + +This package provides resources to be used for Security Solution app features + diff --git a/x-pack/packages/security-solution/features/app_features.ts b/x-pack/packages/security-solution/features/app_features.ts new file mode 100644 index 0000000000000..b9209441cff85 --- /dev/null +++ b/x-pack/packages/security-solution/features/app_features.ts @@ -0,0 +1,10 @@ +/* + * 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. + */ + +export { getSecurityFeature } from './src/security'; +export { getCasesFeature } from './src/cases'; +export { getAssistantFeature } from './src/assistant'; diff --git a/x-pack/packages/security-solution/features/config.ts b/x-pack/packages/security-solution/features/config.ts new file mode 100644 index 0000000000000..8f382fc13487f --- /dev/null +++ b/x-pack/packages/security-solution/features/config.ts @@ -0,0 +1,12 @@ +/* + * 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. + */ + +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 { createEnabledAppFeaturesConfigMap } from './src/helpers'; diff --git a/x-pack/packages/security-solution/features/index.ts b/x-pack/packages/security-solution/features/index.ts new file mode 100644 index 0000000000000..a7fe0b5131c73 --- /dev/null +++ b/x-pack/packages/security-solution/features/index.ts @@ -0,0 +1,7 @@ +/* + * 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. + */ +export * from './src/types'; diff --git a/x-pack/packages/security-solution/features/jest.config.js b/x-pack/packages/security-solution/features/jest.config.js new file mode 100644 index 0000000000000..47da21e7adff0 --- /dev/null +++ b/x-pack/packages/security-solution/features/jest.config.js @@ -0,0 +1,12 @@ +/* + * 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. + */ + +module.exports = { + preset: '@kbn/test', + rootDir: '../../../..', + roots: ['/x-pack/packages/security-solution/features'], +}; diff --git a/x-pack/plugins/security_solution/server/lib/app_features/index.ts b/x-pack/packages/security-solution/features/keys.ts similarity index 84% rename from x-pack/plugins/security_solution/server/lib/app_features/index.ts rename to x-pack/packages/security-solution/features/keys.ts index 5f30b21f9c862..11063c154567c 100644 --- a/x-pack/plugins/security_solution/server/lib/app_features/index.ts +++ b/x-pack/packages/security-solution/features/keys.ts @@ -5,4 +5,4 @@ * 2.0. */ -export { AppFeatures } from './app_features'; +export * from './src/app_features_keys'; diff --git a/x-pack/packages/security-solution/features/kibana.jsonc b/x-pack/packages/security-solution/features/kibana.jsonc new file mode 100644 index 0000000000000..0e5a360ea9929 --- /dev/null +++ b/x-pack/packages/security-solution/features/kibana.jsonc @@ -0,0 +1,5 @@ +{ + "type": "shared-common", + "id": "@kbn/security-solution-features", + "owner": "@elastic/security-threat-hunting-explore" +} diff --git a/x-pack/packages/security-solution/features/package.json b/x-pack/packages/security-solution/features/package.json new file mode 100644 index 0000000000000..77abf87117eb8 --- /dev/null +++ b/x-pack/packages/security-solution/features/package.json @@ -0,0 +1,6 @@ +{ + "name": "@kbn/security-solution-features", + "private": true, + "version": "1.0.0", + "license": "Elastic License 2.0" +} \ No newline at end of file diff --git a/x-pack/packages/security-solution/features/privileges.ts b/x-pack/packages/security-solution/features/privileges.ts new file mode 100644 index 0000000000000..2e5a99095e4f5 --- /dev/null +++ b/x-pack/packages/security-solution/features/privileges.ts @@ -0,0 +1,7 @@ +/* + * 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. + */ +export { AppFeaturesPrivilegeId, AppFeaturesPrivileges } from './src/app_features_privileges'; diff --git a/x-pack/plugins/security_solution/common/types/app_features.ts b/x-pack/packages/security-solution/features/src/app_features_keys.ts similarity index 64% rename from x-pack/plugins/security_solution/common/types/app_features.ts rename to x-pack/packages/security-solution/features/src/app_features_keys.ts index a8c65aeadfc8a..ea3939e2b9f28 100644 --- a/x-pack/plugins/security_solution/common/types/app_features.ts +++ b/x-pack/packages/security-solution/features/src/app_features_keys.ts @@ -6,60 +6,48 @@ */ export enum AppFeatureSecurityKey { - /** - * Enables Advanced Insights (Entity Risk, GenAI) - */ + /** Enables Advanced Insights (Entity Risk, GenAI) */ advancedInsights = 'advanced_insights', - /** * Enables Investigation guide in Timeline */ investigationGuide = 'investigation_guide', - /** * Enables access to the Endpoint List and associated views that allows management of hosts * running endpoint security */ endpointHostManagement = 'endpoint_host_management', - /** * Enables endpoint policy views that enables user to manage endpoint security policies */ endpointPolicyManagement = 'endpoint_policy_management', - /** * Enables Endpoint Policy protections (like Malware, Ransomware, etc) */ endpointPolicyProtections = 'endpoint_policy_protections', - /** * Enables management of all endpoint related artifacts (ex. Trusted Applications, Event Filters, * Host Isolation Exceptions, Blocklist. */ endpointArtifactManagement = 'endpoint_artifact_management', - /** * Enables all of endpoint's supported response actions - like host isolation, file operations, * process operations, command execution, etc. */ endpointResponseActions = 'endpoint_response_actions', - /** * Enables Threat Intelligence */ threatIntelligence = 'threat-intelligence', - /** * Enables Osquery Response Actions */ osqueryAutomatedResponseActions = 'osquery_automated_response_actions', -} -export enum AppFeatureAssistantKey { /** - * Enables Elastic AI Assistant + * Enables managing endpoint exceptions on rules and alerts */ - assistant = 'assistant', + endpointExceptions = 'endpointExceptions', } export enum AppFeatureCasesKey { @@ -69,14 +57,46 @@ export enum AppFeatureCasesKey { casesConnectors = 'cases_connectors', } -// Merges the two enums. -export type AppFeatureKey = AppFeatureSecurityKey | AppFeatureCasesKey | AppFeatureAssistantKey; -export type AppFeatureKeys = AppFeatureKey[]; +export enum AppFeatureAssistantKey { + /** + * Enables Elastic AI Assistant + */ + assistant = 'assistant', +} -// We need to merge the value and the type and export both to replicate how enum works. +// Merges the two enums. export const AppFeatureKey = { ...AppFeatureSecurityKey, ...AppFeatureCasesKey, ...AppFeatureAssistantKey, }; +// We need to merge the value and the type and export both to replicate how enum works. +export type AppFeatureKeyType = AppFeatureSecurityKey | AppFeatureCasesKey | AppFeatureAssistantKey; + export const ALL_APP_FEATURE_KEYS = Object.freeze(Object.values(AppFeatureKey)); + +/** Sub-features IDs for Security */ +export enum SecuritySubFeatureId { + endpointList = 'endpointListSubFeature', + endpointExceptions = 'endpointExceptionsSubFeature', + trustedApplications = 'trustedApplicationsSubFeature', + hostIsolationExceptions = 'hostIsolationExceptionsSubFeature', + blocklist = 'blocklistSubFeature', + eventFilters = 'eventFiltersSubFeature', + policyManagement = 'policyManagementSubFeature', + responseActionsHistory = 'responseActionsHistorySubFeature', + hostIsolation = 'hostIsolationSubFeature', + processOperations = 'processOperationsSubFeature', + fileOperations = 'fileOperationsSubFeature', + executeAction = 'executeActionSubFeature', +} + +/** Sub-features IDs for Cases */ +export enum CasesSubFeatureId { + deleteCases = 'deleteCasesSubFeature', +} + +/** Sub-features IDs for Security Assistant */ +export enum AssistantSubFeatureId { + createConversation = 'createConversationSubFeature', +} diff --git a/x-pack/packages/security-solution/features/src/app_features_privileges.ts b/x-pack/packages/security-solution/features/src/app_features_privileges.ts new file mode 100644 index 0000000000000..24fe0e25a19cc --- /dev/null +++ b/x-pack/packages/security-solution/features/src/app_features_privileges.ts @@ -0,0 +1,30 @@ +/* + * 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 { APP_ID } from './constants'; + +export enum AppFeaturesPrivilegeId { + endpointExceptions = 'endpoint_exceptions', +} + +/** + * This is the mapping of the privileges that are registered + * using a different Kibana feature configuration (sub-feature, main feature privilege, etc) + * in each offering type (ess, serverless) + */ +export const AppFeaturesPrivileges = { + [AppFeaturesPrivilegeId.endpointExceptions]: { + all: { + ui: ['showEndpointExceptions', 'crudEndpointExceptions'], + api: [`${APP_ID}-showEndpointExceptions`, `${APP_ID}-crudEndpointExceptions`], + }, + read: { + ui: ['showEndpointExceptions'], + api: [`${APP_ID}-showEndpointExceptions`], + }, + }, +}; diff --git a/x-pack/packages/security-solution/features/src/assistant/app_feature_config.ts b/x-pack/packages/security-solution/features/src/assistant/app_feature_config.ts new file mode 100644 index 0000000000000..b55c43f82c953 --- /dev/null +++ b/x-pack/packages/security-solution/features/src/assistant/app_feature_config.ts @@ -0,0 +1,33 @@ +/* + * 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 { AssistantSubFeatureId } from '../app_features_keys'; +import { AppFeatureAssistantKey } from '../app_features_keys'; +import type { AppFeatureKibanaConfig } from '../types'; + +/** + * App features privileges configuration for the Security Assistant Kibana Feature app. + * These are the configs that are shared between both offering types (ess and serverless). + * They can be extended on each offering plugin to register privileges using different way on each offering type. + * + * Privileges can be added in different ways: + * - `privileges`: the privileges that will be added directly into the main Security feature. + * - `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 +> = { + [AppFeatureAssistantKey.assistant]: { + privileges: { + all: { + ui: ['ai-assistant'], + }, + }, + }, +}; diff --git a/x-pack/packages/security-solution/features/src/assistant/index.ts b/x-pack/packages/security-solution/features/src/assistant/index.ts new file mode 100644 index 0000000000000..d1319fd637913 --- /dev/null +++ b/x-pack/packages/security-solution/features/src/assistant/index.ts @@ -0,0 +1,19 @@ +/* + * 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 { AssistantSubFeatureId } from '../app_features_keys'; +import type { AppFeatureParams } from '../types'; +import { getAssistantBaseKibanaFeature } from './kibana_features'; +import { + getAssistantBaseKibanaSubFeatureIds, + assistantSubFeaturesMap, +} from './kibana_sub_features'; + +export const getAssistantFeature = (): AppFeatureParams => ({ + baseKibanaFeature: getAssistantBaseKibanaFeature(), + baseKibanaSubFeatureIds: getAssistantBaseKibanaSubFeatureIds(), + subFeaturesMap: assistantSubFeaturesMap, +}); diff --git a/x-pack/packages/security-solution/features/src/assistant/kibana_features.ts b/x-pack/packages/security-solution/features/src/assistant/kibana_features.ts new file mode 100644 index 0000000000000..e04b1f44df739 --- /dev/null +++ b/x-pack/packages/security-solution/features/src/assistant/kibana_features.ts @@ -0,0 +1,48 @@ +/* + * 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 { i18n } from '@kbn/i18n'; + +import { DEFAULT_APP_CATEGORIES } from '@kbn/core-application-common'; +import { type BaseKibanaFeatureConfig } from '../types'; +import { APP_ID, ASSISTANT_FEATURE_ID } from '../constants'; + +export const getAssistantBaseKibanaFeature = (): BaseKibanaFeatureConfig => ({ + id: ASSISTANT_FEATURE_ID, + name: i18n.translate( + 'securitySolutionPackages.features.featureRegistry.linkSecuritySolutionAssistantTitle', + { + defaultMessage: 'Elastic AI Assistant', + } + ), + order: 1100, + category: DEFAULT_APP_CATEGORIES.security, + app: [ASSISTANT_FEATURE_ID, 'kibana'], + catalogue: [APP_ID], + minimumLicense: 'enterprise', + privileges: { + all: { + api: [], + app: [ASSISTANT_FEATURE_ID, 'kibana'], + catalogue: [APP_ID], + savedObject: { + all: [], + read: [], + }, + ui: [], + }, + read: { + // No read-only mode currently supported + disabled: true, + savedObject: { + all: [], + read: [], + }, + ui: [], + }, + }, +}); diff --git a/x-pack/plugins/security_solution/server/lib/app_features/security_assistant_kibana_sub_features.ts b/x-pack/packages/security-solution/features/src/assistant/kibana_sub_features.ts similarity index 66% rename from x-pack/plugins/security_solution/server/lib/app_features/security_assistant_kibana_sub_features.ts rename to x-pack/packages/security-solution/features/src/assistant/kibana_sub_features.ts index bc495e8c24d60..253b98f602c92 100644 --- a/x-pack/plugins/security_solution/server/lib/app_features/security_assistant_kibana_sub_features.ts +++ b/x-pack/packages/security-solution/features/src/assistant/kibana_sub_features.ts @@ -12,13 +12,13 @@ import type { SubFeatureConfig } from '@kbn/features-plugin/common'; // @ts-expect-error unused variable const createConversationSubFeature: SubFeatureConfig = { name: i18n.translate( - 'xpack.securitySolution.featureRegistry.assistant.createConversationSubFeatureName', + 'securitySolutionPackages.features.featureRegistry.assistant.createConversationSubFeatureName', { defaultMessage: 'Create Conversations', } ), description: i18n.translate( - 'xpack.securitySolution.featureRegistry.subFeatures.assistant.description', + 'securitySolutionPackages.features.featureRegistry.subFeatures.assistant.description', { defaultMessage: 'Create custom conversations.' } ), privilegeGroups: [ @@ -29,7 +29,7 @@ const createConversationSubFeature: SubFeatureConfig = { api: [], id: 'create_conversation', name: i18n.translate( - 'xpack.securitySolution.featureRegistry.assistant.createConversationSubFeatureDetails', + 'securitySolutionPackages.features.featureRegistry.assistant.createConversationSubFeatureDetails', { defaultMessage: 'Create conversations', } @@ -50,7 +50,19 @@ export enum AssistantSubFeatureId { createConversation = 'createConversationSubFeature', } -// Defines all the ordered Security Assistant subFeatures available +/** + * Sub-features that will always be available for Security Assistant + * regardless of the product type. + */ +export const getAssistantBaseKibanaSubFeatureIds = (): AssistantSubFeatureId[] => [ + // This is a sample sub-feature that can be used for future implementations + // AssistantSubFeatureId.createConversation, +]; + +/** + * Defines all the Security Assistant subFeatures available. + * The order of the subFeatures is the order they will be displayed + */ export const assistantSubFeaturesMap = Object.freeze( new Map([ // This is a sample sub-feature that can be used for future implementations diff --git a/x-pack/packages/security-solution/features/src/cases/app_feature_config.ts b/x-pack/packages/security-solution/features/src/cases/app_feature_config.ts new file mode 100644 index 0000000000000..cfad7bfa7715d --- /dev/null +++ b/x-pack/packages/security-solution/features/src/cases/app_feature_config.ts @@ -0,0 +1,44 @@ +/* + * 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 { AppFeatureCasesKey } from '../app_features_keys'; +import { APP_ID } from '../constants'; +import type { DefaultCasesAppFeaturesConfig } from './types'; + +/** + * App features privileges configuration for the Security Cases Kibana Feature app. + * These are the configs that are shared between both offering types (ess and serverless). + * They can be extended on each offering plugin to register privileges using different way on each offering type. + * + * Privileges can be added in different ways: + * - `privileges`: the privileges that will be added directly into the main Security feature. + * - `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 = ({ + apiTags, + uiCapabilities, +}: { + apiTags: { connectors: string }; + uiCapabilities: { connectors: string }; +}): DefaultCasesAppFeaturesConfig => ({ + [AppFeatureCasesKey.casesConnectors]: { + privileges: { + all: { + api: [apiTags.connectors], + ui: [uiCapabilities.connectors], + cases: { + push: [APP_ID], + }, + }, + read: { + api: [apiTags.connectors], + ui: [uiCapabilities.connectors], + }, + }, + }, +}); diff --git a/x-pack/packages/security-solution/features/src/cases/index.ts b/x-pack/packages/security-solution/features/src/cases/index.ts new file mode 100644 index 0000000000000..dbc0355d36565 --- /dev/null +++ b/x-pack/packages/security-solution/features/src/cases/index.ts @@ -0,0 +1,19 @@ +/* + * 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 { CasesSubFeatureId } from '../app_features_keys'; +import type { AppFeatureParams } 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 => ({ + baseKibanaFeature: getCasesBaseKibanaFeature(params), + baseKibanaSubFeatureIds: getCasesBaseKibanaSubFeatureIds(), + subFeaturesMap: getCasesSubFeaturesMap(params), +}); diff --git a/x-pack/packages/security-solution/features/src/cases/kibana_features.ts b/x-pack/packages/security-solution/features/src/cases/kibana_features.ts new file mode 100644 index 0000000000000..a8da25bb6e40b --- /dev/null +++ b/x-pack/packages/security-solution/features/src/cases/kibana_features.ts @@ -0,0 +1,64 @@ +/* + * 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 { i18n } from '@kbn/i18n'; + +import { DEFAULT_APP_CATEGORIES } from '@kbn/core-application-common'; +import type { BaseKibanaFeatureConfig } from '../types'; +import { APP_ID, CASES_FEATURE_ID } from '../constants'; +import type { CasesFeatureParams } from './types'; + +export const getCasesBaseKibanaFeature = ({ + uiCapabilities, + apiTags, + savedObjects, +}: CasesFeatureParams): BaseKibanaFeatureConfig => { + return { + id: CASES_FEATURE_ID, + name: i18n.translate( + 'securitySolutionPackages.features.featureRegistry.linkSecuritySolutionCaseTitle', + { + defaultMessage: 'Cases', + } + ), + order: 1100, + category: DEFAULT_APP_CATEGORIES.security, + app: [CASES_FEATURE_ID, 'kibana'], + catalogue: [APP_ID], + cases: [APP_ID], + privileges: { + all: { + api: apiTags.all, + app: [CASES_FEATURE_ID, 'kibana'], + catalogue: [APP_ID], + cases: { + create: [APP_ID], + read: [APP_ID], + update: [APP_ID], + }, + savedObject: { + all: [...savedObjects.files], + read: [...savedObjects.files], + }, + ui: uiCapabilities.all, + }, + read: { + api: apiTags.read, + app: [CASES_FEATURE_ID, 'kibana'], + catalogue: [APP_ID], + cases: { + read: [APP_ID], + }, + savedObject: { + all: [], + read: [...savedObjects.files], + }, + ui: uiCapabilities.read, + }, + }, + }; +}; diff --git a/x-pack/packages/security-solution/features/src/cases/kibana_sub_features.ts b/x-pack/packages/security-solution/features/src/cases/kibana_sub_features.ts new file mode 100644 index 0000000000000..3cbdb3f0e9123 --- /dev/null +++ b/x-pack/packages/security-solution/features/src/cases/kibana_sub_features.ts @@ -0,0 +1,66 @@ +/* + * 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 { i18n } from '@kbn/i18n'; +import type { SubFeatureConfig } from '@kbn/features-plugin/common'; +import { CasesSubFeatureId } from '../app_features_keys'; +import { APP_ID } from '../constants'; +import type { CasesFeatureParams } from './types'; + +/** + * Sub-features that will always be available for Security Cases + * regardless of the product type. + */ +export const getCasesBaseKibanaSubFeatureIds = (): CasesSubFeatureId[] => [ + CasesSubFeatureId.deleteCases, +]; + +/** + * Defines all the Security Assistant subFeatures available. + * The order of the subFeatures is the order they will be displayed + */ +export const getCasesSubFeaturesMap = ({ + uiCapabilities, + apiTags, + savedObjects, +}: CasesFeatureParams) => { + const deleteCasesSubFeature: SubFeatureConfig = { + name: i18n.translate('securitySolutionPackages.features.featureRegistry.deleteSubFeatureName', { + defaultMessage: 'Delete', + }), + privilegeGroups: [ + { + groupType: 'independent', + privileges: [ + { + api: apiTags.delete, + id: 'cases_delete', + name: i18n.translate( + 'securitySolutionPackages.features.featureRegistry.deleteSubFeatureDetails', + { + defaultMessage: 'Delete cases and comments', + } + ), + includeIn: 'all', + savedObject: { + all: [...savedObjects.files], + read: [...savedObjects.files], + }, + cases: { + delete: [APP_ID], + }, + ui: uiCapabilities.delete, + }, + ], + }, + ], + }; + + return new Map([ + [CasesSubFeatureId.deleteCases, deleteCasesSubFeature], + ]); +}; diff --git a/x-pack/packages/security-solution/features/src/cases/types.ts b/x-pack/packages/security-solution/features/src/cases/types.ts new file mode 100644 index 0000000000000..b7c093b0cadc3 --- /dev/null +++ b/x-pack/packages/security-solution/features/src/cases/types.ts @@ -0,0 +1,21 @@ +/* + * 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 { CasesUiCapabilities, CasesApiTags } from '@kbn/cases-plugin/common'; +import type { AppFeatureCasesKey, CasesSubFeatureId } from '../app_features_keys'; +import type { AppFeatureKibanaConfig } from '../types'; + +export interface CasesFeatureParams { + uiCapabilities: CasesUiCapabilities; + apiTags: CasesApiTags; + savedObjects: { files: string[] }; +} + +export type DefaultCasesAppFeaturesConfig = Record< + AppFeatureCasesKey, + AppFeatureKibanaConfig +>; diff --git a/x-pack/packages/security-solution/features/src/constants.ts b/x-pack/packages/security-solution/features/src/constants.ts new file mode 100644 index 0000000000000..2054749d0eabb --- /dev/null +++ b/x-pack/packages/security-solution/features/src/constants.ts @@ -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. + */ + +// Same as the plugin id defined by Security Solution +export const APP_ID = 'securitySolution' as const; +export const SERVER_APP_ID = 'siem' as const; + +export const CASES_FEATURE_ID = 'securitySolutionCases' as const; +export const ASSISTANT_FEATURE_ID = 'securitySolutionAssistant' as const; + +// Same as the plugin id defined by Cloud Security Posture +export const CLOUD_POSTURE_APP_ID = 'csp' as const; + +/** + * Id for the notifications alerting type + * @deprecated Once we are confident all rules relying on side-car actions SO's have been migrated to SO references we should remove this function + */ +export const LEGACY_NOTIFICATIONS_ID = `siem.notifications` as const; diff --git a/x-pack/packages/security-solution/features/src/helpers.ts b/x-pack/packages/security-solution/features/src/helpers.ts new file mode 100644 index 0000000000000..1beb8a3a6284c --- /dev/null +++ b/x-pack/packages/security-solution/features/src/helpers.ts @@ -0,0 +1,30 @@ +/* + * 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, AppFeatureKeyType, AppFeatureKibanaConfig } from './types'; + +/** + * Creates the AppFeaturesConfig Map from the given appFeatures object and a set of enabled appFeatures keys. + */ +export const createEnabledAppFeaturesConfigMap = < + K extends AppFeatureKeyType, + T extends string = string +>( + appFeatures: Record>, + enabledAppFeaturesKeys: AppFeatureKeys +) => { + return new Map( + Object.entries>(appFeatures).reduce< + Array<[K, AppFeatureKibanaConfig]> + >((acc, [key, value]) => { + if (enabledAppFeaturesKeys.includes(key as K)) { + acc.push([key as K, value]); + } + return acc; + }, []) + ); +}; diff --git a/x-pack/packages/security-solution/features/src/security/app_feature_config.ts b/x-pack/packages/security-solution/features/src/security/app_feature_config.ts new file mode 100644 index 0000000000000..a27dccd6c5bf6 --- /dev/null +++ b/x-pack/packages/security-solution/features/src/security/app_feature_config.ts @@ -0,0 +1,109 @@ +/* + * 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 { AppFeatureSecurityKey, SecuritySubFeatureId } from '../app_features_keys'; +import { APP_ID } from '../constants'; +import type { DefaultSecurityAppFeaturesConfig } from './types'; + +/** + * App features privileges configuration for the Security Solution Kibana Feature app. + * These are the configs that are shared between both offering types (ess and serverless). + * They can be extended on each offering plugin to register privileges using different way on each offering type. + * + * Privileges can be added in different ways: + * - `privileges`: the privileges that will be added directly into the main Security feature. + * - `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]: { + privileges: { + all: { + ui: ['entity-analytics'], + api: [`${APP_ID}-entity-analytics`], + }, + read: { + ui: ['entity-analytics'], + api: [`${APP_ID}-entity-analytics`], + }, + }, + }, + [AppFeatureSecurityKey.investigationGuide]: { + privileges: { + all: { + ui: ['investigation-guide'], + }, + read: { + ui: ['investigation-guide'], + }, + }, + }, + + [AppFeatureSecurityKey.threatIntelligence]: { + privileges: { + all: { + ui: ['threat-intelligence'], + api: [`${APP_ID}-threat-intelligence`], + }, + read: { + ui: ['threat-intelligence'], + api: [`${APP_ID}-threat-intelligence`], + }, + }, + }, + + [AppFeatureSecurityKey.endpointHostManagement]: { + subFeatureIds: [SecuritySubFeatureId.endpointList], + }, + + [AppFeatureSecurityKey.endpointPolicyManagement]: { + subFeatureIds: [SecuritySubFeatureId.policyManagement], + }, + + // Adds no additional kibana feature controls + [AppFeatureSecurityKey.endpointPolicyProtections]: {}, + + [AppFeatureSecurityKey.endpointArtifactManagement]: { + subFeatureIds: [ + SecuritySubFeatureId.trustedApplications, + SecuritySubFeatureId.blocklist, + SecuritySubFeatureId.eventFilters, + ], + subFeaturesPrivileges: [ + { + id: 'host_isolation_exceptions_all', + api: [`${APP_ID}-accessHostIsolationExceptions`, `${APP_ID}-writeHostIsolationExceptions`], + ui: ['accessHostIsolationExceptions', 'writeHostIsolationExceptions'], + }, + { + id: 'host_isolation_exceptions_read', + api: [`${APP_ID}-accessHostIsolationExceptions`], + ui: ['accessHostIsolationExceptions'], + }, + ], + }, + + [AppFeatureSecurityKey.endpointResponseActions]: { + subFeatureIds: [ + SecuritySubFeatureId.hostIsolationExceptions, + SecuritySubFeatureId.responseActionsHistory, + SecuritySubFeatureId.hostIsolation, + SecuritySubFeatureId.processOperations, + SecuritySubFeatureId.fileOperations, + SecuritySubFeatureId.executeAction, + ], + subFeaturesPrivileges: [ + { + id: 'host_isolation_all', + api: [`${APP_ID}-writeHostIsolation`], + ui: ['writeHostIsolation'], + }, + ], + }, + + [AppFeatureSecurityKey.osqueryAutomatedResponseActions]: {}, +}; diff --git a/x-pack/packages/security-solution/features/src/security/index.ts b/x-pack/packages/security-solution/features/src/security/index.ts new file mode 100644 index 0000000000000..67f72361fb0cc --- /dev/null +++ b/x-pack/packages/security-solution/features/src/security/index.ts @@ -0,0 +1,19 @@ +/* + * 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 { SecuritySubFeatureId } from '../app_features_keys'; +import type { AppFeatureParams } 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 => ({ + baseKibanaFeature: getSecurityBaseKibanaFeature(params), + baseKibanaSubFeatureIds: getSecurityBaseKibanaSubFeatureIds(params), + subFeaturesMap: securitySubFeaturesMap, +}); diff --git a/x-pack/packages/security-solution/features/src/security/kibana_features.ts b/x-pack/packages/security-solution/features/src/security/kibana_features.ts new file mode 100644 index 0000000000000..34252ec1a35be --- /dev/null +++ b/x-pack/packages/security-solution/features/src/security/kibana_features.ts @@ -0,0 +1,105 @@ +/* + * 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 { i18n } from '@kbn/i18n'; + +import { DEFAULT_APP_CATEGORIES } from '@kbn/core-application-common'; +import { + EQL_RULE_TYPE_ID, + INDICATOR_RULE_TYPE_ID, + ML_RULE_TYPE_ID, + NEW_TERMS_RULE_TYPE_ID, + QUERY_RULE_TYPE_ID, + SAVED_QUERY_RULE_TYPE_ID, + THRESHOLD_RULE_TYPE_ID, +} from '@kbn/securitysolution-rules'; +import type { BaseKibanaFeatureConfig } from '../types'; +import { APP_ID, SERVER_APP_ID, LEGACY_NOTIFICATIONS_ID, CLOUD_POSTURE_APP_ID } from '../constants'; +import type { SecurityFeatureParams } from './types'; + +const SECURITY_RULE_TYPES = [ + LEGACY_NOTIFICATIONS_ID, + EQL_RULE_TYPE_ID, + INDICATOR_RULE_TYPE_ID, + ML_RULE_TYPE_ID, + QUERY_RULE_TYPE_ID, + SAVED_QUERY_RULE_TYPE_ID, + THRESHOLD_RULE_TYPE_ID, + NEW_TERMS_RULE_TYPE_ID, +]; + +export const getSecurityBaseKibanaFeature = ({ + savedObjects, +}: SecurityFeatureParams): BaseKibanaFeatureConfig => ({ + id: SERVER_APP_ID, + name: i18n.translate( + 'securitySolutionPackages.features.featureRegistry.linkSecuritySolutionTitle', + { + defaultMessage: 'Security', + } + ), + order: 1100, + category: DEFAULT_APP_CATEGORIES.security, + app: [APP_ID, CLOUD_POSTURE_APP_ID, 'kibana'], + catalogue: [APP_ID], + management: { + insightsAndAlerting: ['triggersActions'], + }, + alerting: SECURITY_RULE_TYPES, + privileges: { + all: { + app: [APP_ID, CLOUD_POSTURE_APP_ID, 'kibana'], + catalogue: [APP_ID], + api: [ + APP_ID, + 'lists-all', + 'lists-read', + 'lists-summary', + 'rac', + 'cloud-security-posture-all', + 'cloud-security-posture-read', + ], + savedObject: { + all: ['alert', ...savedObjects], + read: [], + }, + alerting: { + rule: { + all: SECURITY_RULE_TYPES, + }, + alert: { + all: SECURITY_RULE_TYPES, + }, + }, + management: { + insightsAndAlerting: ['triggersActions'], + }, + ui: ['show', 'crud'], + }, + read: { + app: [APP_ID, CLOUD_POSTURE_APP_ID, 'kibana'], + catalogue: [APP_ID], + api: [APP_ID, 'lists-read', 'rac', 'cloud-security-posture-read'], + savedObject: { + all: [], + read: [...savedObjects], + }, + alerting: { + rule: { + read: SECURITY_RULE_TYPES, + }, + alert: { + all: SECURITY_RULE_TYPES, + }, + }, + management: { + insightsAndAlerting: ['triggersActions'], + }, + ui: ['show'], + }, + }, +}); diff --git a/x-pack/plugins/security_solution/server/lib/app_features/security_kibana_sub_features.ts b/x-pack/packages/security-solution/features/src/security/kibana_sub_features.ts similarity index 68% rename from x-pack/plugins/security_solution/server/lib/app_features/security_kibana_sub_features.ts rename to x-pack/packages/security-solution/features/src/security/kibana_sub_features.ts index a8410e4e4253d..86cbf89f26a6f 100644 --- a/x-pack/plugins/security_solution/server/lib/app_features/security_kibana_sub_features.ts +++ b/x-pack/packages/security-solution/features/src/security/kibana_sub_features.ts @@ -8,21 +8,27 @@ import { i18n } from '@kbn/i18n'; import type { SubFeatureConfig } from '@kbn/features-plugin/common'; import { EXCEPTION_LIST_NAMESPACE_AGNOSTIC } from '@kbn/securitysolution-list-constants'; -import { APP_ID } from '../../../common'; +import { AppFeaturesPrivilegeId, AppFeaturesPrivileges } from '../app_features_privileges'; +import { SecuritySubFeatureId } from '../app_features_keys'; +import { APP_ID } from '../constants'; +import type { SecurityFeatureParams } from './types'; const endpointListSubFeature: SubFeatureConfig = { requireAllSpaces: true, privilegesTooltip: i18n.translate( - 'xpack.securitySolution.featureRegistry.subFeatures.endpointList.privilegesTooltip', + 'securitySolutionPackages.features.featureRegistry.subFeatures.endpointList.privilegesTooltip', { defaultMessage: 'All Spaces is required for Endpoint List access.', } ), - name: i18n.translate('xpack.securitySolution.featureRegistry.subFeatures.endpointList', { - defaultMessage: 'Endpoint List', - }), + name: i18n.translate( + 'securitySolutionPackages.features.featureRegistry.subFeatures.endpointList', + { + defaultMessage: 'Endpoint List', + } + ), description: i18n.translate( - 'xpack.securitySolution.featureRegistry.subFeatures.endpointList.description', + 'securitySolutionPackages.features.featureRegistry.subFeatures.endpointList.description', { defaultMessage: 'Displays all hosts running Elastic Defend and their relevant integration details.', @@ -61,16 +67,19 @@ const endpointListSubFeature: SubFeatureConfig = { const trustedApplicationsSubFeature: SubFeatureConfig = { requireAllSpaces: true, privilegesTooltip: i18n.translate( - 'xpack.securitySolution.featureRegistry.subFeatures.trustedApplications.privilegesTooltip', + 'securitySolutionPackages.features.featureRegistry.subFeatures.trustedApplications.privilegesTooltip', { defaultMessage: 'All Spaces is required for Trusted Applications access.', } ), - name: i18n.translate('xpack.securitySolution.featureRegistry.subFeatures.trustedApplications', { - defaultMessage: 'Trusted Applications', - }), + name: i18n.translate( + 'securitySolutionPackages.features.featureRegistry.subFeatures.trustedApplications', + { + defaultMessage: 'Trusted Applications', + } + ), description: i18n.translate( - 'xpack.securitySolution.featureRegistry.subFeatures.trustedApplications.description', + 'securitySolutionPackages.features.featureRegistry.subFeatures.trustedApplications.description', { defaultMessage: 'Helps mitigate conflicts with other software, usually other antivirus or endpoint security applications.', @@ -115,19 +124,19 @@ const trustedApplicationsSubFeature: SubFeatureConfig = { const hostIsolationExceptionsSubFeature: SubFeatureConfig = { requireAllSpaces: true, privilegesTooltip: i18n.translate( - 'xpack.securitySolution.featureRegistry.subFeatures.hostIsolationExceptions.privilegesTooltip', + 'securitySolutionPackages.features.featureRegistry.subFeatures.hostIsolationExceptions.privilegesTooltip', { defaultMessage: 'All Spaces is required for Host Isolation Exceptions access.', } ), name: i18n.translate( - 'xpack.securitySolution.featureRegistry.subFeatures.hostIsolationExceptions', + 'securitySolutionPackages.features.featureRegistry.subFeatures.hostIsolationExceptions', { defaultMessage: 'Host Isolation Exceptions', } ), description: i18n.translate( - 'xpack.securitySolution.featureRegistry.subFeatures.hostIsolationExceptions.description', + 'securitySolutionPackages.features.featureRegistry.subFeatures.hostIsolationExceptions.description', { defaultMessage: 'Add specific IP addresses that isolated hosts are still allowed to communicate with, even when isolated from the rest of the network.', @@ -172,16 +181,16 @@ const hostIsolationExceptionsSubFeature: SubFeatureConfig = { const blocklistSubFeature: SubFeatureConfig = { requireAllSpaces: true, privilegesTooltip: i18n.translate( - 'xpack.securitySolution.featureRegistry.subFeatures.blockList.privilegesTooltip', + 'securitySolutionPackages.features.featureRegistry.subFeatures.blockList.privilegesTooltip', { defaultMessage: 'All Spaces is required for Blocklist access.', } ), - name: i18n.translate('xpack.securitySolution.featureRegistry.subFeatures.blockList', { + name: i18n.translate('securitySolutionPackages.features.featureRegistry.subFeatures.blockList', { defaultMessage: 'Blocklist', }), description: i18n.translate( - 'xpack.securitySolution.featureRegistry.subFeatures.blockList.description', + 'securitySolutionPackages.features.featureRegistry.subFeatures.blockList.description', { defaultMessage: 'Extend Elastic Defend’s protection against malicious processes and protect against potentially harmful applications.', @@ -226,16 +235,19 @@ const blocklistSubFeature: SubFeatureConfig = { const eventFiltersSubFeature: SubFeatureConfig = { requireAllSpaces: true, privilegesTooltip: i18n.translate( - 'xpack.securitySolution.featureRegistry.subFeatures.eventFilters.privilegesTooltip', + 'securitySolutionPackages.features.featureRegistry.subFeatures.eventFilters.privilegesTooltip', { defaultMessage: 'All Spaces is required for Event Filters access.', } ), - name: i18n.translate('xpack.securitySolution.featureRegistry.subFeatures.eventFilters', { - defaultMessage: 'Event Filters', - }), + name: i18n.translate( + 'securitySolutionPackages.features.featureRegistry.subFeatures.eventFilters', + { + defaultMessage: 'Event Filters', + } + ), description: i18n.translate( - 'xpack.securitySolution.featureRegistry.subFeatures.eventFilters.description', + 'securitySolutionPackages.features.featureRegistry.subFeatures.eventFilters.description', { defaultMessage: 'Filter out endpoint events that you do not need or want stored in Elasticsearch.', @@ -280,16 +292,19 @@ const eventFiltersSubFeature: SubFeatureConfig = { const policyManagementSubFeature: SubFeatureConfig = { requireAllSpaces: true, privilegesTooltip: i18n.translate( - 'xpack.securitySolution.featureRegistry.subFeatures.policyManagement.privilegesTooltip', + 'securitySolutionPackages.features.featureRegistry.subFeatures.policyManagement.privilegesTooltip', { defaultMessage: 'All Spaces is required for Policy Management access.', } ), - name: i18n.translate('xpack.securitySolution.featureRegistry.subFeatures.policyManagement', { - defaultMessage: 'Elastic Defend Policy Management', - }), + name: i18n.translate( + 'securitySolutionPackages.features.featureRegistry.subFeatures.policyManagement', + { + defaultMessage: 'Elastic Defend Policy Management', + } + ), description: i18n.translate( - 'xpack.securitySolution.featureRegistry.subFeatures.policyManagement.description', + 'securitySolutionPackages.features.featureRegistry.subFeatures.policyManagement.description', { defaultMessage: 'Access the Elastic Defend integration policy to configure protections, event collection, and advanced policy features.', @@ -329,19 +344,19 @@ const policyManagementSubFeature: SubFeatureConfig = { const responseActionsHistorySubFeature: SubFeatureConfig = { requireAllSpaces: true, privilegesTooltip: i18n.translate( - 'xpack.securitySolution.featureRegistry.subFeatures.responseActionsHistory.privilegesTooltip', + 'securitySolutionPackages.features.featureRegistry.subFeatures.responseActionsHistory.privilegesTooltip', { defaultMessage: 'All Spaces is required for Response Actions History access.', } ), name: i18n.translate( - 'xpack.securitySolution.featureRegistry.subFeatures.responseActionsHistory', + 'securitySolutionPackages.features.featureRegistry.subFeatures.responseActionsHistory', { defaultMessage: 'Response Actions History', } ), description: i18n.translate( - 'xpack.securitySolution.featureRegistry.subFeatures.responseActionsHistory.description', + 'securitySolutionPackages.features.featureRegistry.subFeatures.responseActionsHistory.description', { defaultMessage: 'Access the history of response actions performed on endpoints.', } @@ -379,16 +394,19 @@ const responseActionsHistorySubFeature: SubFeatureConfig = { const hostIsolationSubFeature: SubFeatureConfig = { requireAllSpaces: true, privilegesTooltip: i18n.translate( - 'xpack.securitySolution.featureRegistry.subFeatures.hostIsolation.privilegesTooltip', + 'securitySolutionPackages.features.featureRegistry.subFeatures.hostIsolation.privilegesTooltip', { defaultMessage: 'All Spaces is required for Host Isolation access.', } ), - name: i18n.translate('xpack.securitySolution.featureRegistry.subFeatures.hostIsolation', { - defaultMessage: 'Host Isolation', - }), + name: i18n.translate( + 'securitySolutionPackages.features.featureRegistry.subFeatures.hostIsolation', + { + defaultMessage: 'Host Isolation', + } + ), description: i18n.translate( - 'xpack.securitySolution.featureRegistry.subFeatures.hostIsolation.description', + 'securitySolutionPackages.features.featureRegistry.subFeatures.hostIsolation.description', { defaultMessage: 'Perform the "isolate" and "release" response actions.' } ), privilegeGroups: [ @@ -396,6 +414,7 @@ const hostIsolationSubFeature: SubFeatureConfig = { groupType: 'mutually_exclusive', privileges: [ { + api: [`${APP_ID}-writeHostIsolationRelease`], id: 'host_isolation_all', includeIn: 'none', name: 'All', @@ -403,11 +422,6 @@ const hostIsolationSubFeature: SubFeatureConfig = { all: [], read: [], }, - // FYI: The current set of values below (`api`, `ui`) cover only `release` response action. - // There is a second set of values for API and UI that are added later if `endpointResponseActions` - // appFeature is enabled. Needed to ensure that in a downgrade of license condition, - // users are still able to un-isolate a host machine. - api: [`${APP_ID}-writeHostIsolationRelease`], ui: ['writeHostIsolationRelease'], }, ], @@ -418,16 +432,19 @@ const hostIsolationSubFeature: SubFeatureConfig = { const processOperationsSubFeature: SubFeatureConfig = { requireAllSpaces: true, privilegesTooltip: i18n.translate( - 'xpack.securitySolution.featureRegistry.subFeatures.processOperations.privilegesTooltip', + 'securitySolutionPackages.features.featureRegistry.subFeatures.processOperations.privilegesTooltip', { defaultMessage: 'All Spaces is required for Process Operations access.', } ), - name: i18n.translate('xpack.securitySolution.featureRegistry.subFeatures.processOperations', { - defaultMessage: 'Process Operations', - }), + name: i18n.translate( + 'securitySolutionPackages.features.featureRegistry.subFeatures.processOperations', + { + defaultMessage: 'Process Operations', + } + ), description: i18n.translate( - 'xpack.securitySolution.featureRegistry.subFeatures.processOperations.description', + 'securitySolutionPackages.features.featureRegistry.subFeatures.processOperations.description', { defaultMessage: 'Perform process-related response actions in the response console.', } @@ -454,16 +471,19 @@ const processOperationsSubFeature: SubFeatureConfig = { const fileOperationsSubFeature: SubFeatureConfig = { requireAllSpaces: true, privilegesTooltip: i18n.translate( - 'xpack.securitySolution.featureRegistry.subFeatures.fileOperations.privilegesTooltip', + 'securitySolutionPackages.features.featureRegistry.subFeatures.fileOperations.privilegesTooltip', { defaultMessage: 'All Spaces is required for File Operations access.', } ), - name: i18n.translate('xpack.securitySolution.featureRegistry.subFeatures.fileOperations', { - defaultMessage: 'File Operations', - }), + name: i18n.translate( + 'securitySolutionPackages.features.featureRegistry.subFeatures.fileOperations', + { + defaultMessage: 'File Operations', + } + ), description: i18n.translate( - 'xpack.securitySolution.featureRegistry.subFeatures.fileOperations.description', + 'securitySolutionPackages.features.featureRegistry.subFeatures.fileOperations.description', { defaultMessage: 'Perform file-related response actions in the response console.', } @@ -493,16 +513,19 @@ const fileOperationsSubFeature: SubFeatureConfig = { const executeActionSubFeature: SubFeatureConfig = { requireAllSpaces: true, privilegesTooltip: i18n.translate( - 'xpack.securitySolution.featureRegistry.subFeatures.executeOperations.privilegesTooltip', + 'securitySolutionPackages.features.featureRegistry.subFeatures.executeOperations.privilegesTooltip', { defaultMessage: 'All Spaces is required for Execute Operations access.', } ), - name: i18n.translate('xpack.securitySolution.featureRegistry.subFeatures.executeOperations', { - defaultMessage: 'Execute Operations', - }), + name: i18n.translate( + 'securitySolutionPackages.features.featureRegistry.subFeatures.executeOperations', + { + defaultMessage: 'Execute Operations', + } + ), description: i18n.translate( - 'xpack.securitySolution.featureRegistry.subFeatures.executeOperations.description', + 'securitySolutionPackages.features.featureRegistry.subFeatures.executeOperations.description', { // TODO: Update this description before 8.8 FF defaultMessage: 'Perform script execution on the endpoint.', @@ -528,24 +551,71 @@ const executeActionSubFeature: SubFeatureConfig = { ], }; -export enum SecuritySubFeatureId { - endpointList = 'endpointListSubFeature', - trustedApplications = 'trustedApplicationsSubFeature', - hostIsolationExceptions = 'hostIsolationExceptionsSubFeature', - blocklist = 'blocklistSubFeature', - eventFilters = 'eventFiltersSubFeature', - policyManagement = 'policyManagementSubFeature', - responseActionsHistory = 'responseActionsHistorySubFeature', - hostIsolation = 'hostIsolationSubFeature', - processOperations = 'processOperationsSubFeature', - fileOperations = 'fileOperationsSubFeature', - executeAction = 'executeActionSubFeature', -} +const endpointExceptionsSubFeature: SubFeatureConfig = { + requireAllSpaces: true, + privilegesTooltip: i18n.translate( + 'securitySolutionPackages.features.featureRegistry.subFeatures.endpointExceptions.privilegesTooltip', + { + defaultMessage: 'All Spaces is required for Endpoint Exceptions access.', + } + ), + name: i18n.translate( + 'securitySolutionPackages.features.featureRegistry.subFeatures.endpointExceptions', + { + defaultMessage: 'Endpoint Exceptions', + } + ), + description: i18n.translate( + 'securitySolutionPackages.features.featureRegistry.subFeatures.endpointExceptions.description', + { + defaultMessage: 'Use Endpoint Exceptions (this is a test sub-feature).', + } + ), + privilegeGroups: [ + { + groupType: 'mutually_exclusive', + privileges: [ + { + id: 'endpoint_exceptions_all', + includeIn: 'all', + name: 'All', + savedObject: { + all: [], + read: [], + }, + ...AppFeaturesPrivileges[AppFeaturesPrivilegeId.endpointExceptions].all, + }, + { + id: 'endpoint_exceptions_read', + includeIn: 'read', + name: 'Read', + savedObject: { + all: [], + read: [], + }, + ...AppFeaturesPrivileges[AppFeaturesPrivilegeId.endpointExceptions].read, + }, + ], + }, + ], +}; + +/** + * Sub-features that will always be available for Security + * regardless of the product type. + */ +export const getSecurityBaseKibanaSubFeatureIds = ( + { experimentalFeatures }: SecurityFeatureParams // currently un-used, but left here as a convenience for possible future use +): SecuritySubFeatureId[] => [SecuritySubFeatureId.hostIsolation]; -// Defines all the ordered Security subFeatures available +/** + * Defines all the Security Assistant subFeatures available. + * The order of the subFeatures is the order they will be displayed + */ export const securitySubFeaturesMap = Object.freeze( new Map([ [SecuritySubFeatureId.endpointList, endpointListSubFeature], + [SecuritySubFeatureId.endpointExceptions, endpointExceptionsSubFeature], [SecuritySubFeatureId.trustedApplications, trustedApplicationsSubFeature], [SecuritySubFeatureId.hostIsolationExceptions, hostIsolationExceptionsSubFeature], [SecuritySubFeatureId.blocklist, blocklistSubFeature], diff --git a/x-pack/packages/security-solution/features/src/security/types.ts b/x-pack/packages/security-solution/features/src/security/types.ts new file mode 100644 index 0000000000000..4c2fee865ecd2 --- /dev/null +++ b/x-pack/packages/security-solution/features/src/security/types.ts @@ -0,0 +1,20 @@ +/* + * 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 { AppFeatureSecurityKey, SecuritySubFeatureId } from '../app_features_keys'; +import type { AppFeatureKibanaConfig } from '../types'; + +export interface SecurityFeatureParams { + experimentalFeatures: Record; + savedObjects: string[]; +} + +export type DefaultSecurityAppFeaturesConfig = Omit< + Record>, + AppFeatureSecurityKey.endpointExceptions + // | add not default security app features here +>; diff --git a/x-pack/packages/security-solution/features/src/types.ts b/x-pack/packages/security-solution/features/src/types.ts new file mode 100644 index 0000000000000..825e2e8e4c3b2 --- /dev/null +++ b/x-pack/packages/security-solution/features/src/types.ts @@ -0,0 +1,60 @@ +/* + * 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 { + KibanaFeatureConfig, + SubFeatureConfig, + SubFeaturePrivilegeConfig, +} from '@kbn/features-plugin/common'; +import type { RecursivePartial } from '@kbn/utility-types'; +import type { + AppFeatureAssistantKey, + AppFeatureCasesKey, + AppFeatureKeyType, + AppFeatureSecurityKey, + AssistantSubFeatureId, + CasesSubFeatureId, + SecuritySubFeatureId, +} from './app_features_keys'; + +export type { AppFeatureKeyType }; +export type AppFeatureKeys = AppFeatureKeyType[]; + +// Features types +export type BaseKibanaFeatureConfig = Omit; +export type SubFeaturesPrivileges = RecursivePartial; +export type AppFeatureKibanaConfig = + RecursivePartial & { + subFeatureIds?: T[]; + subFeaturesPrivileges?: SubFeaturesPrivileges[]; + }; +export type AppFeaturesConfig = Map< + AppFeatureKeyType, + AppFeatureKibanaConfig +>; + +export type AppFeaturesSecurityConfig = Map< + AppFeatureSecurityKey, + AppFeatureKibanaConfig +>; +export type AppFeaturesCasesConfig = Map< + AppFeatureCasesKey, + AppFeatureKibanaConfig +>; + +export type AppFeaturesAssistantConfig = Map< + AppFeatureAssistantKey, + AppFeatureKibanaConfig +>; + +export type AppSubFeaturesMap = Map; + +export interface AppFeatureParams { + baseKibanaFeature: BaseKibanaFeatureConfig; + baseKibanaSubFeatureIds: T[]; + subFeaturesMap: AppSubFeaturesMap; +} diff --git a/x-pack/packages/security-solution/features/tsconfig.json b/x-pack/packages/security-solution/features/tsconfig.json new file mode 100644 index 0000000000000..2c153f831721d --- /dev/null +++ b/x-pack/packages/security-solution/features/tsconfig.json @@ -0,0 +1,22 @@ +{ + "extends": "../../../../tsconfig.base.json", + "compilerOptions": { + "outDir": "target/types", + "types": [ + "jest", + "node", + "react", + ] + }, + "include": ["**/*.ts", "**/*.tsx"], + "kbn_references": [ + "@kbn/features-plugin", + "@kbn/utility-types", + "@kbn/i18n", + "@kbn/core-application-common", + "@kbn/cases-plugin", + "@kbn/securitysolution-rules", + "@kbn/securitysolution-list-constants", + ], + "exclude": ["target/**/*"] +} diff --git a/x-pack/plugins/apm/scripts/diagnostics_bundle/diagnostics_bundle.ts b/x-pack/plugins/apm/scripts/diagnostics_bundle/diagnostics_bundle.ts index a620d3e0be017..83671f6b97fc9 100644 --- a/x-pack/plugins/apm/scripts/diagnostics_bundle/diagnostics_bundle.ts +++ b/x-pack/plugins/apm/scripts/diagnostics_bundle/diagnostics_bundle.ts @@ -51,7 +51,6 @@ export async function initDiagnosticsBundle({ const kibanaClient = axios.create({ baseURL: kbHost ?? kibanaHost, auth, - // @ts-expect-error headers: { 'kbn-xsrf': 'true', ...apiKeyHeader }, }); const apmIndices = await getApmIndices(kibanaClient); diff --git a/x-pack/plugins/cases/common/index.ts b/x-pack/plugins/cases/common/index.ts index fdd10cbb5bf94..4283adf4c081a 100644 --- a/x-pack/plugins/cases/common/index.ts +++ b/x-pack/plugins/cases/common/index.ts @@ -50,13 +50,15 @@ export { INTERNAL_BULK_CREATE_ATTACHMENTS_URL, SAVED_OBJECT_TYPES, CASE_COMMENT_SAVED_OBJECT, + CASES_CONNECTORS_CAPABILITY, + GET_CONNECTORS_CONFIGURE_API_TAG, } from './constants'; export type { AttachmentAttributes } from './types/domain'; export { ConnectorTypes, AttachmentType, ExternalReferenceStorageType } from './types/domain'; export { getCasesFromAlertsUrl, getCaseFindUserActionsUrl, throwErrors } from './api'; export { StatusAll } from './ui/types'; -export { createUICapabilities } from './utils/capabilities'; -export { getApiTags } from './utils/api_tags'; +export { createUICapabilities, type CasesUiCapabilities } from './utils/capabilities'; +export { getApiTags, type CasesApiTags } from './utils/api_tags'; export { CaseMetricsFeature } from './types/api'; export type { SingleCaseMetricsResponse, CasesMetricsResponse } from './types/api'; diff --git a/x-pack/plugins/cases/common/utils/api_tags.ts b/x-pack/plugins/cases/common/utils/api_tags.ts index 2568c0e79b9a0..3fbad714e55f9 100644 --- a/x-pack/plugins/cases/common/utils/api_tags.ts +++ b/x-pack/plugins/cases/common/utils/api_tags.ts @@ -14,7 +14,13 @@ import { HttpApiTagOperation } from '../constants/types'; import type { Owner } from '../constants/types'; import { constructFilesHttpOperationTag } from '../files'; -export const getApiTags = (owner: Owner) => { +export interface CasesApiTags { + all: readonly string[]; + read: readonly string[]; + delete: readonly string[]; +} + +export const getApiTags = (owner: Owner): CasesApiTags => { const create = constructFilesHttpOperationTag(owner, HttpApiTagOperation.Create); const deleteTag = constructFilesHttpOperationTag(owner, HttpApiTagOperation.Delete); const read = constructFilesHttpOperationTag(owner, HttpApiTagOperation.Read); diff --git a/x-pack/plugins/cases/common/utils/capabilities.ts b/x-pack/plugins/cases/common/utils/capabilities.ts index e9c05eda47171..28b3fa00f9272 100644 --- a/x-pack/plugins/cases/common/utils/capabilities.ts +++ b/x-pack/plugins/cases/common/utils/capabilities.ts @@ -14,11 +14,16 @@ import { UPDATE_CASES_CAPABILITY, } from '../constants'; +export interface CasesUiCapabilities { + all: readonly string[]; + read: readonly string[]; + delete: readonly string[]; +} /** * Return the UI capabilities for each type of operation. These strings must match the values defined in the UI * here: x-pack/plugins/cases/public/client/helpers/capabilities.ts */ -export const createUICapabilities = () => ({ +export const createUICapabilities = (): CasesUiCapabilities => ({ all: [ CREATE_CASES_CAPABILITY, READ_CASES_CAPABILITY, diff --git a/x-pack/plugins/security_solution/common/index.ts b/x-pack/plugins/security_solution/common/index.ts index 24beb602305dd..9c0fe90ba8572 100644 --- a/x-pack/plugins/security_solution/common/index.ts +++ b/x-pack/plugins/security_solution/common/index.ts @@ -20,8 +20,6 @@ export { } from './constants'; export { ELASTIC_SECURITY_RULE_ID } from './detection_engine/constants'; export { allowedExperimentalValues, type ExperimentalFeatures } from './experimental_features'; -export type { AppFeatureKeys } from './types/app_features'; -export { AppFeatureKey, ALL_APP_FEATURE_KEYS } from './types/app_features'; // Careful of exporting anything from this file as any file(s) you export here will cause your page bundle size to increase. // If you're using functions/types/etc... internally it's best to import directly from their paths than expose the functions/types/etc... here. diff --git a/x-pack/plugins/security_solution/public/common/components/header_actions/actions.test.tsx b/x-pack/plugins/security_solution/public/common/components/header_actions/actions.test.tsx index d7e887d54ea6e..d8d57ba17ef0d 100644 --- a/x-pack/plugins/security_solution/public/common/components/header_actions/actions.test.tsx +++ b/x-pack/plugins/security_solution/public/common/components/header_actions/actions.test.tsx @@ -44,6 +44,7 @@ jest.mock('../../lib/kibana', () => { const originalKibanaLib = jest.requireActual('../../lib/kibana'); return { + ...originalKibanaLib, useKibana: () => ({ services: { application: { @@ -71,7 +72,6 @@ jest.mock('../../lib/kibana', () => { useNavigateTo: jest.fn().mockReturnValue({ navigateTo: jest.fn(), }), - useGetUserCasesPermissions: originalKibanaLib.useGetUserCasesPermissions, }; }); diff --git a/x-pack/plugins/security_solution/public/detection_engine/rule_response_actions/osquery/osquery_response_action.tsx b/x-pack/plugins/security_solution/public/detection_engine/rule_response_actions/osquery/osquery_response_action.tsx index f8e1a60bda79f..9f9ad505ff332 100644 --- a/x-pack/plugins/security_solution/public/detection_engine/rule_response_actions/osquery/osquery_response_action.tsx +++ b/x-pack/plugins/security_solution/public/detection_engine/rule_response_actions/osquery/osquery_response_action.tsx @@ -9,13 +9,13 @@ import React, { useMemo } 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 { useUpsellingComponent } from '../../../common/hooks/use_upselling'; -import { AppFeatureKey } from '../../../../common'; import { ResponseActionFormField } from './osquery_response_action_form_field'; import type { ArrayItem } from '../../../shared_imports'; +import { UseField } from '../../../shared_imports'; import { useKibana } from '../../../common/lib/kibana'; import { NOT_AVAILABLE, PERMISSION_DENIED, SHORT_EMPTY_TITLE } from './translations'; -import { UseField } from '../../../shared_imports'; interface OsqueryResponseActionProps { item: ArrayItem; diff --git a/x-pack/plugins/security_solution/public/detections/components/alerts_table/timeline_actions/alert_context_menu.test.tsx b/x-pack/plugins/security_solution/public/detections/components/alerts_table/timeline_actions/alert_context_menu.test.tsx index 9907aea709e58..b180856da2b29 100644 --- a/x-pack/plugins/security_solution/public/detections/components/alerts_table/timeline_actions/alert_context_menu.test.tsx +++ b/x-pack/plugins/security_solution/public/detections/components/alerts_table/timeline_actions/alert_context_menu.test.tsx @@ -57,31 +57,36 @@ const props = { timelineId: 'alerts-page', }; -jest.mock('../../../../common/lib/kibana', () => ({ - useToasts: jest.fn().mockReturnValue({ - addError: jest.fn(), - addSuccess: jest.fn(), - addWarning: jest.fn(), - remove: jest.fn(), - }), - useKibana: () => ({ - services: { - timelines: { ...mockTimelines }, - application: { - capabilities: { siem: { crud_alerts: true, read_alerts: true } }, +jest.mock('../../../../common/lib/kibana', () => { + const original = jest.requireActual('../../../../common/lib/kibana'); + + return { + ...original, + useToasts: jest.fn().mockReturnValue({ + addError: jest.fn(), + addSuccess: jest.fn(), + addWarning: jest.fn(), + remove: jest.fn(), + }), + useKibana: () => ({ + services: { + timelines: { ...mockTimelines }, + application: { + capabilities: { siem: { crud_alerts: true, read_alerts: true } }, + }, + cases: mockCasesContract(), }, - cases: mockCasesContract(), - }, - }), - useGetUserCasesPermissions: jest.fn().mockReturnValue({ - all: true, - create: true, - read: true, - update: true, - delete: true, - push: true, - }), -})); + }), + useGetUserCasesPermissions: jest.fn().mockReturnValue({ + all: true, + create: true, + read: true, + update: true, + delete: true, + push: true, + }), + }; +}); jest.mock('../../../containers/detection_engine/alerts/use_alerts_privileges', () => ({ useAlertsPrivileges: jest.fn().mockReturnValue({ hasIndexWrite: true, hasKibanaCRUD: true }), diff --git a/x-pack/plugins/security_solution/public/detections/components/alerts_table/timeline_actions/alert_context_menu.tsx b/x-pack/plugins/security_solution/public/detections/components/alerts_table/timeline_actions/alert_context_menu.tsx index 0e1b32c3c77d9..a05c351f3d22d 100644 --- a/x-pack/plugins/security_solution/public/detections/components/alerts_table/timeline_actions/alert_context_menu.tsx +++ b/x-pack/plugins/security_solution/public/detections/components/alerts_table/timeline_actions/alert_context_menu.tsx @@ -7,7 +7,7 @@ import React, { useCallback, useMemo, useState } from 'react'; -import { EuiButtonIcon, EuiPopover, EuiToolTip, EuiContextMenu } from '@elastic/eui'; +import { EuiButtonIcon, EuiContextMenu, EuiPopover, EuiToolTip } from '@elastic/eui'; import { indexOf } from 'lodash'; import type { ConnectedProps } from 'react-redux'; import { connect } from 'react-redux'; @@ -36,7 +36,7 @@ import { useSignalIndex } from '../../../containers/detection_engine/alerts/use_ import { EventFiltersFlyout } from '../../../../management/pages/event_filters/view/components/event_filters_flyout'; import { useAlertsActions } from './use_alerts_actions'; import { useExceptionFlyout } from './use_add_exception_flyout'; -import { useExceptionActions } from './use_add_exception_actions'; +import { useAlertExceptionActions } from './use_add_exception_actions'; import { useEventFilterModal } from './use_event_filter_modal'; import type { Status } from '../../../../../common/api/detection_engine'; import { ATTACH_ALERT_TO_CASE_FOR_ROW } from '../../../../timelines/components/timeline/body/translations'; @@ -196,7 +196,7 @@ const AlertContextMenuComponent: React.FC { + const { exceptionActionItems } = useExceptionActions({ + isEndpointAlert, + onAddExceptionTypeClick, + }); + + const { loading: listsConfigLoading, needsConfiguration: needsListsConfiguration } = + useListsConfig(); + const canReadEndpointExceptions = useHasSecurityCapability('crudEndpointExceptions'); + + const canWriteEndpointExceptions = useMemo( + () => !listsConfigLoading && !needsListsConfiguration && canReadEndpointExceptions, + [canReadEndpointExceptions, listsConfigLoading, needsListsConfiguration] + ); + // Endpoint exceptions are available for: + // Serverless Endpoint Essentials/Complete PLI and + // on ESS Security Kibana sub-feature Endpoint Exceptions (enabled when Security feature is enabled) + if (!canWriteEndpointExceptions) { + return { + exceptionActionItems: exceptionActionItems.map((item) => { + return { ...item, disabled: item.name === ACTION_ADD_ENDPOINT_EXCEPTION }; + }), + }; + } + return { exceptionActionItems }; +}; diff --git a/x-pack/plugins/security_solution/public/detections/components/take_action_dropdown/index.tsx b/x-pack/plugins/security_solution/public/detections/components/take_action_dropdown/index.tsx index e7703c86f23bb..67175f05ece2e 100644 --- a/x-pack/plugins/security_solution/public/detections/components/take_action_dropdown/index.tsx +++ b/x-pack/plugins/security_solution/public/detections/components/take_action_dropdown/index.tsx @@ -10,6 +10,7 @@ import { EuiButton, EuiContextMenu, EuiPopover } from '@elastic/eui'; import type { ExceptionListTypeEnum } from '@kbn/securitysolution-io-ts-list-types'; import type { EcsSecurityExtension as Ecs } from '@kbn/securitysolution-ecs'; import { TableId } from '@kbn/securitysolution-data-table'; +import type { TimelineEventsDetailsItem } from '@kbn/timelines-plugin/common'; import { GuidedOnboardingTourStep } from '../../../common/components/guided_onboarding_tour/tour_step'; import { AlertsCasesTourSteps, @@ -17,9 +18,8 @@ import { } from '../../../common/components/guided_onboarding_tour/tour_config'; import { isActiveTimeline } from '../../../helpers'; import { useResponderActionItem } from '../endpoint_responder'; -import type { TimelineEventsDetailsItem } from '../../../../common/search_strategy'; import { TAKE_ACTION } from '../alerts_table/additional_filters_action/translations'; -import { useExceptionActions } from '../alerts_table/timeline_actions/use_add_exception_actions'; +import { useAlertExceptionActions } from '../alerts_table/timeline_actions/use_add_exception_actions'; import { useAlertsActions } from '../alerts_table/timeline_actions/use_alerts_actions'; import { useInvestigateInTimeline } from '../alerts_table/timeline_actions/use_investigate_in_timeline'; @@ -157,7 +157,7 @@ export const TakeActionDropdown = React.memo( [onAddExceptionTypeClick] ); - const { exceptionActionItems } = useExceptionActions({ + const { exceptionActionItems } = useAlertExceptionActions({ isEndpointAlert: isAlertFromEndpointAlert({ ecsData }), onAddExceptionTypeClick: handleOnAddExceptionTypeClick, }); diff --git a/x-pack/plugins/security_solution/public/exceptions/pages/shared_lists/index.tsx b/x-pack/plugins/security_solution/public/exceptions/pages/shared_lists/index.tsx index c1fdf61a57cdc..0e457d520984c 100644 --- a/x-pack/plugins/security_solution/public/exceptions/pages/shared_lists/index.tsx +++ b/x-pack/plugins/security_solution/public/exceptions/pages/shared_lists/index.tsx @@ -5,41 +5,41 @@ * 2.0. */ -import React, { useMemo, useEffect, useCallback, useState } from 'react'; +import React, { useCallback, useEffect, useMemo, useState } from 'react'; import type { EuiSearchBarProps } from '@elastic/eui'; - import { + EuiButton, EuiButtonEmpty, EuiButtonIcon, EuiContextMenuItem, EuiContextMenuPanel, - EuiPagination, - EuiPopover, - EuiButton, EuiFlexGroup, EuiFlexItem, - EuiSpacer, - EuiPageHeader, EuiHorizontalRule, + EuiPageHeader, + EuiPagination, + EuiPopover, + EuiSpacer, EuiText, } from '@elastic/eui'; -import type { NamespaceType, ExceptionListFilter } from '@kbn/securitysolution-io-ts-list-types'; +import type { ExceptionListFilter, NamespaceType } from '@kbn/securitysolution-io-ts-list-types'; import { ExceptionListTypeEnum } from '@kbn/securitysolution-io-ts-list-types'; import { useApi, useExceptionLists } from '@kbn/securitysolution-list-hooks'; -import { ViewerStatus, EmptyViewerState } from '@kbn/securitysolution-exception-list-components'; +import { EmptyViewerState, ViewerStatus } from '@kbn/securitysolution-exception-list-components'; +import { useHasSecurityCapability } from '../../../helper_hooks'; import { AutoDownload } from '../../../common/components/auto_download/auto_download'; import { useKibana } from '../../../common/lib/kibana'; import { useAppToasts } from '../../../common/hooks/use_app_toasts'; import * as i18n from '../../translations/shared_list'; import { - ExceptionsTableUtilityBar, - ListsSearchBar, + CreateSharedListFlyout, ExceptionsListCard, + ExceptionsTableUtilityBar, ImportExceptionListFlyout, - CreateSharedListFlyout, + ListsSearchBar, } from '../../components'; import { useAllExceptionLists } from '../../hooks/use_all_exception_lists'; import { ReferenceErrorModal } from '../../../detections/components/value_lists_management_flyout/reference_error_modal'; @@ -82,9 +82,15 @@ const SORT_FIELDS: Array<{ field: string; label: string; defaultOrder: 'asc' | ' export const SharedLists = React.memo(() => { const [{ loading: userInfoLoading, canUserCRUD, canUserREAD }] = useUserData(); - const { loading: listsConfigLoading } = useListsConfig(); + const { loading: listsConfigLoading, needsConfiguration: needsListsConfiguration } = + useListsConfig(); const loading = userInfoLoading || listsConfigLoading; + const canShowEndpointExceptions = useHasSecurityCapability('showEndpointExceptions'); + const canAccessEndpointExceptions = useMemo( + () => !listsConfigLoading && !needsListsConfiguration && canShowEndpointExceptions, + [canShowEndpointExceptions, listsConfigLoading, needsListsConfiguration] + ); const { services: { http, @@ -103,6 +109,13 @@ export const SharedLists = React.memo(() => { const [viewerStatus, setViewStatus] = useState(ViewerStatus.LOADING); + const exceptionListTypes = useMemo(() => { + const lists = [ExceptionListTypeEnum.DETECTION]; + if (canAccessEndpointExceptions) { + lists.push(ExceptionListTypeEnum.ENDPOINT); + } + return lists; + }, [canAccessEndpointExceptions]); const [ loadingExceptions, exceptions, @@ -115,7 +128,7 @@ export const SharedLists = React.memo(() => { errorMessage: i18n.ERROR_EXCEPTION_LISTS, filterOptions: { ...filters, - types: [ExceptionListTypeEnum.DETECTION, ExceptionListTypeEnum.ENDPOINT], + types: exceptionListTypes, }, http, namespaceTypes: ['single', 'agnostic'], diff --git a/x-pack/plugins/security_solution/public/timelines/components/side_panel/event_details/flyout/footer.test.tsx b/x-pack/plugins/security_solution/public/timelines/components/side_panel/event_details/flyout/footer.test.tsx index ab24a16ca1a93..91a2904287ffc 100644 --- a/x-pack/plugins/security_solution/public/timelines/components/side_panel/event_details/flyout/footer.test.tsx +++ b/x-pack/plugins/security_solution/public/timelines/components/side_panel/event_details/flyout/footer.test.tsx @@ -15,8 +15,8 @@ import { mockAlertDetailsData } from '../../../../../common/components/event_det import type { TimelineEventsDetailsItem } from '../../../../../../common/search_strategy'; import { KibanaServices, - useKibana, useGetUserCasesPermissions, + useKibana, } from '../../../../../common/lib/kibana'; import { coreMock } from '@kbn/core/public/mocks'; import { mockCasesContract } from '@kbn/cases-plugin/public/mocks'; @@ -132,6 +132,15 @@ describe('event details footer component', () => { query: jest.fn(), }, cases: mockCasesContract(), + application: { + ...coreStartMock.application, + capabilities: { + ...coreStartMock.application.capabilities, + siem: { + crudEndpointExceptions: true, + }, + }, + }, }, }); }); diff --git a/x-pack/plugins/security_solution/public/timelines/components/timeline/body/events/event_column_view.test.tsx b/x-pack/plugins/security_solution/public/timelines/components/timeline/body/events/event_column_view.test.tsx index e399a21575667..1911b45fe475a 100644 --- a/x-pack/plugins/security_solution/public/timelines/components/timeline/body/events/event_column_view.test.tsx +++ b/x-pack/plugins/security_solution/public/timelines/components/timeline/body/events/event_column_view.test.tsx @@ -12,7 +12,7 @@ import { TestProviders } from '../../../../../common/mock'; import { EventColumnView } from './event_column_view'; import { DefaultCellRenderer } from '../../cell_rendering/default_cell_renderer'; -import { TimelineTabs, TimelineId } from '../../../../../../common/types/timeline'; +import { TimelineId, TimelineTabs } from '../../../../../../common/types/timeline'; import { TimelineType } from '../../../../../../common/api/timeline'; import { useShallowEqualSelector } from '../../../../../common/hooks/use_selector'; import { useIsExperimentalFeatureEnabled } from '../../../../../common/hooks/use_experimental_features'; @@ -47,6 +47,7 @@ jest.mock('../../../../../common/lib/kibana', () => { const originalModule = jest.requireActual('../../../../../common/lib/kibana'); return { + ...originalModule, useKibana: () => ({ services: { timelines: { ...mockTimelines }, @@ -71,7 +72,6 @@ jest.mock('../../../../../common/lib/kibana', () => { addWarning: jest.fn(), remove: jest.fn(), }), - useGetUserCasesPermissions: originalModule.useGetUserCasesPermissions, }; }); diff --git a/x-pack/plugins/security_solution/server/endpoint/endpoint_app_context_services.ts b/x-pack/plugins/security_solution/server/endpoint/endpoint_app_context_services.ts index 058f8892013a0..c039895eaaef5 100644 --- a/x-pack/plugins/security_solution/server/endpoint/endpoint_app_context_services.ts +++ b/x-pack/plugins/security_solution/server/endpoint/endpoint_app_context_services.ts @@ -17,7 +17,6 @@ import type { import type { PluginStartContract as AlertsPluginStartContract } from '@kbn/alerting-plugin/server'; import type { CloudSetup } from '@kbn/cloud-plugin/server'; import type { FleetActionsClientInterface } from '@kbn/fleet-plugin/server/services/actions/types'; -import type { AppFeatures } from '../lib/app_features'; import { getPackagePolicyCreateCallback, getPackagePolicyUpdateCallback, @@ -43,6 +42,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'; export interface EndpointAppContextServiceSetupContract { securitySolutionRequestContextFactory: IRequestContextFactory; @@ -70,7 +70,7 @@ export interface EndpointAppContextServiceStartContract { actionCreateService: ActionCreateService | undefined; cloud: CloudSetup; esClient: ElasticsearchClient; - appFeatures: AppFeatures; + appFeaturesService: AppFeaturesService; } /** @@ -108,7 +108,7 @@ export class EndpointAppContextService { featureUsageService, endpointMetadataService, esClient, - appFeatures, + appFeaturesService, } = dependencies; registerIngestCallback( @@ -121,7 +121,7 @@ export class EndpointAppContextService { licenseService, exceptionListsClient, cloud, - appFeatures + appFeaturesService ) ); @@ -139,7 +139,7 @@ export class EndpointAppContextService { endpointMetadataService, cloud, esClient, - appFeatures + appFeaturesService ) ); diff --git a/x-pack/plugins/security_solution/server/endpoint/migrations/turn_off_policy_protections.test.ts b/x-pack/plugins/security_solution/server/endpoint/migrations/turn_off_policy_protections.test.ts index 1d39b72670b98..519d864b726e7 100644 --- a/x-pack/plugins/security_solution/server/endpoint/migrations/turn_off_policy_protections.test.ts +++ b/x-pack/plugins/security_solution/server/endpoint/migrations/turn_off_policy_protections.test.ts @@ -9,29 +9,32 @@ import { createMockEndpointAppContextServiceStartContract } from '../mocks'; import type { Logger } from '@kbn/logging'; import type { ElasticsearchClient } from '@kbn/core-elasticsearch-server'; import type { EndpointInternalFleetServicesInterface } from '../services/fleet'; -import type { AppFeatures } from '../../lib/app_features'; -import { createAppFeaturesMock } from '../../lib/app_features/mocks'; -import { ALL_APP_FEATURE_KEYS } from '../../../common'; + +import { ALL_APP_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'; describe('Turn Off Policy Protections Migration', () => { let esClient: ElasticsearchClient; let fleetServices: EndpointInternalFleetServicesInterface; - let appFeatures: AppFeatures; + let appFeatureService: AppFeaturesService; let logger: Logger; const callTurnOffPolicyProtections = () => - turnOffPolicyProtectionsIfNotSupported(esClient, fleetServices, appFeatures, logger); + turnOffPolicyProtectionsIfNotSupported(esClient, fleetServices, appFeatureService, logger); beforeEach(() => { const endpointContextStartContract = createMockEndpointAppContextServiceStartContract(); - ({ esClient, appFeatures, logger } = endpointContextStartContract); + ({ esClient, logger } = endpointContextStartContract); + + appFeatureService = endpointContextStartContract.appFeaturesService; fleetServices = endpointContextStartContract.endpointFleetServicesFactory.asInternalUser(); }); @@ -70,7 +73,7 @@ describe('Turn Off Policy Protections Migration', () => { policyGenerator = new FleetPackagePolicyGenerator('seed'); const packagePolicyListSrv = fleetServices.packagePolicy.list as jest.Mock; - appFeatures = createAppFeaturesMock( + appFeatureService = createAppFeaturesServiceMock( ALL_APP_FEATURE_KEYS.filter((key) => key !== 'endpoint_policy_protections') ); diff --git a/x-pack/plugins/security_solution/server/endpoint/migrations/turn_off_policy_protections.ts b/x-pack/plugins/security_solution/server/endpoint/migrations/turn_off_policy_protections.ts index c4a63b8ec841c..13bdb1496da48 100644 --- a/x-pack/plugins/security_solution/server/endpoint/migrations/turn_off_policy_protections.ts +++ b/x-pack/plugins/security_solution/server/endpoint/migrations/turn_off_policy_protections.ts @@ -8,20 +8,20 @@ import type { Logger, ElasticsearchClient } 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 { isPolicySetToEventCollectionOnly, ensureOnlyEventCollectionIsAllowed, } from '../../../common/endpoint/models/policy_config_helpers'; import type { PolicyData } from '../../../common/endpoint/types'; -import { AppFeatureSecurityKey } from '../../../common/types/app_features'; import type { EndpointInternalFleetServicesInterface } from '../services/fleet'; -import type { AppFeatures } from '../../lib/app_features'; import { getPolicyDataForUpdate } from '../../../common/endpoint/service/policy'; +import type { AppFeaturesService } from '../../lib/app_features_service/app_features_service'; export const turnOffPolicyProtectionsIfNotSupported = async ( esClient: ElasticsearchClient, fleetServices: EndpointInternalFleetServicesInterface, - appFeaturesService: AppFeatures, + appFeaturesService: AppFeaturesService, logger: Logger ): Promise => { const log = logger.get('endpoint', 'policyProtections'); diff --git a/x-pack/plugins/security_solution/server/endpoint/mocks.ts b/x-pack/plugins/security_solution/server/endpoint/mocks.ts index 5a3c9ee2297ac..73beb5aea6838 100644 --- a/x-pack/plugins/security_solution/server/endpoint/mocks.ts +++ b/x-pack/plugins/security_solution/server/endpoint/mocks.ts @@ -17,12 +17,12 @@ import { savedObjectsServiceMock, } from '@kbn/core/server/mocks'; import type { + IRouter, KibanaRequest, - RouteConfig, - SavedObjectsClientContract, RequestHandler, - IRouter, + RouteConfig, RouteMethod, + SavedObjectsClientContract, } from '@kbn/core/server'; import { listMock } from '@kbn/lists-plugin/server/mocks'; import { securityMock } from '@kbn/security-plugin/server/mocks'; @@ -30,14 +30,14 @@ import { alertsMock } from '@kbn/alerting-plugin/server/mocks'; import { cloudMock } from '@kbn/cloud-plugin/server/mocks'; import type { FleetStartContract } from '@kbn/fleet-plugin/server'; import { - createPackagePolicyServiceMock, + createFleetActionsClientMock, + createFleetFromHostFilesClientMock, + createFleetToHostFilesClientMock, + createMessageSigningServiceMock, createMockAgentPolicyService, createMockAgentService, createMockPackageService, - createMessageSigningServiceMock, - createFleetFromHostFilesClientMock, - createFleetToHostFilesClientMock, - createFleetActionsClientMock, + createPackagePolicyServiceMock, } from '@kbn/fleet-plugin/server/mocks'; import { createFleetAuthzMock } from '@kbn/fleet-plugin/common/mocks'; import type { RequestFixtureOptions, RouterMock } from '@kbn/core-http-router-server-mocks'; @@ -45,7 +45,7 @@ import type { ElasticsearchClientMock } from '@kbn/core-elasticsearch-client-ser import { elasticsearchClientMock } from '@kbn/core-elasticsearch-client-server-mocks'; import { casesPluginMock } from '@kbn/cases-plugin/server/mocks'; import { createCasesClientMock } from '@kbn/cases-plugin/server/client/mocks'; -import type { VersionedRouteConfig, AddVersionOpts } from '@kbn/core-http-server'; +import type { AddVersionOpts, VersionedRouteConfig } from '@kbn/core-http-server'; import { createActionCreateServiceMock } from './services/actions/mocks'; import { getEndpointAuthzInitialStateMock } from '../../common/endpoint/service/authz/mocks'; import { createMockConfig, requestContextMock } from '../lib/detection_engine/routes/__mocks__'; @@ -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 { createAppFeaturesMock } from '../lib/app_features/mocks'; +import { createAppFeaturesServiceMock } from '../lib/app_features_service/mocks'; /** * Creates a mocked EndpointAppContext. @@ -165,7 +165,12 @@ export const createMockEndpointAppContextServiceStartContract = savedObjectsStart ); const experimentalFeatures = config.experimentalFeatures; - const appFeatures = createAppFeaturesMock(undefined, experimentalFeatures, undefined, logger); + const appFeaturesService = createAppFeaturesServiceMock( + undefined, + experimentalFeatures, + undefined, + logger + ); packagePolicyService.list.mockImplementation(async (_, options) => { return { @@ -215,7 +220,7 @@ export const createMockEndpointAppContextServiceStartContract = actionCreateService: undefined, createFleetActionsClient: jest.fn((_) => fleetActionsClientMock), esClient: elasticsearchClientMock.createElasticsearchClient(), - appFeatures, + appFeaturesService, }; }; diff --git a/x-pack/plugins/security_solution/server/endpoint/services/artifacts/manifest_manager/manifest_manager.mock.ts b/x-pack/plugins/security_solution/server/endpoint/services/artifacts/manifest_manager/manifest_manager.mock.ts index 4ddb81bfacd49..442708e625d84 100644 --- a/x-pack/plugins/security_solution/server/endpoint/services/artifacts/manifest_manager/manifest_manager.mock.ts +++ b/x-pack/plugins/security_solution/server/endpoint/services/artifacts/manifest_manager/manifest_manager.mock.ts @@ -6,9 +6,9 @@ */ import { - savedObjectsClientMock, - loggingSystemMock, elasticsearchServiceMock, + loggingSystemMock, + savedObjectsClientMock, } from '@kbn/core/server/mocks'; import type { Logger } from '@kbn/core/server'; import type { PackagePolicyClient } from '@kbn/fleet-plugin/server'; @@ -16,20 +16,20 @@ 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 { - createPackagePolicyWithManifestMock, createPackagePolicyWithInitialManifestMock, - getMockManifest, - getMockArtifactsWithDiff, + createPackagePolicyWithManifestMock, getEmptyMockArtifacts, + getMockArtifactsWithDiff, + getMockManifest, } from '../../../lib/artifacts/mocks'; import { createEndpointArtifactClientMock, getManifestClientMock } from '../mocks'; import type { ManifestManagerContext } from './manifest_manager'; import { ManifestManager } from './manifest_manager'; import { parseExperimentalConfigValue } from '../../../../../common/experimental_features'; -import { createAppFeaturesMock } from '../../../../lib/app_features/mocks'; -import type { AppFeatureKeys } from '../../../../../common/types/app_features'; -import type { AppFeatures } from '../../../../lib/app_features/app_features'; +import { createAppFeaturesServiceMock } from '../../../../lib/app_features_service/mocks'; +import type { AppFeaturesService } from '../../../../lib/app_features_service/app_features_service'; export const createExceptionListResponse = (data: ExceptionListItemSchema[], total?: number) => ({ data, @@ -71,7 +71,7 @@ export interface ManifestManagerMockOptions { exceptionListClient: ExceptionListClient; packagePolicyService: jest.Mocked; savedObjectsClient: ReturnType; - appFeatures: AppFeatures; + appFeaturesService: AppFeaturesService; } export const buildManifestManagerMockOptions = ( @@ -83,7 +83,7 @@ export const buildManifestManagerMockOptions = ( exceptionListClient: listMock.getExceptionListClient(savedObjectMock), packagePolicyService: createPackagePolicyServiceMock(), savedObjectsClient: savedObjectMock, - appFeatures: createAppFeaturesMock(customAppFeatures), + appFeaturesService: createAppFeaturesServiceMock(customAppFeatures), ...opts, }; }; diff --git a/x-pack/plugins/security_solution/server/endpoint/services/artifacts/manifest_manager/manifest_manager.test.ts b/x-pack/plugins/security_solution/server/endpoint/services/artifacts/manifest_manager/manifest_manager.test.ts index 379a098bb1613..47753c7db0c97 100644 --- a/x-pack/plugins/security_solution/server/endpoint/services/artifacts/manifest_manager/manifest_manager.test.ts +++ b/x-pack/plugins/security_solution/server/endpoint/services/artifacts/manifest_manager/manifest_manager.test.ts @@ -7,11 +7,11 @@ import { savedObjectsClientMock } from '@kbn/core/server/mocks'; import { + ENDPOINT_BLOCKLISTS_LIST_ID, + ENDPOINT_EVENT_FILTERS_LIST_ID, ENDPOINT_HOST_ISOLATION_EXCEPTIONS_LIST_ID, ENDPOINT_LIST_ID, ENDPOINT_TRUSTED_APPS_LIST_ID, - ENDPOINT_EVENT_FILTERS_LIST_ID, - ENDPOINT_BLOCKLISTS_LIST_ID, } from '@kbn/securitysolution-list-constants'; import { getExceptionListItemSchemaMock } from '@kbn/lists-plugin/common/schemas/response/exception_list_item_schema.mock'; import type { PackagePolicy } from '@kbn/fleet-plugin/common/types/models'; @@ -27,10 +27,10 @@ import { toArtifactRecords, } from '../../../lib/artifacts/mocks'; import { - ManifestConstants, getArtifactId, - translateToEndpointExceptions, Manifest, + ManifestConstants, + translateToEndpointExceptions, } from '../../../lib/artifacts'; import { @@ -43,7 +43,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 '../../../../../common/types/app_features'; +import { AppFeatureSecurityKey } from '@kbn/security-solution-features/keys'; import type { ExceptionListItemSchema } from '@kbn/securitysolution-io-ts-list-types/src/response/exception_list_item_schema'; const getArtifactObject = (artifact: InternalArtifactSchema) => @@ -257,7 +257,7 @@ describe('ManifestManager', () => { ( manifestManagerContext.artifactClient as jest.Mocked - ).listArtifacts.mockImplementation(async (id) => { + ).listArtifacts.mockImplementation(async () => { // report the MACOS Exceptions artifact as not found return { items: [ diff --git a/x-pack/plugins/security_solution/server/endpoint/services/artifacts/manifest_manager/manifest_manager.ts b/x-pack/plugins/security_solution/server/endpoint/services/artifacts/manifest_manager/manifest_manager.ts index 865a75fc4b5ab..1cda8bdd9533b 100644 --- a/x-pack/plugins/security_solution/server/endpoint/services/artifacts/manifest_manager/manifest_manager.ts +++ b/x-pack/plugins/security_solution/server/endpoint/services/artifacts/manifest_manager/manifest_manager.ts @@ -6,43 +6,46 @@ */ import semver from 'semver'; -import { isEqual, isEmpty, chunk, keyBy } from 'lodash'; +import { chunk, isEmpty, isEqual, keyBy } from 'lodash'; import type { ElasticsearchClient } from '@kbn/core/server'; import { type Logger, type SavedObjectsClientContract } from '@kbn/core/server'; import { - ENDPOINT_EVENT_FILTERS_LIST_ID, - ENDPOINT_TRUSTED_APPS_LIST_ID, ENDPOINT_BLOCKLISTS_LIST_ID, + ENDPOINT_EVENT_FILTERS_LIST_ID, ENDPOINT_HOST_ISOLATION_EXCEPTIONS_LIST_ID, ENDPOINT_LIST_ID, + ENDPOINT_TRUSTED_APPS_LIST_ID, } from '@kbn/securitysolution-list-constants'; import type { ListResult, 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 '../../../../../common/types/app_features'; -import type { AppFeatures } from '../../../../lib/app_features'; +import { AppFeatureKey } from '@kbn/security-solution-features/keys'; +import type { AppFeaturesService } from '../../../../lib/app_features_service/app_features_service'; +import type { ExperimentalFeatures } from '../../../../../common'; import type { ManifestSchemaVersion } from '../../../../../common/endpoint/schema/common'; -import type { ManifestSchema } from '../../../../../common/endpoint/schema/manifest'; -import { manifestDispatchSchema } from '../../../../../common/endpoint/schema/manifest'; +import { + manifestDispatchSchema, + type ManifestSchema, +} from '../../../../../common/endpoint/schema/manifest'; -import type { ArtifactListId } from '../../../lib/artifacts'; import { ArtifactConstants, + type ArtifactListId, buildArtifact, + convertExceptionsToEndpointFormat, getAllItemsFromEndpointExceptionList, getArtifactId, Manifest, - convertExceptionsToEndpointFormat, } from '../../../lib/artifacts'; -import type { - InternalArtifactCompleteSchema, - WrappedTranslatedExceptionList, + +import { + internalArtifactCompleteSchema, + type InternalArtifactCompleteSchema, + type WrappedTranslatedExceptionList, } from '../../../schemas/artifacts'; -import { internalArtifactCompleteSchema } from '../../../schemas/artifacts'; import type { EndpointArtifactClientInterface } from '../artifact_client'; import { ManifestClient } from '../manifest_client'; -import type { ExperimentalFeatures } from '../../../../../common/experimental_features'; import { InvalidInternalManifestError } from '../errors'; import { wrapErrorIfNeeded } from '../../../utils'; import { EndpointError } from '../../../../../common/endpoint/errors'; @@ -99,7 +102,7 @@ export interface ManifestManagerContext { experimentalFeatures: ExperimentalFeatures; packagerTaskPackagePolicyUpdateBatchSize: number; esClient: ElasticsearchClient; - appFeatures: AppFeatures; + appFeaturesService: AppFeaturesService; } const getArtifactIds = (manifest: ManifestSchema) => @@ -121,7 +124,7 @@ export class ManifestManager { protected cachedExceptionsListsByOs: Map; protected packagerTaskPackagePolicyUpdateBatchSize: number; protected esClient: ElasticsearchClient; - protected appFeatures: AppFeatures; + protected appFeaturesService: AppFeaturesService; constructor(context: ManifestManagerContext) { this.artifactClient = context.artifactClient; @@ -135,7 +138,7 @@ export class ManifestManager { this.packagerTaskPackagePolicyUpdateBatchSize = context.packagerTaskPackagePolicyUpdateBatchSize; this.esClient = context.esClient; - this.appFeatures = context.appFeatures; + this.appFeaturesService = context.appFeaturesService; } /** @@ -167,9 +170,9 @@ export class ManifestManager { let itemsByListId: ExceptionListItemSchema[] = []; if ( (listId === ENDPOINT_HOST_ISOLATION_EXCEPTIONS_LIST_ID && - this.appFeatures.isEnabled(AppFeatureKey.endpointResponseActions)) || + this.appFeaturesService.isEnabled(AppFeatureKey.endpointResponseActions)) || (listId !== ENDPOINT_HOST_ISOLATION_EXCEPTIONS_LIST_ID && - this.appFeatures.isEnabled(AppFeatureKey.endpointArtifactManagement)) + this.appFeaturesService.isEnabled(AppFeatureKey.endpointArtifactManagement)) ) { itemsByListId = await getAllItemsFromEndpointExceptionList({ elClient, diff --git a/x-pack/plugins/security_solution/server/fleet_integration/fleet_integration.test.ts b/x-pack/plugins/security_solution/server/fleet_integration/fleet_integration.test.ts index 0ff3692971ad4..e2ce386337a85 100644 --- a/x-pack/plugins/security_solution/server/fleet_integration/fleet_integration.test.ts +++ b/x-pack/plugins/security_solution/server/fleet_integration/fleet_integration.test.ts @@ -25,11 +25,12 @@ import { import { buildManifestManagerMock } from '../endpoint/services/artifacts/manifest_manager/manifest_manager.mock'; import { getPackagePolicyCreateCallback, - getPackagePolicyPostCreateCallback, getPackagePolicyDeleteCallback, + getPackagePolicyPostCreateCallback, getPackagePolicyUpdateCallback, } from './fleet_integration'; import type { KibanaRequest } from '@kbn/core/server'; +import { ALL_APP_FEATURE_KEYS } 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'; @@ -54,9 +55,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 { AppFeatures } from '../lib/app_features'; -import { createAppFeaturesMock } from '../lib/app_features/mocks'; -import { ALL_APP_FEATURE_KEYS } from '../../common'; +import type { AppFeaturesService } from '../lib/app_features_service/app_features_service'; +import { createAppFeaturesServiceMock } from '../lib/app_features_service/mocks'; jest.mock('uuid', () => ({ v4: (): string => 'NEW_UUID', @@ -77,7 +77,7 @@ describe('ingest_integration tests ', () => { }); const generator = new EndpointDocGenerator(); const cloudService = cloudMock.createSetup(); - let appFeatures: AppFeatures; + let appFeaturesService: AppFeaturesService; beforeEach(() => { endpointAppContextMock = createMockEndpointAppContextServiceStartContract(); @@ -86,7 +86,7 @@ describe('ingest_integration tests ', () => { licenseEmitter = new Subject(); licenseService = new LicenseService(); licenseService.start(licenseEmitter); - appFeatures = endpointAppContextMock.appFeatures; + appFeaturesService = endpointAppContextMock.appFeaturesService; jest .spyOn(endpointAppContextMock.endpointMetadataService, 'getFleetEndpointPackagePolicy') @@ -143,7 +143,7 @@ describe('ingest_integration tests ', () => { licenseService, exceptionListClient, cloudService, - appFeatures + appFeaturesService ); return callback( @@ -395,7 +395,7 @@ describe('ingest_integration tests ', () => { endpointAppContextMock.endpointMetadataService, cloudService, esClient, - appFeatures + appFeaturesService ); const policyConfig = generator.generatePolicyPackagePolicy(); policyConfig.inputs[0]!.config!.policy.value = mockPolicy; @@ -414,7 +414,7 @@ describe('ingest_integration tests ', () => { endpointAppContextMock.endpointMetadataService, cloudService, esClient, - appFeatures + appFeaturesService ); const policyConfig = generator.generatePolicyPackagePolicy(); policyConfig.inputs[0]!.config!.policy.value = mockPolicy; @@ -448,7 +448,7 @@ describe('ingest_integration tests ', () => { endpointAppContextMock.endpointMetadataService, cloudService, esClient, - appFeatures + appFeaturesService ); const policyConfig = generator.generatePolicyPackagePolicy(); policyConfig.inputs[0]!.config!.policy.value = mockPolicy; @@ -463,7 +463,7 @@ describe('ingest_integration tests ', () => { }); it('should turn off protections if endpointPolicyProtections appFeature is disabled', async () => { - appFeatures = createAppFeaturesMock( + appFeaturesService = createAppFeaturesServiceMock( ALL_APP_FEATURE_KEYS.filter((key) => key !== 'endpoint_policy_protections') ); const callback = getPackagePolicyUpdateCallback( @@ -473,7 +473,7 @@ describe('ingest_integration tests ', () => { endpointAppContextMock.endpointMetadataService, cloudService, esClient, - appFeatures + appFeaturesService ); const updatedPolicy = await callback( @@ -552,7 +552,7 @@ describe('ingest_integration tests ', () => { endpointAppContextMock.endpointMetadataService, cloudService, esClient, - appFeatures + appFeaturesService ); const policyConfig = generator.generatePolicyPackagePolicy(); @@ -589,7 +589,7 @@ describe('ingest_integration tests ', () => { endpointAppContextMock.endpointMetadataService, cloudService, esClient, - appFeatures + appFeaturesService ); const policyConfig = generator.generatePolicyPackagePolicy(); // values should be updated diff --git a/x-pack/plugins/security_solution/server/fleet_integration/fleet_integration.ts b/x-pack/plugins/security_solution/server/fleet_integration/fleet_integration.ts index a9da860a5008e..554417eee480d 100644 --- a/x-pack/plugins/security_solution/server/fleet_integration/fleet_integration.ts +++ b/x-pack/plugins/security_solution/server/fleet_integration/fleet_integration.ts @@ -22,12 +22,11 @@ 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 '../../common/types/app_features'; +import { AppFeatureSecurityKey } from '@kbn/security-solution-features/keys'; import { isPolicySetToEventCollectionOnly, ensureOnlyEventCollectionIsAllowed, } from '../../common/endpoint/models/policy_config_helpers'; -import type { AppFeatures } from '../lib/app_features'; import type { NewPolicyData, PolicyConfig } from '../../common/endpoint/types'; import type { LicenseService } from '../../common/license'; import type { ManifestManager } from '../endpoint/services'; @@ -44,6 +43,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'; const isEndpointPackagePolicy = ( packagePolicy: T @@ -81,7 +81,7 @@ export const getPackagePolicyCreateCallback = ( licenseService: LicenseService, exceptionsClient: ExceptionListClient | undefined, cloud: CloudSetup, - appFeatures: AppFeatures + appFeatures: AppFeaturesService ): PostPackagePolicyCreateCallback => { return async ( newPackagePolicy, @@ -186,7 +186,7 @@ export const getPackagePolicyUpdateCallback = ( endpointMetadataService: EndpointMetadataService, cloud: CloudSetup, esClient: ElasticsearchClient, - appFeatures: AppFeatures + appFeatures: AppFeaturesService ): PutPackagePolicyUpdateCallback => { return async (newPackagePolicy: NewPackagePolicy): Promise => { if (!isEndpointPackagePolicy(newPackagePolicy)) { diff --git a/x-pack/plugins/security_solution/server/fleet_integration/handlers/create_default_policy.test.ts b/x-pack/plugins/security_solution/server/fleet_integration/handlers/create_default_policy.test.ts index 2e847eff58f93..03abd2a6b1fb1 100644 --- a/x-pack/plugins/security_solution/server/fleet_integration/handlers/create_default_policy.test.ts +++ b/x-pack/plugins/security_solution/server/fleet_integration/handlers/create_default_policy.test.ts @@ -8,6 +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 { LicenseService } from '../../../common/license'; import { createDefaultPolicy } from './create_default_policy'; import { ProtectionModes } from '../../../common/endpoint/types'; @@ -19,9 +20,8 @@ import type { PolicyCreateCloudConfig, PolicyCreateEndpointConfig, } from '../types'; -import type { AppFeatures } from '../../lib/app_features'; -import { createAppFeaturesMock } from '../../lib/app_features/mocks'; -import { ALL_APP_FEATURE_KEYS } from '../../../common'; +import type { AppFeaturesService } from '../../lib/app_features_service/app_features_service'; +import { createAppFeaturesServiceMock } from '../../lib/app_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; let licenseService: LicenseService; - let appFeatures: AppFeatures; + let appFeaturesService: AppFeaturesService; 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, appFeatures); + return createDefaultPolicy(licenseService, config, cloud, esClientInfo, appFeaturesService); }; beforeEach(() => { @@ -47,7 +47,7 @@ describe('Create Default Policy tests ', () => { licenseService = new LicenseService(); licenseService.start(licenseEmitter); licenseEmitter.next(Platinum); // set license level to platinum - appFeatures = createAppFeaturesMock(); + appFeaturesService = createAppFeaturesServiceMock(); }); describe('When no config is set', () => { @@ -211,7 +211,7 @@ describe('Create Default Policy tests ', () => { }); it('should set policy to event collection only if endpointPolicyProtections appFeature is disabled', async () => { - appFeatures = createAppFeaturesMock( + appFeaturesService = createAppFeaturesServiceMock( ALL_APP_FEATURE_KEYS.filter((key) => key !== 'endpoint_policy_protections') ); diff --git a/x-pack/plugins/security_solution/server/fleet_integration/handlers/create_default_policy.ts b/x-pack/plugins/security_solution/server/fleet_integration/handlers/create_default_policy.ts index cd78b4c46493a..0dda27872af0e 100644 --- a/x-pack/plugins/security_solution/server/fleet_integration/handlers/create_default_policy.ts +++ b/x-pack/plugins/security_solution/server/fleet_integration/handlers/create_default_policy.ts @@ -7,8 +7,7 @@ import type { CloudSetup } from '@kbn/cloud-plugin/server'; import type { InfoResponse } from '@elastic/elasticsearch/lib/api/types'; -import { AppFeatureSecurityKey } from '../../../common/types/app_features'; -import type { AppFeatures } from '../../lib/app_features'; +import { AppFeatureSecurityKey } from '@kbn/security-solution-features/keys'; import { policyFactory as policyConfigFactory, policyFactoryWithoutPaidFeatures as policyConfigFactoryWithoutPaidFeatures, @@ -26,6 +25,7 @@ import { disableProtections, ensureOnlyEventCollectionIsAllowed, } from '../../../common/endpoint/models/policy_config_helpers'; +import type { AppFeaturesService } from '../../lib/app_features_service/app_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: AppFeatures + appFeatures: AppFeaturesService ): PolicyConfig => { // Pass license and cloud information to use in Policy creation const factoryPolicy = policyConfigFactory( diff --git a/x-pack/plugins/security_solution/server/lib/app_features/app_features.test.ts b/x-pack/plugins/security_solution/server/lib/app_features/app_features.test.ts deleted file mode 100644 index 1951f6d8b00fa..0000000000000 --- a/x-pack/plugins/security_solution/server/lib/app_features/app_features.test.ts +++ /dev/null @@ -1,175 +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 { AppFeatures } from '.'; -import type { AppFeatureKeys, ExperimentalFeatures } from '../../../common'; -import type { PluginSetupContract } from '@kbn/features-plugin/server'; -import { loggingSystemMock } from '@kbn/core-logging-server-mocks'; - -const SECURITY_BASE_CONFIG = { - foo: 'foo', -}; - -const SECURITY_APP_FEATURE_CONFIG = { - 'test-base-feature': { - privileges: { - all: { - ui: ['test-capability'], - api: ['test-capability'], - }, - read: { - ui: ['test-capability'], - api: ['test-capability'], - }, - }, - }, -}; - -const CASES_BASE_CONFIG = { - bar: 'bar', -}; - -const CASES_APP_FEATURE_CONFIG = { - 'test-cases-feature': { - privileges: { - all: { - ui: ['test-cases-capability'], - api: ['test-cases-capability'], - }, - read: { - ui: ['test-cases-capability'], - api: ['test-cases-capability'], - }, - }, - }, -}; - -const ASSISTANT_BASE_CONFIG = { - bar: 'bar', -}; - -const ASSISTANT_APP_FEATURE_CONFIG = { - 'test-assistant-feature': { - privileges: { - all: { - ui: ['test-assistant-capability'], - api: ['test-assistant-capability'], - }, - read: { - ui: ['test-assistant-capability'], - api: ['test-assistant-capability'], - }, - }, - }, -}; - -jest.mock('./security_kibana_features', () => { - return { - getSecurityBaseKibanaFeature: jest.fn(() => SECURITY_BASE_CONFIG), - getSecurityBaseKibanaSubFeatureIds: jest.fn(() => ['subFeature1']), - getSecurityAppFeaturesConfig: jest.fn(() => SECURITY_APP_FEATURE_CONFIG), - }; -}); -jest.mock('./security_kibana_sub_features', () => { - return { - securitySubFeaturesMap: new Map([['subFeature1', { baz: 'baz' }]]), - }; -}); - -jest.mock('./security_cases_kibana_features', () => { - return { - getCasesBaseKibanaFeature: jest.fn(() => CASES_BASE_CONFIG), - getCasesBaseKibanaSubFeatureIds: jest.fn(() => ['subFeature1']), - getCasesAppFeaturesConfig: jest.fn(() => CASES_APP_FEATURE_CONFIG), - }; -}); - -jest.mock('./security_cases_kibana_sub_features', () => { - return { - casesSubFeaturesMap: new Map([['subFeature1', { baz: 'baz' }]]), - }; -}); - -jest.mock('./security_assistant_kibana_features', () => { - return { - getAssistantBaseKibanaFeature: jest.fn(() => ASSISTANT_BASE_CONFIG), - getAssistantBaseKibanaSubFeatureIds: jest.fn(() => ['subFeature1']), - getAssistantAppFeaturesConfig: jest.fn(() => ASSISTANT_APP_FEATURE_CONFIG), - }; -}); - -jest.mock('./security_assistant_kibana_sub_features', () => { - return { - assistantSubFeaturesMap: new Map([['subFeature1', { baz: 'baz' }]]), - }; -}); - -describe('AppFeatures', () => { - it('should register enabled kibana features', () => { - const featuresSetup = { - registerKibanaFeature: jest.fn(), - getKibanaFeatures: jest.fn(), - } as unknown as PluginSetupContract; - - const appFeatureKeys = ['test-base-feature'] as unknown as AppFeatureKeys; - - const appFeatures = new AppFeatures( - loggingSystemMock.create().get('mock'), - [] as unknown as ExperimentalFeatures - ); - appFeatures.init(featuresSetup); - appFeatures.set(appFeatureKeys); - - expect(featuresSetup.registerKibanaFeature).toHaveBeenCalledWith({ - ...SECURITY_BASE_CONFIG, - ...SECURITY_APP_FEATURE_CONFIG['test-base-feature'], - subFeatures: [{ baz: 'baz' }], - }); - }); - - it('should register enabled cases features', () => { - const featuresSetup = { - registerKibanaFeature: jest.fn(), - } as unknown as PluginSetupContract; - - const appFeatureKeys = ['test-cases-feature'] as unknown as AppFeatureKeys; - - const appFeatures = new AppFeatures( - loggingSystemMock.create().get('mock'), - [] as unknown as ExperimentalFeatures - ); - appFeatures.init(featuresSetup); - appFeatures.set(appFeatureKeys); - - expect(featuresSetup.registerKibanaFeature).toHaveBeenCalledWith({ - ...CASES_BASE_CONFIG, - ...CASES_APP_FEATURE_CONFIG['test-cases-feature'], - subFeatures: [{ baz: 'baz' }], - }); - }); - - it('should register enabled assistant features', () => { - const featuresSetup = { - registerKibanaFeature: jest.fn(), - } as unknown as PluginSetupContract; - - const appFeatureKeys = ['test-assistant-feature'] as unknown as AppFeatureKeys; - - const appFeatures = new AppFeatures( - loggingSystemMock.create().get('mock'), - [] as unknown as ExperimentalFeatures - ); - appFeatures.init(featuresSetup); - appFeatures.set(appFeatureKeys); - - expect(featuresSetup.registerKibanaFeature).toHaveBeenCalledWith({ - ...ASSISTANT_BASE_CONFIG, - ...ASSISTANT_APP_FEATURE_CONFIG['test-assistant-feature'], - subFeatures: [{ baz: 'baz' }], - }); - }); -}); diff --git a/x-pack/plugins/security_solution/server/lib/app_features/app_features.ts b/x-pack/plugins/security_solution/server/lib/app_features/app_features.ts deleted file mode 100644 index 0b17f6d71d00d..0000000000000 --- a/x-pack/plugins/security_solution/server/lib/app_features/app_features.ts +++ /dev/null @@ -1,144 +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 { Logger } from '@kbn/core/server'; -import type { PluginSetupContract as FeaturesPluginSetup } from '@kbn/features-plugin/server'; -import type { AppFeatureKey, AppFeatureKeys, ExperimentalFeatures } from '../../../common'; -import type { AppFeatureKibanaConfig, AppFeaturesConfig } from './types'; -import { - getSecurityAppFeaturesConfig, - getSecurityBaseKibanaFeature, - getSecurityBaseKibanaSubFeatureIds, -} from './security_kibana_features'; -import { - getCasesBaseKibanaFeature, - getCasesAppFeaturesConfig, - getCasesBaseKibanaSubFeatureIds, -} from './security_cases_kibana_features'; -import { AppFeaturesConfigMerger } from './app_features_config_merger'; -import { casesSubFeaturesMap } from './security_cases_kibana_sub_features'; -import { securitySubFeaturesMap } from './security_kibana_sub_features'; -import { assistantSubFeaturesMap } from './security_assistant_kibana_sub_features'; -import { - getAssistantAppFeaturesConfig, - getAssistantBaseKibanaFeature, - getAssistantBaseKibanaSubFeatureIds, -} from './security_assistant_kibana_features'; - -export class AppFeatures { - private securityFeatureConfigMerger: AppFeaturesConfigMerger; - private assistantFeatureConfigMerger: AppFeaturesConfigMerger; - private casesFeatureConfigMerger: AppFeaturesConfigMerger; - private appFeatures?: Set; - private featuresSetup?: FeaturesPluginSetup; - - constructor( - private readonly logger: Logger, - private readonly experimentalFeatures: ExperimentalFeatures - ) { - this.securityFeatureConfigMerger = new AppFeaturesConfigMerger( - this.logger, - securitySubFeaturesMap - ); - this.casesFeatureConfigMerger = new AppFeaturesConfigMerger(this.logger, casesSubFeaturesMap); - this.assistantFeatureConfigMerger = new AppFeaturesConfigMerger( - this.logger, - assistantSubFeaturesMap - ); - } - - public init(featuresSetup: FeaturesPluginSetup) { - this.featuresSetup = featuresSetup; - } - - public set(appFeatureKeys: AppFeatureKeys) { - if (this.appFeatures) { - throw new Error('AppFeatures has already been initialized'); - } - this.appFeatures = new Set(appFeatureKeys); - this.registerEnabledKibanaFeatures(); - } - - public isEnabled(appFeatureKey: AppFeatureKey): boolean { - if (!this.appFeatures) { - throw new Error('AppFeatures has not been initialized'); - } - return this.appFeatures.has(appFeatureKey); - } - - protected registerEnabledKibanaFeatures() { - if (this.featuresSetup == null) { - throw new Error( - 'Cannot sync kibana features as featuresSetup is not present. Did you call init?' - ); - } - // register main security Kibana features - const securityBaseKibanaFeature = getSecurityBaseKibanaFeature(); - const securityBaseKibanaSubFeatureIds = getSecurityBaseKibanaSubFeatureIds( - this.experimentalFeatures - ); - const enabledSecurityAppFeaturesConfigs = this.getEnabledAppFeaturesConfigs( - getSecurityAppFeaturesConfig(this.experimentalFeatures) - ); - const completeAppFeatureConfig = this.securityFeatureConfigMerger.mergeAppFeatureConfigs( - securityBaseKibanaFeature, - securityBaseKibanaSubFeatureIds, - enabledSecurityAppFeaturesConfigs - ); - - this.logger.debug(JSON.stringify(completeAppFeatureConfig)); - - this.featuresSetup.registerKibanaFeature(completeAppFeatureConfig); - - // register security cases Kibana features - const securityCasesBaseKibanaFeature = getCasesBaseKibanaFeature(); - const securityCasesBaseKibanaSubFeatureIds = getCasesBaseKibanaSubFeatureIds(); - const enabledCasesAppFeaturesConfigs = this.getEnabledAppFeaturesConfigs( - getCasesAppFeaturesConfig() - ); - const completeCasesAppFeatureConfig = this.casesFeatureConfigMerger.mergeAppFeatureConfigs( - securityCasesBaseKibanaFeature, - securityCasesBaseKibanaSubFeatureIds, - enabledCasesAppFeaturesConfigs - ); - - this.logger.info(JSON.stringify(completeCasesAppFeatureConfig)); - - this.featuresSetup.registerKibanaFeature(completeCasesAppFeatureConfig); - - // register security assistant Kibana features - const securityAssistantBaseKibanaFeature = getAssistantBaseKibanaFeature(); - const securityAssistantBaseKibanaSubFeatureIds = getAssistantBaseKibanaSubFeatureIds(); - const enabledAssistantAppFeaturesConfigs = this.getEnabledAppFeaturesConfigs( - getAssistantAppFeaturesConfig() - ); - const completeAssistantAppFeatureConfig = - this.assistantFeatureConfigMerger.mergeAppFeatureConfigs( - securityAssistantBaseKibanaFeature, - securityAssistantBaseKibanaSubFeatureIds, - enabledAssistantAppFeaturesConfigs - ); - - this.logger.info(JSON.stringify(completeAssistantAppFeatureConfig)); - - this.featuresSetup.registerKibanaFeature(completeAssistantAppFeatureConfig); - } - - private getEnabledAppFeaturesConfigs( - appFeaturesConfigs: Partial - ): AppFeatureKibanaConfig[] { - return Object.entries(appFeaturesConfigs).reduce( - (acc, [appFeatureKey, appFeatureConfig]) => { - if (this.isEnabled(appFeatureKey as AppFeatureKey)) { - acc.push(appFeatureConfig); - } - return acc; - }, - [] - ); - } -} diff --git a/x-pack/plugins/security_solution/server/lib/app_features/mocks.ts b/x-pack/plugins/security_solution/server/lib/app_features/mocks.ts deleted file mode 100644 index 1a5efc9c64e37..0000000000000 --- a/x-pack/plugins/security_solution/server/lib/app_features/mocks.ts +++ /dev/null @@ -1,38 +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 { Logger } from '@kbn/core/server'; -import type { PluginSetupContract as FeaturesPluginSetup } from '@kbn/features-plugin/server'; -import { loggingSystemMock } from '@kbn/core-logging-server-mocks'; -import { featuresPluginMock } from '@kbn/features-plugin/server/mocks'; -import { AppFeatures } from './app_features'; -import type { AppFeatureKeys, ExperimentalFeatures } from '../../../common'; -import { ALL_APP_FEATURE_KEYS, allowedExperimentalValues } from '../../../common'; - -class AppFeaturesMock extends AppFeatures { - protected registerEnabledKibanaFeatures() { - // NOOP - } -} - -export const createAppFeaturesMock = ( - /** What features keys should be enabled. Default is all */ - enabledFeatureKeys: AppFeatureKeys = [...ALL_APP_FEATURE_KEYS], - experimentalFeatures: ExperimentalFeatures = { ...allowedExperimentalValues }, - featuresPluginSetupContract: FeaturesPluginSetup = featuresPluginMock.createSetup(), - logger: Logger = loggingSystemMock.create().get('appFeatureMock') -) => { - const appFeatures = new AppFeaturesMock(logger, experimentalFeatures); - - appFeatures.init(featuresPluginSetupContract); - - if (enabledFeatureKeys) { - appFeatures.set(enabledFeatureKeys); - } - - return appFeatures; -}; diff --git a/x-pack/plugins/security_solution/server/lib/app_features/security_assistant_kibana_features.ts b/x-pack/plugins/security_solution/server/lib/app_features/security_assistant_kibana_features.ts deleted file mode 100644 index 1927591da202f..0000000000000 --- a/x-pack/plugins/security_solution/server/lib/app_features/security_assistant_kibana_features.ts +++ /dev/null @@ -1,74 +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 { i18n } from '@kbn/i18n'; - -import { DEFAULT_APP_CATEGORIES } from '@kbn/core/server'; -import type { AppFeaturesAssistantConfig, BaseKibanaFeatureConfig } from './types'; -import { APP_ID, ASSISTANT_FEATURE_ID } from '../../../common/constants'; -import { AppFeatureAssistantKey } from '../../../common/types/app_features'; -import type { AssistantSubFeatureId } from './security_assistant_kibana_sub_features'; - -export const getAssistantBaseKibanaFeature = (): BaseKibanaFeatureConfig => ({ - id: ASSISTANT_FEATURE_ID, - name: i18n.translate( - 'xpack.securitySolution.featureRegistry.linkSecuritySolutionAssistantTitle', - { - defaultMessage: 'Elastic AI Assistant', - } - ), - order: 1100, - category: DEFAULT_APP_CATEGORIES.security, - app: [ASSISTANT_FEATURE_ID, 'kibana'], - catalogue: [APP_ID], - minimumLicense: 'enterprise', - privileges: { - all: { - api: [], - app: [ASSISTANT_FEATURE_ID, 'kibana'], - catalogue: [APP_ID], - savedObject: { - all: [], - read: [], - }, - ui: [], - }, - read: { - // No read-only mode currently supported - disabled: true, - savedObject: { - all: [], - read: [], - }, - ui: [], - }, - }, -}); - -export const getAssistantBaseKibanaSubFeatureIds = (): AssistantSubFeatureId[] => [ - // This is a sample sub-feature that can be used for future implementations - // AssistantSubFeatureId.createConversation, -]; - -/** - * Maps the AppFeatures keys to Kibana privileges that will be merged - * into the base privileges config for the Security app. - * - * Privileges can be added in different ways: - * - `privileges`: the privileges that will be added directly into the main Security Assistant feature. - * - `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. - */ -export const getAssistantAppFeaturesConfig = (): AppFeaturesAssistantConfig => ({ - [AppFeatureAssistantKey.assistant]: { - privileges: { - all: { - ui: ['ai-assistant'], - }, - }, - }, -}); diff --git a/x-pack/plugins/security_solution/server/lib/app_features/security_cases_kibana_features.ts b/x-pack/plugins/security_solution/server/lib/app_features/security_cases_kibana_features.ts deleted file mode 100644 index a2bf02c59b306..0000000000000 --- a/x-pack/plugins/security_solution/server/lib/app_features/security_cases_kibana_features.ts +++ /dev/null @@ -1,118 +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 { i18n } from '@kbn/i18n'; - -import { hiddenTypes as filesSavedObjectTypes } from '@kbn/files-plugin/server/saved_objects'; -import { DEFAULT_APP_CATEGORIES } from '@kbn/core/server'; -import { - createUICapabilities as createCasesUICapabilities, - getApiTags as getCasesApiTags, -} from '@kbn/cases-plugin/common'; -import { - CASES_CONNECTORS_CAPABILITY, - GET_CONNECTORS_CONFIGURE_API_TAG, -} from '@kbn/cases-plugin/common/constants'; -import type { AppFeaturesCasesConfig, BaseKibanaFeatureConfig } from './types'; -import { APP_ID, CASES_FEATURE_ID } from '../../../common/constants'; -import { CasesSubFeatureId } from './security_cases_kibana_sub_features'; -import { AppFeatureCasesKey } from '../../../common/types/app_features'; - -const casesCapabilities = createCasesUICapabilities(); -const casesApiTags = getCasesApiTags(APP_ID); - -export const getCasesBaseKibanaFeature = (): BaseKibanaFeatureConfig => { - // On SecuritySolution essentials cases does not have the connector feature - const casesAllUICapabilities = casesCapabilities.all.filter( - (capability) => capability !== CASES_CONNECTORS_CAPABILITY - ); - - const casesReadUICapabilities = casesCapabilities.read.filter( - (capability) => capability !== CASES_CONNECTORS_CAPABILITY - ); - - const casesAllAPICapabilities = casesApiTags.all.filter( - (capability) => capability !== GET_CONNECTORS_CONFIGURE_API_TAG - ); - - const casesReadAPICapabilities = casesApiTags.read.filter( - (capability) => capability !== GET_CONNECTORS_CONFIGURE_API_TAG - ); - - return { - id: CASES_FEATURE_ID, - name: i18n.translate('xpack.securitySolution.featureRegistry.linkSecuritySolutionCaseTitle', { - defaultMessage: 'Cases', - }), - order: 1100, - category: DEFAULT_APP_CATEGORIES.security, - app: [CASES_FEATURE_ID, 'kibana'], - catalogue: [APP_ID], - cases: [APP_ID], - privileges: { - all: { - api: casesAllAPICapabilities, - app: [CASES_FEATURE_ID, 'kibana'], - catalogue: [APP_ID], - cases: { - create: [APP_ID], - read: [APP_ID], - update: [APP_ID], - }, - savedObject: { - all: [...filesSavedObjectTypes], - read: [...filesSavedObjectTypes], - }, - ui: casesAllUICapabilities, - }, - read: { - api: casesReadAPICapabilities, - app: [CASES_FEATURE_ID, 'kibana'], - catalogue: [APP_ID], - cases: { - read: [APP_ID], - }, - savedObject: { - all: [], - read: [...filesSavedObjectTypes], - }, - ui: casesReadUICapabilities, - }, - }, - }; -}; - -export const getCasesBaseKibanaSubFeatureIds = (): CasesSubFeatureId[] => [ - CasesSubFeatureId.deleteCases, -]; - -/** - * Maps the AppFeatures 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: - * - `privileges`: the privileges that will be added directly into the main Security Cases feature. - * - `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. - */ -export const getCasesAppFeaturesConfig = (): AppFeaturesCasesConfig => ({ - [AppFeatureCasesKey.casesConnectors]: { - privileges: { - all: { - api: [GET_CONNECTORS_CONFIGURE_API_TAG], // Add cases connector get connectors API privileges - ui: [CASES_CONNECTORS_CAPABILITY], // Add cases connector UI privileges - cases: { - push: [APP_ID], // Add cases connector push privileges - }, - }, - read: { - api: [GET_CONNECTORS_CONFIGURE_API_TAG], // Add cases connector get connectors API privileges - ui: [CASES_CONNECTORS_CAPABILITY], // Add cases connector UI privileges - }, - }, - }, -}); diff --git a/x-pack/plugins/security_solution/server/lib/app_features/security_cases_kibana_sub_features.ts b/x-pack/plugins/security_solution/server/lib/app_features/security_cases_kibana_sub_features.ts deleted file mode 100644 index ab3069a59cc88..0000000000000 --- a/x-pack/plugins/security_solution/server/lib/app_features/security_cases_kibana_sub_features.ts +++ /dev/null @@ -1,58 +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 { i18n } from '@kbn/i18n'; -import { hiddenTypes as filesSavedObjectTypes } from '@kbn/files-plugin/server/saved_objects'; -import type { SubFeatureConfig } from '@kbn/features-plugin/common'; -import { - createUICapabilities as createCasesUICapabilities, - getApiTags as getCasesApiTags, -} from '@kbn/cases-plugin/common'; -import { APP_ID } from '../../../common/constants'; - -const casesCapabilities = createCasesUICapabilities(); -const casesApiTags = getCasesApiTags(APP_ID); - -const deleteCasesSubFeature: SubFeatureConfig = { - name: i18n.translate('xpack.securitySolution.featureRegistry.deleteSubFeatureName', { - defaultMessage: 'Delete', - }), - privilegeGroups: [ - { - groupType: 'independent', - privileges: [ - { - api: casesApiTags.delete, - id: 'cases_delete', - name: i18n.translate('xpack.securitySolution.featureRegistry.deleteSubFeatureDetails', { - defaultMessage: 'Delete cases and comments', - }), - includeIn: 'all', - savedObject: { - all: [...filesSavedObjectTypes], - read: [...filesSavedObjectTypes], - }, - cases: { - delete: [APP_ID], - }, - ui: casesCapabilities.delete, - }, - ], - }, - ], -}; - -export enum CasesSubFeatureId { - deleteCases = 'deleteCasesSubFeature', -} - -// Defines all the ordered Security Cases subFeatures available -export const casesSubFeaturesMap = Object.freeze( - new Map([ - [CasesSubFeatureId.deleteCases, deleteCasesSubFeature], - ]) -); diff --git a/x-pack/plugins/security_solution/server/lib/app_features/security_kibana_features.ts b/x-pack/plugins/security_solution/server/lib/app_features/security_kibana_features.ts deleted file mode 100644 index 0a77e5a9e5d7e..0000000000000 --- a/x-pack/plugins/security_solution/server/lib/app_features/security_kibana_features.ts +++ /dev/null @@ -1,239 +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 { i18n } from '@kbn/i18n'; - -import { DEFAULT_APP_CATEGORIES } from '@kbn/core/server'; -import { DATA_VIEW_SAVED_OBJECT_TYPE } from '@kbn/data-views-plugin/common'; -import { EXCEPTION_LIST_NAMESPACE_AGNOSTIC } from '@kbn/securitysolution-list-constants'; -import { - EQL_RULE_TYPE_ID, - INDICATOR_RULE_TYPE_ID, - ML_RULE_TYPE_ID, - NEW_TERMS_RULE_TYPE_ID, - QUERY_RULE_TYPE_ID, - SAVED_QUERY_RULE_TYPE_ID, - THRESHOLD_RULE_TYPE_ID, -} from '@kbn/securitysolution-rules'; -import type { ExperimentalFeatures } from '../../../common'; -import { SecuritySubFeatureId } from './security_kibana_sub_features'; -import { APP_ID, LEGACY_NOTIFICATIONS_ID, SERVER_APP_ID } from '../../../common/constants'; -import { savedObjectTypes } from '../../saved_objects'; -import type { AppFeaturesSecurityConfig, BaseKibanaFeatureConfig } from './types'; -import { AppFeatureSecurityKey } from '../../../common/types/app_features'; - -// Same as the plugin id defined by Cloud Security Posture -const CLOUD_POSTURE_APP_ID = 'csp'; -// Same as the saved-object type for rules defined by Cloud Security Posture -const CLOUD_POSTURE_SAVED_OBJECT_RULE_TYPE = 'csp_rule'; - -const SECURITY_RULE_TYPES = [ - LEGACY_NOTIFICATIONS_ID, - EQL_RULE_TYPE_ID, - INDICATOR_RULE_TYPE_ID, - ML_RULE_TYPE_ID, - QUERY_RULE_TYPE_ID, - SAVED_QUERY_RULE_TYPE_ID, - THRESHOLD_RULE_TYPE_ID, - NEW_TERMS_RULE_TYPE_ID, -]; - -export const getSecurityBaseKibanaFeature = (): BaseKibanaFeatureConfig => ({ - id: SERVER_APP_ID, - name: i18n.translate('xpack.securitySolution.featureRegistry.linkSecuritySolutionTitle', { - defaultMessage: 'Security', - }), - order: 1100, - category: DEFAULT_APP_CATEGORIES.security, - app: [APP_ID, CLOUD_POSTURE_APP_ID, 'kibana'], - catalogue: [APP_ID], - management: { - insightsAndAlerting: ['triggersActions'], - }, - alerting: SECURITY_RULE_TYPES, - privileges: { - all: { - app: [APP_ID, CLOUD_POSTURE_APP_ID, 'kibana'], - catalogue: [APP_ID], - api: [ - APP_ID, - 'lists-all', - 'lists-read', - 'lists-summary', - 'rac', - 'cloud-security-posture-all', - 'cloud-security-posture-read', - ], - savedObject: { - all: [ - 'alert', - 'exception-list', - EXCEPTION_LIST_NAMESPACE_AGNOSTIC, - DATA_VIEW_SAVED_OBJECT_TYPE, - ...savedObjectTypes, - CLOUD_POSTURE_SAVED_OBJECT_RULE_TYPE, - ], - read: [], - }, - alerting: { - rule: { - all: SECURITY_RULE_TYPES, - }, - alert: { - all: SECURITY_RULE_TYPES, - }, - }, - management: { - insightsAndAlerting: ['triggersActions'], - }, - ui: ['show', 'crud'], - }, - read: { - app: [APP_ID, CLOUD_POSTURE_APP_ID, 'kibana'], - catalogue: [APP_ID], - api: [APP_ID, 'lists-read', 'rac', 'cloud-security-posture-read'], - savedObject: { - all: [], - read: [ - 'exception-list', - EXCEPTION_LIST_NAMESPACE_AGNOSTIC, - DATA_VIEW_SAVED_OBJECT_TYPE, - ...savedObjectTypes, - CLOUD_POSTURE_SAVED_OBJECT_RULE_TYPE, - ], - }, - alerting: { - rule: { - read: SECURITY_RULE_TYPES, - }, - alert: { - all: SECURITY_RULE_TYPES, - }, - }, - management: { - insightsAndAlerting: ['triggersActions'], - }, - ui: ['show'], - }, - }, -}); - -/** - * Returns the list of Security SubFeature IDs that should be loaded and available in - * kibana regardless of PLI or License level. - * @param _ - */ -export const getSecurityBaseKibanaSubFeatureIds = ( - _: ExperimentalFeatures // currently un-used, but left here as a convenience for possible future use -): SecuritySubFeatureId[] => [SecuritySubFeatureId.hostIsolation]; - -/** - * Maps the AppFeatures keys to Kibana privileges that will be merged - * into the base privileges config for the Security app. - * - * Privileges can be added in different ways: - * - `privileges`: the privileges that will be added directly into the main Security feature. - * - `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 getSecurityAppFeaturesConfig = ( - _: ExperimentalFeatures // currently un-used, but left here as a convenience for possible future use -): AppFeaturesSecurityConfig => { - return { - [AppFeatureSecurityKey.advancedInsights]: { - privileges: { - all: { - ui: ['entity-analytics'], - api: [`${APP_ID}-entity-analytics`], - }, - read: { - ui: ['entity-analytics'], - api: [`${APP_ID}-entity-analytics`], - }, - }, - }, - [AppFeatureSecurityKey.investigationGuide]: { - privileges: { - all: { - ui: ['investigation-guide'], - }, - read: { - ui: ['investigation-guide'], - }, - }, - }, - - [AppFeatureSecurityKey.threatIntelligence]: { - privileges: { - all: { - ui: ['threat-intelligence'], - api: [`${APP_ID}-threat-intelligence`], - }, - read: { - ui: ['threat-intelligence'], - api: [`${APP_ID}-threat-intelligence`], - }, - }, - }, - - [AppFeatureSecurityKey.endpointHostManagement]: { - subFeatureIds: [SecuritySubFeatureId.endpointList], - }, - - [AppFeatureSecurityKey.endpointPolicyManagement]: { - subFeatureIds: [SecuritySubFeatureId.policyManagement], - }, - - // Adds no additional kibana feature controls - [AppFeatureSecurityKey.endpointPolicyProtections]: {}, - - [AppFeatureSecurityKey.endpointArtifactManagement]: { - subFeatureIds: [ - SecuritySubFeatureId.trustedApplications, - SecuritySubFeatureId.blocklist, - SecuritySubFeatureId.eventFilters, - ], - subFeaturesPrivileges: [ - { - id: 'host_isolation_exceptions_all', - api: [ - `${APP_ID}-accessHostIsolationExceptions`, - `${APP_ID}-writeHostIsolationExceptions`, - ], - ui: ['accessHostIsolationExceptions', 'writeHostIsolationExceptions'], - }, - { - id: 'host_isolation_exceptions_read', - api: [`${APP_ID}-accessHostIsolationExceptions`], - ui: ['accessHostIsolationExceptions'], - }, - ], - }, - - [AppFeatureSecurityKey.endpointResponseActions]: { - subFeatureIds: [ - SecuritySubFeatureId.hostIsolationExceptions, - - SecuritySubFeatureId.responseActionsHistory, - SecuritySubFeatureId.processOperations, - SecuritySubFeatureId.fileOperations, - SecuritySubFeatureId.executeAction, - ], - subFeaturesPrivileges: [ - // Adds the privilege to Isolate hosts to the already loaded `host_isolation_all` - // sub-feature (always loaded), which included the `release` privilege already - { - id: 'host_isolation_all', - api: [`${APP_ID}-writeHostIsolation`], - ui: ['writeHostIsolation'], - }, - ], - }, - - [AppFeatureSecurityKey.osqueryAutomatedResponseActions]: {}, - }; -}; diff --git a/x-pack/plugins/security_solution/server/lib/app_features/types.ts b/x-pack/plugins/security_solution/server/lib/app_features/types.ts deleted file mode 100644 index e6a4fd8db0304..0000000000000 --- a/x-pack/plugins/security_solution/server/lib/app_features/types.ts +++ /dev/null @@ -1,39 +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 { KibanaFeatureConfig, SubFeaturePrivilegeConfig } from '@kbn/features-plugin/common'; -import type { AppFeatureKey } from '../../../common'; -import type { - AppFeatureSecurityKey, - AppFeatureCasesKey, - AppFeatureAssistantKey, -} from '../../../common/types/app_features'; -import type { RecursivePartial } from '../../../common/utility_types'; - -export type BaseKibanaFeatureConfig = Omit; -export type SubFeaturesPrivileges = RecursivePartial; -export type AppFeatureKibanaConfig = - RecursivePartial & { - subFeatureIds?: T[]; - subFeaturesPrivileges?: SubFeaturesPrivileges[]; - }; -export type AppFeaturesConfig = Record< - AppFeatureKey, - AppFeatureKibanaConfig ->; -export type AppFeaturesSecurityConfig = Record< - AppFeatureSecurityKey, - AppFeatureKibanaConfig ->; -export type AppFeaturesCasesConfig = Record< - AppFeatureCasesKey, - AppFeatureKibanaConfig ->; -export type AppFeaturesAssistantConfig = Record< - AppFeatureAssistantKey, - AppFeatureKibanaConfig ->; diff --git a/x-pack/plugins/security_solution/server/lib/app_features_service/app_features.test.ts b/x-pack/plugins/security_solution/server/lib/app_features_service/app_features.test.ts new file mode 100644 index 0000000000000..8effe3837f76d --- /dev/null +++ b/x-pack/plugins/security_solution/server/lib/app_features_service/app_features.test.ts @@ -0,0 +1,185 @@ +/* + * 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 { PluginSetupContract } from '@kbn/features-plugin/server'; +import { loggingSystemMock } from '@kbn/core-logging-server-mocks'; +import { AppFeatures } from './app_features'; +import type { + AppFeatureKeyType, + AppFeaturesConfig, + AppSubFeaturesMap, + BaseKibanaFeatureConfig, +} from '@kbn/security-solution-features'; + +const category = { + id: 'security', + label: 'Security app category', +}; + +const baseKibanaFeature: BaseKibanaFeatureConfig = { + id: 'FEATURE_ID', + name: 'Base Feature', + order: 1100, + app: ['FEATURE_ID', 'kibana'], + catalogue: ['APP_ID'], + privileges: { + all: { + api: ['api-read', 'api-write'], + app: ['FEATURE_ID', 'kibana'], + catalogue: ['APP_ID'], + savedObject: { + all: [], + read: [], + }, + ui: ['write', 'read'], + }, + read: { + api: ['api-read'], + app: ['FEATURE_ID', 'kibana'], + catalogue: ['APP_ID'], + savedObject: { + all: [], + read: [], + }, + ui: ['read'], + }, + }, + category, +}; + +const privileges = { + privileges: { + all: { + api: ['api-read', 'api-write', 'test-capability'], + app: ['FEATURE_ID', 'kibana'], + catalogue: ['APP_ID'], + savedObject: { + all: [], + read: [], + }, + ui: ['write', 'read', 'test-capability'], + }, + read: { + api: ['api-read', 'test-capability'], + app: ['FEATURE_ID', 'kibana'], + catalogue: ['APP_ID'], + savedObject: { + all: [], + read: [], + }, + ui: ['read', 'test-capability'], + }, + }, +}; + +const SECURITY_APP_FEATURE_CONFIG: AppFeaturesConfig = new Map(); +SECURITY_APP_FEATURE_CONFIG.set('test-base-feature' as AppFeatureKeyType, { + privileges: { + all: { + ui: ['test-capability'], + api: ['test-capability'], + }, + read: { + ui: ['test-capability'], + api: ['test-capability'], + }, + }, +}); + +const CASES_BASE_CONFIG = { + privileges: { + all: { + api: ['api-read', 'api-write', 'test-cases-capability'], + app: ['FEATURE_ID', 'kibana'], + catalogue: ['APP_ID'], + savedObject: { + all: [], + read: [], + }, + ui: ['write', 'read', 'test-cases-capability'], + }, + read: { + api: ['api-read', 'test-cases-capability'], + app: ['FEATURE_ID', 'kibana'], + catalogue: ['APP_ID'], + savedObject: { + all: [], + read: [], + }, + ui: ['read', 'test-cases-capability'], + }, + }, +}; + +const CASES_APP_FEATURE_CONFIG: AppFeaturesConfig = new Map(); +CASES_APP_FEATURE_CONFIG.set('test-cases-feature' as AppFeatureKeyType, { + privileges: { + all: { + ui: ['test-cases-capability'], + api: ['test-cases-capability'], + }, + read: { + ui: ['test-cases-capability'], + api: ['test-cases-capability'], + }, + }, +}); + +const securityKibanaSubFeatures = { + securitySubFeaturesMap: new Map([['subFeature1', { baz: 'baz' }]]), +}; + +const securityCasesKibanaSubFeatures = { + casesSubFeaturesMap: new Map([['subFeature1', { baz: 'baz' }]]), +}; + +describe('AppFeatures', () => { + it('should register enabled kibana features', () => { + const featuresSetup = { + registerKibanaFeature: jest.fn(), + getKibanaFeatures: jest.fn(), + } as unknown as PluginSetupContract; + + const appFeatures = new AppFeatures( + loggingSystemMock.create().get('mock'), + securityKibanaSubFeatures.securitySubFeaturesMap as unknown as AppSubFeaturesMap, + baseKibanaFeature, + ['subFeature1'] + ); + appFeatures.init(featuresSetup); + appFeatures.setConfig(SECURITY_APP_FEATURE_CONFIG); + + expect(featuresSetup.registerKibanaFeature).toHaveBeenCalledWith({ + ...baseKibanaFeature, + ...SECURITY_APP_FEATURE_CONFIG.get('test-base-feature' as AppFeatureKeyType), + ...privileges, + subFeatures: [{ baz: 'baz' }], + }); + }); + + it('should register enabled cases features', () => { + const featuresSetup = { + registerKibanaFeature: jest.fn(), + } as unknown as PluginSetupContract; + + const appFeatures = new AppFeatures( + loggingSystemMock.create().get('mock'), + securityCasesKibanaSubFeatures.casesSubFeaturesMap as unknown as AppSubFeaturesMap, + baseKibanaFeature, + ['subFeature1'] + ); + appFeatures.init(featuresSetup); + appFeatures.setConfig(CASES_APP_FEATURE_CONFIG); + + expect(featuresSetup.registerKibanaFeature).toHaveBeenCalledWith({ + ...baseKibanaFeature, + ...CASES_APP_FEATURE_CONFIG.get('test-cases-feature' as AppFeatureKeyType), + subFeatures: [{ baz: 'baz' }], + ...CASES_BASE_CONFIG, + }); + }); +}); diff --git a/x-pack/plugins/security_solution/server/lib/app_features_service/app_features.ts b/x-pack/plugins/security_solution/server/lib/app_features_service/app_features.ts new file mode 100644 index 0000000000000..7b264f49faa50 --- /dev/null +++ b/x-pack/plugins/security_solution/server/lib/app_features_service/app_features.ts @@ -0,0 +1,60 @@ +/* + * 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 { Logger } from '@kbn/core/server'; +import type { PluginSetupContract as FeaturesPluginSetup } from '@kbn/features-plugin/server'; +import type { + AppFeatureKeyType, + AppFeaturesConfig, + AppSubFeaturesMap, + BaseKibanaFeatureConfig, +} from '@kbn/security-solution-features'; +import { AppFeaturesConfigMerger } from './app_features_config_merger'; + +export class AppFeatures { + private featureConfigMerger: AppFeaturesConfigMerger; + private appFeatures?: Set; + private featuresSetup?: FeaturesPluginSetup; + + constructor( + private readonly logger: Logger, + subFeaturesMap: AppSubFeaturesMap, + private readonly baseKibanaFeature: BaseKibanaFeatureConfig, + private readonly baseKibanaSubFeatureIds: T[] + ) { + this.featureConfigMerger = new AppFeaturesConfigMerger(this.logger, subFeaturesMap); + } + + public init(featuresSetup: FeaturesPluginSetup) { + this.featuresSetup = featuresSetup; + } + + public setConfig(config: AppFeaturesConfig) { + if (this.appFeatures) { + throw new Error('AppFeatures has already been registered'); + } + this.registerEnabledKibanaFeatures(config); + } + + private registerEnabledKibanaFeatures(appFeatureConfig: AppFeaturesConfig) { + 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( + this.baseKibanaFeature, + this.baseKibanaSubFeatureIds, + Array.from(appFeatureConfig.values()) + ); + + this.logger.debug(JSON.stringify(completeAppFeatureConfig)); + + this.featuresSetup.registerKibanaFeature(completeAppFeatureConfig); + } +} diff --git a/x-pack/plugins/security_solution/server/lib/app_features/app_features_config_merger.test.ts b/x-pack/plugins/security_solution/server/lib/app_features_service/app_features_config_merger.test.ts similarity index 99% rename from x-pack/plugins/security_solution/server/lib/app_features/app_features_config_merger.test.ts rename to x-pack/plugins/security_solution/server/lib/app_features_service/app_features_config_merger.test.ts index 49845c694ddb2..d0b95d50e8fb8 100644 --- a/x-pack/plugins/security_solution/server/lib/app_features/app_features_config_merger.test.ts +++ b/x-pack/plugins/security_solution/server/lib/app_features_service/app_features_config_merger.test.ts @@ -8,7 +8,7 @@ import { loggingSystemMock } from '@kbn/core/server/mocks'; import { AppFeaturesConfigMerger } from './app_features_config_merger'; import type { Logger } from '@kbn/core/server'; -import type { AppFeatureKibanaConfig } from './types'; +import type { AppFeatureKibanaConfig } from '@kbn/security-solution-features'; import type { KibanaFeatureConfig, SubFeatureConfig } from '@kbn/features-plugin/common'; const category = { diff --git a/x-pack/plugins/security_solution/server/lib/app_features/app_features_config_merger.ts b/x-pack/plugins/security_solution/server/lib/app_features_service/app_features_config_merger.ts similarity index 96% rename from x-pack/plugins/security_solution/server/lib/app_features/app_features_config_merger.ts rename to x-pack/plugins/security_solution/server/lib/app_features_service/app_features_config_merger.ts index a35273d34f3b1..524b6564eab26 100644 --- a/x-pack/plugins/security_solution/server/lib/app_features/app_features_config_merger.ts +++ b/x-pack/plugins/security_solution/server/lib/app_features_service/app_features_config_merger.ts @@ -5,14 +5,14 @@ * 2.0. */ -import { cloneDeep, mergeWith, isArray, uniq } from 'lodash'; +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, BaseKibanaFeatureConfig, SubFeaturesPrivileges, -} from './types'; +} from '@kbn/security-solution-features'; export class AppFeaturesConfigMerger { constructor( @@ -23,6 +23,7 @@ export class AppFeaturesConfigMerger { /** * Merges `appFeaturesConfigs` into `kibanaFeatureConfig`. * @param kibanaFeatureConfig the KibanaFeatureConfig to merge into + * @param kibanaSubFeatureIds * @param appFeaturesConfigs the AppFeatureKibanaConfig to merge * @returns mergedKibanaFeatureConfig the merged KibanaFeatureConfig * */ diff --git a/x-pack/plugins/security_solution/server/lib/app_features_service/app_features_service.ts b/x-pack/plugins/security_solution/server/lib/app_features_service/app_features_service.ts new file mode 100644 index 0000000000000..97fcf6cf67ed6 --- /dev/null +++ b/x-pack/plugins/security_solution/server/lib/app_features_service/app_features_service.ts @@ -0,0 +1,102 @@ +/* + * 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. + */ +/* + * 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 { 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 { + getAssistantFeature, + getCasesFeature, + getSecurityFeature, +} from '@kbn/security-solution-features/app_features'; +import type { ExperimentalFeatures } from '../../../common'; +import { AppFeatures } from './app_features'; +import type { AppFeaturesConfigurator } 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; + + constructor( + private readonly logger: Logger, + private readonly experimentalFeatures: ExperimentalFeatures + ) { + const securityFeature = getSecurityFeature({ + savedObjects: securityDefaultSavedObjects, + experimentalFeatures: this.experimentalFeatures, + }); + this.securityAppFeatures = new AppFeatures( + this.logger, + securityFeature.subFeaturesMap, + securityFeature.baseKibanaFeature, + securityFeature.baseKibanaSubFeatureIds + ); + + const casesFeature = getCasesFeature({ + uiCapabilities: casesUiCapabilities, + apiTags: casesApiTags, + savedObjects: { files: filesSavedObjectTypes }, + }); + this.casesAppFeatures = new AppFeatures( + this.logger, + casesFeature.subFeaturesMap, + casesFeature.baseKibanaFeature, + casesFeature.baseKibanaSubFeatureIds + ); + + const assistantFeature = getAssistantFeature(); + this.securityAssistantAppFeatures = new AppFeatures( + this.logger, + assistantFeature.subFeaturesMap, + assistantFeature.baseKibanaFeature, + assistantFeature.baseKibanaSubFeatureIds + ); + } + + public init(featuresSetup: FeaturesPluginSetup) { + this.securityAppFeatures.init(featuresSetup); + this.casesAppFeatures.init(featuresSetup); + this.securityAssistantAppFeatures.init(featuresSetup); + } + + public setAppFeaturesConfigurator(configurator: AppFeaturesConfigurator) { + const securityAppFeaturesConfig = configurator.security(this.experimentalFeatures); + this.securityAppFeatures.setConfig(securityAppFeaturesConfig); + + const casesAppFeaturesConfig = configurator.cases(); + this.casesAppFeatures.setConfig(casesAppFeaturesConfig); + + const securityAssistantAppFeaturesConfig = configurator.securityAssistant(); + this.securityAssistantAppFeatures.setConfig(securityAssistantAppFeaturesConfig); + + this.appFeatures = new Set( + Object.freeze([ + ...securityAppFeaturesConfig.keys(), + ...casesAppFeaturesConfig.keys(), + ...securityAssistantAppFeaturesConfig.keys(), + ]) as readonly AppFeatureKeyType[] + ); + } + + public isEnabled(appFeatureKey: AppFeatureKeyType): boolean { + if (!this.appFeatures) { + throw new Error('AppFeatures has not yet been configured'); + } + return this.appFeatures.has(appFeatureKey); + } +} diff --git a/x-pack/plugins/security_solution/server/lib/app_features_service/cases_privileges.ts b/x-pack/plugins/security_solution/server/lib/app_features_service/cases_privileges.ts new file mode 100644 index 0000000000000..9e863385271b9 --- /dev/null +++ b/x-pack/plugins/security_solution/server/lib/app_features_service/cases_privileges.ts @@ -0,0 +1,40 @@ +/* + * 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 { + createUICapabilities as createCasesUICapabilities, + getApiTags as getCasesApiTags, +} from '@kbn/cases-plugin/common'; +import { + CASES_CONNECTORS_CAPABILITY, + GET_CONNECTORS_CONFIGURE_API_TAG, +} from '@kbn/cases-plugin/common/constants'; + +import { APP_ID } from '../../../common/constants'; + +const originalCasesUiCapabilities = createCasesUICapabilities(); +const originalCasesApiTags = getCasesApiTags(APP_ID); + +export const casesUiCapabilities = { + ...originalCasesUiCapabilities, + all: originalCasesUiCapabilities.all.filter( + (capability) => capability !== CASES_CONNECTORS_CAPABILITY + ), + read: originalCasesUiCapabilities.read.filter( + (capability) => capability !== CASES_CONNECTORS_CAPABILITY + ), +}; + +export const casesApiTags = { + ...originalCasesApiTags, + all: originalCasesApiTags.all.filter( + (capability) => capability !== GET_CONNECTORS_CONFIGURE_API_TAG + ), + read: originalCasesApiTags.read.filter( + (capability) => capability !== GET_CONNECTORS_CONFIGURE_API_TAG + ), +}; diff --git a/x-pack/plugins/security_solution/server/lib/app_features_service/index.ts b/x-pack/plugins/security_solution/server/lib/app_features_service/index.ts new file mode 100644 index 0000000000000..5cec9493aca89 --- /dev/null +++ b/x-pack/plugins/security_solution/server/lib/app_features_service/index.ts @@ -0,0 +1,7 @@ +/* + * 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. + */ +export { AppFeaturesService } from './app_features_service'; diff --git a/x-pack/plugins/security_solution/server/lib/app_features_service/mocks.ts b/x-pack/plugins/security_solution/server/lib/app_features_service/mocks.ts new file mode 100644 index 0000000000000..4f47befce92b9 --- /dev/null +++ b/x-pack/plugins/security_solution/server/lib/app_features_service/mocks.ts @@ -0,0 +1,122 @@ +/* + * 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 { Logger } from '@kbn/core/server'; +import type { PluginSetupContract as FeaturesPluginSetup } from '@kbn/features-plugin/server'; +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 { allowedExperimentalValues, type ExperimentalFeatures } from '../../../common'; +import { AppFeaturesService } from './app_features_service'; + +const SECURITY_BASE_CONFIG = { + foo: 'foo', +}; + +const CASES_BASE_CONFIG = { + bar: 'bar', +}; + +const ASSISTANT_BASE_CONFIG = { + bar: 'bar', +}; + +jest.mock('@kbn/security-solution-features/app_features', () => ({ + getSecurityFeature: jest.fn(() => ({ + baseKibanaFeature: SECURITY_BASE_CONFIG, + baseKibanaSubFeatureIds: ['subFeature1'], + subFeaturesMap: new Map([['subFeature1', { baz: 'baz' }]]), + })), + getCasesFeature: jest.fn(() => ({ + baseKibanaFeature: CASES_BASE_CONFIG, + baseKibanaSubFeatureIds: ['subFeature1'], + subFeaturesMap: new Map([['subFeature1', { baz: 'baz' }]]), + })), + getAssistantFeature: jest.fn(() => ({ + baseKibanaFeature: ASSISTANT_BASE_CONFIG, + baseKibanaSubFeatureIds: [], + subFeaturesMap: new Map([]), + })), +})); + +export const createAppFeaturesServiceMock = ( + /** What features keys should be enabled. Default is all */ + enabledFeatureKeys: AppFeatureKeys = [...ALL_APP_FEATURE_KEYS], + experimentalFeatures: ExperimentalFeatures = { ...allowedExperimentalValues }, + featuresPluginSetupContract: FeaturesPluginSetup = featuresPluginMock.createSetup(), + logger: Logger = loggingSystemMock.create().get('appFeatureMock') +) => { + const appFeaturesService = new AppFeaturesService(logger, experimentalFeatures); + + appFeaturesService.init(featuresPluginSetupContract); + + if (enabledFeatureKeys) { + appFeaturesService.setAppFeaturesConfigurator({ + security: jest.fn().mockReturnValue( + new Map( + enabledFeatureKeys.map((key) => [ + key, + { + privileges: { + all: { + ui: ['entity-analytics'], + api: [`test-entity-analytics`], + }, + read: { + ui: ['entity-analytics'], + api: [`test-entity-analytics`], + }, + }, + }, + ]) + ) + ), + cases: jest.fn().mockReturnValue( + new Map( + enabledFeatureKeys.map((key) => [ + key, + { + privileges: { + all: { + ui: ['entity-analytics'], + api: [`test-entity-analytics`], + }, + read: { + ui: ['entity-analytics'], + api: [`test-entity-analytics`], + }, + }, + }, + ]) + ) + ), + securityAssistant: jest.fn().mockReturnValue( + new Map( + enabledFeatureKeys.map((key) => [ + key, + { + privileges: { + all: { + ui: ['entity-analytics'], + api: [`test-entity-analytics`], + }, + read: { + ui: ['entity-analytics'], + api: [`test-entity-analytics`], + }, + }, + }, + ]) + ) + ), + }); + } + + return appFeaturesService; +}; diff --git a/x-pack/plugins/security_solution/server/lib/app_features_service/security_saved_objects.ts b/x-pack/plugins/security_solution/server/lib/app_features_service/security_saved_objects.ts new file mode 100644 index 0000000000000..e34009121afb0 --- /dev/null +++ b/x-pack/plugins/security_solution/server/lib/app_features_service/security_saved_objects.ts @@ -0,0 +1,21 @@ +/* + * 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 { DATA_VIEW_SAVED_OBJECT_TYPE } from '@kbn/data-views-plugin/common'; +import { EXCEPTION_LIST_NAMESPACE_AGNOSTIC } from '@kbn/securitysolution-list-constants'; +import { savedObjectTypes } from '../../saved_objects'; + +// Same as the saved-object type for rules defined by Cloud Security Posture +const CLOUD_POSTURE_SAVED_OBJECT_RULE_TYPE = 'csp_rule'; + +export const securityDefaultSavedObjects = [ + 'exception-list', + EXCEPTION_LIST_NAMESPACE_AGNOSTIC, + DATA_VIEW_SAVED_OBJECT_TYPE, + ...savedObjectTypes, + CLOUD_POSTURE_SAVED_OBJECT_RULE_TYPE, +]; diff --git a/x-pack/plugins/security_solution/server/lib/app_features_service/types.ts b/x-pack/plugins/security_solution/server/lib/app_features_service/types.ts new file mode 100644 index 0000000000000..b2d1054985c4c --- /dev/null +++ b/x-pack/plugins/security_solution/server/lib/app_features_service/types.ts @@ -0,0 +1,20 @@ +/* + * 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 { AppFeaturesConfig } from '@kbn/security-solution-features'; +import type { + SecuritySubFeatureId, + CasesSubFeatureId, + AssistantSubFeatureId, +} from '@kbn/security-solution-features/keys'; +import type { ExperimentalFeatures } from '../../../common'; + +export interface AppFeaturesConfigurator { + security: (experimentalFlags: ExperimentalFeatures) => AppFeaturesConfig; + cases: () => AppFeaturesConfig; + securityAssistant: () => AppFeaturesConfig; +} diff --git a/x-pack/plugins/security_solution/server/plugin.ts b/x-pack/plugins/security_solution/server/plugin.ts index 35719e21a20e6..4ba3d0a2cbc07 100644 --- a/x-pack/plugins/security_solution/server/plugin.ts +++ b/x-pack/plugins/security_solution/server/plugin.ts @@ -20,7 +20,7 @@ import type { ILicense } from '@kbn/licensing-plugin/server'; import { turnOffPolicyProtectionsIfNotSupported } from './endpoint/migrations/turn_off_policy_protections'; import { endpointSearchStrategyProvider } from './search_strategy/endpoint'; import { getScheduleNotificationResponseActionsService } from './lib/detection_engine/rule_response_actions/schedule_notification_response_actions'; -import { siemGuideId, siemGuideConfig } from '../common/guided_onboarding/siem_guide_config'; +import { siemGuideConfig, siemGuideId } from '../common/guided_onboarding/siem_guide_config'; import { createEqlAlertType, createIndicatorMatchAlertType, @@ -38,7 +38,7 @@ import { AppClientFactory } from './client'; import type { ConfigType } from './config'; import { createConfig } from './config'; import { initUiSettings } from './ui_settings'; -import { APP_ID, SERVER_APP_ID, DEFAULT_ALERTS_INDEX } from '../common/constants'; +import { APP_ID, DEFAULT_ALERTS_INDEX, SERVER_APP_ID } from '../common/constants'; import { registerEndpointRoutes } from './endpoint/routes/metadata'; import { registerPolicyRoutes } from './endpoint/routes/policy'; import { registerActionRoutes } from './endpoint/routes/actions'; @@ -60,13 +60,13 @@ import type { IRuleMonitoringService } from './lib/detection_engine/rule_monitor import { createRuleMonitoringService } from './lib/detection_engine/rule_monitoring'; import { EndpointMetadataService } from './endpoint/services/metadata'; import type { - CreateRuleOptions, CreateQueryRuleAdditionalOptions, + CreateRuleOptions, } from './lib/detection_engine/rule_types/types'; // eslint-disable-next-line no-restricted-imports import { - legacyRulesNotificationAlertType, legacyIsNotificationAlertExecutor, + legacyRulesNotificationAlertType, } from './lib/detection_engine/rule_actions_legacy'; import { createSecurityRuleTypeWrapper, @@ -77,13 +77,13 @@ import { RequestContextFactory } from './request_context_factory'; import type { ISecuritySolutionPlugin, - SecuritySolutionPluginSetupDependencies, - SecuritySolutionPluginStartDependencies, + PluginInitializerContext, SecuritySolutionPluginCoreSetupDependencies, SecuritySolutionPluginCoreStartDependencies, SecuritySolutionPluginSetup, + SecuritySolutionPluginSetupDependencies, SecuritySolutionPluginStart, - PluginInitializerContext, + SecuritySolutionPluginStartDependencies, } from './plugin_contract'; import { EndpointFleetServicesFactory } from './endpoint/services/fleet'; import { featureUsageService } from './endpoint/services/feature_usage'; @@ -96,7 +96,7 @@ import { ENDPOINT_SEARCH_STRATEGY, } from '../common/endpoint/constants'; -import { AppFeatures } from './lib/app_features'; +import { AppFeaturesService } from './lib/app_features_service/app_features_service'; import { registerRiskScoringTask } from './lib/risk_engine/tasks/risk_scoring_task'; export type { SetupPlugins, StartPlugins, PluginSetup, PluginStart } from './plugin_contract'; @@ -106,7 +106,7 @@ export class Plugin implements ISecuritySolutionPlugin { private readonly config: ConfigType; private readonly logger: Logger; private readonly appClientFactory: AppClientFactory; - private readonly appFeatures: AppFeatures; + private readonly appFeaturesService: AppFeaturesService; private readonly ruleMonitoringService: IRuleMonitoringService; private readonly endpointAppContextService = new EndpointAppContextService(); @@ -129,7 +129,7 @@ export class Plugin implements ISecuritySolutionPlugin { this.config = serverConfig; this.logger = context.logger.get(); this.appClientFactory = new AppClientFactory(); - this.appFeatures = new AppFeatures(this.logger, this.config.experimentalFeatures); + this.appFeaturesService = new AppFeaturesService(this.logger, this.config.experimentalFeatures); this.ruleMonitoringService = createRuleMonitoringService(this.config, this.logger); this.telemetryEventsSender = new TelemetryEventsSender(this.logger); @@ -153,12 +153,12 @@ export class Plugin implements ISecuritySolutionPlugin { ): SecuritySolutionPluginSetup { this.logger.debug('plugin setup'); - const { appClientFactory, appFeatures, pluginContext, config, logger } = this; + const { appClientFactory, appFeaturesService, pluginContext, config, logger } = this; const experimentalFeatures = config.experimentalFeatures; initSavedObjects(core.savedObjects); initUiSettings(core.uiSettings, experimentalFeatures); - appFeatures.init(plugins.features); + appFeaturesService.init(plugins.features); this.ruleMonitoringService.setup(core, plugins); @@ -403,7 +403,8 @@ export class Plugin implements ISecuritySolutionPlugin { plugins.guidedOnboarding.registerGuideConfig(siemGuideId, siemGuideConfig); return { - setAppFeatures: this.appFeatures.set.bind(this.appFeatures), + setAppFeaturesConfigurator: + appFeaturesService.setAppFeaturesConfigurator.bind(appFeaturesService), }; } @@ -411,7 +412,7 @@ export class Plugin implements ISecuritySolutionPlugin { core: SecuritySolutionPluginCoreStartDependencies, plugins: SecuritySolutionPluginStartDependencies ): SecuritySolutionPluginStart { - const { config, logger } = this; + const { config, logger, appFeaturesService } = this; this.ruleMonitoringService.start(core, plugins); @@ -467,7 +468,7 @@ export class Plugin implements ISecuritySolutionPlugin { experimentalFeatures: config.experimentalFeatures, packagerTaskPackagePolicyUpdateBatchSize: config.packagerTaskPackagePolicyUpdateBatchSize, esClient: core.elasticsearch.client.asInternalUser, - appFeatures: this.appFeatures, + appFeaturesService, }); // Migrate artifacts to fleet and then start the manifest task after that is done @@ -484,7 +485,7 @@ export class Plugin implements ISecuritySolutionPlugin { turnOffPolicyProtectionsIfNotSupported( core.elasticsearch.client.asInternalUser, endpointFleetServicesFactory.asInternalUser(), - this.appFeatures, + appFeaturesService, logger ); }); @@ -513,7 +514,7 @@ export class Plugin implements ISecuritySolutionPlugin { endpointFleetServicesFactory, security: plugins.security, alerting: plugins.alerting, - config: this.config, + config, cases: plugins.cases, logger, manifestManager, @@ -531,7 +532,7 @@ export class Plugin implements ISecuritySolutionPlugin { ), createFleetActionsClient, esClient: core.elasticsearch.client.asInternalUser, - appFeatures: this.appFeatures, + appFeaturesService, }); this.telemetryReceiver.start( diff --git a/x-pack/plugins/security_solution/server/plugin_contract.ts b/x-pack/plugins/security_solution/server/plugin_contract.ts index 26dee3fbbb016..0c34bede016f1 100644 --- a/x-pack/plugins/security_solution/server/plugin_contract.ts +++ b/x-pack/plugins/security_solution/server/plugin_contract.ts @@ -41,7 +41,7 @@ import type { CloudExperimentsPluginStart } from '@kbn/cloud-experiments-plugin/ 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 { AppFeatures } from './lib/app_features/app_features'; +import type { AppFeaturesService } from './lib/app_features_service/app_features_service'; export interface SecuritySolutionPluginSetupDependencies { alerting: AlertingPluginSetup; @@ -84,9 +84,9 @@ export interface SecuritySolutionPluginStartDependencies { export interface SecuritySolutionPluginSetup { /** - * Sets the app features that are available to the Security Solution + * Sets the configurations for app features that are available to the Security Solution */ - setAppFeatures: AppFeatures['set']; + setAppFeaturesConfigurator: AppFeaturesService['setAppFeaturesConfigurator']; } // eslint-disable-next-line @typescript-eslint/no-empty-interface diff --git a/x-pack/plugins/security_solution/tsconfig.json b/x-pack/plugins/security_solution/tsconfig.json index 9604dae1909ce..8355537470d67 100644 --- a/x-pack/plugins/security_solution/tsconfig.json +++ b/x-pack/plugins/security_solution/tsconfig.json @@ -171,6 +171,7 @@ "@kbn/navigation-plugin", "@kbn/core-logging-server-mocks", "@kbn/core-lifecycle-browser", + "@kbn/security-solution-features", "@kbn/handlebars" ] } diff --git a/x-pack/plugins/security_solution_ess/public/upselling/register_upsellings.tsx b/x-pack/plugins/security_solution_ess/public/upselling/register_upsellings.tsx index 930f8443369f8..05af48c280395 100644 --- a/x-pack/plugins/security_solution_ess/public/upselling/register_upsellings.tsx +++ b/x-pack/plugins/security_solution_ess/public/upselling/register_upsellings.tsx @@ -6,14 +6,14 @@ */ import { SecurityPageName } from '@kbn/security-solution-plugin/common'; -import type { UpsellingService } from '@kbn/security-solution-plugin/public'; import type { MessageUpsellings, PageUpsellings, SectionUpsellings, UpsellingMessageId, UpsellingSectionId, -} from '@kbn/security-solution-upselling/service/types'; + UpsellingService, +} from '@kbn/security-solution-upselling/service'; import type { ILicense, LicenseType } from '@kbn/licensing-plugin/public'; import React, { lazy } from 'react'; import { UPGRADE_INVESTIGATION_GUIDE } from '@kbn/security-solution-upselling/messages'; diff --git a/x-pack/plugins/security_solution_ess/server/app_features/cases_app_features_config.ts b/x-pack/plugins/security_solution_ess/server/app_features/cases_app_features_config.ts new file mode 100644 index 0000000000000..58dcaee8f94ff --- /dev/null +++ b/x-pack/plugins/security_solution_ess/server/app_features/cases_app_features_config.ts @@ -0,0 +1,46 @@ +/* + * 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 { + AppFeatureKibanaConfig, + AppFeaturesCasesConfig, + AppFeatureKeys, +} from '@kbn/security-solution-features'; +import type { AppFeatureCasesKey, CasesSubFeatureId } from '@kbn/security-solution-features/keys'; +import { + getCasesDefaultAppFeaturesConfig, + createEnabledAppFeaturesConfigMap, +} from '@kbn/security-solution-features/config'; + +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); + }; + +/** + * Maps the AppFeatures 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: + * - `privileges`: the privileges that will be added directly into the main Security Cases feature. + * - `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 +> = { + ...getCasesDefaultAppFeaturesConfig({ + apiTags: { connectors: GET_CONNECTORS_CONFIGURE_API_TAG }, + uiCapabilities: { connectors: CASES_CONNECTORS_CAPABILITY }, + }), + // ess-specific app features configs here +}; diff --git a/x-pack/plugins/security_solution_ess/server/app_features/index.ts b/x-pack/plugins/security_solution_ess/server/app_features/index.ts new file mode 100644 index 0000000000000..95c5bea8f05df --- /dev/null +++ b/x-pack/plugins/security_solution_ess/server/app_features/index.ts @@ -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 { 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), + }; +}; diff --git a/x-pack/plugins/security_solution_ess/server/app_features/security_app_features_config.ts b/x-pack/plugins/security_solution_ess/server/app_features/security_app_features_config.ts new file mode 100644 index 0000000000000..6badb63d30ed1 --- /dev/null +++ b/x-pack/plugins/security_solution_ess/server/app_features/security_app_features_config.ts @@ -0,0 +1,51 @@ +/* + * 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 { ExperimentalFeatures } from '@kbn/security-solution-plugin/common'; +import type { + AppFeatureKeys, + AppFeatureKibanaConfig, + AppFeaturesSecurityConfig, +} from '@kbn/security-solution-features'; +import { + AppFeatureSecurityKey, + type SecuritySubFeatureId, +} from '@kbn/security-solution-features/keys'; +import { + securityDefaultAppFeaturesConfig, + createEnabledAppFeaturesConfigMap, +} from '@kbn/security-solution-features/config'; +import { + AppFeaturesPrivilegeId, + AppFeaturesPrivileges, +} from '@kbn/security-solution-features/privileges'; + +export const getSecurityAppFeaturesConfigurator = + (enabledAppFeatureKeys: AppFeatureKeys) => + ( + _: ExperimentalFeatures // currently un-used, but left here as a convenience for possible future use + ): AppFeaturesSecurityConfig => { + return createEnabledAppFeaturesConfigMap(securityAppFeaturesConfig, enabledAppFeatureKeys); + }; + +/** + * Maps the AppFeatures keys to Kibana privileges that will be merged + * into the base privileges config for the Security app. + * + * Privileges can be added in different ways: + * - `privileges`: the privileges that will be added directly into the main Security feature. + * - `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 +> = { + ...securityDefaultAppFeaturesConfig, + [AppFeatureSecurityKey.endpointExceptions]: { + privileges: AppFeaturesPrivileges[AppFeaturesPrivilegeId.endpointExceptions], + }, +}; diff --git a/x-pack/plugins/security_solution_ess/server/app_features/security_assistant_app_features_config.ts b/x-pack/plugins/security_solution_ess/server/app_features/security_assistant_app_features_config.ts new file mode 100644 index 0000000000000..b17ebf859a29a --- /dev/null +++ b/x-pack/plugins/security_solution_ess/server/app_features/security_assistant_app_features_config.ts @@ -0,0 +1,41 @@ +/* + * 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, + AppFeatureKibanaConfig, + AppFeaturesAssistantConfig, +} from '@kbn/security-solution-features'; +import { + assistantDefaultAppFeaturesConfig, + createEnabledAppFeaturesConfigMap, +} from '@kbn/security-solution-features/config'; +import type { + AppFeatureAssistantKey, + AssistantSubFeatureId, +} from '@kbn/security-solution-features/keys'; + +export const getSecurityAssistantAppFeaturesConfigurator = + (enabledAppFeatureKeys: AppFeatureKeys) => (): AppFeaturesAssistantConfig => { + return createEnabledAppFeaturesConfigMap(assistantAppFeaturesConfig, enabledAppFeatureKeys); + }; + +/** + * Maps the AppFeatures 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: + * - `privileges`: the privileges that will be added directly into the main Security Assistant feature. + * - `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 +> = { + ...assistantDefaultAppFeaturesConfig, + // ess-specific app features configs here +}; diff --git a/x-pack/plugins/security_solution_ess/server/constants.ts b/x-pack/plugins/security_solution_ess/server/constants.ts index f86558aa1f0bb..4c16f0d14b951 100644 --- a/x-pack/plugins/security_solution_ess/server/constants.ts +++ b/x-pack/plugins/security_solution_ess/server/constants.ts @@ -5,7 +5,7 @@ * 2.0. */ -import { ALL_APP_FEATURE_KEYS } from '@kbn/security-solution-plugin/common'; +import { ALL_APP_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 diff --git a/x-pack/plugins/security_solution_ess/server/plugin.ts b/x-pack/plugins/security_solution_ess/server/plugin.ts index 0083fe1314cfd..784df48988774 100644 --- a/x-pack/plugins/security_solution_ess/server/plugin.ts +++ b/x-pack/plugins/security_solution_ess/server/plugin.ts @@ -6,6 +6,7 @@ */ import type { Plugin, CoreSetup } from '@kbn/core/server'; +import { getProductAppFeaturesConfigurator } from './app_features'; import { DEFAULT_APP_FEATURES } from './constants'; import type { @@ -25,7 +26,8 @@ export class SecuritySolutionEssPlugin > { public setup(_coreSetup: CoreSetup, pluginsSetup: SecuritySolutionEssPluginSetupDeps) { - pluginsSetup.securitySolution.setAppFeatures(DEFAULT_APP_FEATURES); + const appFeaturesConfigurator = getProductAppFeaturesConfigurator(DEFAULT_APP_FEATURES); + pluginsSetup.securitySolution.setAppFeaturesConfigurator(appFeaturesConfigurator); return {}; } diff --git a/x-pack/plugins/security_solution_ess/tsconfig.json b/x-pack/plugins/security_solution_ess/tsconfig.json index 57c520bf896c1..0d18f6a324785 100644 --- a/x-pack/plugins/security_solution_ess/tsconfig.json +++ b/x-pack/plugins/security_solution_ess/tsconfig.json @@ -19,6 +19,8 @@ "@kbn/i18n", "@kbn/cloud-experiments-plugin", "@kbn/kibana-react-plugin", + "@kbn/security-solution-features", + "@kbn/cases-plugin", "@kbn/security-solution-navigation", "@kbn/licensing-plugin", "@kbn/security-solution-upselling", diff --git a/x-pack/plugins/security_solution_serverless/common/pli/pli_config.ts b/x-pack/plugins/security_solution_serverless/common/pli/pli_config.ts index 6122c65aa5de7..1ef3da121b910 100644 --- a/x-pack/plugins/security_solution_serverless/common/pli/pli_config.ts +++ b/x-pack/plugins/security_solution_serverless/common/pli/pli_config.ts @@ -5,7 +5,8 @@ * 2.0. */ -import { AppFeatureKey, type AppFeatureKeys } from '@kbn/security-solution-plugin/common'; +import type { AppFeatureKeys } from '@kbn/security-solution-features'; +import { AppFeatureKey } from '@kbn/security-solution-features/keys'; import type { SecurityProductLine, SecurityProductTier } from '../config'; type PliAppFeatures = Readonly< @@ -24,7 +25,11 @@ export const PLI_APP_FEATURES: PliAppFeatures = { ], }, endpoint: { - essentials: [AppFeatureKey.endpointPolicyProtections, AppFeatureKey.endpointArtifactManagement], + essentials: [ + AppFeatureKey.endpointPolicyProtections, + AppFeatureKey.endpointArtifactManagement, + AppFeatureKey.endpointExceptions, + ], complete: [ AppFeatureKey.endpointResponseActions, AppFeatureKey.osqueryAutomatedResponseActions, diff --git a/x-pack/plugins/security_solution_serverless/common/pli/pli_features.ts b/x-pack/plugins/security_solution_serverless/common/pli/pli_features.ts index 5db2945f512e8..cd20bc6681b3f 100644 --- a/x-pack/plugins/security_solution_serverless/common/pli/pli_features.ts +++ b/x-pack/plugins/security_solution_serverless/common/pli/pli_features.ts @@ -5,7 +5,7 @@ * 2.0. */ -import type { AppFeatureKeys } from '@kbn/security-solution-plugin/common'; +import type { AppFeatureKeys } from '@kbn/security-solution-features/src/types'; import type { SecurityProductTypes } from '../config'; import { ProductTier } from '../product'; import { PLI_APP_FEATURES } from './pli_config'; diff --git a/x-pack/plugins/security_solution_serverless/public/upselling/register_upsellings.test.tsx b/x-pack/plugins/security_solution_serverless/public/upselling/register_upsellings.test.tsx index 6c886c681d0aa..cc045caf3cc21 100644 --- a/x-pack/plugins/security_solution_serverless/public/upselling/register_upsellings.test.tsx +++ b/x-pack/plugins/security_solution_serverless/public/upselling/register_upsellings.test.tsx @@ -5,8 +5,6 @@ * 2.0. */ -import type { UpsellingService } from '@kbn/security-solution-plugin/public'; -import { ALL_APP_FEATURE_KEYS } from '@kbn/security-solution-plugin/common'; import { registerUpsellings, upsellingMessages, @@ -15,6 +13,9 @@ 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 type { UpsellingService } from '@kbn/security-solution-upselling/service'; +import { mockServices } from '../common/services/__mocks__/services.mock'; const mockGetProductAppFeatures = jest.fn(); jest.mock('../../common/pli/pli_features', () => ({ @@ -40,7 +41,7 @@ describe('registerUpsellings', () => { setMessages, } as unknown as UpsellingService; - registerUpsellings(upselling, allProductTypes); + registerUpsellings(upselling, allProductTypes, mockServices); expect(setPages).toHaveBeenCalledTimes(1); expect(setPages).toHaveBeenCalledWith({}); @@ -65,7 +66,7 @@ describe('registerUpsellings', () => { setMessages, } as unknown as UpsellingService; - registerUpsellings(upselling, allProductTypes); + registerUpsellings(upselling, allProductTypes, mockServices); const expectedPagesObject = Object.fromEntries( upsellingPages.map(({ pageName }) => [pageName, expect.anything()]) diff --git a/x-pack/plugins/security_solution_serverless/public/upselling/register_upsellings.tsx b/x-pack/plugins/security_solution_serverless/public/upselling/register_upsellings.tsx index ee8427717ad01..f1b8da6b1557d 100644 --- a/x-pack/plugins/security_solution_serverless/public/upselling/register_upsellings.tsx +++ b/x-pack/plugins/security_solution_serverless/public/upselling/register_upsellings.tsx @@ -4,38 +4,38 @@ * 2.0; you may not use this file except in compliance with the Elastic License * 2.0. */ -import { SecurityPageName, AppFeatureKey } from '@kbn/security-solution-plugin/common'; +import { SecurityPageName } from '@kbn/security-solution-plugin/common'; import type { - UpsellingService, + MessageUpsellings, PageUpsellings, SectionUpsellings, - UpsellingSectionId, -} from '@kbn/security-solution-plugin/public'; -import type { - MessageUpsellings, UpsellingMessageId, + UpsellingSectionId, } from '@kbn/security-solution-upselling/service/types'; +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 { EndpointPolicyProtectionsLazy } from './sections/endpoint_management'; import type { SecurityProductTypes } from '../../common/config'; import { getProductAppFeatures } from '../../common/pli/pli_features'; import { + EntityAnalyticsUpsellingLazy, OsqueryResponseActionsUpsellingSectionLazy, ThreatIntelligencePaywallLazy, - EntityAnalyticsUpsellingLazy, } from './lazy_upselling'; import { getProductTypeByPLI } from './hooks/use_product_type_by_pli'; import type { Services } from '../common/services'; import { withServicesProvider } from '../common/services'; interface UpsellingsConfig { - pli: AppFeatureKey; + pli: AppFeatureKeyType; component: React.ComponentType; } interface UpsellingsMessageConfig { - pli: AppFeatureKey; + pli: AppFeatureKeyType; message: string; id: UpsellingMessageId; } diff --git a/x-pack/plugins/security_solution_serverless/server/app_features/cases_app_features_config.ts b/x-pack/plugins/security_solution_serverless/server/app_features/cases_app_features_config.ts new file mode 100644 index 0000000000000..7e62866816cc3 --- /dev/null +++ b/x-pack/plugins/security_solution_serverless/server/app_features/cases_app_features_config.ts @@ -0,0 +1,45 @@ +/* + * 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 { + AppFeatureKibanaConfig, + AppFeaturesCasesConfig, + AppFeatureKeys, +} from '@kbn/security-solution-features'; +import { + getCasesDefaultAppFeaturesConfig, + createEnabledAppFeaturesConfigMap, +} from '@kbn/security-solution-features/config'; +import type { AppFeatureCasesKey, 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); + }; + +/** + * Maps the AppFeatures 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: + * - `privileges`: the privileges that will be added directly into the main Security Cases feature. + * - `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 +> = { + ...getCasesDefaultAppFeaturesConfig({ + apiTags: { connectors: GET_CONNECTORS_CONFIGURE_API_TAG }, + uiCapabilities: { connectors: CASES_CONNECTORS_CAPABILITY }, + }), + // serverless-specific app features configs here +}; diff --git a/x-pack/plugins/security_solution_serverless/server/app_features/index.ts b/x-pack/plugins/security_solution_serverless/server/app_features/index.ts new file mode 100644 index 0000000000000..95c5bea8f05df --- /dev/null +++ b/x-pack/plugins/security_solution_serverless/server/app_features/index.ts @@ -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 { 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), + }; +}; diff --git a/x-pack/plugins/security_solution_serverless/server/app_features/security_app_features_config.ts b/x-pack/plugins/security_solution_serverless/server/app_features/security_app_features_config.ts new file mode 100644 index 0000000000000..a18e9c39d2f5a --- /dev/null +++ b/x-pack/plugins/security_solution_serverless/server/app_features/security_app_features_config.ts @@ -0,0 +1,44 @@ +/* + * 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 { ExperimentalFeatures } from '@kbn/security-solution-plugin/common'; +import type { + AppFeatureKeys, + AppFeatureKibanaConfig, + AppFeaturesSecurityConfig, +} from '@kbn/security-solution-features'; +import { + securityDefaultAppFeaturesConfig, + createEnabledAppFeaturesConfigMap, +} from '@kbn/security-solution-features/config'; +import { AppFeatureSecurityKey, SecuritySubFeatureId } from '@kbn/security-solution-features/keys'; + +export const getSecurityAppFeaturesConfigurator = + (enabledAppFeatureKeys: AppFeatureKeys) => + ( + _: ExperimentalFeatures // currently un-used, but left here as a convenience for possible future use + ): AppFeaturesSecurityConfig => { + return createEnabledAppFeaturesConfigMap(securityAppFeaturesConfig, enabledAppFeatureKeys); + }; + +/** + * Maps the AppFeatures keys to Kibana privileges that will be merged + * into the base privileges config for the Security app. + * + * Privileges can be added in different ways: + * - `privileges`: the privileges that will be added directly into the main Security feature. + * - `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 +> = { + ...securityDefaultAppFeaturesConfig, + [AppFeatureSecurityKey.endpointExceptions]: { + subFeatureIds: [SecuritySubFeatureId.endpointExceptions], + }, +}; diff --git a/x-pack/plugins/security_solution_serverless/server/app_features/security_assistant_app_features_config.ts b/x-pack/plugins/security_solution_serverless/server/app_features/security_assistant_app_features_config.ts new file mode 100644 index 0000000000000..8c3971faee45e --- /dev/null +++ b/x-pack/plugins/security_solution_serverless/server/app_features/security_assistant_app_features_config.ts @@ -0,0 +1,41 @@ +/* + * 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, + AppFeatureKibanaConfig, + AppFeaturesAssistantConfig, +} from '@kbn/security-solution-features'; +import { + assistantDefaultAppFeaturesConfig, + createEnabledAppFeaturesConfigMap, +} from '@kbn/security-solution-features/config'; +import type { + AppFeatureAssistantKey, + AssistantSubFeatureId, +} from '@kbn/security-solution-features/keys'; + +export const getSecurityAssistantAppFeaturesConfigurator = + (enabledAppFeatureKeys: AppFeatureKeys) => (): AppFeaturesAssistantConfig => { + return createEnabledAppFeaturesConfigMap(assistantAppFeaturesConfig, enabledAppFeatureKeys); + }; + +/** + * Maps the AppFeatures 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: + * - `privileges`: the privileges that will be added directly into the main Security Assistant feature. + * - `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 +> = { + ...assistantDefaultAppFeaturesConfig, + // serverless-specific app features configs here +}; diff --git a/x-pack/plugins/security_solution_serverless/server/plugin.ts b/x-pack/plugins/security_solution_serverless/server/plugin.ts index f83afd4593e4f..f4937ea4f0b32 100644 --- a/x-pack/plugins/security_solution_serverless/server/plugin.ts +++ b/x-pack/plugins/security_solution_serverless/server/plugin.ts @@ -24,6 +24,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 { METERING_TASK as ENDPOINT_METERING_TASK } from './endpoint/constants/metering'; import { endpointMeteringService, @@ -57,7 +58,10 @@ 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}`); - pluginsSetup.securitySolution.setAppFeatures(getProductAppFeatures(this.config.productTypes)); + const appFeaturesConfigurator = getProductAppFeaturesConfigurator( + getProductAppFeatures(this.config.productTypes) + ); + pluginsSetup.securitySolution.setAppFeaturesConfigurator(appFeaturesConfigurator); } pluginsSetup.ml.setFeaturesEnabled({ ad: true, dfa: true, nlp: false }); diff --git a/x-pack/plugins/security_solution_serverless/tsconfig.json b/x-pack/plugins/security_solution_serverless/tsconfig.json index 7e3bf59fad132..0f517caa077aa 100644 --- a/x-pack/plugins/security_solution_serverless/tsconfig.json +++ b/x-pack/plugins/security_solution_serverless/tsconfig.json @@ -10,8 +10,6 @@ "public/**/*.tsx", "server/**/*.ts", "../../../typings/**/*" - , - "../../packages/security-solution/upselling/sections/generic_upselling_section.tsx" ], "exclude": ["target/**/*"], "kbn_references": [ @@ -39,6 +37,8 @@ "@kbn/task-manager-plugin", "@kbn/cloud-plugin", "@kbn/cloud-security-posture-plugin", + "@kbn/security-solution-features", + "@kbn/cases-plugin", "@kbn/fleet-plugin", "@kbn/core-elasticsearch-server", "@kbn/usage-collection-plugin" diff --git a/x-pack/plugins/translations/translations/fr-FR.json b/x-pack/plugins/translations/translations/fr-FR.json index 9ade8eaf3ee41..f4568832a716f 100644 --- a/x-pack/plugins/translations/translations/fr-FR.json +++ b/x-pack/plugins/translations/translations/fr-FR.json @@ -33186,43 +33186,6 @@ "xpack.securitySolution.expandedValue.showTopN.showTopValues": "Afficher les valeurs les plus élevées", "xpack.securitySolution.explore.landing.pageTitle": "Explorer", "xpack.securitySolution.featureCatalogueDescription": "Prévenez, collectez, détectez et traitez les menaces pour une protection unifiée dans toute votre infrastructure.", - "xpack.securitySolution.featureRegistry.deleteSubFeatureDetails": "Supprimer les cas et les commentaires", - "xpack.securitySolution.featureRegistry.deleteSubFeatureName": "Supprimer", - "xpack.securitySolution.featureRegistry.linkSecuritySolutionCaseTitle": "Cas", - "xpack.securitySolution.featureRegistry.linkSecuritySolutionTitle": "Sécurité", - "xpack.securitySolution.featureRegistry.subFeatures.blockList": "Liste noire", - "xpack.securitySolution.featureRegistry.subFeatures.blockList.description": "Étendez la protection d'Elastic Defend contre les processus malveillants et protégez-vous des applications potentiellement nuisibles.", - "xpack.securitySolution.featureRegistry.subFeatures.blockList.privilegesTooltip": "\"Tous les espaces\" est requis pour l'accès à la liste noire.", - "xpack.securitySolution.featureRegistry.subFeatures.endpointList": "Liste de points de terminaison", - "xpack.securitySolution.featureRegistry.subFeatures.endpointList.description": "Affiche tous les hôtes exécutant Elastic Defend et leurs détails d'intégration associés.", - "xpack.securitySolution.featureRegistry.subFeatures.endpointList.privilegesTooltip": "\"Tous les espaces\" est requis pour l'accès à la liste de points de terminaison.", - "xpack.securitySolution.featureRegistry.subFeatures.eventFilters": "Filtres d'événements", - "xpack.securitySolution.featureRegistry.subFeatures.eventFilters.description": "Excluez les événements de point de terminaison dont vous n'avez pas besoin ou que vous ne souhaitez pas stocker dans Elasticsearch.", - "xpack.securitySolution.featureRegistry.subFeatures.eventFilters.privilegesTooltip": "\"Tous les espaces\" est requis pour l'accès aux filtres d'événements.", - "xpack.securitySolution.featureRegistry.subFeatures.executeOperations": "Exécuter les opérations", - "xpack.securitySolution.featureRegistry.subFeatures.executeOperations.description": "Effectuez l'exécution de script sur le point de terminaison.", - "xpack.securitySolution.featureRegistry.subFeatures.executeOperations.privilegesTooltip": "\"Tous les espaces\" est requis pour l'accès aux opérations d'exécution.", - "xpack.securitySolution.featureRegistry.subFeatures.fileOperations": "Opérations de fichier", - "xpack.securitySolution.featureRegistry.subFeatures.fileOperations.description": "Effectuez les actions de réponse liées aux fichiers dans la console de réponse.", - "xpack.securitySolution.featureRegistry.subFeatures.fileOperations.privilegesTooltip": "\"Tous les espaces\" est requis pour l'accès aux opérations de fichier.", - "xpack.securitySolution.featureRegistry.subFeatures.hostIsolation": "Isolation de l'hôte", - "xpack.securitySolution.featureRegistry.subFeatures.hostIsolation.description": "Effectuez les actions de réponse \"isoler\" et \"libérer\".", - "xpack.securitySolution.featureRegistry.subFeatures.hostIsolation.privilegesTooltip": "\"Tous les espaces\" est requis pour l'accès à l'isolation de l'hôte.", - "xpack.securitySolution.featureRegistry.subFeatures.hostIsolationExceptions": "Exceptions d'isolation de l'hôte", - "xpack.securitySolution.featureRegistry.subFeatures.hostIsolationExceptions.description": "Ajoutez des adresses IP spécifiques avec lesquelles les hôtes isolés sont toujours autorisés à communiquer, même lorsqu'ils sont isolés du reste du réseau.", - "xpack.securitySolution.featureRegistry.subFeatures.hostIsolationExceptions.privilegesTooltip": "\"Tous les espaces\" est requis pour l'accès aux exceptions d'isolation de l'hôte.", - "xpack.securitySolution.featureRegistry.subFeatures.policyManagement": "Gestion des politiques Elastic Defend", - "xpack.securitySolution.featureRegistry.subFeatures.policyManagement.description": "Accédez à la politique d'intégration Elastic Defend pour configurer les protections, la collecte des événements et les fonctionnalités de politique avancées.", - "xpack.securitySolution.featureRegistry.subFeatures.policyManagement.privilegesTooltip": "\"Tous les espaces\" est requis pour l'accès à la gestion des politiques.", - "xpack.securitySolution.featureRegistry.subFeatures.processOperations": "Opérations de traitement", - "xpack.securitySolution.featureRegistry.subFeatures.processOperations.description": "Effectuez les actions de réponse liées aux processus dans la console de réponse.", - "xpack.securitySolution.featureRegistry.subFeatures.processOperations.privilegesTooltip": "\"Tous les espaces\" est requis pour l'accès aux opérations de traitement.", - "xpack.securitySolution.featureRegistry.subFeatures.responseActionsHistory": "Historique des actions de réponse", - "xpack.securitySolution.featureRegistry.subFeatures.responseActionsHistory.description": "Accédez à l'historique des actions de réponse effectuées sur les points de terminaison.", - "xpack.securitySolution.featureRegistry.subFeatures.responseActionsHistory.privilegesTooltip": "\"Tous les espaces\" est requis pour l'accès à l'historique des actions de réponse.", - "xpack.securitySolution.featureRegistry.subFeatures.trustedApplications": "Applications de confiance", - "xpack.securitySolution.featureRegistry.subFeatures.trustedApplications.description": "Aide à atténuer les conflits avec d'autres logiciels, généralement d'autres applications d'antivirus ou de sécurité des points de terminaison.", - "xpack.securitySolution.featureRegistry.subFeatures.trustedApplications.privilegesTooltip": "\"Tous les espaces\" est requis pour l'accès aux applications de confiance.", "xpack.securitySolution.fieldBrowser.actionsLabel": "Actions", "xpack.securitySolution.fieldBrowser.categoryLabel": "Catégorie", "xpack.securitySolution.fieldBrowser.createFieldButton": "Créer un champ", diff --git a/x-pack/plugins/translations/translations/ja-JP.json b/x-pack/plugins/translations/translations/ja-JP.json index 81c28867cb7bc..91d283a8e7d35 100644 --- a/x-pack/plugins/translations/translations/ja-JP.json +++ b/x-pack/plugins/translations/translations/ja-JP.json @@ -33185,43 +33185,6 @@ "xpack.securitySolution.expandedValue.showTopN.showTopValues": "上位の値を表示", "xpack.securitySolution.explore.landing.pageTitle": "探索", "xpack.securitySolution.featureCatalogueDescription": "インフラストラクチャー全体の統合保護のため、脅威を防止、収集、検出し、それに対応します。", - "xpack.securitySolution.featureRegistry.deleteSubFeatureDetails": "ケースとコメントを削除", - "xpack.securitySolution.featureRegistry.deleteSubFeatureName": "削除", - "xpack.securitySolution.featureRegistry.linkSecuritySolutionCaseTitle": "ケース", - "xpack.securitySolution.featureRegistry.linkSecuritySolutionTitle": "セキュリティ", - "xpack.securitySolution.featureRegistry.subFeatures.blockList": "ブロックリスト", - "xpack.securitySolution.featureRegistry.subFeatures.blockList.description": "Elastic Defendの悪意のあるプロセスに対する保護機能を拡張し、潜在的に有害なアプリケーションから保護します。", - "xpack.securitySolution.featureRegistry.subFeatures.blockList.privilegesTooltip": "ブロックリストのアクセスには、すべてのスペースが必要です。", - "xpack.securitySolution.featureRegistry.subFeatures.endpointList": "エンドポイントリスト", - "xpack.securitySolution.featureRegistry.subFeatures.endpointList.description": "Elastic Defendを実行しているすべてのホストと、関連する統合の詳細が表示されます。", - "xpack.securitySolution.featureRegistry.subFeatures.endpointList.privilegesTooltip": "エンドポイントリストのアクセスには、すべてのスペースが必要です。", - "xpack.securitySolution.featureRegistry.subFeatures.eventFilters": "イベントフィルター", - "xpack.securitySolution.featureRegistry.subFeatures.eventFilters.description": "Elasticsearchに保存する必要のない、あるいは保存しないエンドポイントイベントをフィルターします。", - "xpack.securitySolution.featureRegistry.subFeatures.eventFilters.privilegesTooltip": "イベントフィルターのアクセスには、すべてのスペースが必要です。", - "xpack.securitySolution.featureRegistry.subFeatures.executeOperations": "実行操作", - "xpack.securitySolution.featureRegistry.subFeatures.executeOperations.description": "エンドポイントでスクリプトを実行します。", - "xpack.securitySolution.featureRegistry.subFeatures.executeOperations.privilegesTooltip": "実行操作のアクセスには、すべてのスペースが必要です。", - "xpack.securitySolution.featureRegistry.subFeatures.fileOperations": "ファイル操作", - "xpack.securitySolution.featureRegistry.subFeatures.fileOperations.description": "対応コンソールでファイル関連の対応アクションを実行します。", - "xpack.securitySolution.featureRegistry.subFeatures.fileOperations.privilegesTooltip": "ファイル操作のアクセスには、すべてのスペースが必要です。", - "xpack.securitySolution.featureRegistry.subFeatures.hostIsolation": "ホスト分離", - "xpack.securitySolution.featureRegistry.subFeatures.hostIsolation.description": "「isolate」および「release」応答アクションを実行します。", - "xpack.securitySolution.featureRegistry.subFeatures.hostIsolation.privilegesTooltip": "ホスト分離のアクセスには、すべてのスペースが必要です。", - "xpack.securitySolution.featureRegistry.subFeatures.hostIsolationExceptions": "ホスト分離例外", - "xpack.securitySolution.featureRegistry.subFeatures.hostIsolationExceptions.description": "ネットワークの他の部分から分離された場合でも、分離されたホストが通信することを許可する特定のIPアドレスを追加します。", - "xpack.securitySolution.featureRegistry.subFeatures.hostIsolationExceptions.privilegesTooltip": "ホスト分離例外のアクセスには、すべてのスペースが必要です。", - "xpack.securitySolution.featureRegistry.subFeatures.policyManagement": "Elastic Defendポリシー管理", - "xpack.securitySolution.featureRegistry.subFeatures.policyManagement.description": "Elastic Defendの統合ポリシーにアクセスし、プロテクション、イベント収集、および高度なポリシー機能を設定することができます。", - "xpack.securitySolution.featureRegistry.subFeatures.policyManagement.privilegesTooltip": "ポリシー管理のアクセスには、すべてのスペースが必要です。", - "xpack.securitySolution.featureRegistry.subFeatures.processOperations": "プロセス操作", - "xpack.securitySolution.featureRegistry.subFeatures.processOperations.description": "対応コンソールでプロセス関連の対応アクションを実行します。", - "xpack.securitySolution.featureRegistry.subFeatures.processOperations.privilegesTooltip": "プロセス操作のアクセスには、すべてのスペースが必要です。", - "xpack.securitySolution.featureRegistry.subFeatures.responseActionsHistory": "対応アクション履歴", - "xpack.securitySolution.featureRegistry.subFeatures.responseActionsHistory.description": "エンドポイントで実行された対応アクションの履歴を表示します。", - "xpack.securitySolution.featureRegistry.subFeatures.responseActionsHistory.privilegesTooltip": "対応アクション履歴アクセスにはすべてのスペースが必要です。", - "xpack.securitySolution.featureRegistry.subFeatures.trustedApplications": "信頼できるアプリケーション", - "xpack.securitySolution.featureRegistry.subFeatures.trustedApplications.description": "他のソフトウェア(通常は他のウイルス対策またはエンドポイントセキュリティアプリケーション)との競合を軽減することができます。", - "xpack.securitySolution.featureRegistry.subFeatures.trustedApplications.privilegesTooltip": "信頼できるアプリケーションのアクセスには、すべてのスペースが必要です。", "xpack.securitySolution.fieldBrowser.actionsLabel": "アクション", "xpack.securitySolution.fieldBrowser.categoryLabel": "カテゴリー", "xpack.securitySolution.fieldBrowser.createFieldButton": "フィールドを作成", diff --git a/x-pack/plugins/translations/translations/zh-CN.json b/x-pack/plugins/translations/translations/zh-CN.json index 5a7410a88fcee..d655c65576ce3 100644 --- a/x-pack/plugins/translations/translations/zh-CN.json +++ b/x-pack/plugins/translations/translations/zh-CN.json @@ -33181,43 +33181,6 @@ "xpack.securitySolution.expandedValue.showTopN.showTopValues": "显示排名最前值", "xpack.securitySolution.explore.landing.pageTitle": "浏览", "xpack.securitySolution.featureCatalogueDescription": "预防、收集、检测和响应威胁,以对整个基础架构提供统一的保护。", - "xpack.securitySolution.featureRegistry.deleteSubFeatureDetails": "删除案例和注释", - "xpack.securitySolution.featureRegistry.deleteSubFeatureName": "删除", - "xpack.securitySolution.featureRegistry.linkSecuritySolutionCaseTitle": "案例", - "xpack.securitySolution.featureRegistry.linkSecuritySolutionTitle": "安全", - "xpack.securitySolution.featureRegistry.subFeatures.blockList": "阻止列表", - "xpack.securitySolution.featureRegistry.subFeatures.blockList.description": "针对恶意进程扩大 Elastic Defend 防护,并防范具有潜在危害的应用程序。", - "xpack.securitySolution.featureRegistry.subFeatures.blockList.privilegesTooltip": "访问阻止列表需要所有工作区。", - "xpack.securitySolution.featureRegistry.subFeatures.endpointList": "终端列表", - "xpack.securitySolution.featureRegistry.subFeatures.endpointList.description": "显示运行 Elastic Defend 的所有主机及其相关集成详情。", - "xpack.securitySolution.featureRegistry.subFeatures.endpointList.privilegesTooltip": "访问终端列表需要所有工作区。", - "xpack.securitySolution.featureRegistry.subFeatures.eventFilters": "事件筛选", - "xpack.securitySolution.featureRegistry.subFeatures.eventFilters.description": "筛除您不需要或希望存储在 Elasticsearch 中的终端事件。", - "xpack.securitySolution.featureRegistry.subFeatures.eventFilters.privilegesTooltip": "访问事件筛选需要所有工作区。", - "xpack.securitySolution.featureRegistry.subFeatures.executeOperations": "执行操作", - "xpack.securitySolution.featureRegistry.subFeatures.executeOperations.description": "在终端上执行脚本。", - "xpack.securitySolution.featureRegistry.subFeatures.executeOperations.privilegesTooltip": "访问执行操作需要所有工作区。", - "xpack.securitySolution.featureRegistry.subFeatures.fileOperations": "文件操作", - "xpack.securitySolution.featureRegistry.subFeatures.fileOperations.description": "在响应控制台中执行文件相关响应操作。", - "xpack.securitySolution.featureRegistry.subFeatures.fileOperations.privilegesTooltip": "访问文件操作需要所有工作区。", - "xpack.securitySolution.featureRegistry.subFeatures.hostIsolation": "主机隔离", - "xpack.securitySolution.featureRegistry.subFeatures.hostIsolation.description": "执行“隔离”和“释放”响应操作。", - "xpack.securitySolution.featureRegistry.subFeatures.hostIsolation.privilegesTooltip": "访问主机隔离需要所有工作区。", - "xpack.securitySolution.featureRegistry.subFeatures.hostIsolationExceptions": "主机隔离例外", - "xpack.securitySolution.featureRegistry.subFeatures.hostIsolationExceptions.description": "添加仍允许已隔离(即使与剩余网络隔离)主机与其通信的特定 IP 地址。", - "xpack.securitySolution.featureRegistry.subFeatures.hostIsolationExceptions.privilegesTooltip": "访问主机隔离例外需要所有工作区。", - "xpack.securitySolution.featureRegistry.subFeatures.policyManagement": "Elastic Defend 策略管理", - "xpack.securitySolution.featureRegistry.subFeatures.policyManagement.description": "访问 Elastic Defend 集成策略以配置防护、事件收集和高级策略功能。", - "xpack.securitySolution.featureRegistry.subFeatures.policyManagement.privilegesTooltip": "访问策略管理需要所有工作区。", - "xpack.securitySolution.featureRegistry.subFeatures.processOperations": "进程操作", - "xpack.securitySolution.featureRegistry.subFeatures.processOperations.description": "在响应控制台中执行进程相关响应操作。", - "xpack.securitySolution.featureRegistry.subFeatures.processOperations.privilegesTooltip": "访问进程操作需要所有工作区。", - "xpack.securitySolution.featureRegistry.subFeatures.responseActionsHistory": "响应操作历史记录", - "xpack.securitySolution.featureRegistry.subFeatures.responseActionsHistory.description": "访问在终端上执行的响应操作的历史记录。", - "xpack.securitySolution.featureRegistry.subFeatures.responseActionsHistory.privilegesTooltip": "访问响应操作历史记录需要所有工作区。", - "xpack.securitySolution.featureRegistry.subFeatures.trustedApplications": "受信任的应用程序", - "xpack.securitySolution.featureRegistry.subFeatures.trustedApplications.description": "帮助减少与其他软件(通常指其他防病毒或终端安全应用程序)的冲突。", - "xpack.securitySolution.featureRegistry.subFeatures.trustedApplications.privilegesTooltip": "访问受信任的应用程序需要所有工作区。", "xpack.securitySolution.fieldBrowser.actionsLabel": "操作", "xpack.securitySolution.fieldBrowser.categoryLabel": "类别", "xpack.securitySolution.fieldBrowser.createFieldButton": "创建字段", diff --git a/x-pack/test/security_solution_cypress/cypress/e2e/exceptions/shared_exception_lists_management/shared_exception_list_page/duplicate_lists.cy.ts b/x-pack/test/security_solution_cypress/cypress/e2e/exceptions/shared_exception_lists_management/shared_exception_list_page/duplicate_lists.cy.ts index 33fd21bceddcc..881c21472d7ef 100644 --- a/x-pack/test/security_solution_cypress/cypress/e2e/exceptions/shared_exception_lists_management/shared_exception_list_page/duplicate_lists.cy.ts +++ b/x-pack/test/security_solution_cypress/cypress/e2e/exceptions/shared_exception_lists_management/shared_exception_list_page/duplicate_lists.cy.ts @@ -64,9 +64,7 @@ describe('Duplicate List', { tags: ['@ess', '@serverless'] }, () => { ); // Create exception list not used by any rules - createExceptionList(getExceptionList1(), getExceptionList1().list_id).as( - 'exceptionListResponse' - ); + createExceptionList(getExceptionList1(), getExceptionList1().list_id); // Create exception list associated with a rule createExceptionList(getExceptionList2(), getExceptionList2().list_id); diff --git a/x-pack/test/security_solution_cypress/cypress/e2e/exceptions/shared_exception_lists_management/shared_exception_list_page/filter_table.cy.ts b/x-pack/test/security_solution_cypress/cypress/e2e/exceptions/shared_exception_lists_management/shared_exception_list_page/filter_table.cy.ts index 2d74476d41a1f..8fc3acdb2c7ec 100644 --- a/x-pack/test/security_solution_cypress/cypress/e2e/exceptions/shared_exception_lists_management/shared_exception_list_page/filter_table.cy.ts +++ b/x-pack/test/security_solution_cypress/cypress/e2e/exceptions/shared_exception_lists_management/shared_exception_list_page/filter_table.cy.ts @@ -56,9 +56,7 @@ describe('Filter Lists', { tags: ['@ess', '@serverless'] }, () => { ); // Create exception list not used by any rules - createExceptionList(getExceptionList1(), getExceptionList1().list_id).as( - 'exceptionListResponse' - ); + createExceptionList(getExceptionList1(), getExceptionList1().list_id); login(); visitWithoutDateRange(EXCEPTIONS_URL); }); diff --git a/x-pack/test/security_solution_cypress/cypress/e2e/exceptions/shared_exception_lists_management/shared_exception_list_page/manage_lists.cy.ts b/x-pack/test/security_solution_cypress/cypress/e2e/exceptions/shared_exception_lists_management/shared_exception_list_page/manage_lists.cy.ts index 59488f6a92132..238d39bcd6e17 100644 --- a/x-pack/test/security_solution_cypress/cypress/e2e/exceptions/shared_exception_lists_management/shared_exception_list_page/manage_lists.cy.ts +++ b/x-pack/test/security_solution_cypress/cypress/e2e/exceptions/shared_exception_lists_management/shared_exception_list_page/manage_lists.cy.ts @@ -5,7 +5,8 @@ * 2.0. */ -import { getExceptionList, expectedExportedExceptionList } from '../../../../objects/exception'; +import { ExceptionListSchema } from '@kbn/securitysolution-io-ts-list-types'; +import { expectedExportedExceptionList, getExceptionList } from '../../../../objects/exception'; import { getNewRule } from '../../../../objects/rule'; import { createRule } from '../../../../tasks/api_calls/rules'; @@ -13,13 +14,13 @@ import { login, visitWithoutDateRange, waitForPageWithoutDateRange } from '../.. import { EXCEPTIONS_URL } from '../../../../urls/navigation'; import { + assertNumberLinkedRules, + createSharedExceptionList, deleteExceptionListWithoutRuleReferenceByListId, deleteExceptionListWithRuleReferenceByListId, exportExceptionList, - waitForExceptionsTableToBeLoaded, - createSharedExceptionList, linkRulesToExceptionList, - assertNumberLinkedRules, + waitForExceptionsTableToBeLoaded, } from '../../../../tasks/exceptions_table'; import { EXCEPTIONS_LIST_MANAGEMENT_NAME, @@ -44,12 +45,15 @@ const getExceptionList2 = () => ({ list_id: 'exception_list_2', }); +let exceptionListResponse: Cypress.Response; + describe( 'Manage lists from "Shared Exception Lists" page', { tags: ['@ess', '@serverless'] }, () => { describe('Create/Export/Delete List', () => { before(() => { + cy.task('esArchiverResetKibana'); createRule(getNewRule({ name: 'Another rule' })); // Create exception list associated with a rule @@ -69,9 +73,9 @@ describe( ); // Create exception list not used by any rules - createExceptionList(getExceptionList1(), getExceptionList1().list_id).as( - 'exceptionListResponse' - ); + createExceptionList(getExceptionList1(), getExceptionList1().list_id).then((response) => { + exceptionListResponse = response; + }); }); beforeEach(() => { @@ -88,7 +92,7 @@ describe( cy.wait('@export').then(({ response }) => { cy.wrap(response?.body).should( 'eql', - expectedExportedExceptionList(this.exceptionListResponse) + expectedExportedExceptionList(exceptionListResponse) ); cy.get(TOASTER).should( diff --git a/x-pack/test/security_solution_cypress/cypress/objects/exception.ts b/x-pack/test/security_solution_cypress/cypress/objects/exception.ts index cae710b4bd846..6e754d837dcf5 100644 --- a/x-pack/test/security_solution_cypress/cypress/objects/exception.ts +++ b/x-pack/test/security_solution_cypress/cypress/objects/exception.ts @@ -5,7 +5,7 @@ * 2.0. */ -import type { ExceptionListItemSchema } from '@kbn/securitysolution-io-ts-list-types'; +import type { ExceptionListSchema } from '@kbn/securitysolution-io-ts-list-types'; export interface Exception { field: string; @@ -58,8 +58,8 @@ export const getException = (): Exception => ({ }); export const expectedExportedExceptionList = ( - exceptionListResponse: Cypress.Response + exceptionListResponse: Cypress.Response ): string => { - const jsonrule = exceptionListResponse.body; - return `{"_version":"${jsonrule._version}","created_at":"${jsonrule.created_at}","created_by":"system_indices_superuser","description":"${jsonrule.description}","id":"${jsonrule.id}","immutable":false,"list_id":"${jsonrule.list_id}","name":"${jsonrule.name}","namespace_type":"single","os_types":[],"tags":[],"tie_breaker_id":"${jsonrule.tie_breaker_id}","type":"${jsonrule.type}","updated_at":"${jsonrule.updated_at}","updated_by":"system_indices_superuser","version":1}\n{"exported_exception_list_count":1,"exported_exception_list_item_count":0,"missing_exception_list_item_count":0,"missing_exception_list_items":[],"missing_exception_lists":[],"missing_exception_lists_count":0}\n`; + const jsonRule = exceptionListResponse.body; + return `{"_version":"${jsonRule._version}","created_at":"${jsonRule.created_at}","created_by":"system_indices_superuser","description":"${jsonRule.description}","id":"${jsonRule.id}","immutable":false,"list_id":"${jsonRule.list_id}","name":"${jsonRule.name}","namespace_type":"single","os_types":[],"tags":[],"tie_breaker_id":"${jsonRule.tie_breaker_id}","type":"${jsonRule.type}","updated_at":"${jsonRule.updated_at}","updated_by":"system_indices_superuser","version":1}\n{"exported_exception_list_count":1,"exported_exception_list_item_count":0,"missing_exception_list_item_count":0,"missing_exception_list_items":[],"missing_exception_lists":[],"missing_exception_lists_count":0}\n`; }; diff --git a/yarn.lock b/yarn.lock index b7b5e99534103..54671604766c1 100644 --- a/yarn.lock +++ b/yarn.lock @@ -5313,6 +5313,10 @@ version "0.0.0" uid "" +"@kbn/security-solution-features@link:x-pack/packages/security-solution/features": + version "0.0.0" + uid "" + "@kbn/security-solution-fixtures-plugin@link:x-pack/test/cases_api_integration/common/plugins/security_solution": version "0.0.0" uid ""