diff --git a/x-pack/plugins/security_solution/common/endpoint/models/policy_config.ts b/x-pack/plugins/security_solution/common/endpoint/models/policy_config.ts index 1b3c3105121e5..eb5389586b2a8 100644 --- a/x-pack/plugins/security_solution/common/endpoint/models/policy_config.ts +++ b/x-pack/plugins/security_solution/common/endpoint/models/policy_config.ts @@ -200,7 +200,7 @@ export const policyFactoryWithoutPaidFeatures = ( ...policy.windows.popup, malware: { message: '', - enabled: true, + enabled: true, // disabling/configuring malware popup is a paid feature }, ransomware: { message: '', @@ -230,7 +230,7 @@ export const policyFactoryWithoutPaidFeatures = ( ...policy.mac.popup, malware: { message: '', - enabled: true, + enabled: true, // disabling/configuring malware popup is a paid feature }, memory_protection: { message: '', @@ -256,7 +256,7 @@ export const policyFactoryWithoutPaidFeatures = ( ...policy.linux.popup, malware: { message: '', - enabled: true, + enabled: true, // disabling/configuring malware popup is a paid feature }, memory_protection: { message: '', @@ -272,7 +272,7 @@ export const policyFactoryWithoutPaidFeatures = ( }; /** - * Strips paid features from an existing or new `PolicyConfig` for gold and below license + * Enables support for paid features for an existing or new `PolicyConfig` for platinum and above license */ export const policyFactoryWithSupportedFeatures = ( policy: PolicyConfig = policyFactory() diff --git a/x-pack/plugins/security_solution/common/endpoint/models/policy_config_helpers.test.ts b/x-pack/plugins/security_solution/common/endpoint/models/policy_config_helpers.test.ts new file mode 100644 index 0000000000000..d5ba6d03cd430 --- /dev/null +++ b/x-pack/plugins/security_solution/common/endpoint/models/policy_config_helpers.test.ts @@ -0,0 +1,175 @@ +/* + * 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 { PolicyConfig } from '../types'; +import { ProtectionModes } from '../types'; +import { policyFactory } from './policy_config'; +import { disableProtections } from './policy_config_helpers'; + +describe('Policy Config helpers', () => { + describe('disableProtections', () => { + it('disables all the protections in the default policy', () => { + expect(disableProtections(policyFactory())).toEqual(eventsOnlyPolicy); + }); + + it('does not enable supported fields', () => { + const defaultPolicy: PolicyConfig = policyFactory(); + + const notSupported: PolicyConfig['windows']['memory_protection'] = { + mode: ProtectionModes.off, + supported: false, + }; + + const inputPolicyWithoutSupportedProtections: PolicyConfig = { + ...defaultPolicy, + windows: { + ...defaultPolicy.windows, + memory_protection: notSupported, + behavior_protection: notSupported, + ransomware: notSupported, + }, + mac: { + ...defaultPolicy.mac, + memory_protection: notSupported, + behavior_protection: notSupported, + }, + linux: { + ...defaultPolicy.linux, + memory_protection: notSupported, + behavior_protection: notSupported, + }, + }; + + const expectedPolicyWithoutSupportedProtections: PolicyConfig = { + ...eventsOnlyPolicy, + windows: { + ...eventsOnlyPolicy.windows, + memory_protection: notSupported, + behavior_protection: notSupported, + ransomware: notSupported, + }, + mac: { + ...eventsOnlyPolicy.mac, + memory_protection: notSupported, + behavior_protection: notSupported, + }, + linux: { + ...eventsOnlyPolicy.linux, + memory_protection: notSupported, + behavior_protection: notSupported, + }, + }; + + const policy = disableProtections(inputPolicyWithoutSupportedProtections); + + expect(policy).toEqual(expectedPolicyWithoutSupportedProtections); + }); + + it('does not enable events', () => { + const defaultPolicy: PolicyConfig = policyFactory(); + + const windowsEvents: typeof defaultPolicy.windows.events = { + dll_and_driver_load: false, + dns: false, + file: false, + network: false, + process: false, + registry: false, + security: false, + }; + + const macEvents: typeof defaultPolicy.mac.events = { + file: false, + process: false, + network: false, + }; + + const linuxEvents: typeof defaultPolicy.linux.events = { + file: false, + process: false, + network: false, + session_data: false, + tty_io: false, + }; + + const expectedPolicy: PolicyConfig = { + ...eventsOnlyPolicy, + windows: { ...eventsOnlyPolicy.windows, events: { ...windowsEvents } }, + mac: { ...eventsOnlyPolicy.mac, events: { ...macEvents } }, + linux: { ...eventsOnlyPolicy.linux, events: { ...linuxEvents } }, + }; + + const inputPolicy = { + ...defaultPolicy, + windows: { ...defaultPolicy.windows, events: { ...windowsEvents } }, + mac: { ...defaultPolicy.mac, events: { ...macEvents } }, + linux: { ...defaultPolicy.linux, events: { ...linuxEvents } }, + }; + + expect(disableProtections(inputPolicy)).toEqual(expectedPolicy); + }); + }); +}); + +// This constant makes sure that if the type `PolicyConfig` is ever modified, +// the logic for disabling protections is also modified due to type check. +export const eventsOnlyPolicy: PolicyConfig = { + windows: { + events: { + dll_and_driver_load: true, + dns: true, + file: true, + network: true, + process: true, + registry: true, + security: true, + }, + malware: { mode: ProtectionModes.off, blocklist: false }, + ransomware: { mode: ProtectionModes.off, supported: true }, + memory_protection: { mode: ProtectionModes.off, supported: true }, + behavior_protection: { mode: ProtectionModes.off, supported: true }, + popup: { + malware: { message: '', enabled: false }, + ransomware: { message: '', enabled: false }, + memory_protection: { message: '', enabled: false }, + behavior_protection: { message: '', enabled: false }, + }, + logging: { file: 'info' }, + antivirus_registration: { enabled: false }, + attack_surface_reduction: { credential_hardening: { enabled: false } }, + }, + mac: { + events: { process: true, file: true, network: true }, + malware: { mode: ProtectionModes.off, blocklist: false }, + behavior_protection: { mode: ProtectionModes.off, supported: true }, + memory_protection: { mode: ProtectionModes.off, supported: true }, + popup: { + malware: { message: '', enabled: false }, + behavior_protection: { message: '', enabled: false }, + memory_protection: { message: '', enabled: false }, + }, + logging: { file: 'info' }, + }, + linux: { + events: { + process: true, + file: true, + network: true, + session_data: false, + tty_io: false, + }, + malware: { mode: ProtectionModes.off, blocklist: false }, + behavior_protection: { mode: ProtectionModes.off, supported: true }, + memory_protection: { mode: ProtectionModes.off, supported: true }, + popup: { + malware: { message: '', enabled: false }, + behavior_protection: { message: '', enabled: false }, + memory_protection: { message: '', enabled: false }, + }, + logging: { file: 'info' }, + }, +}; diff --git a/x-pack/plugins/security_solution/common/endpoint/models/policy_config_helpers.ts b/x-pack/plugins/security_solution/common/endpoint/models/policy_config_helpers.ts new file mode 100644 index 0000000000000..27a15e0be5401 --- /dev/null +++ b/x-pack/plugins/security_solution/common/endpoint/models/policy_config_helpers.ts @@ -0,0 +1,105 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import type { PolicyConfig } from '../types'; +import { ProtectionModes } from '../types'; + +/** + * Returns a copy of the passed `PolicyConfig` with all protections set to disabled. + * + * @param policy + * @returns + */ +export const disableProtections = (policy: PolicyConfig): PolicyConfig => { + const result = disableCommonProtections(policy); + + return { + ...result, + windows: { + ...result.windows, + ...getDisabledWindowsSpecificProtections(result), + popup: { + ...result.windows.popup, + ...getDisabledWindowsSpecificPopups(result), + }, + }, + }; +}; + +const disableCommonProtections = (policy: PolicyConfig) => { + let policyOutput = policy; + + for (const key in policyOutput) { + if (Object.prototype.hasOwnProperty.call(policyOutput, key)) { + const os = key as keyof PolicyConfig; + + policyOutput = { + ...policyOutput, + [os]: { + ...policyOutput[os], + ...getDisabledCommonProtectionsForOS(policyOutput, os), + popup: { + ...policyOutput[os].popup, + ...getDisabledCommonPopupsForOS(policyOutput, os), + }, + }, + }; + } + } + return policyOutput; +}; + +const getDisabledCommonProtectionsForOS = (policy: PolicyConfig, os: keyof PolicyConfig) => ({ + behavior_protection: { + ...policy[os].behavior_protection, + mode: ProtectionModes.off, + }, + memory_protection: { + ...policy[os].memory_protection, + mode: ProtectionModes.off, + }, + malware: { + ...policy[os].malware, + blocklist: false, + mode: ProtectionModes.off, + }, +}); + +const getDisabledCommonPopupsForOS = (policy: PolicyConfig, os: keyof PolicyConfig) => ({ + behavior_protection: { + ...policy[os].popup.behavior_protection, + enabled: false, + }, + malware: { + ...policy[os].popup.malware, + enabled: false, + }, + memory_protection: { + ...policy[os].popup.memory_protection, + enabled: false, + }, +}); + +const getDisabledWindowsSpecificProtections = (policy: PolicyConfig) => ({ + ransomware: { + ...policy.windows.ransomware, + mode: ProtectionModes.off, + }, + attack_surface_reduction: { + ...policy.windows.attack_surface_reduction, + credential_hardening: { + enabled: false, + }, + }, +}); + +const getDisabledWindowsSpecificPopups = (policy: PolicyConfig) => ({ + ransomware: { + ...policy.windows.popup.ransomware, + enabled: false, + }, +}); diff --git a/x-pack/plugins/security_solution/server/fleet_integration/fleet_integration.test.ts b/x-pack/plugins/security_solution/server/fleet_integration/fleet_integration.test.ts index 17d1d8f9a9295..44f9a8bafae3d 100644 --- a/x-pack/plugins/security_solution/server/fleet_integration/fleet_integration.test.ts +++ b/x-pack/plugins/security_solution/server/fleet_integration/fleet_integration.test.ts @@ -47,6 +47,7 @@ import type { DeletePackagePoliciesResponse } from '@kbn/fleet-plugin/common'; import { createMockPolicyData } from '../endpoint/services/feature_usage/mocks'; import { ALL_ENDPOINT_ARTIFACT_LIST_IDS } from '../../common/endpoint/service/artifacts/constants'; import { ENDPOINT_EVENT_FILTERS_LIST_ID } from '@kbn/securitysolution-list-constants'; +import { disableProtections } from '../../common/endpoint/models/policy_config_helpers'; jest.mock('uuid', () => ({ v4: (): string => 'NEW_UUID', @@ -89,7 +90,7 @@ describe('ingest_integration tests ', () => { streams: [], config: { integration_config: {}, - policy: { value: policyFactory() }, + policy: { value: disableProtections(policyFactory()) }, artifact_manifest: { value: manifest }, }, }); diff --git a/x-pack/plugins/security_solution/server/fleet_integration/handlers/create_default_policy.test.ts b/x-pack/plugins/security_solution/server/fleet_integration/handlers/create_default_policy.test.ts index 73440a310b625..916d3adbe96ae 100644 --- a/x-pack/plugins/security_solution/server/fleet_integration/handlers/create_default_policy.test.ts +++ b/x-pack/plugins/security_solution/server/fleet_integration/handlers/create_default_policy.test.ts @@ -9,11 +9,9 @@ import type { ILicense } from '@kbn/licensing-plugin/common/types'; import { licenseMock } from '@kbn/licensing-plugin/common/licensing.mock'; import { LicenseService } from '../../../common/license'; import { createDefaultPolicy } from './create_default_policy'; +import { ProtectionModes } from '../../../common/endpoint/types'; import type { PolicyConfig } from '../../../common/endpoint/types'; -import { - policyFactory as policyConfigFactory, - policyFactoryWithoutPaidFeatures as policyConfigFactoryWithoutPaidFeatures, -} from '../../../common/endpoint/models/policy_config'; +import { policyFactory } from '../../../common/endpoint/models/policy_config'; import type { AnyPolicyCreateConfig, PolicyCreateCloudConfig, @@ -37,16 +35,52 @@ describe('Create Default Policy tests ', () => { licenseEmitter.next(Platinum); // set license level to platinum }); describe('When no config is set', () => { - it('Should return the Default Policy Config when license is at least platinum', () => { + it('Should return PolicyConfig for events only when license is at least platinum', () => { + const defaultPolicy = policyFactory(); + const policy = createDefaultPolicyCallback(undefined); - expect(policy).toEqual(policyConfigFactory()); + + // events are the same + expect(policy.windows.events).toEqual(defaultPolicy.windows.events); + expect(policy.linux.events).toEqual(defaultPolicy.linux.events); + expect(policy.mac.events).toEqual(defaultPolicy.mac.events); + + // check some of the protections to be disabled + const disabledButSupported = { mode: ProtectionModes.off, supported: true }; + expect(policy.windows.behavior_protection).toEqual(disabledButSupported); + expect(policy.mac.memory_protection).toEqual(disabledButSupported); + expect(policy.linux.behavior_protection).toEqual(disabledButSupported); + + // malware popups should be disabled + expect(policy.windows.popup.malware.enabled).toBeFalsy(); + expect(policy.mac.popup.malware.enabled).toBeFalsy(); + expect(policy.linux.popup.malware.enabled).toBeFalsy(); }); - it('Should return the Default Policy Config without paid features when license is below platinum', () => { + + it('Should return PolicyConfig for events only without paid features when license is below platinum', () => { + const defaultPolicy = policyFactory(); licenseEmitter.next(Gold); + const policy = createDefaultPolicyCallback(undefined); - expect(policy).toEqual(policyConfigFactoryWithoutPaidFeatures()); + + // events are the same + expect(policy.windows.events).toEqual(defaultPolicy.windows.events); + expect(policy.linux.events).toEqual(defaultPolicy.linux.events); + expect(policy.mac.events).toEqual(defaultPolicy.mac.events); + + // check some of the protections to be disabled and unsupported + const disabledAndUnsupported = { mode: ProtectionModes.off, supported: false }; + expect(policy.windows.behavior_protection).toEqual(disabledAndUnsupported); + expect(policy.mac.memory_protection).toEqual(disabledAndUnsupported); + expect(policy.linux.behavior_protection).toEqual(disabledAndUnsupported); + + // malware popups are enabled on unpaid license + expect(policy.windows.popup.malware.enabled).toBeTruthy(); + expect(policy.mac.popup.malware.enabled).toBeTruthy(); + expect(policy.linux.popup.malware.enabled).toBeTruthy(); }); }); + describe('When endpoint config is set', () => { const createEndpointConfig = ( endpointConfig: PolicyCreateEndpointConfig['endpointConfig'] @@ -112,8 +146,8 @@ describe('Create Default Policy tests ', () => { it('Should return the default config when preset is EDR Complete', () => { const config = createEndpointConfig({ preset: 'EDRComplete' }); const policy = createDefaultPolicyCallback(config); - const policyFactory = policyConfigFactory(); - expect(policy).toMatchObject(policyFactory); + const defaultPolicy = policyFactory(); + expect(policy).toMatchObject(defaultPolicy); }); }); describe('When cloud config is set', () => { diff --git a/x-pack/plugins/security_solution/server/fleet_integration/handlers/create_default_policy.ts b/x-pack/plugins/security_solution/server/fleet_integration/handlers/create_default_policy.ts index a2da989a9c08d..9e598198ba9a0 100644 --- a/x-pack/plugins/security_solution/server/fleet_integration/handlers/create_default_policy.ts +++ b/x-pack/plugins/security_solution/server/fleet_integration/handlers/create_default_policy.ts @@ -10,11 +10,15 @@ import { policyFactoryWithoutPaidFeatures as policyConfigFactoryWithoutPaidFeatures, } from '../../../common/endpoint/models/policy_config'; import type { LicenseService } from '../../../common/license/license'; -import { isAtLeast } from '../../../common/license/license'; import { ProtectionModes } from '../../../common/endpoint/types'; import type { PolicyConfig } from '../../../common/endpoint/types'; import type { AnyPolicyCreateConfig, PolicyCreateEndpointConfig } from '../types'; -import { ENDPOINT_CONFIG_PRESET_EDR_ESSENTIAL, ENDPOINT_CONFIG_PRESET_NGAV } from '../constants'; +import { + ENDPOINT_CONFIG_PRESET_EDR_COMPLETE, + ENDPOINT_CONFIG_PRESET_EDR_ESSENTIAL, + ENDPOINT_CONFIG_PRESET_NGAV, +} from '../constants'; +import { disableProtections } from '../../../common/endpoint/models/policy_config_helpers'; /** * Create the default endpoint policy based on the current license and configuration type @@ -23,19 +27,21 @@ export const createDefaultPolicy = ( licenseService: LicenseService, config: AnyPolicyCreateConfig | undefined ): PolicyConfig => { - const policy = isAtLeast(licenseService.getLicenseInformation(), 'platinum') - ? policyConfigFactory() - : policyConfigFactoryWithoutPaidFeatures(); + const factoryPolicy = policyConfigFactory(); - if (config?.type === 'cloud') { - return getCloudPolicyConfig(policy); - } + const defaultPolicyPerType = + config?.type === 'cloud' + ? getCloudPolicyConfig(factoryPolicy) + : getEndpointPolicyWithIntegrationConfig(factoryPolicy, config); - return getEndpointPolicyWithIntegrationConfig(policy, config); + // Apply license limitations in the final step, so it's not overriden (see malware popup) + return licenseService.isPlatinumPlus() + ? defaultPolicyPerType + : policyConfigFactoryWithoutPaidFeatures(defaultPolicyPerType); }; /** - * Set all keys of the given object to false + * Create a copy of an object with all keys set to false */ const falsyObjectKeys = >(obj: T): T => { return Object.keys(obj).reduce((accumulator, key) => { @@ -43,6 +49,14 @@ const falsyObjectKeys = >(obj: T): T => { }, {} as T); }; +const getEndpointPolicyConfigPreset = (config: PolicyCreateEndpointConfig | undefined) => { + const isNGAV = config?.endpointConfig?.preset === ENDPOINT_CONFIG_PRESET_NGAV; + const isEDREssential = config?.endpointConfig?.preset === ENDPOINT_CONFIG_PRESET_EDR_ESSENTIAL; + const isEDRComplete = config?.endpointConfig?.preset === ENDPOINT_CONFIG_PRESET_EDR_COMPLETE; + + return { isNGAV, isEDREssential, isEDRComplete }; +}; + /** * Retrieve policy for endpoint based on the preset selected in the endpoint integration config */ @@ -50,9 +64,11 @@ const getEndpointPolicyWithIntegrationConfig = ( policy: PolicyConfig, config: PolicyCreateEndpointConfig | undefined ): PolicyConfig => { - const isEDREssential = config?.endpointConfig?.preset === ENDPOINT_CONFIG_PRESET_EDR_ESSENTIAL; + const { isNGAV, isEDREssential, isEDRComplete } = getEndpointPolicyConfigPreset(config); - if (config?.endpointConfig?.preset === ENDPOINT_CONFIG_PRESET_NGAV || isEDREssential) { + if (isEDRComplete) { + return policy; + } else if (isNGAV || isEDREssential) { const events = { process: true, file: isEDREssential, @@ -85,7 +101,8 @@ const getEndpointPolicyWithIntegrationConfig = ( }; } - return policy; + // data collection by default + return disableProtections(policy); }; /**