mirror of
https://github.com/elastic/kibana.git
synced 2025-04-24 01:38:56 -04:00
[Cloud Security] Removing license gate keeping and displaying the table when there are findings (#190285)
This commit is contained in:
parent
4e31c9b976
commit
d0c1349122
19 changed files with 396 additions and 300 deletions
|
@ -133,6 +133,7 @@ export interface BaseCspSetupStatus {
|
|||
vuln_mgmt: BaseCspSetupBothPolicy;
|
||||
isPluginInitialized: boolean;
|
||||
installedPackageVersion?: string | undefined;
|
||||
hasMisconfigurationsFindings?: boolean;
|
||||
}
|
||||
|
||||
export type CspSetupStatus = BaseCspSetupStatus;
|
||||
|
|
|
@ -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);
|
|
@ -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('<CloudPosturePage />', () => {
|
||||
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('<CloudPosturePage />', () => {
|
|||
);
|
||||
};
|
||||
|
||||
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('<CloudPosturePage />', () => {
|
|||
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('<CloudPosturePage />', () => {
|
|||
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('<CloudPosturePage />', () => {
|
|||
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('<CloudPosturePage />', () => {
|
|||
[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('<CloudPosturePage />', () => {
|
|||
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('<CloudPosturePage />', () => {
|
|||
|
||||
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('<CloudPosturePage />', () => {
|
|||
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();
|
||||
|
|
|
@ -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 = () => (
|
|||
</FullSizeCenteredPage>
|
||||
);
|
||||
|
||||
const subscriptionNotAllowedRenderer = () => (
|
||||
<FullSizeCenteredPage data-test-subj={SUBSCRIPTION_NOT_ALLOWED_TEST_SUBJECT}>
|
||||
<SubscriptionNotAllowed />
|
||||
</FullSizeCenteredPage>
|
||||
);
|
||||
|
||||
interface CloudPosturePageProps<TData, TError> {
|
||||
children: React.ReactNode;
|
||||
query?: UseQueryResult<TData, TError>;
|
||||
|
@ -171,21 +162,7 @@ export const CloudPosturePage = <TData, TError>({
|
|||
errorRender = defaultErrorRenderer,
|
||||
noDataRenderer = defaultNoDataRenderer,
|
||||
}: CloudPosturePageProps<TData, TError>) => {
|
||||
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;
|
||||
}
|
||||
|
|
|
@ -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('<CspPolicyTemplateForm />', () => {
|
|||
(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('<CspPolicyTemplateForm />', () => {
|
|||
},
|
||||
})
|
||||
);
|
||||
|
||||
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('<CspPolicyTemplateForm />', () => {
|
|||
);
|
||||
};
|
||||
|
||||
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(<WrappedComponent newPolicy={policy} />);
|
||||
|
||||
rerender(<WrappedComponent newPolicy={{ ...policy, namespace: 'some-namespace' }} />);
|
||||
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(<WrappedComponent newPolicy={policy} />);
|
||||
|
||||
rerender(<WrappedComponent newPolicy={{ ...policy, namespace: 'some-namespace' }} />);
|
||||
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(<WrappedComponent newPolicy={policy} />);
|
||||
|
||||
rerender(<WrappedComponent newPolicy={{ ...policy, namespace: 'some-namespace' }} />);
|
||||
expect(screen.getByTestId('no_locator')).toBeInTheDocument();
|
||||
});
|
||||
|
||||
it('updates package policy namespace to default when it changes', () => {
|
||||
const policy = getMockPolicyK8s();
|
||||
const { rerender } = render(<WrappedComponent newPolicy={policy} />);
|
||||
|
|
|
@ -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) =
|
|||
</div>
|
||||
);
|
||||
|
||||
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<PackagePolicyReplaceDefineStepExtensionComponentProps>(
|
||||
({
|
||||
newPolicy,
|
||||
|
@ -553,7 +675,11 @@ export const CspPolicyTemplateForm = memo<PackagePolicyReplaceDefineStepExtensio
|
|||
: undefined;
|
||||
// Handling validation state
|
||||
const [isValid, setIsValid] = useState(true);
|
||||
const { cloud } = useKibana().services;
|
||||
const isServerless = !!cloud.serverless.projectType;
|
||||
const input = getSelectedOption(newPolicy.inputs, integration);
|
||||
const getIsSubscriptionValid = useIsSubscriptionStatusValid();
|
||||
const isSubscriptionValid = !!getIsSubscriptionValid.data;
|
||||
const { isAgentlessAvailable, setupTechnology, updateSetupTechnology } = useSetupTechnology({
|
||||
input,
|
||||
isAgentlessEnabled,
|
||||
|
@ -615,6 +741,7 @@ export const CspPolicyTemplateForm = memo<PackagePolicyReplaceDefineStepExtensio
|
|||
},
|
||||
[onChange, isValid]
|
||||
);
|
||||
|
||||
/**
|
||||
* - Updates policy inputs by user selection
|
||||
* - Updates hidden policy vars
|
||||
|
@ -652,6 +779,12 @@ export const CspPolicyTemplateForm = memo<PackagePolicyReplaceDefineStepExtensio
|
|||
enabled: canFetchIntegration,
|
||||
});
|
||||
|
||||
useEffect(() => {
|
||||
if (!isServerless) {
|
||||
setIsValid(isSubscriptionValid);
|
||||
}
|
||||
}, [isServerless, isSubscriptionValid]);
|
||||
|
||||
useEffect(() => {
|
||||
if (isEditPage) return;
|
||||
if (isLoading) return;
|
||||
|
@ -726,6 +859,10 @@ export const CspPolicyTemplateForm = memo<PackagePolicyReplaceDefineStepExtensio
|
|||
},
|
||||
];
|
||||
|
||||
if (!isSubscriptionValid) {
|
||||
return <SubscriptionNotAllowed />;
|
||||
}
|
||||
|
||||
return (
|
||||
<>
|
||||
{isEditPage && <EditScreenStepTitle />}
|
||||
|
@ -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]);
|
||||
};
|
||||
|
|
|
@ -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 (
|
||||
<EuiPageSection color="danger" alignment="center">
|
||||
<EuiPageSection
|
||||
color="danger"
|
||||
alignment="center"
|
||||
data-test-subj={SUBSCRIPTION_NOT_ALLOWED_TEST_SUBJECT}
|
||||
>
|
||||
<EuiEmptyPrompt
|
||||
iconType="warning"
|
||||
title={
|
||||
<h2>
|
||||
<FormattedMessage
|
||||
id="xpack.csp.subscriptionNotAllowed.promptTitle"
|
||||
defaultMessage="Upgrade for subscription features"
|
||||
defaultMessage="Upgrade your subscription to an Enterprise license"
|
||||
/>
|
||||
</h2>
|
||||
}
|
||||
|
|
|
@ -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';
|
||||
|
|
|
@ -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('<Benchmarks />', () => {
|
|||
})
|
||||
);
|
||||
|
||||
(useSubscriptionStatus as jest.Mock).mockImplementation(() =>
|
||||
createReactQueryResponse({
|
||||
status: 'success',
|
||||
data: true,
|
||||
})
|
||||
);
|
||||
|
||||
(useLicenseManagementLocatorApi as jest.Mock).mockImplementation(() =>
|
||||
createReactQueryResponse({
|
||||
status: 'success',
|
||||
|
|
|
@ -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('<ComplianceDashboard />', () => {
|
|||
})
|
||||
);
|
||||
|
||||
(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',
|
||||
|
|
|
@ -44,7 +44,7 @@ describe('<Findings />', () => {
|
|||
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('<Findings />', () => {
|
|||
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');
|
||||
|
|
|
@ -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 <NoFindingsStates postureType={noFindingsForPostureType} />;
|
||||
if (!hasFindings) return <NoFindingsStates postureType={noFindingsForPostureType} />;
|
||||
|
||||
const dataViewContextValue = {
|
||||
dataView: dataViewQuery.data!,
|
||||
|
|
|
@ -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('<Rules />', () => {
|
|||
})
|
||||
);
|
||||
|
||||
(useSubscriptionStatus as jest.Mock).mockImplementation(() =>
|
||||
createReactQueryResponse({
|
||||
status: 'success',
|
||||
data: true,
|
||||
})
|
||||
);
|
||||
|
||||
(useLicenseManagementLocatorApi as jest.Mock).mockImplementation(() =>
|
||||
createReactQueryResponse({
|
||||
status: 'success',
|
||||
|
|
|
@ -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',
|
||||
|
|
|
@ -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',
|
||||
|
|
|
@ -69,6 +69,7 @@ export interface CspClientPluginStartDeps {
|
|||
share: SharePluginStart;
|
||||
storage: Storage;
|
||||
spaces: SpacesPluginStart;
|
||||
cloud: CloudSetup;
|
||||
|
||||
// optional
|
||||
usageCollection?: UsageCollectionStart;
|
||||
|
|
|
@ -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: {
|
||||
|
|
|
@ -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<CspSetupStatus> => {
|
||||
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);
|
||||
|
|
|
@ -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,
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue