Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Unauthorized route migration for routes owned by obs-ux-management-team #198374

Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -95,6 +95,12 @@ export function registerAnnotationAPIs({
router.post(
{
path: '/api/observability/annotation',
security: {
authz: {
enabled: false,
reason: 'This route delegates authorization to Elasticsearch',
},
},
validate: {
body: unknowns,
},
Expand All @@ -110,6 +116,12 @@ export function registerAnnotationAPIs({
router.put(
{
path: '/api/observability/annotation/{id}',
security: {
authz: {
enabled: false,
reason: 'This route delegates authorization to Elasticsearch',
},
},
validate: {
body: unknowns,
},
Expand All @@ -125,6 +137,12 @@ export function registerAnnotationAPIs({
router.delete(
{
path: '/api/observability/annotation/{id}',
security: {
authz: {
enabled: false,
reason: 'This route delegates authorization to Elasticsearch',
},
},
validate: {
params: unknowns,
},
Expand All @@ -140,6 +158,12 @@ export function registerAnnotationAPIs({
router.get(
{
path: '/api/observability/annotation/{id}',
security: {
authz: {
enabled: false,
reason: 'This route delegates authorization to Elasticsearch',
},
},
validate: {
params: unknowns,
},
Expand All @@ -155,6 +179,12 @@ export function registerAnnotationAPIs({
router.get(
{
path: '/api/observability/annotation/find',
security: {
authz: {
enabled: false,
reason: 'This route delegates authorization to Elasticsearch',
},
},
validate: {
query: unknowns,
},
Expand All @@ -170,6 +200,12 @@ export function registerAnnotationAPIs({
router.get(
{
path: '/api/observability/annotation/permissions',
security: {
authz: {
enabled: false,
reason: 'This route delegates authorization to Elasticsearch',
},
},
validate: {
query: unknowns,
},
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@
*/
import { schema } from '@kbn/config-schema';
import { DEFAULT_SPACE_ID } from '@kbn/spaces-plugin/common';
import { isEmpty } from 'lodash';
import { PrivateLocationAttributes } from '../../runtime_types/private_locations';
import { getPrivateLocationsForMonitor } from '../monitor_cruds/add_monitor/utils';
import { SyntheticsRestApiRouteFactory } from '../types';
Expand All @@ -31,6 +32,9 @@ export const runOnceSyntheticsMonitorRoute: SyntheticsRestApiRouteFactory = () =
}): Promise<any> => {
const monitor = request.body as MonitorFields;
const { monitorId } = request.params;
if (isEmpty(monitor)) {
return response.badRequest({ body: { message: 'Monitor data is empty.' } });
}

const validationResult = validateMonitor(monitor);

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,8 @@
*/
import { schema } from '@kbn/config-schema';
import { v4 as uuidv4 } from 'uuid';
import { SavedObjectsErrorHelpers } from '@kbn/core-saved-objects-server';
import { IKibanaResponse } from '@kbn/core-http-server';
import { getDecryptedMonitor } from '../../saved_objects/synthetics_monitor';
import { PrivateLocationAttributes } from '../../runtime_types/private_locations';
import { RouteContext, SyntheticsRestApiRouteFactory } from '../types';
Expand All @@ -14,6 +16,7 @@ import { ConfigKey, MonitorFields } from '../../../common/runtime_types';
import { SYNTHETICS_API_URLS } from '../../../common/constants';
import { normalizeSecrets } from '../../synthetics_service/utils/secrets';
import { getPrivateLocationsForMonitor } from '../monitor_cruds/add_monitor/utils';
import { getMonitorNotFoundResponse } from './service_errors';

export const testNowMonitorRoute: SyntheticsRestApiRouteFactory<TestNowResponse> = () => ({
method: 'POST',
Expand All @@ -33,48 +36,56 @@ export const testNowMonitorRoute: SyntheticsRestApiRouteFactory<TestNowResponse>
export const triggerTestNow = async (
monitorId: string,
routeContext: RouteContext
): Promise<TestNowResponse> => {
const { server, spaceId, syntheticsMonitorClient, savedObjectsClient } = routeContext;
): Promise<TestNowResponse | IKibanaResponse<any>> => {
const { server, spaceId, syntheticsMonitorClient, savedObjectsClient, response } = routeContext;

const monitorWithSecrets = await getDecryptedMonitor(server, monitorId, spaceId);
const normalizedMonitor = normalizeSecrets(monitorWithSecrets);
try {
const monitorWithSecrets = await getDecryptedMonitor(server, monitorId, spaceId);
const normalizedMonitor = normalizeSecrets(monitorWithSecrets);

const { [ConfigKey.SCHEDULE]: schedule, [ConfigKey.LOCATIONS]: locations } =
monitorWithSecrets.attributes;
const { [ConfigKey.SCHEDULE]: schedule, [ConfigKey.LOCATIONS]: locations } =
monitorWithSecrets.attributes;

const privateLocations: PrivateLocationAttributes[] = await getPrivateLocationsForMonitor(
savedObjectsClient,
normalizedMonitor.attributes
);
const testRunId = uuidv4();
const privateLocations: PrivateLocationAttributes[] = await getPrivateLocationsForMonitor(
savedObjectsClient,
normalizedMonitor.attributes
);
const testRunId = uuidv4();

const [, errors] = await syntheticsMonitorClient.testNowConfigs(
{
monitor: normalizedMonitor.attributes as MonitorFields,
id: monitorId,
testRunId,
},
savedObjectsClient,
privateLocations,
spaceId
);
const [, errors] = await syntheticsMonitorClient.testNowConfigs(
{
monitor: normalizedMonitor.attributes as MonitorFields,
id: monitorId,
testRunId,
},
savedObjectsClient,
privateLocations,
spaceId
);

if (errors && errors?.length > 0) {
return {
errors,
testRunId,
schedule,
locations,
configId: monitorId,
monitor: normalizedMonitor.attributes,
};
}

if (errors && errors?.length > 0) {
return {
errors,
testRunId,
schedule,
locations,
configId: monitorId,
monitor: normalizedMonitor.attributes,
};
}
} catch (getErr) {
if (SavedObjectsErrorHelpers.isNotFoundError(getErr)) {
return getMonitorNotFoundResponse(response, monitorId);
}

return {
testRunId,
schedule,
locations,
configId: monitorId,
monitor: normalizedMonitor.attributes,
};
throw getErr;
}
};
23 changes: 8 additions & 15 deletions x-pack/plugins/observability_solution/synthetics/server/server.ts
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@ export const initSyntheticsServer = (
) => {
const { router } = server;
syntheticsAppRestApiRoutes.forEach((route) => {
const { method, options, handler, validate, path } = syntheticsRouteWrapper(
const { method, options, handler, validate, path, security } = syntheticsRouteWrapper(
createSyntheticsRouteWithAuth(route),
server,
syntheticsMonitorClient
Expand All @@ -30,6 +30,7 @@ export const initSyntheticsServer = (
const routeDefinition = {
path,
validate,
security,
options,
};

Expand All @@ -52,11 +53,8 @@ export const initSyntheticsServer = (
});

syntheticsAppPublicRestApiRoutes.forEach((route) => {
const { method, options, handler, validate, path, validation } = syntheticsRouteWrapper(
createSyntheticsRouteWithAuth(route),
server,
syntheticsMonitorClient
);
const { method, options, handler, validate, path, validation, security } =
syntheticsRouteWrapper(createSyntheticsRouteWithAuth(route), server, syntheticsMonitorClient);

const routeDefinition = {
path,
Expand All @@ -70,13 +68,11 @@ export const initSyntheticsServer = (
.get({
access: 'public',
path: routeDefinition.path,
options: {
tags: options?.tags,
},
})
.addVersion(
{
version: '2023-10-31',
security,
validate: validation ?? false,
},
handler
Expand All @@ -87,13 +83,11 @@ export const initSyntheticsServer = (
.put({
access: 'public',
path: routeDefinition.path,
options: {
tags: options?.tags,
},
})
.addVersion(
{
version: '2023-10-31',
security,
validate: validation ?? false,
},
handler
Expand All @@ -104,13 +98,11 @@ export const initSyntheticsServer = (
.post({
access: 'public',
path: routeDefinition.path,
options: {
tags: options?.tags,
},
})
.addVersion(
{
version: '2023-10-31',
security,
validate: validation ?? false,
},
handler
Expand All @@ -128,6 +120,7 @@ export const initSyntheticsServer = (
.addVersion(
{
version: '2023-10-31',
security,
validate: validation ?? false,
},
handler
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -20,9 +20,13 @@ export const syntheticsRouteWrapper: SyntheticsRouteWrapper = (
) => ({
...uptimeRoute,
options: {
tags: ['access:uptime-read', ...(uptimeRoute?.writeAccess ? ['access:uptime-write'] : [])],
...(uptimeRoute.options ?? {}),
},
security: {
authz: {
requiredPrivileges: ['uptime-read', ...(uptimeRoute?.writeAccess ? ['uptime-write'] : [])],
},
},
handler: async (context, request, response) => {
const { elasticsearch, savedObjects, uiSettings } = await context.core;

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -12,11 +12,12 @@ import { UptimeEsClient } from '../lib/lib';
export const uptimeRouteWrapper: UMKibanaRouteWrapper = (uptimeRoute, server) => ({
...uptimeRoute,
options: {
tags: [
'oas-tag:uptime',
'access:uptime-read',
...(uptimeRoute?.writeAccess ? ['access:uptime-write'] : []),
],
tags: ['oas-tag:uptime'],
},
security: {
authz: {
requiredPrivileges: ['uptime-read', ...(uptimeRoute?.writeAccess ? ['uptime-write'] : [])],
},
},
handler: async (context, request, response) => {
const coreContext = await context.core;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -41,7 +41,7 @@ export const initUptimeServer = (
router: UptimeRouter
) => {
legacyUptimeRestApiRoutes.forEach((route) => {
const { method, options, handler, validate, path } = uptimeRouteWrapper(
const { method, options, handler, validate, path, security } = uptimeRouteWrapper(
createRouteWithAuth(libs, route),
server
);
Expand All @@ -50,6 +50,7 @@ export const initUptimeServer = (
path,
validate,
options,
security,
};

switch (method) {
Expand All @@ -71,33 +72,28 @@ export const initUptimeServer = (
});

legacyUptimePublicRestApiRoutes.forEach((route) => {
const { method, options, handler, path, ...rest } = uptimeRouteWrapper(
const { method, options, handler, path, security, ...rest } = uptimeRouteWrapper(
createRouteWithAuth(libs, route),
server
);

const validate = rest.validate ? getRequestValidation(rest.validate) : rest.validate;

const routeDefinition = {
path,
validate,
options,
};

switch (method) {
case 'GET':
router.versioned
.get({
access: 'public',
description: `Get uptime settings`,
path: routeDefinition.path,
path,
options: {
tags: options?.tags,
},
})
.addVersion(
{
version: INITIAL_REST_VERSION,
security,
validate: {
request: {
body: validate ? validate?.body : undefined,
Expand All @@ -117,14 +113,15 @@ export const initUptimeServer = (
.put({
access: 'public',
description: `Update uptime settings`,
path: routeDefinition.path,
path,
options: {
tags: options?.tags,
},
})
.addVersion(
{
version: INITIAL_REST_VERSION,
security,
validate: {
request: {
body: validate ? validate?.body : undefined,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -304,7 +304,9 @@ export default function ({ getService }: FtrProviderContext) {
.send(httpMonitorJson);

expect(apiResponse.status).eql(403);
expect(apiResponse.body.message).eql('Forbidden');
expect(apiResponse.body.message).eql(
'API [POST /api/synthetics/monitors] is unauthorized for user, this action is granted by the Kibana privileges [uptime-write]'
);
});
});

Expand Down
Loading