[Cloud Security] Support for missing fields in vulnerabilities and removing integration installation block (#191504)

This commit is contained in:
Jordan 2024-09-09 12:23:50 +03:00 committed by GitHub
parent 7b84fa8394
commit a36b22aa6c
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
29 changed files with 263 additions and 131 deletions

View file

@ -5,12 +5,6 @@
* 2.0.
*/
/*
* 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 { EcsDataStream, EcsEvent } from '@elastic/ecs';
import type { CspBenchmarkRuleMetadata } from '../schema/rules/latest';

View file

@ -46,6 +46,7 @@ export interface BaseCspSetupStatus {
isPluginInitialized: boolean;
installedPackageVersion?: string | undefined;
hasMisconfigurationsFindings?: boolean;
hasVulnerabilitiesFindings?: boolean;
}
export type CspSetupStatus = BaseCspSetupStatus;

View file

@ -75,6 +75,7 @@ export interface CspVulnerabilityFinding {
name: string;
fixed_version?: string;
};
data_stream: { dataset: string };
}
export interface Vulnerability {

View file

@ -242,6 +242,7 @@ export const VULNERABILITY_FIELDS = {
CLOUD_ACCOUNT_NAME: 'cloud.account.name',
CLOUD_PROVIDER: 'cloud.provider',
DESCRIPTION: 'vulnerability.description',
SOURCE: 'data_stream.dataset',
} as const;
export const VULNERABILITY_GROUPING_OPTIONS = {
NONE: 'none',

View file

@ -0,0 +1,42 @@
/*
* 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 {
CSP_MISCONFIGURATIONS_DATASET,
CSP_VULN_DATASET,
getDatasetDisplayName,
WIZ_MISCONFIGURATIONS_DATASET,
WIZ_VULN_DATASET,
} from './get_dataset_display_name';
describe('getDatasetDisplayName', () => {
it('should return "Wiz" when dataset is from Wiz integration', () => {
const wizMisconfigsDatasetDisplayName = getDatasetDisplayName(WIZ_MISCONFIGURATIONS_DATASET);
expect(wizMisconfigsDatasetDisplayName).toBe('Wiz');
const wizVulnDatasetDisplayName = getDatasetDisplayName(WIZ_VULN_DATASET);
expect(wizVulnDatasetDisplayName).toBe('Wiz');
});
it('should return "Elastic CSP" when dataset is from Elastic CSP integration', () => {
const elasticCspMisconfigsDatasetDisplayName = getDatasetDisplayName(
CSP_MISCONFIGURATIONS_DATASET
);
expect(elasticCspMisconfigsDatasetDisplayName).toBe('Elastic CSP');
const elasticCspVulnDatasetDisplayName = getDatasetDisplayName(CSP_VULN_DATASET);
expect(elasticCspVulnDatasetDisplayName).toBe('Elastic CSP');
});
it('should return undefined when dataset is undefined', () => {
const result = getDatasetDisplayName(undefined);
expect(result).toBeUndefined();
});
it('should return undefined when dataset is an empty string', () => {
const result = getDatasetDisplayName('');
expect(result).toBeUndefined();
});
});

View file

@ -7,10 +7,13 @@
type Dataset = 'wiz.cloud_configuration_finding' | 'cloud_security_posture.findings';
export const WIZ_DATASET = 'wiz.cloud_configuration_finding';
export const CSP_DATASET = 'cloud_security_posture.findings';
export const CSP_MISCONFIGURATIONS_DATASET = 'cloud_security_posture.findings';
export const CSP_VULN_DATASET = 'cloud_security_posture.vulnerabilities';
export const WIZ_MISCONFIGURATIONS_DATASET = 'wiz.cloud_configuration_finding';
export const WIZ_VULN_DATASET = 'wiz.vulnerability';
export const getDatasetDisplayName = (dataset?: Dataset | string) => {
if (dataset === WIZ_DATASET) return 'Wiz';
if (dataset === CSP_DATASET) return 'Elastic CSP';
if (dataset === WIZ_MISCONFIGURATIONS_DATASET || dataset === WIZ_VULN_DATASET) return 'Wiz';
if (dataset === CSP_MISCONFIGURATIONS_DATASET || dataset === CSP_VULN_DATASET)
return 'Elastic CSP';
};

View file

@ -30,7 +30,7 @@ import {
getPackageInfoMock,
} from './mocks';
import type { NewPackagePolicy, PackageInfo, PackagePolicy } from '@kbn/fleet-plugin/common';
import { getPosturePolicy } from './utils';
import { getPosturePolicy, POLICY_TEMPLATE_FORM_DTS } from './utils';
import {
CLOUDBEAT_AWS,
CLOUDBEAT_AZURE,
@ -161,6 +161,20 @@ describe('<CspPolicyTemplateForm />', () => {
);
};
it('shows loader when useIsSubscriptionStatusValid is loading', () => {
(useIsSubscriptionStatusValid as jest.Mock).mockImplementation(() =>
createReactQueryResponse({
status: 'loading',
data: undefined,
})
);
const policy = getMockPolicyAWS();
render(<WrappedComponent newPolicy={policy} />);
expect(screen.getByTestId(POLICY_TEMPLATE_FORM_DTS.LOADER)).toBeInTheDocument();
});
it('shows license block if subscription is not allowed', () => {
(useIsSubscriptionStatusValid as jest.Mock).mockImplementation(() =>
createReactQueryResponse({

View file

@ -51,6 +51,7 @@ import {
isBelowMinVersion,
type NewPackagePolicyPostureInput,
POSTURE_NAMESPACE,
POLICY_TEMPLATE_FORM_DTS,
} from './utils';
import {
PolicyTemplateInfo,
@ -775,6 +776,10 @@ export const CspPolicyTemplateForm = memo<PackagePolicyReplaceDefineStepExtensio
setTimeout(() => setIsLoading(false), 200);
}, [validationResultsNonNullFields]);
useEffect(() => {
setIsLoading(getIsSubscriptionValid.isLoading);
}, [getIsSubscriptionValid.isLoading]);
const { data: packagePolicyList, refetch } = usePackagePolicyList(packageInfo.name, {
enabled: canFetchIntegration,
});
@ -826,7 +831,7 @@ export const CspPolicyTemplateForm = memo<PackagePolicyReplaceDefineStepExtensio
if (isLoading) {
return (
<EuiFlexGroup justifyContent="spaceAround">
<EuiFlexGroup justifyContent="spaceAround" data-test-subj={POLICY_TEMPLATE_FORM_DTS.LOADER}>
<EuiFlexItem grow={false}>
<EuiLoadingSpinner size="xl" />
</EuiFlexItem>
@ -859,7 +864,7 @@ export const CspPolicyTemplateForm = memo<PackagePolicyReplaceDefineStepExtensio
},
];
if (!isSubscriptionValid) {
if (!getIsSubscriptionValid.isLoading && !isSubscriptionValid) {
return <SubscriptionNotAllowed />;
}

View file

@ -394,3 +394,7 @@ export const findVariableDef = (packageInfo: PackageInfo, key: string) => {
.flat()
.find((vars) => vars?.name === key);
};
export const POLICY_TEMPLATE_FORM_DTS = {
LOADER: 'policy-template-form-loader',
};

View file

@ -40,9 +40,9 @@ export const CVSScoreBadge = ({ score, version }: CVSScoreBadgeProps) => {
`}
data-test-subj={VULNERABILITIES_CVSS_SCORE_BADGE_SUBJ}
>
{versionDisplay && (
<>
<EuiTextColor color="ghost">{score < 10 ? score.toFixed(1) : score}</EuiTextColor>
<>
<EuiTextColor color="ghost">{score < 10 ? score.toFixed(1) : score}</EuiTextColor>
<hr
css={css`
width: 1px;
@ -51,9 +51,9 @@ export const CVSScoreBadge = ({ score, version }: CVSScoreBadgeProps) => {
margin: 0px 6px;
`}
/>
<EuiTextColor color="ghost">{versionDisplay}</EuiTextColor>
<EuiTextColor color="ghost">{versionDisplay || '-'}</EuiTextColor>
</>
)}
</>
</EuiBadge>
);
};

View file

@ -37,7 +37,10 @@ import { css } from '@emotion/react';
import { euiThemeVars } from '@kbn/ui-theme';
import { CspEvaluationBadge } from '@kbn/cloud-security-posture';
import type { CspFinding } from '@kbn/cloud-security-posture-common';
import { CSP_DATASET, getDatasetDisplayName } from '../../../common/utils/get_dataset_display_name';
import {
CSP_MISCONFIGURATIONS_DATASET,
getDatasetDisplayName,
} from '../../../common/utils/get_dataset_display_name';
import { truthy } from '../../../../common/utils/helpers';
import { benchmarksNavigation } from '../../../common/navigation/constants';
import cisLogoIcon from '../../../assets/icons/cis_logo.svg';
@ -170,7 +173,7 @@ const FindingsTab = ({ tab, finding }: { finding: CspFinding; tab: FindingsTab }
const ruleFlyoutLink =
// currently we only support rule linking for native CSP findings
finding.data_stream.dataset === CSP_DATASET &&
finding.data_stream.dataset === CSP_MISCONFIGURATIONS_DATASET &&
finding.rule?.benchmark?.version &&
finding.rule?.benchmark?.id &&
finding.rule?.id
@ -197,7 +200,8 @@ const FindingsTab = ({ tab, finding }: { finding: CspFinding; tab: FindingsTab }
}
};
const isNativeCspFinding = (finding: CspFinding) => finding.data_stream.dataset === CSP_DATASET;
const isNativeCspFinding = (finding: CspFinding) =>
finding.data_stream.dataset === CSP_MISCONFIGURATIONS_DATASET;
const MissingFieldsCallout = ({ finding }: { finding: CspFinding }) => {
const { euiTheme } = useEuiTheme();

View file

@ -86,4 +86,7 @@ export const mockVulnerabilityHit: CspVulnerabilityFinding = {
hostname: 'ip-172-31-33-74',
architecture: 'aarch64',
},
data_stream: {
dataset: 'cloud_security_posture.vulnerabilities',
},
};

View file

@ -49,4 +49,5 @@ export const defaultColumns: CloudSecurityDefaultColumn[] = [
{ id: VULNERABILITY_FIELDS.PACKAGE_NAME },
{ id: VULNERABILITY_FIELDS.PACKAGE_VERSION },
{ id: VULNERABILITY_FIELDS.PACKAGE_FIXED_VERSION },
{ id: VULNERABILITY_FIELDS.SOURCE },
];

View file

@ -150,6 +150,7 @@ export const LatestVulnerabilitiesContainer = () => {
</>
);
}
return (
<>
<FindingsSearchBar query={urlQuery} setQuery={setUrlQuery} loading={isFetching} />

View file

@ -11,6 +11,7 @@ import { i18n } from '@kbn/i18n';
import { EuiDataGridCellValueElementProps, EuiSpacer } from '@elastic/eui';
import { Filter } from '@kbn/es-query';
import { HttpSetup } from '@kbn/core-http-browser';
import { getDatasetDisplayName } from '../../common/utils/get_dataset_display_name';
import { CspVulnerabilityFinding } from '../../../common/schemas';
import { CloudSecurityDataTable } from '../../components/cloud_security_data_table';
import { useLatestVulnerabilitiesTable } from './hooks/use_latest_vulnerabilities_table';
@ -88,6 +89,11 @@ const customCellRenderer = (rows: DataTableRecord[]) => ({
{({ finding }) => <SeverityStatusBadge severity={finding.vulnerability.severity} />}
</CspVulnerabilityFindingRenderer>
),
'data_stream.dataset': ({ rowIndex }: EuiDataGridCellValueElementProps) => (
<CspVulnerabilityFindingRenderer row={rows[rowIndex]}>
{({ finding }) => <>{getDatasetDisplayName(finding.data_stream.dataset) || '-'}</>}
</CspVulnerabilityFindingRenderer>
),
});
export const LatestVulnerabilitiesTable = ({

View file

@ -15,3 +15,5 @@ export const TAB_ID_VULNERABILITY_FLYOUT = (tabId: string) =>
export const LATEST_VULNERABILITIES_TABLE = 'latest_vulnerabilities_table';
export const VULNERABILITIES_GROUPING_COUNTER = 'vulnerabilities_grouping_counter';
export const VULNERABILITIES_PAGE = 'cloud-sec-vulnerabilities-page';

View file

@ -43,21 +43,11 @@ enum AlertSuppressionMissingFieldsStrategy {
}
const CSP_RULE_TAG = 'Cloud Security';
const CNVM_RULE_TAG = 'CNVM';
const CNVM_RULE_TAG_DATA_SOURCE = 'Data Source: Cloud Native Vulnerability Management';
const CNVM_RULE_TAG_USE_CASE = 'Use Case: Vulnerability';
const CNVM_RULE_TAG_OS = 'OS: Linux';
const STATIC_RULE_TAGS = [
CSP_RULE_TAG,
CNVM_RULE_TAG,
CNVM_RULE_TAG_DATA_SOURCE,
CNVM_RULE_TAG_USE_CASE,
CNVM_RULE_TAG_OS,
];
const STATIC_RULE_TAGS = [CSP_RULE_TAG];
const generateVulnerabilitiesTags = (vulnerability: Vulnerability) => {
return [...STATIC_RULE_TAGS, vulnerability.id];
const generateVulnerabilitiesTags = (tags?: string[]) => {
return [...STATIC_RULE_TAGS, ...(!!tags?.length ? tags : [])];
};
const getVulnerabilityRuleName = (vulnerability: Vulnerability) => {
@ -80,7 +70,8 @@ const generateVulnerabilitiesRuleQuery = (vulnerability: Vulnerability) => {
*/
export const createDetectionRuleFromVulnerabilityFinding = async (
http: HttpSetup,
vulnerability: Vulnerability
vulnerability: Vulnerability,
tags?: string[]
) => {
return await createDetectionRule({
http,
@ -144,7 +135,7 @@ export const createDetectionRuleFromVulnerabilityFinding = async (
references: vulnerability.reference ? [vulnerability.reference] : [],
name: getVulnerabilityRuleName(vulnerability),
description: vulnerability.description,
tags: generateVulnerabilitiesTags(vulnerability),
tags: generateVulnerabilitiesTags(tags),
investigation_fields: DEFAULT_INVESTIGATION_FIELDS,
},
});

View file

@ -9,6 +9,7 @@ import { Routes, Route } from '@kbn/shared-ux-router';
import { findingsNavigation } from '@kbn/cloud-security-posture';
import { useCspSetupStatusApi } from '@kbn/cloud-security-posture/src/hooks/use_csp_setup_status_api';
import { useDataView } from '@kbn/cloud-security-posture/src/hooks/use_data_view';
import { VULNERABILITIES_PAGE } from './test_subjects';
import { CDR_VULNERABILITIES_DATA_VIEW_ID_PREFIX } from '../../../common/constants';
import { NoVulnerabilitiesStates } from '../../components/no_vulnerabilities_states';
import { CloudPosturePage } from '../../components/cloud_posture_page';
@ -17,10 +18,13 @@ import { DataViewContext } from '../../common/contexts/data_view_context';
export const Vulnerabilities = () => {
const dataViewQuery = useDataView(CDR_VULNERABILITIES_DATA_VIEW_ID_PREFIX);
const getSetupStatus = useCspSetupStatusApi();
const setupStatus = getSetupStatus.data;
if (getSetupStatus?.data?.vuln_mgmt?.status !== 'indexed') return <NoVulnerabilitiesStates />;
const hasFindings =
!!setupStatus?.hasVulnerabilitiesFindings || setupStatus?.vuln_mgmt?.status === 'indexed';
if (!hasFindings) return <NoVulnerabilitiesStates />;
const dataViewContextValue = {
dataView: dataViewQuery.data!,
@ -30,16 +34,18 @@ export const Vulnerabilities = () => {
return (
<CloudPosturePage query={dataViewQuery}>
<Routes>
<Route
path={findingsNavigation.vulnerabilities.path}
render={() => (
<DataViewContext.Provider value={dataViewContextValue}>
<LatestVulnerabilitiesContainer />
</DataViewContext.Provider>
)}
/>
</Routes>
<div data-test-subj={VULNERABILITIES_PAGE}>
<Routes>
<Route
path={findingsNavigation.vulnerabilities.path}
render={() => (
<DataViewContext.Provider value={dataViewContextValue}>
<LatestVulnerabilitiesContainer />
</DataViewContext.Provider>
)}
/>
</Routes>
</div>
</CloudPosturePage>
);
};

View file

@ -7,24 +7,40 @@
import React from 'react';
import type { HttpSetup } from '@kbn/core/public';
import { Vulnerability } from '../../../../common/schemas';
import { CSP_VULN_DATASET } from '../../../common/utils/get_dataset_display_name';
import { CspVulnerabilityFinding } from '../../../../common/schemas';
import { DetectionRuleCounter } from '../../../components/detection_rule_counter';
import { createDetectionRuleFromVulnerabilityFinding } from '../utils/create_detection_rule_from_vulnerability';
const CNVM_TAG = 'CNVM';
const CNVM_RULE_TAG_DATA_SOURCE = 'Data Source: Cloud Native Vulnerability Management';
const CNVM_RULE_TAG_USE_CASE = 'Use Case: Vulnerability';
const CNVM_RULE_TAG_OS = 'OS: Linux';
const getTags = (vulnerabilityRecord: CspVulnerabilityFinding) => {
let tags = [vulnerabilityRecord.vulnerability.id];
if (vulnerabilityRecord?.data_stream?.dataset === CSP_VULN_DATASET) {
tags = [CNVM_TAG, CNVM_RULE_TAG_DATA_SOURCE, CNVM_RULE_TAG_USE_CASE, CNVM_RULE_TAG_OS, ...tags];
} else {
tags.push(vulnerabilityRecord?.data_stream?.dataset);
}
return tags;
};
export const VulnerabilityDetectionRuleCounter = ({
vulnerability,
vulnerabilityRecord,
}: {
vulnerability: Vulnerability;
vulnerabilityRecord: CspVulnerabilityFinding;
}) => {
const tags = getTags(vulnerabilityRecord);
const createVulnerabilityRuleFn = async (http: HttpSetup) =>
await createDetectionRuleFromVulnerabilityFinding(http, vulnerability);
await createDetectionRuleFromVulnerabilityFinding(
http,
vulnerabilityRecord.vulnerability,
tags
);
return (
<DetectionRuleCounter
tags={[CNVM_TAG, vulnerability.id]}
createRuleFn={createVulnerabilityRuleFn}
/>
);
return <DetectionRuleCounter tags={tags} createRuleFn={createVulnerabilityRuleFn} />;
};

View file

@ -64,6 +64,7 @@ describe('<VulnerabilityFindingFlyout/>', () => {
</TestProvider>
);
getByText(mockVulnerabilityHit.vulnerability.data_source.ID);
getByText('Elastic CSP');
getByText(moment(mockVulnerabilityHit.vulnerability.published_date).format('LL').toString());
getByText(mockVulnerabilityHit.vulnerability.description);
getAllByText(mockVulnerabilityHit.vulnerability?.cvss?.nvd?.V3Vector as string);

View file

@ -63,14 +63,14 @@ const getFlyoutDescriptionList = (
),
description: vulnerabilityRecord.resource.name,
},
{
vulnerabilityRecord.package?.name && {
title: i18n.translate(
'xpack.csp.vulnerabilities.vulnerabilitiesFindingFlyout.flyoutDescriptionList.packageTitle',
{ defaultMessage: 'Package' }
),
description: vulnerabilityRecord.package.name,
},
{
vulnerabilityRecord.package?.version && {
title: i18n.translate(
'xpack.csp.vulnerabilities.vulnerabilitiesFindingFlyout.flyoutDescriptionList.versionTitle',
{ defaultMessage: 'Version' }

View file

@ -14,14 +14,10 @@ interface VulnerabilityJsonTabProps {
vulnerabilityRecord: CspVulnerabilityFinding;
}
export const VulnerabilityJsonTab = ({ vulnerabilityRecord }: VulnerabilityJsonTabProps) => {
const offsetTopHeight = 188;
const offsetBottomHeight = 60;
return (
<div
style={{ position: 'absolute', inset: 0, top: offsetTopHeight, bottom: offsetBottomHeight }}
data-test-subj={JSON_TAB_VULNERABILITY_FLYOUT}
>
<div style={{ position: 'absolute', inset: 0 }} data-test-subj={JSON_TAB_VULNERABILITY_FLYOUT}>
<CodeEditor
enableFindAction
isCopyable
allowFullScreen
languageId={XJsonLang.ID}

View file

@ -19,6 +19,7 @@ import moment from 'moment';
import React from 'react';
import { euiThemeVars } from '@kbn/ui-theme';
import { i18n } from '@kbn/i18n';
import { getDatasetDisplayName } from '../../../common/utils/get_dataset_display_name';
import { VectorScoreBase, CspVulnerabilityFinding } from '../../../../common/schemas';
import { CspFlyoutMarkdown } from '../../configurations/findings_flyout/findings_flyout';
import { NvdLogo } from '../../../assets/icons/nvd_logo_svg';
@ -44,6 +45,8 @@ interface VulnerabilityTabProps {
vulnerabilityRecord: CspVulnerabilityFinding;
}
const EMPTY_VALUE = '-';
const CVSScore = ({ vectorBaseScore, vendor }: CVSScoreProps) => {
const vendorName = cvssVendors[vendor];
@ -145,7 +148,7 @@ const VulnerabilityOverviewTiles = ({ vulnerabilityRecord }: VulnerabilityTabPro
return (
<EuiFlexGroup data-test-subj={OVERVIEW_TAB_VULNERABILITY_FLYOUT}>
{vulnerability?.score?.version && vulnerability?.score?.base && (
{vulnerability?.score?.base && (
<EuiFlexItem css={tileStyle}>
<EuiText css={tileTitleTextStyle}>
<FormattedMessage
@ -153,41 +156,46 @@ const VulnerabilityOverviewTiles = ({ vulnerabilityRecord }: VulnerabilityTabPro
defaultMessage="CVSS"
/>
</EuiText>
<div>
<CVSScoreBadge version={vulnerability.score.version} score={vulnerability.score.base} />
<CVSScoreBadge
version={vulnerability.score?.version}
score={vulnerability.score.base}
/>
</div>
</EuiFlexItem>
)}
<EuiFlexItem css={tileStyle}>
<EuiText css={tileTitleTextStyle}>
<FormattedMessage
id="xpack.csp.vulnerabilities.vulnerabilityOverviewTile.dataSource"
defaultMessage="Data Source"
/>
</EuiText>
<EuiLink href={vulnerability?.data_source?.URL} target="_blank">
{vulnerability?.data_source?.ID}
</EuiLink>
</EuiFlexItem>
<EuiFlexItem css={tileStyle}>
<EuiText css={tileTitleTextStyle}>
<FormattedMessage
id="xpack.csp.vulnerabilities.vulnerabilityOverviewTile.publishedDate"
defaultMessage="Published Date"
/>
</EuiText>
<strong>
<FormattedMessage
id="xpack.csp.vulnerabilities.vulnerabilityOverviewTile.publishedDateText"
defaultMessage="{date}"
values={{
date,
}}
/>
</strong>
</EuiFlexItem>
{vulnerability?.data_source?.ID && (
<EuiFlexItem css={tileStyle}>
<EuiText css={tileTitleTextStyle}>
<FormattedMessage
id="xpack.csp.vulnerabilities.vulnerabilityOverviewTile.dataSource"
defaultMessage="Data Source"
/>
</EuiText>
<EuiLink href={vulnerability.data_source?.URL} target="_blank">
{vulnerability.data_source.ID}
</EuiLink>
</EuiFlexItem>
)}
{date && (
<EuiFlexItem css={tileStyle}>
<EuiText css={tileTitleTextStyle}>
<FormattedMessage
id="xpack.csp.vulnerabilities.vulnerabilityOverviewTile.publishedDate"
defaultMessage="Published Date"
/>
</EuiText>
<strong>
<FormattedMessage
id="xpack.csp.vulnerabilities.vulnerabilityOverviewTile.publishedDateText"
defaultMessage="{date}"
values={{
date,
}}
/>
</strong>
</EuiFlexItem>
)}
</EuiFlexGroup>
);
};
@ -237,6 +245,7 @@ export const VulnerabilityOverviewTab = ({ vulnerabilityRecord }: VulnerabilityT
</EuiFlexItem>
<EuiHorizontalRule css={horizontalStyle} />
<EuiFlexItem>
<h4 css={flyoutSubheadingStyle}>
<FormattedMessage
@ -244,8 +253,23 @@ export const VulnerabilityOverviewTab = ({ vulnerabilityRecord }: VulnerabilityT
defaultMessage="Alerts"
/>
</h4>
<VulnerabilityDetectionRuleCounter vulnerability={vulnerability} />
{vulnerabilityRecord.vulnerability.id ? (
<VulnerabilityDetectionRuleCounter vulnerabilityRecord={vulnerabilityRecord} />
) : (
EMPTY_VALUE
)}
</EuiFlexItem>
<EuiFlexItem>
<h4 css={flyoutSubheadingStyle}>
<FormattedMessage
id="xpack.csp.vulnerabilities.vulnerabilityOverviewTab.sourceTitle"
defaultMessage="Source"
/>
</h4>
{getDatasetDisplayName(vulnerabilityRecord.data_stream?.dataset) || EMPTY_VALUE}
</EuiFlexItem>
<EuiFlexItem>
<h4 css={flyoutSubheadingStyle}>
<FormattedMessage
@ -253,7 +277,7 @@ export const VulnerabilityOverviewTab = ({ vulnerabilityRecord }: VulnerabilityT
defaultMessage="Description"
/>
</h4>
<CspFlyoutMarkdown>{vulnerability?.description || ''}</CspFlyoutMarkdown>
<CspFlyoutMarkdown>{vulnerability?.description || EMPTY_VALUE}</CspFlyoutMarkdown>
</EuiFlexItem>
<EuiHorizontalRule css={horizontalStyle} />
@ -265,7 +289,7 @@ export const VulnerabilityOverviewTab = ({ vulnerabilityRecord }: VulnerabilityT
defaultMessage="Fixes"
/>
</h4>
<EuiText>{fixesDisplayText}</EuiText>
<EuiText>{fixesDisplayText || EMPTY_VALUE}</EuiText>
</EuiFlexItem>
<EuiHorizontalRule css={horizontalStyle} />

View file

@ -8,35 +8,39 @@ import { i18n } from '@kbn/i18n';
export const vulnerabilitiesTableFieldLabels: Record<string, string> = {
'resource.id': i18n.translate(
'xpack.csp.findings.findingsTable.findingsTableColumn.resourceIdColumnLabel',
'xpack.csp.vulnerabilityFindings.vulnerabilityFindingsTable.vulnerabilityFindingsTableColumn.resourceIdColumnLabel',
{ defaultMessage: 'Resource ID' }
),
'resource.name': i18n.translate(
'xpack.csp.findings.findingsTable.findingsTableColumn.resourceNameColumnLabel',
'xpack.csp.vulnerabilityFindings.vulnerabilityFindingsTable.vulnerabilityFindingsTableColumn.resourceNameColumnLabel',
{ defaultMessage: 'Resource Name' }
),
'vulnerability.id': i18n.translate(
'xpack.csp.findings.findingsTable.findingsTableColumn.vulnerabilityIdColumnLabel',
'xpack.csp.vulnerabilityFindings.vulnerabilityFindingsTable.vulnerabilityFindingsTableColumn.vulnerabilityIdColumnLabel',
{ defaultMessage: 'Vulnerability' }
),
'vulnerability.score.base': i18n.translate(
'xpack.csp.findings.findingsTable.findingsTableColumn.vulnerabilityScoreColumnLabel',
'xpack.csp.vulnerabilityFindings.vulnerabilityFindingsTable.vulnerabilityFindingsTableColumn.vulnerabilityScoreColumnLabel',
{ defaultMessage: 'CVSS' }
),
'vulnerability.severity': i18n.translate(
'xpack.csp.findings.findingsTable.findingsTableColumn.vulnerabilitySeverityColumnLabel',
'xpack.csp.vulnerabilityFindings.vulnerabilityFindingsTable.vulnerabilityFindingsTableColumn.vulnerabilitySeverityColumnLabel',
{ defaultMessage: 'Severity' }
),
'package.name': i18n.translate(
'xpack.csp.findings.findingsTable.findingsTableColumn.packageNameColumnLabel',
'xpack.csp.vulnerabilityFindings.vulnerabilityFindingsTable.vulnerabilityFindingsTableColumn.packageNameColumnLabel',
{ defaultMessage: 'Package' }
),
'package.version': i18n.translate(
'xpack.csp.findings.findingsTable.findingsTableColumn.packageVersionColumnLabel',
'xpack.csp.vulnerabilityFindings.vulnerabilityFindingsTable.vulnerabilityFindingsTableColumn.packageVersionColumnLabel',
{ defaultMessage: 'Version' }
),
'package.fixed_version': i18n.translate(
'xpack.csp.findings.findingsTable.findingsTableColumn.packageFixedVersionColumnLabel',
'xpack.csp.vulnerabilityFindings.vulnerabilityFindingsTable.vulnerabilityFindingsTableColumn.packageFixedVersionColumnLabel',
{ defaultMessage: 'Fix Version' }
),
'data_stream.dataset': i18n.translate(
'xpack.csp.vulnerabilityFindings.vulnerabilityFindingsTable.vulnerabilityFindingsTableColumn.sourceColumnLabel',
{ defaultMessage: 'Source' }
),
} as const;

View file

@ -21,11 +21,12 @@ import {
NO_VULNERABILITIES_STATUS_TEST_SUBJ,
VULNERABILITIES_CONTAINER_TEST_SUBJ,
} from '../../components/test_subjects';
import { render } from '@testing-library/react';
import { render, screen } from '@testing-library/react';
import { expectIdsInDoc } from '../../test/utils';
import { TestProvider } from '../../test/test_provider';
import { useLicenseManagementLocatorApi } from '../../common/api/use_license_management_locator_api';
import { createStubDataView } from '@kbn/data-views-plugin/common/stubs';
import { VULNERABILITIES_PAGE } from './test_subjects';
jest.mock('@kbn/cloud-security-posture/src/hooks/use_data_view');
jest.mock('@kbn/cloud-security-posture/src/hooks/use_csp_setup_status_api');
@ -170,10 +171,6 @@ describe('<Vulnerabilities />', () => {
});
});
xit("renders the success state component when 'latest vulnerabilities findings' DataView exists and request status is 'success'", async () => {
// TODO: Add test cases for VulnerabilityContent
});
it('renders vuln_mgmt integrations installation prompt if vuln_mgmt integration is not installed', () => {
(useCspSetupStatusApi as jest.Mock).mockImplementation(() =>
createReactQueryResponse({
@ -203,4 +200,28 @@ describe('<Vulnerabilities />', () => {
],
});
});
it('renders Vulnerabilities page when there are findings', async () => {
(useCspSetupStatusApi as jest.Mock).mockImplementation(() =>
createReactQueryResponse({
status: 'success',
data: {
hasVulnerabilitiesFindings: true,
},
})
);
(useDataView as jest.Mock).mockReturnValue({
status: 'success',
data: createStubDataView({
spec: {
id: CDR_MISCONFIGURATIONS_DATA_VIEW_ID_PREFIX,
},
}),
});
renderVulnerabilitiesPage();
expect(screen.getByTestId(VULNERABILITIES_PAGE)).toBeInTheDocument();
});
});

View file

@ -40,6 +40,7 @@ import {
VULN_MGMT_POLICY_TEMPLATE,
POSTURE_TYPE_ALL,
LATEST_VULNERABILITIES_RETENTION_POLICY,
CDR_VULNERABILITIES_INDEX_PATTERN,
} from '../../../common/constants';
import type {
CspApiRequestHandlerContext,
@ -194,6 +195,7 @@ export const getCspStatus = async ({
}: CspStatusDependencies): Promise<CspSetupStatus> => {
const [
hasMisconfigurationsFindings,
hasVulnerabilitiesFindings,
findingsLatestIndexStatus,
findingsIndexStatus,
scoreIndexStatus,
@ -218,6 +220,12 @@ export const getCspStatus = async ({
LATEST_FINDINGS_RETENTION_POLICY,
logger
),
checkIndexHasFindings(
esClient,
CDR_VULNERABILITIES_INDEX_PATTERN,
LATEST_VULNERABILITIES_RETENTION_POLICY,
logger
),
checkIndexStatus(esClient, LATEST_FINDINGS_INDEX_DEFAULT_NS, logger, {
postureType: POSTURE_TYPE_ALL,
retentionTime: LATEST_VULNERABILITIES_RETENTION_POLICY,
@ -405,6 +413,7 @@ export const getCspStatus = async ({
...statusResponseInfo,
installedPackageVersion: installation?.install_version,
hasMisconfigurationsFindings,
hasVulnerabilitiesFindings,
};
assertResponse(response, logger);

View file

@ -14331,9 +14331,6 @@
"xpack.csp.findings.findingsFlyout.ruleTabTitle": "Règle",
"xpack.csp.findings.findingsFlyout.tableTabTitle": "Tableau",
"xpack.csp.findings.findingsTable.findingsTableColumn.lastCheckedColumnLabel": "Dernière vérification",
"xpack.csp.findings.findingsTable.findingsTableColumn.packageFixedVersionColumnLabel": "Version du correctif",
"xpack.csp.findings.findingsTable.findingsTableColumn.packageNameColumnLabel": "Pack",
"xpack.csp.findings.findingsTable.findingsTableColumn.packageVersionColumnLabel": "Version",
"xpack.csp.findings.findingsTable.findingsTableColumn.resourceIdColumnLabel": "ID ressource",
"xpack.csp.findings.findingsTable.findingsTableColumn.resourceNameColumnLabel": "Nom de ressource",
"xpack.csp.findings.findingsTable.findingsTableColumn.resourceTypeColumnLabel": "Type de ressource",
@ -14341,9 +14338,6 @@
"xpack.csp.findings.findingsTable.findingsTableColumn.ruleNameColumnLabel": "Nom de règle",
"xpack.csp.findings.findingsTable.findingsTableColumn.ruleNumberColumnLabel": "Numéro de règle",
"xpack.csp.findings.findingsTable.findingsTableColumn.ruleSectionColumnLabel": "Section CIS",
"xpack.csp.findings.findingsTable.findingsTableColumn.vulnerabilityIdColumnLabel": "Vulnérabilité",
"xpack.csp.findings.findingsTable.findingsTableColumn.vulnerabilityScoreColumnLabel": "CVSS",
"xpack.csp.findings.findingsTable.findingsTableColumn.vulnerabilitySeverityColumnLabel": "Sévérité",
"xpack.csp.findings.gcpIntegration.gcpInputText.credentialFileText": "Chemin vers le fichier JSON qui contient les informations d'identification et la clé utilisés pour souscrire",
"xpack.csp.findings.gcpIntegration.gcpInputText.credentialJSONText": "Blob JSON qui contient les informations d'identification et la clé utilisées pour souscrire",
"xpack.csp.findings.gcpIntegration.gcpInputText.credentialSelectBoxTitle": "Informations d'identification",

View file

@ -14321,9 +14321,6 @@
"xpack.csp.findings.findingsFlyout.ruleTabTitle": "ルール",
"xpack.csp.findings.findingsFlyout.tableTabTitle": "表",
"xpack.csp.findings.findingsTable.findingsTableColumn.lastCheckedColumnLabel": "最終確認",
"xpack.csp.findings.findingsTable.findingsTableColumn.packageFixedVersionColumnLabel": "修正バージョン",
"xpack.csp.findings.findingsTable.findingsTableColumn.packageNameColumnLabel": "パッケージ",
"xpack.csp.findings.findingsTable.findingsTableColumn.packageVersionColumnLabel": "Version",
"xpack.csp.findings.findingsTable.findingsTableColumn.resourceIdColumnLabel": "リソースID",
"xpack.csp.findings.findingsTable.findingsTableColumn.resourceNameColumnLabel": "リソース名",
"xpack.csp.findings.findingsTable.findingsTableColumn.resourceTypeColumnLabel": "リソースタイプ",
@ -14331,9 +14328,6 @@
"xpack.csp.findings.findingsTable.findingsTableColumn.ruleNameColumnLabel": "ルール名",
"xpack.csp.findings.findingsTable.findingsTableColumn.ruleNumberColumnLabel": "ルール番号",
"xpack.csp.findings.findingsTable.findingsTableColumn.ruleSectionColumnLabel": "CISセクション",
"xpack.csp.findings.findingsTable.findingsTableColumn.vulnerabilityIdColumnLabel": "脆弱性",
"xpack.csp.findings.findingsTable.findingsTableColumn.vulnerabilityScoreColumnLabel": "CVSS",
"xpack.csp.findings.findingsTable.findingsTableColumn.vulnerabilitySeverityColumnLabel": "深刻度",
"xpack.csp.findings.gcpIntegration.gcpInputText.credentialFileText": "サブスクライブに使用される資格情報とキーを含むJSONファイルへのパス",
"xpack.csp.findings.gcpIntegration.gcpInputText.credentialJSONText": "サブスクライブに使用される資格情報とキーを含むJSON blob",
"xpack.csp.findings.gcpIntegration.gcpInputText.credentialSelectBoxTitle": "資格情報",

View file

@ -14343,9 +14343,6 @@
"xpack.csp.findings.findingsFlyout.ruleTabTitle": "规则",
"xpack.csp.findings.findingsFlyout.tableTabTitle": "表",
"xpack.csp.findings.findingsTable.findingsTableColumn.lastCheckedColumnLabel": "上次检查时间",
"xpack.csp.findings.findingsTable.findingsTableColumn.packageFixedVersionColumnLabel": "修复版本",
"xpack.csp.findings.findingsTable.findingsTableColumn.packageNameColumnLabel": "软件包",
"xpack.csp.findings.findingsTable.findingsTableColumn.packageVersionColumnLabel": "版本",
"xpack.csp.findings.findingsTable.findingsTableColumn.resourceIdColumnLabel": "资源 ID",
"xpack.csp.findings.findingsTable.findingsTableColumn.resourceNameColumnLabel": "资源名称",
"xpack.csp.findings.findingsTable.findingsTableColumn.resourceTypeColumnLabel": "资源类型",
@ -14353,9 +14350,6 @@
"xpack.csp.findings.findingsTable.findingsTableColumn.ruleNameColumnLabel": "规则名称",
"xpack.csp.findings.findingsTable.findingsTableColumn.ruleNumberColumnLabel": "规则编号",
"xpack.csp.findings.findingsTable.findingsTableColumn.ruleSectionColumnLabel": "CIS 部分",
"xpack.csp.findings.findingsTable.findingsTableColumn.vulnerabilityIdColumnLabel": "漏洞",
"xpack.csp.findings.findingsTable.findingsTableColumn.vulnerabilityScoreColumnLabel": "CVSS",
"xpack.csp.findings.findingsTable.findingsTableColumn.vulnerabilitySeverityColumnLabel": "严重性",
"xpack.csp.findings.gcpIntegration.gcpInputText.credentialFileText": "包含用于订阅的凭据和密钥的 JSON 文件的路径",
"xpack.csp.findings.gcpIntegration.gcpInputText.credentialJSONText": "包含用于订阅的凭据和密钥的 JSON Blob",
"xpack.csp.findings.gcpIntegration.gcpInputText.credentialSelectBoxTitle": "凭据",