Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[Cloud Security Posture][Benchmark Improvements] Rules Page Improvements phase 1 + Empty Benchmark #173345

Merged
merged 18 commits into from
Dec 26, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions x-pack/plugins/cloud_security_posture/common/types/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,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 benchmarkV1 from './benchmarks/v1';
export * as benchmarkV2 from './benchmarks/v2';
Expand All @@ -22,4 +23,5 @@ export type {
BenchmarkScore,
Benchmark,
GetBenchmarkResponse,
BenchmarkRuleSelectParams,
} from './latest';
Original file line number Diff line number Diff line change
Expand Up @@ -5,5 +5,5 @@
* 2.0.
*/

export * from './rules/v3';
export * from './rules/v4';
export * from './benchmarks/v2';
11 changes: 9 additions & 2 deletions x-pack/plugins/cloud_security_posture/common/types/rules/v3.ts
Original file line number Diff line number Diff line change
Expand Up @@ -107,9 +107,14 @@ export const findCspBenchmarkRuleRequestSchema = schema.object({
* benchmark id
*/
benchmarkId: schema.maybe(
schema.oneOf([schema.literal('cis_k8s'), schema.literal('cis_eks'), schema.literal('cis_aws')])
schema.oneOf([
schema.literal('cis_k8s'),
schema.literal('cis_eks'),
schema.literal('cis_aws'),
schema.literal('cis_azure'),
schema.literal('cis_gcp'),
])
),

/**
* package_policy_id
*/
Expand All @@ -130,6 +135,8 @@ export interface FindCspBenchmarkRuleResponse {
perPage: number;
}

export type PageUrlParams = Record<'policyId' | 'packagePolicyId', string>;

export const cspBenchmarkRules = schema.arrayOf(
schema.object({
rule_id: schema.string(),
Expand Down
115 changes: 115 additions & 0 deletions x-pack/plugins/cloud_security_posture/common/types/rules/v4.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,115 @@
/*
* 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,
CspSettings,
CspBenchmarkRulesStates,
} from './v3';

const DEFAULT_BENCHMARK_RULES_PER_PAGE = 25;

export const findCspBenchmarkRuleRequestSchema = schema.object({
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@animehart was there any breaking change to the interfaces so we needed to introduce a new version?

/**
* 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.string()),
ruleNumber: schema.maybe(schema.string()),
});

export type FindCspBenchmarkRuleRequest = TypeOf<typeof findCspBenchmarkRuleRequestSchema>;

export interface BenchmarkRuleSelectParams {
section?: string;
ruleNumber?: string;
}

export interface PageUrlParams {
benchmarkId: BenchmarksCisId;
benchmarkVersion: string;
}
19 changes: 16 additions & 3 deletions x-pack/plugins/cloud_security_posture/common/utils/helpers.ts
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,7 @@ import type {
AzureCredentialsType,
RuleSection,
} from '../types_old';
import type { BenchmarksCisId } from '../types/latest';
import type { BenchmarkRuleSelectParams, BenchmarksCisId } from '../types/latest';

/**
* @example
Expand Down Expand Up @@ -187,7 +187,6 @@ export const getBenchmarkCisName = (benchmarkId: BenchmarksCisId) => {
case 'cis_gcp':
return 'CIS GCP';
}
return null;
};

export const getBenchmarkApplicableTo = (benchmarkId: BenchmarksCisId) => {
Expand All @@ -203,5 +202,19 @@ export const getBenchmarkApplicableTo = (benchmarkId: BenchmarksCisId) => {
case 'cis_gcp':
return 'Google Cloud Provider';
}
return null;
};

export const getBenchmarkFilterQuery = (
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}"`;
const sectionQuery = selectParams?.section
? ` AND ${CSP_BENCHMARK_RULE_SAVED_OBJECT_TYPE}.attributes.metadata.section: "${selectParams.section}"`
: '';
const ruleNumberQuery = selectParams?.ruleNumber
? ` AND ${CSP_BENCHMARK_RULE_SAVED_OBJECT_TYPE}.attributes.metadata.benchmark.rule_number: "${selectParams.ruleNumber}"`
: '';
return baseQuery + sectionQuery + ruleNumberQuery;
};
Original file line number Diff line number Diff line change
Expand Up @@ -63,7 +63,7 @@ export const cloudPosturePages: Record<CspPage, CspPageNavigationItem> = {
export const benchmarksNavigation: Record<CspBenchmarksPage, CspPageNavigationItem> = {
rules: {
name: NAV_ITEMS_NAMES.RULES,
path: `${CLOUD_SECURITY_POSTURE_BASE_PATH}/benchmarks/:packagePolicyId/:policyId/rules`,
path: `${CLOUD_SECURITY_POSTURE_BASE_PATH}/benchmarks/:benchmarkId/:benchmarkVersion/rules`,
id: 'cloud_security_posture-benchmarks-rules',
},
};
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -31,10 +31,10 @@ import {
import { CloudPosturePage, PACKAGE_NOT_INSTALLED_TEST_SUBJECT } from './cloud_posture_page';
import { useCspSetupStatusApi } from '../common/api/use_setup_status_api';
import type { IndexDetails, PostureTypes } from '../../common/types_old';
import { cspIntegrationDocsNavigation } from '../common/navigation/constants';
import noDataIllustration from '../assets/illustrations/no_data_illustration.svg';
import { useCspIntegrationLink } from '../common/navigation/use_csp_integration_link';
import { NO_FINDINGS_STATUS_REFRESH_INTERVAL_MS } from '../common/constants';
import { cspIntegrationDocsNavigation } from '../common/navigation/constants';

const NotDeployed = ({ postureType }: { postureType: PostureTypes }) => {
const integrationPoliciesLink = useCISIntegrationPoliciesLink({
Expand Down Expand Up @@ -246,14 +246,14 @@ const ConfigurationFindingsInstalledEmptyPrompt = ({
* This component will return the render states based on cloud posture setup status API
* since 'not-installed' is being checked globally by CloudPosturePage and 'indexed' is the pass condition, those states won't be handled here
* */
export const NoFindingsStates = ({ posturetype }: { posturetype: PostureTypes }) => {
export const NoFindingsStates = ({ postureType }: { postureType: PostureTypes }) => {
const getSetupStatus = useCspSetupStatusApi({
refetchInterval: NO_FINDINGS_STATUS_REFRESH_INTERVAL_MS,
});
const statusKspm = getSetupStatus.data?.kspm?.status;
const statusCspm = getSetupStatus.data?.cspm?.status;
const indicesStatus = getSetupStatus.data?.indicesDetails;
const status = posturetype === 'cspm' ? statusCspm : statusKspm;
const status = postureType === 'cspm' ? statusCspm : statusKspm;
const showConfigurationInstallPrompt =
getSetupStatus.data?.kspm?.status === 'not-installed' &&
getSetupStatus.data?.cspm?.status === 'not-installed';
Expand All @@ -267,7 +267,7 @@ export const NoFindingsStates = ({ posturetype }: { posturetype: PostureTypes })
.map((idxDetails: IndexDetails) => idxDetails.index)
.sort((a, b) => a.localeCompare(b));
const render = () => {
if (status === 'not-deployed') return <NotDeployed postureType={posturetype} />; // integration installed, but no agents added
if (status === 'not-deployed') return <NotDeployed postureType={postureType} />; // integration installed, but no agents added
if (status === 'indexing' || status === 'waiting_for_results') return <Indexing />; // agent added, index timeout hasn't passed since installation
if (status === 'index-timeout') return <IndexTimeout />; // agent added, index timeout has passed
if (status === 'unprivileged')
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -31,9 +31,14 @@ import {
} from './use_csp_benchmark_integrations';
import { extractErrorMessage, getBenchmarkCisName } from '../../../common/utils/helpers';
import * as TEST_SUBJ from './test_subjects';
import { LOCAL_STORAGE_PAGE_SIZE_BENCHMARK_KEY } from '../../common/constants';
import {
LOCAL_STORAGE_PAGE_SIZE_BENCHMARK_KEY,
NO_FINDINGS_STATUS_REFRESH_INTERVAL_MS,
} from '../../common/constants';
import { usePageSize } from '../../common/hooks/use_page_size';
import { useKibana } from '../../common/hooks/use_kibana';
import { useCspSetupStatusApi } from '../../common/api/use_setup_status_api';
import { NoFindingsStates } from '../../components/no_findings_states';

const SEARCH_DEBOUNCE_MS = 300;

Expand Down Expand Up @@ -151,6 +156,14 @@ export const Benchmarks = () => {
[];
const totalItemCount = queryResult.data?.items.length || 0;

// Check if we have any CSP Integration or not
const getSetupStatus = useCspSetupStatusApi({
refetchInterval: NO_FINDINGS_STATUS_REFRESH_INTERVAL_MS,
});
const showConfigurationInstallPrompt =
getSetupStatus.data?.kspm?.status === 'not-installed' &&
getSetupStatus.data?.cspm?.status === 'not-installed';

return (
<CloudPosturePage>
<EuiPageHeader
Expand All @@ -166,44 +179,51 @@ export const Benchmarks = () => {
bottomBorder
/>
<EuiSpacer />
<BenchmarkSearchField
isLoading={queryResult.isFetching}
onSearch={(name) => setQuery((current) => ({ ...current, name }))}
/>
<EuiSpacer />
<TotalIntegrationsCount
pageCount={(queryResult.data?.items || []).length}
totalCount={totalItemCount}
/>
<EuiSpacer size="s" />
<BenchmarksTable
benchmarks={benchmarkResult}
data-test-subj={TEST_SUBJ.BENCHMARKS_TABLE_DATA_TEST_SUBJ}
error={queryResult.error ? extractErrorMessage(queryResult.error) : undefined}
loading={queryResult.isFetching}
pageIndex={query.page}
pageSize={pageSize || query.perPage}
sorting={{
// @ts-expect-error - EUI types currently do not support sorting by nested fields
sort: { field: query.sortField, direction: query.sortOrder },
allowNeutralSort: false,
}}
totalItemCount={totalItemCount}
setQuery={({ page, sort }) => {
setPageSize(page.size);
setQuery((current) => ({
...current,
page: page.index,
perPage: page.size,
sortField:
(sort?.field as UseCspBenchmarkIntegrationsProps['sortField']) || current.sortField,
sortOrder: sort?.direction || current.sortOrder,
}));
}}
noItemsMessage={
queryResult.isSuccess ? <BenchmarkEmptyState name={query.name} /> : undefined
}
/>
{showConfigurationInstallPrompt ? (
<NoFindingsStates postureType={'all'} />
) : (
<>
<BenchmarkSearchField
isLoading={queryResult.isFetching}
onSearch={(name) => setQuery((current) => ({ ...current, name }))}
/>
<EuiSpacer />
<TotalIntegrationsCount
pageCount={(queryResult.data?.items || []).length}
totalCount={totalItemCount}
/>
<EuiSpacer size="s" />
<BenchmarksTable
benchmarks={benchmarkResult}
data-test-subj={TEST_SUBJ.BENCHMARKS_TABLE_DATA_TEST_SUBJ}
error={queryResult.error ? extractErrorMessage(queryResult.error) : undefined}
loading={queryResult.isFetching}
pageIndex={query.page}
pageSize={pageSize || query.perPage}
sorting={{
// @ts-expect-error - EUI types currently do not support sorting by nested fields
sort: { field: query.sortField, direction: query.sortOrder },
allowNeutralSort: false,
}}
totalItemCount={totalItemCount}
setQuery={({ page, sort }) => {
setPageSize(page.size);
setQuery((current) => ({
...current,
page: page.index,
perPage: page.size,
sortField:
(sort?.field as UseCspBenchmarkIntegrationsProps['sortField']) ||
current.sortField,
sortOrder: sort?.direction || current.sortOrder,
}));
}}
noItemsMessage={
queryResult.isSuccess ? <BenchmarkEmptyState name={query.name} /> : undefined
}
/>
</>
)}
</CloudPosturePage>
);
};
Loading
Loading