mirror of
https://github.com/elastic/kibana.git
synced 2025-04-24 09:48:58 -04:00
[Security Solution] add feature usage notifications (#124766)
This commit is contained in:
parent
17a997cccb
commit
93aa4f4184
21 changed files with 376 additions and 49 deletions
|
@ -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',
|
||||
}
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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> =
|
||||
|
|
|
@ -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',
|
||||
|
|
|
@ -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',
|
||||
|
|
|
@ -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',
|
||||
|
|
|
@ -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',
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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(),
|
||||
};
|
||||
};
|
||||
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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();
|
|
@ -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;
|
||||
}
|
|
@ -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]);
|
||||
}
|
||||
}
|
|
@ -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);
|
||||
|
|
|
@ -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;
|
||||
};
|
||||
};
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
});
|
||||
});
|
||||
}
|
|
@ -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;
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
|
|
|
@ -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(
|
||||
|
|
|
@ -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 {
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue