mirror of
https://github.com/elastic/kibana.git
synced 2025-04-19 15:35:00 -04:00
[Cloud Security] Update Misconfiguration Flyout UI to Match Security Solution Flyouts (#216938)
## Summary This PR Updates the UI for Misconfiguration Findings Flyout. It now follows the UI looks of Security Solution flyouts https://github.com/user-attachments/assets/7443101f-2238-403b-a672-5bbd1e6827cd --------- Co-authored-by: Paulo Silva <paulo.henrique@elastic.co>
This commit is contained in:
parent
c9b32ff7ec
commit
6d688deebb
17 changed files with 626 additions and 413 deletions
|
@ -15309,24 +15309,14 @@
|
|||
"xpack.csp.findings.errorCallout.pageSearchErrorTitle": "Une erreur s’est produite lors de la récupération des résultats de recherche.",
|
||||
"xpack.csp.findings.errorCallout.showErrorButtonLabel": "Afficher le message d'erreur",
|
||||
"xpack.csp.findings.findingsFlyout.calloutTitle": "Certains champs ne sont pas fournis par {vendor}",
|
||||
"xpack.csp.findings.findingsFlyout.flyoutDescriptionList.resourceId": "ID ressource",
|
||||
"xpack.csp.findings.findingsFlyout.flyoutDescriptionList.resourceName": "Nom de ressource",
|
||||
"xpack.csp.findings.findingsFlyout.jsonTabTitle": "JSON",
|
||||
"xpack.csp.findings.findingsFlyout.overviewTab.alertsTitle": "Alertes",
|
||||
"xpack.csp.findings.findingsFlyout.overviewTab.cisSectionTitle": "Section de Framework",
|
||||
"xpack.csp.findings.findingsFlyout.overviewTab.dataViewTitle": "Vue de données",
|
||||
"xpack.csp.findings.findingsFlyout.overviewTab.defaultValueTitle": "Valeur par défaut",
|
||||
"xpack.csp.findings.findingsFlyout.overviewTab.detailsTitle": "Détails",
|
||||
"xpack.csp.findings.findingsFlyout.overviewTab.evaluatedAtTitle": "Évalué à",
|
||||
"xpack.csp.findings.findingsFlyout.overviewTab.evidenceDescription": "Les métadonnées spécifiques de la ressource évaluées pour établir ces conclusions de niveau",
|
||||
"xpack.csp.findings.findingsFlyout.overviewTab.evidenceSourcesTitle": "Preuve",
|
||||
"xpack.csp.findings.findingsFlyout.overviewTab.frameworkSourcesTitle": "Sources du framework",
|
||||
"xpack.csp.findings.findingsFlyout.overviewTab.impactTitle": "Impact",
|
||||
"xpack.csp.findings.findingsFlyout.overviewTab.rationaleTitle": "Environnement",
|
||||
"xpack.csp.findings.findingsFlyout.overviewTab.remediationTitle": "Résolution",
|
||||
"xpack.csp.findings.findingsFlyout.overviewTab.ruleNameTitle": "Nom de règle",
|
||||
"xpack.csp.findings.findingsFlyout.overviewTab.ruleTagsTitle": "Balises de règle",
|
||||
"xpack.csp.findings.findingsFlyout.overviewTab.vendorTitle": "Fournisseur",
|
||||
"xpack.csp.findings.findingsFlyout.overviewTabTitle": "Aperçu",
|
||||
"xpack.csp.findings.findingsFlyout.ruleNameTabField.ruleNameTooltip": "Gérer la règle",
|
||||
"xpack.csp.findings.findingsFlyout.ruleTab.AlertsTitle": "Alertes",
|
||||
|
@ -15340,7 +15330,6 @@
|
|||
"xpack.csp.findings.findingsFlyout.ruleTab.profileApplicabilityTitle": "Applicabilité du profil",
|
||||
"xpack.csp.findings.findingsFlyout.ruleTab.referencesTitle": "Références",
|
||||
"xpack.csp.findings.findingsFlyout.ruleTab.tagsTitle": "Balises",
|
||||
"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.resourceIdColumnLabel": "ID ressource",
|
||||
|
|
|
@ -15291,24 +15291,14 @@
|
|||
"xpack.csp.findings.errorCallout.pageSearchErrorTitle": "検索結果の取得中にエラーが発生しました",
|
||||
"xpack.csp.findings.errorCallout.showErrorButtonLabel": "エラーメッセージを表示",
|
||||
"xpack.csp.findings.findingsFlyout.calloutTitle": "一部のフィールドは{vendor}によって提供されていません",
|
||||
"xpack.csp.findings.findingsFlyout.flyoutDescriptionList.resourceId": "リソースID",
|
||||
"xpack.csp.findings.findingsFlyout.flyoutDescriptionList.resourceName": "リソース名",
|
||||
"xpack.csp.findings.findingsFlyout.jsonTabTitle": "JSON",
|
||||
"xpack.csp.findings.findingsFlyout.overviewTab.alertsTitle": "アラート",
|
||||
"xpack.csp.findings.findingsFlyout.overviewTab.cisSectionTitle": "フレームワークセクション",
|
||||
"xpack.csp.findings.findingsFlyout.overviewTab.dataViewTitle": "データビュー",
|
||||
"xpack.csp.findings.findingsFlyout.overviewTab.defaultValueTitle": "デフォルト値",
|
||||
"xpack.csp.findings.findingsFlyout.overviewTab.detailsTitle": "詳細",
|
||||
"xpack.csp.findings.findingsFlyout.overviewTab.evaluatedAtTitle": "評価日",
|
||||
"xpack.csp.findings.findingsFlyout.overviewTab.evidenceDescription": "この態勢の調査結果を生成するために評価された特定のリソースメタデータ",
|
||||
"xpack.csp.findings.findingsFlyout.overviewTab.evidenceSourcesTitle": "証拠",
|
||||
"xpack.csp.findings.findingsFlyout.overviewTab.frameworkSourcesTitle": "フレームワークソース",
|
||||
"xpack.csp.findings.findingsFlyout.overviewTab.impactTitle": "インパクト",
|
||||
"xpack.csp.findings.findingsFlyout.overviewTab.rationaleTitle": "根拠",
|
||||
"xpack.csp.findings.findingsFlyout.overviewTab.remediationTitle": "修正",
|
||||
"xpack.csp.findings.findingsFlyout.overviewTab.ruleNameTitle": "ルール名",
|
||||
"xpack.csp.findings.findingsFlyout.overviewTab.ruleTagsTitle": "ルールタグ",
|
||||
"xpack.csp.findings.findingsFlyout.overviewTab.vendorTitle": "ベンダー",
|
||||
"xpack.csp.findings.findingsFlyout.overviewTabTitle": "概要",
|
||||
"xpack.csp.findings.findingsFlyout.ruleNameTabField.ruleNameTooltip": "ルールの管理",
|
||||
"xpack.csp.findings.findingsFlyout.ruleTab.AlertsTitle": "アラート",
|
||||
|
@ -15322,7 +15312,6 @@
|
|||
"xpack.csp.findings.findingsFlyout.ruleTab.profileApplicabilityTitle": "プロファイル適用性",
|
||||
"xpack.csp.findings.findingsFlyout.ruleTab.referencesTitle": "基準",
|
||||
"xpack.csp.findings.findingsFlyout.ruleTab.tagsTitle": "タグ",
|
||||
"xpack.csp.findings.findingsFlyout.ruleTabTitle": "ルール",
|
||||
"xpack.csp.findings.findingsFlyout.tableTabTitle": "表",
|
||||
"xpack.csp.findings.findingsTable.findingsTableColumn.lastCheckedColumnLabel": "最終確認",
|
||||
"xpack.csp.findings.findingsTable.findingsTableColumn.resourceIdColumnLabel": "リソースID",
|
||||
|
|
|
@ -15325,24 +15325,14 @@
|
|||
"xpack.csp.findings.errorCallout.pageSearchErrorTitle": "检索搜索结果时遇到问题",
|
||||
"xpack.csp.findings.errorCallout.showErrorButtonLabel": "显示错误消息",
|
||||
"xpack.csp.findings.findingsFlyout.calloutTitle": "{vendor} 未提供某些字段",
|
||||
"xpack.csp.findings.findingsFlyout.flyoutDescriptionList.resourceId": "资源 ID",
|
||||
"xpack.csp.findings.findingsFlyout.flyoutDescriptionList.resourceName": "资源名称",
|
||||
"xpack.csp.findings.findingsFlyout.jsonTabTitle": "JSON",
|
||||
"xpack.csp.findings.findingsFlyout.overviewTab.alertsTitle": "告警",
|
||||
"xpack.csp.findings.findingsFlyout.overviewTab.cisSectionTitle": "框架部分",
|
||||
"xpack.csp.findings.findingsFlyout.overviewTab.dataViewTitle": "数据视图",
|
||||
"xpack.csp.findings.findingsFlyout.overviewTab.defaultValueTitle": "默认值",
|
||||
"xpack.csp.findings.findingsFlyout.overviewTab.detailsTitle": "详情",
|
||||
"xpack.csp.findings.findingsFlyout.overviewTab.evaluatedAtTitle": "评估时间",
|
||||
"xpack.csp.findings.findingsFlyout.overviewTab.evidenceDescription": "已进行评估以生成此态势结果的特定资源元数据",
|
||||
"xpack.csp.findings.findingsFlyout.overviewTab.evidenceSourcesTitle": "证据",
|
||||
"xpack.csp.findings.findingsFlyout.overviewTab.frameworkSourcesTitle": "框架源",
|
||||
"xpack.csp.findings.findingsFlyout.overviewTab.impactTitle": "影响",
|
||||
"xpack.csp.findings.findingsFlyout.overviewTab.rationaleTitle": "理由",
|
||||
"xpack.csp.findings.findingsFlyout.overviewTab.remediationTitle": "补救",
|
||||
"xpack.csp.findings.findingsFlyout.overviewTab.ruleNameTitle": "规则名称",
|
||||
"xpack.csp.findings.findingsFlyout.overviewTab.ruleTagsTitle": "规则标签",
|
||||
"xpack.csp.findings.findingsFlyout.overviewTab.vendorTitle": "向量",
|
||||
"xpack.csp.findings.findingsFlyout.overviewTabTitle": "概览",
|
||||
"xpack.csp.findings.findingsFlyout.ruleNameTabField.ruleNameTooltip": "管理规则",
|
||||
"xpack.csp.findings.findingsFlyout.ruleTab.AlertsTitle": "告警",
|
||||
|
@ -15356,7 +15346,6 @@
|
|||
"xpack.csp.findings.findingsFlyout.ruleTab.profileApplicabilityTitle": "配置文件适用性",
|
||||
"xpack.csp.findings.findingsFlyout.ruleTab.referencesTitle": "参考",
|
||||
"xpack.csp.findings.findingsFlyout.ruleTab.tagsTitle": "标签",
|
||||
"xpack.csp.findings.findingsFlyout.ruleTabTitle": "规则",
|
||||
"xpack.csp.findings.findingsFlyout.tableTabTitle": "表",
|
||||
"xpack.csp.findings.findingsTable.findingsTableColumn.lastCheckedColumnLabel": "上次检查时间",
|
||||
"xpack.csp.findings.findingsTable.findingsTableColumn.resourceIdColumnLabel": "资源 ID",
|
||||
|
|
|
@ -13,7 +13,7 @@ import { UiActionsStart } from '@kbn/ui-actions-plugin/public';
|
|||
import { FieldFormatsStart } from '@kbn/field-formats-plugin/public';
|
||||
import { IndexPatternFieldEditorStart } from '@kbn/data-view-field-editor-plugin/public';
|
||||
import type { DataPublicPluginStart } from '@kbn/data-plugin/public';
|
||||
import { ToastsStart } from '@kbn/core/public';
|
||||
import { HttpSetup, ToastsStart } from '@kbn/core/public';
|
||||
import { Storage } from '@kbn/kibana-utils-plugin/public';
|
||||
import type { FlyoutPanelProps } from '@kbn/expandable-flyout';
|
||||
import type { ChartsPluginStart } from '@kbn/charts-plugin/public';
|
||||
|
@ -22,7 +22,7 @@ import type { FleetStart } from '@kbn/fleet-plugin/public';
|
|||
import type { UsageCollectionStart } from '@kbn/usage-collection-plugin/public';
|
||||
import { SharePluginStart } from '@kbn/share-plugin/public';
|
||||
import { SpacesPluginStart } from '@kbn/spaces-plugin/public';
|
||||
import { CspFinding } from '@kbn/cloud-security-posture-common';
|
||||
import { CspFinding, RuleResponse } from '@kbn/cloud-security-posture-common';
|
||||
import type { estypes } from '@elastic/elasticsearch';
|
||||
import type { IKibanaSearchResponse, IKibanaSearchRequest } from '@kbn/search-types';
|
||||
|
||||
|
@ -89,3 +89,19 @@ export interface FindingsMisconfigurationPanelExpandableFlyoutProps extends Flyo
|
|||
key: 'findings-misconfiguration-panel';
|
||||
params: FindingMisconfigurationFlyoutProps;
|
||||
}
|
||||
export interface FindingsMisconfigurationFlyoutHeaderProps {
|
||||
finding: CspFinding;
|
||||
}
|
||||
|
||||
export interface FindingsMisconfigurationFlyoutContentProps {
|
||||
finding: CspFinding;
|
||||
}
|
||||
|
||||
export interface FindingMisconfigurationFlyoutFooterProps {
|
||||
createRuleFn: (http: HttpSetup) => Promise<RuleResponse>;
|
||||
}
|
||||
|
||||
export interface FindingMisconfigurationFlyoutContentProps {
|
||||
finding: CspFinding;
|
||||
createRuleFn: (http: HttpSetup) => Promise<RuleResponse>;
|
||||
}
|
||||
|
|
|
@ -7,15 +7,29 @@
|
|||
import React from 'react';
|
||||
import { CDR_MISCONFIGURATIONS_INDEX_PATTERN } from '@kbn/cloud-security-posture-common';
|
||||
import userEvent from '@testing-library/user-event';
|
||||
import { FindingsRuleFlyout } from './findings_flyout';
|
||||
import { render, screen } from '@testing-library/react';
|
||||
import { useMisconfigurationFinding } from '@kbn/cloud-security-posture/src/hooks/use_misconfiguration_finding';
|
||||
import { TestProvider } from '../../../test/test_provider';
|
||||
import { mockFindingsHit, mockWizFinding } from '../__mocks__/findings';
|
||||
import { FindingMisconfigurationFlyoutContentProps } from '@kbn/cloud-security-posture';
|
||||
import FindingsMisconfigurationFlyoutContent from './findings_right/content';
|
||||
import FindingsMisconfigurationFlyoutFooter from './findings_right/footer';
|
||||
import FindingsMisconfigurationFlyoutHeader from './findings_right/header';
|
||||
import FindingsRuleFlyout from './findings_flyout';
|
||||
|
||||
const TestComponent = () => (
|
||||
<TestProvider>
|
||||
<FindingsRuleFlyout ruleId={'rule_id_test'} resourceId={'resource_id_test'} />
|
||||
<FindingsRuleFlyout ruleId={'rule_id_test'} resourceId={'resource_id_test'}>
|
||||
{({ finding, createRuleFn }: FindingMisconfigurationFlyoutContentProps) => {
|
||||
return (
|
||||
<>
|
||||
<FindingsMisconfigurationFlyoutHeader finding={finding} />
|
||||
<FindingsMisconfigurationFlyoutContent finding={finding} />
|
||||
<FindingsMisconfigurationFlyoutFooter createRuleFn={createRuleFn} />
|
||||
</>
|
||||
);
|
||||
}}
|
||||
</FindingsRuleFlyout>
|
||||
</TestProvider>
|
||||
);
|
||||
|
||||
|
@ -32,9 +46,8 @@ describe('<FindingsFlyout/>', () => {
|
|||
|
||||
const { getAllByText, getByText } = render(<TestComponent />);
|
||||
|
||||
getAllByText(mockFindingsHit.rule.name);
|
||||
getAllByText(mockFindingsHit.resource.name);
|
||||
getByText(mockFindingsHit.resource.id);
|
||||
getByText(mockFindingsHit.resource.name);
|
||||
getAllByText(mockFindingsHit.rule.section);
|
||||
getByText(CDR_MISCONFIGURATIONS_INDEX_PATTERN);
|
||||
mockFindingsHit.rule.tags.forEach((tag) => {
|
||||
|
@ -60,41 +73,6 @@ describe('<FindingsFlyout/>', () => {
|
|||
});
|
||||
});
|
||||
|
||||
describe('Rule Tab', () => {
|
||||
it('displays rule text details', async () => {
|
||||
const { getByText, getAllByText } = render(<TestComponent />);
|
||||
await userEvent.click(screen.getByTestId('findings_flyout_tab_rule'));
|
||||
|
||||
getAllByText(mockFindingsHit.rule.name);
|
||||
getByText(mockFindingsHit.rule.benchmark.name);
|
||||
getAllByText(mockFindingsHit.rule.section);
|
||||
mockFindingsHit.rule.tags.forEach((tag) => {
|
||||
getAllByText(tag);
|
||||
});
|
||||
});
|
||||
|
||||
it('displays missing info callout when data source is not CSP', async () => {
|
||||
(useMisconfigurationFinding as jest.Mock).mockReturnValue({
|
||||
data: { result: { hits: [{ _source: mockWizFinding }] } },
|
||||
});
|
||||
const { getByText } = render(<TestComponent />);
|
||||
await userEvent.click(screen.getByTestId('findings_flyout_tab_rule'));
|
||||
|
||||
getByText('Some fields not provided by Wiz');
|
||||
});
|
||||
|
||||
it('does not display missing info callout when data source is CSP', async () => {
|
||||
(useMisconfigurationFinding as jest.Mock).mockReturnValue({
|
||||
data: { result: { hits: [{ _source: mockFindingsHit }] } },
|
||||
});
|
||||
const { queryByText } = render(<TestComponent />);
|
||||
await userEvent.click(screen.getByTestId('findings_flyout_tab_rule'));
|
||||
|
||||
const missingInfoCallout = queryByText('Some fields not provided by Wiz');
|
||||
expect(missingInfoCallout).toBeNull();
|
||||
});
|
||||
});
|
||||
|
||||
describe('Table Tab', () => {
|
||||
it('displays resource name and id', async () => {
|
||||
(useMisconfigurationFinding as jest.Mock).mockReturnValue({
|
||||
|
|
|
@ -5,96 +5,32 @@
|
|||
* 2.0.
|
||||
*/
|
||||
|
||||
import React, { useState } from 'react';
|
||||
import React from 'react';
|
||||
import {
|
||||
useEuiTheme,
|
||||
EuiFlexItem,
|
||||
EuiSpacer,
|
||||
EuiTextColor,
|
||||
EuiFlyoutHeader,
|
||||
EuiTitle,
|
||||
EuiFlyoutBody,
|
||||
EuiTabs,
|
||||
EuiTab,
|
||||
EuiFlexGroup,
|
||||
PropsOf,
|
||||
EuiCodeBlock,
|
||||
EuiMarkdownFormat,
|
||||
EuiIcon,
|
||||
EuiFlyoutFooter,
|
||||
EuiToolTip,
|
||||
EuiDescriptionListProps,
|
||||
EuiCallOut,
|
||||
EuiLink,
|
||||
EuiPanel,
|
||||
EuiIconProps,
|
||||
} from '@elastic/eui';
|
||||
import { FormattedMessage } from '@kbn/i18n-react';
|
||||
import { assertNever } from '@kbn/std';
|
||||
import { i18n } from '@kbn/i18n';
|
||||
import type { HttpSetup } from '@kbn/core/public';
|
||||
import { generatePath } from 'react-router-dom';
|
||||
import { css } from '@emotion/react';
|
||||
import {
|
||||
CspEvaluationBadge,
|
||||
benchmarksNavigation,
|
||||
createMisconfigurationFindingsQuery,
|
||||
} from '@kbn/cloud-security-posture';
|
||||
import { createMisconfigurationFindingsQuery } from '@kbn/cloud-security-posture';
|
||||
import type { CspFinding, BenchmarkId } from '@kbn/cloud-security-posture-common';
|
||||
import { BenchmarkName, CSP_MISCONFIGURATIONS_DATASET } from '@kbn/cloud-security-posture-common';
|
||||
import { BenchmarkName } from '@kbn/cloud-security-posture-common';
|
||||
import { CspVulnerabilityFinding } from '@kbn/cloud-security-posture-common/schema/vulnerabilities/csp_vulnerability_finding';
|
||||
import { isNativeCspFinding } from '@kbn/cloud-security-posture/src/utils/is_native_csp_finding';
|
||||
import { getVendorName } from '@kbn/cloud-security-posture/src/utils/get_vendor_name';
|
||||
import { truthy } from '@kbn/cloud-security-posture/src/utils/helpers';
|
||||
import type { CoreStart } from '@kbn/core/public';
|
||||
import { useKibana } from '@kbn/kibana-react-plugin/public';
|
||||
import type {
|
||||
CspClientPluginStartDeps,
|
||||
FindingMisconfigurationFlyoutProps,
|
||||
} from '@kbn/cloud-security-posture';
|
||||
import { useMisconfigurationFinding } from '@kbn/cloud-security-posture/src/hooks/use_misconfiguration_finding';
|
||||
import { createDetectionRuleFromBenchmarkRule } from '@kbn/cloud-security-posture/src/utils/create_detection_rule_from_benchmark';
|
||||
import cisLogoIcon from '../../../assets/icons/cis_logo.svg';
|
||||
import { TakeAction } from '../../../components/take_action';
|
||||
import { TableTab } from './table_tab';
|
||||
import { JsonTab } from './json_tab';
|
||||
import { OverviewTab } from './overview_tab';
|
||||
import { RuleTab } from './rule_tab';
|
||||
import { CISBenchmarkIcon } from '../../../components/cis_benchmark_icon';
|
||||
import { CspInlineDescriptionList } from '../../../components/csp_inline_description_list';
|
||||
|
||||
const FINDINGS_MISCONFIGS_FLYOUT_DESCRIPTION_LIST = 'misconfigs-findings-flyout-description-list';
|
||||
|
||||
const FINDINGS_FLYOUT = 'findings_flyout';
|
||||
|
||||
const tabs = [
|
||||
{
|
||||
id: 'overview',
|
||||
title: i18n.translate('xpack.csp.findings.findingsFlyout.overviewTabTitle', {
|
||||
defaultMessage: 'Overview',
|
||||
}),
|
||||
},
|
||||
{
|
||||
id: 'rule',
|
||||
title: i18n.translate('xpack.csp.findings.findingsFlyout.ruleTabTitle', {
|
||||
defaultMessage: 'Rule',
|
||||
}),
|
||||
},
|
||||
{
|
||||
id: 'table',
|
||||
title: i18n.translate('xpack.csp.findings.findingsFlyout.tableTabTitle', {
|
||||
defaultMessage: 'Table',
|
||||
}),
|
||||
},
|
||||
{
|
||||
id: 'json',
|
||||
title: i18n.translate('xpack.csp.findings.findingsFlyout.jsonTabTitle', {
|
||||
defaultMessage: 'JSON',
|
||||
}),
|
||||
},
|
||||
] as const;
|
||||
|
||||
type FindingsTab = (typeof tabs)[number];
|
||||
|
||||
export const EMPTY_VALUE = '-';
|
||||
|
||||
|
@ -151,55 +87,6 @@ export const RuleNameLink = ({
|
|||
);
|
||||
};
|
||||
|
||||
const getFlyoutDescriptionList = (finding: CspFinding): EuiDescriptionListProps['listItems'] =>
|
||||
[
|
||||
finding.resource?.id && {
|
||||
title: i18n.translate('xpack.csp.findings.findingsFlyout.flyoutDescriptionList.resourceId', {
|
||||
defaultMessage: 'Resource ID',
|
||||
}),
|
||||
description: finding.resource.id,
|
||||
},
|
||||
finding.resource?.name && {
|
||||
title: i18n.translate(
|
||||
'xpack.csp.findings.findingsFlyout.flyoutDescriptionList.resourceName',
|
||||
{ defaultMessage: 'Resource Name' }
|
||||
),
|
||||
description: finding.resource.name,
|
||||
},
|
||||
].filter(truthy);
|
||||
|
||||
const FindingsTab = ({ tab, finding }: { finding: CspFinding; tab: FindingsTab }) => {
|
||||
const { application } = useKibana<CoreStart & CspClientPluginStartDeps>().services;
|
||||
|
||||
const ruleFlyoutLink =
|
||||
// currently we only support rule linking for native CSP findings
|
||||
finding.data_stream.dataset === CSP_MISCONFIGURATIONS_DATASET &&
|
||||
finding.rule?.benchmark?.version &&
|
||||
finding.rule?.benchmark?.id &&
|
||||
finding.rule?.id
|
||||
? application.getUrlForApp('security', {
|
||||
path: generatePath(benchmarksNavigation.rules.path, {
|
||||
benchmarkVersion: finding.rule.benchmark.version.split('v')[1], // removing the v from the version
|
||||
benchmarkId: finding.rule.benchmark.id,
|
||||
ruleId: finding.rule.id,
|
||||
}),
|
||||
})
|
||||
: undefined;
|
||||
|
||||
switch (tab.id) {
|
||||
case 'overview':
|
||||
return <OverviewTab data={finding} ruleFlyoutLink={ruleFlyoutLink} />;
|
||||
case 'rule':
|
||||
return <RuleTab data={finding} ruleFlyoutLink={ruleFlyoutLink} />;
|
||||
case 'table':
|
||||
return <TableTab data={finding} />;
|
||||
case 'json':
|
||||
return <JsonTab data={finding} />;
|
||||
default:
|
||||
assertNever(tab);
|
||||
}
|
||||
};
|
||||
|
||||
export const MissingFieldsCallout = ({
|
||||
finding,
|
||||
}: {
|
||||
|
@ -231,93 +118,29 @@ export const MissingFieldsCallout = ({
|
|||
);
|
||||
};
|
||||
|
||||
export const FindingsRuleFlyout = ({ ruleId, resourceId }: FindingMisconfigurationFlyoutProps) => {
|
||||
const FindingsRuleFlyout = ({
|
||||
ruleId,
|
||||
resourceId,
|
||||
children,
|
||||
}: {
|
||||
ruleId: string;
|
||||
resourceId: string;
|
||||
children: any;
|
||||
}) => {
|
||||
const { data } = useMisconfigurationFinding({
|
||||
query: createMisconfigurationFindingsQuery(resourceId, ruleId),
|
||||
enabled: true,
|
||||
pageSize: 1,
|
||||
});
|
||||
const finding = data?.result.hits[0]._source;
|
||||
|
||||
const { euiTheme } = useEuiTheme();
|
||||
const [tab, setTab] = useState<FindingsTab>(tabs[0]);
|
||||
const finding = data?.result.hits[0]?._source;
|
||||
|
||||
if (!finding) return null;
|
||||
|
||||
const createMisconfigurationRuleFn = async (http: HttpSetup) =>
|
||||
createDetectionRuleFromBenchmarkRule(http, finding?.rule);
|
||||
|
||||
return (
|
||||
<>
|
||||
<EuiFlexGroup gutterSize={'none'} direction={'column'} data-test-subj={FINDINGS_FLYOUT}>
|
||||
<EuiFlyoutHeader>
|
||||
<EuiPanel hasShadow={false}>
|
||||
<EuiSpacer />
|
||||
<EuiFlexGroup alignItems="center">
|
||||
<EuiFlexItem grow={false}>
|
||||
<CspEvaluationBadge type={finding?.result?.evaluation} />
|
||||
</EuiFlexItem>
|
||||
<EuiFlexItem grow style={{ minWidth: 0 }}>
|
||||
<EuiTitle size="m" className="eui-textTruncate">
|
||||
<EuiTextColor color="primary" title={finding?.rule?.name}>
|
||||
{finding?.rule?.name}
|
||||
</EuiTextColor>
|
||||
</EuiTitle>
|
||||
</EuiFlexItem>
|
||||
</EuiFlexGroup>
|
||||
{finding && (
|
||||
<div
|
||||
css={css`
|
||||
line-height: 20px;
|
||||
margin-top: ${euiTheme.size.m};
|
||||
`}
|
||||
>
|
||||
<CspInlineDescriptionList
|
||||
testId={FINDINGS_MISCONFIGS_FLYOUT_DESCRIPTION_LIST}
|
||||
listItems={getFlyoutDescriptionList(finding)}
|
||||
/>
|
||||
</div>
|
||||
)}
|
||||
|
||||
<EuiSpacer />
|
||||
<EuiTabs>
|
||||
{tabs.map((v) => (
|
||||
<EuiTab
|
||||
key={v.id}
|
||||
isSelected={tab.id === v.id}
|
||||
onClick={() => setTab(v)}
|
||||
data-test-subj={`findings_flyout_tab_${v.id}`}
|
||||
>
|
||||
{v.title}
|
||||
</EuiTab>
|
||||
))}
|
||||
</EuiTabs>
|
||||
</EuiPanel>
|
||||
</EuiFlyoutHeader>
|
||||
{finding && (
|
||||
<EuiPanel hasShadow={false}>
|
||||
<EuiFlyoutBody key={tab.id}>
|
||||
{!isNativeCspFinding(finding) && ['overview', 'rule'].includes(tab.id) && (
|
||||
<div style={{ marginBottom: euiTheme.size.base }}>
|
||||
<MissingFieldsCallout finding={finding} />
|
||||
</div>
|
||||
)}
|
||||
<FindingsTab tab={tab} finding={finding} />
|
||||
</EuiFlyoutBody>
|
||||
</EuiPanel>
|
||||
)}
|
||||
<EuiFlyoutFooter>
|
||||
<EuiPanel color="transparent">
|
||||
<EuiFlexGroup justifyContent="flexEnd" alignItems="center">
|
||||
<EuiFlexItem grow={false}>
|
||||
<TakeAction createRuleFn={createMisconfigurationRuleFn} />
|
||||
</EuiFlexItem>
|
||||
</EuiFlexGroup>
|
||||
</EuiPanel>
|
||||
</EuiFlyoutFooter>
|
||||
</EuiFlexGroup>
|
||||
</>
|
||||
);
|
||||
return children({
|
||||
finding,
|
||||
createRuleFn: (http: HttpSetup) => createDetectionRuleFromBenchmarkRule(http, finding?.rule),
|
||||
});
|
||||
};
|
||||
|
||||
// eslint-disable-next-line import/no-default-export
|
||||
|
|
|
@ -0,0 +1,163 @@
|
|||
/*
|
||||
* 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, { useState } from 'react';
|
||||
import { css } from '@emotion/react';
|
||||
import {
|
||||
EuiPanel,
|
||||
EuiTabs,
|
||||
EuiTab,
|
||||
EuiCallOut,
|
||||
useEuiTheme,
|
||||
EuiFlexGroup,
|
||||
EuiFlyoutBody,
|
||||
} from '@elastic/eui';
|
||||
import {
|
||||
CSP_MISCONFIGURATIONS_DATASET,
|
||||
CspFinding,
|
||||
CspVulnerabilityFinding,
|
||||
} from '@kbn/cloud-security-posture-common';
|
||||
import { generatePath } from 'react-router-dom';
|
||||
import type { CoreStart } from '@kbn/core/public';
|
||||
import { useKibana } from '@kbn/kibana-react-plugin/public';
|
||||
import { benchmarksNavigation, type CspClientPluginStartDeps } from '@kbn/cloud-security-posture';
|
||||
import { assertNever } from '@kbn/std';
|
||||
import { i18n } from '@kbn/i18n';
|
||||
import { getVendorName } from '@kbn/cloud-security-posture/src/utils/get_vendor_name';
|
||||
import { isNativeCspFinding } from '@kbn/cloud-security-posture/src/utils/is_native_csp_finding';
|
||||
import { FormattedMessage } from '@kbn/i18n-react';
|
||||
import { OverviewTab } from '../overview_tab';
|
||||
import { JsonTab } from '../json_tab';
|
||||
import { TableTab } from '../table_tab';
|
||||
|
||||
type FindingsTab = (typeof tabs)[number];
|
||||
|
||||
export const MissingFieldsCallout = ({
|
||||
finding,
|
||||
}: {
|
||||
finding: CspFinding | CspVulnerabilityFinding;
|
||||
}) => {
|
||||
const { euiTheme } = useEuiTheme();
|
||||
const vendor = getVendorName(finding);
|
||||
|
||||
return (
|
||||
<EuiCallOut
|
||||
style={{
|
||||
borderRadius: 4,
|
||||
overflow: 'hidden',
|
||||
}}
|
||||
size="s"
|
||||
iconType="iInCircle"
|
||||
title={
|
||||
<span style={{ color: euiTheme.colors.text }}>
|
||||
<FormattedMessage
|
||||
id="xpack.csp.findings.findingsFlyout.calloutTitle"
|
||||
defaultMessage="Some fields not provided by {vendor}"
|
||||
values={{
|
||||
vendor: vendor || 'the vendor',
|
||||
}}
|
||||
/>
|
||||
</span>
|
||||
}
|
||||
/>
|
||||
);
|
||||
};
|
||||
|
||||
const tabs = [
|
||||
{
|
||||
id: 'overview',
|
||||
title: i18n.translate('xpack.csp.findings.findingsFlyout.overviewTabTitle', {
|
||||
defaultMessage: 'Overview',
|
||||
}),
|
||||
},
|
||||
{
|
||||
id: 'table',
|
||||
title: i18n.translate('xpack.csp.findings.findingsFlyout.tableTabTitle', {
|
||||
defaultMessage: 'Table',
|
||||
}),
|
||||
},
|
||||
{
|
||||
id: 'json',
|
||||
title: i18n.translate('xpack.csp.findings.findingsFlyout.jsonTabTitle', {
|
||||
defaultMessage: 'JSON',
|
||||
}),
|
||||
},
|
||||
] as const;
|
||||
|
||||
const FindingsTab = ({ tab, finding }: { finding: CspFinding; tab: FindingsTab }) => {
|
||||
const { application } = useKibana<CoreStart & CspClientPluginStartDeps>().services;
|
||||
|
||||
const ruleFlyoutLink =
|
||||
// currently we only support rule linking for native CSP findings
|
||||
finding.data_stream.dataset === CSP_MISCONFIGURATIONS_DATASET &&
|
||||
finding.rule?.benchmark?.version &&
|
||||
finding.rule?.benchmark?.id &&
|
||||
finding.rule?.id
|
||||
? application.getUrlForApp('security', {
|
||||
path: generatePath(benchmarksNavigation.rules.path, {
|
||||
benchmarkVersion: finding.rule.benchmark.version.split('v')[1], // removing the v from the version
|
||||
benchmarkId: finding.rule.benchmark.id,
|
||||
ruleId: finding.rule.id,
|
||||
}),
|
||||
})
|
||||
: undefined;
|
||||
|
||||
switch (tab.id) {
|
||||
case 'overview':
|
||||
return <OverviewTab data={finding} ruleFlyoutLink={ruleFlyoutLink} />;
|
||||
case 'table':
|
||||
return <TableTab data={finding} />;
|
||||
case 'json':
|
||||
return <JsonTab data={finding} />;
|
||||
default:
|
||||
assertNever(tab);
|
||||
}
|
||||
};
|
||||
|
||||
export const FindingsMisconfigurationFlyoutContent = ({ finding }: { finding: CspFinding }) => {
|
||||
const [tab, setTab] = useState<FindingsTab>(tabs[0]);
|
||||
const { euiTheme } = useEuiTheme();
|
||||
|
||||
return (
|
||||
<>
|
||||
<EuiFlexGroup gutterSize={'none'} direction={'column'}>
|
||||
<EuiTabs expand>
|
||||
{tabs.map((v) => (
|
||||
<EuiTab
|
||||
key={v.id}
|
||||
isSelected={tab.id === v.id}
|
||||
onClick={() => setTab(v)}
|
||||
data-test-subj={`findings_flyout_tab_${v.id}`}
|
||||
>
|
||||
{v.title}
|
||||
</EuiTab>
|
||||
))}
|
||||
</EuiTabs>
|
||||
{finding && (
|
||||
<EuiPanel
|
||||
hasShadow={false}
|
||||
css={css`
|
||||
position: relative;
|
||||
`}
|
||||
>
|
||||
<EuiFlyoutBody key={tab.id}>
|
||||
{!isNativeCspFinding(finding) && ['overview', 'rule'].includes(tab.id) && (
|
||||
<div style={{ marginBottom: euiTheme.size.base }}>
|
||||
<MissingFieldsCallout finding={finding} />
|
||||
</div>
|
||||
)}
|
||||
<FindingsTab tab={tab} finding={finding} />
|
||||
</EuiFlyoutBody>
|
||||
</EuiPanel>
|
||||
)}
|
||||
</EuiFlexGroup>
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
// eslint-disable-next-line import/no-default-export
|
||||
export default FindingsMisconfigurationFlyoutContent;
|
|
@ -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 { EuiFlyoutFooter, EuiPanel, EuiFlexGroup, EuiFlexItem } from '@elastic/eui';
|
||||
import React from 'react';
|
||||
import { RuleResponse } from '@kbn/cloud-security-posture-common';
|
||||
import { TakeAction } from '../../../../components/take_action';
|
||||
|
||||
export const FindingsMisconfigurationFlyoutFooter = ({
|
||||
createRuleFn,
|
||||
}: {
|
||||
createRuleFn: (http: any) => Promise<RuleResponse>;
|
||||
}) => {
|
||||
return (
|
||||
<EuiFlyoutFooter>
|
||||
<EuiPanel color="transparent">
|
||||
<EuiFlexGroup justifyContent="flexEnd" alignItems="center">
|
||||
<EuiFlexItem grow={false}>
|
||||
<TakeAction createRuleFn={createRuleFn} />
|
||||
</EuiFlexItem>
|
||||
</EuiFlexGroup>
|
||||
</EuiPanel>
|
||||
</EuiFlyoutFooter>
|
||||
);
|
||||
};
|
||||
|
||||
// eslint-disable-next-line import/no-default-export
|
||||
export default FindingsMisconfigurationFlyoutFooter;
|
|
@ -0,0 +1,146 @@
|
|||
/*
|
||||
* 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 {
|
||||
EuiSpacer,
|
||||
EuiFlexItem,
|
||||
EuiFlexGroup,
|
||||
useEuiTheme,
|
||||
EuiBadge,
|
||||
EuiPanel,
|
||||
EuiCopy,
|
||||
EuiIcon,
|
||||
EuiTextTruncate,
|
||||
EuiToolTip,
|
||||
} from '@elastic/eui';
|
||||
import { CspFinding } from '@kbn/cloud-security-posture-common';
|
||||
import { BenchmarkIcons } from '../findings_flyout';
|
||||
|
||||
export interface FindingsMisconfigurationFlyoutHeaderProps {
|
||||
finding: CspFinding;
|
||||
}
|
||||
|
||||
export const FindingsMisconfigurationFlyoutHeader = ({
|
||||
finding,
|
||||
}: FindingsMisconfigurationFlyoutHeaderProps) => {
|
||||
const { euiTheme } = useEuiTheme();
|
||||
|
||||
const rulesTags = finding?.rule?.tags;
|
||||
const resourceName = finding?.resource?.name;
|
||||
const vendor = finding?.observer?.vendor;
|
||||
const ruleBenchmarkId = finding?.rule?.benchmark?.id;
|
||||
const ruleBenchmarkName = finding?.rule?.benchmark?.name;
|
||||
|
||||
return (
|
||||
<>
|
||||
<EuiSpacer size="s" />
|
||||
{rulesTags &&
|
||||
rulesTags.map((tag: string) => (
|
||||
<EuiBadge key={tag} color={'hollow'}>
|
||||
{tag}
|
||||
</EuiBadge>
|
||||
))}
|
||||
<EuiSpacer size="m" />
|
||||
<EuiFlexGroup gutterSize="s">
|
||||
<EuiFlexItem>
|
||||
<EuiPanel hasBorder={true}>
|
||||
<EuiFlexGroup gutterSize="none">
|
||||
<EuiFlexItem>
|
||||
<EuiPanel
|
||||
borderRadius="none"
|
||||
paddingSize="xl"
|
||||
css={{ borderRight: 'solid 1px #D3DAE6', padding: '12px' }}
|
||||
hasBorder={false}
|
||||
hasShadow={false}
|
||||
>
|
||||
<EuiFlexGroup direction="column" gutterSize="m">
|
||||
<EuiFlexItem>
|
||||
<b>Resource Name</b>
|
||||
</EuiFlexItem>
|
||||
<EuiFlexItem>
|
||||
<EuiFlexGroup direction="row" gutterSize="none">
|
||||
<EuiFlexItem>
|
||||
<EuiToolTip content={resourceName} position="top">
|
||||
<EuiTextTruncate text={resourceName} />
|
||||
</EuiToolTip>
|
||||
</EuiFlexItem>
|
||||
<EuiFlexItem>
|
||||
<EuiCopy textToCopy={resourceName}>
|
||||
{(copy) => (
|
||||
<EuiIcon
|
||||
css={css`
|
||||
:hover {
|
||||
cursor: pointer;
|
||||
}
|
||||
`}
|
||||
onClick={copy}
|
||||
type="copy"
|
||||
/>
|
||||
)}
|
||||
</EuiCopy>
|
||||
</EuiFlexItem>
|
||||
</EuiFlexGroup>
|
||||
</EuiFlexItem>
|
||||
</EuiFlexGroup>
|
||||
</EuiPanel>
|
||||
</EuiFlexItem>
|
||||
<EuiFlexItem>
|
||||
<EuiPanel
|
||||
borderRadius="none"
|
||||
paddingSize="xl"
|
||||
css={{ borderRight: 'solid 1px #D3DAE6', padding: '12px' }}
|
||||
hasBorder={false}
|
||||
hasShadow={false}
|
||||
>
|
||||
<EuiFlexGroup direction="column" gutterSize="m">
|
||||
<EuiFlexItem>
|
||||
<b>Framework</b>
|
||||
<EuiSpacer size="m" />
|
||||
{ruleBenchmarkId && ruleBenchmarkName && (
|
||||
<BenchmarkIcons
|
||||
benchmarkId={ruleBenchmarkId}
|
||||
benchmarkName={ruleBenchmarkName}
|
||||
size={'l'}
|
||||
/>
|
||||
)}
|
||||
</EuiFlexItem>
|
||||
</EuiFlexGroup>
|
||||
</EuiPanel>
|
||||
</EuiFlexItem>
|
||||
<EuiFlexItem>
|
||||
<EuiPanel
|
||||
borderRadius="none"
|
||||
paddingSize="xl"
|
||||
css={{ padding: '12px' }}
|
||||
hasBorder={false}
|
||||
hasShadow={false}
|
||||
>
|
||||
<EuiFlexGroup direction="column" gutterSize="m">
|
||||
<EuiFlexItem>
|
||||
<b>Vendor</b>
|
||||
</EuiFlexItem>
|
||||
<EuiFlexItem> {vendor} </EuiFlexItem>
|
||||
</EuiFlexGroup>
|
||||
</EuiPanel>
|
||||
</EuiFlexItem>
|
||||
</EuiFlexGroup>
|
||||
</EuiPanel>
|
||||
</EuiFlexItem>
|
||||
</EuiFlexGroup>
|
||||
<div
|
||||
css={css`
|
||||
margin: ${euiTheme.size.s};
|
||||
`}
|
||||
/>
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
// eslint-disable-next-line import/no-default-export
|
||||
export default FindingsMisconfigurationFlyoutHeader;
|
|
@ -9,20 +9,23 @@ import React from 'react';
|
|||
import { CodeEditor } from '@kbn/code-editor';
|
||||
import { XJsonLang } from '@kbn/monaco';
|
||||
import type { CspFinding } from '@kbn/cloud-security-posture-common';
|
||||
import { EuiPanel } from '@elastic/eui';
|
||||
|
||||
export const JsonTab = ({ data }: { data: CspFinding }) => (
|
||||
<div style={{ position: 'absolute', inset: 0 }}>
|
||||
<CodeEditor
|
||||
isCopyable
|
||||
allowFullScreen
|
||||
enableFindAction
|
||||
languageId={XJsonLang.ID}
|
||||
value={JSON.stringify(data, null, 2)}
|
||||
options={{
|
||||
readOnly: true,
|
||||
lineNumbers: 'on',
|
||||
folding: true,
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
<EuiPanel>
|
||||
<div style={{ height: '100vh' }}>
|
||||
<CodeEditor
|
||||
isCopyable
|
||||
allowFullScreen
|
||||
enableFindAction
|
||||
languageId={XJsonLang.ID}
|
||||
value={JSON.stringify(data, null, 2)}
|
||||
options={{
|
||||
readOnly: true,
|
||||
lineNumbers: 'on',
|
||||
folding: true,
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
</EuiPanel>
|
||||
);
|
||||
|
|
|
@ -7,58 +7,105 @@
|
|||
|
||||
import {
|
||||
EuiAccordion,
|
||||
EuiBadge,
|
||||
EuiBasicTable,
|
||||
EuiDescriptionList,
|
||||
EuiFlexGroup,
|
||||
EuiFlexItem,
|
||||
EuiIcon,
|
||||
EuiLink,
|
||||
EuiPanel,
|
||||
EuiSpacer,
|
||||
EuiText,
|
||||
} from '@elastic/eui';
|
||||
import React, { useMemo } from 'react';
|
||||
import moment from 'moment';
|
||||
import type { EuiDescriptionListProps, EuiAccordionProps } from '@elastic/eui';
|
||||
import type { EuiDescriptionListProps, EuiAccordionProps, EuiBasicTableColumn } from '@elastic/eui';
|
||||
import { i18n } from '@kbn/i18n';
|
||||
import {
|
||||
CDR_MISCONFIGURATIONS_INDEX_PATTERN,
|
||||
CDR_MISCONFIGURATIONS_DATA_VIEW_ID_PREFIX,
|
||||
CSP_MOMENT_FORMAT,
|
||||
INTERNAL_FEATURE_FLAGS,
|
||||
} from '@kbn/cloud-security-posture-common';
|
||||
import { FormattedMessage } from '@kbn/i18n-react';
|
||||
import { isEmpty } from 'lodash';
|
||||
import type { CspFinding } from '@kbn/cloud-security-posture-common';
|
||||
import { useDataView } from '@kbn/cloud-security-posture/src/hooks/use_data_view';
|
||||
import { getVendorName } from '@kbn/cloud-security-posture/src/utils/get_vendor_name';
|
||||
import { truthy } from '@kbn/cloud-security-posture/src/utils/helpers';
|
||||
import type { CoreStart } from '@kbn/core/public';
|
||||
import { useKibana } from '@kbn/kibana-react-plugin/public';
|
||||
import type { CspClientPluginStartDeps } from '@kbn/cloud-security-posture';
|
||||
import {
|
||||
BenchmarkIcons,
|
||||
CodeBlock,
|
||||
CspFlyoutMarkdown,
|
||||
EMPTY_VALUE,
|
||||
RuleNameLink,
|
||||
} from './findings_flyout';
|
||||
import { CodeBlock, CspFlyoutMarkdown, EMPTY_VALUE } from './findings_flyout';
|
||||
import { FindingsDetectionRuleCounter } from './findings_detection_rule_counter';
|
||||
|
||||
type Accordion = Pick<EuiAccordionProps, 'title' | 'id' | 'initialIsOpen'> &
|
||||
Pick<EuiDescriptionListProps, 'listItems'>;
|
||||
|
||||
const columns: Array<EuiBasicTableColumn<any>> = [
|
||||
{
|
||||
field: 'field',
|
||||
name: 'Field',
|
||||
'data-test-subj': 'firstNameCell',
|
||||
},
|
||||
{
|
||||
field: 'value',
|
||||
name: 'Value',
|
||||
truncateText: true,
|
||||
},
|
||||
];
|
||||
|
||||
const convertObjectToArray = (obj: { [key: string]: any }) => {
|
||||
if (obj === undefined) return null;
|
||||
return Object.keys(obj)
|
||||
.filter((key) => key !== 'raw')
|
||||
.map((key) => ({
|
||||
field: key,
|
||||
value: obj[key],
|
||||
}));
|
||||
};
|
||||
|
||||
const getResourceList = (data: CspFinding) => [
|
||||
{
|
||||
title: '',
|
||||
description: data ? (
|
||||
<EuiPanel>
|
||||
<EuiPanel hasBorder>
|
||||
<EuiBasicTable
|
||||
tableCaption="Demo of EuiBasicTable"
|
||||
items={convertObjectToArray(data.resource) || []}
|
||||
rowHeader="Field"
|
||||
columns={columns}
|
||||
/>
|
||||
</EuiPanel>
|
||||
</EuiPanel>
|
||||
) : (
|
||||
EMPTY_VALUE
|
||||
),
|
||||
},
|
||||
];
|
||||
|
||||
const getDetailsList = (
|
||||
data: CspFinding,
|
||||
ruleFlyoutLink?: string,
|
||||
discoverDataViewLink?: string
|
||||
) => [
|
||||
{
|
||||
title: i18n.translate('xpack.csp.findings.findingsFlyout.overviewTab.ruleNameTitle', {
|
||||
defaultMessage: 'Rule Name',
|
||||
}),
|
||||
description: data.rule?.name ? (
|
||||
<RuleNameLink ruleFlyoutLink={ruleFlyoutLink} ruleName={data.rule.name} />
|
||||
) : (
|
||||
EMPTY_VALUE
|
||||
title: (
|
||||
<>
|
||||
<EuiFlexGroup direction="row" gutterSize="m">
|
||||
<EuiFlexItem>
|
||||
<b>Rule Description</b>
|
||||
</EuiFlexItem>
|
||||
<EuiFlexItem>
|
||||
<EuiLink href={ruleFlyoutLink} target="_blank" style={{ textAlign: 'right' }}>
|
||||
<EuiIcon type="expand" />
|
||||
{i18n.translate('xpack.csp.findings.findingsFlyout.overviewTab.showRuleDetails', {
|
||||
defaultMessage: 'Show rule details',
|
||||
})}
|
||||
</EuiLink>
|
||||
</EuiFlexItem>
|
||||
</EuiFlexGroup>
|
||||
</>
|
||||
),
|
||||
description: data.rule?.description ? <EuiText>{data.rule?.description}</EuiText> : EMPTY_VALUE,
|
||||
},
|
||||
{
|
||||
title: i18n.translate('xpack.csp.findings.findingsFlyout.overviewTab.alertsTitle', {
|
||||
|
@ -66,54 +113,6 @@ const getDetailsList = (
|
|||
}),
|
||||
description: <FindingsDetectionRuleCounter finding={data} />,
|
||||
},
|
||||
{
|
||||
title: i18n.translate('xpack.csp.findings.findingsFlyout.overviewTab.ruleTagsTitle', {
|
||||
defaultMessage: 'Rule Tags',
|
||||
}),
|
||||
description: data.rule?.tags?.length ? (
|
||||
<>
|
||||
{data.rule.tags.map((tag) => (
|
||||
<EuiBadge key={tag}>{tag}</EuiBadge>
|
||||
))}
|
||||
</>
|
||||
) : (
|
||||
EMPTY_VALUE
|
||||
),
|
||||
},
|
||||
{
|
||||
title: i18n.translate('xpack.csp.findings.findingsFlyout.overviewTab.evaluatedAtTitle', {
|
||||
defaultMessage: 'Evaluated at',
|
||||
}),
|
||||
description: data['@timestamp']
|
||||
? moment(data['@timestamp']).format(CSP_MOMENT_FORMAT)
|
||||
: EMPTY_VALUE,
|
||||
},
|
||||
{
|
||||
title: i18n.translate('xpack.csp.findings.findingsFlyout.overviewTab.frameworkSourcesTitle', {
|
||||
defaultMessage: 'Framework Sources',
|
||||
}),
|
||||
description:
|
||||
data.rule?.benchmark?.id && data.rule?.benchmark?.name ? (
|
||||
<BenchmarkIcons
|
||||
benchmarkId={data.rule?.benchmark?.id}
|
||||
benchmarkName={data.rule?.benchmark?.name}
|
||||
/>
|
||||
) : (
|
||||
EMPTY_VALUE
|
||||
),
|
||||
},
|
||||
{
|
||||
title: i18n.translate('xpack.csp.findings.findingsFlyout.overviewTab.cisSectionTitle', {
|
||||
defaultMessage: 'Framework Section',
|
||||
}),
|
||||
description: data.rule?.section ? data.rule?.section : EMPTY_VALUE,
|
||||
},
|
||||
{
|
||||
title: i18n.translate('xpack.csp.findings.findingsFlyout.overviewTab.vendorTitle', {
|
||||
defaultMessage: 'Vendor',
|
||||
}),
|
||||
description: getVendorName(data) || EMPTY_VALUE,
|
||||
},
|
||||
{
|
||||
title: i18n.translate('xpack.csp.findings.findingsFlyout.overviewTab.dataViewTitle', {
|
||||
defaultMessage: 'Data View',
|
||||
|
@ -135,22 +134,6 @@ export const getRemediationList = (rule: CspFinding['rule']) => [
|
|||
EMPTY_VALUE
|
||||
),
|
||||
},
|
||||
{
|
||||
title: i18n.translate('xpack.csp.findings.findingsFlyout.overviewTab.impactTitle', {
|
||||
defaultMessage: 'Impact',
|
||||
}),
|
||||
description: rule?.impact ? <CspFlyoutMarkdown>{rule.impact}</CspFlyoutMarkdown> : EMPTY_VALUE,
|
||||
},
|
||||
{
|
||||
title: i18n.translate('xpack.csp.findings.findingsFlyout.overviewTab.defaultValueTitle', {
|
||||
defaultMessage: 'Default Value',
|
||||
}),
|
||||
description: rule?.default_value ? (
|
||||
<CspFlyoutMarkdown>{rule.default_value}</CspFlyoutMarkdown>
|
||||
) : (
|
||||
EMPTY_VALUE
|
||||
),
|
||||
},
|
||||
{
|
||||
title: i18n.translate('xpack.csp.findings.findingsFlyout.overviewTab.rationaleTitle', {
|
||||
defaultMessage: 'Rationale',
|
||||
|
@ -223,12 +206,20 @@ export const OverviewTab = ({
|
|||
[
|
||||
{
|
||||
initialIsOpen: true,
|
||||
title: i18n.translate('xpack.csp.findings.findingsFlyout.overviewTab.detailsTitle', {
|
||||
defaultMessage: 'Details',
|
||||
title: i18n.translate('xpack.csp.findings.findingsFlyout.overviewTab.aboutTitle', {
|
||||
defaultMessage: 'About',
|
||||
}),
|
||||
id: 'detailsAccordion',
|
||||
listItems: getDetailsList(data, ruleFlyoutLink, discoverDataViewLink),
|
||||
},
|
||||
{
|
||||
initialIsOpen: true,
|
||||
title: i18n.translate('xpack.csp.findings.findingsFlyout.overviewTab.detailsTitle', {
|
||||
defaultMessage: 'Resource',
|
||||
}),
|
||||
id: 'detailsAccordion',
|
||||
listItems: getResourceList(data),
|
||||
},
|
||||
{
|
||||
initialIsOpen: true,
|
||||
title: i18n.translate('xpack.csp.findings.findingsFlyout.overviewTab.remediationTitle', {
|
||||
|
@ -255,21 +246,19 @@ export const OverviewTab = ({
|
|||
<>
|
||||
{accordions.map((accordion) => (
|
||||
<React.Fragment key={accordion.id}>
|
||||
<EuiPanel hasShadow={false} hasBorder>
|
||||
<EuiAccordion
|
||||
id={accordion.id}
|
||||
buttonContent={
|
||||
<EuiText>
|
||||
<strong>{accordion.title}</strong>
|
||||
</EuiText>
|
||||
}
|
||||
arrowDisplay="left"
|
||||
initialIsOpen={accordion.initialIsOpen}
|
||||
>
|
||||
<EuiSpacer size="m" />
|
||||
<EuiDescriptionList listItems={accordion.listItems} />
|
||||
</EuiAccordion>
|
||||
</EuiPanel>
|
||||
<EuiAccordion
|
||||
id={accordion.id}
|
||||
buttonContent={
|
||||
<EuiText>
|
||||
<strong>{accordion.title}</strong>
|
||||
</EuiText>
|
||||
}
|
||||
arrowDisplay="left"
|
||||
initialIsOpen={accordion.initialIsOpen}
|
||||
>
|
||||
<EuiSpacer size="m" />
|
||||
<EuiDescriptionList listItems={accordion.listItems} />
|
||||
</EuiAccordion>
|
||||
<EuiSpacer size="m" />
|
||||
</React.Fragment>
|
||||
))}
|
||||
|
|
|
@ -11,7 +11,10 @@ import { Storage } from '@kbn/kibana-utils-plugin/public';
|
|||
import { RedirectAppLinks } from '@kbn/shared-ux-link-redirect-app';
|
||||
import type {
|
||||
CspClientPluginStartDeps,
|
||||
FindingMisconfigurationFlyoutFooterProps,
|
||||
FindingMisconfigurationFlyoutProps,
|
||||
FindingsMisconfigurationFlyoutContentProps,
|
||||
FindingsMisconfigurationFlyoutHeaderProps,
|
||||
} from '@kbn/cloud-security-posture';
|
||||
import { uiMetricService } from '@kbn/cloud-security-posture-common/utils/ui_metrics';
|
||||
import { CspLoadingState } from './components/csp_loading_state';
|
||||
|
@ -34,9 +37,19 @@ const LazyCspCustomAssets = lazy(
|
|||
() => import('./components/fleet_extensions/custom_assets_extension')
|
||||
);
|
||||
|
||||
const LazyCspFindingsMisconfigurationFlyout = lazy(
|
||||
export const LazyCspFindingsMisconfigurationFlyout = lazy(
|
||||
() => import('./pages/configurations/findings_flyout/findings_flyout')
|
||||
);
|
||||
export const LazyCspFindingsMisconfigurationFlyoutHeader = lazy(
|
||||
() => import('./pages/configurations/findings_flyout/findings_right/header')
|
||||
);
|
||||
export const LazyCspFindingsMisconfigurationFlyoutBody = lazy(
|
||||
() => import('./pages/configurations/findings_flyout/findings_right/content')
|
||||
);
|
||||
|
||||
export const LazyCspFindingsMisconfigurationFlyoutFooter = lazy(
|
||||
() => import('./pages/configurations/findings_flyout/findings_right/footer')
|
||||
);
|
||||
|
||||
const CspRouterLazy = lazy(() => import('./application/csp_router'));
|
||||
const CspRouter = (props: CspRouterProps) => (
|
||||
|
@ -108,12 +121,24 @@ export class CspPlugin
|
|||
|
||||
return {
|
||||
getCloudSecurityPostureRouter: () => App,
|
||||
getCloudSecurityPostureMisconfigurationFlyout: ({
|
||||
ruleId,
|
||||
resourceId,
|
||||
}: FindingMisconfigurationFlyoutProps) => (
|
||||
<LazyCspFindingsMisconfigurationFlyout ruleId={ruleId} resourceId={resourceId} />
|
||||
),
|
||||
getCloudSecurityPostureMisconfigurationFlyout: () => {
|
||||
return {
|
||||
Component: (props: FindingMisconfigurationFlyoutProps) => (
|
||||
<LazyCspFindingsMisconfigurationFlyout {...props}>
|
||||
{props.children}
|
||||
</LazyCspFindingsMisconfigurationFlyout>
|
||||
),
|
||||
Header: (props: FindingsMisconfigurationFlyoutHeaderProps) => (
|
||||
<LazyCspFindingsMisconfigurationFlyoutHeader {...props} />
|
||||
),
|
||||
Body: (props: FindingsMisconfigurationFlyoutContentProps) => (
|
||||
<LazyCspFindingsMisconfigurationFlyoutBody {...props} />
|
||||
),
|
||||
Footer: (props: FindingMisconfigurationFlyoutFooterProps) => (
|
||||
<LazyCspFindingsMisconfigurationFlyoutFooter {...props} />
|
||||
),
|
||||
};
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
|
|
|
@ -13,7 +13,12 @@ import { CoreStart } from '@kbn/core/public';
|
|||
import type { FleetSetup } from '@kbn/fleet-plugin/public';
|
||||
import type { UsageCollectionSetup } from '@kbn/usage-collection-plugin/public';
|
||||
import { ExpandableFlyoutApi } from '@kbn/expandable-flyout';
|
||||
import { FindingMisconfigurationFlyoutProps } from '@kbn/cloud-security-posture';
|
||||
import {
|
||||
FindingMisconfigurationFlyoutFooterProps,
|
||||
FindingMisconfigurationFlyoutProps,
|
||||
FindingsMisconfigurationFlyoutContentProps,
|
||||
FindingsMisconfigurationFlyoutHeaderProps,
|
||||
} from '@kbn/cloud-security-posture';
|
||||
import type { CspRouterProps } from './application/csp_router';
|
||||
import type { CloudSecurityPosturePageId } from './common/navigation/types';
|
||||
|
||||
|
@ -36,10 +41,13 @@ export interface CspClientPluginSetup {}
|
|||
export interface CspClientPluginStart {
|
||||
/** Gets the cloud security posture router component for embedding in the security solution. */
|
||||
getCloudSecurityPostureRouter(): ComponentType<CspRouterProps>;
|
||||
getCloudSecurityPostureMisconfigurationFlyout: ({
|
||||
ruleId,
|
||||
resourceId,
|
||||
}: FindingMisconfigurationFlyoutProps) => React.JSX.Element;
|
||||
// getCloudSecurityPostureMisconfigurationFlyout: () => React.JSX.Element;
|
||||
getCloudSecurityPostureMisconfigurationFlyout: () => {
|
||||
Component: React.FC<FindingMisconfigurationFlyoutProps>;
|
||||
Header: React.FC<FindingsMisconfigurationFlyoutHeaderProps>;
|
||||
Body: React.FC<FindingsMisconfigurationFlyoutContentProps>;
|
||||
Footer: React.FC<FindingMisconfigurationFlyoutFooterProps>;
|
||||
};
|
||||
}
|
||||
|
||||
export interface CspClientPluginSetupDeps {
|
||||
|
|
|
@ -0,0 +1,80 @@
|
|||
/*
|
||||
* 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 type { FlyoutPanelProps } from '@kbn/expandable-flyout';
|
||||
import { EuiFlexGroup, EuiFlexItem, EuiText, EuiSpacer, EuiFlyoutFooter } from '@elastic/eui';
|
||||
import type {
|
||||
FindingMisconfigurationFlyoutContentProps,
|
||||
FindingMisconfigurationFlyoutProps,
|
||||
} from '@kbn/cloud-security-posture';
|
||||
import { CspEvaluationBadge } from '@kbn/cloud-security-posture';
|
||||
import { i18n } from '@kbn/i18n';
|
||||
import { FlyoutNavigation } from '../../../shared/components/flyout_navigation';
|
||||
import { FlyoutHeader } from '../../../shared/components/flyout_header';
|
||||
import { useKibana } from '../../../../common/lib/kibana';
|
||||
import { PreferenceFormattedDate } from '../../../../common/components/formatted_date';
|
||||
import { FlyoutTitle } from '../../../shared/components/flyout_title';
|
||||
import { FlyoutBody } from '../../../shared/components/flyout_body';
|
||||
export interface FindingsMisconfigurationPanelExpandableFlyoutProps extends FlyoutPanelProps {
|
||||
key: 'findings-misconfiguration-panel';
|
||||
params: FindingMisconfigurationFlyoutProps;
|
||||
}
|
||||
|
||||
export const FindingsMisconfigurationPanel = ({
|
||||
resourceId,
|
||||
ruleId,
|
||||
}: FindingMisconfigurationFlyoutProps) => {
|
||||
const { cloudSecurityPosture } = useKibana().services;
|
||||
const CspFlyout = cloudSecurityPosture.getCloudSecurityPostureMisconfigurationFlyout();
|
||||
|
||||
return (
|
||||
<>
|
||||
<FlyoutNavigation flyoutIsExpandable={false} />
|
||||
<CspFlyout.Component ruleId={ruleId} resourceId={resourceId}>
|
||||
{({ finding, createRuleFn }: FindingMisconfigurationFlyoutContentProps) => {
|
||||
return (
|
||||
<>
|
||||
<FlyoutHeader>
|
||||
<EuiFlexGroup gutterSize="xs" responsive={false} direction="column">
|
||||
<EuiFlexItem grow={false}>
|
||||
<CspEvaluationBadge type={finding?.result?.evaluation} />
|
||||
</EuiFlexItem>
|
||||
{finding['@timestamp'] && (
|
||||
<EuiFlexItem grow={false}>
|
||||
<EuiText size="xs">
|
||||
<b>
|
||||
{i18n.translate('xpack.securitySolution.csp.findingsFlyout.evaluatedAt', {
|
||||
defaultMessage: 'Evaluated at ',
|
||||
})}
|
||||
</b>
|
||||
<PreferenceFormattedDate value={new Date(finding['@timestamp'])} />
|
||||
<EuiSpacer size="xs" />
|
||||
</EuiText>
|
||||
</EuiFlexItem>
|
||||
)}
|
||||
<EuiFlexItem grow={false}>
|
||||
<FlyoutTitle title={finding.rule.name} />
|
||||
</EuiFlexItem>
|
||||
</EuiFlexGroup>
|
||||
<CspFlyout.Header finding={finding} />
|
||||
</FlyoutHeader>
|
||||
<FlyoutBody>
|
||||
<CspFlyout.Body finding={finding} />
|
||||
</FlyoutBody>
|
||||
<EuiFlyoutFooter>
|
||||
<CspFlyout.Footer createRuleFn={createRuleFn} />
|
||||
</EuiFlyoutFooter>
|
||||
</>
|
||||
);
|
||||
}}
|
||||
</CspFlyout.Component>
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
FindingsMisconfigurationPanel.displayName = 'FindingsMisconfigurationPanel';
|
|
@ -1,17 +0,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 { FindingMisconfigurationFlyoutProps } from '@kbn/cloud-security-posture';
|
||||
import { useKibana } from '../../../common/lib/kibana';
|
||||
|
||||
export const FindingsMisconfigurationPanel = ({
|
||||
ruleId,
|
||||
resourceId,
|
||||
}: FindingMisconfigurationFlyoutProps) => {
|
||||
const { cloudSecurityPosture } = useKibana().services;
|
||||
return cloudSecurityPosture.getCloudSecurityPostureMisconfigurationFlyout({ ruleId, resourceId });
|
||||
};
|
|
@ -62,8 +62,8 @@ import type { ServicePanelExpandableFlyoutProps } from './entity_details/service
|
|||
import { ServicePanel } from './entity_details/service_right';
|
||||
import type { ServiceDetailsExpandableFlyoutProps } from './entity_details/service_details_left';
|
||||
import { ServiceDetailsPanel, ServiceDetailsPanelKey } from './entity_details/service_details_left';
|
||||
import { FindingsMisconfigurationPanel } from './csp_details/findings_flyout/helper';
|
||||
import { MisconfigurationFindingsPanelKey } from './csp_details/findings_flyout/constants';
|
||||
import { FindingsMisconfigurationPanel } from './csp_details/findings_flyout/findings_right';
|
||||
import { IOCPanelKey } from './ai_for_soc/constants/panel_keys';
|
||||
|
||||
/**
|
||||
|
|
|
@ -324,7 +324,7 @@ export function FindingsPageProvider({ getService, getPageObjects }: FtrProvider
|
|||
},
|
||||
});
|
||||
|
||||
const misconfigurationsFlyout = createFlyoutObject('findings_flyout');
|
||||
const misconfigurationsFlyout = createFlyoutObject('rightSection');
|
||||
|
||||
const groupSelector = (testSubj = 'group-selector-dropdown') => ({
|
||||
async getElement() {
|
||||
|
|
Loading…
Add table
Reference in a new issue