Skip to content

Commit

Permalink
[Cloud Security] Mute CSP Benchmark Rules
Browse files Browse the repository at this point in the history
  • Loading branch information
CohenIdo authored Dec 18, 2023
1 parent b944280 commit 86ed41e
Show file tree
Hide file tree
Showing 24 changed files with 563 additions and 81 deletions.
3 changes: 3 additions & 0 deletions packages/kbn-check-mappings-update-cli/current_fields.json
Original file line number Diff line number Diff line change
Expand Up @@ -968,5 +968,8 @@
"kuery",
"serviceEnvironmentFilterEnabled",
"serviceNameFilterEnabled"
],
"cloud-security-posture-settings": [
"rules"
]
}
4 changes: 4 additions & 0 deletions packages/kbn-check-mappings-update-cli/current_mappings.json
Original file line number Diff line number Diff line change
Expand Up @@ -2349,6 +2349,10 @@
}
}
},
"cloud-security-posture-settings": {
"dynamic": false,
"properties": {}
},
"slo": {
"dynamic": false,
"properties": {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -76,6 +76,7 @@ describe('checking migration metadata changes on all registered SO types', () =>
"cases-connector-mappings": "f9d1ac57e484e69506c36a8051e4d61f4a8cfd25",
"cases-telemetry": "f219eb7e26772884342487fc9602cfea07b3cedc",
"cases-user-actions": "483f10db9b3bd1617948d7032a98b7791bf87414",
"cloud-security-posture-settings": "675e47dd958fbce6c70a20baac12af3145e7c0ef",
"config": "179b3e2bc672626aafce3cf92093a113f456af38",
"config-global": "8e8a134a2952df700d7d4ec51abb794bbd4cf6da",
"connector_token": "5a9ac29fe9c740eb114e9c40517245c71706b005",
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@ const previouslyRegisteredTypes = [
'canvas-element',
'canvas-workpad',
'canvas-workpad-template',
'cloud-security-posture-settings',
'cases',
'cases-comments',
'cases-configure',
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -197,6 +197,7 @@ describe('split .kibana index into multiple system indices', () => {
"cases-connector-mappings",
"cases-telemetry",
"cases-user-actions",
"cloud-security-posture-settings",
"config",
"config-global",
"connector_token",
Expand Down
9 changes: 7 additions & 2 deletions x-pack/plugins/cloud_security_posture/common/constants.ts
Original file line number Diff line number Diff line change
Expand Up @@ -26,11 +26,14 @@ export const BENCHMARKS_API_CURRENT_VERSION = '1';
export const FIND_CSP_BENCHMARK_RULE_ROUTE_PATH = '/internal/cloud_security_posture/rules/_find';
export const FIND_CSP_BENCHMARK_RULE_API_CURRENT_VERSION = '1';

export const DETECTION_RULE_ALERTS_STATUS_API_CURRENT_VERSION = '1';
export const DETECTION_RULE_RULES_API_CURRENT_VERSION = '2023-10-31';
export const CSP_BENCHMARK_RULES_BULK_ACTION_ROUTE_PATH =
'/internal/cloud_security_posture/rules/_bulk_action';
export const CSP_BENCHMARK_RULES_BULK_ACTION_API_CURRENT_VERSION = '1';

export const GET_DETECTION_RULE_ALERTS_STATUS_PATH =
'/internal/cloud_security_posture/detection_engine_rules/alerts/_status';
export const DETECTION_RULE_ALERTS_STATUS_API_CURRENT_VERSION = '1';
export const DETECTION_RULE_RULES_API_CURRENT_VERSION = '2023-10-31';

export const CLOUD_SECURITY_POSTURE_PACKAGE_NAME = 'cloud_security_posture';
// TODO: REMOVE CSP_LATEST_FINDINGS_DATA_VIEW and replace it with LATEST_FINDINGS_INDEX_PATTERN
Expand Down Expand Up @@ -88,6 +91,8 @@ export const INTERNAL_FEATURE_FLAGS = {
} as const;

export const CSP_BENCHMARK_RULE_SAVED_OBJECT_TYPE = 'csp-rule-template';
export const INTERNAL_CSP_SETTINGS_SAVED_OBJECT_TYPE = 'cloud-security-posture-settings';
export const INTERNAL_CSP_SETTINGS_SAVED_OBJECT_ID = 'csp-internal-settings';

export const CLOUDBEAT_VANILLA = 'cloudbeat/cis_k8s';
export const CLOUDBEAT_EKS = 'cloudbeat/cis_eks';
Expand Down
33 changes: 33 additions & 0 deletions x-pack/plugins/cloud_security_posture/common/types/rules/v3.ts
Original file line number Diff line number Diff line change
Expand Up @@ -129,3 +129,36 @@ export interface FindCspBenchmarkRuleResponse {
page: number;
perPage: number;
}

export const cspBenchmarkRules = schema.arrayOf(
schema.object({
benchmark_id: schema.string(),
benchmark_version: schema.string(),
rule_number: schema.string(),
})
);

export const cspBenchmarkRulesBulkActionRequestSchema = schema.object({
action: schema.oneOf([schema.literal('mute'), schema.literal('unmute')]),
rules: cspBenchmarkRules,
});

export type CspBenchmarkRules = TypeOf<typeof cspBenchmarkRules>;

export type CspBenchmarkRulesBulkActionRequestSchema = TypeOf<
typeof cspBenchmarkRulesBulkActionRequestSchema
>;

const rulesStates = schema.recordOf(
schema.string(),
schema.object({
muted: schema.boolean(),
})
);

export const cspSettingsSchema = schema.object({
rules: rulesStates,
});

export type CspBenchmarkRulesStates = TypeOf<typeof rulesStates>;
export type CspSettings = TypeOf<typeof cspSettingsSchema>;
6 changes: 4 additions & 2 deletions x-pack/plugins/cloud_security_posture/server/plugin.ts
Original file line number Diff line number Diff line change
Expand Up @@ -35,7 +35,7 @@ import type {
CspServerPluginStartServices,
} from './types';
import { setupRoutes } from './routes/setup_routes';
import { setupSavedObjects } from './saved_objects';
import { cspBenchmarkRule, cspSettings } from './saved_objects';
import { initializeCspIndices } from './create_indices/create_indices';
import { initializeCspTransforms } from './create_transforms/create_transforms';
import {
Expand All @@ -50,6 +50,7 @@ import {
} from './tasks/findings_stats_task';
import { registerCspmUsageCollector } from './lib/telemetry/collectors/register';
import { CloudSecurityPostureConfig } from './config';
import { CspBenchmarkRule, CspSettings } from '../common/types/latest';

export class CspPlugin
implements
Expand Down Expand Up @@ -80,7 +81,8 @@ export class CspPlugin
core: CoreSetup<CspServerPluginStartDeps, CspServerPluginStart>,
plugins: CspServerPluginSetupDeps
): CspServerPluginSetup {
setupSavedObjects(core.savedObjects);
core.savedObjects.registerType<CspBenchmarkRule>(cspBenchmarkRule);
core.savedObjects.registerType<CspSettings>(cspSettings);

setupRoutes({
core,
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
/*
* 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 expect from 'expect';
import { setRulesStates, buildRuleKey } from './utils';

describe('CSP Rule State Management', () => {
beforeEach(() => {
jest.clearAllMocks();
});

it('should set rules states correctly', () => {
const ruleIds = ['rule1', 'rule3'];
const newState = true;

const updatedRulesStates = setRulesStates(ruleIds, newState);

expect(updatedRulesStates).toEqual({
rule1: { muted: true },
rule3: { muted: true },
});
});

it('should build a rule key with the provided benchmarkId, benchmarkVersion, and ruleNumber', () => {
const benchmarkId = 'randomId';
const benchmarkVersion = 'v1';
const ruleNumber = '001';

const result = buildRuleKey(benchmarkId, benchmarkVersion, ruleNumber);

expect(result).toBe(`${benchmarkId};${benchmarkVersion};${ruleNumber}`);
});
});
Original file line number Diff line number Diff line change
@@ -0,0 +1,68 @@
/*
* 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 { transformError } from '@kbn/securitysolution-es-utils';
import {
CspBenchmarkRulesBulkActionRequestSchema,
CspBenchmarkRulesStates,
cspBenchmarkRulesBulkActionRequestSchema,
} from '../../../../common/types/rules/v3';
import { CspRouter } from '../../../types';

import { CSP_BENCHMARK_RULES_BULK_ACTION_ROUTE_PATH } from '../../../../common/constants';
import { bulkActionBenchmarkRulesHandler } from './v1';

export const defineBulkActionCspBenchmarkRulesRoute = (router: CspRouter) =>
router.versioned
.post({
access: 'internal',
path: CSP_BENCHMARK_RULES_BULK_ACTION_ROUTE_PATH,
})
.addVersion(
{
version: '1',
validate: {
request: {
body: cspBenchmarkRulesBulkActionRequestSchema,
},
},
},
async (context, request, response) => {
if (!(await context.fleet).authz.fleet.all) {
return response.forbidden();
}
const cspContext = await context.csp;

try {
const requestBody: CspBenchmarkRulesBulkActionRequestSchema = request.body;

const benchmarkRulesToUpdate = requestBody.rules;

const handlerResponse = await bulkActionBenchmarkRulesHandler(
cspContext.encryptedSavedObjects,
benchmarkRulesToUpdate,
requestBody.action
);

const updatedBenchmarkRules: CspBenchmarkRulesStates = handlerResponse;
return response.ok({
body: {
updated_benchmark_rules: updatedBenchmarkRules,
message: 'The bulk operation has been executed successfully.',
},
});
} catch (err) {
const error = transformError(err);

cspContext.logger.error(`Bulk action failed: ${error.message}`);
return response.customError({
body: { message: error.message },
statusCode: error.statusCode || 500, // Default to 500 if no specific status code is provided
});
}
}
);
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
/*
* 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 {
SavedObjectsClientContract,
SavedObjectsUpdateResponse,
} from '@kbn/core-saved-objects-api-server';
import { CspBenchmarkRulesStates, CspSettings } from '../../../../common/types/rules/v3';

import {
INTERNAL_CSP_SETTINGS_SAVED_OBJECT_ID,
INTERNAL_CSP_SETTINGS_SAVED_OBJECT_TYPE,
} from '../../../../common/constants';

export const updateRulesStates = async (
encryptedSoClient: SavedObjectsClientContract,
newRulesStates: CspBenchmarkRulesStates
): Promise<SavedObjectsUpdateResponse<CspSettings>> => {
return await encryptedSoClient.update<CspSettings>(
INTERNAL_CSP_SETTINGS_SAVED_OBJECT_TYPE,
INTERNAL_CSP_SETTINGS_SAVED_OBJECT_ID,
{ rules: newRulesStates },
// if there is no saved object yet, insert a new SO
{ upsert: { rules: newRulesStates } }
);
};

export const setRulesStates = (ruleIds: string[], state: boolean): CspBenchmarkRulesStates => {
const rulesStates: CspBenchmarkRulesStates = {};
ruleIds.forEach((ruleId) => {
rulesStates[ruleId] = { muted: state };
});
return rulesStates;
};

export const buildRuleKey = (benchmarkId: string, benchmarkVersion: string, ruleNumber: string) => {
return `${benchmarkId};${benchmarkVersion};${ruleNumber}`;
};
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
/*
* 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 { SavedObjectsClientContract } from '@kbn/core-saved-objects-api-server';
import { CspBenchmarkRules, CspBenchmarkRulesStates } from '../../../../common/types/rules/v3';
import { buildRuleKey, setRulesStates, updateRulesStates } from './utils';

const muteStatesMap = {
mute: true,
unmute: false,
};

export const bulkActionBenchmarkRulesHandler = async (
encryptedSoClient: SavedObjectsClientContract,
rulesToUpdate: CspBenchmarkRules,
action: 'mute' | 'unmute'
): Promise<CspBenchmarkRulesStates> => {
const ruleKeys = rulesToUpdate.map((rule) =>
buildRuleKey(rule.benchmark_id, rule.benchmark_version, rule.rule_number)
);

const newRulesStates = setRulesStates(ruleKeys, muteStatesMap[action]);

const newCspSettings = await updateRulesStates(encryptedSoClient, newRulesStates);

return newCspSettings.attributes.rules!;
};
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@
* 2.0.
*/

import { getSortedCspBenchmarkRulesTemplates } from './v1';
import { getSortedCspBenchmarkRulesTemplates } from './utils';
import { CspBenchmarkRule } from '../../../../common/types/latest';

describe('getSortedCspBenchmarkRules', () => {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -9,11 +9,10 @@ import {
FindCspBenchmarkRuleRequest,
FindCspBenchmarkRuleResponse,
findCspBenchmarkRuleRequestSchema,
} from '../../../../common/types/latest';

} from '../../../../common/types/rules/v3';
import { FIND_CSP_BENCHMARK_RULE_ROUTE_PATH } from '../../../../common/constants';
import { CspRouter } from '../../../types';
import { findRuleHandler as findRuleHandlerV1 } from './v1';
import { findBenchmarkRuleHandler as findRuleHandlerV1 } from './v1';

export const defineFindCspBenchmarkRuleRoute = (router: CspRouter) =>
router.versioned
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
/*
* 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 semverValid from 'semver/functions/valid';
import semverCompare from 'semver/functions/compare';
import { NewPackagePolicy } from '@kbn/fleet-plugin/common';
import { SavedObjectsClientContract } from '@kbn/core-saved-objects-api-server';
import { PACKAGE_POLICY_SAVED_OBJECT_TYPE } from '../../benchmarks/benchmarks';
import { getBenchmarkFromPackagePolicy } from '../../../../common/utils/helpers';

import type { CspBenchmarkRule } from '../../../../common/types/latest';

export const getSortedCspBenchmarkRulesTemplates = (cspBenchmarkRules: CspBenchmarkRule[]) => {
return cspBenchmarkRules.slice().sort((a, b) => {
const ruleNumberA = a?.metadata?.benchmark?.rule_number;
const ruleNumberB = b?.metadata?.benchmark?.rule_number;

const versionA = semverValid(ruleNumberA);
const versionB = semverValid(ruleNumberB);

if (versionA !== null && versionB !== null) {
return semverCompare(versionA, versionB);
} else {
return String(ruleNumberA).localeCompare(String(ruleNumberB));
}
});
};

export const getBenchmarkIdFromPackagePolicyId = async (
soClient: SavedObjectsClientContract,
packagePolicyId: string
): Promise<string> => {
const res = await soClient.get<NewPackagePolicy>(
PACKAGE_POLICY_SAVED_OBJECT_TYPE,
packagePolicyId
);
return getBenchmarkFromPackagePolicy(res.attributes.inputs);
};
Loading

0 comments on commit 86ed41e

Please sign in to comment.