mirror of
https://github.com/elastic/kibana.git
synced 2025-04-25 02:09:32 -04:00
[Cloud Posture] Subscription gating (#140894)
This commit is contained in:
parent
140f720710
commit
5a81f0b559
20 changed files with 384 additions and 30 deletions
|
@ -0,0 +1,44 @@
|
||||||
|
/*
|
||||||
|
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
|
||||||
|
* or more contributor license agreements. Licensed under the Elastic License
|
||||||
|
* 2.0; you may not use this file except in compliance with the Elastic License
|
||||||
|
* 2.0.
|
||||||
|
*/
|
||||||
|
|
||||||
|
import type { LicenseType } from '@kbn/licensing-plugin/common/types';
|
||||||
|
import { isSubscriptionAllowed } from './subscription';
|
||||||
|
import { licenseMock } from '@kbn/licensing-plugin/common/licensing.mock';
|
||||||
|
|
||||||
|
const ON_PREM_ALLOWED_LICENSES: readonly LicenseType[] = ['enterprise', 'trial'];
|
||||||
|
const ON_PREM_NOT_ALLOWED_LICENSES: readonly LicenseType[] = ['basic', 'gold', 'platinum'];
|
||||||
|
const ALL_LICENSE_TYPES: readonly LicenseType[] = [
|
||||||
|
'standard',
|
||||||
|
...ON_PREM_NOT_ALLOWED_LICENSES,
|
||||||
|
...ON_PREM_NOT_ALLOWED_LICENSES,
|
||||||
|
];
|
||||||
|
|
||||||
|
describe('isSubscriptionAllowed', () => {
|
||||||
|
it('should allow any cloud subscription', () => {
|
||||||
|
const isCloudEnabled = true;
|
||||||
|
ALL_LICENSE_TYPES.forEach((licenseType) => {
|
||||||
|
const license = licenseMock.createLicense({ license: { type: licenseType } });
|
||||||
|
expect(isSubscriptionAllowed(isCloudEnabled, license)).toBeTruthy();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should allow enterprise and trial licenses for on-prem', () => {
|
||||||
|
const isCloudEnabled = false;
|
||||||
|
ON_PREM_ALLOWED_LICENSES.forEach((licenseType) => {
|
||||||
|
const license = licenseMock.createLicense({ license: { type: licenseType } });
|
||||||
|
expect(isSubscriptionAllowed(isCloudEnabled, license)).toBeTruthy();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should not allow enterprise and trial licenses for on-prem', () => {
|
||||||
|
const isCloudEnabled = false;
|
||||||
|
ON_PREM_NOT_ALLOWED_LICENSES.forEach((licenseType) => {
|
||||||
|
const license = licenseMock.createLicense({ license: { type: licenseType } });
|
||||||
|
expect(isSubscriptionAllowed(isCloudEnabled, license)).toBeFalsy();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
|
@ -0,0 +1,24 @@
|
||||||
|
/*
|
||||||
|
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
|
||||||
|
* or more contributor license agreements. Licensed under the Elastic License
|
||||||
|
* 2.0; you may not use this file except in compliance with the Elastic License
|
||||||
|
* 2.0.
|
||||||
|
*/
|
||||||
|
|
||||||
|
import type { ILicense, LicenseType } from '@kbn/licensing-plugin/common/types';
|
||||||
|
import { PLUGIN_NAME } from '..';
|
||||||
|
|
||||||
|
const MINIMUM_NON_CLOUD_LICENSE_TYPE: LicenseType = 'enterprise';
|
||||||
|
|
||||||
|
export const isSubscriptionAllowed = (isCloudEnabled?: boolean, license?: ILicense): boolean => {
|
||||||
|
if (isCloudEnabled) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!license) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
const licenseCheck = license.check(PLUGIN_NAME, MINIMUM_NON_CLOUD_LICENSE_TYPE);
|
||||||
|
return licenseCheck.state === 'valid';
|
||||||
|
};
|
|
@ -10,6 +10,17 @@
|
||||||
"description": "The cloud security posture plugin",
|
"description": "The cloud security posture plugin",
|
||||||
"server": true,
|
"server": true,
|
||||||
"ui": true,
|
"ui": true,
|
||||||
"requiredPlugins": ["navigation", "data", "fleet", "unifiedSearch", "taskManager", "security", "charts", "discover"],
|
"requiredPlugins": [
|
||||||
|
"navigation",
|
||||||
|
"data",
|
||||||
|
"fleet",
|
||||||
|
"unifiedSearch",
|
||||||
|
"taskManager",
|
||||||
|
"security",
|
||||||
|
"charts",
|
||||||
|
"discover",
|
||||||
|
"cloud",
|
||||||
|
"licensing"
|
||||||
|
],
|
||||||
"requiredBundles": ["kibanaReact"]
|
"requiredBundles": ["kibanaReact"]
|
||||||
}
|
}
|
||||||
|
|
|
@ -0,0 +1,16 @@
|
||||||
|
/*
|
||||||
|
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
|
||||||
|
* or more contributor license agreements. Licensed under the Elastic License
|
||||||
|
* 2.0; you may not use this file except in compliance with the Elastic License
|
||||||
|
* 2.0.
|
||||||
|
*/
|
||||||
|
import { createContext } from 'react';
|
||||||
|
|
||||||
|
interface SetupContextValue {
|
||||||
|
isCloudEnabled?: boolean;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A utility to pass data from the plugin setup lifecycle stage to application components
|
||||||
|
*/
|
||||||
|
export const SetupContext = createContext<SetupContextValue>({});
|
|
@ -0,0 +1,22 @@
|
||||||
|
/*
|
||||||
|
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
|
||||||
|
* or more contributor license agreements. Licensed under the Elastic License
|
||||||
|
* 2.0; you may not use this file except in compliance with the Elastic License
|
||||||
|
* 2.0.
|
||||||
|
*/
|
||||||
|
import { useContext } from 'react';
|
||||||
|
import { useQuery } from '@tanstack/react-query';
|
||||||
|
import { SetupContext } from '../../application/setup_context';
|
||||||
|
import { isSubscriptionAllowed } from '../../../common/utils/subscription';
|
||||||
|
import { useKibana } from './use_kibana';
|
||||||
|
|
||||||
|
const SUBSCRIPTION_QUERY_KEY = 'csp_subscription_query_key';
|
||||||
|
|
||||||
|
export const useSubscriptionStatus = () => {
|
||||||
|
const { licensing } = useKibana().services;
|
||||||
|
const { isCloudEnabled } = useContext(SetupContext);
|
||||||
|
return useQuery([SUBSCRIPTION_QUERY_KEY], async () => {
|
||||||
|
const license = await licensing.refresh();
|
||||||
|
return isSubscriptionAllowed(isCloudEnabled, license);
|
||||||
|
});
|
||||||
|
};
|
|
@ -5,6 +5,7 @@
|
||||||
* 2.0.
|
* 2.0.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
|
import { useSubscriptionStatus } from '../common/hooks/use_subscription_status';
|
||||||
import Chance from 'chance';
|
import Chance from 'chance';
|
||||||
import {
|
import {
|
||||||
DEFAULT_NO_DATA_TEST_SUBJECT,
|
DEFAULT_NO_DATA_TEST_SUBJECT,
|
||||||
|
@ -12,6 +13,7 @@ import {
|
||||||
isCommonError,
|
isCommonError,
|
||||||
LOADING_STATE_TEST_SUBJECT,
|
LOADING_STATE_TEST_SUBJECT,
|
||||||
PACKAGE_NOT_INSTALLED_TEST_SUBJECT,
|
PACKAGE_NOT_INSTALLED_TEST_SUBJECT,
|
||||||
|
SUBSCRIPTION_NOT_ALLOWED_TEST_SUBJECT,
|
||||||
} from './cloud_posture_page';
|
} from './cloud_posture_page';
|
||||||
import { createReactQueryResponse } from '../test/fixtures/react_query';
|
import { createReactQueryResponse } from '../test/fixtures/react_query';
|
||||||
import { TestProvider } from '../test/test_provider';
|
import { TestProvider } from '../test/test_provider';
|
||||||
|
@ -27,6 +29,7 @@ import { useCISIntegrationLink } from '../common/navigation/use_navigate_to_cis_
|
||||||
const chance = new Chance();
|
const chance = new Chance();
|
||||||
jest.mock('../common/api/use_setup_status_api');
|
jest.mock('../common/api/use_setup_status_api');
|
||||||
jest.mock('../common/navigation/use_navigate_to_cis_integration');
|
jest.mock('../common/navigation/use_navigate_to_cis_integration');
|
||||||
|
jest.mock('../common/hooks/use_subscription_status');
|
||||||
|
|
||||||
describe('<CloudPosturePage />', () => {
|
describe('<CloudPosturePage />', () => {
|
||||||
beforeEach(() => {
|
beforeEach(() => {
|
||||||
|
@ -37,6 +40,13 @@ describe('<CloudPosturePage />', () => {
|
||||||
data: { status: 'indexed' },
|
data: { status: 'indexed' },
|
||||||
})
|
})
|
||||||
);
|
);
|
||||||
|
|
||||||
|
(useSubscriptionStatus as jest.Mock).mockImplementation(() =>
|
||||||
|
createReactQueryResponse({
|
||||||
|
status: 'success',
|
||||||
|
data: true,
|
||||||
|
})
|
||||||
|
);
|
||||||
});
|
});
|
||||||
|
|
||||||
const renderCloudPosturePage = (
|
const renderCloudPosturePage = (
|
||||||
|
@ -69,10 +79,66 @@ describe('<CloudPosturePage />', () => {
|
||||||
|
|
||||||
expect(screen.getByText(children)).toBeInTheDocument();
|
expect(screen.getByText(children)).toBeInTheDocument();
|
||||||
expect(screen.queryByTestId(LOADING_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.queryByTestId(ERROR_STATE_TEST_SUBJECT)).not.toBeInTheDocument();
|
expect(screen.queryByTestId(ERROR_STATE_TEST_SUBJECT)).not.toBeInTheDocument();
|
||||||
expect(screen.queryByTestId(PACKAGE_NOT_INSTALLED_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 integrations installation prompt if integration is not installed', () => {
|
it('renders integrations installation prompt if integration is not installed', () => {
|
||||||
(useCspSetupStatusApi as jest.Mock).mockImplementation(() =>
|
(useCspSetupStatusApi as jest.Mock).mockImplementation(() =>
|
||||||
createReactQueryResponse({
|
createReactQueryResponse({
|
||||||
|
@ -88,6 +154,7 @@ describe('<CloudPosturePage />', () => {
|
||||||
expect(screen.getByTestId(PACKAGE_NOT_INSTALLED_TEST_SUBJECT)).toBeInTheDocument();
|
expect(screen.getByTestId(PACKAGE_NOT_INSTALLED_TEST_SUBJECT)).toBeInTheDocument();
|
||||||
expect(screen.queryByText(children)).not.toBeInTheDocument();
|
expect(screen.queryByText(children)).not.toBeInTheDocument();
|
||||||
expect(screen.queryByTestId(LOADING_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.queryByTestId(ERROR_STATE_TEST_SUBJECT)).not.toBeInTheDocument();
|
expect(screen.queryByTestId(ERROR_STATE_TEST_SUBJECT)).not.toBeInTheDocument();
|
||||||
});
|
});
|
||||||
|
|
||||||
|
@ -105,6 +172,7 @@ describe('<CloudPosturePage />', () => {
|
||||||
expect(screen.getByTestId(LOADING_STATE_TEST_SUBJECT)).toBeInTheDocument();
|
expect(screen.getByTestId(LOADING_STATE_TEST_SUBJECT)).toBeInTheDocument();
|
||||||
expect(screen.queryByText(children)).not.toBeInTheDocument();
|
expect(screen.queryByText(children)).not.toBeInTheDocument();
|
||||||
expect(screen.queryByTestId(ERROR_STATE_TEST_SUBJECT)).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();
|
expect(screen.queryByTestId(PACKAGE_NOT_INSTALLED_TEST_SUBJECT)).not.toBeInTheDocument();
|
||||||
});
|
});
|
||||||
|
|
||||||
|
@ -122,6 +190,7 @@ describe('<CloudPosturePage />', () => {
|
||||||
|
|
||||||
expect(screen.getByTestId(ERROR_STATE_TEST_SUBJECT)).toBeInTheDocument();
|
expect(screen.getByTestId(ERROR_STATE_TEST_SUBJECT)).toBeInTheDocument();
|
||||||
expect(screen.queryByTestId(LOADING_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.queryByText(children)).not.toBeInTheDocument();
|
||||||
expect(screen.queryByTestId(PACKAGE_NOT_INSTALLED_TEST_SUBJECT)).not.toBeInTheDocument();
|
expect(screen.queryByTestId(PACKAGE_NOT_INSTALLED_TEST_SUBJECT)).not.toBeInTheDocument();
|
||||||
});
|
});
|
||||||
|
@ -135,6 +204,7 @@ describe('<CloudPosturePage />', () => {
|
||||||
renderCloudPosturePage({ children, query });
|
renderCloudPosturePage({ children, query });
|
||||||
|
|
||||||
expect(screen.getByTestId(LOADING_STATE_TEST_SUBJECT)).toBeInTheDocument();
|
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.queryByText(children)).not.toBeInTheDocument();
|
||||||
expect(screen.queryByTestId(ERROR_STATE_TEST_SUBJECT)).not.toBeInTheDocument();
|
expect(screen.queryByTestId(ERROR_STATE_TEST_SUBJECT)).not.toBeInTheDocument();
|
||||||
expect(screen.queryByTestId(PACKAGE_NOT_INSTALLED_TEST_SUBJECT)).not.toBeInTheDocument();
|
expect(screen.queryByTestId(PACKAGE_NOT_INSTALLED_TEST_SUBJECT)).not.toBeInTheDocument();
|
||||||
|
@ -149,6 +219,7 @@ describe('<CloudPosturePage />', () => {
|
||||||
renderCloudPosturePage({ children, query });
|
renderCloudPosturePage({ children, query });
|
||||||
|
|
||||||
expect(screen.getByTestId(LOADING_STATE_TEST_SUBJECT)).toBeInTheDocument();
|
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.queryByText(children)).not.toBeInTheDocument();
|
||||||
expect(screen.queryByTestId(ERROR_STATE_TEST_SUBJECT)).not.toBeInTheDocument();
|
expect(screen.queryByTestId(ERROR_STATE_TEST_SUBJECT)).not.toBeInTheDocument();
|
||||||
expect(screen.queryByTestId(PACKAGE_NOT_INSTALLED_TEST_SUBJECT)).not.toBeInTheDocument();
|
expect(screen.queryByTestId(PACKAGE_NOT_INSTALLED_TEST_SUBJECT)).not.toBeInTheDocument();
|
||||||
|
@ -177,6 +248,7 @@ describe('<CloudPosturePage />', () => {
|
||||||
expect(screen.getByText(text, { exact: false })).toBeInTheDocument()
|
expect(screen.getByText(text, { exact: false })).toBeInTheDocument()
|
||||||
);
|
);
|
||||||
expect(screen.getByTestId(ERROR_STATE_TEST_SUBJECT)).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.queryByTestId(LOADING_STATE_TEST_SUBJECT)).not.toBeInTheDocument();
|
||||||
expect(screen.queryByText(children)).not.toBeInTheDocument();
|
expect(screen.queryByText(children)).not.toBeInTheDocument();
|
||||||
expect(screen.queryByTestId(PACKAGE_NOT_INSTALLED_TEST_SUBJECT)).not.toBeInTheDocument();
|
expect(screen.queryByTestId(PACKAGE_NOT_INSTALLED_TEST_SUBJECT)).not.toBeInTheDocument();
|
||||||
|
@ -209,6 +281,7 @@ describe('<CloudPosturePage />', () => {
|
||||||
[error, statusCode].forEach((text) => expect(screen.queryByText(text)).not.toBeInTheDocument());
|
[error, statusCode].forEach((text) => expect(screen.queryByText(text)).not.toBeInTheDocument());
|
||||||
expect(screen.queryByTestId(ERROR_STATE_TEST_SUBJECT)).not.toBeInTheDocument();
|
expect(screen.queryByTestId(ERROR_STATE_TEST_SUBJECT)).not.toBeInTheDocument();
|
||||||
expect(screen.queryByTestId(LOADING_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.queryByText(children)).not.toBeInTheDocument();
|
||||||
expect(screen.queryByTestId(PACKAGE_NOT_INSTALLED_TEST_SUBJECT)).not.toBeInTheDocument();
|
expect(screen.queryByTestId(PACKAGE_NOT_INSTALLED_TEST_SUBJECT)).not.toBeInTheDocument();
|
||||||
});
|
});
|
||||||
|
@ -230,6 +303,7 @@ describe('<CloudPosturePage />', () => {
|
||||||
expect(screen.getByText(loading)).toBeInTheDocument();
|
expect(screen.getByText(loading)).toBeInTheDocument();
|
||||||
expect(screen.queryByTestId(ERROR_STATE_TEST_SUBJECT)).not.toBeInTheDocument();
|
expect(screen.queryByTestId(ERROR_STATE_TEST_SUBJECT)).not.toBeInTheDocument();
|
||||||
expect(screen.queryByTestId(LOADING_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.queryByText(children)).not.toBeInTheDocument();
|
||||||
expect(screen.queryByTestId(PACKAGE_NOT_INSTALLED_TEST_SUBJECT)).not.toBeInTheDocument();
|
expect(screen.queryByTestId(PACKAGE_NOT_INSTALLED_TEST_SUBJECT)).not.toBeInTheDocument();
|
||||||
});
|
});
|
||||||
|
@ -245,6 +319,7 @@ describe('<CloudPosturePage />', () => {
|
||||||
|
|
||||||
expect(screen.getByTestId(DEFAULT_NO_DATA_TEST_SUBJECT)).toBeInTheDocument();
|
expect(screen.getByTestId(DEFAULT_NO_DATA_TEST_SUBJECT)).toBeInTheDocument();
|
||||||
expect(screen.queryByTestId(LOADING_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.queryByText(children)).not.toBeInTheDocument();
|
||||||
expect(screen.queryByTestId(ERROR_STATE_TEST_SUBJECT)).not.toBeInTheDocument();
|
expect(screen.queryByTestId(ERROR_STATE_TEST_SUBJECT)).not.toBeInTheDocument();
|
||||||
expect(screen.queryByTestId(PACKAGE_NOT_INSTALLED_TEST_SUBJECT)).not.toBeInTheDocument();
|
expect(screen.queryByTestId(PACKAGE_NOT_INSTALLED_TEST_SUBJECT)).not.toBeInTheDocument();
|
||||||
|
@ -273,6 +348,7 @@ describe('<CloudPosturePage />', () => {
|
||||||
expect(screen.getByText(pageTitle)).toBeInTheDocument();
|
expect(screen.getByText(pageTitle)).toBeInTheDocument();
|
||||||
expect(screen.getAllByText(solution, { exact: false })[0]).toBeInTheDocument();
|
expect(screen.getAllByText(solution, { exact: false })[0]).toBeInTheDocument();
|
||||||
expect(screen.queryByTestId(LOADING_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.queryByText(children)).not.toBeInTheDocument();
|
||||||
expect(screen.queryByTestId(ERROR_STATE_TEST_SUBJECT)).not.toBeInTheDocument();
|
expect(screen.queryByTestId(ERROR_STATE_TEST_SUBJECT)).not.toBeInTheDocument();
|
||||||
expect(screen.queryByTestId(PACKAGE_NOT_INSTALLED_TEST_SUBJECT)).not.toBeInTheDocument();
|
expect(screen.queryByTestId(PACKAGE_NOT_INSTALLED_TEST_SUBJECT)).not.toBeInTheDocument();
|
||||||
|
|
|
@ -11,6 +11,8 @@ import { EuiEmptyPrompt } from '@elastic/eui';
|
||||||
import { FormattedMessage } from '@kbn/i18n-react';
|
import { FormattedMessage } from '@kbn/i18n-react';
|
||||||
import { NoDataPage } from '@kbn/kibana-react-plugin/public';
|
import { NoDataPage } from '@kbn/kibana-react-plugin/public';
|
||||||
import { css } from '@emotion/react';
|
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 { FullSizeCenteredPage } from './full_size_centered_page';
|
||||||
import { useCspSetupStatusApi } from '../common/api/use_setup_status_api';
|
import { useCspSetupStatusApi } from '../common/api/use_setup_status_api';
|
||||||
import { CspLoadingState } from './csp_loading_state';
|
import { CspLoadingState } from './csp_loading_state';
|
||||||
|
@ -20,6 +22,7 @@ export const LOADING_STATE_TEST_SUBJECT = 'cloud_posture_page_loading';
|
||||||
export const ERROR_STATE_TEST_SUBJECT = 'cloud_posture_page_error';
|
export const ERROR_STATE_TEST_SUBJECT = 'cloud_posture_page_error';
|
||||||
export const PACKAGE_NOT_INSTALLED_TEST_SUBJECT = 'cloud_posture_page_package_not_installed';
|
export const PACKAGE_NOT_INSTALLED_TEST_SUBJECT = 'cloud_posture_page_package_not_installed';
|
||||||
export const DEFAULT_NO_DATA_TEST_SUBJECT = 'cloud_posture_page_no_data';
|
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 {
|
interface CommonError {
|
||||||
body: {
|
body: {
|
||||||
|
@ -120,28 +123,29 @@ const defaultErrorRenderer = (error: unknown) => (
|
||||||
</FullSizeCenteredPage>
|
</FullSizeCenteredPage>
|
||||||
);
|
);
|
||||||
|
|
||||||
const defaultNoDataRenderer = () => {
|
const defaultNoDataRenderer = () => (
|
||||||
return (
|
<FullSizeCenteredPage>
|
||||||
<FullSizeCenteredPage>
|
<NoDataPage
|
||||||
<NoDataPage
|
data-test-subj={DEFAULT_NO_DATA_TEST_SUBJECT}
|
||||||
data-test-subj={DEFAULT_NO_DATA_TEST_SUBJECT}
|
pageTitle={i18n.translate('xpack.csp.cloudPosturePage.defaultNoDataConfig.pageTitle', {
|
||||||
pageTitle={i18n.translate('xpack.csp.cloudPosturePage.defaultNoDataConfig.pageTitle', {
|
defaultMessage: 'No data found',
|
||||||
defaultMessage: 'No data found',
|
})}
|
||||||
})}
|
solution={i18n.translate('xpack.csp.cloudPosturePage.defaultNoDataConfig.solutionNameLabel', {
|
||||||
solution={i18n.translate(
|
defaultMessage: 'Cloud Security Posture',
|
||||||
'xpack.csp.cloudPosturePage.defaultNoDataConfig.solutionNameLabel',
|
})}
|
||||||
{
|
// TODO: Add real docs link once we have it
|
||||||
defaultMessage: 'Cloud Security Posture',
|
docsLink={'https://www.elastic.co/guide/index.html'}
|
||||||
}
|
logo={'logoSecurity'}
|
||||||
)}
|
actions={{}}
|
||||||
// TODO: Add real docs link once we have it
|
/>
|
||||||
docsLink={'https://www.elastic.co/guide/index.html'}
|
</FullSizeCenteredPage>
|
||||||
logo={'logoSecurity'}
|
);
|
||||||
actions={{}}
|
|
||||||
/>
|
const subscriptionNotAllowedRenderer = () => (
|
||||||
</FullSizeCenteredPage>
|
<FullSizeCenteredPage data-test-subj={SUBSCRIPTION_NOT_ALLOWED_TEST_SUBJECT}>
|
||||||
);
|
<SubscriptionNotAllowed />
|
||||||
};
|
</FullSizeCenteredPage>
|
||||||
|
);
|
||||||
|
|
||||||
interface CloudPosturePageProps<TData, TError> {
|
interface CloudPosturePageProps<TData, TError> {
|
||||||
children: React.ReactNode;
|
children: React.ReactNode;
|
||||||
|
@ -158,10 +162,23 @@ export const CloudPosturePage = <TData, TError>({
|
||||||
errorRender = defaultErrorRenderer,
|
errorRender = defaultErrorRenderer,
|
||||||
noDataRenderer = defaultNoDataRenderer,
|
noDataRenderer = defaultNoDataRenderer,
|
||||||
}: CloudPosturePageProps<TData, TError>) => {
|
}: CloudPosturePageProps<TData, TError>) => {
|
||||||
|
const subscriptionStatus = useSubscriptionStatus();
|
||||||
const getSetupStatus = useCspSetupStatusApi();
|
const getSetupStatus = useCspSetupStatusApi();
|
||||||
const cisIntegrationLink = useCISIntegrationLink();
|
const cisIntegrationLink = useCISIntegrationLink();
|
||||||
|
|
||||||
const render = () => {
|
const render = () => {
|
||||||
|
if (subscriptionStatus.isError) {
|
||||||
|
return defaultErrorRenderer(subscriptionStatus.error);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (subscriptionStatus.isLoading) {
|
||||||
|
return defaultLoadingRenderer();
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!subscriptionStatus.data) {
|
||||||
|
return subscriptionNotAllowedRenderer();
|
||||||
|
}
|
||||||
|
|
||||||
if (getSetupStatus.isError) {
|
if (getSetupStatus.isError) {
|
||||||
return defaultErrorRenderer(getSetupStatus.error);
|
return defaultErrorRenderer(getSetupStatus.error);
|
||||||
}
|
}
|
||||||
|
|
|
@ -0,0 +1,52 @@
|
||||||
|
/*
|
||||||
|
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
|
||||||
|
* or more contributor license agreements. Licensed under the Elastic License
|
||||||
|
* 2.0; you may not use this file except in compliance with the Elastic License
|
||||||
|
* 2.0.
|
||||||
|
*/
|
||||||
|
|
||||||
|
import { EuiEmptyPrompt, EuiPageSection, EuiLink } from '@elastic/eui';
|
||||||
|
import { FormattedMessage } from '@kbn/i18n-react';
|
||||||
|
import React from 'react';
|
||||||
|
import { useKibana } from '../common/hooks/use_kibana';
|
||||||
|
|
||||||
|
export const SubscriptionNotAllowed = () => {
|
||||||
|
const { application } = useKibana().services;
|
||||||
|
return (
|
||||||
|
<EuiPageSection color="danger" alignment="center">
|
||||||
|
<EuiEmptyPrompt
|
||||||
|
iconType="alert"
|
||||||
|
title={
|
||||||
|
<h2>
|
||||||
|
<FormattedMessage
|
||||||
|
id="xpack.csp.subscriptionNotAllowed.promptTitle"
|
||||||
|
defaultMessage="Upgrade for subscription features"
|
||||||
|
/>
|
||||||
|
</h2>
|
||||||
|
}
|
||||||
|
body={
|
||||||
|
<p>
|
||||||
|
<FormattedMessage
|
||||||
|
id="xpack.csp.subscriptionNotAllowed.promptDescription"
|
||||||
|
defaultMessage="To use these cloud security features, you must {link}."
|
||||||
|
values={{
|
||||||
|
link: (
|
||||||
|
<EuiLink
|
||||||
|
href={application.getUrlForApp('management', {
|
||||||
|
path: 'stack/license_management/home',
|
||||||
|
})}
|
||||||
|
>
|
||||||
|
<FormattedMessage
|
||||||
|
id="xpack.csp.subscriptionNotAllowed.promptLinkText"
|
||||||
|
defaultMessage="start a trial or upgrade your subscription"
|
||||||
|
/>
|
||||||
|
</EuiLink>
|
||||||
|
),
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
</p>
|
||||||
|
}
|
||||||
|
/>
|
||||||
|
</EuiPageSection>
|
||||||
|
);
|
||||||
|
};
|
|
@ -15,10 +15,12 @@ import { Benchmarks } from './benchmarks';
|
||||||
import * as TEST_SUBJ from './test_subjects';
|
import * as TEST_SUBJ from './test_subjects';
|
||||||
import { useCspBenchmarkIntegrations } from './use_csp_benchmark_integrations';
|
import { useCspBenchmarkIntegrations } from './use_csp_benchmark_integrations';
|
||||||
import { useCspSetupStatusApi } from '../../common/api/use_setup_status_api';
|
import { useCspSetupStatusApi } from '../../common/api/use_setup_status_api';
|
||||||
|
import { useSubscriptionStatus } from '../../common/hooks/use_subscription_status';
|
||||||
import { useCISIntegrationLink } from '../../common/navigation/use_navigate_to_cis_integration';
|
import { useCISIntegrationLink } from '../../common/navigation/use_navigate_to_cis_integration';
|
||||||
|
|
||||||
jest.mock('./use_csp_benchmark_integrations');
|
jest.mock('./use_csp_benchmark_integrations');
|
||||||
jest.mock('../../common/api/use_setup_status_api');
|
jest.mock('../../common/api/use_setup_status_api');
|
||||||
|
jest.mock('../../common/hooks/use_subscription_status');
|
||||||
jest.mock('../../common/navigation/use_navigate_to_cis_integration');
|
jest.mock('../../common/navigation/use_navigate_to_cis_integration');
|
||||||
const chance = new Chance();
|
const chance = new Chance();
|
||||||
|
|
||||||
|
@ -31,6 +33,14 @@ describe('<Benchmarks />', () => {
|
||||||
data: { status: 'indexed' },
|
data: { status: 'indexed' },
|
||||||
})
|
})
|
||||||
);
|
);
|
||||||
|
|
||||||
|
(useSubscriptionStatus as jest.Mock).mockImplementation(() =>
|
||||||
|
createReactQueryResponse({
|
||||||
|
status: 'success',
|
||||||
|
data: true,
|
||||||
|
})
|
||||||
|
);
|
||||||
|
|
||||||
(useCISIntegrationLink as jest.Mock).mockImplementation(() => chance.url());
|
(useCISIntegrationLink as jest.Mock).mockImplementation(() => chance.url());
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
|
@ -12,6 +12,7 @@ import { render } from '@testing-library/react';
|
||||||
import { TestProvider } from '../../test/test_provider';
|
import { TestProvider } from '../../test/test_provider';
|
||||||
import { ComplianceDashboard } from '.';
|
import { ComplianceDashboard } from '.';
|
||||||
import { useCspSetupStatusApi } from '../../common/api/use_setup_status_api';
|
import { useCspSetupStatusApi } from '../../common/api/use_setup_status_api';
|
||||||
|
import { useSubscriptionStatus } from '../../common/hooks/use_subscription_status';
|
||||||
import { useComplianceDashboardDataApi } from '../../common/api/use_compliance_dashboard_data_api';
|
import { useComplianceDashboardDataApi } from '../../common/api/use_compliance_dashboard_data_api';
|
||||||
import { DASHBOARD_CONTAINER } from './test_subjects';
|
import { DASHBOARD_CONTAINER } from './test_subjects';
|
||||||
import { createReactQueryResponse } from '../../test/fixtures/react_query';
|
import { createReactQueryResponse } from '../../test/fixtures/react_query';
|
||||||
|
@ -22,6 +23,7 @@ import { expectIdsInDoc } from '../../test/utils';
|
||||||
|
|
||||||
jest.mock('../../common/api/use_setup_status_api');
|
jest.mock('../../common/api/use_setup_status_api');
|
||||||
jest.mock('../../common/api/use_compliance_dashboard_data_api');
|
jest.mock('../../common/api/use_compliance_dashboard_data_api');
|
||||||
|
jest.mock('../../common/hooks/use_subscription_status');
|
||||||
jest.mock('../../common/navigation/use_navigate_to_cis_integration_policies');
|
jest.mock('../../common/navigation/use_navigate_to_cis_integration_policies');
|
||||||
jest.mock('../../common/navigation/use_navigate_to_cis_integration');
|
jest.mock('../../common/navigation/use_navigate_to_cis_integration');
|
||||||
const chance = new Chance();
|
const chance = new Chance();
|
||||||
|
@ -179,6 +181,13 @@ describe('<ComplianceDashboard />', () => {
|
||||||
data: { status: 'indexed' },
|
data: { status: 'indexed' },
|
||||||
})
|
})
|
||||||
);
|
);
|
||||||
|
|
||||||
|
(useSubscriptionStatus as jest.Mock).mockImplementation(() =>
|
||||||
|
createReactQueryResponse({
|
||||||
|
status: 'success',
|
||||||
|
data: true,
|
||||||
|
})
|
||||||
|
);
|
||||||
});
|
});
|
||||||
|
|
||||||
const renderComplianceDashboardPage = () => {
|
const renderComplianceDashboardPage = () => {
|
||||||
|
|
|
@ -20,6 +20,7 @@ import type { DataView } from '@kbn/data-plugin/common';
|
||||||
import { chartPluginMock } from '@kbn/charts-plugin/public/mocks';
|
import { chartPluginMock } from '@kbn/charts-plugin/public/mocks';
|
||||||
import { discoverPluginMock } from '@kbn/discover-plugin/public/mocks';
|
import { discoverPluginMock } from '@kbn/discover-plugin/public/mocks';
|
||||||
import { useCspSetupStatusApi } from '../../common/api/use_setup_status_api';
|
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 { createReactQueryResponse } from '../../test/fixtures/react_query';
|
||||||
import { useCISIntegrationPoliciesLink } from '../../common/navigation/use_navigate_to_cis_integration_policies';
|
import { useCISIntegrationPoliciesLink } from '../../common/navigation/use_navigate_to_cis_integration_policies';
|
||||||
import { useCISIntegrationLink } from '../../common/navigation/use_navigate_to_cis_integration';
|
import { useCISIntegrationLink } from '../../common/navigation/use_navigate_to_cis_integration';
|
||||||
|
@ -28,9 +29,11 @@ import { render } from '@testing-library/react';
|
||||||
import { useFindingsEsPit } from './es_pit/use_findings_es_pit';
|
import { useFindingsEsPit } from './es_pit/use_findings_es_pit';
|
||||||
import { expectIdsInDoc } from '../../test/utils';
|
import { expectIdsInDoc } from '../../test/utils';
|
||||||
import { fleetMock } from '@kbn/fleet-plugin/public/mocks';
|
import { fleetMock } from '@kbn/fleet-plugin/public/mocks';
|
||||||
|
import { licensingMock } from '@kbn/licensing-plugin/public/mocks';
|
||||||
|
|
||||||
jest.mock('../../common/api/use_latest_findings_data_view');
|
jest.mock('../../common/api/use_latest_findings_data_view');
|
||||||
jest.mock('../../common/api/use_setup_status_api');
|
jest.mock('../../common/api/use_setup_status_api');
|
||||||
|
jest.mock('../../common/hooks/use_subscription_status');
|
||||||
jest.mock('../../common/navigation/use_navigate_to_cis_integration_policies');
|
jest.mock('../../common/navigation/use_navigate_to_cis_integration_policies');
|
||||||
jest.mock('../../common/navigation/use_navigate_to_cis_integration');
|
jest.mock('../../common/navigation/use_navigate_to_cis_integration');
|
||||||
jest.mock('./es_pit/use_findings_es_pit');
|
jest.mock('./es_pit/use_findings_es_pit');
|
||||||
|
@ -46,6 +49,13 @@ beforeEach(() => {
|
||||||
setPitId: () => {},
|
setPitId: () => {},
|
||||||
pitIdRef: chance.guid(),
|
pitIdRef: chance.guid(),
|
||||||
}));
|
}));
|
||||||
|
|
||||||
|
(useSubscriptionStatus as jest.Mock).mockImplementation(() =>
|
||||||
|
createReactQueryResponse({
|
||||||
|
status: 'success',
|
||||||
|
data: true,
|
||||||
|
})
|
||||||
|
);
|
||||||
});
|
});
|
||||||
|
|
||||||
const renderFindingsPage = () => {
|
const renderFindingsPage = () => {
|
||||||
|
@ -57,6 +67,7 @@ const renderFindingsPage = () => {
|
||||||
charts: chartPluginMock.createStartContract(),
|
charts: chartPluginMock.createStartContract(),
|
||||||
discover: discoverPluginMock.createStartContract(),
|
discover: discoverPluginMock.createStartContract(),
|
||||||
fleet: fleetMock.createStartMock(),
|
fleet: fleetMock.createStartMock(),
|
||||||
|
licensing: licensingMock.createStart(),
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<Findings />
|
<Findings />
|
||||||
|
|
|
@ -24,6 +24,7 @@ import { FindingsEsPitContext } from '../es_pit/findings_es_pit_context';
|
||||||
import { chartPluginMock } from '@kbn/charts-plugin/public/mocks';
|
import { chartPluginMock } from '@kbn/charts-plugin/public/mocks';
|
||||||
import { discoverPluginMock } from '@kbn/discover-plugin/public/mocks';
|
import { discoverPluginMock } from '@kbn/discover-plugin/public/mocks';
|
||||||
import { fleetMock } from '@kbn/fleet-plugin/public/mocks';
|
import { fleetMock } from '@kbn/fleet-plugin/public/mocks';
|
||||||
|
import { licensingMock } from '@kbn/licensing-plugin/public/mocks';
|
||||||
|
|
||||||
jest.mock('../../../common/api/use_latest_findings_data_view');
|
jest.mock('../../../common/api/use_latest_findings_data_view');
|
||||||
jest.mock('../../../common/api/use_cis_kubernetes_integration');
|
jest.mock('../../../common/api/use_cis_kubernetes_integration');
|
||||||
|
@ -70,6 +71,7 @@ describe('<LatestFindingsContainer />', () => {
|
||||||
charts: chartPluginMock.createStartContract(),
|
charts: chartPluginMock.createStartContract(),
|
||||||
discover: discoverPluginMock.createStartContract(),
|
discover: discoverPluginMock.createStartContract(),
|
||||||
fleet: fleetMock.createStartMock(),
|
fleet: fleetMock.createStartMock(),
|
||||||
|
licensing: licensingMock.createStart(),
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<FindingsEsPitContext.Provider value={{ setPitId, pitIdRef, pitQuery }}>
|
<FindingsEsPitContext.Provider value={{ setPitId, pitIdRef, pitQuery }}>
|
||||||
|
|
|
@ -18,12 +18,14 @@ import * as TEST_SUBJECTS from './test_subjects';
|
||||||
import { createReactQueryResponse } from '../../test/fixtures/react_query';
|
import { createReactQueryResponse } from '../../test/fixtures/react_query';
|
||||||
import { coreMock } from '@kbn/core/public/mocks';
|
import { coreMock } from '@kbn/core/public/mocks';
|
||||||
import { useCspSetupStatusApi } from '../../common/api/use_setup_status_api';
|
import { useCspSetupStatusApi } from '../../common/api/use_setup_status_api';
|
||||||
|
import { useSubscriptionStatus } from '../../common/hooks/use_subscription_status';
|
||||||
import { useCISIntegrationLink } from '../../common/navigation/use_navigate_to_cis_integration';
|
import { useCISIntegrationLink } from '../../common/navigation/use_navigate_to_cis_integration';
|
||||||
|
|
||||||
jest.mock('./use_csp_integration', () => ({
|
jest.mock('./use_csp_integration', () => ({
|
||||||
useCspIntegrationInfo: jest.fn(),
|
useCspIntegrationInfo: jest.fn(),
|
||||||
}));
|
}));
|
||||||
jest.mock('../../common/api/use_setup_status_api');
|
jest.mock('../../common/api/use_setup_status_api');
|
||||||
|
jest.mock('../../common/hooks/use_subscription_status');
|
||||||
jest.mock('../../common/navigation/use_navigate_to_cis_integration');
|
jest.mock('../../common/navigation/use_navigate_to_cis_integration');
|
||||||
const chance = new Chance();
|
const chance = new Chance();
|
||||||
|
|
||||||
|
@ -68,6 +70,14 @@ describe('<Rules />', () => {
|
||||||
data: { status: 'indexed' },
|
data: { status: 'indexed' },
|
||||||
})
|
})
|
||||||
);
|
);
|
||||||
|
|
||||||
|
(useSubscriptionStatus as jest.Mock).mockImplementation(() =>
|
||||||
|
createReactQueryResponse({
|
||||||
|
status: 'success',
|
||||||
|
data: true,
|
||||||
|
})
|
||||||
|
);
|
||||||
|
|
||||||
(useCISIntegrationLink as jest.Mock).mockImplementation(() => chance.url());
|
(useCISIntegrationLink as jest.Mock).mockImplementation(() => chance.url());
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
|
@ -17,6 +17,7 @@ import type {
|
||||||
CspClientPluginStartDeps,
|
CspClientPluginStartDeps,
|
||||||
} from './types';
|
} from './types';
|
||||||
import { CLOUD_SECURITY_POSTURE_PACKAGE_NAME } from '../common/constants';
|
import { CLOUD_SECURITY_POSTURE_PACKAGE_NAME } from '../common/constants';
|
||||||
|
import { SetupContext } from './application/setup_context';
|
||||||
|
|
||||||
const LazyCspEditPolicy = lazy(() => import('./components/fleet_extensions/policy_extension_edit'));
|
const LazyCspEditPolicy = lazy(() => import('./components/fleet_extensions/policy_extension_edit'));
|
||||||
const LazyCspCreatePolicy = lazy(
|
const LazyCspCreatePolicy = lazy(
|
||||||
|
@ -42,10 +43,13 @@ export class CspPlugin
|
||||||
CspClientPluginStartDeps
|
CspClientPluginStartDeps
|
||||||
>
|
>
|
||||||
{
|
{
|
||||||
|
private isCloudEnabled?: boolean;
|
||||||
|
|
||||||
public setup(
|
public setup(
|
||||||
core: CoreSetup<CspClientPluginStartDeps, CspClientPluginStart>,
|
core: CoreSetup<CspClientPluginStartDeps, CspClientPluginStart>,
|
||||||
plugins: CspClientPluginSetupDeps
|
plugins: CspClientPluginSetupDeps
|
||||||
): CspClientPluginSetup {
|
): CspClientPluginSetup {
|
||||||
|
this.isCloudEnabled = plugins.cloud.isCloudEnabled;
|
||||||
// Return methods that should be available to other plugins
|
// Return methods that should be available to other plugins
|
||||||
return {};
|
return {};
|
||||||
}
|
}
|
||||||
|
@ -74,7 +78,9 @@ export class CspPlugin
|
||||||
(
|
(
|
||||||
<KibanaContextProvider services={{ ...core, ...plugins }}>
|
<KibanaContextProvider services={{ ...core, ...plugins }}>
|
||||||
<RedirectAppLinks coreStart={core}>
|
<RedirectAppLinks coreStart={core}>
|
||||||
<CspRouter {...props} />
|
<SetupContext.Provider value={{ isCloudEnabled: this.isCloudEnabled }}>
|
||||||
|
<CspRouter {...props} />
|
||||||
|
</SetupContext.Provider>
|
||||||
</RedirectAppLinks>
|
</RedirectAppLinks>
|
||||||
</KibanaContextProvider>
|
</KibanaContextProvider>
|
||||||
),
|
),
|
||||||
|
|
|
@ -17,6 +17,7 @@ import { chartPluginMock } from '@kbn/charts-plugin/public/mocks';
|
||||||
import { unifiedSearchPluginMock } from '@kbn/unified-search-plugin/public/mocks';
|
import { unifiedSearchPluginMock } from '@kbn/unified-search-plugin/public/mocks';
|
||||||
import { discoverPluginMock } from '@kbn/discover-plugin/public/mocks';
|
import { discoverPluginMock } from '@kbn/discover-plugin/public/mocks';
|
||||||
import { fleetMock } from '@kbn/fleet-plugin/public/mocks';
|
import { fleetMock } from '@kbn/fleet-plugin/public/mocks';
|
||||||
|
import { licensingMock } from '@kbn/licensing-plugin/public/mocks';
|
||||||
import type { CspClientPluginStartDeps } from '../types';
|
import type { CspClientPluginStartDeps } from '../types';
|
||||||
|
|
||||||
interface CspAppDeps {
|
interface CspAppDeps {
|
||||||
|
@ -33,6 +34,7 @@ export const TestProvider: React.FC<Partial<CspAppDeps>> = ({
|
||||||
charts: chartPluginMock.createStartContract(),
|
charts: chartPluginMock.createStartContract(),
|
||||||
discover: discoverPluginMock.createStartContract(),
|
discover: discoverPluginMock.createStartContract(),
|
||||||
fleet: fleetMock.createStartMock(),
|
fleet: fleetMock.createStartMock(),
|
||||||
|
licensing: licensingMock.createStart(),
|
||||||
},
|
},
|
||||||
params = coreMock.createAppMountParameters(),
|
params = coreMock.createAppMountParameters(),
|
||||||
children,
|
children,
|
||||||
|
|
|
@ -5,6 +5,8 @@
|
||||||
* 2.0.
|
* 2.0.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
|
import type { CloudSetup } from '@kbn/cloud-plugin/public';
|
||||||
|
import type { LicensingPluginStart } from '@kbn/licensing-plugin/public';
|
||||||
import type { ComponentType, ReactNode } from 'react';
|
import type { ComponentType, ReactNode } from 'react';
|
||||||
import type { UnifiedSearchPublicPluginStart } from '@kbn/unified-search-plugin/public';
|
import type { UnifiedSearchPublicPluginStart } from '@kbn/unified-search-plugin/public';
|
||||||
import type { DataPublicPluginSetup, DataPublicPluginStart } from '@kbn/data-plugin/public';
|
import type { DataPublicPluginSetup, DataPublicPluginStart } from '@kbn/data-plugin/public';
|
||||||
|
@ -32,6 +34,7 @@ export interface CspClientPluginSetupDeps {
|
||||||
// required
|
// required
|
||||||
data: DataPublicPluginSetup;
|
data: DataPublicPluginSetup;
|
||||||
fleet: FleetSetup;
|
fleet: FleetSetup;
|
||||||
|
cloud: CloudSetup;
|
||||||
// optional
|
// optional
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -42,6 +45,7 @@ export interface CspClientPluginStartDeps {
|
||||||
charts: ChartsPluginStart;
|
charts: ChartsPluginStart;
|
||||||
discover: DiscoverStart;
|
discover: DiscoverStart;
|
||||||
fleet: FleetStart;
|
fleet: FleetStart;
|
||||||
|
licensing: LicensingPluginStart;
|
||||||
// optional
|
// optional
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -20,6 +20,7 @@ import {
|
||||||
import { DeepReadonly } from 'utility-types';
|
import { DeepReadonly } from 'utility-types';
|
||||||
import { createCspRuleSearchFilterByPackagePolicy } from '../../common/utils/helpers';
|
import { createCspRuleSearchFilterByPackagePolicy } from '../../common/utils/helpers';
|
||||||
import {
|
import {
|
||||||
|
CLOUD_SECURITY_POSTURE_PACKAGE_NAME,
|
||||||
CLOUDBEAT_VANILLA,
|
CLOUDBEAT_VANILLA,
|
||||||
CSP_RULE_SAVED_OBJECT_TYPE,
|
CSP_RULE_SAVED_OBJECT_TYPE,
|
||||||
CSP_RULE_TEMPLATE_SAVED_OBJECT_TYPE,
|
CSP_RULE_TEMPLATE_SAVED_OBJECT_TYPE,
|
||||||
|
@ -127,6 +128,9 @@ export const isCspPackageInstalled = async (
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
export const isCspPackage = (packageName?: string) =>
|
||||||
|
packageName === CLOUD_SECURITY_POSTURE_PACKAGE_NAME;
|
||||||
|
|
||||||
const generateRulesFromTemplates = (
|
const generateRulesFromTemplates = (
|
||||||
packagePolicyId: string,
|
packagePolicyId: string,
|
||||||
policyId: string,
|
policyId: string,
|
||||||
|
|
|
@ -41,6 +41,7 @@ import {
|
||||||
SavedObjectsClientContract,
|
SavedObjectsClientContract,
|
||||||
} from '@kbn/core/server';
|
} from '@kbn/core/server';
|
||||||
import { securityMock } from '@kbn/security-plugin/server/mocks';
|
import { securityMock } from '@kbn/security-plugin/server/mocks';
|
||||||
|
import { licensingMock } from '@kbn/licensing-plugin/server/mocks';
|
||||||
|
|
||||||
const chance = new Chance();
|
const chance = new Chance();
|
||||||
|
|
||||||
|
@ -76,6 +77,7 @@ describe('Cloud Security Posture Plugin', () => {
|
||||||
data: dataPluginMock.createStartContract(),
|
data: dataPluginMock.createStartContract(),
|
||||||
taskManager: taskManagerMock.createStart(),
|
taskManager: taskManagerMock.createStart(),
|
||||||
security: securityMock.createStart(),
|
security: securityMock.createStart(),
|
||||||
|
licensing: licensingMock.createStart(),
|
||||||
};
|
};
|
||||||
|
|
||||||
const contextMock = coreMock.createCustomRequestHandlerContext(mockRouteContext);
|
const contextMock = coreMock.createCustomRequestHandlerContext(mockRouteContext);
|
||||||
|
|
|
@ -14,12 +14,17 @@ import type {
|
||||||
Plugin,
|
Plugin,
|
||||||
Logger,
|
Logger,
|
||||||
} from '@kbn/core/server';
|
} from '@kbn/core/server';
|
||||||
import { DeepReadonly } from 'utility-types';
|
import type { DeepReadonly } from 'utility-types';
|
||||||
import { DeletePackagePoliciesResponse, PackagePolicy } from '@kbn/fleet-plugin/common';
|
import type {
|
||||||
import {
|
DeletePackagePoliciesResponse,
|
||||||
|
PackagePolicy,
|
||||||
|
NewPackagePolicy,
|
||||||
|
} from '@kbn/fleet-plugin/common';
|
||||||
|
import type {
|
||||||
TaskManagerSetupContract,
|
TaskManagerSetupContract,
|
||||||
TaskManagerStartContract,
|
TaskManagerStartContract,
|
||||||
} from '@kbn/task-manager-plugin/server';
|
} from '@kbn/task-manager-plugin/server';
|
||||||
|
import { isSubscriptionAllowed } from '../common/utils/subscription';
|
||||||
import type {
|
import type {
|
||||||
CspServerPluginSetup,
|
CspServerPluginSetup,
|
||||||
CspServerPluginStart,
|
CspServerPluginStart,
|
||||||
|
@ -32,6 +37,7 @@ import { setupSavedObjects } from './saved_objects';
|
||||||
import { initializeCspIndices } from './create_indices/create_indices';
|
import { initializeCspIndices } from './create_indices/create_indices';
|
||||||
import { initializeCspTransforms } from './create_transforms/create_transforms';
|
import { initializeCspTransforms } from './create_transforms/create_transforms';
|
||||||
import {
|
import {
|
||||||
|
isCspPackage,
|
||||||
isCspPackageInstalled,
|
isCspPackageInstalled,
|
||||||
onPackagePolicyPostCreateCallback,
|
onPackagePolicyPostCreateCallback,
|
||||||
removeCspRulesInstancesCallback,
|
removeCspRulesInstancesCallback,
|
||||||
|
@ -41,7 +47,6 @@ import {
|
||||||
updatePackagePolicyRuntimeCfgVar,
|
updatePackagePolicyRuntimeCfgVar,
|
||||||
getCspRulesSO,
|
getCspRulesSO,
|
||||||
} from './routes/configuration/update_rules_configuration';
|
} from './routes/configuration/update_rules_configuration';
|
||||||
|
|
||||||
import {
|
import {
|
||||||
removeFindingsStatsTask,
|
removeFindingsStatsTask,
|
||||||
scheduleFindingsStatsTask,
|
scheduleFindingsStatsTask,
|
||||||
|
@ -58,6 +63,7 @@ export class CspPlugin
|
||||||
>
|
>
|
||||||
{
|
{
|
||||||
private readonly logger: Logger;
|
private readonly logger: Logger;
|
||||||
|
private isCloudEnabled?: boolean;
|
||||||
|
|
||||||
constructor(initializerContext: PluginInitializerContext) {
|
constructor(initializerContext: PluginInitializerContext) {
|
||||||
this.logger = initializerContext.logger.get();
|
this.logger = initializerContext.logger.get();
|
||||||
|
@ -77,6 +83,8 @@ export class CspPlugin
|
||||||
const coreStartServices = core.getStartServices();
|
const coreStartServices = core.getStartServices();
|
||||||
this.setupCspTasks(plugins.taskManager, coreStartServices, this.logger);
|
this.setupCspTasks(plugins.taskManager, coreStartServices, this.logger);
|
||||||
|
|
||||||
|
this.isCloudEnabled = plugins.cloud.isCloudEnabled;
|
||||||
|
|
||||||
return {};
|
return {};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -92,6 +100,26 @@ export class CspPlugin
|
||||||
this.initialize(core, plugins.taskManager);
|
this.initialize(core, plugins.taskManager);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
plugins.fleet.registerExternalCallback(
|
||||||
|
'packagePolicyCreate',
|
||||||
|
async (
|
||||||
|
packagePolicy: NewPackagePolicy,
|
||||||
|
_context: RequestHandlerContext,
|
||||||
|
_request: KibanaRequest
|
||||||
|
): Promise<NewPackagePolicy> => {
|
||||||
|
const license = await plugins.licensing.refresh();
|
||||||
|
if (isCspPackage(packagePolicy.package?.name)) {
|
||||||
|
if (!isSubscriptionAllowed(this.isCloudEnabled, license)) {
|
||||||
|
throw new Error(
|
||||||
|
'To use this feature you must upgrade your subscription or start a trial'
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return packagePolicy;
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
plugins.fleet.registerExternalCallback(
|
plugins.fleet.registerExternalCallback(
|
||||||
'packagePolicyPostCreate',
|
'packagePolicyPostCreate',
|
||||||
async (
|
async (
|
||||||
|
@ -99,7 +127,7 @@ export class CspPlugin
|
||||||
context: RequestHandlerContext,
|
context: RequestHandlerContext,
|
||||||
request: KibanaRequest
|
request: KibanaRequest
|
||||||
): Promise<PackagePolicy> => {
|
): Promise<PackagePolicy> => {
|
||||||
if (packagePolicy.package?.name === CLOUD_SECURITY_POSTURE_PACKAGE_NAME) {
|
if (isCspPackage(packagePolicy.package?.name)) {
|
||||||
await this.initialize(core, plugins.taskManager);
|
await this.initialize(core, plugins.taskManager);
|
||||||
|
|
||||||
const soClient = (await context.core).savedObjects.client;
|
const soClient = (await context.core).savedObjects.client;
|
||||||
|
@ -128,7 +156,7 @@ export class CspPlugin
|
||||||
'postPackagePolicyDelete',
|
'postPackagePolicyDelete',
|
||||||
async (deletedPackagePolicies: DeepReadonly<DeletePackagePoliciesResponse>) => {
|
async (deletedPackagePolicies: DeepReadonly<DeletePackagePoliciesResponse>) => {
|
||||||
for (const deletedPackagePolicy of deletedPackagePolicies) {
|
for (const deletedPackagePolicy of deletedPackagePolicies) {
|
||||||
if (deletedPackagePolicy.package?.name === CLOUD_SECURITY_POSTURE_PACKAGE_NAME) {
|
if (isCspPackage(deletedPackagePolicy.package?.name)) {
|
||||||
const soClient = core.savedObjects.createInternalRepository();
|
const soClient = core.savedObjects.createInternalRepository();
|
||||||
await removeCspRulesInstancesCallback(deletedPackagePolicy, soClient, this.logger);
|
await removeCspRulesInstancesCallback(deletedPackagePolicy, soClient, this.logger);
|
||||||
|
|
||||||
|
|
|
@ -4,10 +4,12 @@
|
||||||
* 2.0; you may not use this file except in compliance with the Elastic License
|
* 2.0; you may not use this file except in compliance with the Elastic License
|
||||||
* 2.0.
|
* 2.0.
|
||||||
*/
|
*/
|
||||||
|
import type { CloudSetup } from '@kbn/cloud-plugin/server';
|
||||||
import type {
|
import type {
|
||||||
PluginSetup as DataPluginSetup,
|
PluginSetup as DataPluginSetup,
|
||||||
PluginStart as DataPluginStart,
|
PluginStart as DataPluginStart,
|
||||||
} from '@kbn/data-plugin/server';
|
} from '@kbn/data-plugin/server';
|
||||||
|
import type { LicensingPluginStart } from '@kbn/licensing-plugin/server';
|
||||||
import {
|
import {
|
||||||
TaskManagerSetupContract,
|
TaskManagerSetupContract,
|
||||||
TaskManagerStartContract,
|
TaskManagerStartContract,
|
||||||
|
@ -42,6 +44,7 @@ export interface CspServerPluginSetupDeps {
|
||||||
data: DataPluginSetup;
|
data: DataPluginSetup;
|
||||||
taskManager: TaskManagerSetupContract;
|
taskManager: TaskManagerSetupContract;
|
||||||
security: SecurityPluginSetup;
|
security: SecurityPluginSetup;
|
||||||
|
cloud: CloudSetup;
|
||||||
// optional
|
// optional
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -51,6 +54,7 @@ export interface CspServerPluginStartDeps {
|
||||||
fleet: FleetStartContract;
|
fleet: FleetStartContract;
|
||||||
taskManager: TaskManagerStartContract;
|
taskManager: TaskManagerStartContract;
|
||||||
security: SecurityPluginStart;
|
security: SecurityPluginStart;
|
||||||
|
licensing: LicensingPluginStart;
|
||||||
}
|
}
|
||||||
|
|
||||||
export type CspServerPluginStartServices = Promise<
|
export type CspServerPluginStartServices = Promise<
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue