-
Notifications
You must be signed in to change notification settings - Fork 8.6k
[Security Solution][Endpoint] Guided onboarding configures events only for endpoint policy #144087
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Merged
gergoabraham
merged 10 commits into
elastic:main
from
gergoabraham:feature/olm-5224-guided-onboarding-configures-events-only-for-endpoint-policy
Nov 2, 2022
Merged
Changes from all commits
Commits
Show all changes
10 commits
Select commit
Hold shift + click to select a range
be7bd43
update comment
gergoabraham 13b0763
use previous setting instead of possibly enabling features
gergoabraham d8bf21b
disable protections for default Endpoint config
gergoabraham a260a80
refactor tests
gergoabraham 07e8184
extract config preset selection logic
gergoabraham 4747431
fix fleet_integration jest tests
gergoabraham 5b30c69
use original logic for basic tier policy factory
gergoabraham 8324562
update tests for `disableProtections`
gergoabraham 570c16d
apply license limitations as the final step
gergoabraham 7218b30
Merge branch 'main' into feature/olm-5224-guided-onboarding-configure…
kibanamachine File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
175 changes: 175 additions & 0 deletions
175
x-pack/plugins/security_solution/common/endpoint/models/policy_config_helpers.test.ts
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| 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' }, | ||
| }, | ||
| }; |
105 changes: 105 additions & 0 deletions
105
x-pack/plugins/security_solution/common/endpoint/models/policy_config_helpers.ts
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| 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 => { | ||
| 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, | ||
| }, | ||
| }); | ||
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
Uh oh!
There was an error while loading. Please reload this page.
There was a problem hiding this comment.
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
getCloudPolicyConfigdoes 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
disabledProtectionsfunction, we could create apolicyFactoryWithDisabledProtections, then Typescript would ensure every new field added on PolicyConfig is explicitly set on the policy:For example (in policy_config.ts):
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.There was a problem hiding this comment.
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
PolicyConfigconstant to the tests that will throw a type error ifPolicyConfigis 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 reusedisableProtections()right now, because it only disables protections and does not touch anything else.What do you think? Would you like me to add
disableProtections()togetCloudPolicyConfig()?There was a problem hiding this comment.
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! 🙌
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
That makes sense to me,
getCloudPolicyConfiginherits all policy defaults, then needs all protections off, and haspolicy.linux.events.session_dataastrue. If you can make that change that would be awesome!