[Cloud Security] Get rules state API

This commit is contained in:
Ido Cohen 2023-12-28 14:35:42 +02:00 committed by GitHub
parent 445b757a65
commit 03726cf2f1
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
11 changed files with 327 additions and 20 deletions

View file

@ -30,6 +30,10 @@ 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 CSP_GET_BENCHMARK_RULES_STATE_ROUTE_PATH =
'/internal/cloud_security_posture/rules/_get_states';
export const CSP_GET_BENCHMARK_RULES_STATE_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';

View file

@ -137,18 +137,21 @@ export interface FindCspBenchmarkRuleResponse {
export type PageUrlParams = Record<'policyId' | 'packagePolicyId', string>;
export const cspBenchmarkRules = schema.arrayOf(
export const rulesToUpdate = schema.arrayOf(
schema.object({
rule_id: schema.string(),
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,
rules: rulesToUpdate,
});
export type CspBenchmarkRules = TypeOf<typeof cspBenchmarkRules>;
export type RulesToUpdate = TypeOf<typeof rulesToUpdate>;
export type CspBenchmarkRulesBulkActionRequestSchema = TypeOf<
typeof cspBenchmarkRulesBulkActionRequestSchema

View file

@ -23,6 +23,9 @@ import { bulkActionBenchmarkRulesHandler } from './v1';
action: 'mute' | 'unmute'; // Specify the bulk action type (mute or unmute)
rules: [
{
benchmark_id: string; // Identifier for the CSP benchmark
benchmark_version: string; // Version of the CSP benchmark
rule_number: string; // Rule number within the benchmark
rule_id: string; // Unique identifier for the rule
},
// ... (additional benchmark rules)

View file

@ -13,6 +13,7 @@ import type { FindResult, RulesClient } from '@kbn/alerting-plugin/server';
import type { RuleParams } from '@kbn/alerting-plugin/server/application/rule/types';
import type {
CspBenchmarkRule,
RulesToUpdate,
CspBenchmarkRulesStates,
CspSettings,
} from '../../../../common/types/rules/v3';
@ -118,23 +119,22 @@ export const updateRulesStates = async (
export const setRulesStates = (
ruleIds: string[],
state: boolean,
benchmarkRules: CspBenchmarkRule[]
rulesToUpdate: RulesToUpdate
): CspBenchmarkRulesStates => {
const rulesStates: CspBenchmarkRulesStates = {};
ruleIds.forEach((ruleId, index) => {
const benchmarkRule = benchmarkRules[index];
const benchmarkRule = rulesToUpdate[index];
rulesStates[ruleId] = {
muted: state,
benchmark_id: benchmarkRule.metadata.benchmark.id,
benchmark_version: benchmarkRule.metadata.benchmark.version,
rule_number: benchmarkRule.metadata.benchmark.rule_number || '',
rule_id: benchmarkRule.metadata.id,
benchmark_id: benchmarkRule.benchmark_id,
benchmark_version: benchmarkRule.benchmark_version,
rule_number: benchmarkRule.rule_number,
rule_id: benchmarkRule.rule_id,
};
});
return rulesStates;
};
export const buildRuleKey = (benchmarkRule: CspBenchmarkRule) => {
const ruleNumber = benchmarkRule.metadata.benchmark.rule_number;
return `${benchmarkRule.metadata.benchmark.id};${benchmarkRule.metadata.benchmark.version};${ruleNumber}`;
export const buildRuleKey = (benchmarkId: string, benchmarkVersion: string, ruleNumber: string) => {
return `${benchmarkId};${benchmarkVersion};${ruleNumber}`;
};

View file

@ -16,8 +16,7 @@ import {
} from './utils';
import type {
BulkActionBenchmarkRulesResponse,
CspBenchmarkRule,
CspBenchmarkRules,
RulesToUpdate,
} from '../../../../common/types/rules/v3';
const muteStatesMap = {
@ -29,7 +28,7 @@ export const bulkActionBenchmarkRulesHandler = async (
soClient: SavedObjectsClientContract,
encryptedSoClient: SavedObjectsClientContract,
detectionRulesClient: RulesClient,
rulesToUpdate: CspBenchmarkRules,
rulesToUpdate: RulesToUpdate,
action: 'mute' | 'unmute',
logger: Logger
): Promise<BulkActionBenchmarkRulesResponse> => {
@ -39,13 +38,12 @@ export const bulkActionBenchmarkRulesHandler = async (
if (benchmarkRules.includes(undefined))
throw new Error('At least one of the provided benchmark rule IDs does not exist');
const rulesKeys = benchmarkRules.map((benchmarkRule) => buildRuleKey(benchmarkRule!));
const newRulesStates = setRulesStates(
rulesKeys,
muteStatesMap[action],
benchmarkRules as CspBenchmarkRule[]
const rulesKeys = rulesToUpdate.map((rule) =>
buildRuleKey(rule.benchmark_id, rule.benchmark_version, rule.rule_number)
);
const newRulesStates = setRulesStates(rulesKeys, muteStatesMap[action], rulesToUpdate);
const newCspSettings = await updateRulesStates(encryptedSoClient, newRulesStates);
const disabledRulesCounter =
action === 'mute' ? await muteDetectionRules(soClient, detectionRulesClient, rulesIds) : 0;

View file

@ -0,0 +1,51 @@
/*
* 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 { CspRouter } from '../../../types';
import { CSP_GET_BENCHMARK_RULES_STATE_ROUTE_PATH } from '../../../../common/constants';
import { CspBenchmarkRulesStates } from '../../../../common/types/rules/v3';
import { getCspBenchmarkRulesStatesHandler } from './v1';
export const defineGetCspBenchmarkRulesStatesRoute = (router: CspRouter) =>
router.versioned
.get({
access: 'internal',
path: CSP_GET_BENCHMARK_RULES_STATE_ROUTE_PATH,
})
.addVersion(
{
version: '1',
validate: {},
},
async (context, request, response) => {
if (!(await context.fleet).authz.fleet.all) {
return response.forbidden();
}
const cspContext = await context.csp;
try {
const encryptedSoClient = cspContext.encryptedSavedObjects;
const rulesStates: CspBenchmarkRulesStates = await getCspBenchmarkRulesStatesHandler(
encryptedSoClient
);
return response.ok({
body: rulesStates,
});
} catch (err) {
const error = transformError(err);
cspContext.logger.error(`Failed to fetch CSP benchmark rules state: ${error.message}`);
return response.customError({
body: { message: error.message },
statusCode: error.statusCode || 500, // Default to 500 if no specific status code is provided
});
}
}
);

View file

@ -0,0 +1,45 @@
/*
* 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 { transformError } from '@kbn/securitysolution-es-utils';
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 createCspSettingObject = async (soClient: SavedObjectsClientContract) => {
return soClient.create<CspSettings>(
INTERNAL_CSP_SETTINGS_SAVED_OBJECT_TYPE,
{
rules: {},
},
{ id: INTERNAL_CSP_SETTINGS_SAVED_OBJECT_ID }
);
};
export const getCspBenchmarkRulesStatesHandler = async (
encryptedSoClient: SavedObjectsClientContract
): Promise<CspBenchmarkRulesStates> => {
try {
const getSoResponse = await encryptedSoClient.get<CspSettings>(
INTERNAL_CSP_SETTINGS_SAVED_OBJECT_TYPE,
INTERNAL_CSP_SETTINGS_SAVED_OBJECT_ID
);
return getSoResponse.attributes.rules;
} catch (err) {
const error = transformError(err);
if (error.statusCode === 404) {
const newCspSettings = await createCspSettingObject(encryptedSoClient);
return newCspSettings.attributes.rules;
}
throw new Error(
`An error occurred while trying to fetch csp settings: ${error.message}, ${error.statusCode}`
);
}
};

View file

@ -21,6 +21,7 @@ import { defineGetCspStatusRoute } from './status/status';
import { defineFindCspBenchmarkRuleRoute } from './benchmark_rules/find/find';
import { defineGetDetectionEngineAlertsStatus } from './detection_engine/get_detection_engine_alerts_count_by_rule_tags';
import { defineBulkActionCspBenchmarkRulesRoute } from './benchmark_rules/bulk_action/bulk_action';
import { defineGetCspBenchmarkRulesStatesRoute } from './benchmark_rules/get_states/get_states';
/**
* 1. Registers routes
@ -43,6 +44,7 @@ export async function setupRoutes({
defineFindCspBenchmarkRuleRoute(router);
defineGetDetectionEngineAlertsStatus(router);
defineBulkActionCspBenchmarkRulesRoute(router);
defineGetCspBenchmarkRulesStatesRoute(router);
core.http.registerRouteHandlerContext<CspRequestHandlerContext, typeof PLUGIN_ID>(
PLUGIN_ID,

View file

@ -19,6 +19,7 @@ export default async function ({ readConfigFile }: FtrConfigProviderContext) {
require.resolve('./routes/vulnerabilities_dashboard.ts'),
require.resolve('./routes/stats.ts'),
require.resolve('./routes/csp_benchmark_rules_bulk_update.ts'),
require.resolve('./routes/csp_benchmark_rules_get_states.ts'),
],
junit: {
reportName: 'X-Pack Cloud Security Posture API Tests',

View file

@ -115,9 +115,15 @@ export default function ({ getService }: FtrProviderContext) {
action: 'mute',
rules: [
{
benchmark_id: rule1.metadata.benchmark.id,
benchmark_version: rule1.metadata.benchmark.version,
rule_number: rule1.metadata.benchmark.rule_number || '',
rule_id: rule1.metadata.id,
},
{
benchmark_id: rule2.metadata.benchmark.id,
benchmark_version: rule2.metadata.benchmark.version,
rule_number: rule2.metadata.benchmark.rule_number || '',
rule_id: rule2.metadata.id,
},
],
@ -162,9 +168,15 @@ export default function ({ getService }: FtrProviderContext) {
action: 'unmute',
rules: [
{
benchmark_id: rule1.metadata.benchmark.id,
benchmark_version: rule1.metadata.benchmark.version,
rule_number: rule1.metadata.benchmark.rule_number || '',
rule_id: rule1.metadata.id,
},
{
benchmark_id: rule2.metadata.benchmark.id,
benchmark_version: rule2.metadata.benchmark.version,
rule_number: rule2.metadata.benchmark.rule_number || '',
rule_id: rule2.metadata.id,
},
],
@ -210,9 +222,15 @@ export default function ({ getService }: FtrProviderContext) {
action: 'unmute',
rules: [
{
benchmark_id: rule1.metadata.benchmark.id,
benchmark_version: rule1.metadata.benchmark.version,
rule_number: rule1.metadata.benchmark.rule_number || '',
rule_id: rule1.metadata.id,
},
{
benchmark_id: rule2.metadata.benchmark.id,
benchmark_version: rule2.metadata.benchmark.version,
rule_number: rule2.metadata.benchmark.rule_number || '',
rule_id: rule2.metadata.id,
},
],
@ -252,9 +270,15 @@ export default function ({ getService }: FtrProviderContext) {
action: 'mute',
rules: [
{
benchmark_id: rule1.metadata.benchmark.id,
benchmark_version: rule1.metadata.benchmark.version,
rule_number: rule1.metadata.benchmark.rule_number || '',
rule_id: rule1.metadata.id,
},
{
benchmark_id: rule3.metadata.benchmark.id,
benchmark_version: rule3.metadata.benchmark.version,
rule_number: rule3.metadata.benchmark.rule_number || '',
rule_id: rule3.metadata.id,
},
],
@ -299,6 +323,9 @@ export default function ({ getService }: FtrProviderContext) {
action: 'mute',
rules: [
{
benchmark_id: rule1.metadata.benchmark.id,
benchmark_version: rule1.metadata.benchmark.version,
rule_number: rule1.metadata.benchmark.rule_number || '',
rule_id: rule1.metadata.id,
},
],
@ -323,9 +350,15 @@ export default function ({ getService }: FtrProviderContext) {
action: 'mute',
rules: [
{
benchmark_id: rule1.metadata.benchmark.id,
benchmark_version: rule1.metadata.benchmark.version,
rule_number: rule1.metadata.benchmark.rule_number || '',
rule_id: rule1.metadata.id,
},
{
benchmark_id: rule2.metadata.benchmark.id,
benchmark_version: rule2.metadata.benchmark.version,
rule_number: rule2.metadata.benchmark.rule_number || '',
rule_id: rule2.metadata.id,
},
],
@ -347,6 +380,9 @@ export default function ({ getService }: FtrProviderContext) {
action: 'foo',
rules: [
{
benchmark_id: rule1.metadata.benchmark.id,
benchmark_version: rule1.metadata.benchmark.version,
rule_number: rule1.metadata.benchmark.rule_number || '',
rule_id: rule1.metadata.id,
},
],

View file

@ -0,0 +1,164 @@
/*
* 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 '@kbn/expect';
import { expect as expectExpect } from 'expect';
import {
ELASTIC_HTTP_VERSION_HEADER,
X_ELASTIC_INTERNAL_ORIGIN_REQUEST,
} from '@kbn/core-http-common';
import { CSP_BENCHMARK_RULE_SAVED_OBJECT_TYPE } from '@kbn/cloud-security-posture-plugin/common/constants';
import type { CspBenchmarkRule } from '@kbn/cloud-security-posture-plugin/common/types/latest';
import type { FtrProviderContext } from '../ftr_provider_context';
// eslint-disable-next-line import/no-default-export
export default function ({ getService }: FtrProviderContext) {
const retry = getService('retry');
const supertest = getService('supertest');
const log = getService('log');
const kibanaServer = getService('kibanaServer');
const generateRuleKey = (rule: CspBenchmarkRule): string => {
return `${rule.metadata.benchmark.id};${rule.metadata.benchmark.version};${rule.metadata.benchmark.rule_number}`;
};
const getRandomCspBenchmarkRule = async () => {
const cspBenchmarkRules = await kibanaServer.savedObjects.find<CspBenchmarkRule>({
type: CSP_BENCHMARK_RULE_SAVED_OBJECT_TYPE,
});
expect(cspBenchmarkRules.saved_objects.length).greaterThan(0);
const randomIndex = Math.floor(Math.random() * cspBenchmarkRules.saved_objects.length);
return cspBenchmarkRules.saved_objects[randomIndex].attributes;
};
/**
* required before indexing findings
*/
const waitForPluginInitialized = (): Promise<void> =>
retry.try(async () => {
log.debug('Check CSP plugin is initialized');
const response = await supertest
.get('/internal/cloud_security_posture/status?check=init')
.set(ELASTIC_HTTP_VERSION_HEADER, '1')
.expect(200);
expect(response.body).to.eql({ isPluginInitialized: true });
log.debug('CSP plugin is initialized');
});
describe('Tests get rules states API', async () => {
before(async () => {
await waitForPluginInitialized();
});
beforeEach(async () => {
await kibanaServer.savedObjects.clean({
types: ['cloud-security-posture-settings'],
});
});
it('get rules states successfully', async () => {
const rule1 = await getRandomCspBenchmarkRule();
const rule2 = await getRandomCspBenchmarkRule();
const rule3 = await getRandomCspBenchmarkRule();
await supertest
.post(`/internal/cloud_security_posture/rules/_bulk_action`)
.set(ELASTIC_HTTP_VERSION_HEADER, '1')
.set(X_ELASTIC_INTERNAL_ORIGIN_REQUEST, 'kibana')
.set('kbn-xsrf', 'xxxx')
.send({
action: 'mute',
rules: [
{
benchmark_id: rule1.metadata.benchmark.id,
benchmark_version: rule1.metadata.benchmark.version,
rule_number: rule1.metadata.benchmark.rule_number || '',
rule_id: rule1.metadata.id,
},
{
benchmark_id: rule2.metadata.benchmark.id,
benchmark_version: rule2.metadata.benchmark.version,
rule_number: rule2.metadata.benchmark.rule_number || '',
rule_id: rule2.metadata.id,
},
],
})
.expect(200);
await supertest
.post(`/internal/cloud_security_posture/rules/_bulk_action`)
.set(ELASTIC_HTTP_VERSION_HEADER, '1')
.set(X_ELASTIC_INTERNAL_ORIGIN_REQUEST, 'kibana')
.set('kbn-xsrf', 'xxxx')
.send({
action: 'unmute',
rules: [
{
benchmark_id: rule3.metadata.benchmark.id,
benchmark_version: rule3.metadata.benchmark.version,
rule_number: rule3.metadata.benchmark.rule_number || '',
rule_id: rule3.metadata.id,
},
],
})
.expect(200);
const { body } = await supertest
.get(`/internal/cloud_security_posture/rules/_get_states`)
.set(ELASTIC_HTTP_VERSION_HEADER, '1')
.set(X_ELASTIC_INTERNAL_ORIGIN_REQUEST, 'kibana')
.set('kbn-xsrf', 'xxxx')
.expect(200);
expectExpect(body).toEqual(
expectExpect.objectContaining({
[generateRuleKey(rule1)]: {
muted: true,
benchmark_id: rule1.metadata.benchmark.id,
benchmark_version: rule1.metadata.benchmark.version,
rule_number: rule1.metadata.benchmark.rule_number
? rule1.metadata.benchmark.rule_number
: '',
rule_id: rule1.metadata.id,
},
[generateRuleKey(rule2)]: {
muted: true,
benchmark_id: rule2.metadata.benchmark.id,
benchmark_version: rule2.metadata.benchmark.version,
rule_number: rule2.metadata.benchmark.rule_number
? rule2.metadata.benchmark.rule_number
: '',
rule_id: rule2.metadata.id,
},
[generateRuleKey(rule3)]: {
muted: false,
benchmark_id: rule3.metadata.benchmark.id,
benchmark_version: rule3.metadata.benchmark.version,
rule_number: rule3.metadata.benchmark.rule_number
? rule3.metadata.benchmark.rule_number
: '',
rule_id: rule3.metadata.id,
},
})
);
});
it('get empty object when rules states not exists', async () => {
const { body } = await supertest
.get(`/internal/cloud_security_posture/rules/_get_states`)
.set(ELASTIC_HTTP_VERSION_HEADER, '1')
.set(X_ELASTIC_INTERNAL_ORIGIN_REQUEST, 'kibana')
.set('kbn-xsrf', 'xxxx')
.expect(200);
expectExpect(body).toEqual({});
});
});
}