From 03fdd917d6f1e784fcd0c70b555fe27758422ed3 Mon Sep 17 00:00:00 2001 From: animehart Date: Thu, 18 Jan 2024 17:02:03 -0800 Subject: [PATCH 01/13] Rules API update -still dirty --- .../common/types/index.ts | 1 + .../common/types/latest.ts | 2 +- .../common/types/rules/v4.ts | 4 +- .../common/types/rules/v5.ts | 167 ++++++++++++++++++ .../common/utils/helpers.ts | 26 +++ .../public/pages/rules/rules_table_header.tsx | 14 +- .../server/routes/benchmark_rules/find/v2.ts | 14 +- .../server/routes/benchmark_rules/find/v3.ts | 56 ++++++ 8 files changed, 269 insertions(+), 15 deletions(-) create mode 100644 x-pack/plugins/cloud_security_posture/common/types/rules/v5.ts create mode 100644 x-pack/plugins/cloud_security_posture/server/routes/benchmark_rules/find/v3.ts diff --git a/x-pack/plugins/cloud_security_posture/common/types/index.ts b/x-pack/plugins/cloud_security_posture/common/types/index.ts index e53f34d5cf919..04fa2a95a8d5e 100644 --- a/x-pack/plugins/cloud_security_posture/common/types/index.ts +++ b/x-pack/plugins/cloud_security_posture/common/types/index.ts @@ -9,6 +9,7 @@ export * as rulesV1 from './rules/v1'; export * as rulesV2 from './rules/v2'; export * as rulesV3 from './rules/v3'; export * as rulesV4 from './rules/v4'; +export * as rulesV5 from './rules/v5'; export * as benchmarkV1 from './benchmarks/v1'; export * as benchmarkV2 from './benchmarks/v2'; diff --git a/x-pack/plugins/cloud_security_posture/common/types/latest.ts b/x-pack/plugins/cloud_security_posture/common/types/latest.ts index 73d86e76db250..32006fe5b5aef 100644 --- a/x-pack/plugins/cloud_security_posture/common/types/latest.ts +++ b/x-pack/plugins/cloud_security_posture/common/types/latest.ts @@ -5,5 +5,5 @@ * 2.0. */ -export * from './rules/v4'; +export * from './rules/v5'; export * from './benchmarks/v2'; diff --git a/x-pack/plugins/cloud_security_posture/common/types/rules/v4.ts b/x-pack/plugins/cloud_security_posture/common/types/rules/v4.ts index 19711c7e7eb13..d15827c7bef0a 100644 --- a/x-pack/plugins/cloud_security_posture/common/types/rules/v4.ts +++ b/x-pack/plugins/cloud_security_posture/common/types/rules/v4.ts @@ -95,8 +95,8 @@ export const findCspBenchmarkRuleRequestSchema = schema.object({ /** * rule section */ - section: schema.maybe(schema.string()), - ruleNumber: schema.maybe(schema.string()), + section: schema.maybe(schema.arrayOf(schema.string())), + ruleNumber: schema.maybe(schema.arrayOf(schema.string())), }); export type FindCspBenchmarkRuleRequest = TypeOf; diff --git a/x-pack/plugins/cloud_security_posture/common/types/rules/v5.ts b/x-pack/plugins/cloud_security_posture/common/types/rules/v5.ts new file mode 100644 index 0000000000000..be62d92219510 --- /dev/null +++ b/x-pack/plugins/cloud_security_posture/common/types/rules/v5.ts @@ -0,0 +1,167 @@ +/* + * 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 { schema, TypeOf } from '@kbn/config-schema'; +import { BenchmarksCisId } from '../latest'; +export type { + cspBenchmarkRuleMetadataSchema, + CspBenchmarkRuleMetadata, + cspBenchmarkRuleSchema, + CspBenchmarkRule, + FindCspBenchmarkRuleResponse, +} from './v3'; + +const DEFAULT_BENCHMARK_RULES_PER_PAGE = 25; + +export const findCspBenchmarkRuleRequestSchema = schema.object({ + /** + * An Elasticsearch simple_query_string + */ + search: schema.maybe(schema.string()), + + /** + * The page of objects to return + */ + page: schema.number({ defaultValue: 1, min: 1 }), + + /** + * The number of objects to include in each page + */ + perPage: schema.number({ defaultValue: DEFAULT_BENCHMARK_RULES_PER_PAGE, min: 0 }), + + /** + * Fields to retrieve from CspBenchmarkRule saved object + */ + fields: schema.maybe(schema.arrayOf(schema.string())), + + /** + * The fields to perform the parsed query against. + * Valid fields are fields which mapped to 'text' in cspBenchmarkRuleSavedObjectMapping + */ + searchFields: schema.arrayOf( + schema.oneOf([schema.literal('metadata.name.text'), schema.literal('metadata.section.text')]), + { defaultValue: ['metadata.name.text'] } + ), + + /** + * Sort Field + */ + sortField: schema.oneOf( + [ + schema.literal('metadata.name'), + schema.literal('metadata.section'), + schema.literal('metadata.id'), + schema.literal('metadata.version'), + schema.literal('metadata.benchmark.id'), + schema.literal('metadata.benchmark.name'), + schema.literal('metadata.benchmark.posture_type'), + schema.literal('metadata.benchmark.version'), + schema.literal('metadata.benchmark.rule_number'), + ], + { + defaultValue: 'metadata.benchmark.rule_number', + } + ), + + /** + * The order to sort by + */ + sortOrder: schema.oneOf([schema.literal('asc'), schema.literal('desc')], { + defaultValue: 'asc', + }), + + /** + * benchmark id + */ + benchmarkId: schema.maybe( + schema.oneOf([ + schema.literal('cis_k8s'), + schema.literal('cis_eks'), + schema.literal('cis_aws'), + schema.literal('cis_azure'), + schema.literal('cis_gcp'), + ]) + ), + + /** + * benchmark version + */ + benchmarkVersion: schema.maybe(schema.string()), + + /** + * rule section + */ + section: schema.maybe( + schema.oneOf([schema.string(), schema.arrayOf(schema.string(), { minSize: 1 })]) + ), + ruleNumber: schema.maybe( + schema.oneOf([schema.string(), schema.arrayOf(schema.string(), { minSize: 1 })]) + ), +}); + +export type FindCspBenchmarkRuleRequest = TypeOf; + +export interface BenchmarkRuleSelectParams { + section?: string[]; + ruleNumber?: string[]; +} + +export interface PageUrlParams { + benchmarkId: BenchmarksCisId; + benchmarkVersion: string; +} + +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: rulesToUpdate, +}); + +export type RulesToUpdate = TypeOf; + +export type CspBenchmarkRulesBulkActionRequestSchema = TypeOf< + typeof cspBenchmarkRulesBulkActionRequestSchema +>; + +export interface CspBenchmarkRulesBulkActionResponse { + updated_benchmark_rules: CspBenchmarkRulesStates; + disabled_detection_rules?: string[]; + message: string; +} + +const ruleStateAttributes = schema.object({ + muted: schema.boolean(), + benchmark_id: schema.string(), + benchmark_version: schema.string(), + rule_number: schema.string(), + rule_id: schema.string(), +}); + +export type RuleStateAttributes = TypeOf; + +const rulesStates = schema.recordOf(schema.string(), ruleStateAttributes); + +export type CspBenchmarkRulesStates = TypeOf; + +export const cspSettingsSchema = schema.object({ + rules: rulesStates, +}); + +export type CspSettings = TypeOf; + +export interface BulkActionBenchmarkRulesResponse { + updatedBenchmarkRulesStates: CspBenchmarkRulesStates; + disabledDetectionRules: string[]; +} diff --git a/x-pack/plugins/cloud_security_posture/common/utils/helpers.ts b/x-pack/plugins/cloud_security_posture/common/utils/helpers.ts index 3c70b3a7964b9..2211f784095a1 100644 --- a/x-pack/plugins/cloud_security_posture/common/utils/helpers.ts +++ b/x-pack/plugins/cloud_security_posture/common/utils/helpers.ts @@ -218,3 +218,29 @@ export const getBenchmarkFilterQuery = ( : ''; return baseQuery + sectionQuery + ruleNumberQuery; }; + +export const getBenchmarkFilterQueryV2 = ( + id: BenchmarkId, + version?: string, + selectParams?: BenchmarkRuleSelectParams +): string => { + const baseQuery = `${CSP_BENCHMARK_RULE_SAVED_OBJECT_TYPE}.attributes.metadata.benchmark.id:${id} AND ${CSP_BENCHMARK_RULE_SAVED_OBJECT_TYPE}.attributes.metadata.benchmark.version:"v${version}"`; + + let sectionQuery = ''; + let ruleNumberQuery = ''; + if (selectParams?.section) { + const sectionParamsArr = selectParams.section?.map( + (params) => `${CSP_BENCHMARK_RULE_SAVED_OBJECT_TYPE}.attributes.metadata.section: "${params}"` + ); + sectionQuery = ' AND (' + sectionParamsArr.join(' OR ') + ')'; + } + if (selectParams?.ruleNumber) { + const ruleNumbersParamsArr = selectParams?.ruleNumber?.map( + (params) => + `${CSP_BENCHMARK_RULE_SAVED_OBJECT_TYPE}.attributes.metadata.benchmark.rule_number: "${params}"` + ); + ruleNumberQuery = ' AND (' + ruleNumbersParamsArr.join(' OR ') + ')'; + } + + return baseQuery + sectionQuery + ruleNumberQuery; +}; diff --git a/x-pack/plugins/cloud_security_posture/public/pages/rules/rules_table_header.tsx b/x-pack/plugins/cloud_security_posture/public/pages/rules/rules_table_header.tsx index a960d541f1630..965da8f541e66 100644 --- a/x-pack/plugins/cloud_security_posture/public/pages/rules/rules_table_header.tsx +++ b/x-pack/plugins/cloud_security_posture/public/pages/rules/rules_table_header.tsx @@ -39,8 +39,8 @@ export const RULES_ENABLED_FILTER = 'rules-enabled-filter'; interface RulesTableToolbarProps { search: (value: string) => void; - onSectionChange: (value: string | undefined) => void; - onRuleNumberChange: (value: string | undefined) => void; + onSectionChange: (value: string[] | undefined) => void; + onRuleNumberChange: (value: string[] | undefined) => void; sectionSelectOptions: string[]; ruleNumberSelectOptions: string[]; totalRulesCount: number; @@ -144,12 +144,13 @@ export const RulesTableHeader = ({ defaultMessage: 'CIS Section', } )} - singleSelection={{ asPlainText: true }} + // singleSelection={{ asPlainText: true }} options={sectionOptions} selectedOptions={selectedSection} onChange={(option) => { + const optionSectionArray = option.map((val) => val.label); setSelectedSection(option); - onSectionChange(option.length ? option[0].label : undefined); + onSectionChange(option.length ? optionSectionArray : undefined); }} /> @@ -166,12 +167,13 @@ export const RulesTableHeader = ({ defaultMessage: 'Rule Number', } )} - singleSelection={{ asPlainText: true }} + // singleSelection={{ asPlainText: true }} options={ruleNumberOptions} selectedOptions={selectedRuleNumber} onChange={(option) => { + const optionRuleNumberArray = option.map((val) => val.label); setSelectedRuleNumber(option); - onRuleNumberChange(option.length ? option[0].label : undefined); + onRuleNumberChange(option.length ? optionRuleNumberArray : undefined); }} /> diff --git a/x-pack/plugins/cloud_security_posture/server/routes/benchmark_rules/find/v2.ts b/x-pack/plugins/cloud_security_posture/server/routes/benchmark_rules/find/v2.ts index 228d7d5e9c3e6..6cf4efd5705a0 100644 --- a/x-pack/plugins/cloud_security_posture/server/routes/benchmark_rules/find/v2.ts +++ b/x-pack/plugins/cloud_security_posture/server/routes/benchmark_rules/find/v2.ts @@ -6,7 +6,7 @@ */ import { SavedObjectsClientContract } from '@kbn/core-saved-objects-api-server'; -import { getBenchmarkFilterQuery } from '../../../../common/utils/helpers'; +import { getBenchmarkFilterQueryV2 } from '../../../../common/utils/helpers'; import { CSP_BENCHMARK_RULE_SAVED_OBJECT_TYPE } from '../../../../common/constants'; import type { @@ -23,9 +23,11 @@ export const findBenchmarkRuleHandler = async ( if (!options.benchmarkId) { throw new Error('Please provide benchmarkId'); } - + const sectionFilter: string[] | undefined = + typeof options?.section === 'string' ? [options?.section] : options?.section; + const ruleNumberFilter: string[] | undefined = + typeof options?.ruleNumber === 'string' ? [options?.ruleNumber] : options?.ruleNumber; const benchmarkId = options.benchmarkId; - const cspCspBenchmarkRulesSo = await soClient.find({ type: CSP_BENCHMARK_RULE_SAVED_OBJECT_TYPE, searchFields: options.searchFields, @@ -34,9 +36,9 @@ export const findBenchmarkRuleHandler = async ( perPage: options.perPage, sortField: options.sortField, fields: options?.fields, - filter: getBenchmarkFilterQuery(benchmarkId, options.benchmarkVersion || '', { - section: options.section, - ruleNumber: options.ruleNumber, + filter: getBenchmarkFilterQueryV2(benchmarkId, options.benchmarkVersion || '', { + section: sectionFilter, + ruleNumber: ruleNumberFilter, }), }); diff --git a/x-pack/plugins/cloud_security_posture/server/routes/benchmark_rules/find/v3.ts b/x-pack/plugins/cloud_security_posture/server/routes/benchmark_rules/find/v3.ts new file mode 100644 index 0000000000000..228d7d5e9c3e6 --- /dev/null +++ b/x-pack/plugins/cloud_security_posture/server/routes/benchmark_rules/find/v3.ts @@ -0,0 +1,56 @@ +/* + * 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 { getBenchmarkFilterQuery } from '../../../../common/utils/helpers'; +import { CSP_BENCHMARK_RULE_SAVED_OBJECT_TYPE } from '../../../../common/constants'; + +import type { + CspBenchmarkRule, + FindCspBenchmarkRuleRequest, + FindCspBenchmarkRuleResponse, +} from '../../../../common/types/latest'; +import { getSortedCspBenchmarkRulesTemplates } from './utils'; + +export const findBenchmarkRuleHandler = async ( + soClient: SavedObjectsClientContract, + options: FindCspBenchmarkRuleRequest +): Promise => { + if (!options.benchmarkId) { + throw new Error('Please provide benchmarkId'); + } + + const benchmarkId = options.benchmarkId; + + const cspCspBenchmarkRulesSo = await soClient.find({ + type: CSP_BENCHMARK_RULE_SAVED_OBJECT_TYPE, + searchFields: options.searchFields, + search: options.search ? `"${options.search}"*` : '', + page: options.page, + perPage: options.perPage, + sortField: options.sortField, + fields: options?.fields, + filter: getBenchmarkFilterQuery(benchmarkId, options.benchmarkVersion || '', { + section: options.section, + ruleNumber: options.ruleNumber, + }), + }); + + const cspBenchmarkRules = cspCspBenchmarkRulesSo.saved_objects.map( + (cspBenchmarkRule) => cspBenchmarkRule.attributes + ); + + // Semantic version sorting using semver for valid versions and custom comparison for invalid versions + const sortedCspBenchmarkRules = getSortedCspBenchmarkRulesTemplates(cspBenchmarkRules); + + return { + items: sortedCspBenchmarkRules, + total: cspCspBenchmarkRulesSo.total, + page: options.page, + perPage: options.perPage, + }; +}; From 4a035819c1a6b384319a9887d44a7bb710d2b4b8 Mon Sep 17 00:00:00 2001 From: animehart Date: Thu, 18 Jan 2024 18:54:52 -0800 Subject: [PATCH 02/13] api updates, added helper test --- .../common/utils/helpers.test.ts | 45 ++++++++++++++++++- .../common/utils/helpers.ts | 2 +- .../pages/rules/use_csp_benchmark_rules.ts | 2 +- .../routes/benchmark_rules/find/find.ts | 41 ++++++++++++++++- .../server/routes/benchmark_rules/find/v2.ts | 16 +++---- .../server/routes/benchmark_rules/find/v3.ts | 14 +++--- 6 files changed, 101 insertions(+), 19 deletions(-) diff --git a/x-pack/plugins/cloud_security_posture/common/utils/helpers.test.ts b/x-pack/plugins/cloud_security_posture/common/utils/helpers.test.ts index 6ce2add754f20..951800d8a3cc0 100644 --- a/x-pack/plugins/cloud_security_posture/common/utils/helpers.test.ts +++ b/x-pack/plugins/cloud_security_posture/common/utils/helpers.test.ts @@ -6,7 +6,12 @@ */ import { createPackagePolicyMock } from '@kbn/fleet-plugin/common/mocks'; -import { getBenchmarkFromPackagePolicy, getBenchmarkFilter, cleanupCredentials } from './helpers'; +import { + getBenchmarkFromPackagePolicy, + getBenchmarkFilter, + cleanupCredentials, + getBenchmarkFilterQueryV2, +} from './helpers'; describe('test helper methods', () => { it('get default integration type from inputs with multiple enabled types', () => { @@ -70,6 +75,44 @@ describe('test helper methods', () => { ); }); + it('get benchmark filter query based on a benchmark Id, version', () => { + const typeFilter = getBenchmarkFilterQueryV2('cis_eks', '1.0.1'); + expect(typeFilter).toMatch( + 'csp-rule-template.attributes.metadata.benchmark.id:cis_eks AND csp-rule-template.attributes.metadata.benchmark.version:"v1.0.1"' + ); + }); + + it('get benchmark filter query based on a benchmark Id, version and multiple sections and rule numbers', () => { + const mockSelectParams = { + section: ['section_1', 'section_2'], + ruleNumber: ['1a', '2b', '3c'], + }; + const typeFilter = getBenchmarkFilterQueryV2('cis_eks', '1.0.1', mockSelectParams); + expect(typeFilter).toMatch( + 'csp-rule-template.attributes.metadata.benchmark.id:cis_eks AND csp-rule-template.attributes.metadata.benchmark.version:"v1.0.1" AND (csp-rule-template.attributes.metadata.section: "section_1" OR csp-rule-template.attributes.metadata.section: "section_2") AND (csp-rule-template.attributes.metadata.benchmark.rule_number: "1a" OR csp-rule-template.attributes.metadata.benchmark.rule_number: "2b" OR csp-rule-template.attributes.metadata.benchmark.rule_number: "3c")' + ); + }); + + it('get benchmark filter query based on a benchmark Id, version and just sections', () => { + const mockSelectParams = { + section: ['section_1', 'section_2'], + }; + const typeFilter = getBenchmarkFilterQueryV2('cis_eks', '1.0.1', mockSelectParams); + expect(typeFilter).toMatch( + 'csp-rule-template.attributes.metadata.benchmark.id:cis_eks AND csp-rule-template.attributes.metadata.benchmark.version:"v1.0.1" AND (csp-rule-template.attributes.metadata.section: "section_1" OR csp-rule-template.attributes.metadata.section: "section_2")' + ); + }); + + it('get benchmark filter query based on a benchmark Id, version and just rule numbers', () => { + const mockSelectParams = { + ruleNumber: ['1a', '2b', '3c'], + }; + const typeFilter = getBenchmarkFilterQueryV2('cis_eks', '1.0.1', mockSelectParams); + expect(typeFilter).toMatch( + 'csp-rule-template.attributes.metadata.benchmark.id:cis_eks AND csp-rule-template.attributes.metadata.benchmark.version:"v1.0.1" AND (csp-rule-template.attributes.metadata.benchmark.rule_number: "1a" OR csp-rule-template.attributes.metadata.benchmark.rule_number: "2b" OR csp-rule-template.attributes.metadata.benchmark.rule_number: "3c")' + ); + }); + describe('cleanupCredentials', () => { it('cleans unused aws credential methods, except role_arn when using assume_role', () => { const mockPackagePolicy = createPackagePolicyMock(); diff --git a/x-pack/plugins/cloud_security_posture/common/utils/helpers.ts b/x-pack/plugins/cloud_security_posture/common/utils/helpers.ts index 2211f784095a1..ea1879f4c073d 100644 --- a/x-pack/plugins/cloud_security_posture/common/utils/helpers.ts +++ b/x-pack/plugins/cloud_security_posture/common/utils/helpers.ts @@ -235,7 +235,7 @@ export const getBenchmarkFilterQueryV2 = ( sectionQuery = ' AND (' + sectionParamsArr.join(' OR ') + ')'; } if (selectParams?.ruleNumber) { - const ruleNumbersParamsArr = selectParams?.ruleNumber?.map( + const ruleNumbersParamsArr = selectParams.ruleNumber?.map( (params) => `${CSP_BENCHMARK_RULE_SAVED_OBJECT_TYPE}.attributes.metadata.benchmark.rule_number: "${params}"` ); diff --git a/x-pack/plugins/cloud_security_posture/public/pages/rules/use_csp_benchmark_rules.ts b/x-pack/plugins/cloud_security_posture/public/pages/rules/use_csp_benchmark_rules.ts index b7b772bdf63fe..d1a677e4e5182 100644 --- a/x-pack/plugins/cloud_security_posture/public/pages/rules/use_csp_benchmark_rules.ts +++ b/x-pack/plugins/cloud_security_posture/public/pages/rules/use_csp_benchmark_rules.ts @@ -37,7 +37,7 @@ export const useFindCspBenchmarkRule = ( () => { return http.get(FIND_CSP_BENCHMARK_RULE_ROUTE_PATH, { query: { benchmarkId, page, perPage, search, section, benchmarkVersion, ruleNumber }, - version: '2', + version: '3', }); } ); diff --git a/x-pack/plugins/cloud_security_posture/server/routes/benchmark_rules/find/find.ts b/x-pack/plugins/cloud_security_posture/server/routes/benchmark_rules/find/find.ts index 12954073e552a..8dc8f36554600 100644 --- a/x-pack/plugins/cloud_security_posture/server/routes/benchmark_rules/find/find.ts +++ b/x-pack/plugins/cloud_security_posture/server/routes/benchmark_rules/find/find.ts @@ -14,10 +14,15 @@ import { FindCspBenchmarkRuleRequest as FindCspBenchmarkRuleRequestV1, findCspBenchmarkRuleRequestSchema as findCspBenchmarkRuleRequestSchemaV1, } from '../../../../common/types/rules/v3'; +import { + FindCspBenchmarkRuleRequest as FindCspBenchmarkRuleRequestV2, + findCspBenchmarkRuleRequestSchema as findCspBenchmarkRuleRequestSchemaV2, +} from '../../../../common/types/rules/v4'; import { FIND_CSP_BENCHMARK_RULE_ROUTE_PATH } from '../../../../common/constants'; import { CspRouter } from '../../../types'; import { findBenchmarkRuleHandler as findRuleHandlerV1 } from './v1'; import { findBenchmarkRuleHandler as findRuleHandlerV2 } from './v2'; +import { findBenchmarkRuleHandler as findRuleHandlerV3 } from './v3'; export const defineFindCspBenchmarkRuleRoute = (router: CspRouter) => router.versioned @@ -61,6 +66,40 @@ export const defineFindCspBenchmarkRuleRoute = (router: CspRouter) => .addVersion( { version: '2', + validate: { + request: { + query: findCspBenchmarkRuleRequestSchemaV2, + }, + }, + }, + async (context, request, response) => { + if (!(await context.fleet).authz.fleet.all) { + return response.forbidden(); + } + + const requestBody: FindCspBenchmarkRuleRequestV2 = request.query; + const cspContext = await context.csp; + + try { + const cspBenchmarkRules: FindCspBenchmarkRuleResponse = await findRuleHandlerV2( + cspContext.soClient, + requestBody + ); + + return response.ok({ body: cspBenchmarkRules }); + } catch (err) { + const error = transformError(err); + cspContext.logger.error(`Failed to fetch csp rules templates ${err}`); + return response.customError({ + body: { message: error.message }, + statusCode: error.statusCode, + }); + } + } + ) + .addVersion( + { + version: '3', validate: { request: { query: findCspBenchmarkRuleRequestSchema, @@ -76,7 +115,7 @@ export const defineFindCspBenchmarkRuleRoute = (router: CspRouter) => const cspContext = await context.csp; try { - const cspBenchmarkRules: FindCspBenchmarkRuleResponse = await findRuleHandlerV2( + const cspBenchmarkRules: FindCspBenchmarkRuleResponse = await findRuleHandlerV3( cspContext.soClient, requestBody ); diff --git a/x-pack/plugins/cloud_security_posture/server/routes/benchmark_rules/find/v2.ts b/x-pack/plugins/cloud_security_posture/server/routes/benchmark_rules/find/v2.ts index 6cf4efd5705a0..324293487747b 100644 --- a/x-pack/plugins/cloud_security_posture/server/routes/benchmark_rules/find/v2.ts +++ b/x-pack/plugins/cloud_security_posture/server/routes/benchmark_rules/find/v2.ts @@ -6,14 +6,14 @@ */ import { SavedObjectsClientContract } from '@kbn/core-saved-objects-api-server'; -import { getBenchmarkFilterQueryV2 } from '../../../../common/utils/helpers'; +import { getBenchmarkFilterQuery } from '../../../../common/utils/helpers'; import { CSP_BENCHMARK_RULE_SAVED_OBJECT_TYPE } from '../../../../common/constants'; import type { CspBenchmarkRule, - FindCspBenchmarkRuleRequest, FindCspBenchmarkRuleResponse, } from '../../../../common/types/latest'; +import type { FindCspBenchmarkRuleRequest } from '../../../../common/types/rules/v4'; import { getSortedCspBenchmarkRulesTemplates } from './utils'; export const findBenchmarkRuleHandler = async ( @@ -23,11 +23,9 @@ export const findBenchmarkRuleHandler = async ( if (!options.benchmarkId) { throw new Error('Please provide benchmarkId'); } - const sectionFilter: string[] | undefined = - typeof options?.section === 'string' ? [options?.section] : options?.section; - const ruleNumberFilter: string[] | undefined = - typeof options?.ruleNumber === 'string' ? [options?.ruleNumber] : options?.ruleNumber; + const benchmarkId = options.benchmarkId; + const cspCspBenchmarkRulesSo = await soClient.find({ type: CSP_BENCHMARK_RULE_SAVED_OBJECT_TYPE, searchFields: options.searchFields, @@ -36,9 +34,9 @@ export const findBenchmarkRuleHandler = async ( perPage: options.perPage, sortField: options.sortField, fields: options?.fields, - filter: getBenchmarkFilterQueryV2(benchmarkId, options.benchmarkVersion || '', { - section: sectionFilter, - ruleNumber: ruleNumberFilter, + filter: getBenchmarkFilterQuery(benchmarkId, options.benchmarkVersion || '', { + section: options.section, + ruleNumber: options.ruleNumber, }), }); diff --git a/x-pack/plugins/cloud_security_posture/server/routes/benchmark_rules/find/v3.ts b/x-pack/plugins/cloud_security_posture/server/routes/benchmark_rules/find/v3.ts index 228d7d5e9c3e6..6cf4efd5705a0 100644 --- a/x-pack/plugins/cloud_security_posture/server/routes/benchmark_rules/find/v3.ts +++ b/x-pack/plugins/cloud_security_posture/server/routes/benchmark_rules/find/v3.ts @@ -6,7 +6,7 @@ */ import { SavedObjectsClientContract } from '@kbn/core-saved-objects-api-server'; -import { getBenchmarkFilterQuery } from '../../../../common/utils/helpers'; +import { getBenchmarkFilterQueryV2 } from '../../../../common/utils/helpers'; import { CSP_BENCHMARK_RULE_SAVED_OBJECT_TYPE } from '../../../../common/constants'; import type { @@ -23,9 +23,11 @@ export const findBenchmarkRuleHandler = async ( if (!options.benchmarkId) { throw new Error('Please provide benchmarkId'); } - + const sectionFilter: string[] | undefined = + typeof options?.section === 'string' ? [options?.section] : options?.section; + const ruleNumberFilter: string[] | undefined = + typeof options?.ruleNumber === 'string' ? [options?.ruleNumber] : options?.ruleNumber; const benchmarkId = options.benchmarkId; - const cspCspBenchmarkRulesSo = await soClient.find({ type: CSP_BENCHMARK_RULE_SAVED_OBJECT_TYPE, searchFields: options.searchFields, @@ -34,9 +36,9 @@ export const findBenchmarkRuleHandler = async ( perPage: options.perPage, sortField: options.sortField, fields: options?.fields, - filter: getBenchmarkFilterQuery(benchmarkId, options.benchmarkVersion || '', { - section: options.section, - ruleNumber: options.ruleNumber, + filter: getBenchmarkFilterQueryV2(benchmarkId, options.benchmarkVersion || '', { + section: sectionFilter, + ruleNumber: ruleNumberFilter, }), }); From 11a0180b9f05056458cb5102d8f612f6ce2904c3 Mon Sep 17 00:00:00 2001 From: animehart Date: Fri, 19 Jan 2024 18:17:40 -0800 Subject: [PATCH 03/13] added custom component for Section and Rule number filter as well as FTR --- .../common/component/multi_select_filter.tsx | 199 ++++++++++++++++++ .../public/pages/rules/rules_table.tsx | 4 +- .../public/pages/rules/rules_table_header.tsx | 55 ++--- .../public/pages/rules/use_csp_rules_state.ts | 2 +- .../page_objects/rule_page.ts | 25 +++ .../pages/rules.ts | 29 +++ 6 files changed, 284 insertions(+), 30 deletions(-) create mode 100644 x-pack/plugins/cloud_security_posture/public/common/component/multi_select_filter.tsx diff --git a/x-pack/plugins/cloud_security_posture/public/common/component/multi_select_filter.tsx b/x-pack/plugins/cloud_security_posture/public/common/component/multi_select_filter.tsx new file mode 100644 index 0000000000000..272a9a918ec02 --- /dev/null +++ b/x-pack/plugins/cloud_security_posture/public/common/component/multi_select_filter.tsx @@ -0,0 +1,199 @@ +/* + * 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 React, { useState, useEffect } from 'react'; +import { css } from '@emotion/react'; +import type { EuiSelectableOption } from '@elastic/eui'; +import { + EuiPopoverTitle, + EuiCallOut, + EuiHorizontalRule, + EuiPopover, + EuiSelectable, + EuiFilterButton, + EuiFilterGroup, + EuiText, +} from '@elastic/eui'; +import { isEqual } from 'lodash/fp'; +import { i18n } from '@kbn/i18n'; + +type FilterOption = EuiSelectableOption<{ + key: K; + label: T; +}>; + +export type { FilterOption as MultiSelectFilterOption }; + +export const mapToMultiSelectOption = (options: T[]) => { + return options.map((option) => { + return { + key: option, + label: option, + }; + }); +}; + +const fromRawOptionsToEuiSelectableOptions = ( + options: Array>, + selectedOptionKeys: string[] +): Array> => { + return options.map(({ key, label }) => { + const selectableOption: FilterOption = { label, key }; + if (selectedOptionKeys.includes(key)) { + selectableOption.checked = 'on'; + } + selectableOption['data-test-subj'] = `options-filter-popover-item-${key.split(' ').join('-')}`; + return selectableOption; + }); +}; + +const fromEuiSelectableOptionToRawOption = ( + options: Array> +): string[] => { + return options.map((option) => option.key); +}; + +const getEuiSelectableCheckedOptions = ( + options: Array> +) => options.filter((option) => option.checked === 'on') as Array>; + +interface UseFilterParams { + buttonIconType?: string; + buttonLabel?: string; + hideActiveOptionsNumber?: boolean; + id: string; + limit?: number; + limitReachedMessage?: string; + onChange: (params: { filterId: string; selectedOptionKeys: string[] }) => void; + options: Array>; + renderOption?: (option: FilterOption) => React.ReactNode; + selectedOptionKeys?: string[]; + transparentBackground?: boolean; +} +export const MultiSelectFilter = ({ + buttonLabel, + buttonIconType, + hideActiveOptionsNumber, + id, + limit, + limitReachedMessage, + onChange, + options: rawOptions, + selectedOptionKeys = [], + renderOption, + transparentBackground, +}: UseFilterParams) => { + const [isPopoverOpen, setIsPopoverOpen] = useState(false); + const toggleIsPopoverOpen = () => setIsPopoverOpen((prevValue) => !prevValue); + const showActiveOptionsNumber = !hideActiveOptionsNumber; + const isInvalid = Boolean(limit && limitReachedMessage && selectedOptionKeys.length >= limit); + const options = fromRawOptionsToEuiSelectableOptions(rawOptions, selectedOptionKeys); + + useEffect(() => { + const newSelectedOptions = selectedOptionKeys.filter((selectedOptionKey) => + rawOptions.some(({ key: optionKey }) => optionKey === selectedOptionKey) + ); + if (!isEqual(newSelectedOptions, selectedOptionKeys)) { + onChange({ + filterId: id, + selectedOptionKeys: newSelectedOptions, + }); + } + }, [selectedOptionKeys, rawOptions, id, onChange]); + + const _onChange = (newOptions: Array>) => { + const newSelectedOptions = getEuiSelectableCheckedOptions(newOptions); + if (isInvalid && limit && newSelectedOptions.length >= limit) { + return; + } + + onChange({ + filterId: id, + selectedOptionKeys: fromEuiSelectableOptionToRawOption(newSelectedOptions), + }); + }; + + return ( + + 0 : undefined} + numActiveFilters={showActiveOptionsNumber ? selectedOptionKeys.length : undefined} + aria-label={buttonLabel} + > + + {buttonLabel} + + + } + isOpen={isPopoverOpen} + closePopover={() => setIsPopoverOpen(false)} + panelPaddingSize="none" + repositionOnScroll + > + {isInvalid && ( + <> + + + + + )} + > + options={options} + searchable + searchProps={{ + placeholder: + i18n.translate('xpack.csp.common.component.multiSelectFilter.searchWord', { + defaultMessage: 'Search ', + }) + buttonLabel, + compressed: false, + 'data-test-subj': `${id}-search-input`, + css: css` + border-radius: 0px !important; + `, + }} + emptyMessage={'empty'} + onChange={_onChange} + singleSelection={false} + renderOption={renderOption} + > + {(list, search) => ( +
+ {search} + {list} +
+ )} + +
+
+ ); +}; + +MultiSelectFilter.displayName = 'MultiSelectFilter'; diff --git a/x-pack/plugins/cloud_security_posture/public/pages/rules/rules_table.tsx b/x-pack/plugins/cloud_security_posture/public/pages/rules/rules_table.tsx index 44bf8cc62785a..0dacb9b1a1a9a 100644 --- a/x-pack/plugins/cloud_security_posture/public/pages/rules/rules_table.tsx +++ b/x-pack/plugins/cloud_security_posture/public/pages/rules/rules_table.tsx @@ -190,7 +190,7 @@ const getColumns = ({ }} /> ), - width: '30px', + width: '3%', sortable: false, render: (rules, item: CspBenchmarkRulesWithStates) => { return ( @@ -255,7 +255,7 @@ const getColumns = ({ name: i18n.translate('xpack.csp.rules.rulesTable.mutedColumnLabel', { defaultMessage: 'Enabled', }), - width: '10%', + width: '5%', truncateText: true, render: (name, rule: CspBenchmarkRulesWithStates) => { const rulesObjectRequest = { diff --git a/x-pack/plugins/cloud_security_posture/public/pages/rules/rules_table_header.tsx b/x-pack/plugins/cloud_security_posture/public/pages/rules/rules_table_header.tsx index 965da8f541e66..7cda06e96ea43 100644 --- a/x-pack/plugins/cloud_security_posture/public/pages/rules/rules_table_header.tsx +++ b/x-pack/plugins/cloud_security_posture/public/pages/rules/rules_table_header.tsx @@ -6,13 +6,11 @@ */ import React, { useState } from 'react'; import { - EuiComboBox, EuiFieldSearch, EuiFlexItem, EuiText, EuiSpacer, EuiFlexGroup, - type EuiComboBoxOptionOption, EuiPopover, EuiButtonEmpty, EuiContextMenuItem, @@ -28,6 +26,7 @@ import { css } from '@emotion/react'; import { euiThemeVars } from '@kbn/ui-theme'; import { RuleStateAttributesWithoutStates, useChangeCspRuleState } from './change_csp_rule_state'; import { CspBenchmarkRulesWithStates } from './rules_container'; +import { MultiSelectFilter } from '../../common/component/multi_select_filter'; export const RULES_BULK_ACTION_BUTTON = 'bulk-action-button'; export const RULES_BULK_ACTION_OPTION_ENABLE = 'bulk-action-option-enable'; @@ -36,6 +35,8 @@ export const RULES_SELECT_ALL_RULES = 'select-all-rules-button'; export const RULES_CLEAR_ALL_RULES_SELECTION = 'clear-rules-selection-button'; export const RULES_DISABLED_FILTER = 'rules-disabled-filter'; export const RULES_ENABLED_FILTER = 'rules-enabled-filter'; +export const CIS_SECTION_FILTER = 'cis-section-filter'; +export const RULE_NUMBER_FILTER = 'rule-number-filter'; interface RulesTableToolbarProps { search: (value: string) => void; @@ -81,16 +82,16 @@ export const RulesTableHeader = ({ setSelectAllRules, setSelectedRules, }: RulesTableToolbarProps) => { - const [selectedSection, setSelectedSection] = useState([]); - const [selectedRuleNumber, setSelectedRuleNumber] = useState([]); + const [selectedSection, setSelectedSection] = useState([]); + const [selectedRuleNumber, setSelectedRuleNumber] = useState([]); const sectionOptions = sectionSelectOptions.map((option) => ({ + key: option, label: option, })); - const ruleNumberOptions = ruleNumberSelectOptions.map((option) => ({ + key: option, label: option, })); - const [isEnabledRulesFilterOn, setIsEnabledRulesFilterOn] = useState(false); const [isDisabledRulesFilterOn, setisDisabledRulesFilterOn] = useState(false); @@ -130,28 +131,28 @@ export const RulesTableHeader = ({ /> - + - { - const optionSectionArray = option.map((val) => val.label); - setSelectedSection(option); - onSectionChange(option.length ? optionSectionArray : undefined); + id={'section'} + onChange={(section) => { + setSelectedSection([...section?.selectedOptionKeys]); + onSectionChange( + section?.selectedOptionKeys ? section?.selectedOptionKeys : undefined + ); }} + options={sectionOptions} + selectedOptionKeys={selectedSection} /> - { - const optionRuleNumberArray = option.map((val) => val.label); - setSelectedRuleNumber(option); - onRuleNumberChange(option.length ? optionRuleNumberArray : undefined); + id={'rule-number'} + onChange={(ruleNumber) => { + setSelectedRuleNumber([...ruleNumber?.selectedOptionKeys]); + onRuleNumberChange( + ruleNumber?.selectedOptionKeys ? ruleNumber?.selectedOptionKeys : undefined + ); }} + options={ruleNumberOptions} + selectedOptionKeys={selectedRuleNumber} /> { const { http } = useKibana().services; diff --git a/x-pack/test/cloud_security_posture_functional/page_objects/rule_page.ts b/x-pack/test/cloud_security_posture_functional/page_objects/rule_page.ts index 3136b2b240fb4..1f2fde39739bf 100644 --- a/x-pack/test/cloud_security_posture_functional/page_objects/rule_page.ts +++ b/x-pack/test/cloud_security_posture_functional/page_objects/rule_page.ts @@ -20,6 +20,9 @@ export const RULES_CLEAR_ALL_RULES_SELECTION = 'clear-rules-selection-button'; export const RULES_ROWS_ENABLE_SWITCH_BUTTON = 'rules-row-enable-switch-button'; export const RULES_DISABLED_FILTER = 'rules-disabled-filter'; export const RULES_ENABLED_FILTER = 'rules-enabled-filter'; +export const CIS_SECTION_FILTER = 'options-filter-popover-button-section'; +export const RULE_NUMBER_FILTER = 'options-filter-popover-button-rule-number'; +export const RULE_NUMBER_FILTER_SEARCH_FIELD = 'rule-number-search-input'; export function RulePagePageProvider({ getService, getPageObjects }: FtrProviderContext) { const testSubjects = getService('testSubjects'); @@ -86,6 +89,28 @@ export function RulePagePageProvider({ getService, getPageObjects }: FtrProvider const enableRulesRowSwitch = await testSubjects.findAll(RULES_ROWS_ENABLE_SWITCH_BUTTON); return await enableRulesRowSwitch.length; }, + + clickFilterPopover: async (filterType: 'section' | 'ruleNumber') => { + const filterPopoverButton = + (await filterType) === 'section' + ? await testSubjects.find(CIS_SECTION_FILTER) + : await testSubjects.find(RULE_NUMBER_FILTER); + + await filterPopoverButton.click(); + await PageObjects.header.waitUntilLoadingHasFinished(); + }, + + clickFilterPopOverOption: async (value: string) => { + const chosenValue = await testSubjects.find('options-filter-popover-item-' + (await value)); + await chosenValue.click(); + await PageObjects.header.waitUntilLoadingHasFinished(); + }, + + filterTextInput: async (selector: string, value: string) => { + const textField = await testSubjects.find('rule-number-search-input'); + await textField.type(value); + await PageObjects.header.waitUntilLoadingHasFinished(); + }, }; const navigateToRulePage = async (benchmarkCisId: string, benchmarkCisVersion: string) => { diff --git a/x-pack/test/cloud_security_posture_functional/pages/rules.ts b/x-pack/test/cloud_security_posture_functional/pages/rules.ts index 46859f75572d8..a8bee3fffae5d 100644 --- a/x-pack/test/cloud_security_posture_functional/pages/rules.ts +++ b/x-pack/test/cloud_security_posture_functional/pages/rules.ts @@ -12,6 +12,7 @@ import type { FtrProviderContext } from '../ftr_provider_context'; import { RULES_BULK_ACTION_OPTION_DISABLE, RULES_BULK_ACTION_OPTION_ENABLE, + RULE_NUMBER_FILTER_SEARCH_FIELD, } from '../page_objects/rule_page'; // eslint-disable-next-line import/no-default-export @@ -139,5 +140,33 @@ export default function ({ getPageObjects, getService }: FtrProviderContext) { expect((await rule.rulePage.getEnableRulesRowSwitchButton()) > 1).to.be(true); }); }); + + describe('Rules Page - CIS Section & Rule Number filters', () => { + it('Table should only show result that has the same section as in the Section filter', async () => { + await rule.rulePage.clickFilterPopover('section'); + await rule.rulePage.clickFilterPopOverOption('etcd'); + await rule.rulePage.clickFilterPopOverOption('Scheduler'); + expect((await rule.rulePage.getEnableRulesRowSwitchButton()) < 10).to.be(true); + }); + + it('TTable should only show result that has the same section as in the Rule number filter', async () => { + await rule.rulePage.clickFilterPopover('ruleNumber'); + await rule.rulePage.clickFilterPopOverOption('1.1.1'); + await rule.rulePage.clickFilterPopOverOption('1.1.2'); + expect((await rule.rulePage.getEnableRulesRowSwitchButton()) === 2).to.be(true); + }); + + it('Table should only show result that passes both Section and Rule number filter', async () => { + await rule.rulePage.clickFilterPopover('section'); + await rule.rulePage.clickFilterPopOverOption('etcd'); + await rule.rulePage.clickFilterPopOverOption('Scheduler'); + await rule.rulePage.clickFilterPopover('section'); + await rule.rulePage.clickFilterPopover('ruleNumber'); + await rule.rulePage.filterTextInput(RULE_NUMBER_FILTER_SEARCH_FIELD, '1.4.2'); + await pageObjects.header.waitUntilLoadingHasFinished(); + await rule.rulePage.clickFilterPopOverOption('1.4.2'); + expect((await rule.rulePage.getEnableRulesRowSwitchButton()) === 1).to.be(true); + }); + }); }); } From e220abcc43b8550cdb70bba2f8854b580274580c Mon Sep 17 00:00:00 2001 From: animehart Date: Sun, 21 Jan 2024 13:36:06 -0800 Subject: [PATCH 04/13] FTR fix --- .../page_objects/rule_page.ts | 4 ++-- x-pack/test/cloud_security_posture_functional/pages/rules.ts | 3 +-- 2 files changed, 3 insertions(+), 4 deletions(-) diff --git a/x-pack/test/cloud_security_posture_functional/page_objects/rule_page.ts b/x-pack/test/cloud_security_posture_functional/page_objects/rule_page.ts index 1f2fde39739bf..229afa72854d6 100644 --- a/x-pack/test/cloud_security_posture_functional/page_objects/rule_page.ts +++ b/x-pack/test/cloud_security_posture_functional/page_objects/rule_page.ts @@ -103,11 +103,10 @@ export function RulePagePageProvider({ getService, getPageObjects }: FtrProvider clickFilterPopOverOption: async (value: string) => { const chosenValue = await testSubjects.find('options-filter-popover-item-' + (await value)); await chosenValue.click(); - await PageObjects.header.waitUntilLoadingHasFinished(); }, filterTextInput: async (selector: string, value: string) => { - const textField = await testSubjects.find('rule-number-search-input'); + const textField = await testSubjects.find(selector); await textField.type(value); await PageObjects.header.waitUntilLoadingHasFinished(); }, @@ -119,6 +118,7 @@ export function RulePagePageProvider({ getService, getPageObjects }: FtrProvider `cloud_security_posture/benchmarks/${benchmarkCisId}/${benchmarkCisVersion}/rules`, { shouldUseHashForSubUrl: false } ); + await PageObjects.header.waitUntilLoadingHasFinished(); }; return { diff --git a/x-pack/test/cloud_security_posture_functional/pages/rules.ts b/x-pack/test/cloud_security_posture_functional/pages/rules.ts index a8bee3fffae5d..f2656b5a61678 100644 --- a/x-pack/test/cloud_security_posture_functional/pages/rules.ts +++ b/x-pack/test/cloud_security_posture_functional/pages/rules.ts @@ -60,7 +60,6 @@ export default function ({ getPageObjects, getService }: FtrProviderContext) { ); await rule.waitForPluginInitialized(); await rule.navigateToRulePage('cis_k8s', '1.0.1'); - await pageObjects.header.waitUntilLoadingHasFinished(); }); afterEach(async () => { @@ -149,7 +148,7 @@ export default function ({ getPageObjects, getService }: FtrProviderContext) { expect((await rule.rulePage.getEnableRulesRowSwitchButton()) < 10).to.be(true); }); - it('TTable should only show result that has the same section as in the Rule number filter', async () => { + it('Table should only show result that has the same section as in the Rule number filter', async () => { await rule.rulePage.clickFilterPopover('ruleNumber'); await rule.rulePage.clickFilterPopOverOption('1.1.1'); await rule.rulePage.clickFilterPopOverOption('1.1.2'); From 4a144ef44f8dbdf7f7f29264033fd8b1cbd094c0 Mon Sep 17 00:00:00 2001 From: animehart Date: Sun, 21 Jan 2024 14:38:51 -0800 Subject: [PATCH 05/13] ftr fix --- .../test/cloud_security_posture_functional/pages/rules.ts | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/x-pack/test/cloud_security_posture_functional/pages/rules.ts b/x-pack/test/cloud_security_posture_functional/pages/rules.ts index f2656b5a61678..f3960237a9edf 100644 --- a/x-pack/test/cloud_security_posture_functional/pages/rules.ts +++ b/x-pack/test/cloud_security_posture_functional/pages/rules.ts @@ -157,13 +157,12 @@ export default function ({ getPageObjects, getService }: FtrProviderContext) { it('Table should only show result that passes both Section and Rule number filter', async () => { await rule.rulePage.clickFilterPopover('section'); - await rule.rulePage.clickFilterPopOverOption('etcd'); - await rule.rulePage.clickFilterPopOverOption('Scheduler'); + await rule.rulePage.clickFilterPopOverOption('Control Plane Node Configuration Files'); await rule.rulePage.clickFilterPopover('section'); await rule.rulePage.clickFilterPopover('ruleNumber'); - await rule.rulePage.filterTextInput(RULE_NUMBER_FILTER_SEARCH_FIELD, '1.4.2'); + await rule.rulePage.filterTextInput(RULE_NUMBER_FILTER_SEARCH_FIELD, '1.1.5'); await pageObjects.header.waitUntilLoadingHasFinished(); - await rule.rulePage.clickFilterPopOverOption('1.4.2'); + await rule.rulePage.clickFilterPopOverOption('1.1.5'); expect((await rule.rulePage.getEnableRulesRowSwitchButton()) === 1).to.be(true); }); }); From 390dfe9561e30589d1a6358a3154883a27b4144f Mon Sep 17 00:00:00 2001 From: animehart Date: Sun, 21 Jan 2024 15:34:38 -0800 Subject: [PATCH 06/13] ftr fix again --- .../cloud_security_posture_functional/page_objects/rule_page.ts | 2 +- x-pack/test/cloud_security_posture_functional/pages/rules.ts | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/x-pack/test/cloud_security_posture_functional/page_objects/rule_page.ts b/x-pack/test/cloud_security_posture_functional/page_objects/rule_page.ts index 229afa72854d6..0ba6b032f6de4 100644 --- a/x-pack/test/cloud_security_posture_functional/page_objects/rule_page.ts +++ b/x-pack/test/cloud_security_posture_functional/page_objects/rule_page.ts @@ -101,7 +101,7 @@ export function RulePagePageProvider({ getService, getPageObjects }: FtrProvider }, clickFilterPopOverOption: async (value: string) => { - const chosenValue = await testSubjects.find('options-filter-popover-item-' + (await value)); + const chosenValue = await testSubjects.find('options-filter-popover-item-' + value); await chosenValue.click(); }, diff --git a/x-pack/test/cloud_security_posture_functional/pages/rules.ts b/x-pack/test/cloud_security_posture_functional/pages/rules.ts index f3960237a9edf..ebae60da6a804 100644 --- a/x-pack/test/cloud_security_posture_functional/pages/rules.ts +++ b/x-pack/test/cloud_security_posture_functional/pages/rules.ts @@ -157,7 +157,7 @@ export default function ({ getPageObjects, getService }: FtrProviderContext) { it('Table should only show result that passes both Section and Rule number filter', async () => { await rule.rulePage.clickFilterPopover('section'); - await rule.rulePage.clickFilterPopOverOption('Control Plane Node Configuration Files'); + await rule.rulePage.clickFilterPopOverOption('Control-Plane-Node-Configuration-Files'); await rule.rulePage.clickFilterPopover('section'); await rule.rulePage.clickFilterPopover('ruleNumber'); await rule.rulePage.filterTextInput(RULE_NUMBER_FILTER_SEARCH_FIELD, '1.1.5'); From f0aab5c81da1d14d504c111103d9590cee493e79 Mon Sep 17 00:00:00 2001 From: animehart Date: Sun, 21 Jan 2024 16:22:47 -0800 Subject: [PATCH 07/13] clean up + attempt to check ftr issue --- .../common/types/rules/v5.ts | 70 ++++--------------- .../pages/rules.ts | 54 +++++++------- 2 files changed, 39 insertions(+), 85 deletions(-) diff --git a/x-pack/plugins/cloud_security_posture/common/types/rules/v5.ts b/x-pack/plugins/cloud_security_posture/common/types/rules/v5.ts index be62d92219510..7726e8b03fc71 100644 --- a/x-pack/plugins/cloud_security_posture/common/types/rules/v5.ts +++ b/x-pack/plugins/cloud_security_posture/common/types/rules/v5.ts @@ -4,9 +4,8 @@ * 2.0; you may not use this file except in compliance with the Elastic License * 2.0. */ - import { schema, TypeOf } from '@kbn/config-schema'; -import { BenchmarksCisId } from '../latest'; + export type { cspBenchmarkRuleMetadataSchema, CspBenchmarkRuleMetadata, @@ -14,6 +13,17 @@ export type { CspBenchmarkRule, FindCspBenchmarkRuleResponse, } from './v3'; +export type { + PageUrlParams, + rulesToUpdate, + CspBenchmarkRulesBulkActionRequestSchema, + CspBenchmarkRulesBulkActionResponse, + RuleStateAttributes, + CspBenchmarkRulesStates, + cspSettingsSchema, + CspSettings, + BulkActionBenchmarkRulesResponse, +} from './v4'; const DEFAULT_BENCHMARK_RULES_PER_PAGE = 25; @@ -109,59 +119,3 @@ export interface BenchmarkRuleSelectParams { section?: string[]; ruleNumber?: string[]; } - -export interface PageUrlParams { - benchmarkId: BenchmarksCisId; - benchmarkVersion: string; -} - -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: rulesToUpdate, -}); - -export type RulesToUpdate = TypeOf; - -export type CspBenchmarkRulesBulkActionRequestSchema = TypeOf< - typeof cspBenchmarkRulesBulkActionRequestSchema ->; - -export interface CspBenchmarkRulesBulkActionResponse { - updated_benchmark_rules: CspBenchmarkRulesStates; - disabled_detection_rules?: string[]; - message: string; -} - -const ruleStateAttributes = schema.object({ - muted: schema.boolean(), - benchmark_id: schema.string(), - benchmark_version: schema.string(), - rule_number: schema.string(), - rule_id: schema.string(), -}); - -export type RuleStateAttributes = TypeOf; - -const rulesStates = schema.recordOf(schema.string(), ruleStateAttributes); - -export type CspBenchmarkRulesStates = TypeOf; - -export const cspSettingsSchema = schema.object({ - rules: rulesStates, -}); - -export type CspSettings = TypeOf; - -export interface BulkActionBenchmarkRulesResponse { - updatedBenchmarkRulesStates: CspBenchmarkRulesStates; - disabledDetectionRules: string[]; -} diff --git a/x-pack/test/cloud_security_posture_functional/pages/rules.ts b/x-pack/test/cloud_security_posture_functional/pages/rules.ts index ebae60da6a804..b64bc2b02fac4 100644 --- a/x-pack/test/cloud_security_posture_functional/pages/rules.ts +++ b/x-pack/test/cloud_security_posture_functional/pages/rules.ts @@ -12,7 +12,7 @@ import type { FtrProviderContext } from '../ftr_provider_context'; import { RULES_BULK_ACTION_OPTION_DISABLE, RULES_BULK_ACTION_OPTION_ENABLE, - RULE_NUMBER_FILTER_SEARCH_FIELD, + // RULE_NUMBER_FILTER_SEARCH_FIELD, } from '../page_objects/rule_page'; // eslint-disable-next-line import/no-default-export @@ -140,31 +140,31 @@ export default function ({ getPageObjects, getService }: FtrProviderContext) { }); }); - describe('Rules Page - CIS Section & Rule Number filters', () => { - it('Table should only show result that has the same section as in the Section filter', async () => { - await rule.rulePage.clickFilterPopover('section'); - await rule.rulePage.clickFilterPopOverOption('etcd'); - await rule.rulePage.clickFilterPopOverOption('Scheduler'); - expect((await rule.rulePage.getEnableRulesRowSwitchButton()) < 10).to.be(true); - }); - - it('Table should only show result that has the same section as in the Rule number filter', async () => { - await rule.rulePage.clickFilterPopover('ruleNumber'); - await rule.rulePage.clickFilterPopOverOption('1.1.1'); - await rule.rulePage.clickFilterPopOverOption('1.1.2'); - expect((await rule.rulePage.getEnableRulesRowSwitchButton()) === 2).to.be(true); - }); - - it('Table should only show result that passes both Section and Rule number filter', async () => { - await rule.rulePage.clickFilterPopover('section'); - await rule.rulePage.clickFilterPopOverOption('Control-Plane-Node-Configuration-Files'); - await rule.rulePage.clickFilterPopover('section'); - await rule.rulePage.clickFilterPopover('ruleNumber'); - await rule.rulePage.filterTextInput(RULE_NUMBER_FILTER_SEARCH_FIELD, '1.1.5'); - await pageObjects.header.waitUntilLoadingHasFinished(); - await rule.rulePage.clickFilterPopOverOption('1.1.5'); - expect((await rule.rulePage.getEnableRulesRowSwitchButton()) === 1).to.be(true); - }); - }); + // describe('Rules Page - CIS Section & Rule Number filters', () => { + // it('Table should only show result that has the same section as in the Section filter', async () => { + // await rule.rulePage.clickFilterPopover('section'); + // await rule.rulePage.clickFilterPopOverOption('etcd'); + // await rule.rulePage.clickFilterPopOverOption('Scheduler'); + // expect((await rule.rulePage.getEnableRulesRowSwitchButton()) < 10).to.be(true); + // }); + + // it('Table should only show result that has the same section as in the Rule number filter', async () => { + // await rule.rulePage.clickFilterPopover('ruleNumber'); + // await rule.rulePage.clickFilterPopOverOption('1.1.1'); + // await rule.rulePage.clickFilterPopOverOption('1.1.2'); + // expect((await rule.rulePage.getEnableRulesRowSwitchButton()) === 2).to.be(true); + // }); + + // it('Table should only show result that passes both Section and Rule number filter', async () => { + // await rule.rulePage.clickFilterPopover('section'); + // await rule.rulePage.clickFilterPopOverOption('Control-Plane-Node-Configuration-Files'); + // await rule.rulePage.clickFilterPopover('section'); + // await rule.rulePage.clickFilterPopover('ruleNumber'); + // await rule.rulePage.filterTextInput(RULE_NUMBER_FILTER_SEARCH_FIELD, '1.1.5'); + // await pageObjects.header.waitUntilLoadingHasFinished(); + // await rule.rulePage.clickFilterPopOverOption('1.1.5'); + // expect((await rule.rulePage.getEnableRulesRowSwitchButton()) === 1).to.be(true); + // }); + // }); }); } From fad18822ef0e48073bf9395812b506a296195046 Mon Sep 17 00:00:00 2001 From: animehart Date: Sun, 21 Jan 2024 17:59:55 -0800 Subject: [PATCH 08/13] update ftr --- .../pages/rules.ts | 58 +++++++++---------- 1 file changed, 29 insertions(+), 29 deletions(-) diff --git a/x-pack/test/cloud_security_posture_functional/pages/rules.ts b/x-pack/test/cloud_security_posture_functional/pages/rules.ts index b64bc2b02fac4..cbcd54dfd05f6 100644 --- a/x-pack/test/cloud_security_posture_functional/pages/rules.ts +++ b/x-pack/test/cloud_security_posture_functional/pages/rules.ts @@ -12,7 +12,7 @@ import type { FtrProviderContext } from '../ftr_provider_context'; import { RULES_BULK_ACTION_OPTION_DISABLE, RULES_BULK_ACTION_OPTION_ENABLE, - // RULE_NUMBER_FILTER_SEARCH_FIELD, + RULE_NUMBER_FILTER_SEARCH_FIELD, } from '../page_objects/rule_page'; // eslint-disable-next-line import/no-default-export @@ -28,8 +28,8 @@ export default function ({ getPageObjects, getService }: FtrProviderContext) { 'findings', ]); - describe('Cloud Posture Dashboard Page', function () { - this.tags(['cloud_security_posture_compliance_dashboard']); + describe('Cloud Posture Rules Page', function () { + this.tags(['cloud_security_posture_rules_page']); let rule: typeof pageObjects.rule; let agentPolicyId: string; @@ -140,31 +140,31 @@ export default function ({ getPageObjects, getService }: FtrProviderContext) { }); }); - // describe('Rules Page - CIS Section & Rule Number filters', () => { - // it('Table should only show result that has the same section as in the Section filter', async () => { - // await rule.rulePage.clickFilterPopover('section'); - // await rule.rulePage.clickFilterPopOverOption('etcd'); - // await rule.rulePage.clickFilterPopOverOption('Scheduler'); - // expect((await rule.rulePage.getEnableRulesRowSwitchButton()) < 10).to.be(true); - // }); - - // it('Table should only show result that has the same section as in the Rule number filter', async () => { - // await rule.rulePage.clickFilterPopover('ruleNumber'); - // await rule.rulePage.clickFilterPopOverOption('1.1.1'); - // await rule.rulePage.clickFilterPopOverOption('1.1.2'); - // expect((await rule.rulePage.getEnableRulesRowSwitchButton()) === 2).to.be(true); - // }); - - // it('Table should only show result that passes both Section and Rule number filter', async () => { - // await rule.rulePage.clickFilterPopover('section'); - // await rule.rulePage.clickFilterPopOverOption('Control-Plane-Node-Configuration-Files'); - // await rule.rulePage.clickFilterPopover('section'); - // await rule.rulePage.clickFilterPopover('ruleNumber'); - // await rule.rulePage.filterTextInput(RULE_NUMBER_FILTER_SEARCH_FIELD, '1.1.5'); - // await pageObjects.header.waitUntilLoadingHasFinished(); - // await rule.rulePage.clickFilterPopOverOption('1.1.5'); - // expect((await rule.rulePage.getEnableRulesRowSwitchButton()) === 1).to.be(true); - // }); - // }); + describe('Rules Page - CIS Section & Rule Number filters', () => { + it('Table should only show result that has the same section as in the Section filter', async () => { + await rule.rulePage.clickFilterPopover('section'); + await rule.rulePage.clickFilterPopOverOption('etcd'); + await rule.rulePage.clickFilterPopOverOption('Scheduler'); + expect((await rule.rulePage.getEnableRulesRowSwitchButton()) < 10).to.be(true); + }); + + it('Table should only show result that has the same section as in the Rule number filter', async () => { + await rule.rulePage.clickFilterPopover('ruleNumber'); + await rule.rulePage.clickFilterPopOverOption('1.1.1'); + await rule.rulePage.clickFilterPopOverOption('1.1.2'); + expect((await rule.rulePage.getEnableRulesRowSwitchButton()) === 2).to.be(true); + }); + + it('Table should only show result that passes both Section and Rule number filter', async () => { + await rule.rulePage.clickFilterPopover('section'); + await rule.rulePage.clickFilterPopOverOption('Control-Plane-Node-Configuration-Files'); + await rule.rulePage.clickFilterPopover('section'); + await rule.rulePage.clickFilterPopover('ruleNumber'); + await rule.rulePage.filterTextInput(RULE_NUMBER_FILTER_SEARCH_FIELD, '1.1.5'); + await pageObjects.header.waitUntilLoadingHasFinished(); + await rule.rulePage.clickFilterPopOverOption('1.1.5'); + expect((await rule.rulePage.getEnableRulesRowSwitchButton()) === 1).to.be(true); + }); + }); }); } From 7563a50be14352a3194a51a910ce3a6d69af9b7d Mon Sep 17 00:00:00 2001 From: animehart Date: Mon, 22 Jan 2024 15:04:57 -0800 Subject: [PATCH 09/13] update comment --- .../public/pages/rules/rules_container.tsx | 2 +- .../cloud_security_posture/public/pages/rules/rules_table.tsx | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/x-pack/plugins/cloud_security_posture/public/pages/rules/rules_container.tsx b/x-pack/plugins/cloud_security_posture/public/pages/rules/rules_container.tsx index d469632e21bdb..6e2b5b204f91e 100644 --- a/x-pack/plugins/cloud_security_posture/public/pages/rules/rules_container.tsx +++ b/x-pack/plugins/cloud_security_posture/public/pages/rules/rules_container.tsx @@ -116,7 +116,7 @@ export const RulesContainer = () => { const rulesKey = buildRuleKey( rule.metadata.benchmark.id, rule.metadata.benchmark.version, - /* Since Packages are automatically upgraded, we can be sure that rule_number will Always exist */ + /* Rule number always exists* from 8.7 */ rule.metadata.benchmark.rule_number! ); diff --git a/x-pack/plugins/cloud_security_posture/public/pages/rules/rules_table.tsx b/x-pack/plugins/cloud_security_posture/public/pages/rules/rules_table.tsx index 0dacb9b1a1a9a..87c731809db3c 100644 --- a/x-pack/plugins/cloud_security_posture/public/pages/rules/rules_table.tsx +++ b/x-pack/plugins/cloud_security_posture/public/pages/rules/rules_table.tsx @@ -261,7 +261,7 @@ const getColumns = ({ const rulesObjectRequest = { benchmark_id: rule?.metadata.benchmark.id, benchmark_version: rule?.metadata.benchmark.version, - /* Since Packages are automatically upgraded, we can be sure that rule_number will Always exist */ + /* Rule number always exists* from 8.7 */ rule_number: rule?.metadata.benchmark.rule_number!, rule_id: rule?.metadata.id, }; From 05284d18bce93789dbc090dab91b01301fcc7fa7 Mon Sep 17 00:00:00 2001 From: animehart Date: Wed, 24 Jan 2024 02:56:22 -0800 Subject: [PATCH 10/13] pr comments --- .../common/types/rules/v3.ts | 2 +- .../common/types/rules/v4.ts | 3 +- .../common/types/rules/v5.ts | 3 +- .../common/component/multi_select_filter.tsx | 1 + .../public/pages/rules/rules_container.tsx | 3 +- .../public/pages/rules/rules_table.tsx | 35 ++++++++++++------- .../public/pages/rules/rules_table_header.tsx | 4 +-- 7 files changed, 31 insertions(+), 20 deletions(-) diff --git a/x-pack/plugins/cloud_security_posture/common/types/rules/v3.ts b/x-pack/plugins/cloud_security_posture/common/types/rules/v3.ts index fb0310c088a19..475702250be94 100644 --- a/x-pack/plugins/cloud_security_posture/common/types/rules/v3.ts +++ b/x-pack/plugins/cloud_security_posture/common/types/rules/v3.ts @@ -8,7 +8,7 @@ import { schema, TypeOf } from '@kbn/config-schema'; import { CSPM_POLICY_TEMPLATE, KSPM_POLICY_TEMPLATE } from '../../constants'; -const DEFAULT_BENCHMARK_RULES_PER_PAGE = 25; +export const DEFAULT_BENCHMARK_RULES_PER_PAGE = 25; // Since version 8.7.0 export const cspBenchmarkRuleMetadataSchema = schema.object({ diff --git a/x-pack/plugins/cloud_security_posture/common/types/rules/v4.ts b/x-pack/plugins/cloud_security_posture/common/types/rules/v4.ts index d15827c7bef0a..9bb91de31cd41 100644 --- a/x-pack/plugins/cloud_security_posture/common/types/rules/v4.ts +++ b/x-pack/plugins/cloud_security_posture/common/types/rules/v4.ts @@ -7,6 +7,7 @@ import { schema, TypeOf } from '@kbn/config-schema'; import { BenchmarksCisId } from '../latest'; +import { DEFAULT_BENCHMARK_RULES_PER_PAGE } from './v3'; export type { cspBenchmarkRuleMetadataSchema, CspBenchmarkRuleMetadata, @@ -15,8 +16,6 @@ export type { FindCspBenchmarkRuleResponse, } from './v3'; -const DEFAULT_BENCHMARK_RULES_PER_PAGE = 25; - export const findCspBenchmarkRuleRequestSchema = schema.object({ /** * An Elasticsearch simple_query_string diff --git a/x-pack/plugins/cloud_security_posture/common/types/rules/v5.ts b/x-pack/plugins/cloud_security_posture/common/types/rules/v5.ts index 7726e8b03fc71..4c79b59d3c955 100644 --- a/x-pack/plugins/cloud_security_posture/common/types/rules/v5.ts +++ b/x-pack/plugins/cloud_security_posture/common/types/rules/v5.ts @@ -5,6 +5,7 @@ * 2.0. */ import { schema, TypeOf } from '@kbn/config-schema'; +import { DEFAULT_BENCHMARK_RULES_PER_PAGE } from './v3'; export type { cspBenchmarkRuleMetadataSchema, @@ -25,8 +26,6 @@ export type { BulkActionBenchmarkRulesResponse, } from './v4'; -const DEFAULT_BENCHMARK_RULES_PER_PAGE = 25; - export const findCspBenchmarkRuleRequestSchema = schema.object({ /** * An Elasticsearch simple_query_string diff --git a/x-pack/plugins/cloud_security_posture/public/common/component/multi_select_filter.tsx b/x-pack/plugins/cloud_security_posture/public/common/component/multi_select_filter.tsx index 272a9a918ec02..6eece8a31c5e8 100644 --- a/x-pack/plugins/cloud_security_posture/public/common/component/multi_select_filter.tsx +++ b/x-pack/plugins/cloud_security_posture/public/common/component/multi_select_filter.tsx @@ -5,6 +5,7 @@ * 2.0. */ +/* This code is based on MultiSelectFilter component from x-pack/plugins/cases/public/components/all_cases/multi_select_filter.tsx */ import React, { useState, useEffect } from 'react'; import { css } from '@emotion/react'; import type { EuiSelectableOption } from '@elastic/eui'; diff --git a/x-pack/plugins/cloud_security_posture/public/pages/rules/rules_container.tsx b/x-pack/plugins/cloud_security_posture/public/pages/rules/rules_container.tsx index 6e2b5b204f91e..1e47b26ce2e2c 100644 --- a/x-pack/plugins/cloud_security_posture/public/pages/rules/rules_container.tsx +++ b/x-pack/plugins/cloud_security_posture/public/pages/rules/rules_container.tsx @@ -5,6 +5,7 @@ * 2.0. */ import React, { useState, useMemo } from 'react'; +import compareVersions from 'compare-versions'; import { EuiPanel, EuiSpacer } from '@elastic/eui'; import { useParams } from 'react-router-dom'; import { buildRuleKey } from '../../../common/utils/rules_states'; @@ -146,7 +147,7 @@ export const RulesContainer = () => { const cleanedSectionList = [...new Set(sectionList)].sort((a, b) => { return a.localeCompare(b, 'en', { sensitivity: 'base' }); }); - const cleanedRuleNumberList = [...new Set(ruleNumberList)]; + const cleanedRuleNumberList = [...new Set(ruleNumberList)].sort(compareVersions); const rulesPageData = useMemo( () => getRulesPage(filteredRulesWithStates, status, error, rulesQuery), diff --git a/x-pack/plugins/cloud_security_posture/public/pages/rules/rules_table.tsx b/x-pack/plugins/cloud_security_posture/public/pages/rules/rules_table.tsx index 87c731809db3c..db9e766d0bd70 100644 --- a/x-pack/plugins/cloud_security_posture/public/pages/rules/rules_table.tsx +++ b/x-pack/plugins/cloud_security_posture/public/pages/rules/rules_table.tsx @@ -5,6 +5,7 @@ * 2.0. */ import React, { useEffect, useMemo, useState } from 'react'; +import { css } from '@emotion/react'; import { Criteria, EuiButtonEmpty, @@ -14,6 +15,8 @@ import { useEuiTheme, EuiSwitch, EuiCheckbox, + EuiFlexGroup, + EuiFlexItem, } from '@elastic/eui'; import { i18n } from '@kbn/i18n'; import { uniqBy } from 'lodash'; @@ -190,7 +193,7 @@ const getColumns = ({ }} /> ), - width: '3%', + width: '40px', sortable: false, render: (rules, item: CspBenchmarkRulesWithStates) => { return ( @@ -219,7 +222,7 @@ const getColumns = ({ name: i18n.translate('xpack.csp.rules.rulesTable.ruleNumberColumnLabel', { defaultMessage: 'Rule Number', }), - width: '10%', + width: '15%', sortable: true, }, { @@ -248,20 +251,20 @@ const getColumns = ({ name: i18n.translate('xpack.csp.rules.rulesTable.cisSectionColumnLabel', { defaultMessage: 'CIS Section', }), - width: '15%', + width: '24%', }, { field: 'metadata.name', name: i18n.translate('xpack.csp.rules.rulesTable.mutedColumnLabel', { defaultMessage: 'Enabled', }), - width: '5%', + width: '65px', truncateText: true, render: (name, rule: CspBenchmarkRulesWithStates) => { const rulesObjectRequest = { benchmark_id: rule?.metadata.benchmark.id, benchmark_version: rule?.metadata.benchmark.version, - /* Rule number always exists* from 8.7 */ + /* Rule number always exists from 8.7 */ rule_number: rule?.metadata.benchmark.rule_number!, rule_id: rule?.metadata.id, }; @@ -275,13 +278,21 @@ const getColumns = ({ } }; return ( - + + + + + ); }, }, diff --git a/x-pack/plugins/cloud_security_posture/public/pages/rules/rules_table_header.tsx b/x-pack/plugins/cloud_security_posture/public/pages/rules/rules_table_header.tsx index 7cda06e96ea43..366ae740c3e94 100644 --- a/x-pack/plugins/cloud_security_posture/public/pages/rules/rules_table_header.tsx +++ b/x-pack/plugins/cloud_security_posture/public/pages/rules/rules_table_header.tsx @@ -144,7 +144,7 @@ export const RulesTableHeader = ({ defaultMessage: 'CIS Section', } )} - id={'section'} + id={'cis-section-multi-select-filter'} onChange={(section) => { setSelectedSection([...section?.selectedOptionKeys]); onSectionChange( @@ -167,7 +167,7 @@ export const RulesTableHeader = ({ defaultMessage: 'Rule Number', } )} - id={'rule-number'} + id={'rule-number-multi-select-filter'} onChange={(ruleNumber) => { setSelectedRuleNumber([...ruleNumber?.selectedOptionKeys]); onRuleNumberChange( From a41599dff2d0708acbc57bd606ec6323f385f0ac Mon Sep 17 00:00:00 2001 From: animehart Date: Wed, 24 Jan 2024 03:07:46 -0800 Subject: [PATCH 11/13] pr comments --- .../common/types/rules/v1.ts | 5 ++-- .../common/types/rules/v2.ts | 5 ++-- .../common/types/rules/v3.ts | 17 +++++------ .../common/types/rules/v4.ts | 28 +++++++++---------- .../common/types/rules/v5.ts | 4 +-- .../public/pages/rules/rules_container.tsx | 2 +- 6 files changed, 32 insertions(+), 29 deletions(-) diff --git a/x-pack/plugins/cloud_security_posture/common/types/rules/v1.ts b/x-pack/plugins/cloud_security_posture/common/types/rules/v1.ts index afc2b705ab5c3..068c7244ef4f6 100644 --- a/x-pack/plugins/cloud_security_posture/common/types/rules/v1.ts +++ b/x-pack/plugins/cloud_security_posture/common/types/rules/v1.ts @@ -7,6 +7,9 @@ import { schema, TypeOf } from '@kbn/config-schema'; // Since version 8.3.0 + +export type CspBenchmarkRule = TypeOf; + export const cspBenchmarkRuleSchema = schema.object({ audit: schema.string(), benchmark: schema.object({ name: schema.string(), version: schema.string() }), @@ -26,5 +29,3 @@ export const cspBenchmarkRuleSchema = schema.object({ tags: schema.arrayOf(schema.string()), version: schema.string(), }); - -export type CspBenchmarkRule = TypeOf; diff --git a/x-pack/plugins/cloud_security_posture/common/types/rules/v2.ts b/x-pack/plugins/cloud_security_posture/common/types/rules/v2.ts index d88ae6adc089a..729de7736b0c0 100644 --- a/x-pack/plugins/cloud_security_posture/common/types/rules/v2.ts +++ b/x-pack/plugins/cloud_security_posture/common/types/rules/v2.ts @@ -7,6 +7,9 @@ import { schema, TypeOf } from '@kbn/config-schema'; // Since version 8.4.0 + +export type CspBenchmarkRule = TypeOf; + export const cspBenchmarkRuleMetadataSchema = schema.object({ audit: schema.string(), benchmark: schema.object({ @@ -34,5 +37,3 @@ export const cspBenchmarkRuleSchema = schema.object({ metadata: cspBenchmarkRuleMetadataSchema, muted: schema.boolean(), }); - -export type CspBenchmarkRule = TypeOf; diff --git a/x-pack/plugins/cloud_security_posture/common/types/rules/v3.ts b/x-pack/plugins/cloud_security_posture/common/types/rules/v3.ts index 475702250be94..85c38c50022b8 100644 --- a/x-pack/plugins/cloud_security_posture/common/types/rules/v3.ts +++ b/x-pack/plugins/cloud_security_posture/common/types/rules/v3.ts @@ -11,6 +11,15 @@ import { CSPM_POLICY_TEMPLATE, KSPM_POLICY_TEMPLATE } from '../../constants'; export const DEFAULT_BENCHMARK_RULES_PER_PAGE = 25; // Since version 8.7.0 + +export type FindCspBenchmarkRuleRequest = TypeOf; + +export type CspBenchmarkRuleMetadata = TypeOf; + +export type CspBenchmarkRule = TypeOf; + +export type PageUrlParams = Record<'policyId' | 'packagePolicyId', string>; + export const cspBenchmarkRuleMetadataSchema = schema.object({ audit: schema.string(), benchmark: schema.object({ @@ -37,14 +46,10 @@ export const cspBenchmarkRuleMetadataSchema = schema.object({ version: schema.string(), }); -export type CspBenchmarkRuleMetadata = TypeOf; - export const cspBenchmarkRuleSchema = schema.object({ metadata: cspBenchmarkRuleMetadataSchema, }); -export type CspBenchmarkRule = TypeOf; - export const findCspBenchmarkRuleRequestSchema = schema.object({ /** * An Elasticsearch simple_query_string @@ -125,13 +130,9 @@ export const findCspBenchmarkRuleRequestSchema = schema.object({ section: schema.maybe(schema.string()), }); -export type FindCspBenchmarkRuleRequest = TypeOf; - export interface FindCspBenchmarkRuleResponse { items: CspBenchmarkRule[]; total: number; page: number; perPage: number; } - -export type PageUrlParams = Record<'policyId' | 'packagePolicyId', string>; diff --git a/x-pack/plugins/cloud_security_posture/common/types/rules/v4.ts b/x-pack/plugins/cloud_security_posture/common/types/rules/v4.ts index 9bb91de31cd41..375543aef4d61 100644 --- a/x-pack/plugins/cloud_security_posture/common/types/rules/v4.ts +++ b/x-pack/plugins/cloud_security_posture/common/types/rules/v4.ts @@ -16,6 +16,20 @@ export type { FindCspBenchmarkRuleResponse, } from './v3'; +export type FindCspBenchmarkRuleRequest = TypeOf; + +export type RulesToUpdate = TypeOf; + +export type CspBenchmarkRulesBulkActionRequestSchema = TypeOf< + typeof cspBenchmarkRulesBulkActionRequestSchema +>; + +export type RuleStateAttributes = TypeOf; + +export type CspBenchmarkRulesStates = TypeOf; + +export type CspSettings = TypeOf; + export const findCspBenchmarkRuleRequestSchema = schema.object({ /** * An Elasticsearch simple_query_string @@ -98,8 +112,6 @@ export const findCspBenchmarkRuleRequestSchema = schema.object({ ruleNumber: schema.maybe(schema.arrayOf(schema.string())), }); -export type FindCspBenchmarkRuleRequest = TypeOf; - export interface BenchmarkRuleSelectParams { section?: string; ruleNumber?: string; @@ -124,12 +136,6 @@ export const cspBenchmarkRulesBulkActionRequestSchema = schema.object({ rules: rulesToUpdate, }); -export type RulesToUpdate = TypeOf; - -export type CspBenchmarkRulesBulkActionRequestSchema = TypeOf< - typeof cspBenchmarkRulesBulkActionRequestSchema ->; - export interface CspBenchmarkRulesBulkActionResponse { updated_benchmark_rules: CspBenchmarkRulesStates; disabled_detection_rules?: string[]; @@ -144,18 +150,12 @@ const ruleStateAttributes = schema.object({ rule_id: schema.string(), }); -export type RuleStateAttributes = TypeOf; - const rulesStates = schema.recordOf(schema.string(), ruleStateAttributes); -export type CspBenchmarkRulesStates = TypeOf; - export const cspSettingsSchema = schema.object({ rules: rulesStates, }); -export type CspSettings = TypeOf; - export interface BulkActionBenchmarkRulesResponse { updatedBenchmarkRulesStates: CspBenchmarkRulesStates; disabledDetectionRules: string[]; diff --git a/x-pack/plugins/cloud_security_posture/common/types/rules/v5.ts b/x-pack/plugins/cloud_security_posture/common/types/rules/v5.ts index 4c79b59d3c955..6f30ed446531a 100644 --- a/x-pack/plugins/cloud_security_posture/common/types/rules/v5.ts +++ b/x-pack/plugins/cloud_security_posture/common/types/rules/v5.ts @@ -26,6 +26,8 @@ export type { BulkActionBenchmarkRulesResponse, } from './v4'; +export type FindCspBenchmarkRuleRequest = TypeOf; + export const findCspBenchmarkRuleRequestSchema = schema.object({ /** * An Elasticsearch simple_query_string @@ -112,8 +114,6 @@ export const findCspBenchmarkRuleRequestSchema = schema.object({ ), }); -export type FindCspBenchmarkRuleRequest = TypeOf; - export interface BenchmarkRuleSelectParams { section?: string[]; ruleNumber?: string[]; diff --git a/x-pack/plugins/cloud_security_posture/public/pages/rules/rules_container.tsx b/x-pack/plugins/cloud_security_posture/public/pages/rules/rules_container.tsx index af8b9f38884fa..e0017fa7a54e1 100644 --- a/x-pack/plugins/cloud_security_posture/public/pages/rules/rules_container.tsx +++ b/x-pack/plugins/cloud_security_posture/public/pages/rules/rules_container.tsx @@ -6,7 +6,7 @@ */ import React, { useState, useMemo } from 'react'; import compareVersions from 'compare-versions'; -import { EuiPanel, EuiSpacer } from '@elastic/eui'; +import { EuiSpacer } from '@elastic/eui'; import { useParams } from 'react-router-dom'; import { buildRuleKey } from '../../../common/utils/rules_states'; import { extractErrorMessage } from '../../../common/utils/helpers'; From 5aaeffe680d53227bb632005f73ae49c253f571e Mon Sep 17 00:00:00 2001 From: animehart Date: Wed, 24 Jan 2024 04:23:11 -0800 Subject: [PATCH 12/13] ui update --- .../public/pages/rules/rules_table.tsx | 9 +++------ 1 file changed, 3 insertions(+), 6 deletions(-) diff --git a/x-pack/plugins/cloud_security_posture/public/pages/rules/rules_table.tsx b/x-pack/plugins/cloud_security_posture/public/pages/rules/rules_table.tsx index db9e766d0bd70..b792ab851fa74 100644 --- a/x-pack/plugins/cloud_security_posture/public/pages/rules/rules_table.tsx +++ b/x-pack/plugins/cloud_security_posture/public/pages/rules/rules_table.tsx @@ -5,7 +5,6 @@ * 2.0. */ import React, { useEffect, useMemo, useState } from 'react'; -import { css } from '@emotion/react'; import { Criteria, EuiButtonEmpty, @@ -222,7 +221,7 @@ const getColumns = ({ name: i18n.translate('xpack.csp.rules.rulesTable.ruleNumberColumnLabel', { defaultMessage: 'Rule Number', }), - width: '15%', + width: '100px', sortable: true, }, { @@ -258,7 +257,8 @@ const getColumns = ({ name: i18n.translate('xpack.csp.rules.rulesTable.mutedColumnLabel', { defaultMessage: 'Enabled', }), - width: '65px', + align: 'right', + width: '100px', truncateText: true, render: (name, rule: CspBenchmarkRulesWithStates) => { const rulesObjectRequest = { @@ -287,9 +287,6 @@ const getColumns = ({ data-test-subj={RULES_ROWS_ENABLE_SWITCH_BUTTON} label="" compressed={true} - css={css` - padding-left: 18px; - `} /> From 28fa96df490b8547f668979a330d94651c4de95f Mon Sep 17 00:00:00 2001 From: animehart Date: Wed, 24 Jan 2024 07:49:09 -0800 Subject: [PATCH 13/13] pr comments and ftr fix --- .../common/types/rules/v4.ts | 4 ++-- .../common/utils/helpers.ts | 15 ++++++++------- .../server/routes/benchmark_rules/find/v2.ts | 4 ++-- .../page_objects/rule_page.ts | 4 ++-- .../pages/rules.ts | 3 --- 5 files changed, 14 insertions(+), 16 deletions(-) diff --git a/x-pack/plugins/cloud_security_posture/common/types/rules/v4.ts b/x-pack/plugins/cloud_security_posture/common/types/rules/v4.ts index 375543aef4d61..78680bf111dc7 100644 --- a/x-pack/plugins/cloud_security_posture/common/types/rules/v4.ts +++ b/x-pack/plugins/cloud_security_posture/common/types/rules/v4.ts @@ -108,8 +108,8 @@ export const findCspBenchmarkRuleRequestSchema = schema.object({ /** * rule section */ - section: schema.maybe(schema.arrayOf(schema.string())), - ruleNumber: schema.maybe(schema.arrayOf(schema.string())), + section: schema.maybe(schema.string()), + ruleNumber: schema.maybe(schema.string()), }); export interface BenchmarkRuleSelectParams { diff --git a/x-pack/plugins/cloud_security_posture/common/utils/helpers.ts b/x-pack/plugins/cloud_security_posture/common/utils/helpers.ts index ea1879f4c073d..0dc376b19f179 100644 --- a/x-pack/plugins/cloud_security_posture/common/utils/helpers.ts +++ b/x-pack/plugins/cloud_security_posture/common/utils/helpers.ts @@ -32,6 +32,7 @@ import type { RuleSection, } from '../types_old'; import type { BenchmarkRuleSelectParams, BenchmarksCisId } from '../types/latest'; +import type { BenchmarkRuleSelectParams as BenchmarkRuleSelectParamsV1 } from '../types/rules/v4'; /** * @example @@ -205,11 +206,11 @@ export const getBenchmarkApplicableTo = (benchmarkId: BenchmarksCisId) => { }; export const getBenchmarkFilterQuery = ( - id: BenchmarkId, - version?: string, - selectParams?: BenchmarkRuleSelectParams + benchmarkId: BenchmarkId, + benchmarkVersion?: string, + selectParams?: BenchmarkRuleSelectParamsV1 ): string => { - const baseQuery = `${CSP_BENCHMARK_RULE_SAVED_OBJECT_TYPE}.attributes.metadata.benchmark.id:${id} AND ${CSP_BENCHMARK_RULE_SAVED_OBJECT_TYPE}.attributes.metadata.benchmark.version:"v${version}"`; + const baseQuery = `${CSP_BENCHMARK_RULE_SAVED_OBJECT_TYPE}.attributes.metadata.benchmark.id:${benchmarkId} AND ${CSP_BENCHMARK_RULE_SAVED_OBJECT_TYPE}.attributes.metadata.benchmark.version:"v${benchmarkVersion}"`; const sectionQuery = selectParams?.section ? ` AND ${CSP_BENCHMARK_RULE_SAVED_OBJECT_TYPE}.attributes.metadata.section: "${selectParams.section}"` : ''; @@ -220,11 +221,11 @@ export const getBenchmarkFilterQuery = ( }; export const getBenchmarkFilterQueryV2 = ( - id: BenchmarkId, - version?: string, + benchmarkId: BenchmarkId, + benchmarkVersion?: string, selectParams?: BenchmarkRuleSelectParams ): string => { - const baseQuery = `${CSP_BENCHMARK_RULE_SAVED_OBJECT_TYPE}.attributes.metadata.benchmark.id:${id} AND ${CSP_BENCHMARK_RULE_SAVED_OBJECT_TYPE}.attributes.metadata.benchmark.version:"v${version}"`; + const baseQuery = `${CSP_BENCHMARK_RULE_SAVED_OBJECT_TYPE}.attributes.metadata.benchmark.id:${benchmarkId} AND ${CSP_BENCHMARK_RULE_SAVED_OBJECT_TYPE}.attributes.metadata.benchmark.version:"v${benchmarkVersion}"`; let sectionQuery = ''; let ruleNumberQuery = ''; diff --git a/x-pack/plugins/cloud_security_posture/server/routes/benchmark_rules/find/v2.ts b/x-pack/plugins/cloud_security_posture/server/routes/benchmark_rules/find/v2.ts index 324293487747b..5054fc211a529 100644 --- a/x-pack/plugins/cloud_security_posture/server/routes/benchmark_rules/find/v2.ts +++ b/x-pack/plugins/cloud_security_posture/server/routes/benchmark_rules/find/v2.ts @@ -11,9 +11,9 @@ import { CSP_BENCHMARK_RULE_SAVED_OBJECT_TYPE } from '../../../../common/constan import type { CspBenchmarkRule, + FindCspBenchmarkRuleRequest, FindCspBenchmarkRuleResponse, -} from '../../../../common/types/latest'; -import type { FindCspBenchmarkRuleRequest } from '../../../../common/types/rules/v4'; +} from '../../../../common/types/rules/v4'; import { getSortedCspBenchmarkRulesTemplates } from './utils'; export const findBenchmarkRuleHandler = async ( diff --git a/x-pack/test/cloud_security_posture_functional/page_objects/rule_page.ts b/x-pack/test/cloud_security_posture_functional/page_objects/rule_page.ts index 0ba6b032f6de4..7547bf67be8ab 100644 --- a/x-pack/test/cloud_security_posture_functional/page_objects/rule_page.ts +++ b/x-pack/test/cloud_security_posture_functional/page_objects/rule_page.ts @@ -20,8 +20,8 @@ export const RULES_CLEAR_ALL_RULES_SELECTION = 'clear-rules-selection-button'; export const RULES_ROWS_ENABLE_SWITCH_BUTTON = 'rules-row-enable-switch-button'; export const RULES_DISABLED_FILTER = 'rules-disabled-filter'; export const RULES_ENABLED_FILTER = 'rules-enabled-filter'; -export const CIS_SECTION_FILTER = 'options-filter-popover-button-section'; -export const RULE_NUMBER_FILTER = 'options-filter-popover-button-rule-number'; +export const CIS_SECTION_FILTER = 'options-filter-popover-button-cis-section-multi-select-filter'; +export const RULE_NUMBER_FILTER = 'options-filter-popover-button-rule-number-multi-select-filter'; export const RULE_NUMBER_FILTER_SEARCH_FIELD = 'rule-number-search-input'; export function RulePagePageProvider({ getService, getPageObjects }: FtrProviderContext) { diff --git a/x-pack/test/cloud_security_posture_functional/pages/rules.ts b/x-pack/test/cloud_security_posture_functional/pages/rules.ts index cbcd54dfd05f6..4841c9ad6b8b3 100644 --- a/x-pack/test/cloud_security_posture_functional/pages/rules.ts +++ b/x-pack/test/cloud_security_posture_functional/pages/rules.ts @@ -12,7 +12,6 @@ import type { FtrProviderContext } from '../ftr_provider_context'; import { RULES_BULK_ACTION_OPTION_DISABLE, RULES_BULK_ACTION_OPTION_ENABLE, - RULE_NUMBER_FILTER_SEARCH_FIELD, } from '../page_objects/rule_page'; // eslint-disable-next-line import/no-default-export @@ -160,8 +159,6 @@ export default function ({ getPageObjects, getService }: FtrProviderContext) { await rule.rulePage.clickFilterPopOverOption('Control-Plane-Node-Configuration-Files'); await rule.rulePage.clickFilterPopover('section'); await rule.rulePage.clickFilterPopover('ruleNumber'); - await rule.rulePage.filterTextInput(RULE_NUMBER_FILTER_SEARCH_FIELD, '1.1.5'); - await pageObjects.header.waitUntilLoadingHasFinished(); await rule.rulePage.clickFilterPopOverOption('1.1.5'); expect((await rule.rulePage.getEnableRulesRowSwitchButton()) === 1).to.be(true); });