mirror of
https://github.com/elastic/kibana.git
synced 2025-04-23 17:28:26 -04:00
[Cloud Posture] onboarding prompts for vul_mgmt (#154118)
## Summary This PR adds onboarding prompts for when vulnerability management integration is not installed and when vulnerability management is installed then show scanning empty prompt. A `<NoVulnerabilitesState/>` component checks for `vuln_mgmt` status. If the status is `not-deployed` or `indexing` then show <ScanningVulnerabilitiesEmptyPrompt/> The packageNoInstalledRender component will listen for the current tab and renders on `useLocation().pathname`. If `currentTab` is 'vulnerabilities' then we show `<VulnerabilitiesFindingsInstalledEmptyPrompt/>`. If `currentTab` is `configurations` then show `<ConfigurationFindingsInstalledEmptyPrompt/>` Installed Prompt <img width="1728" alt="Screen Shot 2023-03-30 at 3 45 35 PM" src="https://user-images.githubusercontent.com/17135495/229010609-51a8f009-0fe1-45f3-8452-02bdd4234367.png"> Scanning Envioronment <img width="1724" alt="Screen Shot 2023-03-30 at 3 45 20 PM" src="https://user-images.githubusercontent.com/17135495/229010649-2d8c8482-c7d2-4040-927d-310354f99a94.png"> --------- Co-authored-by: Kfir Peled <kfir.peled@elastic.co>
This commit is contained in:
parent
fb0c1bf400
commit
b07743b269
22 changed files with 853 additions and 455 deletions
|
@ -12,7 +12,7 @@ export const STATS_ROUTE_PATH = '/internal/cloud_security_posture/stats/{policy_
|
|||
export const BENCHMARKS_ROUTE_PATH = '/internal/cloud_security_posture/benchmarks';
|
||||
|
||||
export const CLOUD_SECURITY_POSTURE_PACKAGE_NAME = 'cloud_security_posture';
|
||||
|
||||
// TODO: REMOVE CSP_LATEST_FINDINGS_DATA_VIEW and replace it with LATEST_FINDINGS_INDEX_PATTERN
|
||||
export const CSP_LATEST_FINDINGS_DATA_VIEW = 'logs-cloud_security_posture.findings_latest-*';
|
||||
|
||||
export const FINDINGS_INDEX_NAME = 'logs-cloud_security_posture.findings';
|
||||
|
@ -96,4 +96,7 @@ export const POSTURE_TYPES: { [x: string]: PostureTypes } = {
|
|||
[CSPM_POLICY_TEMPLATE]: CSPM_POLICY_TEMPLATE,
|
||||
[VULN_MGMT_POLICY_TEMPLATE]: VULN_MGMT_POLICY_TEMPLATE,
|
||||
[POSTURE_TYPE_ALL]: POSTURE_TYPE_ALL,
|
||||
} as const;
|
||||
};
|
||||
|
||||
export const VULNERABILITIES = 'vulnerabilities';
|
||||
export const CONFIGURATIONS = 'configurations';
|
||||
|
|
|
@ -87,6 +87,7 @@ export interface BaseCspSetupStatus {
|
|||
kspm: BaseCspSetupBothPolicy;
|
||||
vuln_mgmt: BaseCspSetupBothPolicy;
|
||||
isPluginInitialized: boolean;
|
||||
installedPackageVersion?: string | undefined;
|
||||
}
|
||||
|
||||
export type CspSetupStatus = BaseCspSetupStatus;
|
||||
|
|
|
@ -8,25 +8,24 @@
|
|||
import { useQuery } from '@tanstack/react-query';
|
||||
import { useKibana } from '@kbn/kibana-react-plugin/public';
|
||||
import type { DataView } from '@kbn/data-plugin/common';
|
||||
import { CSP_LATEST_FINDINGS_DATA_VIEW } from '../../../common/constants';
|
||||
import { CspClientPluginStartDeps } from '../../types';
|
||||
|
||||
/**
|
||||
* TODO: use perfected kibana data views
|
||||
*/
|
||||
export const useLatestFindingsDataView = () => {
|
||||
export const useLatestFindingsDataView = (dataView: string) => {
|
||||
const {
|
||||
data: { dataViews },
|
||||
} = useKibana<CspClientPluginStartDeps>().services;
|
||||
|
||||
const findDataView = async (): Promise<DataView> => {
|
||||
const dataView = (await dataViews.find(CSP_LATEST_FINDINGS_DATA_VIEW))?.[0];
|
||||
if (!dataView) {
|
||||
throw new Error('Findings data view not found');
|
||||
const dataViewObj = (await dataViews.find(dataView))?.[0];
|
||||
if (!dataViewObj) {
|
||||
throw new Error(`Data view not found [Name: {${dataView}}]`);
|
||||
}
|
||||
|
||||
return dataView;
|
||||
return dataViewObj;
|
||||
};
|
||||
|
||||
return useQuery(['latest_findings_data_view'], findDataView);
|
||||
return useQuery([`useDataView-${dataView}`], findDataView);
|
||||
};
|
||||
|
|
|
@ -12,9 +12,9 @@ import { STATUS_ROUTE_PATH } from '../../../common/constants';
|
|||
|
||||
const getCspSetupStatusQueryKey = 'csp_status_key';
|
||||
|
||||
export const useCspSetupStatusApi = ({
|
||||
options,
|
||||
}: { options?: UseQueryOptions<CspSetupStatus, unknown, CspSetupStatus> } = {}) => {
|
||||
export const useCspSetupStatusApi = (
|
||||
options?: UseQueryOptions<CspSetupStatus, unknown, CspSetupStatus>
|
||||
) => {
|
||||
const { http } = useKibana().services;
|
||||
return useQuery<CspSetupStatus, unknown, CspSetupStatus>(
|
||||
[getCspSetupStatusQueryKey],
|
||||
|
|
|
@ -6,12 +6,12 @@
|
|||
*/
|
||||
|
||||
import { pagePathGetters, pkgKeyFromPackageInfo } from '@kbn/fleet-plugin/public';
|
||||
import type { PosturePolicyTemplate } from '../../../common/types';
|
||||
import type { CloudSecurityPolicyTemplate } from '../../../common/types';
|
||||
import { useCisKubernetesIntegration } from '../api/use_cis_kubernetes_integration';
|
||||
import { useKibana } from '../hooks/use_kibana';
|
||||
|
||||
export const useCspIntegrationLink = (
|
||||
policyTemplate: PosturePolicyTemplate
|
||||
policyTemplate: CloudSecurityPolicyTemplate
|
||||
): string | undefined => {
|
||||
const { http } = useKibana().services;
|
||||
const cisIntegration = useCisKubernetesIntegration();
|
||||
|
|
|
@ -1,53 +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 { CspSetupStatus } from '../../../common/types';
|
||||
|
||||
// Cloud Posture Management Status
|
||||
export const getCpmStatus = (cpmStatusData: CspSetupStatus | undefined) => {
|
||||
// if has findings in any of the integrations.
|
||||
const hasFindings =
|
||||
cpmStatusData?.indicesDetails[0].status === 'not-empty' ||
|
||||
cpmStatusData?.kspm.status === 'indexed' ||
|
||||
cpmStatusData?.cspm.status === 'indexed';
|
||||
|
||||
// kspm
|
||||
const hasKspmFindings =
|
||||
cpmStatusData?.kspm?.status === 'indexed' ||
|
||||
cpmStatusData?.indicesDetails[0].status === 'not-empty';
|
||||
|
||||
// cspm
|
||||
const hasCspmFindings =
|
||||
cpmStatusData?.cspm?.status === 'indexed' ||
|
||||
cpmStatusData?.indicesDetails[0].status === 'not-empty';
|
||||
|
||||
const isKspmInstalled = cpmStatusData?.kspm?.status !== 'not-installed';
|
||||
const isCspmInstalled = cpmStatusData?.cspm?.status !== 'not-installed';
|
||||
const isKspmPrivileged = cpmStatusData?.kspm?.status !== 'unprivileged';
|
||||
const isCspmPrivileged = cpmStatusData?.cspm?.status !== 'unprivileged';
|
||||
|
||||
const isCspmIntegrationInstalled = isCspmInstalled && isCspmPrivileged;
|
||||
const isKspmIntegrationInstalled = isKspmInstalled && isKspmPrivileged;
|
||||
|
||||
const isEmptyData =
|
||||
cpmStatusData?.kspm?.status === 'not-installed' &&
|
||||
cpmStatusData?.cspm?.status === 'not-installed' &&
|
||||
cpmStatusData?.indicesDetails[0].status === 'empty';
|
||||
|
||||
return {
|
||||
hasFindings,
|
||||
hasKspmFindings,
|
||||
hasCspmFindings,
|
||||
isCspmInstalled,
|
||||
isKspmInstalled,
|
||||
isKspmPrivileged,
|
||||
isCspmPrivileged,
|
||||
isCspmIntegrationInstalled,
|
||||
isKspmIntegrationInstalled,
|
||||
isEmptyData,
|
||||
};
|
||||
};
|
|
@ -23,8 +23,6 @@ import React, { ComponentProps } from 'react';
|
|||
import { UseQueryResult } from '@tanstack/react-query';
|
||||
import { CloudPosturePage } from './cloud_posture_page';
|
||||
import { NoDataPage } from '@kbn/kibana-react-plugin/public';
|
||||
import { useCspSetupStatusApi } from '../common/api/use_setup_status_api';
|
||||
import { useCspIntegrationLink } from '../common/navigation/use_csp_integration_link';
|
||||
|
||||
const chance = new Chance();
|
||||
|
||||
|
@ -35,19 +33,6 @@ jest.mock('../common/navigation/use_csp_integration_link');
|
|||
describe('<CloudPosturePage />', () => {
|
||||
beforeEach(() => {
|
||||
jest.resetAllMocks();
|
||||
(useCspSetupStatusApi as jest.Mock).mockImplementation(() =>
|
||||
createReactQueryResponse({
|
||||
status: 'success',
|
||||
data: {
|
||||
cspm: { status: 'indexed' },
|
||||
kspm: { status: 'indexed' },
|
||||
indicesDetails: [
|
||||
{ index: 'logs-cloud_security_posture.findings_latest-default', status: 'not-empty' },
|
||||
{ index: 'logs-cloud_security_posture.findings-default*', status: 'not-empty' },
|
||||
],
|
||||
},
|
||||
})
|
||||
);
|
||||
|
||||
(useSubscriptionStatus as jest.Mock).mockImplementation(() =>
|
||||
createReactQueryResponse({
|
||||
|
@ -58,7 +43,9 @@ describe('<CloudPosturePage />', () => {
|
|||
});
|
||||
|
||||
const renderCloudPosturePage = (
|
||||
props: ComponentProps<typeof CloudPosturePage> = { children: null }
|
||||
props: ComponentProps<typeof CloudPosturePage> = {
|
||||
children: null,
|
||||
}
|
||||
) => {
|
||||
const mockCore = coreMock.createStart();
|
||||
|
||||
|
@ -147,69 +134,6 @@ describe('<CloudPosturePage />', () => {
|
|||
expect(screen.queryByTestId(ERROR_STATE_TEST_SUBJECT)).not.toBeInTheDocument();
|
||||
});
|
||||
|
||||
it('renders integrations installation prompt if integration is not installed', () => {
|
||||
(useCspSetupStatusApi as jest.Mock).mockImplementation(() =>
|
||||
createReactQueryResponse({
|
||||
status: 'success',
|
||||
data: {
|
||||
kspm: { status: 'not-installed' },
|
||||
cspm: { status: 'not-installed' },
|
||||
indicesDetails: [
|
||||
{ index: 'logs-cloud_security_posture.findings_latest-default', status: 'empty' },
|
||||
{ index: 'logs-cloud_security_posture.findings-default*', status: 'empty' },
|
||||
],
|
||||
},
|
||||
})
|
||||
);
|
||||
(useCspIntegrationLink as jest.Mock).mockImplementation(() => chance.url());
|
||||
|
||||
const children = chance.sentence();
|
||||
renderCloudPosturePage({ children });
|
||||
|
||||
expect(screen.getByTestId(PACKAGE_NOT_INSTALLED_TEST_SUBJECT)).toBeInTheDocument();
|
||||
expect(screen.queryByText(children)).not.toBeInTheDocument();
|
||||
expect(screen.queryByTestId(LOADING_STATE_TEST_SUBJECT)).not.toBeInTheDocument();
|
||||
expect(screen.queryByTestId(SUBSCRIPTION_NOT_ALLOWED_TEST_SUBJECT)).not.toBeInTheDocument();
|
||||
expect(screen.queryByTestId(ERROR_STATE_TEST_SUBJECT)).not.toBeInTheDocument();
|
||||
});
|
||||
|
||||
it('renders default loading state when the integration query is loading', () => {
|
||||
(useCspSetupStatusApi as jest.Mock).mockImplementation(
|
||||
() =>
|
||||
createReactQueryResponse({
|
||||
status: 'loading',
|
||||
}) as unknown as UseQueryResult
|
||||
);
|
||||
|
||||
const children = chance.sentence();
|
||||
renderCloudPosturePage({ children });
|
||||
|
||||
expect(screen.getByTestId(LOADING_STATE_TEST_SUBJECT)).toBeInTheDocument();
|
||||
expect(screen.queryByText(children)).not.toBeInTheDocument();
|
||||
expect(screen.queryByTestId(ERROR_STATE_TEST_SUBJECT)).not.toBeInTheDocument();
|
||||
expect(screen.queryByTestId(SUBSCRIPTION_NOT_ALLOWED_TEST_SUBJECT)).not.toBeInTheDocument();
|
||||
expect(screen.queryByTestId(PACKAGE_NOT_INSTALLED_TEST_SUBJECT)).not.toBeInTheDocument();
|
||||
});
|
||||
|
||||
it('renders default error state when the integration query has an error', () => {
|
||||
(useCspSetupStatusApi as jest.Mock).mockImplementation(
|
||||
() =>
|
||||
createReactQueryResponse({
|
||||
status: 'error',
|
||||
error: new Error('error'),
|
||||
}) as unknown as UseQueryResult
|
||||
);
|
||||
|
||||
const children = chance.sentence();
|
||||
renderCloudPosturePage({ children });
|
||||
|
||||
expect(screen.getByTestId(ERROR_STATE_TEST_SUBJECT)).toBeInTheDocument();
|
||||
expect(screen.queryByTestId(LOADING_STATE_TEST_SUBJECT)).not.toBeInTheDocument();
|
||||
expect(screen.queryByTestId(SUBSCRIPTION_NOT_ALLOWED_TEST_SUBJECT)).not.toBeInTheDocument();
|
||||
expect(screen.queryByText(children)).not.toBeInTheDocument();
|
||||
expect(screen.queryByTestId(PACKAGE_NOT_INSTALLED_TEST_SUBJECT)).not.toBeInTheDocument();
|
||||
});
|
||||
|
||||
it('renders default loading text when query isLoading', () => {
|
||||
const query = createReactQueryResponse({
|
||||
status: 'loading',
|
||||
|
|
|
@ -7,34 +7,22 @@
|
|||
import React from 'react';
|
||||
import { i18n } from '@kbn/i18n';
|
||||
import type { UseQueryResult } from '@tanstack/react-query';
|
||||
import {
|
||||
EuiButton,
|
||||
EuiEmptyPrompt,
|
||||
EuiImage,
|
||||
EuiFlexGroup,
|
||||
EuiFlexItem,
|
||||
EuiLink,
|
||||
} from '@elastic/eui';
|
||||
import { EuiEmptyPrompt } from '@elastic/eui';
|
||||
import { FormattedMessage } from '@kbn/i18n-react';
|
||||
import { NoDataPage, NoDataPageProps } from '@kbn/kibana-react-plugin/public';
|
||||
import { css } from '@emotion/react';
|
||||
import { CSPM_POLICY_TEMPLATE, KSPM_POLICY_TEMPLATE } from '../../common/constants';
|
||||
import { SubscriptionNotAllowed } from './subscription_not_allowed';
|
||||
import { useSubscriptionStatus } from '../common/hooks/use_subscription_status';
|
||||
import { FullSizeCenteredPage } from './full_size_centered_page';
|
||||
import { useCspSetupStatusApi } from '../common/api/use_setup_status_api';
|
||||
import { CspLoadingState } from './csp_loading_state';
|
||||
import { useCspIntegrationLink } from '../common/navigation/use_csp_integration_link';
|
||||
|
||||
import noDataIllustration from '../assets/illustrations/no_data_illustration.svg';
|
||||
import { cspIntegrationDocsNavigation } from '../common/navigation/constants';
|
||||
import { getCpmStatus } from '../common/utils/get_cpm_status';
|
||||
|
||||
export const LOADING_STATE_TEST_SUBJECT = 'cloud_posture_page_loading';
|
||||
export const ERROR_STATE_TEST_SUBJECT = 'cloud_posture_page_error';
|
||||
export const PACKAGE_NOT_INSTALLED_TEST_SUBJECT = 'cloud_posture_page_package_not_installed';
|
||||
export const CSPM_INTEGRATION_NOT_INSTALLED_TEST_SUBJECT = 'cloud_posture_page_cspm_not_installed';
|
||||
export const KSPM_INTEGRATION_NOT_INSTALLED_TEST_SUBJECT = 'cloud_posture_page_kspm_not_installed';
|
||||
export const VULN_MGMT_INTEGRATION_NOT_INSTALLED_TEST_SUBJECT =
|
||||
'cloud_posture_page_vuln_mgmt_not_installed';
|
||||
export const DEFAULT_NO_DATA_TEST_SUBJECT = 'cloud_posture_page_no_data';
|
||||
export const SUBSCRIPTION_NOT_ALLOWED_TEST_SUBJECT = 'cloud_posture_page_subscription_not_allowed';
|
||||
|
||||
|
@ -104,71 +92,6 @@ export const CspNoDataPage = ({
|
|||
);
|
||||
};
|
||||
|
||||
const packageNotInstalledRenderer = ({
|
||||
kspmIntegrationLink,
|
||||
cspmIntegrationLink,
|
||||
}: {
|
||||
kspmIntegrationLink?: string;
|
||||
cspmIntegrationLink?: string;
|
||||
}) => {
|
||||
return (
|
||||
<FullSizeCenteredPage>
|
||||
<EuiEmptyPrompt
|
||||
data-test-subj={PACKAGE_NOT_INSTALLED_TEST_SUBJECT}
|
||||
icon={<EuiImage size="fullWidth" src={noDataIllustration} alt="no-data-illustration" />}
|
||||
title={
|
||||
<h2>
|
||||
<FormattedMessage
|
||||
id="xpack.csp.cloudPosturePage.packageNotInstalledRenderer.promptTitle"
|
||||
defaultMessage="Detect security misconfigurations in your cloud infrastructure!"
|
||||
/>
|
||||
</h2>
|
||||
}
|
||||
layout="horizontal"
|
||||
color="plain"
|
||||
body={
|
||||
<p>
|
||||
<FormattedMessage
|
||||
id="xpack.csp.cloudPosturePage.packageNotInstalledRenderer.promptDescription"
|
||||
defaultMessage="Detect and remediate potential configuration risks in your cloud infrastructure, like publicly accessible S3 buckets, with our Cloud and Kubernetes Security Posture Management solutions. {learnMore}"
|
||||
values={{
|
||||
learnMore: (
|
||||
<EuiLink href={cspIntegrationDocsNavigation.cspm.overviewPath} target="_blank">
|
||||
<FormattedMessage
|
||||
id="xpack.csp.cloudPosturePage.packageNotInstalledRenderer.learnMoreTitle"
|
||||
defaultMessage="Learn more about Cloud Security Posture"
|
||||
/>
|
||||
</EuiLink>
|
||||
),
|
||||
}}
|
||||
/>
|
||||
</p>
|
||||
}
|
||||
actions={
|
||||
<EuiFlexGroup>
|
||||
<EuiFlexItem grow={false}>
|
||||
<EuiButton color="primary" fill href={cspmIntegrationLink}>
|
||||
<FormattedMessage
|
||||
id="xpack.csp.cloudPosturePage.packageNotInstalledRenderer.addCspmIntegrationButtonTitle"
|
||||
defaultMessage="Add CSPM Integration"
|
||||
/>
|
||||
</EuiButton>
|
||||
</EuiFlexItem>
|
||||
<EuiFlexItem grow={false}>
|
||||
<EuiButton color="primary" fill href={kspmIntegrationLink}>
|
||||
<FormattedMessage
|
||||
id="xpack.csp.cloudPosturePage.packageNotInstalledRenderer.addKspmIntegrationButtonTitle"
|
||||
defaultMessage="Add KSPM Integration"
|
||||
/>
|
||||
</EuiButton>
|
||||
</EuiFlexItem>
|
||||
</EuiFlexGroup>
|
||||
}
|
||||
/>
|
||||
</FullSizeCenteredPage>
|
||||
);
|
||||
};
|
||||
|
||||
const defaultLoadingRenderer = () => (
|
||||
<CspLoadingState data-test-subj={LOADING_STATE_TEST_SUBJECT}>
|
||||
<FormattedMessage
|
||||
|
@ -251,10 +174,6 @@ export const CloudPosturePage = <TData, TError>({
|
|||
noDataRenderer = defaultNoDataRenderer,
|
||||
}: CloudPosturePageProps<TData, TError>) => {
|
||||
const subscriptionStatus = useSubscriptionStatus();
|
||||
const { data: getSetupStatus, isLoading, isError, error } = useCspSetupStatusApi();
|
||||
const kspmIntegrationLink = useCspIntegrationLink(KSPM_POLICY_TEMPLATE);
|
||||
const cspmIntegrationLink = useCspIntegrationLink(CSPM_POLICY_TEMPLATE);
|
||||
const { isEmptyData, hasFindings } = getCpmStatus(getSetupStatus);
|
||||
|
||||
const render = () => {
|
||||
if (subscriptionStatus.isError) {
|
||||
|
@ -269,23 +188,6 @@ export const CloudPosturePage = <TData, TError>({
|
|||
return subscriptionNotAllowedRenderer();
|
||||
}
|
||||
|
||||
if (isError) {
|
||||
return defaultErrorRenderer(error);
|
||||
}
|
||||
|
||||
if (isLoading) {
|
||||
return defaultLoadingRenderer();
|
||||
}
|
||||
|
||||
/* Checks if its a completely new user which means no integration has been installed and no latest findings default index has been found */
|
||||
if (isEmptyData) {
|
||||
return packageNotInstalledRenderer({ kspmIntegrationLink, cspmIntegrationLink });
|
||||
}
|
||||
|
||||
if (!hasFindings) {
|
||||
return children;
|
||||
}
|
||||
|
||||
if (!query) {
|
||||
return children;
|
||||
}
|
||||
|
|
|
@ -13,17 +13,24 @@ import {
|
|||
EuiIcon,
|
||||
EuiMarkdownFormat,
|
||||
EuiLink,
|
||||
EuiFlexGroup,
|
||||
EuiFlexItem,
|
||||
EuiImage,
|
||||
} from '@elastic/eui';
|
||||
import { FormattedMessage } from '@kbn/i18n-react';
|
||||
import { i18n } from '@kbn/i18n';
|
||||
import { css } from '@emotion/react';
|
||||
import { CSPM_POLICY_TEMPLATE, KSPM_POLICY_TEMPLATE } from '../../common/constants';
|
||||
import { FullSizeCenteredPage } from './full_size_centered_page';
|
||||
import { useCspBenchmarkIntegrations } from '../pages/benchmarks/use_csp_benchmark_integrations';
|
||||
import { useCISIntegrationPoliciesLink } from '../common/navigation/use_navigate_to_cis_integration_policies';
|
||||
import { NO_FINDINGS_STATUS_TEST_SUBJ } from './test_subjects';
|
||||
import { CloudPosturePage } from './cloud_posture_page';
|
||||
import { CloudPosturePage, PACKAGE_NOT_INSTALLED_TEST_SUBJECT } from './cloud_posture_page';
|
||||
import { useCspSetupStatusApi } from '../common/api/use_setup_status_api';
|
||||
import type { IndexDetails, PostureTypes } from '../../common/types';
|
||||
import { cspIntegrationDocsNavigation } from '../common/navigation/constants';
|
||||
import noDataIllustration from '../assets/illustrations/no_data_illustration.svg';
|
||||
import { useCspIntegrationLink } from '../common/navigation/use_csp_integration_link';
|
||||
|
||||
const REFETCH_INTERVAL_MS = 20000;
|
||||
|
||||
|
@ -172,18 +179,87 @@ const Unprivileged = ({ unprivilegedIndices }: { unprivilegedIndices: string[] }
|
|||
/>
|
||||
);
|
||||
|
||||
const ConfigurationFindingsInstalledEmptyPrompt = ({
|
||||
kspmIntegrationLink,
|
||||
cspmIntegrationLink,
|
||||
}: {
|
||||
kspmIntegrationLink?: string;
|
||||
cspmIntegrationLink?: string;
|
||||
}) => {
|
||||
return (
|
||||
<EuiEmptyPrompt
|
||||
data-test-subj={PACKAGE_NOT_INSTALLED_TEST_SUBJECT}
|
||||
icon={<EuiImage size="fullWidth" src={noDataIllustration} alt="no-data-illustration" />}
|
||||
title={
|
||||
<h2>
|
||||
<FormattedMessage
|
||||
id="xpack.csp.cloudPosturePage.packageNotInstalledRenderer.promptTitle"
|
||||
defaultMessage="Detect security misconfigurations in your cloud infrastructure!"
|
||||
/>
|
||||
</h2>
|
||||
}
|
||||
layout="horizontal"
|
||||
color="plain"
|
||||
body={
|
||||
<p>
|
||||
<FormattedMessage
|
||||
id="xpack.csp.cloudPosturePage.packageNotInstalledRenderer.promptDescription"
|
||||
defaultMessage="Detect and remediate potential configuration risks in your cloud infrastructure, like publicly accessible S3 buckets, with our Cloud and Kubernetes Security Posture Management solutions. {learnMore}"
|
||||
values={{
|
||||
learnMore: (
|
||||
<EuiLink href={cspIntegrationDocsNavigation.cspm.overviewPath} target="_blank">
|
||||
<FormattedMessage
|
||||
id="xpack.csp.cloudPosturePage.packageNotInstalledRenderer.learnMoreTitle"
|
||||
defaultMessage="Learn more about Cloud Security Posture"
|
||||
/>
|
||||
</EuiLink>
|
||||
),
|
||||
}}
|
||||
/>
|
||||
</p>
|
||||
}
|
||||
actions={
|
||||
<EuiFlexGroup>
|
||||
<EuiFlexItem grow={false}>
|
||||
<EuiButton color="primary" fill href={cspmIntegrationLink}>
|
||||
<FormattedMessage
|
||||
id="xpack.csp.cloudPosturePage.packageNotInstalledRenderer.addCspmIntegrationButtonTitle"
|
||||
defaultMessage="Add CSPM Integration"
|
||||
/>
|
||||
</EuiButton>
|
||||
</EuiFlexItem>
|
||||
<EuiFlexItem grow={false}>
|
||||
<EuiButton color="primary" fill href={kspmIntegrationLink}>
|
||||
<FormattedMessage
|
||||
id="xpack.csp.cloudPosturePage.packageNotInstalledRenderer.addKspmIntegrationButtonTitle"
|
||||
defaultMessage="Add KSPM Integration"
|
||||
/>
|
||||
</EuiButton>
|
||||
</EuiFlexItem>
|
||||
</EuiFlexGroup>
|
||||
}
|
||||
/>
|
||||
);
|
||||
};
|
||||
|
||||
/**
|
||||
* This component will return the render states based on cloud posture setup status API
|
||||
* since 'not-installed' is being checked globally by CloudPosturePage and 'indexed' is the pass condition, those states won't be handled here
|
||||
* */
|
||||
export const NoFindingsStates = ({ posturetype }: { posturetype: PostureTypes }) => {
|
||||
const getSetupStatus = useCspSetupStatusApi({
|
||||
options: { refetchInterval: REFETCH_INTERVAL_MS },
|
||||
refetchInterval: REFETCH_INTERVAL_MS,
|
||||
});
|
||||
const statusKspm = getSetupStatus.data?.kspm?.status;
|
||||
const statusCspm = getSetupStatus.data?.cspm?.status;
|
||||
const indicesStatus = getSetupStatus.data?.indicesDetails;
|
||||
const status = posturetype === 'cspm' ? statusCspm : statusKspm;
|
||||
const showConfigurationInstallPrompt =
|
||||
getSetupStatus.data?.kspm?.status === 'not-installed' &&
|
||||
getSetupStatus.data?.cspm?.status === 'not-installed';
|
||||
const kspmIntegrationLink = useCspIntegrationLink(KSPM_POLICY_TEMPLATE);
|
||||
const cspmIntegrationLink = useCspIntegrationLink(CSPM_POLICY_TEMPLATE);
|
||||
|
||||
const unprivilegedIndices =
|
||||
indicesStatus &&
|
||||
indicesStatus
|
||||
|
@ -196,6 +272,13 @@ export const NoFindingsStates = ({ posturetype }: { posturetype: PostureTypes })
|
|||
if (status === 'index-timeout') return <IndexTimeout />; // agent added, index timeout has passed
|
||||
if (status === 'unprivileged')
|
||||
return <Unprivileged unprivilegedIndices={unprivilegedIndices || []} />; // user has no privileges for our indices
|
||||
if (showConfigurationInstallPrompt)
|
||||
return (
|
||||
<ConfigurationFindingsInstalledEmptyPrompt
|
||||
kspmIntegrationLink={kspmIntegrationLink}
|
||||
cspmIntegrationLink={cspmIntegrationLink}
|
||||
/>
|
||||
);
|
||||
};
|
||||
|
||||
return (
|
||||
|
|
|
@ -0,0 +1,227 @@
|
|||
/*
|
||||
* 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 {
|
||||
EuiLoadingLogo,
|
||||
EuiEmptyPrompt,
|
||||
EuiIcon,
|
||||
EuiMarkdownFormat,
|
||||
EuiLink,
|
||||
EuiButton,
|
||||
EuiFlexGroup,
|
||||
EuiFlexItem,
|
||||
EuiImage,
|
||||
} from '@elastic/eui';
|
||||
import { FormattedHTMLMessage, FormattedMessage } from '@kbn/i18n-react';
|
||||
import { i18n } from '@kbn/i18n';
|
||||
import { css } from '@emotion/react';
|
||||
import { VULN_MGMT_POLICY_TEMPLATE } from '../../common/constants';
|
||||
import { FullSizeCenteredPage } from './full_size_centered_page';
|
||||
import {
|
||||
CloudPosturePage,
|
||||
VULN_MGMT_INTEGRATION_NOT_INSTALLED_TEST_SUBJECT,
|
||||
} from './cloud_posture_page';
|
||||
import { useCspSetupStatusApi } from '../common/api/use_setup_status_api';
|
||||
import type { IndexDetails } from '../../common/types';
|
||||
import { NO_VULNERABILITIES_STATUS_TEST_SUBJ } from './test_subjects';
|
||||
import noDataIllustration from '../assets/illustrations/no_data_illustration.svg';
|
||||
import { useCspIntegrationLink } from '../common/navigation/use_csp_integration_link';
|
||||
|
||||
const REFETCH_INTERVAL_MS = 20000;
|
||||
|
||||
const ScanningVulnerabilitiesEmptyPrompt = () => (
|
||||
<EuiEmptyPrompt
|
||||
data-test-subj={NO_VULNERABILITIES_STATUS_TEST_SUBJ.SCANNING_VULNERABILITIES}
|
||||
color="plain"
|
||||
icon={<EuiLoadingLogo logo="logoSecurity" size="xl" />}
|
||||
title={
|
||||
<h2>
|
||||
<FormattedMessage
|
||||
id="xpack.csp.noVulnerabilitiesStates.scanningVulnerabilitiesEmptyPrompt.indexingButtonTitle"
|
||||
defaultMessage="Scanning your cloud environment for vulnerabilities"
|
||||
/>
|
||||
</h2>
|
||||
}
|
||||
body={
|
||||
<p>
|
||||
<FormattedMessage
|
||||
id="xpack.csp.noVulnerabilitiesStates.scanningVulnerabilitiesEmptyPrompt.indexingDescription"
|
||||
defaultMessage="Results should start to appear within a minute"
|
||||
/>
|
||||
</p>
|
||||
}
|
||||
/>
|
||||
);
|
||||
|
||||
const VulnerabilitiesFindingsInstalledEmptyPrompt = ({
|
||||
vulnMgmtIntegrationLink,
|
||||
}: {
|
||||
vulnMgmtIntegrationLink?: string;
|
||||
}) => {
|
||||
return (
|
||||
<EuiEmptyPrompt
|
||||
data-test-subj={VULN_MGMT_INTEGRATION_NOT_INSTALLED_TEST_SUBJECT}
|
||||
icon={<EuiImage size="fullWidth" src={noDataIllustration} alt="no-data-illustration" />}
|
||||
title={
|
||||
<h2>
|
||||
<FormattedHTMLMessage
|
||||
tagName="h2"
|
||||
id="xpack.csp.cloudPosturePage.vulnerabilitiesInstalledEmptyPrompt.promptTitle"
|
||||
defaultMessage="Detect vulnerabilities <br/> in your cloud assets!"
|
||||
/>
|
||||
</h2>
|
||||
}
|
||||
layout="horizontal"
|
||||
color="plain"
|
||||
body={
|
||||
<p>
|
||||
<FormattedMessage
|
||||
id="xpack.csp.cloudPosturePage.vulnerabilitiesInstalledEmptyPrompt.promptDescription"
|
||||
defaultMessage="Add the Elastic Vulnerability Management Integration to begin. {learnMore}."
|
||||
values={{
|
||||
learnMore: (
|
||||
<EuiLink href={''} target="_blank">
|
||||
<FormattedMessage
|
||||
id="xpack.csp.cloudPosturePage.vulnerabilitiesInstalledEmptyPrompt.learnMoreTitle"
|
||||
defaultMessage="Learn more about Elastic Vulnerability Management(VM) for Cloud"
|
||||
/>
|
||||
</EuiLink>
|
||||
),
|
||||
}}
|
||||
/>
|
||||
</p>
|
||||
}
|
||||
actions={
|
||||
<EuiFlexGroup>
|
||||
<EuiFlexItem grow={false}>
|
||||
<EuiButton color="primary" fill href={vulnMgmtIntegrationLink}>
|
||||
<FormattedMessage
|
||||
id="xpack.csp.cloudPosturePage.vulnerabilitiesInstalledEmptyPrompt.addVulMngtIntegrationButtonTitle"
|
||||
defaultMessage="Install Elastic Vulnerability Management for Cloud"
|
||||
/>
|
||||
</EuiButton>
|
||||
</EuiFlexItem>
|
||||
</EuiFlexGroup>
|
||||
}
|
||||
/>
|
||||
);
|
||||
};
|
||||
|
||||
const IndexTimeout = () => (
|
||||
<EuiEmptyPrompt
|
||||
data-test-subj={NO_VULNERABILITIES_STATUS_TEST_SUBJ.INDEX_TIMEOUT}
|
||||
color="plain"
|
||||
icon={<EuiLoadingLogo logo="logoSecurity" size="xl" />}
|
||||
title={
|
||||
<h2>
|
||||
<FormattedMessage
|
||||
id="xpack.csp.noVulnerabilitiesStates.indexTimeout.indexTimeoutTitle"
|
||||
defaultMessage="Findings Delayed"
|
||||
/>
|
||||
</h2>
|
||||
}
|
||||
body={
|
||||
<p>
|
||||
<FormattedMessage
|
||||
id="xpack.csp.noVulnerabilitiesStates.indexTimeout.indexTimeoutDescription"
|
||||
defaultMessage="Collecting findings is taking longer than expected, please review our {docs} or reach out to support"
|
||||
values={{
|
||||
docs: (
|
||||
<EuiLink href="https://ela.st/findings" target="_blank">
|
||||
<FormattedMessage
|
||||
id="xpack.csp.noVulnerabilitiesStates.indexTimeout.indexTimeoutDocLink"
|
||||
defaultMessage="docs"
|
||||
/>
|
||||
</EuiLink>
|
||||
),
|
||||
}}
|
||||
/>
|
||||
</p>
|
||||
}
|
||||
/>
|
||||
);
|
||||
|
||||
const Unprivileged = ({ unprivilegedIndices }: { unprivilegedIndices: string[] }) => (
|
||||
<EuiEmptyPrompt
|
||||
data-test-subj={NO_VULNERABILITIES_STATUS_TEST_SUBJ.UNPRIVILEGED}
|
||||
color="plain"
|
||||
icon={<EuiIcon type="logoSecurity" size="xl" />}
|
||||
title={
|
||||
<h2>
|
||||
<FormattedMessage
|
||||
id="xpack.csp.noVulnerabilitiesStates.unprivileged.unprivilegedTitle"
|
||||
defaultMessage="Privileges required"
|
||||
/>
|
||||
</h2>
|
||||
}
|
||||
body={
|
||||
<p>
|
||||
<FormattedMessage
|
||||
id="xpack.csp.noVulnerabilitiesStates.unprivileged.unprivilegedDescription"
|
||||
defaultMessage="To view cloud posture data, you must update privileges. For more information, contact your Kibana administrator."
|
||||
/>
|
||||
</p>
|
||||
}
|
||||
footer={
|
||||
<EuiMarkdownFormat
|
||||
css={css`
|
||||
text-align: initial;
|
||||
`}
|
||||
children={
|
||||
i18n.translate(
|
||||
'xpack.csp.noVulnerabilitiesStates.unprivileged.unprivilegedFooterMarkdown',
|
||||
{
|
||||
defaultMessage:
|
||||
'Required Elasticsearch index privilege `read` for the following indices:',
|
||||
}
|
||||
) + unprivilegedIndices.map((idx) => `\n- \`${idx}\``)
|
||||
}
|
||||
/>
|
||||
}
|
||||
/>
|
||||
);
|
||||
|
||||
/**
|
||||
* This component will return the render states based on cloud posture setup status API
|
||||
* since 'not-installed' is being checked globally by CloudPosturePage and 'indexed' is the pass condition, those states won't be handled here
|
||||
* */
|
||||
export const NoVulnerabilitiesStates = () => {
|
||||
const getSetupStatus = useCspSetupStatusApi({
|
||||
refetchInterval: REFETCH_INTERVAL_MS,
|
||||
});
|
||||
const vulnMgmtIntegrationLink = useCspIntegrationLink(VULN_MGMT_POLICY_TEMPLATE);
|
||||
|
||||
const status = getSetupStatus.data?.vuln_mgmt?.status;
|
||||
const indicesStatus = getSetupStatus.data?.indicesDetails;
|
||||
const unprivilegedIndices =
|
||||
indicesStatus &&
|
||||
indicesStatus
|
||||
.filter((idxDetails) => idxDetails.status === 'unprivileged')
|
||||
.map((idxDetails: IndexDetails) => idxDetails.index)
|
||||
.sort((a, b) => a.localeCompare(b));
|
||||
|
||||
const render = () => {
|
||||
if (status === 'not-deployed' || status === 'indexing')
|
||||
return <ScanningVulnerabilitiesEmptyPrompt />; // integration installed, but no agents added
|
||||
if (status === 'index-timeout') return <IndexTimeout />; // agent added, index timeout has passed
|
||||
if (status === 'not-installed')
|
||||
return (
|
||||
<VulnerabilitiesFindingsInstalledEmptyPrompt
|
||||
vulnMgmtIntegrationLink={vulnMgmtIntegrationLink}
|
||||
/>
|
||||
);
|
||||
if (status === 'unprivileged')
|
||||
return <Unprivileged unprivilegedIndices={unprivilegedIndices || []} />; // user has no privileges for our indices
|
||||
};
|
||||
|
||||
return (
|
||||
<CloudPosturePage query={getSetupStatus}>
|
||||
<FullSizeCenteredPage>{render()}</FullSizeCenteredPage>
|
||||
</CloudPosturePage>
|
||||
);
|
||||
};
|
|
@ -19,3 +19,12 @@ export const NO_FINDINGS_STATUS_TEST_SUBJ = {
|
|||
UNPRIVILEGED: 'status-api-unprivileged',
|
||||
NO_FINDINGS: 'no-findings-found',
|
||||
};
|
||||
|
||||
export const NO_VULNERABILITIES_STATUS_TEST_SUBJ = {
|
||||
SCANNING_VULNERABILITIES: 'scanning-vulnerabilities-empty-prompt',
|
||||
UNPRIVILEGED: 'status-api-vuln-mgmt-unprivileged',
|
||||
INDEX_TIMEOUT: 'status-api-vuln-mgmt-index-timeout',
|
||||
NO_VULNERABILITIES: 'no-vulnerabilities-vuln-mgmt-found',
|
||||
};
|
||||
|
||||
export const VULNERABILITIES_CONTAINER_TEST_SUBJ = 'vulnerabilities_container';
|
||||
|
|
|
@ -42,7 +42,7 @@ describe('<ComplianceDashboard />', () => {
|
|||
(useCspSetupStatusApi as jest.Mock).mockImplementation(() =>
|
||||
createReactQueryResponse({
|
||||
status: 'success',
|
||||
data: { status: 'indexed' },
|
||||
data: { status: 'indexed', installedPackageVersion: '1.2.13' },
|
||||
})
|
||||
);
|
||||
|
||||
|
@ -94,6 +94,7 @@ describe('<ComplianceDashboard />', () => {
|
|||
data: {
|
||||
kspm: { status: 'not-deployed', healthyAgents: 0, installedPackagePolicies: 1 },
|
||||
cspm: { status: 'not-deployed', healthyAgents: 0, installedPackagePolicies: 1 },
|
||||
installedPackageVersion: '1.2.13',
|
||||
indicesDetails: [
|
||||
{ index: 'logs-cloud_security_posture.findings_latest-default', status: 'empty' },
|
||||
{ index: 'logs-cloud_security_posture.findings-default*', status: 'empty' },
|
||||
|
@ -131,6 +132,7 @@ describe('<ComplianceDashboard />', () => {
|
|||
data: {
|
||||
kspm: { status: 'indexing', healthyAgents: 1, installedPackagePolicies: 1 },
|
||||
cspm: { status: 'indexing', healthyAgents: 1, installedPackagePolicies: 1 },
|
||||
installedPackageVersion: '1.2.13',
|
||||
indicesDetails: [
|
||||
{ index: 'logs-cloud_security_posture.findings_latest-default', status: 'empty' },
|
||||
{ index: 'logs-cloud_security_posture.findings-default*', status: 'empty' },
|
||||
|
@ -168,6 +170,7 @@ describe('<ComplianceDashboard />', () => {
|
|||
data: {
|
||||
kspm: { status: 'index-timeout', healthyAgents: 1, installedPackagePolicies: 1 },
|
||||
cspm: { status: 'index-timeout', healthyAgents: 1, installedPackagePolicies: 1 },
|
||||
installedPackageVersion: '1.2.13',
|
||||
indicesDetails: [
|
||||
{ index: 'logs-cloud_security_posture.findings_latest-default', status: 'empty' },
|
||||
{ index: 'logs-cloud_security_posture.findings-default*', status: 'empty' },
|
||||
|
@ -205,6 +208,7 @@ describe('<ComplianceDashboard />', () => {
|
|||
data: {
|
||||
kspm: { status: 'unprivileged', healthyAgents: 1, installedPackagePolicies: 1 },
|
||||
cspm: { status: 'unprivileged', healthyAgents: 1, installedPackagePolicies: 1 },
|
||||
installedPackageVersion: '1.2.13',
|
||||
indicesDetails: [
|
||||
{ index: 'logs-cloud_security_posture.findings_latest-default', status: 'empty' },
|
||||
{ index: 'logs-cloud_security_posture.findings-default*', status: 'empty' },
|
||||
|
@ -242,6 +246,7 @@ describe('<ComplianceDashboard />', () => {
|
|||
data: {
|
||||
kspm: { status: 'indexed' },
|
||||
cspm: { status: 'indexed' },
|
||||
installedPackageVersion: '1.2.13',
|
||||
indicesDetails: [
|
||||
{ index: 'logs-cloud_security_posture.findings_latest-default', status: 'not-empty' },
|
||||
{ index: 'logs-cloud_security_posture.findings-default*', status: 'not-empty' },
|
||||
|
@ -280,6 +285,7 @@ describe('<ComplianceDashboard />', () => {
|
|||
data: {
|
||||
kspm: { status: 'indexed' },
|
||||
cspm: { status: 'not-installed' },
|
||||
installedPackageVersion: '1.2.13',
|
||||
indicesDetails: [
|
||||
{ index: 'logs-cloud_security_posture.findings_latest-default', status: 'not-empty' },
|
||||
{ index: 'logs-cloud_security_posture.findings-default*', status: 'not-empty' },
|
||||
|
@ -318,6 +324,7 @@ describe('<ComplianceDashboard />', () => {
|
|||
status: 'success',
|
||||
data: {
|
||||
cspm: { status: 'indexed' },
|
||||
installedPackageVersion: '1.2.13',
|
||||
indicesDetails: [
|
||||
{ index: 'logs-cloud_security_posture.findings_latest-default', status: 'not-empty' },
|
||||
{ index: 'logs-cloud_security_posture.findings-default*', status: 'not-empty' },
|
||||
|
@ -356,6 +363,7 @@ describe('<ComplianceDashboard />', () => {
|
|||
status: 'success',
|
||||
data: {
|
||||
cspm: { status: 'indexed', healthyAgents: 0, installedPackagePolicies: 1 },
|
||||
installedPackageVersion: '1.2.13',
|
||||
indicesDetails: [
|
||||
{ index: 'logs-cloud_security_posture.findings_latest-default', status: 'not-empty' },
|
||||
{ index: 'logs-cloud_security_posture.findings-default*', status: 'not-empty' },
|
||||
|
@ -395,6 +403,7 @@ describe('<ComplianceDashboard />', () => {
|
|||
data: {
|
||||
kspm: { status: 'indexed', healthyAgents: 0, installedPackagePolicies: 1 },
|
||||
cspm: { status: 'not-installed' },
|
||||
installedPackageVersion: '1.2.13',
|
||||
indicesDetails: [
|
||||
{ index: 'logs-cloud_security_posture.findings_latest-default', status: 'empty' },
|
||||
{ index: 'logs-cloud_security_posture.findings-default*', status: 'empty' },
|
||||
|
@ -434,6 +443,7 @@ describe('<ComplianceDashboard />', () => {
|
|||
data: {
|
||||
cspm: { status: 'indexed' },
|
||||
kspm: { status: 'indexed' },
|
||||
installedPackageVersion: '1.2.13',
|
||||
indicesDetails: [
|
||||
{ index: 'logs-cloud_security_posture.findings_latest-default', status: 'not-empty' },
|
||||
{ index: 'logs-cloud_security_posture.findings-default*', status: 'not-empty' },
|
||||
|
@ -473,6 +483,7 @@ describe('<ComplianceDashboard />', () => {
|
|||
data: {
|
||||
cspm: { status: 'indexed' },
|
||||
kspm: { status: 'indexed' },
|
||||
installedPackageVersion: '1.2.13',
|
||||
indicesDetails: [
|
||||
{ index: 'logs-cloud_security_posture.findings_latest-default', status: 'not-empty' },
|
||||
{ index: 'logs-cloud_security_posture.findings-default*', status: 'not-empty' },
|
||||
|
@ -512,6 +523,7 @@ describe('<ComplianceDashboard />', () => {
|
|||
data: {
|
||||
kspm: { status: 'not-deployed', healthyAgents: 0, installedPackagePolicies: 1 },
|
||||
cspm: { status: 'not-installed' },
|
||||
installedPackageVersion: '1.2.13',
|
||||
indicesDetails: [
|
||||
{ index: 'logs-cloud_security_posture.findings_latest-default', status: 'empty' },
|
||||
{ index: 'logs-cloud_security_posture.findings-default*', status: 'empty' },
|
||||
|
@ -527,7 +539,7 @@ describe('<ComplianceDashboard />', () => {
|
|||
(useCspmStatsApi as jest.Mock).mockImplementation(() => ({
|
||||
isSuccess: true,
|
||||
isLoading: false,
|
||||
data: undefined,
|
||||
data: { stats: { totalFindings: 0 } },
|
||||
}));
|
||||
|
||||
renderComplianceDashboardPage();
|
||||
|
@ -553,6 +565,7 @@ describe('<ComplianceDashboard />', () => {
|
|||
data: {
|
||||
cspm: { status: 'not-deployed' },
|
||||
kspm: { status: 'not-installed' },
|
||||
installedPackageVersion: '1.2.13',
|
||||
indicesDetails: [
|
||||
{ index: 'logs-cloud_security_posture.findings_latest-default', status: 'empty' },
|
||||
{ index: 'logs-cloud_security_posture.findings-default*', status: 'empty' },
|
||||
|
@ -568,7 +581,7 @@ describe('<ComplianceDashboard />', () => {
|
|||
(useKspmStatsApi as jest.Mock).mockImplementation(() => ({
|
||||
isSuccess: true,
|
||||
isLoading: false,
|
||||
data: undefined,
|
||||
data: { stats: { totalFindings: 0 } },
|
||||
}));
|
||||
|
||||
renderComplianceDashboardPage();
|
||||
|
|
|
@ -12,7 +12,11 @@ import { i18n } from '@kbn/i18n';
|
|||
import { FormattedMessage } from '@kbn/i18n-react';
|
||||
import { NO_FINDINGS_STATUS_TEST_SUBJ } from '../../components/test_subjects';
|
||||
import { useCspIntegrationLink } from '../../common/navigation/use_csp_integration_link';
|
||||
import type { PosturePolicyTemplate, ComplianceDashboardData } from '../../../common/types';
|
||||
import type {
|
||||
PosturePolicyTemplate,
|
||||
ComplianceDashboardData,
|
||||
BaseCspSetupStatus,
|
||||
} from '../../../common/types';
|
||||
import { CloudPosturePageTitle } from '../../components/cloud_posture_page_title';
|
||||
import {
|
||||
CloudPosturePage,
|
||||
|
@ -35,7 +39,6 @@ import { SummarySection } from './dashboard_sections/summary_section';
|
|||
import { BenchmarksSection } from './dashboard_sections/benchmarks_section';
|
||||
import { CSPM_POLICY_TEMPLATE, KSPM_POLICY_TEMPLATE } from '../../../common/constants';
|
||||
import { cspIntegrationDocsNavigation } from '../../common/navigation/constants';
|
||||
import { getCpmStatus } from '../../common/utils/get_cpm_status';
|
||||
|
||||
const noDataOptions: Record<
|
||||
PosturePolicyTemplate,
|
||||
|
@ -177,144 +180,157 @@ const IntegrationPostureDashboard = ({
|
|||
);
|
||||
};
|
||||
|
||||
const getDefaultTab = (
|
||||
pluginStatus?: BaseCspSetupStatus,
|
||||
cspmStats?: ComplianceDashboardData,
|
||||
kspmStats?: ComplianceDashboardData
|
||||
) => {
|
||||
const cspmTotalFindings = cspmStats?.stats.totalFindings;
|
||||
const kspmTotalFindings = kspmStats?.stats.totalFindings;
|
||||
const installedPolicyTemplatesCspm = pluginStatus?.cspm?.status;
|
||||
const installedPolicyTemplatesKspm = pluginStatus?.kspm?.status;
|
||||
let preferredDashboard = CSPM_POLICY_TEMPLATE;
|
||||
|
||||
// cspm has findings
|
||||
if (!!cspmTotalFindings) {
|
||||
preferredDashboard = CSPM_POLICY_TEMPLATE;
|
||||
}
|
||||
// kspm has findings
|
||||
else if (!!kspmTotalFindings) {
|
||||
preferredDashboard = KSPM_POLICY_TEMPLATE;
|
||||
}
|
||||
// cspm is installed
|
||||
else if (
|
||||
installedPolicyTemplatesCspm !== 'unprivileged' &&
|
||||
installedPolicyTemplatesCspm !== 'not-installed'
|
||||
) {
|
||||
preferredDashboard = CSPM_POLICY_TEMPLATE;
|
||||
}
|
||||
// kspm is installed
|
||||
else if (
|
||||
installedPolicyTemplatesKspm !== 'unprivileged' &&
|
||||
installedPolicyTemplatesKspm !== 'not-installed'
|
||||
) {
|
||||
preferredDashboard = KSPM_POLICY_TEMPLATE;
|
||||
}
|
||||
|
||||
return preferredDashboard;
|
||||
};
|
||||
|
||||
export const ComplianceDashboard = () => {
|
||||
const [selectedTab, setSelectedTab] = useState(CSPM_POLICY_TEMPLATE);
|
||||
const { data: getSetupStatus } = useCspSetupStatusApi();
|
||||
|
||||
const {
|
||||
hasKspmFindings,
|
||||
hasCspmFindings,
|
||||
isKspmInstalled,
|
||||
isCspmInstalled,
|
||||
isCspmIntegrationInstalled,
|
||||
isKspmIntegrationInstalled,
|
||||
} = getCpmStatus(getSetupStatus);
|
||||
|
||||
const cspmIntegrationLink = useCspIntegrationLink(CSPM_POLICY_TEMPLATE);
|
||||
const kspmIntegrationLink = useCspIntegrationLink(KSPM_POLICY_TEMPLATE);
|
||||
const isCloudSecurityPostureInstalled = !!getSetupStatus?.installedPackageVersion;
|
||||
|
||||
const getCspmDashboardData = useCspmStatsApi({
|
||||
enabled: hasCspmFindings,
|
||||
enabled: isCloudSecurityPostureInstalled,
|
||||
});
|
||||
const getKspmDashboardData = useKspmStatsApi({
|
||||
enabled: hasKspmFindings,
|
||||
enabled: isCloudSecurityPostureInstalled,
|
||||
});
|
||||
|
||||
useEffect(() => {
|
||||
const cspmTotalFindings = getCspmDashboardData.data?.stats.totalFindings;
|
||||
const kspmTotalFindings = getKspmDashboardData.data?.stats.totalFindings;
|
||||
const installedPolicyTemplatesCspm = getSetupStatus?.cspm?.status;
|
||||
const installedPolicyTemplatesKspm = getSetupStatus?.kspm?.status;
|
||||
let preferredDashboard = CSPM_POLICY_TEMPLATE;
|
||||
|
||||
// cspm has findings
|
||||
if (!!cspmTotalFindings) {
|
||||
preferredDashboard = CSPM_POLICY_TEMPLATE;
|
||||
}
|
||||
// kspm has findings
|
||||
else if (!!kspmTotalFindings) {
|
||||
preferredDashboard = KSPM_POLICY_TEMPLATE;
|
||||
}
|
||||
// cspm is installed
|
||||
else if (
|
||||
installedPolicyTemplatesCspm !== 'unprivileged' &&
|
||||
installedPolicyTemplatesCspm !== 'not-installed'
|
||||
) {
|
||||
preferredDashboard = CSPM_POLICY_TEMPLATE;
|
||||
}
|
||||
// kspm is installed
|
||||
else if (
|
||||
installedPolicyTemplatesKspm !== 'unprivileged' &&
|
||||
installedPolicyTemplatesKspm !== 'not-installed'
|
||||
) {
|
||||
preferredDashboard = KSPM_POLICY_TEMPLATE;
|
||||
}
|
||||
const preferredDashboard = getDefaultTab(
|
||||
getSetupStatus,
|
||||
getCspmDashboardData.data,
|
||||
getKspmDashboardData.data
|
||||
);
|
||||
setSelectedTab(preferredDashboard);
|
||||
}, [
|
||||
getCspmDashboardData.data,
|
||||
getCspmDashboardData.data?.stats.totalFindings,
|
||||
getKspmDashboardData.data,
|
||||
getKspmDashboardData.data?.stats.totalFindings,
|
||||
getSetupStatus,
|
||||
getSetupStatus?.cspm?.status,
|
||||
getSetupStatus?.kspm?.status,
|
||||
]);
|
||||
|
||||
const tabs = useMemo(
|
||||
() => [
|
||||
{
|
||||
label: i18n.translate('xpack.csp.dashboardTabs.cloudTab.tabTitle', {
|
||||
defaultMessage: 'Cloud',
|
||||
}),
|
||||
'data-test-subj': CLOUD_DASHBOARD_TAB,
|
||||
isSelected: selectedTab === CSPM_POLICY_TEMPLATE,
|
||||
onClick: () => setSelectedTab(CSPM_POLICY_TEMPLATE),
|
||||
content: (
|
||||
<>
|
||||
{hasCspmFindings || !isCspmInstalled ? (
|
||||
<CloudPosturePage query={getCspmDashboardData}>
|
||||
<div data-test-subj={CLOUD_DASHBOARD_CONTAINER}>
|
||||
<IntegrationPostureDashboard
|
||||
dashboardType={CSPM_POLICY_TEMPLATE}
|
||||
complianceData={getCspmDashboardData.data}
|
||||
notInstalledConfig={getNotInstalledConfig(
|
||||
CSPM_POLICY_TEMPLATE,
|
||||
cspmIntegrationLink
|
||||
)}
|
||||
isIntegrationInstalled={isCspmIntegrationInstalled}
|
||||
/>
|
||||
</div>
|
||||
</CloudPosturePage>
|
||||
) : (
|
||||
<NoFindingsStates posturetype={'cspm'} />
|
||||
)}
|
||||
</>
|
||||
),
|
||||
},
|
||||
{
|
||||
label: i18n.translate('xpack.csp.dashboardTabs.kubernetesTab.tabTitle', {
|
||||
defaultMessage: 'Kubernetes',
|
||||
}),
|
||||
'data-test-subj': KUBERNETES_DASHBOARD_TAB,
|
||||
isSelected: selectedTab === KSPM_POLICY_TEMPLATE,
|
||||
onClick: () => setSelectedTab(KSPM_POLICY_TEMPLATE),
|
||||
content: (
|
||||
<>
|
||||
{hasKspmFindings || !isKspmInstalled ? (
|
||||
<CloudPosturePage query={getKspmDashboardData}>
|
||||
<div data-test-subj={KUBERNETES_DASHBOARD_CONTAINER}>
|
||||
<IntegrationPostureDashboard
|
||||
dashboardType={KSPM_POLICY_TEMPLATE}
|
||||
complianceData={getKspmDashboardData.data}
|
||||
notInstalledConfig={getNotInstalledConfig(
|
||||
KSPM_POLICY_TEMPLATE,
|
||||
kspmIntegrationLink
|
||||
)}
|
||||
isIntegrationInstalled={isKspmIntegrationInstalled}
|
||||
/>
|
||||
</div>
|
||||
</CloudPosturePage>
|
||||
) : (
|
||||
<NoFindingsStates posturetype={'kspm'} />
|
||||
)}
|
||||
</>
|
||||
),
|
||||
},
|
||||
],
|
||||
() =>
|
||||
isCloudSecurityPostureInstalled
|
||||
? [
|
||||
{
|
||||
label: i18n.translate('xpack.csp.dashboardTabs.cloudTab.tabTitle', {
|
||||
defaultMessage: 'Cloud',
|
||||
}),
|
||||
'data-test-subj': CLOUD_DASHBOARD_TAB,
|
||||
isSelected: selectedTab === CSPM_POLICY_TEMPLATE,
|
||||
onClick: () => setSelectedTab(CSPM_POLICY_TEMPLATE),
|
||||
content: (
|
||||
<>
|
||||
{isCloudSecurityPostureInstalled &&
|
||||
(getSetupStatus?.cspm?.status === 'indexed' ||
|
||||
getSetupStatus?.cspm?.status === 'not-installed') ? (
|
||||
<CloudPosturePage query={getCspmDashboardData}>
|
||||
<div data-test-subj={CLOUD_DASHBOARD_CONTAINER}>
|
||||
<IntegrationPostureDashboard
|
||||
dashboardType={CSPM_POLICY_TEMPLATE}
|
||||
complianceData={getCspmDashboardData.data}
|
||||
notInstalledConfig={getNotInstalledConfig(
|
||||
CSPM_POLICY_TEMPLATE,
|
||||
cspmIntegrationLink
|
||||
)}
|
||||
isIntegrationInstalled={getSetupStatus?.cspm?.status !== 'not-installed'}
|
||||
/>
|
||||
</div>
|
||||
</CloudPosturePage>
|
||||
) : (
|
||||
<NoFindingsStates posturetype={'cspm'} />
|
||||
)}
|
||||
</>
|
||||
),
|
||||
},
|
||||
{
|
||||
label: i18n.translate('xpack.csp.dashboardTabs.kubernetesTab.tabTitle', {
|
||||
defaultMessage: 'Kubernetes',
|
||||
}),
|
||||
'data-test-subj': KUBERNETES_DASHBOARD_TAB,
|
||||
isSelected: selectedTab === KSPM_POLICY_TEMPLATE,
|
||||
onClick: () => setSelectedTab(KSPM_POLICY_TEMPLATE),
|
||||
content: (
|
||||
<>
|
||||
{isCloudSecurityPostureInstalled &&
|
||||
(getSetupStatus?.kspm?.status === 'indexed' ||
|
||||
getSetupStatus?.kspm?.status === 'not-installed') ? (
|
||||
<CloudPosturePage query={getKspmDashboardData}>
|
||||
<div data-test-subj={KUBERNETES_DASHBOARD_CONTAINER}>
|
||||
<IntegrationPostureDashboard
|
||||
dashboardType={KSPM_POLICY_TEMPLATE}
|
||||
complianceData={getKspmDashboardData.data}
|
||||
notInstalledConfig={getNotInstalledConfig(
|
||||
KSPM_POLICY_TEMPLATE,
|
||||
kspmIntegrationLink
|
||||
)}
|
||||
isIntegrationInstalled={getSetupStatus?.kspm?.status !== 'not-installed'}
|
||||
/>
|
||||
</div>
|
||||
</CloudPosturePage>
|
||||
) : (
|
||||
<NoFindingsStates posturetype={'kspm'} />
|
||||
)}
|
||||
</>
|
||||
),
|
||||
},
|
||||
]
|
||||
: [],
|
||||
[
|
||||
cspmIntegrationLink,
|
||||
getCspmDashboardData,
|
||||
getKspmDashboardData,
|
||||
kspmIntegrationLink,
|
||||
selectedTab,
|
||||
hasCspmFindings,
|
||||
hasKspmFindings,
|
||||
isKspmIntegrationInstalled,
|
||||
isCspmIntegrationInstalled,
|
||||
isCspmInstalled,
|
||||
isKspmInstalled,
|
||||
isCloudSecurityPostureInstalled,
|
||||
getSetupStatus?.cspm?.status,
|
||||
getSetupStatus?.kspm?.status,
|
||||
]
|
||||
);
|
||||
|
||||
return (
|
||||
<CloudPosturePage query={selectedTab === 'cspm' ? getCspmDashboardData : getKspmDashboardData}>
|
||||
<CloudPosturePage>
|
||||
<EuiPageHeader
|
||||
bottomBorder
|
||||
pageTitle={
|
||||
|
@ -336,6 +352,7 @@ export const ComplianceDashboard = () => {
|
|||
`}
|
||||
>
|
||||
{tabs.find((t) => t.isSelected)?.content}
|
||||
{!isCloudSecurityPostureInstalled && <NoFindingsStates posturetype={'cspm'} />}
|
||||
</div>
|
||||
</CloudPosturePage>
|
||||
);
|
||||
|
|
|
@ -29,6 +29,7 @@ import { render } from '@testing-library/react';
|
|||
import { expectIdsInDoc } from '../../test/utils';
|
||||
import { fleetMock } from '@kbn/fleet-plugin/public/mocks';
|
||||
import { licensingMock } from '@kbn/licensing-plugin/public/mocks';
|
||||
import { PACKAGE_NOT_INSTALLED_TEST_SUBJECT } from '../../components/cloud_posture_page';
|
||||
|
||||
jest.mock('../../common/api/use_latest_findings_data_view');
|
||||
jest.mock('../../common/api/use_setup_status_api');
|
||||
|
@ -222,4 +223,33 @@ describe('<Findings />', () => {
|
|||
],
|
||||
});
|
||||
});
|
||||
|
||||
it('renders integrations installation prompt if integration is not installed', async () => {
|
||||
(useCspSetupStatusApi as jest.Mock).mockImplementation(() =>
|
||||
createReactQueryResponse({
|
||||
status: 'success',
|
||||
data: {
|
||||
kspm: { status: 'not-installed' },
|
||||
cspm: { status: 'not-installed' },
|
||||
indicesDetails: [
|
||||
{ index: 'logs-cloud_security_posture.findings_latest-default', status: 'empty' },
|
||||
{ index: 'logs-cloud_security_posture.findings-default*', status: 'empty' },
|
||||
],
|
||||
},
|
||||
})
|
||||
);
|
||||
(useCspIntegrationLink as jest.Mock).mockImplementation(() => chance.url());
|
||||
renderFindingsPage();
|
||||
|
||||
expectIdsInDoc({
|
||||
be: [PACKAGE_NOT_INSTALLED_TEST_SUBJECT],
|
||||
notToBe: [
|
||||
TEST_SUBJECTS.LATEST_FINDINGS_CONTAINER,
|
||||
NO_FINDINGS_STATUS_TEST_SUBJ.INDEX_TIMEOUT,
|
||||
NO_FINDINGS_STATUS_TEST_SUBJ.NO_AGENTS_DEPLOYED,
|
||||
NO_FINDINGS_STATUS_TEST_SUBJ.INDEXING,
|
||||
NO_FINDINGS_STATUS_TEST_SUBJ.UNPRIVILEGED,
|
||||
],
|
||||
});
|
||||
});
|
||||
});
|
||||
|
|
|
@ -8,6 +8,7 @@ import React from 'react';
|
|||
import { Redirect, Switch, useLocation } from 'react-router-dom';
|
||||
import { Route } from '@kbn/shared-ux-router';
|
||||
import { TrackApplicationView } from '@kbn/usage-collection-plugin/public';
|
||||
import { LATEST_FINDINGS_INDEX_PATTERN } from '../../../common/constants';
|
||||
import { useCspSetupStatusApi } from '../../common/api/use_setup_status_api';
|
||||
import { NoFindingsStates } from '../../components/no_findings_states';
|
||||
import { CloudPosturePage } from '../../components/cloud_posture_page';
|
||||
|
@ -15,17 +16,20 @@ import { useLatestFindingsDataView } from '../../common/api/use_latest_findings_
|
|||
import { cloudPosturePages, findingsNavigation } from '../../common/navigation/constants';
|
||||
import { FindingsByResourceContainer } from './latest_findings_by_resource/findings_by_resource_container';
|
||||
import { LatestFindingsContainer } from './latest_findings/latest_findings_container';
|
||||
import { getCpmStatus } from '../../common/utils/get_cpm_status';
|
||||
|
||||
export const Configurations = () => {
|
||||
const location = useLocation();
|
||||
const dataViewQuery = useLatestFindingsDataView();
|
||||
const dataViewQuery = useLatestFindingsDataView(LATEST_FINDINGS_INDEX_PATTERN);
|
||||
const { data: getSetupStatus } = useCspSetupStatusApi();
|
||||
const { hasFindings, isCspmInstalled } = getCpmStatus(getSetupStatus);
|
||||
const hasConfigurationFindings =
|
||||
getSetupStatus?.kspm.status === 'indexed' || getSetupStatus?.cspm.status === 'indexed';
|
||||
|
||||
const noFindingsForPostureType = isCspmInstalled ? 'cspm' : 'kspm';
|
||||
// For now, when there are no findings we prompt first to install cspm, if it is already installed we will prompt to
|
||||
// install kspm
|
||||
const noFindingsForPostureType =
|
||||
getSetupStatus?.cspm.status !== 'not-installed' ? 'cspm' : 'kspm';
|
||||
|
||||
if (!hasFindings) return <NoFindingsStates posturetype={noFindingsForPostureType} />;
|
||||
if (!hasConfigurationFindings) return <NoFindingsStates posturetype={noFindingsForPostureType} />;
|
||||
|
||||
return (
|
||||
<CloudPosturePage query={dataViewQuery}>
|
||||
|
|
|
@ -23,6 +23,7 @@ import { CSP_MOMENT_FORMAT } from '../../../common/constants';
|
|||
import {
|
||||
INTERNAL_FEATURE_FLAGS,
|
||||
LATEST_FINDINGS_INDEX_DEFAULT_NS,
|
||||
LATEST_FINDINGS_INDEX_PATTERN,
|
||||
} from '../../../../common/constants';
|
||||
import { useLatestFindingsDataView } from '../../../common/api/use_latest_findings_data_view';
|
||||
import { useKibana } from '../../../common/hooks/use_kibana';
|
||||
|
@ -151,7 +152,7 @@ export const OverviewTab = ({ data }: { data: CspFinding }) => {
|
|||
const {
|
||||
services: { discover },
|
||||
} = useKibana();
|
||||
const latestFindingsDataView = useLatestFindingsDataView();
|
||||
const latestFindingsDataView = useLatestFindingsDataView(LATEST_FINDINGS_INDEX_PATTERN);
|
||||
|
||||
const discoverIndexLink = useMemo(
|
||||
() =>
|
||||
|
|
|
@ -29,6 +29,8 @@ import { ErrorCallout } from '../configurations/layout/error_callout';
|
|||
import { FindingsSearchBar } from '../configurations/layout/findings_search_bar';
|
||||
import { useFilteredDataView } from '../../common/api/use_filtered_data_view';
|
||||
import { CVSScoreBadge, SeverityStatusBadge } from '../../components/vulnerability_badges';
|
||||
import { NoVulnerabilitiesStates } from '../../components/no_vulnerabilities_states';
|
||||
import { useCspSetupStatusApi } from '../../common/api/use_setup_status_api';
|
||||
|
||||
const getDefaultQuery = ({ query, filters }: any): any => ({
|
||||
query,
|
||||
|
@ -39,6 +41,9 @@ const getDefaultQuery = ({ query, filters }: any): any => ({
|
|||
|
||||
export const Vulnerabilities = () => {
|
||||
const { data, isLoading, error } = useFilteredDataView(LATEST_VULNERABILITIES_INDEX_PATTERN);
|
||||
const getSetupStatus = useCspSetupStatusApi();
|
||||
|
||||
if (getSetupStatus?.data?.vuln_mgmt.status !== 'indexed') return <NoVulnerabilitiesStates />;
|
||||
|
||||
if (error) {
|
||||
return <ErrorCallout error={error as Error} />;
|
||||
|
|
|
@ -0,0 +1,202 @@
|
|||
/*
|
||||
* 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 Chance from 'chance';
|
||||
import { dataPluginMock } from '@kbn/data-plugin/public/mocks';
|
||||
import { unifiedSearchPluginMock } from '@kbn/unified-search-plugin/public/mocks';
|
||||
import { Vulnerabilities } from './vulnerabilities';
|
||||
import {
|
||||
LATEST_VULNERABILITIES_INDEX_DEFAULT_NS,
|
||||
VULN_MGMT_POLICY_TEMPLATE,
|
||||
} from '../../../common/constants';
|
||||
import { chartPluginMock } from '@kbn/charts-plugin/public/mocks';
|
||||
import { discoverPluginMock } from '@kbn/discover-plugin/public/mocks';
|
||||
import { useCspSetupStatusApi } from '../../common/api/use_setup_status_api';
|
||||
import { useSubscriptionStatus } from '../../common/hooks/use_subscription_status';
|
||||
import { createReactQueryResponse } from '../../test/fixtures/react_query';
|
||||
import { useCISIntegrationPoliciesLink } from '../../common/navigation/use_navigate_to_cis_integration_policies';
|
||||
import { useCspIntegrationLink } from '../../common/navigation/use_csp_integration_link';
|
||||
import {
|
||||
NO_VULNERABILITIES_STATUS_TEST_SUBJ,
|
||||
VULNERABILITIES_CONTAINER_TEST_SUBJ,
|
||||
} from '../../components/test_subjects';
|
||||
import { render } from '@testing-library/react';
|
||||
import { expectIdsInDoc } from '../../test/utils';
|
||||
import { fleetMock } from '@kbn/fleet-plugin/public/mocks';
|
||||
import { licensingMock } from '@kbn/licensing-plugin/public/mocks';
|
||||
import { VULN_MGMT_INTEGRATION_NOT_INSTALLED_TEST_SUBJECT } from '../../components/cloud_posture_page';
|
||||
import { TestProvider } from '../../test/test_provider';
|
||||
|
||||
jest.mock('../../common/api/use_latest_findings_data_view');
|
||||
jest.mock('../../common/api/use_setup_status_api');
|
||||
jest.mock('../../common/hooks/use_subscription_status');
|
||||
jest.mock('../../common/navigation/use_navigate_to_cis_integration_policies');
|
||||
jest.mock('../../common/navigation/use_csp_integration_link');
|
||||
|
||||
const chance = new Chance();
|
||||
|
||||
beforeEach(() => {
|
||||
jest.restoreAllMocks();
|
||||
|
||||
(useSubscriptionStatus as jest.Mock).mockImplementation(() =>
|
||||
createReactQueryResponse({
|
||||
status: 'success',
|
||||
data: true,
|
||||
})
|
||||
);
|
||||
});
|
||||
|
||||
const renderVulnerabilitiesPage = () => {
|
||||
render(
|
||||
<TestProvider
|
||||
deps={{
|
||||
data: dataPluginMock.createStartContract(),
|
||||
unifiedSearch: unifiedSearchPluginMock.createStartContract(),
|
||||
charts: chartPluginMock.createStartContract(),
|
||||
discover: discoverPluginMock.createStartContract(),
|
||||
fleet: fleetMock.createStartMock(),
|
||||
licensing: licensingMock.createStart(),
|
||||
}}
|
||||
>
|
||||
<Vulnerabilities />
|
||||
</TestProvider>
|
||||
);
|
||||
};
|
||||
|
||||
describe('<Vulnerabilities />', () => {
|
||||
it('No vulnerabilities state: not-deployed - shows NotDeployed instead of vulnerabilities ', () => {
|
||||
(useCspSetupStatusApi as jest.Mock).mockImplementation(() =>
|
||||
createReactQueryResponse({
|
||||
status: 'success',
|
||||
data: {
|
||||
[VULN_MGMT_POLICY_TEMPLATE]: { status: 'not-deployed' },
|
||||
indicesDetails: [{ index: LATEST_VULNERABILITIES_INDEX_DEFAULT_NS, status: 'empty' }],
|
||||
},
|
||||
})
|
||||
);
|
||||
(useCISIntegrationPoliciesLink as jest.Mock).mockImplementation(() => chance.url());
|
||||
(useCspIntegrationLink as jest.Mock).mockImplementation(() => chance.url());
|
||||
|
||||
renderVulnerabilitiesPage();
|
||||
|
||||
expectIdsInDoc({
|
||||
be: [NO_VULNERABILITIES_STATUS_TEST_SUBJ.SCANNING_VULNERABILITIES],
|
||||
notToBe: [
|
||||
VULNERABILITIES_CONTAINER_TEST_SUBJ,
|
||||
NO_VULNERABILITIES_STATUS_TEST_SUBJ.INDEX_TIMEOUT,
|
||||
NO_VULNERABILITIES_STATUS_TEST_SUBJ.UNPRIVILEGED,
|
||||
],
|
||||
});
|
||||
});
|
||||
|
||||
it('No vulnerabilities state: indexing - shows Indexing instead of vulnerabilities ', () => {
|
||||
(useCspSetupStatusApi as jest.Mock).mockImplementation(() =>
|
||||
createReactQueryResponse({
|
||||
status: 'success',
|
||||
data: {
|
||||
[VULN_MGMT_POLICY_TEMPLATE]: { status: 'indexing' },
|
||||
indicesDetails: [{ index: LATEST_VULNERABILITIES_INDEX_DEFAULT_NS, status: 'empty' }],
|
||||
},
|
||||
})
|
||||
);
|
||||
(useCspIntegrationLink as jest.Mock).mockImplementation(() => chance.url());
|
||||
|
||||
renderVulnerabilitiesPage();
|
||||
|
||||
expectIdsInDoc({
|
||||
be: [NO_VULNERABILITIES_STATUS_TEST_SUBJ.SCANNING_VULNERABILITIES],
|
||||
notToBe: [
|
||||
VULNERABILITIES_CONTAINER_TEST_SUBJ,
|
||||
NO_VULNERABILITIES_STATUS_TEST_SUBJ.INDEX_TIMEOUT,
|
||||
NO_VULNERABILITIES_STATUS_TEST_SUBJ.UNPRIVILEGED,
|
||||
],
|
||||
});
|
||||
});
|
||||
|
||||
it('No vulnerabilities state: index-timeout - shows IndexTimeout instead of vulnerabilities ', () => {
|
||||
(useCspSetupStatusApi as jest.Mock).mockImplementation(() =>
|
||||
createReactQueryResponse({
|
||||
status: 'success',
|
||||
data: {
|
||||
[VULN_MGMT_POLICY_TEMPLATE]: { status: 'index-timeout' },
|
||||
indicesDetails: [{ index: LATEST_VULNERABILITIES_INDEX_DEFAULT_NS, status: 'empty' }],
|
||||
},
|
||||
})
|
||||
);
|
||||
(useCspIntegrationLink as jest.Mock).mockImplementation(() => chance.url());
|
||||
|
||||
renderVulnerabilitiesPage();
|
||||
|
||||
expectIdsInDoc({
|
||||
be: [NO_VULNERABILITIES_STATUS_TEST_SUBJ.INDEX_TIMEOUT],
|
||||
notToBe: [
|
||||
VULNERABILITIES_CONTAINER_TEST_SUBJ,
|
||||
NO_VULNERABILITIES_STATUS_TEST_SUBJ.SCANNING_VULNERABILITIES,
|
||||
NO_VULNERABILITIES_STATUS_TEST_SUBJ.UNPRIVILEGED,
|
||||
],
|
||||
});
|
||||
});
|
||||
|
||||
it('No vulnerabilities state: unprivileged - shows Unprivileged instead of vulnerabilities ', () => {
|
||||
(useCspSetupStatusApi as jest.Mock).mockImplementation(() =>
|
||||
createReactQueryResponse({
|
||||
status: 'success',
|
||||
data: {
|
||||
[VULN_MGMT_POLICY_TEMPLATE]: { status: 'unprivileged' },
|
||||
indicesDetails: [{ index: LATEST_VULNERABILITIES_INDEX_DEFAULT_NS, status: 'empty' }],
|
||||
},
|
||||
})
|
||||
);
|
||||
(useCspIntegrationLink as jest.Mock).mockImplementation(() => chance.url());
|
||||
|
||||
renderVulnerabilitiesPage();
|
||||
|
||||
expectIdsInDoc({
|
||||
be: [NO_VULNERABILITIES_STATUS_TEST_SUBJ.UNPRIVILEGED],
|
||||
notToBe: [
|
||||
VULNERABILITIES_CONTAINER_TEST_SUBJ,
|
||||
NO_VULNERABILITIES_STATUS_TEST_SUBJ.SCANNING_VULNERABILITIES,
|
||||
NO_VULNERABILITIES_STATUS_TEST_SUBJ.INDEX_TIMEOUT,
|
||||
],
|
||||
});
|
||||
});
|
||||
|
||||
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({
|
||||
status: 'success',
|
||||
data: {
|
||||
kspm: { status: 'not-deployed' },
|
||||
cspm: { status: 'not-deployed' },
|
||||
[VULN_MGMT_POLICY_TEMPLATE]: { status: 'not-installed' },
|
||||
indicesDetails: [
|
||||
{ index: 'logs-cloud_security_posture.findings_latest-default', status: 'empty' },
|
||||
{ index: 'logs-cloud_security_posture.findings-default*', status: 'empty' },
|
||||
{ index: LATEST_VULNERABILITIES_INDEX_DEFAULT_NS, status: 'empty' },
|
||||
],
|
||||
},
|
||||
})
|
||||
);
|
||||
(useCspIntegrationLink as jest.Mock).mockImplementation(() => chance.url());
|
||||
|
||||
renderVulnerabilitiesPage();
|
||||
|
||||
expectIdsInDoc({
|
||||
be: [VULN_MGMT_INTEGRATION_NOT_INSTALLED_TEST_SUBJECT],
|
||||
notToBe: [
|
||||
VULNERABILITIES_CONTAINER_TEST_SUBJ,
|
||||
NO_VULNERABILITIES_STATUS_TEST_SUBJ.SCANNING_VULNERABILITIES,
|
||||
NO_VULNERABILITIES_STATUS_TEST_SUBJ.INDEX_TIMEOUT,
|
||||
NO_VULNERABILITIES_STATUS_TEST_SUBJ.UNPRIVILEGED,
|
||||
],
|
||||
});
|
||||
});
|
||||
});
|
|
@ -5,16 +5,16 @@
|
|||
* 2.0.
|
||||
*/
|
||||
|
||||
import { calculateCspStatusCode } from './status';
|
||||
import { calculateIntegrationStatus } from './status';
|
||||
import { CSPM_POLICY_TEMPLATE, VULN_MGMT_POLICY_TEMPLATE } from '../../../common/constants';
|
||||
|
||||
describe('calculateCspStatusCode for cspm', () => {
|
||||
describe('calculateIntegrationStatus for cspm', () => {
|
||||
it('Verify status when there are no permission for cspm', async () => {
|
||||
const statusCode = calculateCspStatusCode(
|
||||
const statusCode = calculateIntegrationStatus(
|
||||
CSPM_POLICY_TEMPLATE,
|
||||
{
|
||||
findingsLatest: 'unprivileged',
|
||||
findings: 'unprivileged',
|
||||
latest: 'unprivileged',
|
||||
stream: 'unprivileged',
|
||||
score: 'unprivileged',
|
||||
},
|
||||
1,
|
||||
|
@ -26,11 +26,11 @@ describe('calculateCspStatusCode for cspm', () => {
|
|||
});
|
||||
|
||||
it('Verify status when there are no findings, no healthy agents and no installed policy templates', async () => {
|
||||
const statusCode = calculateCspStatusCode(
|
||||
const statusCode = calculateIntegrationStatus(
|
||||
CSPM_POLICY_TEMPLATE,
|
||||
{
|
||||
findingsLatest: 'empty',
|
||||
findings: 'empty',
|
||||
latest: 'empty',
|
||||
stream: 'empty',
|
||||
score: 'empty',
|
||||
},
|
||||
0,
|
||||
|
@ -42,11 +42,11 @@ describe('calculateCspStatusCode for cspm', () => {
|
|||
});
|
||||
|
||||
it('Verify status when there are findings and installed policies but no healthy agents', async () => {
|
||||
const statusCode = calculateCspStatusCode(
|
||||
const statusCode = calculateIntegrationStatus(
|
||||
CSPM_POLICY_TEMPLATE,
|
||||
{
|
||||
findingsLatest: 'empty',
|
||||
findings: 'not-empty',
|
||||
latest: 'empty',
|
||||
stream: 'empty',
|
||||
score: 'not-empty',
|
||||
},
|
||||
0,
|
||||
|
@ -58,11 +58,11 @@ describe('calculateCspStatusCode for cspm', () => {
|
|||
});
|
||||
|
||||
it('Verify status when there are findings ,installed policies and healthy agents', async () => {
|
||||
const statusCode = calculateCspStatusCode(
|
||||
const statusCode = calculateIntegrationStatus(
|
||||
CSPM_POLICY_TEMPLATE,
|
||||
{
|
||||
findingsLatest: 'not-empty',
|
||||
findings: 'not-empty',
|
||||
latest: 'not-empty',
|
||||
stream: 'not-empty',
|
||||
score: 'not-empty',
|
||||
},
|
||||
1,
|
||||
|
@ -74,11 +74,11 @@ describe('calculateCspStatusCode for cspm', () => {
|
|||
});
|
||||
|
||||
it('Verify status when there are no findings ,installed policies and no healthy agents', async () => {
|
||||
const statusCode = calculateCspStatusCode(
|
||||
const statusCode = calculateIntegrationStatus(
|
||||
CSPM_POLICY_TEMPLATE,
|
||||
{
|
||||
findingsLatest: 'empty',
|
||||
findings: 'empty',
|
||||
latest: 'empty',
|
||||
stream: 'empty',
|
||||
score: 'empty',
|
||||
},
|
||||
0,
|
||||
|
@ -90,11 +90,11 @@ describe('calculateCspStatusCode for cspm', () => {
|
|||
});
|
||||
|
||||
it('Verify status when there are installed policies, healthy agents and no findings', async () => {
|
||||
const statusCode = calculateCspStatusCode(
|
||||
const statusCode = calculateIntegrationStatus(
|
||||
CSPM_POLICY_TEMPLATE,
|
||||
{
|
||||
findingsLatest: 'empty',
|
||||
findings: 'empty',
|
||||
latest: 'empty',
|
||||
stream: 'empty',
|
||||
score: 'empty',
|
||||
},
|
||||
1,
|
||||
|
@ -106,11 +106,11 @@ describe('calculateCspStatusCode for cspm', () => {
|
|||
});
|
||||
|
||||
it('Verify status when there are installed policies, healthy agents and no findings and been more than 10 minutes', async () => {
|
||||
const statusCode = calculateCspStatusCode(
|
||||
const statusCode = calculateIntegrationStatus(
|
||||
CSPM_POLICY_TEMPLATE,
|
||||
{
|
||||
findingsLatest: 'empty',
|
||||
findings: 'empty',
|
||||
latest: 'empty',
|
||||
stream: 'empty',
|
||||
score: 'empty',
|
||||
},
|
||||
1,
|
||||
|
@ -122,11 +122,11 @@ describe('calculateCspStatusCode for cspm', () => {
|
|||
});
|
||||
|
||||
it('Verify status when there are installed policies, healthy agents past findings but no recent findings', async () => {
|
||||
const statusCode = calculateCspStatusCode(
|
||||
const statusCode = calculateIntegrationStatus(
|
||||
CSPM_POLICY_TEMPLATE,
|
||||
{
|
||||
findingsLatest: 'empty',
|
||||
findings: 'not-empty',
|
||||
latest: 'empty',
|
||||
stream: 'not-empty',
|
||||
score: 'not-empty',
|
||||
},
|
||||
1,
|
||||
|
@ -138,13 +138,13 @@ describe('calculateCspStatusCode for cspm', () => {
|
|||
});
|
||||
});
|
||||
|
||||
describe('calculateCspStatusCode for vul_mgmt', () => {
|
||||
describe('calculateIntegrationStatus for vul_mgmt', () => {
|
||||
it('Verify status when there are no permission for vul_mgmt', async () => {
|
||||
const statusCode = calculateCspStatusCode(
|
||||
const statusCode = calculateIntegrationStatus(
|
||||
VULN_MGMT_POLICY_TEMPLATE,
|
||||
{
|
||||
findingsLatest: 'unprivileged',
|
||||
findings: 'unprivileged',
|
||||
latest: 'unprivileged',
|
||||
stream: 'unprivileged',
|
||||
score: 'unprivileged',
|
||||
},
|
||||
1,
|
||||
|
@ -156,11 +156,11 @@ describe('calculateCspStatusCode for vul_mgmt', () => {
|
|||
});
|
||||
|
||||
it('Verify status when there are no vul_mgmt findings, no healthy agents and no installed policy templates', async () => {
|
||||
const statusCode = calculateCspStatusCode(
|
||||
const statusCode = calculateIntegrationStatus(
|
||||
VULN_MGMT_POLICY_TEMPLATE,
|
||||
{
|
||||
findingsLatest: 'empty',
|
||||
findings: 'empty',
|
||||
latest: 'empty',
|
||||
stream: 'empty',
|
||||
score: 'empty',
|
||||
},
|
||||
0,
|
||||
|
@ -172,11 +172,11 @@ describe('calculateCspStatusCode for vul_mgmt', () => {
|
|||
});
|
||||
|
||||
it('Verify status when there are vul_mgmt findings and installed policies but no healthy agents', async () => {
|
||||
const statusCode = calculateCspStatusCode(
|
||||
const statusCode = calculateIntegrationStatus(
|
||||
VULN_MGMT_POLICY_TEMPLATE,
|
||||
{
|
||||
findingsLatest: 'empty',
|
||||
findings: 'not-empty',
|
||||
latest: 'empty',
|
||||
stream: 'empty',
|
||||
score: 'not-empty',
|
||||
},
|
||||
0,
|
||||
|
@ -188,11 +188,11 @@ describe('calculateCspStatusCode for vul_mgmt', () => {
|
|||
});
|
||||
|
||||
it('Verify status when there are vul_mgmt findings ,installed policies and healthy agents', async () => {
|
||||
const statusCode = calculateCspStatusCode(
|
||||
const statusCode = calculateIntegrationStatus(
|
||||
VULN_MGMT_POLICY_TEMPLATE,
|
||||
{
|
||||
findingsLatest: 'not-empty',
|
||||
findings: 'not-empty',
|
||||
latest: 'not-empty',
|
||||
stream: 'not-empty',
|
||||
score: 'not-empty',
|
||||
},
|
||||
1,
|
||||
|
@ -204,11 +204,11 @@ describe('calculateCspStatusCode for vul_mgmt', () => {
|
|||
});
|
||||
|
||||
it('Verify status when there are no vul_mgmt findings ,installed policies and no healthy agents', async () => {
|
||||
const statusCode = calculateCspStatusCode(
|
||||
const statusCode = calculateIntegrationStatus(
|
||||
VULN_MGMT_POLICY_TEMPLATE,
|
||||
{
|
||||
findingsLatest: 'empty',
|
||||
findings: 'empty',
|
||||
latest: 'empty',
|
||||
stream: 'empty',
|
||||
score: 'empty',
|
||||
},
|
||||
0,
|
||||
|
@ -220,11 +220,11 @@ describe('calculateCspStatusCode for vul_mgmt', () => {
|
|||
});
|
||||
|
||||
it('Verify status when there are installed policies, healthy agents and no vul_mgmt findings', async () => {
|
||||
const statusCode = calculateCspStatusCode(
|
||||
const statusCode = calculateIntegrationStatus(
|
||||
VULN_MGMT_POLICY_TEMPLATE,
|
||||
{
|
||||
findingsLatest: 'empty',
|
||||
findings: 'empty',
|
||||
latest: 'empty',
|
||||
stream: 'empty',
|
||||
score: 'empty',
|
||||
},
|
||||
1,
|
||||
|
@ -236,11 +236,11 @@ describe('calculateCspStatusCode for vul_mgmt', () => {
|
|||
});
|
||||
|
||||
it('Verify status when there are installed policies, healthy agents and no vul_mgmt findings and been more than 10 minutes', async () => {
|
||||
const statusCode = calculateCspStatusCode(
|
||||
const statusCode = calculateIntegrationStatus(
|
||||
VULN_MGMT_POLICY_TEMPLATE,
|
||||
{
|
||||
findingsLatest: 'empty',
|
||||
findings: 'empty',
|
||||
latest: 'empty',
|
||||
stream: 'empty',
|
||||
score: 'empty',
|
||||
},
|
||||
1,
|
||||
|
@ -252,11 +252,11 @@ describe('calculateCspStatusCode for vul_mgmt', () => {
|
|||
});
|
||||
|
||||
it('Verify status when there are installed policies, healthy agents past vul_mgmt findings but no recent findings', async () => {
|
||||
const statusCode = calculateCspStatusCode(
|
||||
const statusCode = calculateIntegrationStatus(
|
||||
VULN_MGMT_POLICY_TEMPLATE,
|
||||
{
|
||||
findingsLatest: 'empty',
|
||||
findings: 'not-empty',
|
||||
latest: 'empty',
|
||||
stream: 'not-empty',
|
||||
score: 'not-empty',
|
||||
},
|
||||
1,
|
||||
|
|
|
@ -87,11 +87,11 @@ const getHealthyAgents = async (
|
|||
);
|
||||
};
|
||||
|
||||
export const calculateCspStatusCode = (
|
||||
postureType: PostureTypes,
|
||||
export const calculateIntegrationStatus = (
|
||||
integration: PostureTypes,
|
||||
indicesStatus: {
|
||||
findingsLatest: IndexStatus;
|
||||
findings: IndexStatus;
|
||||
latest: IndexStatus;
|
||||
stream: IndexStatus;
|
||||
score?: IndexStatus;
|
||||
},
|
||||
healthyAgents: number,
|
||||
|
@ -99,27 +99,22 @@ export const calculateCspStatusCode = (
|
|||
installedPolicyTemplates: string[]
|
||||
): CspStatusCode => {
|
||||
// We check privileges only for the relevant indices for our pages to appear
|
||||
const postureTypeCheck: PostureTypes = POSTURE_TYPES[postureType];
|
||||
if (indicesStatus.findingsLatest === 'unprivileged' || indicesStatus.score === 'unprivileged')
|
||||
const postureTypeCheck: PostureTypes = POSTURE_TYPES[integration];
|
||||
if (indicesStatus.latest === 'unprivileged' || indicesStatus.score === 'unprivileged')
|
||||
return 'unprivileged';
|
||||
if (indicesStatus.latest === 'not-empty') return 'indexed';
|
||||
if (indicesStatus.stream === 'not-empty' && indicesStatus.latest === 'empty') return 'indexing';
|
||||
|
||||
if (!installedPolicyTemplates.includes(postureTypeCheck)) return 'not-installed';
|
||||
if (healthyAgents === 0) return 'not-deployed';
|
||||
if (
|
||||
indicesStatus.findingsLatest === 'empty' &&
|
||||
indicesStatus.findings === 'empty' &&
|
||||
indicesStatus.latest === 'empty' &&
|
||||
indicesStatus.stream === 'empty' &&
|
||||
timeSinceInstallationInMinutes < INDEX_TIMEOUT_IN_MINUTES
|
||||
)
|
||||
return 'waiting_for_results';
|
||||
if (
|
||||
indicesStatus.findingsLatest === 'empty' &&
|
||||
indicesStatus.findings === 'empty' &&
|
||||
timeSinceInstallationInMinutes > INDEX_TIMEOUT_IN_MINUTES
|
||||
)
|
||||
return 'index-timeout';
|
||||
if (indicesStatus.findingsLatest === 'empty') return 'indexing';
|
||||
if (indicesStatus.findings === 'not-empty') return 'indexed';
|
||||
|
||||
throw new Error('Could not determine csp status');
|
||||
return 'index-timeout';
|
||||
};
|
||||
|
||||
const assertResponse = (resp: CspSetupStatus, logger: CspApiRequestHandlerContext['logger']) => {
|
||||
|
@ -261,11 +256,11 @@ export const getCspStatus = async ({
|
|||
},
|
||||
];
|
||||
|
||||
const statusCspm = calculateCspStatusCode(
|
||||
const statusCspm = calculateIntegrationStatus(
|
||||
CSPM_POLICY_TEMPLATE,
|
||||
{
|
||||
findingsLatest: findingsLatestIndexStatusCspm,
|
||||
findings: findingsIndexStatusCspm,
|
||||
latest: findingsLatestIndexStatusCspm,
|
||||
stream: findingsIndexStatusCspm,
|
||||
score: scoreIndexStatusCspm,
|
||||
},
|
||||
healthyAgentsCspm,
|
||||
|
@ -273,11 +268,11 @@ export const getCspStatus = async ({
|
|||
installedPolicyTemplates
|
||||
);
|
||||
|
||||
const statusKspm = calculateCspStatusCode(
|
||||
const statusKspm = calculateIntegrationStatus(
|
||||
KSPM_POLICY_TEMPLATE,
|
||||
{
|
||||
findingsLatest: findingsLatestIndexStatusKspm,
|
||||
findings: findingsIndexStatusKspm,
|
||||
latest: findingsLatestIndexStatusKspm,
|
||||
stream: findingsIndexStatusKspm,
|
||||
score: scoreIndexStatusKspm,
|
||||
},
|
||||
healthyAgentsKspm,
|
||||
|
@ -285,18 +280,18 @@ export const getCspStatus = async ({
|
|||
installedPolicyTemplates
|
||||
);
|
||||
|
||||
const statusVulnMgmt = calculateCspStatusCode(
|
||||
const statusVulnMgmt = calculateIntegrationStatus(
|
||||
VULN_MGMT_POLICY_TEMPLATE,
|
||||
{
|
||||
findingsLatest: vulnerabilitiesLatestIndexStatus,
|
||||
findings: vulnerabilitiesIndexStatus,
|
||||
latest: vulnerabilitiesLatestIndexStatus,
|
||||
stream: vulnerabilitiesIndexStatus,
|
||||
},
|
||||
healthyAgentsVulMgmt,
|
||||
calculateDiffFromNowInMinutes(installation?.install_started_at || MIN_DATE),
|
||||
installedPolicyTemplates
|
||||
);
|
||||
|
||||
const statusResponseInfo = getStatusResponse({
|
||||
const statusResponseInfo: CspSetupStatus = getStatusResponse({
|
||||
statusCspm,
|
||||
statusKspm,
|
||||
statusVulnMgmt,
|
||||
|
@ -311,9 +306,7 @@ export const getCspStatus = async ({
|
|||
isPluginInitialized: isPluginInitialized(),
|
||||
});
|
||||
|
||||
if ((statusCspm && statusKspm && statusVulnMgmt) === 'not-installed') return statusResponseInfo;
|
||||
|
||||
const response = {
|
||||
const response: CspSetupStatus = {
|
||||
...statusResponseInfo,
|
||||
installedPackageVersion: installation?.install_version,
|
||||
};
|
||||
|
|
|
@ -9,7 +9,8 @@ import expect from '@kbn/expect';
|
|||
import type { FtrProviderContext } from '../ftr_provider_context';
|
||||
|
||||
// Defined in CSP plugin
|
||||
const FINDINGS_INDEX = 'logs-cloud_security_posture.findings_latest-default';
|
||||
const FINDINGS_INDEX = 'logs-cloud_security_posture.findings-default';
|
||||
const FINDINGS_LATEST_INDEX = 'logs-cloud_security_posture.findings_latest-default';
|
||||
|
||||
export function FindingsPageProvider({ getService, getPageObjects }: FtrProviderContext) {
|
||||
const testSubjects = getService('testSubjects');
|
||||
|
@ -33,17 +34,48 @@ export function FindingsPageProvider({ getService, getPageObjects }: FtrProvider
|
|||
});
|
||||
|
||||
const index = {
|
||||
remove: () => es.indices.delete({ index: FINDINGS_INDEX, ignore_unavailable: true }),
|
||||
remove: () =>
|
||||
Promise.all([
|
||||
es.deleteByQuery({
|
||||
index: FINDINGS_INDEX,
|
||||
query: {
|
||||
match_all: {},
|
||||
},
|
||||
ignore_unavailable: true,
|
||||
refresh: true,
|
||||
}),
|
||||
es.deleteByQuery({
|
||||
index: FINDINGS_LATEST_INDEX,
|
||||
query: {
|
||||
match_all: {},
|
||||
},
|
||||
ignore_unavailable: true,
|
||||
refresh: true,
|
||||
}),
|
||||
]),
|
||||
add: async <T>(findingsMock: T[]) => {
|
||||
await waitForPluginInitialized();
|
||||
await Promise.all(
|
||||
findingsMock.map((finding) =>
|
||||
await Promise.all([
|
||||
...findingsMock.map((finding) =>
|
||||
es.index({
|
||||
index: FINDINGS_INDEX,
|
||||
body: finding,
|
||||
body: {
|
||||
...finding,
|
||||
'@timestamp': new Date().toISOString(),
|
||||
},
|
||||
refresh: true,
|
||||
})
|
||||
)
|
||||
);
|
||||
),
|
||||
...findingsMock.map((finding) =>
|
||||
es.index({
|
||||
index: FINDINGS_LATEST_INDEX,
|
||||
body: {
|
||||
...finding,
|
||||
'@timestamp': new Date().toISOString(),
|
||||
},
|
||||
refresh: true,
|
||||
})
|
||||
),
|
||||
]);
|
||||
},
|
||||
};
|
||||
|
||||
|
@ -183,6 +215,7 @@ export function FindingsPageProvider({ getService, getPageObjects }: FtrProvider
|
|||
resourceFindingsTable,
|
||||
findingsByResourceTable,
|
||||
index,
|
||||
waitForPluginInitialized,
|
||||
distributionBar,
|
||||
};
|
||||
}
|
||||
|
|
|
@ -109,8 +109,13 @@ export default function ({ getPageObjects, getService }: FtrProviderContext) {
|
|||
resourceFindingsTable = findings.resourceFindingsTable;
|
||||
distributionBar = findings.distributionBar;
|
||||
|
||||
// Before we start any test we must wait for cloud_security_posture plugin to complete its initialization
|
||||
await findings.waitForPluginInitialized();
|
||||
|
||||
// Prepare mocked findings
|
||||
await findings.index.remove();
|
||||
await findings.index.add(data);
|
||||
|
||||
await findings.navigateToLatestFindingsPage();
|
||||
await retry.waitFor(
|
||||
'Findings table to be loaded',
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue