diff --git a/x-pack/plugins/cloud_security_posture/common/types_old.ts b/x-pack/plugins/cloud_security_posture/common/types_old.ts
index f77ac4678a526..19e18902b7ea1 100644
--- a/x-pack/plugins/cloud_security_posture/common/types_old.ts
+++ b/x-pack/plugins/cloud_security_posture/common/types_old.ts
@@ -133,6 +133,7 @@ export interface BaseCspSetupStatus {
vuln_mgmt: BaseCspSetupBothPolicy;
isPluginInitialized: boolean;
installedPackageVersion?: string | undefined;
+ hasMisconfigurationsFindings?: boolean;
}
export type CspSetupStatus = BaseCspSetupStatus;
diff --git a/x-pack/plugins/cloud_security_posture/public/common/hooks/use_subscription_status.ts b/x-pack/plugins/cloud_security_posture/public/common/hooks/use_is_subscription_status_valid.ts
similarity index 94%
rename from x-pack/plugins/cloud_security_posture/public/common/hooks/use_subscription_status.ts
rename to x-pack/plugins/cloud_security_posture/public/common/hooks/use_is_subscription_status_valid.ts
index f8bda84dbcb65..99ded40b04f63 100644
--- a/x-pack/plugins/cloud_security_posture/public/common/hooks/use_subscription_status.ts
+++ b/x-pack/plugins/cloud_security_posture/public/common/hooks/use_is_subscription_status_valid.ts
@@ -12,9 +12,10 @@ import { useKibana } from './use_kibana';
const SUBSCRIPTION_QUERY_KEY = 'csp_subscription_query_key';
-export const useSubscriptionStatus = () => {
+export const useIsSubscriptionStatusValid = () => {
const { licensing } = useKibana().services;
const { isCloudEnabled } = useContext(SetupContext);
+
return useQuery([SUBSCRIPTION_QUERY_KEY], async () => {
const license = await licensing.refresh();
return isSubscriptionAllowed(isCloudEnabled, license);
diff --git a/x-pack/plugins/cloud_security_posture/public/components/cloud_posture_page.test.tsx b/x-pack/plugins/cloud_security_posture/public/components/cloud_posture_page.test.tsx
index 05fedd4d8adec..22fc6b4ae65f8 100644
--- a/x-pack/plugins/cloud_security_posture/public/components/cloud_posture_page.test.tsx
+++ b/x-pack/plugins/cloud_security_posture/public/components/cloud_posture_page.test.tsx
@@ -5,7 +5,6 @@
* 2.0.
*/
-import { useSubscriptionStatus } from '../common/hooks/use_subscription_status';
import Chance from 'chance';
import {
DEFAULT_NO_DATA_TEST_SUBJECT,
@@ -13,7 +12,6 @@ import {
isCommonError,
LOADING_STATE_TEST_SUBJECT,
PACKAGE_NOT_INSTALLED_TEST_SUBJECT,
- SUBSCRIPTION_NOT_ALLOWED_TEST_SUBJECT,
} from './cloud_posture_page';
import { createReactQueryResponse } from '../test/fixtures/react_query';
import { TestProvider } from '../test/test_provider';
@@ -23,27 +21,17 @@ 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 { useLicenseManagementLocatorApi } from '../common/api/use_license_management_locator_api';
const chance = new Chance();
jest.mock('../common/api/use_setup_status_api');
jest.mock('../common/api/use_license_management_locator_api');
-jest.mock('../common/hooks/use_subscription_status');
+jest.mock('../common/hooks/use_is_subscription_status_valid');
jest.mock('../common/navigation/use_csp_integration_link');
describe('', () => {
beforeEach(() => {
jest.resetAllMocks();
-
- (useSubscriptionStatus as jest.Mock).mockImplementation(() =>
- createReactQueryResponse({
- status: 'success',
- data: true,
- })
- );
-
- (useLicenseManagementLocatorApi as jest.Mock).mockImplementation(undefined);
});
const renderCloudPosturePage = (
@@ -72,101 +60,16 @@ describe('', () => {
);
};
- it('renders with license url locator', () => {
- (useSubscriptionStatus as jest.Mock).mockImplementation(() =>
- createReactQueryResponse({
- status: 'success',
- data: false,
- })
- );
-
- (useLicenseManagementLocatorApi as jest.Mock).mockImplementation(() => 'http://license-url');
-
- renderCloudPosturePage();
-
- expect(screen.getByTestId('has_locator')).toBeInTheDocument();
- });
-
- it('renders no license url locator', () => {
- (useSubscriptionStatus as jest.Mock).mockImplementation(() =>
- createReactQueryResponse({
- status: 'success',
- data: false,
- })
- );
-
- (useLicenseManagementLocatorApi as jest.Mock).mockImplementation(undefined);
-
- renderCloudPosturePage();
- expect(screen.getByTestId('no_locator')).toBeInTheDocument();
- });
-
it('renders children if setup status is indexed', () => {
const children = chance.sentence();
renderCloudPosturePage({ children });
expect(screen.getByText(children)).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();
expect(screen.queryByTestId(PACKAGE_NOT_INSTALLED_TEST_SUBJECT)).not.toBeInTheDocument();
});
- it('renders default loading state when the subscription query is loading', () => {
- (useSubscriptionStatus 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 subscription query has an error', () => {
- (useSubscriptionStatus 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 subscription not allowed prompt if subscription is not installed', () => {
- (useSubscriptionStatus as jest.Mock).mockImplementation(() =>
- createReactQueryResponse({
- status: 'success',
- data: false,
- })
- );
-
- const children = chance.sentence();
- renderCloudPosturePage({ children });
-
- expect(screen.queryByTestId(PACKAGE_NOT_INSTALLED_TEST_SUBJECT)).not.toBeInTheDocument();
- expect(screen.queryByText(children)).not.toBeInTheDocument();
- expect(screen.queryByTestId(LOADING_STATE_TEST_SUBJECT)).not.toBeInTheDocument();
- expect(screen.getByTestId(SUBSCRIPTION_NOT_ALLOWED_TEST_SUBJECT)).toBeInTheDocument();
- expect(screen.queryByTestId(ERROR_STATE_TEST_SUBJECT)).not.toBeInTheDocument();
- });
-
it('renders default loading text when query isLoading', () => {
const query = createReactQueryResponse({
status: 'loading',
@@ -176,7 +79,6 @@ describe('', () => {
renderCloudPosturePage({ children, query });
expect(screen.getByTestId(LOADING_STATE_TEST_SUBJECT)).toBeInTheDocument();
- expect(screen.queryByTestId(SUBSCRIPTION_NOT_ALLOWED_TEST_SUBJECT)).not.toBeInTheDocument();
expect(screen.queryByText(children)).not.toBeInTheDocument();
expect(screen.queryByTestId(ERROR_STATE_TEST_SUBJECT)).not.toBeInTheDocument();
expect(screen.queryByTestId(PACKAGE_NOT_INSTALLED_TEST_SUBJECT)).not.toBeInTheDocument();
@@ -191,7 +93,6 @@ describe('', () => {
renderCloudPosturePage({ children, query });
expect(screen.getByTestId(LOADING_STATE_TEST_SUBJECT)).toBeInTheDocument();
- expect(screen.queryByTestId(SUBSCRIPTION_NOT_ALLOWED_TEST_SUBJECT)).not.toBeInTheDocument();
expect(screen.queryByText(children)).not.toBeInTheDocument();
expect(screen.queryByTestId(ERROR_STATE_TEST_SUBJECT)).not.toBeInTheDocument();
expect(screen.queryByTestId(PACKAGE_NOT_INSTALLED_TEST_SUBJECT)).not.toBeInTheDocument();
@@ -220,7 +121,6 @@ describe('', () => {
expect(screen.getByText(text, { exact: false })).toBeInTheDocument()
);
expect(screen.getByTestId(ERROR_STATE_TEST_SUBJECT)).toBeInTheDocument();
- expect(screen.queryByTestId(SUBSCRIPTION_NOT_ALLOWED_TEST_SUBJECT)).not.toBeInTheDocument();
expect(screen.queryByTestId(LOADING_STATE_TEST_SUBJECT)).not.toBeInTheDocument();
expect(screen.queryByText(children)).not.toBeInTheDocument();
expect(screen.queryByTestId(PACKAGE_NOT_INSTALLED_TEST_SUBJECT)).not.toBeInTheDocument();
@@ -253,7 +153,6 @@ describe('', () => {
[error, statusCode].forEach((text) => expect(screen.queryByText(text)).not.toBeInTheDocument());
expect(screen.queryByTestId(ERROR_STATE_TEST_SUBJECT)).not.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();
});
@@ -275,7 +174,6 @@ describe('', () => {
expect(screen.getByText(loading)).toBeInTheDocument();
expect(screen.queryByTestId(ERROR_STATE_TEST_SUBJECT)).not.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();
});
@@ -291,7 +189,6 @@ describe('', () => {
expect(screen.getByTestId(DEFAULT_NO_DATA_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(ERROR_STATE_TEST_SUBJECT)).not.toBeInTheDocument();
expect(screen.queryByTestId(PACKAGE_NOT_INSTALLED_TEST_SUBJECT)).not.toBeInTheDocument();
@@ -320,7 +217,6 @@ describe('', () => {
expect(screen.getByText(pageTitle)).toBeInTheDocument();
expect(screen.getAllByText(solution, { exact: false })[0]).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(ERROR_STATE_TEST_SUBJECT)).not.toBeInTheDocument();
expect(screen.queryByTestId(PACKAGE_NOT_INSTALLED_TEST_SUBJECT)).not.toBeInTheDocument();
diff --git a/x-pack/plugins/cloud_security_posture/public/components/cloud_posture_page.tsx b/x-pack/plugins/cloud_security_posture/public/components/cloud_posture_page.tsx
index b2f1d892da907..34ea821ed2620 100644
--- a/x-pack/plugins/cloud_security_posture/public/components/cloud_posture_page.tsx
+++ b/x-pack/plugins/cloud_security_posture/public/components/cloud_posture_page.tsx
@@ -11,8 +11,6 @@ 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 { SubscriptionNotAllowed } from './subscription_not_allowed';
-import { useSubscriptionStatus } from '../common/hooks/use_subscription_status';
import { FullSizeCenteredPage } from './full_size_centered_page';
import { CspLoadingState } from './csp_loading_state';
@@ -22,7 +20,6 @@ export const PACKAGE_NOT_INSTALLED_TEST_SUBJECT = 'cloud_posture_page_package_no
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 DEFAULT_NO_DATA_TEST_SUBJECT = 'cloud_posture_page_no_data';
-export const SUBSCRIPTION_NOT_ALLOWED_TEST_SUBJECT = 'cloud_posture_page_subscription_not_allowed';
interface CommonError {
body: {
@@ -150,12 +147,6 @@ export const defaultNoDataRenderer = () => (
);
-const subscriptionNotAllowedRenderer = () => (
-
-
-
-);
-
interface CloudPosturePageProps {
children: React.ReactNode;
query?: UseQueryResult;
@@ -171,21 +162,7 @@ export const CloudPosturePage = ({
errorRender = defaultErrorRenderer,
noDataRenderer = defaultNoDataRenderer,
}: CloudPosturePageProps) => {
- const subscriptionStatus = useSubscriptionStatus();
-
const render = () => {
- if (subscriptionStatus.isError) {
- return defaultErrorRenderer(subscriptionStatus.error);
- }
-
- if (subscriptionStatus.isLoading) {
- return defaultLoadingRenderer();
- }
-
- if (!subscriptionStatus.data) {
- return subscriptionNotAllowedRenderer();
- }
-
if (!query) {
return children;
}
diff --git a/x-pack/plugins/cloud_security_posture/public/components/fleet_extensions/policy_template_form.test.tsx b/x-pack/plugins/cloud_security_posture/public/components/fleet_extensions/policy_template_form.test.tsx
index 116170b2ac29c..76134c4d41df0 100644
--- a/x-pack/plugins/cloud_security_posture/public/components/fleet_extensions/policy_template_form.test.tsx
+++ b/x-pack/plugins/cloud_security_posture/public/components/fleet_extensions/policy_template_form.test.tsx
@@ -5,7 +5,7 @@
* 2.0.
*/
import React from 'react';
-import { render, waitFor, within } from '@testing-library/react';
+import { render, screen, waitFor, within } from '@testing-library/react';
import userEvent from '@testing-library/user-event';
import {
CspPolicyTemplateForm,
@@ -53,9 +53,12 @@ import {
GCP_CREDENTIALS_TYPE_OPTIONS_TEST_SUBJ,
SETUP_TECHNOLOGY_SELECTOR_ACCORDION_TEST_SUBJ,
SETUP_TECHNOLOGY_SELECTOR_TEST_SUBJ,
+ SUBSCRIPTION_NOT_ALLOWED_TEST_SUBJECT,
} from '../test_subjects';
import { ExperimentalFeaturesService } from '@kbn/fleet-plugin/public/services';
import { createFleetTestRendererMock } from '@kbn/fleet-plugin/public/mock';
+import { useIsSubscriptionStatusValid } from '../../common/hooks/use_is_subscription_status_valid';
+import { useLicenseManagementLocatorApi } from '../../common/api/use_license_management_locator_api';
// mock useParams
jest.mock('react-router-dom', () => ({
@@ -66,6 +69,8 @@ jest.mock('react-router-dom', () => ({
}));
jest.mock('../../common/api/use_setup_status_api');
jest.mock('../../common/api/use_package_policy_list');
+jest.mock('../../common/hooks/use_is_subscription_status_valid');
+jest.mock('../../common/api/use_license_management_locator_api');
jest.mock('@kbn/fleet-plugin/public/services/experimental_features');
const onChange = jest.fn();
@@ -85,9 +90,11 @@ describe('', () => {
(useParams as jest.Mock).mockReturnValue({
integration: undefined,
});
+
mockedExperimentalFeaturesService.get.mockReturnValue({
secretsStorage: true,
} as any);
+
(usePackagePolicyList as jest.Mock).mockImplementation((packageName) =>
createReactQueryResponseWithRefetch({
status: 'success',
@@ -96,13 +103,22 @@ describe('', () => {
},
})
);
+
onChange.mockClear();
+
(useCspSetupStatusApi as jest.Mock).mockImplementation(() =>
createReactQueryResponseWithRefetch({
status: 'success',
data: { status: 'indexed', installedPackageVersion: '1.2.13' },
})
);
+
+ (useIsSubscriptionStatusValid as jest.Mock).mockImplementation(() =>
+ createReactQueryResponse({
+ status: 'success',
+ data: true,
+ })
+ );
});
const WrappedComponent = ({
@@ -145,6 +161,53 @@ describe('', () => {
);
};
+ it('shows license block if subscription is not allowed', () => {
+ (useIsSubscriptionStatusValid as jest.Mock).mockImplementation(() =>
+ createReactQueryResponse({
+ status: 'success',
+ data: false,
+ })
+ );
+
+ const policy = getMockPolicyK8s();
+ const { rerender } = render();
+
+ rerender();
+ expect(screen.getByTestId(SUBSCRIPTION_NOT_ALLOWED_TEST_SUBJECT)).toBeInTheDocument();
+ });
+
+ it('license block renders with license url locator', () => {
+ (useIsSubscriptionStatusValid as jest.Mock).mockImplementation(() =>
+ createReactQueryResponse({
+ status: 'success',
+ data: false,
+ })
+ );
+ (useLicenseManagementLocatorApi as jest.Mock).mockImplementation(() => 'http://license-url');
+
+ const policy = getMockPolicyK8s();
+ const { rerender } = render();
+
+ rerender();
+ expect(screen.getByTestId('has_locator')).toBeInTheDocument();
+ });
+
+ it('license block renders without license url locator', () => {
+ (useIsSubscriptionStatusValid as jest.Mock).mockImplementation(() =>
+ createReactQueryResponse({
+ status: 'success',
+ data: false,
+ })
+ );
+ (useLicenseManagementLocatorApi as jest.Mock).mockImplementation(undefined);
+
+ const policy = getMockPolicyK8s();
+ const { rerender } = render();
+
+ rerender();
+ expect(screen.getByTestId('no_locator')).toBeInTheDocument();
+ });
+
it('updates package policy namespace to default when it changes', () => {
const policy = getMockPolicyK8s();
const { rerender } = render();
diff --git a/x-pack/plugins/cloud_security_posture/public/components/fleet_extensions/policy_template_form.tsx b/x-pack/plugins/cloud_security_posture/public/components/fleet_extensions/policy_template_form.tsx
index 6a7d73868ced6..f70fb3a18f690 100644
--- a/x-pack/plugins/cloud_security_posture/public/components/fleet_extensions/policy_template_form.tsx
+++ b/x-pack/plugins/cloud_security_posture/public/components/fleet_extensions/policy_template_form.tsx
@@ -30,6 +30,8 @@ import type {
import { PackageInfo, PackagePolicy } from '@kbn/fleet-plugin/common';
import { useParams } from 'react-router-dom';
import { i18n } from '@kbn/i18n';
+import { useIsSubscriptionStatusValid } from '../../common/hooks/use_is_subscription_status_valid';
+import { SubscriptionNotAllowed } from '../subscription_not_allowed';
import { CspRadioGroupProps, RadioGroup } from './csp_boxed_radio_group';
import { assert } from '../../../common/utils/helpers';
import type { CloudSecurityPolicyTemplate, PostureInput } from '../../../common/types_old';
@@ -67,6 +69,7 @@ import { SetupTechnologySelector } from './setup_technology_selector/setup_techn
import { useSetupTechnology } from './setup_technology_selector/use_setup_technology';
import { AZURE_CREDENTIALS_TYPE } from './azure_credentials_form/azure_credentials_form';
import { AWS_CREDENTIALS_TYPE } from './aws_credentials_form/aws_credentials_form';
+import { useKibana } from '../../common/hooks/use_kibana';
const DEFAULT_INPUT_TYPE = {
kspm: CLOUDBEAT_VANILLA,
@@ -537,6 +540,125 @@ const IntegrationSettings = ({ onChange, fields }: IntegrationInfoFieldsProps) =
);
+const useEnsureDefaultNamespace = ({
+ newPolicy,
+ input,
+ updatePolicy,
+}: {
+ newPolicy: NewPackagePolicy;
+ input: NewPackagePolicyPostureInput;
+ updatePolicy: (policy: NewPackagePolicy) => void;
+}) => {
+ useEffect(() => {
+ if (newPolicy.namespace === POSTURE_NAMESPACE) return;
+
+ const policy = { ...getPosturePolicy(newPolicy, input.type), namespace: POSTURE_NAMESPACE };
+ updatePolicy(policy);
+ }, [newPolicy, input, updatePolicy]);
+};
+
+const usePolicyTemplateInitialName = ({
+ isEditPage,
+ isLoading,
+ integration,
+ newPolicy,
+ packagePolicyList,
+ updatePolicy,
+ setCanFetchIntegration,
+}: {
+ isEditPage: boolean;
+ isLoading: boolean;
+ integration: CloudSecurityPolicyTemplate | undefined;
+ newPolicy: NewPackagePolicy;
+ packagePolicyList: PackagePolicy[] | undefined;
+ updatePolicy: (policy: NewPackagePolicy) => void;
+ setCanFetchIntegration: (canFetch: boolean) => void;
+}) => {
+ useEffect(() => {
+ if (!integration) return;
+ if (isEditPage) return;
+ if (isLoading) return;
+
+ const packagePolicyListByIntegration = packagePolicyList?.filter(
+ (policy) => policy?.vars?.posture?.value === integration
+ );
+
+ const currentIntegrationName = getMaxPackageName(integration, packagePolicyListByIntegration);
+
+ if (newPolicy.name === currentIntegrationName) {
+ return;
+ }
+
+ updatePolicy({
+ ...newPolicy,
+ name: currentIntegrationName,
+ });
+ setCanFetchIntegration(false);
+ // since this useEffect should only run on initial mount updatePolicy and newPolicy shouldn't re-trigger it
+ // eslint-disable-next-line react-hooks/exhaustive-deps
+ }, [isLoading, integration, isEditPage, packagePolicyList]);
+};
+
+const getSelectedOption = (
+ options: NewPackagePolicyInput[],
+ policyTemplate: string = CSPM_POLICY_TEMPLATE
+) => {
+ // Looks for the enabled deployment (aka input). By default, all inputs are disabled.
+ // Initial state when all inputs are disabled is to choose the first available of the relevant policyTemplate
+ // Default selected policy template is CSPM
+ const selectedOption =
+ options.find((i) => i.enabled) ||
+ options.find((i) => i.policy_template === policyTemplate) ||
+ options[0];
+
+ assert(selectedOption, 'Failed to determine selected option'); // We can't provide a default input without knowing the policy template
+ assert(isPostureInput(selectedOption), 'Unknown option: ' + selectedOption.type);
+
+ return selectedOption;
+};
+
+/**
+ * Update CloudFormation template and stack name in the Agent Policy
+ * based on the selected policy template
+ */
+const useCloudFormationTemplate = ({
+ packageInfo,
+ newPolicy,
+ updatePolicy,
+}: {
+ packageInfo: PackageInfo;
+ newPolicy: NewPackagePolicy;
+ updatePolicy: (policy: NewPackagePolicy) => void;
+}) => {
+ useEffect(() => {
+ const templateUrl = getVulnMgmtCloudFormationDefaultValue(packageInfo);
+
+ // If the template is not available, do not update the policy
+ if (templateUrl === '') return;
+
+ const checkCurrentTemplate = newPolicy?.inputs?.find(
+ (i: any) => i.type === CLOUDBEAT_VULN_MGMT_AWS
+ )?.config?.cloud_formation_template_url?.value;
+
+ // If the template is already set, do not update the policy
+ if (checkCurrentTemplate === templateUrl) return;
+
+ updatePolicy?.({
+ ...newPolicy,
+ inputs: newPolicy.inputs.map((input) => {
+ if (input.type === CLOUDBEAT_VULN_MGMT_AWS) {
+ return {
+ ...input,
+ config: { cloud_formation_template_url: { value: templateUrl } },
+ };
+ }
+ return input;
+ }),
+ });
+ // eslint-disable-next-line react-hooks/exhaustive-deps
+ }, [newPolicy?.vars?.cloud_formation_template_url, newPolicy, packageInfo]);
+};
+
export const CspPolicyTemplateForm = memo(
({
newPolicy,
@@ -553,7 +675,11 @@ export const CspPolicyTemplateForm = memo {
+ if (!isServerless) {
+ setIsValid(isSubscriptionValid);
+ }
+ }, [isServerless, isSubscriptionValid]);
+
useEffect(() => {
if (isEditPage) return;
if (isLoading) return;
@@ -726,6 +859,10 @@ export const CspPolicyTemplateForm = memo;
+ }
+
return (
<>
{isEditPage && }
@@ -857,122 +994,3 @@ CspPolicyTemplateForm.displayName = 'CspPolicyTemplateForm';
// eslint-disable-next-line import/no-default-export
export { CspPolicyTemplateForm as default };
-
-const useEnsureDefaultNamespace = ({
- newPolicy,
- input,
- updatePolicy,
-}: {
- newPolicy: NewPackagePolicy;
- input: NewPackagePolicyPostureInput;
- updatePolicy: (policy: NewPackagePolicy) => void;
-}) => {
- useEffect(() => {
- if (newPolicy.namespace === POSTURE_NAMESPACE) return;
-
- const policy = { ...getPosturePolicy(newPolicy, input.type), namespace: POSTURE_NAMESPACE };
- updatePolicy(policy);
- }, [newPolicy, input, updatePolicy]);
-};
-
-const usePolicyTemplateInitialName = ({
- isEditPage,
- isLoading,
- integration,
- newPolicy,
- packagePolicyList,
- updatePolicy,
- setCanFetchIntegration,
-}: {
- isEditPage: boolean;
- isLoading: boolean;
- integration: CloudSecurityPolicyTemplate | undefined;
- newPolicy: NewPackagePolicy;
- packagePolicyList: PackagePolicy[] | undefined;
- updatePolicy: (policy: NewPackagePolicy) => void;
- setCanFetchIntegration: (canFetch: boolean) => void;
-}) => {
- useEffect(() => {
- if (!integration) return;
- if (isEditPage) return;
- if (isLoading) return;
-
- const packagePolicyListByIntegration = packagePolicyList?.filter(
- (policy) => policy?.vars?.posture?.value === integration
- );
-
- const currentIntegrationName = getMaxPackageName(integration, packagePolicyListByIntegration);
-
- if (newPolicy.name === currentIntegrationName) {
- return;
- }
-
- updatePolicy({
- ...newPolicy,
- name: currentIntegrationName,
- });
- setCanFetchIntegration(false);
- // since this useEffect should only run on initial mount updatePolicy and newPolicy shouldn't re-trigger it
- // eslint-disable-next-line react-hooks/exhaustive-deps
- }, [isLoading, integration, isEditPage, packagePolicyList]);
-};
-
-const getSelectedOption = (
- options: NewPackagePolicyInput[],
- policyTemplate: string = CSPM_POLICY_TEMPLATE
-) => {
- // Looks for the enabled deployment (aka input). By default, all inputs are disabled.
- // Initial state when all inputs are disabled is to choose the first available of the relevant policyTemplate
- // Default selected policy template is CSPM
- const selectedOption =
- options.find((i) => i.enabled) ||
- options.find((i) => i.policy_template === policyTemplate) ||
- options[0];
-
- assert(selectedOption, 'Failed to determine selected option'); // We can't provide a default input without knowing the policy template
- assert(isPostureInput(selectedOption), 'Unknown option: ' + selectedOption.type);
-
- return selectedOption;
-};
-
-/**
- * Update CloudFormation template and stack name in the Agent Policy
- * based on the selected policy template
- */
-const useCloudFormationTemplate = ({
- packageInfo,
- newPolicy,
- updatePolicy,
-}: {
- packageInfo: PackageInfo;
- newPolicy: NewPackagePolicy;
- updatePolicy: (policy: NewPackagePolicy) => void;
-}) => {
- useEffect(() => {
- const templateUrl = getVulnMgmtCloudFormationDefaultValue(packageInfo);
-
- // If the template is not available, do not update the policy
- if (templateUrl === '') return;
-
- const checkCurrentTemplate = newPolicy?.inputs?.find(
- (i: any) => i.type === CLOUDBEAT_VULN_MGMT_AWS
- )?.config?.cloud_formation_template_url?.value;
-
- // If the template is already set, do not update the policy
- if (checkCurrentTemplate === templateUrl) return;
-
- updatePolicy?.({
- ...newPolicy,
- inputs: newPolicy.inputs.map((input) => {
- if (input.type === CLOUDBEAT_VULN_MGMT_AWS) {
- return {
- ...input,
- config: { cloud_formation_template_url: { value: templateUrl } },
- };
- }
- return input;
- }),
- });
- // eslint-disable-next-line react-hooks/exhaustive-deps
- }, [newPolicy?.vars?.cloud_formation_template_url, newPolicy, packageInfo]);
-};
diff --git a/x-pack/plugins/cloud_security_posture/public/components/subscription_not_allowed.tsx b/x-pack/plugins/cloud_security_posture/public/components/subscription_not_allowed.tsx
index e527a2f9f0c0c..79571a9a8dc2c 100644
--- a/x-pack/plugins/cloud_security_posture/public/components/subscription_not_allowed.tsx
+++ b/x-pack/plugins/cloud_security_posture/public/components/subscription_not_allowed.tsx
@@ -8,20 +8,25 @@
import React from 'react';
import { EuiEmptyPrompt, EuiLink, EuiPageSection } from '@elastic/eui';
import { FormattedMessage } from '@kbn/i18n-react';
+import { SUBSCRIPTION_NOT_ALLOWED_TEST_SUBJECT } from './test_subjects';
import { useLicenseManagementLocatorApi } from '../common/api/use_license_management_locator_api';
export const SubscriptionNotAllowed = () => {
const handleNavigateToLicenseManagement = useLicenseManagementLocatorApi();
return (
-
+
}
diff --git a/x-pack/plugins/cloud_security_posture/public/components/test_subjects.ts b/x-pack/plugins/cloud_security_posture/public/components/test_subjects.ts
index 4538b04520f84..ee40fe4fc894e 100644
--- a/x-pack/plugins/cloud_security_posture/public/components/test_subjects.ts
+++ b/x-pack/plugins/cloud_security_posture/public/components/test_subjects.ts
@@ -85,3 +85,5 @@ export const CIS_GCP_INPUT_FIELDS_TEST_SUBJECTS = {
CREDENTIALS_FILE: 'credentials_file_test_id',
CREDENTIALS_JSON: 'credentials_json_test_id',
};
+
+export const SUBSCRIPTION_NOT_ALLOWED_TEST_SUBJECT = 'cloud_posture_page_subscription_not_allowed';
diff --git a/x-pack/plugins/cloud_security_posture/public/pages/benchmarks/benchmarks.test.tsx b/x-pack/plugins/cloud_security_posture/public/pages/benchmarks/benchmarks.test.tsx
index e5fed03fc9676..f625470fb08c5 100644
--- a/x-pack/plugins/cloud_security_posture/public/pages/benchmarks/benchmarks.test.tsx
+++ b/x-pack/plugins/cloud_security_posture/public/pages/benchmarks/benchmarks.test.tsx
@@ -15,7 +15,6 @@ import { Benchmarks } from './benchmarks';
import * as TEST_SUBJ from './test_subjects';
import { useCspBenchmarkIntegrationsV2 } from './use_csp_benchmark_integrations';
import { useCspSetupStatusApi } from '../../common/api/use_setup_status_api';
-import { useSubscriptionStatus } from '../../common/hooks/use_subscription_status';
import { useCspIntegrationLink } from '../../common/navigation/use_csp_integration_link';
import { ERROR_STATE_TEST_SUBJECT } from './benchmarks_table';
import { useLicenseManagementLocatorApi } from '../../common/api/use_license_management_locator_api';
@@ -23,7 +22,7 @@ import { useLicenseManagementLocatorApi } from '../../common/api/use_license_man
jest.mock('./use_csp_benchmark_integrations');
jest.mock('../../common/api/use_setup_status_api');
jest.mock('../../common/api/use_license_management_locator_api');
-jest.mock('../../common/hooks/use_subscription_status');
+jest.mock('../../common/hooks/use_is_subscription_status_valid');
jest.mock('../../common/navigation/use_csp_integration_link');
const chance = new Chance();
@@ -45,13 +44,6 @@ describe('', () => {
})
);
- (useSubscriptionStatus as jest.Mock).mockImplementation(() =>
- createReactQueryResponse({
- status: 'success',
- data: true,
- })
- );
-
(useLicenseManagementLocatorApi as jest.Mock).mockImplementation(() =>
createReactQueryResponse({
status: 'success',
diff --git a/x-pack/plugins/cloud_security_posture/public/pages/compliance_dashboard/compliance_dashboard.test.tsx b/x-pack/plugins/cloud_security_posture/public/pages/compliance_dashboard/compliance_dashboard.test.tsx
index b3ed9cff8dc09..915e135967def 100644
--- a/x-pack/plugins/cloud_security_posture/public/pages/compliance_dashboard/compliance_dashboard.test.tsx
+++ b/x-pack/plugins/cloud_security_posture/public/pages/compliance_dashboard/compliance_dashboard.test.tsx
@@ -13,7 +13,6 @@ import { TestProvider } from '../../test/test_provider';
import { ComplianceDashboard, getDefaultTab } from '.';
import { useCspSetupStatusApi } from '../../common/api/use_setup_status_api';
import { useLicenseManagementLocatorApi } from '../../common/api/use_license_management_locator_api';
-import { useSubscriptionStatus } from '../../common/hooks/use_subscription_status';
import { useKspmStatsApi, useCspmStatsApi } from '../../common/api/use_stats_api';
import {
CLOUD_DASHBOARD_CONTAINER,
@@ -43,7 +42,7 @@ import { MemoryRouter } from 'react-router-dom';
jest.mock('../../common/api/use_setup_status_api');
jest.mock('../../common/api/use_stats_api');
jest.mock('../../common/api/use_license_management_locator_api');
-jest.mock('../../common/hooks/use_subscription_status');
+jest.mock('../../common/hooks/use_is_subscription_status_valid');
jest.mock('../../common/navigation/use_navigate_to_cis_integration_policies');
jest.mock('../../common/navigation/use_csp_integration_link');
@@ -58,18 +57,12 @@ describe('', () => {
})
);
- (useSubscriptionStatus as jest.Mock).mockImplementation(() =>
- createReactQueryResponse({
- status: 'success',
- data: true,
- })
- );
-
(useCspmStatsApi as jest.Mock).mockImplementation(() =>
createReactQueryResponse({
status: 'success',
})
);
+
(useKspmStatsApi as jest.Mock).mockImplementation(() =>
createReactQueryResponse({
status: 'success',
diff --git a/x-pack/plugins/cloud_security_posture/public/pages/configurations/configurations.test.tsx b/x-pack/plugins/cloud_security_posture/public/pages/configurations/configurations.test.tsx
index 9ff1b40d49c70..8b1d54b5a1798 100644
--- a/x-pack/plugins/cloud_security_posture/public/pages/configurations/configurations.test.tsx
+++ b/x-pack/plugins/cloud_security_posture/public/pages/configurations/configurations.test.tsx
@@ -44,7 +44,7 @@ describe('', () => {
server.use(rulesGetStatesHandler);
});
- it('renders integrations installation prompt if integration is not installed', async () => {
+ it('renders integrations installation prompt if integration is not installed and there are no findings', async () => {
server.use(statusHandlers.notInstalledHandler);
renderFindingsPage();
@@ -53,6 +53,37 @@ describe('', () => {
expect(screen.getByText(/add kspm integration/i)).toBeInTheDocument();
});
+ it("renders the 'latest misconfigurations findings' DataTable component when the CSPM/KSPM integration status is not installed but there are findings", async () => {
+ const finding1 = generateCspFinding('0003', 'failed');
+ const finding2 = generateCspFinding('0004', 'passed');
+
+ server.use(statusHandlers.notInstalledHasMisconfigurationsFindingsHandler);
+ server.use(bsearchFindingsHandler([finding1, finding2]));
+ renderFindingsPage();
+
+ // Loading while checking the status API and fetching the findings
+ expect(screen.getByText(/loading/i)).toBeInTheDocument();
+
+ await waitFor(() => expect(screen.getByText(/2 findings/i)).toBeInTheDocument());
+
+ const fieldsToCheck = [
+ finding1.resource.name,
+ finding1.resource.id,
+ finding1.rule.benchmark.rule_number as string,
+ finding1.rule.name,
+ finding1.rule.section,
+ finding2.resource.name,
+ finding2.resource.id,
+ finding2.rule.benchmark.rule_number as string,
+ finding2.rule.name,
+ finding2.rule.section,
+ ];
+
+ fieldsToCheck.forEach((fieldValue) => {
+ expect(screen.getByText(fieldValue)).toBeInTheDocument();
+ });
+ });
+
it("renders the 'latest findings' DataTable component when the CSPM/KSPM integration status is 'indexed' grouped by 'none'", async () => {
const finding1 = generateCspFinding('0001', 'failed');
const finding2 = generateCspFinding('0002', 'passed');
diff --git a/x-pack/plugins/cloud_security_posture/public/pages/configurations/configurations.tsx b/x-pack/plugins/cloud_security_posture/public/pages/configurations/configurations.tsx
index dc5cc9b4fd4a5..f17a8039db885 100644
--- a/x-pack/plugins/cloud_security_posture/public/pages/configurations/configurations.tsx
+++ b/x-pack/plugins/cloud_security_posture/public/pages/configurations/configurations.tsx
@@ -21,8 +21,12 @@ export const Configurations = () => {
const location = useLocation();
const dataViewQuery = useDataView(CDR_MISCONFIGURATIONS_DATA_VIEW_ID_PREFIX);
const { data: getSetupStatus, isLoading: getSetupStatusIsLoading } = useCspSetupStatusApi();
- const hasConfigurationFindings =
- getSetupStatus?.kspm.status === 'indexed' || getSetupStatus?.cspm.status === 'indexed';
+ const hasMisconfigurationsFindings = !!getSetupStatus?.hasMisconfigurationsFindings;
+
+ const hasFindings =
+ hasMisconfigurationsFindings ||
+ getSetupStatus?.kspm.status === 'indexed' ||
+ getSetupStatus?.cspm.status === 'indexed';
// For now, when there are no findings we prompt first to install cspm, if it is already installed we will prompt to
// install kspm
@@ -30,7 +34,7 @@ export const Configurations = () => {
getSetupStatus?.cspm.status !== 'not-installed' ? 'cspm' : 'kspm';
if (getSetupStatusIsLoading) return defaultLoadingRenderer();
- if (!hasConfigurationFindings) return ;
+ if (!hasFindings) return ;
const dataViewContextValue = {
dataView: dataViewQuery.data!,
diff --git a/x-pack/plugins/cloud_security_posture/public/pages/rules/rules.test.tsx b/x-pack/plugins/cloud_security_posture/public/pages/rules/rules.test.tsx
index 66829ee739010..642aff1ef267d 100644
--- a/x-pack/plugins/cloud_security_posture/public/pages/rules/rules.test.tsx
+++ b/x-pack/plugins/cloud_security_posture/public/pages/rules/rules.test.tsx
@@ -16,7 +16,6 @@ import { PageUrlParams } from '../../../common/types/latest';
import { createReactQueryResponse } from '../../test/fixtures/react_query';
import { coreMock } from '@kbn/core/public/mocks';
import { useCspSetupStatusApi } from '../../common/api/use_setup_status_api';
-import { useSubscriptionStatus } from '../../common/hooks/use_subscription_status';
import { useCspIntegrationLink } from '../../common/navigation/use_csp_integration_link';
import { useLicenseManagementLocatorApi } from '../../common/api/use_license_management_locator_api';
import { useCspBenchmarkIntegrationsV2 } from '../benchmarks/use_csp_benchmark_integrations';
@@ -24,7 +23,7 @@ import * as TEST_SUBJECTS from './test_subjects';
jest.mock('../../common/api/use_setup_status_api');
jest.mock('../../common/api/use_license_management_locator_api');
-jest.mock('../../common/hooks/use_subscription_status');
+jest.mock('../../common/hooks/use_is_subscription_status_valid');
jest.mock('../../common/navigation/use_csp_integration_link');
jest.mock('../benchmarks/use_csp_benchmark_integrations', () => ({
useCspBenchmarkIntegrationsV2: jest.fn(),
@@ -81,13 +80,6 @@ describe('', () => {
})
);
- (useSubscriptionStatus as jest.Mock).mockImplementation(() =>
- createReactQueryResponse({
- status: 'success',
- data: true,
- })
- );
-
(useLicenseManagementLocatorApi as jest.Mock).mockImplementation(() =>
createReactQueryResponse({
status: 'success',
diff --git a/x-pack/plugins/cloud_security_posture/public/pages/vulnerabilities/vulnerabilties.test.tsx b/x-pack/plugins/cloud_security_posture/public/pages/vulnerabilities/vulnerabilties.test.tsx
index cb975088c5c8e..c9e13c15613a5 100644
--- a/x-pack/plugins/cloud_security_posture/public/pages/vulnerabilities/vulnerabilties.test.tsx
+++ b/x-pack/plugins/cloud_security_posture/public/pages/vulnerabilities/vulnerabilties.test.tsx
@@ -14,7 +14,6 @@ import {
} from '../../../common/constants';
import { useCspSetupStatusApi } from '../../common/api/use_setup_status_api';
import { useDataView } from '../../common/api/use_data_view';
-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';
@@ -31,7 +30,7 @@ import { createStubDataView } from '@kbn/data-views-plugin/common/stubs';
jest.mock('../../common/api/use_data_view');
jest.mock('../../common/api/use_setup_status_api');
jest.mock('../../common/api/use_license_management_locator_api');
-jest.mock('../../common/hooks/use_subscription_status');
+jest.mock('../../common/hooks/use_is_subscription_status_valid');
jest.mock('../../common/navigation/use_navigate_to_cis_integration_policies');
jest.mock('../../common/navigation/use_csp_integration_link');
@@ -40,13 +39,6 @@ const chance = new Chance();
beforeEach(() => {
jest.restoreAllMocks();
- (useSubscriptionStatus as jest.Mock).mockImplementation(() =>
- createReactQueryResponse({
- status: 'success',
- data: true,
- })
- );
-
(useLicenseManagementLocatorApi as jest.Mock).mockImplementation(() =>
createReactQueryResponse({
status: 'success',
diff --git a/x-pack/plugins/cloud_security_posture/public/pages/vulnerability_dashboard/vulnerability_dashboard.test.tsx b/x-pack/plugins/cloud_security_posture/public/pages/vulnerability_dashboard/vulnerability_dashboard.test.tsx
index 9e68d5d051c6b..6203adbb11d28 100644
--- a/x-pack/plugins/cloud_security_posture/public/pages/vulnerability_dashboard/vulnerability_dashboard.test.tsx
+++ b/x-pack/plugins/cloud_security_posture/public/pages/vulnerability_dashboard/vulnerability_dashboard.test.tsx
@@ -15,7 +15,6 @@ import {
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';
@@ -35,7 +34,7 @@ import { mockCnvmDashboardData } from './_mocks_/vulnerability_dashboard.mock';
jest.mock('../../common/api/use_data_view');
jest.mock('../../common/api/use_setup_status_api');
jest.mock('../../common/api/use_license_management_locator_api');
-jest.mock('../../common/hooks/use_subscription_status');
+jest.mock('../../common/hooks/use_is_subscription_status_valid');
jest.mock('../../common/navigation/use_navigate_to_cis_integration_policies');
jest.mock('../../common/navigation/use_csp_integration_link');
jest.mock('../../common/api/use_vulnerability_dashboard_api');
@@ -45,13 +44,6 @@ const chance = new Chance();
beforeEach(() => {
jest.restoreAllMocks();
- (useSubscriptionStatus as jest.Mock).mockImplementation(() =>
- createReactQueryResponse({
- status: 'success',
- data: true,
- })
- );
-
(useLicenseManagementLocatorApi as jest.Mock).mockImplementation(() =>
createReactQueryResponse({
status: 'success',
diff --git a/x-pack/plugins/cloud_security_posture/public/types.ts b/x-pack/plugins/cloud_security_posture/public/types.ts
index d16d4e2b7dcbb..bd45c007112b0 100755
--- a/x-pack/plugins/cloud_security_posture/public/types.ts
+++ b/x-pack/plugins/cloud_security_posture/public/types.ts
@@ -69,6 +69,7 @@ export interface CspClientPluginStartDeps {
share: SharePluginStart;
storage: Storage;
spaces: SpacesPluginStart;
+ cloud: CloudSetup;
// optional
usageCollection?: UsageCollectionStart;
diff --git a/x-pack/plugins/cloud_security_posture/server/routes/status/status.handlers.mock.ts b/x-pack/plugins/cloud_security_posture/server/routes/status/status.handlers.mock.ts
index 0f2b3b9eab640..ca3a8c4762bcd 100644
--- a/x-pack/plugins/cloud_security_posture/server/routes/status/status.handlers.mock.ts
+++ b/x-pack/plugins/cloud_security_posture/server/routes/status/status.handlers.mock.ts
@@ -48,6 +48,47 @@ export const notInstalledHandler = http.get(STATUS_URL, () => {
});
});
+export const notInstalledHasMisconfigurationsFindingsHandler = http.get(STATUS_URL, () => {
+ return HttpResponse.json({
+ hasMisconfigurationsFindings: true,
+ cspm: {
+ status: 'not-installed',
+ healthyAgents: 1,
+ installedPackagePolicies: 1,
+ },
+ kspm: {
+ status: 'not-installed',
+ healthyAgents: 1,
+ installedPackagePolicies: 1,
+ },
+ vuln_mgmt: {
+ status: 'not-installed',
+ healthyAgents: 1,
+ installedPackagePolicies: 1,
+ },
+ indicesDetails: [
+ {
+ index: 'logs-cloud_security_posture.findings_latest-default',
+ status: 'empty',
+ },
+ {
+ index: 'logs-cloud_security_posture.findings-default*',
+ status: 'empty',
+ },
+ {
+ index: 'logs-cloud_security_posture.scores-default',
+ status: 'empty',
+ },
+ {
+ index: 'logs-cloud_security_posture.vulnerabilities_latest-default',
+ status: 'empty',
+ },
+ ],
+ isPluginInitialized: true,
+ latestPackageVersion: '1.9.0',
+ });
+});
+
export const notDeployedHandler = http.get(STATUS_URL, () => {
return HttpResponse.json({
cspm: {
diff --git a/x-pack/plugins/cloud_security_posture/server/routes/status/status.ts b/x-pack/plugins/cloud_security_posture/server/routes/status/status.ts
index df2fc0d36fc63..868deaed0fd82 100644
--- a/x-pack/plugins/cloud_security_posture/server/routes/status/status.ts
+++ b/x-pack/plugins/cloud_security_posture/server/routes/status/status.ts
@@ -32,6 +32,7 @@ import {
POSTURE_TYPE_ALL,
LATEST_VULNERABILITIES_RETENTION_POLICY,
LATEST_FINDINGS_RETENTION_POLICY,
+ CDR_MISCONFIGURATIONS_INDEX_PATTERN,
} from '../../../common/constants';
import type {
CspApiRequestHandlerContext,
@@ -142,6 +143,43 @@ const assertResponse = (resp: CspSetupStatus, logger: CspApiRequestHandlerContex
}
};
+const checkIndexHasFindings = async (
+ esClient: ElasticsearchClient,
+ index: string,
+ retentionPolicy: string,
+ logger: Logger
+) => {
+ try {
+ const response = await esClient.search({
+ index,
+ size: 0, // We only need to know if there are any hits, so we don't need to retrieve documents
+ query: {
+ bool: {
+ filter: [
+ {
+ range: {
+ '@timestamp': {
+ gte: `now-${retentionPolicy}`,
+ lte: 'now',
+ },
+ },
+ },
+ ],
+ },
+ },
+ });
+
+ // Check the number of hits
+ const totalHits =
+ typeof response.hits.total === 'object' ? response.hits.total.value : response.hits.total;
+
+ return !!totalHits;
+ } catch (err) {
+ logger.error(`Error checking if index ${index} has findings`);
+ logger.error(err);
+ }
+};
+
export const getCspStatus = async ({
logger,
esClient,
@@ -153,6 +191,7 @@ export const getCspStatus = async ({
isPluginInitialized,
}: CspStatusDependencies): Promise => {
const [
+ hasMisconfigurationsFindings,
findingsLatestIndexStatus,
findingsIndexStatus,
scoreIndexStatus,
@@ -171,6 +210,12 @@ export const getCspStatus = async ({
installedPackagePoliciesVulnMgmt,
installedPolicyTemplates,
] = await Promise.all([
+ checkIndexHasFindings(
+ esClient,
+ CDR_MISCONFIGURATIONS_INDEX_PATTERN,
+ LATEST_FINDINGS_RETENTION_POLICY,
+ logger
+ ),
checkIndexStatus(esClient, LATEST_FINDINGS_INDEX_DEFAULT_NS, logger, {
postureType: POSTURE_TYPE_ALL,
retentionTime: LATEST_VULNERABILITIES_RETENTION_POLICY,
@@ -357,6 +402,7 @@ export const getCspStatus = async ({
const response: CspSetupStatus = {
...statusResponseInfo,
installedPackageVersion: installation?.install_version,
+ hasMisconfigurationsFindings,
};
assertResponse(response, logger);
diff --git a/x-pack/test/api_integration/apis/cloud_security_posture/status/status_indexed.ts b/x-pack/test/api_integration/apis/cloud_security_posture/status/status_indexed.ts
index 6ba3fbe5c31ef..5e6d639490e45 100644
--- a/x-pack/test/api_integration/apis/cloud_security_posture/status/status_indexed.ts
+++ b/x-pack/test/api_integration/apis/cloud_security_posture/status/status_indexed.ts
@@ -61,6 +61,55 @@ export default function (providerContext: FtrProviderContext) {
await esArchiver.unload('x-pack/test/functional/es_archives/fleet/empty_fleet_server');
});
+ it(`Return hasMisconfigurationsFindings true when there are latest findings but no installed integrations`, async () => {
+ await addIndex(es, findingsMockData, LATEST_FINDINGS_INDEX_DEFAULT_NS);
+
+ const { body: res }: { body: CspSetupStatus } = await supertest
+ .get(`/internal/cloud_security_posture/status`)
+ .set(ELASTIC_HTTP_VERSION_HEADER, '1')
+ .set('kbn-xsrf', 'xxxx')
+ .expect(200);
+
+ expect(res.hasMisconfigurationsFindings).to.eql(
+ true,
+ `expected hasMisconfigurationsFindings to be true but got ${res.hasMisconfigurationsFindings} instead`
+ );
+ });
+
+ it(`Return hasMisconfigurationsFindings true when there are only findings in third party index`, async () => {
+ await deleteIndex(es, INDEX_ARRAY);
+ const mock3PIndex = 'logs-mock-3p-integration_latest_misconfigurations_cdr';
+ await addIndex(es, findingsMockData, mock3PIndex);
+
+ const { body: res }: { body: CspSetupStatus } = await supertest
+ .get(`/internal/cloud_security_posture/status`)
+ .set(ELASTIC_HTTP_VERSION_HEADER, '1')
+ .set('kbn-xsrf', 'xxxx')
+ .expect(200);
+
+ expect(res.hasMisconfigurationsFindings).to.eql(
+ true,
+ `expected hasMisconfigurationsFindings to be true but got ${res.hasMisconfigurationsFindings} instead`
+ );
+
+ await deleteIndex(es, [mock3PIndex]);
+ });
+
+ it(`Return hasMisconfigurationsFindings false when there are no findings`, async () => {
+ await deleteIndex(es, INDEX_ARRAY);
+
+ const { body: res }: { body: CspSetupStatus } = await supertest
+ .get(`/internal/cloud_security_posture/status`)
+ .set(ELASTIC_HTTP_VERSION_HEADER, '1')
+ .set('kbn-xsrf', 'xxxx')
+ .expect(200);
+
+ expect(res.hasMisconfigurationsFindings).to.eql(
+ false,
+ `expected hasMisconfigurationsFindings to be false but got ${res.hasMisconfigurationsFindings} instead`
+ );
+ });
+
it(`Return kspm status indexed when logs-cloud_security_posture.findings_latest-default contains new kspm documents`, async () => {
await createPackagePolicy(
supertest,