diff --git a/.github/CODEOWNERS b/.github/CODEOWNERS index bfe6716b0d2c0..332bf2832f0ba 100644 --- a/.github/CODEOWNERS +++ b/.github/CODEOWNERS @@ -578,6 +578,7 @@ x-pack/plugins/serverless @elastic/appex-sharedux x-pack/plugins/serverless_observability @elastic/appex-sharedux packages/serverless/project_switcher @elastic/appex-sharedux x-pack/plugins/serverless_search @elastic/appex-sharedux +x-pack/plugins/serverless_security @elastic/appex-sharedux packages/serverless/storybook/config @elastic/appex-sharedux packages/serverless/types @elastic/appex-sharedux test/plugin_functional/plugins/session_notifications @elastic/kibana-core diff --git a/config/serverless.security.yml b/config/serverless.security.yml index e906e82191409..f01186a6ceecf 100644 --- a/config/serverless.security.yml +++ b/config/serverless.security.yml @@ -1,2 +1,16 @@ +# Security Project config + +## Disable plugins +enterpriseSearch.enabled: false +xpack.apm.enabled: false +xpack.observability.enabled: false +xpack.uptime.enabled: false + +## Enable the Serverless Security plugin +xpack.serverless.security.enabled: true + +## Set the home route uiSettings.overrides.defaultRoute: /app/security/get_started + +## Set the dev project switcher current type xpack.serverless.plugin.developer.projectSwitcher.currentType: 'security' diff --git a/docs/developer/plugin-list.asciidoc b/docs/developer/plugin-list.asciidoc index 64b718e955f67..e218387d64341 100644 --- a/docs/developer/plugin-list.asciidoc +++ b/docs/developer/plugin-list.asciidoc @@ -722,6 +722,10 @@ Kibana. |This plugin contains configuration and code used to create a Serverless Search project. It leverages universal configuration and other APIs in the serverless plugin to configure Kibana. +|{kib-repo}blob/{branch}/x-pack/plugins/serverless_security/README.mdx[serverlessSecurity] +|This plugin contains configuration and code used to create a Serverless Security project. It leverages universal configuration and other APIs in the serverless plugin to configure Kibana. + + |{kib-repo}blob/{branch}/x-pack/plugins/session_view/README.md[sessionView] |Session View is meant to provide a visualization into what is going on in a particular Linux environment where the agent is running. It looks likes a terminal emulator; however, it is a tool for introspecting process activity and understanding user and service behaviour in your Linux servers and infrastructure. It is a time-ordered series of process executions displayed in a tree over time. diff --git a/package.json b/package.json index 01793344399ed..35957c9e202b6 100644 --- a/package.json +++ b/package.json @@ -579,6 +579,7 @@ "@kbn/serverless-observability": "link:x-pack/plugins/serverless_observability", "@kbn/serverless-project-switcher": "link:packages/serverless/project_switcher", "@kbn/serverless-search": "link:x-pack/plugins/serverless_search", + "@kbn/serverless-security": "link:x-pack/plugins/serverless_security", "@kbn/serverless-types": "link:packages/serverless/types", "@kbn/session-notifications-plugin": "link:test/plugin_functional/plugins/session_notifications", "@kbn/session-view-plugin": "link:x-pack/plugins/session_view", diff --git a/packages/kbn-optimizer/limits.yml b/packages/kbn-optimizer/limits.yml index 85c8b6c888236..13cf535a36873 100644 --- a/packages/kbn-optimizer/limits.yml +++ b/packages/kbn-optimizer/limits.yml @@ -118,6 +118,7 @@ pageLoadAssetSize: serverless: 16573 serverlessObservability: 16582 serverlessSearch: 17548 + serverlessSecurity: 41807 sessionView: 77750 share: 71239 snapshotRestore: 79032 diff --git a/src/plugins/management/public/application.tsx b/src/plugins/management/public/application.tsx index dc16faa4f6d04..8c4a8af510391 100644 --- a/src/plugins/management/public/application.tsx +++ b/src/plugins/management/public/application.tsx @@ -16,15 +16,6 @@ export const renderApp = async ( { history, appBasePath, element, theme$ }: AppMountParameters, dependencies: ManagementAppDependencies ) => { - ReactDOM.render( - , - element - ); - + ReactDOM.render(, element); return () => ReactDOM.unmountComponentAtNode(element); }; diff --git a/src/plugins/management/public/components/management_app/management_app.tsx b/src/plugins/management/public/components/management_app/management_app.tsx index bc0b88e7dffcb..50ab45a867c98 100644 --- a/src/plugins/management/public/components/management_app/management_app.tsx +++ b/src/plugins/management/public/components/management_app/management_app.tsx @@ -8,12 +8,15 @@ import './management_app.scss'; import React, { useState, useEffect, useCallback } from 'react'; +import { BehaviorSubject } from 'rxjs'; + import { I18nProvider } from '@kbn/i18n-react'; import { i18n } from '@kbn/i18n'; import { AppMountParameters, ChromeBreadcrumb, ScopedHistory } from '@kbn/core/public'; import { reactRouterNavigate, KibanaThemeProvider } from '@kbn/kibana-react-plugin/public'; import { KibanaPageTemplate, KibanaPageTemplateProps } from '@kbn/shared-ux-page-kibana-template'; +import useObservable from 'react-use/lib/useObservable'; import { ManagementSection, MANAGEMENT_BREADCRUMB, @@ -34,12 +37,14 @@ export interface ManagementAppDependencies { sections: SectionsServiceStart; kibanaVersion: string; setBreadcrumbs: (newBreadcrumbs: ChromeBreadcrumb[]) => void; + isSidebarEnabled$: BehaviorSubject; } export const ManagementApp = ({ dependencies, history, theme$ }: ManagementAppProps) => { - const { setBreadcrumbs } = dependencies; + const { setBreadcrumbs, isSidebarEnabled$ } = dependencies; const [selectedId, setSelectedId] = useState(''); const [sections, setSections] = useState(); + const isSidebarEnabled = useObservable(isSidebarEnabled$); const onAppMounted = useCallback((id: string) => { setSelectedId(id); @@ -75,18 +80,20 @@ export const ManagementApp = ({ dependencies, history, theme$ }: ManagementAppPr return null; } - const solution: KibanaPageTemplateProps['solutionNav'] = { - name: i18n.translate('management.nav.label', { - defaultMessage: 'Management', - }), - icon: 'managementApp', - 'data-test-subj': 'mgtSideBarNav', - items: managementSidebarNav({ - selectedId, - sections, - history, - }), - }; + const solution: KibanaPageTemplateProps['solutionNav'] | undefined = isSidebarEnabled + ? { + name: i18n.translate('management.nav.label', { + defaultMessage: 'Management', + }), + icon: 'managementApp', + 'data-test-subj': 'mgtSideBarNav', + items: managementSidebarNav({ + selectedId, + sections, + history, + }), + } + : undefined; return ( diff --git a/src/plugins/management/public/mocks/index.ts b/src/plugins/management/public/mocks/index.ts index 92f6f9e1ed4ce..ec4ec2fa7883a 100644 --- a/src/plugins/management/public/mocks/index.ts +++ b/src/plugins/management/public/mocks/index.ts @@ -42,7 +42,7 @@ const createSetupContract = (): ManagementSetup => ({ }); const createStartContract = (): ManagementStart => ({ - sections: {}, + setIsSidebarEnabled: jest.fn(), }); export const managementPluginMock = { diff --git a/src/plugins/management/public/plugin.ts b/src/plugins/management/public/plugin.ts index 0cdb83d4b6793..8310074a63159 100644 --- a/src/plugins/management/public/plugin.ts +++ b/src/plugins/management/public/plugin.ts @@ -71,11 +71,14 @@ export class ManagementPlugin private hasAnyEnabledApps = true; + private isSidebarEnabled$ = new BehaviorSubject(true); + constructor(private initializerContext: PluginInitializerContext) {} public setup(core: CoreSetup, { home, share }: ManagementSetupDependencies) { const kibanaVersion = this.initializerContext.env.packageInfo.version; const locator = share.url.locators.create(new ManagementAppLocatorDefinition()); + const managementPlugin = this; if (home) { home.featureCatalogue.register({ @@ -111,6 +114,7 @@ export class ManagementPlugin sections: getSectionsServiceStartPrivate(), kibanaVersion, setBreadcrumbs: coreStart.chrome.setBreadcrumbs, + isSidebarEnabled$: managementPlugin.isSidebarEnabled$, }); }, }); @@ -121,7 +125,7 @@ export class ManagementPlugin }; } - public start(core: CoreStart, plugins: ManagementStartDependencies) { + public start(core: CoreStart, _plugins: ManagementStartDependencies): ManagementStart { this.managementSections.start({ capabilities: core.application.capabilities }); this.hasAnyEnabledApps = getSectionsServiceStartPrivate() .getSectionsEnabled() @@ -136,6 +140,9 @@ export class ManagementPlugin }); } - return {}; + return { + setIsSidebarEnabled: (isSidebarEnabled: boolean) => + this.isSidebarEnabled$.next(isSidebarEnabled), + }; } } diff --git a/src/plugins/management/public/types.ts b/src/plugins/management/public/types.ts index 5d8d963ea981e..0f632b818a950 100644 --- a/src/plugins/management/public/types.ts +++ b/src/plugins/management/public/types.ts @@ -27,8 +27,9 @@ export interface DefinedSections { stack: ManagementSection; } -// eslint-disable-next-line @typescript-eslint/no-empty-interface -export interface ManagementStart {} +export interface ManagementStart { + setIsSidebarEnabled: (enabled: boolean) => void; +} export interface ManagementSectionsStartPrivate { getSectionsEnabled: () => ManagementSection[]; diff --git a/tsconfig.base.json b/tsconfig.base.json index 58eb34d40debd..4999fc4868696 100644 --- a/tsconfig.base.json +++ b/tsconfig.base.json @@ -1150,6 +1150,8 @@ "@kbn/serverless-project-switcher/*": ["packages/serverless/project_switcher/*"], "@kbn/serverless-search": ["x-pack/plugins/serverless_search"], "@kbn/serverless-search/*": ["x-pack/plugins/serverless_search/*"], + "@kbn/serverless-security": ["x-pack/plugins/serverless_security"], + "@kbn/serverless-security/*": ["x-pack/plugins/serverless_security/*"], "@kbn/serverless-storybook-config": ["packages/serverless/storybook/config"], "@kbn/serverless-storybook-config/*": ["packages/serverless/storybook/config/*"], "@kbn/serverless-types": ["packages/serverless/types"], diff --git a/x-pack/plugins/security_solution/common/index.ts b/x-pack/plugins/security_solution/common/index.ts index 85546afccb694..3133996d79ed8 100644 --- a/x-pack/plugins/security_solution/common/index.ts +++ b/x-pack/plugins/security_solution/common/index.ts @@ -8,6 +8,7 @@ // TODO(jbudz): should be removed when upgrading to TS@4.8 // this is a skip for the errors created when typechecking with isolatedModules export {}; +export { APP_UI_ID, SecurityPageName } from './constants'; export { ELASTIC_SECURITY_RULE_ID } from './detection_engine/constants'; // Careful of exporting anything from this file as any file(s) you export here will cause your page bundle size to increase. diff --git a/x-pack/plugins/security_solution/public/app/deep_links/index.ts b/x-pack/plugins/security_solution/public/app/deep_links/index.ts index 7ea8a9f51f406..e0bd41655545c 100644 --- a/x-pack/plugins/security_solution/public/app/deep_links/index.ts +++ b/x-pack/plugins/security_solution/public/app/deep_links/index.ts @@ -78,7 +78,7 @@ import { USERS_PATH, } from '../../../common/constants'; import type { ExperimentalFeatures } from '../../../common/experimental_features'; -import { hasCapabilities, subscribeAppLinks } from '../../common/links'; +import { appLinks$, hasCapabilities } from '../../common/links'; import type { AppLinkItems } from '../../common/links/types'; export const FEATURE = { @@ -630,7 +630,7 @@ const formatDeepLinks = (appLinks: AppLinkItems): AppDeepLink[] => * Registers any change in appLinks to be updated in app deepLinks */ export const registerDeepLinksUpdater = (appUpdater$: Subject): Subscription => { - return subscribeAppLinks((appLinks) => { + return appLinks$.subscribe((appLinks) => { appUpdater$.next(() => ({ navLinkStatus: AppNavLinkStatus.hidden, // needed to prevent main security link to switch to visible after update deepLinks: formatDeepLinks(appLinks), diff --git a/x-pack/plugins/security_solution/public/common/components/navigation/nav_links.test.ts b/x-pack/plugins/security_solution/public/common/components/navigation/nav_links.test.ts deleted file mode 100644 index c44873414ca11..0000000000000 --- a/x-pack/plugins/security_solution/public/common/components/navigation/nav_links.test.ts +++ /dev/null @@ -1,95 +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 { renderHook } from '@testing-library/react-hooks'; -import { SecurityPageName } from '../../../app/types'; -import type { AppLinkItems } from '../../links'; -import { TestProviders } from '../../mock'; -import { useAppNavLinks, useAppRootNavLink } from './nav_links'; -import type { NavLinkItem } from './types'; - -const mockNavLinks: AppLinkItems = [ - { - description: 'description', - id: SecurityPageName.administration, - links: [ - { - description: 'description 2', - id: SecurityPageName.endpoints, - links: [], - path: '/path_2', - title: 'title 2', - sideNavDisabled: true, - landingIcon: 'someicon', - landingImage: 'someimage', - skipUrlState: true, - }, - ], - path: '/path', - title: 'title', - }, -]; - -jest.mock('../../links', () => ({ - useAppLinks: () => mockNavLinks, -})); - -const renderUseAppNavLinks = () => - renderHook<{}, NavLinkItem[]>(() => useAppNavLinks(), { wrapper: TestProviders }); - -const renderUseAppRootNavLink = (id: SecurityPageName) => - renderHook<{ id: SecurityPageName }, NavLinkItem | undefined>(() => useAppRootNavLink(id), { - wrapper: TestProviders, - }); - -describe('useAppNavLinks', () => { - it('should return all nav links', () => { - const { result } = renderUseAppNavLinks(); - expect(result.current).toMatchInlineSnapshot(` - Array [ - Object { - "description": "description", - "id": "administration", - "links": Array [ - Object { - "description": "description 2", - "disabled": true, - "icon": "someicon", - "id": "endpoints", - "image": "someimage", - "skipUrlState": true, - "title": "title 2", - }, - ], - "title": "title", - }, - ] - `); - }); - - it('should return a root nav links', () => { - const { result } = renderUseAppRootNavLink(SecurityPageName.administration); - expect(result.current).toMatchInlineSnapshot(` - Object { - "description": "description", - "id": "administration", - "links": Array [ - Object { - "description": "description 2", - "disabled": true, - "icon": "someicon", - "id": "endpoints", - "image": "someimage", - "skipUrlState": true, - "title": "title 2", - }, - ], - "title": "title", - } - `); - }); -}); diff --git a/x-pack/plugins/security_solution/public/common/components/navigation/security_side_nav/security_side_nav.test.tsx b/x-pack/plugins/security_solution/public/common/components/navigation/security_side_nav/security_side_nav.test.tsx index acf7765ffa936..a0612e9b63897 100644 --- a/x-pack/plugins/security_solution/public/common/components/navigation/security_side_nav/security_side_nav.test.tsx +++ b/x-pack/plugins/security_solution/public/common/components/navigation/security_side_nav/security_side_nav.test.tsx @@ -51,9 +51,9 @@ jest.mock('../../../links', () => ({ getAncestorLinksInfo: (id: string) => [{ id }], })); -const mockUseAppNavLinks = jest.fn(); -jest.mock('../nav_links', () => ({ - useAppNavLinks: () => mockUseAppNavLinks(), +const mockUseNavLinks = jest.fn(); +jest.mock('../../../links/nav_links', () => ({ + useNavLinks: () => mockUseNavLinks(), })); jest.mock('../../links', () => ({ useGetSecuritySolutionLinkProps: @@ -80,14 +80,14 @@ const renderNav = () => describe('SecuritySideNav', () => { beforeEach(() => { jest.clearAllMocks(); - mockUseAppNavLinks.mockReturnValue([alertsNavLink, manageNavLink]); + mockUseNavLinks.mockReturnValue([alertsNavLink, manageNavLink]); useKibana().services.chrome.hasHeaderBanner$ = jest.fn(() => new BehaviorSubject(false).asObservable() ); }); it('should render main items', () => { - mockUseAppNavLinks.mockReturnValue([alertsNavLink]); + mockUseNavLinks.mockReturnValue([alertsNavLink]); renderNav(); expect(mockSolutionSideNav).toHaveBeenCalledWith({ selectedId: SecurityPageName.alerts, @@ -104,7 +104,7 @@ describe('SecuritySideNav', () => { }); it('should render the loader if items are still empty', () => { - mockUseAppNavLinks.mockReturnValue([]); + mockUseNavLinks.mockReturnValue([]); const result = renderNav(); expect(result.getByTestId('sideNavLoader')).toBeInTheDocument(); expect(mockSolutionSideNav).not.toHaveBeenCalled(); @@ -121,7 +121,7 @@ describe('SecuritySideNav', () => { }); it('should render footer items', () => { - mockUseAppNavLinks.mockReturnValue([manageNavLink]); + mockUseNavLinks.mockReturnValue([manageNavLink]); renderNav(); expect(mockSolutionSideNav).toHaveBeenCalledWith( expect.objectContaining({ @@ -148,7 +148,7 @@ describe('SecuritySideNav', () => { }); it('should not render disabled items', () => { - mockUseAppNavLinks.mockReturnValue([{ ...alertsNavLink, disabled: true }, manageNavLink]); + mockUseNavLinks.mockReturnValue([{ ...alertsNavLink, disabled: true }, manageNavLink]); renderNav(); expect(mockSolutionSideNav).toHaveBeenCalledWith( expect.objectContaining({ @@ -163,7 +163,7 @@ describe('SecuritySideNav', () => { }); it('should render custom item', () => { - mockUseAppNavLinks.mockReturnValue([{ id: SecurityPageName.landing, title: 'get started' }]); + mockUseNavLinks.mockReturnValue([{ id: SecurityPageName.landing, title: 'get started' }]); renderNav(); expect(mockSolutionSideNav).toHaveBeenCalledWith( expect.objectContaining({ diff --git a/x-pack/plugins/security_solution/public/common/components/navigation/security_side_nav/security_side_nav.tsx b/x-pack/plugins/security_solution/public/common/components/navigation/security_side_nav/security_side_nav.tsx index f647ff1f873e1..b34e069c24860 100644 --- a/x-pack/plugins/security_solution/public/common/components/navigation/security_side_nav/security_side_nav.tsx +++ b/x-pack/plugins/security_solution/public/common/components/navigation/security_side_nav/security_side_nav.tsx @@ -13,7 +13,7 @@ import { SecurityPageName } from '../../../../app/types'; import { getAncestorLinksInfo } from '../../../links'; import { useRouteSpy } from '../../../utils/route/use_route_spy'; import { useGetSecuritySolutionLinkProps } from '../../links'; -import { useAppNavLinks } from '../nav_links'; +import { useNavLinks } from '../../../links/nav_links'; import { useShowTimeline } from '../../../utils/timeline/use_show_timeline'; import { useIsPolicySettingsBarVisible } from '../../../../management/pages/policy/view/policy_hooks'; import { track } from '../../../lib/telemetry'; @@ -30,7 +30,7 @@ const isGetStartedNavItem = (id: SecurityPageName) => id === SecurityPageName.la * Returns the formatted `items` and `footerItems` to be rendered in the navigation */ const useSolutionSideNavItems = () => { - const navLinks = useAppNavLinks(); + const navLinks = useNavLinks(); const getSecuritySolutionLinkProps = useGetSecuritySolutionLinkProps(); // adds href and onClick props const sideNavItems = useMemo(() => { diff --git a/x-pack/plugins/security_solution/public/common/components/navigation/use_security_solution_navigation/use_primary_navigation.tsx b/x-pack/plugins/security_solution/public/common/components/navigation/use_security_solution_navigation/use_primary_navigation.tsx index 647193357b66b..867cda2bcf4e8 100644 --- a/x-pack/plugins/security_solution/public/common/components/navigation/use_security_solution_navigation/use_primary_navigation.tsx +++ b/x-pack/plugins/security_solution/public/common/components/navigation/use_security_solution_navigation/use_primary_navigation.tsx @@ -9,10 +9,12 @@ import React, { useEffect, useState, useCallback } from 'react'; import { i18n } from '@kbn/i18n'; import type { KibanaPageTemplateProps } from '@kbn/shared-ux-page-kibana-template'; +import useObservable from 'react-use/lib/useObservable'; import type { PrimaryNavigationProps } from './types'; import { usePrimaryNavigationItems } from './use_navigation_items'; import { useIsGroupedNavigationEnabled } from '../helpers'; import { SecuritySideNav } from '../security_side_nav'; +import { useKibana } from '../../../lib/kibana'; const translatedNavTitle = i18n.translate('xpack.securitySolution.navigation.mainLabel', { defaultMessage: 'Security', @@ -45,6 +47,14 @@ export const usePrimaryNavigation = ({ selectedTabId, }); + const { isSidebarEnabled$ } = useKibana().services; + + const isSidebarEnabled = useObservable(isSidebarEnabled$); + + if (!isSidebarEnabled) { + return undefined; + } + return { canBeCollapsed: true, name: translatedNavTitle, diff --git a/x-pack/plugins/security_solution/public/common/lib/kibana/kibana_react.mock.ts b/x-pack/plugins/security_solution/public/common/lib/kibana/kibana_react.mock.ts index 42a34fa08fce3..0383b4a700420 100644 --- a/x-pack/plugins/security_solution/public/common/lib/kibana/kibana_react.mock.ts +++ b/x-pack/plugins/security_solution/public/common/lib/kibana/kibana_react.mock.ts @@ -45,6 +45,7 @@ import { triggersActionsUiMock } from '@kbn/triggers-actions-ui-plugin/public/mo import { mockApm } from '../apm/service.mock'; import { cloudExperimentsMock } from '@kbn/cloud-experiments-plugin/common/mocks'; import { guidedOnboardingMock } from '@kbn/guided-onboarding-plugin/public/mocks'; +import { of } from 'rxjs'; const mockUiSettings: Record = { [DEFAULT_TIME_RANGE]: { from: 'now-15m', to: 'now', mode: 'quick' }, @@ -183,6 +184,7 @@ export const createStartServicesMock = ( triggersActionsUi, cloudExperiments, guidedOnboarding, + isSidebarEnabled$: of(true), } as unknown as StartServices; }; diff --git a/x-pack/plugins/security_solution/public/common/links/links.ts b/x-pack/plugins/security_solution/public/common/links/links.ts index de30840d02d9d..be1f206e339b2 100644 --- a/x-pack/plugins/security_solution/public/common/links/links.ts +++ b/x-pack/plugins/security_solution/public/common/links/links.ts @@ -7,7 +7,8 @@ import type { Capabilities } from '@kbn/core/public'; import { get, isArray } from 'lodash'; -import { useEffect, useState } from 'react'; +import { useMemo } from 'react'; +import useObservable from 'react-use/lib/useObservable'; import { BehaviorSubject } from 'rxjs'; import type { SecurityPageName } from '../../../common/constants'; import type { @@ -20,72 +21,48 @@ import type { } from './types'; /** - * App links updater, it keeps the value of the app links in sync with all application. - * It can be updated using `updateAppLinks` or `excludeAppLink` - * Read it using `subscribeAppLinks` or `useAppLinks` hook. + * App links updater, it stores the `appLinkItems` recursive hierarchy and keeps + * the value of the app links in sync with all application components. + * It can be updated using `updateAppLinks`. + * Read it using subscription or `useAppLinks` hook. */ -const appLinksUpdater$ = new BehaviorSubject<{ - links: AppLinkItems; - normalizedLinks: NormalizedLinks; -}>({ - links: [], // stores the appLinkItems recursive hierarchy - normalizedLinks: {}, // stores a flatten normalized object for direct id access -}); +const appLinksUpdater$ = new BehaviorSubject([]); +// stores a flatten normalized appLinkItems object for internal direct id access +const normalizedAppLinksUpdater$ = new BehaviorSubject({}); -const getAppLinksValue = (): AppLinkItems => appLinksUpdater$.getValue().links; -const getNormalizedLinksValue = (): NormalizedLinks => appLinksUpdater$.getValue().normalizedLinks; +// AppLinks observable +export const appLinks$ = appLinksUpdater$.asObservable(); /** - * Subscribes to the updater to get the app links updates + * Updates the app links applying the filter by permissions */ -export const subscribeAppLinks = (onChange: (links: AppLinkItems) => void) => - appLinksUpdater$.subscribe(({ links }) => onChange(links)); +export const updateAppLinks = ( + appLinksToUpdate: AppLinkItems, + linksPermissions: LinksPermissions +) => { + const filteredAppLinks = getFilteredAppLinks(appLinksToUpdate, linksPermissions); + appLinksUpdater$.next(Object.freeze(filteredAppLinks)); + normalizedAppLinksUpdater$.next(Object.freeze(getNormalizedLinks(filteredAppLinks))); +}; /** * Hook to get the app links updated value */ -export const useAppLinks = (): AppLinkItems => { - const [appLinks, setAppLinks] = useState(getAppLinksValue); - - useEffect(() => { - const linksSubscription = subscribeAppLinks((newAppLinks) => { - setAppLinks(newAppLinks); - }); - return () => linksSubscription.unsubscribe(); - }, []); - - return appLinks; -}; +export const useAppLinks = (): AppLinkItems => + useObservable(appLinksUpdater$, appLinksUpdater$.getValue()); +/** + * Hook to get the normalized app links updated value + */ +export const useNormalizedAppLinks = (): NormalizedLinks => + useObservable(normalizedAppLinksUpdater$, normalizedAppLinksUpdater$.getValue()); /** * Hook to check if a link exists in the application links, * It can be used to know if a link access is authorized. */ export const useLinkExists = (id: SecurityPageName): boolean => { - const [linkExists, setLinkExists] = useState(!!getNormalizedLink(id)); - - useEffect(() => { - const linksSubscription = subscribeAppLinks(() => { - setLinkExists(!!getNormalizedLink(id)); - }); - return () => linksSubscription.unsubscribe(); - }, [id]); - - return linkExists; -}; - -/** - * Updates the app links applying the filter by permissions - */ -export const updateAppLinks = ( - appLinksToUpdate: AppLinkItems, - linksPermissions: LinksPermissions -) => { - const filteredAppLinks = getFilteredAppLinks(appLinksToUpdate, linksPermissions); - appLinksUpdater$.next({ - links: Object.freeze(filteredAppLinks), - normalizedLinks: Object.freeze(getNormalizedLinks(filteredAppLinks)), - }); + const normalizedLinks = useNormalizedAppLinks(); + return useMemo(() => !!normalizedLinks[id], [normalizedLinks, id]); }; /** @@ -128,6 +105,10 @@ export const needsUrlState = (id: SecurityPageName): boolean => { return !getNormalizedLink(id)?.skipUrlState; }; +export const getLinksWithHiddenTimeline = (): LinkInfo[] => { + return Object.values(normalizedAppLinksUpdater$.getValue()).filter((link) => link.hideTimeline); +}; + // Internal functions /** @@ -136,8 +117,8 @@ export const needsUrlState = (id: SecurityPageName): boolean => { const getNormalizedLinks = ( currentLinks: AppLinkItems, parentId?: SecurityPageName -): NormalizedLinks => { - return currentLinks.reduce((normalized, { links, ...currentLink }) => { +): NormalizedLinks => + currentLinks.reduce((normalized, { links, ...currentLink }) => { normalized[currentLink.id] = { ...currentLink, parentId, @@ -147,10 +128,9 @@ const getNormalizedLinks = ( } return normalized; }, {}); -}; const getNormalizedLink = (id: SecurityPageName): Readonly | undefined => - getNormalizedLinksValue()[id]; + normalizedAppLinksUpdater$.getValue()[id]; const getFilteredAppLinks = ( appLinkToFilter: AppLinkItems, @@ -226,7 +206,3 @@ const isLinkAllowed = ( } return true; }; - -export const getLinksWithHiddenTimeline = (): LinkInfo[] => { - return Object.values(getNormalizedLinksValue()).filter((link) => link.hideTimeline); -}; diff --git a/x-pack/plugins/security_solution/public/common/links/nav_links.test.ts b/x-pack/plugins/security_solution/public/common/links/nav_links.test.ts new file mode 100644 index 0000000000000..d8decac43a86a --- /dev/null +++ b/x-pack/plugins/security_solution/public/common/links/nav_links.test.ts @@ -0,0 +1,57 @@ +/* + * 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 { AppLinkItems } from './types'; +import { formatNavigationLinks } from './nav_links'; +import { SecurityPageName } from '../../app/types'; + +const mockNavLinks: AppLinkItems = [ + { + description: 'description', + id: SecurityPageName.administration, + links: [ + { + description: 'description 2', + id: SecurityPageName.endpoints, + links: [], + path: '/path_2', + title: 'title 2', + sideNavDisabled: true, + landingIcon: 'someicon', + landingImage: 'someimage', + skipUrlState: true, + }, + ], + path: '/path', + title: 'title', + }, +]; + +describe('formatNavigationLinks', () => { + it('should format links', () => { + expect(formatNavigationLinks(mockNavLinks)).toMatchInlineSnapshot(` + Array [ + Object { + "description": "description", + "id": "administration", + "links": Array [ + Object { + "description": "description 2", + "disabled": true, + "icon": "someicon", + "id": "endpoints", + "image": "someimage", + "skipUrlState": true, + "title": "title 2", + }, + ], + "title": "title", + }, + ] + `); + }); +}); diff --git a/x-pack/plugins/security_solution/public/common/components/navigation/nav_links.ts b/x-pack/plugins/security_solution/public/common/links/nav_links.ts similarity index 52% rename from x-pack/plugins/security_solution/public/common/components/navigation/nav_links.ts rename to x-pack/plugins/security_solution/public/common/links/nav_links.ts index 5fff0a9649940..0882bdf233601 100644 --- a/x-pack/plugins/security_solution/public/common/components/navigation/nav_links.ts +++ b/x-pack/plugins/security_solution/public/common/links/nav_links.ts @@ -5,23 +5,13 @@ * 2.0. */ -import { useMemo } from 'react'; -import { useAppLinks } from '../../links'; -import type { SecurityPageName } from '../../../app/types'; -import type { NavLinkItem } from './types'; -import type { AppLinkItems } from '../../links/types'; +import useObservable from 'react-use/lib/useObservable'; +import { map } from 'rxjs'; +import { appLinks$ } from './links'; +import type { SecurityPageName } from '../../app/types'; +import type { AppLinkItems, NavigationLink } from './types'; -export const useAppNavLinks = (): NavLinkItem[] => { - const appLinks = useAppLinks(); - const navLinks = useMemo(() => formatNavLinkItems(appLinks), [appLinks]); - return navLinks; -}; - -export const useAppRootNavLink = (linkId: SecurityPageName): NavLinkItem | undefined => { - return useAppNavLinks().find(({ id }) => id === linkId); -}; - -const formatNavLinkItems = (appLinks: AppLinkItems): NavLinkItem[] => +export const formatNavigationLinks = (appLinks: AppLinkItems): NavigationLink[] => appLinks.map((link) => ({ id: link.id, title: link.title, @@ -33,9 +23,21 @@ const formatNavLinkItems = (appLinks: AppLinkItems): NavLinkItem[] => ...(link.skipUrlState != null ? { skipUrlState: link.skipUrlState } : {}), ...(link.isBeta != null ? { isBeta: link.isBeta } : {}), ...(link.betaOptions != null ? { betaOptions: link.betaOptions } : {}), - ...(link.links && link.links.length - ? { - links: formatNavLinkItems(link.links), - } - : {}), + ...(link.links?.length && { + links: formatNavigationLinks(link.links), + }), })); + +/** + * Navigation links observable based on Security AppLinks, + * It is used to generate the side navigation items + */ +export const navLinks$ = appLinks$.pipe(map(formatNavigationLinks)); + +export const useNavLinks = (): NavigationLink[] => { + return useObservable(navLinks$, []); +}; + +export const useRootNavLink = (linkId: SecurityPageName): NavigationLink | undefined => { + return useNavLinks().find(({ id }) => id === linkId); +}; diff --git a/x-pack/plugins/security_solution/public/common/links/types.ts b/x-pack/plugins/security_solution/public/common/links/types.ts index f9a2c5776262f..162415fb66a16 100644 --- a/x-pack/plugins/security_solution/public/common/links/types.ts +++ b/x-pack/plugins/security_solution/public/common/links/types.ts @@ -131,3 +131,19 @@ export type AppLinkItems = Readonly; export type LinkInfo = Omit; export type NormalizedLink = LinkInfo & { parentId?: SecurityPageName }; export type NormalizedLinks = Partial>; + +export interface NavigationLink { + categories?: LinkCategories; + description?: string; + disabled?: boolean; + icon?: IconType; + id: SecurityPageName; + links?: NavigationLink[]; + image?: string; + title: string; + skipUrlState?: boolean; + isBeta?: boolean; + betaOptions?: { + text: string; + }; +} diff --git a/x-pack/plugins/security_solution/public/dashboards/pages/landing_page/index.test.tsx b/x-pack/plugins/security_solution/public/dashboards/pages/landing_page/index.test.tsx index bfc035a0add12..7ea6ae6198ccb 100644 --- a/x-pack/plugins/security_solution/public/dashboards/pages/landing_page/index.test.tsx +++ b/x-pack/plugins/security_solution/public/dashboards/pages/landing_page/index.test.tsx @@ -49,8 +49,8 @@ const APP_DASHBOARD_LINKS: NavLinkItem = { const URL = '/path/to/dashboards'; const mockAppManageLink = jest.fn(() => APP_DASHBOARD_LINKS); -jest.mock('../../../common/components/navigation/nav_links', () => ({ - useAppRootNavLink: () => mockAppManageLink(), +jest.mock('../../../common/links/nav_links', () => ({ + useRootNavLink: () => mockAppManageLink(), })); const CREATE_DASHBOARD_LINK = { isLoading: false, url: URL }; diff --git a/x-pack/plugins/security_solution/public/dashboards/pages/landing_page/index.tsx b/x-pack/plugins/security_solution/public/dashboards/pages/landing_page/index.tsx index d6d9be365fc30..4e1e48d09793c 100644 --- a/x-pack/plugins/security_solution/public/dashboards/pages/landing_page/index.tsx +++ b/x-pack/plugins/security_solution/public/dashboards/pages/landing_page/index.tsx @@ -14,7 +14,7 @@ import { DashboardsTable } from '../../../common/components/dashboards/dashboard import { LandingImageCards } from '../../../landing_pages/components/landing_links_images'; import { SecurityPageName } from '../../../../common/constants'; import { useCapabilities, useNavigateTo } from '../../../common/lib/kibana'; -import { useAppRootNavLink } from '../../../common/components/navigation/nav_links'; +import { useRootNavLink } from '../../../common/links/nav_links'; import { useCreateSecurityDashboardLink } from '../../../common/containers/dashboards/use_create_security_dashboard_link'; import { Title } from '../../../common/components/header_page/title'; import { LinkButton } from '../../../common/components/links/helpers'; @@ -54,7 +54,7 @@ const Header: React.FC<{ canCreateDashboard: boolean }> = ({ canCreateDashboard }; export const DashboardsLandingPage = () => { - const dashboardLinks = useAppRootNavLink(SecurityPageName.dashboards)?.links ?? []; + const dashboardLinks = useRootNavLink(SecurityPageName.dashboards)?.links ?? []; const { show: canReadDashboard, createNew: canCreateDashboard } = useCapabilities(LEGACY_DASHBOARD_APP_ID); diff --git a/x-pack/plugins/security_solution/public/index.ts b/x-pack/plugins/security_solution/public/index.ts index 1f6f121e04209..7ac596d087fca 100644 --- a/x-pack/plugins/security_solution/public/index.ts +++ b/x-pack/plugins/security_solution/public/index.ts @@ -7,10 +7,10 @@ import type { PluginInitializerContext } from '@kbn/core/public'; import { Plugin } from './plugin'; -import type { PluginSetup } from './types'; +import type { PluginSetup, PluginStart } from './types'; export type { TimelineModel } from './timelines/store/timeline/model'; export const plugin = (context: PluginInitializerContext): Plugin => new Plugin(context); -export type { PluginSetup }; +export type { PluginSetup, PluginStart }; export { Plugin }; diff --git a/x-pack/plugins/security_solution/public/landing_pages/pages/explore.tsx b/x-pack/plugins/security_solution/public/landing_pages/pages/explore.tsx index 17a0d7569b965..26dd3009e1d03 100644 --- a/x-pack/plugins/security_solution/public/landing_pages/pages/explore.tsx +++ b/x-pack/plugins/security_solution/public/landing_pages/pages/explore.tsx @@ -7,14 +7,14 @@ import React from 'react'; import { SecurityPageName } from '../../app/types'; import { HeaderPage } from '../../common/components/header_page'; -import { useAppRootNavLink } from '../../common/components/navigation/nav_links'; +import { useRootNavLink } from '../../common/links/nav_links'; import { SecuritySolutionPageWrapper } from '../../common/components/page_wrapper'; import { SpyRoute } from '../../common/utils/route/spy_routes'; import { LandingLinksImages } from '../components/landing_links_images'; import { EXPLORE_PAGE_TITLE } from './translations'; export const ExploreLandingPage = () => { - const exploreLinks = useAppRootNavLink(SecurityPageName.exploreLanding)?.links ?? []; + const exploreLinks = useRootNavLink(SecurityPageName.exploreLanding)?.links ?? []; return ( diff --git a/x-pack/plugins/security_solution/public/landing_pages/pages/manage.test.tsx b/x-pack/plugins/security_solution/public/landing_pages/pages/manage.test.tsx index 67eb06b60cca6..e900fad75546a 100644 --- a/x-pack/plugins/security_solution/public/landing_pages/pages/manage.test.tsx +++ b/x-pack/plugins/security_solution/public/landing_pages/pages/manage.test.tsx @@ -10,14 +10,14 @@ import React from 'react'; import { SecurityPageName } from '../../app/types'; import { TestProviders } from '../../common/mock'; import { ManagementCategories } from './manage'; -import type { NavLinkItem } from '../../common/components/navigation/types'; +import type { NavigationLink } from '../../common/links'; const RULES_ITEM_LABEL = 'elastic rules!'; const EXCEPTIONS_ITEM_LABEL = 'exceptional!'; const CATEGORY_1_LABEL = 'first tests category'; const CATEGORY_2_LABEL = 'second tests category'; -const defaultAppManageLink: NavLinkItem = { +const defaultAppManageLink: NavigationLink = { id: SecurityPageName.administration, title: 'admin', categories: [ @@ -47,8 +47,8 @@ const defaultAppManageLink: NavLinkItem = { }; const mockAppManageLink = jest.fn(() => defaultAppManageLink); -jest.mock('../../common/components/navigation/nav_links', () => ({ - useAppRootNavLink: () => mockAppManageLink(), +jest.mock('../../common/links/nav_links', () => ({ + useRootNavLink: () => mockAppManageLink(), })); describe('ManagementCategories', () => { diff --git a/x-pack/plugins/security_solution/public/landing_pages/pages/manage.tsx b/x-pack/plugins/security_solution/public/landing_pages/pages/manage.tsx index cb77921a0b673..37e2391801cac 100644 --- a/x-pack/plugins/security_solution/public/landing_pages/pages/manage.tsx +++ b/x-pack/plugins/security_solution/public/landing_pages/pages/manage.tsx @@ -10,8 +10,8 @@ import styled from 'styled-components'; import { SecurityPageName } from '../../app/types'; import { HeaderPage } from '../../common/components/header_page'; -import { useAppRootNavLink } from '../../common/components/navigation/nav_links'; -import type { NavLinkItem } from '../../common/components/navigation/types'; +import { useRootNavLink } from '../../common/links/nav_links'; +import type { NavigationLink } from '../../common/links'; import { SecuritySolutionPageWrapper } from '../../common/components/page_wrapper'; import { SpyRoute } from '../../common/utils/route/spy_routes'; import { LandingLinksIcons } from '../components/landing_links_icons'; @@ -30,14 +30,14 @@ const StyledEuiHorizontalRule = styled(EuiHorizontalRule)` margin-bottom: ${({ theme }) => theme.eui.euiSizeL}; `; -type ManagementCategories = Array<{ label: string; links: NavLinkItem[] }>; +type ManagementCategories = Array<{ label: string; links: NavigationLink[] }>; const useManagementCategories = (): ManagementCategories => { - const { links = [], categories = [] } = useAppRootNavLink(SecurityPageName.administration) ?? {}; + const { links = [], categories = [] } = useRootNavLink(SecurityPageName.administration) ?? {}; const manageLinksById = Object.fromEntries(links.map((link) => [link.id, link])); return categories.reduce((acc, { label, linkIds }) => { - const linksItem = linkIds.reduce((linksAcc, linkId) => { + const linksItem = linkIds.reduce((linksAcc, linkId) => { if (manageLinksById[linkId]) { linksAcc.push(manageLinksById[linkId]); } diff --git a/x-pack/plugins/security_solution/public/management/cypress/e2e/mocked_data/policy_response.cy.ts b/x-pack/plugins/security_solution/public/management/cypress/e2e/mocked_data/policy_response.cy.ts index 3100d4a64fec7..63d9f26514ba5 100644 --- a/x-pack/plugins/security_solution/public/management/cypress/e2e/mocked_data/policy_response.cy.ts +++ b/x-pack/plugins/security_solution/public/management/cypress/e2e/mocked_data/policy_response.cy.ts @@ -15,7 +15,8 @@ import { navigateToFleetAgentDetails } from '../../screens/fleet'; import { EndpointPolicyResponseGenerator } from '../../../../../common/endpoint/data_generators/endpoint_policy_response_generator'; import { descriptions } from '../../../components/policy_response/policy_response_friendly_names'; -describe('Endpoint Policy Response', () => { +// FLAKY: https://github.com/elastic/security-team/issues/6518 +describe.skip('Endpoint Policy Response', () => { let loadedEndpoint: CyIndexEndpointHosts; let endpointMetadata: HostMetadata; let loadedPolicyResponse: IndexedEndpointPolicyResponse; diff --git a/x-pack/plugins/security_solution/public/plugin.tsx b/x-pack/plugins/security_solution/public/plugin.tsx index 61ec29d956414..14b886920613b 100644 --- a/x-pack/plugins/security_solution/public/plugin.tsx +++ b/x-pack/plugins/security_solution/public/plugin.tsx @@ -7,7 +7,7 @@ import { i18n } from '@kbn/i18n'; import type { Subscription } from 'rxjs'; -import { Subject } from 'rxjs'; +import { BehaviorSubject, Subject } from 'rxjs'; import { combineLatestWith } from 'rxjs/operators'; import type * as H from 'history'; import type { @@ -45,6 +45,7 @@ import { import { getDeepLinks, registerDeepLinksUpdater } from './app/deep_links'; import type { LinksPermissions } from './common/links'; import { updateAppLinks } from './common/links'; +import { navLinks$ } from './common/links/nav_links'; import { licenseService } from './common/hooks/use_license'; import type { SecuritySolutionUiConfigType } from './common/types'; import { ExperimentalFeaturesService } from './common/experimental_features_service'; @@ -86,6 +87,7 @@ export class Plugin implements IPlugin; constructor(private readonly initializerContext: PluginInitializerContext) { this.config = this.initializerContext.config.get(); @@ -93,7 +95,7 @@ export class Plugin implements IPlugin(true); this.telemetry = new TelemetryService(); } private appUpdater$ = new Subject(); @@ -168,6 +170,7 @@ export class Plugin implements IPlugin SecuritySolutionTemplateWrapper, }, savedObjectsManagement: startPluginsDeps.savedObjectsManagement, + isSidebarEnabled$: this.isSidebarEnabled$, telemetry: this.telemetry.start(), }; return services; @@ -245,7 +248,7 @@ export class Plugin implements IPlugin + this.isSidebarEnabled$.next(isSidebarEnabled), + }; } public stop() { diff --git a/x-pack/plugins/security_solution/public/types.ts b/x-pack/plugins/security_solution/public/types.ts index e6e10a9c5b3dd..ba5bd460ba376 100644 --- a/x-pack/plugins/security_solution/public/types.ts +++ b/x-pack/plugins/security_solution/public/types.ts @@ -5,6 +5,8 @@ * 2.0. */ +import type { BehaviorSubject, Observable } from 'rxjs'; + import type { AppLeaveHandler, CoreStart } from '@kbn/core/public'; import type { HomePublicPluginSetup } from '@kbn/home-plugin/public'; import type { DataPublicPluginStart } from '@kbn/data-plugin/public'; @@ -46,6 +48,7 @@ import type { CloudExperimentsPluginStart } from '@kbn/cloud-experiments-plugin/ import type { GuidedOnboardingPluginStart } from '@kbn/guided-onboarding-plugin/public'; import type { DataViewsServicePublic } from '@kbn/data-views-plugin/public'; import type { SavedObjectsManagementPluginStart } from '@kbn/saved-objects-management-plugin/public'; + import type { ResolverPluginSetup } from './resolver/types'; import type { Inspect } from '../common/search_strategy'; import type { Detections } from './detections'; @@ -62,6 +65,8 @@ import type { CloudDefend } from './cloud_defend'; import type { ThreatIntelligence } from './threat_intelligence'; import type { SecuritySolutionTemplateWrapper } from './app/home/template_wrapper'; import type { Explore } from './explore'; +import type { NavigationLink } from './common/links'; + import type { TelemetryClientStart } from './common/lib/telemetry'; import type { Dashboards } from './dashboards'; @@ -127,14 +132,18 @@ export type StartServices = CoreStart & getPluginWrapper: () => typeof SecuritySolutionTemplateWrapper; }; savedObjectsManagement: SavedObjectsManagementPluginStart; + isSidebarEnabled$: BehaviorSubject; telemetry: TelemetryClientStart; }; export interface PluginSetup { resolver: () => Promise; } -// eslint-disable-next-line @typescript-eslint/no-empty-interface -export interface PluginStart {} + +export interface PluginStart { + navLinks$: Observable; + setIsSidebarEnabled: (isSidebarEnabled: boolean) => void; +} export interface AppObservableLibs { kibana: CoreStart; diff --git a/x-pack/plugins/serverless/kibana.jsonc b/x-pack/plugins/serverless/kibana.jsonc index af79877f1079f..5a724f7641a6a 100644 --- a/x-pack/plugins/serverless/kibana.jsonc +++ b/x-pack/plugins/serverless/kibana.jsonc @@ -14,8 +14,9 @@ ], "requiredPlugins": [ "kibanaReact", + "management", ], "optionalPlugins": [], "requiredBundles": [] } -} \ No newline at end of file +} diff --git a/x-pack/plugins/serverless/public/plugin.tsx b/x-pack/plugins/serverless/public/plugin.tsx index 9c9debe3e9f21..20f4634fb7fa0 100644 --- a/x-pack/plugins/serverless/public/plugin.tsx +++ b/x-pack/plugins/serverless/public/plugin.tsx @@ -13,23 +13,43 @@ import { CoreSetup, CoreStart, Plugin, PluginInitializerContext } from '@kbn/cor import { ProjectSwitcher, ProjectSwitcherKibanaProvider } from '@kbn/serverless-project-switcher'; import { ProjectType } from '@kbn/serverless-types'; -import { ServerlessPluginSetup, ServerlessPluginStart } from './types'; +import { + ServerlessPluginSetup, + ServerlessPluginStart, + ServerlessPluginSetupDependencies, + ServerlessPluginStartDependencies, +} from './types'; import { ServerlessConfig } from './config'; import { API_SWITCH_PROJECT as projectChangeAPIUrl } from '../common'; -export class ServerlessPlugin implements Plugin { +export class ServerlessPlugin + implements + Plugin< + ServerlessPluginSetup, + ServerlessPluginStart, + ServerlessPluginSetupDependencies, + ServerlessPluginStartDependencies + > +{ private readonly config: ServerlessConfig; constructor(private readonly initializerContext: PluginInitializerContext) { this.config = this.initializerContext.config.get(); } - public setup(_core: CoreSetup): ServerlessPluginSetup { + public setup( + _core: CoreSetup, + _dependencies: ServerlessPluginSetupDependencies + ): ServerlessPluginSetup { return {}; } - public start(core: CoreStart): ServerlessPluginStart { + public start( + core: CoreStart, + dependencies: ServerlessPluginStartDependencies + ): ServerlessPluginStart { const { developer } = this.config; + const { management } = dependencies; if (developer && developer.projectSwitcher && developer.projectSwitcher.enabled) { const { currentType } = developer.projectSwitcher; @@ -40,6 +60,7 @@ export class ServerlessPlugin implements Plugin +{ + public setup( + _core: CoreSetup, + _setupDeps: ServerlessSecurityPluginSetupDependencies + ): ServerlessSecurityPluginSetup { + return {}; + } + + public start( + _core: CoreStart, + { securitySolution }: ServerlessSecurityPluginStartDependencies + ): ServerlessSecurityPluginStart { + securitySolution.setIsSidebarEnabled(false); + return {}; + } + + public stop() {} +} diff --git a/x-pack/plugins/serverless_security/public/types.ts b/x-pack/plugins/serverless_security/public/types.ts new file mode 100644 index 0000000000000..1fc18893ce1fd --- /dev/null +++ b/x-pack/plugins/serverless_security/public/types.ts @@ -0,0 +1,31 @@ +/* + * 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 { SecurityPluginSetup, SecurityPluginStart } from '@kbn/security-plugin/public'; +import { + PluginSetup as SecuritySolutionPluginSetup, + PluginStart as SecuritySolutionPluginStart, +} from '@kbn/security-solution-plugin/public'; +import { ServerlessPluginSetup, ServerlessPluginStart } from '@kbn/serverless/public'; + +// eslint-disable-next-line @typescript-eslint/no-empty-interface +export interface ServerlessSecurityPluginSetup {} + +// eslint-disable-next-line @typescript-eslint/no-empty-interface +export interface ServerlessSecurityPluginStart {} + +export interface ServerlessSecurityPluginSetupDependencies { + security: SecurityPluginSetup; + securitySolution: SecuritySolutionPluginSetup; + serverless: ServerlessPluginSetup; +} + +export interface ServerlessSecurityPluginStartDependencies { + security: SecurityPluginStart; + securitySolution: SecuritySolutionPluginStart; + serverless: ServerlessPluginStart; +} diff --git a/x-pack/plugins/serverless_security/server/config.ts b/x-pack/plugins/serverless_security/server/config.ts new file mode 100644 index 0000000000000..758b22de1514e --- /dev/null +++ b/x-pack/plugins/serverless_security/server/config.ts @@ -0,0 +1,23 @@ +/* + * 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 { schema, TypeOf } from '@kbn/config-schema'; +import { PluginConfigDescriptor } from '@kbn/core/server'; + +export * from './types'; + +const configSchema = schema.object({ + enabled: schema.boolean({ defaultValue: false }), +}); + +type ConfigType = TypeOf; + +export const config: PluginConfigDescriptor = { + schema: configSchema, +}; + +export type ServerlessSecurityConfig = TypeOf; diff --git a/x-pack/plugins/serverless_security/server/index.ts b/x-pack/plugins/serverless_security/server/index.ts new file mode 100644 index 0000000000000..b2b6c8564f788 --- /dev/null +++ b/x-pack/plugins/serverless_security/server/index.ts @@ -0,0 +1,20 @@ +/* + * 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 { PluginInitializerContext } from '@kbn/core/server'; + +import { ServerlessSecurityPlugin } from './plugin'; +export { config } from './config'; + +// This exports static code and TypeScript types, +// as well as, Kibana Platform `plugin()` initializer. + +export function plugin(initializerContext: PluginInitializerContext) { + return new ServerlessSecurityPlugin(initializerContext); +} + +export type { ServerlessSecurityPluginSetup, ServerlessSecurityPluginStart } from './types'; diff --git a/x-pack/plugins/serverless_security/server/plugin.ts b/x-pack/plugins/serverless_security/server/plugin.ts new file mode 100644 index 0000000000000..7dfbae3b64a67 --- /dev/null +++ b/x-pack/plugins/serverless_security/server/plugin.ts @@ -0,0 +1,37 @@ +/* + * 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 { PluginInitializerContext, Plugin } from '@kbn/core/server'; + +import { + ServerlessSecurityPluginSetup, + ServerlessSecurityPluginStart, + ServerlessSecurityPluginSetupDependencies, + ServerlessSecurityPluginStartDependencies, +} from './types'; + +export class ServerlessSecurityPlugin + implements + Plugin< + ServerlessSecurityPluginSetup, + ServerlessSecurityPluginStart, + ServerlessSecurityPluginSetupDependencies, + ServerlessSecurityPluginStartDependencies + > +{ + constructor(_initializerContext: PluginInitializerContext) {} + + public setup() { + return {}; + } + + public start() { + return {}; + } + + public stop() {} +} diff --git a/x-pack/plugins/serverless_security/server/types.ts b/x-pack/plugins/serverless_security/server/types.ts new file mode 100644 index 0000000000000..be7167d030315 --- /dev/null +++ b/x-pack/plugins/serverless_security/server/types.ts @@ -0,0 +1,27 @@ +/* + * 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 { SecurityPluginSetup, SecurityPluginStart } from '@kbn/security-plugin/server'; +import { + PluginSetup as SecuritySolutionPluginSetup, + PluginStart as SecuritySolutionPluginStart, +} from '@kbn/security-solution-plugin/server'; + +// eslint-disable-next-line @typescript-eslint/no-empty-interface +export interface ServerlessSecurityPluginSetup {} +// eslint-disable-next-line @typescript-eslint/no-empty-interface +export interface ServerlessSecurityPluginStart {} + +export interface ServerlessSecurityPluginSetupDependencies { + security: SecurityPluginSetup; + securitySolution: SecuritySolutionPluginSetup; +} + +export interface ServerlessSecurityPluginStartDependencies { + security: SecurityPluginStart; + securitySolution: SecuritySolutionPluginStart; +} diff --git a/x-pack/plugins/serverless_security/tsconfig.json b/x-pack/plugins/serverless_security/tsconfig.json new file mode 100644 index 0000000000000..ddf77b288e628 --- /dev/null +++ b/x-pack/plugins/serverless_security/tsconfig.json @@ -0,0 +1,24 @@ +{ + "extends": "../../../tsconfig.base.json", + "compilerOptions": { + "outDir": "target/types" + }, + "include": [ + "index.ts", + "common/**/*.ts", + "public/**/*.ts", + "public/**/*.tsx", + "server/**/*.ts", + "../../../typings/**/*" + ], + "exclude": [ + "target/**/*" + ], + "kbn_references": [ + "@kbn/core", + "@kbn/config-schema", + "@kbn/security-plugin", + "@kbn/security-solution-plugin", + "@kbn/serverless", + ] +} diff --git a/yarn.lock b/yarn.lock index b9ef08891dd5e..2253ee337d4cd 100644 --- a/yarn.lock +++ b/yarn.lock @@ -5042,6 +5042,10 @@ version "0.0.0" uid "" +"@kbn/serverless-security@link:x-pack/plugins/serverless_security": + version "0.0.0" + uid "" + "@kbn/serverless@link:x-pack/plugins/serverless": version "0.0.0" uid ""