[Serverless][Security Solution][Endpoint] Restrict endpoint exceptions on serverless via plugin sub-features (#164107)

### What this PR changes

branched from elastic/kibana/pull/163759

- Introduces new AppFeatures package `@kbn/security-solution-features`
with the common logic and `AppFeatureService` to apply offering specific
configurations for Security Solution features independently for
Serverless and ESS. This logic is replacing the earlier `AppFeatures` in
order to introduce new Kibana feature privileges for serverless PLIs so
that new Kibana privileges introduced for serverless PLIs do not
affect/show up as new Kibana feature privileges in ESS.
- Gates endpoint exceptions on alerts/rules based on serverless PLI
configurations. On serverless `Endpoint exceptions` should be
accessible/seen only on endpoint essentials/complete.

New AppFeatures logic architecture diagram:

![Security Solution Features
(Current)](f627406d-43bc-4db5-93b1-4e43eeb6d870)

**Note:** Corresponding API changes related to endpoint exceptions will
be in a new PR, along with the last set of UX changes for hiding the
`Endpoint exceptions` tab from the Rules details page.

### How to review

- Setup for _Servlerless_
  - Run `yarn es snapshot` on a terminal window to start ES.
- Copy `config/serverless.security.yml` to
`config/serverless.security.dev.yml`
- Run `yarn serverless-security --no-base-path` on another terminal
window to start kibana in serverless mode
- Run `node
x-pack/plugins/security_solution/scripts/endpoint/endpoint_agent_emulator.js
--asSuperuser` on a new window and then select `1` to `Load Endoints`
and then `1` to `Run` the loader script. This will load some fake
agents/alerts data to test with.

### Tests (Serverless)

- with 
`{ product_line: 'security', product_tier: 'essentials' }` or `{
product_line: 'security', product_tier: 'complete' }`
and
`{ product_line: 'endpoint', product_tier: 'essentials' }` or `{
product_line: 'endpoint', product_tier: 'complete' }`

1. Navigate to Rules>Shared exception lists via
`http://localhost:5601/app/security/exceptions`
2. Test that you can see `Endpoint Security Exception List` card on the
shared exception lists page.
3. Navigate to `Alerts` page via `app/security/alerts`, you should see
endpoint alerts. If not, then click on `Manage Rules` and then
disable/enable `Endpoint Security` rules. That should trigger alerts to
show up on the Alerts table.
4. Click on `View Details` button under `Actions` column. Once the
flyout is visible, click on `Take Action` and verify that `Add Endpoint
exception` is visible/enabled/clickable on the menu.
5. Click on `More actions` button under `Actions` column and verify that
`Add Endpoint exception` is visible/enabled/clickable on the menu.
6. Click on `Investigate in timeline` button under `Actions` column;
when the timeline view is visible and the alert item is displayed, click
on buttons mentioned in 4. and 5. above and verify the same.
7. Navigate to `Rules`>`DetectionRules`>`Endpoint Security` rule under
the `Rules` table. Select the `Alerts` tab.
8. Click and verify `View details`,`More actions` and `Investigate in
timeline` buttons same as in 4., 5., 6. above.
9. You should be able to see the `Endpoint exceptions` tab as well.
Click and verify that you can see the tab's content.

- with 
`{ product_line: 'security', product_tier: 'essentials' }` or `{
product_line: 'security', product_tier: 'complete' }`
1. Edit `config/serverless.security.dev.yml` so that `endpoint` product
line item is commented out.
2. Test that you can not see `Endpoint Security Exception List` card on
the shared exception lists page.
3. Items 4. 5. 6. as above but the menu items should be disabled. This
can be verified with fake data only as with a real endpoint, endpoint
alerts are actually not visible at all.


### Tests (ESS)
On the ESS side, endpoint exceptions are not affected by this change and
work as usual based on index privileges.

---------

Co-authored-by: semd <sergi.massaneda@elastic.co>
Co-authored-by: kibanamachine <42973632+kibanamachine@users.noreply.github.com>
Co-authored-by: YulNaumenko <jo.naumenko@gmail.com>
Co-authored-by: Pablo Neves Machado <pablo.nevesmachado@elastic.co>
Co-authored-by: Pablo Machado <machadoum@gmail.com>
This commit is contained in:
Ash 2023-08-31 13:35:42 +02:00 committed by GitHub
parent 73469bfb11
commit 6e367d94c9
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
107 changed files with 2200 additions and 1305 deletions

View file

@ -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}',

1
.github/CODEOWNERS vendored
View file

@ -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

View file

@ -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",

View file

@ -121,7 +121,7 @@ pageLoadAssetSize:
security: 81771
securitySolution: 66738
securitySolutionEss: 16573
securitySolutionServerless: 40000
securitySolutionServerless: 45000
serverless: 16573
serverlessObservability: 68747
serverlessSearch: 71995

View file

@ -151,3 +151,23 @@ export type ArrayElement<A> = A extends ReadonlyArray<infer T> ? T : never;
export type WithRequiredProperty<Type, Key extends keyof Type> = Omit<Type, Key> & {
[Property in Key]-?: Type[Property];
};
// Recursive partial object type. inspired by EUI RecursivePartial
export type RecursivePartial<T> = {
[P in keyof T]?: T[P] extends NonAny[]
? T[P]
: T[P] extends readonly NonAny[]
? T[P]
: T[P] extends Array<infer U>
? Array<RecursivePartial<U>>
: T[P] extends ReadonlyArray<infer U>
? ReadonlyArray<RecursivePartial<U>>
: T[P] extends Set<infer V>
? Set<RecursivePartial<V>>
: T[P] extends Map<infer K, infer V>
? Map<K, RecursivePartial<V>>
: T[P] extends NonAny
? T[P]
: RecursivePartial<T[P]>;
};
type NonAny = number | boolean | string | symbol | null;

View file

@ -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"],

View file

@ -0,0 +1,4 @@
## Security Solution App Features
This package provides resources to be used for Security Solution app features

View file

@ -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';

View file

@ -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';

View file

@ -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';

View file

@ -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: ['<rootDir>/x-pack/packages/security-solution/features'],
};

View file

@ -5,4 +5,4 @@
* 2.0.
*/
export { AppFeatures } from './app_features';
export * from './src/app_features_keys';

View file

@ -0,0 +1,5 @@
{
"type": "shared-common",
"id": "@kbn/security-solution-features",
"owner": "@elastic/security-threat-hunting-explore"
}

View file

@ -0,0 +1,6 @@
{
"name": "@kbn/security-solution-features",
"private": true,
"version": "1.0.0",
"license": "Elastic License 2.0"
}

View file

@ -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';

View file

@ -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',
}

View file

@ -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`],
},
},
};

View file

@ -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<AssistantSubFeatureId>
> = {
[AppFeatureAssistantKey.assistant]: {
privileges: {
all: {
ui: ['ai-assistant'],
},
},
},
};

View file

@ -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<AssistantSubFeatureId> => ({
baseKibanaFeature: getAssistantBaseKibanaFeature(),
baseKibanaSubFeatureIds: getAssistantBaseKibanaSubFeatureIds(),
subFeaturesMap: assistantSubFeaturesMap,
});

View file

@ -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: [],
},
},
});

View file

@ -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<AssistantSubFeatureId, SubFeatureConfig>([
// This is a sample sub-feature that can be used for future implementations

View file

@ -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],
},
},
},
});

View file

@ -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<CasesSubFeatureId> => ({
baseKibanaFeature: getCasesBaseKibanaFeature(params),
baseKibanaSubFeatureIds: getCasesBaseKibanaSubFeatureIds(),
subFeaturesMap: getCasesSubFeaturesMap(params),
});

View file

@ -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,
},
},
};
};

View file

@ -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, SubFeatureConfig>([
[CasesSubFeatureId.deleteCases, deleteCasesSubFeature],
]);
};

View file

@ -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<CasesSubFeatureId>
>;

View file

@ -0,0 +1,22 @@
/*
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
* or more contributor license agreements. Licensed under the Elastic License
* 2.0; you may not use this file except in compliance with the Elastic License
* 2.0.
*/
// 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;

View file

@ -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<K, AppFeatureKibanaConfig<T>>,
enabledAppFeaturesKeys: AppFeatureKeys
) => {
return new Map(
Object.entries<AppFeatureKibanaConfig<T>>(appFeatures).reduce<
Array<[K, AppFeatureKibanaConfig<T>]>
>((acc, [key, value]) => {
if (enabledAppFeaturesKeys.includes(key as K)) {
acc.push([key as K, value]);
}
return acc;
}, [])
);
};

View file

@ -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]: {},
};

View file

@ -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<SecuritySubFeatureId> => ({
baseKibanaFeature: getSecurityBaseKibanaFeature(params),
baseKibanaSubFeatureIds: getSecurityBaseKibanaSubFeatureIds(params),
subFeaturesMap: securitySubFeaturesMap,
});

View file

@ -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'],
},
},
});

View file

@ -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 Defends 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,
},
],
},
],
};
// Defines all the ordered Security subFeatures available
/**
* 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 Security Assistant subFeatures available.
* The order of the subFeatures is the order they will be displayed
*/
export const securitySubFeaturesMap = Object.freeze(
new Map<SecuritySubFeatureId, SubFeatureConfig>([
[SecuritySubFeatureId.endpointList, endpointListSubFeature],
[SecuritySubFeatureId.endpointExceptions, endpointExceptionsSubFeature],
[SecuritySubFeatureId.trustedApplications, trustedApplicationsSubFeature],
[SecuritySubFeatureId.hostIsolationExceptions, hostIsolationExceptionsSubFeature],
[SecuritySubFeatureId.blocklist, blocklistSubFeature],

View file

@ -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<string, boolean>;
savedObjects: string[];
}
export type DefaultSecurityAppFeaturesConfig = Omit<
Record<AppFeatureSecurityKey, AppFeatureKibanaConfig<SecuritySubFeatureId>>,
AppFeatureSecurityKey.endpointExceptions
// | add not default security app features here
>;

View file

@ -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<KibanaFeatureConfig, 'subFeatures'>;
export type SubFeaturesPrivileges = RecursivePartial<SubFeaturePrivilegeConfig>;
export type AppFeatureKibanaConfig<T extends string = string> =
RecursivePartial<BaseKibanaFeatureConfig> & {
subFeatureIds?: T[];
subFeaturesPrivileges?: SubFeaturesPrivileges[];
};
export type AppFeaturesConfig<T extends string = string> = Map<
AppFeatureKeyType,
AppFeatureKibanaConfig<T>
>;
export type AppFeaturesSecurityConfig = Map<
AppFeatureSecurityKey,
AppFeatureKibanaConfig<SecuritySubFeatureId>
>;
export type AppFeaturesCasesConfig = Map<
AppFeatureCasesKey,
AppFeatureKibanaConfig<CasesSubFeatureId>
>;
export type AppFeaturesAssistantConfig = Map<
AppFeatureAssistantKey,
AppFeatureKibanaConfig<AssistantSubFeatureId>
>;
export type AppSubFeaturesMap<T extends string = string> = Map<T, SubFeatureConfig>;
export interface AppFeatureParams<T extends string = string> {
baseKibanaFeature: BaseKibanaFeatureConfig;
baseKibanaSubFeatureIds: T[];
subFeaturesMap: AppSubFeaturesMap<T>;
}

View file

@ -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/**/*"]
}

View file

@ -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);

View file

@ -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';

View file

@ -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);

View file

@ -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,

View file

@ -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.

View file

@ -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,
};
});

View file

@ -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;

View file

@ -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 }),

View file

@ -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<AlertContextMenuProps & PropsFromRedux
closePopover();
}, [closePopover, onAddEventFilterClick]);
const { exceptionActionItems } = useExceptionActions({
const { exceptionActionItems } = useAlertExceptionActions({
isEndpointAlert: isAlertFromEndpointAlert({ ecsData: ecsRowData }),
onAddExceptionTypeClick: handleOnAddExceptionTypeClick,
});

View file

@ -8,6 +8,8 @@
import { useCallback, useMemo } from 'react';
import { ExceptionListTypeEnum } from '@kbn/securitysolution-io-ts-list-types';
import { useListsConfig } from '../../../containers/detection_engine/lists/use_lists_config';
import { useHasSecurityCapability } from '../../../../helper_hooks';
import { useUserData } from '../../user_info';
import { ACTION_ADD_ENDPOINT_EXCEPTION, ACTION_ADD_EXCEPTION } from '../translations';
import type { AlertTableContextMenuItem } from '../types';
@ -64,3 +66,33 @@ export const useExceptionActions = ({
return { exceptionActionItems };
};
export const useAlertExceptionActions = ({
isEndpointAlert,
onAddExceptionTypeClick,
}: UseExceptionActionProps) => {
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 };
};

View file

@ -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,
});

View file

@ -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,
ExceptionsListCard,
ImportExceptionListFlyout,
CreateSharedListFlyout,
ExceptionsListCard,
ExceptionsTableUtilityBar,
ImportExceptionListFlyout,
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 | null>(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'],

View file

@ -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,
},
},
},
},
});
});

View file

@ -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,
};
});

View file

@ -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
)
);

View file

@ -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')
);

View file

@ -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<void> => {
const log = logger.get('endpoint', 'policyProtections');

View file

@ -17,12 +17,12 @@ import {
savedObjectsServiceMock,
} from '@kbn/core/server/mocks';
import type {
KibanaRequest,
RouteConfig,
SavedObjectsClientContract,
RequestHandler,
IRouter,
KibanaRequest,
RequestHandler,
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,
};
};

View file

@ -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<PackagePolicyClient>;
savedObjectsClient: ReturnType<typeof savedObjectsClientMock.create>;
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,
};
};

View file

@ -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<EndpointArtifactClientInterface>
).listArtifacts.mockImplementation(async (id) => {
).listArtifacts.mockImplementation(async () => {
// report the MACOS Exceptions artifact as not found
return {
items: [

View file

@ -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<string, ExceptionListItemSchema[]>;
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,

View file

@ -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

View file

@ -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 = <T extends { package?: { name: string } }>(
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<UpdatePackagePolicy> => {
if (!isEndpointPackagePolicy(newPackagePolicy)) {

View file

@ -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<ILicense>;
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')
);

View file

@ -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(

View file

@ -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' }],
});
});
});

View file

@ -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<AppFeatureKey>;
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<AppFeaturesConfig>
): AppFeatureKibanaConfig[] {
return Object.entries(appFeaturesConfigs).reduce<AppFeatureKibanaConfig[]>(
(acc, [appFeatureKey, appFeatureConfig]) => {
if (this.isEnabled(appFeatureKey as AppFeatureKey)) {
acc.push(appFeatureConfig);
}
return acc;
},
[]
);
}
}

View file

@ -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;
};

View file

@ -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'],
},
},
},
});

View file

@ -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
},
},
},
});

View file

@ -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, SubFeatureConfig>([
[CasesSubFeatureId.deleteCases, deleteCasesSubFeature],
])
);

View file

@ -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]: {},
};
};

View file

@ -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<KibanaFeatureConfig, 'subFeatures'>;
export type SubFeaturesPrivileges = RecursivePartial<SubFeaturePrivilegeConfig>;
export type AppFeatureKibanaConfig<T extends string = string> =
RecursivePartial<BaseKibanaFeatureConfig> & {
subFeatureIds?: T[];
subFeaturesPrivileges?: SubFeaturesPrivileges[];
};
export type AppFeaturesConfig<T extends string = string> = Record<
AppFeatureKey,
AppFeatureKibanaConfig<T>
>;
export type AppFeaturesSecurityConfig<T extends string = string> = Record<
AppFeatureSecurityKey,
AppFeatureKibanaConfig<T>
>;
export type AppFeaturesCasesConfig<T extends string = string> = Record<
AppFeatureCasesKey,
AppFeatureKibanaConfig<T>
>;
export type AppFeaturesAssistantConfig<T extends string = string> = Record<
AppFeatureAssistantKey,
AppFeatureKibanaConfig<T>
>;

View file

@ -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<string> = 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<string> = 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<string>,
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<string>,
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,
});
});
});

View file

@ -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<T extends string = string, S extends string = string> {
private featureConfigMerger: AppFeaturesConfigMerger;
private appFeatures?: Set<AppFeatureKeyType>;
private featuresSetup?: FeaturesPluginSetup;
constructor(
private readonly logger: Logger,
subFeaturesMap: AppSubFeaturesMap<S>,
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<S>) {
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);
}
}

View file

@ -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 = {

View file

@ -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<T extends string = string> {
constructor(
@ -23,6 +23,7 @@ export class AppFeaturesConfigMerger<T extends string = string> {
/**
* Merges `appFeaturesConfigs` into `kibanaFeatureConfig`.
* @param kibanaFeatureConfig the KibanaFeatureConfig to merge into
* @param kibanaSubFeatureIds
* @param appFeaturesConfigs the AppFeatureKibanaConfig to merge
* @returns mergedKibanaFeatureConfig the merged KibanaFeatureConfig
* */

View file

@ -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<AppFeatureKeyType>;
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<AppFeatureKeyType>(
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);
}
}

View file

@ -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
),
};

View file

@ -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';

View file

@ -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;
};

View file

@ -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,
];

View file

@ -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<SecuritySubFeatureId>;
cases: () => AppFeaturesConfig<CasesSubFeatureId>;
securityAssistant: () => AppFeaturesConfig<AssistantSubFeatureId>;
}

View file

@ -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(

View file

@ -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

View file

@ -171,6 +171,7 @@
"@kbn/navigation-plugin",
"@kbn/core-logging-server-mocks",
"@kbn/core-lifecycle-browser",
"@kbn/security-solution-features",
"@kbn/handlebars"
]
}

View file

@ -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';

View file

@ -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<CasesSubFeatureId>
> = {
...getCasesDefaultAppFeaturesConfig({
apiTags: { connectors: GET_CONNECTORS_CONFIGURE_API_TAG },
uiCapabilities: { connectors: CASES_CONNECTORS_CAPABILITY },
}),
// ess-specific app features configs here
};

View file

@ -0,0 +1,22 @@
/*
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
* or more contributor license agreements. Licensed under the Elastic License
* 2.0; you may not use this file except in compliance with the Elastic License
* 2.0.
*/
import type { AppFeatureKeys } from '@kbn/security-solution-features';
import type { AppFeaturesConfigurator } from '@kbn/security-solution-plugin/server/lib/app_features_service/types';
import { getCasesAppFeaturesConfigurator } from './cases_app_features_config';
import { getSecurityAppFeaturesConfigurator } from './security_app_features_config';
import { getSecurityAssistantAppFeaturesConfigurator } from './security_assistant_app_features_config';
export const getProductAppFeaturesConfigurator = (
enabledAppFeatureKeys: AppFeatureKeys
): AppFeaturesConfigurator => {
return {
security: getSecurityAppFeaturesConfigurator(enabledAppFeatureKeys),
cases: getCasesAppFeaturesConfigurator(enabledAppFeatureKeys),
securityAssistant: getSecurityAssistantAppFeaturesConfigurator(enabledAppFeatureKeys),
};
};

View file

@ -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<SecuritySubFeatureId>
> = {
...securityDefaultAppFeaturesConfig,
[AppFeatureSecurityKey.endpointExceptions]: {
privileges: AppFeaturesPrivileges[AppFeaturesPrivilegeId.endpointExceptions],
},
};

View file

@ -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<AssistantSubFeatureId>
> = {
...assistantDefaultAppFeaturesConfig,
// ess-specific app features configs here
};

View file

@ -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

View file

@ -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 {};
}

View file

@ -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",

View file

@ -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,

View file

@ -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';

View file

@ -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()])

View file

@ -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 type {
UpsellingService,
PageUpsellings,
SectionUpsellings,
UpsellingSectionId,
} from '@kbn/security-solution-plugin/public';
import { SecurityPageName } from '@kbn/security-solution-plugin/common';
import type {
MessageUpsellings,
PageUpsellings,
SectionUpsellings,
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;
}

View file

@ -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<CasesSubFeatureId>
> = {
...getCasesDefaultAppFeaturesConfig({
apiTags: { connectors: GET_CONNECTORS_CONFIGURE_API_TAG },
uiCapabilities: { connectors: CASES_CONNECTORS_CAPABILITY },
}),
// serverless-specific app features configs here
};

View file

@ -0,0 +1,22 @@
/*
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
* or more contributor license agreements. Licensed under the Elastic License
* 2.0; you may not use this file except in compliance with the Elastic License
* 2.0.
*/
import type { AppFeatureKeys } from '@kbn/security-solution-features';
import type { AppFeaturesConfigurator } from '@kbn/security-solution-plugin/server/lib/app_features_service/types';
import { getCasesAppFeaturesConfigurator } from './cases_app_features_config';
import { getSecurityAppFeaturesConfigurator } from './security_app_features_config';
import { getSecurityAssistantAppFeaturesConfigurator } from './security_assistant_app_features_config';
export const getProductAppFeaturesConfigurator = (
enabledAppFeatureKeys: AppFeatureKeys
): AppFeaturesConfigurator => {
return {
security: getSecurityAppFeaturesConfigurator(enabledAppFeatureKeys),
cases: getCasesAppFeaturesConfigurator(enabledAppFeatureKeys),
securityAssistant: getSecurityAssistantAppFeaturesConfigurator(enabledAppFeatureKeys),
};
};

View file

@ -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<SecuritySubFeatureId>
> = {
...securityDefaultAppFeaturesConfig,
[AppFeatureSecurityKey.endpointExceptions]: {
subFeatureIds: [SecuritySubFeatureId.endpointExceptions],
},
};

View file

@ -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<AssistantSubFeatureId>
> = {
...assistantDefaultAppFeaturesConfig,
// serverless-specific app features configs here
};

View file

@ -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 });

View file

@ -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"

View file

@ -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",

Some files were not shown because too many files have changed in this diff Show more