[SecuritySolution] Bootstrap Initial Privileges User Monitoring Onboarding Workflow (#217180)

## Summary

Bootstrap the initial page structure for Privileges User Monitoring
Onboarding Workflow

### What's included
* Create Entity Analytics Page
* Create Privileged User Monitoring Page
* Add the pages to the SecuritySolution and the global menu
* Hide the page when the experimental flag is disabled
`privilegeMonitoringEnabled`

### What's not included
* The content of the Entity Analytics and Privileged User Monitoring
pages

### Answered Questions
* The path `entity_analytics` has already been taken. I chose
`entity_analytics_landing`
* Should the link show up on the global EA page? Yes
* Navigation Menu for ESS? Good for now
* Which permission/capabilities are required? Same as risk engine

### How to test it?
* You only need to run Kibana and check if the navigation item is in the
menu
* The page should show up on the global nav
* The page should show up on the global search bar

* Visibility constraints
  * It should be hidden when `privilegeMonitoringEnabled` is not enabled
* It should be visible when the user has access to the entity analytics
feature
  * It should be visible for platinum users


### Checklist

- [x] Any text added follows [EUI's writing
guidelines](https://elastic.github.io/eui/#/guidelines/writing), uses
sentence case text and includes [i18n
support](https://github.com/elastic/kibana/blob/main/src/platform/packages/shared/kbn-i18n/README.md)
- [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)
This commit is contained in:
Pablo Machado 2025-04-14 13:26:11 +02:00 committed by GitHub
parent f3042efa8f
commit 47090f198e
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
18 changed files with 267 additions and 3 deletions

View file

@ -80,9 +80,11 @@ export enum SecurityPageName {
usersAuthentications = 'users-authentications',
usersEvents = 'users-events',
usersRisk = 'users-risk',
entityAnalytics = 'entity_analytics',
entityAnalytics = 'entity_analytics', // This is the first Entity Analytics page, that is why the name is too generic.
entityAnalyticsManagement = 'entity_analytics-management',
entityAnalyticsAssetClassification = 'entity_analytics-asset-classification',
entityAnalyticsLanding = 'entity_analytics-landing',
privilegedUserMonitoring = 'entity_analytics-privileged_user_monitoring',
entityAnalyticsEntityStoreManagement = 'entity_analytics-entity_store_management',
coverageOverview = 'coverage-overview',
notes = 'notes',

View file

@ -153,6 +153,11 @@ export const i18nStrings = {
entityStore: i18n.translate('securitySolutionPackages.navLinks.entityStore', {
defaultMessage: 'Entity Store',
}),
entityAnalytics: {
landing: i18n.translate('securitySolutionPackages.navLinks.entityAnalytics', {
defaultMessage: 'Entity Analytics',
}),
},
devTools: i18n.translate('securitySolutionPackages.navLinks.devTools', {
defaultMessage: 'Developer tools',
}),

View file

@ -129,6 +129,9 @@ export const ENTITY_ANALYTICS_ASSET_CRITICALITY_PATH =
`/entity_analytics_asset_criticality` as const;
export const ENTITY_ANALYTICS_ENTITY_STORE_MANAGEMENT_PATH =
`/entity_analytics_entity_store` as const;
export const ENTITY_ANALYTICS_LANDING_PATH = '/entity_analytics_landing' as const;
export const ENTITY_ANALYTICS_PRIVILEGED_USER_MONITORING_PATH =
'/entity_analytics_privileged_user_monitoring' as const;
export const APP_ALERTS_PATH = `${APP_PATH}${ALERTS_PATH}` as const;
export const APP_CASES_PATH = `${APP_PATH}${CASES_PATH}` as const;
export const APP_ENDPOINTS_PATH = `${APP_PATH}${ENDPOINTS_PATH}` as const;

View file

@ -37,6 +37,10 @@ export const CATEGORIES: Array<SeparatorLinkCategory<SolutionPageName>> = [
SecurityPageName.exploreLanding,
],
},
{
type: LinkCategoryType.separator,
linkIds: [SecurityPageName.entityAnalyticsLanding],
},
{
type: LinkCategoryType.separator,
linkIds: [SecurityPageName.assetInventory],

View file

@ -54,6 +54,13 @@ export const ENTITY_ANALYTICS = i18n.translate(
}
);
export const ENTITY_ANALYTICS_PRIVILEGED_USER_MONITORING = i18n.translate(
'xpack.securitySolution.navigation.privilegedUserMonitoring',
{
defaultMessage: 'Privileged User Monitoring',
}
);
export const HOSTS = i18n.translate('xpack.securitySolution.navigation.hosts', {
defaultMessage: 'Hosts',
});

View file

@ -21,6 +21,7 @@ import { onboardingLinks } from './onboarding/links';
import { findingsLinks } from './cloud_security_posture/links';
import type { StartPlugins } from './types';
import { dashboardsLinks } from './dashboards/links';
import { entityAnalyticsLinks } from './entity_analytics/links';
// TODO: remove after rollout https://github.com/elastic/kibana/issues/179572
export { solutionAppLinksSwitcher } from './app/solution_navigation/links/app_links';
@ -36,6 +37,7 @@ export const appLinks: AppLinkItems = Object.freeze([
timelinesLinks,
indicatorsLinks,
exploreLinks,
entityAnalyticsLinks,
assetInventoryLinks,
rulesLinks,
onboardingLinks,
@ -59,6 +61,7 @@ export const getFilteredLinks = async (
timelinesLinks,
indicatorsLinks,
exploreLinks,
entityAnalyticsLinks,
assetInventoryLinks,
rulesLinks,
onboardingLinks,

View file

@ -18,7 +18,7 @@ import type { LinkItem } from '../common/links/types';
export const links: LinkItem = {
capabilities: [`${SECURITY_FEATURE_ID}.show`],
globalNavPosition: 10,
globalNavPosition: 11,
globalSearchKeywords: [
i18n.translate('xpack.securitySolution.appLinks.inventory', {
defaultMessage: 'Inventory',

View file

@ -31,6 +31,10 @@ export const CATEGORIES: SeparatorLinkCategory[] = [
SecurityPageName.exploreLanding,
],
},
{
type: LinkCategoryType.separator,
linkIds: [SecurityPageName.entityAnalyticsLanding],
},
{
type: LinkCategoryType.separator,
linkIds: [SecurityPageName.assetInventory],

Binary file not shown.

After

Width:  |  Height:  |  Size: 6.8 KiB

View file

@ -0,0 +1,58 @@
/*
* 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 {
SecurityPageName,
SECURITY_FEATURE_ID,
ENTITY_ANALYTICS_LANDING_PATH,
ENTITY_ANALYTICS_PRIVILEGED_USER_MONITORING_PATH,
} from '../../common/constants';
import type { LinkItem } from '../common/links/types';
import { ENTITY_ANALYTICS, ENTITY_ANALYTICS_PRIVILEGED_USER_MONITORING } from '../app/translations';
import privilegedUserMonitoringPageImg from '../common/images/privileged_user_monitoring_page.png';
const privMonLinks: LinkItem = {
id: SecurityPageName.privilegedUserMonitoring,
title: ENTITY_ANALYTICS_PRIVILEGED_USER_MONITORING,
landingImage: privilegedUserMonitoringPageImg,
description: i18n.translate(
'xpack.securitySolution.appLinks.privilegedUserMonitoring.Description',
{
defaultMessage: '???????????????????', // TODO
}
),
path: ENTITY_ANALYTICS_PRIVILEGED_USER_MONITORING_PATH,
globalSearchKeywords: [
i18n.translate('xpack.securitySolution.appLinks.privilegedUserMonitoring', {
defaultMessage: 'Privileged User Monitoring',
}),
],
experimentalKey: 'privilegeMonitoringEnabled',
hideTimeline: true,
skipUrlState: true,
capabilities: [`${SECURITY_FEATURE_ID}.entity-analytics`],
licenseType: 'platinum',
};
export const entityAnalyticsLinks: LinkItem = {
id: SecurityPageName.entityAnalyticsLanding,
title: ENTITY_ANALYTICS,
path: ENTITY_ANALYTICS_LANDING_PATH,
globalNavPosition: 10,
globalSearchKeywords: [
i18n.translate('xpack.securitySolution.appLinks.entityAnalytics.landing', {
defaultMessage: 'Entity Analytics',
}),
],
links: [privMonLinks],
hideTimeline: true,
skipUrlState: true,
experimentalKey: 'privilegeMonitoringEnabled',
capabilities: [`${SECURITY_FEATURE_ID}.entity-analytics`],
licenseType: 'platinum',
};

View file

@ -0,0 +1,33 @@
/*
* 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 { i18n } from '@kbn/i18n';
import { LandingLinksImages } from '@kbn/security-solution-navigation/landing_links';
import { SecurityPageName } from '../../app/types';
import { HeaderPage } from '../../common/components/header_page';
import { useRootNavLink } from '../../common/links/nav_links';
import { SecuritySolutionPageWrapper } from '../../common/components/page_wrapper';
import { SpyRoute } from '../../common/utils/route/spy_routes';
import { trackLandingLinkClick } from '../../common/lib/telemetry/trackers';
import { useGlobalQueryString } from '../../common/utils/global_query_string';
const PAGE_TITLE = i18n.translate('xpack.securitySolution.entityAnalytics.landing.pageTitle', {
defaultMessage: 'Entity Analytics',
});
export const EntityAnalyticsLandingPage = () => {
const { links = [] } = useRootNavLink(SecurityPageName.entityAnalyticsLanding) ?? {};
const urlState = useGlobalQueryString();
return (
<SecuritySolutionPageWrapper>
<HeaderPage title={PAGE_TITLE} />
<LandingLinksImages items={links} urlState={urlState} onLinkClick={trackLandingLinkClick} />
<SpyRoute pageName={SecurityPageName.entityAnalyticsLanding} />
</SecuritySolutionPageWrapper>
);
};

View file

@ -0,0 +1,28 @@
/*
* 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 { i18n } from '@kbn/i18n';
import { SecurityPageName } from '../../app/types';
import { HeaderPage } from '../../common/components/header_page';
import { SecuritySolutionPageWrapper } from '../../common/components/page_wrapper';
import { SpyRoute } from '../../common/utils/route/spy_routes';
const PAGE_TITLE = i18n.translate(
'xpack.securitySolution.entityAnalytics.privilegedUserMonitoring.pageTitle',
{
defaultMessage: 'Privileged User Monitoring',
}
);
export const EntityAnalyticsPrivilegedUserMonitoringPage = () => {
return (
<SecuritySolutionPageWrapper>
<HeaderPage title={PAGE_TITLE} />
<SpyRoute pageName={SecurityPageName.privilegedUserMonitoring} />
</SecuritySolutionPageWrapper>
);
};

View file

@ -15,12 +15,16 @@ import { NotFoundPage } from '../app/404';
import {
ENTITY_ANALYTICS_ASSET_CRITICALITY_PATH,
ENTITY_ANALYTICS_ENTITY_STORE_MANAGEMENT_PATH,
ENTITY_ANALYTICS_LANDING_PATH,
ENTITY_ANALYTICS_MANAGEMENT_PATH,
ENTITY_ANALYTICS_PRIVILEGED_USER_MONITORING_PATH,
SecurityPageName,
} from '../../common/constants';
import { EntityAnalyticsManagementPage } from './pages/entity_analytics_management_page';
import { PluginTemplateWrapper } from '../common/components/plugin_template_wrapper';
import { EntityStoreManagementPage } from './pages/entity_store_management_page';
import { EntityAnalyticsLandingPage } from './pages/entity_analytics_landing';
import { EntityAnalyticsPrivilegedUserMonitoringPage } from './pages/entity_analytics_privileged_user_monitoring_page';
const EntityAnalyticsManagementTelemetry = () => (
<PluginTemplateWrapper>
@ -94,6 +98,55 @@ const EntityAnalyticsEntityStoreContainer: React.FC = React.memo(() => {
EntityAnalyticsEntityStoreContainer.displayName = 'EntityAnalyticsEntityStoreContainer';
const EntityAnalyticsLandingTelemetry = () => (
<PluginTemplateWrapper>
<TrackApplicationView viewId={SecurityPageName.entityAnalyticsLanding}>
<EntityAnalyticsLandingPage />
<SpyRoute pageName={SecurityPageName.entityAnalyticsLanding} />
</TrackApplicationView>
</PluginTemplateWrapper>
);
const EntityAnalyticsLandingContainer: React.FC = React.memo(() => {
return (
<Routes>
<Route
path={ENTITY_ANALYTICS_LANDING_PATH}
exact
component={EntityAnalyticsLandingTelemetry}
/>
<Route component={NotFoundPage} />
</Routes>
);
});
EntityAnalyticsLandingContainer.displayName = 'EntityAnalyticsLandingContainer';
const EntityAnalyticsPrivilegedUserMonitoringTelemetry = () => (
<PluginTemplateWrapper>
<TrackApplicationView viewId={SecurityPageName.privilegedUserMonitoring}>
<EntityAnalyticsPrivilegedUserMonitoringPage />
<SpyRoute pageName={SecurityPageName.privilegedUserMonitoring} />
</TrackApplicationView>
</PluginTemplateWrapper>
);
const EntityAnalyticsPrivilegedUserMonitoringContainer: React.FC = React.memo(() => {
return (
<Routes>
<Route
path={ENTITY_ANALYTICS_PRIVILEGED_USER_MONITORING_PATH}
exact
component={EntityAnalyticsPrivilegedUserMonitoringTelemetry}
/>
<Route component={NotFoundPage} />
</Routes>
);
});
EntityAnalyticsPrivilegedUserMonitoringContainer.displayName =
'EntityAnalyticsPrivilegedUserMonitoringContainer';
export const routes = [
{
path: ENTITY_ANALYTICS_MANAGEMENT_PATH,
@ -107,4 +160,12 @@ export const routes = [
path: ENTITY_ANALYTICS_ENTITY_STORE_MANAGEMENT_PATH,
component: EntityAnalyticsEntityStoreContainer,
},
{
path: ENTITY_ANALYTICS_LANDING_PATH,
component: EntityAnalyticsLandingContainer,
},
{
path: ENTITY_ANALYTICS_PRIVILEGED_USER_MONITORING_PATH,
component: EntityAnalyticsPrivilegedUserMonitoringContainer,
},
];

View file

@ -92,7 +92,7 @@ export const links: LinkItem = {
path: MANAGE_PATH,
skipUrlState: true,
hideTimeline: true,
globalNavPosition: 11,
globalNavPosition: 12,
capabilities: [`${SECURITY_FEATURE_ID}.show`],
globalSearchKeywords: [
i18n.translate('xpack.securitySolution.appLinks.manage', {

View file

@ -374,6 +374,20 @@ Object {
},
],
},
Object {
"children": Array [
Object {
"id": "entity_analytics-privileged_user_monitoring",
"link": "securitySolutionUI:entity_analytics-privileged_user_monitoring",
"renderAs": "item",
},
],
"id": "entity_analytics-landing",
"link": "securitySolutionUI:entity_analytics-landing",
"renderAs": "panelOpener",
"spaceBefore": null,
"title": "Entity Analytics",
},
Object {
"breadcrumbStatus": "hidden",
"children": Array [

View file

@ -438,6 +438,20 @@ const createNavigationTree$ = (services: Services): Rx.Observable<NavigationTree
},
],
},
{
id: 'entity_analytics-landing',
link: securityLink(SecurityPageName.entityAnalyticsLanding),
title: i18nStrings.entityAnalytics.landing,
spaceBefore: null,
children: [
{
id: 'entity_analytics-privileged_user_monitoring',
link: securityLink(SecurityPageName.privilegedUserMonitoring),
renderAs: 'item',
},
],
renderAs: 'panelOpener',
},
{
breadcrumbStatus: 'hidden',
children: [

View file

@ -360,6 +360,20 @@ Object {
"renderAs": "panelOpener",
"title": "Assets",
},
Object {
"children": Array [
Object {
"id": "entity_analytics-privileged_user_monitoring",
"link": "securitySolutionUI:entity_analytics-privileged_user_monitoring",
"renderAs": "item",
},
],
"id": "entity_analytics-landing",
"link": "securitySolutionUI:entity_analytics-landing",
"renderAs": "panelOpener",
"spaceBefore": null,
"title": "Entity Analytics",
},
Object {
"children": Array [
Object {

View file

@ -416,6 +416,20 @@ export const createSecurityNavigationTree$ = (
],
renderAs: 'panelOpener',
},
{
id: 'entity_analytics-landing',
link: securityLink(SecurityPageName.entityAnalyticsLanding),
title: i18nStrings.entityAnalytics.landing,
spaceBefore: null,
children: [
{
id: 'entity_analytics-privileged_user_monitoring',
link: securityLink(SecurityPageName.privilegedUserMonitoring),
renderAs: 'item',
},
],
renderAs: 'panelOpener',
},
createMachineLearningNavigationTree(),
{
id: 'entity_analytics-management',