[Security Solution] add feature usage notifications (#124766)

This commit is contained in:
Joey F. Poon 2022-02-11 17:20:09 -06:00 committed by GitHub
parent 17a997cccb
commit 93aa4f4184
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
21 changed files with 376 additions and 49 deletions

View file

@ -10,3 +10,10 @@ export enum OperatingSystem {
MAC = 'macos',
WINDOWS = 'windows',
}
// PolicyConfig uses mac instead of macos
export enum PolicyOperatingSystem {
windows = 'windows',
mac = 'mac',
linux = 'linux',
}

View file

@ -109,12 +109,6 @@ export interface PolicyArtifactsState {
removeList: AsyncResourceState<PolicyRemoveTrustedApps>;
}
export enum OS {
windows = 'windows',
mac = 'mac',
linux = 'linux',
}
export interface PolicyDetailsArtifactsPageListLocationParams {
page_index: number;
page_size: number;

View file

@ -8,8 +8,11 @@
import React from 'react';
import { i18n } from '@kbn/i18n';
import { EuiCheckbox, EuiSpacer, EuiText, htmlIdGenerator } from '@elastic/eui';
import { OperatingSystem, UIPolicyConfig } from '../../../../../../../common/endpoint/types';
import { OS } from '../../../types';
import {
OperatingSystem,
PolicyOperatingSystem,
UIPolicyConfig,
} from '../../../../../../../common/endpoint/types';
import { ConfigForm, ConfigFormHeading } from '../../components/config_form';
const OPERATING_SYSTEM_TO_TEST_SUBJ: { [K in OperatingSystem]: string } = {
@ -19,9 +22,9 @@ const OPERATING_SYSTEM_TO_TEST_SUBJ: { [K in OperatingSystem]: string } = {
};
interface OperatingSystemToOsMap {
[OperatingSystem.WINDOWS]: OS.windows;
[OperatingSystem.LINUX]: OS.linux;
[OperatingSystem.MAC]: OS.mac;
[OperatingSystem.WINDOWS]: PolicyOperatingSystem.windows;
[OperatingSystem.LINUX]: PolicyOperatingSystem.linux;
[OperatingSystem.MAC]: PolicyOperatingSystem.mac;
}
export type ProtectionField<T extends OperatingSystem> =

View file

@ -9,8 +9,12 @@ import React from 'react';
import { i18n } from '@kbn/i18n';
import { EuiCallOut, EuiSpacer } from '@elastic/eui';
import { FormattedMessage } from '@kbn/i18n-react';
import { Immutable, OperatingSystem } from '../../../../../../../common/endpoint/types';
import { BehaviorProtectionOSes, OS } from '../../../types';
import {
Immutable,
OperatingSystem,
PolicyOperatingSystem,
} from '../../../../../../../common/endpoint/types';
import { BehaviorProtectionOSes } from '../../../types';
import { ConfigForm } from '../../components/config_form';
import { RadioButtons } from '../components/radio_buttons';
import { UserNotification } from '../components/user_notification';
@ -23,7 +27,11 @@ import { SecurityPageName } from '../../../../../../app/types';
* which will configure for all relevant OSes.
*/
export const BehaviorProtection = React.memo(() => {
const OSes: Immutable<BehaviorProtectionOSes[]> = [OS.windows, OS.mac, OS.linux];
const OSes: Immutable<BehaviorProtectionOSes[]> = [
PolicyOperatingSystem.windows,
PolicyOperatingSystem.mac,
PolicyOperatingSystem.linux,
];
const protection = 'behavior_protection';
const protectionLabel = i18n.translate(
'xpack.securitySolution.endpoint.policy.protections.behavior',

View file

@ -11,8 +11,12 @@ import { FormattedMessage } from '@kbn/i18n-react';
import { EuiCallOut, EuiSpacer } from '@elastic/eui';
import { APP_UI_ID } from '../../../../../../../common/constants';
import { SecurityPageName } from '../../../../../../app/types';
import { Immutable, OperatingSystem } from '../../../../../../../common/endpoint/types';
import { MalwareProtectionOSes, OS } from '../../../types';
import {
Immutable,
OperatingSystem,
PolicyOperatingSystem,
} from '../../../../../../../common/endpoint/types';
import { MalwareProtectionOSes } from '../../../types';
import { ConfigForm } from '../../components/config_form';
import { LinkToApp } from '../../../../../../common/components/endpoint/link_to_app';
import { useLicense } from '../../../../../../common/hooks/use_license';
@ -24,7 +28,11 @@ import { ProtectionSwitch } from '../components/protection_switch';
* which will configure for all relevant OSes.
*/
export const MalwareProtections = React.memo(() => {
const OSes: Immutable<MalwareProtectionOSes[]> = [OS.windows, OS.mac, OS.linux];
const OSes: Immutable<MalwareProtectionOSes[]> = [
PolicyOperatingSystem.windows,
PolicyOperatingSystem.mac,
PolicyOperatingSystem.linux,
];
const protection = 'malware';
const protectionLabel = i18n.translate(
'xpack.securitySolution.endpoint.policy.protections.malware',

View file

@ -11,8 +11,12 @@ import { FormattedMessage } from '@kbn/i18n-react';
import { EuiCallOut, EuiSpacer } from '@elastic/eui';
import { APP_UI_ID } from '../../../../../../../common/constants';
import { SecurityPageName } from '../../../../../../app/types';
import { Immutable, OperatingSystem } from '../../../../../../../common/endpoint/types';
import { MemoryProtectionOSes, OS } from '../../../types';
import {
Immutable,
OperatingSystem,
PolicyOperatingSystem,
} from '../../../../../../../common/endpoint/types';
import { MemoryProtectionOSes } from '../../../types';
import { ConfigForm } from '../../components/config_form';
import { LinkToApp } from '../../../../../../common/components/endpoint/link_to_app';
import { RadioButtons } from '../components/radio_buttons';
@ -23,7 +27,11 @@ import { ProtectionSwitch } from '../components/protection_switch';
* which will configure for all relevant OSes.
*/
export const MemoryProtection = React.memo(() => {
const OSes: Immutable<MemoryProtectionOSes[]> = [OS.windows, OS.mac, OS.linux];
const OSes: Immutable<MemoryProtectionOSes[]> = [
PolicyOperatingSystem.windows,
PolicyOperatingSystem.mac,
PolicyOperatingSystem.linux,
];
const protection = 'memory_protection';
const protectionLabel = i18n.translate(
'xpack.securitySolution.endpoint.policy.protections.memory',

View file

@ -11,8 +11,12 @@ import { FormattedMessage } from '@kbn/i18n-react';
import { EuiCallOut, EuiSpacer } from '@elastic/eui';
import { APP_UI_ID } from '../../../../../../../common/constants';
import { SecurityPageName } from '../../../../../../app/types';
import { Immutable, OperatingSystem } from '../../../../../../../common/endpoint/types';
import { RansomwareProtectionOSes, OS } from '../../../types';
import {
Immutable,
OperatingSystem,
PolicyOperatingSystem,
} from '../../../../../../../common/endpoint/types';
import { RansomwareProtectionOSes } from '../../../types';
import { ConfigForm } from '../../components/config_form';
import { LinkToApp } from '../../../../../../common/components/endpoint/link_to_app';
import { RadioButtons } from '../components/radio_buttons';
@ -23,7 +27,7 @@ import { ProtectionSwitch } from '../components/protection_switch';
* which will configure for all relevant OSes.
*/
export const Ransomware = React.memo(() => {
const OSes: Immutable<RansomwareProtectionOSes[]> = [OS.windows];
const OSes: Immutable<RansomwareProtectionOSes[]> = [PolicyOperatingSystem.windows];
const protection = 'ransomware';
const protectionLabel = i18n.translate(
'xpack.securitySolution.endpoint.policy.protections.ransomware',

View file

@ -43,6 +43,7 @@ import type { ListsServerExtensionRegistrar } from '../../../lists/server';
import { registerListsPluginEndpointExtensionPoints } from '../lists_integration';
import { EndpointAuthz } from '../../common/endpoint/types/authz';
import { calculateEndpointAuthz } from '../../common/endpoint/service/authz';
import { FeatureUsageService } from './services/feature_usage/service';
export interface EndpointAppContextServiceSetupContract {
securitySolutionRequestContextFactory: IRequestContextFactory;
@ -67,6 +68,7 @@ export type EndpointAppContextServiceStartContract = Partial<
licenseService: LicenseService;
exceptionListsClient: ExceptionListClient | undefined;
cases: CasesPluginStartContract | undefined;
featureUsageService: FeatureUsageService;
};
/**
@ -92,27 +94,47 @@ export class EndpointAppContextService {
this.security = dependencies.security;
this.fleetServicesFactory = dependencies.endpointFleetServicesFactory;
if (dependencies.registerIngestCallback && dependencies.manifestManager) {
dependencies.registerIngestCallback(
if (
dependencies.registerIngestCallback &&
dependencies.manifestManager &&
dependencies.packagePolicyService
) {
const {
registerIngestCallback,
logger,
manifestManager,
alerting,
licenseService,
exceptionListsClient,
featureUsageService,
endpointMetadataService,
} = dependencies;
registerIngestCallback(
'packagePolicyCreate',
getPackagePolicyCreateCallback(
dependencies.logger,
dependencies.manifestManager,
logger,
manifestManager,
this.setupDependencies.securitySolutionRequestContextFactory,
dependencies.alerting,
dependencies.licenseService,
dependencies.exceptionListsClient
alerting,
licenseService,
exceptionListsClient
)
);
dependencies.registerIngestCallback(
registerIngestCallback(
'packagePolicyUpdate',
getPackagePolicyUpdateCallback(dependencies.logger, dependencies.licenseService)
getPackagePolicyUpdateCallback(
logger,
licenseService,
featureUsageService,
endpointMetadataService
)
);
dependencies.registerIngestCallback(
registerIngestCallback(
'postPackagePolicyDelete',
getPackagePolicyDeleteCallback(dependencies.exceptionListsClient)
getPackagePolicyDeleteCallback(exceptionListsClient)
);
}
@ -209,4 +231,11 @@ export class EndpointAppContextService {
}
return this.startDependencies.exceptionListsClient;
}
public getFeatureUsageService(): FeatureUsageService {
if (this.startDependencies == null) {
throw new EndpointAppContentServicesNotStartedError();
}
return this.startDependencies.featureUsageService;
}
}

View file

@ -46,6 +46,7 @@ import { createEndpointMetadataServiceTestContextMock } from './services/metadat
import type { EndpointAuthz } from '../../common/endpoint/types/authz';
import { EndpointFleetServicesFactory } from './services/fleet';
import { createLicenseServiceMock } from '../../common/license/mocks';
import { createFeatureUsageServiceMock } from './services/feature_usage';
/**
* Creates a mocked EndpointAppContext.
@ -157,6 +158,7 @@ export const createMockEndpointAppContextServiceStartContract =
cases: {
getCasesClientWithRequest: jest.fn(async () => casesClientMock),
},
featureUsageService: createFeatureUsageServiceMock(),
};
};

View file

@ -111,6 +111,7 @@ export const isolationRequestHandler = function (
SecuritySolutionRequestHandlerContext
> {
return async (context, req, res) => {
endpointContext.service.getFeatureUsageService().notifyUsage('HOST_ISOLATION');
const user = endpointContext.service.security?.authc.getCurrentUser(req);
// fetch the Agent IDs to send the commands to

View file

@ -0,0 +1,11 @@
/*
* 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 { FeatureUsageService } from './service';
export { createFeatureUsageServiceMock, createMockPolicyData } from './mocks';
export const featureUsageService = new FeatureUsageService();

View file

@ -0,0 +1,45 @@
/*
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
* or more contributor license agreements. Licensed under the Elastic License
* 2.0; you may not use this file except in compliance with the Elastic License
* 2.0.
*/
import { FeatureUsageService } from './service';
import { PolicyData } from '../../../../common/endpoint/types';
export function createFeatureUsageServiceMock() {
return {
setup: jest.fn(),
start: jest.fn(),
notifyUsage: jest.fn(),
} as unknown as jest.Mocked<FeatureUsageService>;
}
export function createMockPolicyData() {
return {
inputs: [
{
config: {
policy: {
value: {
windows: {
ransomware: { mode: 'off' },
memory_protection: { mode: 'off' },
behavior_protection: { mode: 'off' },
},
mac: {
memory_protection: { mode: 'off' },
behavior_protection: { mode: 'off' },
},
linux: {
memory_protection: { mode: 'off' },
behavior_protection: { mode: 'off' },
},
},
},
},
},
],
} as PolicyData;
}

View file

@ -0,0 +1,44 @@
/*
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
* or more contributor license agreements. Licensed under the Elastic License
* 2.0; you may not use this file except in compliance with the Elastic License
* 2.0.
*/
import { Values } from '@kbn/utility-types';
import { LicensingPluginSetup, LicensingPluginStart } from '../../../../../licensing/server';
const FEATURES = {
HOST_ISOLATION: 'Host isolation',
HOST_ISOLATION_EXCEPTION: 'Host isolation exception',
HOST_ISOLATION_EXCEPTION_BY_POLICY: 'Host isolation exception by policy',
TRUSTED_APP_BY_POLICY: 'Trusted app by policy',
EVENT_FILTERS_BY_POLICY: 'Event filters by policy',
RANSOMWARE_PROTECTION: 'Ransomeware protection',
MEMORY_THREAT_PROTECTION: 'Memory threat protection',
BEHAVIOR_PROTECTION: 'Behavior protection',
} as const;
export type FeatureKeys = keyof typeof FEATURES;
export class FeatureUsageService {
private licensingPluginStart: LicensingPluginStart | undefined;
private get notify(): (featureName: Values<typeof FEATURES>) => void {
return this.licensingPluginStart?.featureUsage.notifyUsage || function () {};
}
public setup(licensingPluginSetup: LicensingPluginSetup): void {
Object.values(FEATURES).map((featureValue) =>
licensingPluginSetup.featureUsage.register(featureValue, 'platinum')
);
}
public start(licensingPluginStart: LicensingPluginStart): void {
this.licensingPluginStart = licensingPluginStart;
}
public notifyUsage(featureKey: FeatureKeys): void {
this.notify(FEATURES[featureKey]);
}
}

View file

@ -42,6 +42,7 @@ import { NewPackagePolicy } from '../../../fleet/common/types/models';
import { ManifestSchema } from '../../common/endpoint/schema/manifest';
import { DeletePackagePoliciesResponse } from '../../../fleet/common';
import { ARTIFACT_LISTS_IDS_TO_REMOVE } from './handlers/remove_policy_from_artifacts';
import { createMockPolicyData } from '../endpoint/services/feature_usage';
describe('ingest_integration tests ', () => {
let endpointAppContextMock: EndpointAppContextServiceStartContract;
@ -61,11 +62,16 @@ describe('ingest_integration tests ', () => {
licenseEmitter = new Subject();
licenseService = new LicenseService();
licenseService.start(licenseEmitter);
jest
.spyOn(endpointAppContextMock.endpointMetadataService, 'getFleetEndpointPackagePolicy')
.mockResolvedValue(createMockPolicyData());
});
afterEach(() => {
licenseService.stop();
licenseEmitter.complete();
jest.clearAllMocks();
});
describe('package policy init callback (atifacts manifest initialisation tests)', () => {
@ -248,7 +254,12 @@ describe('ingest_integration tests ', () => {
it('returns an error if paid features are turned on in the policy', async () => {
const mockPolicy = policyFactory(); // defaults with paid features on
const logger = loggingSystemMock.create().get('ingest_integration.test');
const callback = getPackagePolicyUpdateCallback(logger, licenseService);
const callback = getPackagePolicyUpdateCallback(
logger,
licenseService,
endpointAppContextMock.featureUsageService,
endpointAppContextMock.endpointMetadataService
);
const policyConfig = generator.generatePolicyPackagePolicy();
policyConfig.inputs[0]!.config!.policy.value = mockPolicy;
await expect(() => callback(policyConfig, ctx, req)).rejects.toThrow(
@ -259,7 +270,12 @@ describe('ingest_integration tests ', () => {
const mockPolicy = policyFactoryWithoutPaidFeatures();
mockPolicy.windows.malware.mode = ProtectionModes.detect;
const logger = loggingSystemMock.create().get('ingest_integration.test');
const callback = getPackagePolicyUpdateCallback(logger, licenseService);
const callback = getPackagePolicyUpdateCallback(
logger,
licenseService,
endpointAppContextMock.featureUsageService,
endpointAppContextMock.endpointMetadataService
);
const policyConfig = generator.generatePolicyPackagePolicy();
policyConfig.inputs[0]!.config!.policy.value = mockPolicy;
const updatedPolicyConfig = await callback(policyConfig, ctx, req);
@ -275,7 +291,12 @@ describe('ingest_integration tests ', () => {
const mockPolicy = policyFactory();
mockPolicy.windows.popup.malware.message = 'paid feature';
const logger = loggingSystemMock.create().get('ingest_integration.test');
const callback = getPackagePolicyUpdateCallback(logger, licenseService);
const callback = getPackagePolicyUpdateCallback(
logger,
licenseService,
endpointAppContextMock.featureUsageService,
endpointAppContextMock.endpointMetadataService
);
const policyConfig = generator.generatePolicyPackagePolicy();
policyConfig.inputs[0]!.config!.policy.value = mockPolicy;
const updatedPolicyConfig = await callback(policyConfig, ctx, req);

View file

@ -25,6 +25,9 @@ import { createPolicyArtifactManifest } from './handlers/create_policy_artifact_
import { createDefaultPolicy } from './handlers/create_default_policy';
import { validatePolicyAgainstLicense } from './handlers/validate_policy_against_license';
import { removePolicyFromArtifacts } from './handlers/remove_policy_from_artifacts';
import { FeatureUsageService } from '../endpoint/services/feature_usage/service';
import { EndpointMetadataService } from '../endpoint/services/metadata';
import { notifyProtectionFeatureUsage } from './notify_protection_feature_usage';
const isEndpointPackagePolicy = <T extends { package?: { name: string } }>(
packagePolicy: T
@ -105,7 +108,9 @@ export const getPackagePolicyCreateCallback = (
export const getPackagePolicyUpdateCallback = (
logger: Logger,
licenseService: LicenseService
licenseService: LicenseService,
featureUsageService: FeatureUsageService,
endpointMetadataService: EndpointMetadataService
): PutPackagePolicyUpdateCallback => {
return async (
newPackagePolicy: NewPackagePolicy
@ -125,6 +130,8 @@ export const getPackagePolicyUpdateCallback = (
logger
);
notifyProtectionFeatureUsage(newPackagePolicy, featureUsageService, endpointMetadataService);
return newPackagePolicy;
};
};

View file

@ -0,0 +1,86 @@
/*
* 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 { NewPackagePolicy } from '../../../fleet/common';
import { PolicyConfig, PolicyOperatingSystem, ProtectionModes } from '../../common/endpoint/types';
import { EndpointMetadataService } from '../endpoint/services/metadata';
import { FeatureUsageService } from '../endpoint/services/feature_usage/service';
const OS_KEYS = Object.values(PolicyOperatingSystem);
const PROTECTION_KEYS = ['memory_protection', 'behavior_protection'] as const;
function isNewlyEnabled(current: ProtectionModes, next: ProtectionModes) {
if (current === 'off' && (next === 'prevent' || next === 'detect')) {
return true;
}
return false;
}
function notifyProtection(type: string, featureUsageService: FeatureUsageService) {
switch (type) {
case 'ransomware':
featureUsageService.notifyUsage('RANSOMWARE_PROTECTION');
return;
case 'memory_protection':
featureUsageService.notifyUsage('MEMORY_THREAT_PROTECTION');
return;
case 'behavior_protection':
featureUsageService.notifyUsage('BEHAVIOR_PROTECTION');
}
}
export async function notifyProtectionFeatureUsage(
newPackagePolicy: NewPackagePolicy,
featureUsageService: FeatureUsageService,
endpointMetadataService: EndpointMetadataService
) {
if (
!newPackagePolicy?.id ||
!newPackagePolicy?.inputs ||
!newPackagePolicy.inputs[0]?.config?.policy?.value
) {
return;
}
const newPolicyConfig = newPackagePolicy.inputs[0].config?.policy?.value as PolicyConfig;
// function is only called on policy update, we need to fetch the current policy
// to compare whether the updated policy is newly enabling protections
const currentPackagePolicy = await endpointMetadataService.getFleetEndpointPackagePolicy(
newPackagePolicy.id as string
);
const currentPolicyConfig = currentPackagePolicy.inputs[0].config.policy.value;
// ransomware is windows only
if (
isNewlyEnabled(
currentPolicyConfig.windows.ransomware.mode,
newPolicyConfig.windows.ransomware.mode
)
) {
notifyProtection('ransomware', featureUsageService);
}
PROTECTION_KEYS.forEach((protectionKey) => {
// only notify once per protection since protection can't be configured per os
let notified = false;
OS_KEYS.forEach((osKey) => {
if (
!notified &&
isNewlyEnabled(
currentPolicyConfig[osKey][protectionKey].mode,
newPolicyConfig[osKey][protectionKey].mode
)
) {
notifyProtection(protectionKey, featureUsageService);
notified = true;
}
});
});
}

View file

@ -27,20 +27,33 @@ export const getExceptionsPreCreateItemHandler = (
// Validate trusted apps
if (TrustedAppValidator.isTrustedApp(data)) {
return new TrustedAppValidator(endpointAppContext, request).validatePreCreateItem(data);
const trustedAppValidator = new TrustedAppValidator(endpointAppContext, request);
const validatedItem = await trustedAppValidator.validatePreCreateItem(data);
trustedAppValidator.notifyFeatureUsage(data, 'TRUSTED_APP_BY_POLICY');
return validatedItem;
}
// Validate event filter
if (EventFilterValidator.isEventFilter(data)) {
return new EventFilterValidator(endpointAppContext, request).validatePreCreateItem(data);
const eventFilterValidator = new EventFilterValidator(endpointAppContext, request);
const validatedItem = await eventFilterValidator.validatePreCreateItem(data);
eventFilterValidator.notifyFeatureUsage(data, 'EVENT_FILTERS_BY_POLICY');
return validatedItem;
}
// Validate host isolation
if (HostIsolationExceptionsValidator.isHostIsolationException(data)) {
return new HostIsolationExceptionsValidator(
const hostIsolationExceptionsValidator = new HostIsolationExceptionsValidator(
endpointAppContext,
request
).validatePreCreateItem(data);
);
const validatedItem = await hostIsolationExceptionsValidator.validatePreCreateItem(data);
hostIsolationExceptionsValidator.notifyFeatureUsage(
data,
'HOST_ISOLATION_EXCEPTION_BY_POLICY'
);
hostIsolationExceptionsValidator.notifyFeatureUsage(data, 'HOST_ISOLATION_EXCEPTION');
return validatedItem;
}
return data;

View file

@ -10,6 +10,7 @@ import {
UpdateExceptionListItemOptions,
} from '../../../../../lists/server';
import { EndpointAppContextService } from '../../../endpoint/endpoint_app_context_services';
import { ExceptionItemLikeOptions } from '../types';
import {
EventFilterValidator,
TrustedAppValidator,
@ -44,26 +45,45 @@ export const getExceptionsPreUpdateItemHandler = (
// Validate Trusted Applications
if (TrustedAppValidator.isTrustedApp({ listId })) {
return new TrustedAppValidator(endpointAppContextService, request).validatePreUpdateItem(
data,
currentSavedItem
const trustedAppValidator = new TrustedAppValidator(endpointAppContextService, request);
const validatedItem = await trustedAppValidator.validatePreUpdateItem(data, currentSavedItem);
trustedAppValidator.notifyFeatureUsage(
data as ExceptionItemLikeOptions,
'TRUSTED_APP_BY_POLICY'
);
return validatedItem;
}
// Validate Event Filters
if (EventFilterValidator.isEventFilter({ listId })) {
return new EventFilterValidator(endpointAppContextService, request).validatePreUpdateItem(
const eventFilterValidator = new EventFilterValidator(endpointAppContextService, request);
const validatedItem = await eventFilterValidator.validatePreUpdateItem(
data,
currentSavedItem
);
eventFilterValidator.notifyFeatureUsage(
data as ExceptionItemLikeOptions,
'EVENT_FILTERS_BY_POLICY'
);
return validatedItem;
}
// Validate host isolation
if (HostIsolationExceptionsValidator.isHostIsolationException({ listId })) {
return new HostIsolationExceptionsValidator(
const hostIsolationExceptionValidator = new HostIsolationExceptionsValidator(
endpointAppContextService,
request
).validatePreUpdateItem(data);
);
const validatedItem = await hostIsolationExceptionValidator.validatePreUpdateItem(data);
hostIsolationExceptionValidator.notifyFeatureUsage(
data as ExceptionItemLikeOptions,
'HOST_ISOLATION_EXCEPTION_BY_POLICY'
);
hostIsolationExceptionValidator.notifyFeatureUsage(
data as ExceptionItemLikeOptions,
'HOST_ISOLATION_EXCEPTION'
);
return validatedItem;
}
return data;

View file

@ -18,6 +18,7 @@ import {
} from '../../../../common/endpoint/service/artifacts';
import { OperatingSystem } from '../../../../common/endpoint/types';
import { EndpointArtifactExceptionValidationError } from './errors';
import type { FeatureKeys } from '../../../endpoint/services/feature_usage/service';
export const BasicEndpointExceptionDataSchema = schema.object(
{
@ -63,6 +64,15 @@ export class BaseValidator {
}
}
public notifyFeatureUsage(item: ExceptionItemLikeOptions, featureKey: FeatureKeys): void {
if (
(this.isItemByPolicy(item) && featureKey.endsWith('_BY_POLICY')) ||
(!this.isItemByPolicy(item) && !featureKey.endsWith('_BY_POLICY'))
) {
this.endpointAppContext.getFeatureUsageService().notifyUsage(featureKey);
}
}
protected isItemByPolicy(item: ExceptionItemLikeOptions): boolean {
return isArtifactByPolicy(item);
}

View file

@ -94,6 +94,7 @@ import type {
} from './plugin_contract';
import { alertsFieldMap, rulesFieldMap } from '../common/field_maps';
import { EndpointFleetServicesFactory } from './endpoint/services/fleet';
import { featureUsageService } from './endpoint/services/feature_usage';
export type { SetupPlugins, StartPlugins, PluginSetup, PluginStart } from './plugin_contract';
@ -339,6 +340,8 @@ export class Plugin implements ISecuritySolutionPlugin {
taskManager: plugins.taskManager!,
});
featureUsageService.setup(plugins.licensing);
return {};
}
@ -401,6 +404,7 @@ export class Plugin implements ISecuritySolutionPlugin {
// License related start
licenseService.start(this.licensing$);
featureUsageService.start(plugins.licensing);
this.policyWatcher = new PolicyWatcher(
plugins.fleet.packagePolicyService,
core.savedObjects,
@ -441,6 +445,7 @@ export class Plugin implements ISecuritySolutionPlugin {
licenseService,
exceptionListsClient: exceptionListClient,
registerListsServerExtension: this.lists?.registerExtension,
featureUsageService,
});
this.telemetryReceiver.start(

View file

@ -25,7 +25,7 @@ import { EncryptedSavedObjectsPluginSetup } from '../../encrypted_saved_objects/
import { IEventLogClientService, IEventLogService } from '../../event_log/server';
import { PluginSetupContract as FeaturesPluginSetup } from '../../features/server';
import { FleetStartContract as FleetPluginStart } from '../../fleet/server';
import { LicensingPluginStart } from '../../licensing/server';
import { LicensingPluginStart, LicensingPluginSetup } from '../../licensing/server';
import { ListPluginSetup } from '../../lists/server';
import { MlPluginSetup } from '../../ml/server';
import {
@ -57,6 +57,7 @@ export interface SecuritySolutionPluginSetupDependencies {
taskManager?: TaskManagerPluginSetup;
telemetry?: TelemetryPluginSetup;
usageCollection?: UsageCollectionPluginSetup;
licensing: LicensingPluginSetup;
}
export interface SecuritySolutionPluginStartDependencies {