Skip to content

Commit

Permalink
[8.x] [Synthetics] Sub-feature for managing private locations !! (#20…
Browse files Browse the repository at this point in the history
…1100) (#202496)

# Backport

This will backport the following commits from `main` to `8.x`:
- [[Synthetics] Sub-feature for managing private locations !!
(#201100)](#201100)

<!--- Backport version: 9.4.3 -->

### Questions ?
Please refer to the [Backport tool
documentation](https://github.com/sqren/backport)

<!--BACKPORT
[{"author":{"name":"Shahzad","email":"[email protected]"},"sourceCommit":{"committedDate":"2024-12-02T14:53:26Z","message":"[Synthetics]
Sub-feature for managing private locations !! (#201100)\n\n##
Summary\r\n\r\nFixes
https://github.com/elastic/kibana/issues/200899\r\n\r\nAdded Synthetics
Sub-feature for managing private locations !!\r\n\r\nUser can configure
a sub feature in a role under synthetics to allow\r\nmanaging private
locations ,\r\n\r\nwith role API it will be with \r\n```\r\n{\r\n
kibana: [\r\n {\r\n feature: {\r\n uptime: [\r\n 'minimal_all',\r\n
'can_manage_private_locations',\r\n ],\r\n },\r\n spaces: ['*'],\r\n
},\r\n ],\r\n }\r\n```\r\n\r\n\r\n\r\n<img width=\"1728\"
alt=\"image\"\r\nsrc=\"https://github.com/user-attachments/assets/f842da22-9c82-43d0-ad34-c6e19ea187c6\">\r\n\r\nCreate/delete
actions on UI will be disabled \r\n\r\n<img width=\"1728\"
alt=\"image\"\r\nsrc=\"https://github.com/user-attachments/assets/1a164d85-357b-42f3-ae15-0682b2db6c75\">\r\n\r\n##
Release note\r\nIf you have already modified sub feature for the
synthetics/uptime\r\nfeature to disable user access for using elastic
managed location,\r\naddition of this feature means, they will also will
not be able to\r\nmanage(add/delete) private locations. Though this will
not impact usage\r\nof private locations in monitors. If you want those
users to have\r\nability to add/delete new private locations, you can
enable that by\r\ntoggle this feature in role ui or via
api.","sha":"93eedff53c8b9c3ebe55dfdb71029336c03542f0","branchLabelMapping":{"^v9.0.0$":"main","^v8.18.0$":"8.x","^v(\\d+).(\\d+).\\d+$":"$1.$2"}},"sourcePullRequest":{"labels":["release_note:breaking","v9.0.0","release_note:feature","ci:project-deploy-observability","Team:obs-ux-management","backport:version","v8.18.0"],"title":"[Synthetics]
Sub-feature for managing private locations
!!","number":201100,"url":"https://github.com/elastic/kibana/pull/201100","mergeCommit":{"message":"[Synthetics]
Sub-feature for managing private locations !! (#201100)\n\n##
Summary\r\n\r\nFixes
https://github.com/elastic/kibana/issues/200899\r\n\r\nAdded Synthetics
Sub-feature for managing private locations !!\r\n\r\nUser can configure
a sub feature in a role under synthetics to allow\r\nmanaging private
locations ,\r\n\r\nwith role API it will be with \r\n```\r\n{\r\n
kibana: [\r\n {\r\n feature: {\r\n uptime: [\r\n 'minimal_all',\r\n
'can_manage_private_locations',\r\n ],\r\n },\r\n spaces: ['*'],\r\n
},\r\n ],\r\n }\r\n```\r\n\r\n\r\n\r\n<img width=\"1728\"
alt=\"image\"\r\nsrc=\"https://github.com/user-attachments/assets/f842da22-9c82-43d0-ad34-c6e19ea187c6\">\r\n\r\nCreate/delete
actions on UI will be disabled \r\n\r\n<img width=\"1728\"
alt=\"image\"\r\nsrc=\"https://github.com/user-attachments/assets/1a164d85-357b-42f3-ae15-0682b2db6c75\">\r\n\r\n##
Release note\r\nIf you have already modified sub feature for the
synthetics/uptime\r\nfeature to disable user access for using elastic
managed location,\r\naddition of this feature means, they will also will
not be able to\r\nmanage(add/delete) private locations. Though this will
not impact usage\r\nof private locations in monitors. If you want those
users to have\r\nability to add/delete new private locations, you can
enable that by\r\ntoggle this feature in role ui or via
api.","sha":"93eedff53c8b9c3ebe55dfdb71029336c03542f0"}},"sourceBranch":"main","suggestedTargetBranches":["8.x"],"targetPullRequestStates":[{"branch":"main","label":"v9.0.0","branchLabelMappingKey":"^v9.0.0$","isSourceBranch":true,"state":"MERGED","url":"https://github.com/elastic/kibana/pull/201100","number":201100,"mergeCommit":{"message":"[Synthetics]
Sub-feature for managing private locations !! (#201100)\n\n##
Summary\r\n\r\nFixes
https://github.com/elastic/kibana/issues/200899\r\n\r\nAdded Synthetics
Sub-feature for managing private locations !!\r\n\r\nUser can configure
a sub feature in a role under synthetics to allow\r\nmanaging private
locations ,\r\n\r\nwith role API it will be with \r\n```\r\n{\r\n
kibana: [\r\n {\r\n feature: {\r\n uptime: [\r\n 'minimal_all',\r\n
'can_manage_private_locations',\r\n ],\r\n },\r\n spaces: ['*'],\r\n
},\r\n ],\r\n }\r\n```\r\n\r\n\r\n\r\n<img width=\"1728\"
alt=\"image\"\r\nsrc=\"https://github.com/user-attachments/assets/f842da22-9c82-43d0-ad34-c6e19ea187c6\">\r\n\r\nCreate/delete
actions on UI will be disabled \r\n\r\n<img width=\"1728\"
alt=\"image\"\r\nsrc=\"https://github.com/user-attachments/assets/1a164d85-357b-42f3-ae15-0682b2db6c75\">\r\n\r\n##
Release note\r\nIf you have already modified sub feature for the
synthetics/uptime\r\nfeature to disable user access for using elastic
managed location,\r\naddition of this feature means, they will also will
not be able to\r\nmanage(add/delete) private locations. Though this will
not impact usage\r\nof private locations in monitors. If you want those
users to have\r\nability to add/delete new private locations, you can
enable that by\r\ntoggle this feature in role ui or via
api.","sha":"93eedff53c8b9c3ebe55dfdb71029336c03542f0"}},{"branch":"8.x","label":"v8.18.0","branchLabelMappingKey":"^v8.18.0$","isSourceBranch":false,"state":"NOT_CREATED"}]}]
BACKPORT-->

Co-authored-by: Shahzad <[email protected]>
  • Loading branch information
kibanamachine and shahzad31 authored Dec 2, 2024
1 parent bef94d0 commit d04aa2a
Show file tree
Hide file tree
Showing 24 changed files with 307 additions and 151 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -33,15 +33,21 @@ export const FleetPermissionsCallout = () => {
export const NoPermissionsTooltip = ({
canEditSynthetics = true,
canUsePublicLocations = true,
canManagePrivateLocations = true,
children,
}: {
canEditSynthetics?: boolean;
canUsePublicLocations?: boolean;
canManagePrivateLocations?: boolean;
children: ReactNode;
}) => {
const { isServiceAllowed } = useEnablement();

const disabledMessage = getRestrictionReasonLabel(canEditSynthetics, canUsePublicLocations);
const disabledMessage = getRestrictionReasonLabel(
canEditSynthetics,
canUsePublicLocations,
canManagePrivateLocations
);

if (!isServiceAllowed) {
return (
Expand All @@ -64,13 +70,18 @@ export const NoPermissionsTooltip = ({

function getRestrictionReasonLabel(
canEditSynthetics = true,
canUsePublicLocations = true
canUsePublicLocations = true,
canManagePrivateLocations = true
): string | undefined {
const message = !canEditSynthetics ? CANNOT_PERFORM_ACTION_SYNTHETICS : undefined;
if (message) {
return message;
}

if (!canManagePrivateLocations) {
return NEED_PERMISSIONS_PRIVATE_LOCATIONS;
}

return !canUsePublicLocations ? CANNOT_PERFORM_ACTION_PUBLIC_LOCATIONS : undefined;
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -52,7 +52,7 @@ export const PrivateLocationsTable = ({

const { locationMonitors, loading } = useLocationMonitors();

const { canSave } = useSyntheticsSettingsContext();
const { canSave, canManagePrivateLocations } = useSyntheticsSettingsContext();

const tagsList = privateLocations.reduce((acc, item) => {
const tags = item.tags || [];
Expand Down Expand Up @@ -128,13 +128,16 @@ export const PrivateLocationsTable = ({

const renderToolRight = () => {
return [
<NoPermissionsTooltip canEditSynthetics={canSave}>
<NoPermissionsTooltip
canEditSynthetics={canSave}
canManagePrivateLocations={canManagePrivateLocations}
key="addPrivateLocationButton"
>
<EuiButton
key="addPrivateLocationButton"
fill
data-test-subj={'addPrivateLocationButton'}
isLoading={loading}
disabled={!canSave}
disabled={!canSave || !canManagePrivateLocations}
onClick={() => setIsAddingNew(true)}
iconType="plusInCircle"
>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -119,6 +119,7 @@ describe('<ManagePrivateLocations />', () => {
const privateLocationName = 'Test private location';
jest.spyOn(settingsHooks, 'useSyntheticsSettingsContext').mockReturnValue({
canSave,
canManagePrivateLocations: true,
} as SyntheticsSettingsContextValues);

jest.spyOn(locationHooks, 'usePrivateLocationsAPI').mockReturnValue({
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,7 @@ export interface SyntheticsAppProps {

export interface SyntheticsSettingsContextValues {
canSave: boolean;
canManagePrivateLocations: boolean;
basePath: string;
dateRangeStart: string;
dateRangeEnd: string;
Expand Down Expand Up @@ -74,6 +75,7 @@ const defaultContext: SyntheticsSettingsContextValues = {
isLogsAvailable: true,
isDev: false,
canSave: false,
canManagePrivateLocations: false,
};
export const SyntheticsSettingsContext = createContext(defaultContext);

Expand All @@ -96,6 +98,8 @@ export const SyntheticsSettingsContextProvider: React.FC<PropsWithChildren<Synth
const { application } = useKibana().services;

const canSave = (application?.capabilities.uptime.save ?? false) as boolean;
const canManagePrivateLocations = (application?.capabilities.uptime.canManagePrivateLocations ??
false) as boolean;

const value = useMemo(() => {
return {
Expand All @@ -109,6 +113,7 @@ export const SyntheticsSettingsContextProvider: React.FC<PropsWithChildren<Synth
dateRangeStart: dateRangeStart ?? DATE_RANGE_START,
dateRangeEnd: dateRangeEnd ?? DATE_RANGE_END,
isServerless,
canManagePrivateLocations,
};
}, [
canSave,
Expand All @@ -121,6 +126,7 @@ export const SyntheticsSettingsContextProvider: React.FC<PropsWithChildren<Synth
dateRangeEnd,
commonlyUsedRanges,
isServerless,
canManagePrivateLocations,
]);

return <SyntheticsSettingsContext.Provider value={value} children={children} />;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -63,6 +63,7 @@ describe('useBreadcrumbs', () => {
setBreadcrumbs: core.chrome.setBreadcrumbs,
isInfraAvailable: false,
isLogsAvailable: false,
canManagePrivateLocations: false,
}}
>
<Component />
Expand Down
48 changes: 41 additions & 7 deletions x-pack/plugins/observability_solution/synthetics/server/feature.ts
Original file line number Diff line number Diff line change
Expand Up @@ -32,15 +32,17 @@ const UPTIME_RULE_TYPES = [
'xpack.uptime.alerts.durationAnomaly',
];

export const PRIVATE_LOCATION_WRITE_API = 'private-location-write';

const ruleTypes = [...UPTIME_RULE_TYPES, ...SYNTHETICS_RULE_TYPES];

const elasticManagedLocationsEnabledPrivilege: SubFeaturePrivilegeGroupConfig = {
groupType: 'independent' as SubFeaturePrivilegeGroupType,
privileges: [
{
id: 'elastic_managed_locations_enabled',
name: i18n.translate('xpack.synthetics.features.elasticManagedLocations', {
defaultMessage: 'Elastic managed locations enabled',
name: i18n.translate('xpack.synthetics.features.elasticManagedLocations.label', {
defaultMessage: 'Enabled',
}),
includeIn: 'all',
savedObject: {
Expand All @@ -52,6 +54,25 @@ const elasticManagedLocationsEnabledPrivilege: SubFeaturePrivilegeGroupConfig =
],
};

const canManagePrivateLocationsPrivilege: SubFeaturePrivilegeGroupConfig = {
groupType: 'independent' as SubFeaturePrivilegeGroupType,
privileges: [
{
id: 'can_manage_private_locations',
name: i18n.translate('xpack.synthetics.features.canManagePrivateLocations', {
defaultMessage: 'Can manage',
}),
includeIn: 'all',
api: [PRIVATE_LOCATION_WRITE_API],
savedObject: {
all: [privateLocationSavedObjectName, legacyPrivateLocationsSavedObjectName],
read: [],
},
ui: ['canManagePrivateLocations'],
},
],
};

export const syntheticsFeature = {
id: PLUGIN.ID,
name: PLUGIN.NAME,
Expand All @@ -74,13 +95,12 @@ export const syntheticsFeature = {
syntheticsSettingsObjectType,
syntheticsMonitorType,
syntheticsApiKeyObjectType,
privateLocationSavedObjectName,
legacyPrivateLocationsSavedObjectName,
syntheticsParamType,

// uptime settings object is also registered here since feature is shared between synthetics and uptime
uptimeSettingsObjectType,
],
read: [],
read: [privateLocationSavedObjectName, legacyPrivateLocationsSavedObjectName],
},
alerting: {
rule: {
Expand Down Expand Up @@ -128,10 +148,24 @@ export const syntheticsFeature = {
},
subFeatures: [
{
name: i18n.translate('xpack.synthetics.features.app', {
defaultMessage: 'Synthetics',
name: i18n.translate('xpack.synthetics.features.app.elastic', {
defaultMessage: 'Elastic managed locations',
}),
description: i18n.translate('xpack.synthetics.features.app.elasticDescription', {
defaultMessage:
'This feature enables users to create monitors that execute tests from Elastic managed infrastructure around the globe. There is an additional charge to use Elastic Managed testing locations. See the Elastic Cloud Pricing https://www.elastic.co/pricing page for current prices.',
}),
privilegeGroups: [elasticManagedLocationsEnabledPrivilege],
},
{
name: i18n.translate('xpack.synthetics.features.app.private', {
defaultMessage: 'Private locations',
}),
description: i18n.translate('xpack.synthetics.features.app.private,description', {
defaultMessage:
'This feature allows you to manage your private locations, for example adding, or deleting them.',
}),
privilegeGroups: [canManagePrivateLocationsPrivilege],
},
],
};
Original file line number Diff line number Diff line change
Expand Up @@ -158,7 +158,7 @@ export class SyntheticsEsClient {
esError,
esRequestParams: { index: SYNTHETICS_INDEX_PATTERN, ...request },
esRequestStatus: RequestStatus.OK,
esResponse: res.body.responses[index],
esResponse: res?.body.responses[index],
kibanaRequest: this.request!,
operationName: operationName ?? '',
startTime: startTimeNow,
Expand All @@ -168,9 +168,10 @@ export class SyntheticsEsClient {
}

return {
responses: res.body?.responses as unknown as Array<
InferSearchResponseOf<TDocument, TSearchRequest>
>,
responses:
(res?.body?.responses as unknown as Array<
InferSearchResponseOf<TDocument, TSearchRequest>
>) ?? [],
};
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -305,27 +305,34 @@ export const syncEditedMonitor = async ({
};

export const validatePermissions = async (
{ server, response, request }: RouteContext,
routeContext: RouteContext,
monitorLocations: MonitorLocations
) => {
const hasPublicLocations = monitorLocations?.some((loc) => loc.isServiceManaged);
if (!hasPublicLocations) {
return;
}

const elasticManagedLocationsEnabled =
Boolean(
(
await server.coreStart?.capabilities.resolveCapabilities(request, {
capabilityPath: 'uptime.*',
})
).uptime.elasticManagedLocationsEnabled
) ?? true;
const { elasticManagedLocationsEnabled } = await validateLocationPermissions(routeContext);
if (!elasticManagedLocationsEnabled) {
return ELASTIC_MANAGED_LOCATIONS_DISABLED;
}
};

export const validateLocationPermissions = async ({ server, request }: RouteContext) => {
const uptimeFeature = await server.coreStart?.capabilities.resolveCapabilities(request, {
capabilityPath: 'uptime.*',
});
const elasticManagedLocationsEnabled =
Boolean(uptimeFeature.uptime.elasticManagedLocationsEnabled) ?? true;
const canManagePrivateLocations = Boolean(uptimeFeature.uptime.canManagePrivateLocations) ?? true;

return {
canManagePrivateLocations,
elasticManagedLocationsEnabled,
};
};

const getInvalidOriginError = (monitor: SyntheticsMonitor) => {
return {
body: {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@
*/

import { schema, TypeOf } from '@kbn/config-schema';
import { PRIVATE_LOCATION_WRITE_API } from '../../../feature';
import { migrateLegacyPrivateLocations } from './migrate_legacy_private_locations';
import { SyntheticsRestApiRouteFactory } from '../../types';
import { getPrivateLocationsAndAgentPolicies } from './get_private_locations';
Expand Down Expand Up @@ -38,10 +39,12 @@ export const addPrivateLocationRoute: SyntheticsRestApiRouteFactory<PrivateLocat
body: PrivateLocationSchema,
},
},
requiredPrivileges: [PRIVATE_LOCATION_WRITE_API],
handler: async (routeContext) => {
await migrateLegacyPrivateLocations(routeContext);
const { response, request, savedObjectsClient, syntheticsMonitorClient, server } = routeContext;
const internalSOClient = server.coreStart.savedObjects.createInternalRepository();

const { response, request, savedObjectsClient, syntheticsMonitorClient } = routeContext;
await migrateLegacyPrivateLocations(internalSOClient, server.logger);

const location = request.body as PrivateLocationObject;

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@

import { schema } from '@kbn/config-schema';
import { isEmpty } from 'lodash';
import { PRIVATE_LOCATION_WRITE_API } from '../../../feature';
import { migrateLegacyPrivateLocations } from './migrate_legacy_private_locations';
import { getMonitorsByLocation } from './get_location_monitors';
import { getPrivateLocationsAndAgentPolicies } from './get_private_locations';
Expand All @@ -25,10 +26,13 @@ export const deletePrivateLocationRoute: SyntheticsRestApiRouteFactory<undefined
}),
},
},
requiredPrivileges: [PRIVATE_LOCATION_WRITE_API],
handler: async (routeContext) => {
await migrateLegacyPrivateLocations(routeContext);

const { savedObjectsClient, syntheticsMonitorClient, request, response, server } = routeContext;
const internalSOClient = server.coreStart.savedObjects.createInternalRepository();

await migrateLegacyPrivateLocations(internalSOClient, server.logger);

const { locationId } = request.params as { locationId: string };

const { locations } = await getPrivateLocationsAndAgentPolicies(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -31,9 +31,11 @@ export const getPrivateLocationsRoute: SyntheticsRestApiRouteFactory<
},
},
handler: async (routeContext) => {
await migrateLegacyPrivateLocations(routeContext);
const { savedObjectsClient, syntheticsMonitorClient, request, response, server } = routeContext;

const internalSOClient = server.coreStart.savedObjects.createInternalRepository();
await migrateLegacyPrivateLocations(internalSOClient, server.logger);

const { savedObjectsClient, syntheticsMonitorClient, request, response } = routeContext;
const { id } = request.params as { id?: string };

const { locations, agentPolicies } = await getPrivateLocationsAndAgentPolicies(
Expand Down
Loading

0 comments on commit d04aa2a

Please sign in to comment.