Skip to content

Commit

Permalink
[Cloud Security][Metering] Report all assets details
Browse files Browse the repository at this point in the history
  • Loading branch information
CohenIdo authored Jul 4, 2024
1 parent 0c71de8 commit 3e9ad41
Show file tree
Hide file tree
Showing 5 changed files with 224 additions and 145 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,15 @@ export const cloudSecurityMetringCallback = async ({
lastSuccessfulReport,
config,
}: MeteringCallbackInput): Promise<MeteringCallBackResponse> => {
const projectHasCloudProductLine = config.productTypes.some(
(product) => product.product_line === ProductLine.cloud
);

if (!projectHasCloudProductLine) {
logger.info('No cloud product line found in the project');
return { records: [] };
}

const projectId = cloudSetup?.serverless?.projectId || 'missing_project_id';

const tier: Tier = getCloudProductTier(config, logger);
Expand All @@ -30,8 +39,8 @@ export const cloudSecurityMetringCallback = async ({

const promiseResults = await Promise.allSettled(
cloudSecuritySolutions.map((cloudSecuritySolution) => {
if (cloudSecuritySolution === CLOUD_DEFEND) {
return getCloudDefendUsageRecords({
if (cloudSecuritySolution !== CLOUD_DEFEND) {
return getCloudSecurityUsageRecord({
esClient,
projectId,
logger,
Expand All @@ -41,7 +50,9 @@ export const cloudSecurityMetringCallback = async ({
tier,
});
}
return getCloudSecurityUsageRecord({

// since lastSuccessfulReport is not used by getCloudSecurityUsageRecord, we want to verify if it is used by getCloudDefendUsageRecords before getCloudSecurityUsageRecord.
return getCloudDefendUsageRecords({
esClient,
projectId,
logger,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -14,16 +14,23 @@ import {
} from './cloud_security_metering_task';

import type { ServerlessSecurityConfig } from '../config';
import type { CloudSecuritySolutions } from './types';

import type { ProductTier } from '../../common/product';
import { CLOUD_SECURITY_TASK_TYPE, CSPM, KSPM, CNVM, CLOUD_DEFEND } from './constants';
import {
CLOUD_SECURITY_TASK_TYPE,
CSPM,
KSPM,
CNVM,
CLOUD_DEFEND,
BILLABLE_ASSETS_CONFIG,
} from './constants';
import { getCloudDefendUsageRecords } from './defend_for_containers_metering';

const mockEsClient = elasticsearchServiceMock.createStart().client.asInternalUser;
const logger: ReturnType<typeof loggingSystemMock.createLogger> = loggingSystemMock.createLogger();
const chance = new Chance();

const cloudSecuritySolutions: CloudSecuritySolutions[] = [CSPM, KSPM, CNVM];
const cloudSecuritySolutions: Array<typeof CSPM | typeof KSPM> = [CSPM, KSPM];

describe('getCloudSecurityUsageRecord', () => {
beforeEach(() => {
Expand Down Expand Up @@ -54,18 +61,33 @@ describe('getCloudSecurityUsageRecord', () => {
});

test.each(cloudSecuritySolutions)(
'should return usageRecords with correct values for cspm, kspm, and cnvm when Elasticsearch response has aggregations',
'should return usageRecords with correct values for cspm and kspm when Elasticsearch response has aggregations',
async (cloudSecuritySolution) => {
// @ts-ignore
mockEsClient.search.mockResolvedValueOnce({
hits: { hits: [{ _id: 'someRecord', _index: 'mockIndex' }] }, // mocking for indexHasDataInDateRange
});
const randomIndex = Math.floor(
Math.random() * BILLABLE_ASSETS_CONFIG[cloudSecuritySolution].values.length
);
const randomBillableAsset = BILLABLE_ASSETS_CONFIG[cloudSecuritySolution].values[randomIndex];

// @ts-ignore
mockEsClient.search.mockResolvedValueOnce({
aggregations: {
unique_assets: {
value: 10,
resource_sub_type: {
buckets: [
{
key: randomBillableAsset,
doc_count: 100,
unique_assets: { value: 10 },
},
{
key: 'not_billable_asset',
doc_count: 50,
unique_assets: { value: 11 },
},
],
},
min_timestamp: {
value_as_string: '2023-07-30T15:11:41.738Z',
Expand Down Expand Up @@ -100,6 +122,10 @@ describe('getCloudSecurityUsageRecord', () => {
sub_type: cloudSecuritySolution,
quantity: 10,
period_seconds: expect.any(Number),
metadata: {
[randomBillableAsset]: '10',
not_billable_asset: '11',
},
},
source: {
id: taskId,
Expand All @@ -113,6 +139,62 @@ describe('getCloudSecurityUsageRecord', () => {
}
);

it('should return usageRecords with correct values for cnvm when Elasticsearch response has aggregations', async () => {
const cloudSecuritySolution = CNVM;

// @ts-ignore
mockEsClient.search.mockResolvedValueOnce({
hits: { hits: [{ _id: 'someRecord', _index: 'mockIndex' }] }, // mocking for indexHasDataInDateRange
});

// @ts-ignore
mockEsClient.search.mockResolvedValueOnce({
aggregations: {
unique_assets: {
value: 10,
},
min_timestamp: {
value_as_string: '2023-07-30T15:11:41.738Z',
},
},
});

const projectId = chance.guid();
const taskId = chance.guid();

const tier = 'essentials' as ProductTier;
const result = await getCloudSecurityUsageRecord({
esClient: mockEsClient,
projectId,
logger,
taskId,
lastSuccessfulReport: new Date(),
cloudSecuritySolution,
tier,
});

expect(result).toEqual([
{
id: expect.stringContaining(`${CLOUD_SECURITY_TASK_TYPE}_cnvm_${projectId}`),
usage_timestamp: '2023-07-30T15:11:41.738Z',
creation_timestamp: expect.any(String), // Expect a valid ISO string
usage: {
type: CLOUD_SECURITY_TASK_TYPE,
sub_type: CNVM,
quantity: 10,
period_seconds: expect.any(Number),
},
source: {
id: taskId,
instance_group_id: projectId,
metadata: {
tier: 'essentials',
},
},
},
]);
});

it('should return undefined when Elasticsearch response does not have aggregations', async () => {
// @ts-ignore
mockEsClient.search.mockResolvedValue({});
Expand Down Expand Up @@ -162,9 +244,7 @@ describe('getCloudSecurityUsageRecord', () => {

describe('getSearchQueryByCloudSecuritySolution', () => {
it('should return the correct search query for CSPM', () => {
const searchFrom = new Date('2023-07-30T15:11:41.738Z');

const result = getSearchQueryByCloudSecuritySolution('cspm', searchFrom);
const result = getSearchQueryByCloudSecuritySolution('cspm');

expect(result).toEqual({
bool: {
Expand All @@ -181,39 +261,13 @@ describe('getSearchQueryByCloudSecuritySolution', () => {
'rule.benchmark.posture_type': 'cspm',
},
},
{
terms: {
'resource.sub_type': [
// 'aws-ebs', we can't include EBS volumes until https://github.com/elastic/security-team/issues/9283 is resolved
// 'aws-ec2', we can't include EC2 instances until https://github.com/elastic/security-team/issues/9254 is resolved
'aws-s3',
'aws-rds',
'azure-disk',
'azure-document-db-database-account',
'azure-flexible-mysql-server-db',
'azure-flexible-postgresql-server-db',
'azure-mysql-server-db',
'azure-postgresql-server-db',
'azure-sql-server',
'azure-storage-account',
'azure-vm',
'gcp-bigquery-dataset',
'gcp-compute-disk',
'gcp-compute-instance',
'gcp-sqladmin-instance',
'gcp-storage-bucket',
],
},
},
],
},
});
});

it('should return the correct search query for KSPM', () => {
const searchFrom = new Date('2023-07-30T15:11:41.738Z');

const result = getSearchQueryByCloudSecuritySolution('kspm', searchFrom);
const result = getSearchQueryByCloudSecuritySolution('kspm');

expect(result).toEqual({
bool: {
Expand All @@ -230,20 +284,13 @@ describe('getSearchQueryByCloudSecuritySolution', () => {
'rule.benchmark.posture_type': 'kspm',
},
},
{
terms: {
'resource.sub_type': ['Node', 'node'],
},
},
],
},
});
});

it('should return the correct search query for CNVM', () => {
const searchFrom = new Date('2023-07-30T15:11:41.738Z');

const result = getSearchQueryByCloudSecuritySolution(CNVM, searchFrom);
const result = getSearchQueryByCloudSecuritySolution(CNVM);

expect(result).toEqual({
bool: {
Expand Down
Loading

0 comments on commit 3e9ad41

Please sign in to comment.