mirror of
https://github.com/elastic/kibana.git
synced 2025-04-24 01:38:56 -04:00
[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  ## 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:
parent
32de23bdb3
commit
275c360314
23 changed files with 472 additions and 457 deletions
|
@ -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;
|
||||
};
|
||||
|
|
|
@ -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',
|
||||
}
|
||||
);
|
|
@ -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.",
|
||||
}
|
||||
);
|
||||
|
||||
|
|
|
@ -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">
|
||||
|
|
|
@ -101,8 +101,8 @@ export const OverviewPage: React.FC = () => {
|
|||
|
||||
return (
|
||||
<>
|
||||
<AlertingCallout />
|
||||
<DisabledCallout total={absoluteTotal} />
|
||||
<AlertingCallout />
|
||||
<EuiFlexGroup gutterSize="s" wrap={true}>
|
||||
<EuiFlexItem>
|
||||
<SearchField />
|
||||
|
|
|
@ -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]),
|
||||
};
|
||||
}
|
||||
|
|
|
@ -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'
|
||||
);
|
||||
|
|
|
@ -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);
|
||||
};
|
||||
|
|
|
@ -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',
|
||||
});
|
||||
|
|
|
@ -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;
|
||||
});
|
||||
});
|
||||
|
||||
|
|
|
@ -72,7 +72,7 @@ const getSyntheticsServiceAPIKey = async (server: UptimeServerSetup) => {
|
|||
}
|
||||
};
|
||||
|
||||
const setSyntheticsServiceApiKey = async (
|
||||
export const setSyntheticsServiceApiKey = async (
|
||||
soClient: SavedObjectsClientContract,
|
||||
apiKey: SyntheticsServiceApiKey
|
||||
) => {
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
},
|
||||
});
|
||||
|
|
|
@ -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'
|
||||
);
|
||||
});
|
||||
});
|
||||
|
|
|
@ -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,
|
||||
};
|
||||
};
|
||||
|
||||
|
|
|
@ -34922,12 +34922,9 @@
|
|||
"xpack.synthetics.monitorManagement.apiKey.label": "Clé d'API",
|
||||
"xpack.synthetics.monitorManagement.apiKeyWarning.label": "Cette clé d’API 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 l’emplacement",
|
||||
"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",
|
||||
|
|
|
@ -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": "テスト結果",
|
||||
|
|
|
@ -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": "测试结果",
|
||||
|
|
|
@ -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')
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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'),
|
||||
|
|
|
@ -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: [
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
});
|
||||
});
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue