From 0b92c268f9e862812a4c2ab4ba50a3b90bc87a65 Mon Sep 17 00:00:00 2001 From: Rickyanto Ang Date: Sat, 5 Oct 2024 02:28:18 +0700 Subject: [PATCH] [Cloud Security] Vulnerabilities Preview & Refactor CSP Plugin PHASE 2 (#193638) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit In an attempt to make Reviewing easier and more accurate, the implementation of Vulnerabilities on Host.name flyout in Alerts Page will be split into 2 Phases Phase 1: Move Functions, Utils or Helpers, Hooks, constants to Package Phase 2: Implementing the feature This is Phase 2 Screenshot 2024-09-20 at 5 33 01 PM --------- Co-authored-by: kibanamachine <42973632+kibanamachine@users.noreply.github.com> Co-authored-by: Maxim Kholod --- .github/CODEOWNERS | 1 + .../constants.ts | 10 + .../utils/helpers.test.ts | 4 +- .../utils/helpers.ts | 8 +- .../kbn-cloud-security-posture/index.ts | 2 + .../hooks/use_misconfiguration_findings.ts | 4 +- .../src/hooks/use_misconfiguration_preview.ts | 4 +- .../src/hooks/use_vulnerabilities_preview.ts | 90 ++++++++ .../utils/get_vulnerabilitiy_colors.test.ts | 30 +++ .../src/utils/get_vulnerability_colors.ts | 25 ++ .../src/utils/get_vulnerability_text.test.ts | 29 +++ .../src/utils/get_vulnerability_text.ts | 24 ++ .../src/utils/hooks_utils.test.ts | 59 +++++ .../src/utils/hooks_utils.ts | 66 +++++- .../kbn-cloud-security-posture/type.ts | 4 +- .../common/constants.ts | 10 +- .../common/utils/get_vulnerability_colors.ts | 17 -- .../utils/get_vulnerabiltity_colors.test.ts | 24 +- .../components/vulnerability_badges.tsx | 3 +- .../components/vulnerability_severity_map.tsx | 2 +- .../use_latest_vulnerabilities_grouping.tsx | 6 +- ...reate_detection_rule_from_vulnerability.ts | 8 +- .../vulnerability_statistics.tsx | 4 +- .../vulnerability_trend_graph.tsx | 4 +- .../get_vulnerabilities_statistics.ts | 6 +- .../server/tasks/findings_stats_task.ts | 6 +- .../components/index.tsx | 27 ++- .../misconfiguration_preview.test.tsx | 1 - .../vulnerabilities_preview.test.tsx | 27 +++ .../vulnerabilities_preview.tsx | 215 ++++++++++++++++++ .../vulnerabilities_contextual_flyout.cy.ts | 195 ++++++++++++++++ .../cypress/tsconfig.json | 1 + 32 files changed, 830 insertions(+), 86 deletions(-) create mode 100644 x-pack/packages/kbn-cloud-security-posture/src/hooks/use_vulnerabilities_preview.ts create mode 100644 x-pack/packages/kbn-cloud-security-posture/src/utils/get_vulnerabilitiy_colors.test.ts create mode 100644 x-pack/packages/kbn-cloud-security-posture/src/utils/get_vulnerability_colors.ts create mode 100644 x-pack/packages/kbn-cloud-security-posture/src/utils/get_vulnerability_text.test.ts create mode 100644 x-pack/packages/kbn-cloud-security-posture/src/utils/get_vulnerability_text.ts create mode 100644 x-pack/packages/kbn-cloud-security-posture/src/utils/hooks_utils.test.ts create mode 100644 x-pack/plugins/security_solution/public/cloud_security_posture/components/vulnerabilities/vulnerabilities_preview.test.tsx create mode 100644 x-pack/plugins/security_solution/public/cloud_security_posture/components/vulnerabilities/vulnerabilities_preview.tsx create mode 100644 x-pack/test/security_solution_cypress/cypress/e2e/investigations/alerts/expandable_flyout/vulnerabilities_contextual_flyout.cy.ts diff --git a/.github/CODEOWNERS b/.github/CODEOWNERS index e8d82c1388cda..66d55e6b7e7b9 100644 --- a/.github/CODEOWNERS +++ b/.github/CODEOWNERS @@ -1777,6 +1777,7 @@ x-pack/plugins/osquery @elastic/security-defend-workflows /x-pack/plugins/fleet/public/applications/fleet/sections/agent_policy/create_package_policy_page/single_page_layout/components/cloud_security_posture @elastic/fleet @elastic/kibana-cloud-security-posture /x-pack/plugins/fleet/public/applications/fleet/sections/agent_policy/create_package_policy_page/single_page_layout/hooks/setup_technology.* @elastic/fleet @elastic/kibana-cloud-security-posture /x-pack/plugins/security_solution/public/cloud_security_posture @elastic/kibana-cloud-security-posture +/x-pack/test/security_solution_cypress/cypress/e2e/explore/hosts/vulnerabilities_contextual_flyout.cy.ts @elastic/kibana-cloud-security-posture # Security Solution onboarding tour /x-pack/plugins/security_solution/public/common/components/guided_onboarding @elastic/security-threat-hunting-explore diff --git a/x-pack/packages/kbn-cloud-security-posture-common/constants.ts b/x-pack/packages/kbn-cloud-security-posture-common/constants.ts index 0e8b1b3e16cfa..7ff50efdd9489 100644 --- a/x-pack/packages/kbn-cloud-security-posture-common/constants.ts +++ b/x-pack/packages/kbn-cloud-security-posture-common/constants.ts @@ -4,6 +4,8 @@ * 2.0; you may not use this file except in compliance with the Elastic License * 2.0. */ +import { VulnSeverity } from './types/vulnerabilities'; + export const KSPM_POLICY_TEMPLATE = 'kspm'; export const CSPM_POLICY_TEMPLATE = 'cspm'; export const CDR_LATEST_NATIVE_MISCONFIGURATIONS_INDEX_PATTERN = @@ -33,3 +35,11 @@ export const CDR_LATEST_THIRD_PARTY_VULNERABILITIES_INDEX_PATTERN = 'security_solution-*.vulnerability_latest'; export const CDR_VULNERABILITIES_INDEX_PATTERN = `${CDR_LATEST_THIRD_PARTY_VULNERABILITIES_INDEX_PATTERN},${CDR_LATEST_NATIVE_VULNERABILITIES_INDEX_PATTERN}`; export const LATEST_VULNERABILITIES_RETENTION_POLICY = '3d'; + +export const VULNERABILITIES_SEVERITY: Record = { + LOW: 'LOW', + MEDIUM: 'MEDIUM', + HIGH: 'HIGH', + CRITICAL: 'CRITICAL', + UNKNOWN: 'UNKNOWN', +}; diff --git a/x-pack/packages/kbn-cloud-security-posture-common/utils/helpers.test.ts b/x-pack/packages/kbn-cloud-security-posture-common/utils/helpers.test.ts index d920a8dc25165..0248cdf9b6e36 100644 --- a/x-pack/packages/kbn-cloud-security-posture-common/utils/helpers.test.ts +++ b/x-pack/packages/kbn-cloud-security-posture-common/utils/helpers.test.ts @@ -153,7 +153,7 @@ describe('test helper methods', () => { filter: [ { bool: { - should: [{ term: { 'host.name': { value: 'exampleHost' } } }], + should: [{ term: { 'host.name': 'exampleHost' } }], minimum_should_match: 1, }, }, @@ -171,7 +171,7 @@ describe('test helper methods', () => { filter: [ { bool: { - should: [{ term: { 'host.name': { value: '' } } }], + should: [{ term: { 'host.name': '' } }], minimum_should_match: 1, }, }, diff --git a/x-pack/packages/kbn-cloud-security-posture-common/utils/helpers.ts b/x-pack/packages/kbn-cloud-security-posture-common/utils/helpers.ts index 1c593fcebf545..7039c99af6d53 100644 --- a/x-pack/packages/kbn-cloud-security-posture-common/utils/helpers.ts +++ b/x-pack/packages/kbn-cloud-security-posture-common/utils/helpers.ts @@ -48,7 +48,13 @@ export const buildEntityFlyoutPreviewQuery = (field: string, queryValue?: string filter: [ { bool: { - should: [{ term: { [field]: { value: `${queryValue || ''}` } } }], + should: [ + { + term: { + [field]: `${queryValue || ''}`, + }, + }, + ], minimum_should_match: 1, }, }, diff --git a/x-pack/packages/kbn-cloud-security-posture/index.ts b/x-pack/packages/kbn-cloud-security-posture/index.ts index 895624ba805e7..73b77376db46c 100644 --- a/x-pack/packages/kbn-cloud-security-posture/index.ts +++ b/x-pack/packages/kbn-cloud-security-posture/index.ts @@ -12,3 +12,5 @@ export type { NavFilter } from './src/hooks/use_navigate_findings'; export { showErrorToast } from './src/utils/show_error_toast'; export { encodeQuery, decodeQuery } from './src/utils/query_utils'; export { CspEvaluationBadge } from './src/components/csp_evaluation_badge'; +export { getSeverityStatusColor } from './src/utils/get_vulnerability_colors'; +export { getSeverityText } from './src/utils/get_vulnerability_text'; diff --git a/x-pack/packages/kbn-cloud-security-posture/src/hooks/use_misconfiguration_findings.ts b/x-pack/packages/kbn-cloud-security-posture/src/hooks/use_misconfiguration_findings.ts index aee35ca602ef8..fe25224cf417a 100644 --- a/x-pack/packages/kbn-cloud-security-posture/src/hooks/use_misconfiguration_findings.ts +++ b/x-pack/packages/kbn-cloud-security-posture/src/hooks/use_misconfiguration_findings.ts @@ -14,7 +14,7 @@ import type { CspClientPluginStartDeps, LatestFindingsRequest, LatestFindingsResponse, - UseMisconfigurationOptions, + UseCspOptions, } from '../../type'; import { useGetCspBenchmarkRulesStatesApi } from './use_get_benchmark_rules_state_api'; @@ -23,7 +23,7 @@ import { getMisconfigurationAggregationCount, } from '../utils/hooks_utils'; -export const useMisconfigurationFindings = (options: UseMisconfigurationOptions) => { +export const useMisconfigurationFindings = (options: UseCspOptions) => { const { data, notifications: { toasts }, diff --git a/x-pack/packages/kbn-cloud-security-posture/src/hooks/use_misconfiguration_preview.ts b/x-pack/packages/kbn-cloud-security-posture/src/hooks/use_misconfiguration_preview.ts index 9828bb32c7752..4711cd752ee5f 100644 --- a/x-pack/packages/kbn-cloud-security-posture/src/hooks/use_misconfiguration_preview.ts +++ b/x-pack/packages/kbn-cloud-security-posture/src/hooks/use_misconfiguration_preview.ts @@ -13,7 +13,7 @@ import type { CspClientPluginStartDeps, LatestFindingsRequest, LatestFindingsResponse, - UseMisconfigurationOptions, + UseCspOptions, } from '../../type'; import { useGetCspBenchmarkRulesStatesApi } from './use_get_benchmark_rules_state_api'; import { @@ -21,7 +21,7 @@ import { getMisconfigurationAggregationCount, } from '../utils/hooks_utils'; -export const useMisconfigurationPreview = (options: UseMisconfigurationOptions) => { +export const useMisconfigurationPreview = (options: UseCspOptions) => { const { data, notifications: { toasts }, diff --git a/x-pack/packages/kbn-cloud-security-posture/src/hooks/use_vulnerabilities_preview.ts b/x-pack/packages/kbn-cloud-security-posture/src/hooks/use_vulnerabilities_preview.ts new file mode 100644 index 0000000000000..00ca9691b013f --- /dev/null +++ b/x-pack/packages/kbn-cloud-security-posture/src/hooks/use_vulnerabilities_preview.ts @@ -0,0 +1,90 @@ +/* + * 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 { useQuery } from '@tanstack/react-query'; +import { useKibana } from '@kbn/kibana-react-plugin/public'; +import { lastValueFrom } from 'rxjs'; +import type { IKibanaSearchResponse, IKibanaSearchRequest } from '@kbn/search-types'; +import { + SearchRequest, + SearchResponse, + AggregationsMultiBucketAggregateBase, + AggregationsStringRareTermsBucketKeys, +} from '@elastic/elasticsearch/lib/api/typesWithBodyKey'; +import { + CDR_VULNERABILITIES_INDEX_PATTERN, + LATEST_VULNERABILITIES_RETENTION_POLICY, +} from '@kbn/cloud-security-posture-common'; +import type { CspVulnerabilityFinding } from '@kbn/cloud-security-posture-common/schema/vulnerabilities/latest'; +import type { CoreStart } from '@kbn/core/public'; +import type { CspClientPluginStartDeps, UseCspOptions } from '../../type'; +import { showErrorToast } from '../..'; +import { + getFindingsCountAggQueryVulnerabilities, + getVulnerabilitiesAggregationCount, +} from '../utils/hooks_utils'; + +type LatestFindingsRequest = IKibanaSearchRequest; +type LatestFindingsResponse = IKibanaSearchResponse< + SearchResponse +>; + +interface FindingsAggs { + count: AggregationsMultiBucketAggregateBase; +} + +const getVulnerabilitiesQuery = ({ query }: UseCspOptions, isPreview = false) => ({ + index: CDR_VULNERABILITIES_INDEX_PATTERN, + size: 0, + aggs: getFindingsCountAggQueryVulnerabilities(), + ignore_unavailable: true, + query: { + ...query, + bool: { + ...query?.bool, + filter: [ + ...(query?.bool?.filter ?? []), + { + range: { + '@timestamp': { + gte: `now-${LATEST_VULNERABILITIES_RETENTION_POLICY}`, + lte: 'now', + }, + }, + }, + ], + }, + }, +}); + +export const useVulnerabilitiesPreview = (options: UseCspOptions) => { + const { + data, + notifications: { toasts }, + } = useKibana().services; + + return useQuery( + ['csp_vulnerabilities_preview', { params: options }], + async () => { + const { + rawResponse: { aggregations }, + } = await lastValueFrom( + data.search.search({ + params: getVulnerabilitiesQuery(options), + }) + ); + + return { + count: getVulnerabilitiesAggregationCount(aggregations?.count?.buckets), + }; + }, + { + keepPreviousData: true, + enabled: options.enabled, + onError: (err: Error) => showErrorToast(toasts, err), + } + ); +}; diff --git a/x-pack/packages/kbn-cloud-security-posture/src/utils/get_vulnerabilitiy_colors.test.ts b/x-pack/packages/kbn-cloud-security-posture/src/utils/get_vulnerabilitiy_colors.test.ts new file mode 100644 index 0000000000000..0516faa7e83f7 --- /dev/null +++ b/x-pack/packages/kbn-cloud-security-posture/src/utils/get_vulnerabilitiy_colors.test.ts @@ -0,0 +1,30 @@ +/* + * 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 { euiThemeVars } from '@kbn/ui-theme'; +import { getSeverityStatusColor } from './get_vulnerability_colors'; +describe('getSeverityStatusColor', () => { + it('should return the correct color for LOW severity', () => { + expect(getSeverityStatusColor('LOW')).toBe(euiThemeVars.euiColorVis0); + }); + + it('should return the correct color for MEDIUM severity', () => { + expect(getSeverityStatusColor('MEDIUM')).toBe(euiThemeVars.euiColorVis5_behindText); + }); + + it('should return the correct color for HIGH severity', () => { + expect(getSeverityStatusColor('HIGH')).toBe(euiThemeVars.euiColorVis9_behindText); + }); + + it('should return the correct color for CRITICAL severity', () => { + expect(getSeverityStatusColor('CRITICAL')).toBe(euiThemeVars.euiColorDanger); + }); + + it('should return #aaa for an unknown severity', () => { + expect(getSeverityStatusColor('UNKNOWN')).toBe('#aaa'); + }); +}); diff --git a/x-pack/packages/kbn-cloud-security-posture/src/utils/get_vulnerability_colors.ts b/x-pack/packages/kbn-cloud-security-posture/src/utils/get_vulnerability_colors.ts new file mode 100644 index 0000000000000..7e651f790fd80 --- /dev/null +++ b/x-pack/packages/kbn-cloud-security-posture/src/utils/get_vulnerability_colors.ts @@ -0,0 +1,25 @@ +/* + * 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 { euiThemeVars } from '@kbn/ui-theme'; +import type { VulnSeverity } from '@kbn/cloud-security-posture-common'; +import { VULNERABILITIES_SEVERITY } from '@kbn/cloud-security-posture-common'; + +export const getSeverityStatusColor = (severity: VulnSeverity): string => { + switch (severity) { + case VULNERABILITIES_SEVERITY.LOW: + return euiThemeVars.euiColorVis0; + case VULNERABILITIES_SEVERITY.MEDIUM: + return euiThemeVars.euiColorVis5_behindText; + case VULNERABILITIES_SEVERITY.HIGH: + return euiThemeVars.euiColorVis9_behindText; + case VULNERABILITIES_SEVERITY.CRITICAL: + return euiThemeVars.euiColorDanger; + default: + return '#aaa'; + } +}; diff --git a/x-pack/packages/kbn-cloud-security-posture/src/utils/get_vulnerability_text.test.ts b/x-pack/packages/kbn-cloud-security-posture/src/utils/get_vulnerability_text.test.ts new file mode 100644 index 0000000000000..68a07d177c664 --- /dev/null +++ b/x-pack/packages/kbn-cloud-security-posture/src/utils/get_vulnerability_text.test.ts @@ -0,0 +1,29 @@ +/* + * 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 { getSeverityText } from './get_vulnerability_text'; +describe('getSeverityStatusColor', () => { + it('should return the correct color for LOW severity', () => { + expect(getSeverityText('LOW')).toBe('Low'); + }); + + it('should return the correct color for MEDIUM severity', () => { + expect(getSeverityText('MEDIUM')).toBe('Medium'); + }); + + it('should return the correct color for HIGH severity', () => { + expect(getSeverityText('HIGH')).toBe('High'); + }); + + it('should return the correct color for CRITICAL severity', () => { + expect(getSeverityText('CRITICAL')).toBe('Critical'); + }); + + it('should return #aaa for an unknown severity', () => { + expect(getSeverityText('UNKNOWN')).toBe('None'); + }); +}); diff --git a/x-pack/packages/kbn-cloud-security-posture/src/utils/get_vulnerability_text.ts b/x-pack/packages/kbn-cloud-security-posture/src/utils/get_vulnerability_text.ts new file mode 100644 index 0000000000000..c2042f5c6fd78 --- /dev/null +++ b/x-pack/packages/kbn-cloud-security-posture/src/utils/get_vulnerability_text.ts @@ -0,0 +1,24 @@ +/* + * 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 type { VulnSeverity } from '@kbn/cloud-security-posture-common'; +import { VULNERABILITIES_SEVERITY } from '@kbn/cloud-security-posture-common'; + +export const getSeverityText = (severity: VulnSeverity): string => { + switch (severity) { + case VULNERABILITIES_SEVERITY.LOW: + return 'Low'; + case VULNERABILITIES_SEVERITY.MEDIUM: + return 'Medium'; + case VULNERABILITIES_SEVERITY.HIGH: + return 'High'; + case VULNERABILITIES_SEVERITY.CRITICAL: + return 'Critical'; + default: + return 'None'; + } +}; diff --git a/x-pack/packages/kbn-cloud-security-posture/src/utils/hooks_utils.test.ts b/x-pack/packages/kbn-cloud-security-posture/src/utils/hooks_utils.test.ts new file mode 100644 index 0000000000000..86e8f3c8f668b --- /dev/null +++ b/x-pack/packages/kbn-cloud-security-posture/src/utils/hooks_utils.test.ts @@ -0,0 +1,59 @@ +/* + * 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 type * as estypes from '@elastic/elasticsearch/lib/api/typesWithBodyKey'; +import { + AggregationBuckets, + getVulnerabilitiesAggregationCount, + VULNERABILITIES_RESULT_EVALUATION, +} from './hooks_utils'; + +describe('getVulnerabilitiesAggregationCount', () => { + it('should return default counts when nothing is provided', () => { + const result = { + [VULNERABILITIES_RESULT_EVALUATION.LOW]: 0, + [VULNERABILITIES_RESULT_EVALUATION.MEDIUM]: 0, + [VULNERABILITIES_RESULT_EVALUATION.HIGH]: 0, + [VULNERABILITIES_RESULT_EVALUATION.CRITICAL]: 0, + [VULNERABILITIES_RESULT_EVALUATION.NONE]: 0, + }; + expect(getVulnerabilitiesAggregationCount()).toEqual(result); + }); + + it('should return default counts when empty bucket is provided', () => { + const result = { + [VULNERABILITIES_RESULT_EVALUATION.LOW]: 0, + [VULNERABILITIES_RESULT_EVALUATION.MEDIUM]: 0, + [VULNERABILITIES_RESULT_EVALUATION.HIGH]: 0, + [VULNERABILITIES_RESULT_EVALUATION.CRITICAL]: 0, + [VULNERABILITIES_RESULT_EVALUATION.NONE]: 0, + }; + expect(getVulnerabilitiesAggregationCount({})).toEqual(result); + }); + + it('should return counts when provided with non empty buckets', () => { + const buckets: AggregationBuckets = { + [VULNERABILITIES_RESULT_EVALUATION.LOW]: { doc_count: 1 }, + [VULNERABILITIES_RESULT_EVALUATION.MEDIUM]: { doc_count: 2 }, + [VULNERABILITIES_RESULT_EVALUATION.HIGH]: { doc_count: 3 }, + [VULNERABILITIES_RESULT_EVALUATION.CRITICAL]: { doc_count: 4 }, + [VULNERABILITIES_RESULT_EVALUATION.NONE]: { doc_count: 5 }, + }; + + const vulnerabilitiesAggregrationCount = getVulnerabilitiesAggregationCount( + buckets as estypes.AggregationsBuckets + ); + const result = { + [VULNERABILITIES_RESULT_EVALUATION.LOW]: 1, + [VULNERABILITIES_RESULT_EVALUATION.MEDIUM]: 2, + [VULNERABILITIES_RESULT_EVALUATION.HIGH]: 3, + [VULNERABILITIES_RESULT_EVALUATION.CRITICAL]: 4, + [VULNERABILITIES_RESULT_EVALUATION.NONE]: 5, + }; + expect(vulnerabilitiesAggregrationCount).toEqual(result); + }); +}); diff --git a/x-pack/packages/kbn-cloud-security-posture/src/utils/hooks_utils.ts b/x-pack/packages/kbn-cloud-security-posture/src/utils/hooks_utils.ts index a1951f7327b40..d99fac8d6d96e 100644 --- a/x-pack/packages/kbn-cloud-security-posture/src/utils/hooks_utils.ts +++ b/x-pack/packages/kbn-cloud-security-posture/src/utils/hooks_utils.ts @@ -12,14 +12,14 @@ import { } from '@kbn/cloud-security-posture-common'; import type { CspBenchmarkRulesStates } from '@kbn/cloud-security-posture-common/schema/rules/latest'; import { buildMutedRulesFilter } from '@kbn/cloud-security-posture-common'; -import type { UseMisconfigurationOptions } from '../../type'; +import type { UseCspOptions } from '../../type'; const MISCONFIGURATIONS_SOURCE_FIELDS = ['result.*', 'rule.*', 'resource.*']; interface AggregationBucket { doc_count?: number; } -type AggregationBuckets = Record; +export type AggregationBuckets = Record; const RESULT_EVALUATION = { PASSED: 'passed', @@ -27,6 +27,14 @@ const RESULT_EVALUATION = { UNKNOWN: 'unknown', }; +export const VULNERABILITIES_RESULT_EVALUATION = { + LOW: 'LOW', + MEDIUM: 'MEDIUM', + HIGH: 'HIGH', + CRITICAL: 'CRITICAL', + NONE: 'NONE', +}; + export const getFindingsCountAggQueryMisconfiguration = () => ({ count: { filters: { @@ -64,7 +72,7 @@ export const getMisconfigurationAggregationCount = ( }; export const buildMisconfigurationsFindingsQuery = ( - { query }: UseMisconfigurationOptions, + { query }: UseCspOptions, rulesStates: CspBenchmarkRulesStates, isPreview = false ) => { @@ -81,7 +89,7 @@ export const buildMisconfigurationsFindingsQuery = ( }; const buildMisconfigurationsFindingsQueryWithFilters = ( - query: UseMisconfigurationOptions['query'], + query: UseCspOptions['query'], mutedRulesFilterQuery: estypes.QueryDslQueryContainer[] ) => { return { @@ -103,3 +111,53 @@ const buildMisconfigurationsFindingsQueryWithFilters = ( }, }; }; + +export const getVulnerabilitiesAggregationCount = ( + buckets?: estypes.AggregationsBuckets +) => { + const defaultBuckets: AggregationBuckets = { + [VULNERABILITIES_RESULT_EVALUATION.LOW]: { doc_count: 0 }, + [VULNERABILITIES_RESULT_EVALUATION.MEDIUM]: { doc_count: 0 }, + [VULNERABILITIES_RESULT_EVALUATION.HIGH]: { doc_count: 0 }, + [VULNERABILITIES_RESULT_EVALUATION.CRITICAL]: { doc_count: 0 }, + [VULNERABILITIES_RESULT_EVALUATION.NONE]: { doc_count: 0 }, + }; + + // if buckets are undefined we will use default buckets + const usedBuckets = buckets || defaultBuckets; + return Object.entries(usedBuckets).reduce( + (evaluation, [key, value]) => { + evaluation[key] = (evaluation[key] || 0) + (value.doc_count || 0); + return evaluation; + }, + { + [VULNERABILITIES_RESULT_EVALUATION.LOW]: 0, + [VULNERABILITIES_RESULT_EVALUATION.MEDIUM]: 0, + [VULNERABILITIES_RESULT_EVALUATION.HIGH]: 0, + [VULNERABILITIES_RESULT_EVALUATION.CRITICAL]: 0, + [VULNERABILITIES_RESULT_EVALUATION.NONE]: 0, + } + ); +}; + +export const getFindingsCountAggQueryVulnerabilities = () => ({ + count: { + filters: { + other_bucket_key: VULNERABILITIES_RESULT_EVALUATION.NONE, + filters: { + [VULNERABILITIES_RESULT_EVALUATION.LOW]: { + match: { 'vulnerability.severity': VULNERABILITIES_RESULT_EVALUATION.LOW }, + }, + [VULNERABILITIES_RESULT_EVALUATION.MEDIUM]: { + match: { 'vulnerability.severity': VULNERABILITIES_RESULT_EVALUATION.MEDIUM }, + }, + [VULNERABILITIES_RESULT_EVALUATION.HIGH]: { + match: { 'vulnerability.severity': VULNERABILITIES_RESULT_EVALUATION.HIGH }, + }, + [VULNERABILITIES_RESULT_EVALUATION.CRITICAL]: { + match: { 'vulnerability.severity': VULNERABILITIES_RESULT_EVALUATION.CRITICAL }, + }, + }, + }, + }, +}); diff --git a/x-pack/packages/kbn-cloud-security-posture/type.ts b/x-pack/packages/kbn-cloud-security-posture/type.ts index 666d432df609d..493e58519bd91 100644 --- a/x-pack/packages/kbn-cloud-security-posture/type.ts +++ b/x-pack/packages/kbn-cloud-security-posture/type.ts @@ -55,7 +55,7 @@ export interface CspClientPluginStartDeps { usageCollection?: UsageCollectionStart; } -export interface MisconfigurationBaseEsQuery { +export interface CspBaseEsQuery { query?: { bool: { filter: estypes.QueryDslQueryContainer[]; @@ -63,7 +63,7 @@ export interface MisconfigurationBaseEsQuery { }; } -export interface UseMisconfigurationOptions extends MisconfigurationBaseEsQuery { +export interface UseCspOptions extends CspBaseEsQuery { sort: string[][]; enabled: boolean; pageSize: number; diff --git a/x-pack/plugins/cloud_security_posture/common/constants.ts b/x-pack/plugins/cloud_security_posture/common/constants.ts index e5d95b882b2e7..d415d4cfcfc69 100644 --- a/x-pack/plugins/cloud_security_posture/common/constants.ts +++ b/x-pack/plugins/cloud_security_posture/common/constants.ts @@ -6,7 +6,7 @@ */ import { KSPM_POLICY_TEMPLATE, CSPM_POLICY_TEMPLATE } from '@kbn/cloud-security-posture-common'; -import type { VulnSeverity } from '@kbn/cloud-security-posture-common'; + import { AwsCredentialsTypeFieldMap, GcpCredentialsTypeFieldMap, PostureTypes } from './types_old'; export const CLOUD_SECURITY_INTERTAL_PREFIX_ROUTE_PATH = '/internal/cloud_security_posture/'; @@ -125,14 +125,6 @@ export const POSTURE_TYPES: { [x: string]: PostureTypes } = { [POSTURE_TYPE_ALL]: POSTURE_TYPE_ALL, }; -export const VULNERABILITIES_SEVERITY: Record = { - LOW: 'LOW', - MEDIUM: 'MEDIUM', - HIGH: 'HIGH', - CRITICAL: 'CRITICAL', - UNKNOWN: 'UNKNOWN', -}; - export const AWS_CREDENTIALS_TYPE_TO_FIELDS_MAP: AwsCredentialsTypeFieldMap = { assume_role: ['role_arn'], direct_access_keys: ['access_key_id', 'secret_access_key'], diff --git a/x-pack/plugins/cloud_security_posture/public/common/utils/get_vulnerability_colors.ts b/x-pack/plugins/cloud_security_posture/public/common/utils/get_vulnerability_colors.ts index fc63ac1131faa..cba51677dd58a 100644 --- a/x-pack/plugins/cloud_security_posture/public/common/utils/get_vulnerability_colors.ts +++ b/x-pack/plugins/cloud_security_posture/public/common/utils/get_vulnerability_colors.ts @@ -6,8 +6,6 @@ */ import { euiThemeVars } from '@kbn/ui-theme'; -import type { VulnSeverity } from '@kbn/cloud-security-posture-common'; -import { VULNERABILITIES_SEVERITY } from '../../../common/constants'; export const getCvsScoreColor = (score: number): string | undefined => { if (score <= 4) { @@ -20,18 +18,3 @@ export const getCvsScoreColor = (score: number): string | undefined => { return euiThemeVars.euiColorDanger; // critical severity } }; - -export const getSeverityStatusColor = (severity: VulnSeverity): string => { - switch (severity) { - case VULNERABILITIES_SEVERITY.LOW: - return euiThemeVars.euiColorVis0; - case VULNERABILITIES_SEVERITY.MEDIUM: - return euiThemeVars.euiColorVis5_behindText; - case VULNERABILITIES_SEVERITY.HIGH: - return euiThemeVars.euiColorVis9_behindText; - case VULNERABILITIES_SEVERITY.CRITICAL: - return euiThemeVars.euiColorDanger; - default: - return '#aaa'; - } -}; diff --git a/x-pack/plugins/cloud_security_posture/public/common/utils/get_vulnerabiltity_colors.test.ts b/x-pack/plugins/cloud_security_posture/public/common/utils/get_vulnerabiltity_colors.test.ts index e276e0eeeafce..5000e14a5afc6 100644 --- a/x-pack/plugins/cloud_security_posture/public/common/utils/get_vulnerabiltity_colors.test.ts +++ b/x-pack/plugins/cloud_security_posture/public/common/utils/get_vulnerabiltity_colors.test.ts @@ -6,7 +6,7 @@ */ import { euiThemeVars } from '@kbn/ui-theme'; -import { getCvsScoreColor, getSeverityStatusColor } from './get_vulnerability_colors'; +import { getCvsScoreColor } from './get_vulnerability_colors'; describe('getCvsScoreColor', () => { it('returns correct color for low severity score', () => { @@ -29,25 +29,3 @@ describe('getCvsScoreColor', () => { expect(getCvsScoreColor(-0.2)).toBe(euiThemeVars.euiColorVis0); }); }); - -describe('getSeverityStatusColor', () => { - it('should return the correct color for LOW severity', () => { - expect(getSeverityStatusColor('LOW')).toBe(euiThemeVars.euiColorVis0); - }); - - it('should return the correct color for MEDIUM severity', () => { - expect(getSeverityStatusColor('MEDIUM')).toBe(euiThemeVars.euiColorVis5_behindText); - }); - - it('should return the correct color for HIGH severity', () => { - expect(getSeverityStatusColor('HIGH')).toBe(euiThemeVars.euiColorVis9_behindText); - }); - - it('should return the correct color for CRITICAL severity', () => { - expect(getSeverityStatusColor('CRITICAL')).toBe(euiThemeVars.euiColorDanger); - }); - - it('should return #aaa for an unknown severity', () => { - expect(getSeverityStatusColor('UNKNOWN')).toBe('#aaa'); - }); -}); diff --git a/x-pack/plugins/cloud_security_posture/public/components/vulnerability_badges.tsx b/x-pack/plugins/cloud_security_posture/public/components/vulnerability_badges.tsx index cb4fbbad83a35..7d4095b4bd662 100644 --- a/x-pack/plugins/cloud_security_posture/public/components/vulnerability_badges.tsx +++ b/x-pack/plugins/cloud_security_posture/public/components/vulnerability_badges.tsx @@ -10,7 +10,8 @@ import React from 'react'; import { css } from '@emotion/react'; import { float } from '@elastic/elasticsearch/lib/api/typesWithBodyKey'; import type { VulnSeverity } from '@kbn/cloud-security-posture-common'; -import { getCvsScoreColor, getSeverityStatusColor } from '../common/utils/get_vulnerability_colors'; +import { getSeverityStatusColor } from '@kbn/cloud-security-posture'; +import { getCvsScoreColor } from '../common/utils/get_vulnerability_colors'; import { VULNERABILITIES_CVSS_SCORE_BADGE_SUBJ } from './test_subjects'; interface CVSScoreBadgeProps { diff --git a/x-pack/plugins/cloud_security_posture/public/components/vulnerability_severity_map.tsx b/x-pack/plugins/cloud_security_posture/public/components/vulnerability_severity_map.tsx index 74341095caac2..9046fcb265a86 100644 --- a/x-pack/plugins/cloud_security_posture/public/components/vulnerability_severity_map.tsx +++ b/x-pack/plugins/cloud_security_posture/public/components/vulnerability_severity_map.tsx @@ -17,7 +17,7 @@ import { import { PaletteColorStop } from '@elastic/eui/src/components/color_picker/color_palette_picker'; import type { VulnSeverity } from '@kbn/cloud-security-posture-common'; import { i18n } from '@kbn/i18n'; -import { getSeverityStatusColor } from '../common/utils/get_vulnerability_colors'; +import { getSeverityStatusColor } from '@kbn/cloud-security-posture'; import { SeverityStatusBadge } from './vulnerability_badges'; interface Props { diff --git a/x-pack/plugins/cloud_security_posture/public/pages/vulnerabilities/hooks/use_latest_vulnerabilities_grouping.tsx b/x-pack/plugins/cloud_security_posture/public/pages/vulnerabilities/hooks/use_latest_vulnerabilities_grouping.tsx index f615ccdb4a293..516cbed0c3975 100644 --- a/x-pack/plugins/cloud_security_posture/public/pages/vulnerabilities/hooks/use_latest_vulnerabilities_grouping.tsx +++ b/x-pack/plugins/cloud_security_posture/public/pages/vulnerabilities/hooks/use_latest_vulnerabilities_grouping.tsx @@ -14,7 +14,10 @@ import { parseGroupingQuery, } from '@kbn/grouping/src'; import { useMemo } from 'react'; -import { LATEST_VULNERABILITIES_RETENTION_POLICY } from '@kbn/cloud-security-posture-common'; +import { + LATEST_VULNERABILITIES_RETENTION_POLICY, + VULNERABILITIES_SEVERITY, +} from '@kbn/cloud-security-posture-common'; import { buildEsQuery, Filter } from '@kbn/es-query'; import { LOCAL_STORAGE_VULNERABILITIES_GROUPING_KEY, @@ -22,7 +25,6 @@ import { VULNERABILITY_FIELDS, } from '../../../common/constants'; import { useDataViewContext } from '../../../common/contexts/data_view_context'; -import { VULNERABILITIES_SEVERITY } from '../../../../common/constants'; import { VulnerabilitiesGroupingAggregation, VulnerabilitiesRootGroupingAggregation, diff --git a/x-pack/plugins/cloud_security_posture/public/pages/vulnerabilities/utils/create_detection_rule_from_vulnerability.ts b/x-pack/plugins/cloud_security_posture/public/pages/vulnerabilities/utils/create_detection_rule_from_vulnerability.ts index 7e817a3d56a15..a09f9130836b2 100644 --- a/x-pack/plugins/cloud_security_posture/public/pages/vulnerabilities/utils/create_detection_rule_from_vulnerability.ts +++ b/x-pack/plugins/cloud_security_posture/public/pages/vulnerabilities/utils/create_detection_rule_from_vulnerability.ts @@ -7,12 +7,12 @@ import { HttpSetup } from '@kbn/core/public'; import { i18n } from '@kbn/i18n'; -import { LATEST_VULNERABILITIES_RETENTION_POLICY } from '@kbn/cloud-security-posture-common'; -import type { Vulnerability } from '@kbn/cloud-security-posture-common/schema/vulnerabilities/latest'; import { - VULNERABILITIES_INDEX_PATTERN, + LATEST_VULNERABILITIES_RETENTION_POLICY, VULNERABILITIES_SEVERITY, -} from '../../../../common/constants'; +} from '@kbn/cloud-security-posture-common'; +import type { Vulnerability } from '@kbn/cloud-security-posture-common/schema/vulnerabilities/latest'; +import { VULNERABILITIES_INDEX_PATTERN } from '../../../../common/constants'; import { createDetectionRule } from '../../../common/api/create_detection_rule'; const DEFAULT_RULE_RISK_SCORE = 0; diff --git a/x-pack/plugins/cloud_security_posture/public/pages/vulnerability_dashboard/vulnerability_statistics.tsx b/x-pack/plugins/cloud_security_posture/public/pages/vulnerability_dashboard/vulnerability_statistics.tsx index 6a82adcb4e05f..114f28ccfc271 100644 --- a/x-pack/plugins/cloud_security_posture/public/pages/vulnerability_dashboard/vulnerability_statistics.tsx +++ b/x-pack/plugins/cloud_security_posture/public/pages/vulnerability_dashboard/vulnerability_statistics.tsx @@ -8,11 +8,11 @@ import React, { useMemo } from 'react'; import { EuiFlexGroup, EuiFlexItem, EuiHealth } from '@elastic/eui'; import { i18n } from '@kbn/i18n'; import { useNavigateVulnerabilities } from '@kbn/cloud-security-posture/src/hooks/use_navigate_findings'; +import { VULNERABILITIES_SEVERITY } from '@kbn/cloud-security-posture-common'; +import { getSeverityStatusColor } from '@kbn/cloud-security-posture'; import { VulnCounterCard, type VulnCounterCardProps } from '../../components/vuln_counter_card'; -import { VULNERABILITIES_SEVERITY } from '../../../common/constants'; import { useVulnerabilityDashboardApi } from '../../common/api/use_vulnerability_dashboard_api'; import { CompactFormattedNumber } from '../../components/compact_formatted_number'; -import { getSeverityStatusColor } from '../../common/utils/get_vulnerability_colors'; export const VulnerabilityStatistics = () => { const navToVulnerabilities = useNavigateVulnerabilities(); diff --git a/x-pack/plugins/cloud_security_posture/public/pages/vulnerability_dashboard/vulnerability_trend_graph.tsx b/x-pack/plugins/cloud_security_posture/public/pages/vulnerability_dashboard/vulnerability_trend_graph.tsx index ca29b18822fae..ff610b640cd3f 100644 --- a/x-pack/plugins/cloud_security_posture/public/pages/vulnerability_dashboard/vulnerability_trend_graph.tsx +++ b/x-pack/plugins/cloud_security_posture/public/pages/vulnerability_dashboard/vulnerability_trend_graph.tsx @@ -21,12 +21,12 @@ import { FormattedMessage } from '@kbn/i18n-react'; import { useNavigateVulnerabilities } from '@kbn/cloud-security-posture/src/hooks/use_navigate_findings'; import type { VulnSeverity } from '@kbn/cloud-security-posture-common'; +import { VULNERABILITIES_SEVERITY } from '@kbn/cloud-security-posture-common'; +import { getSeverityStatusColor } from '@kbn/cloud-security-posture'; import { truthy } from '../../../common/utils/helpers'; import { VulnStatsTrend } from '../../../common/types_old'; import { useVulnerabilityDashboardApi } from '../../common/api/use_vulnerability_dashboard_api'; -import { getSeverityStatusColor } from '../../common/utils/get_vulnerability_colors'; import { ChartPanel } from '../../components/chart_panel'; -import { VULNERABILITIES_SEVERITY } from '../../../common/constants'; import { useKibana } from '../../common/hooks/use_kibana'; const stackAccessors: VulnSeverity[] = [ diff --git a/x-pack/plugins/cloud_security_posture/server/routes/vulnerabilities_dashboard/get_vulnerabilities_statistics.ts b/x-pack/plugins/cloud_security_posture/server/routes/vulnerabilities_dashboard/get_vulnerabilities_statistics.ts index 905810f1efc01..e4c8bbaf1e6b1 100644 --- a/x-pack/plugins/cloud_security_posture/server/routes/vulnerabilities_dashboard/get_vulnerabilities_statistics.ts +++ b/x-pack/plugins/cloud_security_posture/server/routes/vulnerabilities_dashboard/get_vulnerabilities_statistics.ts @@ -7,8 +7,10 @@ import { SearchRequest } from '@elastic/elasticsearch/lib/api/types'; import { ElasticsearchClient } from '@kbn/core-elasticsearch-server'; -import { CDR_LATEST_NATIVE_VULNERABILITIES_INDEX_PATTERN } from '@kbn/cloud-security-posture-common'; -import { VULNERABILITIES_SEVERITY } from '../../../common/constants'; +import { + CDR_LATEST_NATIVE_VULNERABILITIES_INDEX_PATTERN, + VULNERABILITIES_SEVERITY, +} from '@kbn/cloud-security-posture-common'; export interface VulnerabilitiesStatisticsQueryResult { critical: { diff --git a/x-pack/plugins/cloud_security_posture/server/tasks/findings_stats_task.ts b/x-pack/plugins/cloud_security_posture/server/tasks/findings_stats_task.ts index 1f8614c6d4a5e..673ec5cb9f7a9 100644 --- a/x-pack/plugins/cloud_security_posture/server/tasks/findings_stats_task.ts +++ b/x-pack/plugins/cloud_security_posture/server/tasks/findings_stats_task.ts @@ -14,7 +14,10 @@ import { import type { SearchRequest } from '@elastic/elasticsearch/lib/api/types'; import { ElasticsearchClient } from '@kbn/core/server'; import { QueryDslQueryContainer } from '@kbn/data-views-plugin/common/types'; -import { CDR_LATEST_NATIVE_VULNERABILITIES_INDEX_PATTERN } from '@kbn/cloud-security-posture-common'; +import { + CDR_LATEST_NATIVE_VULNERABILITIES_INDEX_PATTERN, + VULNERABILITIES_SEVERITY, +} from '@kbn/cloud-security-posture-common'; import type { ISavedObjectsRepository, Logger } from '@kbn/core/server'; import { getMutedRulesFilterQuery } from '../routes/benchmark_rules/get_states/v1'; import { getSafePostureTypeRuntimeMapping } from '../../common/runtime_mappings/get_safe_posture_type_runtime_mapping'; @@ -25,7 +28,6 @@ import { CSPM_FINDINGS_STATS_INTERVAL, INTERNAL_CSP_SETTINGS_SAVED_OBJECT_TYPE, LATEST_FINDINGS_INDEX_DEFAULT_NS, - VULNERABILITIES_SEVERITY, VULN_MGMT_POLICY_TEMPLATE, } from '../../common/constants'; import { scheduleTaskSafe, removeTaskSafe } from '../lib/task_manager_util'; diff --git a/x-pack/plugins/security_solution/public/cloud_security_posture/components/index.tsx b/x-pack/plugins/security_solution/public/cloud_security_posture/components/index.tsx index 6045a8b8c9a5e..b4ec54a29a073 100644 --- a/x-pack/plugins/security_solution/public/cloud_security_posture/components/index.tsx +++ b/x-pack/plugins/security_solution/public/cloud_security_posture/components/index.tsx @@ -12,6 +12,7 @@ import { css } from '@emotion/react'; import { FormattedMessage } from '@kbn/i18n-react'; import { useCspSetupStatusApi } from '@kbn/cloud-security-posture/src/hooks/use_csp_setup_status_api'; import { MisconfigurationsPreview } from './misconfiguration/misconfiguration_preview'; +import { VulnerabilitiesPreview } from './vulnerabilities/vulnerabilities_preview'; export const EntityInsight = ({ name, @@ -25,10 +26,27 @@ export const EntityInsight = ({ const { euiTheme } = useEuiTheme(); const getSetupStatus = useCspSetupStatusApi(); const hasMisconfigurationFindings = getSetupStatus.data?.hasMisconfigurationsFindings; + const hasVulnerabilitiesFindings = getSetupStatus.data?.hasVulnerabilitiesFindings; + const insightContent: React.ReactElement[] = []; + const isVulnerabilitiesFindingForHost = hasVulnerabilitiesFindings && fieldName === 'host.name'; + if (hasMisconfigurationFindings) + insightContent.push( + <> + + + + ); + if (isVulnerabilitiesFindingForHost) + insightContent.push( + <> + + + + ); return ( <> - {hasMisconfigurationFindings && ( + {(hasMisconfigurationFindings || isVulnerabilitiesFindingForHost) && ( <> ({ } > - - + {insightContent} diff --git a/x-pack/plugins/security_solution/public/cloud_security_posture/components/misconfiguration/misconfiguration_preview.test.tsx b/x-pack/plugins/security_solution/public/cloud_security_posture/components/misconfiguration/misconfiguration_preview.test.tsx index 2e10d481b9934..b7560f5c68921 100644 --- a/x-pack/plugins/security_solution/public/cloud_security_posture/components/misconfiguration/misconfiguration_preview.test.tsx +++ b/x-pack/plugins/security_solution/public/cloud_security_posture/components/misconfiguration/misconfiguration_preview.test.tsx @@ -5,7 +5,6 @@ * 2.0. */ -// Add stuff here import { TestProviders } from '../../../common/mock'; import { render } from '@testing-library/react'; import React from 'react'; diff --git a/x-pack/plugins/security_solution/public/cloud_security_posture/components/vulnerabilities/vulnerabilities_preview.test.tsx b/x-pack/plugins/security_solution/public/cloud_security_posture/components/vulnerabilities/vulnerabilities_preview.test.tsx new file mode 100644 index 0000000000000..0436da3e192b3 --- /dev/null +++ b/x-pack/plugins/security_solution/public/cloud_security_posture/components/vulnerabilities/vulnerabilities_preview.test.tsx @@ -0,0 +1,27 @@ +/* + * 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 { TestProviders } from '../../../common/mock'; +import { render } from '@testing-library/react'; +import React from 'react'; +import { VulnerabilitiesPreview } from './vulnerabilities_preview'; + +const mockProps: { hostName: string } = { + hostName: 'testContextID', +}; + +describe('VulnerabilitiesPreview', () => { + it('renders', () => { + const { queryByTestId } = render(, { + wrapper: TestProviders, + }); + expect( + queryByTestId('securitySolutionFlyoutInsightsVulnerabilitiesContent') + ).toBeInTheDocument(); + expect(queryByTestId('noVulnerabilitiesDataTestSubj')).toBeInTheDocument(); + }); +}); diff --git a/x-pack/plugins/security_solution/public/cloud_security_posture/components/vulnerabilities/vulnerabilities_preview.tsx b/x-pack/plugins/security_solution/public/cloud_security_posture/components/vulnerabilities/vulnerabilities_preview.tsx new file mode 100644 index 0000000000000..6e30d39fc98a6 --- /dev/null +++ b/x-pack/plugins/security_solution/public/cloud_security_posture/components/vulnerabilities/vulnerabilities_preview.tsx @@ -0,0 +1,215 @@ +/* + * 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 from 'react'; +import { css } from '@emotion/react'; +import type { EuiThemeComputed } from '@elastic/eui'; +import { EuiFlexGroup, EuiFlexItem, EuiSpacer, EuiText, useEuiTheme, EuiTitle } from '@elastic/eui'; +import { FormattedMessage } from '@kbn/i18n-react'; +import { DistributionBar } from '@kbn/security-solution-distribution-bar'; +import { useVulnerabilitiesPreview } from '@kbn/cloud-security-posture/src/hooks/use_vulnerabilities_preview'; +import { i18n } from '@kbn/i18n'; +import { ExpandablePanel } from '@kbn/security-solution-common'; +import { + buildEntityFlyoutPreviewQuery, + VULNERABILITIES_SEVERITY, + getAbbreviatedNumber, +} from '@kbn/cloud-security-posture-common'; +import { getSeverityStatusColor, getSeverityText } from '@kbn/cloud-security-posture'; + +interface VulnerabilitiesDistributionBarProps { + key: string; + count: number; + color: string; +} + +const getVulnerabilityStats = ( + critical: number, + high: number, + medium: number, + low: number, + none: number +): VulnerabilitiesDistributionBarProps[] => { + const vulnerabilityStats: VulnerabilitiesDistributionBarProps[] = []; + if (critical === 0 && high === 0 && medium === 0 && low === 0 && none === 0) + return vulnerabilityStats; + + if (none > 0) + vulnerabilityStats.push({ + key: i18n.translate( + 'xpack.securitySolution.flyout.right.insights.vulnerabilities.noneVulnerabilitiesText', + { + defaultMessage: getSeverityText(VULNERABILITIES_SEVERITY.UNKNOWN), + } + ), + count: none, + color: getSeverityStatusColor(VULNERABILITIES_SEVERITY.UNKNOWN), + }); + if (low > 0) + vulnerabilityStats.push({ + key: i18n.translate( + 'xpack.securitySolution.flyout.right.insights.vulnerabilities.lowVulnerabilitiesText', + { + defaultMessage: getSeverityText(VULNERABILITIES_SEVERITY.LOW), + } + ), + count: low, + color: getSeverityStatusColor(VULNERABILITIES_SEVERITY.LOW), + }); + + if (medium > 0) + vulnerabilityStats.push({ + key: i18n.translate( + 'xpack.securitySolution.flyout.right.insights.vulnerabilities.mediumVulnerabilitiesText', + { + defaultMessage: getSeverityText(VULNERABILITIES_SEVERITY.MEDIUM), + } + ), + count: medium, + color: getSeverityStatusColor(VULNERABILITIES_SEVERITY.MEDIUM), + }); + if (high > 0) + vulnerabilityStats.push({ + key: i18n.translate( + 'xpack.securitySolution.flyout.right.insights.vulnerabilities.highVulnerabilitiesText', + { + defaultMessage: getSeverityText(VULNERABILITIES_SEVERITY.HIGH), + } + ), + count: high, + color: getSeverityStatusColor(VULNERABILITIES_SEVERITY.HIGH), + }); + if (critical > 0) + vulnerabilityStats.push({ + key: i18n.translate( + 'xpack.securitySolution.flyout.right.insights.vulnerabilities.CriticalVulnerabilitiesText', + { + defaultMessage: getSeverityText(VULNERABILITIES_SEVERITY.CRITICAL), + } + ), + count: critical, + color: getSeverityStatusColor(VULNERABILITIES_SEVERITY.CRITICAL), + }); + + return vulnerabilityStats; +}; + +const VulnerabilitiesEmptyState = ({ euiTheme }: { euiTheme: EuiThemeComputed<{}> }) => { + return ( + + + + +

{'-'}

+
+
+ + + + + +
+
+ ); +}; + +const VulnerabilitiesCount = ({ + vulnerabilitiesTotal, + euiTheme, +}: { + vulnerabilitiesTotal: string | number; + euiTheme: EuiThemeComputed<{}>; +}) => { + return ( + + + + +

{vulnerabilitiesTotal}

+
+
+ + + + + +
+
+ ); +}; + +export const VulnerabilitiesPreview = ({ hostName }: { hostName: string }) => { + const { data } = useVulnerabilitiesPreview({ + query: buildEntityFlyoutPreviewQuery('host.name', hostName), + sort: [], + enabled: true, + pageSize: 1, + }); + + const { CRITICAL = 0, HIGH = 0, MEDIUM = 0, LOW = 0, NONE = 0 } = data?.count || {}; + + const totalVulnerabilities = CRITICAL + HIGH + MEDIUM + LOW + NONE; + const { euiTheme } = useEuiTheme(); + const hasVulnerabilities = totalVulnerabilities > 0; + return ( + + + + ), + }} + data-test-subj={'securitySolutionFlyoutInsightsVulnerabilities'} + > + + {hasVulnerabilities ? ( + + ) : ( + + )} + + + + + + + + + + + + ); +}; diff --git a/x-pack/test/security_solution_cypress/cypress/e2e/investigations/alerts/expandable_flyout/vulnerabilities_contextual_flyout.cy.ts b/x-pack/test/security_solution_cypress/cypress/e2e/investigations/alerts/expandable_flyout/vulnerabilities_contextual_flyout.cy.ts new file mode 100644 index 0000000000000..04ba10c908df7 --- /dev/null +++ b/x-pack/test/security_solution_cypress/cypress/e2e/investigations/alerts/expandable_flyout/vulnerabilities_contextual_flyout.cy.ts @@ -0,0 +1,195 @@ +/* + * 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 { CDR_LATEST_NATIVE_VULNERABILITIES_INDEX_PATTERN } from '@kbn/cloud-security-posture-common'; +import { createRule } from '../../../../tasks/api_calls/rules'; +import { getNewRule } from '../../../../objects/rule'; +import { getDataTestSubjectSelector } from '../../../../helpers/common'; + +import { rootRequest, deleteAlertsAndRules } from '../../../../tasks/api_calls/common'; +import { expandFirstAlertHostFlyout } from '../../../../tasks/asset_criticality/common'; +import { waitForAlertsToPopulate } from '../../../../tasks/create_new_rule'; +import { login } from '../../../../tasks/login'; +import { ALERTS_URL } from '../../../../urls/navigation'; +import { visit } from '../../../../tasks/navigation'; + +const CSP_INSIGHT_VULNERABILITIES_TITLE = getDataTestSubjectSelector( + 'securitySolutionFlyoutInsightsVulnerabilitiesTitleText' +); + +const NO_VULNERABILITIES_TEXT = getDataTestSubjectSelector('noVulnerabilitiesDataTestSubj'); + +const timestamp = Date.now(); + +// Create a Date object using the timestamp +const date = new Date(timestamp); + +// Convert the Date object to ISO 8601 format +const iso8601String = date.toISOString(); + +const getMockVulnerability = (isNameMatchesAlert: boolean) => { + return { + '@timestamp': iso8601String, + resource: { name: '634yfsdg2.dkr.ecr.eu-central-1.amazon.stage', id: 'ami_12328' }, + agent: { + name: 'ip-172-31-33-74', + type: 'cloudbeat', + version: '8.8.0', + ephemeral_id: '49f19e6a-94e9-4f2b-81e3-2f3794a74068', + id: 'd0313a94-c168-4d95-b1f0-97a388dac29a', + }, + cloud: { + availability_zone: 'eu-west-1c', + service: { name: 'EC2' }, + account: { id: '704479110758' }, + image: { id: 'ami-02dc8dbcc971f2c74' }, + provider: 'aws', + instance: { id: 'i-0fb7759c6e5d400cf' }, + machine: { type: 'c6g.medium' }, + region: 'eu-west-1', + }, + package: { fixed_version: '0.4.0', version: 'v0.2.0', name: 'golang.org/x/net' }, + vulnerability: { + published_date: '2022-08-10T00:00:00.000Z', + data_source: { + ID: 'go-vulndb', + Name: 'The Go Vulnerability Database', + URL: 'https://github.com/golang/vulndb', + }, + enumeration: 'CVE', + description: + 'An attacker can cause excessive memory growth in a Go server accepting HTTP/2 requests. HTTP/2 server connections contain a cache of HTTP header keys sent by the client. While the total number of entries in this cache is capped, an attacker sending very large keys can cause the server to allocate approximately 64 MiB per open connection.', + title: + 'golang: net/http: An attacker can cause excessive memory growth in a Go server accepting HTTP/2 requests', + reference: 'https://avd.aquasec.com/nvd/cve-2022-41717', + severity: 'MEDIUM', + cvss: { + nvd: { V3Vector: 'CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:N/I:N/A:L', V3Score: 5.3 }, + redhat: { V3Vector: 'CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:N/I:N/A:L', V3Score: 5.3 }, + ghsa: { V3Vector: 'CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:N/I:N/A:L', V3Score: 5.3 }, + }, + scanner: { vendor: 'Trivy' }, + score: { base: 5.3, version: '3.0' }, + cwe: ['CWE-770'], + id: 'CVE-2022-41717', + classification: 'CVSS', + }, + cloudbeat: { + commit_sha: 'b5c4b728f0a9268e7f2d195c00dad0320c8a74e6', + commit_time: '2023-03-30T07:47:06Z', + version: '8.8.0', + }, + event: { + category: ['vulnerability'], + created: '2023-03-30T10:27:35.013537768Z', + id: '5cfbcbe5-7f90-47b8-b1d4-7f79313b2a6d', + kind: 'state', + sequence: 1680172055, + outcome: 'success', + type: ['info'], + }, + ecs: { version: '8.0.0' }, + host: { + os: { + kernel: '5.15.0-1028-aws', + codename: 'jammy', + type: 'linux', + platform: 'ubuntu', + version: '22.04.1 LTS (Jammy Jellyfish)', + family: 'debian', + name: 'Ubuntu', + }, + id: 'ec2644f440799ed0cf8aa595a9a105cc', + containerized: false, + name: isNameMatchesAlert ? 'siem-kibana' : 'not-siem-kibana', + ip: ['172.31.33.74', 'fe80::85d:f0ff:fe91:c01b'], + mac: ['0A-5D-F0-91-C0-1B'], + hostname: 'ip-172-31-33-74', + architecture: 'aarch64', + }, + data_stream: { + dataset: 'cloud_security_posture.vulnerabilities', + }, + }; +}; + +const createMockVulnerability = (isNameMatchesAlert: boolean) => { + return rootRequest({ + method: 'POST', + url: `${Cypress.env( + 'ELASTICSEARCH_URL' + )}/${CDR_LATEST_NATIVE_VULNERABILITIES_INDEX_PATTERN}/_doc`, + body: getMockVulnerability(isNameMatchesAlert), + }); +}; + +const deleteDataStream = () => { + return rootRequest({ + method: 'DELETE', + url: `${Cypress.env( + 'ELASTICSEARCH_URL' + )}/_data_stream/${CDR_LATEST_NATIVE_VULNERABILITIES_INDEX_PATTERN}`, + }); +}; + +describe('Alert Host details expandable flyout', { tags: ['@ess', '@serverless'] }, () => { + beforeEach(() => { + deleteAlertsAndRules(); + login(); + createRule(getNewRule()); + visit(ALERTS_URL); + waitForAlertsToPopulate(); + }); + + context('No Vulnerabilities Findings', () => { + it('should not display Vulnerabilities preview under Insights Entities when it does not have Vulnerabilities Findings', () => { + expandFirstAlertHostFlyout(); + + cy.log('check if Vulnerabilities preview title is not shown'); + cy.get(CSP_INSIGHT_VULNERABILITIES_TITLE).should('not.exist'); + }); + }); + + context('Host name - Has Vulnerabilities findings', () => { + beforeEach(() => { + createMockVulnerability(true); + cy.reload(); + expandFirstAlertHostFlyout(); + }); + + afterEach(() => { + deleteDataStream(); + }); + + it('should display Vulnerabilities preview under Insights Entities when it has Vulnerabilities Findings', () => { + cy.log('check if Vulnerabilities preview title shown'); + cy.get(CSP_INSIGHT_VULNERABILITIES_TITLE).should('be.visible'); + }); + }); + + context( + 'Host name - Has Vulnerabilities findings but host name is not the same as alert host name', + () => { + beforeEach(() => { + createMockVulnerability(false); + cy.reload(); + expandFirstAlertHostFlyout(); + }); + + afterEach(() => { + deleteDataStream(); + }); + + it('should display Vulnerabilities preview under Insights Entities when it has Vulnerabilities Findings but it should show no vulnerabilities title', () => { + cy.log('check if Vulnerabilities preview title shown'); + cy.get(CSP_INSIGHT_VULNERABILITIES_TITLE).should('be.visible'); + cy.log('check if no vulnerabilities text is shown'); + cy.get(NO_VULNERABILITIES_TEXT).should('be.visible'); + }); + } + ); +}); diff --git a/x-pack/test/security_solution_cypress/cypress/tsconfig.json b/x-pack/test/security_solution_cypress/cypress/tsconfig.json index 36ef2376ec1ad..b117f42efceae 100644 --- a/x-pack/test/security_solution_cypress/cypress/tsconfig.json +++ b/x-pack/test/security_solution_cypress/cypress/tsconfig.json @@ -44,5 +44,6 @@ "@kbn/securitysolution-endpoint-exceptions-common", "@kbn/repo-info", "@kbn/elastic-assistant-common", + "@kbn/cloud-security-posture-common", ] }