From 126ce01c439bfbbf0c79e042d2ba544b1c08dd81 Mon Sep 17 00:00:00 2001 From: Ashokaditya Date: Fri, 1 Sep 2023 10:04:58 +0200 Subject: [PATCH 01/42] gate endpoint exceptions on rule details refs elastic/security-team/issues/7223 --- .../use_rule_details_tabs.test.tsx | 73 ++++++++++++++++++- .../rule_details/use_rule_details_tabs.tsx | 17 ++++- 2 files changed, 84 insertions(+), 6 deletions(-) diff --git a/x-pack/plugins/security_solution/public/detection_engine/rule_details_ui/pages/rule_details/use_rule_details_tabs.test.tsx b/x-pack/plugins/security_solution/public/detection_engine/rule_details_ui/pages/rule_details/use_rule_details_tabs.test.tsx index 496fb0d94c3fc..5fcf70da9d915 100644 --- a/x-pack/plugins/security_solution/public/detection_engine/rule_details_ui/pages/rule_details/use_rule_details_tabs.test.tsx +++ b/x-pack/plugins/security_solution/public/detection_engine/rule_details_ui/pages/rule_details/use_rule_details_tabs.test.tsx @@ -5,13 +5,21 @@ * 2.0. */ -import { renderHook, cleanup } from '@testing-library/react-hooks'; +import { cleanup, renderHook } from '@testing-library/react-hooks'; import type { UseRuleDetailsTabsProps } from './use_rule_details_tabs'; import { RuleDetailTabs, useRuleDetailsTabs } from './use_rule_details_tabs'; import type { Rule } from '../../../rule_management/logic'; - import { useRuleExecutionSettings } from '../../../rule_monitoring'; +import { useHasSecurityCapability } from '../../../../helper_hooks'; +import { useListsConfig } from '../../../../detections/containers/detection_engine/lists/use_lists_config'; + jest.mock('../../../rule_monitoring'); +jest.mock('../../../../helper_hooks'); +jest.mock('../../../../detections/containers/detection_engine/lists/use_lists_config'); + +const mockUseRuleExecutionSettings = useRuleExecutionSettings as jest.Mock; +const mockUseHasSecurityCapability = useHasSecurityCapability as jest.Mock; +const mockUseListsConfig = useListsConfig as jest.Mock; const mockRule: Rule = { id: 'myfakeruleid', @@ -51,12 +59,17 @@ const mockRule: Rule = { describe('useRuleDetailsTabs', () => { beforeAll(() => { - (useRuleExecutionSettings as jest.Mock).mockReturnValue({ + mockUseRuleExecutionSettings.mockReturnValue({ extendedLogging: { isEnabled: false, minLevel: 'debug', }, }); + mockUseListsConfig.mockReturnValue({ + loading: false, + needsConfiguration: false, + }); + mockUseHasSecurityCapability.mockReturnValue(true); }); beforeEach(() => { @@ -119,6 +132,58 @@ describe('useRuleDetailsTabs', () => { expect(tabsNames).toContain(RuleDetailTabs.endpointExceptions); }); + it('hides endpoint exceptions tab when rule includes endpoint list but no endpoint PLI', async () => { + mockUseHasSecurityCapability.mockReturnValue(false); + const tabs = render({ + rule: { + ...mockRule, + outcome: 'conflict', + alias_target_id: 'aliased_rule_id', + alias_purpose: 'savedObjectConversion', + exceptions_list: [ + { + id: 'endpoint_list', + list_id: 'endpoint_list', + type: 'endpoint', + namespace_type: 'agnostic', + }, + ], + }, + ruleId: mockRule.rule_id, + isExistingRule: true, + hasIndexRead: true, + }); + const tabsNames = Object.keys(tabs.result.current); + + expect(tabsNames).not.toContain(RuleDetailTabs.endpointExceptions); + }); + + it('hides endpoint exceptions tab when rule includes endpoint list, has endpoint PLI, but no index privilege', async () => { + mockUseListsConfig.mockReturnValue({ loading: false, needsConfiguration: true }); + const tabs = render({ + rule: { + ...mockRule, + outcome: 'conflict', + alias_target_id: 'aliased_rule_id', + alias_purpose: 'savedObjectConversion', + exceptions_list: [ + { + id: 'endpoint_list', + list_id: 'endpoint_list', + type: 'endpoint', + namespace_type: 'agnostic', + }, + ], + }, + ruleId: mockRule.rule_id, + isExistingRule: true, + hasIndexRead: true, + }); + const tabsNames = Object.keys(tabs.result.current); + + expect(tabsNames).not.toContain(RuleDetailTabs.endpointExceptions); + }); + it('does not return the execution events tab if extended logging is disabled', async () => { const tabs = render({ rule: mockRule, @@ -132,7 +197,7 @@ describe('useRuleDetailsTabs', () => { }); it('returns the execution events tab if extended logging is enabled', async () => { - (useRuleExecutionSettings as jest.Mock).mockReturnValue({ + mockUseRuleExecutionSettings.mockReturnValue({ extendedLogging: { isEnabled: true, minLevel: 'debug', diff --git a/x-pack/plugins/security_solution/public/detection_engine/rule_details_ui/pages/rule_details/use_rule_details_tabs.tsx b/x-pack/plugins/security_solution/public/detection_engine/rule_details_ui/pages/rule_details/use_rule_details_tabs.tsx index 42a42caeb732d..d16cc5554ab89 100644 --- a/x-pack/plugins/security_solution/public/detection_engine/rule_details_ui/pages/rule_details/use_rule_details_tabs.tsx +++ b/x-pack/plugins/security_solution/public/detection_engine/rule_details_ui/pages/rule_details/use_rule_details_tabs.tsx @@ -8,11 +8,13 @@ import { useEffect, useMemo, useState } from 'react'; import { ExceptionListTypeEnum } from '@kbn/securitysolution-io-ts-list-types'; import { omit } from 'lodash/fp'; +import { useListsConfig } from '../../../../detections/containers/detection_engine/lists/use_lists_config'; import * as detectionI18n from '../../../../detections/pages/detection_engine/translations'; import * as i18n from './translations'; import type { Rule } from '../../../rule_management/logic'; import type { NavTab } from '../../../../common/components/navigation/types'; import { useRuleExecutionSettings } from '../../../rule_monitoring'; +import { useHasSecurityCapability } from '../../../../helper_hooks'; export enum RuleDetailTabs { alerts = 'alerts', @@ -80,9 +82,17 @@ export const useRuleDetailsTabs = ({ ); const [pageTabs, setTabs] = useState>>(ruleDetailTabs); - const ruleExecutionSettings = useRuleExecutionSettings(); + const { loading: listsConfigLoading, needsConfiguration: needsListsConfiguration } = + useListsConfig(); + const canReadEndpointExceptions = useHasSecurityCapability('crudEndpointExceptions'); + + const canWriteEndpointExceptions = useMemo( + () => !listsConfigLoading && !needsListsConfiguration && canReadEndpointExceptions, + [canReadEndpointExceptions, listsConfigLoading, needsListsConfiguration] + ); + useEffect(() => { const hiddenTabs = []; @@ -92,6 +102,9 @@ export const useRuleDetailsTabs = ({ if (!ruleExecutionSettings.extendedLogging.isEnabled) { hiddenTabs.push(RuleDetailTabs.executionEvents); } + if (!canWriteEndpointExceptions) { + hiddenTabs.push(RuleDetailTabs.endpointExceptions); + } if (rule != null) { const hasEndpointList = (rule.exceptions_list ?? []).some( (list) => list.type === ExceptionListTypeEnum.ENDPOINT @@ -104,7 +117,7 @@ export const useRuleDetailsTabs = ({ const tabs = omit>(hiddenTabs, ruleDetailTabs); setTabs(tabs); - }, [hasIndexRead, rule, ruleDetailTabs, ruleExecutionSettings]); + }, [canWriteEndpointExceptions, hasIndexRead, rule, ruleDetailTabs, ruleExecutionSettings]); return pageTabs; }; From 36a2165cc3331fb7e4f32910b4a7ad40ba6e99fd Mon Sep 17 00:00:00 2001 From: Ashokaditya Date: Tue, 12 Sep 2023 12:47:20 +0200 Subject: [PATCH 02/42] add endpoint exceptions privileges to authz refs elastic/security-team/issues/7223 --- x-pack/plugins/fleet/common/authz.test.ts | 26 ++++++++ x-pack/plugins/fleet/common/authz.ts | 27 ++++++++ x-pack/plugins/fleet/public/plugin.ts | 45 +++++++------ .../common/endpoint/service/authz/authz.ts | 20 ++++++ .../common/endpoint/types/authz.ts | 65 ++++++++++--------- .../endpoint/endpoint_app_context_services.ts | 14 +++- 6 files changed, 143 insertions(+), 54 deletions(-) diff --git a/x-pack/plugins/fleet/common/authz.test.ts b/x-pack/plugins/fleet/common/authz.test.ts index 22bfeb6c04cef..9a2e224289b26 100644 --- a/x-pack/plugins/fleet/common/authz.test.ts +++ b/x-pack/plugins/fleet/common/authz.test.ts @@ -10,6 +10,7 @@ import { DEFAULT_APP_CATEGORIES } from '@kbn/core-application-common'; import { TRANSFORM_PLUGIN_ID } from './constants/plugin'; import { + calculateEndpointExceptionsPrivilegesFromCapabilities, calculatePackagePrivilegesFromCapabilities, calculatePackagePrivilegesFromKibanaPrivileges, } from './authz'; @@ -74,6 +75,31 @@ describe('fleet authz', () => { }); }); + describe('#calculateEndpointExceptionsPrivilegesFromCapabilities', () => { + it('calculates endpoint exceptions privileges correctly', () => { + const endpointExceptionsCapabilities = { + showEndpointExceptions: true, + crudEndpointExceptions: true, + }; + + const expected = { + actions: { + showEndpointExceptions: true, + crudEndpointExceptions: true, + }, + }; + + const actual = calculateEndpointExceptionsPrivilegesFromCapabilities({ + navLinks: {}, + management: {}, + catalogue: {}, + siem: endpointExceptionsCapabilities, + }); + + expect(actual).toEqual(expected); + }); + }); + describe('calculatePackagePrivilegesFromKibanaPrivileges', () => { it('calculates privileges correctly', () => { const endpointPrivileges = [ diff --git a/x-pack/plugins/fleet/common/authz.ts b/x-pack/plugins/fleet/common/authz.ts index 27e72857392a8..caf4032efde73 100644 --- a/x-pack/plugins/fleet/common/authz.ts +++ b/x-pack/plugins/fleet/common/authz.ts @@ -49,6 +49,13 @@ export interface FleetAuthz { }; }; }; + + endpointExceptionsPrivileges?: { + actions: { + crudEndpointExceptions: boolean; + showEndpointExceptions: boolean; + }; + }; } interface CalculateParams { @@ -135,6 +142,26 @@ export function calculatePackagePrivilegesFromCapabilities( }; } +export function calculateEndpointExceptionsPrivilegesFromCapabilities( + capabilities: Capabilities | undefined +): FleetAuthz['endpointExceptionsPrivileges'] { + if (!capabilities) { + return; + } + + const endpointExceptionsActions = Object.keys(capabilities.siem).reduce>( + (acc, privilegeName) => { + acc[privilegeName] = (capabilities.siem[privilegeName] as boolean) || false; + return acc; + }, + {} + ); + + return { + actions: endpointExceptionsActions, + } as FleetAuthz['endpointExceptionsPrivileges']; +} + function getAuthorizationFromPrivileges( kibanaPrivileges: Array<{ resource?: string; diff --git a/x-pack/plugins/fleet/public/plugin.ts b/x-pack/plugins/fleet/public/plugin.ts index b06a6a6cec243..7c904b4d8f1d1 100644 --- a/x-pack/plugins/fleet/public/plugin.ts +++ b/x-pack/plugins/fleet/public/plugin.ts @@ -9,18 +9,19 @@ import React from 'react'; import type { AppMountParameters, CoreSetup, + CoreStart, Plugin, PluginInitializerContext, - CoreStart, } from '@kbn/core/public'; +import { AppNavLinkStatus, DEFAULT_APP_CATEGORIES } from '@kbn/core/public'; import { i18n } from '@kbn/i18n'; import type { NavigationPublicPluginStart } from '@kbn/navigation-plugin/public'; import { ELASTIC_HTTP_VERSION_HEADER } from '@kbn/core-http-common'; import type { - CustomIntegrationsStart, CustomIntegrationsSetup, + CustomIntegrationsStart, } from '@kbn/custom-integrations-plugin/public'; import type { SharePluginStart } from '@kbn/share-plugin/public'; @@ -29,20 +30,17 @@ import { once } from 'lodash'; import type { SpacesPluginStart } from '@kbn/spaces-plugin/public'; import type { DiscoverStart } from '@kbn/discover-plugin/public'; -import type { CloudStart } from '@kbn/cloud-plugin/public'; +import type { CloudSetup, CloudStart } from '@kbn/cloud-plugin/public'; import type { UsageCollectionSetup, UsageCollectionStart, } from '@kbn/usage-collection-plugin/public'; -import { DEFAULT_APP_CATEGORIES, AppNavLinkStatus } from '@kbn/core/public'; - import type { DataPublicPluginSetup, DataPublicPluginStart } from '@kbn/data-plugin/public'; import type { DataViewsPublicPluginStart } from '@kbn/data-views-plugin/public'; import type { HomePublicPluginSetup } from '@kbn/home-plugin/public'; import { Storage } from '@kbn/kibana-utils-plugin/public'; import type { LicensingPluginStart } from '@kbn/licensing-plugin/public'; -import type { CloudSetup } from '@kbn/cloud-plugin/public'; import type { GlobalSearchPluginSetup } from '@kbn/global-search-plugin/public'; import type { SendRequestResponse } from '@kbn/es-ui-shared-plugin/public'; @@ -52,40 +50,43 @@ import type { GuidedOnboardingPluginStart } from '@kbn/guided-onboarding-plugin/ import type { DashboardStart } from '@kbn/dashboard-plugin/public'; -import { PLUGIN_ID, INTEGRATIONS_PLUGIN_ID, setupRouteService, appRoutesService } from '../common'; -import { calculateAuthz, calculatePackagePrivilegesFromCapabilities } from '../common/authz'; -import { parseExperimentalConfigValue } from '../common/experimental_features'; -import type { CheckPermissionsResponse, PostFleetSetupResponse } from '../common/types'; import type { FleetAuthz } from '../common'; +import { appRoutesService, INTEGRATIONS_PLUGIN_ID, PLUGIN_ID, setupRouteService } from '../common'; +import { + calculateAuthz, + calculateEndpointExceptionsPrivilegesFromCapabilities, + calculatePackagePrivilegesFromCapabilities, +} from '../common/authz'; import type { ExperimentalFeatures } from '../common/experimental_features'; - -import type { FleetConfigType } from '../common/types'; +import { parseExperimentalConfigValue } from '../common/experimental_features'; +import type { + CheckPermissionsResponse, + FleetConfigType, + PostFleetSetupResponse, +} from '../common/types'; import { API_VERSIONS } from '../common/constants'; import { CUSTOM_LOGS_INTEGRATION_NAME, INTEGRATIONS_BASE_PATH } from './constants'; -import { licenseService } from './hooks'; +import type { RequestError } from './hooks'; +import { licenseService, sendGetBulkAssets } from './hooks'; import { setHttpClient } from './hooks/use_request'; import { createPackageSearchProvider } from './search_provider'; import { TutorialDirectoryHeaderLink, TutorialModuleNotice } from './components/home_integration'; import { createExtensionRegistrationCallback } from './services/ui_extensions'; import { ExperimentalFeaturesService } from './services/experimental_features'; import type { - UIExtensionRegistrationCallback, - UIExtensionsStorage, GetBulkAssetsRequest, GetBulkAssetsResponse, + UIExtensionRegistrationCallback, + UIExtensionsStorage, } from './types'; import { LazyCustomLogsAssetsExtension } from './lazy_custom_logs_assets_extension'; - -export type { FleetConfigType } from '../common/types'; - import { setCustomIntegrations, setCustomIntegrationsStart } from './services/custom_integrations'; - -import type { RequestError } from './hooks'; -import { sendGetBulkAssets } from './hooks'; import { fleetDeepLinks } from './deep_links'; +export type { FleetConfigType } from '../common/types'; + // We need to provide an object instead of void so that dependent plugins know when Fleet // is disabled. // eslint-disable-next-line @typescript-eslint/no-empty-interface @@ -326,6 +327,8 @@ export class FleetPlugin implements Plugin { diff --git a/x-pack/plugins/security_solution/common/endpoint/service/authz/authz.ts b/x-pack/plugins/security_solution/common/endpoint/service/authz/authz.ts index c0c5ee06d5488..922e8c3cdd383 100644 --- a/x-pack/plugins/security_solution/common/endpoint/service/authz/authz.ts +++ b/x-pack/plugins/security_solution/common/endpoint/service/authz/authz.ts @@ -30,6 +30,13 @@ export function hasKibanaPrivilege( return fleetAuthz.packagePrivileges?.endpoint?.actions[privilege].executePackageAction ?? false; } +export function hasEndpointExceptionsPrivilege( + fleetAuthz: FleetAuthz, + privilege: 'showEndpointExceptions' | 'crudEndpointExceptions' +): boolean { + return fleetAuthz.endpointExceptionsPrivileges?.actions[privilege] ?? false; +} + /** * Used by both the server and the UI to generate the Authorization for access to Endpoint related * functionality @@ -84,6 +91,15 @@ export const calculateEndpointAuthz = ( const canWriteExecuteOperations = hasKibanaPrivilege(fleetAuthz, 'writeExecuteOperations'); + const canReadEndpointExceptions = hasEndpointExceptionsPrivilege( + fleetAuthz, + 'showEndpointExceptions' + ); + const canWriteEndpointExceptions = hasEndpointExceptionsPrivilege( + fleetAuthz, + 'crudEndpointExceptions' + ); + const authz: EndpointAuthz = { canWriteSecuritySolution, canReadSecuritySolution, @@ -123,6 +139,8 @@ export const calculateEndpointAuthz = ( canReadBlocklist, canWriteEventFilters, canReadEventFilters, + canReadEndpointExceptions, + canWriteEndpointExceptions, }; // Response console is only accessible when license is Enterprise and user has access to any @@ -172,5 +190,7 @@ export const getEndpointAuthzInitialState = (): EndpointAuthz => { canReadBlocklist: false, canWriteEventFilters: false, canReadEventFilters: false, + canReadEndpointExceptions: false, + canWriteEndpointExceptions: false, }; }; diff --git a/x-pack/plugins/security_solution/common/endpoint/types/authz.ts b/x-pack/plugins/security_solution/common/endpoint/types/authz.ts index 23ed5cbe7c439..1ec8f84a87073 100644 --- a/x-pack/plugins/security_solution/common/endpoint/types/authz.ts +++ b/x-pack/plugins/security_solution/common/endpoint/types/authz.ts @@ -10,74 +10,79 @@ * used both on the client and server for consistency */ export interface EndpointAuthz { - /** if user has write permissions to the security solution app */ + /** If the user has write permissions to the security solution app */ canWriteSecuritySolution: boolean; - /** if user has read permissions to the security solution app */ + /** If the user has read permissions to the security solution app */ canReadSecuritySolution: boolean; - /** If user has permissions to access Fleet */ + /** If the user has permissions to access Fleet */ canAccessFleet: boolean; - /** If user has permissions to access Endpoint management (includes check to ensure they also have access to fleet) */ + /** If the user has permissions to access Endpoint management (includes check to ensure they also have access to fleet) */ canAccessEndpointManagement: boolean; - /** If user has permissions to access Actions Log management and also has a platinum license (used for endpoint details flyout) */ + /** If the user has permissions to access Actions Log management and also has a platinum license (used for endpoint details flyout) */ canAccessEndpointActionsLogManagement: boolean; - /** if user has permissions to create Artifacts by Policy */ + /** If the user has permissions to create Artifacts by Policy */ canCreateArtifactsByPolicy: boolean; - /** if user has write permissions to endpoint list */ + /** If the user has write permissions to endpoint list */ canWriteEndpointList: boolean; - /** if user has read permissions to endpoint list */ + /** If the user has read permissions to endpoint list */ canReadEndpointList: boolean; - /** if user has write permissions for policy management */ + /** If the user has write permissions for policy management */ canWritePolicyManagement: boolean; - /** if user has read permissions for policy management */ + /** If the user has read permissions for policy management */ canReadPolicyManagement: boolean; - /** if user has write permissions for actions log management */ + /** If the user has write permissions for actions log management */ canWriteActionsLogManagement: boolean; - /** if user has read permissions for actions log management */ + /** If the user has read permissions for actions log management */ canReadActionsLogManagement: boolean; - /** If user has permissions to isolate hosts */ + /** If the user has permissions to isolate hosts */ canIsolateHost: boolean; - /** If user has permissions to un-isolate (release) hosts */ + /** If the user has permissions to un-isolate (release) hosts */ canUnIsolateHost: boolean; - /** If user has permissions to kill process on hosts */ + /** If the user has permissions to kill process on hosts */ canKillProcess: boolean; - /** If user has permissions to suspend process on hosts */ + /** If the user has permissions to suspend process on hosts */ canSuspendProcess: boolean; - /** If user has permissions to get running processes on hosts */ + /** If the user has permissions to get running processes on hosts */ canGetRunningProcesses: boolean; - /** If user has permissions to use the Response Actions Console */ + /** If the user has permissions to use the Response Actions Console */ canAccessResponseConsole: boolean; - /** If user has write permissions to use execute action */ + /** If the user has write permissions to use execute action */ canWriteExecuteOperations: boolean; - /** If user has write permissions to use file operations */ + /** If the user has write permissions to use file operations */ canWriteFileOperations: boolean; - /** if user has write permissions for trusted applications */ + /** If the user has write permissions for trusted applications */ canWriteTrustedApplications: boolean; - /** if user has read permissions for trusted applications */ + /** If the user has read permissions for trusted applications */ canReadTrustedApplications: boolean; - /** if user has write permissions for host isolation exceptions */ + /** If the user has write permissions for host isolation exceptions */ canWriteHostIsolationExceptions: boolean; - /** if user has read permissions for host isolation exceptions */ + /** If the user has read permissions for host isolation exceptions */ canReadHostIsolationExceptions: boolean; /** - * if user has permissions to access host isolation exceptions. This could be set to false, while + * If the user has permissions to access host isolation exceptions. This could be set to false, while * `canReadHostIsolationExceptions` is true in cases where the license might have been downgraded. * It is used to show the UI elements that allow users to navigate to the host isolation exceptions. */ canAccessHostIsolationExceptions: boolean; /** - * if user has permissions to delete host isolation exceptions. This could be set to true, while + * If the user has permissions to delete host isolation exceptions. This could be set to true, while * `canWriteHostIsolationExceptions` is false in cases where the license might have been downgraded. * In that use case, users should still be allowed to ONLY delete entries. */ canDeleteHostIsolationExceptions: boolean; - /** if user has write permissions for blocklist entries */ + /** If the user has write permissions for blocklist entries */ canWriteBlocklist: boolean; - /** if user has read permissions for blocklist entries */ + /** If the user has read permissions for blocklist entries */ canReadBlocklist: boolean; - /** if user has write permissions for event filters */ + /** If the user has write permissions for event filters */ canWriteEventFilters: boolean; - /** if user has read permissions for event filters */ + /** If the user has read permissions for event filters */ canReadEventFilters: boolean; + + /** if the user has write permissions for endpoint exceptions */ + canReadEndpointExceptions: boolean; + /** if the user has read permissions for endpoint exceptions */ + canWriteEndpointExceptions: boolean; } export type EndpointAuthzKeyList = Array; diff --git a/x-pack/plugins/security_solution/server/endpoint/endpoint_app_context_services.ts b/x-pack/plugins/security_solution/server/endpoint/endpoint_app_context_services.ts index 917d366354270..2915ad4a6a21c 100644 --- a/x-pack/plugins/security_solution/server/endpoint/endpoint_app_context_services.ts +++ b/x-pack/plugins/security_solution/server/endpoint/endpoint_app_context_services.ts @@ -6,27 +6,27 @@ */ import type { + ElasticsearchClient, KibanaRequest, Logger, - ElasticsearchClient, SavedObjectsClientContract, } from '@kbn/core/server'; import type { ExceptionListClient, ListsServerExtensionRegistrar } from '@kbn/lists-plugin/server'; import type { CasesClient, CasesStart } from '@kbn/cases-plugin/server'; import type { SecurityPluginStart } from '@kbn/security-plugin/server'; import type { + FleetFromHostFileClientInterface, FleetStartContract, MessageSigningServiceInterface, - FleetFromHostFileClientInterface, } from '@kbn/fleet-plugin/server'; 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 { getPackagePolicyCreateCallback, - getPackagePolicyUpdateCallback, getPackagePolicyDeleteCallback, getPackagePolicyPostCreateCallback, + getPackagePolicyUpdateCallback, } from '../fleet_integration/fleet_integration'; import type { ManifestManager } from './services/artifacts'; import type { ConfigType } from '../config'; @@ -276,4 +276,12 @@ export class EndpointAppContextService { return this.startDependencies.createFleetActionsClient('endpoint'); } + + public async getAppFeaturesService(): Promise { + if (!this.startDependencies?.appFeaturesService) { + throw new EndpointAppContentServicesNotStartedError(); + } + + return this.startDependencies.appFeaturesService; + } } From 12dcc1714fa1a9ded73a7efeb0e2d7b8404eaeee Mon Sep 17 00:00:00 2001 From: Ashokaditya Date: Tue, 12 Sep 2023 12:48:53 +0200 Subject: [PATCH 03/42] add endpoint exceptions validator refs elastic/security-team/issues/7223 --- .../endpoint/validators/base_validator.ts | 21 ++++++++++ .../endpoint_exceptions_validator.ts | 40 +++++++++++++++++++ .../endpoint/validators/index.ts | 1 + 3 files changed, 62 insertions(+) create mode 100644 x-pack/plugins/security_solution/server/lists_integration/endpoint/validators/endpoint_exceptions_validator.ts diff --git a/x-pack/plugins/security_solution/server/lists_integration/endpoint/validators/base_validator.ts b/x-pack/plugins/security_solution/server/lists_integration/endpoint/validators/base_validator.ts index ecc2ac7893336..a73a5027751b9 100644 --- a/x-pack/plugins/security_solution/server/lists_integration/endpoint/validators/base_validator.ts +++ b/x-pack/plugins/security_solution/server/lists_integration/endpoint/validators/base_validator.ts @@ -10,6 +10,8 @@ import { schema } from '@kbn/config-schema'; import { isEqual } from 'lodash/fp'; import type { ExceptionListItemSchema } from '@kbn/securitysolution-io-ts-list-types'; import { OperatingSystem } from '@kbn/securitysolution-utils'; + +import { AppFeatureSecurityKey } from '@kbn/security-solution-features/src/app_features_keys'; import type { EndpointAuthz } from '../../../../common/endpoint/types/authz'; import type { EndpointAppContextService } from '../../../endpoint/endpoint_app_context_services'; import type { ExceptionItemLikeOptions } from '../types'; @@ -74,6 +76,25 @@ export class BaseValidator { } } + protected async validateHasEndpointExceptionsPrivileges( + privilege: keyof EndpointAuthz + ): Promise { + const appFeaturesService = this.endpointAppContext.getAppFeaturesService(); + + const isEndpointExceptionsFeaturesEnabled = (await appFeaturesService).isEnabled( + AppFeatureSecurityKey.endpointExceptions + ); + + // for serverless only when Endpoint Exceptions is not enabled + // verify that the user has the privilege to create/read/update/delete endpoint exceptions + if (!isEndpointExceptionsFeaturesEnabled && !(await this.endpointAuthzPromise)[privilege]) { + throw new EndpointArtifactExceptionValidationError( + 'Endpoint exceptions authorization failure', + 403 + ); + } + } + protected async validateHasPrivilege(privilege: keyof EndpointAuthz): Promise { if (!(await this.endpointAuthzPromise)[privilege]) { throw new EndpointArtifactExceptionValidationError('Endpoint authorization failure', 403); diff --git a/x-pack/plugins/security_solution/server/lists_integration/endpoint/validators/endpoint_exceptions_validator.ts b/x-pack/plugins/security_solution/server/lists_integration/endpoint/validators/endpoint_exceptions_validator.ts new file mode 100644 index 0000000000000..2c1c5adde4a06 --- /dev/null +++ b/x-pack/plugins/security_solution/server/lists_integration/endpoint/validators/endpoint_exceptions_validator.ts @@ -0,0 +1,40 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import type { + CreateExceptionListItemOptions, + UpdateExceptionListItemOptions, +} from '@kbn/lists-plugin/server'; +import { BaseValidator } from './base_validator'; + +export class EndpointExceptionsValidator extends BaseValidator { + static isEndpointException(item: { listId: string }): boolean { + return item.listId === 'endpoint_list'; + } + + protected async validateHasReadPrivilege(): Promise { + return this.validateHasEndpointExceptionsPrivileges('canWriteEndpointExceptions'); + } + + protected async validateHasWritePrivilege(): Promise { + return this.validateHasEndpointExceptionsPrivileges('canReadEndpointExceptions'); + } + + async validatePreCreateItem(item: CreateExceptionListItemOptions) { + await this.validateHasWritePrivilege(); + return item; + } + + async validatePreUpdateItem(item: UpdateExceptionListItemOptions) { + await this.validateHasWritePrivilege(); + return item; + } + + async validatePreDeleteItem(): Promise { + await this.validateHasWritePrivilege(); + } +} diff --git a/x-pack/plugins/security_solution/server/lists_integration/endpoint/validators/index.ts b/x-pack/plugins/security_solution/server/lists_integration/endpoint/validators/index.ts index ccd6ebd8e08d6..8dd357b15ebd9 100644 --- a/x-pack/plugins/security_solution/server/lists_integration/endpoint/validators/index.ts +++ b/x-pack/plugins/security_solution/server/lists_integration/endpoint/validators/index.ts @@ -9,3 +9,4 @@ export { TrustedAppValidator } from './trusted_app_validator'; export { EventFilterValidator } from './event_filter_validator'; export { HostIsolationExceptionsValidator } from './host_isolation_exceptions_validator'; export { BlocklistValidator } from './blocklist_validator'; +export { EndpointExceptionsValidator } from './endpoint_exceptions_validator'; From 088c76a2d2ff102a18e5fbfb92d83f1267fb46ff Mon Sep 17 00:00:00 2001 From: Ashokaditya Date: Tue, 12 Sep 2023 14:56:38 +0200 Subject: [PATCH 04/42] use endpoint exceptions validator in extensions refs elastic/security-team/issues/7223 --- .../services/feature_usage/service.ts | 1 + .../handlers/exceptions_pre_create_handler.ts | 16 +++++++++++++-- .../exceptions_pre_delete_item_handler.ts | 16 ++++++++++++--- .../handlers/exceptions_pre_export_handler.ts | 13 +++++++++--- .../exceptions_pre_get_one_handler.ts | 16 ++++++++++++--- .../exceptions_pre_multi_list_find_handler.ts | 16 ++++++++++++--- ...exceptions_pre_single_list_find_handler.ts | 16 ++++++++++++--- .../exceptions_pre_summary_handler.ts | 16 ++++++++++++--- .../handlers/exceptions_pre_update_handler.ts | 19 ++++++++++++++++-- .../endpoint_exceptions_validator.ts | 20 +++++++++++++++++++ 10 files changed, 127 insertions(+), 22 deletions(-) diff --git a/x-pack/plugins/security_solution/server/endpoint/services/feature_usage/service.ts b/x-pack/plugins/security_solution/server/endpoint/services/feature_usage/service.ts index f736fffffcf17..ffebaac4420fc 100644 --- a/x-pack/plugins/security_solution/server/endpoint/services/feature_usage/service.ts +++ b/x-pack/plugins/security_solution/server/endpoint/services/feature_usage/service.ts @@ -24,6 +24,7 @@ const FEATURES = { GET_FILE: 'Get file', EXECUTE: 'Execute command', ALERTS_BY_PROCESS_ANCESTRY: 'Get related alerts by process ancestry', + ENDPOINT_EXCEPTIONS: 'Endpoint exceptions', } as const; export type FeatureKeys = keyof typeof FEATURES; diff --git a/x-pack/plugins/security_solution/server/lists_integration/endpoint/handlers/exceptions_pre_create_handler.ts b/x-pack/plugins/security_solution/server/lists_integration/endpoint/handlers/exceptions_pre_create_handler.ts index f87e845487a04..0e7a2db2d4e47 100644 --- a/x-pack/plugins/security_solution/server/lists_integration/endpoint/handlers/exceptions_pre_create_handler.ts +++ b/x-pack/plugins/security_solution/server/lists_integration/endpoint/handlers/exceptions_pre_create_handler.ts @@ -11,10 +11,11 @@ import type { } from '@kbn/lists-plugin/server'; import type { EndpointAppContextService } from '../../../endpoint/endpoint_app_context_services'; import { + BlocklistValidator, + EndpointExceptionsValidator, EventFilterValidator, - TrustedAppValidator, HostIsolationExceptionsValidator, - BlocklistValidator, + TrustedAppValidator, } from '../validators'; type ValidatorCallback = ExceptionsListPreCreateItemServerExtension['callback']; @@ -65,6 +66,17 @@ export const getExceptionsPreCreateItemHandler = ( return validatedItem; } + // validate endpoint exceptions + if (EndpointExceptionsValidator.isEndpointException(data)) { + const endpointExceptionValidator = new EndpointExceptionsValidator( + endpointAppContext, + request + ); + const validatedItem = await endpointExceptionValidator.validatePreCreateItem(data); + endpointExceptionValidator.notifyFeatureUsage(data, 'ENDPOINT_EXCEPTIONS'); + return validatedItem; + } + return data; }; }; diff --git a/x-pack/plugins/security_solution/server/lists_integration/endpoint/handlers/exceptions_pre_delete_item_handler.ts b/x-pack/plugins/security_solution/server/lists_integration/endpoint/handlers/exceptions_pre_delete_item_handler.ts index 66a9b4709bd2b..719cd7b655b22 100644 --- a/x-pack/plugins/security_solution/server/lists_integration/endpoint/handlers/exceptions_pre_delete_item_handler.ts +++ b/x-pack/plugins/security_solution/server/lists_integration/endpoint/handlers/exceptions_pre_delete_item_handler.ts @@ -9,10 +9,11 @@ import type { ExceptionListItemSchema } from '@kbn/securitysolution-io-ts-list-t import type { ExceptionsListPreDeleteItemServerExtension } from '@kbn/lists-plugin/server'; import type { EndpointAppContextService } from '../../../endpoint/endpoint_app_context_services'; import { - TrustedAppValidator, - HostIsolationExceptionsValidator, - EventFilterValidator, BlocklistValidator, + EndpointExceptionsValidator, + EventFilterValidator, + HostIsolationExceptionsValidator, + TrustedAppValidator, } from '../validators'; type ValidatorCallback = ExceptionsListPreDeleteItemServerExtension['callback']; @@ -64,6 +65,15 @@ export const getExceptionsPreDeleteItemHandler = ( return data; } + // Validate Endpoint Exceptions + if (EndpointExceptionsValidator.isEndpointException({ listId })) { + await new EndpointExceptionsValidator( + endpointAppContextService, + request + ).validatePreDeleteItem(); + return data; + } + return data; }; }; diff --git a/x-pack/plugins/security_solution/server/lists_integration/endpoint/handlers/exceptions_pre_export_handler.ts b/x-pack/plugins/security_solution/server/lists_integration/endpoint/handlers/exceptions_pre_export_handler.ts index 7dac876b34f22..47ed89ce19982 100644 --- a/x-pack/plugins/security_solution/server/lists_integration/endpoint/handlers/exceptions_pre_export_handler.ts +++ b/x-pack/plugins/security_solution/server/lists_integration/endpoint/handlers/exceptions_pre_export_handler.ts @@ -8,10 +8,11 @@ import type { ExceptionsListPreExportServerExtension } from '@kbn/lists-plugin/server'; import type { EndpointAppContextService } from '../../../endpoint/endpoint_app_context_services'; import { - TrustedAppValidator, - HostIsolationExceptionsValidator, - EventFilterValidator, BlocklistValidator, + EndpointExceptionsValidator, + EventFilterValidator, + HostIsolationExceptionsValidator, + TrustedAppValidator, } from '../validators'; type ValidatorCallback = ExceptionsListPreExportServerExtension['callback']; @@ -61,6 +62,12 @@ export const getExceptionsPreExportHandler = ( return data; } + // Validate Endpoint Exceptions + if (EndpointExceptionsValidator.isEndpointException({ listId })) { + await new EndpointExceptionsValidator(endpointAppContextService, request).validatePreExport(); + return data; + } + return data; }; }; diff --git a/x-pack/plugins/security_solution/server/lists_integration/endpoint/handlers/exceptions_pre_get_one_handler.ts b/x-pack/plugins/security_solution/server/lists_integration/endpoint/handlers/exceptions_pre_get_one_handler.ts index 9ed81b1f0d585..15e9af5e61ac9 100644 --- a/x-pack/plugins/security_solution/server/lists_integration/endpoint/handlers/exceptions_pre_get_one_handler.ts +++ b/x-pack/plugins/security_solution/server/lists_integration/endpoint/handlers/exceptions_pre_get_one_handler.ts @@ -9,10 +9,11 @@ import type { ExceptionListItemSchema } from '@kbn/securitysolution-io-ts-list-t import type { ExceptionsListPreGetOneItemServerExtension } from '@kbn/lists-plugin/server'; import type { EndpointAppContextService } from '../../../endpoint/endpoint_app_context_services'; import { - TrustedAppValidator, - HostIsolationExceptionsValidator, - EventFilterValidator, BlocklistValidator, + EndpointExceptionsValidator, + EventFilterValidator, + HostIsolationExceptionsValidator, + TrustedAppValidator, } from '../validators'; type ValidatorCallback = ExceptionsListPreGetOneItemServerExtension['callback']; @@ -64,6 +65,15 @@ export const getExceptionsPreGetOneHandler = ( return data; } + // Validate Endpoint Exceptions + if (EndpointExceptionsValidator.isEndpointException({ listId })) { + await new EndpointExceptionsValidator( + endpointAppContextService, + request + ).validatePreGetOneItem(); + return data; + } + return data; }; }; diff --git a/x-pack/plugins/security_solution/server/lists_integration/endpoint/handlers/exceptions_pre_multi_list_find_handler.ts b/x-pack/plugins/security_solution/server/lists_integration/endpoint/handlers/exceptions_pre_multi_list_find_handler.ts index 973bb6ce5072a..e3e66daebc57d 100644 --- a/x-pack/plugins/security_solution/server/lists_integration/endpoint/handlers/exceptions_pre_multi_list_find_handler.ts +++ b/x-pack/plugins/security_solution/server/lists_integration/endpoint/handlers/exceptions_pre_multi_list_find_handler.ts @@ -8,10 +8,11 @@ import type { ExceptionsListPreMultiListFindServerExtension } from '@kbn/lists-plugin/server'; import type { EndpointAppContextService } from '../../../endpoint/endpoint_app_context_services'; import { - TrustedAppValidator, - HostIsolationExceptionsValidator, - EventFilterValidator, BlocklistValidator, + EndpointExceptionsValidator, + EventFilterValidator, + HostIsolationExceptionsValidator, + TrustedAppValidator, } from '../validators'; type ValidatorCallback = ExceptionsListPreMultiListFindServerExtension['callback']; @@ -54,6 +55,15 @@ export const getExceptionsPreMultiListFindHandler = ( return data; } + // Validate Endpoint Exceptions + if (data.listId.some((id) => EndpointExceptionsValidator.isEndpointException({ listId: id }))) { + await new EndpointExceptionsValidator( + endpointAppContextService, + request + ).validatePreMultiListFind(); + return data; + } + return data; }; }; diff --git a/x-pack/plugins/security_solution/server/lists_integration/endpoint/handlers/exceptions_pre_single_list_find_handler.ts b/x-pack/plugins/security_solution/server/lists_integration/endpoint/handlers/exceptions_pre_single_list_find_handler.ts index 946a0bf6d7c43..e647ea2a710f5 100644 --- a/x-pack/plugins/security_solution/server/lists_integration/endpoint/handlers/exceptions_pre_single_list_find_handler.ts +++ b/x-pack/plugins/security_solution/server/lists_integration/endpoint/handlers/exceptions_pre_single_list_find_handler.ts @@ -8,10 +8,11 @@ import type { ExceptionsListPreSingleListFindServerExtension } from '@kbn/lists-plugin/server'; import type { EndpointAppContextService } from '../../../endpoint/endpoint_app_context_services'; import { - TrustedAppValidator, - HostIsolationExceptionsValidator, - EventFilterValidator, BlocklistValidator, + EndpointExceptionsValidator, + EventFilterValidator, + HostIsolationExceptionsValidator, + TrustedAppValidator, } from '../validators'; type ValidatorCallback = ExceptionsListPreSingleListFindServerExtension['callback']; @@ -55,6 +56,15 @@ export const getExceptionsPreSingleListFindHandler = ( return data; } + // Validate Endpoint Exceptions + if (EndpointExceptionsValidator.isEndpointException({ listId })) { + await new EndpointExceptionsValidator( + endpointAppContextService, + request + ).validatePreSingleListFind(); + return data; + } + return data; }; }; diff --git a/x-pack/plugins/security_solution/server/lists_integration/endpoint/handlers/exceptions_pre_summary_handler.ts b/x-pack/plugins/security_solution/server/lists_integration/endpoint/handlers/exceptions_pre_summary_handler.ts index 6b9af37f877ab..d50504572b369 100644 --- a/x-pack/plugins/security_solution/server/lists_integration/endpoint/handlers/exceptions_pre_summary_handler.ts +++ b/x-pack/plugins/security_solution/server/lists_integration/endpoint/handlers/exceptions_pre_summary_handler.ts @@ -8,10 +8,11 @@ import type { ExceptionsListPreSummaryServerExtension } from '@kbn/lists-plugin/server'; import type { EndpointAppContextService } from '../../../endpoint/endpoint_app_context_services'; import { - TrustedAppValidator, - HostIsolationExceptionsValidator, - EventFilterValidator, BlocklistValidator, + EndpointExceptionsValidator, + EventFilterValidator, + HostIsolationExceptionsValidator, + TrustedAppValidator, } from '../validators'; type ValidatorCallback = ExceptionsListPreSummaryServerExtension['callback']; @@ -61,6 +62,15 @@ export const getExceptionsPreSummaryHandler = ( return data; } + // Validate Endpoint Exceptions + if (EndpointExceptionsValidator.isEndpointException({ listId })) { + await new EndpointExceptionsValidator( + endpointAppContextService, + request + ).validatePreGetListSummary(); + return data; + } + return data; }; }; diff --git a/x-pack/plugins/security_solution/server/lists_integration/endpoint/handlers/exceptions_pre_update_handler.ts b/x-pack/plugins/security_solution/server/lists_integration/endpoint/handlers/exceptions_pre_update_handler.ts index 681d16a1e44b8..810b569ecf8a6 100644 --- a/x-pack/plugins/security_solution/server/lists_integration/endpoint/handlers/exceptions_pre_update_handler.ts +++ b/x-pack/plugins/security_solution/server/lists_integration/endpoint/handlers/exceptions_pre_update_handler.ts @@ -12,10 +12,11 @@ import type { import type { EndpointAppContextService } from '../../../endpoint/endpoint_app_context_services'; import type { ExceptionItemLikeOptions } from '../types'; import { + BlocklistValidator, + EndpointExceptionsValidator, EventFilterValidator, - TrustedAppValidator, HostIsolationExceptionsValidator, - BlocklistValidator, + TrustedAppValidator, } from '../validators'; type ValidatorCallback = ExceptionsListPreUpdateItemServerExtension['callback']; @@ -98,6 +99,20 @@ export const getExceptionsPreUpdateItemHandler = ( return validatedItem; } + // Validate Endpoint Exceptions + if (EndpointExceptionsValidator.isEndpointException({ listId })) { + const endpointExceptionValidator = new EndpointExceptionsValidator( + endpointAppContextService, + request + ); + const validatedItem = await endpointExceptionValidator.validatePreUpdateItem(data); + endpointExceptionValidator.notifyFeatureUsage( + data as ExceptionItemLikeOptions, + 'ENDPOINT_EXCEPTIONS' + ); + return validatedItem; + } + return data; }; }; diff --git a/x-pack/plugins/security_solution/server/lists_integration/endpoint/validators/endpoint_exceptions_validator.ts b/x-pack/plugins/security_solution/server/lists_integration/endpoint/validators/endpoint_exceptions_validator.ts index 2c1c5adde4a06..260fd2997fa31 100644 --- a/x-pack/plugins/security_solution/server/lists_integration/endpoint/validators/endpoint_exceptions_validator.ts +++ b/x-pack/plugins/security_solution/server/lists_integration/endpoint/validators/endpoint_exceptions_validator.ts @@ -37,4 +37,24 @@ export class EndpointExceptionsValidator extends BaseValidator { async validatePreDeleteItem(): Promise { await this.validateHasWritePrivilege(); } + + async validatePreGetOneItem(): Promise { + await this.validateHasReadPrivilege(); + } + + async validatePreMultiListFind(): Promise { + await this.validateHasReadPrivilege(); + } + + async validatePreExport(): Promise { + await this.validateHasReadPrivilege(); + } + + async validatePreSingleListFind(): Promise { + await this.validateHasReadPrivilege(); + } + + async validatePreGetListSummary(): Promise { + await this.validateHasReadPrivilege(); + } } From fa1e8eb010a6c3e2f9452c8e9bb6b75f4a9d06bb Mon Sep 17 00:00:00 2001 From: Ashokaditya Date: Wed, 13 Sep 2023 09:14:33 +0200 Subject: [PATCH 05/42] update initial authz state in test --- x-pack/plugins/fleet/common/mocks.ts | 10 ++++++++-- .../common/endpoint/service/authz/authz.test.ts | 2 ++ 2 files changed, 10 insertions(+), 2 deletions(-) diff --git a/x-pack/plugins/fleet/common/mocks.ts b/x-pack/plugins/fleet/common/mocks.ts index 13d0edcf07e58..8e2545adf5ed5 100644 --- a/x-pack/plugins/fleet/common/mocks.ts +++ b/x-pack/plugins/fleet/common/mocks.ts @@ -6,10 +6,10 @@ */ import type { - PostDeletePackagePoliciesResponse, + AgentPolicy, NewPackagePolicy, PackagePolicy, - AgentPolicy, + PostDeletePackagePoliciesResponse, } from './types'; import type { FleetAuthz } from './authz'; import { dataTypes, ENDPOINT_PRIVILEGES } from './constants'; @@ -108,6 +108,12 @@ export const createFleetAuthzMock = (): FleetAuthz => { }, }, }, + endpointExceptionsPrivileges: { + actions: { + showEndpointExceptions: true, + crudEndpointExceptions: true, + }, + }, }; }; diff --git a/x-pack/plugins/security_solution/common/endpoint/service/authz/authz.test.ts b/x-pack/plugins/security_solution/common/endpoint/service/authz/authz.test.ts index 8c3c697b80e2c..79e28c24fc373 100644 --- a/x-pack/plugins/security_solution/common/endpoint/service/authz/authz.test.ts +++ b/x-pack/plugins/security_solution/common/endpoint/service/authz/authz.test.ts @@ -281,6 +281,8 @@ describe('Endpoint Authz service', () => { canReadBlocklist: false, canWriteEventFilters: false, canReadEventFilters: false, + canReadEndpointExceptions: false, + canWriteEndpointExceptions: false, }); }); }); From bf9577b2e00e90cf0447fd5cb86a42631031edfe Mon Sep 17 00:00:00 2001 From: Ashokaditya Date: Wed, 13 Sep 2023 11:37:45 +0200 Subject: [PATCH 06/42] Update authz.test.ts --- .../endpoint/service/authz/authz.test.ts | 24 ++++++++++++++++++- 1 file changed, 23 insertions(+), 1 deletion(-) diff --git a/x-pack/plugins/security_solution/common/endpoint/service/authz/authz.test.ts b/x-pack/plugins/security_solution/common/endpoint/service/authz/authz.test.ts index 79e28c24fc373..a3b3ebaf26142 100644 --- a/x-pack/plugins/security_solution/common/endpoint/service/authz/authz.test.ts +++ b/x-pack/plugins/security_solution/common/endpoint/service/authz/authz.test.ts @@ -11,8 +11,8 @@ import { createFleetAuthzMock } from '@kbn/fleet-plugin/common/mocks'; import { createLicenseServiceMock } from '../../../license/mocks'; import type { EndpointAuthzKeyList } from '../../types/authz'; import { - RESPONSE_CONSOLE_ACTION_COMMANDS_TO_RBAC_FEATURE_CONTROL, CONSOLE_RESPONSE_ACTION_COMMANDS, + RESPONSE_CONSOLE_ACTION_COMMANDS_TO_RBAC_FEATURE_CONTROL, type ResponseConsoleRbacControls, } from '../response_actions/constants'; @@ -150,6 +150,14 @@ describe('Endpoint Authz service', () => { expect(authz[auth]).toBe(true); }); + it.each<[EndpointAuthzKeyList[number], string]>([ + ['canReadEndpointExceptions', 'showEndpointExceptions'], + ['canWriteEndpointExceptions', 'crudEndpointExceptions'], + ])('%s should be true if `endpointExceptionsPrivileges.%s` is `true`', (auth) => { + const authz = calculateEndpointAuthz(licenseService, fleetAuthz, userRoles); + expect(authz[auth]).toBe(true); + }); + it.each<[EndpointAuthzKeyList[number], string[]]>([ ['canWriteEndpointList', ['writeEndpointList']], ['canReadEndpointList', ['readEndpointList']], @@ -181,6 +189,20 @@ describe('Endpoint Authz service', () => { privileges.forEach((privilege) => { fleetAuthz.packagePrivileges!.endpoint.actions[privilege].executePackageAction = false; }); + + const authz = calculateEndpointAuthz(licenseService, fleetAuthz, userRoles); + expect(authz[auth]).toBe(false); + }); + + it.each<[EndpointAuthzKeyList[number], string[]]>([ + ['canReadEndpointExceptions', ['showEndpointExceptions']], + ['canWriteEndpointExceptions', ['crudEndpointExceptions']], + ])('%s should be false if `endpointExceptionsPrivileges.%s` is `false`', (auth, privileges) => { + privileges.forEach((privilege) => { + // @ts-ignore + fleetAuthz.endpointExceptionsPrivileges!.actions[privilege] = false; + }); + const authz = calculateEndpointAuthz(licenseService, fleetAuthz, userRoles); expect(authz[auth]).toBe(false); }); From c44774db629b02b72957d6363caf038b1d68b45a Mon Sep 17 00:00:00 2001 From: Ashokaditya Date: Thu, 14 Sep 2023 09:36:55 +0200 Subject: [PATCH 07/42] update error class for endpoint exceptions refs elastic/security-team/issues/7223 --- .../endpoint/validators/base_validator.ts | 6 ++---- .../validators/endpoint_exception_errors.ts | 14 ++++++++++++++ 2 files changed, 16 insertions(+), 4 deletions(-) create mode 100644 x-pack/plugins/security_solution/server/lists_integration/endpoint/validators/endpoint_exception_errors.ts diff --git a/x-pack/plugins/security_solution/server/lists_integration/endpoint/validators/base_validator.ts b/x-pack/plugins/security_solution/server/lists_integration/endpoint/validators/base_validator.ts index a73a5027751b9..a918b360966ef 100644 --- a/x-pack/plugins/security_solution/server/lists_integration/endpoint/validators/base_validator.ts +++ b/x-pack/plugins/security_solution/server/lists_integration/endpoint/validators/base_validator.ts @@ -21,6 +21,7 @@ import { isArtifactByPolicy, } from '../../../../common/endpoint/service/artifacts'; import { EndpointArtifactExceptionValidationError } from './errors'; +import { EndpointExceptionsValidationError } from './endpoint_exception_errors'; import type { FeatureKeys } from '../../../endpoint/services/feature_usage/service'; export const BasicEndpointExceptionDataSchema = schema.object( @@ -88,10 +89,7 @@ export class BaseValidator { // for serverless only when Endpoint Exceptions is not enabled // verify that the user has the privilege to create/read/update/delete endpoint exceptions if (!isEndpointExceptionsFeaturesEnabled && !(await this.endpointAuthzPromise)[privilege]) { - throw new EndpointArtifactExceptionValidationError( - 'Endpoint exceptions authorization failure', - 403 - ); + throw new EndpointExceptionsValidationError('Endpoint exceptions authorization failure', 403); } } diff --git a/x-pack/plugins/security_solution/server/lists_integration/endpoint/validators/endpoint_exception_errors.ts b/x-pack/plugins/security_solution/server/lists_integration/endpoint/validators/endpoint_exception_errors.ts new file mode 100644 index 0000000000000..d0f2ba0537b55 --- /dev/null +++ b/x-pack/plugins/security_solution/server/lists_integration/endpoint/validators/endpoint_exception_errors.ts @@ -0,0 +1,14 @@ +/* + * 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 { ListsErrorWithStatusCode } from '@kbn/lists-plugin/server'; + +export class EndpointExceptionsValidationError extends ListsErrorWithStatusCode { + constructor(message: string, statusCode: number = 400) { + super(`EndpointExceptionsError: ${message}`, statusCode); + } +} From 793f6e0b0b6552c35783276bcfef08e731051848 Mon Sep 17 00:00:00 2001 From: Ashokaditya Date: Thu, 14 Sep 2023 10:20:30 +0200 Subject: [PATCH 08/42] use list id const refs elastic/security-team/issues/7223 --- .../endpoint/validators/endpoint_exceptions_validator.ts | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/x-pack/plugins/security_solution/server/lists_integration/endpoint/validators/endpoint_exceptions_validator.ts b/x-pack/plugins/security_solution/server/lists_integration/endpoint/validators/endpoint_exceptions_validator.ts index 260fd2997fa31..e875f476fed6c 100644 --- a/x-pack/plugins/security_solution/server/lists_integration/endpoint/validators/endpoint_exceptions_validator.ts +++ b/x-pack/plugins/security_solution/server/lists_integration/endpoint/validators/endpoint_exceptions_validator.ts @@ -9,11 +9,12 @@ import type { CreateExceptionListItemOptions, UpdateExceptionListItemOptions, } from '@kbn/lists-plugin/server'; +import { ENDPOINT_LIST_ID } from '@kbn/securitysolution-list-constants'; import { BaseValidator } from './base_validator'; export class EndpointExceptionsValidator extends BaseValidator { static isEndpointException(item: { listId: string }): boolean { - return item.listId === 'endpoint_list'; + return item.listId === ENDPOINT_LIST_ID; } protected async validateHasReadPrivilege(): Promise { From c03a8ed9ec225cae340da6d0b5947b08e354223b Mon Sep 17 00:00:00 2001 From: Ashokaditya Date: Thu, 14 Sep 2023 11:11:27 +0200 Subject: [PATCH 09/42] fix type imports refs elastic/kibana/pull/164107 --- .../public/upselling/hooks/use_product_type_by_pli.ts | 4 ++-- .../pages/osquery_automated_response_actions.tsx | 9 ++++----- .../upselling/pages/threat_intelligence_paywall.tsx | 4 ++-- 3 files changed, 8 insertions(+), 9 deletions(-) diff --git a/x-pack/plugins/security_solution_serverless/public/upselling/hooks/use_product_type_by_pli.ts b/x-pack/plugins/security_solution_serverless/public/upselling/hooks/use_product_type_by_pli.ts index dc5d7debb2954..89038bcf4f4af 100644 --- a/x-pack/plugins/security_solution_serverless/public/upselling/hooks/use_product_type_by_pli.ts +++ b/x-pack/plugins/security_solution_serverless/public/upselling/hooks/use_product_type_by_pli.ts @@ -5,10 +5,10 @@ * 2.0. */ -import type { AppFeatureKey } from '@kbn/security-solution-plugin/common'; +import type { AppFeatureKeyType } from '@kbn/security-solution-features/keys'; import { PLI_APP_FEATURES } from '../../../common/pli/pli_config'; -export const getProductTypeByPLI = (requiredPLI: AppFeatureKey): string | null => { +export const getProductTypeByPLI = (requiredPLI: AppFeatureKeyType): string | null => { if (PLI_APP_FEATURES.security.essentials.includes(requiredPLI)) { return 'Security Essentials'; } diff --git a/x-pack/plugins/security_solution_serverless/public/upselling/pages/osquery_automated_response_actions.tsx b/x-pack/plugins/security_solution_serverless/public/upselling/pages/osquery_automated_response_actions.tsx index 2168390eb31a2..acb9816140584 100644 --- a/x-pack/plugins/security_solution_serverless/public/upselling/pages/osquery_automated_response_actions.tsx +++ b/x-pack/plugins/security_solution_serverless/public/upselling/pages/osquery_automated_response_actions.tsx @@ -8,11 +8,11 @@ import { EuiEmptyPrompt, EuiIcon } from '@elastic/eui'; import { FormattedMessage } from '@kbn/i18n-react'; import React from 'react'; -import type { AppFeatureKey } from '@kbn/security-solution-plugin/common'; +import type { AppFeatureKeyType } from '@kbn/security-solution-features/keys'; import { getProductTypeByPLI } from '../hooks/use_product_type_by_pli'; -const OsqueryResponseActionsUpsellingSection: React.FC<{ requiredPLI: AppFeatureKey }> = React.memo( - ({ requiredPLI }) => { +const OsqueryResponseActionsUpsellingSection: React.FC<{ requiredPLI: AppFeatureKeyType }> = + React.memo(({ requiredPLI }) => { const productTypeRequired = getProductTypeByPLI(requiredPLI); return ( @@ -38,8 +38,7 @@ const OsqueryResponseActionsUpsellingSection: React.FC<{ requiredPLI: AppFeature } /> ); - } -); + }); OsqueryResponseActionsUpsellingSection.displayName = 'OsqueryResponseActionsUpsellingSection'; diff --git a/x-pack/plugins/security_solution_serverless/public/upselling/pages/threat_intelligence_paywall.tsx b/x-pack/plugins/security_solution_serverless/public/upselling/pages/threat_intelligence_paywall.tsx index 984b4ac74ca79..9fc0d63a77bd2 100644 --- a/x-pack/plugins/security_solution_serverless/public/upselling/pages/threat_intelligence_paywall.tsx +++ b/x-pack/plugins/security_solution_serverless/public/upselling/pages/threat_intelligence_paywall.tsx @@ -8,11 +8,11 @@ import React from 'react'; import { EuiEmptyPrompt, EuiIcon } from '@elastic/eui'; import { FormattedMessage } from '@kbn/i18n-react'; -import type { AppFeatureKey } from '@kbn/security-solution-plugin/common'; +import type { AppFeatureKeyType } from '@kbn/security-solution-features/keys'; import { KibanaPageTemplate } from '@kbn/shared-ux-page-kibana-template'; import { getProductTypeByPLI } from '../hooks/use_product_type_by_pli'; -const ThreatIntelligencePaywall: React.FC<{ requiredPLI: AppFeatureKey }> = React.memo( +const ThreatIntelligencePaywall: React.FC<{ requiredPLI: AppFeatureKeyType }> = React.memo( function PaywallComponent({ requiredPLI }) { const productTypeRequired = getProductTypeByPLI(requiredPLI); From 99cb5f54f85eb82d6a67fb038fe212706142ce10 Mon Sep 17 00:00:00 2001 From: Ashokaditya Date: Thu, 14 Sep 2023 12:38:11 +0200 Subject: [PATCH 10/42] fix type import related to elastic/kibana/pull/163406 --- x-pack/packages/security-solution/upselling/service/index.ts | 1 + 1 file changed, 1 insertion(+) diff --git a/x-pack/packages/security-solution/upselling/service/index.ts b/x-pack/packages/security-solution/upselling/service/index.ts index 6a71fd9dde2ca..6068999a4e9d3 100644 --- a/x-pack/packages/security-solution/upselling/service/index.ts +++ b/x-pack/packages/security-solution/upselling/service/index.ts @@ -10,4 +10,5 @@ export type { SectionUpsellings, UpsellingSectionId, UpsellingMessageId, + MessageUpsellings, } from './types'; From bc87d990e6b25e22aaf173da964972cd043f012d Mon Sep 17 00:00:00 2001 From: Ashokaditya Date: Thu, 14 Sep 2023 13:20:41 +0200 Subject: [PATCH 11/42] show upsell page for exception details refs elastic/security-team/issues/7223 --- .../public/exceptions/routes.tsx | 11 +++-- .../public/upselling/lazy_upselling.tsx | 4 ++ .../pages/endpoint_exceptions_details.tsx | 47 +++++++++++++++++++ .../public/upselling/register_upsellings.tsx | 16 +++++-- 4 files changed, 69 insertions(+), 9 deletions(-) create mode 100644 x-pack/plugins/security_solution_serverless/public/upselling/pages/endpoint_exceptions_details.tsx diff --git a/x-pack/plugins/security_solution/public/exceptions/routes.tsx b/x-pack/plugins/security_solution/public/exceptions/routes.tsx index 747a96cb72eea..e6faf794dcdbf 100644 --- a/x-pack/plugins/security_solution/public/exceptions/routes.tsx +++ b/x-pack/plugins/security_solution/public/exceptions/routes.tsx @@ -5,21 +5,22 @@ * 2.0. */ import React from 'react'; -import { Routes, Route } from '@kbn/shared-ux-router'; +import { Route, Routes } from '@kbn/shared-ux-router'; import { TrackApplicationView } from '@kbn/usage-collection-plugin/public'; import * as i18n from './translations'; import { + EXCEPTION_LIST_DETAIL_PATH, EXCEPTIONS_PATH, SecurityPageName, - EXCEPTION_LIST_DETAIL_PATH, } from '../../common/constants'; -import { SharedLists, ListsDetailView } from './pages'; +import { ListsDetailView, SharedLists } from './pages'; import { SpyRoute } from '../common/utils/route/spy_routes'; import { NotFoundPage } from '../app/404'; import { useReadonlyHeader } from '../use_readonly_header'; import { PluginTemplateWrapper } from '../common/components/plugin_template_wrapper'; +import { SecurityRoutePageWrapper } from '../common/components/security_route_page_wrapper'; const ExceptionsRoutes = () => ( @@ -32,9 +33,9 @@ const ExceptionsRoutes = () => ( const ExceptionsListDetailRoute = () => ( - + - + ); diff --git a/x-pack/plugins/security_solution_serverless/public/upselling/lazy_upselling.tsx b/x-pack/plugins/security_solution_serverless/public/upselling/lazy_upselling.tsx index e24434ea0b9e8..25282c6f3dffd 100644 --- a/x-pack/plugins/security_solution_serverless/public/upselling/lazy_upselling.tsx +++ b/x-pack/plugins/security_solution_serverless/public/upselling/lazy_upselling.tsx @@ -27,6 +27,10 @@ export const OsqueryResponseActionsUpsellingSectionLazy = withSuspenseUpsell( lazy(() => import('./pages/osquery_automated_response_actions')) ); +export const EndpointExceptionsDetailsUpsellingLazy = withSuspenseUpsell( + lazy(() => import('./pages/endpoint_exceptions_details')) +); + export const EntityAnalyticsUpsellingLazy = withSuspenseUpsell( lazy(() => import('@kbn/security-solution-upselling/pages/entity_analytics')) ); diff --git a/x-pack/plugins/security_solution_serverless/public/upselling/pages/endpoint_exceptions_details.tsx b/x-pack/plugins/security_solution_serverless/public/upselling/pages/endpoint_exceptions_details.tsx new file mode 100644 index 0000000000000..0e9fa391b09d9 --- /dev/null +++ b/x-pack/plugins/security_solution_serverless/public/upselling/pages/endpoint_exceptions_details.tsx @@ -0,0 +1,47 @@ +/* + * 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 { EuiEmptyPrompt, EuiIcon } from '@elastic/eui'; +import { FormattedMessage } from '@kbn/i18n-react'; +import React, { memo } from 'react'; +import type { AppFeatureKeyType } from '@kbn/security-solution-features/keys'; +import { getProductTypeByPLI } from '../hooks/use_product_type_by_pli'; + +const EndpointExceptionsDetailsUpselling: React.FC<{ requiredPLI: AppFeatureKeyType }> = memo( + ({ requiredPLI }) => { + const productTypeRequired = getProductTypeByPLI(requiredPLI); + + return ( + } + color="subdued" + title={ +

+ +

+ } + body={ +

+ +

+ } + /> + ); + } +); + +EndpointExceptionsDetailsUpselling.displayName = 'EndpointExceptionsDetailsUpselling'; + +// eslint-disable-next-line import/no-default-export +export { EndpointExceptionsDetailsUpselling as default }; diff --git a/x-pack/plugins/security_solution_serverless/public/upselling/register_upsellings.tsx b/x-pack/plugins/security_solution_serverless/public/upselling/register_upsellings.tsx index f1b8da6b1557d..5c42f5a21ea58 100644 --- a/x-pack/plugins/security_solution_serverless/public/upselling/register_upsellings.tsx +++ b/x-pack/plugins/security_solution_serverless/public/upselling/register_upsellings.tsx @@ -21,6 +21,7 @@ import { EndpointPolicyProtectionsLazy } from './sections/endpoint_management'; import type { SecurityProductTypes } from '../../common/config'; import { getProductAppFeatures } from '../../common/pli/pli_features'; import { + EndpointExceptionsDetailsUpsellingLazy, EntityAnalyticsUpsellingLazy, OsqueryResponseActionsUpsellingSectionLazy, ThreatIntelligencePaywallLazy, @@ -40,7 +41,7 @@ interface UpsellingsMessageConfig { id: UpsellingMessageId; } -type UpsellingPages = Array; +type UpsellingPages = Array; type UpsellingSections = Array; type UpsellingMessages = UpsellingsMessageConfig[]; @@ -86,7 +87,7 @@ export const registerUpsellings = ( upselling.setMessages(upsellingMessagesToRegister); }; -// Upsellings for entire pages, linked to a SecurityPageName +// Upselling for entire pages, linked to a SecurityPageName export const upsellingPages: UpsellingPages = [ // It is highly advisable to make use of lazy loaded components to minimize bundle size. { @@ -105,9 +106,16 @@ export const upsellingPages: UpsellingPages = [ ), }, + { + pageName: SecurityPageName.exceptions, + pli: AppFeatureKey.endpointExceptions, + component: () => ( + + ), + }, ]; -// Upsellings for sections, linked by arbitrary ids +// Upselling for sections, linked by arbitrary ids export const upsellingSections: UpsellingSections = [ // It is highly advisable to make use of lazy loaded components to minimize bundle size. { @@ -126,7 +134,7 @@ export const upsellingSections: UpsellingSections = [ }, ]; -// Upsellings for sections, linked by arbitrary ids +// Upselling for sections, linked by arbitrary ids export const upsellingMessages: UpsellingMessages = [ { id: 'investigation_guide', From c30e77b90a496e089cd50b97f8ee31380fd5a04b Mon Sep 17 00:00:00 2001 From: Ashokaditya Date: Thu, 14 Sep 2023 15:27:31 +0200 Subject: [PATCH 12/42] upselling page related capability refs elastic/security-team/issues/7223 --- x-pack/plugins/security_solution/public/rules/links.ts | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/x-pack/plugins/security_solution/public/rules/links.ts b/x-pack/plugins/security_solution/public/rules/links.ts index 23334456ac04f..e50a9aa670812 100644 --- a/x-pack/plugins/security_solution/public/rules/links.ts +++ b/x-pack/plugins/security_solution/public/rules/links.ts @@ -7,13 +7,13 @@ import { i18n } from '@kbn/i18n'; import { - RULES_PATH, - RULES_CREATE_PATH, + COVERAGE_OVERVIEW_PATH, EXCEPTIONS_PATH, - RULES_LANDING_PATH, RULES_ADD_PATH, + RULES_CREATE_PATH, + RULES_LANDING_PATH, + RULES_PATH, SERVER_APP_ID, - COVERAGE_OVERVIEW_PATH, } from '../../common/constants'; import { ADD_RULES, @@ -78,6 +78,7 @@ export const links: LinkItem = { }), landingIcon: IconConsoleCloud, path: EXCEPTIONS_PATH, + capabilities: [`${SERVER_APP_ID}.showEndpointExceptions`], skipUrlState: true, hideTimeline: true, globalSearchKeywords: [ From 0dcf24d9ce4e51f30224df6d1713e9608d43b194 Mon Sep 17 00:00:00 2001 From: Ashokaditya Date: Thu, 14 Sep 2023 21:01:14 +0200 Subject: [PATCH 13/42] Show upsell on endpoint exceptions tab Display an upsell message when endpoint exceptions tab is forced loaded on Rule > Endpoint Security > details via a link refs elastic/security-team/issues/7223 --- .../upselling/service/types.ts | 3 +- ...oint_exceptions_unavailablle_component.tsx | 13 ++++ .../pages/rule_details/index.tsx | 33 ++++++---- .../public/upselling/register_upsellings.tsx | 6 ++ .../endpoint_exceptions.tsx | 62 +++++++++++++++++++ .../sections/endpoint_exceptions/index.tsx | 14 +++++ 6 files changed, 119 insertions(+), 12 deletions(-) create mode 100644 x-pack/plugins/security_solution/public/detection_engine/rule_details_ui/pages/rule_details/hooks/use_get_endpoint_exceptions_unavailablle_component.tsx create mode 100644 x-pack/plugins/security_solution_serverless/public/upselling/sections/endpoint_exceptions/endpoint_exceptions.tsx create mode 100644 x-pack/plugins/security_solution_serverless/public/upselling/sections/endpoint_exceptions/index.tsx diff --git a/x-pack/packages/security-solution/upselling/service/types.ts b/x-pack/packages/security-solution/upselling/service/types.ts index 2f5a06c391471..8836d83f7625f 100644 --- a/x-pack/packages/security-solution/upselling/service/types.ts +++ b/x-pack/packages/security-solution/upselling/service/types.ts @@ -14,6 +14,7 @@ export type SectionUpsellings = Partial { + return useUpsellingComponent('endpointExceptions'); +}; diff --git a/x-pack/plugins/security_solution/public/detection_engine/rule_details_ui/pages/rule_details/index.tsx b/x-pack/plugins/security_solution/public/detection_engine/rule_details_ui/pages/rule_details/index.tsx index 674d29b128413..056d662020895 100644 --- a/x-pack/plugins/security_solution/public/detection_engine/rule_details_ui/pages/rule_details/index.tsx +++ b/x-pack/plugins/security_solution/public/detection_engine/rule_details_ui/pages/rule_details/index.tsx @@ -19,7 +19,7 @@ import { EuiWindowEvent, } from '@elastic/eui'; import type { Filter } from '@kbn/es-query'; -import { Routes, Route } from '@kbn/shared-ux-router'; +import { Route, Routes } from '@kbn/shared-ux-router'; import { noop } from 'lodash/fp'; import React, { useCallback, useEffect, useMemo, useRef, useState } from 'react'; @@ -32,12 +32,13 @@ import type { Dispatch } from 'redux'; import { isTab } from '@kbn/timelines-plugin/public'; import { - tableDefaults, dataTableActions, dataTableSelectors, FILTER_OPEN, + tableDefaults, TableId, } from '@kbn/securitysolution-data-table'; +import { useGetEndpointExceptionsUnavailableComponent } from './hooks/use_get_endpoint_exceptions_unavailablle_component'; import { AlertsTableComponent } from '../../../../detections/components/alerts_table'; import { GroupedAlertsTable } from '../../../../detections/components/alerts_table/alerts_grouping'; import { useDataTableFilters } from '../../../../common/hooks/use_data_table_filters'; @@ -100,10 +101,10 @@ import { import { useSourcererDataView } from '../../../../common/containers/sourcerer'; import { SourcererScopeName } from '../../../../common/store/sourcerer/model'; import { - explainLackOfPermission, canEditRuleWithActions, - isBoolean, + explainLackOfPermission, hasUserCRUDPermission, + isBoolean, } from '../../../../common/utils/privileges'; import { @@ -249,6 +250,8 @@ const RuleDetailsPageComponent: React.FC = ({ await startMlJobs(rule?.machine_learning_job_id); }, [rule, startMlJobs]); + const EndpointExceptionsUnavailableComponent = useGetEndpointExceptionsUnavailableComponent(); + const pageTabs = useRuleDetailsTabs({ rule, ruleId, isExistingRule, hasIndexRead }); const [isDeleteConfirmationVisible, showDeleteConfirmation, hideDeleteConfirmation] = @@ -777,13 +780,21 @@ const RuleDetailsPageComponent: React.FC = ({ - + {!EndpointExceptionsUnavailableComponent ? ( + + ) : ( + + + + + + )} diff --git a/x-pack/plugins/security_solution_serverless/public/upselling/register_upsellings.tsx b/x-pack/plugins/security_solution_serverless/public/upselling/register_upsellings.tsx index 5c42f5a21ea58..3a7bc0c722f24 100644 --- a/x-pack/plugins/security_solution_serverless/public/upselling/register_upsellings.tsx +++ b/x-pack/plugins/security_solution_serverless/public/upselling/register_upsellings.tsx @@ -18,6 +18,7 @@ import { UPGRADE_INVESTIGATION_GUIDE } from '@kbn/security-solution-upselling/me import { AppFeatureKey } from '@kbn/security-solution-features/keys'; import type { AppFeatureKeyType } from '@kbn/security-solution-features'; import { EndpointPolicyProtectionsLazy } from './sections/endpoint_management'; +import { RuleDetailsEndpointExceptionsLazy } from './sections/endpoint_exceptions'; import type { SecurityProductTypes } from '../../common/config'; import { getProductAppFeatures } from '../../common/pli/pli_features'; import { @@ -132,6 +133,11 @@ export const upsellingSections: UpsellingSections = [ pli: AppFeatureKey.endpointPolicyProtections, component: EndpointPolicyProtectionsLazy, }, + { + id: 'endpointExceptions', + pli: AppFeatureKey.endpointExceptions, + component: RuleDetailsEndpointExceptionsLazy, + }, ]; // Upselling for sections, linked by arbitrary ids diff --git a/x-pack/plugins/security_solution_serverless/public/upselling/sections/endpoint_exceptions/endpoint_exceptions.tsx b/x-pack/plugins/security_solution_serverless/public/upselling/sections/endpoint_exceptions/endpoint_exceptions.tsx new file mode 100644 index 0000000000000..797f214499c54 --- /dev/null +++ b/x-pack/plugins/security_solution_serverless/public/upselling/sections/endpoint_exceptions/endpoint_exceptions.tsx @@ -0,0 +1,62 @@ +/* + * 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 React, {memo} from 'react'; +import {EuiCard, EuiIcon} from '@elastic/eui'; +import {i18n} from '@kbn/i18n'; +import styled from '@emotion/styled'; + +const BADGE_TEXT = i18n.translate( + 'xpack.securitySolutionServerless.rules.endpointSecurity.endpointExceptions.badgeText', + { + defaultMessage: 'Endpoint Essentials', + } +); +const CARD_TITLE = i18n.translate( + 'xpack.securitySolutionServerless.rules.endpointSecurity.endpointExceptions.cardTitle', + { + defaultMessage: 'Do more with Security!', + } +); +const CARD_MESSAGE = i18n.translate( + 'xpack.securitySolutionServerless.rules.endpointSecurity.endpointExceptions.cardMessage', + { + defaultMessage: + 'Upgrade your license to {productTypeRequired} to use Endpoint Security Exception List.', + values: { productTypeRequired: BADGE_TEXT }, + } +); + +const CardDescription = styled.p` + padding: 0 33.3%; +`; + +/** + * Component displayed trying to access endpoint exceptions tab on Endpoint security rule details. + */ +export const RuleDetailsEndpointExceptions = memo(() => { + return ( + } + betaBadgeProps={{ + 'data-test-subj': 'rules-endpointSecurity-endpointExceptionsLockedCard-badge', + label: BADGE_TEXT, + }} + title={ +

+ {CARD_TITLE} +

+ } + > + {CARD_MESSAGE} +
+ ); +}); +RuleDetailsEndpointExceptions.displayName = 'RuleDetailsEndpointExceptions'; diff --git a/x-pack/plugins/security_solution_serverless/public/upselling/sections/endpoint_exceptions/index.tsx b/x-pack/plugins/security_solution_serverless/public/upselling/sections/endpoint_exceptions/index.tsx new file mode 100644 index 0000000000000..5346fed38f6a4 --- /dev/null +++ b/x-pack/plugins/security_solution_serverless/public/upselling/sections/endpoint_exceptions/index.tsx @@ -0,0 +1,14 @@ +/* + * 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 { lazy } from 'react'; + +export const RuleDetailsEndpointExceptionsLazy = lazy(() => + import('./endpoint_exceptions').then(({ RuleDetailsEndpointExceptions }) => ({ + default: RuleDetailsEndpointExceptions, + })) +); From 637a7e29bd26d29817ce7d9031a0e1fd91c22dd5 Mon Sep 17 00:00:00 2001 From: kibanamachine <42973632+kibanamachine@users.noreply.github.com> Date: Thu, 14 Sep 2023 19:06:59 +0000 Subject: [PATCH 14/42] [CI] Auto-commit changed files from 'node scripts/precommit_hook.js --ref HEAD~1..HEAD --fix' --- .../sections/endpoint_exceptions/endpoint_exceptions.tsx | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/x-pack/plugins/security_solution_serverless/public/upselling/sections/endpoint_exceptions/endpoint_exceptions.tsx b/x-pack/plugins/security_solution_serverless/public/upselling/sections/endpoint_exceptions/endpoint_exceptions.tsx index 797f214499c54..39bf086d7558b 100644 --- a/x-pack/plugins/security_solution_serverless/public/upselling/sections/endpoint_exceptions/endpoint_exceptions.tsx +++ b/x-pack/plugins/security_solution_serverless/public/upselling/sections/endpoint_exceptions/endpoint_exceptions.tsx @@ -5,9 +5,9 @@ * 2.0. */ -import React, {memo} from 'react'; -import {EuiCard, EuiIcon} from '@elastic/eui'; -import {i18n} from '@kbn/i18n'; +import React, { memo } from 'react'; +import { EuiCard, EuiIcon } from '@elastic/eui'; +import { i18n } from '@kbn/i18n'; import styled from '@emotion/styled'; const BADGE_TEXT = i18n.translate( From 52d0bc9b88c0016b64f2adf6e11846ebd8b0ab66 Mon Sep 17 00:00:00 2001 From: Ashokaditya Date: Mon, 18 Sep 2023 13:54:07 +0200 Subject: [PATCH 15/42] rename review suggestions @semd --- x-pack/packages/security-solution/upselling/service/types.ts | 2 +- .../use_get_endpoint_exceptions_unavailablle_component.tsx | 2 +- .../public/upselling/register_upsellings.tsx | 4 ++-- .../index.tsx | 2 +- .../rule_details_endpoint_exceptions.tsx} | 0 5 files changed, 5 insertions(+), 5 deletions(-) rename x-pack/plugins/security_solution_serverless/public/upselling/sections/{endpoint_exceptions => rule_details_endpoint_exceptions}/index.tsx (80%) rename x-pack/plugins/security_solution_serverless/public/upselling/sections/{endpoint_exceptions/endpoint_exceptions.tsx => rule_details_endpoint_exceptions/rule_details_endpoint_exceptions.tsx} (100%) diff --git a/x-pack/packages/security-solution/upselling/service/types.ts b/x-pack/packages/security-solution/upselling/service/types.ts index 8836d83f7625f..d14f39ac9796a 100644 --- a/x-pack/packages/security-solution/upselling/service/types.ts +++ b/x-pack/packages/security-solution/upselling/service/types.ts @@ -15,6 +15,6 @@ export type UpsellingSectionId = | 'entity_analytics_panel' | 'endpointPolicyProtections' | 'osquery_automated_response_actions' - | 'endpointExceptions'; + | 'ruleDetailsEndpointExceptions'; export type UpsellingMessageId = 'investigation_guide'; diff --git a/x-pack/plugins/security_solution/public/detection_engine/rule_details_ui/pages/rule_details/hooks/use_get_endpoint_exceptions_unavailablle_component.tsx b/x-pack/plugins/security_solution/public/detection_engine/rule_details_ui/pages/rule_details/hooks/use_get_endpoint_exceptions_unavailablle_component.tsx index bb1f4b98c76b7..e0ac6a69a5ed6 100644 --- a/x-pack/plugins/security_solution/public/detection_engine/rule_details_ui/pages/rule_details/hooks/use_get_endpoint_exceptions_unavailablle_component.tsx +++ b/x-pack/plugins/security_solution/public/detection_engine/rule_details_ui/pages/rule_details/hooks/use_get_endpoint_exceptions_unavailablle_component.tsx @@ -9,5 +9,5 @@ import type React from 'react'; import { useUpsellingComponent } from '../../../../../common/hooks/use_upselling'; export const useGetEndpointExceptionsUnavailableComponent = (): React.ComponentType | null => { - return useUpsellingComponent('endpointExceptions'); + return useUpsellingComponent('ruleDetailsEndpointExceptions'); }; diff --git a/x-pack/plugins/security_solution_serverless/public/upselling/register_upsellings.tsx b/x-pack/plugins/security_solution_serverless/public/upselling/register_upsellings.tsx index 3a7bc0c722f24..7cdb6a5a88782 100644 --- a/x-pack/plugins/security_solution_serverless/public/upselling/register_upsellings.tsx +++ b/x-pack/plugins/security_solution_serverless/public/upselling/register_upsellings.tsx @@ -18,7 +18,7 @@ import { UPGRADE_INVESTIGATION_GUIDE } from '@kbn/security-solution-upselling/me import { AppFeatureKey } from '@kbn/security-solution-features/keys'; import type { AppFeatureKeyType } from '@kbn/security-solution-features'; import { EndpointPolicyProtectionsLazy } from './sections/endpoint_management'; -import { RuleDetailsEndpointExceptionsLazy } from './sections/endpoint_exceptions'; +import { RuleDetailsEndpointExceptionsLazy } from './sections/rule_details_endpoint_exceptions'; import type { SecurityProductTypes } from '../../common/config'; import { getProductAppFeatures } from '../../common/pli/pli_features'; import { @@ -134,7 +134,7 @@ export const upsellingSections: UpsellingSections = [ component: EndpointPolicyProtectionsLazy, }, { - id: 'endpointExceptions', + id: 'ruleDetailsEndpointExceptions', pli: AppFeatureKey.endpointExceptions, component: RuleDetailsEndpointExceptionsLazy, }, diff --git a/x-pack/plugins/security_solution_serverless/public/upselling/sections/endpoint_exceptions/index.tsx b/x-pack/plugins/security_solution_serverless/public/upselling/sections/rule_details_endpoint_exceptions/index.tsx similarity index 80% rename from x-pack/plugins/security_solution_serverless/public/upselling/sections/endpoint_exceptions/index.tsx rename to x-pack/plugins/security_solution_serverless/public/upselling/sections/rule_details_endpoint_exceptions/index.tsx index 5346fed38f6a4..f684d942995f4 100644 --- a/x-pack/plugins/security_solution_serverless/public/upselling/sections/endpoint_exceptions/index.tsx +++ b/x-pack/plugins/security_solution_serverless/public/upselling/sections/rule_details_endpoint_exceptions/index.tsx @@ -8,7 +8,7 @@ import { lazy } from 'react'; export const RuleDetailsEndpointExceptionsLazy = lazy(() => - import('./endpoint_exceptions').then(({ RuleDetailsEndpointExceptions }) => ({ + import('./rule_details_endpoint_exceptions').then(({ RuleDetailsEndpointExceptions }) => ({ default: RuleDetailsEndpointExceptions, })) ); diff --git a/x-pack/plugins/security_solution_serverless/public/upselling/sections/endpoint_exceptions/endpoint_exceptions.tsx b/x-pack/plugins/security_solution_serverless/public/upselling/sections/rule_details_endpoint_exceptions/rule_details_endpoint_exceptions.tsx similarity index 100% rename from x-pack/plugins/security_solution_serverless/public/upselling/sections/endpoint_exceptions/endpoint_exceptions.tsx rename to x-pack/plugins/security_solution_serverless/public/upselling/sections/rule_details_endpoint_exceptions/rule_details_endpoint_exceptions.tsx From 02eb0a28ea613c5d308852eb926176f2b401b91a Mon Sep 17 00:00:00 2001 From: Ashokaditya Date: Mon, 18 Sep 2023 14:08:34 +0200 Subject: [PATCH 16/42] only check capabilities and not app feature review changes @paul-tavares @semd --- .../endpoint/validators/base_validator.ts | 10 +--------- 1 file changed, 1 insertion(+), 9 deletions(-) diff --git a/x-pack/plugins/security_solution/server/lists_integration/endpoint/validators/base_validator.ts b/x-pack/plugins/security_solution/server/lists_integration/endpoint/validators/base_validator.ts index a918b360966ef..9cf120d80b9e9 100644 --- a/x-pack/plugins/security_solution/server/lists_integration/endpoint/validators/base_validator.ts +++ b/x-pack/plugins/security_solution/server/lists_integration/endpoint/validators/base_validator.ts @@ -11,7 +11,6 @@ import { isEqual } from 'lodash/fp'; import type { ExceptionListItemSchema } from '@kbn/securitysolution-io-ts-list-types'; import { OperatingSystem } from '@kbn/securitysolution-utils'; -import { AppFeatureSecurityKey } from '@kbn/security-solution-features/src/app_features_keys'; import type { EndpointAuthz } from '../../../../common/endpoint/types/authz'; import type { EndpointAppContextService } from '../../../endpoint/endpoint_app_context_services'; import type { ExceptionItemLikeOptions } from '../types'; @@ -80,15 +79,8 @@ export class BaseValidator { protected async validateHasEndpointExceptionsPrivileges( privilege: keyof EndpointAuthz ): Promise { - const appFeaturesService = this.endpointAppContext.getAppFeaturesService(); - const isEndpointExceptionsFeaturesEnabled = (await appFeaturesService).isEnabled( - AppFeatureSecurityKey.endpointExceptions - ); - - // for serverless only when Endpoint Exceptions is not enabled - // verify that the user has the privilege to create/read/update/delete endpoint exceptions - if (!isEndpointExceptionsFeaturesEnabled && !(await this.endpointAuthzPromise)[privilege]) { + if (!(await this.endpointAuthzPromise)[privilege]) { throw new EndpointExceptionsValidationError('Endpoint exceptions authorization failure', 403); } } From 4b5185e4a5091fc6be76659678ac649f6b281724 Mon Sep 17 00:00:00 2001 From: kibanamachine <42973632+kibanamachine@users.noreply.github.com> Date: Mon, 18 Sep 2023 12:47:12 +0000 Subject: [PATCH 17/42] [CI] Auto-commit changed files from 'node scripts/eslint --no-cache --fix' --- .../lists_integration/endpoint/validators/base_validator.ts | 1 - 1 file changed, 1 deletion(-) diff --git a/x-pack/plugins/security_solution/server/lists_integration/endpoint/validators/base_validator.ts b/x-pack/plugins/security_solution/server/lists_integration/endpoint/validators/base_validator.ts index 9cf120d80b9e9..4630ad9edec07 100644 --- a/x-pack/plugins/security_solution/server/lists_integration/endpoint/validators/base_validator.ts +++ b/x-pack/plugins/security_solution/server/lists_integration/endpoint/validators/base_validator.ts @@ -79,7 +79,6 @@ export class BaseValidator { protected async validateHasEndpointExceptionsPrivileges( privilege: keyof EndpointAuthz ): Promise { - if (!(await this.endpointAuthzPromise)[privilege]) { throw new EndpointExceptionsValidationError('Endpoint exceptions authorization failure', 403); } From dc4bb74cab9ad1223f67b4111961c44e3efe6afa Mon Sep 17 00:00:00 2001 From: Ashokaditya Date: Mon, 18 Sep 2023 15:29:24 +0200 Subject: [PATCH 18/42] rename review changes @semd --- .../pages/rule_details/use_rule_details_tabs.tsx | 6 +++--- .../timeline_actions/use_add_exception_actions.tsx | 6 +++--- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/x-pack/plugins/security_solution/public/detection_engine/rule_details_ui/pages/rule_details/use_rule_details_tabs.tsx b/x-pack/plugins/security_solution/public/detection_engine/rule_details_ui/pages/rule_details/use_rule_details_tabs.tsx index d16cc5554ab89..d958575460ea5 100644 --- a/x-pack/plugins/security_solution/public/detection_engine/rule_details_ui/pages/rule_details/use_rule_details_tabs.tsx +++ b/x-pack/plugins/security_solution/public/detection_engine/rule_details_ui/pages/rule_details/use_rule_details_tabs.tsx @@ -86,11 +86,11 @@ export const useRuleDetailsTabs = ({ const { loading: listsConfigLoading, needsConfiguration: needsListsConfiguration } = useListsConfig(); - const canReadEndpointExceptions = useHasSecurityCapability('crudEndpointExceptions'); + const canCrudEndpointExceptions = useHasSecurityCapability('crudEndpointExceptions'); const canWriteEndpointExceptions = useMemo( - () => !listsConfigLoading && !needsListsConfiguration && canReadEndpointExceptions, - [canReadEndpointExceptions, listsConfigLoading, needsListsConfiguration] + () => !listsConfigLoading && !needsListsConfiguration && canCrudEndpointExceptions, + [canCrudEndpointExceptions, listsConfigLoading, needsListsConfiguration] ); useEffect(() => { diff --git a/x-pack/plugins/security_solution/public/detections/components/alerts_table/timeline_actions/use_add_exception_actions.tsx b/x-pack/plugins/security_solution/public/detections/components/alerts_table/timeline_actions/use_add_exception_actions.tsx index 012b0dc65b82f..5dae6df687ec7 100644 --- a/x-pack/plugins/security_solution/public/detections/components/alerts_table/timeline_actions/use_add_exception_actions.tsx +++ b/x-pack/plugins/security_solution/public/detections/components/alerts_table/timeline_actions/use_add_exception_actions.tsx @@ -78,11 +78,11 @@ export const useAlertExceptionActions = ({ const { loading: listsConfigLoading, needsConfiguration: needsListsConfiguration } = useListsConfig(); - const canReadEndpointExceptions = useHasSecurityCapability('crudEndpointExceptions'); + const canCrudEndpointExceptions = useHasSecurityCapability('crudEndpointExceptions'); const canWriteEndpointExceptions = useMemo( - () => !listsConfigLoading && !needsListsConfiguration && canReadEndpointExceptions, - [canReadEndpointExceptions, listsConfigLoading, needsListsConfiguration] + () => !listsConfigLoading && !needsListsConfiguration && canCrudEndpointExceptions, + [canCrudEndpointExceptions, listsConfigLoading, needsListsConfiguration] ); // Endpoint exceptions are available for: // Serverless Endpoint Essentials/Complete PLI and From 795cc0d8eb5e5b671f263c79f7f54c47f0142993 Mon Sep 17 00:00:00 2001 From: Ashokaditya Date: Mon, 18 Sep 2023 15:36:27 +0200 Subject: [PATCH 19/42] remove redundant method for now --- .../server/endpoint/endpoint_app_context_services.ts | 8 -------- 1 file changed, 8 deletions(-) diff --git a/x-pack/plugins/security_solution/server/endpoint/endpoint_app_context_services.ts b/x-pack/plugins/security_solution/server/endpoint/endpoint_app_context_services.ts index 2915ad4a6a21c..3fb28c1c5099d 100644 --- a/x-pack/plugins/security_solution/server/endpoint/endpoint_app_context_services.ts +++ b/x-pack/plugins/security_solution/server/endpoint/endpoint_app_context_services.ts @@ -276,12 +276,4 @@ export class EndpointAppContextService { return this.startDependencies.createFleetActionsClient('endpoint'); } - - public async getAppFeaturesService(): Promise { - if (!this.startDependencies?.appFeaturesService) { - throw new EndpointAppContentServicesNotStartedError(); - } - - return this.startDependencies.appFeaturesService; - } } From 43a18c5a3b98b2d97cdedb274ab273b0af34998f Mon Sep 17 00:00:00 2001 From: Ashokaditya Date: Tue, 19 Sep 2023 18:30:23 +0200 Subject: [PATCH 20/42] fix merge duplicate imports --- x-pack/plugins/fleet/public/plugin.ts | 2 -- 1 file changed, 2 deletions(-) diff --git a/x-pack/plugins/fleet/public/plugin.ts b/x-pack/plugins/fleet/public/plugin.ts index 770a15f9690d3..7c1aadeb530eb 100644 --- a/x-pack/plugins/fleet/public/plugin.ts +++ b/x-pack/plugins/fleet/public/plugin.ts @@ -83,8 +83,6 @@ import type { } from './types'; import { LazyCustomLogsAssetsExtension } from './lazy_custom_logs_assets_extension'; import { setCustomIntegrations, setCustomIntegrationsStart } from './services/custom_integrations'; -import type { RequestError } from './hooks'; -import { sendGetBulkAssets } from './hooks'; import { getFleetDeepLinks } from './deep_links'; export type { FleetConfigType } from '../common/types'; From 72fdb540e7c11ed27922b14552ae06707ce12e11 Mon Sep 17 00:00:00 2001 From: Ashokaditya Date: Wed, 20 Sep 2023 08:57:21 +0200 Subject: [PATCH 21/42] fix privilege calculations for endpoint expections refs 02eb0a28ea613c5d308852eb926176f2b401b91a --- x-pack/plugins/fleet/common/authz.test.ts | 21 ++++ x-pack/plugins/fleet/common/authz.ts | 96 +++++++++++++------ .../plugins/fleet/common/constants/authz.ts | 15 +++ .../server/services/security/security.ts | 61 +++++++----- 4 files changed, 140 insertions(+), 53 deletions(-) diff --git a/x-pack/plugins/fleet/common/authz.test.ts b/x-pack/plugins/fleet/common/authz.test.ts index 9a2e224289b26..402a60106d61d 100644 --- a/x-pack/plugins/fleet/common/authz.test.ts +++ b/x-pack/plugins/fleet/common/authz.test.ts @@ -11,6 +11,7 @@ import { TRANSFORM_PLUGIN_ID } from './constants/plugin'; import { calculateEndpointExceptionsPrivilegesFromCapabilities, + calculateEndpointExceptionsPrivilegesFromKibanaPrivileges, calculatePackagePrivilegesFromCapabilities, calculatePackagePrivilegesFromKibanaPrivileges, } from './authz'; @@ -137,4 +138,24 @@ describe('fleet authz', () => { expect(actual).toEqual(expected); }); }); + + describe('#calculateEndpointExceptionsPrivilegesFromKibanaPrivileges', () => { + it('calculates endpoint exceptions privileges correctly', () => { + const endpointExceptionsPrivileges = [ + { privilege: `${SECURITY_SOLUTION_ID}-showEndpointExceptions`, authorized: true }, + { privilege: `${SECURITY_SOLUTION_ID}-crudEndpointExceptions`, authorized: false }, + { privilege: `${SECURITY_SOLUTION_ID}-ignoreMe`, authorized: true }, + ]; + const expected = { + actions: { + showEndpointExceptions: true, + crudEndpointExceptions: false, + }, + }; + const actual = calculateEndpointExceptionsPrivilegesFromKibanaPrivileges( + endpointExceptionsPrivileges + ); + expect(actual).toEqual(expected); + }); + }); }); diff --git a/x-pack/plugins/fleet/common/authz.ts b/x-pack/plugins/fleet/common/authz.ts index caf4032efde73..2f88c709b475d 100644 --- a/x-pack/plugins/fleet/common/authz.ts +++ b/x-pack/plugins/fleet/common/authz.ts @@ -9,7 +9,7 @@ import type { Capabilities } from '@kbn/core-capabilities-common'; import { TRANSFORM_PLUGIN_ID } from './constants/plugin'; -import { ENDPOINT_PRIVILEGES } from './constants'; +import { ENDPOINT_EXCEPTIONS_PRIVILEGES, ENDPOINT_PRIVILEGES } from './constants'; export type TransformPrivilege = | 'canGetTransform' @@ -151,7 +151,9 @@ export function calculateEndpointExceptionsPrivilegesFromCapabilities( const endpointExceptionsActions = Object.keys(capabilities.siem).reduce>( (acc, privilegeName) => { - acc[privilegeName] = (capabilities.siem[privilegeName] as boolean) || false; + if (['crudEndpointExceptions', 'showEndpointExceptions'].includes(privilegeName)) { + acc[privilegeName] = (capabilities.siem[privilegeName] as boolean) || false; + } return acc; }, {} @@ -162,19 +164,36 @@ export function calculateEndpointExceptionsPrivilegesFromCapabilities( } as FleetAuthz['endpointExceptionsPrivileges']; } -function getAuthorizationFromPrivileges( - kibanaPrivileges: Array<{ - resource?: string; - privilege: string; - authorized: boolean; - }>, - prefix: string, - searchPrivilege: string -): boolean { - const privilege = kibanaPrivileges.find((p) => - p.privilege.endsWith(`${prefix}${searchPrivilege}`) - ); - return privilege?.authorized || false; +export function getAuthorizationFromPrivileges({ + kibanaPrivileges, + searchPrivilege = '', + prefix = '', +}: { + kibanaPrivileges: + | Array<{ + resource?: string; + privilege: string; + authorized: boolean; + }> + | undefined; + prefix?: string; + searchPrivilege?: string; +}) { + if (!kibanaPrivileges) { + return false; + } + const privilege = kibanaPrivileges.find((p) => { + if (prefix.length && searchPrivilege.length) { + return p.privilege.endsWith(`${prefix}${searchPrivilege}`); + } else if (prefix.length) { + return p.privilege.endsWith(`${prefix}`); + } else if (searchPrivilege.length) { + return p.privilege.endsWith(`${searchPrivilege}`); + } + return false; + }); + + return !!privilege?.authorized; } export function calculatePackagePrivilegesFromKibanaPrivileges( @@ -192,11 +211,11 @@ export function calculatePackagePrivilegesFromKibanaPrivileges( const endpointActions = Object.entries(ENDPOINT_PRIVILEGES).reduce( (acc, [privilege, { appId, privilegeSplit, privilegeName }]) => { - const kibanaPrivilege = getAuthorizationFromPrivileges( + const kibanaPrivilege = getAuthorizationFromPrivileges({ kibanaPrivileges, - `${appId}${privilegeSplit}`, - privilegeName - ); + prefix: `${appId}${privilegeSplit}`, + searchPrivilege: privilegeName, + }); acc[privilege] = { executePackageAction: kibanaPrivilege, }; @@ -205,11 +224,11 @@ export function calculatePackagePrivilegesFromKibanaPrivileges( {} ); - const hasTransformAdmin = getAuthorizationFromPrivileges( + const hasTransformAdmin = getAuthorizationFromPrivileges({ kibanaPrivileges, - `${TRANSFORM_PLUGIN_ID}-`, - `admin` - ); + prefix: `${TRANSFORM_PLUGIN_ID}-`, + searchPrivilege: `admin`, + }); const transformActions: { [key in TransformPrivilege]: { executePackageAction: boolean; @@ -225,11 +244,11 @@ export function calculatePackagePrivilegesFromKibanaPrivileges( executePackageAction: hasTransformAdmin, }, canGetTransform: { - executePackageAction: getAuthorizationFromPrivileges( + executePackageAction: getAuthorizationFromPrivileges({ kibanaPrivileges, - `${TRANSFORM_PLUGIN_ID}-`, - `read` - ), + prefix: `${TRANSFORM_PLUGIN_ID}-`, + searchPrivilege: `read`, + }), }, }; @@ -242,3 +261,26 @@ export function calculatePackagePrivilegesFromKibanaPrivileges( }, }; } + +export function calculateEndpointExceptionsPrivilegesFromKibanaPrivileges( + kibanaPrivileges: + | Array<{ + resource?: string; + privilege: string; + authorized: boolean; + }> + | undefined +): FleetAuthz['endpointExceptionsPrivileges'] { + const endpointExceptionsActions = Object.entries(ENDPOINT_EXCEPTIONS_PRIVILEGES).reduce<{ + crudEndpointExceptions: boolean; + showEndpointExceptions: boolean; + }>((acc, [privilege, { appId, privilegeSplit, privilegeName }]) => { + acc[privilege] = getAuthorizationFromPrivileges({ + kibanaPrivileges, + searchPrivilege: privilegeName, + }); + return acc; + }, {}); + + return { actions: endpointExceptionsActions }; +} diff --git a/x-pack/plugins/fleet/common/constants/authz.ts b/x-pack/plugins/fleet/common/constants/authz.ts index 77f5c0b798b2f..3a8716726447b 100644 --- a/x-pack/plugins/fleet/common/constants/authz.ts +++ b/x-pack/plugins/fleet/common/constants/authz.ts @@ -163,3 +163,18 @@ export const ENDPOINT_PRIVILEGES: Record = deepFreez privilegeName: 'writeExecuteOperations', }, }); + +export const ENDPOINT_EXCEPTIONS_PRIVILEGES = deepFreeze({ + showEndpointExceptions: { + appId: DEFAULT_APP_CATEGORIES.security.id, + privilegeSplit: '-', + privilegeType: 'api', + privilegeName: 'showEndpointExceptions', + }, + crudEndpointExceptions: { + appId: DEFAULT_APP_CATEGORIES.security.id, + privilegeSplit: '-', + privilegeType: 'api', + privilegeName: 'crudEndpointExceptions', + }, +}); diff --git a/x-pack/plugins/fleet/server/services/security/security.ts b/x-pack/plugins/fleet/server/services/security/security.ts index 715d8d966484f..1a612771032d8 100644 --- a/x-pack/plugins/fleet/server/services/security/security.ts +++ b/x-pack/plugins/fleet/server/services/security/security.ts @@ -9,13 +9,20 @@ import { pick } from 'lodash'; import type { KibanaRequest } from '@kbn/core/server'; +import { + AppFeaturesPrivilegeId, + AppFeaturesPrivileges, +} from '@kbn/security-solution-features/src/app_features_privileges'; + import { TRANSFORM_PLUGIN_ID } from '../../../common/constants/plugin'; import type { FleetAuthz } from '../../../common'; import { INTEGRATIONS_PLUGIN_ID } from '../../../common'; import { calculateAuthz, + calculateEndpointExceptionsPrivilegesFromKibanaPrivileges, calculatePackagePrivilegesFromKibanaPrivileges, + getAuthorizationFromPrivileges, } from '../../../common/authz'; import { appContextService } from '..'; @@ -23,8 +30,8 @@ import { ENDPOINT_PRIVILEGES, PLUGIN_ID } from '../../constants'; import type { FleetAuthzRequirements, - FleetRouteRequiredAuthz, FleetAuthzRouteConfig, + FleetRouteRequiredAuthz, } from './types'; export function checkSecurityEnabled() { @@ -51,18 +58,6 @@ export function checkSuperuser(req: KibanaRequest) { return true; } -function getAuthorizationFromPrivileges( - kibanaPrivileges: Array<{ - resource?: string; - privilege: string; - authorized: boolean; - }>, - searchPrivilege: string -) { - const privilege = kibanaPrivileges.find((p) => p.privilege.includes(searchPrivilege)); - return privilege ? privilege.authorized : false; -} - export async function getAuthzFromRequest(req: KibanaRequest): Promise { const security = appContextService.getSecurity(); @@ -76,6 +71,12 @@ export async function getAuthzFromRequest(req: KibanaRequest): Promise { + return security.authz.actions.api.get(privilegeName); + }); + const { privileges } = await checkPrivileges({ kibana: [ security.authz.actions.api.get(`${PLUGIN_ID}-all`), @@ -87,20 +88,27 @@ export async function getAuthzFromRequest(req: KibanaRequest): Promise Date: Wed, 20 Sep 2023 07:03:56 +0000 Subject: [PATCH 22/42] [CI] Auto-commit changed files from 'node scripts/lint_ts_projects --fix' --- x-pack/plugins/fleet/tsconfig.json | 1 + 1 file changed, 1 insertion(+) diff --git a/x-pack/plugins/fleet/tsconfig.json b/x-pack/plugins/fleet/tsconfig.json index b3f8a96417f9a..1229f8775107c 100644 --- a/x-pack/plugins/fleet/tsconfig.json +++ b/x-pack/plugins/fleet/tsconfig.json @@ -101,5 +101,6 @@ "@kbn/core-saved-objects-base-server-internal", "@kbn/core-http-common", "@kbn/dashboard-plugin", + "@kbn/security-solution-features", ] } From 41b6ac288bc7af42e494fa8185104e59297bec1c Mon Sep 17 00:00:00 2001 From: Ashokaditya Date: Wed, 20 Sep 2023 09:22:01 +0200 Subject: [PATCH 23/42] fix types refs 72fdb540e7c11ed27922b14552ae06707ce12e11 --- x-pack/plugins/fleet/common/authz.ts | 13 +++--- .../plugins/fleet/common/constants/authz.ts | 4 +- .../plugins/fleet/server/constants/index.ts | 2 + .../server/services/security/security.ts | 40 +++++++++++-------- x-pack/plugins/fleet/tsconfig.json | 3 +- 5 files changed, 34 insertions(+), 28 deletions(-) diff --git a/x-pack/plugins/fleet/common/authz.ts b/x-pack/plugins/fleet/common/authz.ts index 2f88c709b475d..a214b87a3b980 100644 --- a/x-pack/plugins/fleet/common/authz.ts +++ b/x-pack/plugins/fleet/common/authz.ts @@ -151,7 +151,7 @@ export function calculateEndpointExceptionsPrivilegesFromCapabilities( const endpointExceptionsActions = Object.keys(capabilities.siem).reduce>( (acc, privilegeName) => { - if (['crudEndpointExceptions', 'showEndpointExceptions'].includes(privilegeName)) { + if (Object.keys(ENDPOINT_EXCEPTIONS_PRIVILEGES).includes(privilegeName)) { acc[privilegeName] = (capabilities.siem[privilegeName] as boolean) || false; } return acc; @@ -178,7 +178,7 @@ export function getAuthorizationFromPrivileges({ | undefined; prefix?: string; searchPrivilege?: string; -}) { +}): boolean { if (!kibanaPrivileges) { return false; } @@ -271,10 +271,9 @@ export function calculateEndpointExceptionsPrivilegesFromKibanaPrivileges( }> | undefined ): FleetAuthz['endpointExceptionsPrivileges'] { - const endpointExceptionsActions = Object.entries(ENDPOINT_EXCEPTIONS_PRIVILEGES).reduce<{ - crudEndpointExceptions: boolean; - showEndpointExceptions: boolean; - }>((acc, [privilege, { appId, privilegeSplit, privilegeName }]) => { + const endpointExceptionsActions = Object.entries(ENDPOINT_EXCEPTIONS_PRIVILEGES).reduce< + Record + >((acc, [privilege, { appId, privilegeSplit, privilegeName }]) => { acc[privilege] = getAuthorizationFromPrivileges({ kibanaPrivileges, searchPrivilege: privilegeName, @@ -282,5 +281,5 @@ export function calculateEndpointExceptionsPrivilegesFromKibanaPrivileges( return acc; }, {}); - return { actions: endpointExceptionsActions }; + return { actions: endpointExceptionsActions } as FleetAuthz['endpointExceptionsPrivileges']; } diff --git a/x-pack/plugins/fleet/common/constants/authz.ts b/x-pack/plugins/fleet/common/constants/authz.ts index 3a8716726447b..72c975af1a3d1 100644 --- a/x-pack/plugins/fleet/common/constants/authz.ts +++ b/x-pack/plugins/fleet/common/constants/authz.ts @@ -10,7 +10,7 @@ import { DEFAULT_APP_CATEGORIES } from '@kbn/core-application-common'; const SECURITY_SOLUTION_APP_ID = 'siem'; -interface PrivilegeMapObject { +export interface PrivilegeMapObject { appId: string; privilegeSplit: string; privilegeType: 'ui' | 'api'; @@ -164,7 +164,7 @@ export const ENDPOINT_PRIVILEGES: Record = deepFreez }, }); -export const ENDPOINT_EXCEPTIONS_PRIVILEGES = deepFreeze({ +export const ENDPOINT_EXCEPTIONS_PRIVILEGES: Record = deepFreeze({ showEndpointExceptions: { appId: DEFAULT_APP_CATEGORIES.security.id, privilegeSplit: '-', diff --git a/x-pack/plugins/fleet/server/constants/index.ts b/x-pack/plugins/fleet/server/constants/index.ts index 74af2fe533a9b..37e570648e392 100644 --- a/x-pack/plugins/fleet/server/constants/index.ts +++ b/x-pack/plugins/fleet/server/constants/index.ts @@ -75,11 +75,13 @@ export { FLEET_PROXY_SAVED_OBJECT_TYPE, // Authz ENDPOINT_PRIVILEGES, + ENDPOINT_EXCEPTIONS_PRIVILEGES, // Message signing service MESSAGE_SIGNING_SERVICE_API_ROUTES, // secrets SECRETS_ENDPOINT_PATH, SECRETS_MINIMUM_FLEET_SERVER_VERSION, + type PrivilegeMapObject, } from '../../common/constants'; export { diff --git a/x-pack/plugins/fleet/server/services/security/security.ts b/x-pack/plugins/fleet/server/services/security/security.ts index 1a612771032d8..efd809868e166 100644 --- a/x-pack/plugins/fleet/server/services/security/security.ts +++ b/x-pack/plugins/fleet/server/services/security/security.ts @@ -9,10 +9,7 @@ import { pick } from 'lodash'; import type { KibanaRequest } from '@kbn/core/server'; -import { - AppFeaturesPrivilegeId, - AppFeaturesPrivileges, -} from '@kbn/security-solution-features/src/app_features_privileges'; +import type { SecurityPluginStart } from '@kbn/security-plugin/server'; import { TRANSFORM_PLUGIN_ID } from '../../../common/constants/plugin'; @@ -26,7 +23,12 @@ import { } from '../../../common/authz'; import { appContextService } from '..'; -import { ENDPOINT_PRIVILEGES, PLUGIN_ID } from '../../constants'; +import { + ENDPOINT_EXCEPTIONS_PRIVILEGES, + ENDPOINT_PRIVILEGES, + PLUGIN_ID, + type PrivilegeMapObject, +} from '../../constants'; import type { FleetAuthzRequirements, @@ -58,24 +60,28 @@ export function checkSuperuser(req: KibanaRequest) { return true; } +const computeUiApiPrivileges = ( + security: SecurityPluginStart, + privileges: Record +): string[] => { + return Object.entries(privileges).map(([_, { appId, privilegeType, privilegeName }]) => { + if (privilegeType === 'ui') { + return security.authz.actions[privilegeType].get(`${appId}`, `${privilegeName}`); + } + return security.authz.actions[privilegeType].get(`${appId}-${privilegeName}`); + }); +}; + export async function getAuthzFromRequest(req: KibanaRequest): Promise { const security = appContextService.getSecurity(); if (security.authz.mode.useRbacForRequest(req)) { const checkPrivileges = security.authz.checkPrivilegesDynamicallyWithRequest(req); - const endpointPrivileges = Object.entries(ENDPOINT_PRIVILEGES).map( - ([_, { appId, privilegeType, privilegeName }]) => { - if (privilegeType === 'ui') { - return security.authz.actions[privilegeType].get(`${appId}`, `${privilegeName}`); - } - return security.authz.actions[privilegeType].get(`${appId}-${privilegeName}`); - } + const endpointPrivileges = computeUiApiPrivileges(security, ENDPOINT_PRIVILEGES); + const endpointExceptionsPrivileges = computeUiApiPrivileges( + security, + ENDPOINT_EXCEPTIONS_PRIVILEGES ); - const endpointExceptionsPrivileges = AppFeaturesPrivileges[ - AppFeaturesPrivilegeId.endpointExceptions - ].all.api.map((privilegeName) => { - return security.authz.actions.api.get(privilegeName); - }); const { privileges } = await checkPrivileges({ kibana: [ diff --git a/x-pack/plugins/fleet/tsconfig.json b/x-pack/plugins/fleet/tsconfig.json index 1229f8775107c..545d51aa8a80f 100644 --- a/x-pack/plugins/fleet/tsconfig.json +++ b/x-pack/plugins/fleet/tsconfig.json @@ -100,7 +100,6 @@ "@kbn/core-application-browser", "@kbn/core-saved-objects-base-server-internal", "@kbn/core-http-common", - "@kbn/dashboard-plugin", - "@kbn/security-solution-features", + "@kbn/dashboard-plugin" ] } From b2d94f9191a282b63080f8cd1587e084da893b02 Mon Sep 17 00:00:00 2001 From: Ashokaditya Date: Wed, 20 Sep 2023 10:41:14 +0200 Subject: [PATCH 24/42] unit tests for `getAuthorizationFromPrivileges` refs 72fdb540e7c11ed27922b14552ae06707ce12e11 --- x-pack/plugins/fleet/common/authz.test.ts | 63 +++++++++++++++++++++++ x-pack/plugins/fleet/common/authz.ts | 15 ++---- 2 files changed, 68 insertions(+), 10 deletions(-) diff --git a/x-pack/plugins/fleet/common/authz.test.ts b/x-pack/plugins/fleet/common/authz.test.ts index 402a60106d61d..d98857171ef5d 100644 --- a/x-pack/plugins/fleet/common/authz.test.ts +++ b/x-pack/plugins/fleet/common/authz.test.ts @@ -14,6 +14,7 @@ import { calculateEndpointExceptionsPrivilegesFromKibanaPrivileges, calculatePackagePrivilegesFromCapabilities, calculatePackagePrivilegesFromKibanaPrivileges, + getAuthorizationFromPrivileges, } from './authz'; import { ENDPOINT_PRIVILEGES } from './constants'; @@ -158,4 +159,66 @@ describe('fleet authz', () => { expect(actual).toEqual(expected); }); }); + + describe('#getAuthorizationFromPrivileges', () => { + it('returns `false` when no `prefix` nor `searchPrivilege`', () => { + expect( + getAuthorizationFromPrivileges({ + kibanaPrivileges: [ + { + privilege: `${SECURITY_SOLUTION_ID}-ignoreMe`, + authorized: true, + }, + ], + }) + ).toEqual(false); + }); + + it('returns correct Boolean when `prefix` and `searchPrivilege` are given', () => { + const kibanaPrivileges = [ + { privilege: `${SECURITY_SOLUTION_ID}-writeHostIsolationExceptions`, authorized: false }, + { privilege: `${SECURITY_SOLUTION_ID}-writeHostIsolation`, authorized: true }, + { privilege: `${SECURITY_SOLUTION_ID}-ignoreMe`, authorized: false }, + ]; + + expect( + getAuthorizationFromPrivileges({ + kibanaPrivileges, + prefix: `${SECURITY_SOLUTION_ID}-`, + searchPrivilege: `writeHostIsolation`, + }) + ).toEqual(true); + }); + + it('returns correct Boolean when only `prefix` is given', () => { + const kibanaPrivileges = [ + { privilege: `ignore-me-writeHostIsolationExceptions`, authorized: false }, + { privilege: `${SECURITY_SOLUTION_ID}-writeHostIsolation`, authorized: true }, + { privilege: `${SECURITY_SOLUTION_ID}-ignoreMe`, authorized: false }, + ]; + + expect( + getAuthorizationFromPrivileges({ + kibanaPrivileges, + prefix: `${SECURITY_SOLUTION_ID}-`, + searchPrivilege: `writeHostIsolation`, + }) + ).toEqual(true); + }); + + it('returns correct Boolean when only `searchPrivilege` is given', () => { + const kibanaPrivileges = [ + { privilege: `${SECURITY_SOLUTION_ID}-writeHostIsolationExceptions`, authorized: false }, + { privilege: `${SECURITY_SOLUTION_ID}-writeHostIsolation`, authorized: true }, + { privilege: `${SECURITY_SOLUTION_ID}-ignoreMe`, authorized: false }, + ]; + + expect( + getAuthorizationFromPrivileges({ + kibanaPrivileges, + searchPrivilege: `writeHostIsolation`, + }) + ).toEqual(true); + }); + }); }); diff --git a/x-pack/plugins/fleet/common/authz.ts b/x-pack/plugins/fleet/common/authz.ts index a214b87a3b980..76174dea7dbbb 100644 --- a/x-pack/plugins/fleet/common/authz.ts +++ b/x-pack/plugins/fleet/common/authz.ts @@ -169,19 +169,14 @@ export function getAuthorizationFromPrivileges({ searchPrivilege = '', prefix = '', }: { - kibanaPrivileges: - | Array<{ - resource?: string; - privilege: string; - authorized: boolean; - }> - | undefined; + kibanaPrivileges: Array<{ + resource?: string; + privilege: string; + authorized: boolean; + }>; prefix?: string; searchPrivilege?: string; }): boolean { - if (!kibanaPrivileges) { - return false; - } const privilege = kibanaPrivileges.find((p) => { if (prefix.length && searchPrivilege.length) { return p.privilege.endsWith(`${prefix}${searchPrivilege}`); From 1c29f75970bad181e9d7949fd7bc47249373fcae Mon Sep 17 00:00:00 2001 From: Ashokaditya Date: Wed, 20 Sep 2023 13:31:39 +0200 Subject: [PATCH 25/42] fix types --- x-pack/plugins/fleet/common/authz.ts | 12 +++++------- 1 file changed, 5 insertions(+), 7 deletions(-) diff --git a/x-pack/plugins/fleet/common/authz.ts b/x-pack/plugins/fleet/common/authz.ts index 76174dea7dbbb..3fa67fe77aa11 100644 --- a/x-pack/plugins/fleet/common/authz.ts +++ b/x-pack/plugins/fleet/common/authz.ts @@ -258,13 +258,11 @@ export function calculatePackagePrivilegesFromKibanaPrivileges( } export function calculateEndpointExceptionsPrivilegesFromKibanaPrivileges( - kibanaPrivileges: - | Array<{ - resource?: string; - privilege: string; - authorized: boolean; - }> - | undefined + kibanaPrivileges: Array<{ + resource?: string; + privilege: string; + authorized: boolean; + }> ): FleetAuthz['endpointExceptionsPrivileges'] { const endpointExceptionsActions = Object.entries(ENDPOINT_EXCEPTIONS_PRIVILEGES).reduce< Record From 3bf3ca768b9dd9aa26fd5d2d4ae3d70b3704eede Mon Sep 17 00:00:00 2001 From: Ashokaditya Date: Thu, 21 Sep 2023 15:02:19 +0200 Subject: [PATCH 26/42] add endpoint exceptions to endpoint complete refs elastic/security-team/issues/7223 --- .../security_solution_serverless/common/pli/pli_config.ts | 1 + 1 file changed, 1 insertion(+) diff --git a/x-pack/plugins/security_solution_serverless/common/pli/pli_config.ts b/x-pack/plugins/security_solution_serverless/common/pli/pli_config.ts index 1ef3da121b910..62acff6857a8e 100644 --- a/x-pack/plugins/security_solution_serverless/common/pli/pli_config.ts +++ b/x-pack/plugins/security_solution_serverless/common/pli/pli_config.ts @@ -33,6 +33,7 @@ export const PLI_APP_FEATURES: PliAppFeatures = { complete: [ AppFeatureKey.endpointResponseActions, AppFeatureKey.osqueryAutomatedResponseActions, + AppFeatureKey.endpointExceptions, ], }, cloud: { From ca84a31f3f770256422340f955be79d7f3af055c Mon Sep 17 00:00:00 2001 From: Ashokaditya Date: Thu, 21 Sep 2023 16:06:57 +0200 Subject: [PATCH 27/42] tidy up a bit --- x-pack/plugins/fleet/common/authz.test.ts | 4 ++-- x-pack/plugins/fleet/common/authz.ts | 15 ++++++++++----- .../fleet/server/services/security/security.ts | 12 +++++++----- 3 files changed, 19 insertions(+), 12 deletions(-) diff --git a/x-pack/plugins/fleet/common/authz.test.ts b/x-pack/plugins/fleet/common/authz.test.ts index d98857171ef5d..f07d8aed8f5cb 100644 --- a/x-pack/plugins/fleet/common/authz.test.ts +++ b/x-pack/plugins/fleet/common/authz.test.ts @@ -80,13 +80,13 @@ describe('fleet authz', () => { describe('#calculateEndpointExceptionsPrivilegesFromCapabilities', () => { it('calculates endpoint exceptions privileges correctly', () => { const endpointExceptionsCapabilities = { - showEndpointExceptions: true, + showEndpointExceptions: false, crudEndpointExceptions: true, }; const expected = { actions: { - showEndpointExceptions: true, + showEndpointExceptions: false, crudEndpointExceptions: true, }, }; diff --git a/x-pack/plugins/fleet/common/authz.ts b/x-pack/plugins/fleet/common/authz.ts index 3fa67fe77aa11..a6ed56eaf4f8a 100644 --- a/x-pack/plugins/fleet/common/authz.ts +++ b/x-pack/plugins/fleet/common/authz.ts @@ -258,12 +258,17 @@ export function calculatePackagePrivilegesFromKibanaPrivileges( } export function calculateEndpointExceptionsPrivilegesFromKibanaPrivileges( - kibanaPrivileges: Array<{ - resource?: string; - privilege: string; - authorized: boolean; - }> + kibanaPrivileges: + | Array<{ + resource?: string; + privilege: string; + authorized: boolean; + }> + | undefined ): FleetAuthz['endpointExceptionsPrivileges'] { + if (!kibanaPrivileges || !kibanaPrivileges.length) { + return; + } const endpointExceptionsActions = Object.entries(ENDPOINT_EXCEPTIONS_PRIVILEGES).reduce< Record >((acc, [privilege, { appId, privilegeSplit, privilegeName }]) => { diff --git a/x-pack/plugins/fleet/server/services/security/security.ts b/x-pack/plugins/fleet/server/services/security/security.ts index efd809868e166..76986768416ff 100644 --- a/x-pack/plugins/fleet/server/services/security/security.ts +++ b/x-pack/plugins/fleet/server/services/security/security.ts @@ -64,12 +64,14 @@ const computeUiApiPrivileges = ( security: SecurityPluginStart, privileges: Record ): string[] => { - return Object.entries(privileges).map(([_, { appId, privilegeType, privilegeName }]) => { - if (privilegeType === 'ui') { - return security.authz.actions[privilegeType].get(`${appId}`, `${privilegeName}`); + return Object.entries(privileges).map( + ([_, { appId, privilegeType, privilegeSplit, privilegeName }]) => { + if (privilegeType === 'ui') { + return security.authz.actions[privilegeType].get(`${appId}`, `${privilegeName}`); + } + return security.authz.actions[privilegeType].get(`${appId}${privilegeSplit}${privilegeName}`); } - return security.authz.actions[privilegeType].get(`${appId}-${privilegeName}`); - }); + ); }; export async function getAuthzFromRequest(req: KibanaRequest): Promise { From de4f2e219a7f8d5cac3e717213993a479554828b Mon Sep 17 00:00:00 2001 From: Ashokaditya Date: Fri, 22 Sep 2023 15:55:03 +0200 Subject: [PATCH 28/42] fix typo --- .../endpoint/validators/endpoint_exceptions_validator.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/x-pack/plugins/security_solution/server/lists_integration/endpoint/validators/endpoint_exceptions_validator.ts b/x-pack/plugins/security_solution/server/lists_integration/endpoint/validators/endpoint_exceptions_validator.ts index e875f476fed6c..23d1d28ba0a59 100644 --- a/x-pack/plugins/security_solution/server/lists_integration/endpoint/validators/endpoint_exceptions_validator.ts +++ b/x-pack/plugins/security_solution/server/lists_integration/endpoint/validators/endpoint_exceptions_validator.ts @@ -18,11 +18,11 @@ export class EndpointExceptionsValidator extends BaseValidator { } protected async validateHasReadPrivilege(): Promise { - return this.validateHasEndpointExceptionsPrivileges('canWriteEndpointExceptions'); + return this.validateHasEndpointExceptionsPrivileges('canReadEndpointExceptions'); } protected async validateHasWritePrivilege(): Promise { - return this.validateHasEndpointExceptionsPrivileges('canReadEndpointExceptions'); + return this.validateHasEndpointExceptionsPrivileges('canWriteEndpointExceptions'); } async validatePreCreateItem(item: CreateExceptionListItemOptions) { From a9751c61a3cbde0a7c9a8da5e764b8a482003b36 Mon Sep 17 00:00:00 2001 From: Ashokaditya Date: Fri, 22 Sep 2023 16:34:44 +0200 Subject: [PATCH 29/42] update codeowners file --- .github/CODEOWNERS | 1 + .../public/upselling/lazy_upselling.tsx | 2 +- .../endpoint_exceptions_details.tsx | 2 +- .../public/upselling/register_upsellings.tsx | 6 ++++-- .../sections/endpoint_management/index.ts | 6 ++++++ .../rule_details_endpoint_exceptions.tsx | 0 .../rule_details_endpoint_exceptions/index.tsx | 14 -------------- 7 files changed, 13 insertions(+), 18 deletions(-) rename x-pack/plugins/security_solution_serverless/public/upselling/pages/{ => endpoint_management}/endpoint_exceptions_details.tsx (95%) rename x-pack/plugins/security_solution_serverless/public/upselling/sections/{rule_details_endpoint_exceptions => endpoint_management}/rule_details_endpoint_exceptions.tsx (100%) delete mode 100644 x-pack/plugins/security_solution_serverless/public/upselling/sections/rule_details_endpoint_exceptions/index.tsx diff --git a/.github/CODEOWNERS b/.github/CODEOWNERS index def24b72f41c8..e81dee928e0b6 100644 --- a/.github/CODEOWNERS +++ b/.github/CODEOWNERS @@ -1321,6 +1321,7 @@ x-pack/plugins/cloud_integrations/cloud_full_story/server/config.ts @elastic/kib /x-pack/test/security_solution_endpoint_api_int/ @elastic/security-defend-workflows /x-pack/test_serverless/shared/lib/security/kibana_roles/ @elastic/security-defend-workflows /x-pack/plugins/security_solution_serverless/public/upselling/sections/endpoint_management @elastic/security-defend-workflows +/x-pack/plugins/security_solution_serverless/public/upselling/pages/endpoint_management @elastic/security-defend-workflows /x-pack/plugins/security_solution_serverless/server/endpoint @elastic/security-defend-workflows ## Security Solution sub teams - security-telemetry (Data Engineering) diff --git a/x-pack/plugins/security_solution_serverless/public/upselling/lazy_upselling.tsx b/x-pack/plugins/security_solution_serverless/public/upselling/lazy_upselling.tsx index 25282c6f3dffd..71f787e19c3bd 100644 --- a/x-pack/plugins/security_solution_serverless/public/upselling/lazy_upselling.tsx +++ b/x-pack/plugins/security_solution_serverless/public/upselling/lazy_upselling.tsx @@ -28,7 +28,7 @@ export const OsqueryResponseActionsUpsellingSectionLazy = withSuspenseUpsell( ); export const EndpointExceptionsDetailsUpsellingLazy = withSuspenseUpsell( - lazy(() => import('./pages/endpoint_exceptions_details')) + lazy(() => import('./pages/endpoint_management/endpoint_exceptions_details')) ); export const EntityAnalyticsUpsellingLazy = withSuspenseUpsell( diff --git a/x-pack/plugins/security_solution_serverless/public/upselling/pages/endpoint_exceptions_details.tsx b/x-pack/plugins/security_solution_serverless/public/upselling/pages/endpoint_management/endpoint_exceptions_details.tsx similarity index 95% rename from x-pack/plugins/security_solution_serverless/public/upselling/pages/endpoint_exceptions_details.tsx rename to x-pack/plugins/security_solution_serverless/public/upselling/pages/endpoint_management/endpoint_exceptions_details.tsx index 0e9fa391b09d9..fd514350eaf0a 100644 --- a/x-pack/plugins/security_solution_serverless/public/upselling/pages/endpoint_exceptions_details.tsx +++ b/x-pack/plugins/security_solution_serverless/public/upselling/pages/endpoint_management/endpoint_exceptions_details.tsx @@ -9,7 +9,7 @@ import { EuiEmptyPrompt, EuiIcon } from '@elastic/eui'; import { FormattedMessage } from '@kbn/i18n-react'; import React, { memo } from 'react'; import type { AppFeatureKeyType } from '@kbn/security-solution-features/keys'; -import { getProductTypeByPLI } from '../hooks/use_product_type_by_pli'; +import { getProductTypeByPLI } from '../../hooks/use_product_type_by_pli'; const EndpointExceptionsDetailsUpselling: React.FC<{ requiredPLI: AppFeatureKeyType }> = memo( ({ requiredPLI }) => { diff --git a/x-pack/plugins/security_solution_serverless/public/upselling/register_upsellings.tsx b/x-pack/plugins/security_solution_serverless/public/upselling/register_upsellings.tsx index 7cdb6a5a88782..63386a94fcf19 100644 --- a/x-pack/plugins/security_solution_serverless/public/upselling/register_upsellings.tsx +++ b/x-pack/plugins/security_solution_serverless/public/upselling/register_upsellings.tsx @@ -17,8 +17,10 @@ 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 { RuleDetailsEndpointExceptionsLazy } from './sections/rule_details_endpoint_exceptions'; +import { + EndpointPolicyProtectionsLazy, + RuleDetailsEndpointExceptionsLazy, +} from './sections/endpoint_management'; import type { SecurityProductTypes } from '../../common/config'; import { getProductAppFeatures } from '../../common/pli/pli_features'; import { diff --git a/x-pack/plugins/security_solution_serverless/public/upselling/sections/endpoint_management/index.ts b/x-pack/plugins/security_solution_serverless/public/upselling/sections/endpoint_management/index.ts index a76b1cc0bacc8..e6db03492885e 100644 --- a/x-pack/plugins/security_solution_serverless/public/upselling/sections/endpoint_management/index.ts +++ b/x-pack/plugins/security_solution_serverless/public/upselling/sections/endpoint_management/index.ts @@ -12,3 +12,9 @@ export const EndpointPolicyProtectionsLazy = lazy(() => default: EndpointPolicyProtections, })) ); + +export const RuleDetailsEndpointExceptionsLazy = lazy(() => + import('./rule_details_endpoint_exceptions').then(({ RuleDetailsEndpointExceptions }) => ({ + default: RuleDetailsEndpointExceptions, + })) +); diff --git a/x-pack/plugins/security_solution_serverless/public/upselling/sections/rule_details_endpoint_exceptions/rule_details_endpoint_exceptions.tsx b/x-pack/plugins/security_solution_serverless/public/upselling/sections/endpoint_management/rule_details_endpoint_exceptions.tsx similarity index 100% rename from x-pack/plugins/security_solution_serverless/public/upselling/sections/rule_details_endpoint_exceptions/rule_details_endpoint_exceptions.tsx rename to x-pack/plugins/security_solution_serverless/public/upselling/sections/endpoint_management/rule_details_endpoint_exceptions.tsx diff --git a/x-pack/plugins/security_solution_serverless/public/upselling/sections/rule_details_endpoint_exceptions/index.tsx b/x-pack/plugins/security_solution_serverless/public/upselling/sections/rule_details_endpoint_exceptions/index.tsx deleted file mode 100644 index f684d942995f4..0000000000000 --- a/x-pack/plugins/security_solution_serverless/public/upselling/sections/rule_details_endpoint_exceptions/index.tsx +++ /dev/null @@ -1,14 +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 { lazy } from 'react'; - -export const RuleDetailsEndpointExceptionsLazy = lazy(() => - import('./rule_details_endpoint_exceptions').then(({ RuleDetailsEndpointExceptions }) => ({ - default: RuleDetailsEndpointExceptions, - })) -); From b7cbf121e1fe70d8a8f35d83e951386a04f18866 Mon Sep 17 00:00:00 2001 From: Ashokaditya Date: Wed, 27 Sep 2023 08:46:40 +0200 Subject: [PATCH 30/42] cleanup review changes @semd --- .../public/upselling/register_upsellings.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/x-pack/plugins/security_solution_serverless/public/upselling/register_upsellings.tsx b/x-pack/plugins/security_solution_serverless/public/upselling/register_upsellings.tsx index 63386a94fcf19..4c9f9f65fc0a7 100644 --- a/x-pack/plugins/security_solution_serverless/public/upselling/register_upsellings.tsx +++ b/x-pack/plugins/security_solution_serverless/public/upselling/register_upsellings.tsx @@ -44,7 +44,7 @@ interface UpsellingsMessageConfig { id: UpsellingMessageId; } -type UpsellingPages = Array; +type UpsellingPages = Array; type UpsellingSections = Array; type UpsellingMessages = UpsellingsMessageConfig[]; From 8425f830a32ab06a2d284699ea93c9e7e97ebd7d Mon Sep 17 00:00:00 2001 From: Ashokaditya Date: Wed, 27 Sep 2023 09:32:25 +0200 Subject: [PATCH 31/42] fix merge error --- x-pack/packages/security-solution/upselling/service/index.ts | 1 - 1 file changed, 1 deletion(-) diff --git a/x-pack/packages/security-solution/upselling/service/index.ts b/x-pack/packages/security-solution/upselling/service/index.ts index b9a7bcde1fe17..75ae1a0f109bf 100644 --- a/x-pack/packages/security-solution/upselling/service/index.ts +++ b/x-pack/packages/security-solution/upselling/service/index.ts @@ -11,5 +11,4 @@ export type { SectionUpsellings, UpsellingSectionId, UpsellingMessageId, - MessageUpsellings, } from './types'; From 141a413f72a85a4d34361b108a9a3f423e0b3dd7 Mon Sep 17 00:00:00 2001 From: Ashokaditya Date: Fri, 29 Sep 2023 08:33:13 +0200 Subject: [PATCH 32/42] fix run time error --- x-pack/plugins/fleet/common/authz.test.ts | 21 +++++++++++++++++++++ x-pack/plugins/fleet/common/authz.ts | 18 +++++++++--------- 2 files changed, 30 insertions(+), 9 deletions(-) diff --git a/x-pack/plugins/fleet/common/authz.test.ts b/x-pack/plugins/fleet/common/authz.test.ts index f07d8aed8f5cb..5b00cc3463a64 100644 --- a/x-pack/plugins/fleet/common/authz.test.ts +++ b/x-pack/plugins/fleet/common/authz.test.ts @@ -100,6 +100,27 @@ describe('fleet authz', () => { expect(actual).toEqual(expected); }); + + it('calculates endpoint exceptions privileges correctly when no matching capabilities', () => { + const endpointCapabilities = { + writeEndpointList: true, + writeHostIsolation: false, + }; + const expected = { + actions: { + showEndpointExceptions: false, + crudEndpointExceptions: false, + }, + }; + const actual = calculateEndpointExceptionsPrivilegesFromCapabilities({ + navLinks: {}, + management: {}, + catalogue: {}, + siem: endpointCapabilities, + }); + + expect(actual).toEqual(expected); + }); }); describe('calculatePackagePrivilegesFromKibanaPrivileges', () => { diff --git a/x-pack/plugins/fleet/common/authz.ts b/x-pack/plugins/fleet/common/authz.ts index a6ed56eaf4f8a..f2b9a4c7535a9 100644 --- a/x-pack/plugins/fleet/common/authz.ts +++ b/x-pack/plugins/fleet/common/authz.ts @@ -149,15 +149,15 @@ export function calculateEndpointExceptionsPrivilegesFromCapabilities( return; } - const endpointExceptionsActions = Object.keys(capabilities.siem).reduce>( - (acc, privilegeName) => { - if (Object.keys(ENDPOINT_EXCEPTIONS_PRIVILEGES).includes(privilegeName)) { - acc[privilegeName] = (capabilities.siem[privilegeName] as boolean) || false; - } - return acc; - }, - {} - ); + const endpointExceptionsActions = Object.keys(ENDPOINT_EXCEPTIONS_PRIVILEGES).reduce< + Record + >((acc, privilegeName) => { + if (Object.keys(ENDPOINT_EXCEPTIONS_PRIVILEGES).includes(privilegeName)) { + acc[privilegeName] = + (capabilities.siem && (capabilities.siem[privilegeName] as boolean)) || false; + } + return acc; + }, {}); return { actions: endpointExceptionsActions, From 76955d2f046dc96ff5e95dd4a51fe50a1778809a Mon Sep 17 00:00:00 2001 From: Ashokaditya Date: Fri, 29 Sep 2023 09:33:50 +0200 Subject: [PATCH 33/42] tidy up refs 141a413f72a85a4d34361b108a9a3f423e0b3dd7 --- x-pack/plugins/fleet/common/authz.test.ts | 4 ++++ x-pack/plugins/fleet/common/authz.ts | 7 ++----- 2 files changed, 6 insertions(+), 5 deletions(-) diff --git a/x-pack/plugins/fleet/common/authz.test.ts b/x-pack/plugins/fleet/common/authz.test.ts index 5b00cc3463a64..c7686083a6bed 100644 --- a/x-pack/plugins/fleet/common/authz.test.ts +++ b/x-pack/plugins/fleet/common/authz.test.ts @@ -104,6 +104,10 @@ describe('fleet authz', () => { it('calculates endpoint exceptions privileges correctly when no matching capabilities', () => { const endpointCapabilities = { writeEndpointList: true, + writeTrustedApplications: true, + writePolicyManagement: false, + readPolicyManagement: true, + writeHostIsolationExceptions: true, writeHostIsolation: false, }; const expected = { diff --git a/x-pack/plugins/fleet/common/authz.ts b/x-pack/plugins/fleet/common/authz.ts index f2b9a4c7535a9..97918648db25b 100644 --- a/x-pack/plugins/fleet/common/authz.ts +++ b/x-pack/plugins/fleet/common/authz.ts @@ -145,17 +145,14 @@ export function calculatePackagePrivilegesFromCapabilities( export function calculateEndpointExceptionsPrivilegesFromCapabilities( capabilities: Capabilities | undefined ): FleetAuthz['endpointExceptionsPrivileges'] { - if (!capabilities) { + if (!capabilities || !capabilities.siem) { return; } const endpointExceptionsActions = Object.keys(ENDPOINT_EXCEPTIONS_PRIVILEGES).reduce< Record >((acc, privilegeName) => { - if (Object.keys(ENDPOINT_EXCEPTIONS_PRIVILEGES).includes(privilegeName)) { - acc[privilegeName] = - (capabilities.siem && (capabilities.siem[privilegeName] as boolean)) || false; - } + acc[privilegeName] = (capabilities.siem[privilegeName] as boolean) || false; return acc; }, {}); From fe11e51ed9211db196d9a1829ad7c5464199eac6 Mon Sep 17 00:00:00 2001 From: Ashokaditya Date: Fri, 29 Sep 2023 12:49:03 +0200 Subject: [PATCH 34/42] show exceptions tab for sub feature role with read only permission for serverless users with customised sub feature roles with `endpoint_exceptions_read` role show endpoint exceptions tab --- .../pages/rule_details/use_rule_details_tabs.tsx | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/x-pack/plugins/security_solution/public/detection_engine/rule_details_ui/pages/rule_details/use_rule_details_tabs.tsx b/x-pack/plugins/security_solution/public/detection_engine/rule_details_ui/pages/rule_details/use_rule_details_tabs.tsx index d958575460ea5..4faa38b64bca2 100644 --- a/x-pack/plugins/security_solution/public/detection_engine/rule_details_ui/pages/rule_details/use_rule_details_tabs.tsx +++ b/x-pack/plugins/security_solution/public/detection_engine/rule_details_ui/pages/rule_details/use_rule_details_tabs.tsx @@ -86,11 +86,11 @@ export const useRuleDetailsTabs = ({ const { loading: listsConfigLoading, needsConfiguration: needsListsConfiguration } = useListsConfig(); - const canCrudEndpointExceptions = useHasSecurityCapability('crudEndpointExceptions'); + const canSeeEndpointExceptions = useHasSecurityCapability('showEndpointExceptions'); - const canWriteEndpointExceptions = useMemo( - () => !listsConfigLoading && !needsListsConfiguration && canCrudEndpointExceptions, - [canCrudEndpointExceptions, listsConfigLoading, needsListsConfiguration] + const canReadEndpointExceptions = useMemo( + () => !listsConfigLoading && !needsListsConfiguration && canSeeEndpointExceptions, + [canSeeEndpointExceptions, listsConfigLoading, needsListsConfiguration] ); useEffect(() => { @@ -102,7 +102,7 @@ export const useRuleDetailsTabs = ({ if (!ruleExecutionSettings.extendedLogging.isEnabled) { hiddenTabs.push(RuleDetailTabs.executionEvents); } - if (!canWriteEndpointExceptions) { + if (!canReadEndpointExceptions) { hiddenTabs.push(RuleDetailTabs.endpointExceptions); } if (rule != null) { @@ -117,7 +117,7 @@ export const useRuleDetailsTabs = ({ const tabs = omit>(hiddenTabs, ruleDetailTabs); setTabs(tabs); - }, [canWriteEndpointExceptions, hasIndexRead, rule, ruleDetailTabs, ruleExecutionSettings]); + }, [canReadEndpointExceptions, hasIndexRead, rule, ruleDetailTabs, ruleExecutionSettings]); return pageTabs; }; From 7cffc0e81772a5b8059e0737218eb533e2d8dc7a Mon Sep 17 00:00:00 2001 From: Ashokaditya Date: Fri, 29 Sep 2023 13:27:02 +0200 Subject: [PATCH 35/42] disable actions for sub feature role For serverless user with sub feature read only access to endpoint exceptions, disable edit/delete actions refs elastic/security-team/issues/7223 --- .../components/list_exception_items/index.tsx | 17 ++++++++++++++--- 1 file changed, 14 insertions(+), 3 deletions(-) diff --git a/x-pack/plugins/security_solution/public/exceptions/components/list_exception_items/index.tsx b/x-pack/plugins/security_solution/public/exceptions/components/list_exception_items/index.tsx index 1b1b6ad36d1ea..703272d882cb7 100644 --- a/x-pack/plugins/security_solution/public/exceptions/components/list_exception_items/index.tsx +++ b/x-pack/plugins/security_solution/public/exceptions/components/list_exception_items/index.tsx @@ -4,8 +4,8 @@ * 2.0; you may not use this file except in compliance with the Elastic License * 2.0. */ -import React, { useMemo } from 'react'; import type { FC } from 'react'; +import React, { useMemo } from 'react'; import type { ExceptionListItemIdentifiers, GetExceptionItemProps, @@ -13,15 +13,17 @@ import type { ViewerStatus, } from '@kbn/securitysolution-exception-list-components'; import { ExceptionItems } from '@kbn/securitysolution-exception-list-components'; -import { ExceptionListTypeEnum } from '@kbn/securitysolution-io-ts-list-types'; import type { ExceptionListItemSchema } from '@kbn/securitysolution-io-ts-list-types'; +import { ExceptionListTypeEnum } from '@kbn/securitysolution-io-ts-list-types'; import type { Pagination } from '@elastic/eui'; +import { useHasSecurityCapability } from '../../../helper_hooks'; import { FormattedDate } from '../../../common/components/formatted_date'; import { getFormattedComments } from '../../utils/ui.helpers'; import { LinkToRuleDetails } from '../link_to_rule_details'; import { ExceptionsUtility } from '../exceptions_utility'; import * as i18n from '../../translations/list_exception_items'; +import { useListsConfig } from '../../../detections/containers/detection_engine/lists/use_lists_config'; interface ListExceptionItemsProps { isReadOnly: boolean; @@ -58,6 +60,15 @@ const ListExceptionItemsComponent: FC = ({ onPaginationChange, onCreateExceptionListItem, }) => { + const { loading: listsConfigLoading, needsConfiguration: needsListsConfiguration } = + useListsConfig(); + const canCrudEndpointExceptions = useHasSecurityCapability('crudEndpointExceptions'); + + const canWriteEndpointExceptions = useMemo( + () => !listsConfigLoading && !needsListsConfiguration && canCrudEndpointExceptions, + [canCrudEndpointExceptions, listsConfigLoading, needsListsConfiguration] + ); + const editButtonText = useMemo(() => { return listType === ExceptionListTypeEnum.ENDPOINT ? i18n.EXCEPTION_ITEM_CARD_EDIT_ENDPOINT_LABEL @@ -76,7 +87,7 @@ const ListExceptionItemsComponent: FC = ({ viewerStatus={viewerStatus as ViewerStatus} listType={listType as ExceptionListTypeEnum} ruleReferences={ruleReferences} - isReadOnly={isReadOnly} + isReadOnly={isReadOnly || !canWriteEndpointExceptions} exceptions={exceptions} emptyViewerTitle={emptyViewerTitle} emptyViewerBody={emptyViewerBody} From 080c60e996ae554040e43b7cdad0142f5309e2d4 Mon Sep 17 00:00:00 2001 From: Ashokaditya Date: Fri, 29 Sep 2023 15:57:58 +0200 Subject: [PATCH 36/42] disable edit/delete on rule details tabs similar to 7cffc0e81772a5b8059e0737218eb533e2d8dc7a refs elastic/security-team/issues/7223 --- .../all_exception_items_table/index.test.tsx | 13 ++++++++++++ .../all_exception_items_table/index.tsx | 21 ++++++++++++++----- 2 files changed, 29 insertions(+), 5 deletions(-) diff --git a/x-pack/plugins/security_solution/public/detection_engine/rule_exceptions/components/all_exception_items_table/index.test.tsx b/x-pack/plugins/security_solution/public/detection_engine/rule_exceptions/components/all_exception_items_table/index.test.tsx index 1d1dd112da377..5b9998d50a939 100644 --- a/x-pack/plugins/security_solution/public/detection_engine/rule_exceptions/components/all_exception_items_table/index.test.tsx +++ b/x-pack/plugins/security_solution/public/detection_engine/rule_exceptions/components/all_exception_items_table/index.test.tsx @@ -19,7 +19,11 @@ import type { Rule } from '../../../rule_management/logic/types'; import { mockRule } from '../../../rule_management_ui/components/rules_table/__mocks__/mock'; import { useFindExceptionListReferences } from '../../logic/use_find_references'; import * as i18n from './translations'; +import { useHasSecurityCapability } from '../../../../helper_hooks'; +import { useListsConfig } from '../../../../detections/containers/detection_engine/lists/use_lists_config'; +jest.mock('../../../../helper_hooks'); +jest.mock('../../../../detections/containers/detection_engine/lists/use_lists_config'); jest.mock('../../../../common/lib/kibana'); jest.mock('@kbn/securitysolution-list-hooks'); jest.mock('@kbn/securitysolution-list-api'); @@ -29,6 +33,9 @@ jest.mock('react', () => { return { ...r, useReducer: jest.fn() }; }); +const mockUseHasSecurityCapability = useHasSecurityCapability as jest.Mock; +const mockUseListsConfig = useListsConfig as jest.Mock; + const sampleExceptionItem = { _version: 'WzEwMjM4MSwxXQ==', comments: [], @@ -81,6 +88,12 @@ describe('ExceptionsViewer', () => { }, }); + mockUseListsConfig.mockReturnValue({ + loading: false, + needsConfiguration: false, + }); + mockUseHasSecurityCapability.mockReturnValue(true); + (fetchExceptionListsItemsByListIds as jest.Mock).mockReturnValue({ total: 0 }); (useFindExceptionListReferences as jest.Mock).mockReturnValue([ diff --git a/x-pack/plugins/security_solution/public/detection_engine/rule_exceptions/components/all_exception_items_table/index.tsx b/x-pack/plugins/security_solution/public/detection_engine/rule_exceptions/components/all_exception_items_table/index.tsx index df6a6c2835a1d..f3209882fe86f 100644 --- a/x-pack/plugins/security_solution/public/detection_engine/rule_exceptions/components/all_exception_items_table/index.tsx +++ b/x-pack/plugins/security_solution/public/detection_engine/rule_exceptions/components/all_exception_items_table/index.tsx @@ -5,18 +5,18 @@ * 2.0. */ -import React, { useCallback, useMemo, useEffect, useReducer } from 'react'; +import React, { useCallback, useEffect, useMemo, useReducer } from 'react'; import styled from 'styled-components'; import { EuiPanel, EuiSpacer, EuiText } from '@elastic/eui'; -import { ExceptionListTypeEnum } from '@kbn/securitysolution-io-ts-list-types'; import type { ExceptionListItemSchema, - UseExceptionListItemsSuccess, - Pagination, ExceptionListSchema, + Pagination, + UseExceptionListItemsSuccess, } from '@kbn/securitysolution-io-ts-list-types'; +import { ExceptionListTypeEnum } from '@kbn/securitysolution-io-ts-list-types'; import { transformInput } from '@kbn/securitysolution-list-hooks'; import { @@ -29,6 +29,7 @@ import { buildShowExpiredExceptionsFilter, getSavedObjectTypes, } from '@kbn/securitysolution-list-utils'; +import { useListsConfig } from '../../../../detections/containers/detection_engine/lists/use_lists_config'; import { useUserData } from '../../../../detections/components/user_info'; import { useKibana, useToasts } from '../../../../common/lib/kibana'; import { ExceptionsViewerSearchBar } from './search_bar'; @@ -44,6 +45,7 @@ import { AddExceptionFlyout } from '../add_exception_flyout'; import * as i18n from './translations'; import { useFindExceptionListReferences } from '../../logic/use_find_references'; import type { Rule } from '../../../rule_management/logic/types'; +import { useHasSecurityCapability } from '../../../../helper_hooks'; const StyledText = styled(EuiText)` font-style: italic; @@ -120,6 +122,15 @@ const ExceptionsViewerComponent = ({ [listTypes] ); + const { loading: listsConfigLoading, needsConfiguration: needsListsConfiguration } = + useListsConfig(); + const canCrudEndpointExceptions = useHasSecurityCapability('crudEndpointExceptions'); + + const canWriteEndpointExceptions = useMemo( + () => !listsConfigLoading && !needsListsConfiguration && canCrudEndpointExceptions, + [canCrudEndpointExceptions, listsConfigLoading, needsListsConfiguration] + ); + // Reducer state const [ { @@ -531,7 +542,7 @@ const ExceptionsViewerComponent = ({ Date: Fri, 29 Sep 2023 17:08:05 +0200 Subject: [PATCH 37/42] unify capability check logic in a hook --- .../use_rule_details_tabs.test.tsx | 43 +++---------------- .../rule_details/use_rule_details_tabs.tsx | 12 +----- .../all_exception_items_table/index.test.tsx | 15 ++----- .../all_exception_items_table/index.tsx | 12 +----- .../use_add_exception_actions.tsx | 12 +----- .../components/list_exception_items/index.tsx | 12 +----- .../index.tsx | 23 ++++++++++ .../exceptions/pages/shared_lists/index.tsx | 11 ++--- 8 files changed, 43 insertions(+), 97 deletions(-) create mode 100644 x-pack/plugins/security_solution/public/exceptions/hooks/use_endpoint_exceptions_capability/index.tsx diff --git a/x-pack/plugins/security_solution/public/detection_engine/rule_details_ui/pages/rule_details/use_rule_details_tabs.test.tsx b/x-pack/plugins/security_solution/public/detection_engine/rule_details_ui/pages/rule_details/use_rule_details_tabs.test.tsx index 5fcf70da9d915..1dc2d4ee29442 100644 --- a/x-pack/plugins/security_solution/public/detection_engine/rule_details_ui/pages/rule_details/use_rule_details_tabs.test.tsx +++ b/x-pack/plugins/security_solution/public/detection_engine/rule_details_ui/pages/rule_details/use_rule_details_tabs.test.tsx @@ -10,16 +10,13 @@ import type { UseRuleDetailsTabsProps } from './use_rule_details_tabs'; import { RuleDetailTabs, useRuleDetailsTabs } from './use_rule_details_tabs'; import type { Rule } from '../../../rule_management/logic'; import { useRuleExecutionSettings } from '../../../rule_monitoring'; -import { useHasSecurityCapability } from '../../../../helper_hooks'; -import { useListsConfig } from '../../../../detections/containers/detection_engine/lists/use_lists_config'; +import { useEndpointExceptionsCapability } from '../../../../exceptions/hooks/use_endpoint_exceptions_capability'; jest.mock('../../../rule_monitoring'); -jest.mock('../../../../helper_hooks'); -jest.mock('../../../../detections/containers/detection_engine/lists/use_lists_config'); +jest.mock('../../../../exceptions/hooks/use_endpoint_exceptions_capability'); const mockUseRuleExecutionSettings = useRuleExecutionSettings as jest.Mock; -const mockUseHasSecurityCapability = useHasSecurityCapability as jest.Mock; -const mockUseListsConfig = useListsConfig as jest.Mock; +const mockUseEndpointExceptionsCapability = useEndpointExceptionsCapability as jest.Mock; const mockRule: Rule = { id: 'myfakeruleid', @@ -65,11 +62,7 @@ describe('useRuleDetailsTabs', () => { minLevel: 'debug', }, }); - mockUseListsConfig.mockReturnValue({ - loading: false, - needsConfiguration: false, - }); - mockUseHasSecurityCapability.mockReturnValue(true); + mockUseEndpointExceptionsCapability.mockReturnValue(true); }); beforeEach(() => { @@ -133,33 +126,7 @@ describe('useRuleDetailsTabs', () => { }); it('hides endpoint exceptions tab when rule includes endpoint list but no endpoint PLI', async () => { - mockUseHasSecurityCapability.mockReturnValue(false); - const tabs = render({ - rule: { - ...mockRule, - outcome: 'conflict', - alias_target_id: 'aliased_rule_id', - alias_purpose: 'savedObjectConversion', - exceptions_list: [ - { - id: 'endpoint_list', - list_id: 'endpoint_list', - type: 'endpoint', - namespace_type: 'agnostic', - }, - ], - }, - ruleId: mockRule.rule_id, - isExistingRule: true, - hasIndexRead: true, - }); - const tabsNames = Object.keys(tabs.result.current); - - expect(tabsNames).not.toContain(RuleDetailTabs.endpointExceptions); - }); - - it('hides endpoint exceptions tab when rule includes endpoint list, has endpoint PLI, but no index privilege', async () => { - mockUseListsConfig.mockReturnValue({ loading: false, needsConfiguration: true }); + mockUseEndpointExceptionsCapability.mockReturnValue(false); const tabs = render({ rule: { ...mockRule, diff --git a/x-pack/plugins/security_solution/public/detection_engine/rule_details_ui/pages/rule_details/use_rule_details_tabs.tsx b/x-pack/plugins/security_solution/public/detection_engine/rule_details_ui/pages/rule_details/use_rule_details_tabs.tsx index 4faa38b64bca2..8c73bafd049aa 100644 --- a/x-pack/plugins/security_solution/public/detection_engine/rule_details_ui/pages/rule_details/use_rule_details_tabs.tsx +++ b/x-pack/plugins/security_solution/public/detection_engine/rule_details_ui/pages/rule_details/use_rule_details_tabs.tsx @@ -8,13 +8,12 @@ import { useEffect, useMemo, useState } from 'react'; import { ExceptionListTypeEnum } from '@kbn/securitysolution-io-ts-list-types'; import { omit } from 'lodash/fp'; -import { useListsConfig } from '../../../../detections/containers/detection_engine/lists/use_lists_config'; +import { useEndpointExceptionsCapability } from '../../../../exceptions/hooks/use_endpoint_exceptions_capability'; import * as detectionI18n from '../../../../detections/pages/detection_engine/translations'; import * as i18n from './translations'; import type { Rule } from '../../../rule_management/logic'; import type { NavTab } from '../../../../common/components/navigation/types'; import { useRuleExecutionSettings } from '../../../rule_monitoring'; -import { useHasSecurityCapability } from '../../../../helper_hooks'; export enum RuleDetailTabs { alerts = 'alerts', @@ -84,14 +83,7 @@ export const useRuleDetailsTabs = ({ const [pageTabs, setTabs] = useState>>(ruleDetailTabs); const ruleExecutionSettings = useRuleExecutionSettings(); - const { loading: listsConfigLoading, needsConfiguration: needsListsConfiguration } = - useListsConfig(); - const canSeeEndpointExceptions = useHasSecurityCapability('showEndpointExceptions'); - - const canReadEndpointExceptions = useMemo( - () => !listsConfigLoading && !needsListsConfiguration && canSeeEndpointExceptions, - [canSeeEndpointExceptions, listsConfigLoading, needsListsConfiguration] - ); + const canReadEndpointExceptions = useEndpointExceptionsCapability('showEndpointExceptions'); useEffect(() => { const hiddenTabs = []; diff --git a/x-pack/plugins/security_solution/public/detection_engine/rule_exceptions/components/all_exception_items_table/index.test.tsx b/x-pack/plugins/security_solution/public/detection_engine/rule_exceptions/components/all_exception_items_table/index.test.tsx index 5b9998d50a939..05e6e0513f741 100644 --- a/x-pack/plugins/security_solution/public/detection_engine/rule_exceptions/components/all_exception_items_table/index.test.tsx +++ b/x-pack/plugins/security_solution/public/detection_engine/rule_exceptions/components/all_exception_items_table/index.test.tsx @@ -19,11 +19,9 @@ import type { Rule } from '../../../rule_management/logic/types'; import { mockRule } from '../../../rule_management_ui/components/rules_table/__mocks__/mock'; import { useFindExceptionListReferences } from '../../logic/use_find_references'; import * as i18n from './translations'; -import { useHasSecurityCapability } from '../../../../helper_hooks'; -import { useListsConfig } from '../../../../detections/containers/detection_engine/lists/use_lists_config'; +import { useEndpointExceptionsCapability } from '../../../../exceptions/hooks/use_endpoint_exceptions_capability'; -jest.mock('../../../../helper_hooks'); -jest.mock('../../../../detections/containers/detection_engine/lists/use_lists_config'); +jest.mock('../../../../exceptions/hooks/use_endpoint_exceptions_capability'); jest.mock('../../../../common/lib/kibana'); jest.mock('@kbn/securitysolution-list-hooks'); jest.mock('@kbn/securitysolution-list-api'); @@ -33,8 +31,7 @@ jest.mock('react', () => { return { ...r, useReducer: jest.fn() }; }); -const mockUseHasSecurityCapability = useHasSecurityCapability as jest.Mock; -const mockUseListsConfig = useListsConfig as jest.Mock; +const mockUseEndpointExceptionsCapability = useEndpointExceptionsCapability as jest.Mock; const sampleExceptionItem = { _version: 'WzEwMjM4MSwxXQ==', @@ -88,11 +85,7 @@ describe('ExceptionsViewer', () => { }, }); - mockUseListsConfig.mockReturnValue({ - loading: false, - needsConfiguration: false, - }); - mockUseHasSecurityCapability.mockReturnValue(true); + mockUseEndpointExceptionsCapability.mockReturnValue(true); (fetchExceptionListsItemsByListIds as jest.Mock).mockReturnValue({ total: 0 }); diff --git a/x-pack/plugins/security_solution/public/detection_engine/rule_exceptions/components/all_exception_items_table/index.tsx b/x-pack/plugins/security_solution/public/detection_engine/rule_exceptions/components/all_exception_items_table/index.tsx index f3209882fe86f..cc86bbf02291b 100644 --- a/x-pack/plugins/security_solution/public/detection_engine/rule_exceptions/components/all_exception_items_table/index.tsx +++ b/x-pack/plugins/security_solution/public/detection_engine/rule_exceptions/components/all_exception_items_table/index.tsx @@ -29,7 +29,7 @@ import { buildShowExpiredExceptionsFilter, getSavedObjectTypes, } from '@kbn/securitysolution-list-utils'; -import { useListsConfig } from '../../../../detections/containers/detection_engine/lists/use_lists_config'; +import { useEndpointExceptionsCapability } from '../../../../exceptions/hooks/use_endpoint_exceptions_capability'; import { useUserData } from '../../../../detections/components/user_info'; import { useKibana, useToasts } from '../../../../common/lib/kibana'; import { ExceptionsViewerSearchBar } from './search_bar'; @@ -45,7 +45,6 @@ import { AddExceptionFlyout } from '../add_exception_flyout'; import * as i18n from './translations'; import { useFindExceptionListReferences } from '../../logic/use_find_references'; import type { Rule } from '../../../rule_management/logic/types'; -import { useHasSecurityCapability } from '../../../../helper_hooks'; const StyledText = styled(EuiText)` font-style: italic; @@ -122,14 +121,7 @@ const ExceptionsViewerComponent = ({ [listTypes] ); - const { loading: listsConfigLoading, needsConfiguration: needsListsConfiguration } = - useListsConfig(); - const canCrudEndpointExceptions = useHasSecurityCapability('crudEndpointExceptions'); - - const canWriteEndpointExceptions = useMemo( - () => !listsConfigLoading && !needsListsConfiguration && canCrudEndpointExceptions, - [canCrudEndpointExceptions, listsConfigLoading, needsListsConfiguration] - ); + const canWriteEndpointExceptions = useEndpointExceptionsCapability('crudEndpointExceptions'); // Reducer state const [ diff --git a/x-pack/plugins/security_solution/public/detections/components/alerts_table/timeline_actions/use_add_exception_actions.tsx b/x-pack/plugins/security_solution/public/detections/components/alerts_table/timeline_actions/use_add_exception_actions.tsx index 5dae6df687ec7..b2039befafd8a 100644 --- a/x-pack/plugins/security_solution/public/detections/components/alerts_table/timeline_actions/use_add_exception_actions.tsx +++ b/x-pack/plugins/security_solution/public/detections/components/alerts_table/timeline_actions/use_add_exception_actions.tsx @@ -8,8 +8,7 @@ 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 { useEndpointExceptionsCapability } from '../../../../exceptions/hooks/use_endpoint_exceptions_capability'; import { useUserData } from '../../user_info'; import { ACTION_ADD_ENDPOINT_EXCEPTION, ACTION_ADD_EXCEPTION } from '../translations'; import type { AlertTableContextMenuItem } from '../types'; @@ -76,14 +75,7 @@ export const useAlertExceptionActions = ({ onAddExceptionTypeClick, }); - const { loading: listsConfigLoading, needsConfiguration: needsListsConfiguration } = - useListsConfig(); - const canCrudEndpointExceptions = useHasSecurityCapability('crudEndpointExceptions'); - - const canWriteEndpointExceptions = useMemo( - () => !listsConfigLoading && !needsListsConfiguration && canCrudEndpointExceptions, - [canCrudEndpointExceptions, listsConfigLoading, needsListsConfiguration] - ); + const canWriteEndpointExceptions = useEndpointExceptionsCapability('crudEndpointExceptions'); // 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) diff --git a/x-pack/plugins/security_solution/public/exceptions/components/list_exception_items/index.tsx b/x-pack/plugins/security_solution/public/exceptions/components/list_exception_items/index.tsx index 703272d882cb7..990fd818505b7 100644 --- a/x-pack/plugins/security_solution/public/exceptions/components/list_exception_items/index.tsx +++ b/x-pack/plugins/security_solution/public/exceptions/components/list_exception_items/index.tsx @@ -17,13 +17,12 @@ import type { ExceptionListItemSchema } from '@kbn/securitysolution-io-ts-list-t import { ExceptionListTypeEnum } from '@kbn/securitysolution-io-ts-list-types'; import type { Pagination } from '@elastic/eui'; -import { useHasSecurityCapability } from '../../../helper_hooks'; import { FormattedDate } from '../../../common/components/formatted_date'; import { getFormattedComments } from '../../utils/ui.helpers'; import { LinkToRuleDetails } from '../link_to_rule_details'; import { ExceptionsUtility } from '../exceptions_utility'; import * as i18n from '../../translations/list_exception_items'; -import { useListsConfig } from '../../../detections/containers/detection_engine/lists/use_lists_config'; +import { useEndpointExceptionsCapability } from '../../hooks/use_endpoint_exceptions_capability'; interface ListExceptionItemsProps { isReadOnly: boolean; @@ -60,14 +59,7 @@ const ListExceptionItemsComponent: FC = ({ onPaginationChange, onCreateExceptionListItem, }) => { - const { loading: listsConfigLoading, needsConfiguration: needsListsConfiguration } = - useListsConfig(); - const canCrudEndpointExceptions = useHasSecurityCapability('crudEndpointExceptions'); - - const canWriteEndpointExceptions = useMemo( - () => !listsConfigLoading && !needsListsConfiguration && canCrudEndpointExceptions, - [canCrudEndpointExceptions, listsConfigLoading, needsListsConfiguration] - ); + const canWriteEndpointExceptions = useEndpointExceptionsCapability('crudEndpointExceptions'); const editButtonText = useMemo(() => { return listType === ExceptionListTypeEnum.ENDPOINT diff --git a/x-pack/plugins/security_solution/public/exceptions/hooks/use_endpoint_exceptions_capability/index.tsx b/x-pack/plugins/security_solution/public/exceptions/hooks/use_endpoint_exceptions_capability/index.tsx new file mode 100644 index 0000000000000..a96b11225e659 --- /dev/null +++ b/x-pack/plugins/security_solution/public/exceptions/hooks/use_endpoint_exceptions_capability/index.tsx @@ -0,0 +1,23 @@ +/* + * 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 { useMemo } from 'react'; +import { useListsConfig } from '../../../detections/containers/detection_engine/lists/use_lists_config'; +import { useHasSecurityCapability } from '../../../helper_hooks'; + +export const useEndpointExceptionsCapability = ( + capability: 'showEndpointExceptions' | 'crudEndpointExceptions' +) => { + const { loading: listsConfigLoading, needsConfiguration: needsListsConfiguration } = + useListsConfig(); + const hasEndpointExceptionCapability = useHasSecurityCapability(capability); + + return useMemo( + () => !listsConfigLoading && !needsListsConfiguration && hasEndpointExceptionCapability, + [hasEndpointExceptionCapability, listsConfigLoading, needsListsConfiguration] + ); +}; diff --git a/x-pack/plugins/security_solution/public/exceptions/pages/shared_lists/index.tsx b/x-pack/plugins/security_solution/public/exceptions/pages/shared_lists/index.tsx index 0e457d520984c..3c0fab0343a36 100644 --- a/x-pack/plugins/security_solution/public/exceptions/pages/shared_lists/index.tsx +++ b/x-pack/plugins/security_solution/public/exceptions/pages/shared_lists/index.tsx @@ -28,7 +28,6 @@ import { ExceptionListTypeEnum } from '@kbn/securitysolution-io-ts-list-types'; import { useApi, useExceptionLists } from '@kbn/securitysolution-list-hooks'; 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'; @@ -52,6 +51,7 @@ import { MissingPrivilegesCallOut } from '../../../detections/components/callout import { ALL_ENDPOINT_ARTIFACT_LIST_IDS } from '../../../../common/endpoint/service/artifacts/constants'; import { AddExceptionFlyout } from '../../../detection_engine/rule_exceptions/components/add_exception_flyout'; +import { useEndpointExceptionsCapability } from '../../hooks/use_endpoint_exceptions_capability'; export type Func = () => Promise; @@ -82,15 +82,10 @@ const SORT_FIELDS: Array<{ field: string; label: string; defaultOrder: 'asc' | ' export const SharedLists = React.memo(() => { const [{ loading: userInfoLoading, canUserCRUD, canUserREAD }] = useUserData(); - const { loading: listsConfigLoading, needsConfiguration: needsListsConfiguration } = - useListsConfig(); + const { loading: listsConfigLoading } = useListsConfig(); const loading = userInfoLoading || listsConfigLoading; - const canShowEndpointExceptions = useHasSecurityCapability('showEndpointExceptions'); - const canAccessEndpointExceptions = useMemo( - () => !listsConfigLoading && !needsListsConfiguration && canShowEndpointExceptions, - [canShowEndpointExceptions, listsConfigLoading, needsListsConfiguration] - ); + const canAccessEndpointExceptions = useEndpointExceptionsCapability('showEndpointExceptions'); const { services: { http, From 94d78dde913f1754247e47d46aa2a73560465af9 Mon Sep 17 00:00:00 2001 From: Ashokaditya Date: Fri, 29 Sep 2023 17:12:41 +0200 Subject: [PATCH 38/42] move file review changes @banderror --- .../rule_details_ui/pages/rule_details/index.tsx | 2 +- .../use_get_endpoint_exceptions_unavailablle_component.tsx | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) rename x-pack/plugins/security_solution/public/detection_engine/rule_details_ui/pages/rule_details/{hooks => }/use_get_endpoint_exceptions_unavailablle_component.tsx (84%) diff --git a/x-pack/plugins/security_solution/public/detection_engine/rule_details_ui/pages/rule_details/index.tsx b/x-pack/plugins/security_solution/public/detection_engine/rule_details_ui/pages/rule_details/index.tsx index a103ae80a97b2..c97bd423ea346 100644 --- a/x-pack/plugins/security_solution/public/detection_engine/rule_details_ui/pages/rule_details/index.tsx +++ b/x-pack/plugins/security_solution/public/detection_engine/rule_details_ui/pages/rule_details/index.tsx @@ -38,7 +38,7 @@ import { tableDefaults, TableId, } from '@kbn/securitysolution-data-table'; -import { useGetEndpointExceptionsUnavailableComponent } from './hooks/use_get_endpoint_exceptions_unavailablle_component'; +import { useGetEndpointExceptionsUnavailableComponent } from './use_get_endpoint_exceptions_unavailablle_component'; import { AlertsTableComponent } from '../../../../detections/components/alerts_table'; import { GroupedAlertsTable } from '../../../../detections/components/alerts_table/alerts_grouping'; import { useDataTableFilters } from '../../../../common/hooks/use_data_table_filters'; diff --git a/x-pack/plugins/security_solution/public/detection_engine/rule_details_ui/pages/rule_details/hooks/use_get_endpoint_exceptions_unavailablle_component.tsx b/x-pack/plugins/security_solution/public/detection_engine/rule_details_ui/pages/rule_details/use_get_endpoint_exceptions_unavailablle_component.tsx similarity index 84% rename from x-pack/plugins/security_solution/public/detection_engine/rule_details_ui/pages/rule_details/hooks/use_get_endpoint_exceptions_unavailablle_component.tsx rename to x-pack/plugins/security_solution/public/detection_engine/rule_details_ui/pages/rule_details/use_get_endpoint_exceptions_unavailablle_component.tsx index e0ac6a69a5ed6..daa6f8095a85e 100644 --- a/x-pack/plugins/security_solution/public/detection_engine/rule_details_ui/pages/rule_details/hooks/use_get_endpoint_exceptions_unavailablle_component.tsx +++ b/x-pack/plugins/security_solution/public/detection_engine/rule_details_ui/pages/rule_details/use_get_endpoint_exceptions_unavailablle_component.tsx @@ -6,7 +6,7 @@ */ import type React from 'react'; -import { useUpsellingComponent } from '../../../../../common/hooks/use_upselling'; +import { useUpsellingComponent } from '../../../../common/hooks/use_upselling'; export const useGetEndpointExceptionsUnavailableComponent = (): React.ComponentType | null => { return useUpsellingComponent('ruleDetailsEndpointExceptions'); From 35d0a11794d946f21633c0770901b13054bc90ff Mon Sep 17 00:00:00 2001 From: Ashokaditya Date: Fri, 29 Sep 2023 20:23:18 +0200 Subject: [PATCH 39/42] refactor review suggestions @banderror --- .github/CODEOWNERS | 1 + .../endpoint_exceptions_viewer.tsx | 54 +++++++++++++++++++ ...oint_exceptions_unavailablle_component.tsx | 2 +- .../pages/rule_details/index.tsx | 27 +++------- 4 files changed, 63 insertions(+), 21 deletions(-) create mode 100644 x-pack/plugins/security_solution/public/detection_engine/endpoint_exceptions/endpoint_exceptions_viewer.tsx rename x-pack/plugins/security_solution/public/detection_engine/{rule_details_ui/pages/rule_details => endpoint_exceptions}/use_get_endpoint_exceptions_unavailablle_component.tsx (84%) diff --git a/.github/CODEOWNERS b/.github/CODEOWNERS index 6883261e83c13..2f6ebecfa429b 100644 --- a/.github/CODEOWNERS +++ b/.github/CODEOWNERS @@ -1240,6 +1240,7 @@ x-pack/plugins/cloud_integrations/cloud_full_story/server/config.ts @elastic/kib /x-pack/plugins/security_solution/public/common/components/ml_popover @elastic/security-detection-rule-management /x-pack/plugins/security_solution/public/common/components/popover_items @elastic/security-detection-rule-management /x-pack/plugins/security_solution/public/detection_engine/fleet_integrations @elastic/security-detection-rule-management +/x-pack/plugins/security_solution/public/detection_engine/endpoint_exceptions @elastic/security-defend-workflows /x-pack/plugins/security_solution/public/detection_engine/rule_details_ui @elastic/security-detection-rule-management /x-pack/plugins/security_solution/public/detection_engine/rule_management @elastic/security-detection-rule-management /x-pack/plugins/security_solution/public/detection_engine/rule_management_ui @elastic/security-detection-rule-management diff --git a/x-pack/plugins/security_solution/public/detection_engine/endpoint_exceptions/endpoint_exceptions_viewer.tsx b/x-pack/plugins/security_solution/public/detection_engine/endpoint_exceptions/endpoint_exceptions_viewer.tsx new file mode 100644 index 0000000000000..5ee61735a4b89 --- /dev/null +++ b/x-pack/plugins/security_solution/public/detection_engine/endpoint_exceptions/endpoint_exceptions_viewer.tsx @@ -0,0 +1,54 @@ +/* + * 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 React, { memo } from 'react'; +import { EuiFlexGroup, EuiFlexItem } from '@elastic/eui'; +import { ExceptionListTypeEnum } from '@kbn/securitysolution-io-ts-list-types'; +import type { Rule } from '../rule_management/logic'; +import { useGetEndpointExceptionsUnavailableComponent } from './use_get_endpoint_exceptions_unavailablle_component'; +import { ExceptionsViewer } from '../rule_exceptions/components/all_exception_items_table'; + +const RULE_ENDPOINT_EXCEPTION_LIST_TYPE = [ExceptionListTypeEnum.ENDPOINT]; + +interface EndpointExceptionsViewerProps { + isViewReadOnly: boolean; + onRuleChange: () => void; + rule: Rule | null; + 'data-test-subj': string; +} + +export const EndpointExceptionsViewer = memo( + ({ + isViewReadOnly, + onRuleChange, + rule, + 'data-test-subj': dataTestSubj, + }: EndpointExceptionsViewerProps) => { + const EndpointExceptionsUnavailableComponent = useGetEndpointExceptionsUnavailableComponent(); + return ( + <> + {!EndpointExceptionsUnavailableComponent ? ( + + ) : ( + + + + + + )} + + ); + } +); + +EndpointExceptionsViewer.displayName = 'EndpointExceptionsViewer'; diff --git a/x-pack/plugins/security_solution/public/detection_engine/rule_details_ui/pages/rule_details/use_get_endpoint_exceptions_unavailablle_component.tsx b/x-pack/plugins/security_solution/public/detection_engine/endpoint_exceptions/use_get_endpoint_exceptions_unavailablle_component.tsx similarity index 84% rename from x-pack/plugins/security_solution/public/detection_engine/rule_details_ui/pages/rule_details/use_get_endpoint_exceptions_unavailablle_component.tsx rename to x-pack/plugins/security_solution/public/detection_engine/endpoint_exceptions/use_get_endpoint_exceptions_unavailablle_component.tsx index daa6f8095a85e..a3b0afb2caba5 100644 --- a/x-pack/plugins/security_solution/public/detection_engine/rule_details_ui/pages/rule_details/use_get_endpoint_exceptions_unavailablle_component.tsx +++ b/x-pack/plugins/security_solution/public/detection_engine/endpoint_exceptions/use_get_endpoint_exceptions_unavailablle_component.tsx @@ -6,7 +6,7 @@ */ import type React from 'react'; -import { useUpsellingComponent } from '../../../../common/hooks/use_upselling'; +import { useUpsellingComponent } from '../../common/hooks/use_upselling'; export const useGetEndpointExceptionsUnavailableComponent = (): React.ComponentType | null => { return useUpsellingComponent('ruleDetailsEndpointExceptions'); diff --git a/x-pack/plugins/security_solution/public/detection_engine/rule_details_ui/pages/rule_details/index.tsx b/x-pack/plugins/security_solution/public/detection_engine/rule_details_ui/pages/rule_details/index.tsx index c97bd423ea346..223593ef3e095 100644 --- a/x-pack/plugins/security_solution/public/detection_engine/rule_details_ui/pages/rule_details/index.tsx +++ b/x-pack/plugins/security_solution/public/detection_engine/rule_details_ui/pages/rule_details/index.tsx @@ -38,7 +38,7 @@ import { tableDefaults, TableId, } from '@kbn/securitysolution-data-table'; -import { useGetEndpointExceptionsUnavailableComponent } from './use_get_endpoint_exceptions_unavailablle_component'; +import { EndpointExceptionsViewer } from '../../../endpoint_exceptions/endpoint_exceptions_viewer'; import { AlertsTableComponent } from '../../../../detections/components/alerts_table'; import { GroupedAlertsTable } from '../../../../detections/components/alerts_table/alerts_grouping'; import { useDataTableFilters } from '../../../../common/hooks/use_data_table_filters'; @@ -150,8 +150,6 @@ const RULE_EXCEPTION_LIST_TYPES = [ ExceptionListTypeEnum.RULE_DEFAULT, ]; -const RULE_ENDPOINT_EXCEPTION_LIST_TYPE = [ExceptionListTypeEnum.ENDPOINT]; - /** * Need a 100% height here to account for the graph/analyze tool, which sets no explicit height parameters, but fills the available space. */ @@ -251,8 +249,6 @@ const RuleDetailsPageComponent: React.FC = ({ await startMlJobs(rule?.machine_learning_job_id); }, [rule, startMlJobs]); - const EndpointExceptionsUnavailableComponent = useGetEndpointExceptionsUnavailableComponent(); - const pageTabs = useRuleDetailsTabs({ rule, ruleId, isExistingRule, hasIndexRead }); const [isDeleteConfirmationVisible, showDeleteConfirmation, hideDeleteConfirmation] = @@ -783,21 +779,12 @@ const RuleDetailsPageComponent: React.FC = ({ - {!EndpointExceptionsUnavailableComponent ? ( - - ) : ( - - - - - - )} + From 3a8e9c23208a43d06ad61dc5a01d3a5e18091182 Mon Sep 17 00:00:00 2001 From: Ashokaditya Date: Mon, 2 Oct 2023 11:30:10 +0200 Subject: [PATCH 40/42] fix type check error refs elastic/kibana/pull/166781 --- .../detection_engine/rule_types/esql/create_esql_alert_type.ts | 1 + 1 file changed, 1 insertion(+) diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rule_types/esql/create_esql_alert_type.ts b/x-pack/plugins/security_solution/server/lib/detection_engine/rule_types/esql/create_esql_alert_type.ts index f14c1a83c2fe8..32658a6c76dde 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/rule_types/esql/create_esql_alert_type.ts +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/rule_types/esql/create_esql_alert_type.ts @@ -21,6 +21,7 @@ export const createEsqlAlertType = ( return { id: ESQL_RULE_TYPE_ID, name: 'ES|QL Rule', + category: 'securitySolution', validate: { params: { validate: (object: unknown) => { From 71f3a964f2fce83f60eb78c18198f5a94e6b31e9 Mon Sep 17 00:00:00 2001 From: Ashokaditya Date: Mon, 2 Oct 2023 12:26:37 +0200 Subject: [PATCH 41/42] use defined const --- .../detection_engine/rule_types/esql/create_esql_alert_type.ts | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rule_types/esql/create_esql_alert_type.ts b/x-pack/plugins/security_solution/server/lib/detection_engine/rule_types/esql/create_esql_alert_type.ts index 32658a6c76dde..438b8ea44d6e1 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/rule_types/esql/create_esql_alert_type.ts +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/rule_types/esql/create_esql_alert_type.ts @@ -7,6 +7,7 @@ import { validateNonExact } from '@kbn/securitysolution-io-ts-utils'; import { ESQL_RULE_TYPE_ID } from '@kbn/securitysolution-rules'; +import { DEFAULT_APP_CATEGORIES } from '@kbn/core-application-common'; import { SERVER_APP_ID } from '../../../../../common/constants'; import type { EsqlRuleParams } from '../../rule_schema'; @@ -21,7 +22,7 @@ export const createEsqlAlertType = ( return { id: ESQL_RULE_TYPE_ID, name: 'ES|QL Rule', - category: 'securitySolution', + category: DEFAULT_APP_CATEGORIES.security.id, validate: { params: { validate: (object: unknown) => { From c3eca1a17718272db96e329e23dbbd699127c15b Mon Sep 17 00:00:00 2001 From: Ashokaditya Date: Mon, 2 Oct 2023 13:59:51 +0200 Subject: [PATCH 42/42] fix merge error --- .../detection_engine/rule_types/esql/create_esql_alert_type.ts | 1 - 1 file changed, 1 deletion(-) diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rule_types/esql/create_esql_alert_type.ts b/x-pack/plugins/security_solution/server/lib/detection_engine/rule_types/esql/create_esql_alert_type.ts index e97a5ba91ae3d..18f752a4a0d97 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/rule_types/esql/create_esql_alert_type.ts +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/rule_types/esql/create_esql_alert_type.ts @@ -22,7 +22,6 @@ export const createEsqlAlertType = ( return { id: ESQL_RULE_TYPE_ID, name: 'ES|QL Rule', - category: DEFAULT_APP_CATEGORIES.security.id, validate: { params: { validate: (object: unknown) => {