mirror of
https://github.com/elastic/kibana.git
synced 2025-04-23 17:28:26 -04:00
[Security Solution][Endpoint] Add API checks to Endpoint Policy create/update for checking endpointPolicyProtections
is enabled (#163429)
## Summary - Adds checks to both the Policy Create and Policy Update APIs (Fleet API extension points) and turns off all protections if `endpointPolicyProtections` appFeature is disabled - Adds migration of policies to the Plugin `start()` that will check if `endpointPolicyProtections` is disabled and updates all existing policies (if necessary) to disable protections.
This commit is contained in:
parent
2ba659d091
commit
27c394c936
17 changed files with 715 additions and 71 deletions
|
@ -6,14 +6,19 @@
|
|||
*/
|
||||
|
||||
import type { PolicyConfig } from '../types';
|
||||
import { ProtectionModes } from '../types';
|
||||
import { PolicyOperatingSystem, ProtectionModes } from '../types';
|
||||
import { policyFactory } from './policy_config';
|
||||
import { disableProtections } from './policy_config_helpers';
|
||||
import {
|
||||
disableProtections,
|
||||
isPolicySetToEventCollectionOnly,
|
||||
ensureOnlyEventCollectionIsAllowed,
|
||||
} from './policy_config_helpers';
|
||||
import { set } from 'lodash';
|
||||
|
||||
describe('Policy Config helpers', () => {
|
||||
describe('disableProtections', () => {
|
||||
it('disables all the protections in the default policy', () => {
|
||||
expect(disableProtections(policyFactory())).toEqual<PolicyConfig>(eventsOnlyPolicy);
|
||||
expect(disableProtections(policyFactory())).toEqual<PolicyConfig>(eventsOnlyPolicy());
|
||||
});
|
||||
|
||||
it('does not enable supported fields', () => {
|
||||
|
@ -51,20 +56,20 @@ describe('Policy Config helpers', () => {
|
|||
};
|
||||
|
||||
const expectedPolicyWithoutSupportedProtections: PolicyConfig = {
|
||||
...eventsOnlyPolicy,
|
||||
...eventsOnlyPolicy(),
|
||||
windows: {
|
||||
...eventsOnlyPolicy.windows,
|
||||
...eventsOnlyPolicy().windows,
|
||||
memory_protection: notSupported,
|
||||
behavior_protection: notSupportedBehaviorProtection,
|
||||
ransomware: notSupported,
|
||||
},
|
||||
mac: {
|
||||
...eventsOnlyPolicy.mac,
|
||||
...eventsOnlyPolicy().mac,
|
||||
memory_protection: notSupported,
|
||||
behavior_protection: notSupportedBehaviorProtection,
|
||||
},
|
||||
linux: {
|
||||
...eventsOnlyPolicy.linux,
|
||||
...eventsOnlyPolicy().linux,
|
||||
memory_protection: notSupported,
|
||||
behavior_protection: notSupportedBehaviorProtection,
|
||||
},
|
||||
|
@ -104,10 +109,10 @@ describe('Policy Config helpers', () => {
|
|||
};
|
||||
|
||||
const expectedPolicy: PolicyConfig = {
|
||||
...eventsOnlyPolicy,
|
||||
windows: { ...eventsOnlyPolicy.windows, events: { ...windowsEvents } },
|
||||
mac: { ...eventsOnlyPolicy.mac, events: { ...macEvents } },
|
||||
linux: { ...eventsOnlyPolicy.linux, events: { ...linuxEvents } },
|
||||
...eventsOnlyPolicy(),
|
||||
windows: { ...eventsOnlyPolicy().windows, events: { ...windowsEvents } },
|
||||
mac: { ...eventsOnlyPolicy().mac, events: { ...macEvents } },
|
||||
linux: { ...eventsOnlyPolicy().linux, events: { ...linuxEvents } },
|
||||
};
|
||||
|
||||
const inputPolicy = {
|
||||
|
@ -120,11 +125,73 @@ describe('Policy Config helpers', () => {
|
|||
expect(disableProtections(inputPolicy)).toEqual<PolicyConfig>(expectedPolicy);
|
||||
});
|
||||
});
|
||||
|
||||
describe('setPolicyToEventCollectionOnly()', () => {
|
||||
it('should set the policy to event collection only', () => {
|
||||
expect(ensureOnlyEventCollectionIsAllowed(policyFactory())).toEqual(eventsOnlyPolicy());
|
||||
});
|
||||
});
|
||||
|
||||
describe('isPolicySetToEventCollectionOnly', () => {
|
||||
let policy: PolicyConfig;
|
||||
|
||||
beforeEach(() => {
|
||||
policy = ensureOnlyEventCollectionIsAllowed(policyFactory());
|
||||
});
|
||||
|
||||
it.each([
|
||||
{
|
||||
keyPath: `${PolicyOperatingSystem.windows}.malware.mode`,
|
||||
keyValue: ProtectionModes.prevent,
|
||||
expectedResult: false,
|
||||
},
|
||||
{
|
||||
keyPath: `${PolicyOperatingSystem.mac}.malware.mode`,
|
||||
keyValue: ProtectionModes.off,
|
||||
expectedResult: true,
|
||||
},
|
||||
{
|
||||
keyPath: `${PolicyOperatingSystem.windows}.ransomware.mode`,
|
||||
keyValue: ProtectionModes.prevent,
|
||||
expectedResult: false,
|
||||
},
|
||||
{
|
||||
keyPath: `${PolicyOperatingSystem.linux}.memory_protection.mode`,
|
||||
keyValue: ProtectionModes.off,
|
||||
expectedResult: true,
|
||||
},
|
||||
{
|
||||
keyPath: `${PolicyOperatingSystem.mac}.behavior_protection.mode`,
|
||||
keyValue: ProtectionModes.detect,
|
||||
expectedResult: false,
|
||||
},
|
||||
{
|
||||
keyPath: `${PolicyOperatingSystem.windows}.attack_surface_reduction.credential_hardening.enabled`,
|
||||
keyValue: true,
|
||||
expectedResult: false,
|
||||
},
|
||||
{
|
||||
keyPath: `${PolicyOperatingSystem.windows}.antivirus_registration.enabled`,
|
||||
keyValue: true,
|
||||
expectedResult: false,
|
||||
},
|
||||
])(
|
||||
'should return `$expectedResult` if `$keyPath` is set to `$keyValue`',
|
||||
({ keyPath, keyValue, expectedResult }) => {
|
||||
set(policy, keyPath, keyValue);
|
||||
|
||||
expect(isPolicySetToEventCollectionOnly(policy)).toEqual({
|
||||
isOnlyCollectingEvents: expectedResult,
|
||||
message: expectedResult ? undefined : `property [${keyPath}] is set to [${keyValue}]`,
|
||||
});
|
||||
}
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
// 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 = {
|
||||
export const eventsOnlyPolicy = (): PolicyConfig => ({
|
||||
meta: { license: '', cloud: false, license_uid: '', cluster_name: '', cluster_uuid: '' },
|
||||
windows: {
|
||||
events: {
|
||||
|
@ -187,4 +254,4 @@ export const eventsOnlyPolicy: PolicyConfig = {
|
|||
capture_env_vars: 'LD_PRELOAD,LD_LIBRARY_PATH',
|
||||
},
|
||||
},
|
||||
};
|
||||
});
|
||||
|
|
|
@ -5,8 +5,63 @@
|
|||
* 2.0.
|
||||
*/
|
||||
|
||||
import { get, set } from 'lodash';
|
||||
import type { PolicyConfig } from '../types';
|
||||
import { ProtectionModes } from '../types';
|
||||
import { PolicyOperatingSystem, ProtectionModes } from '../types';
|
||||
|
||||
interface PolicyProtectionReference {
|
||||
keyPath: string;
|
||||
osList: PolicyOperatingSystem[];
|
||||
enableValue: unknown;
|
||||
disableValue: unknown;
|
||||
}
|
||||
|
||||
const getPolicyProtectionsReference = (): PolicyProtectionReference[] => {
|
||||
const allOsValues = [
|
||||
PolicyOperatingSystem.mac,
|
||||
PolicyOperatingSystem.linux,
|
||||
PolicyOperatingSystem.windows,
|
||||
];
|
||||
|
||||
return [
|
||||
{
|
||||
keyPath: 'malware.mode',
|
||||
osList: [...allOsValues],
|
||||
disableValue: ProtectionModes.off,
|
||||
enableValue: ProtectionModes.prevent,
|
||||
},
|
||||
{
|
||||
keyPath: 'ransomware.mode',
|
||||
osList: [PolicyOperatingSystem.windows],
|
||||
disableValue: ProtectionModes.off,
|
||||
enableValue: ProtectionModes.prevent,
|
||||
},
|
||||
{
|
||||
keyPath: 'memory_protection.mode',
|
||||
osList: [...allOsValues],
|
||||
disableValue: ProtectionModes.off,
|
||||
enableValue: ProtectionModes.prevent,
|
||||
},
|
||||
{
|
||||
keyPath: 'behavior_protection.mode',
|
||||
osList: [...allOsValues],
|
||||
disableValue: ProtectionModes.off,
|
||||
enableValue: ProtectionModes.prevent,
|
||||
},
|
||||
{
|
||||
keyPath: 'attack_surface_reduction.credential_hardening.enabled',
|
||||
osList: [PolicyOperatingSystem.windows],
|
||||
disableValue: false,
|
||||
enableValue: true,
|
||||
},
|
||||
{
|
||||
keyPath: 'antivirus_registration.enabled',
|
||||
osList: [PolicyOperatingSystem.windows],
|
||||
disableValue: false,
|
||||
enableValue: true,
|
||||
},
|
||||
];
|
||||
};
|
||||
|
||||
/**
|
||||
* Returns a copy of the passed `PolicyConfig` with all protections set to disabled.
|
||||
|
@ -106,3 +161,46 @@ const getDisabledWindowsSpecificPopups = (policy: PolicyConfig) => ({
|
|||
enabled: false,
|
||||
},
|
||||
});
|
||||
|
||||
/**
|
||||
* Returns the provided with only event collection turned enabled
|
||||
* @param policy
|
||||
*/
|
||||
export const ensureOnlyEventCollectionIsAllowed = (policy: PolicyConfig): PolicyConfig => {
|
||||
const updatedPolicy = disableProtections(policy);
|
||||
|
||||
set(updatedPolicy, 'windows.antivirus_registration.enabled', false);
|
||||
|
||||
return updatedPolicy;
|
||||
};
|
||||
|
||||
/**
|
||||
* Checks to see if the provided policy is set to Event Collection only
|
||||
*/
|
||||
export const isPolicySetToEventCollectionOnly = (
|
||||
policy: PolicyConfig
|
||||
): { isOnlyCollectingEvents: boolean; message?: string } => {
|
||||
const protectionsRef = getPolicyProtectionsReference();
|
||||
let message: string | undefined;
|
||||
|
||||
const hasEnabledProtection = protectionsRef.some(({ keyPath, osList, disableValue }) => {
|
||||
const hasOsPropertyEnabled = osList.some((osValue) => {
|
||||
const fullKeyPathForOs = `${osValue}.${keyPath}`;
|
||||
const currentValue = get(policy, fullKeyPathForOs);
|
||||
const isEnabled = currentValue !== disableValue;
|
||||
|
||||
if (isEnabled) {
|
||||
message = `property [${fullKeyPathForOs}] is set to [${currentValue}]`;
|
||||
}
|
||||
|
||||
return isEnabled;
|
||||
});
|
||||
|
||||
return hasOsPropertyEnabled;
|
||||
});
|
||||
|
||||
return {
|
||||
isOnlyCollectingEvents: !hasEnabledProtection,
|
||||
message,
|
||||
};
|
||||
};
|
||||
|
|
|
@ -0,0 +1,12 @@
|
|||
/*
|
||||
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
|
||||
* or more contributor license agreements. Licensed under the Elastic License
|
||||
* 2.0; you may not use this file except in compliance with the Elastic License
|
||||
* 2.0.
|
||||
*/
|
||||
|
||||
/* eslint-disable @typescript-eslint/no-explicit-any */
|
||||
|
||||
export type PromiseResolvedValue<T extends Promise<any>> = T extends Promise<infer Value>
|
||||
? Value
|
||||
: never;
|
|
@ -17,6 +17,7 @@ import type {
|
|||
import type { PluginStartContract as AlertsPluginStartContract } from '@kbn/alerting-plugin/server';
|
||||
import type { CloudSetup } from '@kbn/cloud-plugin/server';
|
||||
import type { FleetActionsClientInterface } from '@kbn/fleet-plugin/server/services/actions/types';
|
||||
import type { AppFeatures } from '../lib/app_features';
|
||||
import {
|
||||
getPackagePolicyCreateCallback,
|
||||
getPackagePolicyUpdateCallback,
|
||||
|
@ -69,6 +70,7 @@ export interface EndpointAppContextServiceStartContract {
|
|||
actionCreateService: ActionCreateService | undefined;
|
||||
cloud: CloudSetup;
|
||||
esClient: ElasticsearchClient;
|
||||
appFeatures: AppFeatures;
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -106,6 +108,7 @@ export class EndpointAppContextService {
|
|||
featureUsageService,
|
||||
endpointMetadataService,
|
||||
esClient,
|
||||
appFeatures,
|
||||
} = dependencies;
|
||||
|
||||
registerIngestCallback(
|
||||
|
@ -117,7 +120,8 @@ export class EndpointAppContextService {
|
|||
alerting,
|
||||
licenseService,
|
||||
exceptionListsClient,
|
||||
cloud
|
||||
cloud,
|
||||
appFeatures
|
||||
)
|
||||
);
|
||||
|
||||
|
@ -134,7 +138,8 @@ export class EndpointAppContextService {
|
|||
featureUsageService,
|
||||
endpointMetadataService,
|
||||
cloud,
|
||||
esClient
|
||||
esClient,
|
||||
appFeatures
|
||||
)
|
||||
);
|
||||
|
||||
|
|
|
@ -0,0 +1,145 @@
|
|||
/*
|
||||
* 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 { createMockEndpointAppContextServiceStartContract } from '../mocks';
|
||||
import type { Logger } from '@kbn/logging';
|
||||
import type { ElasticsearchClient } from '@kbn/core-elasticsearch-server';
|
||||
import type { EndpointInternalFleetServicesInterface } from '../services/fleet';
|
||||
import type { AppFeatures } from '../../lib/app_features';
|
||||
import { createAppFeaturesMock } from '../../lib/app_features/mocks';
|
||||
import { ALL_APP_FEATURE_KEYS } from '../../../common';
|
||||
import { turnOffPolicyProtectionsIfNotSupported } from './turn_off_policy_protections';
|
||||
import { FleetPackagePolicyGenerator } from '../../../common/endpoint/data_generators/fleet_package_policy_generator';
|
||||
import type { PolicyData } from '../../../common/endpoint/types';
|
||||
import type { PackagePolicyClient } from '@kbn/fleet-plugin/server';
|
||||
import type { PromiseResolvedValue } from '../../../common/endpoint/types/utility_types';
|
||||
import { ensureOnlyEventCollectionIsAllowed } from '../../../common/endpoint/models/policy_config_helpers';
|
||||
|
||||
describe('Turn Off Policy Protections Migration', () => {
|
||||
let esClient: ElasticsearchClient;
|
||||
let fleetServices: EndpointInternalFleetServicesInterface;
|
||||
let appFeatures: AppFeatures;
|
||||
let logger: Logger;
|
||||
|
||||
const callTurnOffPolicyProtections = () =>
|
||||
turnOffPolicyProtectionsIfNotSupported(esClient, fleetServices, appFeatures, logger);
|
||||
|
||||
beforeEach(() => {
|
||||
const endpointContextStartContract = createMockEndpointAppContextServiceStartContract();
|
||||
|
||||
({ esClient, appFeatures, logger } = endpointContextStartContract);
|
||||
fleetServices = endpointContextStartContract.endpointFleetServicesFactory.asInternalUser();
|
||||
});
|
||||
|
||||
describe('and `endpointPolicyProtections` is enabled', () => {
|
||||
it('should do nothing', async () => {
|
||||
await callTurnOffPolicyProtections();
|
||||
|
||||
expect(fleetServices.packagePolicy.list as jest.Mock).not.toHaveBeenCalled();
|
||||
expect(logger.info).toHaveBeenLastCalledWith(
|
||||
'App feature [endpoint_policy_protections] is enabled. Nothing to do!'
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
describe('and `endpointPolicyProtections` is disabled', () => {
|
||||
let policyGenerator: FleetPackagePolicyGenerator;
|
||||
let page1Items: PolicyData[] = [];
|
||||
let page2Items: PolicyData[] = [];
|
||||
let bulkUpdateResponse: PromiseResolvedValue<ReturnType<PackagePolicyClient['bulkUpdate']>>;
|
||||
|
||||
const generatePolicyMock = (withDisabledProtections = false): PolicyData => {
|
||||
const policy = policyGenerator.generateEndpointPackagePolicy();
|
||||
|
||||
if (!withDisabledProtections) {
|
||||
return policy;
|
||||
}
|
||||
|
||||
policy.inputs[0].config.policy.value = ensureOnlyEventCollectionIsAllowed(
|
||||
policy.inputs[0].config.policy.value
|
||||
);
|
||||
|
||||
return policy;
|
||||
};
|
||||
|
||||
beforeEach(() => {
|
||||
policyGenerator = new FleetPackagePolicyGenerator('seed');
|
||||
const packagePolicyListSrv = fleetServices.packagePolicy.list as jest.Mock;
|
||||
|
||||
appFeatures = createAppFeaturesMock(
|
||||
ALL_APP_FEATURE_KEYS.filter((key) => key !== 'endpoint_policy_protections')
|
||||
);
|
||||
|
||||
page1Items = [generatePolicyMock(), generatePolicyMock(true)];
|
||||
page2Items = [generatePolicyMock(true), generatePolicyMock()];
|
||||
|
||||
packagePolicyListSrv
|
||||
.mockImplementationOnce(async () => {
|
||||
return {
|
||||
total: 1500,
|
||||
page: 1,
|
||||
perPage: 1000,
|
||||
items: page1Items,
|
||||
};
|
||||
})
|
||||
.mockImplementationOnce(async () => {
|
||||
return {
|
||||
total: 1500,
|
||||
page: 2,
|
||||
perPage: 1000,
|
||||
items: page2Items,
|
||||
};
|
||||
});
|
||||
|
||||
bulkUpdateResponse = {
|
||||
updatedPolicies: [page1Items[0], page2Items[1]],
|
||||
failedPolicies: [],
|
||||
};
|
||||
|
||||
(fleetServices.packagePolicy.bulkUpdate as jest.Mock).mockImplementation(async () => {
|
||||
return bulkUpdateResponse;
|
||||
});
|
||||
});
|
||||
|
||||
it('should update only policies that have protections turn on', async () => {
|
||||
await callTurnOffPolicyProtections();
|
||||
|
||||
expect(fleetServices.packagePolicy.list as jest.Mock).toHaveBeenCalledTimes(2);
|
||||
expect(fleetServices.packagePolicy.bulkUpdate as jest.Mock).toHaveBeenCalledWith(
|
||||
fleetServices.internalSoClient,
|
||||
esClient,
|
||||
[
|
||||
expect.objectContaining({ id: bulkUpdateResponse.updatedPolicies![0].id }),
|
||||
expect.objectContaining({ id: bulkUpdateResponse.updatedPolicies![1].id }),
|
||||
],
|
||||
{ user: { username: 'elastic' } }
|
||||
);
|
||||
expect(logger.info).toHaveBeenCalledWith(
|
||||
'Found 2 policies that need updates:\n' +
|
||||
`Policy [${bulkUpdateResponse.updatedPolicies![0].id}][${
|
||||
bulkUpdateResponse.updatedPolicies![0].name
|
||||
}] updated to disable protections. Trigger: [property [mac.malware.mode] is set to [prevent]]\n` +
|
||||
`Policy [${bulkUpdateResponse.updatedPolicies![1].id}][${
|
||||
bulkUpdateResponse.updatedPolicies![1].name
|
||||
}] updated to disable protections. Trigger: [property [mac.malware.mode] is set to [prevent]]`
|
||||
);
|
||||
expect(logger.info).toHaveBeenCalledWith('Done. All updates applied successfully');
|
||||
});
|
||||
|
||||
it('should log failures', async () => {
|
||||
bulkUpdateResponse.failedPolicies.push({
|
||||
error: new Error('oh oh'),
|
||||
packagePolicy: bulkUpdateResponse.updatedPolicies![0],
|
||||
});
|
||||
await callTurnOffPolicyProtections();
|
||||
|
||||
expect(logger.error).toHaveBeenCalledWith(
|
||||
expect.stringContaining('Done. 1 out of 2 failed to update:')
|
||||
);
|
||||
});
|
||||
});
|
||||
});
|
|
@ -0,0 +1,107 @@
|
|||
/*
|
||||
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
|
||||
* or more contributor license agreements. Licensed under the Elastic License
|
||||
* 2.0; you may not use this file except in compliance with the Elastic License
|
||||
* 2.0.
|
||||
*/
|
||||
|
||||
import type { Logger, ElasticsearchClient } from '@kbn/core/server';
|
||||
import type { UpdatePackagePolicy } from '@kbn/fleet-plugin/common';
|
||||
import type { AuthenticatedUser } from '@kbn/security-plugin/common';
|
||||
import {
|
||||
isPolicySetToEventCollectionOnly,
|
||||
ensureOnlyEventCollectionIsAllowed,
|
||||
} from '../../../common/endpoint/models/policy_config_helpers';
|
||||
import type { PolicyData } from '../../../common/endpoint/types';
|
||||
import { AppFeatureSecurityKey } from '../../../common/types/app_features';
|
||||
import type { EndpointInternalFleetServicesInterface } from '../services/fleet';
|
||||
import type { AppFeatures } from '../../lib/app_features';
|
||||
import { getPolicyDataForUpdate } from '../../../common/endpoint/service/policy';
|
||||
|
||||
export const turnOffPolicyProtectionsIfNotSupported = async (
|
||||
esClient: ElasticsearchClient,
|
||||
fleetServices: EndpointInternalFleetServicesInterface,
|
||||
appFeaturesService: AppFeatures,
|
||||
logger: Logger
|
||||
): Promise<void> => {
|
||||
const log = logger.get('endpoint', 'policyProtections');
|
||||
|
||||
if (appFeaturesService.isEnabled(AppFeatureSecurityKey.endpointPolicyProtections)) {
|
||||
log.info(
|
||||
`App feature [${AppFeatureSecurityKey.endpointPolicyProtections}] is enabled. Nothing to do!`
|
||||
);
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
log.info(
|
||||
`App feature [${AppFeatureSecurityKey.endpointPolicyProtections}] is disabled. Checking endpoint integration policies for compliance`
|
||||
);
|
||||
|
||||
const { packagePolicy, internalSoClient, endpointPolicyKuery } = fleetServices;
|
||||
const updates: UpdatePackagePolicy[] = [];
|
||||
const messages: string[] = [];
|
||||
const perPage = 1000;
|
||||
let hasMoreData = true;
|
||||
let total = 0;
|
||||
let page = 1;
|
||||
|
||||
do {
|
||||
const currentPage = page++;
|
||||
const { items, total: totalPolicies } = await packagePolicy.list(internalSoClient, {
|
||||
page: currentPage,
|
||||
kuery: endpointPolicyKuery,
|
||||
perPage,
|
||||
});
|
||||
|
||||
total = totalPolicies;
|
||||
hasMoreData = currentPage * perPage < total;
|
||||
|
||||
for (const item of items) {
|
||||
const integrationPolicy = item as PolicyData;
|
||||
const policySettings = integrationPolicy.inputs[0].config.policy.value;
|
||||
const { message, isOnlyCollectingEvents } = isPolicySetToEventCollectionOnly(policySettings);
|
||||
|
||||
if (!isOnlyCollectingEvents) {
|
||||
messages.push(
|
||||
`Policy [${integrationPolicy.id}][${integrationPolicy.name}] updated to disable protections. Trigger: [${message}]`
|
||||
);
|
||||
|
||||
integrationPolicy.inputs[0].config.policy.value =
|
||||
ensureOnlyEventCollectionIsAllowed(policySettings);
|
||||
|
||||
updates.push({
|
||||
...getPolicyDataForUpdate(integrationPolicy),
|
||||
id: integrationPolicy.id,
|
||||
});
|
||||
}
|
||||
}
|
||||
} while (hasMoreData);
|
||||
|
||||
if (updates.length > 0) {
|
||||
log.info(`Found ${updates.length} policies that need updates:\n${messages.join('\n')}`);
|
||||
|
||||
const bulkUpdateResponse = await fleetServices.packagePolicy.bulkUpdate(
|
||||
internalSoClient,
|
||||
esClient,
|
||||
updates,
|
||||
{
|
||||
user: { username: 'elastic' } as AuthenticatedUser,
|
||||
}
|
||||
);
|
||||
|
||||
log.debug(`Bulk update response:\n${JSON.stringify(bulkUpdateResponse, null, 2)}`);
|
||||
|
||||
if (bulkUpdateResponse.failedPolicies.length > 0) {
|
||||
log.error(
|
||||
`Done. ${bulkUpdateResponse.failedPolicies.length} out of ${
|
||||
updates.length
|
||||
} failed to update:\n${JSON.stringify(bulkUpdateResponse.failedPolicies, null, 2)}`
|
||||
);
|
||||
} else {
|
||||
log.info(`Done. All updates applied successfully`);
|
||||
}
|
||||
} else {
|
||||
log.info(`Done. Checked ${total} policies and no updates needed`);
|
||||
}
|
||||
};
|
|
@ -71,6 +71,7 @@ import type { EndpointAuthz } from '../../common/endpoint/types/authz';
|
|||
import { EndpointFleetServicesFactory } from './services/fleet';
|
||||
import { createLicenseServiceMock } from '../../common/license/mocks';
|
||||
import { createFeatureUsageServiceMock } from './services/feature_usage/mocks';
|
||||
import { createAppFeaturesMock } from '../lib/app_features/mocks';
|
||||
|
||||
/**
|
||||
* Creates a mocked EndpointAppContext.
|
||||
|
@ -163,6 +164,8 @@ export const createMockEndpointAppContextServiceStartContract =
|
|||
},
|
||||
savedObjectsStart
|
||||
);
|
||||
const experimentalFeatures = config.experimentalFeatures;
|
||||
const appFeatures = createAppFeaturesMock(undefined, experimentalFeatures, undefined, logger);
|
||||
|
||||
packagePolicyService.list.mockImplementation(async (_, options) => {
|
||||
return {
|
||||
|
@ -207,11 +210,12 @@ export const createMockEndpointAppContextServiceStartContract =
|
|||
cases: casesMock,
|
||||
cloud: cloudMock.createSetup(),
|
||||
featureUsageService: createFeatureUsageServiceMock(),
|
||||
experimentalFeatures: createMockConfig().experimentalFeatures,
|
||||
experimentalFeatures,
|
||||
messageSigningService: createMessageSigningServiceMock(),
|
||||
actionCreateService: undefined,
|
||||
createFleetActionsClient: jest.fn((_) => fleetActionsClientMock),
|
||||
esClient: elasticsearchClientMock.createElasticsearchClient(),
|
||||
appFeatures,
|
||||
};
|
||||
};
|
||||
|
||||
|
|
|
@ -13,6 +13,8 @@ import type {
|
|||
PackagePolicyClient,
|
||||
PackageClient,
|
||||
} from '@kbn/fleet-plugin/server';
|
||||
import { PACKAGE_POLICY_SAVED_OBJECT_TYPE } from '@kbn/fleet-plugin/common';
|
||||
import { createInternalSoClient } from '../../utils/create_internal_so_client';
|
||||
import { createInternalReadonlySoClient } from '../../utils/create_internal_readonly_so_client';
|
||||
|
||||
export interface EndpointFleetServicesFactoryInterface {
|
||||
|
@ -42,7 +44,10 @@ export class EndpointFleetServicesFactory implements EndpointFleetServicesFactor
|
|||
packages: packageService.asInternalUser,
|
||||
packagePolicy,
|
||||
|
||||
endpointPolicyKuery: `${PACKAGE_POLICY_SAVED_OBJECT_TYPE}.package.name: "endpoint"`,
|
||||
|
||||
internalReadonlySoClient: createInternalReadonlySoClient(this.savedObjectsStart),
|
||||
internalSoClient: createInternalSoClient(this.savedObjectsStart),
|
||||
};
|
||||
}
|
||||
}
|
||||
|
@ -55,6 +60,8 @@ export interface EndpointFleetServicesInterface {
|
|||
agentPolicy: AgentPolicyServiceInterface;
|
||||
packages: PackageClient;
|
||||
packagePolicy: PackagePolicyClient;
|
||||
/** The `kuery` that can be used to filter for Endpoint integration policies */
|
||||
endpointPolicyKuery: string;
|
||||
}
|
||||
|
||||
export interface EndpointInternalFleetServicesInterface extends EndpointFleetServicesInterface {
|
||||
|
@ -62,4 +69,7 @@ export interface EndpointInternalFleetServicesInterface extends EndpointFleetSer
|
|||
* An internal SO client (readonly) that can be used with the Fleet services that require it
|
||||
*/
|
||||
internalReadonlySoClient: SavedObjectsClientContract;
|
||||
|
||||
/** Internal SO client. USE ONLY WHEN ABSOLUTELY NEEDED. Else, use the `internalReadonlySoClient` */
|
||||
internalSoClient: SavedObjectsClientContract;
|
||||
}
|
||||
|
|
|
@ -5,12 +5,8 @@
|
|||
* 2.0.
|
||||
*/
|
||||
|
||||
import { SECURITY_EXTENSION_ID } from '@kbn/core-saved-objects-server';
|
||||
import type {
|
||||
KibanaRequest,
|
||||
SavedObjectsClientContract,
|
||||
SavedObjectsServiceStart,
|
||||
} from '@kbn/core/server';
|
||||
import type { SavedObjectsClientContract, SavedObjectsServiceStart } from '@kbn/core/server';
|
||||
import { createInternalSoClient } from './create_internal_so_client';
|
||||
import { EndpointError } from '../../../common/endpoint/errors';
|
||||
|
||||
type SavedObjectsClientContractKeys = keyof SavedObjectsClientContract;
|
||||
|
@ -37,18 +33,7 @@ export class InternalReadonlySoClientMethodNotAllowedError extends EndpointError
|
|||
export const createInternalReadonlySoClient = (
|
||||
savedObjectsServiceStart: SavedObjectsServiceStart
|
||||
): SavedObjectsClientContract => {
|
||||
const fakeRequest = {
|
||||
headers: {},
|
||||
getBasePath: () => '',
|
||||
path: '/',
|
||||
route: { settings: {} },
|
||||
url: { href: {} },
|
||||
raw: { req: { url: '/' } },
|
||||
} as unknown as KibanaRequest;
|
||||
|
||||
const internalSoClient = savedObjectsServiceStart.getScopedClient(fakeRequest, {
|
||||
excludedExtensions: [SECURITY_EXTENSION_ID],
|
||||
});
|
||||
const internalSoClient = createInternalSoClient(savedObjectsServiceStart);
|
||||
|
||||
return new Proxy(internalSoClient, {
|
||||
get(
|
||||
|
|
|
@ -0,0 +1,27 @@
|
|||
/*
|
||||
* 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 { SavedObjectsServiceStart } from '@kbn/core-saved-objects-server';
|
||||
import { SECURITY_EXTENSION_ID } from '@kbn/core-saved-objects-server';
|
||||
import type { KibanaRequest, SavedObjectsClientContract } from '@kbn/core/server';
|
||||
|
||||
export const createInternalSoClient = (
|
||||
savedObjectsServiceStart: SavedObjectsServiceStart
|
||||
): SavedObjectsClientContract => {
|
||||
const fakeRequest = {
|
||||
headers: {},
|
||||
getBasePath: () => '',
|
||||
path: '/',
|
||||
route: { settings: {} },
|
||||
url: { href: {} },
|
||||
raw: { req: { url: '/' } },
|
||||
} as unknown as KibanaRequest;
|
||||
|
||||
return savedObjectsServiceStart.getScopedClient(fakeRequest, {
|
||||
excludedExtensions: [SECURITY_EXTENSION_ID],
|
||||
});
|
||||
};
|
|
@ -54,6 +54,9 @@ import { createMockPolicyData } from '../endpoint/services/feature_usage/mocks';
|
|||
import { ALL_ENDPOINT_ARTIFACT_LIST_IDS } from '../../common/endpoint/service/artifacts/constants';
|
||||
import { ENDPOINT_EVENT_FILTERS_LIST_ID } from '@kbn/securitysolution-list-constants';
|
||||
import { disableProtections } from '../../common/endpoint/models/policy_config_helpers';
|
||||
import type { AppFeatures } from '../lib/app_features';
|
||||
import { createAppFeaturesMock } from '../lib/app_features/mocks';
|
||||
import { ALL_APP_FEATURE_KEYS } from '../../common';
|
||||
|
||||
jest.mock('uuid', () => ({
|
||||
v4: (): string => 'NEW_UUID',
|
||||
|
@ -74,6 +77,7 @@ describe('ingest_integration tests ', () => {
|
|||
});
|
||||
const generator = new EndpointDocGenerator();
|
||||
const cloudService = cloudMock.createSetup();
|
||||
let appFeatures: AppFeatures;
|
||||
|
||||
beforeEach(() => {
|
||||
endpointAppContextMock = createMockEndpointAppContextServiceStartContract();
|
||||
|
@ -82,6 +86,7 @@ describe('ingest_integration tests ', () => {
|
|||
licenseEmitter = new Subject();
|
||||
licenseService = new LicenseService();
|
||||
licenseService.start(licenseEmitter);
|
||||
appFeatures = endpointAppContextMock.appFeatures;
|
||||
|
||||
jest
|
||||
.spyOn(endpointAppContextMock.endpointMetadataService, 'getFleetEndpointPackagePolicy')
|
||||
|
@ -129,7 +134,8 @@ describe('ingest_integration tests ', () => {
|
|||
endpointAppContextMock.alerting,
|
||||
licenseService,
|
||||
exceptionListClient,
|
||||
cloudService
|
||||
cloudService,
|
||||
appFeatures
|
||||
);
|
||||
|
||||
return callback(
|
||||
|
@ -363,6 +369,7 @@ describe('ingest_integration tests ', () => {
|
|||
);
|
||||
});
|
||||
});
|
||||
|
||||
describe('package policy update callback (when the license is below platinum)', () => {
|
||||
const soClient = savedObjectsClientMock.create();
|
||||
const esClient = elasticsearchServiceMock.createClusterClient().asInternalUser;
|
||||
|
@ -379,7 +386,8 @@ describe('ingest_integration tests ', () => {
|
|||
endpointAppContextMock.featureUsageService,
|
||||
endpointAppContextMock.endpointMetadataService,
|
||||
cloudService,
|
||||
esClient
|
||||
esClient,
|
||||
appFeatures
|
||||
);
|
||||
const policyConfig = generator.generatePolicyPackagePolicy();
|
||||
policyConfig.inputs[0]!.config!.policy.value = mockPolicy;
|
||||
|
@ -397,7 +405,8 @@ describe('ingest_integration tests ', () => {
|
|||
endpointAppContextMock.featureUsageService,
|
||||
endpointAppContextMock.endpointMetadataService,
|
||||
cloudService,
|
||||
esClient
|
||||
esClient,
|
||||
appFeatures
|
||||
);
|
||||
const policyConfig = generator.generatePolicyPackagePolicy();
|
||||
policyConfig.inputs[0]!.config!.policy.value = mockPolicy;
|
||||
|
@ -419,6 +428,7 @@ describe('ingest_integration tests ', () => {
|
|||
beforeEach(() => {
|
||||
licenseEmitter.next(Platinum); // set license level to platinum
|
||||
});
|
||||
|
||||
it('updates successfully when paid features are turned on', async () => {
|
||||
const mockPolicy = policyFactory();
|
||||
mockPolicy.windows.popup.malware.message = 'paid feature';
|
||||
|
@ -429,7 +439,8 @@ describe('ingest_integration tests ', () => {
|
|||
endpointAppContextMock.featureUsageService,
|
||||
endpointAppContextMock.endpointMetadataService,
|
||||
cloudService,
|
||||
esClient
|
||||
esClient,
|
||||
appFeatures
|
||||
);
|
||||
const policyConfig = generator.generatePolicyPackagePolicy();
|
||||
policyConfig.inputs[0]!.config!.policy.value = mockPolicy;
|
||||
|
@ -442,6 +453,50 @@ describe('ingest_integration tests ', () => {
|
|||
);
|
||||
expect(updatedPolicyConfig.inputs[0]!.config!.policy.value).toEqual(mockPolicy);
|
||||
});
|
||||
|
||||
it('should turn off protections if endpointPolicyProtections appFeature is disabled', async () => {
|
||||
appFeatures = createAppFeaturesMock(
|
||||
ALL_APP_FEATURE_KEYS.filter((key) => key !== 'endpoint_policy_protections')
|
||||
);
|
||||
const callback = getPackagePolicyUpdateCallback(
|
||||
endpointAppContextMock.logger,
|
||||
licenseService,
|
||||
endpointAppContextMock.featureUsageService,
|
||||
endpointAppContextMock.endpointMetadataService,
|
||||
cloudService,
|
||||
esClient,
|
||||
appFeatures
|
||||
);
|
||||
|
||||
const updatedPolicy = await callback(
|
||||
generator.generatePolicyPackagePolicy(),
|
||||
soClient,
|
||||
esClient,
|
||||
requestContextMock.convertContext(ctx),
|
||||
req
|
||||
);
|
||||
|
||||
expect(updatedPolicy.inputs?.[0]?.config?.policy.value).toMatchObject({
|
||||
linux: {
|
||||
behavior_protection: { mode: 'off' },
|
||||
malware: { mode: 'off' },
|
||||
memory_protection: { mode: 'off' },
|
||||
},
|
||||
mac: {
|
||||
behavior_protection: { mode: 'off' },
|
||||
malware: { mode: 'off' },
|
||||
memory_protection: { mode: 'off' },
|
||||
},
|
||||
windows: {
|
||||
antivirus_registration: { enabled: false },
|
||||
attack_surface_reduction: { credential_hardening: { enabled: false } },
|
||||
behavior_protection: { mode: 'off' },
|
||||
malware: { blocklist: false },
|
||||
memory_protection: { mode: 'off' },
|
||||
ransomware: { mode: 'off' },
|
||||
},
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('package policy update callback when meta fields should be updated', () => {
|
||||
|
@ -486,7 +541,8 @@ describe('ingest_integration tests ', () => {
|
|||
endpointAppContextMock.featureUsageService,
|
||||
endpointAppContextMock.endpointMetadataService,
|
||||
cloudService,
|
||||
esClient
|
||||
esClient,
|
||||
appFeatures
|
||||
);
|
||||
const policyConfig = generator.generatePolicyPackagePolicy();
|
||||
|
||||
|
@ -520,7 +576,8 @@ describe('ingest_integration tests ', () => {
|
|||
endpointAppContextMock.featureUsageService,
|
||||
endpointAppContextMock.endpointMetadataService,
|
||||
cloudService,
|
||||
esClient
|
||||
esClient,
|
||||
appFeatures
|
||||
);
|
||||
const policyConfig = generator.generatePolicyPackagePolicy();
|
||||
// values should be updated
|
||||
|
|
|
@ -22,6 +22,12 @@ import type {
|
|||
} from '@kbn/fleet-plugin/common';
|
||||
import type { CloudSetup } from '@kbn/cloud-plugin/server';
|
||||
import type { InfoResponse } from '@elastic/elasticsearch/lib/api/types';
|
||||
import { AppFeatureSecurityKey } from '../../common/types/app_features';
|
||||
import {
|
||||
isPolicySetToEventCollectionOnly,
|
||||
ensureOnlyEventCollectionIsAllowed,
|
||||
} from '../../common/endpoint/models/policy_config_helpers';
|
||||
import type { AppFeatures } from '../lib/app_features';
|
||||
import type { NewPolicyData, PolicyConfig } from '../../common/endpoint/types';
|
||||
import type { LicenseService } from '../../common/license';
|
||||
import type { ManifestManager } from '../endpoint/services';
|
||||
|
@ -72,7 +78,8 @@ export const getPackagePolicyCreateCallback = (
|
|||
alerts: AlertsStartContract,
|
||||
licenseService: LicenseService,
|
||||
exceptionsClient: ExceptionListClient | undefined,
|
||||
cloud: CloudSetup
|
||||
cloud: CloudSetup,
|
||||
appFeatures: AppFeatures
|
||||
): PostPackagePolicyCreateCallback => {
|
||||
return async (
|
||||
newPackagePolicy,
|
||||
|
@ -140,7 +147,8 @@ export const getPackagePolicyCreateCallback = (
|
|||
licenseService,
|
||||
endpointIntegrationConfig,
|
||||
cloud,
|
||||
esClientInfo
|
||||
esClientInfo,
|
||||
appFeatures
|
||||
);
|
||||
|
||||
return {
|
||||
|
@ -175,31 +183,38 @@ export const getPackagePolicyUpdateCallback = (
|
|||
featureUsageService: FeatureUsageService,
|
||||
endpointMetadataService: EndpointMetadataService,
|
||||
cloud: CloudSetup,
|
||||
esClient: ElasticsearchClient
|
||||
esClient: ElasticsearchClient,
|
||||
appFeatures: AppFeatures
|
||||
): PutPackagePolicyUpdateCallback => {
|
||||
return async (newPackagePolicy: NewPackagePolicy): Promise<UpdatePackagePolicy> => {
|
||||
if (!isEndpointPackagePolicy(newPackagePolicy)) {
|
||||
return newPackagePolicy;
|
||||
}
|
||||
|
||||
const endpointIntegrationData = newPackagePolicy as NewPolicyData;
|
||||
|
||||
// Validate that Endpoint Security policy is valid against current license
|
||||
validatePolicyAgainstLicense(
|
||||
// The cast below is needed in order to ensure proper typing for
|
||||
// the policy configuration specific for endpoint
|
||||
newPackagePolicy.inputs[0].config?.policy?.value as PolicyConfig,
|
||||
endpointIntegrationData.inputs[0].config?.policy?.value as PolicyConfig,
|
||||
licenseService,
|
||||
logger
|
||||
);
|
||||
|
||||
notifyProtectionFeatureUsage(newPackagePolicy, featureUsageService, endpointMetadataService);
|
||||
notifyProtectionFeatureUsage(
|
||||
endpointIntegrationData,
|
||||
featureUsageService,
|
||||
endpointMetadataService
|
||||
);
|
||||
|
||||
const newEndpointPackagePolicy = newPackagePolicy.inputs[0].config?.policy
|
||||
const newEndpointPackagePolicy = endpointIntegrationData.inputs[0].config?.policy
|
||||
?.value as PolicyConfig;
|
||||
|
||||
const esClientInfo: InfoResponse = await esClient.info();
|
||||
|
||||
if (
|
||||
newPackagePolicy.inputs[0].config?.policy?.value &&
|
||||
endpointIntegrationData.inputs[0].config?.policy?.value &&
|
||||
shouldUpdateMetaValues(
|
||||
newEndpointPackagePolicy,
|
||||
licenseService.getLicenseType(),
|
||||
|
@ -214,10 +229,25 @@ export const getPackagePolicyUpdateCallback = (
|
|||
newEndpointPackagePolicy.meta.cluster_name = esClientInfo.cluster_name;
|
||||
newEndpointPackagePolicy.meta.cluster_uuid = esClientInfo.cluster_uuid;
|
||||
newEndpointPackagePolicy.meta.license_uid = licenseService.getLicenseUID();
|
||||
newPackagePolicy.inputs[0].config.policy.value = newEndpointPackagePolicy;
|
||||
|
||||
endpointIntegrationData.inputs[0].config.policy.value = newEndpointPackagePolicy;
|
||||
}
|
||||
|
||||
return newPackagePolicy;
|
||||
// If no Policy Protection allowed (ex. serverless)
|
||||
const eventsOnlyPolicy = isPolicySetToEventCollectionOnly(newEndpointPackagePolicy);
|
||||
if (
|
||||
!appFeatures.isEnabled(AppFeatureSecurityKey.endpointPolicyProtections) &&
|
||||
!eventsOnlyPolicy.isOnlyCollectingEvents
|
||||
) {
|
||||
logger.warn(
|
||||
`Endpoint integration policy [${endpointIntegrationData.id}][${endpointIntegrationData.name}] adjusted due to [endpointPolicyProtections] appFeature not being enabled. Trigger [${eventsOnlyPolicy.message}]`
|
||||
);
|
||||
|
||||
endpointIntegrationData.inputs[0].config.policy.value =
|
||||
ensureOnlyEventCollectionIsAllowed(newEndpointPackagePolicy);
|
||||
}
|
||||
|
||||
return endpointIntegrationData;
|
||||
};
|
||||
};
|
||||
|
||||
|
|
|
@ -19,6 +19,9 @@ import type {
|
|||
PolicyCreateCloudConfig,
|
||||
PolicyCreateEndpointConfig,
|
||||
} from '../types';
|
||||
import type { AppFeatures } from '../../lib/app_features';
|
||||
import { createAppFeaturesMock } from '../../lib/app_features/mocks';
|
||||
import { ALL_APP_FEATURE_KEYS } from '../../../common';
|
||||
|
||||
describe('Create Default Policy tests ', () => {
|
||||
const cloud = cloudMock.createSetup();
|
||||
|
@ -28,6 +31,7 @@ describe('Create Default Policy tests ', () => {
|
|||
const Gold = licenseMock.createLicense({ license: { type: 'gold', mode: 'gold', uid: '' } });
|
||||
let licenseEmitter: Subject<ILicense>;
|
||||
let licenseService: LicenseService;
|
||||
let appFeatures: AppFeatures;
|
||||
|
||||
const createDefaultPolicyCallback = async (
|
||||
config: AnyPolicyCreateConfig | undefined
|
||||
|
@ -35,7 +39,7 @@ describe('Create Default Policy tests ', () => {
|
|||
const esClientInfo = await elasticsearchServiceMock.createClusterClient().asInternalUser.info();
|
||||
esClientInfo.cluster_name = '';
|
||||
esClientInfo.cluster_uuid = '';
|
||||
return createDefaultPolicy(licenseService, config, cloud, esClientInfo);
|
||||
return createDefaultPolicy(licenseService, config, cloud, esClientInfo, appFeatures);
|
||||
};
|
||||
|
||||
beforeEach(() => {
|
||||
|
@ -43,7 +47,9 @@ describe('Create Default Policy tests ', () => {
|
|||
licenseService = new LicenseService();
|
||||
licenseService.start(licenseEmitter);
|
||||
licenseEmitter.next(Platinum); // set license level to platinum
|
||||
appFeatures = createAppFeaturesMock();
|
||||
});
|
||||
|
||||
describe('When no config is set', () => {
|
||||
it('Should return PolicyConfig for events only when license is at least platinum', async () => {
|
||||
const defaultPolicy = policyFactory();
|
||||
|
@ -174,6 +180,7 @@ describe('Create Default Policy tests ', () => {
|
|||
});
|
||||
});
|
||||
});
|
||||
|
||||
it('Should return process, file and network events enabled when preset is EDR Essential', async () => {
|
||||
const config = createEndpointConfig({ preset: 'EDREssential' });
|
||||
const policy = await createDefaultPolicyCallback(config);
|
||||
|
@ -190,6 +197,7 @@ describe('Create Default Policy tests ', () => {
|
|||
});
|
||||
});
|
||||
});
|
||||
|
||||
it('Should return the default config when preset is EDR Complete', async () => {
|
||||
const config = createEndpointConfig({ preset: 'EDRComplete' });
|
||||
const policy = await createDefaultPolicyCallback(config);
|
||||
|
@ -199,7 +207,37 @@ describe('Create Default Policy tests ', () => {
|
|||
defaultPolicy.meta.cloud = true;
|
||||
expect(policy).toMatchObject(defaultPolicy);
|
||||
});
|
||||
|
||||
it('should set policy to event collection only if endpointPolicyProtections appFeature is disabled', async () => {
|
||||
appFeatures = createAppFeaturesMock(
|
||||
ALL_APP_FEATURE_KEYS.filter((key) => key !== 'endpoint_policy_protections')
|
||||
);
|
||||
|
||||
await expect(
|
||||
createDefaultPolicyCallback(createEndpointConfig({ preset: 'EDRComplete' }))
|
||||
).resolves.toMatchObject({
|
||||
linux: {
|
||||
behavior_protection: { mode: 'off' },
|
||||
malware: { mode: 'off' },
|
||||
memory_protection: { mode: 'off' },
|
||||
},
|
||||
mac: {
|
||||
behavior_protection: { mode: 'off' },
|
||||
malware: { mode: 'off' },
|
||||
memory_protection: { mode: 'off' },
|
||||
},
|
||||
windows: {
|
||||
antivirus_registration: { enabled: false },
|
||||
attack_surface_reduction: { credential_hardening: { enabled: false } },
|
||||
behavior_protection: { mode: 'off' },
|
||||
malware: { blocklist: false },
|
||||
memory_protection: { mode: 'off' },
|
||||
ransomware: { mode: 'off' },
|
||||
},
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('When cloud config is set', () => {
|
||||
const createCloudConfig = (): PolicyCreateCloudConfig => ({
|
||||
type: 'cloud',
|
||||
|
|
|
@ -7,6 +7,8 @@
|
|||
|
||||
import type { CloudSetup } from '@kbn/cloud-plugin/server';
|
||||
import type { InfoResponse } from '@elastic/elasticsearch/lib/api/types';
|
||||
import { AppFeatureSecurityKey } from '../../../common/types/app_features';
|
||||
import type { AppFeatures } from '../../lib/app_features';
|
||||
import {
|
||||
policyFactory as policyConfigFactory,
|
||||
policyFactoryWithoutPaidFeatures as policyConfigFactoryWithoutPaidFeatures,
|
||||
|
@ -20,7 +22,10 @@ import {
|
|||
ENDPOINT_CONFIG_PRESET_NGAV,
|
||||
ENDPOINT_CONFIG_PRESET_DATA_COLLECTION,
|
||||
} from '../constants';
|
||||
import { disableProtections } from '../../../common/endpoint/models/policy_config_helpers';
|
||||
import {
|
||||
disableProtections,
|
||||
ensureOnlyEventCollectionIsAllowed,
|
||||
} from '../../../common/endpoint/models/policy_config_helpers';
|
||||
|
||||
/**
|
||||
* Create the default endpoint policy based on the current license and configuration type
|
||||
|
@ -29,7 +34,8 @@ export const createDefaultPolicy = (
|
|||
licenseService: LicenseService,
|
||||
config: AnyPolicyCreateConfig | undefined,
|
||||
cloud: CloudSetup,
|
||||
esClientInfo: InfoResponse
|
||||
esClientInfo: InfoResponse,
|
||||
appFeatures: AppFeatures
|
||||
): PolicyConfig => {
|
||||
const factoryPolicy = policyConfigFactory();
|
||||
|
||||
|
@ -44,15 +50,21 @@ export const createDefaultPolicy = (
|
|||
: factoryPolicy.meta.cluster_uuid;
|
||||
factoryPolicy.meta.license_uid = licenseService.getLicenseUID();
|
||||
|
||||
const defaultPolicyPerType =
|
||||
let defaultPolicyPerType: PolicyConfig =
|
||||
config?.type === 'cloud'
|
||||
? getCloudPolicyConfig(factoryPolicy)
|
||||
: getEndpointPolicyWithIntegrationConfig(factoryPolicy, config);
|
||||
|
||||
// Apply license limitations in the final step, so it's not overriden (see malware popup)
|
||||
return licenseService.isPlatinumPlus()
|
||||
? defaultPolicyPerType
|
||||
: policyConfigFactoryWithoutPaidFeatures(defaultPolicyPerType);
|
||||
if (!licenseService.isPlatinumPlus()) {
|
||||
defaultPolicyPerType = policyConfigFactoryWithoutPaidFeatures(defaultPolicyPerType);
|
||||
}
|
||||
|
||||
// If no Policy Protection allowed (ex. serverless)
|
||||
if (!appFeatures.isEnabled(AppFeatureSecurityKey.endpointPolicyProtections)) {
|
||||
defaultPolicyPerType = ensureOnlyEventCollectionIsAllowed(defaultPolicyPerType);
|
||||
}
|
||||
|
||||
return defaultPolicyPerType;
|
||||
};
|
||||
|
||||
/**
|
||||
|
|
|
@ -59,7 +59,7 @@ export class AppFeatures {
|
|||
return this.appFeatures.has(appFeatureKey);
|
||||
}
|
||||
|
||||
private registerEnabledKibanaFeatures() {
|
||||
protected registerEnabledKibanaFeatures() {
|
||||
if (this.featuresSetup == null) {
|
||||
throw new Error(
|
||||
'Cannot sync kibana features as featuresSetup is not present. Did you call init?'
|
||||
|
|
|
@ -0,0 +1,38 @@
|
|||
/*
|
||||
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
|
||||
* or more contributor license agreements. Licensed under the Elastic License
|
||||
* 2.0; you may not use this file except in compliance with the Elastic License
|
||||
* 2.0.
|
||||
*/
|
||||
|
||||
import type { Logger } from '@kbn/core/server';
|
||||
import type { PluginSetupContract as FeaturesPluginSetup } from '@kbn/features-plugin/server';
|
||||
import { loggingSystemMock } from '@kbn/core-logging-server-mocks';
|
||||
import { featuresPluginMock } from '@kbn/features-plugin/server/mocks';
|
||||
import { AppFeatures } from './app_features';
|
||||
import type { AppFeatureKeys, ExperimentalFeatures } from '../../../common';
|
||||
import { ALL_APP_FEATURE_KEYS, allowedExperimentalValues } from '../../../common';
|
||||
|
||||
class AppFeaturesMock extends AppFeatures {
|
||||
protected registerEnabledKibanaFeatures() {
|
||||
// NOOP
|
||||
}
|
||||
}
|
||||
|
||||
export const createAppFeaturesMock = (
|
||||
/** What features keys should be enabled. Default is all */
|
||||
enabledFeatureKeys: AppFeatureKeys = [...ALL_APP_FEATURE_KEYS],
|
||||
experimentalFeatures: ExperimentalFeatures = { ...allowedExperimentalValues },
|
||||
featuresPluginSetupContract: FeaturesPluginSetup = featuresPluginMock.createSetup(),
|
||||
logger: Logger = loggingSystemMock.create().get('appFeatureMock')
|
||||
) => {
|
||||
const appFeatures = new AppFeaturesMock(logger, experimentalFeatures);
|
||||
|
||||
appFeatures.init(featuresPluginSetupContract);
|
||||
|
||||
if (enabledFeatureKeys) {
|
||||
appFeatures.set(enabledFeatureKeys);
|
||||
}
|
||||
|
||||
return appFeatures;
|
||||
};
|
|
@ -17,6 +17,7 @@ import { Dataset } from '@kbn/rule-registry-plugin/server';
|
|||
import type { ListPluginSetup } from '@kbn/lists-plugin/server';
|
||||
import type { ILicense } from '@kbn/licensing-plugin/server';
|
||||
|
||||
import { turnOffPolicyProtectionsIfNotSupported } from './endpoint/migrations/turn_off_policy_protections';
|
||||
import { endpointSearchStrategyProvider } from './search_strategy/endpoint';
|
||||
import { getScheduleNotificationResponseActionsService } from './lib/detection_engine/rule_response_actions/schedule_notification_response_actions';
|
||||
import { siemGuideId, siemGuideConfig } from '../common/guided_onboarding/siem_guide_config';
|
||||
|
@ -438,6 +439,15 @@ export class Plugin implements ISecuritySolutionPlugin {
|
|||
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
|
||||
plugins.fleet!;
|
||||
let manifestManager: ManifestManager | undefined;
|
||||
const endpointFleetServicesFactory = new EndpointFleetServicesFactory(
|
||||
{
|
||||
agentService,
|
||||
packageService,
|
||||
packagePolicyService,
|
||||
agentPolicyService,
|
||||
},
|
||||
core.savedObjects
|
||||
);
|
||||
|
||||
this.licensing$ = plugins.licensing.license$;
|
||||
|
||||
|
@ -459,17 +469,23 @@ export class Plugin implements ISecuritySolutionPlugin {
|
|||
esClient: core.elasticsearch.client.asInternalUser,
|
||||
});
|
||||
|
||||
// Migrate artifacts to fleet and then start the minifest task after that is done
|
||||
// Migrate artifacts to fleet and then start the manifest task after that is done
|
||||
plugins.fleet.fleetSetupCompleted().then(() => {
|
||||
logger.info('Dependent plugin setup complete - Starting ManifestTask');
|
||||
|
||||
if (this.manifestTask) {
|
||||
logger.info('Dependent plugin setup complete - Starting ManifestTask');
|
||||
this.manifestTask.start({
|
||||
taskManager,
|
||||
});
|
||||
} else {
|
||||
logger.error(new Error('User artifacts task not available.'));
|
||||
}
|
||||
|
||||
turnOffPolicyProtectionsIfNotSupported(
|
||||
core.elasticsearch.client.asInternalUser,
|
||||
endpointFleetServicesFactory.asInternalUser(),
|
||||
this.appFeatures,
|
||||
logger
|
||||
);
|
||||
});
|
||||
|
||||
// License related start
|
||||
|
@ -493,15 +509,7 @@ export class Plugin implements ISecuritySolutionPlugin {
|
|||
packagePolicyService,
|
||||
logger
|
||||
),
|
||||
endpointFleetServicesFactory: new EndpointFleetServicesFactory(
|
||||
{
|
||||
agentService,
|
||||
packageService,
|
||||
packagePolicyService,
|
||||
agentPolicyService,
|
||||
},
|
||||
core.savedObjects
|
||||
),
|
||||
endpointFleetServicesFactory,
|
||||
security: plugins.security,
|
||||
alerting: plugins.alerting,
|
||||
config: this.config,
|
||||
|
@ -522,6 +530,7 @@ export class Plugin implements ISecuritySolutionPlugin {
|
|||
),
|
||||
createFleetActionsClient,
|
||||
esClient: core.elasticsearch.client.asInternalUser,
|
||||
appFeatures: this.appFeatures,
|
||||
});
|
||||
|
||||
this.telemetryReceiver.start(
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue