Skip to content

Commit

Permalink
[Security Solution][Endpoint] Add Endpoint with event collection only…
Browse files Browse the repository at this point in the history
… 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
  • Loading branch information
paul-tavares authored Aug 4, 2023
1 parent 0069062 commit 3efc73c
Show file tree
Hide file tree
Showing 42 changed files with 543 additions and 132 deletions.
1 change: 1 addition & 0 deletions .github/CODEOWNERS
Validating CODEOWNERS rules …
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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';
Expand All @@ -34,14 +35,17 @@ export interface IndexedFleetEndpointPolicyResponse {
export const indexFleetEndpointPolicy = async (
kbnClient: KbnClient,
policyName: string,
endpointPackageVersion: string = kibanaPackageJson.version,
endpointPackageVersion?: string,
agentPolicyName?: string
): Promise<IndexedFleetEndpointPolicyResponse> => {
const response: IndexedFleetEndpointPolicyResponse = {
integrationPolicies: [],
agentPolicies: [],
};

const packageVersion =
endpointPackageVersion ?? (await getDefaultEndpointPackageVersion(kbnClient));

// Create Agent Policy first
const newAgentPolicyData: CreateAgentPolicyRequest['body'] = {
name:
Expand Down Expand Up @@ -89,7 +93,7 @@ export const indexFleetEndpointPolicy = async (
package: {
name: 'endpoint',
title: 'Elastic Defend',
version: endpointPackageVersion,
version: packageVersion,
},
};
const packagePolicy = (await kbnClient
Expand Down Expand Up @@ -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('/');
}
);
5 changes: 5 additions & 0 deletions x-pack/plugins/security_solution/common/types/app_features.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand Down
18 changes: 11 additions & 7 deletions x-pack/plugins/security_solution/public/app/app.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -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';
Expand Down Expand Up @@ -65,6 +66,7 @@ const StartAppComponent: FC<StartAppComponent> = ({
http,
triggersActionsUi: { actionTypeRegistry },
uiActions,
upselling,
} = services;

const { conversations, setConversations } = useConversationStore();
Expand Down Expand Up @@ -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>
Expand Down
Original file line number Diff line number Diff line change
@@ -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';
Original file line number Diff line number Diff line change
@@ -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;
};
Original file line number Diff line number Diff line change
Expand Up @@ -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();

Expand All @@ -28,14 +29,19 @@ 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', () => {
mockUpselling.registerSections({
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);
});

Expand All @@ -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);
});

Expand All @@ -54,16 +62,21 @@ 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);
});

test('useUpsellingMessage returns null when upsellingMessageId not found', () => {
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);
});
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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';
Original file line number Diff line number Diff line change
Expand Up @@ -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';
Expand All @@ -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';
Expand All @@ -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';
Expand Down Expand Up @@ -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) => {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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';
Expand All @@ -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
Expand All @@ -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>
Expand Down
Loading

0 comments on commit 3efc73c

Please sign in to comment.