[HTTP] Set explicit access for public HTTP APIs (#192554)

## Summary

We will be enforcing restricted access to internal HTTP APIs [from
9.0](https://github.com/elastic/kibana/issues/186781). This PR is part 1
of audit checking that our public APIs have their access tag set
explicitly to ensure they are still available to end users after we
start enforcing HTTP API restrictions. APIs reviewed in this PR
([docs](https://www.elastic.co/guide/en/kibana/current/dashboard-import-api.html)):

<img width="260" alt="Screenshot 2024-09-11 at 11 25 55"
src="https://github.com/user-attachments/assets/499b1f1f-8e01-4463-9410-4500e438cd23">

## Note to reviewers

This audit is focussed on set `access: 'public'` where needed. Per the
screenshot our public-facing documentation is taken as the source of
truth for which APIs should be public. This may differ per offering so
please consider whether a given HTTP API should be public on both
serverless and stateful offerings.

## Risks

* If we miss an API that should be public, end users will encounter a
`400` response when they try to use the HTTP API on 9.0
* If we set an API's access to "public" it will not have the same
restrictions applied to it.
This commit is contained in:
Jean-Louis Leysens 2024-09-23 16:53:31 +02:00 committed by GitHub
parent 946c26ea77
commit 3fa5bdf873
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
21 changed files with 58 additions and 10 deletions

View file

@ -84,8 +84,14 @@ export function registerRoutes({
maxImportPayloadBytes: config.maxImportPayloadBytes,
coreUsageData,
logger,
access: internalOnServerless,
});
registerLegacyExportRoute(legacyRouter, {
kibanaVersion,
coreUsageData,
logger,
access: internalOnServerless,
});
registerLegacyExportRoute(legacyRouter, { kibanaVersion, coreUsageData, logger });
const internalRouter = http.createRouter<InternalSavedObjectsRequestHandlerContext>(
'/internal/saved_objects/'

View file

@ -11,6 +11,7 @@ import moment from 'moment';
import { schema } from '@kbn/config-schema';
import type { Logger } from '@kbn/logging';
import type { InternalCoreUsageDataSetup } from '@kbn/core-usage-data-base-server-internal';
import type { RouteAccess } from '@kbn/core-http-server';
import type { InternalSavedObjectRouter } from '../../internal_types';
import { exportDashboards } from './lib';
@ -20,7 +21,13 @@ export const registerLegacyExportRoute = (
kibanaVersion,
coreUsageData,
logger,
}: { kibanaVersion: string; coreUsageData: InternalCoreUsageDataSetup; logger: Logger }
access,
}: {
kibanaVersion: string;
coreUsageData: InternalCoreUsageDataSetup;
logger: Logger;
access: RouteAccess;
}
) => {
router.get(
{
@ -31,6 +38,7 @@ export const registerLegacyExportRoute = (
}),
},
options: {
access,
tags: ['api'],
},
},

View file

@ -11,6 +11,7 @@ import { schema } from '@kbn/config-schema';
import type { Logger } from '@kbn/logging';
import type { SavedObject } from '@kbn/core-saved-objects-server';
import type { InternalCoreUsageDataSetup } from '@kbn/core-usage-data-base-server-internal';
import type { RouteAccess } from '@kbn/core-http-server';
import type { InternalSavedObjectRouter } from '../../internal_types';
import { importDashboards } from './lib';
@ -20,7 +21,13 @@ export const registerLegacyImportRoute = (
maxImportPayloadBytes,
coreUsageData,
logger,
}: { maxImportPayloadBytes: number; coreUsageData: InternalCoreUsageDataSetup; logger: Logger }
access,
}: {
maxImportPayloadBytes: number;
coreUsageData: InternalCoreUsageDataSetup;
logger: Logger;
access: RouteAccess;
}
) => {
router.post(
{
@ -38,6 +45,7 @@ export const registerLegacyImportRoute = (
}),
},
options: {
access,
tags: ['api'],
body: {
maxBytes: maxImportPayloadBytes,

View file

@ -62,6 +62,7 @@ describe('POST /api/dashboards/export', () => {
kibanaVersion: 'mockversion',
coreUsageData,
logger: loggerMock.create(),
access: 'public',
});
handlerContext.savedObjects.client.bulkGet

View file

@ -62,6 +62,7 @@ describe('POST /api/dashboards/import', () => {
maxImportPayloadBytes: 26214400,
coreUsageData,
logger: loggerMock.create(),
access: 'public',
});
handlerContext.savedObjects.client.bulkCreate.mockResolvedValueOnce({

View file

@ -16,6 +16,7 @@ export function defineDeleteRolesRoutes({ router }: RouteDefinitionParams) {
{
path: '/api/security/role/{name}',
options: {
access: 'public',
summary: `Delete a role`,
},
validate: {

View file

@ -22,6 +22,7 @@ export function defineGetRolesRoutes({
{
path: '/api/security/role/{name}',
options: {
access: 'public',
summary: `Get a role`,
},
validate: {

View file

@ -22,6 +22,7 @@ export function defineGetAllRolesRoutes({
{
path: '/api/security/role',
options: {
access: 'public',
summary: `Get all roles`,
},
validate: false,

View file

@ -43,6 +43,7 @@ export function defineBulkCreateOrUpdateRolesRoutes({
{
path: '/api/security/roles',
options: {
access: 'public',
summary: 'Create or update roles',
},
validate: {

View file

@ -24,6 +24,7 @@ export function definePutRolesRoutes({
{
path: '/api/security/role/{name}',
options: {
access: 'public',
summary: `Create or update a role`,
},
validate: {

View file

@ -44,6 +44,7 @@ describe('Invalidate sessions routes', () => {
it('correctly defines route.', () => {
expect(routeConfig.options).toEqual({
access: 'public',
summary: 'Invalidate user sessions',
tags: ['access:sessionManagement'],
});

View file

@ -34,6 +34,7 @@ export function defineInvalidateSessionsRoutes({ router, getSession }: RouteDefi
}),
},
options: {
access: 'public',
tags: ['access:sessionManagement'],
summary: `Invalidate user sessions`,
},

View file

@ -24,13 +24,21 @@ const areObjectsUnique = (objects: SavedObjectIdentifier[]) =>
_.uniqBy(objects, (o: SavedObjectIdentifier) => `${o.type}:${o.id}`).length === objects.length;
export function initCopyToSpacesApi(deps: ExternalRouteDeps) {
const { router, getSpacesService, usageStatsServicePromise, getStartServices, log } = deps;
const {
router,
getSpacesService,
usageStatsServicePromise,
getStartServices,
log,
isServerless,
} = deps;
const usageStatsClientPromise = usageStatsServicePromise.then(({ getClient }) => getClient());
router.post(
{
path: '/api/spaces/_copy_saved_objects',
options: {
access: isServerless ? 'internal' : 'public',
tags: ['access:copySavedObjectsToSpaces'],
description: `Copy saved objects to spaces`,
},
@ -148,6 +156,7 @@ export function initCopyToSpacesApi(deps: ExternalRouteDeps) {
{
path: '/api/spaces/_resolve_copy_saved_objects_errors',
options: {
access: isServerless ? 'internal' : 'public',
tags: ['access:copySavedObjectsToSpaces'],
description: `Resolve conflicts copying saved objects`,
},

View file

@ -15,12 +15,13 @@ import { wrapError } from '../../../lib/errors';
import { createLicensedRouteHandler } from '../../lib';
export function initDeleteSpacesApi(deps: ExternalRouteDeps) {
const { router, log, getSpacesService } = deps;
const { router, log, getSpacesService, isServerless } = deps;
router.delete(
{
path: '/api/spaces/space/{id}',
options: {
access: isServerless ? 'internal' : 'public',
description: `Delete a space`,
},
validate: {

View file

@ -12,13 +12,14 @@ import { wrapError } from '../../../lib/errors';
import { createLicensedRouteHandler } from '../../lib';
export function initDisableLegacyUrlAliasesApi(deps: ExternalRouteDeps) {
const { router, getSpacesService, usageStatsServicePromise, log } = deps;
const { router, getSpacesService, usageStatsServicePromise, log, isServerless } = deps;
const usageStatsClientPromise = usageStatsServicePromise.then(({ getClient }) => getClient());
router.post(
{
path: '/api/spaces/_disable_legacy_url_aliases',
options: {
access: isServerless ? 'internal' : 'public',
description: `Disable legacy URL aliases`,
},
validate: {

View file

@ -13,12 +13,13 @@ import { wrapError } from '../../../lib/errors';
import { createLicensedRouteHandler } from '../../lib';
export function initGetSpaceApi(deps: ExternalRouteDeps) {
const { router, getSpacesService } = deps;
const { router, getSpacesService, isServerless } = deps;
router.get(
{
path: '/api/spaces/space/{id}',
options: {
access: isServerless ? 'internal' : 'public',
description: `Get a space`,
},
validate: {

View file

@ -13,12 +13,13 @@ import { wrapError } from '../../../lib/errors';
import { createLicensedRouteHandler } from '../../lib';
export function initGetAllSpacesApi(deps: ExternalRouteDeps) {
const { router, log, getSpacesService } = deps;
const { router, log, getSpacesService, isServerless } = deps;
router.get(
{
path: '/api/spaces/space',
options: {
access: isServerless ? 'internal' : 'public',
description: `Get all spaces`,
},
validate: {

View file

@ -12,12 +12,13 @@ import { wrapError } from '../../../lib/errors';
import { createLicensedRouteHandler } from '../../lib';
export function initGetShareableReferencesApi(deps: ExternalRouteDeps) {
const { router, getStartServices } = deps;
const { router, getStartServices, isServerless } = deps;
router.post(
{
path: '/api/spaces/_get_shareable_references',
options: {
access: isServerless ? 'internal' : 'public',
description: `Get shareable references`,
},
validate: {

View file

@ -21,6 +21,7 @@ export function initPostSpacesApi(deps: ExternalRouteDeps) {
{
path: '/api/spaces/space',
options: {
access: isServerless ? 'internal' : 'public',
description: `Create a space`,
},
validate: {

View file

@ -21,6 +21,7 @@ export function initPutSpacesApi(deps: ExternalRouteDeps) {
{
path: '/api/spaces/space/{id}',
options: {
access: isServerless ? 'internal' : 'public',
description: `Update a space`,
},
validate: {

View file

@ -14,7 +14,7 @@ import { SPACE_ID_REGEX } from '../../../lib/space_schema';
import { createLicensedRouteHandler } from '../../lib';
export function initUpdateObjectsSpacesApi(deps: ExternalRouteDeps) {
const { router, getStartServices } = deps;
const { router, getStartServices, isServerless } = deps;
const spacesSchema = schema.arrayOf(
schema.string({
@ -37,6 +37,7 @@ export function initUpdateObjectsSpacesApi(deps: ExternalRouteDeps) {
{
path: '/api/spaces/_update_objects_spaces',
options: {
access: isServerless ? 'internal' : 'public',
description: `Update saved objects in spaces`,
},
validate: {