Skip to content

Commit

Permalink
[Security Solution] add endpoint policy billable flag (#186404)
Browse files Browse the repository at this point in the history
## Summary

Adds a `meta.billable` flag to the endpoint policy. This flag is used to
make sure we don't bill policy configurations that aren't intended to be
billed in serverless (such as data collection only).


### Checklist

- [x] [Unit or functional
tests](https://www.elastic.co/guide/en/kibana/master/development-tests.html)
were updated or added to match the most common scenarios


### For maintainers

- [x] This was checked for breaking API changes and was [labeled
appropriately](https://www.elastic.co/guide/en/kibana/master/contributing.html#kibana-release-notes-process)
  • Loading branch information
joeypoon authored Jun 21, 2024
1 parent e969602 commit 4f799b8
Show file tree
Hide file tree
Showing 13 changed files with 214 additions and 19 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,8 @@
import type { PolicyConfig } from '../types';
import { ProtectionModes, AntivirusRegistrationModes } from '../types';

import { isBillablePolicy } from './policy_config_helpers';

/**
* Return a new default `PolicyConfig` for platinum and above licenses
*/
Expand All @@ -19,7 +21,7 @@ export const policyFactory = (
clusterName = '',
serverless = false
): PolicyConfig => {
return {
const policy: PolicyConfig = {
meta: {
license,
license_uuid: licenseUid,
Expand Down Expand Up @@ -174,6 +176,9 @@ export const policyFactory = (
},
},
};
policy.meta.billable = isBillablePolicy(policy);

return policy;
};

/**
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,8 @@ import {
disableProtections,
isPolicySetToEventCollectionOnly,
ensureOnlyEventCollectionIsAllowed,
isBillablePolicy,
getPolicyProtectionsReference,
} from './policy_config_helpers';
import { set } from 'lodash';

Expand Down Expand Up @@ -192,6 +194,35 @@ describe('Policy Config helpers', () => {
}
);
});

describe('isBillablePolicy', () => {
it('doesnt bill if serverless false', () => {
const policy = policyFactory();
const isBillable = isBillablePolicy(policy);
expect(policy.meta.serverless).toBe(false);
expect(isBillable).toBe(false);
});

it('doesnt bill if event collection only', () => {
const policy = ensureOnlyEventCollectionIsAllowed(policyFactory());
policy.meta.serverless = true;
const isBillable = isBillablePolicy(policy);
expect(isBillable).toBe(false);
});

it.each(getPolicyProtectionsReference())(
'correctly bills if $keyPath is enabled',
(feature) => {
for (const os of feature.osList) {
const policy = ensureOnlyEventCollectionIsAllowed(policyFactory());
policy.meta.serverless = true;
set(policy, `${os}.${feature.keyPath}`, feature.enableValue);
const isBillable = isBillablePolicy(policy);
expect(isBillable).toBe(true);
}
}
);
});
});

// This constant makes sure that if the type `PolicyConfig` is ever modified,
Expand All @@ -205,6 +236,7 @@ export const eventsOnlyPolicy = (): PolicyConfig => ({
cluster_name: '',
cluster_uuid: '',
serverless: false,
billable: false,
},
windows: {
events: {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@ const allOsValues = [
PolicyOperatingSystem.windows,
];

const getPolicyProtectionsReference = (): PolicyProtectionReference[] => [
export const getPolicyProtectionsReference = (): PolicyProtectionReference[] => [
{
keyPath: 'malware.mode',
osList: [...allOsValues],
Expand Down Expand Up @@ -199,3 +199,9 @@ export const isPolicySetToEventCollectionOnly = (
message,
};
};

export function isBillablePolicy(policy: PolicyConfig) {
if (!policy.meta.serverless) return false;

return !isPolicySetToEventCollectionOnly(policy).isOnlyCollectingEvents;
}
Original file line number Diff line number Diff line change
Expand Up @@ -949,6 +949,7 @@ export interface PolicyConfig {
cluster_uuid: string;
cluster_name: string;
serverless: boolean;
billable?: boolean;
heartbeatinterval?: number;
};
global_manifest_version: 'latest' | string;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -279,6 +279,7 @@ describe('policy details: ', () => {
cluster_name: '',
cluster_uuid: '',
serverless: false,
billable: false,
},
windows: {
events: {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -67,6 +67,7 @@ import type {
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 * as PolicyConfigHelpers from '../../common/endpoint/models/policy_config_helpers';
import { disableProtections } from '../../common/endpoint/models/policy_config_helpers';
import type { ProductFeaturesService } from '../lib/product_features_service/product_features_service';
import { createProductFeaturesServiceMock } from '../lib/product_features_service/mocks';
Expand Down Expand Up @@ -322,6 +323,24 @@ describe('ingest_integration tests ', () => {
expect(manifestManager.pushArtifacts).not.toHaveBeenCalled();
expect(manifestManager.commit).not.toHaveBeenCalled();
});

it('should correctly set meta.billable', async () => {
const isBillablePolicySpy = jest.spyOn(PolicyConfigHelpers, 'isBillablePolicy');
isBillablePolicySpy.mockReturnValue(false);
const manifestManager = buildManifestManagerMock();

let packagePolicy = await invokeCallback(manifestManager);
expect(isBillablePolicySpy).toHaveBeenCalled();
expect(packagePolicy.inputs[0].config!.policy.value.meta.billable).toBe(false);

isBillablePolicySpy.mockReset();
isBillablePolicySpy.mockReturnValue(true);
packagePolicy = await invokeCallback(manifestManager);
expect(isBillablePolicySpy).toHaveBeenCalled();
expect(packagePolicy.inputs[0].config!.policy.value.meta.billable).toBe(true);

isBillablePolicySpy.mockRestore();
});
});

describe('package policy post create callback', () => {
Expand Down Expand Up @@ -844,6 +863,7 @@ describe('ingest_integration tests ', () => {
mockPolicy.meta.cluster_uuid = 'updated-uuid';
mockPolicy.meta.license_uuid = 'updated-uid';
mockPolicy.meta.serverless = false;
mockPolicy.meta.billable = false;
const logger = loggingSystemMock.create().get('ingest_integration.test');
const callback = getPackagePolicyUpdateCallback(
logger,
Expand All @@ -863,6 +883,7 @@ describe('ingest_integration tests ', () => {
policyConfig.inputs[0]!.config!.policy.value.meta.cluster_uuid = 'original-uuid';
policyConfig.inputs[0]!.config!.policy.value.meta.license_uuid = 'original-uid';
policyConfig.inputs[0]!.config!.policy.value.meta.serverless = true;
policyConfig.inputs[0]!.config!.policy.value.meta.billable = true;
const updatedPolicyConfig = await callback(
policyConfig,
soClient,
Expand All @@ -881,6 +902,7 @@ describe('ingest_integration tests ', () => {
mockPolicy.meta.cluster_uuid = 'updated-uuid';
mockPolicy.meta.license_uuid = 'updated-uid';
mockPolicy.meta.serverless = false;
mockPolicy.meta.billable = false;
const logger = loggingSystemMock.create().get('ingest_integration.test');
const callback = getPackagePolicyUpdateCallback(
logger,
Expand All @@ -899,6 +921,7 @@ describe('ingest_integration tests ', () => {
policyConfig.inputs[0]!.config!.policy.value.meta.cluster_uuid = 'updated-uuid';
policyConfig.inputs[0]!.config!.policy.value.meta.license_uuid = 'updated-uid';
policyConfig.inputs[0]!.config!.policy.value.meta.serverless = false;
policyConfig.inputs[0]!.config!.policy.value.meta.billable = false;
const updatedPolicyConfig = await callback(
policyConfig,
soClient,
Expand Down Expand Up @@ -1015,6 +1038,51 @@ describe('ingest_integration tests ', () => {
expect(antivirusRegistrationIn(updatedPolicyConfig)).toBe(false);
});
});

it('should correctly set meta.billable', async () => {
const isBillablePolicySpy = jest.spyOn(PolicyConfigHelpers, 'isBillablePolicy');

const soClient = savedObjectsClientMock.create();
const esClient = elasticsearchServiceMock.createClusterClient().asInternalUser;
const logger = loggingSystemMock.create().get('ingest_integration.test');
licenseEmitter.next(Enterprise);

const callback = getPackagePolicyUpdateCallback(
logger,
licenseService,
endpointAppContextMock.featureUsageService,
endpointAppContextMock.endpointMetadataService,
cloudService,
esClient,
productFeaturesService
);
const policyConfig = generator.generatePolicyPackagePolicy();

isBillablePolicySpy.mockReturnValue(false);
let updatedPolicyConfig = await callback(
policyConfig,
soClient,
esClient,
requestContextMock.convertContext(ctx),
req
);
expect(isBillablePolicySpy).toHaveBeenCalled();
expect(updatedPolicyConfig.inputs[0]!.config!.policy.value.meta.billable).toEqual(false);

isBillablePolicySpy.mockReset();
isBillablePolicySpy.mockReturnValue(true);
updatedPolicyConfig = await callback(
policyConfig,
soClient,
esClient,
requestContextMock.convertContext(ctx),
req
);
expect(isBillablePolicySpy).toHaveBeenCalled();
expect(updatedPolicyConfig.inputs[0]!.config!.policy.value.meta.billable).toEqual(true);

isBillablePolicySpy.mockRestore();
});
});

describe('package policy delete callback', () => {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,7 @@ import { validateEndpointPackagePolicy } from './handlers/validate_endpoint_pack
import {
isPolicySetToEventCollectionOnly,
ensureOnlyEventCollectionIsAllowed,
isBillablePolicy,
} from '../../common/endpoint/models/policy_config_helpers';
import type { NewPolicyData, PolicyConfig } from '../../common/endpoint/types';
import type { LicenseService } from '../../common/license';
Expand Down Expand Up @@ -272,6 +273,8 @@ export const getPackagePolicyUpdateCallback = (

updateAntivirusRegistrationEnabled(newEndpointPackagePolicy);

newEndpointPackagePolicy.meta.billable = isBillablePolicy(newEndpointPackagePolicy);

return endpointIntegrationData;
};
};
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ import { createDefaultPolicy } from './create_default_policy';
import { ProtectionModes } from '../../../common/endpoint/types';
import type { PolicyConfig } from '../../../common/endpoint/types';
import { policyFactory } from '../../../common/endpoint/models/policy_config';
import * as PolicyConfigHelpers from '../../../common/endpoint/models/policy_config_helpers';
import { elasticsearchServiceMock } from '@kbn/core/server/mocks';
import type {
AnyPolicyCreateConfig,
Expand Down Expand Up @@ -238,6 +239,21 @@ describe('Create Default Policy tests ', () => {
},
});
});

it('should set meta.billable', async () => {
const isBillablePolicySpy = jest.spyOn(PolicyConfigHelpers, 'isBillablePolicy');
const config = createEndpointConfig({ preset: 'DataCollection' });

isBillablePolicySpy.mockReturnValue(false);
let policy = await createDefaultPolicyCallback(config);
expect(policy.meta.billable).toBe(false);

isBillablePolicySpy.mockReturnValue(true);
policy = await createDefaultPolicyCallback(config);
expect(policy.meta.billable).toBe(true);

isBillablePolicySpy.mockRestore();
});
});

describe('When cloud config is set', () => {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@ import {
import {
disableProtections,
ensureOnlyEventCollectionIsAllowed,
isBillablePolicy,
} from '../../../common/endpoint/models/policy_config_helpers';
import type { ProductFeaturesService } from '../../lib/product_features_service/product_features_service';

Expand Down Expand Up @@ -61,6 +62,8 @@ export const createDefaultPolicy = (
defaultPolicyPerType = ensureOnlyEventCollectionIsAllowed(defaultPolicyPerType);
}

defaultPolicyPerType.meta.billable = isBillablePolicy(defaultPolicyPerType);

return defaultPolicyPerType;
};

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,4 +6,4 @@
*/

export { endpointMeteringService } from './metering_service';
export { setEndpointPackagePolicyServerlessFlag } from './set_package_policy_flag';
export { setEndpointPackagePolicyServerlessBillingFlags } from './set_package_policy_flag';
Loading

0 comments on commit 4f799b8

Please sign in to comment.