From 6d5be7461bf808c8831b58f00d8e9e1c1f636f7e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Alberto=20Bl=C3=A1zquez?= Date: Mon, 23 Dec 2024 20:05:17 +0100 Subject: [PATCH] Add 'inventory' item to Security navigation menu (#204373) ## Summary Add 'inventory' item to Security navigation menu (either when the sidebar is expanded and the full navigation menu is shown or when the sidebar is collapsed and only the Security menu is visible). ### Changeset details - Render 'inventory' item and enable `/app/security/asset_inventory` route both conditionally based on feature flag - Async loading/rendering of AssetInventory main page from within SecuritySolution plugin - Delete unnecessary boilerplate existing in AssetInventory ### Out of scope - AssetInventory nav sub-menu is skipped until more concrete requirements are defined on what to do with them ### How to test Activate the feature flag by adding this line to your local `kibana.dev.yml`: ```yml xpack.securitySolution.enableExperimental: ['assetInventoryStoreEnabled'] ``` ### Screenshots
Full menu (expanded mode) Screenshot 2024-12-16 at 13 12 45
Only Security menu (collapsed mode) Screenshot 2024-12-16 at 13 12 33
AssetInventory loaded async from within Security Solution Screenshot 2024-12-16 at 17 23 01
### Checklist - [x] If a plugin configuration key changed, check if it needs to be allowlisted in the cloud and added to the [docker list](https://github.com/elastic/kibana/blob/main/src/dev/build/tasks/os_packages/docker_generator/resources/base/bin/kibana-docker) - [x] The PR description includes the appropriate Release Notes section, and the correct `release_note:*` label is applied per the [guidelines](https://www.elastic.co/guide/en/kibana/master/contributing.html#kibana-release-notes-process) ### Risks No risks. Navigation item will be added if and only if feature flag is enabled, which shouldn't happen for end users until development is completed. --- .../shared/deeplinks/security/deep_links.ts | 1 + .../asset_inventory/public/application.tsx | 24 ----------- .../asset_inventory/public/components/app.tsx | 43 ++++++++----------- .../asset_inventory/public/methods/index.tsx | 34 +++++++++++++++ .../plugins/asset_inventory/public/plugin.ts | 15 +++---- .../plugins/asset_inventory/public/types.ts | 5 ++- .../plugins/asset_inventory/tsconfig.json | 7 +-- .../security_solution/common/constants.ts | 2 + .../plugins/security_solution/kibana.jsonc | 1 + .../app/solution_navigation/categories.ts | 4 ++ .../public/app/translations.ts | 4 ++ .../security_solution/public/app_links.ts | 3 ++ .../public/asset_inventory/index.ts | 19 ++++++++ .../public/asset_inventory/links.ts | 26 +++++++++++ .../public/asset_inventory/pages/index.tsx | 25 +++++++++++ .../public/asset_inventory/routes.tsx | 30 +++++++++++++ .../security_side_nav/categories.ts | 4 ++ .../public/lazy_sub_plugins.tsx | 2 + .../public/management/links.ts | 2 +- .../security_solution/public/plugin.tsx | 2 + .../plugins/security_solution/public/types.ts | 5 +++ .../plugins/security_solution/tsconfig.json | 1 + 22 files changed, 191 insertions(+), 68 deletions(-) delete mode 100644 x-pack/solutions/security/plugins/asset_inventory/public/application.tsx create mode 100644 x-pack/solutions/security/plugins/asset_inventory/public/methods/index.tsx create mode 100644 x-pack/solutions/security/plugins/security_solution/public/asset_inventory/index.ts create mode 100644 x-pack/solutions/security/plugins/security_solution/public/asset_inventory/links.ts create mode 100644 x-pack/solutions/security/plugins/security_solution/public/asset_inventory/pages/index.tsx create mode 100644 x-pack/solutions/security/plugins/security_solution/public/asset_inventory/routes.tsx diff --git a/src/platform/packages/shared/deeplinks/security/deep_links.ts b/src/platform/packages/shared/deeplinks/security/deep_links.ts index 464003abd369c..cb47043cc4a18 100644 --- a/src/platform/packages/shared/deeplinks/security/deep_links.ts +++ b/src/platform/packages/shared/deeplinks/security/deep_links.ts @@ -11,6 +11,7 @@ export enum SecurityPageName { administration = 'administration', alerts = 'alerts', assets = 'assets', + assetInventory = 'asset_inventory', attackDiscovery = 'attack_discovery', blocklist = 'blocklist', /* diff --git a/x-pack/solutions/security/plugins/asset_inventory/public/application.tsx b/x-pack/solutions/security/plugins/asset_inventory/public/application.tsx deleted file mode 100644 index f442f01d17f7c..0000000000000 --- a/x-pack/solutions/security/plugins/asset_inventory/public/application.tsx +++ /dev/null @@ -1,24 +0,0 @@ -/* - * 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 from 'react'; -import ReactDOM from 'react-dom'; -import type { AppMountParameters, CoreStart } from '@kbn/core/public'; -import type { AppPluginStartDependencies } from './types'; -import { AssetInventoryApp } from './components/app'; - -export const renderApp = ( - { notifications, http }: CoreStart, - {}: AppPluginStartDependencies, - { appBasePath, element }: AppMountParameters -) => { - ReactDOM.render( - , - element - ); - - return () => ReactDOM.unmountComponentAtNode(element); -}; diff --git a/x-pack/solutions/security/plugins/asset_inventory/public/components/app.tsx b/x-pack/solutions/security/plugins/asset_inventory/public/components/app.tsx index 924091034353b..50040ab9d22d3 100644 --- a/x-pack/solutions/security/plugins/asset_inventory/public/components/app.tsx +++ b/x-pack/solutions/security/plugins/asset_inventory/public/components/app.tsx @@ -6,33 +6,26 @@ */ import React from 'react'; import { FormattedMessage, I18nProvider } from '@kbn/i18n-react'; -import { BrowserRouter as Router } from '@kbn/shared-ux-router'; import { EuiPageTemplate, EuiTitle } from '@elastic/eui'; -import type { CoreStart } from '@kbn/core/public'; -interface AssetInventoryAppDeps { - basename: string; - notifications: CoreStart['notifications']; - http: CoreStart['http']; -} - -export const AssetInventoryApp = ({ basename }: AssetInventoryAppDeps) => { +const AssetInventoryApp = () => { return ( - - - <> - - - -

- -

-
-
- -
- -
-
+ + <> + + + +

+ +

+
+
+ +
+ +
); }; + +// we need to use default exports to import it via React.lazy +export default AssetInventoryApp; // eslint-disable-line import/no-default-export diff --git a/x-pack/solutions/security/plugins/asset_inventory/public/methods/index.tsx b/x-pack/solutions/security/plugins/asset_inventory/public/methods/index.tsx new file mode 100644 index 0000000000000..bc44445b9b14a --- /dev/null +++ b/x-pack/solutions/security/plugins/asset_inventory/public/methods/index.tsx @@ -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, { lazy, Suspense } from 'react'; +import { EuiLoadingSpinner } from '@elastic/eui'; +import { QueryClient, QueryClientProvider } from '@tanstack/react-query'; +import { AppPluginStartDependencies } from '../types'; + +// Initializing react-query +const queryClient = new QueryClient({ + defaultOptions: { + queries: { + refetchOnWindowFocus: false, + refetchOnMount: false, + refetchOnReconnect: false, + }, + }, +}); + +const AssetInventoryLazy = lazy(() => import('../components/app')); + +export const getAssetInventoryLazy = (props: AppPluginStartDependencies) => { + return ( + + }> + + + + ); +}; diff --git a/x-pack/solutions/security/plugins/asset_inventory/public/plugin.ts b/x-pack/solutions/security/plugins/asset_inventory/public/plugin.ts index fd2841f5b2335..f6663399e7a2b 100644 --- a/x-pack/solutions/security/plugins/asset_inventory/public/plugin.ts +++ b/x-pack/solutions/security/plugins/asset_inventory/public/plugin.ts @@ -4,12 +4,13 @@ * 2.0; you may not use this file except in compliance with the Elastic License * 2.0. */ -import type { AppMountParameters, CoreSetup, CoreStart, Plugin } from '@kbn/core/public'; +import type { CoreSetup, CoreStart, Plugin } from '@kbn/core/public'; import type { AssetInventoryPluginSetup, AssetInventoryPluginStart, AppPluginStartDependencies, } from './types'; +import { getAssetInventoryLazy } from './methods'; export class AssetInventoryPlugin implements Plugin @@ -17,16 +18,10 @@ export class AssetInventoryPlugin public setup(core: CoreSetup): AssetInventoryPluginSetup { return {}; } - public start( - coreStart: CoreStart, - depsStart: AppPluginStartDependencies - ): AssetInventoryPluginStart { + public start(coreStart: CoreStart): AssetInventoryPluginStart { return { - getAssetInventoryPage: async (params: AppMountParameters) => { - // Load application bundle - const { renderApp } = await import('./application'); - // Render the application - return renderApp(coreStart, depsStart as AppPluginStartDependencies, params); + getAssetInventoryPage: (assetInventoryDeps: AppPluginStartDependencies) => { + return getAssetInventoryLazy(assetInventoryDeps); }, }; } diff --git a/x-pack/solutions/security/plugins/asset_inventory/public/types.ts b/x-pack/solutions/security/plugins/asset_inventory/public/types.ts index a551b4d231c3d..2d1f51329e5c9 100644 --- a/x-pack/solutions/security/plugins/asset_inventory/public/types.ts +++ b/x-pack/solutions/security/plugins/asset_inventory/public/types.ts @@ -8,8 +8,9 @@ // eslint-disable-next-line @typescript-eslint/no-empty-interface export interface AssetInventoryPluginSetup {} -// eslint-disable-next-line @typescript-eslint/no-empty-interface -export interface AssetInventoryPluginStart {} +export interface AssetInventoryPluginStart { + getAssetInventoryPage: (assetInventoryStartDeps: AppPluginStartDependencies) => JSX.Element; +} // eslint-disable-next-line @typescript-eslint/no-empty-interface export interface AppPluginStartDependencies {} diff --git a/x-pack/solutions/security/plugins/asset_inventory/tsconfig.json b/x-pack/solutions/security/plugins/asset_inventory/tsconfig.json index b733fc545be25..b4dd4bdb16a02 100644 --- a/x-pack/solutions/security/plugins/asset_inventory/tsconfig.json +++ b/x-pack/solutions/security/plugins/asset_inventory/tsconfig.json @@ -14,10 +14,5 @@ "../../../../../typings/**/*" ], "exclude": ["target/**/*"], - "kbn_references": [ - "@kbn/core", - "@kbn/i18n-react", - "@kbn/shared-ux-router", - "@kbn/securitysolution-es-utils" - ] + "kbn_references": ["@kbn/core", "@kbn/i18n-react", "@kbn/securitysolution-es-utils"] } diff --git a/x-pack/solutions/security/plugins/security_solution/common/constants.ts b/x-pack/solutions/security/plugins/security_solution/common/constants.ts index 3818813d94a72..3339c97a61a6a 100644 --- a/x-pack/solutions/security/plugins/security_solution/common/constants.ts +++ b/x-pack/solutions/security/plugins/security_solution/common/constants.ts @@ -19,6 +19,7 @@ export { SecurityPageName } from '@kbn/security-solution-navigation'; */ export const APP_ID = 'securitySolution' as const; export const APP_UI_ID = 'securitySolutionUI' as const; +export const ASSET_INVENTORY_FEATURE_ID = 'securitySolutionAssetInventory' as const; export const ASSISTANT_FEATURE_ID = 'securitySolutionAssistant' as const; export const ATTACK_DISCOVERY_FEATURE_ID = 'securitySolutionAttackDiscovery' as const; export const CASES_FEATURE_ID = 'securitySolutionCasesV2' as const; @@ -102,6 +103,7 @@ export const EXCEPTIONS_PATH = '/exceptions' as const; export const EXCEPTION_LIST_DETAIL_PATH = `${EXCEPTIONS_PATH}/details/:detailName` as const; export const HOSTS_PATH = '/hosts' as const; export const ATTACK_DISCOVERY_PATH = '/attack_discovery' as const; +export const ASSET_INVENTORY_PATH = '/asset_inventory' as const; export const USERS_PATH = '/users' as const; export const KUBERNETES_PATH = '/kubernetes' as const; export const NETWORK_PATH = '/network' as const; diff --git a/x-pack/solutions/security/plugins/security_solution/kibana.jsonc b/x-pack/solutions/security/plugins/security_solution/kibana.jsonc index f672378c88df8..0ffd1922fd2ab 100644 --- a/x-pack/solutions/security/plugins/security_solution/kibana.jsonc +++ b/x-pack/solutions/security/plugins/security_solution/kibana.jsonc @@ -16,6 +16,7 @@ ], "requiredPlugins": [ "actions", + "assetInventory", "alerting", "cases", "cloud", diff --git a/x-pack/solutions/security/plugins/security_solution/public/app/solution_navigation/categories.ts b/x-pack/solutions/security/plugins/security_solution/public/app/solution_navigation/categories.ts index 8d815ded5a3c4..5cfabe266eda8 100644 --- a/x-pack/solutions/security/plugins/security_solution/public/app/solution_navigation/categories.ts +++ b/x-pack/solutions/security/plugins/security_solution/public/app/solution_navigation/categories.ts @@ -37,6 +37,10 @@ export const CATEGORIES: Array> = [ SecurityPageName.exploreLanding, ], }, + { + type: LinkCategoryType.separator, + linkIds: [SecurityPageName.assetInventory], + }, { type: LinkCategoryType.separator, linkIds: [SecurityPageName.assets], diff --git a/x-pack/solutions/security/plugins/security_solution/public/app/translations.ts b/x-pack/solutions/security/plugins/security_solution/public/app/translations.ts index 1769a805f488f..79de4e62f0473 100644 --- a/x-pack/solutions/security/plugins/security_solution/public/app/translations.ts +++ b/x-pack/solutions/security/plugins/security_solution/public/app/translations.ts @@ -119,6 +119,10 @@ export const ATTACK_DISCOVERY = i18n.translate( } ); +export const INVENTORY = i18n.translate('xpack.securitySolution.navigation.inventory', { + defaultMessage: 'Inventory', +}); + export const TIMELINES = i18n.translate('xpack.securitySolution.navigation.timelines', { defaultMessage: 'Timelines', }); diff --git a/x-pack/solutions/security/plugins/security_solution/public/app_links.ts b/x-pack/solutions/security/plugins/security_solution/public/app_links.ts index dca76b1c37f70..d70b96bbd8250 100644 --- a/x-pack/solutions/security/plugins/security_solution/public/app_links.ts +++ b/x-pack/solutions/security/plugins/security_solution/public/app_links.ts @@ -7,6 +7,7 @@ import type { CoreStart } from '@kbn/core/public'; import { links as attackDiscoveryLinks } from './attack_discovery/links'; +import { links as assetInventoryLinks } from './asset_inventory/links'; import type { AppLinkItems } from './common/links/types'; import { indicatorsLinks } from './threat_intelligence/links'; import { links as alertsLinks } from './detections/links'; @@ -32,6 +33,7 @@ export const appLinks: AppLinkItems = Object.freeze([ timelinesLinks, indicatorsLinks, exploreLinks, + assetInventoryLinks, rulesLinks, onboardingLinks, managementLinks, @@ -52,6 +54,7 @@ export const getFilteredLinks = async ( timelinesLinks, indicatorsLinks, exploreLinks, + assetInventoryLinks, rulesLinks, onboardingLinks, managementFilteredLinks, diff --git a/x-pack/solutions/security/plugins/security_solution/public/asset_inventory/index.ts b/x-pack/solutions/security/plugins/security_solution/public/asset_inventory/index.ts new file mode 100644 index 0000000000000..78f27a3e42328 --- /dev/null +++ b/x-pack/solutions/security/plugins/security_solution/public/asset_inventory/index.ts @@ -0,0 +1,19 @@ +/* + * 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 { SecuritySubPlugin } from '../app/types'; +import { routes } from './routes'; + +export class AssetInventory { + public setup() {} + + public start(): SecuritySubPlugin { + return { + routes, + }; + } +} diff --git a/x-pack/solutions/security/plugins/security_solution/public/asset_inventory/links.ts b/x-pack/solutions/security/plugins/security_solution/public/asset_inventory/links.ts new file mode 100644 index 0000000000000..2eb5902006744 --- /dev/null +++ b/x-pack/solutions/security/plugins/security_solution/public/asset_inventory/links.ts @@ -0,0 +1,26 @@ +/* + * 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 { i18n } from '@kbn/i18n'; + +import { INVENTORY } from '../app/translations'; +import { ASSET_INVENTORY_PATH, SecurityPageName, SERVER_APP_ID } from '../../common/constants'; +import type { LinkItem } from '../common/links/types'; + +export const links: LinkItem = { + capabilities: [`${SERVER_APP_ID}.show`], + globalNavPosition: 10, + globalSearchKeywords: [ + i18n.translate('xpack.securitySolution.appLinks.inventory', { + defaultMessage: 'Inventory', + }), + ], + experimentalKey: 'assetInventoryStoreEnabled', + id: SecurityPageName.assetInventory, + path: ASSET_INVENTORY_PATH, + title: INVENTORY, +}; diff --git a/x-pack/solutions/security/plugins/security_solution/public/asset_inventory/pages/index.tsx b/x-pack/solutions/security/plugins/security_solution/public/asset_inventory/pages/index.tsx new file mode 100644 index 0000000000000..4f7a0f5947c88 --- /dev/null +++ b/x-pack/solutions/security/plugins/security_solution/public/asset_inventory/pages/index.tsx @@ -0,0 +1,25 @@ +/* + * 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 from 'react'; +import { SecuritySolutionPageWrapper } from '../../common/components/page_wrapper'; +import { useKibana } from '../../common/lib/kibana'; +import { SecurityPageName } from '../../../common/constants'; +import { SpyRoute } from '../../common/utils/route/spy_routes'; + +export const AssetInventoryContainer = React.memo(() => { + const { assetInventory } = useKibana().services; + + return ( + + {assetInventory.getAssetInventoryPage({})} + + + ); +}); + +AssetInventoryContainer.displayName = 'AssetInventoryContainer'; diff --git a/x-pack/solutions/security/plugins/security_solution/public/asset_inventory/routes.tsx b/x-pack/solutions/security/plugins/security_solution/public/asset_inventory/routes.tsx new file mode 100644 index 0000000000000..5707d859d844e --- /dev/null +++ b/x-pack/solutions/security/plugins/security_solution/public/asset_inventory/routes.tsx @@ -0,0 +1,30 @@ +/* + * 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 from 'react'; +import type { SecuritySubPluginRoutes } from '../app/types'; +import { SecurityPageName } from '../app/types'; +import { ASSET_INVENTORY_PATH } from '../../common/constants'; +import { PluginTemplateWrapper } from '../common/components/plugin_template_wrapper'; +import { SecurityRoutePageWrapper } from '../common/components/security_route_page_wrapper'; +import { ExperimentalFeaturesService } from '../common/experimental_features_service'; +import { AssetInventoryContainer } from './pages'; + +export const AssetInventoryRoutes = () => ( + + + + + +); + +export const routes: SecuritySubPluginRoutes = [ + { + path: ExperimentalFeaturesService.get().assetInventoryStoreEnabled ? ASSET_INVENTORY_PATH : [], + component: AssetInventoryRoutes, + }, +]; diff --git a/x-pack/solutions/security/plugins/security_solution/public/common/components/navigation/security_side_nav/categories.ts b/x-pack/solutions/security/plugins/security_solution/public/common/components/navigation/security_side_nav/categories.ts index cc695d72a8ffc..49f0601dc11c6 100644 --- a/x-pack/solutions/security/plugins/security_solution/public/common/components/navigation/security_side_nav/categories.ts +++ b/x-pack/solutions/security/plugins/security_solution/public/common/components/navigation/security_side_nav/categories.ts @@ -31,4 +31,8 @@ export const CATEGORIES: SeparatorLinkCategory[] = [ SecurityPageName.exploreLanding, ], }, + { + type: LinkCategoryType.separator, + linkIds: [SecurityPageName.assetInventory], + }, ]; diff --git a/x-pack/solutions/security/plugins/security_solution/public/lazy_sub_plugins.tsx b/x-pack/solutions/security/plugins/security_solution/public/lazy_sub_plugins.tsx index 1be423c988397..a399f25fb8bda 100644 --- a/x-pack/solutions/security/plugins/security_solution/public/lazy_sub_plugins.tsx +++ b/x-pack/solutions/security/plugins/security_solution/public/lazy_sub_plugins.tsx @@ -10,6 +10,7 @@ * By loading these later we can reduce the initial bundle size and allow users to delay loading these dependencies until they are needed. */ +import { AssetInventory } from './asset_inventory'; import { AttackDiscovery } from './attack_discovery'; import { Cases } from './cases'; import { Detections } from './detections'; @@ -35,6 +36,7 @@ import { SiemMigrations } from './siem_migrations'; * The classes used to instantiate the sub plugins. These are grouped into a single object for the sake of bundling them in a single dynamic import. */ const subPluginClasses = { + AssetInventory, AttackDiscovery, Detections, Cases, diff --git a/x-pack/solutions/security/plugins/security_solution/public/management/links.ts b/x-pack/solutions/security/plugins/security_solution/public/management/links.ts index 49049218d4dc1..909395df8df4f 100644 --- a/x-pack/solutions/security/plugins/security_solution/public/management/links.ts +++ b/x-pack/solutions/security/plugins/security_solution/public/management/links.ts @@ -99,7 +99,7 @@ export const links: LinkItem = { path: MANAGE_PATH, skipUrlState: true, hideTimeline: true, - globalNavPosition: 10, + globalNavPosition: 11, capabilities: [`${SERVER_APP_ID}.show`], globalSearchKeywords: [ i18n.translate('xpack.securitySolution.appLinks.manage', { diff --git a/x-pack/solutions/security/plugins/security_solution/public/plugin.tsx b/x-pack/solutions/security/plugins/security_solution/public/plugin.tsx index 497b92637dad5..64146d5b587e4 100644 --- a/x-pack/solutions/security/plugins/security_solution/public/plugin.tsx +++ b/x-pack/solutions/security/plugins/security_solution/public/plugin.tsx @@ -288,6 +288,7 @@ export class Plugin implements IPlugin; alerts: Awaited>; + assetInventory: Awaited>; attackDiscovery: ReturnType; cloudDefend: ReturnType; cloudSecurityPosture: ReturnType; diff --git a/x-pack/solutions/security/plugins/security_solution/tsconfig.json b/x-pack/solutions/security/plugins/security_solution/tsconfig.json index c6f5b8ac09166..655fe3a489694 100644 --- a/x-pack/solutions/security/plugins/security_solution/tsconfig.json +++ b/x-pack/solutions/security/plugins/security_solution/tsconfig.json @@ -215,6 +215,7 @@ "@kbn/cbor", "@kbn/zod", "@kbn/cloud-security-posture", + "@kbn/asset-inventory-plugin", "@kbn/security-solution-distribution-bar", "@kbn/cloud-security-posture-common", "@kbn/cloud-security-posture-graph",