From 0284cc158d7687790657c657cd5b3f4489b69b65 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Alejandro=20Fern=C3=A1ndez=20Haro?= Date: Sat, 12 Aug 2023 23:20:06 +0200 Subject: [PATCH] [Telemetry] Use header-based versioned APIs instead of path-based (#159839) --- .../using-server-logs.asciidoc | 4 +- .../usage/integration_tests/usage.test.ts | 8 +- src/plugins/files/tsconfig.json | 1 + src/plugins/telemetry/common/routes.ts | 44 +++- src/plugins/telemetry/common/types/index.ts | 1 + src/plugins/telemetry/public/plugin.ts | 4 +- .../public/services/telemetry_service.test.ts | 19 +- .../public/services/telemetry_service.ts | 40 +-- .../server/routes/telemetry_config.ts | 109 +++++--- .../server/routes/telemetry_last_reported.ts | 61 ++--- .../server/routes/telemetry_opt_in.ts | 161 ++++++------ .../server/routes/telemetry_opt_in_stats.ts | 92 ++++--- .../routes/telemetry_usage_stats.test.ts | 13 +- .../server/routes/telemetry_usage_stats.ts | 127 ++++----- .../routes/telemetry_user_has_seen_notice.ts | 71 +++--- src/plugins/telemetry/tsconfig.json | 1 + test/api_integration/apis/telemetry/opt_in.ts | 10 +- .../apis/telemetry/telemetry_config.ts | 240 ++++++++++-------- .../apis/telemetry/telemetry_last_reported.ts | 24 +- .../telemetry/telemetry_optin_notice_seen.ts | 13 +- .../test_suites/telemetry/telemetry.ts | 12 +- .../api_debug/apis/telemetry/index.js | 2 +- .../advanced_settings/feature_controls.ts | 8 +- .../apis/maps/maps_telemetry.ts | 8 +- .../apis/telemetry/telemetry.ts | 50 +++- .../apis/telemetry/telemetry_local.ts | 10 +- .../api_integration/services/usage_api.ts | 9 +- .../telemetry/telemetry.ts | 25 +- .../utils/get_stats.ts | 6 + .../utils/get_stats_url.ts | 2 +- .../apis/fleet_telemetry.ts | 8 +- .../apps/infra/logs_source_configuration.ts | 8 +- .../tests/server.ts | 8 +- ...elemetry.cluster_stats.1600_dataviews.json | 8 +- .../apis/api.telemetry.cluster_stats.json | 8 +- ...cluster_stats.no_cache.1600_dataviews.json | 8 +- .../api.telemetry.cluster_stats.no_cache.json | 8 +- 37 files changed, 779 insertions(+), 452 deletions(-) diff --git a/docs/user/troubleshooting/using-server-logs.asciidoc b/docs/user/troubleshooting/using-server-logs.asciidoc index ee6f1858478c..894b229d3f34 100644 --- a/docs/user/troubleshooting/using-server-logs.asciidoc +++ b/docs/user/troubleshooting/using-server-logs.asciidoc @@ -54,12 +54,12 @@ Once you set up the APM infrastructure, you can enable the APM agent and put {ki *Prerequisites* {kib} logs are configured to be in {ecs-ref}/ecs-reference.html[ECS JSON] format to include tracing identifiers. Open {kib} Logs and search for an operation you are interested in. -For example, suppose you want to investigate the response times for queries to the `/api/telemetry/v2/clusters/_stats` {kib} endpoint. +For example, suppose you want to investigate the response times for queries to the `/internal/telemetry/clusters/_stats` {kib} endpoint. Open Kibana Logs and search for the HTTP server response for the endpoint. It looks similar to the following (some fields are omitted for brevity). [source,json] ---- { - "message":"POST /api/telemetry/v2/clusters/_stats 200 1014ms - 43.2KB", + "message":"POST /internal/telemetry/clusters/_stats 200 1014ms - 43.2KB", "log":{"level":"DEBUG","logger":"http.server.response"}, "trace":{"id":"9b99131a6f66587971ef085ef97dfd07"}, "transaction":{"id":"d0c5bbf14f5febca"} diff --git a/src/plugins/files/server/usage/integration_tests/usage.test.ts b/src/plugins/files/server/usage/integration_tests/usage.test.ts index 02cc7dfee55f..945545302373 100644 --- a/src/plugins/files/server/usage/integration_tests/usage.test.ts +++ b/src/plugins/files/server/usage/integration_tests/usage.test.ts @@ -6,6 +6,10 @@ * Side Public License, v 1. */ +import { + ELASTIC_HTTP_VERSION_HEADER, + X_ELASTIC_INTERNAL_ORIGIN_REQUEST, +} from '@kbn/core-http-common'; import { setupIntegrationEnvironment, TestEnvironmentUtils } from '../../test_utils'; describe('Files usage telemetry', () => { @@ -45,7 +49,9 @@ describe('Files usage telemetry', () => { ]); const { body } = await request - .post(root, '/api/telemetry/v2/clusters/_stats') + .post(root, '/internal/telemetry/clusters/_stats') + .set(ELASTIC_HTTP_VERSION_HEADER, '2') + .set(X_ELASTIC_INTERNAL_ORIGIN_REQUEST, 'kibana') .send({ unencrypted: true }); expect(body[0].stats.stack_stats.kibana.plugins.files).toMatchInlineSnapshot(` diff --git a/src/plugins/files/tsconfig.json b/src/plugins/files/tsconfig.json index 08d910f23c5e..a45132f21d59 100644 --- a/src/plugins/files/tsconfig.json +++ b/src/plugins/files/tsconfig.json @@ -32,6 +32,7 @@ "@kbn/core-elasticsearch-server-mocks", "@kbn/core-saved-objects-server-mocks", "@kbn/logging", + "@kbn/core-http-common", ], "exclude": [ "target/**/*", diff --git a/src/plugins/telemetry/common/routes.ts b/src/plugins/telemetry/common/routes.ts index 06d6f746bf2c..ce4db9c87b5e 100644 --- a/src/plugins/telemetry/common/routes.ts +++ b/src/plugins/telemetry/common/routes.ts @@ -6,7 +6,49 @@ * Side Public License, v 1. */ +const BASE_INTERNAL_PATH = '/internal/telemetry'; + +export const INTERNAL_VERSION = { version: '2' }; + /** * Fetch Telemetry Config + * @public Kept public and path-based because we know other Elastic products fetch the opt-in status via this endpoint. */ -export const FetchTelemetryConfigRoute = '/api/telemetry/v2/config'; +export const FetchTelemetryConfigRoutePathBasedV2 = '/api/telemetry/v2/config'; + +/** + * Fetch Telemetry Config + * @internal + */ +export const FetchTelemetryConfigRoute = `${BASE_INTERNAL_PATH}/config`; + +/** + * GET/PUT Last reported date for Snapshot telemetry + * @internal + */ +export const LastReportedRoute = `${BASE_INTERNAL_PATH}/last_reported`; + +/** + * Set user has seen notice + * @internal + */ +export const UserHasSeenNoticeRoute = `${BASE_INTERNAL_PATH}/userHasSeenNotice`; + +/** + * Set opt-in/out status + * @internal + */ +export const OptInRoute = `${BASE_INTERNAL_PATH}/optIn`; + +/** + * Fetch the Snapshot telemetry report + * @internal + */ +export const FetchSnapshotTelemetry = `${BASE_INTERNAL_PATH}/clusters/_stats`; + +/** + * Get Opt-in stats + * @internal + * @deprecated + */ +export const GetOptInStatsRoutePathBasedV2 = '/api/telemetry/v2/clusters/_opt_in_stats'; diff --git a/src/plugins/telemetry/common/types/index.ts b/src/plugins/telemetry/common/types/index.ts index 14b2d3cbefcf..f03d8f821b1e 100644 --- a/src/plugins/telemetry/common/types/index.ts +++ b/src/plugins/telemetry/common/types/index.ts @@ -8,4 +8,5 @@ export * from './latest'; +export * as v1 from './v2'; // Just so v1 can also be used (but for some reason telemetry endpoints have always been v2 :shrug:) export * as v2 from './v2'; diff --git a/src/plugins/telemetry/public/plugin.ts b/src/plugins/telemetry/public/plugin.ts index cf879472a2bb..c0d0faf0819b 100644 --- a/src/plugins/telemetry/public/plugin.ts +++ b/src/plugins/telemetry/public/plugin.ts @@ -24,7 +24,7 @@ import type { HomePublicPluginSetup } from '@kbn/home-plugin/public'; import { ElasticV3BrowserShipper } from '@kbn/analytics-shippers-elastic-v3-browser'; import { of } from 'rxjs'; -import { FetchTelemetryConfigRoute } from '../common/routes'; +import { FetchTelemetryConfigRoute, INTERNAL_VERSION } from '../common/routes'; import type { v2 } from '../common/types'; import { TelemetrySender, TelemetryService, TelemetryNotifications } from './services'; import { renderWelcomeTelemetryNotice } from './render_welcome_telemetry_notice'; @@ -329,7 +329,7 @@ export class TelemetryPlugin */ private async fetchUpdatedConfig(http: HttpStart | HttpSetup): Promise { const { allowChangingOptInStatus, optIn, sendUsageFrom, telemetryNotifyUserAboutOptInDefault } = - await http.get(FetchTelemetryConfigRoute); + await http.get(FetchTelemetryConfigRoute, INTERNAL_VERSION); return { ...this.config, diff --git a/src/plugins/telemetry/public/services/telemetry_service.test.ts b/src/plugins/telemetry/public/services/telemetry_service.test.ts index 4934495d57d8..d072d654ccea 100644 --- a/src/plugins/telemetry/public/services/telemetry_service.test.ts +++ b/src/plugins/telemetry/public/services/telemetry_service.test.ts @@ -10,6 +10,12 @@ /* eslint-disable dot-notation */ import { mockTelemetryService } from '../mocks'; +import { + FetchSnapshotTelemetry, + INTERNAL_VERSION, + OptInRoute, + UserHasSeenNoticeRoute, +} from '../../common/routes'; describe('TelemetryService', () => { describe('fetchTelemetry', () => { @@ -17,7 +23,8 @@ describe('TelemetryService', () => { const telemetryService = mockTelemetryService(); await telemetryService.fetchTelemetry(); - expect(telemetryService['http'].post).toBeCalledWith('/api/telemetry/v2/clusters/_stats', { + expect(telemetryService['http'].post).toBeCalledWith(FetchSnapshotTelemetry, { + ...INTERNAL_VERSION, body: JSON.stringify({ unencrypted: false, refreshCache: false }), }); }); @@ -64,7 +71,8 @@ describe('TelemetryService', () => { const optedIn = true; await telemetryService.setOptIn(optedIn); - expect(telemetryService['http'].post).toBeCalledWith('/api/telemetry/v2/optIn', { + expect(telemetryService['http'].post).toBeCalledWith(OptInRoute, { + ...INTERNAL_VERSION, body: JSON.stringify({ enabled: optedIn }), }); }); @@ -77,7 +85,8 @@ describe('TelemetryService', () => { const optedIn = false; await telemetryService.setOptIn(optedIn); - expect(telemetryService['http'].post).toBeCalledWith('/api/telemetry/v2/optIn', { + expect(telemetryService['http'].post).toBeCalledWith(OptInRoute, { + ...INTERNAL_VERSION, body: JSON.stringify({ enabled: optedIn }), }); }); @@ -110,7 +119,7 @@ describe('TelemetryService', () => { config: { allowChangingOptInStatus: true }, }); telemetryService['http'].post = jest.fn().mockImplementation((url: string) => { - if (url === '/api/telemetry/v2/optIn') { + if (url === OptInRoute) { throw Error('failed to update opt in.'); } }); @@ -203,7 +212,7 @@ describe('TelemetryService', () => { }); telemetryService['http'].put = jest.fn().mockImplementation((url: string) => { - if (url === '/api/telemetry/v2/userHasSeenNotice') { + if (url === UserHasSeenNoticeRoute) { throw Error('failed to update opt in.'); } }); diff --git a/src/plugins/telemetry/public/services/telemetry_service.ts b/src/plugins/telemetry/public/services/telemetry_service.ts index 0629630fea48..ec67a4e675e2 100644 --- a/src/plugins/telemetry/public/services/telemetry_service.ts +++ b/src/plugins/telemetry/public/services/telemetry_service.ts @@ -8,11 +8,19 @@ import { i18n } from '@kbn/i18n'; import type { CoreSetup, CoreStart } from '@kbn/core/public'; +import { + LastReportedRoute, + INTERNAL_VERSION, + OptInRoute, + FetchSnapshotTelemetry, + UserHasSeenNoticeRoute, +} from '../../common/routes'; import type { TelemetryPluginConfig } from '../plugin'; -import { getTelemetryChannelEndpoint } from '../../common/telemetry_config/get_telemetry_channel_endpoint'; +import { getTelemetryChannelEndpoint } from '../../common/telemetry_config'; import type { UnencryptedTelemetryPayload, EncryptedTelemetryPayload, + FetchLastReportedResponse, } from '../../common/types/latest'; import { PAYLOAD_CONTENT_ENCODING } from '../../common/constants'; @@ -93,8 +101,7 @@ export class TelemetryService { /** Is the cluster allowed to change the opt-in/out status **/ public getCanChangeOptInStatus = () => { - const allowChangingOptInStatus = this.config.allowChangingOptInStatus; - return allowChangingOptInStatus; + return this.config.allowChangingOptInStatus; }; /** Retrieve the opt-in/out notification URL **/ @@ -156,17 +163,18 @@ export class TelemetryService { }; public fetchLastReported = async (): Promise => { - const response = await this.http.get<{ lastReported?: number }>( - '/api/telemetry/v2/last_reported' + const response = await this.http.get( + LastReportedRoute, + INTERNAL_VERSION ); return response?.lastReported; }; public updateLastReported = async (): Promise => { - return this.http.put('/api/telemetry/v2/last_reported'); + return this.http.put(LastReportedRoute); }; - /** Fetches an unencrypted telemetry payload so we can show it to the user **/ + /** Fetches an unencrypted telemetry payload, so we can show it to the user **/ public fetchExample = async (): Promise => { return await this.fetchTelemetry({ unencrypted: true, refreshCache: true }); }; @@ -174,12 +182,14 @@ export class TelemetryService { /** * Fetches telemetry payload * @param unencrypted Default `false`. Whether the returned payload should be encrypted or not. + * @param refreshCache Default `false`. Set to `true` to force the regeneration of the telemetry report. */ public fetchTelemetry = async ({ unencrypted = false, refreshCache = false, } = {}): Promise => { - return this.http.post('/api/telemetry/v2/clusters/_stats', { + return this.http.post(FetchSnapshotTelemetry, { + ...INTERNAL_VERSION, body: JSON.stringify({ unencrypted, refreshCache }), }); }; @@ -198,12 +208,10 @@ export class TelemetryService { try { // Report the option to the Kibana server to store the settings. // It returns the encrypted update to send to the telemetry cluster [{cluster_uuid, opt_in_status}] - const optInStatusPayload = await this.http.post( - '/api/telemetry/v2/optIn', - { - body: JSON.stringify({ enabled: optedIn }), - } - ); + const optInStatusPayload = await this.http.post(OptInRoute, { + ...INTERNAL_VERSION, + body: JSON.stringify({ enabled: optedIn }), + }); if (this.reportOptInStatusChange) { // Use the response to report about the change to the remote telemetry cluster. // If it's opt-out, this will be the last communication to the remote service. @@ -231,7 +239,7 @@ export class TelemetryService { */ public setUserHasSeenNotice = async (): Promise => { try { - await this.http.put('/api/telemetry/v2/userHasSeenNotice'); + await this.http.put(UserHasSeenNoticeRoute, INTERNAL_VERSION); this.userHasSeenOptedInNotice = true; } catch (error) { this.notifications.toasts.addError(error, { @@ -248,7 +256,7 @@ export class TelemetryService { /** * Pushes the encrypted payload [{cluster_uuid, opt_in_status}] to the remote telemetry service - * @param optInPayload [{cluster_uuid, opt_in_status}] encrypted by the server into an array of strings + * @param optInStatusPayload [{cluster_uuid, opt_in_status}] encrypted by the server into an array of strings */ private reportOptInStatus = async ( optInStatusPayload: EncryptedTelemetryPayload diff --git a/src/plugins/telemetry/server/routes/telemetry_config.ts b/src/plugins/telemetry/server/routes/telemetry_config.ts index 60a34d80aad2..37daef537b56 100644 --- a/src/plugins/telemetry/server/routes/telemetry_config.ts +++ b/src/plugins/telemetry/server/routes/telemetry_config.ts @@ -8,9 +8,14 @@ import { type Observable, firstValueFrom } from 'rxjs'; import type { IRouter, SavedObjectsClient } from '@kbn/core/server'; +import { schema } from '@kbn/config-schema'; +import { RequestHandler } from '@kbn/core-http-server'; import type { TelemetryConfigType } from '../config'; import { v2 } from '../../common/types'; -import { FetchTelemetryConfigRoute } from '../../common/routes'; +import { + FetchTelemetryConfigRoutePathBasedV2, + FetchTelemetryConfigRoute, +} from '../../common/routes'; import { getTelemetrySavedObject } from '../saved_objects'; import { getNotifyUserAboutOptInDefault, @@ -25,54 +30,74 @@ interface RegisterTelemetryConfigRouteOptions { currentKibanaVersion: string; savedObjectsInternalClient$: Observable; } + export function registerTelemetryConfigRoutes({ router, config$, currentKibanaVersion, savedObjectsInternalClient$, }: RegisterTelemetryConfigRouteOptions) { - // GET to retrieve - router.get( - { - path: FetchTelemetryConfigRoute, - validate: false, + const v2Handler: RequestHandler = async (context, req, res) => { + const config = await firstValueFrom(config$); + const savedObjectsInternalClient = await firstValueFrom(savedObjectsInternalClient$); + const telemetrySavedObject = await getTelemetrySavedObject(savedObjectsInternalClient); + const allowChangingOptInStatus = getTelemetryAllowChangingOptInStatus({ + configTelemetryAllowChangingOptInStatus: config.allowChangingOptInStatus, + telemetrySavedObject, + }); + + const optIn = getTelemetryOptIn({ + configTelemetryOptIn: config.optIn, + allowChangingOptInStatus, + telemetrySavedObject, + currentKibanaVersion, + }); + + const sendUsageFrom = getTelemetrySendUsageFrom({ + configTelemetrySendUsageFrom: config.sendUsageFrom, + telemetrySavedObject, + }); + + const telemetryNotifyUserAboutOptInDefault = getNotifyUserAboutOptInDefault({ + telemetrySavedObject, + allowChangingOptInStatus, + configTelemetryOptIn: config.optIn, + telemetryOptedIn: optIn, + }); + + const body: v2.FetchTelemetryConfigResponse = { + allowChangingOptInStatus, + optIn, + sendUsageFrom, + telemetryNotifyUserAboutOptInDefault, + }; + + return res.ok({ body }); + }; + + const v2Validations = { + response: { + 200: { + body: schema.object({ + allowChangingOptInStatus: schema.boolean(), + optIn: schema.oneOf([schema.boolean(), schema.literal(null)]), + sendUsageFrom: schema.oneOf([schema.literal('server'), schema.literal('browser')]), + telemetryNotifyUserAboutOptInDefault: schema.boolean(), + }), + }, }, - async (context, req, res) => { - const config = await firstValueFrom(config$); - const savedObjectsInternalClient = await firstValueFrom(savedObjectsInternalClient$); - const telemetrySavedObject = await getTelemetrySavedObject(savedObjectsInternalClient); - const allowChangingOptInStatus = getTelemetryAllowChangingOptInStatus({ - configTelemetryAllowChangingOptInStatus: config.allowChangingOptInStatus, - telemetrySavedObject, - }); + }; - const optIn = getTelemetryOptIn({ - configTelemetryOptIn: config.optIn, - allowChangingOptInStatus, - telemetrySavedObject, - currentKibanaVersion, - }); + // Register the internal versioned API + router.versioned + .get({ access: 'internal', path: FetchTelemetryConfigRoute }) + // Just because it used to be /v2/, we are creating identical v1 and v2. + .addVersion({ version: '1', validate: v2Validations }, v2Handler) + .addVersion({ version: '2', validate: v2Validations }, v2Handler); - const sendUsageFrom = getTelemetrySendUsageFrom({ - configTelemetrySendUsageFrom: config.sendUsageFrom, - telemetrySavedObject, - }); - - const telemetryNotifyUserAboutOptInDefault = getNotifyUserAboutOptInDefault({ - telemetrySavedObject, - allowChangingOptInStatus, - configTelemetryOptIn: config.optIn, - telemetryOptedIn: optIn, - }); - - const body: v2.FetchTelemetryConfigResponse = { - allowChangingOptInStatus, - optIn, - sendUsageFrom, - telemetryNotifyUserAboutOptInDefault, - }; - - return res.ok({ body }); - } - ); + // Register the deprecated public and path-based for BWC + // as we know this one is used by other Elastic products to fetch the opt-in status. + router.versioned + .get({ access: 'public', path: FetchTelemetryConfigRoutePathBasedV2 }) + .addVersion({ version: '2023-10-31', validate: v2Validations }, v2Handler); } diff --git a/src/plugins/telemetry/server/routes/telemetry_last_reported.ts b/src/plugins/telemetry/server/routes/telemetry_last_reported.ts index 2e21785b9296..23c15521d870 100644 --- a/src/plugins/telemetry/server/routes/telemetry_last_reported.ts +++ b/src/plugins/telemetry/server/routes/telemetry_last_reported.ts @@ -6,9 +6,12 @@ * Side Public License, v 1. */ +import { schema } from '@kbn/config-schema'; import type { IRouter, SavedObjectsClient } from '@kbn/core/server'; import type { Observable } from 'rxjs'; import { firstValueFrom } from 'rxjs'; +import { RequestHandler } from '@kbn/core-http-server'; +import { LastReportedRoute } from '../../common/routes'; import { v2 } from '../../common/types'; import { getTelemetrySavedObject, updateTelemetrySavedObject } from '../saved_objects'; @@ -17,38 +20,38 @@ export function registerTelemetryLastReported( savedObjectsInternalClient$: Observable ) { // GET to retrieve - router.get( - { - path: '/api/telemetry/v2/last_reported', - validate: false, - }, - async (context, req, res) => { - const savedObjectsInternalClient = await firstValueFrom(savedObjectsInternalClient$); - const telemetrySavedObject = await getTelemetrySavedObject(savedObjectsInternalClient); + const v2GetValidations = { + response: { 200: { body: schema.object({ lastReported: schema.maybe(schema.number()) }) } }, + }; - const body: v2.FetchLastReportedResponse = { - lastReported: telemetrySavedObject && telemetrySavedObject?.lastReported, - }; + const v2GetHandler: RequestHandler = async (context, req, res) => { + const savedObjectsInternalClient = await firstValueFrom(savedObjectsInternalClient$); + const telemetrySavedObject = await getTelemetrySavedObject(savedObjectsInternalClient); - return res.ok({ - body, - }); - } - ); + const body: v2.FetchLastReportedResponse = { + lastReported: telemetrySavedObject && telemetrySavedObject?.lastReported, + }; + return res.ok({ body }); + }; + + router.versioned + .get({ access: 'internal', path: LastReportedRoute }) + // Just because it used to be /v2/, we are creating identical v1 and v2. + .addVersion({ version: '1', validate: v2GetValidations }, v2GetHandler) + .addVersion({ version: '2', validate: v2GetValidations }, v2GetHandler); // PUT to update - router.put( - { - path: '/api/telemetry/v2/last_reported', - validate: false, - }, - async (context, req, res) => { - const savedObjectsInternalClient = await firstValueFrom(savedObjectsInternalClient$); - await updateTelemetrySavedObject(savedObjectsInternalClient, { - lastReported: Date.now(), - }); + const v2PutHandler: RequestHandler = async (context, req, res) => { + const savedObjectsInternalClient = await firstValueFrom(savedObjectsInternalClient$); + await updateTelemetrySavedObject(savedObjectsInternalClient, { + lastReported: Date.now(), + }); + return res.ok(); + }; - return res.ok(); - } - ); + router.versioned + .put({ access: 'internal', path: LastReportedRoute }) + // Just because it used to be /v2/, we are creating identical v1 and v2. + .addVersion({ version: '1', validate: false }, v2PutHandler) + .addVersion({ version: '2', validate: false }, v2PutHandler); } diff --git a/src/plugins/telemetry/server/routes/telemetry_opt_in.ts b/src/plugins/telemetry/server/routes/telemetry_opt_in.ts index f523031e181e..689e0cd06fad 100644 --- a/src/plugins/telemetry/server/routes/telemetry_opt_in.ts +++ b/src/plugins/telemetry/server/routes/telemetry_opt_in.ts @@ -9,12 +9,14 @@ import { firstValueFrom, type Observable } from 'rxjs'; import { schema } from '@kbn/config-schema'; import type { IRouter, Logger } from '@kbn/core/server'; -import { SavedObjectsErrorHelpers } from '@kbn/core/server'; +import { RequestHandlerContext, SavedObjectsErrorHelpers } from '@kbn/core/server'; import type { StatsGetterConfig, TelemetryCollectionManagerPluginSetup, } from '@kbn/telemetry-collection-manager-plugin/server'; -import { v2 } from '../../common/types'; +import { RequestHandler } from '@kbn/core-http-server'; +import { OptInRoute } from '../../common/routes'; +import { OptInBody, v2 } from '../../common/types'; import { sendTelemetryOptInStatus } from './telemetry_opt_in_stats'; import { getTelemetrySavedObject, @@ -41,78 +43,91 @@ export function registerTelemetryOptInRoutes({ currentKibanaVersion, telemetryCollectionManager, }: RegisterOptInRoutesParams) { - router.post( - { - path: '/api/telemetry/v2/optIn', - validate: { - body: schema.object({ enabled: schema.boolean() }), + const v2Handler: RequestHandler = async ( + context, + req, + res + ) => { + const newOptInStatus = req.body.enabled; + const soClient = (await context.core).savedObjects.getClient({ + includedHiddenTypes: [TELEMETRY_SAVED_OBJECT_TYPE], + }); + const attributes: TelemetrySavedObject = { + enabled: newOptInStatus, + lastVersionChecked: currentKibanaVersion, + }; + const config = await firstValueFrom(config$); + + let telemetrySavedObject: TelemetrySavedObject | undefined; + try { + telemetrySavedObject = await getTelemetrySavedObject(soClient); + } catch (err) { + if (SavedObjectsErrorHelpers.isForbiddenError(err)) { + // If we couldn't get the saved object due to lack of permissions, + // we can assume the user won't be able to update it either + return res.forbidden(); + } + } + + const allowChangingOptInStatus = getTelemetryAllowChangingOptInStatus({ + configTelemetryAllowChangingOptInStatus: config.allowChangingOptInStatus, + telemetrySavedObject, + }); + if (!allowChangingOptInStatus) { + return res.badRequest({ + body: JSON.stringify({ error: 'Not allowed to change Opt-in Status.' }), + }); + } + + const statsGetterConfig: StatsGetterConfig = { + unencrypted: false, + }; + + const optInStatus = await telemetryCollectionManager.getOptInStats( + newOptInStatus, + statsGetterConfig + ); + + if (config.sendUsageFrom === 'server') { + const { appendServerlessChannelsSuffix, sendUsageTo } = config; + sendTelemetryOptInStatus( + telemetryCollectionManager, + { appendServerlessChannelsSuffix, sendUsageTo, newOptInStatus, currentKibanaVersion }, + statsGetterConfig + ).catch((err) => { + // The server is likely behind a firewall and can't reach the remote service + logger.warn( + `Failed to notify the telemetry endpoint about the opt-in selection. Possibly blocked by a firewall? - Error: ${err.message}` + ); + }); + } + + try { + await updateTelemetrySavedObject(soClient, attributes); + } catch (e) { + if (SavedObjectsErrorHelpers.isForbiddenError(e)) { + return res.forbidden(); + } + } + + const body: v2.OptInResponse = optInStatus; + return res.ok({ body }); + }; + + const v2Validations = { + request: { body: schema.object({ enabled: schema.boolean() }) }, + response: { + 200: { + body: schema.arrayOf( + schema.object({ clusterUuid: schema.string(), stats: schema.string() }) + ), }, }, - async (context, req, res) => { - const newOptInStatus = req.body.enabled; - const soClient = (await context.core).savedObjects.getClient({ - includedHiddenTypes: [TELEMETRY_SAVED_OBJECT_TYPE], - }); - const attributes: TelemetrySavedObject = { - enabled: newOptInStatus, - lastVersionChecked: currentKibanaVersion, - }; - const config = await firstValueFrom(config$); + }; - let telemetrySavedObject: TelemetrySavedObject | undefined; - try { - telemetrySavedObject = await getTelemetrySavedObject(soClient); - } catch (err) { - if (SavedObjectsErrorHelpers.isForbiddenError(err)) { - // If we couldn't get the saved object due to lack of permissions, - // we can assume the user won't be able to update it either - return res.forbidden(); - } - } - - const allowChangingOptInStatus = getTelemetryAllowChangingOptInStatus({ - configTelemetryAllowChangingOptInStatus: config.allowChangingOptInStatus, - telemetrySavedObject, - }); - if (!allowChangingOptInStatus) { - return res.badRequest({ - body: JSON.stringify({ error: 'Not allowed to change Opt-in Status.' }), - }); - } - - const statsGetterConfig: StatsGetterConfig = { - unencrypted: false, - }; - - const optInStatus = await telemetryCollectionManager.getOptInStats( - newOptInStatus, - statsGetterConfig - ); - - if (config.sendUsageFrom === 'server') { - const { appendServerlessChannelsSuffix, sendUsageTo } = config; - sendTelemetryOptInStatus( - telemetryCollectionManager, - { appendServerlessChannelsSuffix, sendUsageTo, newOptInStatus, currentKibanaVersion }, - statsGetterConfig - ).catch((err) => { - // The server is likely behind a firewall and can't reach the remote service - logger.warn( - `Failed to notify the telemetry endpoint about the opt-in selection. Possibly blocked by a firewall? - Error: ${err.message}` - ); - }); - } - - try { - await updateTelemetrySavedObject(soClient, attributes); - } catch (e) { - if (SavedObjectsErrorHelpers.isForbiddenError(e)) { - return res.forbidden(); - } - } - - const body: v2.OptInResponse = optInStatus; - return res.ok({ body }); - } - ); + router.versioned + .post({ access: 'internal', path: OptInRoute }) + // Just because it used to be /v2/, we are creating identical v1 and v2. + .addVersion({ version: '1', validate: v2Validations }, v2Handler) + .addVersion({ version: '2', validate: v2Validations }, v2Handler); } diff --git a/src/plugins/telemetry/server/routes/telemetry_opt_in_stats.ts b/src/plugins/telemetry/server/routes/telemetry_opt_in_stats.ts index f715c84fc934..378dbdcc9e49 100644 --- a/src/plugins/telemetry/server/routes/telemetry_opt_in_stats.ts +++ b/src/plugins/telemetry/server/routes/telemetry_opt_in_stats.ts @@ -14,6 +14,7 @@ import type { TelemetryCollectionManagerPluginSetup, StatsGetterConfig, } from '@kbn/telemetry-collection-manager-plugin/server'; +import { GetOptInStatsRoutePathBasedV2 } from '../../common/routes'; import type { v2 } from '../../common/types'; import { EncryptedTelemetryPayload, UnencryptedTelemetryPayload } from '../../common/types'; import { getTelemetryChannelEndpoint } from '../../common/telemetry_config'; @@ -62,43 +63,64 @@ export function registerTelemetryOptInStatsRoutes( router: IRouter, telemetryCollectionManager: TelemetryCollectionManagerPluginSetup ) { - router.post( - { - path: '/api/telemetry/v2/clusters/_opt_in_stats', - validate: { - body: schema.object({ - enabled: schema.boolean(), - unencrypted: schema.boolean({ defaultValue: true }), - }), + router.versioned + .post({ + access: 'public', // It's not used across Kibana, and I didn't want to remove it in this PR just in case. + path: GetOptInStatsRoutePathBasedV2, + }) + .addVersion( + { + version: '2023-10-31', + validate: { + request: { + body: schema.object({ + enabled: schema.boolean(), + unencrypted: schema.boolean({ defaultValue: true }), + }), + }, + response: { + 200: { + body: schema.arrayOf( + schema.object({ + clusterUuid: schema.string(), + stats: schema.object({ + cluster_uuid: schema.string(), + opt_in_status: schema.boolean(), + }), + }) + ), + }, + 503: { body: schema.string() }, + }, + }, }, - }, - async (context, req, res) => { - try { - const newOptInStatus = req.body.enabled; - const unencrypted = req.body.unencrypted; + async (context, req, res) => { + try { + const newOptInStatus = req.body.enabled; + const unencrypted = req.body.unencrypted; - if (!(await telemetryCollectionManager.shouldGetTelemetry())) { - // We probably won't reach here because there is a license check in the auth phase of the HTTP requests. - // But let's keep it here should that changes at any point. - return res.customError({ - statusCode: 503, - body: `Can't fetch telemetry at the moment because some services are down. Check the /status page for more details.`, - }); + if (!(await telemetryCollectionManager.shouldGetTelemetry())) { + // We probably won't reach here because there is a license check in the auth phase of the HTTP requests. + // But let's keep it here should that changes at any point. + return res.customError({ + statusCode: 503, + body: `Can't fetch telemetry at the moment because some services are down. Check the /status page for more details.`, + }); + } + + const statsGetterConfig: StatsGetterConfig = { + unencrypted, + }; + + const optInStatus = await telemetryCollectionManager.getOptInStats( + newOptInStatus, + statsGetterConfig + ); + const body: v2.OptInStatsResponse = optInStatus; + return res.ok({ body }); + } catch (err) { + return res.ok({ body: [] }); } - - const statsGetterConfig: StatsGetterConfig = { - unencrypted, - }; - - const optInStatus = await telemetryCollectionManager.getOptInStats( - newOptInStatus, - statsGetterConfig - ); - const body: v2.OptInStatsResponse = optInStatus; - return res.ok({ body }); - } catch (err) { - return res.ok({ body: [] }); } - } - ); + ); } diff --git a/src/plugins/telemetry/server/routes/telemetry_usage_stats.test.ts b/src/plugins/telemetry/server/routes/telemetry_usage_stats.test.ts index 152aaa4c9eed..4cbb1381c056 100644 --- a/src/plugins/telemetry/server/routes/telemetry_usage_stats.test.ts +++ b/src/plugins/telemetry/server/routes/telemetry_usage_stats.test.ts @@ -16,8 +16,9 @@ async function runRequest( mockRouter: IRouter, body?: { unencrypted?: boolean; refreshCache?: boolean } ) { - expect(mockRouter.post).toBeCalled(); - const [, handler] = (mockRouter.post as jest.Mock).mock.calls[0]; + expect(mockRouter.versioned.post).toBeCalled(); + const [, handler] = (mockRouter.versioned.post as jest.Mock).mock.results[0].value.addVersion.mock + .calls[0]; const mockResponse = httpServerMock.createResponseFactory(); const mockRequest = httpServerMock.createKibanaRequest({ body }); await handler(null, mockRequest, mockResponse); @@ -49,10 +50,10 @@ describe('registerTelemetryUsageStatsRoutes', () => { describe('clusters/_stats POST route', () => { it('registers _stats POST route and accepts body configs', () => { registerTelemetryUsageStatsRoutes(mockRouter, telemetryCollectionManager, true, getSecurity); - expect(mockRouter.post).toBeCalledTimes(1); - const [routeConfig, handler] = (mockRouter.post as jest.Mock).mock.calls[0]; - expect(routeConfig.path).toMatchInlineSnapshot(`"/api/telemetry/v2/clusters/_stats"`); - expect(Object.keys(routeConfig.validate.body.props)).toEqual(['unencrypted', 'refreshCache']); + expect(mockRouter.versioned.post).toBeCalledTimes(1); + const [routeConfig, handler] = (mockRouter.versioned.post as jest.Mock).mock.results[0].value + .addVersion.mock.calls[0]; + expect(routeConfig.version).toMatchInlineSnapshot(`"1"`); expect(handler).toBeInstanceOf(Function); }); diff --git a/src/plugins/telemetry/server/routes/telemetry_usage_stats.ts b/src/plugins/telemetry/server/routes/telemetry_usage_stats.ts index 600c6d3ef2c7..a828de911c29 100644 --- a/src/plugins/telemetry/server/routes/telemetry_usage_stats.ts +++ b/src/plugins/telemetry/server/routes/telemetry_usage_stats.ts @@ -13,7 +13,9 @@ import type { StatsGetterConfig, } from '@kbn/telemetry-collection-manager-plugin/server'; import type { SecurityPluginStart } from '@kbn/security-plugin/server'; -import { v2 } from '../../common/types'; +import { RequestHandler } from '@kbn/core-http-server'; +import { FetchSnapshotTelemetry } from '../../common/routes'; +import { UsageStatsBody, v2 } from '../../common/types'; export type SecurityGetter = () => SecurityPluginStart | undefined; @@ -23,64 +25,75 @@ export function registerTelemetryUsageStatsRoutes( isDev: boolean, getSecurity: SecurityGetter ) { - router.post( - { - path: '/api/telemetry/v2/clusters/_stats', - validate: { - body: schema.object({ - unencrypted: schema.boolean({ defaultValue: false }), - refreshCache: schema.boolean({ defaultValue: false }), - }), - }, - }, - async (context, req, res) => { - const { unencrypted, refreshCache } = req.body; + const v2Handler: RequestHandler = async ( + context, + req, + res + ) => { + const { unencrypted, refreshCache } = req.body; - if (!(await telemetryCollectionManager.shouldGetTelemetry())) { - // We probably won't reach here because there is a license check in the auth phase of the HTTP requests. - // But let's keep it here should that changes at any point. - return res.customError({ - statusCode: 503, - body: `Can't fetch telemetry at the moment because some services are down. Check the /status page for more details.`, - }); - } + if (!(await telemetryCollectionManager.shouldGetTelemetry())) { + // We probably won't reach here because there is a license check in the auth phase of the HTTP requests. + // But let's keep it here should that changes at any point. + return res.customError({ + statusCode: 503, + body: `Can't fetch telemetry at the moment because some services are down. Check the /status page for more details.`, + }); + } - const security = getSecurity(); - // We need to check useRbacForRequest to figure out if ES has security enabled before making the privileges check - if (security && unencrypted && security.authz.mode.useRbacForRequest(req)) { - // Normally we would use `options: { tags: ['access:decryptedTelemetry'] }` in the route definition to check authorization for an - // API action, however, we want to check this conditionally based on the `unencrypted` parameter. In this case we need to use the - // security API directly to check privileges for this action. Note that the 'decryptedTelemetry' API privilege string is only - // granted to users that have "Global All" or "Global Read" privileges in Kibana. - const { checkPrivilegesWithRequest, actions } = security.authz; - const privileges = { kibana: actions.api.get('decryptedTelemetry') }; - const { hasAllRequested } = await checkPrivilegesWithRequest(req).globally(privileges); - if (!hasAllRequested) { - return res.forbidden(); - } - } - - try { - const statsConfig: StatsGetterConfig = { - unencrypted, - refreshCache: unencrypted || refreshCache, - }; - - const body: v2.UnencryptedTelemetryPayload = await telemetryCollectionManager.getStats( - statsConfig - ); - return res.ok({ body }); - } catch (err) { - if (isDev) { - // don't ignore errors when running in dev mode - throw err; - } - if (unencrypted && err.status === 403) { - return res.forbidden(); - } - // ignore errors and return empty set - return res.ok({ body: [] }); + const security = getSecurity(); + // We need to check useRbacForRequest to figure out if ES has security enabled before making the privileges check + if (security && unencrypted && security.authz.mode.useRbacForRequest(req)) { + // Normally we would use `options: { tags: ['access:decryptedTelemetry'] }` in the route definition to check authorization for an + // API action, however, we want to check this conditionally based on the `unencrypted` parameter. In this case we need to use the + // security API directly to check privileges for this action. Note that the 'decryptedTelemetry' API privilege string is only + // granted to users that have "Global All" or "Global Read" privileges in Kibana. + const { checkPrivilegesWithRequest, actions } = security.authz; + const privileges = { kibana: actions.api.get('decryptedTelemetry') }; + const { hasAllRequested } = await checkPrivilegesWithRequest(req).globally(privileges); + if (!hasAllRequested) { + return res.forbidden(); } } - ); + + try { + const statsConfig: StatsGetterConfig = { + unencrypted, + refreshCache: unencrypted || refreshCache, + }; + + const body: v2.UnencryptedTelemetryPayload = await telemetryCollectionManager.getStats( + statsConfig + ); + return res.ok({ body }); + } catch (err) { + if (isDev) { + // don't ignore errors when running in dev mode + throw err; + } + if (unencrypted && err.status === 403) { + return res.forbidden(); + } + // ignore errors and return empty set + return res.ok({ body: [] }); + } + }; + + const v2Validations = { + request: { + body: schema.object({ + unencrypted: schema.boolean({ defaultValue: false }), + refreshCache: schema.boolean({ defaultValue: false }), + }), + }, + }; + + router.versioned + .post({ + access: 'internal', + path: FetchSnapshotTelemetry, + }) + // Just because it used to be /v2/, we are creating identical v1 and v2. + .addVersion({ version: '1', validate: v2Validations }, v2Handler) + .addVersion({ version: '2', validate: v2Validations }, v2Handler); } diff --git a/src/plugins/telemetry/server/routes/telemetry_user_has_seen_notice.ts b/src/plugins/telemetry/server/routes/telemetry_user_has_seen_notice.ts index d9cb0b981b0a..b59ada443054 100644 --- a/src/plugins/telemetry/server/routes/telemetry_user_has_seen_notice.ts +++ b/src/plugins/telemetry/server/routes/telemetry_user_has_seen_notice.ts @@ -7,6 +7,9 @@ */ import type { IRouter } from '@kbn/core/server'; +import { RequestHandler } from '@kbn/core-http-server'; +import { RequestHandlerContext } from '@kbn/core/server'; +import { UserHasSeenNoticeRoute } from '../../common/routes'; import { TELEMETRY_SAVED_OBJECT_TYPE } from '../saved_objects'; import { v2 } from '../../common/types'; import { @@ -16,38 +19,42 @@ import { } from '../saved_objects'; export function registerTelemetryUserHasSeenNotice(router: IRouter, currentKibanaVersion: string) { - router.put( - { - path: '/api/telemetry/v2/userHasSeenNotice', - validate: false, - }, - async (context, req, res) => { - const soClient = (await context.core).savedObjects.getClient({ - includedHiddenTypes: [TELEMETRY_SAVED_OBJECT_TYPE], - }); - const telemetrySavedObject = await getTelemetrySavedObject(soClient); + const v2Handler: RequestHandler = async ( + context, + req, + res + ) => { + const soClient = (await context.core).savedObjects.getClient({ + includedHiddenTypes: [TELEMETRY_SAVED_OBJECT_TYPE], + }); + const telemetrySavedObject = await getTelemetrySavedObject(soClient); - // update the object with a flag stating that the opt-in notice has been seen - const updatedAttributes: TelemetrySavedObjectAttributes = { - ...telemetrySavedObject, - userHasSeenNotice: true, - // We need to store that the user was notified in this version. - // Otherwise, it'll continuously show the banner if previously opted-out. - lastVersionChecked: currentKibanaVersion, - }; - await updateTelemetrySavedObject(soClient, updatedAttributes); + // update the object with a flag stating that the opt-in notice has been seen + const updatedAttributes: TelemetrySavedObjectAttributes = { + ...telemetrySavedObject, + userHasSeenNotice: true, + // We need to store that the user was notified in this version. + // Otherwise, it'll continuously show the banner if previously opted-out. + lastVersionChecked: currentKibanaVersion, + }; + await updateTelemetrySavedObject(soClient, updatedAttributes); - const body: v2.Telemetry = { - allowChangingOptInStatus: updatedAttributes.allowChangingOptInStatus, - enabled: updatedAttributes.enabled, - lastReported: updatedAttributes.lastReported, - lastVersionChecked: updatedAttributes.lastVersionChecked, - reportFailureCount: updatedAttributes.reportFailureCount, - reportFailureVersion: updatedAttributes.reportFailureVersion, - sendUsageFrom: updatedAttributes.sendUsageFrom, - userHasSeenNotice: updatedAttributes.userHasSeenNotice, - }; - return res.ok({ body }); - } - ); + const body: v2.Telemetry = { + allowChangingOptInStatus: updatedAttributes.allowChangingOptInStatus, + enabled: updatedAttributes.enabled, + lastReported: updatedAttributes.lastReported, + lastVersionChecked: updatedAttributes.lastVersionChecked, + reportFailureCount: updatedAttributes.reportFailureCount, + reportFailureVersion: updatedAttributes.reportFailureVersion, + sendUsageFrom: updatedAttributes.sendUsageFrom, + userHasSeenNotice: updatedAttributes.userHasSeenNotice, + }; + return res.ok({ body }); + }; + + router.versioned + .put({ access: 'internal', path: UserHasSeenNoticeRoute }) + // Just because it used to be /v2/, we are creating identical v1 and v2. + .addVersion({ version: '1', validate: false }, v2Handler) + .addVersion({ version: '2', validate: false }, v2Handler); } diff --git a/src/plugins/telemetry/tsconfig.json b/src/plugins/telemetry/tsconfig.json index 7da7e89bae02..638bfb4f722a 100644 --- a/src/plugins/telemetry/tsconfig.json +++ b/src/plugins/telemetry/tsconfig.json @@ -34,6 +34,7 @@ "@kbn/std", "@kbn/core-http-browser-mocks", "@kbn/core-http-browser", + "@kbn/core-http-server", ], "exclude": [ "target/**/*", diff --git a/test/api_integration/apis/telemetry/opt_in.ts b/test/api_integration/apis/telemetry/opt_in.ts index 943d7534acc0..c7d8a42c6e39 100644 --- a/test/api_integration/apis/telemetry/opt_in.ts +++ b/test/api_integration/apis/telemetry/opt_in.ts @@ -11,6 +11,10 @@ import expect from '@kbn/expect'; import SuperTest from 'supertest'; import type { KbnClient } from '@kbn/test'; import type { TelemetrySavedObjectAttributes } from '@kbn/telemetry-plugin/server/saved_objects'; +import { + ELASTIC_HTTP_VERSION_HEADER, + X_ELASTIC_INTERNAL_ORIGIN_REQUEST, +} from '@kbn/core-http-common'; import type { FtrProviderContext } from '../../ftr_provider_context'; export default function optInTest({ getService }: FtrProviderContext) { @@ -18,7 +22,7 @@ export default function optInTest({ getService }: FtrProviderContext) { const kibanaServer = getService('kibanaServer'); const esArchiver = getService('esArchiver'); - describe('/api/telemetry/v2/optIn API', () => { + describe('/internal/telemetry/optIn API', () => { let defaultAttributes: TelemetrySavedObjectAttributes; let kibanaVersion: string; before(async () => { @@ -88,8 +92,10 @@ async function postTelemetryV2OptIn( statusCode: number ): Promise { const { body } = await supertest - .post('/api/telemetry/v2/optIn') + .post('/internal/telemetry/optIn') .set('kbn-xsrf', 'xxx') + .set(ELASTIC_HTTP_VERSION_HEADER, '2') + .set(X_ELASTIC_INTERNAL_ORIGIN_REQUEST, 'kibana') .send({ enabled: value }) .expect(statusCode); diff --git a/test/api_integration/apis/telemetry/telemetry_config.ts b/test/api_integration/apis/telemetry/telemetry_config.ts index f6dd12b0c2a9..a9a04a3986ba 100644 --- a/test/api_integration/apis/telemetry/telemetry_config.ts +++ b/test/api_integration/apis/telemetry/telemetry_config.ts @@ -7,6 +7,10 @@ */ import { AxiosError } from 'axios'; +import { + ELASTIC_HTTP_VERSION_HEADER, + X_ELASTIC_INTERNAL_ORIGIN_REQUEST, +} from '@kbn/core-http-common'; import type { FtrProviderContext } from '../../ftr_provider_context'; const TELEMETRY_SO_TYPE = 'telemetry'; @@ -16,110 +20,146 @@ export default function telemetryConfigTest({ getService }: FtrProviderContext) const kbnClient = getService('kibanaServer'); const supertest = getService('supertest'); - describe('/api/telemetry/v2/config API Telemetry config', () => { - before(async () => { - try { - await kbnClient.savedObjects.delete({ type: TELEMETRY_SO_TYPE, id: TELEMETRY_SO_ID }); - } catch (err) { - const is404Error = err instanceof AxiosError && err.response?.status === 404; - if (!is404Error) { - throw err; - } - } - }); - - it('GET should get the default config', async () => { - await supertest.get('/api/telemetry/v2/config').set('kbn-xsrf', 'xxx').expect(200, { - allowChangingOptInStatus: true, - optIn: null, // the config.js for this FTR sets it to `false`, we are bound to ask again. - sendUsageFrom: 'server', - telemetryNotifyUserAboutOptInDefault: false, // it's not opted-in by default (that's what this flag is about) - }); - }); - - it('GET should get `true` when opted-in', async () => { - // Opt-in - await supertest - .post('/api/telemetry/v2/optIn') - .set('kbn-xsrf', 'xxx') - .send({ enabled: true }) - .expect(200); - - await supertest.get('/api/telemetry/v2/config').set('kbn-xsrf', 'xxx').expect(200, { - allowChangingOptInStatus: true, - optIn: true, - sendUsageFrom: 'server', - telemetryNotifyUserAboutOptInDefault: false, - }); - }); - - it('GET should get false when opted-out', async () => { - // Opt-in - await supertest - .post('/api/telemetry/v2/optIn') - .set('kbn-xsrf', 'xxx') - .send({ enabled: false }) - .expect(200); - - await supertest.get('/api/telemetry/v2/config').set('kbn-xsrf', 'xxx').expect(200, { - allowChangingOptInStatus: true, - optIn: false, - sendUsageFrom: 'server', - telemetryNotifyUserAboutOptInDefault: false, - }); - }); - - describe('From a previous version', function () { - this.tags(['skipCloud']); - - // Get current values - let attributes: Record; - let currentVersion: string; - let previousMinor: string; - - before(async () => { - [{ attributes }, currentVersion] = await Promise.all([ - kbnClient.savedObjects.get({ type: TELEMETRY_SO_TYPE, id: TELEMETRY_SO_ID }), - kbnClient.version.get(), - ]); - - const [major, minor, patch] = currentVersion.match(/^(\d+)\.(\d+)\.(\d+)/)!.map(parseInt); - previousMinor = `${minor === 0 ? major - 1 : major}.${ - minor === 0 ? minor : minor - 1 - }.${patch}`; - }); - - it('GET should get `true` when opted-in in the current version', async () => { - // Opt-in from a previous version - await kbnClient.savedObjects.create({ - overwrite: true, - type: TELEMETRY_SO_TYPE, - id: TELEMETRY_SO_ID, - attributes: { ...attributes, enabled: true, lastVersionChecked: previousMinor }, + describe('API Telemetry config', () => { + ['/api/telemetry/v2/config', '/internal/telemetry/config'].forEach((api) => { + describe(`GET ${api}`, () => { + const apiVersion = api === '/api/telemetry/v2/config' ? '2023-10-31' : '2'; + before(async () => { + try { + await kbnClient.savedObjects.delete({ type: TELEMETRY_SO_TYPE, id: TELEMETRY_SO_ID }); + } catch (err) { + const is404Error = err instanceof AxiosError && err.response?.status === 404; + if (!is404Error) { + throw err; + } + } }); - await supertest.get('/api/telemetry/v2/config').set('kbn-xsrf', 'xxx').expect(200, { - allowChangingOptInStatus: true, - optIn: true, - sendUsageFrom: 'server', - telemetryNotifyUserAboutOptInDefault: false, - }); - }); - - it('GET should get `null` when opted-out in a previous version', async () => { - // Opt-out from previous version - await kbnClient.savedObjects.create({ - overwrite: true, - type: TELEMETRY_SO_TYPE, - id: TELEMETRY_SO_ID, - attributes: { ...attributes, enabled: false, lastVersionChecked: previousMinor }, + it('GET should get the default config', async () => { + await supertest + .get(api) + .set('kbn-xsrf', 'xxx') + .set(ELASTIC_HTTP_VERSION_HEADER, apiVersion) + .set(X_ELASTIC_INTERNAL_ORIGIN_REQUEST, 'kibana') + .expect(200, { + allowChangingOptInStatus: true, + optIn: null, // the config.js for this FTR sets it to `false`, we are bound to ask again. + sendUsageFrom: 'server', + telemetryNotifyUserAboutOptInDefault: false, // it's not opted-in by default (that's what this flag is about) + }); }); - await supertest.get('/api/telemetry/v2/config').set('kbn-xsrf', 'xxx').expect(200, { - allowChangingOptInStatus: true, - optIn: null, - sendUsageFrom: 'server', - telemetryNotifyUserAboutOptInDefault: false, + it('GET should get `true` when opted-in', async () => { + // Opt-in + await supertest + .post('/internal/telemetry/optIn') + .set('kbn-xsrf', 'xxx') + .set(ELASTIC_HTTP_VERSION_HEADER, '2') + .set(X_ELASTIC_INTERNAL_ORIGIN_REQUEST, 'kibana') + .send({ enabled: true }) + .expect(200); + + await supertest + .get(api) + .set('kbn-xsrf', 'xxx') + .set(ELASTIC_HTTP_VERSION_HEADER, apiVersion) + .set(X_ELASTIC_INTERNAL_ORIGIN_REQUEST, 'kibana') + .expect(200, { + allowChangingOptInStatus: true, + optIn: true, + sendUsageFrom: 'server', + telemetryNotifyUserAboutOptInDefault: false, + }); + }); + + it('GET should get false when opted-out', async () => { + // Opt-in + await supertest + .post('/internal/telemetry/optIn') + .set('kbn-xsrf', 'xxx') + .set(ELASTIC_HTTP_VERSION_HEADER, '2') + .set(X_ELASTIC_INTERNAL_ORIGIN_REQUEST, 'kibana') + .send({ enabled: false }) + .expect(200); + + await supertest + .get(api) + .set('kbn-xsrf', 'xxx') + .set(ELASTIC_HTTP_VERSION_HEADER, apiVersion) + .set(X_ELASTIC_INTERNAL_ORIGIN_REQUEST, 'kibana') + .expect(200, { + allowChangingOptInStatus: true, + optIn: false, + sendUsageFrom: 'server', + telemetryNotifyUserAboutOptInDefault: false, + }); + }); + + describe('From a previous version', function () { + this.tags(['skipCloud']); + + // Get current values + let attributes: Record; + let currentVersion: string; + let previousMinor: string; + + before(async () => { + [{ attributes }, currentVersion] = await Promise.all([ + kbnClient.savedObjects.get({ type: TELEMETRY_SO_TYPE, id: TELEMETRY_SO_ID }), + kbnClient.version.get(), + ]); + + const [major, minor, patch] = currentVersion + .match(/^(\d+)\.(\d+)\.(\d+)/)! + .map(parseInt); + previousMinor = `${minor === 0 ? major - 1 : major}.${ + minor === 0 ? minor : minor - 1 + }.${patch}`; + }); + + it('GET should get `true` when opted-in in the current version', async () => { + // Opt-in from a previous version + await kbnClient.savedObjects.create({ + overwrite: true, + type: TELEMETRY_SO_TYPE, + id: TELEMETRY_SO_ID, + attributes: { ...attributes, enabled: true, lastVersionChecked: previousMinor }, + }); + + await supertest + .get(api) + .set('kbn-xsrf', 'xxx') + .set(ELASTIC_HTTP_VERSION_HEADER, apiVersion) + .set(X_ELASTIC_INTERNAL_ORIGIN_REQUEST, 'kibana') + .expect(200, { + allowChangingOptInStatus: true, + optIn: true, + sendUsageFrom: 'server', + telemetryNotifyUserAboutOptInDefault: false, + }); + }); + + it('GET should get `null` when opted-out in a previous version', async () => { + // Opt-out from previous version + await kbnClient.savedObjects.create({ + overwrite: true, + type: TELEMETRY_SO_TYPE, + id: TELEMETRY_SO_ID, + attributes: { ...attributes, enabled: false, lastVersionChecked: previousMinor }, + }); + + await supertest + .get(api) + .set('kbn-xsrf', 'xxx') + .set(ELASTIC_HTTP_VERSION_HEADER, apiVersion) + .set(X_ELASTIC_INTERNAL_ORIGIN_REQUEST, 'kibana') + .expect(200, { + allowChangingOptInStatus: true, + optIn: null, + sendUsageFrom: 'server', + telemetryNotifyUserAboutOptInDefault: false, + }); + }); }); }); }); diff --git a/test/api_integration/apis/telemetry/telemetry_last_reported.ts b/test/api_integration/apis/telemetry/telemetry_last_reported.ts index e553fa0218aa..6d077dd2857d 100644 --- a/test/api_integration/apis/telemetry/telemetry_last_reported.ts +++ b/test/api_integration/apis/telemetry/telemetry_last_reported.ts @@ -7,23 +7,37 @@ */ import expect from '@kbn/expect'; +import { + ELASTIC_HTTP_VERSION_HEADER, + X_ELASTIC_INTERNAL_ORIGIN_REQUEST, +} from '@kbn/core-http-common'; import type { FtrProviderContext } from '../../ftr_provider_context'; export default function optInTest({ getService }: FtrProviderContext) { const client = getService('kibanaServer'); const supertest = getService('supertest'); - describe('/api/telemetry/v2/last_reported API Telemetry lastReported', () => { + describe('/internal/telemetry/last_reported API Telemetry lastReported', () => { before(async () => { await client.savedObjects.delete({ type: 'telemetry', id: 'telemetry' }); }); it('GET should return undefined when there is no stored telemetry.lastReported value', async () => { - await supertest.get('/api/telemetry/v2/last_reported').set('kbn-xsrf', 'xxx').expect(200, {}); + await supertest + .get('/internal/telemetry/last_reported') + .set('kbn-xsrf', 'xxx') + .set(ELASTIC_HTTP_VERSION_HEADER, '2') + .set(X_ELASTIC_INTERNAL_ORIGIN_REQUEST, 'kibana') + .expect(200, {}); }); it('PUT should update telemetry.lastReported to now', async () => { - await supertest.put('/api/telemetry/v2/last_reported').set('kbn-xsrf', 'xxx').expect(200); + await supertest + .put('/internal/telemetry/last_reported') + .set('kbn-xsrf', 'xxx') + .set(ELASTIC_HTTP_VERSION_HEADER, '2') + .set(X_ELASTIC_INTERNAL_ORIGIN_REQUEST, 'kibana') + .expect(200); const { attributes: { lastReported }, @@ -46,8 +60,10 @@ export default function optInTest({ getService }: FtrProviderContext) { expect(lastReported).to.be.a('number'); await supertest - .get('/api/telemetry/v2/last_reported') + .get('/internal/telemetry/last_reported') .set('kbn-xsrf', 'xxx') + .set(ELASTIC_HTTP_VERSION_HEADER, '2') + .set(X_ELASTIC_INTERNAL_ORIGIN_REQUEST, 'kibana') .expect(200, { lastReported }); }); }); diff --git a/test/api_integration/apis/telemetry/telemetry_optin_notice_seen.ts b/test/api_integration/apis/telemetry/telemetry_optin_notice_seen.ts index 53b0d2cadca6..5310e32b87fe 100644 --- a/test/api_integration/apis/telemetry/telemetry_optin_notice_seen.ts +++ b/test/api_integration/apis/telemetry/telemetry_optin_notice_seen.ts @@ -7,17 +7,26 @@ */ import expect from '@kbn/expect'; +import { + ELASTIC_HTTP_VERSION_HEADER, + X_ELASTIC_INTERNAL_ORIGIN_REQUEST, +} from '@kbn/core-http-common'; import type { FtrProviderContext } from '../../ftr_provider_context'; export default function optInTest({ getService }: FtrProviderContext) { const client = getService('kibanaServer'); const supertest = getService('supertest'); - describe('/api/telemetry/v2/userHasSeenNotice API Telemetry User has seen OptIn Notice', () => { + describe('/internal/telemetry/userHasSeenNotice API Telemetry User has seen OptIn Notice', () => { it('should update telemetry setting field via PUT', async () => { await client.savedObjects.delete({ type: 'telemetry', id: 'telemetry' }); - await supertest.put('/api/telemetry/v2/userHasSeenNotice').set('kbn-xsrf', 'xxx').expect(200); + await supertest + .put('/internal/telemetry/userHasSeenNotice') + .set('kbn-xsrf', 'xxx') + .set(ELASTIC_HTTP_VERSION_HEADER, '2') + .set(X_ELASTIC_INTERNAL_ORIGIN_REQUEST, 'kibana') + .expect(200); const { attributes: { userHasSeenNotice }, diff --git a/test/plugin_functional/test_suites/telemetry/telemetry.ts b/test/plugin_functional/test_suites/telemetry/telemetry.ts index cbba01a9ddcb..a998e139eb5c 100644 --- a/test/plugin_functional/test_suites/telemetry/telemetry.ts +++ b/test/plugin_functional/test_suites/telemetry/telemetry.ts @@ -8,6 +8,10 @@ import expect from '@kbn/expect'; import { KBN_SCREENSHOT_MODE_ENABLED_KEY } from '@kbn/screenshot-mode-plugin/public'; +import { + ELASTIC_HTTP_VERSION_HEADER, + X_ELASTIC_INTERNAL_ORIGIN_REQUEST, +} from '@kbn/core-http-common'; import { PluginFunctionalProviderContext } from '../../services'; const TELEMETRY_SO_TYPE = 'telemetry'; @@ -83,8 +87,10 @@ export default function ({ getService, getPageObjects }: PluginFunctionalProvide it('does not show the banner if opted-in', async () => { await supertest - .post('/api/telemetry/v2/optIn') + .post('/internal/telemetry/optIn') .set('kbn-xsrf', 'xxx') + .set(ELASTIC_HTTP_VERSION_HEADER, '2') + .set(X_ELASTIC_INTERNAL_ORIGIN_REQUEST, 'kibana') .send({ enabled: true }) .expect(200); @@ -95,8 +101,10 @@ export default function ({ getService, getPageObjects }: PluginFunctionalProvide it('does not show the banner if opted-out in this version', async () => { await supertest - .post('/api/telemetry/v2/optIn') + .post('/internal/telemetry/optIn') .set('kbn-xsrf', 'xxx') + .set(ELASTIC_HTTP_VERSION_HEADER, '2') + .set(X_ELASTIC_INTERNAL_ORIGIN_REQUEST, 'kibana') .send({ enabled: false }) .expect(200); diff --git a/x-pack/dev-tools/api_debug/apis/telemetry/index.js b/x-pack/dev-tools/api_debug/apis/telemetry/index.js index bd9ffb5ed6c0..1b2e622d91c7 100644 --- a/x-pack/dev-tools/api_debug/apis/telemetry/index.js +++ b/x-pack/dev-tools/api_debug/apis/telemetry/index.js @@ -8,6 +8,6 @@ export const name = 'telemetry'; export const description = 'Get the clusters stats from the Kibana server'; export const method = 'POST'; -export const path = '/api/telemetry/v2/clusters/_stats'; +export const path = '/internal/telemetry/clusters/_stats'; export const body = { unencrypted: true, refreshCache: true }; diff --git a/x-pack/test/api_integration/apis/management/advanced_settings/feature_controls.ts b/x-pack/test/api_integration/apis/management/advanced_settings/feature_controls.ts index ccc9b92ac829..4af49a399161 100644 --- a/x-pack/test/api_integration/apis/management/advanced_settings/feature_controls.ts +++ b/x-pack/test/api_integration/apis/management/advanced_settings/feature_controls.ts @@ -8,6 +8,10 @@ import expect from '@kbn/expect'; import { SuperTest } from 'supertest'; import { CSV_QUOTE_VALUES_SETTING } from '@kbn/share-plugin/common/constants'; +import { + ELASTIC_HTTP_VERSION_HEADER, + X_ELASTIC_INTERNAL_ORIGIN_REQUEST, +} from '@kbn/core-http-common'; import { FtrProviderContext } from '../../../ftr_provider_context'; export default function featureControlsTests({ getService }: FtrProviderContext) { @@ -64,9 +68,11 @@ export default function featureControlsTests({ getService }: FtrProviderContext) const basePath = spaceId ? `/s/${spaceId}` : ''; return await supertest - .post(`${basePath}/api/telemetry/v2/optIn`) + .post(`${basePath}/internal/telemetry/optIn`) .auth(username, password) .set('kbn-xsrf', 'foo') + .set(ELASTIC_HTTP_VERSION_HEADER, '2') + .set(X_ELASTIC_INTERNAL_ORIGIN_REQUEST, 'kibana') .send({ enabled: true }) .then((response: any) => ({ error: undefined, response })) .catch((error: any) => ({ error, response: undefined })); diff --git a/x-pack/test/api_integration/apis/maps/maps_telemetry.ts b/x-pack/test/api_integration/apis/maps/maps_telemetry.ts index 8dbfca9a40a7..4883f01b5ac6 100644 --- a/x-pack/test/api_integration/apis/maps/maps_telemetry.ts +++ b/x-pack/test/api_integration/apis/maps/maps_telemetry.ts @@ -7,6 +7,10 @@ import expect from '@kbn/expect'; import { estypes } from '@elastic/elasticsearch'; +import { + ELASTIC_HTTP_VERSION_HEADER, + X_ELASTIC_INTERNAL_ORIGIN_REQUEST, +} from '@kbn/core-http-common'; import { FtrProviderContext } from '../../ftr_provider_context'; export default function ({ getService }: FtrProviderContext) { @@ -17,7 +21,9 @@ export default function ({ getService }: FtrProviderContext) { const { body: [{ stats: apiResponse }], } = await supertest - .post(`/api/telemetry/v2/clusters/_stats`) + .post(`/internal/telemetry/clusters/_stats`) + .set(ELASTIC_HTTP_VERSION_HEADER, '2') + .set(X_ELASTIC_INTERNAL_ORIGIN_REQUEST, 'kibana') .set('kbn-xsrf', 'xxxx') .send({ unencrypted: true, diff --git a/x-pack/test/api_integration/apis/telemetry/telemetry.ts b/x-pack/test/api_integration/apis/telemetry/telemetry.ts index 2d02f0a97642..601e2fddbd83 100644 --- a/x-pack/test/api_integration/apis/telemetry/telemetry.ts +++ b/x-pack/test/api_integration/apis/telemetry/telemetry.ts @@ -21,6 +21,10 @@ import type { CacheDetails, } from '@kbn/telemetry-collection-manager-plugin/server/types'; import { assertTelemetryPayload } from '@kbn/telemetry-tools'; +import { + ELASTIC_HTTP_VERSION_HEADER, + X_ELASTIC_INTERNAL_ORIGIN_REQUEST, +} from '@kbn/core-http-common'; import basicClusterFixture from './fixtures/basiccluster.json'; import multiClusterFixture from './fixtures/multicluster.json'; import type { SecurityService } from '../../../../../test/common/services/security/security'; @@ -97,7 +101,7 @@ export default function ({ getService }: FtrProviderContext) { const esSupertest = getService('esSupertest'); const security = getService('security'); - describe('/api/telemetry/v2/clusters/_stats', () => { + describe('/internal/telemetry/clusters/_stats', () => { const timestamp = new Date().toISOString(); describe('monitoring/multicluster', () => { let localXPack: Record; @@ -112,8 +116,10 @@ export default function ({ getService }: FtrProviderContext) { await updateMonitoringDates(esSupertest, fromTimestamp, toTimestamp, timestamp); const { body }: { body: UnencryptedTelemetryPayload } = await supertest - .post('/api/telemetry/v2/clusters/_stats') + .post('/internal/telemetry/clusters/_stats') .set('kbn-xsrf', 'xxx') + .set(ELASTIC_HTTP_VERSION_HEADER, '2') + .set(X_ELASTIC_INTERNAL_ORIGIN_REQUEST, 'kibana') .send({ unencrypted: true, refreshCache: true }) .expect(200); @@ -167,8 +173,10 @@ export default function ({ getService }: FtrProviderContext) { after(() => esArchiver.unload(archive)); it('should load non-expiring basic cluster', async () => { const { body }: { body: UnencryptedTelemetryPayload } = await supertest - .post('/api/telemetry/v2/clusters/_stats') + .post('/internal/telemetry/clusters/_stats') .set('kbn-xsrf', 'xxx') + .set(ELASTIC_HTTP_VERSION_HEADER, '2') + .set(X_ELASTIC_INTERNAL_ORIGIN_REQUEST, 'kibana') .send({ unencrypted: true, refreshCache: true }) .expect(200); @@ -193,8 +201,10 @@ export default function ({ getService }: FtrProviderContext) { await updateMonitoringDates(esSupertest, fromTimestamp, toTimestamp, timestamp); // hit the endpoint to cache results await supertest - .post('/api/telemetry/v2/clusters/_stats') + .post('/internal/telemetry/clusters/_stats') .set('kbn-xsrf', 'xxx') + .set(ELASTIC_HTTP_VERSION_HEADER, '2') + .set(X_ELASTIC_INTERNAL_ORIGIN_REQUEST, 'kibana') .send({ unencrypted: true, refreshCache: true }) .expect(200); }); @@ -204,8 +214,10 @@ export default function ({ getService }: FtrProviderContext) { it('returns non-cached results when unencrypted', async () => { const now = Date.now(); const { body }: { body: UnencryptedTelemetryPayload } = await supertest - .post('/api/telemetry/v2/clusters/_stats') + .post('/internal/telemetry/clusters/_stats') .set('kbn-xsrf', 'xxx') + .set(ELASTIC_HTTP_VERSION_HEADER, '2') + .set(X_ELASTIC_INTERNAL_ORIGIN_REQUEST, 'kibana') .send({ unencrypted: true }) .expect(200); @@ -224,8 +236,10 @@ export default function ({ getService }: FtrProviderContext) { it('grabs a fresh copy on refresh', async () => { const now = Date.now(); const { body }: { body: UnencryptedTelemetryPayload } = await supertest - .post('/api/telemetry/v2/clusters/_stats') + .post('/internal/telemetry/clusters/_stats') .set('kbn-xsrf', 'xxx') + .set(ELASTIC_HTTP_VERSION_HEADER, '2') + .set(X_ELASTIC_INTERNAL_ORIGIN_REQUEST, 'kibana') .send({ unencrypted: true, refreshCache: true }) .expect(200); @@ -243,16 +257,20 @@ export default function ({ getService }: FtrProviderContext) { describe('superadmin user', () => { it('should return unencrypted telemetry for the admin user', async () => { await supertest - .post('/api/telemetry/v2/clusters/_stats') + .post('/internal/telemetry/clusters/_stats') .set('kbn-xsrf', 'xxx') + .set(ELASTIC_HTTP_VERSION_HEADER, '2') + .set(X_ELASTIC_INTERNAL_ORIGIN_REQUEST, 'kibana') .send({ unencrypted: true }) .expect(200); }); it('should return encrypted telemetry for the admin user', async () => { await supertest - .post('/api/telemetry/v2/clusters/_stats') + .post('/internal/telemetry/clusters/_stats') .set('kbn-xsrf', 'xxx') + .set(ELASTIC_HTTP_VERSION_HEADER, '2') + .set(X_ELASTIC_INTERNAL_ORIGIN_REQUEST, 'kibana') .send({ unencrypted: false }) .expect(200); }); @@ -281,18 +299,22 @@ export default function ({ getService }: FtrProviderContext) { it('should return encrypted telemetry for the global-read user', async () => { await supertestWithoutAuth - .post('/api/telemetry/v2/clusters/_stats') + .post('/internal/telemetry/clusters/_stats') .auth(globalReadOnlyUser, password(globalReadOnlyUser)) .set('kbn-xsrf', 'xxx') + .set(ELASTIC_HTTP_VERSION_HEADER, '2') + .set(X_ELASTIC_INTERNAL_ORIGIN_REQUEST, 'kibana') .send({ unencrypted: false }) .expect(200); }); it('should return unencrypted telemetry for the global-read user', async () => { await supertestWithoutAuth - .post('/api/telemetry/v2/clusters/_stats') + .post('/internal/telemetry/clusters/_stats') .auth(globalReadOnlyUser, password(globalReadOnlyUser)) .set('kbn-xsrf', 'xxx') + .set(ELASTIC_HTTP_VERSION_HEADER, '2') + .set(X_ELASTIC_INTERNAL_ORIGIN_REQUEST, 'kibana') .send({ unencrypted: true }) .expect(200); }); @@ -330,18 +352,22 @@ export default function ({ getService }: FtrProviderContext) { it('should return encrypted telemetry for the read-only user', async () => { await supertestWithoutAuth - .post('/api/telemetry/v2/clusters/_stats') + .post('/internal/telemetry/clusters/_stats') .auth(noGlobalUser, password(noGlobalUser)) .set('kbn-xsrf', 'xxx') + .set(ELASTIC_HTTP_VERSION_HEADER, '2') + .set(X_ELASTIC_INTERNAL_ORIGIN_REQUEST, 'kibana') .send({ unencrypted: false }) .expect(200); }); it('should return 403 when the read-only user requests unencrypted telemetry', async () => { await supertestWithoutAuth - .post('/api/telemetry/v2/clusters/_stats') + .post('/internal/telemetry/clusters/_stats') .auth(noGlobalUser, password(noGlobalUser)) .set('kbn-xsrf', 'xxx') + .set(ELASTIC_HTTP_VERSION_HEADER, '2') + .set(X_ELASTIC_INTERNAL_ORIGIN_REQUEST, 'kibana') .send({ unencrypted: true }) .expect(403); }); diff --git a/x-pack/test/api_integration/apis/telemetry/telemetry_local.ts b/x-pack/test/api_integration/apis/telemetry/telemetry_local.ts index f0bb15d29b87..51e60c2e22bd 100644 --- a/x-pack/test/api_integration/apis/telemetry/telemetry_local.ts +++ b/x-pack/test/api_integration/apis/telemetry/telemetry_local.ts @@ -12,6 +12,10 @@ import ossPluginsTelemetrySchema from '@kbn/telemetry-plugin/schema/oss_plugins. import xpackRootTelemetrySchema from '@kbn/telemetry-collection-xpack-plugin/schema/xpack_root.json'; import xpackPluginsTelemetrySchema from '@kbn/telemetry-collection-xpack-plugin/schema/xpack_plugins.json'; import { assertTelemetryPayload } from '@kbn/telemetry-tools'; +import { + ELASTIC_HTTP_VERSION_HEADER, + X_ELASTIC_INTERNAL_ORIGIN_REQUEST, +} from '@kbn/core-http-common'; import { flatKeys } from '../../../../../test/api_integration/apis/telemetry/utils'; import type { FtrProviderContext } from '../../ftr_provider_context'; @@ -31,7 +35,7 @@ export default function ({ getService }: FtrProviderContext) { const supertest = getService('supertest'); const es = getService('es'); - describe('/api/telemetry/v2/clusters/_stats with monitoring disabled', () => { + describe('/internal/telemetry/clusters/_stats with monitoring disabled', () => { let stats: Record; before('disable monitoring and pull local stats', async () => { @@ -39,8 +43,10 @@ export default function ({ getService }: FtrProviderContext) { await new Promise((r) => setTimeout(r, 1000)); const { body } = await supertest - .post('/api/telemetry/v2/clusters/_stats') + .post('/internal/telemetry/clusters/_stats') .set('kbn-xsrf', 'xxx') + .set(ELASTIC_HTTP_VERSION_HEADER, '2') + .set(X_ELASTIC_INTERNAL_ORIGIN_REQUEST, 'kibana') .send({ unencrypted: true, refreshCache: true }) .expect(200); diff --git a/x-pack/test/api_integration/services/usage_api.ts b/x-pack/test/api_integration/services/usage_api.ts index fbcddfb3dc51..500212d96ddf 100644 --- a/x-pack/test/api_integration/services/usage_api.ts +++ b/x-pack/test/api_integration/services/usage_api.ts @@ -6,6 +6,10 @@ */ import { UsageStatsPayload } from '@kbn/telemetry-collection-manager-plugin/server'; +import { + ELASTIC_HTTP_VERSION_HEADER, + X_ELASTIC_INTERNAL_ORIGIN_REQUEST, +} from '@kbn/core-http-common'; import { FtrProviderContext } from '../ftr_provider_context'; export interface UsageStatsPayloadTestFriendly extends UsageStatsPayload { @@ -29,9 +33,10 @@ export function UsageAPIProvider({ getService }: FtrProviderContext) { refreshCache?: boolean; }): Promise> { const { body } = await supertest - .post('/api/telemetry/v2/clusters/_stats') + .post('/internal/telemetry/clusters/_stats') .set('kbn-xsrf', 'xxx') - .set('x-elastic-internal-origin', 'xxx') + .set(ELASTIC_HTTP_VERSION_HEADER, '2') + .set(X_ELASTIC_INTERNAL_ORIGIN_REQUEST, 'kibana') .send({ refreshCache: true, ...payload }) .expect(200); return body; diff --git a/x-pack/test/cloud_security_posture_api/telemetry/telemetry.ts b/x-pack/test/cloud_security_posture_api/telemetry/telemetry.ts index 797a88881a87..e5867ccd7489 100644 --- a/x-pack/test/cloud_security_posture_api/telemetry/telemetry.ts +++ b/x-pack/test/cloud_security_posture_api/telemetry/telemetry.ts @@ -6,7 +6,10 @@ */ import expect from '@kbn/expect'; -import { ELASTIC_HTTP_VERSION_HEADER } from '@kbn/core-http-common'; +import { + ELASTIC_HTTP_VERSION_HEADER, + X_ELASTIC_INTERNAL_ORIGIN_REQUEST, +} from '@kbn/core-http-common'; import { data, MockTelemetryFindings } from './data'; import type { FtrProviderContext } from '../ftr_provider_context'; @@ -67,7 +70,9 @@ export default function ({ getService }: FtrProviderContext) { const { body: [{ stats: apiResponse }], } = await supertest - .post(`/api/telemetry/v2/clusters/_stats`) + .post(`/internal/telemetry/clusters/_stats`) + .set(ELASTIC_HTTP_VERSION_HEADER, '2') + .set(X_ELASTIC_INTERNAL_ORIGIN_REQUEST, 'kibana') .set('kbn-xsrf', 'xxxx') .send({ unencrypted: true, @@ -119,8 +124,10 @@ export default function ({ getService }: FtrProviderContext) { const { body: [{ stats: apiResponse }], } = await supertest - .post(`/api/telemetry/v2/clusters/_stats`) + .post(`/internal/telemetry/clusters/_stats`) .set('kbn-xsrf', 'xxxx') + .set(ELASTIC_HTTP_VERSION_HEADER, '2') + .set(X_ELASTIC_INTERNAL_ORIGIN_REQUEST, 'kibana') .send({ unencrypted: true, refreshCache: true, @@ -164,8 +171,10 @@ export default function ({ getService }: FtrProviderContext) { const { body: [{ stats: apiResponse }], } = await supertest - .post(`/api/telemetry/v2/clusters/_stats`) + .post(`/internal/telemetry/clusters/_stats`) .set('kbn-xsrf', 'xxxx') + .set(ELASTIC_HTTP_VERSION_HEADER, '2') + .set(X_ELASTIC_INTERNAL_ORIGIN_REQUEST, 'kibana') .send({ unencrypted: true, refreshCache: true, @@ -240,8 +249,10 @@ export default function ({ getService }: FtrProviderContext) { const { body: [{ stats: apiResponse }], } = await supertest - .post(`/api/telemetry/v2/clusters/_stats`) + .post(`/internal/telemetry/clusters/_stats`) .set('kbn-xsrf', 'xxxx') + .set(ELASTIC_HTTP_VERSION_HEADER, '2') + .set(X_ELASTIC_INTERNAL_ORIGIN_REQUEST, 'kibana') .send({ unencrypted: true, refreshCache: true, @@ -294,8 +305,10 @@ export default function ({ getService }: FtrProviderContext) { const { body: [{ stats: apiResponse }], } = await supertest - .post(`/api/telemetry/v2/clusters/_stats`) + .post(`/internal/telemetry/clusters/_stats`) .set('kbn-xsrf', 'xxxx') + .set(ELASTIC_HTTP_VERSION_HEADER, '2') + .set(X_ELASTIC_INTERNAL_ORIGIN_REQUEST, 'kibana') .send({ unencrypted: true, refreshCache: true, diff --git a/x-pack/test/detection_engine_api_integration/utils/get_stats.ts b/x-pack/test/detection_engine_api_integration/utils/get_stats.ts index 0871012f8749..7f4a2bddbd83 100644 --- a/x-pack/test/detection_engine_api_integration/utils/get_stats.ts +++ b/x-pack/test/detection_engine_api_integration/utils/get_stats.ts @@ -8,6 +8,10 @@ import type { ToolingLog } from '@kbn/tooling-log'; import type SuperTest from 'supertest'; import type { DetectionMetrics } from '@kbn/security-solution-plugin/server/usage/detections/types'; +import { + ELASTIC_HTTP_VERSION_HEADER, + X_ELASTIC_INTERNAL_ORIGIN_REQUEST, +} from '@kbn/core-http-common'; import { getStatsUrl } from './get_stats_url'; import { getDetectionMetricsFromBody } from './get_detection_metrics_from_body'; @@ -24,6 +28,8 @@ export const getStats = async ( const response = await supertest .post(getStatsUrl()) .set('kbn-xsrf', 'true') + .set(ELASTIC_HTTP_VERSION_HEADER, '2') + .set(X_ELASTIC_INTERNAL_ORIGIN_REQUEST, 'kibana') .send({ unencrypted: true, refreshCache: true }); if (response.status !== 200) { log.error( diff --git a/x-pack/test/detection_engine_api_integration/utils/get_stats_url.ts b/x-pack/test/detection_engine_api_integration/utils/get_stats_url.ts index ac6537f670f7..1cd397df9226 100644 --- a/x-pack/test/detection_engine_api_integration/utils/get_stats_url.ts +++ b/x-pack/test/detection_engine_api_integration/utils/get_stats_url.ts @@ -8,4 +8,4 @@ /** * Cluster stats URL. Replace this with any from kibana core if there is ever a constant there for this. */ -export const getStatsUrl = (): string => '/api/telemetry/v2/clusters/_stats'; +export const getStatsUrl = (): string => '/internal/telemetry/clusters/_stats'; diff --git a/x-pack/test/fleet_api_integration/apis/fleet_telemetry.ts b/x-pack/test/fleet_api_integration/apis/fleet_telemetry.ts index 04b8d80fdbee..3efb072907fa 100644 --- a/x-pack/test/fleet_api_integration/apis/fleet_telemetry.ts +++ b/x-pack/test/fleet_api_integration/apis/fleet_telemetry.ts @@ -5,6 +5,10 @@ * 2.0. */ +import { + ELASTIC_HTTP_VERSION_HEADER, + X_ELASTIC_INTERNAL_ORIGIN_REQUEST, +} from '@kbn/core-http-common'; import expect from '@kbn/expect'; import { FtrProviderContext } from '../../api_integration/ftr_provider_context'; import { skipIfNoDockerRegistry, generateAgent } from '../helpers'; @@ -124,8 +128,10 @@ export default function (providerContext: FtrProviderContext) { const { body: [{ stats: apiResponse }], } = await supertest - .post(`/api/telemetry/v2/clusters/_stats`) + .post(`/internal/telemetry/clusters/_stats`) .set('kbn-xsrf', 'xxxx') + .set(ELASTIC_HTTP_VERSION_HEADER, '2') + .set(X_ELASTIC_INTERNAL_ORIGIN_REQUEST, 'kibana') .send({ unencrypted: true, refreshCache: true, diff --git a/x-pack/test/functional/apps/infra/logs_source_configuration.ts b/x-pack/test/functional/apps/infra/logs_source_configuration.ts index 8166af784827..daf6296ed2c2 100644 --- a/x-pack/test/functional/apps/infra/logs_source_configuration.ts +++ b/x-pack/test/functional/apps/infra/logs_source_configuration.ts @@ -6,6 +6,10 @@ */ import expect from '@kbn/expect'; +import { + ELASTIC_HTTP_VERSION_HEADER, + X_ELASTIC_INTERNAL_ORIGIN_REQUEST, +} from '@kbn/core-http-common'; import { DATES } from './constants'; import { FtrProviderContext } from '../../ftr_provider_context'; @@ -133,8 +137,10 @@ export default ({ getPageObjects, getService }: FtrProviderContext) => { await logsUi.logStreamPage.getStreamEntries(); const [{ stats }] = await supertest - .post(`/api/telemetry/v2/clusters/_stats`) + .post(`/internal/telemetry/clusters/_stats`) .set(COMMON_REQUEST_HEADERS) + .set(ELASTIC_HTTP_VERSION_HEADER, '2') + .set(X_ELASTIC_INTERNAL_ORIGIN_REQUEST, 'kibana') .set('Accept', 'application/json') .send({ unencrypted: true, diff --git a/x-pack/test/functional_execution_context/tests/server.ts b/x-pack/test/functional_execution_context/tests/server.ts index 1d854fed2b94..64035a007796 100644 --- a/x-pack/test/functional_execution_context/tests/server.ts +++ b/x-pack/test/functional_execution_context/tests/server.ts @@ -5,6 +5,10 @@ * 2.0. */ +import { + ELASTIC_HTTP_VERSION_HEADER, + X_ELASTIC_INTERNAL_ORIGIN_REQUEST, +} from '@kbn/core-http-common'; import expect from '@kbn/expect'; import type { FtrProviderContext } from '../ftr_provider_context'; import { assertLogContains, isExecutionContextLog, ANY } from '../test_utils'; @@ -111,8 +115,10 @@ export default function ({ getService }: FtrProviderContext) { it('propagates context for Telemetry collection', async () => { await supertest - .post('/api/telemetry/v2/clusters/_stats') + .post('/internal/telemetry/clusters/_stats') .set('kbn-xsrf', 'true') + .set(ELASTIC_HTTP_VERSION_HEADER, '2') + .set(X_ELASTIC_INTERNAL_ORIGIN_REQUEST, 'kibana') .send({ unencrypted: false }) .expect(200); diff --git a/x-pack/test/scalability/apis/api.telemetry.cluster_stats.1600_dataviews.json b/x-pack/test/scalability/apis/api.telemetry.cluster_stats.1600_dataviews.json index 621bd21556d6..485208916d48 100644 --- a/x-pack/test/scalability/apis/api.telemetry.cluster_stats.1600_dataviews.json +++ b/x-pack/test/scalability/apis/api.telemetry.cluster_stats.1600_dataviews.json @@ -1,5 +1,5 @@ { - "journeyName": "POST /api/telemetry/v2/clusters/_stats - 1600 dataviews", + "journeyName": "POST /internal/telemetry/clusters/_stats - 1600 dataviews", "scalabilitySetup": { "warmup": [ { @@ -30,13 +30,15 @@ { "http": { "method": "POST", - "path": "/api/telemetry/v2/clusters/_stats", + "path": "/internal/telemetry/clusters/_stats", "body": "{}", "headers": { "Cookie": "", "Kbn-Version": "", "Accept-Encoding": "gzip, deflate, br", - "Content-Type": "application/json" + "Content-Type": "application/json", + "elastic-api-version": "2", + "x-elastic-internal-origin": "kibana" }, "statusCode": 200 } diff --git a/x-pack/test/scalability/apis/api.telemetry.cluster_stats.json b/x-pack/test/scalability/apis/api.telemetry.cluster_stats.json index eb5bf0808d3e..041fb1fae31e 100644 --- a/x-pack/test/scalability/apis/api.telemetry.cluster_stats.json +++ b/x-pack/test/scalability/apis/api.telemetry.cluster_stats.json @@ -1,5 +1,5 @@ { - "journeyName": "POST /api/telemetry/v2/clusters/_stats", + "journeyName": "POST /internal/telemetry/clusters/_stats", "scalabilitySetup": { "warmup": [ { @@ -28,13 +28,15 @@ { "http": { "method": "POST", - "path": "/api/telemetry/v2/clusters/_stats", + "path": "/internal/telemetry/clusters/_stats", "body": "{}", "headers": { "Cookie": "", "Kbn-Version": "", "Accept-Encoding": "gzip, deflate, br", - "Content-Type": "application/json" + "Content-Type": "application/json", + "elastic-api-version": "2", + "x-elastic-internal-origin": "kibana" }, "statusCode": 200 } diff --git a/x-pack/test/scalability/apis/api.telemetry.cluster_stats.no_cache.1600_dataviews.json b/x-pack/test/scalability/apis/api.telemetry.cluster_stats.no_cache.1600_dataviews.json index 191d01c6a742..2a3095447e8b 100644 --- a/x-pack/test/scalability/apis/api.telemetry.cluster_stats.no_cache.1600_dataviews.json +++ b/x-pack/test/scalability/apis/api.telemetry.cluster_stats.no_cache.1600_dataviews.json @@ -1,5 +1,5 @@ { - "journeyName": "POST /api/telemetry/v2/clusters/_stats - no cache - 1600 dataviews", + "journeyName": "POST /internal/telemetry/clusters/_stats - no cache - 1600 dataviews", "scalabilitySetup": { "responseTimeThreshold": { "threshold1": 1000, @@ -35,13 +35,15 @@ { "http": { "method": "POST", - "path": "/api/telemetry/v2/clusters/_stats", + "path": "/internal/telemetry/clusters/_stats", "body": "{ \"refreshCache\": true }", "headers": { "Cookie": "", "Kbn-Version": "", "Accept-Encoding": "gzip, deflate, br", - "Content-Type": "application/json" + "Content-Type": "application/json", + "elastic-api-version": "2", + "x-elastic-internal-origin": "kibana" }, "timeout": 240000, "statusCode": 200 diff --git a/x-pack/test/scalability/apis/api.telemetry.cluster_stats.no_cache.json b/x-pack/test/scalability/apis/api.telemetry.cluster_stats.no_cache.json index b3099941180a..c0521ffb2607 100644 --- a/x-pack/test/scalability/apis/api.telemetry.cluster_stats.no_cache.json +++ b/x-pack/test/scalability/apis/api.telemetry.cluster_stats.no_cache.json @@ -1,5 +1,5 @@ { - "journeyName": "POST /api/telemetry/v2/clusters/_stats - no cache", + "journeyName": "POST /internal/telemetry/clusters/_stats - no cache", "scalabilitySetup": { "responseTimeThreshold": { "threshold1": 1000, @@ -33,13 +33,15 @@ { "http": { "method": "POST", - "path": "/api/telemetry/v2/clusters/_stats", + "path": "/internal/telemetry/clusters/_stats", "body": "{ \"refreshCache\": true }", "headers": { "Cookie": "", "Kbn-Version": "", "Accept-Encoding": "gzip, deflate, br", - "Content-Type": "application/json" + "Content-Type": "application/json", + "elastic-api-version": "2", + "x-elastic-internal-origin": "kibana" }, "statusCode": 200 }