[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:
Rickyanto Ang 2025-04-14 14:17:57 -07:00 committed by GitHub
parent c9b32ff7ec
commit 6d688deebb
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
17 changed files with 626 additions and 413 deletions

View file

@ -15309,24 +15309,14 @@
"xpack.csp.findings.errorCallout.pageSearchErrorTitle": "Une erreur sest 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",

View file

@ -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",

View file

@ -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",

View file

@ -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>;
}

View file

@ -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({

View file

@ -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

View file

@ -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;

View file

@ -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;

View file

@ -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;

View file

@ -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>
);

View file

@ -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>
))}

View file

@ -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} />
),
};
},
};
}

View file

@ -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 {

View file

@ -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';

View file

@ -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 });
};

View file

@ -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';
/**

View file

@ -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() {