Skip to content

Commit

Permalink
[Cloud Security] Disabled rules counter and query invalidations (elas…
Browse files Browse the repository at this point in the history
  • Loading branch information
JordanSh authored and fkanout committed Mar 4, 2024
1 parent 4813201 commit e2afa52
Show file tree
Hide file tree
Showing 16 changed files with 930 additions and 50 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -15,8 +15,8 @@ import {
} from '../../../common/constants';

// TODO: consolidate both hooks into one hook with a dynamic key
const getCspmStatsKey = ['csp_cspm_dashboard_stats'];
const getKspmStatsKey = ['csp_kspm_dashboard_stats'];
export const getCspmStatsKey = ['csp_cspm_dashboard_stats'];
export const getKspmStatsKey = ['csp_kspm_dashboard_stats'];

export const getStatsRoute = (policyTemplate: PosturePolicyTemplate) => {
return STATS_ROUTE_PATH.replace('{policy_template}', policyTemplate);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -12,8 +12,7 @@ import { useKibana } from '../../common/hooks/use_kibana';
import type { GetBenchmarkResponse } from '../../../common/types/latest';
import type { GetBenchmarkResponse as GetBenchmarkResponseV1 } from '../../../common/types/benchmarks/v1';

const QUERY_KEY_V1 = 'csp_benchmark_integrations_v1';
const QUERY_KEY_V2 = 'csp_benchmark_integrations_v2';
const BENCHMARK_INTEGRATION_QUERY_KEY_V1 = 'csp_benchmark_integrations_v1';

export interface UseCspBenchmarkIntegrationsProps {
name: string;
Expand All @@ -40,7 +39,7 @@ export const useCspBenchmarkIntegrationsV1 = ({
};

return useQuery(
[QUERY_KEY_V1, query],
[BENCHMARK_INTEGRATION_QUERY_KEY_V1, query],
() =>
http.get<GetBenchmarkResponseV1>(BENCHMARKS_ROUTE_PATH, {
query,
Expand All @@ -50,11 +49,13 @@ export const useCspBenchmarkIntegrationsV1 = ({
);
};

export const BENCHMARK_INTEGRATION_QUERY_KEY_V2 = ['csp_benchmark_integrations_v2'];

export const useCspBenchmarkIntegrationsV2 = () => {
const { http } = useKibana().services;

return useQuery(
[QUERY_KEY_V2],
BENCHMARK_INTEGRATION_QUERY_KEY_V2,
() =>
http.get<GetBenchmarkResponse>(BENCHMARKS_ROUTE_PATH, {
version: '2',
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -163,7 +163,8 @@ const PercentageLabels = ({
);
};

const getPostureScorePercentage = (postureScore: number): string => `${Math.round(postureScore)}%`;
export const getPostureScorePercentage = (postureScore: number): string =>
`${Math.round(postureScore)}%`;

const PercentageInfo = ({
compact,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -13,15 +13,13 @@ import {
} from '../../../../common/constants';
import { useKibana } from '../../../common/hooks/use_kibana';

const getRuleStatesKey = 'get_rules_state_key';
export const getRuleStatesKey = ['get_rules_state_key'];

export const useGetCspBenchmarkRulesStatesApi = () => {
const { http } = useKibana().services;
return useQuery<CspBenchmarkRulesStates, unknown, CspBenchmarkRulesStates>(
[getRuleStatesKey],
() =>
http.get<CspBenchmarkRulesStates>(CSP_GET_BENCHMARK_RULES_STATE_ROUTE_PATH, {
version: CSP_GET_BENCHMARK_RULES_STATE_API_CURRENT_VERSION,
})
return useQuery<CspBenchmarkRulesStates, unknown, CspBenchmarkRulesStates>(getRuleStatesKey, () =>
http.get<CspBenchmarkRulesStates>(CSP_GET_BENCHMARK_RULES_STATE_ROUTE_PATH, {
version: CSP_GET_BENCHMARK_RULES_STATE_API_CURRENT_VERSION,
})
);
};
Original file line number Diff line number Diff line change
Expand Up @@ -133,7 +133,7 @@ export const useLatestFindings = (options: UseFindingsOptions) => {
* the last loaded record to be used as a from parameter to fetch the next chunk of data.
*/
return useInfiniteQuery(
['csp_findings', { params: options }],
['csp_findings', { params: options }, rulesStates],
async ({ pageParam }) => {
const {
rawResponse: { hits, aggregations },
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,10 @@
*/

import { useKibana } from '@kbn/kibana-react-plugin/public';
import { useQueryClient } from '@tanstack/react-query';
import { getRuleStatesKey } from '../configurations/latest_findings/use_get_benchmark_rules_state_api';
import { getCspmStatsKey, getKspmStatsKey } from '../../common/api';
import { BENCHMARK_INTEGRATION_QUERY_KEY_V2 } from '../benchmarks/use_csp_benchmark_integrations';
import {
CspBenchmarkRulesBulkActionRequestSchema,
RuleStateAttributes,
Expand All @@ -15,18 +19,26 @@ import { CSP_BENCHMARK_RULES_BULK_ACTION_ROUTE_PATH } from '../../../common/cons
export type RuleStateAttributesWithoutStates = Omit<RuleStateAttributes, 'muted'>;
export const useChangeCspRuleState = () => {
const { http } = useKibana().services;
const queryClient = useQueryClient();

return async (actionOnRule: 'mute' | 'unmute', ruleIds: RuleStateAttributesWithoutStates[]) => {
const query = {
action: actionOnRule,
rules: ruleIds,
};
return await http?.post<CspBenchmarkRulesBulkActionRequestSchema>(

const cspRuleBulkActionResponse = await http?.post<CspBenchmarkRulesBulkActionRequestSchema>(
CSP_BENCHMARK_RULES_BULK_ACTION_ROUTE_PATH,
{
version: '1',
body: JSON.stringify(query),
}
);
await queryClient.invalidateQueries(BENCHMARK_INTEGRATION_QUERY_KEY_V2); // causing rules counters refetch
await queryClient.invalidateQueries(getCspmStatsKey); // causing cloud dashboard refetch
await queryClient.invalidateQueries(getKspmStatsKey); // causing kubernetes dashboard refetch
await queryClient.invalidateQueries(getRuleStatesKey); // the rule states are part of the findings query key, invalidating them will cause the latest findings to refetch only after the rules states were changed

return cspRuleBulkActionResponse;
};
};
Original file line number Diff line number Diff line change
Expand Up @@ -67,9 +67,8 @@ const MAX_ITEMS_PER_PAGE = 10000;
export const RulesContainer = () => {
const params = useParams<PageUrlParams>();
const [selectedRuleId, setSelectedRuleId] = useState<string | null>(null);
const { pageSize, setPageSize } = usePageSize(LOCAL_STORAGE_PAGE_SIZE_RULES_KEY);

const [enabledDisabledItemsFilter, setEnabledDisabledItemsFilter] = useState('no-filter');
const { pageSize, setPageSize } = usePageSize(LOCAL_STORAGE_PAGE_SIZE_RULES_KEY);

const [rulesQuery, setRulesQuery] = useState<RulesQuery>({
section: undefined,
Expand Down Expand Up @@ -109,6 +108,7 @@ export const RulesContainer = () => {

const rulesStates = useCspGetRulesStates();
const arrayRulesStates: RuleStateAttributes[] = Object.values(rulesStates.data || {});

const filteredRulesStates: RuleStateAttributes[] = arrayRulesStates.filter(
(ruleState: RuleStateAttributes) =>
ruleState.benchmark_id === params.benchmarkId &&
Expand All @@ -135,6 +135,8 @@ export const RulesContainer = () => {
});
}, [data, rulesStates?.data]);

const mutedRulesCount = rulesWithStates.filter((rule) => rule.state === 'muted').length;

const filteredRulesWithStates: CspBenchmarkRulesWithStates[] = useMemo(() => {
if (enabledDisabledItemsFilter === 'disabled')
return rulesWithStates?.filter((rule) => rule?.state === 'muted');
Expand All @@ -147,13 +149,16 @@ export const RulesContainer = () => {
() => allRules.data?.items.map((rule) => rule.metadata.section),
[allRules.data]
);

const ruleNumberList = useMemo(
() => allRules.data?.items.map((rule) => rule.metadata.benchmark.rule_number || ''),
[allRules.data]
);

const cleanedSectionList = [...new Set(sectionList)].sort((a, b) => {
return a.localeCompare(b, 'en', { sensitivity: 'base' });
});

const cleanedRuleNumberList = [...new Set(ruleNumberList)].sort(compareVersions);

const rulesPageData = useMemo(
Expand Down Expand Up @@ -181,7 +186,10 @@ export const RulesContainer = () => {

return (
<div data-test-subj={TEST_SUBJECTS.CSP_RULES_CONTAINER}>
<RulesCounters />
<RulesCounters
mutedRulesCount={mutedRulesCount}
setEnabledDisabledItemsFilter={setEnabledDisabledItemsFilter}
/>
<EuiSpacer />
<RulesTableHeader
onSectionChange={(value) =>
Expand All @@ -200,7 +208,7 @@ export const RulesContainer = () => {
selectedRules={selectedRules}
refetchRulesStates={rulesStates.refetch}
setEnabledDisabledItemsFilter={setEnabledDisabledItemsFilter}
currentEnabledDisabledItemsFilterState={enabledDisabledItemsFilter}
enabledDisabledItemsFilterState={enabledDisabledItemsFilter}
setSelectAllRules={setSelectAllRules}
setSelectedRules={setSelectedRules}
/>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,8 @@ import { i18n } from '@kbn/i18n';
import { useParams } from 'react-router-dom';
import { Chart, Partition, PartitionLayout, Settings } from '@elastic/charts';
import { FormattedMessage } from '@kbn/i18n-react';
import { getPostureScorePercentage } from '../compliance_dashboard/compliance_charts/compliance_score_chart';
import { RULE_COUNTERS_TEST_SUBJ } from './test_subjects';
import noDataIllustration from '../../assets/illustrations/no_data_illustration.svg';
import { BenchmarksCisId } from '../../../common/types/benchmarks/v2';
import { useCspIntegrationLink } from '../../common/navigation/use_csp_integration_link';
Expand Down Expand Up @@ -88,7 +90,13 @@ const EvaluationPieChart = ({ failed, passed }: { failed: number; passed: number
);
};

export const RulesCounters = () => {
export const RulesCounters = ({
mutedRulesCount,
setEnabledDisabledItemsFilter,
}: {
mutedRulesCount: number;
setEnabledDisabledItemsFilter: (filterState: string) => void;
}) => {
const { http } = useKibana().services;
const rulesPageParams = useParams<{ benchmarkId: string; benchmarkVersion: string }>();
const getBenchmarks = useCspBenchmarkIntegrationsV2();
Expand Down Expand Up @@ -156,6 +164,7 @@ export const RulesCounters = () => {
if (benchmarkRulesStats.score.totalFindings === 0) {
return (
<EuiEmptyPrompt
data-test-subj={RULE_COUNTERS_TEST_SUBJ.RULE_COUNTERS_EMPTY_STATE}
color="plain"
icon={
<EuiImage
Expand Down Expand Up @@ -227,7 +236,7 @@ export const RulesCounters = () => {

const counters = [
{
id: 'rules-counters-posture-score',
id: RULE_COUNTERS_TEST_SUBJ.POSTURE_SCORE_COUNTER,
description: i18n.translate('xpack.csp.rulesCounters.postureScoreTitle', {
defaultMessage: 'Posture Score',
}),
Expand All @@ -239,11 +248,14 @@ export const RulesCounters = () => {
passed={benchmarkRulesStats.score.totalPassed}
/>
</EuiFlexItem>
<EuiFlexItem>{`${benchmarkRulesStats.score.postureScore}%`}</EuiFlexItem>
<EuiFlexItem>
{getPostureScorePercentage(benchmarkRulesStats.score.postureScore)}
</EuiFlexItem>
</EuiFlexGroup>
),
button: (
<EuiButtonEmpty
data-test-subj={RULE_COUNTERS_TEST_SUBJ.POSTURE_SCORE_BUTTON}
iconType="pivot"
href={http.basePath.prepend(`/app/security${cloudPosturePages.dashboard.path}`)}
>
Expand All @@ -254,7 +266,7 @@ export const RulesCounters = () => {
),
},
{
id: 'rules-counters-evaluated',
id: RULE_COUNTERS_TEST_SUBJ.INTEGRATIONS_EVALUATED_COUNTER,
description: i18n.translate('xpack.csp.rulesCounters.accountsEvaluatedTitle', {
defaultMessage: '{resourceName} Evaluated',
values: {
Expand All @@ -264,6 +276,7 @@ export const RulesCounters = () => {
title: benchmarkRulesStats.evaluation || 0,
button: (
<EuiButtonEmpty
data-test-subj={RULE_COUNTERS_TEST_SUBJ.INTEGRATIONS_EVALUATED_BUTTON}
iconType="listAdd"
href={benchmarkDynamicValues[benchmarkRulesStats.id].integrationLink}
>
Expand All @@ -278,14 +291,15 @@ export const RulesCounters = () => {
),
},
{
id: 'rules-counters-failed-findings',
id: RULE_COUNTERS_TEST_SUBJ.FAILED_FINDINGS_COUNTER,
description: i18n.translate('xpack.csp.rulesCounters.failedFindingsTitle', {
defaultMessage: 'Failed Findings',
}),
title: benchmarkRulesStats.score.totalFailed,
titleColor: benchmarkRulesStats.score.totalFailed > 0 ? statusColors.failed : undefined,
button: (
<EuiButtonEmpty
data-test-subj={RULE_COUNTERS_TEST_SUBJ.FAILED_FINDINGS_BUTTON}
iconType="pivot"
onClick={() =>
navToFindings({
Expand All @@ -302,13 +316,17 @@ export const RulesCounters = () => {
),
},
{
id: 'rules-counters-disabled-rules',
id: RULE_COUNTERS_TEST_SUBJ.DISABLED_RULES_COUNTER,
description: i18n.translate('xpack.csp.rulesCounters.disabledRulesCounterTitle', {
defaultMessage: 'Disabled Rules',
}),
title: 'WIP',
title: mutedRulesCount,
button: (
<EuiButtonEmpty iconType="search">
<EuiButtonEmpty
data-test-subj={RULE_COUNTERS_TEST_SUBJ.DISABLED_RULES_BUTTON}
iconType="search"
onClick={() => setEnabledDisabledItemsFilter('disabled')}
>
{i18n.translate('xpack.csp.rulesCounters.disabledRulesCounterButton', {
defaultMessage: 'View all disabled rules',
})}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -51,7 +51,7 @@ interface RulesTableToolbarProps {
selectedRules: CspBenchmarkRulesWithStates[];
refetchRulesStates: () => void;
setEnabledDisabledItemsFilter: (filterState: string) => void;
currentEnabledDisabledItemsFilterState: string;
enabledDisabledItemsFilterState: string;
setSelectAllRules: () => void;
setSelectedRules: (rules: CspBenchmarkRulesWithStates[]) => void;
}
Expand All @@ -78,7 +78,7 @@ export const RulesTableHeader = ({
selectedRules,
refetchRulesStates,
setEnabledDisabledItemsFilter,
currentEnabledDisabledItemsFilterState,
enabledDisabledItemsFilterState,
setSelectAllRules,
setSelectedRules,
}: RulesTableToolbarProps) => {
Expand All @@ -92,26 +92,14 @@ export const RulesTableHeader = ({
key: option,
label: option,
}));
const [isEnabledRulesFilterOn, setIsEnabledRulesFilterOn] = useState(false);
const [isDisabledRulesFilterOn, setisDisabledRulesFilterOn] = useState(false);

const toggleEnabledRulesFilter = () => {
setIsEnabledRulesFilterOn(!isEnabledRulesFilterOn);
setisDisabledRulesFilterOn(
isDisabledRulesFilterOn && !isEnabledRulesFilterOn ? false : isDisabledRulesFilterOn
);
if (currentEnabledDisabledItemsFilterState === 'enabled')
setEnabledDisabledItemsFilter('no-filter');
if (enabledDisabledItemsFilterState === 'enabled') setEnabledDisabledItemsFilter('no-filter');
else setEnabledDisabledItemsFilter('enabled');
};

const toggleDisabledRulesFilter = () => {
setisDisabledRulesFilterOn(!isDisabledRulesFilterOn);
setIsEnabledRulesFilterOn(
isEnabledRulesFilterOn && !isDisabledRulesFilterOn ? false : isEnabledRulesFilterOn
);
if (currentEnabledDisabledItemsFilterState === 'disabled')
setEnabledDisabledItemsFilter('no-filter');
if (enabledDisabledItemsFilterState === 'disabled') setEnabledDisabledItemsFilter('no-filter');
else setEnabledDisabledItemsFilter('disabled');
};

Expand Down Expand Up @@ -186,7 +174,7 @@ export const RulesTableHeader = ({
<EuiFilterGroup>
<EuiFilterButton
withNext
hasActiveFilters={isEnabledRulesFilterOn}
hasActiveFilters={enabledDisabledItemsFilterState === 'enabled'}
onClick={toggleEnabledRulesFilter}
data-test-subj={RULES_ENABLED_FILTER}
>
Expand All @@ -196,7 +184,7 @@ export const RulesTableHeader = ({
/>
</EuiFilterButton>
<EuiFilterButton
hasActiveFilters={isDisabledRulesFilterOn}
hasActiveFilters={enabledDisabledItemsFilterState === 'disabled'}
onClick={toggleDisabledRulesFilter}
data-test-subj={RULES_DISABLED_FILTER}
>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -13,5 +13,17 @@ export const CSP_RULES_TABLE = 'csp_rules_table';
export const CSP_RULES_TABLE_ROW_ITEM_NAME = 'csp_rules_table_row_item_name';
export const CSP_RULES_FLYOUT_CONTAINER = 'csp_rules_flyout_container';

export const RULE_COUNTERS_TEST_SUBJ = {
RULE_COUNTERS_EMPTY_STATE: 'rules-counters-empty-state',
POSTURE_SCORE_COUNTER: 'rules-counters-posture-score-counter',
POSTURE_SCORE_BUTTON: 'rules-counters-posture-score-button',
INTEGRATIONS_EVALUATED_COUNTER: 'rules-counters-integrations-evaluated-counter',
INTEGRATIONS_EVALUATED_BUTTON: 'rules-counters-integrations-evaluated-button',
FAILED_FINDINGS_COUNTER: 'rules-counters-failed-findings-counter',
FAILED_FINDINGS_BUTTON: 'rules-counters-failed-findings-button',
DISABLED_RULES_COUNTER: 'rules-counters-disabled-rules-counter',
DISABLED_RULES_BUTTON: 'rules-counters-disabled-rules-button',
};

export const getCspBenchmarkRuleTableRowItemTestId = (id: string) =>
`${CSP_RULES_TABLE_ROW_ITEM_NAME}_${id}`;
Original file line number Diff line number Diff line change
Expand Up @@ -121,7 +121,7 @@ export default function ({ getService }: FtrProviderContext) {
.set('kbn-xsrf', 'xxxx')
.expect(200);

expect(res.items.length).equal(3);
expect(res.items.length).equal(5);
});
});
}
Loading

0 comments on commit e2afa52

Please sign in to comment.