mirror of
https://github.com/elastic/kibana.git
synced 2025-04-24 09:48:58 -04:00
[Cloud Security] Support for missing fields in vulnerabilities and removing integration installation block (#191504)
This commit is contained in:
parent
7b84fa8394
commit
a36b22aa6c
29 changed files with 263 additions and 131 deletions
|
@ -5,12 +5,6 @@
|
|||
* 2.0.
|
||||
*/
|
||||
|
||||
/*
|
||||
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
|
||||
* or more contributor license agreements. Licensed under the Elastic License
|
||||
* 2.0; you may not use this file except in compliance with the Elastic License
|
||||
* 2.0.
|
||||
*/
|
||||
import type { EcsDataStream, EcsEvent } from '@elastic/ecs';
|
||||
import type { CspBenchmarkRuleMetadata } from '../schema/rules/latest';
|
||||
|
||||
|
|
|
@ -46,6 +46,7 @@ export interface BaseCspSetupStatus {
|
|||
isPluginInitialized: boolean;
|
||||
installedPackageVersion?: string | undefined;
|
||||
hasMisconfigurationsFindings?: boolean;
|
||||
hasVulnerabilitiesFindings?: boolean;
|
||||
}
|
||||
|
||||
export type CspSetupStatus = BaseCspSetupStatus;
|
||||
|
|
|
@ -75,6 +75,7 @@ export interface CspVulnerabilityFinding {
|
|||
name: string;
|
||||
fixed_version?: string;
|
||||
};
|
||||
data_stream: { dataset: string };
|
||||
}
|
||||
|
||||
export interface Vulnerability {
|
||||
|
|
|
@ -242,6 +242,7 @@ export const VULNERABILITY_FIELDS = {
|
|||
CLOUD_ACCOUNT_NAME: 'cloud.account.name',
|
||||
CLOUD_PROVIDER: 'cloud.provider',
|
||||
DESCRIPTION: 'vulnerability.description',
|
||||
SOURCE: 'data_stream.dataset',
|
||||
} as const;
|
||||
export const VULNERABILITY_GROUPING_OPTIONS = {
|
||||
NONE: 'none',
|
||||
|
|
|
@ -0,0 +1,42 @@
|
|||
/*
|
||||
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
|
||||
* or more contributor license agreements. Licensed under the Elastic License
|
||||
* 2.0; you may not use this file except in compliance with the Elastic License
|
||||
* 2.0.
|
||||
*/
|
||||
|
||||
import {
|
||||
CSP_MISCONFIGURATIONS_DATASET,
|
||||
CSP_VULN_DATASET,
|
||||
getDatasetDisplayName,
|
||||
WIZ_MISCONFIGURATIONS_DATASET,
|
||||
WIZ_VULN_DATASET,
|
||||
} from './get_dataset_display_name';
|
||||
|
||||
describe('getDatasetDisplayName', () => {
|
||||
it('should return "Wiz" when dataset is from Wiz integration', () => {
|
||||
const wizMisconfigsDatasetDisplayName = getDatasetDisplayName(WIZ_MISCONFIGURATIONS_DATASET);
|
||||
expect(wizMisconfigsDatasetDisplayName).toBe('Wiz');
|
||||
const wizVulnDatasetDisplayName = getDatasetDisplayName(WIZ_VULN_DATASET);
|
||||
expect(wizVulnDatasetDisplayName).toBe('Wiz');
|
||||
});
|
||||
|
||||
it('should return "Elastic CSP" when dataset is from Elastic CSP integration', () => {
|
||||
const elasticCspMisconfigsDatasetDisplayName = getDatasetDisplayName(
|
||||
CSP_MISCONFIGURATIONS_DATASET
|
||||
);
|
||||
expect(elasticCspMisconfigsDatasetDisplayName).toBe('Elastic CSP');
|
||||
const elasticCspVulnDatasetDisplayName = getDatasetDisplayName(CSP_VULN_DATASET);
|
||||
expect(elasticCspVulnDatasetDisplayName).toBe('Elastic CSP');
|
||||
});
|
||||
|
||||
it('should return undefined when dataset is undefined', () => {
|
||||
const result = getDatasetDisplayName(undefined);
|
||||
expect(result).toBeUndefined();
|
||||
});
|
||||
|
||||
it('should return undefined when dataset is an empty string', () => {
|
||||
const result = getDatasetDisplayName('');
|
||||
expect(result).toBeUndefined();
|
||||
});
|
||||
});
|
|
@ -7,10 +7,13 @@
|
|||
|
||||
type Dataset = 'wiz.cloud_configuration_finding' | 'cloud_security_posture.findings';
|
||||
|
||||
export const WIZ_DATASET = 'wiz.cloud_configuration_finding';
|
||||
export const CSP_DATASET = 'cloud_security_posture.findings';
|
||||
export const CSP_MISCONFIGURATIONS_DATASET = 'cloud_security_posture.findings';
|
||||
export const CSP_VULN_DATASET = 'cloud_security_posture.vulnerabilities';
|
||||
export const WIZ_MISCONFIGURATIONS_DATASET = 'wiz.cloud_configuration_finding';
|
||||
export const WIZ_VULN_DATASET = 'wiz.vulnerability';
|
||||
|
||||
export const getDatasetDisplayName = (dataset?: Dataset | string) => {
|
||||
if (dataset === WIZ_DATASET) return 'Wiz';
|
||||
if (dataset === CSP_DATASET) return 'Elastic CSP';
|
||||
if (dataset === WIZ_MISCONFIGURATIONS_DATASET || dataset === WIZ_VULN_DATASET) return 'Wiz';
|
||||
if (dataset === CSP_MISCONFIGURATIONS_DATASET || dataset === CSP_VULN_DATASET)
|
||||
return 'Elastic CSP';
|
||||
};
|
||||
|
|
|
@ -30,7 +30,7 @@ import {
|
|||
getPackageInfoMock,
|
||||
} from './mocks';
|
||||
import type { NewPackagePolicy, PackageInfo, PackagePolicy } from '@kbn/fleet-plugin/common';
|
||||
import { getPosturePolicy } from './utils';
|
||||
import { getPosturePolicy, POLICY_TEMPLATE_FORM_DTS } from './utils';
|
||||
import {
|
||||
CLOUDBEAT_AWS,
|
||||
CLOUDBEAT_AZURE,
|
||||
|
@ -161,6 +161,20 @@ describe('<CspPolicyTemplateForm />', () => {
|
|||
);
|
||||
};
|
||||
|
||||
it('shows loader when useIsSubscriptionStatusValid is loading', () => {
|
||||
(useIsSubscriptionStatusValid as jest.Mock).mockImplementation(() =>
|
||||
createReactQueryResponse({
|
||||
status: 'loading',
|
||||
data: undefined,
|
||||
})
|
||||
);
|
||||
|
||||
const policy = getMockPolicyAWS();
|
||||
render(<WrappedComponent newPolicy={policy} />);
|
||||
|
||||
expect(screen.getByTestId(POLICY_TEMPLATE_FORM_DTS.LOADER)).toBeInTheDocument();
|
||||
});
|
||||
|
||||
it('shows license block if subscription is not allowed', () => {
|
||||
(useIsSubscriptionStatusValid as jest.Mock).mockImplementation(() =>
|
||||
createReactQueryResponse({
|
||||
|
|
|
@ -51,6 +51,7 @@ import {
|
|||
isBelowMinVersion,
|
||||
type NewPackagePolicyPostureInput,
|
||||
POSTURE_NAMESPACE,
|
||||
POLICY_TEMPLATE_FORM_DTS,
|
||||
} from './utils';
|
||||
import {
|
||||
PolicyTemplateInfo,
|
||||
|
@ -775,6 +776,10 @@ export const CspPolicyTemplateForm = memo<PackagePolicyReplaceDefineStepExtensio
|
|||
setTimeout(() => setIsLoading(false), 200);
|
||||
}, [validationResultsNonNullFields]);
|
||||
|
||||
useEffect(() => {
|
||||
setIsLoading(getIsSubscriptionValid.isLoading);
|
||||
}, [getIsSubscriptionValid.isLoading]);
|
||||
|
||||
const { data: packagePolicyList, refetch } = usePackagePolicyList(packageInfo.name, {
|
||||
enabled: canFetchIntegration,
|
||||
});
|
||||
|
@ -826,7 +831,7 @@ export const CspPolicyTemplateForm = memo<PackagePolicyReplaceDefineStepExtensio
|
|||
|
||||
if (isLoading) {
|
||||
return (
|
||||
<EuiFlexGroup justifyContent="spaceAround">
|
||||
<EuiFlexGroup justifyContent="spaceAround" data-test-subj={POLICY_TEMPLATE_FORM_DTS.LOADER}>
|
||||
<EuiFlexItem grow={false}>
|
||||
<EuiLoadingSpinner size="xl" />
|
||||
</EuiFlexItem>
|
||||
|
@ -859,7 +864,7 @@ export const CspPolicyTemplateForm = memo<PackagePolicyReplaceDefineStepExtensio
|
|||
},
|
||||
];
|
||||
|
||||
if (!isSubscriptionValid) {
|
||||
if (!getIsSubscriptionValid.isLoading && !isSubscriptionValid) {
|
||||
return <SubscriptionNotAllowed />;
|
||||
}
|
||||
|
||||
|
|
|
@ -394,3 +394,7 @@ export const findVariableDef = (packageInfo: PackageInfo, key: string) => {
|
|||
.flat()
|
||||
.find((vars) => vars?.name === key);
|
||||
};
|
||||
|
||||
export const POLICY_TEMPLATE_FORM_DTS = {
|
||||
LOADER: 'policy-template-form-loader',
|
||||
};
|
||||
|
|
|
@ -40,9 +40,9 @@ export const CVSScoreBadge = ({ score, version }: CVSScoreBadgeProps) => {
|
|||
`}
|
||||
data-test-subj={VULNERABILITIES_CVSS_SCORE_BADGE_SUBJ}
|
||||
>
|
||||
{versionDisplay && (
|
||||
<>
|
||||
<EuiTextColor color="ghost">{score < 10 ? score.toFixed(1) : score}</EuiTextColor>
|
||||
<>
|
||||
<EuiTextColor color="ghost">{score < 10 ? score.toFixed(1) : score}</EuiTextColor>
|
||||
<hr
|
||||
css={css`
|
||||
width: 1px;
|
||||
|
@ -51,9 +51,9 @@ export const CVSScoreBadge = ({ score, version }: CVSScoreBadgeProps) => {
|
|||
margin: 0px 6px;
|
||||
`}
|
||||
/>
|
||||
<EuiTextColor color="ghost">{versionDisplay}</EuiTextColor>
|
||||
<EuiTextColor color="ghost">{versionDisplay || '-'}</EuiTextColor>
|
||||
</>
|
||||
)}
|
||||
</>
|
||||
</EuiBadge>
|
||||
);
|
||||
};
|
||||
|
|
|
@ -37,7 +37,10 @@ import { css } from '@emotion/react';
|
|||
import { euiThemeVars } from '@kbn/ui-theme';
|
||||
import { CspEvaluationBadge } from '@kbn/cloud-security-posture';
|
||||
import type { CspFinding } from '@kbn/cloud-security-posture-common';
|
||||
import { CSP_DATASET, getDatasetDisplayName } from '../../../common/utils/get_dataset_display_name';
|
||||
import {
|
||||
CSP_MISCONFIGURATIONS_DATASET,
|
||||
getDatasetDisplayName,
|
||||
} from '../../../common/utils/get_dataset_display_name';
|
||||
import { truthy } from '../../../../common/utils/helpers';
|
||||
import { benchmarksNavigation } from '../../../common/navigation/constants';
|
||||
import cisLogoIcon from '../../../assets/icons/cis_logo.svg';
|
||||
|
@ -170,7 +173,7 @@ const FindingsTab = ({ tab, finding }: { finding: CspFinding; tab: FindingsTab }
|
|||
|
||||
const ruleFlyoutLink =
|
||||
// currently we only support rule linking for native CSP findings
|
||||
finding.data_stream.dataset === CSP_DATASET &&
|
||||
finding.data_stream.dataset === CSP_MISCONFIGURATIONS_DATASET &&
|
||||
finding.rule?.benchmark?.version &&
|
||||
finding.rule?.benchmark?.id &&
|
||||
finding.rule?.id
|
||||
|
@ -197,7 +200,8 @@ const FindingsTab = ({ tab, finding }: { finding: CspFinding; tab: FindingsTab }
|
|||
}
|
||||
};
|
||||
|
||||
const isNativeCspFinding = (finding: CspFinding) => finding.data_stream.dataset === CSP_DATASET;
|
||||
const isNativeCspFinding = (finding: CspFinding) =>
|
||||
finding.data_stream.dataset === CSP_MISCONFIGURATIONS_DATASET;
|
||||
|
||||
const MissingFieldsCallout = ({ finding }: { finding: CspFinding }) => {
|
||||
const { euiTheme } = useEuiTheme();
|
||||
|
|
|
@ -86,4 +86,7 @@ export const mockVulnerabilityHit: CspVulnerabilityFinding = {
|
|||
hostname: 'ip-172-31-33-74',
|
||||
architecture: 'aarch64',
|
||||
},
|
||||
data_stream: {
|
||||
dataset: 'cloud_security_posture.vulnerabilities',
|
||||
},
|
||||
};
|
||||
|
|
|
@ -49,4 +49,5 @@ export const defaultColumns: CloudSecurityDefaultColumn[] = [
|
|||
{ id: VULNERABILITY_FIELDS.PACKAGE_NAME },
|
||||
{ id: VULNERABILITY_FIELDS.PACKAGE_VERSION },
|
||||
{ id: VULNERABILITY_FIELDS.PACKAGE_FIXED_VERSION },
|
||||
{ id: VULNERABILITY_FIELDS.SOURCE },
|
||||
];
|
||||
|
|
|
@ -150,6 +150,7 @@ export const LatestVulnerabilitiesContainer = () => {
|
|||
</>
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<>
|
||||
<FindingsSearchBar query={urlQuery} setQuery={setUrlQuery} loading={isFetching} />
|
||||
|
|
|
@ -11,6 +11,7 @@ import { i18n } from '@kbn/i18n';
|
|||
import { EuiDataGridCellValueElementProps, EuiSpacer } from '@elastic/eui';
|
||||
import { Filter } from '@kbn/es-query';
|
||||
import { HttpSetup } from '@kbn/core-http-browser';
|
||||
import { getDatasetDisplayName } from '../../common/utils/get_dataset_display_name';
|
||||
import { CspVulnerabilityFinding } from '../../../common/schemas';
|
||||
import { CloudSecurityDataTable } from '../../components/cloud_security_data_table';
|
||||
import { useLatestVulnerabilitiesTable } from './hooks/use_latest_vulnerabilities_table';
|
||||
|
@ -88,6 +89,11 @@ const customCellRenderer = (rows: DataTableRecord[]) => ({
|
|||
{({ finding }) => <SeverityStatusBadge severity={finding.vulnerability.severity} />}
|
||||
</CspVulnerabilityFindingRenderer>
|
||||
),
|
||||
'data_stream.dataset': ({ rowIndex }: EuiDataGridCellValueElementProps) => (
|
||||
<CspVulnerabilityFindingRenderer row={rows[rowIndex]}>
|
||||
{({ finding }) => <>{getDatasetDisplayName(finding.data_stream.dataset) || '-'}</>}
|
||||
</CspVulnerabilityFindingRenderer>
|
||||
),
|
||||
});
|
||||
|
||||
export const LatestVulnerabilitiesTable = ({
|
||||
|
|
|
@ -15,3 +15,5 @@ export const TAB_ID_VULNERABILITY_FLYOUT = (tabId: string) =>
|
|||
export const LATEST_VULNERABILITIES_TABLE = 'latest_vulnerabilities_table';
|
||||
|
||||
export const VULNERABILITIES_GROUPING_COUNTER = 'vulnerabilities_grouping_counter';
|
||||
|
||||
export const VULNERABILITIES_PAGE = 'cloud-sec-vulnerabilities-page';
|
||||
|
|
|
@ -43,21 +43,11 @@ enum AlertSuppressionMissingFieldsStrategy {
|
|||
}
|
||||
|
||||
const CSP_RULE_TAG = 'Cloud Security';
|
||||
const CNVM_RULE_TAG = 'CNVM';
|
||||
const CNVM_RULE_TAG_DATA_SOURCE = 'Data Source: Cloud Native Vulnerability Management';
|
||||
const CNVM_RULE_TAG_USE_CASE = 'Use Case: Vulnerability';
|
||||
const CNVM_RULE_TAG_OS = 'OS: Linux';
|
||||
|
||||
const STATIC_RULE_TAGS = [
|
||||
CSP_RULE_TAG,
|
||||
CNVM_RULE_TAG,
|
||||
CNVM_RULE_TAG_DATA_SOURCE,
|
||||
CNVM_RULE_TAG_USE_CASE,
|
||||
CNVM_RULE_TAG_OS,
|
||||
];
|
||||
const STATIC_RULE_TAGS = [CSP_RULE_TAG];
|
||||
|
||||
const generateVulnerabilitiesTags = (vulnerability: Vulnerability) => {
|
||||
return [...STATIC_RULE_TAGS, vulnerability.id];
|
||||
const generateVulnerabilitiesTags = (tags?: string[]) => {
|
||||
return [...STATIC_RULE_TAGS, ...(!!tags?.length ? tags : [])];
|
||||
};
|
||||
|
||||
const getVulnerabilityRuleName = (vulnerability: Vulnerability) => {
|
||||
|
@ -80,7 +70,8 @@ const generateVulnerabilitiesRuleQuery = (vulnerability: Vulnerability) => {
|
|||
*/
|
||||
export const createDetectionRuleFromVulnerabilityFinding = async (
|
||||
http: HttpSetup,
|
||||
vulnerability: Vulnerability
|
||||
vulnerability: Vulnerability,
|
||||
tags?: string[]
|
||||
) => {
|
||||
return await createDetectionRule({
|
||||
http,
|
||||
|
@ -144,7 +135,7 @@ export const createDetectionRuleFromVulnerabilityFinding = async (
|
|||
references: vulnerability.reference ? [vulnerability.reference] : [],
|
||||
name: getVulnerabilityRuleName(vulnerability),
|
||||
description: vulnerability.description,
|
||||
tags: generateVulnerabilitiesTags(vulnerability),
|
||||
tags: generateVulnerabilitiesTags(tags),
|
||||
investigation_fields: DEFAULT_INVESTIGATION_FIELDS,
|
||||
},
|
||||
});
|
||||
|
|
|
@ -9,6 +9,7 @@ import { Routes, Route } from '@kbn/shared-ux-router';
|
|||
import { findingsNavigation } from '@kbn/cloud-security-posture';
|
||||
import { useCspSetupStatusApi } from '@kbn/cloud-security-posture/src/hooks/use_csp_setup_status_api';
|
||||
import { useDataView } from '@kbn/cloud-security-posture/src/hooks/use_data_view';
|
||||
import { VULNERABILITIES_PAGE } from './test_subjects';
|
||||
import { CDR_VULNERABILITIES_DATA_VIEW_ID_PREFIX } from '../../../common/constants';
|
||||
import { NoVulnerabilitiesStates } from '../../components/no_vulnerabilities_states';
|
||||
import { CloudPosturePage } from '../../components/cloud_posture_page';
|
||||
|
@ -17,10 +18,13 @@ import { DataViewContext } from '../../common/contexts/data_view_context';
|
|||
|
||||
export const Vulnerabilities = () => {
|
||||
const dataViewQuery = useDataView(CDR_VULNERABILITIES_DATA_VIEW_ID_PREFIX);
|
||||
|
||||
const getSetupStatus = useCspSetupStatusApi();
|
||||
const setupStatus = getSetupStatus.data;
|
||||
|
||||
if (getSetupStatus?.data?.vuln_mgmt?.status !== 'indexed') return <NoVulnerabilitiesStates />;
|
||||
const hasFindings =
|
||||
!!setupStatus?.hasVulnerabilitiesFindings || setupStatus?.vuln_mgmt?.status === 'indexed';
|
||||
|
||||
if (!hasFindings) return <NoVulnerabilitiesStates />;
|
||||
|
||||
const dataViewContextValue = {
|
||||
dataView: dataViewQuery.data!,
|
||||
|
@ -30,16 +34,18 @@ export const Vulnerabilities = () => {
|
|||
|
||||
return (
|
||||
<CloudPosturePage query={dataViewQuery}>
|
||||
<Routes>
|
||||
<Route
|
||||
path={findingsNavigation.vulnerabilities.path}
|
||||
render={() => (
|
||||
<DataViewContext.Provider value={dataViewContextValue}>
|
||||
<LatestVulnerabilitiesContainer />
|
||||
</DataViewContext.Provider>
|
||||
)}
|
||||
/>
|
||||
</Routes>
|
||||
<div data-test-subj={VULNERABILITIES_PAGE}>
|
||||
<Routes>
|
||||
<Route
|
||||
path={findingsNavigation.vulnerabilities.path}
|
||||
render={() => (
|
||||
<DataViewContext.Provider value={dataViewContextValue}>
|
||||
<LatestVulnerabilitiesContainer />
|
||||
</DataViewContext.Provider>
|
||||
)}
|
||||
/>
|
||||
</Routes>
|
||||
</div>
|
||||
</CloudPosturePage>
|
||||
);
|
||||
};
|
||||
|
|
|
@ -7,24 +7,40 @@
|
|||
|
||||
import React from 'react';
|
||||
import type { HttpSetup } from '@kbn/core/public';
|
||||
import { Vulnerability } from '../../../../common/schemas';
|
||||
import { CSP_VULN_DATASET } from '../../../common/utils/get_dataset_display_name';
|
||||
import { CspVulnerabilityFinding } from '../../../../common/schemas';
|
||||
import { DetectionRuleCounter } from '../../../components/detection_rule_counter';
|
||||
import { createDetectionRuleFromVulnerabilityFinding } from '../utils/create_detection_rule_from_vulnerability';
|
||||
|
||||
const CNVM_TAG = 'CNVM';
|
||||
const CNVM_RULE_TAG_DATA_SOURCE = 'Data Source: Cloud Native Vulnerability Management';
|
||||
const CNVM_RULE_TAG_USE_CASE = 'Use Case: Vulnerability';
|
||||
const CNVM_RULE_TAG_OS = 'OS: Linux';
|
||||
|
||||
const getTags = (vulnerabilityRecord: CspVulnerabilityFinding) => {
|
||||
let tags = [vulnerabilityRecord.vulnerability.id];
|
||||
|
||||
if (vulnerabilityRecord?.data_stream?.dataset === CSP_VULN_DATASET) {
|
||||
tags = [CNVM_TAG, CNVM_RULE_TAG_DATA_SOURCE, CNVM_RULE_TAG_USE_CASE, CNVM_RULE_TAG_OS, ...tags];
|
||||
} else {
|
||||
tags.push(vulnerabilityRecord?.data_stream?.dataset);
|
||||
}
|
||||
|
||||
return tags;
|
||||
};
|
||||
|
||||
export const VulnerabilityDetectionRuleCounter = ({
|
||||
vulnerability,
|
||||
vulnerabilityRecord,
|
||||
}: {
|
||||
vulnerability: Vulnerability;
|
||||
vulnerabilityRecord: CspVulnerabilityFinding;
|
||||
}) => {
|
||||
const tags = getTags(vulnerabilityRecord);
|
||||
const createVulnerabilityRuleFn = async (http: HttpSetup) =>
|
||||
await createDetectionRuleFromVulnerabilityFinding(http, vulnerability);
|
||||
await createDetectionRuleFromVulnerabilityFinding(
|
||||
http,
|
||||
vulnerabilityRecord.vulnerability,
|
||||
tags
|
||||
);
|
||||
|
||||
return (
|
||||
<DetectionRuleCounter
|
||||
tags={[CNVM_TAG, vulnerability.id]}
|
||||
createRuleFn={createVulnerabilityRuleFn}
|
||||
/>
|
||||
);
|
||||
return <DetectionRuleCounter tags={tags} createRuleFn={createVulnerabilityRuleFn} />;
|
||||
};
|
||||
|
|
|
@ -64,6 +64,7 @@ describe('<VulnerabilityFindingFlyout/>', () => {
|
|||
</TestProvider>
|
||||
);
|
||||
getByText(mockVulnerabilityHit.vulnerability.data_source.ID);
|
||||
getByText('Elastic CSP');
|
||||
getByText(moment(mockVulnerabilityHit.vulnerability.published_date).format('LL').toString());
|
||||
getByText(mockVulnerabilityHit.vulnerability.description);
|
||||
getAllByText(mockVulnerabilityHit.vulnerability?.cvss?.nvd?.V3Vector as string);
|
||||
|
|
|
@ -63,14 +63,14 @@ const getFlyoutDescriptionList = (
|
|||
),
|
||||
description: vulnerabilityRecord.resource.name,
|
||||
},
|
||||
{
|
||||
vulnerabilityRecord.package?.name && {
|
||||
title: i18n.translate(
|
||||
'xpack.csp.vulnerabilities.vulnerabilitiesFindingFlyout.flyoutDescriptionList.packageTitle',
|
||||
{ defaultMessage: 'Package' }
|
||||
),
|
||||
description: vulnerabilityRecord.package.name,
|
||||
},
|
||||
{
|
||||
vulnerabilityRecord.package?.version && {
|
||||
title: i18n.translate(
|
||||
'xpack.csp.vulnerabilities.vulnerabilitiesFindingFlyout.flyoutDescriptionList.versionTitle',
|
||||
{ defaultMessage: 'Version' }
|
||||
|
|
|
@ -14,14 +14,10 @@ interface VulnerabilityJsonTabProps {
|
|||
vulnerabilityRecord: CspVulnerabilityFinding;
|
||||
}
|
||||
export const VulnerabilityJsonTab = ({ vulnerabilityRecord }: VulnerabilityJsonTabProps) => {
|
||||
const offsetTopHeight = 188;
|
||||
const offsetBottomHeight = 60;
|
||||
return (
|
||||
<div
|
||||
style={{ position: 'absolute', inset: 0, top: offsetTopHeight, bottom: offsetBottomHeight }}
|
||||
data-test-subj={JSON_TAB_VULNERABILITY_FLYOUT}
|
||||
>
|
||||
<div style={{ position: 'absolute', inset: 0 }} data-test-subj={JSON_TAB_VULNERABILITY_FLYOUT}>
|
||||
<CodeEditor
|
||||
enableFindAction
|
||||
isCopyable
|
||||
allowFullScreen
|
||||
languageId={XJsonLang.ID}
|
||||
|
|
|
@ -19,6 +19,7 @@ import moment from 'moment';
|
|||
import React from 'react';
|
||||
import { euiThemeVars } from '@kbn/ui-theme';
|
||||
import { i18n } from '@kbn/i18n';
|
||||
import { getDatasetDisplayName } from '../../../common/utils/get_dataset_display_name';
|
||||
import { VectorScoreBase, CspVulnerabilityFinding } from '../../../../common/schemas';
|
||||
import { CspFlyoutMarkdown } from '../../configurations/findings_flyout/findings_flyout';
|
||||
import { NvdLogo } from '../../../assets/icons/nvd_logo_svg';
|
||||
|
@ -44,6 +45,8 @@ interface VulnerabilityTabProps {
|
|||
vulnerabilityRecord: CspVulnerabilityFinding;
|
||||
}
|
||||
|
||||
const EMPTY_VALUE = '-';
|
||||
|
||||
const CVSScore = ({ vectorBaseScore, vendor }: CVSScoreProps) => {
|
||||
const vendorName = cvssVendors[vendor];
|
||||
|
||||
|
@ -145,7 +148,7 @@ const VulnerabilityOverviewTiles = ({ vulnerabilityRecord }: VulnerabilityTabPro
|
|||
|
||||
return (
|
||||
<EuiFlexGroup data-test-subj={OVERVIEW_TAB_VULNERABILITY_FLYOUT}>
|
||||
{vulnerability?.score?.version && vulnerability?.score?.base && (
|
||||
{vulnerability?.score?.base && (
|
||||
<EuiFlexItem css={tileStyle}>
|
||||
<EuiText css={tileTitleTextStyle}>
|
||||
<FormattedMessage
|
||||
|
@ -153,41 +156,46 @@ const VulnerabilityOverviewTiles = ({ vulnerabilityRecord }: VulnerabilityTabPro
|
|||
defaultMessage="CVSS"
|
||||
/>
|
||||
</EuiText>
|
||||
|
||||
<div>
|
||||
<CVSScoreBadge version={vulnerability.score.version} score={vulnerability.score.base} />
|
||||
<CVSScoreBadge
|
||||
version={vulnerability.score?.version}
|
||||
score={vulnerability.score.base}
|
||||
/>
|
||||
</div>
|
||||
</EuiFlexItem>
|
||||
)}
|
||||
<EuiFlexItem css={tileStyle}>
|
||||
<EuiText css={tileTitleTextStyle}>
|
||||
<FormattedMessage
|
||||
id="xpack.csp.vulnerabilities.vulnerabilityOverviewTile.dataSource"
|
||||
defaultMessage="Data Source"
|
||||
/>
|
||||
</EuiText>
|
||||
<EuiLink href={vulnerability?.data_source?.URL} target="_blank">
|
||||
{vulnerability?.data_source?.ID}
|
||||
</EuiLink>
|
||||
</EuiFlexItem>
|
||||
<EuiFlexItem css={tileStyle}>
|
||||
<EuiText css={tileTitleTextStyle}>
|
||||
<FormattedMessage
|
||||
id="xpack.csp.vulnerabilities.vulnerabilityOverviewTile.publishedDate"
|
||||
defaultMessage="Published Date"
|
||||
/>
|
||||
</EuiText>
|
||||
|
||||
<strong>
|
||||
<FormattedMessage
|
||||
id="xpack.csp.vulnerabilities.vulnerabilityOverviewTile.publishedDateText"
|
||||
defaultMessage="{date}"
|
||||
values={{
|
||||
date,
|
||||
}}
|
||||
/>
|
||||
</strong>
|
||||
</EuiFlexItem>
|
||||
{vulnerability?.data_source?.ID && (
|
||||
<EuiFlexItem css={tileStyle}>
|
||||
<EuiText css={tileTitleTextStyle}>
|
||||
<FormattedMessage
|
||||
id="xpack.csp.vulnerabilities.vulnerabilityOverviewTile.dataSource"
|
||||
defaultMessage="Data Source"
|
||||
/>
|
||||
</EuiText>
|
||||
<EuiLink href={vulnerability.data_source?.URL} target="_blank">
|
||||
{vulnerability.data_source.ID}
|
||||
</EuiLink>
|
||||
</EuiFlexItem>
|
||||
)}
|
||||
{date && (
|
||||
<EuiFlexItem css={tileStyle}>
|
||||
<EuiText css={tileTitleTextStyle}>
|
||||
<FormattedMessage
|
||||
id="xpack.csp.vulnerabilities.vulnerabilityOverviewTile.publishedDate"
|
||||
defaultMessage="Published Date"
|
||||
/>
|
||||
</EuiText>
|
||||
<strong>
|
||||
<FormattedMessage
|
||||
id="xpack.csp.vulnerabilities.vulnerabilityOverviewTile.publishedDateText"
|
||||
defaultMessage="{date}"
|
||||
values={{
|
||||
date,
|
||||
}}
|
||||
/>
|
||||
</strong>
|
||||
</EuiFlexItem>
|
||||
)}
|
||||
</EuiFlexGroup>
|
||||
);
|
||||
};
|
||||
|
@ -237,6 +245,7 @@ export const VulnerabilityOverviewTab = ({ vulnerabilityRecord }: VulnerabilityT
|
|||
</EuiFlexItem>
|
||||
|
||||
<EuiHorizontalRule css={horizontalStyle} />
|
||||
|
||||
<EuiFlexItem>
|
||||
<h4 css={flyoutSubheadingStyle}>
|
||||
<FormattedMessage
|
||||
|
@ -244,8 +253,23 @@ export const VulnerabilityOverviewTab = ({ vulnerabilityRecord }: VulnerabilityT
|
|||
defaultMessage="Alerts"
|
||||
/>
|
||||
</h4>
|
||||
<VulnerabilityDetectionRuleCounter vulnerability={vulnerability} />
|
||||
{vulnerabilityRecord.vulnerability.id ? (
|
||||
<VulnerabilityDetectionRuleCounter vulnerabilityRecord={vulnerabilityRecord} />
|
||||
) : (
|
||||
EMPTY_VALUE
|
||||
)}
|
||||
</EuiFlexItem>
|
||||
|
||||
<EuiFlexItem>
|
||||
<h4 css={flyoutSubheadingStyle}>
|
||||
<FormattedMessage
|
||||
id="xpack.csp.vulnerabilities.vulnerabilityOverviewTab.sourceTitle"
|
||||
defaultMessage="Source"
|
||||
/>
|
||||
</h4>
|
||||
{getDatasetDisplayName(vulnerabilityRecord.data_stream?.dataset) || EMPTY_VALUE}
|
||||
</EuiFlexItem>
|
||||
|
||||
<EuiFlexItem>
|
||||
<h4 css={flyoutSubheadingStyle}>
|
||||
<FormattedMessage
|
||||
|
@ -253,7 +277,7 @@ export const VulnerabilityOverviewTab = ({ vulnerabilityRecord }: VulnerabilityT
|
|||
defaultMessage="Description"
|
||||
/>
|
||||
</h4>
|
||||
<CspFlyoutMarkdown>{vulnerability?.description || ''}</CspFlyoutMarkdown>
|
||||
<CspFlyoutMarkdown>{vulnerability?.description || EMPTY_VALUE}</CspFlyoutMarkdown>
|
||||
</EuiFlexItem>
|
||||
|
||||
<EuiHorizontalRule css={horizontalStyle} />
|
||||
|
@ -265,7 +289,7 @@ export const VulnerabilityOverviewTab = ({ vulnerabilityRecord }: VulnerabilityT
|
|||
defaultMessage="Fixes"
|
||||
/>
|
||||
</h4>
|
||||
<EuiText>{fixesDisplayText}</EuiText>
|
||||
<EuiText>{fixesDisplayText || EMPTY_VALUE}</EuiText>
|
||||
</EuiFlexItem>
|
||||
|
||||
<EuiHorizontalRule css={horizontalStyle} />
|
||||
|
|
|
@ -8,35 +8,39 @@ import { i18n } from '@kbn/i18n';
|
|||
|
||||
export const vulnerabilitiesTableFieldLabels: Record<string, string> = {
|
||||
'resource.id': i18n.translate(
|
||||
'xpack.csp.findings.findingsTable.findingsTableColumn.resourceIdColumnLabel',
|
||||
'xpack.csp.vulnerabilityFindings.vulnerabilityFindingsTable.vulnerabilityFindingsTableColumn.resourceIdColumnLabel',
|
||||
{ defaultMessage: 'Resource ID' }
|
||||
),
|
||||
'resource.name': i18n.translate(
|
||||
'xpack.csp.findings.findingsTable.findingsTableColumn.resourceNameColumnLabel',
|
||||
'xpack.csp.vulnerabilityFindings.vulnerabilityFindingsTable.vulnerabilityFindingsTableColumn.resourceNameColumnLabel',
|
||||
{ defaultMessage: 'Resource Name' }
|
||||
),
|
||||
'vulnerability.id': i18n.translate(
|
||||
'xpack.csp.findings.findingsTable.findingsTableColumn.vulnerabilityIdColumnLabel',
|
||||
'xpack.csp.vulnerabilityFindings.vulnerabilityFindingsTable.vulnerabilityFindingsTableColumn.vulnerabilityIdColumnLabel',
|
||||
{ defaultMessage: 'Vulnerability' }
|
||||
),
|
||||
'vulnerability.score.base': i18n.translate(
|
||||
'xpack.csp.findings.findingsTable.findingsTableColumn.vulnerabilityScoreColumnLabel',
|
||||
'xpack.csp.vulnerabilityFindings.vulnerabilityFindingsTable.vulnerabilityFindingsTableColumn.vulnerabilityScoreColumnLabel',
|
||||
{ defaultMessage: 'CVSS' }
|
||||
),
|
||||
'vulnerability.severity': i18n.translate(
|
||||
'xpack.csp.findings.findingsTable.findingsTableColumn.vulnerabilitySeverityColumnLabel',
|
||||
'xpack.csp.vulnerabilityFindings.vulnerabilityFindingsTable.vulnerabilityFindingsTableColumn.vulnerabilitySeverityColumnLabel',
|
||||
{ defaultMessage: 'Severity' }
|
||||
),
|
||||
'package.name': i18n.translate(
|
||||
'xpack.csp.findings.findingsTable.findingsTableColumn.packageNameColumnLabel',
|
||||
'xpack.csp.vulnerabilityFindings.vulnerabilityFindingsTable.vulnerabilityFindingsTableColumn.packageNameColumnLabel',
|
||||
{ defaultMessage: 'Package' }
|
||||
),
|
||||
'package.version': i18n.translate(
|
||||
'xpack.csp.findings.findingsTable.findingsTableColumn.packageVersionColumnLabel',
|
||||
'xpack.csp.vulnerabilityFindings.vulnerabilityFindingsTable.vulnerabilityFindingsTableColumn.packageVersionColumnLabel',
|
||||
{ defaultMessage: 'Version' }
|
||||
),
|
||||
'package.fixed_version': i18n.translate(
|
||||
'xpack.csp.findings.findingsTable.findingsTableColumn.packageFixedVersionColumnLabel',
|
||||
'xpack.csp.vulnerabilityFindings.vulnerabilityFindingsTable.vulnerabilityFindingsTableColumn.packageFixedVersionColumnLabel',
|
||||
{ defaultMessage: 'Fix Version' }
|
||||
),
|
||||
'data_stream.dataset': i18n.translate(
|
||||
'xpack.csp.vulnerabilityFindings.vulnerabilityFindingsTable.vulnerabilityFindingsTableColumn.sourceColumnLabel',
|
||||
{ defaultMessage: 'Source' }
|
||||
),
|
||||
} as const;
|
||||
|
|
|
@ -21,11 +21,12 @@ import {
|
|||
NO_VULNERABILITIES_STATUS_TEST_SUBJ,
|
||||
VULNERABILITIES_CONTAINER_TEST_SUBJ,
|
||||
} from '../../components/test_subjects';
|
||||
import { render } from '@testing-library/react';
|
||||
import { render, screen } from '@testing-library/react';
|
||||
import { expectIdsInDoc } from '../../test/utils';
|
||||
import { TestProvider } from '../../test/test_provider';
|
||||
import { useLicenseManagementLocatorApi } from '../../common/api/use_license_management_locator_api';
|
||||
import { createStubDataView } from '@kbn/data-views-plugin/common/stubs';
|
||||
import { VULNERABILITIES_PAGE } from './test_subjects';
|
||||
|
||||
jest.mock('@kbn/cloud-security-posture/src/hooks/use_data_view');
|
||||
jest.mock('@kbn/cloud-security-posture/src/hooks/use_csp_setup_status_api');
|
||||
|
@ -170,10 +171,6 @@ describe('<Vulnerabilities />', () => {
|
|||
});
|
||||
});
|
||||
|
||||
xit("renders the success state component when 'latest vulnerabilities findings' DataView exists and request status is 'success'", async () => {
|
||||
// TODO: Add test cases for VulnerabilityContent
|
||||
});
|
||||
|
||||
it('renders vuln_mgmt integrations installation prompt if vuln_mgmt integration is not installed', () => {
|
||||
(useCspSetupStatusApi as jest.Mock).mockImplementation(() =>
|
||||
createReactQueryResponse({
|
||||
|
@ -203,4 +200,28 @@ describe('<Vulnerabilities />', () => {
|
|||
],
|
||||
});
|
||||
});
|
||||
|
||||
it('renders Vulnerabilities page when there are findings', async () => {
|
||||
(useCspSetupStatusApi as jest.Mock).mockImplementation(() =>
|
||||
createReactQueryResponse({
|
||||
status: 'success',
|
||||
data: {
|
||||
hasVulnerabilitiesFindings: true,
|
||||
},
|
||||
})
|
||||
);
|
||||
|
||||
(useDataView as jest.Mock).mockReturnValue({
|
||||
status: 'success',
|
||||
data: createStubDataView({
|
||||
spec: {
|
||||
id: CDR_MISCONFIGURATIONS_DATA_VIEW_ID_PREFIX,
|
||||
},
|
||||
}),
|
||||
});
|
||||
|
||||
renderVulnerabilitiesPage();
|
||||
|
||||
expect(screen.getByTestId(VULNERABILITIES_PAGE)).toBeInTheDocument();
|
||||
});
|
||||
});
|
||||
|
|
|
@ -40,6 +40,7 @@ import {
|
|||
VULN_MGMT_POLICY_TEMPLATE,
|
||||
POSTURE_TYPE_ALL,
|
||||
LATEST_VULNERABILITIES_RETENTION_POLICY,
|
||||
CDR_VULNERABILITIES_INDEX_PATTERN,
|
||||
} from '../../../common/constants';
|
||||
import type {
|
||||
CspApiRequestHandlerContext,
|
||||
|
@ -194,6 +195,7 @@ export const getCspStatus = async ({
|
|||
}: CspStatusDependencies): Promise<CspSetupStatus> => {
|
||||
const [
|
||||
hasMisconfigurationsFindings,
|
||||
hasVulnerabilitiesFindings,
|
||||
findingsLatestIndexStatus,
|
||||
findingsIndexStatus,
|
||||
scoreIndexStatus,
|
||||
|
@ -218,6 +220,12 @@ export const getCspStatus = async ({
|
|||
LATEST_FINDINGS_RETENTION_POLICY,
|
||||
logger
|
||||
),
|
||||
checkIndexHasFindings(
|
||||
esClient,
|
||||
CDR_VULNERABILITIES_INDEX_PATTERN,
|
||||
LATEST_VULNERABILITIES_RETENTION_POLICY,
|
||||
logger
|
||||
),
|
||||
checkIndexStatus(esClient, LATEST_FINDINGS_INDEX_DEFAULT_NS, logger, {
|
||||
postureType: POSTURE_TYPE_ALL,
|
||||
retentionTime: LATEST_VULNERABILITIES_RETENTION_POLICY,
|
||||
|
@ -405,6 +413,7 @@ export const getCspStatus = async ({
|
|||
...statusResponseInfo,
|
||||
installedPackageVersion: installation?.install_version,
|
||||
hasMisconfigurationsFindings,
|
||||
hasVulnerabilitiesFindings,
|
||||
};
|
||||
|
||||
assertResponse(response, logger);
|
||||
|
|
|
@ -14331,9 +14331,6 @@
|
|||
"xpack.csp.findings.findingsFlyout.ruleTabTitle": "Règle",
|
||||
"xpack.csp.findings.findingsFlyout.tableTabTitle": "Tableau",
|
||||
"xpack.csp.findings.findingsTable.findingsTableColumn.lastCheckedColumnLabel": "Dernière vérification",
|
||||
"xpack.csp.findings.findingsTable.findingsTableColumn.packageFixedVersionColumnLabel": "Version du correctif",
|
||||
"xpack.csp.findings.findingsTable.findingsTableColumn.packageNameColumnLabel": "Pack",
|
||||
"xpack.csp.findings.findingsTable.findingsTableColumn.packageVersionColumnLabel": "Version",
|
||||
"xpack.csp.findings.findingsTable.findingsTableColumn.resourceIdColumnLabel": "ID ressource",
|
||||
"xpack.csp.findings.findingsTable.findingsTableColumn.resourceNameColumnLabel": "Nom de ressource",
|
||||
"xpack.csp.findings.findingsTable.findingsTableColumn.resourceTypeColumnLabel": "Type de ressource",
|
||||
|
@ -14341,9 +14338,6 @@
|
|||
"xpack.csp.findings.findingsTable.findingsTableColumn.ruleNameColumnLabel": "Nom de règle",
|
||||
"xpack.csp.findings.findingsTable.findingsTableColumn.ruleNumberColumnLabel": "Numéro de règle",
|
||||
"xpack.csp.findings.findingsTable.findingsTableColumn.ruleSectionColumnLabel": "Section CIS",
|
||||
"xpack.csp.findings.findingsTable.findingsTableColumn.vulnerabilityIdColumnLabel": "Vulnérabilité",
|
||||
"xpack.csp.findings.findingsTable.findingsTableColumn.vulnerabilityScoreColumnLabel": "CVSS",
|
||||
"xpack.csp.findings.findingsTable.findingsTableColumn.vulnerabilitySeverityColumnLabel": "Sévérité",
|
||||
"xpack.csp.findings.gcpIntegration.gcpInputText.credentialFileText": "Chemin vers le fichier JSON qui contient les informations d'identification et la clé utilisés pour souscrire",
|
||||
"xpack.csp.findings.gcpIntegration.gcpInputText.credentialJSONText": "Blob JSON qui contient les informations d'identification et la clé utilisées pour souscrire",
|
||||
"xpack.csp.findings.gcpIntegration.gcpInputText.credentialSelectBoxTitle": "Informations d'identification",
|
||||
|
|
|
@ -14321,9 +14321,6 @@
|
|||
"xpack.csp.findings.findingsFlyout.ruleTabTitle": "ルール",
|
||||
"xpack.csp.findings.findingsFlyout.tableTabTitle": "表",
|
||||
"xpack.csp.findings.findingsTable.findingsTableColumn.lastCheckedColumnLabel": "最終確認",
|
||||
"xpack.csp.findings.findingsTable.findingsTableColumn.packageFixedVersionColumnLabel": "修正バージョン",
|
||||
"xpack.csp.findings.findingsTable.findingsTableColumn.packageNameColumnLabel": "パッケージ",
|
||||
"xpack.csp.findings.findingsTable.findingsTableColumn.packageVersionColumnLabel": "Version",
|
||||
"xpack.csp.findings.findingsTable.findingsTableColumn.resourceIdColumnLabel": "リソースID",
|
||||
"xpack.csp.findings.findingsTable.findingsTableColumn.resourceNameColumnLabel": "リソース名",
|
||||
"xpack.csp.findings.findingsTable.findingsTableColumn.resourceTypeColumnLabel": "リソースタイプ",
|
||||
|
@ -14331,9 +14328,6 @@
|
|||
"xpack.csp.findings.findingsTable.findingsTableColumn.ruleNameColumnLabel": "ルール名",
|
||||
"xpack.csp.findings.findingsTable.findingsTableColumn.ruleNumberColumnLabel": "ルール番号",
|
||||
"xpack.csp.findings.findingsTable.findingsTableColumn.ruleSectionColumnLabel": "CISセクション",
|
||||
"xpack.csp.findings.findingsTable.findingsTableColumn.vulnerabilityIdColumnLabel": "脆弱性",
|
||||
"xpack.csp.findings.findingsTable.findingsTableColumn.vulnerabilityScoreColumnLabel": "CVSS",
|
||||
"xpack.csp.findings.findingsTable.findingsTableColumn.vulnerabilitySeverityColumnLabel": "深刻度",
|
||||
"xpack.csp.findings.gcpIntegration.gcpInputText.credentialFileText": "サブスクライブに使用される資格情報とキーを含むJSONファイルへのパス",
|
||||
"xpack.csp.findings.gcpIntegration.gcpInputText.credentialJSONText": "サブスクライブに使用される資格情報とキーを含むJSON blob",
|
||||
"xpack.csp.findings.gcpIntegration.gcpInputText.credentialSelectBoxTitle": "資格情報",
|
||||
|
|
|
@ -14343,9 +14343,6 @@
|
|||
"xpack.csp.findings.findingsFlyout.ruleTabTitle": "规则",
|
||||
"xpack.csp.findings.findingsFlyout.tableTabTitle": "表",
|
||||
"xpack.csp.findings.findingsTable.findingsTableColumn.lastCheckedColumnLabel": "上次检查时间",
|
||||
"xpack.csp.findings.findingsTable.findingsTableColumn.packageFixedVersionColumnLabel": "修复版本",
|
||||
"xpack.csp.findings.findingsTable.findingsTableColumn.packageNameColumnLabel": "软件包",
|
||||
"xpack.csp.findings.findingsTable.findingsTableColumn.packageVersionColumnLabel": "版本",
|
||||
"xpack.csp.findings.findingsTable.findingsTableColumn.resourceIdColumnLabel": "资源 ID",
|
||||
"xpack.csp.findings.findingsTable.findingsTableColumn.resourceNameColumnLabel": "资源名称",
|
||||
"xpack.csp.findings.findingsTable.findingsTableColumn.resourceTypeColumnLabel": "资源类型",
|
||||
|
@ -14353,9 +14350,6 @@
|
|||
"xpack.csp.findings.findingsTable.findingsTableColumn.ruleNameColumnLabel": "规则名称",
|
||||
"xpack.csp.findings.findingsTable.findingsTableColumn.ruleNumberColumnLabel": "规则编号",
|
||||
"xpack.csp.findings.findingsTable.findingsTableColumn.ruleSectionColumnLabel": "CIS 部分",
|
||||
"xpack.csp.findings.findingsTable.findingsTableColumn.vulnerabilityIdColumnLabel": "漏洞",
|
||||
"xpack.csp.findings.findingsTable.findingsTableColumn.vulnerabilityScoreColumnLabel": "CVSS",
|
||||
"xpack.csp.findings.findingsTable.findingsTableColumn.vulnerabilitySeverityColumnLabel": "严重性",
|
||||
"xpack.csp.findings.gcpIntegration.gcpInputText.credentialFileText": "包含用于订阅的凭据和密钥的 JSON 文件的路径",
|
||||
"xpack.csp.findings.gcpIntegration.gcpInputText.credentialJSONText": "包含用于订阅的凭据和密钥的 JSON Blob",
|
||||
"xpack.csp.findings.gcpIntegration.gcpInputText.credentialSelectBoxTitle": "凭据",
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue