Skip to content

Commit 0a27e53

Browse files
authored
[Monitoring] Improve permissions required around setup mode (#50421) (#50919)
* Add error messages when setup mode is not enabled, disable it for users without the necessary permissions, and change one query to relax the privilege requirements * Fix default value * PR feedback * Forgot to update this part * Fix tests
1 parent cf2bdb3 commit 0a27e53

File tree

14 files changed

+186
-16
lines changed

14 files changed

+186
-16
lines changed

x-pack/legacy/plugins/monitoring/public/lib/setup_mode.js

Lines changed: 21 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@
77
import { ajaxErrorHandlersProvider } from './ajax_error_handler';
88
import { get, contains } from 'lodash';
99
import chrome from 'ui/chrome';
10+
import { toastNotifications } from 'ui/notify';
1011
import { i18n } from '@kbn/i18n';
1112

1213
function isOnPage(hash) {
@@ -81,7 +82,26 @@ export const updateSetupModeData = async (uuid, fetchWithoutClusterUuid = false)
8182
const oldData = setupModeState.data;
8283
const data = await fetchCollectionData(uuid, fetchWithoutClusterUuid);
8384
setupModeState.data = data;
84-
if (chrome.getInjected('isOnCloud')) {
85+
86+
const isCloud = chrome.getInjected('isOnCloud');
87+
const hasPermissions = get(data, '_meta.hasPermissions', false);
88+
if (isCloud || !hasPermissions) {
89+
const text = !hasPermissions
90+
? i18n.translate('xpack.monitoring.setupMode.notAvailablePermissions', {
91+
defaultMessage: 'You do not have the necessary permissions to do this.'
92+
})
93+
: i18n.translate('xpack.monitoring.setupMode.notAvailableCloud', {
94+
defaultMessage: 'This feature is not available on cloud.'
95+
});
96+
97+
angularState.scope.$evalAsync(() => {
98+
toastNotifications.addDanger({
99+
title: i18n.translate('xpack.monitoring.setupMode.notAvailableTitle', {
100+
defaultMessage: 'Setup mode is not available'
101+
}),
102+
text,
103+
});
104+
});
85105
return toggleSetupMode(false); // eslint-disable-line no-use-before-define
86106
}
87107
notifySetupModeDataChange(oldData);

x-pack/legacy/plugins/monitoring/public/lib/setup_mode.test.js

Lines changed: 47 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -40,7 +40,8 @@ const angularStateMock = {
4040
}
4141
},
4242
scope: {
43-
$apply: fn => fn && fn()
43+
$apply: fn => fn && fn(),
44+
$evalAsync: fn => fn && fn()
4445
}
4546
};
4647

@@ -123,19 +124,60 @@ describe('setup_mode', () => {
123124
});
124125

125126
it('should not fetch data if on cloud', async (done) => {
127+
const addDanger = jest.fn();
126128
jest.doMock('ui/chrome', () => ({
127129
getInjected: (key) => {
128130
if (key === 'isOnCloud') {
129131
return true;
130132
}
131133
}
132134
}));
135+
data = {
136+
_meta: {
137+
hasPermissions: true
138+
}
139+
};
140+
jest.doMock('ui/notify', () => ({
141+
toastNotifications: {
142+
addDanger,
143+
}
144+
}));
145+
setModules();
146+
initSetupModeState(angularStateMock.scope, angularStateMock.injector);
147+
await toggleSetupMode(true);
148+
waitForSetupModeData(() => {
149+
const state = getSetupModeState();
150+
expect(state.enabled).toBe(false);
151+
expect(addDanger).toHaveBeenCalledWith({
152+
title: 'Setup mode is not available',
153+
text: 'This feature is not available on cloud.'
154+
});
155+
done();
156+
});
157+
});
158+
159+
it('should not fetch data if the user does not have sufficient permissions', async (done) => {
160+
const addDanger = jest.fn();
161+
jest.doMock('ui/notify', () => ({
162+
toastNotifications: {
163+
addDanger,
164+
}
165+
}));
166+
data = {
167+
_meta: {
168+
hasPermissions: false
169+
}
170+
};
133171
setModules();
134172
initSetupModeState(angularStateMock.scope, angularStateMock.injector);
135173
await toggleSetupMode(true);
136174
waitForSetupModeData(() => {
137175
const state = getSetupModeState();
138176
expect(state.enabled).toBe(false);
177+
expect(addDanger).toHaveBeenCalledWith({
178+
title: 'Setup mode is not available',
179+
text: 'You do not have the necessary permissions to do this.'
180+
});
139181
done();
140182
});
141183
});
@@ -144,7 +186,8 @@ describe('setup_mode', () => {
144186
const clusterUuid = '1ajy';
145187
data = {
146188
_meta: {
147-
liveClusterUuid: clusterUuid
189+
liveClusterUuid: clusterUuid,
190+
hasPermissions: true
148191
},
149192
elasticsearch: {
150193
byUuid: {
@@ -166,7 +209,8 @@ describe('setup_mode', () => {
166209
const clusterUuid = '1ajy';
167210
data = {
168211
_meta: {
169-
liveClusterUuid: clusterUuid
212+
liveClusterUuid: clusterUuid,
213+
hasPermissions: true
170214
},
171215
elasticsearch: {
172216
byUuid: {

x-pack/legacy/plugins/monitoring/server/lib/setup/collection/get_collection_status.js

Lines changed: 39 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -145,6 +145,21 @@ const getRecentMonitoringDocuments = async (req, indexPatterns, clusterUuid, nod
145145
return await callWithRequest(req, 'search', params);
146146
};
147147

148+
async function doesIndexExist(req, index) {
149+
const params = {
150+
index,
151+
size: 0,
152+
terminate_after: 1,
153+
ignoreUnavailable: true,
154+
filterPath: [
155+
'hits.total.value'
156+
],
157+
};
158+
const { callWithRequest } = req.server.plugins.elasticsearch.getCluster('monitoring');
159+
const response = await callWithRequest(req, 'search', params);
160+
return get(response, 'hits.total.value', 0) > 0;
161+
}
162+
148163
async function detectProducts(req, isLiveCluster) {
149164
const result = {
150165
[KIBANA_SYSTEM_ID]: {
@@ -188,10 +203,9 @@ async function detectProducts(req, isLiveCluster) {
188203
];
189204

190205
if (isLiveCluster) {
191-
const { callWithRequest } = req.server.plugins.elasticsearch.getCluster('data');
192206
for (const { id, indices } of detectionSearch) {
193-
const response = await callWithRequest(req, 'cat.indices', { index: indices, format: 'json' });
194-
if (response.length) {
207+
const exists = await doesIndexExist(req, indices.join(','));
208+
if (exists) {
195209
result[id].mightExist = true;
196210
}
197211
}
@@ -223,6 +237,19 @@ function isBeatFromAPM(bucket) {
223237
return get(beatType, 'buckets[0].key') === 'apm-server';
224238
}
225239

240+
async function hasNecessaryPermissions(req) {
241+
const { callWithRequest } = req.server.plugins.elasticsearch.getCluster('data');
242+
const response = await callWithRequest(req, 'transport.request', {
243+
method: 'POST',
244+
path: '/_security/user/_has_privileges',
245+
body: {
246+
cluster: ['monitor'],
247+
}
248+
});
249+
// If there is some problem, assume they do not have access
250+
return get(response, 'has_all_requested', false);
251+
}
252+
226253
/**
227254
* Determines if we should ignore this bucket from this product.
228255
*
@@ -316,6 +343,14 @@ async function getLiveElasticsearchCollectionEnabled(req) {
316343
export const getCollectionStatus = async (req, indexPatterns, clusterUuid, nodeUuid, skipLiveData) => {
317344
const config = req.server.config();
318345
const kibanaUuid = config.get('server.uuid');
346+
const hasPermissions = await hasNecessaryPermissions(req);
347+
if (!hasPermissions) {
348+
return {
349+
_meta: {
350+
hasPermissions: false
351+
}
352+
};
353+
}
319354
const liveClusterUuid = skipLiveData ? null : await getLiveElasticsearchClusterUuid(req);
320355
const isLiveCluster = !clusterUuid || liveClusterUuid === clusterUuid;
321356

@@ -547,6 +582,7 @@ export const getCollectionStatus = async (req, indexPatterns, clusterUuid, nodeU
547582
status._meta = {
548583
secondsAgo: NUMBER_OF_SECONDS_AGO_TO_LOOK,
549584
liveClusterUuid,
585+
hasPermissions,
550586
};
551587

552588
return status;

x-pack/test/api_integration/apis/monitoring/setup/collection/fixtures/detect_apm.json

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -51,6 +51,7 @@
5151
},
5252
"_meta": {
5353
"secondsAgo": 30,
54-
"liveClusterUuid": null
54+
"liveClusterUuid": null,
55+
"hasPermissions": true
5556
}
5657
}

x-pack/test/api_integration/apis/monitoring/setup/collection/fixtures/detect_beats.json

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -60,6 +60,7 @@
6060
},
6161
"_meta": {
6262
"secondsAgo": 30,
63-
"liveClusterUuid": null
63+
"liveClusterUuid": null,
64+
"hasPermissions": true
6465
}
6566
}

x-pack/test/api_integration/apis/monitoring/setup/collection/fixtures/detect_beats_management.json

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -51,6 +51,7 @@
5151
},
5252
"_meta": {
5353
"secondsAgo": 30,
54-
"liveClusterUuid": null
54+
"liveClusterUuid": null,
55+
"hasPermissions": true
5556
}
5657
}

x-pack/test/api_integration/apis/monitoring/setup/collection/fixtures/detect_logstash.json

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -51,6 +51,7 @@
5151
},
5252
"_meta": {
5353
"secondsAgo": 30,
54-
"liveClusterUuid": null
54+
"liveClusterUuid": null,
55+
"hasPermissions": true
5556
}
5657
}

x-pack/test/api_integration/apis/monitoring/setup/collection/fixtures/detect_logstash_management.json

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -51,6 +51,7 @@
5151
},
5252
"_meta": {
5353
"secondsAgo": 30,
54-
"liveClusterUuid": null
54+
"liveClusterUuid": null,
55+
"hasPermissions": true
5556
}
5657
}

x-pack/test/api_integration/apis/monitoring/setup/collection/fixtures/es_and_kibana_exclusive_mb.json

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -71,6 +71,7 @@
7171
},
7272
"_meta": {
7373
"secondsAgo": 30,
74-
"liveClusterUuid": null
74+
"liveClusterUuid": null,
75+
"hasPermissions": true
7576
}
7677
}

x-pack/test/api_integration/apis/monitoring/setup/collection/fixtures/es_and_kibana_mb.json

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -71,6 +71,7 @@
7171
},
7272
"_meta": {
7373
"secondsAgo": 30,
74-
"liveClusterUuid": null
74+
"liveClusterUuid": null,
75+
"hasPermissions": true
7576
}
7677
}

0 commit comments

Comments
 (0)