Skip to content

Commit

Permalink
[Security Solution][Endpoint] Add API checks to Endpoint Policy creat…
Browse files Browse the repository at this point in the history
…e/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.
  • Loading branch information
paul-tavares committed Aug 11, 2023
1 parent 2ba659d commit 27c394c
Show file tree
Hide file tree
Showing 17 changed files with 715 additions and 71 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -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', () => {
Expand Down Expand Up @@ -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,
},
Expand Down Expand Up @@ -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 = {
Expand All @@ -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: {
Expand Down Expand Up @@ -187,4 +254,4 @@ export const eventsOnlyPolicy: PolicyConfig = {
capture_env_vars: 'LD_PRELOAD,LD_LIBRARY_PATH',
},
},
};
});
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand Down Expand Up @@ -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,
};
};
Original file line number Diff line number Diff line change
@@ -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;
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand Down Expand Up @@ -69,6 +70,7 @@ export interface EndpointAppContextServiceStartContract {
actionCreateService: ActionCreateService | undefined;
cloud: CloudSetup;
esClient: ElasticsearchClient;
appFeatures: AppFeatures;
}

/**
Expand Down Expand Up @@ -106,6 +108,7 @@ export class EndpointAppContextService {
featureUsageService,
endpointMetadataService,
esClient,
appFeatures,
} = dependencies;

registerIngestCallback(
Expand All @@ -117,7 +120,8 @@ export class EndpointAppContextService {
alerting,
licenseService,
exceptionListsClient,
cloud
cloud,
appFeatures
)
);

Expand All @@ -134,7 +138,8 @@ export class EndpointAppContextService {
featureUsageService,
endpointMetadataService,
cloud,
esClient
esClient,
appFeatures
)
);

Expand Down
Loading

0 comments on commit 27c394c

Please sign in to comment.