Skip to content
Original file line number Diff line number Diff line change
Expand Up @@ -200,7 +200,7 @@ export const policyFactoryWithoutPaidFeatures = (
...policy.windows.popup,
malware: {
message: '',
enabled: true,
enabled: true, // disabling/configuring malware popup is a paid feature
},
ransomware: {
message: '',
Expand Down Expand Up @@ -230,7 +230,7 @@ export const policyFactoryWithoutPaidFeatures = (
...policy.mac.popup,
malware: {
message: '',
enabled: true,
enabled: true, // disabling/configuring malware popup is a paid feature
},
memory_protection: {
message: '',
Expand All @@ -256,7 +256,7 @@ export const policyFactoryWithoutPaidFeatures = (
...policy.linux.popup,
malware: {
message: '',
enabled: true,
enabled: true, // disabling/configuring malware popup is a paid feature
},
memory_protection: {
message: '',
Expand All @@ -272,7 +272,7 @@ export const policyFactoryWithoutPaidFeatures = (
};

/**
* Strips paid features from an existing or new `PolicyConfig` for gold and below license
* Enables support for paid features for an existing or new `PolicyConfig` for platinum and above license
*/
export const policyFactoryWithSupportedFeatures = (
policy: PolicyConfig = policyFactory()
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,175 @@
/*
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
* or more contributor license agreements. Licensed under the Elastic License
* 2.0; you may not use this file except in compliance with the Elastic License
* 2.0.
*/

import type { PolicyConfig } from '../types';
import { ProtectionModes } from '../types';
import { policyFactory } from './policy_config';
import { disableProtections } from './policy_config_helpers';

describe('Policy Config helpers', () => {
describe('disableProtections', () => {
it('disables all the protections in the default policy', () => {
expect(disableProtections(policyFactory())).toEqual<PolicyConfig>(eventsOnlyPolicy);
});

it('does not enable supported fields', () => {
const defaultPolicy: PolicyConfig = policyFactory();

const notSupported: PolicyConfig['windows']['memory_protection'] = {
mode: ProtectionModes.off,
supported: false,
};

const inputPolicyWithoutSupportedProtections: PolicyConfig = {
...defaultPolicy,
windows: {
...defaultPolicy.windows,
memory_protection: notSupported,
behavior_protection: notSupported,
ransomware: notSupported,
},
mac: {
...defaultPolicy.mac,
memory_protection: notSupported,
behavior_protection: notSupported,
},
linux: {
...defaultPolicy.linux,
memory_protection: notSupported,
behavior_protection: notSupported,
},
};

const expectedPolicyWithoutSupportedProtections: PolicyConfig = {
...eventsOnlyPolicy,
windows: {
...eventsOnlyPolicy.windows,
memory_protection: notSupported,
behavior_protection: notSupported,
ransomware: notSupported,
},
mac: {
...eventsOnlyPolicy.mac,
memory_protection: notSupported,
behavior_protection: notSupported,
},
linux: {
...eventsOnlyPolicy.linux,
memory_protection: notSupported,
behavior_protection: notSupported,
},
};

const policy = disableProtections(inputPolicyWithoutSupportedProtections);

expect(policy).toEqual<PolicyConfig>(expectedPolicyWithoutSupportedProtections);
});

it('does not enable events', () => {
const defaultPolicy: PolicyConfig = policyFactory();

const windowsEvents: typeof defaultPolicy.windows.events = {
dll_and_driver_load: false,
dns: false,
file: false,
network: false,
process: false,
registry: false,
security: false,
};

const macEvents: typeof defaultPolicy.mac.events = {
file: false,
process: false,
network: false,
};

const linuxEvents: typeof defaultPolicy.linux.events = {
file: false,
process: false,
network: false,
session_data: false,
tty_io: false,
};

const expectedPolicy: PolicyConfig = {
...eventsOnlyPolicy,
windows: { ...eventsOnlyPolicy.windows, events: { ...windowsEvents } },
mac: { ...eventsOnlyPolicy.mac, events: { ...macEvents } },
linux: { ...eventsOnlyPolicy.linux, events: { ...linuxEvents } },
};

const inputPolicy = {
...defaultPolicy,
windows: { ...defaultPolicy.windows, events: { ...windowsEvents } },
mac: { ...defaultPolicy.mac, events: { ...macEvents } },
linux: { ...defaultPolicy.linux, events: { ...linuxEvents } },
};

expect(disableProtections(inputPolicy)).toEqual<PolicyConfig>(expectedPolicy);
});
});
});

// This constant makes sure that if the type `PolicyConfig` is ever modified,
// the logic for disabling protections is also modified due to type check.
export const eventsOnlyPolicy: PolicyConfig = {
windows: {
events: {
dll_and_driver_load: true,
dns: true,
file: true,
network: true,
process: true,
registry: true,
security: true,
},
malware: { mode: ProtectionModes.off, blocklist: false },
ransomware: { mode: ProtectionModes.off, supported: true },
memory_protection: { mode: ProtectionModes.off, supported: true },
behavior_protection: { mode: ProtectionModes.off, supported: true },
popup: {
malware: { message: '', enabled: false },
ransomware: { message: '', enabled: false },
memory_protection: { message: '', enabled: false },
behavior_protection: { message: '', enabled: false },
},
logging: { file: 'info' },
antivirus_registration: { enabled: false },
attack_surface_reduction: { credential_hardening: { enabled: false } },
},
mac: {
events: { process: true, file: true, network: true },
malware: { mode: ProtectionModes.off, blocklist: false },
behavior_protection: { mode: ProtectionModes.off, supported: true },
memory_protection: { mode: ProtectionModes.off, supported: true },
popup: {
malware: { message: '', enabled: false },
behavior_protection: { message: '', enabled: false },
memory_protection: { message: '', enabled: false },
},
logging: { file: 'info' },
},
linux: {
events: {
process: true,
file: true,
network: true,
session_data: false,
tty_io: false,
},
malware: { mode: ProtectionModes.off, blocklist: false },
behavior_protection: { mode: ProtectionModes.off, supported: true },
memory_protection: { mode: ProtectionModes.off, supported: true },
popup: {
malware: { message: '', enabled: false },
behavior_protection: { message: '', enabled: false },
memory_protection: { message: '', enabled: false },
},
logging: { file: 'info' },
},
};
Original file line number Diff line number Diff line change
@@ -0,0 +1,105 @@
/*
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
* or more contributor license agreements. Licensed under the Elastic License
* 2.0; you may not use this file except in compliance with the Elastic License
* 2.0.
*/

import type { PolicyConfig } from '../types';
import { ProtectionModes } from '../types';

/**
* Returns a copy of the passed `PolicyConfig` with all protections set to disabled.
*
* @param policy
* @returns
*/
export const disableProtections = (policy: PolicyConfig): PolicyConfig => {
Copy link
Copy Markdown
Contributor

@opauloh opauloh Oct 27, 2022

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I was thinking about your comment on slack that getCloudPolicyConfig does not have all protections disabled, and I'm wondering how we can enforce that all protections will be disabled in case more protections are added in the future since protections are not included under a specific object.

So I think, instead of a disabledProtections function, we could create a policyFactoryWithDisabledProtections, then Typescript would ensure every new field added on PolicyConfig is explicitly set on the policy:

For example (in policy_config.ts):

/**
 * Return a policyFactory with all protections disabled`
 */
export const policyFactoryWithDisabledProtections = (): ReturnType<typeof policyFactory> => {
  const policy = policyFactory();
  return {
    windows: {
      events: {
        ...policy.windows.events
      },
      malware: {
        mode: ProtectionModes.off,
        blocklist: true,
      },
      ransomware: {
        mode: ProtectionModes.off,
        supported: false,
      },
      memory_protection: {
        mode: ProtectionModes.off,
        supported: false,
      },
      behavior_protection: {
        mode: ProtectionModes.off,
        supported: false,
      },
      popup: {
        malware: {
          message: '',
          enabled: false,
        },
        ransomware: {
          message: '',
          enabled: false,
        },
        memory_protection: {
          message: '',
          enabled: false,
        },
        behavior_protection: {
          message: '',
          enabled: false,
        },
      },
      logging: {
        ...policy.windows.logging
      },
      antivirus_registration: {
        enabled: false,
      },
      attack_surface_reduction: {
        credential_hardening: {
          enabled: false,
        },
      },
    },
    mac: {
      events: {
        ...policy.mac.events
      },
      malware: {
        mode: ProtectionModes.off,
        blocklist: true,
      },
      behavior_protection: {
        mode: ProtectionModes.off,
        supported: true,
      },
      memory_protection: {
        mode: ProtectionModes.off,
        supported: true,
      },
      popup: {
        malware: {
          message: '',
          enabled: false,
        },
        behavior_protection: {
          message: '',
          enabled: false,
        },
        memory_protection: {
          message: '',
          enabled: false,
        },
      },
      logging: {
        ...policy.mac.logging
      },
    },
    linux: {
      events: {
        ...policy.linux.events
      },
      malware: {
        mode: ProtectionModes.off,
        blocklist: false,
      },
      behavior_protection: {
        mode: ProtectionModes.off,
        supported: false,
      },
      memory_protection: {
        mode: ProtectionModes.off,
        supported: false,
      },
      popup: {
        malware: {
          message: '',
          enabled: false,
        },
        behavior_protection: {
          message: '',
          enabled: false,
        },
        memory_protection: {
          message: '',
          enabled: false,
        },
      },
      logging: {
        ...policy.linux.logging
      },
    },
  };
};

This way, we would still spread over specific fields, but new fields on the root would need to be manually added, and that would enforce the addition of new protection fields to the policy into this function.

Also, this way, the getCloudPolicyConfig() function would be able to reuse the policy.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

That was my biggest question, too: how to ensure that we won't forget to add new protections to disableProtections?

Actually, it is enforced: I added a PolicyConfig constant to the tests that will throw a type error if PolicyConfig is ever modified. See it here.

Why did I add it to the test? Mostly because I didn't want to interfere with the current logic. Now there is only one default config (all ON). And there are 3 modifiers: one for stripping of paid features, one for enabling the support for paid features, and the new one for disabling protections.

I think we can go with either having a constant config for every scenario, or having these modifiers that can be applied after each other. As subsets of the config are independent from each other (like events from protections), I think using modifiers is a better choice.

This means, that getCloudPolicyConfig() can reuse disableProtections() right now, because it only disables protections and does not touch anything else.

What do you think? Would you like me to add disableProtections() to getCloudPolicyConfig()?

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hey, @gergoabraham just tested adding new protection and the test works great, nicely done! 🙌

image

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What do you think? Would you like me to add disableProtections() to getCloudPolicyConfig()?

That makes sense to me, getCloudPolicyConfig inherits all policy defaults, then needs all protections off, and has policy.linux.events.session_data as true. If you can make that change that would be awesome!

const result = disableCommonProtections(policy);

return {
...result,
windows: {
...result.windows,
...getDisabledWindowsSpecificProtections(result),
popup: {
...result.windows.popup,
...getDisabledWindowsSpecificPopups(result),
},
},
};
};

const disableCommonProtections = (policy: PolicyConfig) => {
let policyOutput = policy;

for (const key in policyOutput) {
if (Object.prototype.hasOwnProperty.call(policyOutput, key)) {
const os = key as keyof PolicyConfig;

policyOutput = {
...policyOutput,
[os]: {
...policyOutput[os],
...getDisabledCommonProtectionsForOS(policyOutput, os),
popup: {
...policyOutput[os].popup,
...getDisabledCommonPopupsForOS(policyOutput, os),
},
},
};
}
}
return policyOutput;
};

const getDisabledCommonProtectionsForOS = (policy: PolicyConfig, os: keyof PolicyConfig) => ({
behavior_protection: {
...policy[os].behavior_protection,
mode: ProtectionModes.off,
},
memory_protection: {
...policy[os].memory_protection,
mode: ProtectionModes.off,
},
malware: {
...policy[os].malware,
blocklist: false,
mode: ProtectionModes.off,
},
});

const getDisabledCommonPopupsForOS = (policy: PolicyConfig, os: keyof PolicyConfig) => ({
behavior_protection: {
...policy[os].popup.behavior_protection,
enabled: false,
},
malware: {
...policy[os].popup.malware,
enabled: false,
},
memory_protection: {
...policy[os].popup.memory_protection,
enabled: false,
},
});

const getDisabledWindowsSpecificProtections = (policy: PolicyConfig) => ({
ransomware: {
...policy.windows.ransomware,
mode: ProtectionModes.off,
},
attack_surface_reduction: {
...policy.windows.attack_surface_reduction,
credential_hardening: {
enabled: false,
},
},
});

const getDisabledWindowsSpecificPopups = (policy: PolicyConfig) => ({
ransomware: {
...policy.windows.popup.ransomware,
enabled: false,
},
});
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,7 @@ import type { DeletePackagePoliciesResponse } from '@kbn/fleet-plugin/common';
import { createMockPolicyData } from '../endpoint/services/feature_usage/mocks';
import { ALL_ENDPOINT_ARTIFACT_LIST_IDS } from '../../common/endpoint/service/artifacts/constants';
import { ENDPOINT_EVENT_FILTERS_LIST_ID } from '@kbn/securitysolution-list-constants';
import { disableProtections } from '../../common/endpoint/models/policy_config_helpers';

jest.mock('uuid', () => ({
v4: (): string => 'NEW_UUID',
Expand Down Expand Up @@ -89,7 +90,7 @@ describe('ingest_integration tests ', () => {
streams: [],
config: {
integration_config: {},
policy: { value: policyFactory() },
policy: { value: disableProtections(policyFactory()) },
artifact_manifest: { value: manifest },
},
});
Expand Down
Loading