Skip to content

Commit 2b51b2f

Browse files
[Ingest] Enforce security required and superuser permissions (#65315) (#65368)
* Add check permissions route and request hook * Conditionally register routes * Add security not enabled and permissions missing warning UIs * Hide global settings when there is an error # Conflicts: # x-pack/plugins/ingest_manager/server/plugin.ts Co-authored-by: Elastic Machine <elasticmachine@users.noreply.github.com>
1 parent c0f4c74 commit 2b51b2f

File tree

15 files changed

+286
-94
lines changed

15 files changed

+286
-94
lines changed

x-pack/plugins/ingest_manager/common/constants/routes.ts

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -61,6 +61,11 @@ export const SETTINGS_API_ROUTES = {
6161
UPDATE_PATTERN: `${API_ROOT}/settings`,
6262
};
6363

64+
// App API routes
65+
export const APP_API_ROUTES = {
66+
CHECK_PERMISSIONS_PATTERN: `${API_ROOT}/check-permissions`,
67+
};
68+
6469
// Agent API routes
6570
export const AGENT_API_ROUTES = {
6671
LIST_PATTERN: `${FLEET_API_ROOT}/agents`,

x-pack/plugins/ingest_manager/common/services/routes.ts

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@ import {
1515
SETUP_API_ROUTE,
1616
OUTPUT_API_ROUTES,
1717
SETTINGS_API_ROUTES,
18+
APP_API_ROUTES,
1819
} from '../constants';
1920

2021
export const epmRouteService = {
@@ -126,6 +127,10 @@ export const settingsRoutesService = {
126127
getUpdatePath: () => SETTINGS_API_ROUTES.UPDATE_PATTERN,
127128
};
128129

130+
export const appRoutesService = {
131+
getCheckPermissionsPath: () => APP_API_ROUTES.CHECK_PERMISSIONS_PATTERN,
132+
};
133+
129134
export const enrollmentAPIKeyRouteService = {
130135
getListPath: () => ENROLLMENT_API_KEY_ROUTES.LIST_PATTERN,
131136
getCreatePath: () => ENROLLMENT_API_KEY_ROUTES.CREATE_PATTERN,
Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
/*
2+
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
3+
* or more contributor license agreements. Licensed under the Elastic License;
4+
* you may not use this file except in compliance with the Elastic License.
5+
*/
6+
7+
export interface CheckPermissionsResponse {
8+
error?: 'MISSING_SECURITY' | 'MISSING_SUPERUSER_ROLE';
9+
success: boolean;
10+
}

x-pack/plugins/ingest_manager/common/types/rest_spec/index.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,3 +14,4 @@ export * from './enrollment_api_key';
1414
export * from './install_script';
1515
export * from './output';
1616
export * from './settings';
17+
export * from './app';
Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
/*
2+
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
3+
* or more contributor license agreements. Licensed under the Elastic License;
4+
* you may not use this file except in compliance with the Elastic License.
5+
*/
6+
7+
import { sendRequest } from './use_request';
8+
import { appRoutesService } from '../../services';
9+
import { CheckPermissionsResponse } from '../../types';
10+
11+
export const sendGetPermissionsCheck = () => {
12+
return sendRequest<CheckPermissionsResponse>({
13+
path: appRoutesService.getCheckPermissionsPath(),
14+
method: 'get',
15+
});
16+
};

x-pack/plugins/ingest_manager/public/applications/ingest_manager/hooks/use_request/index.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,3 +13,4 @@ export * from './epm';
1313
export * from './outputs';
1414
export * from './settings';
1515
export * from './setup';
16+
export * from './app';

x-pack/plugins/ingest_manager/public/applications/ingest_manager/index.tsx

Lines changed: 149 additions & 62 deletions
Original file line numberDiff line numberDiff line change
@@ -7,8 +7,10 @@ import React, { useEffect, useState } from 'react';
77
import ReactDOM from 'react-dom';
88
import { useObservable } from 'react-use';
99
import { HashRouter as Router, Redirect, Switch, Route, RouteProps } from 'react-router-dom';
10+
import { i18n } from '@kbn/i18n';
1011
import { FormattedMessage } from '@kbn/i18n/react';
11-
import { EuiErrorBoundary } from '@elastic/eui';
12+
import styled from 'styled-components';
13+
import { EuiErrorBoundary, EuiPanel, EuiEmptyPrompt, EuiCode } from '@elastic/eui';
1214
import { CoreStart, AppMountParameters } from 'src/core/public';
1315
import { EuiThemeProvider } from '../../../../../legacy/common/eui_styled_components';
1416
import {
@@ -22,7 +24,7 @@ import { Loading, Error } from './components';
2224
import { IngestManagerOverview, EPMApp, AgentConfigApp, FleetApp, DataStreamApp } from './sections';
2325
import { CoreContext, DepsContext, ConfigContext, setHttpClient, useConfig } from './hooks';
2426
import { PackageInstallProvider } from './sections/epm/hooks';
25-
import { sendSetup } from './hooks/use_request/setup';
27+
import { useCore, sendSetup, sendGetPermissionsCheck } from './hooks';
2628
import { FleetStatusProvider } from './hooks/use_fleet_status';
2729
import './index.scss';
2830

@@ -39,86 +41,175 @@ export const ProtectedRoute: React.FunctionComponent<ProtectedRouteProps> = ({
3941
return isAllowed ? <Route {...routeProps} /> : <Redirect to={{ pathname: restrictedPath }} />;
4042
};
4143

44+
const Panel = styled(EuiPanel)`
45+
max-width: 500px;
46+
margin-right: auto;
47+
margin-left: auto;
48+
`;
49+
50+
const ErrorLayout = ({ children }: { children: JSX.Element }) => (
51+
<EuiErrorBoundary>
52+
<DefaultLayout showSettings={false}>
53+
<WithoutHeaderLayout>{children}</WithoutHeaderLayout>
54+
</DefaultLayout>
55+
</EuiErrorBoundary>
56+
);
57+
4258
const IngestManagerRoutes = ({ ...rest }) => {
4359
const { epm, fleet } = useConfig();
60+
const { notifications } = useCore();
4461

62+
const [isPermissionsLoading, setIsPermissionsLoading] = useState<boolean>(false);
63+
const [permissionsError, setPermissionsError] = useState<string>();
4564
const [isInitialized, setIsInitialized] = useState(false);
4665
const [initializationError, setInitializationError] = useState<Error | null>(null);
4766

4867
useEffect(() => {
4968
(async () => {
69+
setIsPermissionsLoading(false);
70+
setPermissionsError(undefined);
5071
setIsInitialized(false);
5172
setInitializationError(null);
5273
try {
53-
const res = await sendSetup();
54-
if (res.error) {
55-
setInitializationError(res.error);
74+
setIsPermissionsLoading(true);
75+
const permissionsResponse = await sendGetPermissionsCheck();
76+
setIsPermissionsLoading(false);
77+
if (permissionsResponse.data?.success) {
78+
try {
79+
const setupResponse = await sendSetup();
80+
if (setupResponse.error) {
81+
setInitializationError(setupResponse.error);
82+
}
83+
} catch (err) {
84+
setInitializationError(err);
85+
}
86+
setIsInitialized(true);
87+
} else {
88+
setPermissionsError(permissionsResponse.data?.error || 'REQUEST_ERROR');
5689
}
5790
} catch (err) {
58-
setInitializationError(err);
91+
setPermissionsError('REQUEST_ERROR');
5992
}
60-
setIsInitialized(true);
6193
})();
6294
// eslint-disable-next-line react-hooks/exhaustive-deps
6395
}, []);
6496

97+
if (isPermissionsLoading || permissionsError) {
98+
return (
99+
<ErrorLayout>
100+
{isPermissionsLoading ? (
101+
<Loading />
102+
) : permissionsError === 'REQUEST_ERROR' ? (
103+
<Error
104+
title={
105+
<FormattedMessage
106+
id="xpack.ingestManager.permissionsRequestErrorMessageTitle"
107+
defaultMessage="Unable to check permissions"
108+
/>
109+
}
110+
error={i18n.translate('xpack.ingestManager.permissionsRequestErrorMessageDescription', {
111+
defaultMessage: 'There was a problem checking Ingest Manager permissions',
112+
})}
113+
/>
114+
) : (
115+
<Panel>
116+
<EuiEmptyPrompt
117+
iconType="securityApp"
118+
title={
119+
<h2>
120+
{permissionsError === 'MISSING_SUPERUSER_ROLE' ? (
121+
<FormattedMessage
122+
id="xpack.ingestManager.permissionDeniedErrorTitle"
123+
defaultMessage="Permission denied"
124+
/>
125+
) : (
126+
<FormattedMessage
127+
id="xpack.ingestManager.securityRequiredErrorTitle"
128+
defaultMessage="Security is not enabled"
129+
/>
130+
)}
131+
</h2>
132+
}
133+
body={
134+
<p>
135+
{permissionsError === 'MISSING_SUPERUSER_ROLE' ? (
136+
<FormattedMessage
137+
id="xpack.ingestManager.permissionDeniedErrorMessage"
138+
defaultMessage="You are not authorized to access Ingest Manager. Ingest Manager requires {roleName} privileges."
139+
values={{ roleName: <EuiCode>superuser</EuiCode> }}
140+
/>
141+
) : (
142+
<FormattedMessage
143+
id="xpack.ingestManager.securityRequiredErrorMessage"
144+
defaultMessage="You must enable security in Kibana and Elasticsearch to use Ingest Manager."
145+
/>
146+
)}
147+
</p>
148+
}
149+
/>
150+
</Panel>
151+
)}
152+
</ErrorLayout>
153+
);
154+
}
155+
65156
if (!isInitialized || initializationError) {
66157
return (
67-
<EuiErrorBoundary>
68-
<DefaultLayout>
69-
<WithoutHeaderLayout>
70-
{initializationError ? (
71-
<Error
72-
title={
73-
<FormattedMessage
74-
id="xpack.ingestManager.initializationErrorMessageTitle"
75-
defaultMessage="Unable to initialize Ingest Manager"
76-
/>
77-
}
78-
error={initializationError}
158+
<ErrorLayout>
159+
{initializationError ? (
160+
<Error
161+
title={
162+
<FormattedMessage
163+
id="xpack.ingestManager.initializationErrorMessageTitle"
164+
defaultMessage="Unable to initialize Ingest Manager"
79165
/>
80-
) : (
81-
<Loading />
82-
)}
83-
</WithoutHeaderLayout>
84-
</DefaultLayout>
85-
</EuiErrorBoundary>
166+
}
167+
error={initializationError}
168+
/>
169+
) : (
170+
<Loading />
171+
)}
172+
</ErrorLayout>
86173
);
87174
}
88175

89176
return (
90-
<EuiErrorBoundary>
91-
<Router {...rest}>
92-
<Switch>
93-
<ProtectedRoute path={EPM_PATH} isAllowed={epm.enabled}>
94-
<DefaultLayout section="epm">
95-
<EPMApp />
96-
</DefaultLayout>
97-
</ProtectedRoute>
98-
<Route path={AGENT_CONFIG_PATH}>
99-
<DefaultLayout section="agent_config">
100-
<AgentConfigApp />
101-
</DefaultLayout>
102-
</Route>
103-
<Route path={DATA_STREAM_PATH}>
104-
<DefaultLayout section="data_stream">
105-
<DataStreamApp />
106-
</DefaultLayout>
107-
</Route>
108-
<ProtectedRoute path={FLEET_PATH} isAllowed={fleet.enabled}>
109-
<DefaultLayout section="fleet">
110-
<FleetApp />
111-
</DefaultLayout>
112-
</ProtectedRoute>
113-
<Route exact path="/">
114-
<DefaultLayout section="overview">
115-
<IngestManagerOverview />
116-
</DefaultLayout>
117-
</Route>
118-
<Redirect to="/" />
119-
</Switch>
120-
</Router>
121-
</EuiErrorBoundary>
177+
<PackageInstallProvider notifications={notifications}>
178+
<FleetStatusProvider>
179+
<EuiErrorBoundary>
180+
<Router {...rest}>
181+
<Switch>
182+
<ProtectedRoute path={EPM_PATH} isAllowed={epm.enabled}>
183+
<DefaultLayout section="epm">
184+
<EPMApp />
185+
</DefaultLayout>
186+
</ProtectedRoute>
187+
<Route path={AGENT_CONFIG_PATH}>
188+
<DefaultLayout section="agent_config">
189+
<AgentConfigApp />
190+
</DefaultLayout>
191+
</Route>
192+
<Route path={DATA_STREAM_PATH}>
193+
<DefaultLayout section="data_stream">
194+
<DataStreamApp />
195+
</DefaultLayout>
196+
</Route>
197+
<ProtectedRoute path={FLEET_PATH} isAllowed={fleet.enabled}>
198+
<DefaultLayout section="fleet">
199+
<FleetApp />
200+
</DefaultLayout>
201+
</ProtectedRoute>
202+
<Route exact path="/">
203+
<DefaultLayout section="overview">
204+
<IngestManagerOverview />
205+
</DefaultLayout>
206+
</Route>
207+
<Redirect to="/" />
208+
</Switch>
209+
</Router>
210+
</EuiErrorBoundary>
211+
</FleetStatusProvider>
212+
</PackageInstallProvider>
122213
);
123214
};
124215

@@ -142,11 +233,7 @@ const IngestManagerApp = ({
142233
<DepsContext.Provider value={{ setup: setupDeps, start: startDeps }}>
143234
<ConfigContext.Provider value={config}>
144235
<EuiThemeProvider darkMode={isDarkMode}>
145-
<PackageInstallProvider notifications={coreStart.notifications}>
146-
<FleetStatusProvider>
147-
<IngestManagerRoutes basepath={basepath} />
148-
</FleetStatusProvider>
149-
</PackageInstallProvider>
236+
<IngestManagerRoutes basepath={basepath} />
150237
</EuiThemeProvider>
151238
</ConfigContext.Provider>
152239
</DepsContext.Provider>

x-pack/plugins/ingest_manager/public/applications/ingest_manager/layouts/default.tsx

Lines changed: 16 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@ import { useLink, useConfig } from '../hooks';
1313
import { EPM_PATH, FLEET_PATH, AGENT_CONFIG_PATH, DATA_STREAM_PATH } from '../constants';
1414

1515
interface Props {
16+
showSettings?: boolean;
1617
section?: Section;
1718
children?: React.ReactNode;
1819
}
@@ -33,7 +34,11 @@ const Nav = styled.nav`
3334
}
3435
`;
3536

36-
export const DefaultLayout: React.FunctionComponent<Props> = ({ section, children }) => {
37+
export const DefaultLayout: React.FunctionComponent<Props> = ({
38+
showSettings = true,
39+
section,
40+
children,
41+
}) => {
3742
const { epm, fleet } = useConfig();
3843

3944
const [isSettingsFlyoutOpen, setIsSettingsFlyoutOpen] = React.useState(false);
@@ -109,14 +114,16 @@ export const DefaultLayout: React.FunctionComponent<Props> = ({ section, childre
109114
/>
110115
</EuiButtonEmpty>
111116
</EuiFlexItem>
112-
<EuiFlexItem>
113-
<EuiButtonEmpty iconType="gear" onClick={() => setIsSettingsFlyoutOpen(true)}>
114-
<FormattedMessage
115-
id="xpack.ingestManager.appNavigation.settingsButton"
116-
defaultMessage="Settings"
117-
/>
118-
</EuiButtonEmpty>
119-
</EuiFlexItem>
117+
{showSettings ? (
118+
<EuiFlexItem>
119+
<EuiButtonEmpty iconType="gear" onClick={() => setIsSettingsFlyoutOpen(true)}>
120+
<FormattedMessage
121+
id="xpack.ingestManager.appNavigation.settingsButton"
122+
defaultMessage="Settings"
123+
/>
124+
</EuiButtonEmpty>
125+
</EuiFlexItem>
126+
) : null}
120127
</EuiFlexGroup>
121128
</EuiFlexItem>
122129
</EuiFlexGroup>

x-pack/plugins/ingest_manager/public/applications/ingest_manager/services/index.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@ export {
1717
setupRouteService,
1818
outputRoutesService,
1919
settingsRoutesService,
20+
appRoutesService,
2021
packageToConfigDatasourceInputs,
2122
storedDatasourceToAgentDatasource,
2223
AgentStatusKueryHelper,

x-pack/plugins/ingest_manager/public/applications/ingest_manager/types/index.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -62,6 +62,8 @@ export {
6262
GetSettingsResponse,
6363
PutSettingsRequest,
6464
PutSettingsResponse,
65+
// API schemas - app
66+
CheckPermissionsResponse,
6567
// EPM types
6668
AssetReference,
6769
AssetsGroupedByServiceByType,

0 commit comments

Comments
 (0)