[serverless] Create Security Serverless plugin (#156104)

> Derived from https://github.com/elastic/kibana/pull/153274
> Builds upon https://github.com/elastic/kibana/pull/155582

## Summary

This PR creates the Serverless Security plugin, based on the work from
https://github.com/elastic/kibana/pull/153274:

- creates the plugin,
- adds API to hide the solution navigation from Security,
- calls that API if the chrome style is `project`.

<img width="1688" alt="Screenshot 2023-04-27 at 12 37 46 PM"
src="https://user-images.githubusercontent.com/297604/234979670-425bfb12-8194-4916-8f92-efff7804b577.png">

## Next Steps

- render the left nav from https://github.com/elastic/kibana/pull/153274
using an API provided by @elastic/appex-sharedux
  - this low-level API should be coming in the next few days.

---------

Co-authored-by: kibanamachine <42973632+kibanamachine@users.noreply.github.com>
This commit is contained in:
Clint Andrew Hall 2023-05-02 08:42:55 -04:00 committed by GitHub
parent a16930fc5b
commit b217dbf001
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
49 changed files with 572 additions and 252 deletions

1
.github/CODEOWNERS vendored
View file

@ -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

View file

@ -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'

View file

@ -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.

View file

@ -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",

View file

@ -118,6 +118,7 @@ pageLoadAssetSize:
serverless: 16573
serverlessObservability: 16582
serverlessSearch: 17548
serverlessSecurity: 41807
sessionView: 77750
share: 71239
snapshotRestore: 79032

View file

@ -16,15 +16,6 @@ export const renderApp = async (
{ history, appBasePath, element, theme$ }: AppMountParameters,
dependencies: ManagementAppDependencies
) => {
ReactDOM.render(
<ManagementApp
dependencies={dependencies}
appBasePath={appBasePath}
history={history}
theme$={theme$}
/>,
element
);
ReactDOM.render(<ManagementApp {...{ history, appBasePath, theme$, dependencies }} />, element);
return () => ReactDOM.unmountComponentAtNode(element);
};

View file

@ -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<boolean>;
}
export const ManagementApp = ({ dependencies, history, theme$ }: ManagementAppProps) => {
const { setBreadcrumbs } = dependencies;
const { setBreadcrumbs, isSidebarEnabled$ } = dependencies;
const [selectedId, setSelectedId] = useState<string>('');
const [sections, setSections] = useState<ManagementSection[]>();
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 (
<I18nProvider>

View file

@ -42,7 +42,7 @@ const createSetupContract = (): ManagementSetup => ({
});
const createStartContract = (): ManagementStart => ({
sections: {},
setIsSidebarEnabled: jest.fn(),
});
export const managementPluginMock = {

View file

@ -71,11 +71,14 @@ export class ManagementPlugin
private hasAnyEnabledApps = true;
private isSidebarEnabled$ = new BehaviorSubject<boolean>(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),
};
}
}

View file

@ -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[];

View file

@ -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"],

View file

@ -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.

View file

@ -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<AppUpdater>): 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),

View file

@ -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",
}
`);
});
});

View file

@ -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({

View file

@ -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(() => {

View file

@ -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,

View file

@ -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<string, unknown> = {
[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;
};

View file

@ -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,59 +21,17 @@ 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<AppLinkItems>([]);
// stores a flatten normalized appLinkItems object for internal direct id access
const normalizedAppLinksUpdater$ = new BehaviorSubject<NormalizedLinks>({});
const getAppLinksValue = (): AppLinkItems => appLinksUpdater$.getValue().links;
const getNormalizedLinksValue = (): NormalizedLinks => appLinksUpdater$.getValue().normalizedLinks;
/**
* Subscribes to the updater to get the app links updates
*/
export const subscribeAppLinks = (onChange: (links: AppLinkItems) => void) =>
appLinksUpdater$.subscribe(({ links }) => onChange(links));
/**
* 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;
};
/**
* 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;
};
// AppLinks observable
export const appLinks$ = appLinksUpdater$.asObservable();
/**
* Updates the app links applying the filter by permissions
@ -82,10 +41,28 @@ export const updateAppLinks = (
linksPermissions: LinksPermissions
) => {
const filteredAppLinks = getFilteredAppLinks(appLinksToUpdate, linksPermissions);
appLinksUpdater$.next({
links: Object.freeze(filteredAppLinks),
normalizedLinks: Object.freeze(getNormalizedLinks(filteredAppLinks)),
});
appLinksUpdater$.next(Object.freeze(filteredAppLinks));
normalizedAppLinksUpdater$.next(Object.freeze(getNormalizedLinks(filteredAppLinks)));
};
/**
* Hook to get the app links updated value
*/
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 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<NormalizedLinks>((normalized, { links, ...currentLink }) => {
): NormalizedLinks =>
currentLinks.reduce<NormalizedLinks>((normalized, { links, ...currentLink }) => {
normalized[currentLink.id] = {
...currentLink,
parentId,
@ -147,10 +128,9 @@ const getNormalizedLinks = (
}
return normalized;
}, {});
};
const getNormalizedLink = (id: SecurityPageName): Readonly<NormalizedLink> | 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);
};

View file

@ -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",
},
]
`);
});
});

View file

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

View file

@ -131,3 +131,19 @@ export type AppLinkItems = Readonly<LinkItem[]>;
export type LinkInfo = Omit<LinkItem, 'links'>;
export type NormalizedLink = LinkInfo & { parentId?: SecurityPageName };
export type NormalizedLinks = Partial<Record<SecurityPageName, NormalizedLink>>;
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;
};
}

View file

@ -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 };

View file

@ -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<DashboardCapabilities>(LEGACY_DASHBOARD_APP_ID);

View file

@ -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 };

View file

@ -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 (
<SecuritySolutionPageWrapper>

View file

@ -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', () => {

View file

@ -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<ManagementCategories>((acc, { label, linkIds }) => {
const linksItem = linkIds.reduce<NavLinkItem[]>((linksAcc, linkId) => {
const linksItem = linkIds.reduce<NavigationLink[]>((linksAcc, linkId) => {
if (manageLinksById[linkId]) {
linksAcc.push(manageLinksById[linkId]);
}

View file

@ -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;

View file

@ -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<PluginSetup, PluginStart, SetupPlugins, S
private telemetry: TelemetryService;
readonly experimentalFeatures: ExperimentalFeatures;
private isSidebarEnabled$: BehaviorSubject<boolean>;
constructor(private readonly initializerContext: PluginInitializerContext) {
this.config = this.initializerContext.config.get<SecuritySolutionUiConfigType>();
@ -93,7 +95,7 @@ export class Plugin implements IPlugin<PluginSetup, PluginStart, SetupPlugins, S
this.kibanaVersion = initializerContext.env.packageInfo.version;
this.kibanaBranch = initializerContext.env.packageInfo.branch;
this.prebuiltRulesPackageVersion = this.config.prebuiltRulesPackageVersion;
this.isSidebarEnabled$ = new BehaviorSubject<boolean>(true);
this.telemetry = new TelemetryService();
}
private appUpdater$ = new Subject<AppUpdater>();
@ -168,6 +170,7 @@ export class Plugin implements IPlugin<PluginSetup, PluginStart, SetupPlugins, S
getPluginWrapper: () => SecuritySolutionTemplateWrapper,
},
savedObjectsManagement: startPluginsDeps.savedObjectsManagement,
isSidebarEnabled$: this.isSidebarEnabled$,
telemetry: this.telemetry.start(),
};
return services;
@ -245,7 +248,7 @@ export class Plugin implements IPlugin<PluginSetup, PluginStart, SetupPlugins, S
};
}
public start(core: CoreStart, plugins: StartPlugins) {
public start(core: CoreStart, plugins: StartPlugins): PluginStart {
KibanaServices.init({
...core,
...plugins,
@ -306,7 +309,11 @@ export class Plugin implements IPlugin<PluginSetup, PluginStart, SetupPlugins, S
// Not using await to prevent blocking start execution
this.registerAppLinks(core, plugins);
return {};
return {
navLinks$,
setIsSidebarEnabled: (isSidebarEnabled: boolean) =>
this.isSidebarEnabled$.next(isSidebarEnabled),
};
}
public stop() {

View file

@ -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<boolean>;
telemetry: TelemetryClientStart;
};
export interface PluginSetup {
resolver: () => Promise<ResolverPluginSetup>;
}
// eslint-disable-next-line @typescript-eslint/no-empty-interface
export interface PluginStart {}
export interface PluginStart {
navLinks$: Observable<NavigationLink[]>;
setIsSidebarEnabled: (isSidebarEnabled: boolean) => void;
}
export interface AppObservableLibs {
kibana: CoreStart;

View file

@ -14,8 +14,9 @@
],
"requiredPlugins": [
"kibanaReact",
"management",
],
"optionalPlugins": [],
"requiredBundles": []
}
}
}

View file

@ -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<ServerlessPluginSetup, ServerlessPluginStart> {
export class ServerlessPlugin
implements
Plugin<
ServerlessPluginSetup,
ServerlessPluginStart,
ServerlessPluginSetupDependencies,
ServerlessPluginStartDependencies
>
{
private readonly config: ServerlessConfig;
constructor(private readonly initializerContext: PluginInitializerContext) {
this.config = this.initializerContext.config.get<ServerlessConfig>();
}
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<ServerlessPluginSetup, Serverles
}
core.chrome.setChromeStyle('project');
management.setIsSidebarEnabled(false);
return {};
}

View file

@ -5,8 +5,18 @@
* 2.0.
*/
import type { ManagementSetup, ManagementStart } from '@kbn/management-plugin/public';
// eslint-disable-next-line @typescript-eslint/no-empty-interface
export interface ServerlessPluginSetup {}
// eslint-disable-next-line @typescript-eslint/no-empty-interface
export interface ServerlessPluginStart {}
export interface ServerlessPluginSetupDependencies {
management: ManagementSetup;
}
export interface ServerlessPluginStartDependencies {
management: ManagementStart;
}

View file

@ -14,11 +14,12 @@
"target/**/*"
],
"kbn_references": [
"@kbn/core",
"@kbn/config-schema",
"@kbn/core",
"@kbn/kibana-react-plugin",
"@kbn/utils",
"@kbn/management-plugin",
"@kbn/serverless-project-switcher",
"@kbn/serverless-types",
"@kbn/utils",
]
}

View file

@ -0,0 +1,2 @@
/build
/target

View file

@ -0,0 +1,3 @@
# Serverless Security project plugin
This plugin contains configuration and code used to create a Serverless Security project. It leverages universal configuration and other APIs in the [`serverless`](../serverless/README.mdx) plugin to configure Kibana.

View file

@ -0,0 +1,9 @@
/*
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
* or more contributor license agreements. Licensed under the Elastic License
* 2.0; you may not use this file except in compliance with the Elastic License
* 2.0.
*/
export const PLUGIN_ID = 'serverlessSecurity';
export const PLUGIN_NAME = 'serverlessSecurity';

View file

@ -0,0 +1,24 @@
{
"type": "plugin",
"id": "@kbn/serverless-security",
"owner": "@elastic/appex-sharedux",
"description": "Serverless customizations for security.",
"plugin": {
"id": "serverlessSecurity",
"server": true,
"browser": true,
"configPath": [
"xpack",
"serverless",
"security"
],
"requiredPlugins": [
"serverless",
"security",
"securitySolution",
"kibanaReact"
],
"optionalPlugins": [],
"requiredBundles": []
}
}

View file

@ -0,0 +1,11 @@
{
"name": "@kbn/serverless-security",
"version": "1.0.0",
"license": "Elastic License 2.0",
"private": true,
"scripts": {
"build": "yarn plugin-helpers build",
"plugin-helpers": "node ../../../scripts/plugin_helpers",
"kbn": "node ../../../scripts/kbn"
}
}

View file

@ -0,0 +1,16 @@
/*
* 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 { ServerlessSecurityPlugin } from './plugin';
// This exports static code and TypeScript types,
// as well as, Kibana Platform `plugin()` initializer.
export function plugin() {
return new ServerlessSecurityPlugin();
}
export type { ServerlessSecurityPluginSetup, ServerlessSecurityPluginStart } from './types';

View file

@ -0,0 +1,41 @@
/*
* 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 { CoreSetup, CoreStart, Plugin } from '@kbn/core/public';
import {
ServerlessSecurityPluginSetup,
ServerlessSecurityPluginStart,
ServerlessSecurityPluginSetupDependencies,
ServerlessSecurityPluginStartDependencies,
} from './types';
export class ServerlessSecurityPlugin
implements
Plugin<
ServerlessSecurityPluginSetup,
ServerlessSecurityPluginStart,
ServerlessSecurityPluginSetupDependencies,
ServerlessSecurityPluginStartDependencies
>
{
public setup(
_core: CoreSetup,
_setupDeps: ServerlessSecurityPluginSetupDependencies
): ServerlessSecurityPluginSetup {
return {};
}
public start(
_core: CoreStart,
{ securitySolution }: ServerlessSecurityPluginStartDependencies
): ServerlessSecurityPluginStart {
securitySolution.setIsSidebarEnabled(false);
return {};
}
public stop() {}
}

View file

@ -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;
}

View file

@ -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<typeof configSchema>;
export const config: PluginConfigDescriptor<ConfigType> = {
schema: configSchema,
};
export type ServerlessSecurityConfig = TypeOf<typeof configSchema>;

View file

@ -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';

View file

@ -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() {}
}

View file

@ -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;
}

View file

@ -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",
]
}

View file

@ -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 ""