mirror of
https://github.com/elastic/kibana.git
synced 2025-04-24 01:38:56 -04:00
[Security Solution][Endpoint] Add Endpoint with event collection only to Serverless Security Essentials PLI (#162927)
## Summary - Adds Endpoint Management and Policy Management to the base Security Essentials Product Line Item in serverless - Removes access to Endpoint policy protections (Malware, Ransomware, etc) from the policy form when endpoint is being used without the Endpoint Essentials/Complete addon
This commit is contained in:
parent
0069062fb4
commit
3efc73ca85
42 changed files with 543 additions and 132 deletions
1
.github/CODEOWNERS
vendored
1
.github/CODEOWNERS
vendored
|
@ -1242,6 +1242,7 @@ x-pack/plugins/cloud_integrations/cloud_full_story/server/config.ts @elastic/kib
|
|||
/x-pack/test_serverless/functional/test_suites/security/cypress/e2e/endpoint_management @elastic/security-defend-workflows
|
||||
/x-pack/test_serverless/functional/test_suites/security/cypress/screens/endpoint_management @elastic/security-defend-workflows
|
||||
/x-pack/test_serverless/functional/test_suites/security/cypress/tasks/endpoint_management @elastic/security-defend-workflows
|
||||
/x-pack/plugins/security_solution_serverless/public/upselling/sections/endpoint_management @elastic/security-defend-workflows
|
||||
|
||||
## Security Solution sub teams - security-telemetry (Data Engineering)
|
||||
x-pack/plugins/security_solution/server/usage/ @elastic/security-data-analytics
|
||||
|
|
|
@ -16,8 +16,9 @@ import type {
|
|||
DeleteAgentPolicyResponse,
|
||||
PostDeletePackagePoliciesResponse,
|
||||
} from '@kbn/fleet-plugin/common';
|
||||
import { kibanaPackageJson } from '@kbn/repo-info';
|
||||
import { AGENT_POLICY_API_ROUTES, PACKAGE_POLICY_API_ROUTES } from '@kbn/fleet-plugin/common';
|
||||
import { memoize } from 'lodash';
|
||||
import { getEndpointPackageInfo } from '../utils/package';
|
||||
import type { PolicyData } from '../types';
|
||||
import { policyFactory as policyConfigFactory } from '../models/policy_config';
|
||||
import { wrapErrorAndRejectPromise } from './utils';
|
||||
|
@ -34,7 +35,7 @@ export interface IndexedFleetEndpointPolicyResponse {
|
|||
export const indexFleetEndpointPolicy = async (
|
||||
kbnClient: KbnClient,
|
||||
policyName: string,
|
||||
endpointPackageVersion: string = kibanaPackageJson.version,
|
||||
endpointPackageVersion?: string,
|
||||
agentPolicyName?: string
|
||||
): Promise<IndexedFleetEndpointPolicyResponse> => {
|
||||
const response: IndexedFleetEndpointPolicyResponse = {
|
||||
|
@ -42,6 +43,9 @@ export const indexFleetEndpointPolicy = async (
|
|||
agentPolicies: [],
|
||||
};
|
||||
|
||||
const packageVersion =
|
||||
endpointPackageVersion ?? (await getDefaultEndpointPackageVersion(kbnClient));
|
||||
|
||||
// Create Agent Policy first
|
||||
const newAgentPolicyData: CreateAgentPolicyRequest['body'] = {
|
||||
name:
|
||||
|
@ -89,7 +93,7 @@ export const indexFleetEndpointPolicy = async (
|
|||
package: {
|
||||
name: 'endpoint',
|
||||
title: 'Elastic Defend',
|
||||
version: endpointPackageVersion,
|
||||
version: packageVersion,
|
||||
},
|
||||
};
|
||||
const packagePolicy = (await kbnClient
|
||||
|
@ -162,3 +166,12 @@ export const deleteIndexedFleetEndpointPolicies = async (
|
|||
|
||||
return response;
|
||||
};
|
||||
|
||||
const getDefaultEndpointPackageVersion = memoize(
|
||||
async (kbnClient: KbnClient) => {
|
||||
return (await getEndpointPackageInfo(kbnClient)).version;
|
||||
},
|
||||
(kbnClient: KbnClient) => {
|
||||
return kbnClient.resolveUrl('/');
|
||||
}
|
||||
);
|
||||
|
|
|
@ -27,6 +27,11 @@ export enum AppFeatureSecurityKey {
|
|||
*/
|
||||
endpointPolicyManagement = 'endpoint_policy_management',
|
||||
|
||||
/**
|
||||
* Enables Endpoint Policy protections (like Malware, Ransomware, etc)
|
||||
*/
|
||||
endpointPolicyProtections = 'endpoint_policy_protections',
|
||||
|
||||
/**
|
||||
* Enables management of all endpoint related artifacts (ex. Trusted Applications, Event Filters,
|
||||
* Host Isolation Exceptions, Blocklist.
|
||||
|
|
|
@ -20,6 +20,7 @@ import { EuiThemeProvider } from '@kbn/kibana-react-plugin/common';
|
|||
import { CellActionsProvider } from '@kbn/cell-actions';
|
||||
|
||||
import { NavigationProvider } from '@kbn/security-solution-navigation';
|
||||
import { UpsellingProvider } from '../common/components/upselling_provider';
|
||||
import { getComments } from '../assistant/get_comments';
|
||||
import { augmentMessageCodeBlocks, LOCAL_STORAGE_KEY } from '../assistant/helpers';
|
||||
import { useConversationStore } from '../assistant/use_conversation_store';
|
||||
|
@ -65,6 +66,7 @@ const StartAppComponent: FC<StartAppComponent> = ({
|
|||
http,
|
||||
triggersActionsUi: { actionTypeRegistry },
|
||||
uiActions,
|
||||
upselling,
|
||||
} = services;
|
||||
|
||||
const { conversations, setConversations } = useConversationStore();
|
||||
|
@ -115,13 +117,15 @@ const StartAppComponent: FC<StartAppComponent> = ({
|
|||
<CellActionsProvider
|
||||
getTriggerCompatibleActions={uiActions.getTriggerCompatibleActions}
|
||||
>
|
||||
<PageRouter
|
||||
history={history}
|
||||
onAppLeave={onAppLeave}
|
||||
setHeaderActionMenu={setHeaderActionMenu}
|
||||
>
|
||||
{children}
|
||||
</PageRouter>
|
||||
<UpsellingProvider upsellingService={upselling}>
|
||||
<PageRouter
|
||||
history={history}
|
||||
onAppLeave={onAppLeave}
|
||||
setHeaderActionMenu={setHeaderActionMenu}
|
||||
>
|
||||
{children}
|
||||
</PageRouter>
|
||||
</UpsellingProvider>
|
||||
</CellActionsProvider>
|
||||
</ReactQueryClientProvider>
|
||||
</NavigationProvider>
|
||||
|
|
|
@ -0,0 +1,8 @@
|
|||
/*
|
||||
* 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.
|
||||
*/
|
||||
|
||||
export * from './upselling_provider';
|
|
@ -0,0 +1,34 @@
|
|||
/*
|
||||
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
|
||||
* or more contributor license agreements. Licensed under the Elastic License
|
||||
* 2.0; you may not use this file except in compliance with the Elastic License
|
||||
* 2.0.
|
||||
*/
|
||||
|
||||
import React, { memo, useContext } from 'react';
|
||||
import type { UpsellingService } from '../../..';
|
||||
|
||||
export const UpsellingProviderContext = React.createContext<UpsellingService | null>(null);
|
||||
|
||||
export type UpsellingProviderProps = React.PropsWithChildren<{
|
||||
upsellingService: UpsellingService;
|
||||
}>;
|
||||
|
||||
export const UpsellingProvider = memo<UpsellingProviderProps>(({ upsellingService, children }) => {
|
||||
return (
|
||||
<UpsellingProviderContext.Provider value={upsellingService}>
|
||||
{children}
|
||||
</UpsellingProviderContext.Provider>
|
||||
);
|
||||
});
|
||||
UpsellingProvider.displayName = 'UpsellingProvider';
|
||||
|
||||
export const useUpsellingService = (): UpsellingService => {
|
||||
const upsellingService = useContext(UpsellingProviderContext);
|
||||
|
||||
if (!upsellingService) {
|
||||
throw new Error('UpsellingProviderContext not found');
|
||||
}
|
||||
|
||||
return upsellingService;
|
||||
};
|
|
@ -10,6 +10,7 @@ import React from 'react';
|
|||
import { SecurityPageName } from '../../../common';
|
||||
import { UpsellingService } from '../lib/upsellings';
|
||||
import { useUpsellingComponent, useUpsellingMessage, useUpsellingPage } from './use_upselling';
|
||||
import { UpsellingProvider } from '../components/upselling_provider';
|
||||
|
||||
const mockUpselling = new UpsellingService();
|
||||
|
||||
|
@ -28,6 +29,9 @@ jest.mock('../lib/kibana', () => {
|
|||
});
|
||||
|
||||
const TestComponent = () => <div>{'TEST 1 2 3'}</div>;
|
||||
const RenderWrapper: React.FunctionComponent = ({ children }) => {
|
||||
return <UpsellingProvider upsellingService={mockUpselling}>{children}</UpsellingProvider>;
|
||||
};
|
||||
|
||||
describe('use_upselling', () => {
|
||||
test('useUpsellingComponent returns sections', () => {
|
||||
|
@ -35,7 +39,9 @@ describe('use_upselling', () => {
|
|||
entity_analytics_panel: TestComponent,
|
||||
});
|
||||
|
||||
const { result } = renderHook(() => useUpsellingComponent('entity_analytics_panel'));
|
||||
const { result } = renderHook(() => useUpsellingComponent('entity_analytics_panel'), {
|
||||
wrapper: RenderWrapper,
|
||||
});
|
||||
expect(result.current).toBe(TestComponent);
|
||||
});
|
||||
|
||||
|
@ -44,7 +50,9 @@ describe('use_upselling', () => {
|
|||
[SecurityPageName.hosts]: TestComponent,
|
||||
});
|
||||
|
||||
const { result } = renderHook(() => useUpsellingPage(SecurityPageName.hosts));
|
||||
const { result } = renderHook(() => useUpsellingPage(SecurityPageName.hosts), {
|
||||
wrapper: RenderWrapper,
|
||||
});
|
||||
expect(result.current).toBe(TestComponent);
|
||||
});
|
||||
|
||||
|
@ -54,7 +62,9 @@ describe('use_upselling', () => {
|
|||
investigation_guide: testMessage,
|
||||
});
|
||||
|
||||
const { result } = renderHook(() => useUpsellingMessage('investigation_guide'));
|
||||
const { result } = renderHook(() => useUpsellingMessage('investigation_guide'), {
|
||||
wrapper: RenderWrapper,
|
||||
});
|
||||
expect(result.current).toBe(testMessage);
|
||||
});
|
||||
|
||||
|
@ -62,8 +72,11 @@ describe('use_upselling', () => {
|
|||
const emptyMessages = {};
|
||||
mockUpselling.registerMessages(emptyMessages);
|
||||
|
||||
const { result } = renderHook(() =>
|
||||
useUpsellingMessage('my_fake_message_id' as 'investigation_guide')
|
||||
const { result } = renderHook(
|
||||
() => useUpsellingMessage('my_fake_message_id' as 'investigation_guide'),
|
||||
{
|
||||
wrapper: RenderWrapper,
|
||||
}
|
||||
);
|
||||
expect(result.current).toBe(null);
|
||||
});
|
||||
|
|
|
@ -7,27 +7,28 @@
|
|||
|
||||
import { useMemo } from 'react';
|
||||
import useObservable from 'react-use/lib/useObservable';
|
||||
import type React from 'react';
|
||||
import { useUpsellingService } from '../components/upselling_provider';
|
||||
import type { UpsellingSectionId } from '../lib/upsellings';
|
||||
import { useKibana } from '../lib/kibana';
|
||||
import type { SecurityPageName } from '../../../common';
|
||||
import type { UpsellingMessageId } from '../lib/upsellings/types';
|
||||
|
||||
export const useUpsellingComponent = (id: UpsellingSectionId): React.ComponentType | null => {
|
||||
const { upselling } = useKibana().services;
|
||||
const upselling = useUpsellingService();
|
||||
const upsellingSections = useObservable(upselling.sections$);
|
||||
|
||||
return useMemo(() => upsellingSections?.get(id) ?? null, [id, upsellingSections]);
|
||||
};
|
||||
|
||||
export const useUpsellingMessage = (id: UpsellingMessageId): string | null => {
|
||||
const { upselling } = useKibana().services;
|
||||
const upselling = useUpsellingService();
|
||||
const upsellingMessages = useObservable(upselling.messages$);
|
||||
|
||||
return useMemo(() => upsellingMessages?.get(id) ?? null, [id, upsellingMessages]);
|
||||
};
|
||||
|
||||
export const useUpsellingPage = (pageName: SecurityPageName): React.ComponentType | null => {
|
||||
const { upselling } = useKibana().services;
|
||||
const upselling = useUpsellingService();
|
||||
const UpsellingPage = useMemo(() => upselling.getPageUpselling(pageName), [pageName, upselling]);
|
||||
|
||||
return UpsellingPage ?? null;
|
||||
|
|
|
@ -11,6 +11,6 @@ export type PageUpsellings = Partial<Record<SecurityPageName, React.ComponentTyp
|
|||
export type MessageUpsellings = Partial<Record<UpsellingMessageId, string>>;
|
||||
export type SectionUpsellings = Partial<Record<UpsellingSectionId, React.ComponentType>>;
|
||||
|
||||
export type UpsellingSectionId = 'entity_analytics_panel';
|
||||
export type UpsellingSectionId = 'entity_analytics_panel' | 'endpointPolicyProtections';
|
||||
|
||||
export type UpsellingMessageId = 'investigation_guide';
|
||||
|
|
|
@ -12,7 +12,7 @@ import { createMemoryHistory } from 'history';
|
|||
import type { RenderOptions, RenderResult } from '@testing-library/react';
|
||||
import { render as reactRender } from '@testing-library/react';
|
||||
import type { Action, Reducer, Store } from 'redux';
|
||||
import { QueryClient, QueryClientProvider } from '@tanstack/react-query';
|
||||
import { QueryClient } from '@tanstack/react-query';
|
||||
import { coreMock } from '@kbn/core/public/mocks';
|
||||
import { PLUGIN_ID } from '@kbn/fleet-plugin/common';
|
||||
import type { RenderHookOptions, RenderHookResult } from '@testing-library/react-hooks';
|
||||
|
@ -23,11 +23,9 @@ import type {
|
|||
} from '@testing-library/react-hooks/src/types/react';
|
||||
import type { UseBaseQueryResult } from '@tanstack/react-query';
|
||||
import ReactDOM from 'react-dom';
|
||||
import { NavigationProvider } from '@kbn/security-solution-navigation';
|
||||
import type { AppLinkItems } from '../../links/types';
|
||||
import { ExperimentalFeaturesService } from '../../experimental_features_service';
|
||||
import { applyIntersectionObserverMock } from '../intersection_observer_mock';
|
||||
import { ConsoleManager } from '../../../management/components/console';
|
||||
import type { StartPlugins, StartServices } from '../../../types';
|
||||
import { depsStartMock } from './dependencies_start_mock';
|
||||
import type { MiddlewareActionSpyHelper } from '../../store/test_utils';
|
||||
|
@ -41,7 +39,7 @@ import { createStartServicesMock } from '../../lib/kibana/kibana_react.mock';
|
|||
import { SUB_PLUGINS_REDUCER, mockGlobalState, createSecuritySolutionStorageMock } from '..';
|
||||
import type { ExperimentalFeatures } from '../../../../common/experimental_features';
|
||||
import { APP_UI_ID, APP_PATH } from '../../../../common/constants';
|
||||
import { KibanaContextProvider, KibanaServices } from '../../lib/kibana';
|
||||
import { KibanaServices } from '../../lib/kibana';
|
||||
import { links } from '../../links/app_links';
|
||||
import { fleetGetPackageHttpMock } from '../../../management/mocks';
|
||||
import { allowedExperimentalValues } from '../../../../common/experimental_features';
|
||||
|
@ -238,15 +236,16 @@ export const createAppRootMockRenderer = (): AppContextTestRender => {
|
|||
});
|
||||
|
||||
const AppWrapper: React.FC<{ children: React.ReactElement }> = ({ children }) => (
|
||||
<KibanaContextProvider services={startServices}>
|
||||
<AppRootProvider store={store} history={history} coreStart={coreStart} depsStart={depsStart}>
|
||||
<QueryClientProvider client={queryClient}>
|
||||
<NavigationProvider core={startServices}>
|
||||
<ConsoleManager>{children}</ConsoleManager>
|
||||
</NavigationProvider>
|
||||
</QueryClientProvider>
|
||||
</AppRootProvider>
|
||||
</KibanaContextProvider>
|
||||
<AppRootProvider
|
||||
store={store}
|
||||
history={history}
|
||||
coreStart={coreStart}
|
||||
depsStart={depsStart}
|
||||
startServices={startServices}
|
||||
queryClient={queryClient}
|
||||
>
|
||||
{children}
|
||||
</AppRootProvider>
|
||||
);
|
||||
|
||||
const render: UiRender = (ui, options) => {
|
||||
|
|
|
@ -6,7 +6,7 @@
|
|||
*/
|
||||
|
||||
import type { ReactNode } from 'react';
|
||||
import React, { memo, useMemo } from 'react';
|
||||
import React, { memo } from 'react';
|
||||
import { Provider } from 'react-redux';
|
||||
import { I18nProvider } from '@kbn/i18n-react';
|
||||
import { Router } from '@kbn/shared-ux-router';
|
||||
|
@ -17,9 +17,13 @@ import { EuiThemeProvider } from '@kbn/kibana-react-plugin/common';
|
|||
import { KibanaContextProvider } from '@kbn/kibana-react-plugin/public';
|
||||
import type { CoreStart } from '@kbn/core/public';
|
||||
import { NavigationProvider } from '@kbn/security-solution-navigation';
|
||||
import type { QueryClient } from '@tanstack/react-query';
|
||||
import { QueryClientProvider } from '@tanstack/react-query';
|
||||
import { UpsellingProvider } from '../../components/upselling_provider';
|
||||
import { ConsoleManager } from '../../../management/components/console';
|
||||
import { MockAssistantProvider } from '../mock_assistant_provider';
|
||||
import { RouteCapture } from '../../components/endpoint/route_capture';
|
||||
import type { StartPlugins } from '../../../types';
|
||||
import type { StartPlugins, StartServices } from '../../../types';
|
||||
|
||||
/**
|
||||
* Provides the context for rendering the endpoint app
|
||||
|
@ -29,26 +33,31 @@ export const AppRootProvider = memo<{
|
|||
history: History;
|
||||
coreStart: CoreStart;
|
||||
depsStart: Pick<StartPlugins, 'data' | 'fleet'>;
|
||||
startServices: StartServices;
|
||||
queryClient: QueryClient;
|
||||
children: ReactNode | ReactNode[];
|
||||
}>(({ store, history, coreStart, depsStart: { data }, children }) => {
|
||||
const { http, notifications, uiSettings, application } = coreStart;
|
||||
}>(({ store, history, coreStart, depsStart: { data }, queryClient, startServices, children }) => {
|
||||
const { uiSettings } = coreStart;
|
||||
const isDarkMode = useObservable<boolean>(uiSettings.get$('theme:darkMode'));
|
||||
const services = useMemo(
|
||||
() => ({ http, notifications, application, data }),
|
||||
[application, data, http, notifications]
|
||||
);
|
||||
|
||||
return (
|
||||
<Provider store={store}>
|
||||
<I18nProvider>
|
||||
<KibanaContextProvider services={services}>
|
||||
<KibanaContextProvider services={startServices}>
|
||||
<EuiThemeProvider darkMode={isDarkMode}>
|
||||
<MockAssistantProvider>
|
||||
<NavigationProvider core={coreStart}>
|
||||
<Router history={history}>
|
||||
<RouteCapture>{children}</RouteCapture>
|
||||
</Router>
|
||||
</NavigationProvider>
|
||||
</MockAssistantProvider>
|
||||
<QueryClientProvider client={queryClient}>
|
||||
<UpsellingProvider upsellingService={startServices.upselling}>
|
||||
<MockAssistantProvider>
|
||||
<NavigationProvider core={coreStart}>
|
||||
<Router history={history}>
|
||||
<ConsoleManager>
|
||||
<RouteCapture>{children}</RouteCapture>
|
||||
</ConsoleManager>
|
||||
</Router>
|
||||
</NavigationProvider>
|
||||
</MockAssistantProvider>
|
||||
</UpsellingProvider>
|
||||
</QueryClientProvider>
|
||||
</EuiThemeProvider>
|
||||
</KibanaContextProvider>
|
||||
</I18nProvider>
|
||||
|
|
|
@ -21,6 +21,8 @@ import { QueryClient, QueryClientProvider } from '@tanstack/react-query';
|
|||
import type { Action } from '@kbn/ui-actions-plugin/public';
|
||||
import { CellActionsProvider } from '@kbn/cell-actions';
|
||||
import { ExpandableFlyoutProvider } from '@kbn/expandable-flyout';
|
||||
import { useKibana } from '../lib/kibana';
|
||||
import { UpsellingProvider } from '../components/upselling_provider';
|
||||
import { MockAssistantProvider } from './mock_assistant_provider';
|
||||
import { ConsoleManager } from '../../management/components/console';
|
||||
import type { State } from '../store';
|
||||
|
@ -68,31 +70,40 @@ export const TestProvidersComponent: React.FC<Props> = ({
|
|||
},
|
||||
},
|
||||
});
|
||||
|
||||
return (
|
||||
<I18nProvider>
|
||||
<MockKibanaContextProvider>
|
||||
<ReduxStoreProvider store={store}>
|
||||
<ThemeProvider theme={() => ({ eui: euiDarkVars, darkMode: true })}>
|
||||
<MockAssistantProvider>
|
||||
<QueryClientProvider client={queryClient}>
|
||||
<ExpandableFlyoutProvider>
|
||||
<ConsoleManager>
|
||||
<CellActionsProvider
|
||||
getTriggerCompatibleActions={() => Promise.resolve(cellActions)}
|
||||
>
|
||||
<DragDropContext onDragEnd={onDragEnd}>{children}</DragDropContext>
|
||||
</CellActionsProvider>
|
||||
</ConsoleManager>
|
||||
</ExpandableFlyoutProvider>
|
||||
</QueryClientProvider>
|
||||
</MockAssistantProvider>
|
||||
</ThemeProvider>
|
||||
</ReduxStoreProvider>
|
||||
<UpsellingProviderMock>
|
||||
<ReduxStoreProvider store={store}>
|
||||
<ThemeProvider theme={() => ({ eui: euiDarkVars, darkMode: true })}>
|
||||
<MockAssistantProvider>
|
||||
<QueryClientProvider client={queryClient}>
|
||||
<ExpandableFlyoutProvider>
|
||||
<ConsoleManager>
|
||||
<CellActionsProvider
|
||||
getTriggerCompatibleActions={() => Promise.resolve(cellActions)}
|
||||
>
|
||||
<DragDropContext onDragEnd={onDragEnd}>{children}</DragDropContext>
|
||||
</CellActionsProvider>
|
||||
</ConsoleManager>
|
||||
</ExpandableFlyoutProvider>
|
||||
</QueryClientProvider>
|
||||
</MockAssistantProvider>
|
||||
</ThemeProvider>
|
||||
</ReduxStoreProvider>
|
||||
</UpsellingProviderMock>
|
||||
</MockKibanaContextProvider>
|
||||
</I18nProvider>
|
||||
);
|
||||
};
|
||||
|
||||
const UpsellingProviderMock = ({ children }: React.PropsWithChildren<{}>) => {
|
||||
const upselingService = useKibana().services.upselling;
|
||||
|
||||
return <UpsellingProvider upsellingService={upselingService}>{children}</UpsellingProvider>;
|
||||
};
|
||||
|
||||
/**
|
||||
* A utility for wrapping children in the providers required to run most tests
|
||||
* WITH user privileges provider.
|
||||
|
|
|
@ -88,7 +88,7 @@ export const dataLoaders = (
|
|||
agentPolicyName,
|
||||
}: {
|
||||
policyName: string;
|
||||
endpointPackageVersion: string;
|
||||
endpointPackageVersion?: string;
|
||||
agentPolicyName?: string;
|
||||
}) => {
|
||||
const { kbnClient } = await stackServicesPromise;
|
||||
|
|
|
@ -10,6 +10,7 @@ import React, { memo } from 'react';
|
|||
import { Provider as ReduxStoreProvider } from 'react-redux';
|
||||
import type { Store } from 'redux';
|
||||
import { NavigationProvider } from '@kbn/security-solution-navigation';
|
||||
import { UpsellingProvider } from '../../../../../../../common/components/upselling_provider';
|
||||
import { UserPrivilegesProvider } from '../../../../../../../common/components/user_privileges/user_privileges_context';
|
||||
import type { SecuritySolutionQueryClient } from '../../../../../../../common/containers/query_client/query_client_provider';
|
||||
import { ReactQueryClientProvider } from '../../../../../../../common/containers/query_client/query_client_provider';
|
||||
|
@ -17,15 +18,17 @@ import { SecuritySolutionStartDependenciesContext } from '../../../../../../../c
|
|||
import { CurrentLicense } from '../../../../../../../common/components/current_license';
|
||||
import type { StartPlugins } from '../../../../../../../types';
|
||||
import { useKibana } from '../../../../../../../common/lib/kibana';
|
||||
import type { UpsellingService } from '../../../../../../..';
|
||||
|
||||
export type RenderContextProvidersProps = PropsWithChildren<{
|
||||
store: Store;
|
||||
depsStart: Pick<StartPlugins, 'data' | 'fleet'>;
|
||||
upsellingService: UpsellingService;
|
||||
queryClient?: SecuritySolutionQueryClient;
|
||||
}>;
|
||||
|
||||
export const RenderContextProviders = memo<RenderContextProvidersProps>(
|
||||
({ store, depsStart, queryClient, children }) => {
|
||||
({ store, depsStart, queryClient, upsellingService, children }) => {
|
||||
const services = useKibana().services;
|
||||
const {
|
||||
application: { capabilities },
|
||||
|
@ -36,7 +39,11 @@ export const RenderContextProviders = memo<RenderContextProvidersProps>(
|
|||
<SecuritySolutionStartDependenciesContext.Provider value={depsStart}>
|
||||
<UserPrivilegesProvider kibanaCapabilities={capabilities}>
|
||||
<NavigationProvider core={services}>
|
||||
<CurrentLicense>{children}</CurrentLicense>
|
||||
<CurrentLicense>
|
||||
<UpsellingProvider upsellingService={upsellingService}>
|
||||
{children}
|
||||
</UpsellingProvider>
|
||||
</CurrentLicense>
|
||||
</NavigationProvider>
|
||||
</UserPrivilegesProvider>
|
||||
</SecuritySolutionStartDependenciesContext.Provider>
|
||||
|
|
|
@ -7,14 +7,11 @@
|
|||
|
||||
import type { ComponentType } from 'react';
|
||||
import React, { memo } from 'react';
|
||||
import type { CoreStart } from '@kbn/core/public';
|
||||
import type { StartPlugins } from '../../../../../../../types';
|
||||
import { createFleetContextReduxStore } from './store';
|
||||
import { RenderContextProviders } from './render_context_providers';
|
||||
import type { FleetUiExtensionGetterOptions } from '../../types';
|
||||
|
||||
interface WithSecurityContextProps<P extends {}> {
|
||||
coreStart: CoreStart;
|
||||
depsStart: Pick<StartPlugins, 'data' | 'fleet'>;
|
||||
interface WithSecurityContextProps<P extends {}> extends FleetUiExtensionGetterOptions {
|
||||
WrappedComponent: ComponentType<P>;
|
||||
}
|
||||
|
||||
|
@ -30,6 +27,7 @@ interface WithSecurityContextProps<P extends {}> {
|
|||
export const withSecurityContext = <P extends {}>({
|
||||
coreStart,
|
||||
depsStart,
|
||||
services: { upsellingService },
|
||||
WrappedComponent,
|
||||
}: WithSecurityContextProps<P>): ComponentType<P> => {
|
||||
let store: ReturnType<typeof createFleetContextReduxStore>; // created on first render
|
||||
|
@ -41,7 +39,11 @@ export const withSecurityContext = <P extends {}>({
|
|||
}
|
||||
|
||||
return (
|
||||
<RenderContextProviders store={store} depsStart={depsStart}>
|
||||
<RenderContextProviders
|
||||
store={store}
|
||||
depsStart={depsStart}
|
||||
upsellingService={upsellingService}
|
||||
>
|
||||
<WrappedComponent {...props} />
|
||||
</RenderContextProviders>
|
||||
);
|
||||
|
|
|
@ -6,17 +6,17 @@
|
|||
*/
|
||||
|
||||
import { lazy } from 'react';
|
||||
import type { CoreStart } from '@kbn/core/public';
|
||||
import type {
|
||||
PackageGenericErrorsListComponent,
|
||||
PackageGenericErrorsListProps,
|
||||
} from '@kbn/fleet-plugin/public';
|
||||
import type { StartPlugins } from '../../../../../types';
|
||||
import type { FleetUiExtensionGetterOptions } from './types';
|
||||
|
||||
export const getLazyEndpointGenericErrorsListExtension = (
|
||||
coreStart: CoreStart,
|
||||
depsStart: Pick<StartPlugins, 'data' | 'fleet'>
|
||||
) => {
|
||||
export const getLazyEndpointGenericErrorsListExtension = ({
|
||||
coreStart,
|
||||
depsStart,
|
||||
services,
|
||||
}: FleetUiExtensionGetterOptions) => {
|
||||
return lazy<PackageGenericErrorsListComponent>(async () => {
|
||||
const [{ withSecurityContext }, { EndpointGenericErrorsList }] = await Promise.all([
|
||||
import('./components/with_security_context/with_security_context'),
|
||||
|
@ -27,6 +27,7 @@ export const getLazyEndpointGenericErrorsListExtension = (
|
|||
default: withSecurityContext<PackageGenericErrorsListProps>({
|
||||
coreStart,
|
||||
depsStart,
|
||||
services,
|
||||
WrappedComponent: EndpointGenericErrorsList,
|
||||
}),
|
||||
};
|
||||
|
|
|
@ -5,15 +5,15 @@
|
|||
* 2.0.
|
||||
*/
|
||||
|
||||
import type { CoreStart } from '@kbn/core/public';
|
||||
import { lazy } from 'react';
|
||||
import type { PackageCustomExtensionComponent } from '@kbn/fleet-plugin/public';
|
||||
import type { StartPlugins } from '../../../../../types';
|
||||
import type { FleetUiExtensionGetterOptions } from './types';
|
||||
|
||||
export const getLazyEndpointPackageCustomExtension = (
|
||||
coreStart: CoreStart,
|
||||
depsStart: Pick<StartPlugins, 'data' | 'fleet'>
|
||||
) => {
|
||||
export const getLazyEndpointPackageCustomExtension = ({
|
||||
coreStart,
|
||||
depsStart,
|
||||
services,
|
||||
}: FleetUiExtensionGetterOptions) => {
|
||||
return lazy<PackageCustomExtensionComponent>(async () => {
|
||||
const [{ withSecurityContext }, { EndpointPackageCustomExtension }] = await Promise.all([
|
||||
import('./components/with_security_context/with_security_context'),
|
||||
|
@ -23,6 +23,7 @@ export const getLazyEndpointPackageCustomExtension = (
|
|||
default: withSecurityContext({
|
||||
coreStart,
|
||||
depsStart,
|
||||
services,
|
||||
WrappedComponent: EndpointPackageCustomExtension,
|
||||
}),
|
||||
};
|
||||
|
|
|
@ -6,17 +6,17 @@
|
|||
*/
|
||||
|
||||
import { lazy } from 'react';
|
||||
import type { CoreStart } from '@kbn/core/public';
|
||||
import type {
|
||||
PackagePolicyEditExtensionComponent,
|
||||
PackagePolicyEditExtensionComponentProps,
|
||||
} from '@kbn/fleet-plugin/public';
|
||||
import type { StartPlugins } from '../../../../../types';
|
||||
import type { FleetUiExtensionGetterOptions } from './types';
|
||||
|
||||
export const getLazyEndpointPolicyEditExtension = (
|
||||
coreStart: CoreStart,
|
||||
depsStart: Pick<StartPlugins, 'data' | 'fleet'>
|
||||
) => {
|
||||
export const getLazyEndpointPolicyEditExtension = ({
|
||||
coreStart,
|
||||
depsStart,
|
||||
services,
|
||||
}: FleetUiExtensionGetterOptions) => {
|
||||
return lazy<PackagePolicyEditExtensionComponent>(async () => {
|
||||
const [{ withSecurityContext }, { EndpointPolicyEditExtension }] = await Promise.all([
|
||||
import('./components/with_security_context/with_security_context'),
|
||||
|
@ -27,6 +27,7 @@ export const getLazyEndpointPolicyEditExtension = (
|
|||
default: withSecurityContext<PackagePolicyEditExtensionComponentProps>({
|
||||
coreStart,
|
||||
depsStart,
|
||||
services,
|
||||
WrappedComponent: EndpointPolicyEditExtension,
|
||||
}),
|
||||
};
|
||||
|
|
|
@ -6,17 +6,17 @@
|
|||
*/
|
||||
|
||||
import { lazy } from 'react';
|
||||
import type { CoreStart } from '@kbn/core/public';
|
||||
import type {
|
||||
PackagePolicyResponseExtensionComponent,
|
||||
PackagePolicyResponseExtensionComponentProps,
|
||||
} from '@kbn/fleet-plugin/public';
|
||||
import type { StartPlugins } from '../../../../../types';
|
||||
import type { FleetUiExtensionGetterOptions } from './types';
|
||||
|
||||
export const getLazyEndpointPolicyResponseExtension = (
|
||||
coreStart: CoreStart,
|
||||
depsStart: Pick<StartPlugins, 'data' | 'fleet'>
|
||||
) => {
|
||||
export const getLazyEndpointPolicyResponseExtension = ({
|
||||
coreStart,
|
||||
depsStart,
|
||||
services,
|
||||
}: FleetUiExtensionGetterOptions) => {
|
||||
return lazy<PackagePolicyResponseExtensionComponent>(async () => {
|
||||
const [{ withSecurityContext }, { EndpointPolicyResponseExtension }] = await Promise.all([
|
||||
import('./components/with_security_context/with_security_context'),
|
||||
|
@ -27,6 +27,7 @@ export const getLazyEndpointPolicyResponseExtension = (
|
|||
default: withSecurityContext<PackagePolicyResponseExtensionComponentProps>({
|
||||
coreStart,
|
||||
depsStart,
|
||||
services,
|
||||
WrappedComponent: EndpointPolicyResponseExtension,
|
||||
}),
|
||||
};
|
||||
|
|
|
@ -102,7 +102,12 @@ export const createFleetContextRendererMock = (): AppContextTestRender => {
|
|||
<I18nProvider>
|
||||
<EuiThemeProvider>
|
||||
<KibanaContextProvider services={startServices}>
|
||||
<RenderContextProviders store={store} depsStart={depsStart} queryClient={queryClient}>
|
||||
<RenderContextProviders
|
||||
store={store}
|
||||
depsStart={depsStart}
|
||||
queryClient={queryClient}
|
||||
upsellingService={startServices.upselling}
|
||||
>
|
||||
{children}
|
||||
</RenderContextProviders>
|
||||
</KibanaContextProvider>
|
||||
|
|
|
@ -0,0 +1,18 @@
|
|||
/*
|
||||
* 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 { CoreStart } from '@kbn/core-lifecycle-browser';
|
||||
import type { StartPlugins } from '../../../../../types';
|
||||
import type { UpsellingService } from '../../../../..';
|
||||
|
||||
export interface FleetUiExtensionGetterOptions {
|
||||
coreStart: CoreStart;
|
||||
depsStart: Pick<StartPlugins, 'data' | 'fleet'>;
|
||||
services: {
|
||||
upsellingService: UpsellingService;
|
||||
};
|
||||
}
|
|
@ -10,6 +10,7 @@ import { OperatingSystem } from '@kbn/securitysolution-utils';
|
|||
import { i18n } from '@kbn/i18n';
|
||||
import { EuiSpacer, EuiSwitch, EuiText } from '@elastic/eui';
|
||||
import { cloneDeep } from 'lodash';
|
||||
import { useGetProtectionsUnavailableComponent } from '../../hooks/use_get_protections_unavailable_component';
|
||||
import { useTestIdGenerator } from '../../../../../../hooks/use_test_id_generator';
|
||||
import { SettingCard } from '../setting_card';
|
||||
import type { PolicyFormComponentCommonProps } from '../../types';
|
||||
|
@ -49,6 +50,7 @@ export type AntivirusRegistrationCardProps = PolicyFormComponentCommonProps;
|
|||
export const AntivirusRegistrationCard = memo<AntivirusRegistrationCardProps>(
|
||||
({ policy, onChange, mode, 'data-test-subj': dataTestSubj }) => {
|
||||
const getTestId = useTestIdGenerator(dataTestSubj);
|
||||
const isProtectionsAllowed = !useGetProtectionsUnavailableComponent();
|
||||
const isChecked = policy.windows.antivirus_registration.enabled;
|
||||
const isEditMode = mode === 'edit';
|
||||
const label = isChecked ? REGISTERED_LABEL : NOT_REGISTERED_LABEL;
|
||||
|
@ -63,6 +65,10 @@ export const AntivirusRegistrationCard = memo<AntivirusRegistrationCardProps>(
|
|||
[onChange, policy]
|
||||
);
|
||||
|
||||
if (!isProtectionsAllowed) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return (
|
||||
<SettingCard
|
||||
type={CARD_TITLE}
|
||||
|
|
|
@ -10,6 +10,7 @@ import { OperatingSystem } from '@kbn/securitysolution-utils';
|
|||
import { EuiSwitch } from '@elastic/eui';
|
||||
import { i18n } from '@kbn/i18n';
|
||||
import { cloneDeep } from 'lodash';
|
||||
import { useGetProtectionsUnavailableComponent } from '../../hooks/use_get_protections_unavailable_component';
|
||||
import { useTestIdGenerator } from '../../../../../../hooks/use_test_id_generator';
|
||||
import { useLicense } from '../../../../../../../common/hooks/use_license';
|
||||
import { SettingLockedCard } from '../setting_locked_card';
|
||||
|
@ -52,6 +53,7 @@ export const AttackSurfaceReductionCard = memo<AttackSurfaceReductionCardProps>(
|
|||
({ policy, onChange, mode, 'data-test-subj': dataTestSubj }) => {
|
||||
const isPlatinumPlus = useLicense().isPlatinumPlus();
|
||||
const getTestId = useTestIdGenerator(dataTestSubj);
|
||||
const isProtectionsAllowed = !useGetProtectionsUnavailableComponent();
|
||||
const isChecked = policy.windows.attack_surface_reduction.credential_hardening.enabled;
|
||||
const isEditMode = mode === 'edit';
|
||||
const label = isChecked ? SWITCH_ENABLED_LABEL : SWITCH_DISABLED_LABEL;
|
||||
|
@ -68,6 +70,10 @@ export const AttackSurfaceReductionCard = memo<AttackSurfaceReductionCardProps>(
|
|||
[onChange, policy]
|
||||
);
|
||||
|
||||
if (!isProtectionsAllowed) {
|
||||
return null;
|
||||
}
|
||||
|
||||
if (!isPlatinumPlus) {
|
||||
return (
|
||||
<SettingLockedCard
|
||||
|
|
|
@ -11,6 +11,7 @@ import { FormattedMessage } from '@kbn/i18n-react';
|
|||
import { EuiSpacer, EuiSwitch, EuiFlexGroup, EuiFlexItem, EuiIconTip } from '@elastic/eui';
|
||||
import { OperatingSystem } from '@kbn/securitysolution-utils';
|
||||
import { cloneDeep } from 'lodash';
|
||||
import { useGetProtectionsUnavailableComponent } from '../../hooks/use_get_protections_unavailable_component';
|
||||
import { RelatedDetectionRulesCallout } from '../related_detection_rules_callout';
|
||||
import { NotifyUserOption } from '../notify_user_option';
|
||||
import { SettingCard } from '../setting_card';
|
||||
|
@ -55,12 +56,14 @@ const MALWARE_OS_VALUES: Immutable<MalwareProtectionOSes[]> = [
|
|||
|
||||
export type MalwareProtectionsProps = PolicyFormComponentCommonProps;
|
||||
|
||||
/** The Malware Protections form for policy details
|
||||
* which will configure for all relevant OSes.
|
||||
/**
|
||||
* The Malware Protections form for policy details
|
||||
* which will configure for all relevant OSes.
|
||||
*/
|
||||
export const MalwareProtectionsCard = React.memo<MalwareProtectionsProps>(
|
||||
({ policy, onChange, mode = 'edit', 'data-test-subj': dataTestSubj }) => {
|
||||
const getTestId = useTestIdGenerator(dataTestSubj);
|
||||
const isProtectionsAllowed = !useGetProtectionsUnavailableComponent();
|
||||
const protection = 'malware';
|
||||
const protectionLabel = i18n.translate(
|
||||
'xpack.securitySolution.endpoint.policy.protections.malware',
|
||||
|
@ -69,6 +72,10 @@ export const MalwareProtectionsCard = React.memo<MalwareProtectionsProps>(
|
|||
}
|
||||
);
|
||||
|
||||
if (!isProtectionsAllowed) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return (
|
||||
<SettingCard
|
||||
type={i18n.translate('xpack.securitySolution.endpoint.policy.details.malware', {
|
||||
|
|
|
@ -9,6 +9,7 @@ import React, { memo } from 'react';
|
|||
import { i18n } from '@kbn/i18n';
|
||||
import { OperatingSystem } from '@kbn/securitysolution-utils';
|
||||
import { EuiSpacer } from '@elastic/eui';
|
||||
import { useGetProtectionsUnavailableComponent } from '../../hooks/use_get_protections_unavailable_component';
|
||||
import { useTestIdGenerator } from '../../../../../../hooks/use_test_id_generator';
|
||||
import { NotifyUserOption } from '../notify_user_option';
|
||||
import { DetectPreventProtectionLevel } from '../detect_prevent_protection_level';
|
||||
|
@ -41,6 +42,7 @@ export const MemoryProtectionCard = memo<MemoryProtectionCardProps>(
|
|||
({ policy, onChange, mode, 'data-test-subj': dataTestSubj }) => {
|
||||
const isPlatinumPlus = useLicense().isPlatinumPlus();
|
||||
const getTestId = useTestIdGenerator(dataTestSubj);
|
||||
const isProtectionsAllowed = !useGetProtectionsUnavailableComponent();
|
||||
const protection = 'memory_protection';
|
||||
const protectionLabel = i18n.translate(
|
||||
'xpack.securitySolution.endpoint.policy.protections.memory',
|
||||
|
@ -49,6 +51,10 @@ export const MemoryProtectionCard = memo<MemoryProtectionCardProps>(
|
|||
}
|
||||
);
|
||||
|
||||
if (!isProtectionsAllowed) {
|
||||
return null;
|
||||
}
|
||||
|
||||
if (!isPlatinumPlus) {
|
||||
return (
|
||||
<SettingLockedCard title={LOCKED_CARD_MEMORY_TITLE} data-test-subj={getTestId('locked')} />
|
||||
|
|
|
@ -9,6 +9,7 @@ import React, { memo } from 'react';
|
|||
import { i18n } from '@kbn/i18n';
|
||||
import { OperatingSystem } from '@kbn/securitysolution-utils';
|
||||
import { EuiSpacer } from '@elastic/eui';
|
||||
import { useGetProtectionsUnavailableComponent } from '../../../hooks/use_get_protections_unavailable_component';
|
||||
import { RelatedDetectionRulesCallout } from '../../related_detection_rules_callout';
|
||||
import { ReputationService } from './components/reputation_service';
|
||||
import { useTestIdGenerator } from '../../../../../../../hooks/use_test_id_generator';
|
||||
|
@ -42,6 +43,7 @@ export const BehaviourProtectionCard = memo<BehaviourProtectionCardProps>(
|
|||
({ policy, onChange, mode, 'data-test-subj': dataTestSubj }) => {
|
||||
const isPlatinumPlus = useLicense().isPlatinumPlus();
|
||||
const getTestId = useTestIdGenerator(dataTestSubj);
|
||||
const isProtectionsAllowed = !useGetProtectionsUnavailableComponent();
|
||||
const protection = 'behavior_protection';
|
||||
const protectionLabel = i18n.translate(
|
||||
'xpack.securitySolution.endpoint.policy.protections.behavior',
|
||||
|
@ -50,6 +52,10 @@ export const BehaviourProtectionCard = memo<BehaviourProtectionCardProps>(
|
|||
}
|
||||
);
|
||||
|
||||
if (!isProtectionsAllowed) {
|
||||
return null;
|
||||
}
|
||||
|
||||
if (!isPlatinumPlus) {
|
||||
return (
|
||||
<SettingLockedCard
|
||||
|
|
|
@ -21,6 +21,7 @@ import type { RansomwareProtectionOSes } from '../../../../types';
|
|||
import { useLicense } from '../../../../../../../common/hooks/use_license';
|
||||
import { SettingLockedCard } from '../setting_locked_card';
|
||||
import { RelatedDetectionRulesCallout } from '../related_detection_rules_callout';
|
||||
import { useGetProtectionsUnavailableComponent } from '../../hooks/use_get_protections_unavailable_component';
|
||||
|
||||
const RANSOMEWARE_OS_VALUES: Immutable<RansomwareProtectionOSes[]> = [
|
||||
PolicyOperatingSystem.windows,
|
||||
|
@ -38,6 +39,7 @@ export type RansomwareProtectionCardProps = PolicyFormComponentCommonProps;
|
|||
export const RansomwareProtectionCard = React.memo<RansomwareProtectionCardProps>(
|
||||
({ policy, onChange, mode, 'data-test-subj': dataTestSubj }) => {
|
||||
const isPlatinumPlus = useLicense().isPlatinumPlus();
|
||||
const isProtectionsAllowed = !useGetProtectionsUnavailableComponent();
|
||||
const getTestId = useTestIdGenerator(dataTestSubj);
|
||||
const protection = 'ransomware';
|
||||
const protectionLabel = i18n.translate(
|
||||
|
@ -47,6 +49,10 @@ export const RansomwareProtectionCard = React.memo<RansomwareProtectionCardProps
|
|||
}
|
||||
);
|
||||
|
||||
if (!isProtectionsAllowed) {
|
||||
return null;
|
||||
}
|
||||
|
||||
if (!isPlatinumPlus) {
|
||||
return (
|
||||
<SettingLockedCard
|
||||
|
|
|
@ -0,0 +1,13 @@
|
|||
/*
|
||||
* 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 React from 'react';
|
||||
import { useUpsellingComponent } from '../../../../../../common/hooks/use_upselling';
|
||||
|
||||
export const useGetProtectionsUnavailableComponent = (): React.ComponentType | null => {
|
||||
return useUpsellingComponent('endpointPolicyProtections');
|
||||
};
|
|
@ -12,6 +12,7 @@ import { createAppRootMockRenderer } from '../../../../../common/mock/endpoint';
|
|||
import type { PolicySettingsFormProps } from './policy_settings_form';
|
||||
import { PolicySettingsForm } from './policy_settings_form';
|
||||
import { FleetPackagePolicyGenerator } from '../../../../../../common/endpoint/data_generators/fleet_package_policy_generator';
|
||||
import type { UpsellingService } from '../../../../../common/lib/upsellings';
|
||||
|
||||
jest.mock('../../../../../common/hooks/use_license');
|
||||
|
||||
|
@ -21,10 +22,13 @@ describe('Endpoint Policy Settings Form', () => {
|
|||
let formProps: PolicySettingsFormProps;
|
||||
let render: () => ReturnType<AppContextTestRender['render']>;
|
||||
let renderResult: ReturnType<typeof render>;
|
||||
let upsellingService: UpsellingService;
|
||||
|
||||
beforeEach(() => {
|
||||
const mockedContext = createAppRootMockRenderer();
|
||||
|
||||
upsellingService = mockedContext.startServices.upselling;
|
||||
|
||||
formProps = {
|
||||
policy: new FleetPackagePolicyGenerator('seed').generateEndpointPackagePolicy().inputs[0]
|
||||
.config.policy.value,
|
||||
|
@ -59,4 +63,30 @@ describe('Endpoint Policy Settings Form', () => {
|
|||
|
||||
expectIsViewOnly(renderResult.getByTestId('test'));
|
||||
});
|
||||
|
||||
describe('and when policy protections are not available', () => {
|
||||
beforeEach(() => {
|
||||
upsellingService.registerSections({
|
||||
endpointPolicyProtections: () => <div data-test-subj="paywall">{'pay up!'}</div>,
|
||||
});
|
||||
});
|
||||
|
||||
it.each([
|
||||
['malware', testSubj.malware.card],
|
||||
['ransomware', testSubj.ransomware.card],
|
||||
['memory', testSubj.memory.card],
|
||||
['behaviour', testSubj.behaviour.card],
|
||||
['attack surface', testSubj.attackSurface.card],
|
||||
['antivirus registration', testSubj.antivirusRegistration.card],
|
||||
])('should include %s card', (_, testSubjSelector) => {
|
||||
render();
|
||||
|
||||
expect(renderResult.queryByTestId(testSubjSelector)).toBeNull();
|
||||
});
|
||||
|
||||
it('should display upselling component', () => {
|
||||
render();
|
||||
expect(renderResult.getByTestId('paywall'));
|
||||
});
|
||||
});
|
||||
});
|
||||
|
|
|
@ -8,6 +8,7 @@
|
|||
import React, { memo } from 'react';
|
||||
import { EuiSpacer, EuiText } from '@elastic/eui';
|
||||
import { i18n } from '@kbn/i18n';
|
||||
import { useGetProtectionsUnavailableComponent } from './hooks/use_get_protections_unavailable_component';
|
||||
import { AntivirusRegistrationCard } from './components/cards/antivirus_registration_card';
|
||||
import { LinuxEventCollectionCard } from './components/cards/linux_event_collection_card';
|
||||
import { MacEventCollectionCard } from './components/cards/mac_event_collection_card';
|
||||
|
@ -35,26 +36,39 @@ export type PolicySettingsFormProps = PolicyFormComponentCommonProps;
|
|||
|
||||
export const PolicySettingsForm = memo<PolicySettingsFormProps>((props) => {
|
||||
const getTestId = useTestIdGenerator(props['data-test-subj']);
|
||||
const ProtectionsUpSellingComponent = useGetProtectionsUnavailableComponent();
|
||||
|
||||
return (
|
||||
<div data-test-subj={getTestId()}>
|
||||
<FormSectionTitle>{PROTECTIONS_SECTION_TITLE}</FormSectionTitle>
|
||||
<EuiSpacer size="s" />
|
||||
|
||||
<MalwareProtectionsCard {...props} data-test-subj={getTestId('malware')} />
|
||||
<EuiSpacer size="l" />
|
||||
{ProtectionsUpSellingComponent && (
|
||||
<>
|
||||
<EuiSpacer size="m" />
|
||||
<ProtectionsUpSellingComponent />
|
||||
<EuiSpacer size="l" />
|
||||
</>
|
||||
)}
|
||||
|
||||
<RansomwareProtectionCard {...props} data-test-subj={getTestId('ransomware')} />
|
||||
<EuiSpacer size="l" />
|
||||
{!ProtectionsUpSellingComponent && (
|
||||
<>
|
||||
<MalwareProtectionsCard {...props} data-test-subj={getTestId('malware')} />
|
||||
<EuiSpacer size="l" />
|
||||
|
||||
<MemoryProtectionCard {...props} data-test-subj={getTestId('memory')} />
|
||||
<EuiSpacer size="l" />
|
||||
<RansomwareProtectionCard {...props} data-test-subj={getTestId('ransomware')} />
|
||||
<EuiSpacer size="l" />
|
||||
|
||||
<BehaviourProtectionCard {...props} data-test-subj={getTestId('behaviour')} />
|
||||
<EuiSpacer size="l" />
|
||||
<MemoryProtectionCard {...props} data-test-subj={getTestId('memory')} />
|
||||
<EuiSpacer size="l" />
|
||||
|
||||
<AttackSurfaceReductionCard {...props} data-test-subj={getTestId('attackSurface')} />
|
||||
<EuiSpacer size="l" />
|
||||
<BehaviourProtectionCard {...props} data-test-subj={getTestId('behaviour')} />
|
||||
<EuiSpacer size="l" />
|
||||
|
||||
<AttackSurfaceReductionCard {...props} data-test-subj={getTestId('attackSurface')} />
|
||||
<EuiSpacer size="l" />
|
||||
</>
|
||||
)}
|
||||
|
||||
<FormSectionTitle>{SETTINGS_SECTION_TITLE}</FormSectionTitle>
|
||||
<EuiSpacer size="s" />
|
||||
|
|
|
@ -20,6 +20,7 @@ import type { DataPublicPluginStart } from '@kbn/data-plugin/public';
|
|||
import { FilterManager, NowProvider, QueryService } from '@kbn/data-plugin/public';
|
||||
import { DEFAULT_APP_CATEGORIES, AppNavLinkStatus } from '@kbn/core/public';
|
||||
import { Storage } from '@kbn/kibana-utils-plugin/public';
|
||||
import type { FleetUiExtensionGetterOptions } from './management/pages/policy/view/ingest_manager_integration/types';
|
||||
import type {
|
||||
PluginSetup,
|
||||
PluginStart,
|
||||
|
@ -276,23 +277,30 @@ export class Plugin implements IPlugin<PluginSetup, PluginStart, SetupPlugins, S
|
|||
|
||||
if (plugins.fleet) {
|
||||
const { registerExtension } = plugins.fleet;
|
||||
const registerOptions: FleetUiExtensionGetterOptions = {
|
||||
coreStart: core,
|
||||
depsStart: plugins,
|
||||
services: {
|
||||
upsellingService: this.contract.upsellingService,
|
||||
},
|
||||
};
|
||||
|
||||
registerExtension({
|
||||
package: 'endpoint',
|
||||
view: 'package-policy-edit',
|
||||
Component: getLazyEndpointPolicyEditExtension(core, plugins),
|
||||
Component: getLazyEndpointPolicyEditExtension(registerOptions),
|
||||
});
|
||||
|
||||
registerExtension({
|
||||
package: 'endpoint',
|
||||
view: 'package-policy-response',
|
||||
Component: getLazyEndpointPolicyResponseExtension(core, plugins),
|
||||
Component: getLazyEndpointPolicyResponseExtension(registerOptions),
|
||||
});
|
||||
|
||||
registerExtension({
|
||||
package: 'endpoint',
|
||||
view: 'package-generic-errors-list',
|
||||
Component: getLazyEndpointGenericErrorsListExtension(core, plugins),
|
||||
Component: getLazyEndpointGenericErrorsListExtension(registerOptions),
|
||||
});
|
||||
|
||||
registerExtension({
|
||||
|
@ -310,7 +318,7 @@ export class Plugin implements IPlugin<PluginSetup, PluginStart, SetupPlugins, S
|
|||
registerExtension({
|
||||
package: 'endpoint',
|
||||
view: 'package-detail-custom',
|
||||
Component: getLazyEndpointPackageCustomExtension(core, plugins),
|
||||
Component: getLazyEndpointPackageCustomExtension(registerOptions),
|
||||
});
|
||||
|
||||
registerExtension({
|
||||
|
|
|
@ -183,6 +183,9 @@ export const getSecurityAppFeaturesConfig = (
|
|||
subFeatureIds: [SecuritySubFeatureId.policyManagement],
|
||||
},
|
||||
|
||||
// Adds no additional kibana feature controls
|
||||
[AppFeatureSecurityKey.endpointPolicyProtections]: {},
|
||||
|
||||
[AppFeatureSecurityKey.endpointArtifactManagement]: {
|
||||
subFeatureIds: [
|
||||
SecuritySubFeatureId.trustedApplications,
|
||||
|
|
|
@ -168,5 +168,6 @@
|
|||
"@kbn/navigation-plugin",
|
||||
"@kbn/alerts-ui-shared",
|
||||
"@kbn/core-logging-server-mocks",
|
||||
"@kbn/core-lifecycle-browser",
|
||||
]
|
||||
}
|
||||
|
|
|
@ -14,7 +14,7 @@ type PliAppFeatures = Readonly<
|
|||
|
||||
export const PLI_APP_FEATURES: PliAppFeatures = {
|
||||
security: {
|
||||
essentials: [],
|
||||
essentials: [AppFeatureKey.endpointHostManagement, AppFeatureKey.endpointPolicyManagement],
|
||||
complete: [
|
||||
AppFeatureKey.advancedInsights,
|
||||
AppFeatureKey.investigationGuide,
|
||||
|
@ -26,6 +26,7 @@ export const PLI_APP_FEATURES: PliAppFeatures = {
|
|||
essentials: [
|
||||
AppFeatureKey.endpointHostManagement,
|
||||
AppFeatureKey.endpointPolicyManagement,
|
||||
AppFeatureKey.endpointPolicyProtections,
|
||||
AppFeatureKey.endpointArtifactManagement,
|
||||
],
|
||||
complete: [AppFeatureKey.endpointResponseActions],
|
||||
|
|
|
@ -68,13 +68,13 @@ describe('registerUpsellings', () => {
|
|||
registerUpsellings(upselling, allProductTypes);
|
||||
|
||||
const expectedPagesObject = Object.fromEntries(
|
||||
upsellingPages.map(({ pageName }) => [pageName, expect.any(Function)])
|
||||
upsellingPages.map(({ pageName }) => [pageName, expect.any(Object)])
|
||||
);
|
||||
expect(registerPages).toHaveBeenCalledTimes(1);
|
||||
expect(registerPages).toHaveBeenCalledWith(expectedPagesObject);
|
||||
|
||||
const expectedSectionsObject = Object.fromEntries(
|
||||
upsellingSections.map(({ id }) => [id, expect.any(Function)])
|
||||
upsellingSections.map(({ id }) => [id, expect.any(Object)])
|
||||
);
|
||||
expect(registerSections).toHaveBeenCalledTimes(1);
|
||||
expect(registerSections).toHaveBeenCalledWith(expectedSectionsObject);
|
||||
|
|
|
@ -11,19 +11,25 @@ import type {
|
|||
SectionUpsellings,
|
||||
UpsellingSectionId,
|
||||
} from '@kbn/security-solution-plugin/public';
|
||||
import React, { lazy } from 'react';
|
||||
import type {
|
||||
MessageUpsellings,
|
||||
UpsellingMessageId,
|
||||
} from '@kbn/security-solution-plugin/public/common/lib/upsellings/types';
|
||||
import React, { lazy } from 'react';
|
||||
import { EndpointPolicyProtectionsLazy } from './sections/endpoint_management';
|
||||
import type { SecurityProductTypes } from '../../common/config';
|
||||
import { getProductAppFeatures } from '../../common/pli/pli_features';
|
||||
import investigationGuideUpselling from './pages/investigation_guide_upselling';
|
||||
const ThreatIntelligencePaywallLazy = lazy(() => import('./pages/threat_intelligence_paywall'));
|
||||
const ThreatIntelligencePaywallLazy = lazy(async () => {
|
||||
const ThreatIntelligencePaywall = (await import('./pages/threat_intelligence_paywall')).default;
|
||||
|
||||
return {
|
||||
default: () => <ThreatIntelligencePaywall requiredPLI={AppFeatureKey.threatIntelligence} />,
|
||||
};
|
||||
});
|
||||
interface UpsellingsConfig {
|
||||
pli: AppFeatureKey;
|
||||
component: React.ComponentType;
|
||||
component: React.LazyExoticComponent<React.ComponentType>;
|
||||
}
|
||||
|
||||
interface UpsellingsMessageConfig {
|
||||
|
@ -89,9 +95,7 @@ export const upsellingPages: UpsellingPages = [
|
|||
{
|
||||
pageName: SecurityPageName.threatIntelligence,
|
||||
pli: AppFeatureKey.threatIntelligence,
|
||||
component: () => (
|
||||
<ThreatIntelligencePaywallLazy requiredPLI={AppFeatureKey.threatIntelligence} />
|
||||
),
|
||||
component: ThreatIntelligencePaywallLazy,
|
||||
},
|
||||
];
|
||||
|
||||
|
@ -104,6 +108,12 @@ export const upsellingSections: UpsellingSections = [
|
|||
// pli: AppFeatureKey.advancedInsights,
|
||||
// component: () => <GenericUpsellingSectionLazy requiredPLI={AppFeatureKey.advancedInsights} />,
|
||||
// },
|
||||
|
||||
{
|
||||
id: 'endpointPolicyProtections',
|
||||
pli: AppFeatureKey.endpointPolicyProtections,
|
||||
component: EndpointPolicyProtectionsLazy,
|
||||
},
|
||||
];
|
||||
|
||||
// Upsellings for sections, linked by arbitrary ids
|
||||
|
|
|
@ -0,0 +1,61 @@
|
|||
/*
|
||||
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
|
||||
* or more contributor license agreements. Licensed under the Elastic License
|
||||
* 2.0; you may not use this file except in compliance with the Elastic License
|
||||
* 2.0.
|
||||
*/
|
||||
|
||||
import React, { memo } from 'react';
|
||||
import { EuiCard, EuiIcon } from '@elastic/eui';
|
||||
import { i18n } from '@kbn/i18n';
|
||||
import styled from '@emotion/styled';
|
||||
|
||||
const CARD_TITLE = i18n.translate(
|
||||
'xpack.securitySolutionServerless.endpointPolicyProtections.cardTitle',
|
||||
{
|
||||
defaultMessage: 'Policy Protections',
|
||||
}
|
||||
);
|
||||
const CARD_MESSAGE = i18n.translate(
|
||||
'xpack.securitySolutionServerless.endpointPolicyProtections.cardMessage',
|
||||
{
|
||||
defaultMessage:
|
||||
'To turn on policy protections, like malware, ransomware and others, you must add at least Endpoint Essentials to your project. ',
|
||||
}
|
||||
);
|
||||
const BADGE_TEXT = i18n.translate(
|
||||
'xpack.securitySolutionServerless.endpointPolicyProtections.badgeText',
|
||||
{
|
||||
defaultMessage: 'Endpoint Essentials',
|
||||
}
|
||||
);
|
||||
|
||||
const CardDescription = styled.p`
|
||||
padding: 0 33.3%;
|
||||
`;
|
||||
|
||||
/**
|
||||
* Component displayed when a given product tier is not allowed to use endpoint policy protections.
|
||||
*/
|
||||
export const EndpointPolicyProtections = memo(() => {
|
||||
return (
|
||||
<EuiCard
|
||||
data-test-subj="endpointPolicy-protectionsLockedCard"
|
||||
isDisabled={true}
|
||||
description={false}
|
||||
icon={<EuiIcon size="xl" type="lock" />}
|
||||
betaBadgeProps={{
|
||||
'data-test-subj': 'endpointPolicy-protectionsLockedCard-badge',
|
||||
label: BADGE_TEXT,
|
||||
}}
|
||||
title={
|
||||
<h3 data-test-subj="endpointPolicy-protectionsLockedCard-title">
|
||||
<strong>{CARD_TITLE}</strong>
|
||||
</h3>
|
||||
}
|
||||
>
|
||||
<CardDescription>{CARD_MESSAGE}</CardDescription>
|
||||
</EuiCard>
|
||||
);
|
||||
});
|
||||
EndpointPolicyProtections.displayName = 'EndpointPolicyProtections';
|
|
@ -0,0 +1,14 @@
|
|||
/*
|
||||
* 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 { lazy } from 'react';
|
||||
|
||||
export const EndpointPolicyProtectionsLazy = lazy(() =>
|
||||
import('./endpoint_policy_protections').then(({ EndpointPolicyProtections }) => ({
|
||||
default: EndpointPolicyProtections,
|
||||
}))
|
||||
);
|
|
@ -19,7 +19,13 @@ describe(
|
|||
},
|
||||
},
|
||||
() => {
|
||||
const pages = getEndpointManagementPageList();
|
||||
const allPages = getEndpointManagementPageList();
|
||||
const deniedPages = allPages.filter(({ id }) => {
|
||||
return id !== 'endpointList' && id !== 'policyList';
|
||||
});
|
||||
const allowedPages = allPages.filter(({ id }) => {
|
||||
return id === 'endpointList' || id === 'policyList';
|
||||
});
|
||||
let username: string;
|
||||
let password: string;
|
||||
|
||||
|
@ -30,7 +36,14 @@ describe(
|
|||
});
|
||||
});
|
||||
|
||||
for (const { url, title } of pages) {
|
||||
for (const { url, title, pageTestSubj } of allowedPages) {
|
||||
it(`should allow access to ${title}`, () => {
|
||||
cy.visit(url);
|
||||
cy.getByTestSubj(pageTestSubj).should('exist');
|
||||
});
|
||||
}
|
||||
|
||||
for (const { url, title } of deniedPages) {
|
||||
it(`should not allow access to ${title}`, () => {
|
||||
cy.visit(url);
|
||||
getNoPrivilegesPage().should('exist');
|
||||
|
|
|
@ -8,8 +8,8 @@
|
|||
import { RESPONSE_ACTION_API_COMMANDS_NAMES } from '@kbn/security-solution-plugin/common/endpoint/service/response_actions/constants';
|
||||
import { login } from '../../../tasks/login';
|
||||
import { getNoPrivilegesPage } from '../../../screens/endpoint_management/common';
|
||||
import { getEndpointManagementPageList } from '../../../screens/endpoint_management';
|
||||
import { ensureResponseActionAuthzAccess } from '../../../tasks/endpoint_management';
|
||||
import { getEndpointManagementPageList } from '../../../screens/endpoint_management';
|
||||
|
||||
describe(
|
||||
'App Features for Essential PLI',
|
||||
|
@ -21,7 +21,13 @@ describe(
|
|||
},
|
||||
},
|
||||
() => {
|
||||
const pages = getEndpointManagementPageList();
|
||||
const allPages = getEndpointManagementPageList();
|
||||
const deniedPages = allPages.filter(({ id }) => {
|
||||
return id !== 'endpointList' && id !== 'policyList';
|
||||
});
|
||||
const allowedPages = allPages.filter(({ id }) => {
|
||||
return id === 'endpointList' || id === 'policyList';
|
||||
});
|
||||
let username: string;
|
||||
let password: string;
|
||||
|
||||
|
@ -32,15 +38,22 @@ describe(
|
|||
});
|
||||
});
|
||||
|
||||
for (const { url, title } of pages) {
|
||||
it(`should not allow access to ${title}`, () => {
|
||||
for (const { url, title, pageTestSubj } of allowedPages) {
|
||||
it(`should allow access to ${title}`, () => {
|
||||
cy.visit(url);
|
||||
cy.getByTestSubj(pageTestSubj).should('exist');
|
||||
});
|
||||
}
|
||||
|
||||
for (const { url, title } of deniedPages) {
|
||||
it(`should NOT allow access to ${title}`, () => {
|
||||
cy.visit(url);
|
||||
getNoPrivilegesPage().should('exist');
|
||||
});
|
||||
}
|
||||
|
||||
for (const actionName of RESPONSE_ACTION_API_COMMANDS_NAMES) {
|
||||
it(`should not allow access to Response Action: ${actionName}`, () => {
|
||||
it(`should NOT allow access to Response Action: ${actionName}`, () => {
|
||||
ensureResponseActionAuthzAccess('none', actionName, username, password);
|
||||
});
|
||||
}
|
||||
|
|
|
@ -0,0 +1,47 @@
|
|||
/*
|
||||
* 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 { IndexedFleetEndpointPolicyResponse } from '@kbn/security-solution-plugin/common/endpoint/data_loaders/index_fleet_endpoint_policy';
|
||||
import { login } from '../../tasks/login';
|
||||
import { visitPolicyDetails } from '../../screens/endpoint_management/policy_details';
|
||||
|
||||
describe(
|
||||
'When displaying the Policy Details in Security Essentials PLI',
|
||||
{
|
||||
env: {
|
||||
ftrConfig: {
|
||||
productTypes: [{ product_line: 'security', product_tier: 'essentials' }],
|
||||
},
|
||||
},
|
||||
},
|
||||
() => {
|
||||
let loadedPolicyData: IndexedFleetEndpointPolicyResponse;
|
||||
|
||||
before(() => {
|
||||
cy.task('indexFleetEndpointPolicy', { policyName: 'tests-serverless' }).then((response) => {
|
||||
loadedPolicyData = response as IndexedFleetEndpointPolicyResponse;
|
||||
});
|
||||
});
|
||||
|
||||
after(() => {
|
||||
if (loadedPolicyData) {
|
||||
cy.task('deleteIndexedFleetEndpointPolicies', loadedPolicyData);
|
||||
}
|
||||
});
|
||||
|
||||
beforeEach(() => {
|
||||
login();
|
||||
visitPolicyDetails(loadedPolicyData.integrationPolicies[0].id);
|
||||
});
|
||||
|
||||
it('should display upselling section for protections', () => {
|
||||
cy.getByTestSubj('endpointPolicy-protectionsLockedCard', { timeout: 60000 })
|
||||
.should('exist')
|
||||
.and('be.visible');
|
||||
});
|
||||
}
|
||||
);
|
|
@ -0,0 +1,12 @@
|
|||
/*
|
||||
* 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 { APP_POLICIES_PATH } from '@kbn/security-solution-plugin/common/constants';
|
||||
|
||||
export const visitPolicyDetails = (policyId: string): Cypress.Chainable => {
|
||||
return cy.visit(`${APP_POLICIES_PATH}/${policyId}`);
|
||||
};
|
Loading…
Add table
Add a link
Reference in a new issue