Skip to content
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

[Security Solution] Add Endpoint policy feature checks #83972

Merged
merged 10 commits into from
Nov 25, 2020
Original file line number Diff line number Diff line change
Expand Up @@ -68,3 +68,8 @@ export const factory = (): PolicyConfig => {
},
};
};

/**
* Reflects what string the Endpoint will use when message field is default/empty
*/
export const DefaultMalwareMessage = 'Elastic Security { action } { filename }';
33 changes: 14 additions & 19 deletions x-pack/plugins/security_solution/common/license/license.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@
* you may not use this file except in compliance with the Elastic License.
*/
import { Observable, Subscription } from 'rxjs';
import { ILicense } from '../../../licensing/common/types';
import { ILicense, LicenseType } from '../../../licensing/common/types';

// Generic license service class that works with the license observable
// Both server and client plugins instancates a singleton version of this class
Expand Down Expand Up @@ -36,25 +36,20 @@ export class LicenseService {
return this.observable;
}

public isGoldPlus() {
return (
this.licenseInformation?.isAvailable &&
this.licenseInformation?.isActive &&
this.licenseInformation?.hasAtLeast('gold')
);
public isAtLeast(level: LicenseType): boolean {
return isAtLeast(this.licenseInformation, level);
}
public isPlatinumPlus() {
return (
this.licenseInformation?.isAvailable &&
this.licenseInformation?.isActive &&
this.licenseInformation?.hasAtLeast('platinum')
);
public isGoldPlus(): boolean {
return this.isAtLeast('gold');
}
public isEnterprise() {
return (
this.licenseInformation?.isAvailable &&
this.licenseInformation?.isActive &&
this.licenseInformation?.hasAtLeast('enterprise')
);
public isPlatinumPlus(): boolean {
return this.isAtLeast('platinum');
}
public isEnterprise(): boolean {
return this.isAtLeast('enterprise');
}
}

export const isAtLeast = (license: ILicense | null, level: LicenseType): boolean => {
Copy link
Contributor

Choose a reason for hiding this comment

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

wait this is nice and less repetitive :D

return license !== null && license.isAvailable && license.isActive && license.hasAtLeast(level);
};
110 changes: 110 additions & 0 deletions x-pack/plugins/security_solution/common/license/policy_config.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,110 @@
/*
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
* or more contributor license agreements. Licensed under the Elastic License;
* you may not use this file except in compliance with the Elastic License.
*/

import {
isEndpointPolicyValidForLicense,
unsetPolicyFeaturesAboveLicenseLevel,
} from './policy_config';
import { DefaultMalwareMessage, factory } from '../endpoint/models/policy_config';
import { licenseMock } from '../../../licensing/common/licensing.mock';

describe('policy_config and licenses', () => {
Copy link
Contributor

Choose a reason for hiding this comment

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

this entire file is 🔥

const Platinum = licenseMock.createLicense({ license: { type: 'platinum', mode: 'platinum' } });
const Gold = licenseMock.createLicense({ license: { type: 'gold', mode: 'gold' } });
const Basic = licenseMock.createLicense({ license: { type: 'basic', mode: 'basic' } });

describe('isEndpointPolicyValidForLicense', () => {
it('allows malware notification to be disabled with a Platinum license', () => {
const policy = factory();
policy.windows.popup.malware.enabled = false; // make policy change
const valid = isEndpointPolicyValidForLicense(policy, Platinum);
expect(valid).toBeTruthy();
});
it('blocks windows malware notification changes below Platinum licenses', () => {
const policy = factory();
policy.windows.popup.malware.enabled = false; // make policy change
let valid = isEndpointPolicyValidForLicense(policy, Gold);
expect(valid).toBeFalsy();

valid = isEndpointPolicyValidForLicense(policy, Basic);
expect(valid).toBeFalsy();
});

it('blocks mac malware notification changes below Platinum licenses', () => {
const policy = factory();
policy.mac.popup.malware.enabled = false; // make policy change
let valid = isEndpointPolicyValidForLicense(policy, Gold);
expect(valid).toBeFalsy();

valid = isEndpointPolicyValidForLicense(policy, Basic);
expect(valid).toBeFalsy();
});

it('allows malware notification message changes with a Platinum license', () => {
const policy = factory();
policy.windows.popup.malware.message = 'BOOM'; // make policy change
const valid = isEndpointPolicyValidForLicense(policy, Platinum);
expect(valid).toBeTruthy();
});
it('blocks windows malware notification message changes below Platinum licenses', () => {
const policy = factory();
policy.windows.popup.malware.message = 'BOOM'; // make policy change
let valid = isEndpointPolicyValidForLicense(policy, Gold);
expect(valid).toBeFalsy();

valid = isEndpointPolicyValidForLicense(policy, Basic);
expect(valid).toBeFalsy();
});
it('blocks mac malware notification message changes below Platinum licenses', () => {
const policy = factory();
policy.mac.popup.malware.message = 'BOOM'; // make policy change
let valid = isEndpointPolicyValidForLicense(policy, Gold);
expect(valid).toBeFalsy();

valid = isEndpointPolicyValidForLicense(policy, Basic);
expect(valid).toBeFalsy();
});

it('allows default policyConfig with Basic', () => {
const policy = factory();
const valid = isEndpointPolicyValidForLicense(policy, Basic);
expect(valid).toBeTruthy();
});
});

describe('unsetPolicyFeaturesAboveLicenseLevel', () => {
it('does not change any fields with a Platinum license', () => {
const policy = factory();
const popupMessage = 'WOOP WOOP';
policy.windows.popup.malware.message = popupMessage;
policy.mac.popup.malware.message = popupMessage;
policy.windows.popup.malware.enabled = false;

const retPolicy = unsetPolicyFeaturesAboveLicenseLevel(policy, Platinum);
expect(retPolicy.windows.popup.malware.enabled).toBeFalsy();
expect(retPolicy.windows.popup.malware.message).toEqual(popupMessage);
expect(retPolicy.mac.popup.malware.message).toEqual(popupMessage);
});
it('resets Platinum-paid fields for lower license tiers', () => {
const defaults = factory(); // reference
const policy = factory(); // what we will modify, and should be reset
const popupMessage = 'WOOP WOOP';
policy.windows.popup.malware.message = popupMessage;
policy.mac.popup.malware.message = popupMessage;
policy.windows.popup.malware.enabled = false;

const retPolicy = unsetPolicyFeaturesAboveLicenseLevel(policy, Gold);
expect(retPolicy.windows.popup.malware.enabled).toEqual(
defaults.windows.popup.malware.enabled
);
expect(retPolicy.windows.popup.malware.message).not.toEqual(popupMessage);
expect(retPolicy.mac.popup.malware.message).not.toEqual(popupMessage);

// need to invert the test, since it could be either value
expect(['', DefaultMalwareMessage]).toContain(retPolicy.windows.popup.malware.message);
});
});
});
66 changes: 66 additions & 0 deletions x-pack/plugins/security_solution/common/license/policy_config.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,66 @@
/*
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
* or more contributor license agreements. Licensed under the Elastic License;
* you may not use this file except in compliance with the Elastic License.
*/

import { ILicense } from '../../../licensing/common/types';
import { isAtLeast } from './license';
import { PolicyConfig } from '../endpoint/types';
import { DefaultMalwareMessage, factory } from '../endpoint/models/policy_config';

/**
* Given an endpoint package policy, verifies that all enabled features that
* require a certain license level have a valid license for them.
*/
export const isEndpointPolicyValidForLicense = (
policy: PolicyConfig,
license: ILicense | null
): boolean => {
if (isAtLeast(license, 'platinum')) {
return true; // currently, platinum allows all features
}

const defaults = factory();

// only platinum or higher may disable malware notification
if (
policy.windows.popup.malware.enabled !== defaults.windows.popup.malware.enabled ||
policy.mac.popup.malware.enabled !== defaults.mac.popup.malware.enabled
) {
return false;
}

// Only Platinum or higher may change the malware message (which can be blank or what Endpoint defaults)
if (
[policy.windows, policy.mac].some(
(p) => p.popup.malware.message !== '' && p.popup.malware.message !== DefaultMalwareMessage
)
) {
return false;
}

return true;
};

/**
* Resets paid features in a PolicyConfig back to default values
* when unsupported by the given license level.
*/
export const unsetPolicyFeaturesAboveLicenseLevel = (
policy: PolicyConfig,
license: ILicense | null
): PolicyConfig => {
if (isAtLeast(license, 'platinum')) {
return policy;
}

const defaults = factory();
// set any license-gated features back to the defaults
policy.windows.popup.malware.enabled = defaults.windows.popup.malware.enabled;
policy.mac.popup.malware.enabled = defaults.mac.popup.malware.enabled;
policy.windows.popup.malware.message = defaults.windows.popup.malware.message;
policy.mac.popup.malware.message = defaults.mac.popup.malware.message;

return policy;
};
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
*/

import { IHttpFetchError } from 'kibana/public';
import { DefaultMalwareMessage } from '../../../../../../common/endpoint/models/policy_config';
import { PolicyDetailsState, UpdatePolicyResponse } from '../../types';
import {
policyIdFromParams,
Expand Down Expand Up @@ -38,10 +39,8 @@ export const policyDetailsMiddlewareFactory: ImmutableMiddlewareFactory<PolicyDe
policyItem = (await sendGetPackagePolicy(http, id)).item;
// sets default user notification message if policy config message is empty
if (policyItem.inputs[0].config.policy.value.windows.popup.malware.message === '') {
policyItem.inputs[0].config.policy.value.windows.popup.malware.message =
'Elastic Security { action } { filename }';
policyItem.inputs[0].config.policy.value.mac.popup.malware.message =
'Elastic Security { action } { filename }';
policyItem.inputs[0].config.policy.value.windows.popup.malware.message = DefaultMalwareMessage;
policyItem.inputs[0].config.policy.value.mac.popup.malware.message = DefaultMalwareMessage;
}
} catch (error) {
dispatch({
Expand Down