Skip to content

Commit

Permalink
[Synthetics] enable auto re-generation of monitor management api when…
Browse files Browse the repository at this point in the history
… read permissions are missing (#155203)

Resolves #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
  • Loading branch information
dominiqueclarke authored Apr 24, 2023
1 parent 32de23b commit 275c360
Show file tree
Hide file tree
Showing 23 changed files with 395 additions and 380 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -51,7 +34,8 @@ export const DisabledCallout = ({ total }: { total: number }) => {
{labels.LEARN_MORE_LABEL}
</EuiLink>
</p>
)}
</EuiCallOut>
);
</EuiCallOut>
<EuiSpacer size="m" />
</>
) : null;
};

This file was deleted.

Original file line number Diff line number Diff line change
Expand Up @@ -24,22 +24,22 @@ 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.',
}
);

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

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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);

Expand Down Expand Up @@ -44,11 +44,6 @@ export const EnablementEmptyState = () => {
}
}, [isEnabled, isEnabling, error]);

const handleEnableSynthetics = () => {
enableSynthetics();
setIsEnabling(true);
};

useEffect(() => {
if (shouldFocusEnablementButton) {
buttonRef.current?.focus();
Expand All @@ -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">
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -101,8 +101,8 @@ export const OverviewPage: React.FC = () => {

return (
<>
<AlertingCallout />
<DisabledCallout total={absoluteTotal} />
<AlertingCallout />
<EuiFlexGroup gutterSize="s" wrap={true}>
<EuiFlexItem>
<SearchField />
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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();
Expand All @@ -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]),
};
}
Original file line number Diff line number Diff line change
Expand Up @@ -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'
);
Original file line number Diff line number Diff line change
Expand Up @@ -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);
};
Original file line number Diff line number Diff line change
Expand Up @@ -5,36 +5,29 @@
* 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(
getSyntheticsEnablement,
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',
});
Loading

0 comments on commit 275c360

Please sign in to comment.