diff --git a/x-pack/plugins/cloud_security_posture/public/common/api/use_stats_api.ts b/x-pack/plugins/cloud_security_posture/public/common/api/use_stats_api.ts index dd893ff356865..db6883810d672 100644 --- a/x-pack/plugins/cloud_security_posture/public/common/api/use_stats_api.ts +++ b/x-pack/plugins/cloud_security_posture/public/common/api/use_stats_api.ts @@ -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); diff --git a/x-pack/plugins/cloud_security_posture/public/pages/benchmarks/use_csp_benchmark_integrations.ts b/x-pack/plugins/cloud_security_posture/public/pages/benchmarks/use_csp_benchmark_integrations.ts index 1683c3e63d2bd..3b21e7978e839 100644 --- a/x-pack/plugins/cloud_security_posture/public/pages/benchmarks/use_csp_benchmark_integrations.ts +++ b/x-pack/plugins/cloud_security_posture/public/pages/benchmarks/use_csp_benchmark_integrations.ts @@ -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; @@ -40,7 +39,7 @@ export const useCspBenchmarkIntegrationsV1 = ({ }; return useQuery( - [QUERY_KEY_V1, query], + [BENCHMARK_INTEGRATION_QUERY_KEY_V1, query], () => http.get(BENCHMARKS_ROUTE_PATH, { query, @@ -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(BENCHMARKS_ROUTE_PATH, { version: '2', diff --git a/x-pack/plugins/cloud_security_posture/public/pages/compliance_dashboard/compliance_charts/compliance_score_chart.tsx b/x-pack/plugins/cloud_security_posture/public/pages/compliance_dashboard/compliance_charts/compliance_score_chart.tsx index d986d5a97c2f6..aff4feeed048a 100644 --- a/x-pack/plugins/cloud_security_posture/public/pages/compliance_dashboard/compliance_charts/compliance_score_chart.tsx +++ b/x-pack/plugins/cloud_security_posture/public/pages/compliance_dashboard/compliance_charts/compliance_score_chart.tsx @@ -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, diff --git a/x-pack/plugins/cloud_security_posture/public/pages/configurations/latest_findings/use_get_benchmark_rules_state_api.ts b/x-pack/plugins/cloud_security_posture/public/pages/configurations/latest_findings/use_get_benchmark_rules_state_api.ts index a0c957907c0af..ceada5c3148eb 100644 --- a/x-pack/plugins/cloud_security_posture/public/pages/configurations/latest_findings/use_get_benchmark_rules_state_api.ts +++ b/x-pack/plugins/cloud_security_posture/public/pages/configurations/latest_findings/use_get_benchmark_rules_state_api.ts @@ -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( - [getRuleStatesKey], - () => - http.get(CSP_GET_BENCHMARK_RULES_STATE_ROUTE_PATH, { - version: CSP_GET_BENCHMARK_RULES_STATE_API_CURRENT_VERSION, - }) + return useQuery(getRuleStatesKey, () => + http.get(CSP_GET_BENCHMARK_RULES_STATE_ROUTE_PATH, { + version: CSP_GET_BENCHMARK_RULES_STATE_API_CURRENT_VERSION, + }) ); }; diff --git a/x-pack/plugins/cloud_security_posture/public/pages/configurations/latest_findings/use_latest_findings.ts b/x-pack/plugins/cloud_security_posture/public/pages/configurations/latest_findings/use_latest_findings.ts index 9968bb9c414bf..5a516920df1bd 100644 --- a/x-pack/plugins/cloud_security_posture/public/pages/configurations/latest_findings/use_latest_findings.ts +++ b/x-pack/plugins/cloud_security_posture/public/pages/configurations/latest_findings/use_latest_findings.ts @@ -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 }, diff --git a/x-pack/plugins/cloud_security_posture/public/pages/rules/change_csp_rule_state.ts b/x-pack/plugins/cloud_security_posture/public/pages/rules/change_csp_rule_state.ts index 68fca9056e74e..a574ec5f3efee 100644 --- a/x-pack/plugins/cloud_security_posture/public/pages/rules/change_csp_rule_state.ts +++ b/x-pack/plugins/cloud_security_posture/public/pages/rules/change_csp_rule_state.ts @@ -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, @@ -15,18 +19,26 @@ import { CSP_BENCHMARK_RULES_BULK_ACTION_ROUTE_PATH } from '../../../common/cons export type RuleStateAttributesWithoutStates = Omit; 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( + + const cspRuleBulkActionResponse = await http?.post( 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; }; }; 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 01623235c42a7..62d1371b01f3f 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 @@ -67,9 +67,8 @@ const MAX_ITEMS_PER_PAGE = 10000; export const RulesContainer = () => { const params = useParams(); const [selectedRuleId, setSelectedRuleId] = useState(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({ section: undefined, @@ -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 && @@ -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'); @@ -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( @@ -181,7 +186,10 @@ export const RulesContainer = () => { return (
- + @@ -200,7 +208,7 @@ export const RulesContainer = () => { selectedRules={selectedRules} refetchRulesStates={rulesStates.refetch} setEnabledDisabledItemsFilter={setEnabledDisabledItemsFilter} - currentEnabledDisabledItemsFilterState={enabledDisabledItemsFilter} + enabledDisabledItemsFilterState={enabledDisabledItemsFilter} setSelectAllRules={setSelectAllRules} setSelectedRules={setSelectedRules} /> diff --git a/x-pack/plugins/cloud_security_posture/public/pages/rules/rules_counters.tsx b/x-pack/plugins/cloud_security_posture/public/pages/rules/rules_counters.tsx index 469d2eff5f42d..9c311406fe172 100644 --- a/x-pack/plugins/cloud_security_posture/public/pages/rules/rules_counters.tsx +++ b/x-pack/plugins/cloud_security_posture/public/pages/rules/rules_counters.tsx @@ -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'; @@ -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(); @@ -156,6 +164,7 @@ export const RulesCounters = () => { if (benchmarkRulesStats.score.totalFindings === 0) { return ( { 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', }), @@ -239,11 +248,14 @@ export const RulesCounters = () => { passed={benchmarkRulesStats.score.totalPassed} /> - {`${benchmarkRulesStats.score.postureScore}%`} + + {getPostureScorePercentage(benchmarkRulesStats.score.postureScore)} + ), button: ( @@ -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: { @@ -264,6 +276,7 @@ export const RulesCounters = () => { title: benchmarkRulesStats.evaluation || 0, button: ( @@ -278,7 +291,7 @@ 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', }), @@ -286,6 +299,7 @@ export const RulesCounters = () => { titleColor: benchmarkRulesStats.score.totalFailed > 0 ? statusColors.failed : undefined, button: ( navToFindings({ @@ -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: ( - + setEnabledDisabledItemsFilter('disabled')} + > {i18n.translate('xpack.csp.rulesCounters.disabledRulesCounterButton', { defaultMessage: 'View all disabled rules', })} 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 366ae740c3e94..28ab839515dfb 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 @@ -51,7 +51,7 @@ interface RulesTableToolbarProps { selectedRules: CspBenchmarkRulesWithStates[]; refetchRulesStates: () => void; setEnabledDisabledItemsFilter: (filterState: string) => void; - currentEnabledDisabledItemsFilterState: string; + enabledDisabledItemsFilterState: string; setSelectAllRules: () => void; setSelectedRules: (rules: CspBenchmarkRulesWithStates[]) => void; } @@ -78,7 +78,7 @@ export const RulesTableHeader = ({ selectedRules, refetchRulesStates, setEnabledDisabledItemsFilter, - currentEnabledDisabledItemsFilterState, + enabledDisabledItemsFilterState, setSelectAllRules, setSelectedRules, }: RulesTableToolbarProps) => { @@ -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'); }; @@ -186,7 +174,7 @@ export const RulesTableHeader = ({ @@ -196,7 +184,7 @@ export const RulesTableHeader = ({ /> diff --git a/x-pack/plugins/cloud_security_posture/public/pages/rules/test_subjects.ts b/x-pack/plugins/cloud_security_posture/public/pages/rules/test_subjects.ts index 5f7de36484609..43b209c6ce7d4 100644 --- a/x-pack/plugins/cloud_security_posture/public/pages/rules/test_subjects.ts +++ b/x-pack/plugins/cloud_security_posture/public/pages/rules/test_subjects.ts @@ -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}`; diff --git a/x-pack/test/api_integration/apis/cloud_security_posture/benchmark/v2.ts b/x-pack/test/api_integration/apis/cloud_security_posture/benchmark/v2.ts index c64d2579e27ab..e359d7146fcd7 100644 --- a/x-pack/test/api_integration/apis/cloud_security_posture/benchmark/v2.ts +++ b/x-pack/test/api_integration/apis/cloud_security_posture/benchmark/v2.ts @@ -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); }); }); } diff --git a/x-pack/test/api_integration/apis/cloud_security_posture/helper.ts b/x-pack/test/api_integration/apis/cloud_security_posture/helper.ts index de9fdfe9952ea..03a61807f1b31 100644 --- a/x-pack/test/api_integration/apis/cloud_security_posture/helper.ts +++ b/x-pack/test/api_integration/apis/cloud_security_posture/helper.ts @@ -51,7 +51,7 @@ export async function createPackagePolicy( posture: string, packageName: string = 'cloud_security_posture-1' ) { - const version = '1.3.0'; + const version = '1.7.1'; const title = 'Security Posture Management'; const streams = [ { diff --git a/x-pack/test/cloud_security_posture_functional/mocks/latest_findings_mock.ts b/x-pack/test/cloud_security_posture_functional/mocks/latest_findings_mock.ts new file mode 100644 index 0000000000000..70d211d99ee06 --- /dev/null +++ b/x-pack/test/cloud_security_posture_functional/mocks/latest_findings_mock.ts @@ -0,0 +1,722 @@ +/* + * 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. + */ + +const timeOneHourAgo = (Date.now() - 3600000).toString(); + +export const k8sFindingsMock = [ + { + agent: { + name: 'kind-multi-worker', + id: 'f6d3cea6-7893-45cf-aba9-4961bf955853', + ephemeral_id: '528cabe6-19d6-443d-bf17-40f2a0014419', + type: 'cloudbeat', + version: '8.12.0', + }, + process: { + args: [ + '/usr/bin/kubelet', + '--bootstrap-kubeconfig=/etc/kubernetes/bootstrap-kubelet.conf', + '--kubeconfig=/etc/kubernetes/kubelet.conf', + '--config=/var/lib/kubelet/config.yaml', + '--container-runtime=remote', + '--container-runtime-endpoint=unix:///run/containerd/containerd.sock', + '--fail-swap-on=false', + '--node-ip=172.18.0.3', + '--node-labels=', + '--pod-infra-container-image=k8s.gcr.io/pause:3.6', + '--provider-id=kind://docker/kind-multi/kind-multi-worker', + '--fail-swap-on=false', + '--cgroup-root=/kubelet', + ], + parent: { + pid: 1, + }, + pgid: 197, + name: 'kubelet', + start: '2023-04-10T12:21:01.160Z', + pid: 197, + args_count: 13, + title: 'kubelet', + command_line: + '/usr/bin/kubelet --bootstrap-kubeconfig=/etc/kubernetes/bootstrap-kubelet.conf --kubeconfig=/etc/kubernetes/kubelet.conf --config=/var/lib/kubelet/config.yaml --container-runtime=remote --container-runtime-endpoint=unix:///run/containerd/containerd.sock --fail-swap-on=false --node-ip=172.18.0.3 --node-labels= --pod-infra-container-image=k8s.gcr.io/pause:3.6 --provider-id=kind://docker/kind-multi/kind-multi-worker --fail-swap-on=false --cgroup-root=/kubelet', + uptime: 25320267, + }, + resource: { + sub_type: 'process', + name: 'kubelet', + raw: { + stat: { + UserTime: '12186434', + Group: '197', + RealGID: '', + Parent: '1', + StartTime: '439085', + ResidentSize: '84292000', + SavedGID: '', + TotalSize: '2914080000', + EffectiveUID: '', + Name: 'kubelet', + Threads: '30', + RealUID: '', + State: 'S', + Nice: '0', + SavedUID: '', + EffectiveGID: '', + SystemTime: '9042092', + }, + external_data: { + config: { + nodeStatusReportFrequency: '0s', + cpuManagerReconcilePeriod: '0s', + shutdownGracePeriod: '0s', + syncFrequency: '0s', + httpCheckFrequency: '0s', + clusterDomain: 'cluster.local', + healthzPort: 10248, + evictionHard: { + 'imagefs.available': '0%', + 'nodefs.available': '0%', + 'nodefs.inodesFree': '0%', + }, + staticPodPath: '/etc/kubernetes/manifests', + nodeStatusUpdateFrequency: '0s', + authorization: { + mode: 'Webhook', + webhook: { + cacheAuthorizedTTL: '0s', + cacheUnauthorizedTTL: '0s', + }, + }, + volumeStatsAggPeriod: '0s', + apiVersion: 'kubelet.config.k8s.io/v1beta1', + evictionPressureTransitionPeriod: '0s', + runtimeRequestTimeout: '0s', + clusterDNS: ['10.96.0.10'], + authentication: { + x509: { + clientCAFile: '/etc/kubernetes/pki/ca.crt', + }, + webhook: { + cacheTTL: '0s', + enabled: true, + }, + anonymous: { + enabled: false, + }, + }, + memorySwap: {}, + imageMinimumGCAge: '0s', + kind: 'KubeletConfiguration', + rotateCertificates: true, + imageGCHighThresholdPercent: 100, + cgroupDriver: 'cgroupfs', + logging: { + flushFrequency: 0, + options: { + json: { + infoBufferSize: '0', + }, + }, + verbosity: 0, + }, + streamingConnectionIdleTimeout: '0s', + shutdownGracePeriodCriticalPods: '0s', + fileCheckFrequency: '0s', + healthzBindAddress: '127.0.0.1', + }, + }, + pid: '197', + command: + '/usr/bin/kubelet --bootstrap-kubeconfig=/etc/kubernetes/bootstrap-kubelet.conf --kubeconfig=/etc/kubernetes/kubelet.conf --config=/var/lib/kubelet/config.yaml --container-runtime=remote --container-runtime-endpoint=unix:///run/containerd/containerd.sock --fail-swap-on=false --node-ip=172.18.0.3 --node-labels= --pod-infra-container-image=k8s.gcr.io/pause:3.6 --provider-id=kind://docker/kind-multi/kind-multi-worker --fail-swap-on=false --cgroup-root=/kubelet', + }, + id: 'b5848858-d546-5f81-b681-9b6ae6fb145f', + type: 'process', + }, + cloud_security_posture: { + package_policy: { + id: '16aec061-cb66-472d-956a-51a23bcea333', + revision: 3, + }, + }, + elastic_agent: { + id: 'f6d3cea6-7893-45cf-aba9-4961bf955853', + version: '8.12.0', + snapshot: false, + }, + rule: { + references: '1. https://kubernetes.io/docs/admin/kubelet/', + impact: + 'Removal of the read-only port will require that any service which made use of it will need to be re-configured to use the main Kubelet API.', + description: 'Disable the read-only port.', + default_value: + 'By default, `--read-only-port` is set to `10255/TCP`. However, if a config file\nis specified by --config the default value for `readOnlyPort` is `0`.\n', + section: 'Kubelet', + rationale: + 'The Kubelet process provides a read-only API in addition to the main Kubelet API.\nUnauthenticated access is provided to this read-only API which could possibly retrieve potentially sensitive information about the cluster.', + version: '1.0', + benchmark: { + name: 'CIS Kubernetes V1.23', + rule_number: '4.2.4', + id: 'cis_k8s', + version: 'v1.0.1', + posture_type: 'kspm', + }, + tags: ['CIS', 'Kubernetes', 'CIS 4.2.4', 'Kubelet'], + remediation: + 'If using a Kubelet config file, edit the file to set `readOnlyPort` to `0`.\n\nIf using command line arguments, edit the kubelet service file `/etc/systemd/system/kubelet.service.d/10-kubeadm.conf` on each worker node and set the below parameter in `KUBELET_SYSTEM_PODS_ARGS` variable.\n\n```\n--read-only-port=0\n```\n\nBased on your system, restart the `kubelet` service.\nFor example:\n\n```\nsystemctl daemon-reload\nsystemctl restart kubelet.service\n```', + audit: + 'Run the following command on each node:\n\n```\nps -ef | grep kubelet\n```\n\nVerify that the `--read-only-port` argument exists and is set to `0`.\n\nIf the `--read-only-port` argument is not present, check that there is a Kubelet config file specified by `--config`.\nCheck that if there is a `readOnlyPort` entry in the file, it is set to `0`.', + name: 'Verify that the --read-only-port argument is set to 0', + id: '95e368ec-eebe-5aa1-bc86-9fa532a82d3a', + profile_applicability: '* Level 1 - Worker Node', + }, + message: 'Rule "Verify that the --read-only-port argument is set to 0": failed', + result: { + evaluation: 'failed', + evidence: { + process_args: { + '--cgroup-root': '/kubelet', + '--kubeconfig': '/etc/kubernetes/kubelet.conf', + '--node-ip': '172.18.0.3', + '--container-runtime': 'remote', + '--node-labels': '', + '--/usr/bin/kubelet': '', + '--bootstrap-kubeconfig': '/etc/kubernetes/bootstrap-kubelet.conf', + '--fail-swap-on': 'false', + '--provider-id': 'kind://docker/kind-multi/kind-multi-worker', + '--pod-infra-container-image': 'k8s.gcr.io/pause:3.6', + '--container-runtime-endpoint': 'unix:///run/containerd/containerd.sock', + '--config': '/var/lib/kubelet/config.yaml', + }, + process_config: { + config: { + nodeStatusReportFrequency: '0s', + cpuManagerReconcilePeriod: '0s', + shutdownGracePeriod: '0s', + httpCheckFrequency: '0s', + syncFrequency: '0s', + clusterDomain: 'cluster.local', + healthzPort: 10248, + evictionHard: { + 'imagefs.available': '0%', + 'nodefs.available': '0%', + 'nodefs.inodesFree': '0%', + }, + staticPodPath: '/etc/kubernetes/manifests', + nodeStatusUpdateFrequency: '0s', + authorization: { + mode: 'Webhook', + webhook: { + cacheAuthorizedTTL: '0s', + cacheUnauthorizedTTL: '0s', + }, + }, + volumeStatsAggPeriod: '0s', + apiVersion: 'kubelet.config.k8s.io/v1beta1', + evictionPressureTransitionPeriod: '0s', + runtimeRequestTimeout: '0s', + clusterDNS: ['10.96.0.10'], + authentication: { + x509: { + clientCAFile: '/etc/kubernetes/pki/ca.crt', + }, + webhook: { + cacheTTL: '0s', + enabled: true, + }, + anonymous: { + enabled: false, + }, + }, + memorySwap: {}, + imageMinimumGCAge: '0s', + kind: 'KubeletConfiguration', + rotateCertificates: true, + imageGCHighThresholdPercent: 100, + cgroupDriver: 'cgroupfs', + logging: { + flushFrequency: 0, + options: { + json: { + infoBufferSize: '0', + }, + }, + verbosity: 0, + }, + streamingConnectionIdleTimeout: '0s', + shutdownGracePeriodCriticalPods: '0s', + fileCheckFrequency: '0s', + healthzBindAddress: '127.0.0.1', + }, + }, + }, + expected: null, + }, + orchestrator: { + cluster: { + name: 'kind-multi', + id: '80b085f5-c172-4143-a74d-4b881c51edb2', + version: 'v1.23.12', + }, + }, + cluster_id: '80b085f5-c172-4143-a74d-4b881c51edb2', + '@timestamp': timeOneHourAgo, + cloudbeat: { + commit_sha: 'b23ca39a0ca08aa94f8e4a644517bb73f3cf1f66', + commit_time: '2024-01-11T09:14:22Z', + version: '8.12.0', + policy: { + commit_sha: 'b23ca39a0ca08aa94f8e4a644517bb73f3cf1f66', + commit_time: '2024-01-11T09:14:22Z', + version: '8.12.0', + }, + }, + ecs: { + version: '8.6.0', + }, + data_stream: { + namespace: 'default', + type: 'logs', + dataset: 'cloud_security_posture.findings', + }, + host: { + name: 'kind-multi-worker', + }, + event: { + agent_id_status: 'auth_metadata_missing', + sequence: 1706449528, + ingested: '2024-01-28T13:49:31Z', + kind: 'state', + created: '2024-01-28T13:45:28.365459493Z', + id: '0f4cca7e-4160-4060-ad1b-af122ee71e7d', + type: ['info'], + category: ['configuration'], + dataset: 'cloud_security_posture.findings', + outcome: 'success', + }, + }, + { + agent: { + name: 'kind-multi-control-plane', + id: '92265e5e-4694-4056-8c10-f5d588cc95a8', + type: 'cloudbeat', + ephemeral_id: '6bdf59e6-e13c-4a39-8f31-8ad2fc96343f', + version: '8.12.0', + }, + resource: { + sub_type: 'file', + name: '/hostfs/etc/kubernetes/manifests/kube-apiserver.yaml', + raw: { + owner: 'root', + inode: '776303', + mode: '600', + uid: '0', + path: '/hostfs/etc/kubernetes/manifests/kube-apiserver.yaml', + gid: '0', + sub_type: 'file', + name: 'kube-apiserver.yaml', + group: 'root', + }, + id: '07afe6a0-970d-5767-a0c2-8839d414c904', + type: 'file', + }, + cloud_security_posture: { + package_policy: { + id: '16aec061-cb66-472d-956a-51a23bcea333', + revision: 3, + }, + }, + elastic_agent: { + id: '92265e5e-4694-4056-8c10-f5d588cc95a8', + version: '8.12.0', + snapshot: false, + }, + rule: { + references: '1. https://kubernetes.io/docs/admin/kube-apiserver/', + impact: 'None', + description: + 'Ensure that the API server pod specification file has permissions of `644` or more restrictive.', + default_value: 'By default, the `kube-apiserver.yaml` file has permissions of `640`.\n', + section: 'Control Plane Node Configuration Files', + version: '1.0', + rationale: + 'The API server pod specification file controls various parameters that set the behavior of the API server.\nYou should restrict its file permissions to maintain the integrity of the file.\nThe file should be writable by only the administrators on the system.', + benchmark: { + name: 'CIS Kubernetes V1.23', + rule_number: '1.1.1', + id: 'cis_k8s', + version: 'v1.0.1', + posture_type: 'kspm', + }, + tags: ['CIS', 'Kubernetes', 'CIS 1.1.1', 'Control Plane Node Configuration Files'], + remediation: + 'Run the below command (based on the file location on your system) on the Control Plane node.\nFor example,\n\n```\nchmod 644 /etc/kubernetes/manifests/kube-apiserver.yaml\n```', + audit: + 'Run the below command (based on the file location on your system) on the Control Plane node.\nFor example,\n\n```\nstat -c %a /etc/kubernetes/manifests/kube-apiserver.yaml\n```\n\nVerify that the permissions are `644` or more restrictive.', + name: 'Ensure that the API server pod specification file permissions are set to 644 or more restrictive', + id: 'c444d9e3-d3de-5598-90e7-95a922b51664', + profile_applicability: '* Level 1 - Master Node', + }, + message: + 'Rule "Ensure that the API server pod specification file permissions are set to 644 or more restrictive": passed', + result: { + evaluation: 'passed', + evidence: { + filemode: '600', + }, + expected: { + filemode: '644', + }, + }, + orchestrator: { + cluster: { + name: 'kind-multi', + id: '80b085f5-c172-4143-a74d-4b881c51edb2', + version: 'v1.23.12', + }, + }, + cluster_id: '80b085f5-c172-4143-a74d-4b881c51edb2', + '@timestamp': '2024-01-29T13:45:03.456Z', + file: { + owner: 'root', + mode: '600', + inode: '776303', + path: '/hostfs/etc/kubernetes/manifests/kube-apiserver.yaml', + uid: '0', + extension: '.yaml', + gid: '0', + size: 3869, + name: 'kube-apiserver.yaml', + type: 'file', + directory: '/hostfs/etc/kubernetes/manifests', + group: 'root', + }, + cloudbeat: { + commit_sha: 'b23ca39a0ca08aa94f8e4a644517bb73f3cf1f66', + commit_time: '2024-01-11T09:14:22Z', + version: '8.12.0', + policy: { + commit_sha: 'b23ca39a0ca08aa94f8e4a644517bb73f3cf1f66', + commit_time: '2024-01-11T09:14:22Z', + version: '8.12.0', + }, + }, + ecs: { + version: '8.6.0', + }, + data_stream: { + namespace: 'default', + type: 'logs', + dataset: 'cloud_security_posture.findings', + }, + host: { + name: 'kind-multi-control-plane', + }, + event: { + agent_id_status: 'auth_metadata_missing', + sequence: 1706535902, + ingested: '2024-01-29T13:49:51Z', + created: '2024-01-29T13:45:03.456357519Z', + kind: 'state', + id: '433d9499-64b9-4f71-ae96-6e0439a0c776', + type: ['info'], + category: ['configuration'], + dataset: 'cloud_security_posture.findings', + outcome: 'success', + }, + }, + { + agent: { + name: 'kind-multi-worker', + id: 'f6d3cea6-7893-45cf-aba9-4961bf955853', + ephemeral_id: '528cabe6-19d6-443d-bf17-40f2a0014419', + type: 'cloudbeat', + version: '8.12.0', + }, + process: { + args: [ + '/usr/bin/kubelet', + '--bootstrap-kubeconfig=/etc/kubernetes/bootstrap-kubelet.conf', + '--kubeconfig=/etc/kubernetes/kubelet.conf', + '--config=/var/lib/kubelet/config.yaml', + '--container-runtime=remote', + '--container-runtime-endpoint=unix:///run/containerd/containerd.sock', + '--fail-swap-on=false', + '--node-ip=172.18.0.3', + '--node-labels=', + '--pod-infra-container-image=k8s.gcr.io/pause:3.6', + '--provider-id=kind://docker/kind-multi/kind-multi-worker', + '--fail-swap-on=false', + '--cgroup-root=/kubelet', + ], + parent: { + pid: 1, + }, + pgid: 197, + start: '2023-04-10T12:21:01.160Z', + name: 'kubelet', + pid: 197, + args_count: 13, + title: 'kubelet', + command_line: + '/usr/bin/kubelet --bootstrap-kubeconfig=/etc/kubernetes/bootstrap-kubelet.conf --kubeconfig=/etc/kubernetes/kubelet.conf --config=/var/lib/kubelet/config.yaml --container-runtime=remote --container-runtime-endpoint=unix:///run/containerd/containerd.sock --fail-swap-on=false --node-ip=172.18.0.3 --node-labels= --pod-infra-container-image=k8s.gcr.io/pause:3.6 --provider-id=kind://docker/kind-multi/kind-multi-worker --fail-swap-on=false --cgroup-root=/kubelet', + uptime: 25320267, + }, + resource: { + sub_type: 'process', + name: 'kubelet', + raw: { + stat: { + UserTime: '12186434', + Group: '197', + RealGID: '', + Parent: '1', + StartTime: '439085', + ResidentSize: '84292000', + EffectiveUID: '', + TotalSize: '2914080000', + SavedGID: '', + Name: 'kubelet', + Threads: '30', + RealUID: '', + State: 'S', + Nice: '0', + EffectiveGID: '', + SavedUID: '', + SystemTime: '9042092', + }, + external_data: { + config: { + nodeStatusReportFrequency: '0s', + shutdownGracePeriod: '0s', + cpuManagerReconcilePeriod: '0s', + syncFrequency: '0s', + httpCheckFrequency: '0s', + clusterDomain: 'cluster.local', + healthzPort: 10248, + evictionHard: { + 'imagefs.available': '0%', + 'nodefs.available': '0%', + 'nodefs.inodesFree': '0%', + }, + staticPodPath: '/etc/kubernetes/manifests', + nodeStatusUpdateFrequency: '0s', + authorization: { + mode: 'Webhook', + webhook: { + cacheAuthorizedTTL: '0s', + cacheUnauthorizedTTL: '0s', + }, + }, + volumeStatsAggPeriod: '0s', + apiVersion: 'kubelet.config.k8s.io/v1beta1', + evictionPressureTransitionPeriod: '0s', + runtimeRequestTimeout: '0s', + clusterDNS: ['10.96.0.10'], + authentication: { + x509: { + clientCAFile: '/etc/kubernetes/pki/ca.crt', + }, + webhook: { + cacheTTL: '0s', + enabled: true, + }, + anonymous: { + enabled: false, + }, + }, + memorySwap: {}, + imageMinimumGCAge: '0s', + kind: 'KubeletConfiguration', + rotateCertificates: true, + imageGCHighThresholdPercent: 100, + cgroupDriver: 'cgroupfs', + logging: { + flushFrequency: 0, + options: { + json: { + infoBufferSize: '0', + }, + }, + verbosity: 0, + }, + streamingConnectionIdleTimeout: '0s', + shutdownGracePeriodCriticalPods: '0s', + fileCheckFrequency: '0s', + healthzBindAddress: '127.0.0.1', + }, + }, + pid: '197', + command: + '/usr/bin/kubelet --bootstrap-kubeconfig=/etc/kubernetes/bootstrap-kubelet.conf --kubeconfig=/etc/kubernetes/kubelet.conf --config=/var/lib/kubelet/config.yaml --container-runtime=remote --container-runtime-endpoint=unix:///run/containerd/containerd.sock --fail-swap-on=false --node-ip=172.18.0.3 --node-labels= --pod-infra-container-image=k8s.gcr.io/pause:3.6 --provider-id=kind://docker/kind-multi/kind-multi-worker --fail-swap-on=false --cgroup-root=/kubelet', + }, + id: 'b5848858-d546-5f81-b681-9b6ae6fb145f', + type: 'process', + }, + cloud_security_posture: { + package_policy: { + id: '16aec061-cb66-472d-956a-51a23bcea333', + revision: 3, + }, + }, + elastic_agent: { + id: 'f6d3cea6-7893-45cf-aba9-4961bf955853', + version: '8.12.0', + snapshot: false, + }, + rule: { + references: '', + impact: '', + description: 'Setup TLS connection on the Kubelets.', + section: 'Kubelet', + default_value: '', + version: '1.0', + rationale: + 'The connections from the apiserver to the kubelet are used for fetching logs for pods, attaching (through kubectl) to running pods, and using the kubelet’s port-forwarding functionality.\nThese connections terminate at the kubelet’s HTTPS endpoint.\nBy default, the apiserver does not verify the kubelet’s serving certificate, which makes the connection subject to man-in-the-middle attacks, and unsafe to run over untrusted and/or public networks.', + benchmark: { + name: 'CIS Kubernetes V1.23', + rule_number: '4.2.10', + id: 'cis_k8s', + version: 'v1.0.1', + posture_type: 'kspm', + }, + tags: ['CIS', 'Kubernetes', 'CIS 4.2.10', 'Kubelet'], + remediation: + 'If using a Kubelet config file, edit the file to set tlsCertFile to the location of the certificate file to use to identify this Kubelet, and tlsPrivateKeyFile to the location of the corresponding private key file.\n\nIf using command line arguments, edit the kubelet service file /etc/systemd/system/kubelet.service.d/10-kubeadm.conf on each worker node and set the below parameters in KUBELET_CERTIFICATE_ARGS variable.\n\n--tls-cert-file= --tls-private-key-file=\nBased on your system, restart the kubelet service.\nFor example:\n\n```\nsystemctl daemon-reload\nsystemctl restart kubelet.service\n```', + audit: + 'Run the following command on each node:\n\n```\nps -ef | grep kubelet\n```\n\nVerify that the --tls-cert-file and --tls-private-key-file arguments exist and they are set as appropriate.\n\nIf these arguments are not present, check that there is a Kubelet config specified by --config and that it contains appropriate settings for tlsCertFile and tlsPrivateKeyFile.', + name: 'Ensure that the --tls-cert-file and --tls-private-key-file arguments are set as appropriate', + id: 'f78dad83-1fe2-5aba-8507-64ea9efb53d6', + profile_applicability: '* Level 1 - Worker Node', + }, + message: + 'Rule "Ensure that the --tls-cert-file and --tls-private-key-file arguments are set as appropriate": failed', + result: { + evaluation: 'failed', + evidence: { + process_config: { + config: { + nodeStatusReportFrequency: '0s', + shutdownGracePeriod: '0s', + cpuManagerReconcilePeriod: '0s', + syncFrequency: '0s', + httpCheckFrequency: '0s', + clusterDomain: 'cluster.local', + healthzPort: 10248, + evictionHard: { + 'imagefs.available': '0%', + 'nodefs.available': '0%', + 'nodefs.inodesFree': '0%', + }, + nodeStatusUpdateFrequency: '0s', + staticPodPath: '/etc/kubernetes/manifests', + authorization: { + mode: 'Webhook', + webhook: { + cacheAuthorizedTTL: '0s', + cacheUnauthorizedTTL: '0s', + }, + }, + volumeStatsAggPeriod: '0s', + apiVersion: 'kubelet.config.k8s.io/v1beta1', + evictionPressureTransitionPeriod: '0s', + runtimeRequestTimeout: '0s', + clusterDNS: ['10.96.0.10'], + authentication: { + x509: { + clientCAFile: '/etc/kubernetes/pki/ca.crt', + }, + webhook: { + cacheTTL: '0s', + enabled: true, + }, + anonymous: { + enabled: false, + }, + }, + memorySwap: {}, + imageMinimumGCAge: '0s', + kind: 'KubeletConfiguration', + rotateCertificates: true, + imageGCHighThresholdPercent: 100, + cgroupDriver: 'cgroupfs', + logging: { + flushFrequency: 0, + options: { + json: { + infoBufferSize: '0', + }, + }, + verbosity: 0, + }, + streamingConnectionIdleTimeout: '0s', + shutdownGracePeriodCriticalPods: '0s', + fileCheckFrequency: '0s', + healthzBindAddress: '127.0.0.1', + }, + }, + process_args: { + '--cgroup-root': '/kubelet', + '--kubeconfig': '/etc/kubernetes/kubelet.conf', + '--node-ip': '172.18.0.3', + '--node-labels': '', + '--container-runtime': 'remote', + '--/usr/bin/kubelet': '', + '--bootstrap-kubeconfig': '/etc/kubernetes/bootstrap-kubelet.conf', + '--fail-swap-on': 'false', + '--provider-id': 'kind://docker/kind-multi/kind-multi-worker', + '--pod-infra-container-image': 'k8s.gcr.io/pause:3.6', + '--container-runtime-endpoint': 'unix:///run/containerd/containerd.sock', + '--config': '/var/lib/kubelet/config.yaml', + }, + }, + expected: null, + }, + orchestrator: { + cluster: { + name: 'kind-multi', + id: '80b085f5-c172-4143-a74d-4b881c51edb2', + version: 'v1.23.12', + }, + }, + cluster_id: '80b085f5-c172-4143-a74d-4b881c51edb2', + '@timestamp': timeOneHourAgo, + cloudbeat: { + commit_sha: 'b23ca39a0ca08aa94f8e4a644517bb73f3cf1f66', + commit_time: '2024-01-11T09:14:22Z', + version: '8.12.0', + policy: { + commit_sha: 'b23ca39a0ca08aa94f8e4a644517bb73f3cf1f66', + commit_time: '2024-01-11T09:14:22Z', + version: '8.12.0', + }, + }, + ecs: { + version: '8.6.0', + }, + data_stream: { + namespace: 'default', + type: 'logs', + dataset: 'cloud_security_posture.findings', + }, + host: { + name: 'kind-multi-worker', + }, + event: { + agent_id_status: 'auth_metadata_missing', + sequence: 1706449528, + ingested: '2024-01-28T13:49:31Z', + created: '2024-01-28T13:45:28.365459493Z', + kind: 'state', + id: '126158e6-1c6f-4087-83a7-59108f116e67', + type: ['info'], + category: ['configuration'], + dataset: 'cloud_security_posture.findings', + outcome: 'success', + }, + }, +]; 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 0f8891dcb3b94..8ab9afd818146 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 @@ -139,6 +139,48 @@ export function RulePagePageProvider({ getService, getPageObjects }: FtrProvider ); await takeActionOption.click(); }, + + getCountersEmptyState: async () => { + return await testSubjects.exists('rules-counters-empty-state'); + }, + + getPostureScoreCounter: async () => { + return await testSubjects.find('rules-counters-posture-score-counter'); + }, + + clickPostureScoreButton: async () => { + const postureScoreButton = await testSubjects.find('rules-counters-posture-score-button'); + await postureScoreButton.click(); + }, + + getIntegrationsEvaluatedCounter: async () => { + return await testSubjects.find('rules-counters-integrations-evaluated-counter'); + }, + + clickIntegrationsEvaluatedButton: async () => { + const integrationsEvaluatedButton = await testSubjects.find( + 'rules-counters-integrations-evaluated-button' + ); + await integrationsEvaluatedButton.click(); + }, + + getFailedFindingsCounter: async () => { + return await testSubjects.find('rules-counters-failed-findings-counter'); + }, + + clickFailedFindingsButton: async () => { + const failedFindingsButton = await testSubjects.find('rules-counters-failed-findings-button'); + await failedFindingsButton.click(); + }, + + getDisabledRulesCounter: async () => { + return await testSubjects.find('rules-counters-disabled-rules-counter'); + }, + + clickDisabledRulesButton: async () => { + const disabledRulesButton = await testSubjects.find('rules-counters-disabled-rules-button'); + await disabledRulesButton.click(); + }, }; 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 1dec5a494dc2a..81629da46439c 100644 --- a/x-pack/test/cloud_security_posture_functional/pages/rules.ts +++ b/x-pack/test/cloud_security_posture_functional/pages/rules.ts @@ -13,6 +13,7 @@ import { RULES_BULK_ACTION_OPTION_DISABLE, RULES_BULK_ACTION_OPTION_ENABLE, } from '../page_objects/rule_page'; +import { k8sFindingsMock } from '../mocks/latest_findings_mock'; // eslint-disable-next-line import/no-default-export export default function ({ getPageObjects, getService }: FtrProviderContext) { @@ -31,11 +32,13 @@ export default function ({ getPageObjects, getService }: FtrProviderContext) { describe.skip('Cloud Posture Rules Page', function () { this.tags(['cloud_security_posture_rules_page']); let rule: typeof pageObjects.rule; - + let findings: typeof pageObjects.findings; let agentPolicyId: string; beforeEach(async () => { rule = pageObjects.rule; + findings = pageObjects.findings; + await kibanaServer.savedObjects.cleanStandardList(); await esArchiver.load('x-pack/test/functional/es_archives/fleet/empty_fleet_server'); @@ -59,12 +62,14 @@ export default function ({ getPageObjects, getService }: FtrProviderContext) { 'kspm' ); await rule.waitForPluginInitialized(); + await findings.index.add(k8sFindingsMock); await rule.navigateToRulePage('cis_k8s', '1.0.1'); }); afterEach(async () => { await kibanaServer.savedObjects.cleanStandardList(); await esArchiver.unload('x-pack/test/functional/es_archives/fleet/empty_fleet_server'); + await findings.index.remove(); }); // FLAKY: https://github.com/elastic/kibana/issues/175614 @@ -181,5 +186,78 @@ export default function ({ getPageObjects, getService }: FtrProviderContext) { expect((await rule.rulePage.getEnableSwitchButtonState()) === 'true').to.be(true); }); }); + + describe('Rules Page - Rules Counters', () => { + it('Shows posture score when there are findings', async () => { + const isEmptyStateVisible = await rule.rulePage.getCountersEmptyState(); + expect(isEmptyStateVisible).to.be(false); + + const postureScoreCounter = await rule.rulePage.getPostureScoreCounter(); + expect((await postureScoreCounter.getVisibleText()).includes('33%')).to.be(true); + }); + + it('Clicking the posture score button leads to the dashboard', async () => { + await rule.rulePage.clickPostureScoreButton(); + await pageObjects.common.waitUntilUrlIncludes('cloud_security_posture/dashboard'); + }); + + it('Shows integrations count when there are findings', async () => { + const integrationsCounter = await rule.rulePage.getIntegrationsEvaluatedCounter(); + expect((await integrationsCounter.getVisibleText()).includes('1')).to.be(true); + }); + + it('Clicking the integrations counter button leads to the integration page', async () => { + await rule.rulePage.clickIntegrationsEvaluatedButton(); + await pageObjects.common.waitUntilUrlIncludes( + 'cloud_security_posture/add-integration/kspm' + ); + }); + + it('Shows the failed findings counter when there are findings', async () => { + const failedFindingsCounter = await rule.rulePage.getFailedFindingsCounter(); + expect((await failedFindingsCounter.getVisibleText()).includes('2')).to.be(true); + }); + + it('Clicking the failed findings button leads to the findings page', async () => { + await rule.rulePage.clickFailedFindingsButton(); + await pageObjects.common.waitUntilUrlIncludes( + 'cloud_security_posture/findings/configurations' + ); + }); + + it('Shows the disabled rules count', async () => { + const disabledRulesCounter = await rule.rulePage.getDisabledRulesCounter(); + expect((await disabledRulesCounter.getVisibleText()).includes('0')).to.be(true); + + // disable rule 1.1.1 (k8s findings mock contains a findings from that rule) + await rule.rulePage.clickEnableRulesRowSwitchButton(0); + await pageObjects.header.waitUntilLoadingHasFinished(); + expect((await disabledRulesCounter.getVisibleText()).includes('1')).to.be(true); + + const postureScoreCounter = await rule.rulePage.getPostureScoreCounter(); + expect((await postureScoreCounter.getVisibleText()).includes('0%')).to.be(true); + + // enable rule back + await rule.rulePage.clickEnableRulesRowSwitchButton(0); + }); + + it('Clicking the disabled rules button shows enables the disabled filter', async () => { + await rule.rulePage.clickEnableRulesRowSwitchButton(0); + await pageObjects.header.waitUntilLoadingHasFinished(); + + await rule.rulePage.clickDisabledRulesButton(); + await pageObjects.header.waitUntilLoadingHasFinished(); + expect((await rule.rulePage.getEnableRulesRowSwitchButton()) === 1).to.be(true); + }); + + it('Shows empty state when there are no findings', async () => { + // Ensure there are no findings initially + await findings.index.remove(); + await rule.navigateToRulePage('cis_k8s', '1.0.1'); + + const isEmptyStateVisible = await rule.rulePage.getCountersEmptyState(); + expect(isEmptyStateVisible).to.be(true); + }); + }); }); } diff --git a/x-pack/test_serverless/api_integration/test_suites/security/cloud_security_posture/benchmark/v2.ts b/x-pack/test_serverless/api_integration/test_suites/security/cloud_security_posture/benchmark/v2.ts index dce12caae3769..a6e856ea0129e 100644 --- a/x-pack/test_serverless/api_integration/test_suites/security/cloud_security_posture/benchmark/v2.ts +++ b/x-pack/test_serverless/api_integration/test_suites/security/cloud_security_posture/benchmark/v2.ts @@ -125,7 +125,7 @@ export default function ({ getService }: FtrProviderContext) { .set('kbn-xsrf', 'xxxx') .expect(200); - expect(res.items.length).equal(3); + expect(res.items.length).equal(5); }); }); }