mirror of
https://github.com/elastic/kibana.git
synced 2025-04-24 09:48:58 -04:00
[Cloud Posture] Vul mgmt flyout details panel (#154873)
## Summary Summarize your PR. If it involves visual changes include a screenshot or gif. <img width="1695" alt="Screen Shot 2023-04-12 at 5 04 30 PM" src="https://user-images.githubusercontent.com/17135495/231596052-d47d0e7a-7abb-4aa1-a022-0fd1b1def981.png"> <img width="1656" alt="Screen Shot 2023-04-12 at 5 30 55 PM" src="https://user-images.githubusercontent.com/17135495/231596060-89ea7d40-8727-4dab-a3ba-c57cf83ad0cc.png"> <img width="1659" alt="Screen Shot 2023-04-12 at 5 31 09 PM" src="https://user-images.githubusercontent.com/17135495/231596067-839c0c1f-84a6-42b5-9683-77c70594af65.png"> Flyou details Feature includes: Results are fetched from the logs-cloud_security_posture.vulnerabilities-default index. A Flyout opens when clicking the Expand Icon in the Vulnerabilities table. The Flyout has two tabs: Overview and JSON. The Overview tab consists of four sections: CVSS, Data source, Publish date, Description, Fixes, and Vulnerability Scores TODO Add pagination in follow up pr Add unit tests
This commit is contained in:
parent
d213107c15
commit
eb6bfd8476
7 changed files with 616 additions and 126 deletions
|
@ -5,7 +5,7 @@
|
|||
* 2.0.
|
||||
*/
|
||||
|
||||
import { EuiBadge, EuiIcon, EuiTextColor, useEuiFontSize } from '@elastic/eui';
|
||||
import { EuiBadge, EuiIcon, EuiTextColor } from '@elastic/eui';
|
||||
import React from 'react';
|
||||
import { css } from '@emotion/react';
|
||||
import { float } from '@elastic/elasticsearch/lib/api/typesWithBodyKey';
|
||||
|
@ -21,10 +21,6 @@ interface SeverityStatusBadgeProps {
|
|||
score: float;
|
||||
}
|
||||
|
||||
interface ExploitsStatusBadgeProps {
|
||||
totalExploits: number;
|
||||
}
|
||||
|
||||
export const CVSScoreBadge = ({ score, version }: CVSScoreBadgeProps) => {
|
||||
const color = getCvsScoreColor(score);
|
||||
const versionDisplay = version ? `v${version.split('.')[0]}` : null;
|
||||
|
@ -60,28 +56,15 @@ export const SeverityStatusBadge = ({ score, status }: SeverityStatusBadgeProps)
|
|||
const color = getCvsScoreColor(score);
|
||||
|
||||
return (
|
||||
<>
|
||||
<EuiIcon type="dot" color={color} />
|
||||
{status}
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
export const ExploitsStatusBadge = ({ totalExploits }: ExploitsStatusBadgeProps) => {
|
||||
const xxsFontSize = useEuiFontSize('xxs').fontSize;
|
||||
|
||||
return (
|
||||
<EuiBadge
|
||||
color={'hollow'}
|
||||
iconType="bug"
|
||||
<div
|
||||
css={css`
|
||||
.euiBadge__text {
|
||||
font-weight: 400;
|
||||
font-size: ${xxsFontSize};
|
||||
}
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
align-items: center;
|
||||
`}
|
||||
>
|
||||
{totalExploits}
|
||||
</EuiBadge>
|
||||
<EuiIcon type="dot" color={color} />
|
||||
{status}
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
|
|
@ -20,48 +20,7 @@ export interface VulnerabilityRecord {
|
|||
sequence: number;
|
||||
outcome: string;
|
||||
};
|
||||
vulnerability: {
|
||||
score: {
|
||||
version: string;
|
||||
impact: number;
|
||||
base: number;
|
||||
};
|
||||
cwe: string[];
|
||||
id: string;
|
||||
title: string;
|
||||
reference: string;
|
||||
severity: string;
|
||||
cvss: {
|
||||
nvd: {
|
||||
V3Vector: string;
|
||||
V3Score: number;
|
||||
};
|
||||
redhat?: {
|
||||
V3Vector: string;
|
||||
V3Score: number;
|
||||
};
|
||||
ghsa?: {
|
||||
V3Vector: string;
|
||||
V3Score: number;
|
||||
};
|
||||
};
|
||||
data_source: {
|
||||
ID: string;
|
||||
Name: string;
|
||||
URL: string;
|
||||
};
|
||||
enumeration: string;
|
||||
description: string;
|
||||
classification: string;
|
||||
scanner: {
|
||||
vendor: string;
|
||||
};
|
||||
package: {
|
||||
version: string;
|
||||
name: string;
|
||||
fixed_version: string;
|
||||
};
|
||||
};
|
||||
vulnerability: Vulnerability;
|
||||
ecs: {
|
||||
version: string;
|
||||
};
|
||||
|
@ -116,3 +75,58 @@ export interface VulnerabilityRecord {
|
|||
commit_time: string;
|
||||
};
|
||||
}
|
||||
|
||||
export interface Vulnerability {
|
||||
published_at: string;
|
||||
score: {
|
||||
version: string;
|
||||
impact: number;
|
||||
base: number;
|
||||
};
|
||||
cwe: string[];
|
||||
id: string;
|
||||
title: string;
|
||||
reference: string;
|
||||
severity: string;
|
||||
cvss: {
|
||||
nvd: VectorScoreBase;
|
||||
redhat?: VectorScoreBase;
|
||||
ghsa?: VectorScoreBase;
|
||||
};
|
||||
data_source: {
|
||||
ID: string;
|
||||
Name: string;
|
||||
URL: string;
|
||||
};
|
||||
enumeration: string;
|
||||
description: string;
|
||||
classification: string;
|
||||
scanner: {
|
||||
vendor: string;
|
||||
};
|
||||
package: {
|
||||
version: string;
|
||||
name: string;
|
||||
fixed_version: string;
|
||||
};
|
||||
}
|
||||
|
||||
export interface VectorScoreBase {
|
||||
V3Score?: number;
|
||||
V3Vector?: string;
|
||||
V2Score?: number;
|
||||
V2Vector?: string;
|
||||
}
|
||||
|
||||
export type Vendor = 'NVD' | 'Red Hat' | 'GHSA';
|
||||
|
||||
export interface CVSScoreProps {
|
||||
vectorBaseScore: VectorScoreBase;
|
||||
vendor: string;
|
||||
}
|
||||
|
||||
export interface Vector {
|
||||
version: string;
|
||||
vector: string;
|
||||
score: number | undefined;
|
||||
}
|
||||
|
|
|
@ -7,6 +7,7 @@
|
|||
|
||||
import { EuiDataGridColumn } from '@elastic/eui';
|
||||
import { i18n } from '@kbn/i18n';
|
||||
import { VectorScoreBase, Vector } from './types';
|
||||
|
||||
export const vulnerabilitiesColumns = {
|
||||
actions: 'actions',
|
||||
|
@ -85,3 +86,29 @@ export const getVulnerabilitiesColumnsGrid = (): EuiDataGridColumn[] => {
|
|||
},
|
||||
];
|
||||
};
|
||||
|
||||
export const getVectorScoreList = (vectorBaseScore: VectorScoreBase) => {
|
||||
const result: Vector[] = [];
|
||||
const v2Vector = vectorBaseScore?.V2Vector;
|
||||
const v2Score = vectorBaseScore?.V2Score;
|
||||
const v3Vector = vectorBaseScore?.V3Vector;
|
||||
const v3Score = vectorBaseScore?.V3Score;
|
||||
|
||||
if (v2Vector) {
|
||||
result.push({
|
||||
version: '2.0',
|
||||
vector: v2Vector,
|
||||
score: v2Score,
|
||||
});
|
||||
}
|
||||
|
||||
if (v3Vector) {
|
||||
result.push({
|
||||
version: '2.0',
|
||||
vector: v3Vector,
|
||||
score: v3Score,
|
||||
});
|
||||
}
|
||||
|
||||
return result;
|
||||
};
|
||||
|
|
|
@ -17,7 +17,7 @@ import {
|
|||
import { css } from '@emotion/react';
|
||||
import { FormattedMessage } from '@kbn/i18n-react';
|
||||
import { DataView } from '@kbn/data-views-plugin/common';
|
||||
import React, { useMemo } from 'react';
|
||||
import React, { useMemo, useState } from 'react';
|
||||
import { i18n } from '@kbn/i18n';
|
||||
import { LOCAL_STORAGE_PAGE_SIZE_FINDINGS_KEY } from '../../common/constants';
|
||||
import { useCloudPostureTable } from '../../common/hooks/use_cloud_posture_table';
|
||||
|
@ -29,6 +29,7 @@ import { ErrorCallout } from '../configurations/layout/error_callout';
|
|||
import { FindingsSearchBar } from '../configurations/layout/findings_search_bar';
|
||||
import { useFilteredDataView } from '../../common/api/use_filtered_data_view';
|
||||
import { CVSScoreBadge, SeverityStatusBadge } from '../../components/vulnerability_badges';
|
||||
import { VulnerabilityFindingFlyout } from './vulnerabilities_finding_flyout/vulnerability_finding_flyout';
|
||||
import { NoVulnerabilitiesStates } from '../../components/no_vulnerabilities_states';
|
||||
import { useCspSetupStatusApi } from '../../common/api/use_setup_status_api';
|
||||
|
||||
|
@ -79,6 +80,21 @@ const VulnerabilitiesContent = ({ dataView }: { dataView: DataView }) => {
|
|||
enabled: !queryError,
|
||||
});
|
||||
|
||||
const [isVulnerabilityDetailFlyoutVisible, setIsVulnerabilityDetailFlyoutVisible] =
|
||||
useState(false);
|
||||
|
||||
const [vulnerability, setVulnerability] = useState<VulnerabilityRecord>();
|
||||
|
||||
const showFlyout = (vulnerabilityRecord: VulnerabilityRecord) => {
|
||||
setIsVulnerabilityDetailFlyoutVisible(true);
|
||||
setVulnerability(vulnerabilityRecord);
|
||||
};
|
||||
|
||||
const hideFlyout = () => {
|
||||
setIsVulnerabilityDetailFlyoutVisible(false);
|
||||
setVulnerability(undefined);
|
||||
};
|
||||
|
||||
const renderCellValue = useMemo(() => {
|
||||
return ({ rowIndex, columnId }: EuiDataGridCellValueElementProps) => {
|
||||
const vulnerabilityRow = data?.page[rowIndex] as VulnerabilityRecord;
|
||||
|
@ -92,7 +108,7 @@ const VulnerabilitiesContent = ({ dataView }: { dataView: DataView }) => {
|
|||
iconType="expand"
|
||||
aria-label="View"
|
||||
onClick={() => {
|
||||
alert(`Flyout id ${vulnerabilityRow.vulnerability.id}`);
|
||||
showFlyout(vulnerabilityRow);
|
||||
}}
|
||||
/>
|
||||
);
|
||||
|
@ -181,64 +197,73 @@ const VulnerabilitiesContent = ({ dataView }: { dataView: DataView }) => {
|
|||
}
|
||||
/>
|
||||
) : (
|
||||
<EuiDataGrid
|
||||
css={css`
|
||||
& .euiDataGridHeaderCell__icon {
|
||||
display: none;
|
||||
}
|
||||
& .euiDataGrid__controls {
|
||||
border-bottom: none;
|
||||
}
|
||||
& .euiButtonIcon {
|
||||
color: ${euiTheme.colors.primary};
|
||||
}
|
||||
& .euiDataGridRowCell {
|
||||
font-size: ${euiTheme.size.m};
|
||||
}
|
||||
`}
|
||||
aria-label="Data grid styling demo"
|
||||
columns={columns}
|
||||
columnVisibility={{
|
||||
visibleColumns: columns.map(({ id }) => id),
|
||||
setVisibleColumns: () => {},
|
||||
}}
|
||||
rowCount={data?.total}
|
||||
toolbarVisibility={{
|
||||
showColumnSelector: false,
|
||||
showDisplaySelector: false,
|
||||
showKeyboardShortcuts: false,
|
||||
additionalControls: {
|
||||
left: {
|
||||
prepend: (
|
||||
<EuiButtonEmpty size="xs" color="text">
|
||||
{i18n.translate('xpack.csp.vulnerabilities.totalVulnerabilities', {
|
||||
defaultMessage:
|
||||
'{total, plural, one {# Vulnerability} other {# Vulnerabilities}}',
|
||||
values: { total: data?.total },
|
||||
})}
|
||||
</EuiButtonEmpty>
|
||||
),
|
||||
<>
|
||||
<EuiDataGrid
|
||||
css={css`
|
||||
& .euiDataGridHeaderCell__icon {
|
||||
display: none;
|
||||
}
|
||||
& .euiDataGrid__controls {
|
||||
border-bottom: none;
|
||||
}
|
||||
& .euiButtonIcon {
|
||||
color: ${euiTheme.colors.primary};
|
||||
}
|
||||
& .euiDataGridRowCell {
|
||||
font-size: ${euiTheme.size.m};
|
||||
}
|
||||
`}
|
||||
aria-label="Data grid styling demo"
|
||||
columns={columns}
|
||||
columnVisibility={{
|
||||
visibleColumns: columns.map(({ id }) => id),
|
||||
setVisibleColumns: () => {},
|
||||
}}
|
||||
rowCount={data?.total}
|
||||
toolbarVisibility={{
|
||||
showColumnSelector: false,
|
||||
showDisplaySelector: false,
|
||||
showKeyboardShortcuts: false,
|
||||
additionalControls: {
|
||||
left: {
|
||||
prepend: (
|
||||
<EuiButtonEmpty size="xs" color="text">
|
||||
{i18n.translate('xpack.csp.vulnerabilities.totalVulnerabilities', {
|
||||
defaultMessage:
|
||||
'{total, plural, one {# Vulnerability} other {# Vulnerabilities}}',
|
||||
values: { total: data?.total },
|
||||
})}
|
||||
</EuiButtonEmpty>
|
||||
),
|
||||
},
|
||||
},
|
||||
},
|
||||
}}
|
||||
gridStyle={{
|
||||
border: 'horizontal',
|
||||
cellPadding: 'l',
|
||||
stripes: false,
|
||||
rowHover: 'none',
|
||||
header: 'underline',
|
||||
}}
|
||||
renderCellValue={renderCellValue}
|
||||
inMemory={{ level: 'sorting' }}
|
||||
sorting={{ columns: sort, onSort }}
|
||||
pagination={{
|
||||
pageIndex,
|
||||
pageSize,
|
||||
pageSizeOptions: [10, 25, 100],
|
||||
onChangeItemsPerPage,
|
||||
onChangePage,
|
||||
}}
|
||||
/>
|
||||
}}
|
||||
gridStyle={{
|
||||
border: 'horizontal',
|
||||
cellPadding: 'l',
|
||||
stripes: false,
|
||||
rowHover: 'none',
|
||||
header: 'underline',
|
||||
}}
|
||||
renderCellValue={renderCellValue}
|
||||
inMemory={{ level: 'sorting' }}
|
||||
sorting={{ columns: sort, onSort }}
|
||||
pagination={{
|
||||
pageIndex,
|
||||
pageSize,
|
||||
pageSizeOptions: [10, 25, 100],
|
||||
onChangeItemsPerPage,
|
||||
onChangePage,
|
||||
}}
|
||||
/>
|
||||
{/* Todo: Add Pagination */}
|
||||
{isVulnerabilityDetailFlyoutVisible && !!vulnerability && (
|
||||
<VulnerabilityFindingFlyout
|
||||
vulnerabilityRecord={vulnerability}
|
||||
closeFlyout={hideFlyout}
|
||||
/>
|
||||
)}
|
||||
</>
|
||||
)}
|
||||
</>
|
||||
);
|
||||
|
|
|
@ -0,0 +1,149 @@
|
|||
/*
|
||||
* 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, { useMemo, useState } from 'react';
|
||||
import {
|
||||
EuiFlexGroup,
|
||||
EuiFlexItem,
|
||||
EuiFlyout,
|
||||
EuiFlyoutBody,
|
||||
EuiFlyoutHeader,
|
||||
EuiLink,
|
||||
EuiTab,
|
||||
EuiTabs,
|
||||
EuiTitle,
|
||||
} from '@elastic/eui';
|
||||
|
||||
import { FormattedMessage } from '@kbn/i18n-react';
|
||||
import { euiThemeVars } from '@kbn/ui-theme';
|
||||
import { css } from '@emotion/react';
|
||||
import { VulnerabilityOverviewTab } from './vulnerability_overview_tab';
|
||||
import { VulnerabilityJsonTab } from './vulnerability_json_tab';
|
||||
import { SeverityStatusBadge } from '../../../components/vulnerability_badges';
|
||||
import { VulnerabilityRecord } from '../types';
|
||||
|
||||
const overviewTabId = 'overview';
|
||||
const jsonTabId = 'json';
|
||||
|
||||
export const VulnerabilityFindingFlyout = ({
|
||||
closeFlyout,
|
||||
vulnerabilityRecord,
|
||||
}: {
|
||||
closeFlyout: () => void;
|
||||
vulnerabilityRecord: VulnerabilityRecord;
|
||||
}) => {
|
||||
const [selectedTabId, setSelectedTabId] = useState(overviewTabId);
|
||||
const vulnerability = vulnerabilityRecord?.vulnerability;
|
||||
const resourceName = vulnerabilityRecord?.resource?.name;
|
||||
|
||||
const tabs = useMemo(
|
||||
() => [
|
||||
{
|
||||
id: overviewTabId,
|
||||
name: (
|
||||
<FormattedMessage
|
||||
id="xpack.csp.vulnerabilities.vulnerabilityFindingFlyout.overviewTabLabel"
|
||||
defaultMessage="Overview"
|
||||
/>
|
||||
),
|
||||
content: <VulnerabilityOverviewTab vulnerability={vulnerability} />,
|
||||
},
|
||||
{
|
||||
id: jsonTabId,
|
||||
name: (
|
||||
<FormattedMessage
|
||||
id="xpack.csp.vulnerabilities.vulnerabilityFindingFlyout.jsonTabLabel"
|
||||
defaultMessage="JSON"
|
||||
/>
|
||||
),
|
||||
content: <VulnerabilityJsonTab vulnerabilityRecord={vulnerabilityRecord} />,
|
||||
},
|
||||
],
|
||||
[vulnerability, vulnerabilityRecord]
|
||||
);
|
||||
|
||||
const onSelectedTabChanged = (id: string) => setSelectedTabId(id);
|
||||
|
||||
const renderTabs = () =>
|
||||
tabs.map((tab, index) => (
|
||||
<EuiTab
|
||||
onClick={() => onSelectedTabChanged(tab.id)}
|
||||
isSelected={tab.id === selectedTabId}
|
||||
key={index}
|
||||
>
|
||||
{tab.name}
|
||||
</EuiTab>
|
||||
));
|
||||
|
||||
const selectedTabContent = useMemo(
|
||||
() => tabs.find((obj) => obj.id === selectedTabId)?.content,
|
||||
[selectedTabId, tabs]
|
||||
);
|
||||
const nvdDomain = 'https://nvd';
|
||||
const nvdWebsite = `${nvdDomain}.nist.gov/vuln/detail/${vulnerabilityRecord?.vulnerability?.id}`;
|
||||
|
||||
const vulnerabilityReference = vulnerability?.reference.startsWith(nvdDomain)
|
||||
? vulnerability?.reference
|
||||
: nvdWebsite;
|
||||
|
||||
return (
|
||||
<EuiFlyout onClose={closeFlyout}>
|
||||
<EuiFlyoutHeader>
|
||||
<EuiFlexGroup
|
||||
direction="column"
|
||||
css={css`
|
||||
gap: ${euiThemeVars.euiSizeS};
|
||||
`}
|
||||
>
|
||||
<EuiFlexItem>
|
||||
<SeverityStatusBadge
|
||||
score={vulnerability?.score?.impact}
|
||||
status={vulnerability?.severity}
|
||||
/>
|
||||
</EuiFlexItem>
|
||||
<EuiFlexItem>
|
||||
<EuiFlexGroup
|
||||
direction="column"
|
||||
css={css`
|
||||
gap: ${euiThemeVars.euiSizeS};
|
||||
`}
|
||||
>
|
||||
<EuiFlexItem>
|
||||
<EuiTitle
|
||||
size="m"
|
||||
css={css`
|
||||
color: ${euiThemeVars.euiColorPrimaryText};
|
||||
line-height: 32px;
|
||||
`}
|
||||
>
|
||||
<EuiLink target={'_blank'} href={vulnerabilityReference}>
|
||||
{vulnerability?.id}
|
||||
</EuiLink>
|
||||
</EuiTitle>
|
||||
</EuiFlexItem>
|
||||
<EuiFlexItem>
|
||||
<p
|
||||
css={css`
|
||||
line-height: 20px;
|
||||
margin-bottom: ${euiThemeVars.euiSizeM};
|
||||
`}
|
||||
>
|
||||
{`${resourceName} | ${vulnerability?.package?.name} ${vulnerability?.package?.version}`}
|
||||
</p>
|
||||
</EuiFlexItem>
|
||||
</EuiFlexGroup>
|
||||
</EuiFlexItem>
|
||||
<EuiFlexItem>
|
||||
<EuiTabs>{renderTabs()}</EuiTabs>
|
||||
</EuiFlexItem>
|
||||
</EuiFlexGroup>
|
||||
</EuiFlyoutHeader>
|
||||
<EuiFlyoutBody>{selectedTabContent}</EuiFlyoutBody>
|
||||
{/* Todo: Add Pagination */}
|
||||
</EuiFlyout>
|
||||
);
|
||||
};
|
|
@ -0,0 +1,32 @@
|
|||
/*
|
||||
* 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 { CodeEditor } from '@kbn/kibana-react-plugin/public';
|
||||
import React from 'react';
|
||||
import { XJsonLang } from '@kbn/monaco';
|
||||
import { VulnerabilityRecord } from '../types';
|
||||
interface VulnerabilityJsonTabProps {
|
||||
vulnerabilityRecord: VulnerabilityRecord;
|
||||
}
|
||||
export const VulnerabilityJsonTab = ({ vulnerabilityRecord }: VulnerabilityJsonTabProps) => {
|
||||
const offsetHeight = 188;
|
||||
return (
|
||||
<div style={{ position: 'absolute', inset: 0, top: offsetHeight }}>
|
||||
<CodeEditor
|
||||
isCopyable
|
||||
allowFullScreen
|
||||
languageId={XJsonLang.ID}
|
||||
value={JSON.stringify(vulnerabilityRecord, null, 2)}
|
||||
options={{
|
||||
readOnly: true,
|
||||
lineNumbers: 'on',
|
||||
folding: true,
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
};
|
|
@ -0,0 +1,260 @@
|
|||
/*
|
||||
* 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 { EuiFlexGroup, EuiFlexItem, EuiHorizontalRule, EuiLink, EuiText } from '@elastic/eui';
|
||||
import { css } from '@emotion/react';
|
||||
import { FormattedMessage } from '@kbn/i18n-react';
|
||||
import moment from 'moment';
|
||||
import React from 'react';
|
||||
import { euiThemeVars } from '@kbn/ui-theme';
|
||||
import { i18n } from '@kbn/i18n';
|
||||
import { CVSScoreBadge } from '../../../components/vulnerability_badges';
|
||||
import { CVSScoreProps, VectorScoreBase, Vendor, Vulnerability } from '../types';
|
||||
import { getVectorScoreList } from '../utils';
|
||||
|
||||
const cvssVendors: Record<string, Vendor> = {
|
||||
nvd: 'NVD',
|
||||
redhat: 'Red Hat',
|
||||
ghsa: 'GHSA',
|
||||
};
|
||||
|
||||
interface VulnerabilityTabProps {
|
||||
vulnerability: Vulnerability;
|
||||
}
|
||||
|
||||
const CVSScore = ({ vectorBaseScore, vendor }: CVSScoreProps) => {
|
||||
const vendorName =
|
||||
cvssVendors[vendor] ??
|
||||
i18n.translate(
|
||||
'xpack.csp.vulnerabilities.vulnerabilityOverviewTab.cvsScore.unknownVendorName',
|
||||
{
|
||||
defaultMessage: 'Unknown vendor',
|
||||
}
|
||||
);
|
||||
|
||||
const vectorScores = getVectorScoreList(vectorBaseScore);
|
||||
|
||||
return (
|
||||
<EuiFlexGroup
|
||||
alignItems="center"
|
||||
css={css`
|
||||
border: 1px solid #d3dae6;
|
||||
border-radius: 6px;
|
||||
padding: ${euiThemeVars.euiSizeM};
|
||||
`}
|
||||
>
|
||||
<EuiFlexItem
|
||||
grow={false}
|
||||
css={css`
|
||||
width: 94px;
|
||||
font-weight: 600;
|
||||
`}
|
||||
>
|
||||
{vendorName}
|
||||
</EuiFlexItem>
|
||||
<EuiFlexGroup direction="column" gutterSize="s">
|
||||
{vectorScores.length > 0 &&
|
||||
vectorScores.map((vectorScore) => <VectorScore vectorScore={vectorScore} />)}
|
||||
</EuiFlexGroup>
|
||||
</EuiFlexGroup>
|
||||
);
|
||||
};
|
||||
|
||||
const VectorScore = ({
|
||||
vectorScore,
|
||||
}: {
|
||||
vectorScore: {
|
||||
vector: string;
|
||||
score: number | undefined;
|
||||
version: string;
|
||||
};
|
||||
}) => {
|
||||
const { score, vector, version } = vectorScore;
|
||||
return (
|
||||
<>
|
||||
<EuiFlexGroup
|
||||
alignItems="center"
|
||||
css={css`
|
||||
background: #f1f4fa;
|
||||
padding: ${euiThemeVars.euiSizeXS} ${euiThemeVars.euiSizeS};
|
||||
border-radius: 6px;
|
||||
`}
|
||||
>
|
||||
<EuiFlexItem>
|
||||
<EuiText
|
||||
css={css`
|
||||
font-size: ${euiThemeVars.euiFontSizeM};
|
||||
`}
|
||||
>
|
||||
{vector}{' '}
|
||||
</EuiText>
|
||||
</EuiFlexItem>
|
||||
|
||||
<EuiFlexItem grow={false}>
|
||||
{score && <CVSScoreBadge score={score} version={version} />}
|
||||
</EuiFlexItem>
|
||||
</EuiFlexGroup>
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
const VulnerabilityOverviewTiles = ({ vulnerability }: VulnerabilityTabProps) => {
|
||||
const tileStyle = css`
|
||||
padding: ${euiThemeVars.euiFontSizeM};
|
||||
background: ${euiThemeVars.euiColorLightestShade};
|
||||
border-radius: 6px;
|
||||
height: 74px;
|
||||
`;
|
||||
const tileTitleTextStyle = css`
|
||||
line-height: 20px;
|
||||
margin-bottom: 6px;
|
||||
`;
|
||||
|
||||
const date = moment(vulnerability?.published_at).format('LL').toString();
|
||||
|
||||
return (
|
||||
<EuiFlexGroup>
|
||||
<EuiFlexItem css={tileStyle}>
|
||||
<EuiText css={tileTitleTextStyle}>
|
||||
<FormattedMessage
|
||||
id="xpack.csp.vulnerabilities.vulnerabilityOverviewTile.cvsScore"
|
||||
defaultMessage="CVSS"
|
||||
/>
|
||||
</EuiText>
|
||||
<div>
|
||||
<CVSScoreBadge
|
||||
version={vulnerability?.score?.version}
|
||||
score={vulnerability?.score?.impact}
|
||||
/>
|
||||
</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>
|
||||
</EuiFlexGroup>
|
||||
);
|
||||
};
|
||||
|
||||
export const VulnerabilityOverviewTab = ({ vulnerability }: VulnerabilityTabProps) => {
|
||||
const emptyFixesMessageState = i18n.translate(
|
||||
'xpack.csp.vulnerabilities.vulnerabilityOverviewTab.emptyFixesMessage',
|
||||
{
|
||||
defaultMessage: 'No available fixes yet.',
|
||||
}
|
||||
);
|
||||
|
||||
const fixesDisplayText = vulnerability?.package?.fixed_version
|
||||
? `${vulnerability?.package?.name} ${vulnerability?.package?.fixed_version}`
|
||||
: emptyFixesMessageState;
|
||||
|
||||
const cvssScores: JSX.Element[] = Object.entries<VectorScoreBase>(vulnerability?.cvss).map(
|
||||
([vendor, vectorScoreBase]: [string, VectorScoreBase]) => {
|
||||
return (
|
||||
<EuiFlexItem>
|
||||
<CVSScore vectorBaseScore={vectorScoreBase} vendor={vendor} />
|
||||
</EuiFlexItem>
|
||||
);
|
||||
}
|
||||
);
|
||||
|
||||
const horizontalStyle = css`
|
||||
margin-block: 12px;
|
||||
`;
|
||||
|
||||
const flyoutSubheadingStyle = css`
|
||||
font-size: ${euiThemeVars.euiFontSizeM};
|
||||
line-height: 24px;
|
||||
margin-bottom: ${euiThemeVars.euiSizeS};
|
||||
font-weight: 600;
|
||||
`;
|
||||
|
||||
return (
|
||||
<EuiFlexGroup direction="column">
|
||||
<EuiFlexItem>
|
||||
<VulnerabilityOverviewTiles vulnerability={vulnerability} />
|
||||
</EuiFlexItem>
|
||||
|
||||
<EuiHorizontalRule css={horizontalStyle} />
|
||||
|
||||
<EuiFlexItem>
|
||||
<h4 css={flyoutSubheadingStyle}>
|
||||
<FormattedMessage
|
||||
id="xpack.csp.vulnerabilities.vulnerabilityOverviewTab.descriptionTitle"
|
||||
defaultMessage="Description"
|
||||
/>
|
||||
</h4>
|
||||
<EuiText>
|
||||
<FormattedMessage
|
||||
id="xpack.csp.vulnerabilities.vulnerabilityOverviewTab.descriptionText"
|
||||
defaultMessage="{description}"
|
||||
values={{ description: vulnerability?.description }}
|
||||
/>
|
||||
</EuiText>
|
||||
</EuiFlexItem>
|
||||
|
||||
<EuiHorizontalRule css={horizontalStyle} />
|
||||
|
||||
<EuiFlexItem>
|
||||
<h4 css={flyoutSubheadingStyle}>
|
||||
<FormattedMessage
|
||||
id="xpack.csp.vulnerabilities.vulnerabilityOverviewTab.fixes"
|
||||
defaultMessage="Fixes"
|
||||
/>
|
||||
</h4>
|
||||
<EuiText>{fixesDisplayText}</EuiText>
|
||||
</EuiFlexItem>
|
||||
|
||||
<EuiHorizontalRule css={horizontalStyle} />
|
||||
|
||||
{cvssScores?.length > 0 && (
|
||||
<EuiFlexItem>
|
||||
<h4 css={flyoutSubheadingStyle}>
|
||||
<FormattedMessage
|
||||
id="xpack.csp.vulnerabilities.vulnerabilityOverviewTab.vulnerabilityScores"
|
||||
defaultMessage="Vulnerability Scores"
|
||||
/>
|
||||
</h4>
|
||||
<EuiFlexGroup
|
||||
direction="column"
|
||||
gutterSize="l"
|
||||
css={css`
|
||||
margin-top: ${euiThemeVars.euiSizeS};
|
||||
`}
|
||||
>
|
||||
{cvssScores}
|
||||
</EuiFlexGroup>
|
||||
</EuiFlexItem>
|
||||
)}
|
||||
</EuiFlexGroup>
|
||||
);
|
||||
};
|
Loading…
Add table
Add a link
Reference in a new issue