[Synthetics] enable auto re-generation of monitor management api when read permissions are missing (#155203)

Resolves https://github.com/elastic/kibana/issues/151695

Auto regenerates the synthetics api key when it does not include
`synthetics-*` read permissions.

Also ensures key are regenerated when deleted via stack management. 

A user without permissions to enable monitor management will see this
callout when monitor management is disabled for either reason
![Synthetics-Overview-Synthetics-Kibana
(1)](https://user-images.githubusercontent.com/11356435/232926046-ea39115b-acc7-40a7-8ec1-de77a20daf53.png)

## Testing lack of read permissions

This PR is hard to test. I did so by adjusting the code to force the
creation of an api key without read permissions. Here's how I did it:
1. connect to a clean ES instance by creating a new oblt cluster or
running `yarn es snapshot
2. Remove read permissions for the api key
https://github.com/elastic/kibana/pull/155203/files#diff-e38e55402aedfdb1a8a17bdd557364cd3649e1590b5e92fb44ed639f03ba880dR30
3. Remove read permission check here
https://github.com/elastic/kibana/pull/155203/files#diff-e38e55402aedfdb1a8a17bdd557364cd3649e1590b5e92fb44ed639f03ba880dR60
4. Navigate to Synthetics app and create your first monitor
5. Navigate to Stack Management -> Api Keys. Click on he api key to
inspect it's privileges. You should not see `read` permissions.
6. Remove the changes listed in step 2 and 3 and make sure the branch is
back in sync with this PR
7. Navigate to the Synthetics app again.
9. Navigate to stack management -> api keys. Ensure there is only one
synthetics monitor management api key. Click on he api key to inspect
it's privileges. You should now see `read` permissions.
10. Delete this api key
11. Navigate back to the Synthetics app
12. Navigate back to stack management -> api keys. Notice tha api key
has been regenerated
This commit is contained in:
Dominique Clarke 2023-04-24 16:19:59 -04:00 committed by GitHub
parent 32de23bdb3
commit 275c360314
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
23 changed files with 472 additions and 457 deletions

View file

@ -6,41 +6,24 @@
*/
import React from 'react';
import { EuiButton, EuiCallOut, EuiLink } from '@elastic/eui';
import { InvalidApiKeyCalloutCallout } from './invalid_api_key_callout';
import { EuiCallOut, EuiLink, EuiSpacer } from '@elastic/eui';
import * as labels from './labels';
import { useEnablement } from '../../../hooks';
export const DisabledCallout = ({ total }: { total: number }) => {
const { enablement, enableSynthetics, invalidApiKeyError, loading } = useEnablement();
const { enablement, invalidApiKeyError, loading } = useEnablement();
const showDisableCallout = !enablement.isEnabled && total > 0;
const showInvalidApiKeyError = invalidApiKeyError && total > 0;
const showInvalidApiKeyCallout = invalidApiKeyError && total > 0;
if (showInvalidApiKeyError) {
return <InvalidApiKeyCalloutCallout />;
}
if (!showDisableCallout) {
if (!showDisableCallout && !showInvalidApiKeyCallout) {
return null;
}
return (
<EuiCallOut title={labels.CALLOUT_MANAGEMENT_DISABLED} color="warning" iconType="help">
<p>{labels.CALLOUT_MANAGEMENT_DESCRIPTION}</p>
{enablement.canEnable || loading ? (
<EuiButton
data-test-subj="syntheticsMonitorManagementPageButton"
fill
color="primary"
onClick={() => {
enableSynthetics();
}}
isLoading={loading}
>
{labels.SYNTHETICS_ENABLE_LABEL}
</EuiButton>
) : (
return !enablement.canEnable && !loading ? (
<>
<EuiCallOut title={labels.CALLOUT_MANAGEMENT_DISABLED} color="warning">
<p>{labels.CALLOUT_MANAGEMENT_DESCRIPTION}</p>
<p>
{labels.CALLOUT_MANAGEMENT_CONTACT_ADMIN}{' '}
<EuiLink
@ -51,7 +34,8 @@ export const DisabledCallout = ({ total }: { total: number }) => {
{labels.LEARN_MORE_LABEL}
</EuiLink>
</p>
)}
</EuiCallOut>
);
</EuiCallOut>
<EuiSpacer size="m" />
</>
) : null;
};

View file

@ -1,80 +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 React from 'react';
import { EuiButton, EuiCallOut, EuiLink, EuiSpacer } from '@elastic/eui';
import { i18n } from '@kbn/i18n';
import { useEnablement } from '../../../hooks';
export const InvalidApiKeyCalloutCallout = () => {
const { enablement, enableSynthetics, loading } = useEnablement();
return (
<>
<EuiCallOut title={API_KEY_MISSING} color="warning" iconType="help">
<p>{CALLOUT_MANAGEMENT_DESCRIPTION}</p>
{enablement.canEnable || loading ? (
<EuiButton
data-test-subj="syntheticsInvalidApiKeyCalloutCalloutButton"
fill
color="primary"
onClick={() => {
enableSynthetics();
}}
isLoading={loading}
>
{SYNTHETICS_ENABLE_LABEL}
</EuiButton>
) : (
<p>
{CALLOUT_MANAGEMENT_CONTACT_ADMIN}{' '}
<EuiLink
data-test-subj="syntheticsInvalidApiKeyCalloutCalloutLink"
href="https://www.elastic.co/guide/en/observability/current/synthetics-get-started-ui.html#uptime-set-up-prereq"
target="_blank"
>
{LEARN_MORE_LABEL}
</EuiLink>
</p>
)}
</EuiCallOut>
<EuiSpacer size="s" />
</>
);
};
const LEARN_MORE_LABEL = i18n.translate(
'xpack.synthetics.monitorManagement.manageMonitorLoadingLabel.callout.invalidKey',
{
defaultMessage: 'Learn more',
}
);
const API_KEY_MISSING = i18n.translate('xpack.synthetics.monitorManagement.callout.apiKeyMissing', {
defaultMessage: 'Monitor Management is currently disabled because of missing API key',
});
const CALLOUT_MANAGEMENT_CONTACT_ADMIN = i18n.translate(
'xpack.synthetics.monitorManagement.callout.disabledCallout.invalidKey',
{
defaultMessage: 'Contact your administrator to enable Monitor Management.',
}
);
const CALLOUT_MANAGEMENT_DESCRIPTION = i18n.translate(
'xpack.synthetics.monitorManagement.callout.description.invalidKey',
{
defaultMessage: `Monitor Management is currently disabled. To run your monitors in one of Elastic's global managed testing locations, you need to re-enable monitor management.`,
}
);
const SYNTHETICS_ENABLE_LABEL = i18n.translate(
'xpack.synthetics.monitorManagement.syntheticsEnableLabel.invalidKey',
{
defaultMessage: 'Enable monitor management',
}
);

View file

@ -24,14 +24,14 @@ export const LEARN_MORE_LABEL = i18n.translate(
export const CALLOUT_MANAGEMENT_DISABLED = i18n.translate(
'xpack.synthetics.monitorManagement.callout.disabled',
{
defaultMessage: 'Monitor Management is disabled',
defaultMessage: 'Monitor Management is currently disabled',
}
);
export const CALLOUT_MANAGEMENT_CONTACT_ADMIN = i18n.translate(
'xpack.synthetics.monitorManagement.callout.disabled.adminContact',
{
defaultMessage: 'Please contact your administrator to enable Monitor Management.',
defaultMessage: 'Monitor Management will be enabled when an admin visits the Synthetics app.',
}
);
@ -39,7 +39,7 @@ export const CALLOUT_MANAGEMENT_DESCRIPTION = i18n.translate(
'xpack.synthetics.monitorManagement.callout.description.disabled',
{
defaultMessage:
'Monitor Management is currently disabled. To run your monitors on Elastic managed Synthetics service, enable Monitor Management. Your existing monitors are paused.',
"Monitor Management requires a valid API key to run your monitors on Elastic's global managed testing locations. If you already had enabled Monitor Management previously, the API key may no longer be valid.",
}
);

View file

@ -6,16 +6,16 @@
*/
import React, { useState, useEffect, useRef } from 'react';
import { EuiEmptyPrompt, EuiButton, EuiTitle, EuiLink } from '@elastic/eui';
import { EuiEmptyPrompt, EuiTitle, EuiLink } from '@elastic/eui';
import { useEnablement } from '../../../../hooks/use_enablement';
import { kibanaService } from '../../../../../../utils/kibana_service';
import * as labels from './labels';
export const EnablementEmptyState = () => {
const { error, enablement, enableSynthetics, loading } = useEnablement();
const { error, enablement, loading } = useEnablement();
const [shouldFocusEnablementButton, setShouldFocusEnablementButton] = useState(false);
const [isEnabling, setIsEnabling] = useState(false);
const { isEnabled, canEnable } = enablement;
const { isEnabled } = enablement;
const isEnabledRef = useRef(isEnabled);
const buttonRef = useRef<HTMLButtonElement>(null);
@ -44,11 +44,6 @@ export const EnablementEmptyState = () => {
}
}, [isEnabled, isEnabling, error]);
const handleEnableSynthetics = () => {
enableSynthetics();
setIsEnabling(true);
};
useEffect(() => {
if (shouldFocusEnablementButton) {
buttonRef.current?.focus();
@ -57,33 +52,8 @@ export const EnablementEmptyState = () => {
return !isEnabled && !loading ? (
<EuiEmptyPrompt
title={
<h2>
{canEnable
? labels.MONITOR_MANAGEMENT_ENABLEMENT_LABEL
: labels.SYNTHETICS_APP_DISABLED_LABEL}
</h2>
}
body={
<p>
{canEnable
? labels.MONITOR_MANAGEMENT_ENABLEMENT_MESSAGE
: labels.MONITOR_MANAGEMENT_DISABLED_MESSAGE}
</p>
}
actions={
canEnable ? (
<EuiButton
color="primary"
fill
onClick={handleEnableSynthetics}
data-test-subj="syntheticsEnableButton"
buttonRef={buttonRef}
>
{labels.MONITOR_MANAGEMENT_ENABLEMENT_BTN_LABEL}
</EuiButton>
) : null
}
title={<h2>{labels.SYNTHETICS_APP_DISABLED_LABEL}</h2>}
body={<p>{labels.MONITOR_MANAGEMENT_DISABLED_MESSAGE}</p>}
footer={
<>
<EuiTitle size="xxs">

View file

@ -101,8 +101,8 @@ export const OverviewPage: React.FC = () => {
return (
<>
<AlertingCallout />
<DisabledCallout total={absoluteTotal} />
<AlertingCallout />
<EuiFlexGroup gutterSize="s" wrap={true}>
<EuiFlexItem>
<SearchField />

View file

@ -5,14 +5,9 @@
* 2.0.
*/
import { useEffect, useCallback } from 'react';
import { useEffect } from 'react';
import { useDispatch, useSelector } from 'react-redux';
import {
getSyntheticsEnablement,
enableSynthetics,
disableSynthetics,
selectSyntheticsEnablement,
} from '../state';
import { getSyntheticsEnablement, selectSyntheticsEnablement } from '../state';
export function useEnablement() {
const dispatch = useDispatch();
@ -35,7 +30,5 @@ export function useEnablement() {
invalidApiKeyError: enablement ? !Boolean(enablement?.isValidApiKey) : false,
error,
loading,
enableSynthetics: useCallback(() => dispatch(enableSynthetics()), [dispatch]),
disableSynthetics: useCallback(() => dispatch(disableSynthetics()), [dispatch]),
};
}

View file

@ -16,17 +16,3 @@ export const getSyntheticsEnablementSuccess = createAction<MonitorManagementEnab
export const getSyntheticsEnablementFailure = createAction<IHttpSerializedFetchError>(
'[SYNTHETICS_ENABLEMENT] GET FAILURE'
);
export const disableSynthetics = createAction('[SYNTHETICS_ENABLEMENT] DISABLE');
export const disableSyntheticsSuccess = createAction<{}>('[SYNTHETICS_ENABLEMENT] DISABLE SUCCESS');
export const disableSyntheticsFailure = createAction<IHttpSerializedFetchError>(
'[SYNTHETICS_ENABLEMENT] DISABLE FAILURE'
);
export const enableSynthetics = createAction('[SYNTHETICS_ENABLEMENT] ENABLE');
export const enableSyntheticsSuccess = createAction<MonitorManagementEnablementResult>(
'[SYNTHETICS_ENABLEMENT] ENABLE SUCCESS'
);
export const enableSyntheticsFailure = createAction<IHttpSerializedFetchError>(
'[SYNTHETICS_ENABLEMENT] ENABLE FAILURE'
);

View file

@ -14,17 +14,9 @@ import { apiService } from '../../../../utils/api_service';
export const fetchGetSyntheticsEnablement =
async (): Promise<MonitorManagementEnablementResult> => {
return await apiService.get(
return await apiService.put(
API_URLS.SYNTHETICS_ENABLEMENT,
undefined,
MonitorManagementEnablementResultCodec
);
};
export const fetchDisableSynthetics = async (): Promise<{}> => {
return await apiService.delete(API_URLS.SYNTHETICS_ENABLEMENT);
};
export const fetchEnableSynthetics = async (): Promise<MonitorManagementEnablementResult> => {
return await apiService.post(API_URLS.SYNTHETICS_ENABLEMENT);
};

View file

@ -5,20 +5,15 @@
* 2.0.
*/
import { takeLatest, takeLeading } from 'redux-saga/effects';
import { takeLeading } from 'redux-saga/effects';
import { i18n } from '@kbn/i18n';
import {
getSyntheticsEnablement,
getSyntheticsEnablementSuccess,
getSyntheticsEnablementFailure,
disableSynthetics,
disableSyntheticsSuccess,
disableSyntheticsFailure,
enableSynthetics,
enableSyntheticsSuccess,
enableSyntheticsFailure,
} from './actions';
import { fetchGetSyntheticsEnablement, fetchDisableSynthetics, fetchEnableSynthetics } from './api';
import { fetchEffectFactory } from '../utils/fetch_effect';
import { fetchGetSyntheticsEnablement } from './api';
export function* fetchSyntheticsEnablementEffect() {
yield takeLeading(
@ -26,15 +21,13 @@ export function* fetchSyntheticsEnablementEffect() {
fetchEffectFactory(
fetchGetSyntheticsEnablement,
getSyntheticsEnablementSuccess,
getSyntheticsEnablementFailure
getSyntheticsEnablementFailure,
undefined,
failureMessage
)
);
yield takeLatest(
disableSynthetics,
fetchEffectFactory(fetchDisableSynthetics, disableSyntheticsSuccess, disableSyntheticsFailure)
);
yield takeLatest(
enableSynthetics,
fetchEffectFactory(fetchEnableSynthetics, enableSyntheticsSuccess, enableSyntheticsFailure)
);
}
const failureMessage = i18n.translate('xpack.synthetics.settings.enablement.fail', {
defaultMessage: 'Failed to enable Monitor Management',
});

View file

@ -9,12 +9,6 @@ import { createReducer } from '@reduxjs/toolkit';
import {
getSyntheticsEnablement,
getSyntheticsEnablementSuccess,
disableSynthetics,
disableSyntheticsSuccess,
disableSyntheticsFailure,
enableSynthetics,
enableSyntheticsSuccess,
enableSyntheticsFailure,
getSyntheticsEnablementFailure,
} from './actions';
import { MonitorManagementEnablementResult } from '../../../../../common/runtime_types';
@ -45,39 +39,6 @@ export const syntheticsEnablementReducer = createReducer(initialState, (builder)
.addCase(getSyntheticsEnablementFailure, (state, action) => {
state.loading = false;
state.error = action.payload;
})
.addCase(disableSynthetics, (state) => {
state.loading = true;
})
.addCase(disableSyntheticsSuccess, (state, action) => {
state.loading = false;
state.error = null;
state.enablement = {
canEnable: state.enablement?.canEnable ?? false,
areApiKeysEnabled: state.enablement?.areApiKeysEnabled ?? false,
canManageApiKeys: state.enablement?.canManageApiKeys ?? false,
isEnabled: false,
isValidApiKey: true,
};
})
.addCase(disableSyntheticsFailure, (state, action) => {
state.loading = false;
state.error = action.payload;
})
.addCase(enableSynthetics, (state) => {
state.loading = true;
state.enablement = null;
})
.addCase(enableSyntheticsSuccess, (state, action) => {
state.loading = false;
state.error = null;
state.enablement = action.payload;
})
.addCase(enableSyntheticsFailure, (state, action) => {
state.loading = false;
state.error = action.payload;
});
});

View file

@ -72,7 +72,7 @@ const getSyntheticsServiceAPIKey = async (server: UptimeServerSetup) => {
}
};
const setSyntheticsServiceApiKey = async (
export const setSyntheticsServiceApiKey = async (
soClient: SavedObjectsClientContract,
apiKey: SyntheticsServiceApiKey
) => {

View file

@ -20,7 +20,6 @@ import { getServiceLocationsRoute } from './synthetics_service/get_service_locat
import { deleteSyntheticsMonitorRoute } from './monitor_cruds/delete_monitor';
import {
disableSyntheticsRoute,
enableSyntheticsRoute,
getSyntheticsEnablementRoute,
} from './synthetics_service/enablement';
import {
@ -61,7 +60,6 @@ export const syntheticsAppRestApiRoutes: SyntheticsRestApiRouteFactory[] = [
deleteSyntheticsMonitorProjectRoute,
disableSyntheticsRoute,
editSyntheticsMonitorRoute,
enableSyntheticsRoute,
getServiceLocationsRoute,
getSyntheticsMonitorRoute,
getSyntheticsProjectMonitorsRoute,

View file

@ -5,18 +5,12 @@
* 2.0.
*/
import { syntheticsServiceAPIKeySavedObject } from '../../legacy_uptime/lib/saved_objects/service_api_key';
import {
SyntheticsRestApiRouteFactory,
UMRestApiRouteFactory,
} from '../../legacy_uptime/routes/types';
import { SyntheticsRestApiRouteFactory } from '../../legacy_uptime/routes/types';
import { API_URLS } from '../../../common/constants';
import {
generateAndSaveServiceAPIKey,
SyntheticsForbiddenError,
} from '../../synthetics_service/get_api_key';
import { generateAndSaveServiceAPIKey } from '../../synthetics_service/get_api_key';
export const getSyntheticsEnablementRoute: UMRestApiRouteFactory = (libs) => ({
method: 'GET',
export const getSyntheticsEnablementRoute: SyntheticsRestApiRouteFactory = (libs) => ({
method: 'PUT',
path: API_URLS.SYNTHETICS_ENABLEMENT,
validate: {},
handler: async ({ savedObjectsClient, request, server }): Promise<any> => {
@ -25,7 +19,18 @@ export const getSyntheticsEnablementRoute: UMRestApiRouteFactory = (libs) => ({
server,
});
const { canEnable, isEnabled } = result;
if (canEnable && !isEnabled && server.config.service?.manifestUrl) {
const { security } = server;
const { apiKey, isValid } = await libs.requests.getAPIKeyForSyntheticsService({
server,
});
if (apiKey && !isValid) {
await syntheticsServiceAPIKeySavedObject.delete(savedObjectsClient);
await security.authc.apiKeys?.invalidateAsInternalUser({
ids: [apiKey?.id || ''],
});
}
const regenerationRequired = !isEnabled || !isValid;
if (canEnable && regenerationRequired && server.config.service?.manifestUrl) {
await generateAndSaveServiceAPIKey({
request,
authSavedObjectsClient: savedObjectsClient,
@ -68,7 +73,7 @@ export const disableSyntheticsRoute: SyntheticsRestApiRouteFactory = (libs) => (
server,
});
await syntheticsServiceAPIKeySavedObject.delete(savedObjectsClient);
await security.authc.apiKeys?.invalidate(request, { ids: [apiKey?.id || ''] });
await security.authc.apiKeys?.invalidateAsInternalUser({ ids: [apiKey?.id || ''] });
return response.ok({});
} catch (e) {
server.logger.error(e);
@ -76,30 +81,3 @@ export const disableSyntheticsRoute: SyntheticsRestApiRouteFactory = (libs) => (
}
},
});
export const enableSyntheticsRoute: UMRestApiRouteFactory = (libs) => ({
method: 'POST',
path: API_URLS.SYNTHETICS_ENABLEMENT,
validate: {},
handler: async ({ request, response, server, savedObjectsClient }): Promise<any> => {
const { logger } = server;
try {
await generateAndSaveServiceAPIKey({
request,
authSavedObjectsClient: savedObjectsClient,
server,
});
return response.ok({
body: await libs.requests.getSyntheticsEnablement({
server,
}),
});
} catch (e) {
logger.error(e);
if (e instanceof SyntheticsForbiddenError) {
return response.forbidden();
}
throw e;
}
},
});

View file

@ -25,16 +25,6 @@ describe('getAPIKeyTest', function () {
const logger = loggerMock.create();
jest.spyOn(authUtils, 'checkHasPrivileges').mockResolvedValue({
index: {
[syntheticsIndex]: {
auto_configure: true,
create_doc: true,
view_index_metadata: true,
},
},
} as any);
const server = {
logger,
security,
@ -52,6 +42,20 @@ describe('getAPIKeyTest', function () {
encoded: '@#$%^&',
});
beforeEach(() => {
jest.clearAllMocks();
jest.spyOn(authUtils, 'checkHasPrivileges').mockResolvedValue({
index: {
[syntheticsIndex]: {
auto_configure: true,
create_doc: true,
view_index_metadata: true,
read: true,
},
},
} as any);
});
it('should return existing api key', async () => {
const getObject = jest
.fn()
@ -79,4 +83,43 @@ describe('getAPIKeyTest', function () {
'ba997842-b0cf-4429-aa9d-578d9bf0d391'
);
});
it('invalidates api keys with missing read permissions', async () => {
jest.spyOn(authUtils, 'checkHasPrivileges').mockResolvedValue({
index: {
[syntheticsIndex]: {
auto_configure: true,
create_doc: true,
view_index_metadata: true,
read: false,
},
},
} as any);
const getObject = jest
.fn()
.mockReturnValue({ attributes: { apiKey: 'qwerty', id: 'test', name: 'service-api-key' } });
encryptedSavedObjects.getClient = jest.fn().mockReturnValue({
getDecryptedAsInternalUser: getObject,
});
const apiKey = await getAPIKeyForSyntheticsService({
server,
});
expect(apiKey).toEqual({
apiKey: { apiKey: 'qwerty', id: 'test', name: 'service-api-key' },
isValid: false,
});
expect(encryptedSavedObjects.getClient).toHaveBeenCalledTimes(1);
expect(getObject).toHaveBeenCalledTimes(1);
expect(encryptedSavedObjects.getClient).toHaveBeenCalledWith({
includedHiddenTypes: [syntheticsServiceApiKey.name],
});
expect(getObject).toHaveBeenCalledWith(
'uptime-synthetics-api-key',
'ba997842-b0cf-4429-aa9d-578d9bf0d391'
);
});
});

View file

@ -56,7 +56,8 @@ export const getAPIKeyForSyntheticsService = async ({
const hasPermissions =
indexPermissions.auto_configure &&
indexPermissions.create_doc &&
indexPermissions.view_index_metadata;
indexPermissions.view_index_metadata &&
indexPermissions.read;
if (!hasPermissions) {
return { isValid: false, apiKey };
@ -92,6 +93,7 @@ export const generateAPIKey = async ({
}
if (uptimePrivileges) {
/* Exposed to the user. Must create directly with the user */
return security.authc.apiKeys?.create(request, {
name: 'synthetics-api-key (required for project monitors)',
kibana_role_descriptors: {
@ -122,7 +124,8 @@ export const generateAPIKey = async ({
throw new SyntheticsForbiddenError();
}
return security.authc.apiKeys?.create(request, {
/* Not exposed to the user. May grant as internal user */
return security.authc.apiKeys?.grantAsInternalUser(request, {
name: 'synthetics-api-key (required for monitor management)',
role_descriptors: {
synthetics_writer: serviceApiKeyPrivileges,
@ -160,16 +163,6 @@ export const generateAndSaveServiceAPIKey = async ({
export const getSyntheticsEnablement = async ({ server }: { server: UptimeServerSetup }) => {
const { security, config } = server;
if (!config.service?.manifestUrl) {
return {
canEnable: true,
canManageApiKeys: true,
isEnabled: true,
isValidApiKey: true,
areApiKeysEnabled: true,
};
}
const [apiKey, hasPrivileges, areApiKeysEnabled] = await Promise.all([
getAPIKeyForSyntheticsService({ server }),
hasEnablePermissions(server),
@ -177,6 +170,17 @@ export const getSyntheticsEnablement = async ({ server }: { server: UptimeServer
]);
const { canEnable, canManageApiKeys } = hasPrivileges;
if (!config.service?.manifestUrl) {
return {
canEnable: true,
canManageApiKeys,
isEnabled: true,
isValidApiKey: true,
areApiKeysEnabled: true,
};
}
return {
canEnable,
canManageApiKeys,
@ -217,7 +221,7 @@ const hasEnablePermissions = async ({ uptimeEsClient }: UptimeServerSetup) => {
return {
canManageApiKeys,
canEnable: canManageApiKeys && hasClusterPermissions && hasIndexPermissions,
canEnable: hasClusterPermissions && hasIndexPermissions,
};
};

View file

@ -34922,12 +34922,9 @@
"xpack.synthetics.monitorManagement.apiKey.label": "Clé d'API",
"xpack.synthetics.monitorManagement.apiKeyWarning.label": "Cette clé dAPI ne sera affichée qu'une seule fois. Veuillez en conserver une copie pour vos propres dossiers.",
"xpack.synthetics.monitorManagement.areYouSure": "Voulez-vous vraiment supprimer cet emplacement ?",
"xpack.synthetics.monitorManagement.callout.apiKeyMissing": "La Gestion des moniteurs est actuellement désactivée en raison d'une clé d'API manquante",
"xpack.synthetics.monitorManagement.callout.description.disabled": "La Gestion des moniteurs est actuellement désactivée. Pour exécuter vos moniteurs sur le service Synthetics géré par Elastic, activez la Gestion des moniteurs. Vos moniteurs existants ont été suspendus.",
"xpack.synthetics.monitorManagement.callout.description.invalidKey": "La Gestion des moniteurs est actuellement désactivée. Pour exécuter vos moniteurs dans l'un des emplacements de tests gérés globaux d'Elastic, vous devez ré-activer la Gestion des moniteurs.",
"xpack.synthetics.monitorManagement.callout.disabled": "La Gestion des moniteurs est désactivée",
"xpack.synthetics.monitorManagement.callout.disabled.adminContact": "Veuillez contacter votre administrateur pour activer la Gestion des moniteurs.",
"xpack.synthetics.monitorManagement.callout.disabledCallout.invalidKey": "Contactez votre administrateur pour activer la Gestion des moniteurs.",
"xpack.synthetics.monitorManagement.cancelLabel": "Annuler",
"xpack.synthetics.monitorManagement.cannotSaveIntegration": "Vous n'êtes pas autorisé à mettre à jour les intégrations. Des autorisations d'écriture pour les intégrations sont requises.",
"xpack.synthetics.monitorManagement.closeButtonLabel": "Fermer",
@ -34976,7 +34973,6 @@
"xpack.synthetics.monitorManagement.locationName": "Nom de lemplacement",
"xpack.synthetics.monitorManagement.locationsLabel": "Emplacements",
"xpack.synthetics.monitorManagement.manageMonitorLoadingLabel": "Chargement de la liste Gestion des moniteurs",
"xpack.synthetics.monitorManagement.manageMonitorLoadingLabel.callout.invalidKey": "En savoir plus",
"xpack.synthetics.monitorManagement.manageMonitorLoadingLabel.callout.learnMore": "En savoir plus.",
"xpack.synthetics.monitorManagement.monitorAddedSuccessMessage": "Moniteur ajouté avec succès.",
"xpack.synthetics.monitorManagement.monitorEditedSuccessMessage": "Moniteur mis à jour.",
@ -35017,7 +35013,6 @@
"xpack.synthetics.monitorManagement.steps": "Étapes",
"xpack.synthetics.monitorManagement.summary.heading": "Résumé",
"xpack.synthetics.monitorManagement.syntheticsDisabledSuccess": "Gestion des moniteurs désactivée avec succès.",
"xpack.synthetics.monitorManagement.syntheticsEnableLabel.invalidKey": "Activer la Gestion des moniteurs",
"xpack.synthetics.monitorManagement.syntheticsEnableLabel.management": "Activer la Gestion des moniteurs",
"xpack.synthetics.monitorManagement.syntheticsEnableSuccess": "Gestion des moniteurs activée avec succès.",
"xpack.synthetics.monitorManagement.testResult": "Résultat du test",

View file

@ -34901,12 +34901,9 @@
"xpack.synthetics.monitorManagement.apiKey.label": "API キー",
"xpack.synthetics.monitorManagement.apiKeyWarning.label": "このAPIキーは1回だけ表示されます。自分の記録用にコピーして保管してください。",
"xpack.synthetics.monitorManagement.areYouSure": "この場所を削除しますか?",
"xpack.synthetics.monitorManagement.callout.apiKeyMissing": "現在、APIキーがないため、モニター管理は無効です",
"xpack.synthetics.monitorManagement.callout.description.disabled": "モニター管理は現在無効です。Elasticで管理されたSyntheticsサービスでモニターを実行するには、モニター管理を有効にします。既存のモニターが一時停止しています。",
"xpack.synthetics.monitorManagement.callout.description.invalidKey": "モニター管理は現在無効です。Elasticのグローバル管理されたテストロケーションのいずれかでモニターを実行するには、モニター管理を再有効化する必要があります。",
"xpack.synthetics.monitorManagement.callout.disabled": "モニター管理が無効です",
"xpack.synthetics.monitorManagement.callout.disabled.adminContact": "モニター管理を有効にするには、管理者に連絡してください。",
"xpack.synthetics.monitorManagement.callout.disabledCallout.invalidKey": "モニター管理を有効にするには、管理者に連絡してください。",
"xpack.synthetics.monitorManagement.cancelLabel": "キャンセル",
"xpack.synthetics.monitorManagement.cannotSaveIntegration": "統合を更新する権限がありません。統合書き込み権限が必要です。",
"xpack.synthetics.monitorManagement.closeButtonLabel": "閉じる",
@ -34955,7 +34952,6 @@
"xpack.synthetics.monitorManagement.locationName": "場所名",
"xpack.synthetics.monitorManagement.locationsLabel": "場所",
"xpack.synthetics.monitorManagement.manageMonitorLoadingLabel": "モニター管理を読み込んでいます",
"xpack.synthetics.monitorManagement.manageMonitorLoadingLabel.callout.invalidKey": "詳細",
"xpack.synthetics.monitorManagement.manageMonitorLoadingLabel.callout.learnMore": "詳細情報",
"xpack.synthetics.monitorManagement.monitorAddedSuccessMessage": "モニターが正常に追加されました。",
"xpack.synthetics.monitorManagement.monitorEditedSuccessMessage": "モニターは正常に更新されました。",
@ -34996,7 +34992,6 @@
"xpack.synthetics.monitorManagement.steps": "ステップ",
"xpack.synthetics.monitorManagement.summary.heading": "まとめ",
"xpack.synthetics.monitorManagement.syntheticsDisabledSuccess": "モニター管理は正常に無効にされました。",
"xpack.synthetics.monitorManagement.syntheticsEnableLabel.invalidKey": "モニター管理を有効にする",
"xpack.synthetics.monitorManagement.syntheticsEnableLabel.management": "モニター管理を有効にする",
"xpack.synthetics.monitorManagement.syntheticsEnableSuccess": "モニター管理は正常に有効にされました。",
"xpack.synthetics.monitorManagement.testResult": "テスト結果",

View file

@ -34917,12 +34917,9 @@
"xpack.synthetics.monitorManagement.apiKey.label": "API 密钥",
"xpack.synthetics.monitorManagement.apiKeyWarning.label": "此 API 密钥仅显示一次。请保留副本作为您自己的记录。",
"xpack.synthetics.monitorManagement.areYouSure": "是否确定要删除此位置?",
"xpack.synthetics.monitorManagement.callout.apiKeyMissing": "由于缺少 API 密钥,监测管理当前已禁用",
"xpack.synthetics.monitorManagement.callout.description.disabled": "监测管理当前处于禁用状态。要在 Elastic 托管 Synthetics 服务上运行监测,请启用监测管理。现有监测已暂停。",
"xpack.synthetics.monitorManagement.callout.description.invalidKey": "监测管理当前处于禁用状态。要在 Elastic 的全球托管测试位置之一运行监测,您需要重新启用监测管理。",
"xpack.synthetics.monitorManagement.callout.disabled": "已禁用监测管理",
"xpack.synthetics.monitorManagement.callout.disabled.adminContact": "请联系管理员启用监测管理。",
"xpack.synthetics.monitorManagement.callout.disabledCallout.invalidKey": "请联系管理员启用监测管理。",
"xpack.synthetics.monitorManagement.cancelLabel": "取消",
"xpack.synthetics.monitorManagement.cannotSaveIntegration": "您无权更新集成。需要集成写入权限。",
"xpack.synthetics.monitorManagement.closeButtonLabel": "关闭",
@ -34971,7 +34968,6 @@
"xpack.synthetics.monitorManagement.locationName": "位置名称",
"xpack.synthetics.monitorManagement.locationsLabel": "位置",
"xpack.synthetics.monitorManagement.manageMonitorLoadingLabel": "正在加载监测管理",
"xpack.synthetics.monitorManagement.manageMonitorLoadingLabel.callout.invalidKey": "了解详情",
"xpack.synthetics.monitorManagement.manageMonitorLoadingLabel.callout.learnMore": "了解详情。",
"xpack.synthetics.monitorManagement.monitorAddedSuccessMessage": "已成功添加监测。",
"xpack.synthetics.monitorManagement.monitorEditedSuccessMessage": "已成功更新监测。",
@ -35012,7 +35008,6 @@
"xpack.synthetics.monitorManagement.steps": "步长",
"xpack.synthetics.monitorManagement.summary.heading": "摘要",
"xpack.synthetics.monitorManagement.syntheticsDisabledSuccess": "已成功禁用监测管理。",
"xpack.synthetics.monitorManagement.syntheticsEnableLabel.invalidKey": "启用监测管理",
"xpack.synthetics.monitorManagement.syntheticsEnableLabel.management": "启用监测管理",
"xpack.synthetics.monitorManagement.syntheticsEnableSuccess": "已成功启用监测管理。",
"xpack.synthetics.monitorManagement.testResult": "测试结果",

View file

@ -74,7 +74,7 @@ export default function ({ getService }: FtrProviderContext) {
};
before(async () => {
await supertest.post(API_URLS.SYNTHETICS_ENABLEMENT).set('kbn-xsrf', 'true').expect(200);
await supertest.put(API_URLS.SYNTHETICS_ENABLEMENT).set('kbn-xsrf', 'true').expect(200);
await supertest.post('/api/fleet/setup').set('kbn-xsrf', 'true').send().expect(200);
await supertest
.post('/api/fleet/epm/packages/synthetics/0.11.4')

View file

@ -43,7 +43,7 @@ export default function ({ getService }: FtrProviderContext) {
before(async () => {
_httpMonitorJson = getFixtureJson('http_monitor');
await supertest.post('/api/fleet/setup').set('kbn-xsrf', 'true').send().expect(200);
await supertest.post(API_URLS.SYNTHETICS_ENABLEMENT).set('kbn-xsrf', 'true').expect(200);
await supertest.put(API_URLS.SYNTHETICS_ENABLEMENT).set('kbn-xsrf', 'true').expect(200);
const testPolicyName = 'Fleet test server policy' + Date.now();
const apiResponse = await testPrivateLocations.addFleetPolicy(testPolicyName);

View file

@ -32,7 +32,7 @@ export default function ({ getService }: FtrProviderContext) {
};
before(async () => {
await supertest.post(API_URLS.SYNTHETICS_ENABLEMENT).set('kbn-xsrf', 'true').expect(200);
await supertest.put(API_URLS.SYNTHETICS_ENABLEMENT).set('kbn-xsrf', 'true').expect(200);
_monitors = [
getFixtureJson('icmp_monitor'),

View file

@ -55,7 +55,7 @@ export default function ({ getService }: FtrProviderContext) {
};
before(async () => {
await supertest.post(API_URLS.SYNTHETICS_ENABLEMENT).set('kbn-xsrf', 'true').expect(200);
await supertest.put(API_URLS.SYNTHETICS_ENABLEMENT).set('kbn-xsrf', 'true').expect(200);
await kibanaServer.spaces.create({ id: SPACE_ID, name: SPACE_NAME });
await security.role.create(roleName, {
kibana: [

View file

@ -6,24 +6,57 @@
*/
import { API_URLS } from '@kbn/synthetics-plugin/common/constants';
import {
syntheticsApiKeyID,
syntheticsApiKeyObjectType,
} from '@kbn/synthetics-plugin/server/legacy_uptime/lib/saved_objects/service_api_key';
import { serviceApiKeyPrivileges } from '@kbn/synthetics-plugin/server/synthetics_service/get_api_key';
import expect from '@kbn/expect';
import { FtrProviderContext } from '../../ftr_provider_context';
export default function ({ getService }: FtrProviderContext) {
const correctPrivileges = {
applications: [],
cluster: ['monitor', 'read_ilm', 'read_pipeline'],
indices: [
{
allow_restricted_indices: false,
names: ['synthetics-*'],
privileges: ['view_index_metadata', 'create_doc', 'auto_configure', 'read'],
},
],
metadata: {},
run_as: [],
transient_metadata: {
enabled: true,
},
};
describe('SyntheticsEnablement', () => {
const supertestWithAuth = getService('supertest');
const supertest = getService('supertestWithoutAuth');
const security = getService('security');
const kibanaServer = getService('kibanaServer');
before(async () => {
await supertestWithAuth.delete(API_URLS.SYNTHETICS_ENABLEMENT).set('kbn-xsrf', 'true');
});
const esSupertest = getService('esSupertest');
describe('[GET] - /internal/uptime/service/enablement', () => {
['manage_security', 'manage_own_api_key', 'manage_api_key'].forEach((privilege) => {
it(`returns response for an admin with privilege ${privilege}`, async () => {
const getApiKeys = async () => {
const { body } = await esSupertest.get(`/_security/api_key`).query({ with_limited_by: true });
const apiKeys = body.api_keys || [];
return apiKeys.filter(
(apiKey: any) => apiKey.name.includes('synthetics-api-key') && apiKey.invalidated === false
);
};
describe('[PUT] /internal/uptime/service/enablement', () => {
beforeEach(async () => {
const apiKeys = await getApiKeys();
if (apiKeys.length) {
await supertestWithAuth.delete(API_URLS.SYNTHETICS_ENABLEMENT).set('kbn-xsrf', 'true');
}
});
['manage_security', 'manage_api_key', 'manage_own_api_key'].forEach((privilege) => {
it(`returns response when user can manage api keys`, async () => {
const username = 'admin';
const roleName = `synthetics_admin-${privilege}`;
const password = `${username}-password`;
@ -38,7 +71,7 @@ export default function ({ getService }: FtrProviderContext) {
},
],
elasticsearch: {
cluster: [privilege, ...serviceApiKeyPrivileges.cluster],
cluster: [privilege],
indices: serviceApiKeyPrivileges.indices,
},
});
@ -50,7 +83,7 @@ export default function ({ getService }: FtrProviderContext) {
});
const apiResponse = await supertest
.get(API_URLS.SYNTHETICS_ENABLEMENT)
.put(API_URLS.SYNTHETICS_ENABLEMENT)
.auth(username, password)
.set('kbn-xsrf', 'true')
.expect(200);
@ -58,17 +91,10 @@ export default function ({ getService }: FtrProviderContext) {
expect(apiResponse.body).eql({
areApiKeysEnabled: true,
canManageApiKeys: true,
canEnable: true,
isEnabled: true,
isValidApiKey: true,
canEnable: false,
isEnabled: false,
isValidApiKey: false,
});
if (privilege !== 'manage_own_api_key') {
await supertest
.delete(API_URLS.SYNTHETICS_ENABLEMENT)
.auth(username, password)
.set('kbn-xsrf', 'true')
.expect(200);
}
} finally {
await security.user.delete(username);
await security.role.delete(roleName);
@ -76,6 +102,284 @@ export default function ({ getService }: FtrProviderContext) {
});
});
it(`returns response for an admin with privilege`, async () => {
const username = 'admin';
const roleName = `synthetics_admin`;
const password = `${username}-password`;
try {
await security.role.create(roleName, {
kibana: [
{
feature: {
uptime: ['all'],
},
spaces: ['*'],
},
],
elasticsearch: {
cluster: serviceApiKeyPrivileges.cluster,
indices: serviceApiKeyPrivileges.indices,
},
});
await security.user.create(username, {
password,
roles: [roleName],
full_name: 'a kibana user',
});
const apiResponse = await supertest
.put(API_URLS.SYNTHETICS_ENABLEMENT)
.auth(username, password)
.set('kbn-xsrf', 'true')
.expect(200);
expect(apiResponse.body).eql({
areApiKeysEnabled: true,
canManageApiKeys: false,
canEnable: true,
isEnabled: true,
isValidApiKey: true,
});
const validApiKeys = await getApiKeys();
expect(validApiKeys.length).eql(1);
expect(validApiKeys[0].role_descriptors.synthetics_writer).eql(correctPrivileges);
} finally {
await security.user.delete(username);
await security.role.delete(roleName);
}
});
it(`does not create excess api keys`, async () => {
const username = 'admin';
const roleName = `synthetics_admin`;
const password = `${username}-password`;
try {
await security.role.create(roleName, {
kibana: [
{
feature: {
uptime: ['all'],
},
spaces: ['*'],
},
],
elasticsearch: {
cluster: serviceApiKeyPrivileges.cluster,
indices: serviceApiKeyPrivileges.indices,
},
});
await security.user.create(username, {
password,
roles: [roleName],
full_name: 'a kibana user',
});
const apiResponse = await supertest
.put(API_URLS.SYNTHETICS_ENABLEMENT)
.auth(username, password)
.set('kbn-xsrf', 'true')
.expect(200);
expect(apiResponse.body).eql({
areApiKeysEnabled: true,
canManageApiKeys: false,
canEnable: true,
isEnabled: true,
isValidApiKey: true,
});
const validApiKeys = await getApiKeys();
expect(validApiKeys.length).eql(1);
expect(validApiKeys[0].role_descriptors.synthetics_writer).eql(correctPrivileges);
// call api a second time
const apiResponse2 = await supertest
.put(API_URLS.SYNTHETICS_ENABLEMENT)
.auth(username, password)
.set('kbn-xsrf', 'true')
.expect(200);
expect(apiResponse2.body).eql({
areApiKeysEnabled: true,
canManageApiKeys: false,
canEnable: true,
isEnabled: true,
isValidApiKey: true,
});
const validApiKeys2 = await getApiKeys();
expect(validApiKeys2.length).eql(1);
expect(validApiKeys2[0].role_descriptors.synthetics_writer).eql(correctPrivileges);
} finally {
await security.user.delete(username);
await security.role.delete(roleName);
}
});
it(`auto re-enables the api key when created with invalid permissions and invalidates old api key`, async () => {
const username = 'admin';
const roleName = `synthetics_admin`;
const password = `${username}-password`;
try {
// create api key with incorrect permissions
const apiKeyResult = await esSupertest
.post(`/_security/api_key`)
.send({
name: 'synthetics-api-key',
expiration: '1d',
role_descriptors: {
'role-a': {
cluster: serviceApiKeyPrivileges.cluster,
indices: [
{
names: ['synthetics-*'],
privileges: ['view_index_metadata', 'create_doc', 'auto_configure'],
},
],
},
},
})
.expect(200);
kibanaServer.savedObjects.create({
id: syntheticsApiKeyID,
type: syntheticsApiKeyObjectType,
overwrite: true,
attributes: {
id: apiKeyResult.body.id,
name: 'synthetics-api-key (required for monitor management)',
apiKey: apiKeyResult.body.api_key,
},
});
const validApiKeys = await getApiKeys();
expect(validApiKeys.length).eql(1);
expect(validApiKeys[0].role_descriptors.synthetics_writer).not.eql(correctPrivileges);
await security.role.create(roleName, {
kibana: [
{
feature: {
uptime: ['all'],
},
spaces: ['*'],
},
],
elasticsearch: {
cluster: serviceApiKeyPrivileges.cluster,
indices: serviceApiKeyPrivileges.indices,
},
});
await security.user.create(username, {
password,
roles: [roleName],
full_name: 'a kibana user',
});
const apiResponse = await supertest
.put(API_URLS.SYNTHETICS_ENABLEMENT)
.auth(username, password)
.set('kbn-xsrf', 'true')
.expect(200);
expect(apiResponse.body).eql({
areApiKeysEnabled: true,
canManageApiKeys: false,
canEnable: true,
isEnabled: true,
isValidApiKey: true,
});
const validApiKeys2 = await getApiKeys();
expect(validApiKeys2.length).eql(1);
expect(validApiKeys2[0].role_descriptors.synthetics_writer).eql(correctPrivileges);
} finally {
await security.user.delete(username);
await security.role.delete(roleName);
}
});
it(`auto re-enables api key when invalidated`, async () => {
const username = 'admin';
const roleName = `synthetics_admin`;
const password = `${username}-password`;
try {
await security.role.create(roleName, {
kibana: [
{
feature: {
uptime: ['all'],
},
spaces: ['*'],
},
],
elasticsearch: {
cluster: serviceApiKeyPrivileges.cluster,
indices: serviceApiKeyPrivileges.indices,
},
});
await security.user.create(username, {
password,
roles: [roleName],
full_name: 'a kibana user',
});
const apiResponse = await supertest
.put(API_URLS.SYNTHETICS_ENABLEMENT)
.auth(username, password)
.set('kbn-xsrf', 'true')
.expect(200);
expect(apiResponse.body).eql({
areApiKeysEnabled: true,
canManageApiKeys: false,
canEnable: true,
isEnabled: true,
isValidApiKey: true,
});
const validApiKeys = await getApiKeys();
expect(validApiKeys.length).eql(1);
expect(validApiKeys[0].role_descriptors.synthetics_writer).eql(correctPrivileges);
// delete api key
await esSupertest
.delete(`/_security/api_key`)
.send({
ids: [validApiKeys[0].id],
})
.expect(200);
const validApiKeysAferDeletion = await getApiKeys();
expect(validApiKeysAferDeletion.length).eql(0);
// call api a second time
const apiResponse2 = await supertest
.put(API_URLS.SYNTHETICS_ENABLEMENT)
.auth(username, password)
.set('kbn-xsrf', 'true')
.expect(200);
expect(apiResponse2.body).eql({
areApiKeysEnabled: true,
canManageApiKeys: false,
canEnable: true,
isEnabled: true,
isValidApiKey: true,
});
const validApiKeys2 = await getApiKeys();
expect(validApiKeys2.length).eql(1);
expect(validApiKeys2[0].role_descriptors.synthetics_writer).eql(correctPrivileges);
} finally {
await security.user.delete(username);
await security.role.delete(roleName);
}
});
it('returns response for an uptime all user without admin privileges', async () => {
const username = 'uptime';
const roleName = 'uptime_user';
@ -100,7 +404,7 @@ export default function ({ getService }: FtrProviderContext) {
});
const apiResponse = await supertest
.get(API_URLS.SYNTHETICS_ENABLEMENT)
.put(API_URLS.SYNTHETICS_ENABLEMENT)
.auth(username, password)
.set('kbn-xsrf', 'true')
.expect(200);
@ -119,7 +423,13 @@ export default function ({ getService }: FtrProviderContext) {
});
});
describe('[POST] - /internal/uptime/service/enablement', () => {
describe('[DELETE] /internal/uptime/service/enablement', () => {
beforeEach(async () => {
const apiKeys = await getApiKeys();
if (apiKeys.length) {
await supertestWithAuth.delete(API_URLS.SYNTHETICS_ENABLEMENT).set('kbn-xsrf', 'true');
}
});
it('with an admin', async () => {
const username = 'admin';
const roleName = `synthetics_admin`;
@ -135,7 +445,7 @@ export default function ({ getService }: FtrProviderContext) {
},
],
elasticsearch: {
cluster: ['manage_security', ...serviceApiKeyPrivileges.cluster],
cluster: serviceApiKeyPrivileges.cluster,
indices: serviceApiKeyPrivileges.indices,
},
});
@ -147,110 +457,7 @@ export default function ({ getService }: FtrProviderContext) {
});
await supertest
.post(API_URLS.SYNTHETICS_ENABLEMENT)
.auth(username, password)
.set('kbn-xsrf', 'true')
.expect(200);
const apiResponse = await supertest
.get(API_URLS.SYNTHETICS_ENABLEMENT)
.auth(username, password)
.set('kbn-xsrf', 'true')
.expect(200);
expect(apiResponse.body).eql({
areApiKeysEnabled: true,
canManageApiKeys: true,
canEnable: true,
isEnabled: true,
isValidApiKey: true,
});
} finally {
await supertest
.delete(API_URLS.SYNTHETICS_ENABLEMENT)
.auth(username, password)
.set('kbn-xsrf', 'true')
.expect(200);
await security.user.delete(username);
await security.role.delete(roleName);
}
});
it('with an uptime user', async () => {
const username = 'uptime';
const roleName = `uptime_user`;
const password = `${username}-password`;
try {
await security.role.create(roleName, {
kibana: [
{
feature: {
uptime: ['all'],
},
spaces: ['*'],
},
],
elasticsearch: {},
});
await security.user.create(username, {
password,
roles: [roleName],
full_name: 'a kibana user',
});
await supertest
.post(API_URLS.SYNTHETICS_ENABLEMENT)
.auth(username, password)
.set('kbn-xsrf', 'true')
.expect(403);
const apiResponse = await supertest
.get(API_URLS.SYNTHETICS_ENABLEMENT)
.auth(username, password)
.set('kbn-xsrf', 'true')
.expect(200);
expect(apiResponse.body).eql({
areApiKeysEnabled: true,
canManageApiKeys: false,
canEnable: false,
isEnabled: false,
isValidApiKey: false,
});
} finally {
await security.user.delete(username);
await security.role.delete(roleName);
}
});
});
describe('[DELETE] - /internal/uptime/service/enablement', () => {
it('with an admin', async () => {
const username = 'admin';
const roleName = `synthetics_admin`;
const password = `${username}-password`;
try {
await security.role.create(roleName, {
kibana: [
{
feature: {
uptime: ['all'],
},
spaces: ['*'],
},
],
elasticsearch: {
cluster: ['manage_security', ...serviceApiKeyPrivileges.cluster],
indices: serviceApiKeyPrivileges.indices,
},
});
await security.user.create(username, {
password,
roles: [roleName],
full_name: 'a kibana user',
});
await supertest
.post(API_URLS.SYNTHETICS_ENABLEMENT)
.put(API_URLS.SYNTHETICS_ENABLEMENT)
.auth(username, password)
.set('kbn-xsrf', 'true')
.expect(200);
@ -261,14 +468,14 @@ export default function ({ getService }: FtrProviderContext) {
.expect(200);
expect(delResponse.body).eql({});
const apiResponse = await supertest
.get(API_URLS.SYNTHETICS_ENABLEMENT)
.put(API_URLS.SYNTHETICS_ENABLEMENT)
.auth(username, password)
.set('kbn-xsrf', 'true')
.expect(200);
expect(apiResponse.body).eql({
areApiKeysEnabled: true,
canManageApiKeys: true,
canManageApiKeys: false,
canEnable: true,
isEnabled: true,
isValidApiKey: true,
@ -303,7 +510,7 @@ export default function ({ getService }: FtrProviderContext) {
});
await supertestWithAuth
.post(API_URLS.SYNTHETICS_ENABLEMENT)
.put(API_URLS.SYNTHETICS_ENABLEMENT)
.set('kbn-xsrf', 'true')
.expect(200);
await supertest
@ -312,7 +519,7 @@ export default function ({ getService }: FtrProviderContext) {
.set('kbn-xsrf', 'true')
.expect(403);
const apiResponse = await supertest
.get(API_URLS.SYNTHETICS_ENABLEMENT)
.put(API_URLS.SYNTHETICS_ENABLEMENT)
.auth(username, password)
.set('kbn-xsrf', 'true')
.expect(200);
@ -351,7 +558,7 @@ export default function ({ getService }: FtrProviderContext) {
},
],
elasticsearch: {
cluster: ['manage_security', ...serviceApiKeyPrivileges.cluster],
cluster: serviceApiKeyPrivileges.cluster,
indices: serviceApiKeyPrivileges.indices,
},
});
@ -364,21 +571,21 @@ export default function ({ getService }: FtrProviderContext) {
// can enable synthetics in default space when enabled in a non default space
const apiResponseGet = await supertest
.get(`/s/${SPACE_ID}${API_URLS.SYNTHETICS_ENABLEMENT}`)
.put(`/s/${SPACE_ID}${API_URLS.SYNTHETICS_ENABLEMENT}`)
.auth(username, password)
.set('kbn-xsrf', 'true')
.expect(200);
expect(apiResponseGet.body).eql({
areApiKeysEnabled: true,
canManageApiKeys: true,
canManageApiKeys: false,
canEnable: true,
isEnabled: true,
isValidApiKey: true,
});
await supertest
.post(`/s/${SPACE_ID}${API_URLS.SYNTHETICS_ENABLEMENT}`)
.put(`/s/${SPACE_ID}${API_URLS.SYNTHETICS_ENABLEMENT}`)
.auth(username, password)
.set('kbn-xsrf', 'true')
.expect(200);
@ -388,14 +595,14 @@ export default function ({ getService }: FtrProviderContext) {
.set('kbn-xsrf', 'true')
.expect(200);
const apiResponse = await supertest
.get(API_URLS.SYNTHETICS_ENABLEMENT)
.put(API_URLS.SYNTHETICS_ENABLEMENT)
.auth(username, password)
.set('kbn-xsrf', 'true')
.expect(200);
expect(apiResponse.body).eql({
areApiKeysEnabled: true,
canManageApiKeys: true,
canManageApiKeys: false,
canEnable: true,
isEnabled: true,
isValidApiKey: true,
@ -403,7 +610,7 @@ export default function ({ getService }: FtrProviderContext) {
// can disable synthetics in non default space when enabled in default space
await supertest
.post(API_URLS.SYNTHETICS_ENABLEMENT)
.put(API_URLS.SYNTHETICS_ENABLEMENT)
.auth(username, password)
.set('kbn-xsrf', 'true')
.expect(200);
@ -413,14 +620,14 @@ export default function ({ getService }: FtrProviderContext) {
.set('kbn-xsrf', 'true')
.expect(200);
const apiResponse2 = await supertest
.get(API_URLS.SYNTHETICS_ENABLEMENT)
.put(API_URLS.SYNTHETICS_ENABLEMENT)
.auth(username, password)
.set('kbn-xsrf', 'true')
.expect(200);
expect(apiResponse2.body).eql({
areApiKeysEnabled: true,
canManageApiKeys: true,
canManageApiKeys: false,
canEnable: true,
isEnabled: true,
isValidApiKey: true,
@ -428,6 +635,7 @@ export default function ({ getService }: FtrProviderContext) {
} finally {
await security.user.delete(username);
await security.role.delete(roleName);
await kibanaServer.spaces.delete(SPACE_ID);
}
});
});