diff --git a/src/dev/build/tasks/os_packages/docker_generator/resources/base/bin/kibana-docker b/src/dev/build/tasks/os_packages/docker_generator/resources/base/bin/kibana-docker index 44e0fc6205c8e4..3295de5a336d37 100755 --- a/src/dev/build/tasks/os_packages/docker_generator/resources/base/bin/kibana-docker +++ b/src/dev/build/tasks/os_packages/docker_generator/resources/base/bin/kibana-docker @@ -191,6 +191,7 @@ kibana_vars=( telemetry.enabled telemetry.optIn telemetry.optInStatusUrl + telemetry.sendUsageTo telemetry.sendUsageFrom tilemap.options.attribution tilemap.options.maxZoom diff --git a/src/plugins/telemetry/common/constants.ts b/src/plugins/telemetry/common/constants.ts index 8aeb26e8253b66..f6b99badca492c 100644 --- a/src/plugins/telemetry/common/constants.ts +++ b/src/plugins/telemetry/common/constants.ts @@ -49,3 +49,17 @@ export const PRIVACY_STATEMENT_URL = `https://www.elastic.co/legal/privacy-state * The endpoint version when hitting the remote telemetry service */ export const ENDPOINT_VERSION = 'v2'; + +/** + * The telemetry endpoints for the remote telemetry service. + */ +export const TELEMETRY_ENDPOINT = { + MAIN_CHANNEL: { + PROD: `https://telemetry.elastic.co/xpack/${ENDPOINT_VERSION}/send`, + STAGING: `https://telemetry-staging.elastic.co/xpack/${ENDPOINT_VERSION}/send`, + }, + OPT_IN_STATUS_CHANNEL: { + PROD: `https://telemetry.elastic.co/opt_in_status/${ENDPOINT_VERSION}/send`, + STAGING: `https://telemetry-staging.elastic.co/opt_in_status/${ENDPOINT_VERSION}/send`, + }, +}; diff --git a/src/plugins/telemetry/common/telemetry_config/get_telemetry_channel_endpoint.test.ts b/src/plugins/telemetry/common/telemetry_config/get_telemetry_channel_endpoint.test.ts new file mode 100644 index 00000000000000..74d45f6a9f7d40 --- /dev/null +++ b/src/plugins/telemetry/common/telemetry_config/get_telemetry_channel_endpoint.test.ts @@ -0,0 +1,48 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0 and the Server Side Public License, v 1; you may not use this file except + * in compliance with, at your election, the Elastic License 2.0 or the Server + * Side Public License, v 1. + */ + +import { getTelemetryChannelEndpoint } from './get_telemetry_channel_endpoint'; +import { TELEMETRY_ENDPOINT } from '../constants'; + +describe('getTelemetryChannelEndpoint', () => { + it('throws on unknown env', () => { + expect(() => + // @ts-expect-error + getTelemetryChannelEndpoint({ env: 'ANY', channelName: 'main' }) + ).toThrowErrorMatchingInlineSnapshot(`"Unknown telemetry endpoint env ANY."`); + }); + + it('throws on unknown channelName', () => { + expect(() => + // @ts-expect-error + getTelemetryChannelEndpoint({ env: 'prod', channelName: 'ANY' }) + ).toThrowErrorMatchingInlineSnapshot(`"Unknown telemetry channel ANY."`); + }); + + describe('main channel', () => { + it('returns correct prod endpoint', () => { + const endpoint = getTelemetryChannelEndpoint({ env: 'prod', channelName: 'main' }); + expect(endpoint).toBe(TELEMETRY_ENDPOINT.MAIN_CHANNEL.PROD); + }); + it('returns correct staging endpoint', () => { + const endpoint = getTelemetryChannelEndpoint({ env: 'staging', channelName: 'main' }); + expect(endpoint).toBe(TELEMETRY_ENDPOINT.MAIN_CHANNEL.STAGING); + }); + }); + + describe('optInStatus channel', () => { + it('returns correct prod endpoint', () => { + const endpoint = getTelemetryChannelEndpoint({ env: 'prod', channelName: 'optInStatus' }); + expect(endpoint).toBe(TELEMETRY_ENDPOINT.OPT_IN_STATUS_CHANNEL.PROD); + }); + it('returns correct staging endpoint', () => { + const endpoint = getTelemetryChannelEndpoint({ env: 'staging', channelName: 'optInStatus' }); + expect(endpoint).toBe(TELEMETRY_ENDPOINT.OPT_IN_STATUS_CHANNEL.STAGING); + }); + }); +}); diff --git a/src/plugins/telemetry/common/telemetry_config/get_telemetry_channel_endpoint.ts b/src/plugins/telemetry/common/telemetry_config/get_telemetry_channel_endpoint.ts new file mode 100644 index 00000000000000..a0af7878afef64 --- /dev/null +++ b/src/plugins/telemetry/common/telemetry_config/get_telemetry_channel_endpoint.ts @@ -0,0 +1,34 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0 and the Server Side Public License, v 1; you may not use this file except + * in compliance with, at your election, the Elastic License 2.0 or the Server + * Side Public License, v 1. + */ + +import { TELEMETRY_ENDPOINT } from '../constants'; + +export interface GetTelemetryChannelEndpointConfig { + channelName: 'main' | 'optInStatus'; + env: 'staging' | 'prod'; +} + +export function getTelemetryChannelEndpoint({ + channelName, + env, +}: GetTelemetryChannelEndpointConfig): string { + if (env !== 'staging' && env !== 'prod') { + throw new Error(`Unknown telemetry endpoint env ${env}.`); + } + + const endpointEnv = env === 'staging' ? 'STAGING' : 'PROD'; + + switch (channelName) { + case 'main': + return TELEMETRY_ENDPOINT.MAIN_CHANNEL[endpointEnv]; + case 'optInStatus': + return TELEMETRY_ENDPOINT.OPT_IN_STATUS_CHANNEL[endpointEnv]; + default: + throw new Error(`Unknown telemetry channel ${channelName}.`); + } +} diff --git a/src/plugins/telemetry/common/telemetry_config/index.ts b/src/plugins/telemetry/common/telemetry_config/index.ts index cc4ff102742d7f..eb268639cad919 100644 --- a/src/plugins/telemetry/common/telemetry_config/index.ts +++ b/src/plugins/telemetry/common/telemetry_config/index.ts @@ -11,3 +11,5 @@ export { getTelemetrySendUsageFrom } from './get_telemetry_send_usage_from'; export { getTelemetryAllowChangingOptInStatus } from './get_telemetry_allow_changing_opt_in_status'; export { getTelemetryFailureDetails } from './get_telemetry_failure_details'; export type { TelemetryFailureDetails } from './get_telemetry_failure_details'; +export { getTelemetryChannelEndpoint } from './get_telemetry_channel_endpoint'; +export type { GetTelemetryChannelEndpointConfig } from './get_telemetry_channel_endpoint'; diff --git a/src/plugins/telemetry/public/mocks.ts b/src/plugins/telemetry/public/mocks.ts index 418aeace77c704..405c6620e802aa 100644 --- a/src/plugins/telemetry/public/mocks.ts +++ b/src/plugins/telemetry/public/mocks.ts @@ -34,8 +34,7 @@ export function mockTelemetryService({ }: TelemetryServiceMockOptions = {}) { const config = { enabled: true, - url: 'http://localhost', - optInStatusUrl: 'http://localhost', + sendUsageTo: 'staging' as const, sendUsageFrom: 'browser' as const, optIn: true, banner: true, diff --git a/src/plugins/telemetry/public/plugin.ts b/src/plugins/telemetry/public/plugin.ts index 04fabe3d62b17c..73dc07d7a4fb93 100644 --- a/src/plugins/telemetry/public/plugin.ts +++ b/src/plugins/telemetry/public/plugin.ts @@ -90,16 +90,14 @@ interface TelemetryPluginSetupDependencies { export interface TelemetryPluginConfig { /** Is the plugin enabled? **/ enabled: boolean; - /** Remote telemetry service's URL **/ - url: string; /** The banner is expected to be shown when needed **/ banner: boolean; /** Does the cluster allow changing the opt-in/out status via the UI? **/ allowChangingOptInStatus: boolean; /** Is the cluster opted-in? **/ optIn: boolean | null; - /** Opt-in/out notification URL **/ - optInStatusUrl: string; + /** Specify if telemetry should send usage to the prod or staging remote telemetry service **/ + sendUsageTo: 'prod' | 'staging'; /** Should the telemetry payloads be sent from the server or the browser? **/ sendUsageFrom: 'browser' | 'server'; /** Should notify the user about the opt-in status? **/ diff --git a/src/plugins/telemetry/public/services/telemetry_service.test.ts b/src/plugins/telemetry/public/services/telemetry_service.test.ts index 94630c0cb8d80e..b23ba127c1522c 100644 --- a/src/plugins/telemetry/public/services/telemetry_service.test.ts +++ b/src/plugins/telemetry/public/services/telemetry_service.test.ts @@ -10,7 +10,7 @@ /* eslint-disable dot-notation */ import { mockTelemetryService } from '../mocks'; - +import { TELEMETRY_ENDPOINT } from '../../common/constants'; describe('TelemetryService', () => { describe('fetchTelemetry', () => { it('calls expected URL with 20 minutes - now', async () => { @@ -137,13 +137,42 @@ describe('TelemetryService', () => { }); describe('getTelemetryUrl', () => { - it('should return the config.url parameter', async () => { - const url = 'http://test.com'; + it('should return staging endpoint when sendUsageTo is set to staging', async () => { + const telemetryService = mockTelemetryService({ + config: { sendUsageTo: 'staging' }, + }); + + expect(telemetryService.getTelemetryUrl()).toBe(TELEMETRY_ENDPOINT.MAIN_CHANNEL.STAGING); + }); + + it('should return prod endpoint when sendUsageTo is set to prod', async () => { + const telemetryService = mockTelemetryService({ + config: { sendUsageTo: 'prod' }, + }); + + expect(telemetryService.getTelemetryUrl()).toBe(TELEMETRY_ENDPOINT.MAIN_CHANNEL.PROD); + }); + }); + + describe('getOptInStatusUrl', () => { + it('should return staging endpoint when sendUsageTo is set to staging', async () => { + const telemetryService = mockTelemetryService({ + config: { sendUsageTo: 'staging' }, + }); + + expect(telemetryService.getOptInStatusUrl()).toBe( + TELEMETRY_ENDPOINT.OPT_IN_STATUS_CHANNEL.STAGING + ); + }); + + it('should return prod endpoint when sendUsageTo is set to prod', async () => { const telemetryService = mockTelemetryService({ - config: { url }, + config: { sendUsageTo: 'prod' }, }); - expect(telemetryService.getTelemetryUrl()).toBe(url); + expect(telemetryService.getOptInStatusUrl()).toBe( + TELEMETRY_ENDPOINT.OPT_IN_STATUS_CHANNEL.PROD + ); }); }); diff --git a/src/plugins/telemetry/public/services/telemetry_service.ts b/src/plugins/telemetry/public/services/telemetry_service.ts index 515d2039a4a114..4e52ec3a7e6edf 100644 --- a/src/plugins/telemetry/public/services/telemetry_service.ts +++ b/src/plugins/telemetry/public/services/telemetry_service.ts @@ -9,6 +9,7 @@ import { i18n } from '@kbn/i18n'; import { CoreStart } from 'kibana/public'; import { TelemetryPluginConfig } from '../plugin'; +import { getTelemetryChannelEndpoint } from '../../common/telemetry_config'; interface TelemetryServiceConstructor { config: TelemetryPluginConfig; @@ -93,14 +94,14 @@ export class TelemetryService { /** Retrieve the opt-in/out notification URL **/ public getOptInStatusUrl = () => { - const telemetryOptInStatusUrl = this.config.optInStatusUrl; - return telemetryOptInStatusUrl; + const { sendUsageTo } = this.config; + return getTelemetryChannelEndpoint({ channelName: 'optInStatus', env: sendUsageTo }); }; /** Retrieve the URL to report telemetry **/ public getTelemetryUrl = () => { - const telemetryUrl = this.config.url; - return telemetryUrl; + const { sendUsageTo } = this.config; + return getTelemetryChannelEndpoint({ channelName: 'main', env: sendUsageTo }); }; /** diff --git a/src/plugins/telemetry/server/config.ts b/src/plugins/telemetry/server/config/config.ts similarity index 50% rename from src/plugins/telemetry/server/config.ts rename to src/plugins/telemetry/server/config/config.ts index 8123ae4c66e3c5..8d75f0aba1726c 100644 --- a/src/plugins/telemetry/server/config.ts +++ b/src/plugins/telemetry/server/config/config.ts @@ -6,11 +6,18 @@ * Side Public License, v 1. */ -import { schema, TypeOf } from '@kbn/config-schema'; +import { schema, TypeOf, Type } from '@kbn/config-schema'; import { getConfigPath } from '@kbn/utils'; -import { ENDPOINT_VERSION } from '../common/constants'; +import { PluginConfigDescriptor } from 'kibana/server'; +import { TELEMETRY_ENDPOINT } from '../../common/constants'; +import { deprecateEndpointConfigs } from './deprecations'; -export const configSchema = schema.object({ +const clusterEnvSchema: [Type<'prod'>, Type<'staging'>] = [ + schema.literal('prod'), + schema.literal('staging'), +]; + +const configSchema = schema.object({ enabled: schema.boolean({ defaultValue: true }), allowChangingOptInStatus: schema.boolean({ defaultValue: true }), optIn: schema.conditional( @@ -23,24 +30,38 @@ export const configSchema = schema.object({ // `config` is used internally and not intended to be set config: schema.string({ defaultValue: getConfigPath() }), banner: schema.boolean({ defaultValue: true }), + sendUsageTo: schema.conditional( + schema.contextRef('dist'), + schema.literal(false), // Point to staging if it's not a distributable release + schema.oneOf(clusterEnvSchema, { defaultValue: 'staging' }), + schema.oneOf(clusterEnvSchema, { defaultValue: 'prod' }) + ), + /** + * REMOVE IN 8.0 - INTERNAL CONFIG DEPRECATED IN 7.15 + * REPLACED WITH `telemetry.sendUsageTo: staging | prod` + */ url: schema.conditional( schema.contextRef('dist'), schema.literal(false), // Point to staging if it's not a distributable release schema.string({ - defaultValue: `https://telemetry-staging.elastic.co/xpack/${ENDPOINT_VERSION}/send`, + defaultValue: TELEMETRY_ENDPOINT.MAIN_CHANNEL.STAGING, }), schema.string({ - defaultValue: `https://telemetry.elastic.co/xpack/${ENDPOINT_VERSION}/send`, + defaultValue: TELEMETRY_ENDPOINT.MAIN_CHANNEL.PROD, }) ), + /** + * REMOVE IN 8.0 - INTERNAL CONFIG DEPRECATED IN 7.15 + * REPLACED WITH `telemetry.sendUsageTo: staging | prod` + */ optInStatusUrl: schema.conditional( schema.contextRef('dist'), schema.literal(false), // Point to staging if it's not a distributable release schema.string({ - defaultValue: `https://telemetry-staging.elastic.co/opt_in_status/${ENDPOINT_VERSION}/send`, + defaultValue: TELEMETRY_ENDPOINT.OPT_IN_STATUS_CHANNEL.STAGING, }), schema.string({ - defaultValue: `https://telemetry.elastic.co/opt_in_status/${ENDPOINT_VERSION}/send`, + defaultValue: TELEMETRY_ENDPOINT.OPT_IN_STATUS_CHANNEL.PROD, }) ), sendUsageFrom: schema.oneOf([schema.literal('server'), schema.literal('browser')], { @@ -49,3 +70,16 @@ export const configSchema = schema.object({ }); export type TelemetryConfigType = TypeOf; + +export const config: PluginConfigDescriptor = { + schema: configSchema, + exposeToBrowser: { + enabled: true, + banner: true, + allowChangingOptInStatus: true, + optIn: true, + sendUsageFrom: true, + sendUsageTo: true, + }, + deprecations: () => [deprecateEndpointConfigs], +}; diff --git a/src/plugins/telemetry/server/config/deprecations.test.ts b/src/plugins/telemetry/server/config/deprecations.test.ts new file mode 100644 index 00000000000000..0823b521b23a7f --- /dev/null +++ b/src/plugins/telemetry/server/config/deprecations.test.ts @@ -0,0 +1,164 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0 and the Server Side Public License, v 1; you may not use this file except + * in compliance with, at your election, the Elastic License 2.0 or the Server + * Side Public License, v 1. + */ + +import { deprecateEndpointConfigs } from './deprecations'; +import type { TelemetryConfigType } from './config'; +import { TELEMETRY_ENDPOINT } from '../../common/constants'; +describe('deprecateEndpointConfigs', () => { + const fromPath = 'telemetry'; + const mockAddDeprecation = jest.fn(); + beforeEach(() => { + jest.clearAllMocks(); + }); + + function createMockRawConfig(telemetryConfig?: Partial) { + return { + elasticsearch: { username: 'kibana_system', password: 'changeme' }, + plugins: { paths: [] }, + server: { port: 5603, basePath: '/hln', rewriteBasePath: true }, + logging: { json: false }, + ...(telemetryConfig ? { telemetry: telemetryConfig } : {}), + }; + } + + it('returns void if telemetry.* config is not set', () => { + const rawConfig = createMockRawConfig(); + const result = deprecateEndpointConfigs(rawConfig, fromPath, mockAddDeprecation); + expect(result).toBe(undefined); + }); + + it('sets "telemetryConfig.sendUsageTo: staging" if "telemetry.url" uses the staging endpoint', () => { + const rawConfig = createMockRawConfig({ + url: TELEMETRY_ENDPOINT.MAIN_CHANNEL.STAGING, + }); + const result = deprecateEndpointConfigs(rawConfig, fromPath, mockAddDeprecation); + expect(result).toMatchInlineSnapshot(` + Object { + "set": Array [ + Object { + "path": "telemetry.sendUsageTo", + "value": "staging", + }, + ], + "unset": Array [ + Object { + "path": "telemetry.url", + }, + ], + } + `); + }); + + it('sets "telemetryConfig.sendUsageTo: prod" if "telemetry.url" uses the non-staging endpoint', () => { + const rawConfig = createMockRawConfig({ + url: 'random-endpoint', + }); + const result = deprecateEndpointConfigs(rawConfig, fromPath, mockAddDeprecation); + expect(result).toMatchInlineSnapshot(` + Object { + "set": Array [ + Object { + "path": "telemetry.sendUsageTo", + "value": "prod", + }, + ], + "unset": Array [ + Object { + "path": "telemetry.url", + }, + ], + } + `); + }); + + it('sets "telemetryConfig.sendUsageTo: staging" if "telemetry.optInStatusUrl" uses the staging endpoint', () => { + const rawConfig = createMockRawConfig({ + optInStatusUrl: TELEMETRY_ENDPOINT.MAIN_CHANNEL.STAGING, + }); + const result = deprecateEndpointConfigs(rawConfig, fromPath, mockAddDeprecation); + expect(result).toMatchInlineSnapshot(` + Object { + "set": Array [ + Object { + "path": "telemetry.sendUsageTo", + "value": "staging", + }, + ], + "unset": Array [ + Object { + "path": "telemetry.optInStatusUrl", + }, + ], + } + `); + }); + + it('sets "telemetryConfig.sendUsageTo: prod" if "telemetry.optInStatusUrl" uses the non-staging endpoint', () => { + const rawConfig = createMockRawConfig({ + optInStatusUrl: 'random-endpoint', + }); + const result = deprecateEndpointConfigs(rawConfig, fromPath, mockAddDeprecation); + expect(result).toMatchInlineSnapshot(` + Object { + "set": Array [ + Object { + "path": "telemetry.sendUsageTo", + "value": "prod", + }, + ], + "unset": Array [ + Object { + "path": "telemetry.optInStatusUrl", + }, + ], + } + `); + }); + + it('registers deprecation when "telemetry.url" is set', () => { + const rawConfig = createMockRawConfig({ + url: TELEMETRY_ENDPOINT.MAIN_CHANNEL.PROD, + }); + deprecateEndpointConfigs(rawConfig, fromPath, mockAddDeprecation); + expect(mockAddDeprecation).toBeCalledTimes(1); + expect(mockAddDeprecation.mock.calls[0]).toMatchInlineSnapshot(` + Array [ + Object { + "correctiveActions": Object { + "manualSteps": Array [ + "Remove \\"telemetry.url\\" from the Kibana configuration.", + "To send usage to the staging endpoint add \\"telemetry.sendUsageTo: staging\\" to the Kibana configuration.", + ], + }, + "message": "\\"telemetry.url\\" has been deprecated. Set \\"telemetry.sendUsageTo: staging\\" to the Kibana configurations to send usage to the staging endpoint.", + }, + ] + `); + }); + + it('registers deprecation when "telemetry.optInStatusUrl" is set', () => { + const rawConfig = createMockRawConfig({ + optInStatusUrl: 'random-endpoint', + }); + deprecateEndpointConfigs(rawConfig, fromPath, mockAddDeprecation); + expect(mockAddDeprecation).toBeCalledTimes(1); + expect(mockAddDeprecation.mock.calls[0]).toMatchInlineSnapshot(` + Array [ + Object { + "correctiveActions": Object { + "manualSteps": Array [ + "Remove \\"telemetry.optInStatusUrl\\" from the Kibana configuration.", + "To send usage to the staging endpoint add \\"telemetry.sendUsageTo: staging\\" to the Kibana configuration.", + ], + }, + "message": "\\"telemetry.optInStatusUrl\\" has been deprecated. Set \\"telemetry.sendUsageTo: staging\\" to the Kibana configurations to send usage to the staging endpoint.", + }, + ] + `); + }); +}); diff --git a/src/plugins/telemetry/server/config/deprecations.ts b/src/plugins/telemetry/server/config/deprecations.ts new file mode 100644 index 00000000000000..5507c3e1f6675b --- /dev/null +++ b/src/plugins/telemetry/server/config/deprecations.ts @@ -0,0 +1,63 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0 and the Server Side Public License, v 1; you may not use this file except + * in compliance with, at your election, the Elastic License 2.0 or the Server + * Side Public License, v 1. + */ + +import { i18n } from '@kbn/i18n'; +import type { ConfigDeprecation } from 'kibana/server'; +import type { TelemetryConfigType } from './config'; + +export const deprecateEndpointConfigs: ConfigDeprecation = ( + rawConfig, + fromPath, + addDeprecation +) => { + const telemetryConfig: TelemetryConfigType = rawConfig[fromPath]; + if (!telemetryConfig) { + return; + } + + const unset: Array<{ path: string }> = []; + const endpointConfigPaths = ['url', 'optInStatusUrl'] as const; + let useStaging = telemetryConfig.sendUsageTo === 'staging' ? true : false; + + for (const configPath of endpointConfigPaths) { + const configValue = telemetryConfig[configPath]; + const fullConfigPath = `telemetry.${configPath}`; + if (typeof configValue !== 'undefined') { + unset.push({ path: fullConfigPath }); + + if (/telemetry-staging\.elastic\.co/i.test(configValue)) { + useStaging = true; + } + + addDeprecation({ + message: i18n.translate('telemetry.endpointConfigs.deprecationMessage', { + defaultMessage: + '"{configPath}" has been deprecated. Set "telemetry.sendUsageTo: staging" to the Kibana configurations to send usage to the staging endpoint.', + values: { configPath: fullConfigPath }, + }), + correctiveActions: { + manualSteps: [ + i18n.translate('telemetry.endpointConfigs.deprecationManualStep1', { + defaultMessage: 'Remove "{configPath}" from the Kibana configuration.', + values: { configPath: fullConfigPath }, + }), + i18n.translate('telemetry.endpointConfigs.deprecationManualStep2', { + defaultMessage: + 'To send usage to the staging endpoint add "telemetry.sendUsageTo: staging" to the Kibana configuration.', + }), + ], + }, + }); + } + } + + return { + set: [{ path: 'telemetry.sendUsageTo', value: useStaging ? 'staging' : 'prod' }], + unset, + }; +}; diff --git a/src/plugins/telemetry/server/config/index.ts b/src/plugins/telemetry/server/config/index.ts new file mode 100644 index 00000000000000..19ccd73e17fcde --- /dev/null +++ b/src/plugins/telemetry/server/config/index.ts @@ -0,0 +1,10 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0 and the Server Side Public License, v 1; you may not use this file except + * in compliance with, at your election, the Elastic License 2.0 or the Server + * Side Public License, v 1. + */ + +export { config } from './config'; +export type { TelemetryConfigType } from './config'; diff --git a/src/plugins/telemetry/server/fetcher.ts b/src/plugins/telemetry/server/fetcher.ts index fb188a2414b989..e15b5be2604ec4 100644 --- a/src/plugins/telemetry/server/fetcher.ts +++ b/src/plugins/telemetry/server/fetcher.ts @@ -19,6 +19,7 @@ import { ICustomClusterClient, } from '../../../core/server'; import { + getTelemetryChannelEndpoint, getTelemetryOptIn, getTelemetrySendUsageFrom, getTelemetryFailureDetails, @@ -139,7 +140,10 @@ export class FetcherTask { const configTelemetrySendUsageFrom = config.sendUsageFrom; const allowChangingOptInStatus = config.allowChangingOptInStatus; const configTelemetryOptIn = typeof config.optIn === 'undefined' ? null : config.optIn; - const telemetryUrl = config.url; + const telemetryUrl = getTelemetryChannelEndpoint({ + channelName: 'main', + env: config.sendUsageTo, + }); const { failureCount, failureVersion } = getTelemetryFailureDetails({ telemetrySavedObject, }); @@ -208,17 +212,17 @@ export class FetcherTask { }); } - private async sendTelemetry(url: string, cluster: string): Promise { + private async sendTelemetry(telemetryUrl: string, cluster: string): Promise { this.logger.debug(`Sending usage stats.`); /** * send OPTIONS before sending usage data. * OPTIONS is less intrusive as it does not contain any payload and is used here to check if the endpoint is reachable. */ - await fetch(url, { + await fetch(telemetryUrl, { method: 'options', }); - await fetch(url, { + await fetch(telemetryUrl, { method: 'post', body: cluster, headers: { 'X-Elastic-Stack-Version': this.currentKibanaVersion }, diff --git a/src/plugins/telemetry/server/index.ts b/src/plugins/telemetry/server/index.ts index 530f7c499c3f20..6b56996c756aa7 100644 --- a/src/plugins/telemetry/server/index.ts +++ b/src/plugins/telemetry/server/index.ts @@ -6,25 +6,13 @@ * Side Public License, v 1. */ -import { PluginInitializerContext, PluginConfigDescriptor } from 'kibana/server'; +import type { PluginInitializerContext } from 'kibana/server'; +import type { TelemetryConfigType } from './config'; import { TelemetryPlugin } from './plugin'; -import { configSchema, TelemetryConfigType } from './config'; +export { config } from './config'; export type { TelemetryPluginSetup, TelemetryPluginStart } from './plugin'; -export const config: PluginConfigDescriptor = { - schema: configSchema, - exposeToBrowser: { - enabled: true, - url: true, - banner: true, - allowChangingOptInStatus: true, - optIn: true, - optInStatusUrl: true, - sendUsageFrom: true, - }, -}; - export const plugin = (initializerContext: PluginInitializerContext) => new TelemetryPlugin(initializerContext); export { getClusterUuids, getLocalStats } from './telemetry_collection'; diff --git a/src/plugins/telemetry/server/plugin.ts b/src/plugins/telemetry/server/plugin.ts index 40714bf4cf2be2..d38f054a4402e0 100644 --- a/src/plugins/telemetry/server/plugin.ts +++ b/src/plugins/telemetry/server/plugin.ts @@ -30,11 +30,11 @@ import { registerTelemetryUsageCollector, registerTelemetryPluginUsageCollector, } from './collectors'; -import { TelemetryConfigType } from './config'; +import type { TelemetryConfigType } from './config'; import { FetcherTask } from './fetcher'; import { handleOldSettings } from './handle_old_settings'; import { getTelemetrySavedObject } from './telemetry_repository'; -import { getTelemetryOptIn } from '../common/telemetry_config'; +import { getTelemetryOptIn, getTelemetryChannelEndpoint } from '../common/telemetry_config'; interface TelemetryPluginsDepsSetup { usageCollection: UsageCollectionSetup; @@ -117,8 +117,10 @@ export class TelemetryPlugin implements Plugin { - const config = await config$.pipe(take(1)).toPromise(); - return new URL(config.url); + const { sendUsageTo } = await config$.pipe(take(1)).toPromise(); + const telemetryUrl = getTelemetryChannelEndpoint({ env: sendUsageTo, channelName: 'main' }); + + return new URL(telemetryUrl); }, }; } diff --git a/src/plugins/telemetry/server/routes/telemetry_opt_in.ts b/src/plugins/telemetry/server/routes/telemetry_opt_in.ts index 196631f33384dd..e3fad0642303c2 100644 --- a/src/plugins/telemetry/server/routes/telemetry_opt_in.ts +++ b/src/plugins/telemetry/server/routes/telemetry_opt_in.ts @@ -83,15 +83,15 @@ export function registerTelemetryOptInRoutes({ ); if (config.sendUsageFrom === 'server') { - const optInStatusUrl = config.optInStatusUrl; + const { sendUsageTo } = config; sendTelemetryOptInStatus( telemetryCollectionManager, - { optInStatusUrl, newOptInStatus, currentKibanaVersion }, + { 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 "${optInStatusUrl}" from the server about the opt-in selection. Possibly blocked by a firewall? - Error: ${err.message}` + `Failed to notify the telemetry endpoint about the opt-in selection. Possibly blocked by a firewall? - Error: ${err.message}` ); }); } diff --git a/src/plugins/telemetry/server/routes/telemetry_opt_in_stats.test.ts b/src/plugins/telemetry/server/routes/telemetry_opt_in_stats.test.ts index 6660a36e76199c..acc9a863af61b4 100644 --- a/src/plugins/telemetry/server/routes/telemetry_opt_in_stats.test.ts +++ b/src/plugins/telemetry/server/routes/telemetry_opt_in_stats.test.ts @@ -10,30 +10,53 @@ jest.mock('node-fetch'); import fetch from 'node-fetch'; import { sendTelemetryOptInStatus } from './telemetry_opt_in_stats'; import { StatsGetterConfig } from 'src/plugins/telemetry_collection_manager/server'; - +import { TELEMETRY_ENDPOINT } from '../../common/constants'; describe('sendTelemetryOptInStatus', () => { + const mockStatsGetterConfig = { unencrypted: false } as StatsGetterConfig; + const mockTelemetryCollectionManager = { + getOptInStats: jest.fn().mockResolvedValue(['mock_opt_in_hashed_value']), + }; + + beforeEach(() => { + jest.clearAllMocks(); + }); + it('calls fetch with the opt in status returned from the telemetryCollectionManager', async () => { - const mockOptInStatus = ['mock_opt_in_hashed_value']; - const mockTelemetryCollectionManager = { - getOptInStats: jest.fn().mockResolvedValue(mockOptInStatus), - }; const mockConfig = { - optInStatusUrl: 'some_url', + sendUsageTo: 'prod' as const, newOptInStatus: true, currentKibanaVersion: 'mock_kibana_version', }; - const mockStatsGetterConfig = { - unencrypted: false, - }; const result = await sendTelemetryOptInStatus( mockTelemetryCollectionManager, mockConfig, - mockStatsGetterConfig as StatsGetterConfig + mockStatsGetterConfig ); expect(result).toBeUndefined(); expect(fetch).toBeCalledTimes(1); - expect(fetch).toBeCalledWith(mockConfig.optInStatusUrl, { + expect(fetch).toBeCalledWith(TELEMETRY_ENDPOINT.OPT_IN_STATUS_CHANNEL.PROD, { + method: 'post', + body: '["mock_opt_in_hashed_value"]', + headers: { 'X-Elastic-Stack-Version': mockConfig.currentKibanaVersion }, + }); + }); + + it('sends to staging endpoint on "sendUsageTo: staging"', async () => { + const mockConfig = { + sendUsageTo: 'staging' as const, + newOptInStatus: true, + currentKibanaVersion: 'mock_kibana_version', + }; + + await sendTelemetryOptInStatus( + mockTelemetryCollectionManager, + mockConfig, + mockStatsGetterConfig + ); + + expect(fetch).toBeCalledTimes(1); + expect(fetch).toBeCalledWith(TELEMETRY_ENDPOINT.OPT_IN_STATUS_CHANNEL.STAGING, { method: 'post', body: '["mock_opt_in_hashed_value"]', headers: { 'X-Elastic-Stack-Version': mockConfig.currentKibanaVersion }, 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 77ec981d4c35bf..f6b7eddcbe765c 100644 --- a/src/plugins/telemetry/server/routes/telemetry_opt_in_stats.ts +++ b/src/plugins/telemetry/server/routes/telemetry_opt_in_stats.ts @@ -14,9 +14,10 @@ import { TelemetryCollectionManagerPluginSetup, StatsGetterConfig, } from 'src/plugins/telemetry_collection_manager/server'; +import { getTelemetryChannelEndpoint } from '../../common/telemetry_config'; interface SendTelemetryOptInStatusConfig { - optInStatusUrl: string; + sendUsageTo: 'staging' | 'prod'; newOptInStatus: boolean; currentKibanaVersion: string; } @@ -26,7 +27,12 @@ export async function sendTelemetryOptInStatus( config: SendTelemetryOptInStatusConfig, statsGetterConfig: StatsGetterConfig ) { - const { optInStatusUrl, newOptInStatus, currentKibanaVersion } = config; + const { sendUsageTo, newOptInStatus, currentKibanaVersion } = config; + const optInStatusUrl = getTelemetryChannelEndpoint({ + env: sendUsageTo, + channelName: 'optInStatus', + }); + const optInStatus = await telemetryCollectionManager.getOptInStats( newOptInStatus, statsGetterConfig diff --git a/src/plugins/telemetry_management_section/public/components/__snapshots__/telemetry_management_section.test.tsx.snap b/src/plugins/telemetry_management_section/public/components/__snapshots__/telemetry_management_section.test.tsx.snap index 014142a2a3d068..7c9154cba4f885 100644 --- a/src/plugins/telemetry_management_section/public/components/__snapshots__/telemetry_management_section.test.tsx.snap +++ b/src/plugins/telemetry_management_section/public/components/__snapshots__/telemetry_management_section.test.tsx.snap @@ -311,9 +311,8 @@ exports[`TelemetryManagementSectionComponent renders null because allowChangingO "banner": true, "enabled": true, "optIn": true, - "optInStatusUrl": "", "sendUsageFrom": "browser", - "url": "", + "sendUsageTo": "staging", }, "fetchExample": [Function], "fetchTelemetry": [Function], diff --git a/src/plugins/telemetry_management_section/public/components/telemetry_management_section.test.tsx b/src/plugins/telemetry_management_section/public/components/telemetry_management_section.test.tsx index fe6f8e254142b5..b8332317e6b682 100644 --- a/src/plugins/telemetry_management_section/public/components/telemetry_management_section.test.tsx +++ b/src/plugins/telemetry_management_section/public/components/telemetry_management_section.test.tsx @@ -24,12 +24,11 @@ describe('TelemetryManagementSectionComponent', () => { const isSecurityExampleEnabled = jest.fn().mockReturnValue(true); const telemetryService = new TelemetryService({ config: { + sendUsageTo: 'staging', enabled: true, - url: '', banner: true, allowChangingOptInStatus: true, optIn: true, - optInStatusUrl: '', sendUsageFrom: 'browser', }, isScreenshotMode: false, @@ -60,12 +59,11 @@ describe('TelemetryManagementSectionComponent', () => { const telemetryService = new TelemetryService({ config: { enabled: true, - url: '', banner: true, allowChangingOptInStatus: true, optIn: false, - optInStatusUrl: '', sendUsageFrom: 'browser', + sendUsageTo: 'staging', }, isScreenshotMode: false, reportOptInStatusChange: false, @@ -116,11 +114,10 @@ describe('TelemetryManagementSectionComponent', () => { const telemetryService = new TelemetryService({ config: { enabled: true, - url: '', banner: true, allowChangingOptInStatus: true, optIn: false, - optInStatusUrl: '', + sendUsageTo: 'staging', sendUsageFrom: 'browser', }, isScreenshotMode: false, @@ -165,11 +162,10 @@ describe('TelemetryManagementSectionComponent', () => { const telemetryService = new TelemetryService({ config: { enabled: true, - url: '', banner: true, allowChangingOptInStatus: false, optIn: true, - optInStatusUrl: '', + sendUsageTo: 'staging', sendUsageFrom: 'browser', }, isScreenshotMode: false, @@ -205,11 +201,10 @@ describe('TelemetryManagementSectionComponent', () => { const telemetryService = new TelemetryService({ config: { enabled: true, - url: '', banner: true, allowChangingOptInStatus: true, optIn: false, - optInStatusUrl: '', + sendUsageTo: 'staging', sendUsageFrom: 'browser', }, isScreenshotMode: false, @@ -246,11 +241,10 @@ describe('TelemetryManagementSectionComponent', () => { const telemetryService = new TelemetryService({ config: { enabled: true, - url: '', banner: true, allowChangingOptInStatus: true, optIn: false, - optInStatusUrl: '', + sendUsageTo: 'staging', sendUsageFrom: 'browser', }, isScreenshotMode: false, @@ -287,11 +281,10 @@ describe('TelemetryManagementSectionComponent', () => { const telemetryService = new TelemetryService({ config: { enabled: true, - url: '', banner: true, allowChangingOptInStatus: true, optIn: false, - optInStatusUrl: '', + sendUsageTo: 'staging', sendUsageFrom: 'browser', }, isScreenshotMode: false, @@ -328,11 +321,10 @@ describe('TelemetryManagementSectionComponent', () => { const telemetryService = new TelemetryService({ config: { enabled: true, - url: '', banner: true, allowChangingOptInStatus: true, optIn: false, - optInStatusUrl: '', + sendUsageTo: 'staging', sendUsageFrom: 'browser', }, isScreenshotMode: false, @@ -379,11 +371,10 @@ describe('TelemetryManagementSectionComponent', () => { const telemetryService = new TelemetryService({ config: { enabled: true, - url: '', banner: true, allowChangingOptInStatus: false, optIn: false, - optInStatusUrl: '', + sendUsageTo: 'staging', sendUsageFrom: 'browser', }, isScreenshotMode: false, diff --git a/src/plugins/telemetry_management_section/public/plugin.tsx b/src/plugins/telemetry_management_section/public/plugin.tsx index 24583260329a69..2e3b1beb64d3c5 100644 --- a/src/plugins/telemetry_management_section/public/plugin.tsx +++ b/src/plugins/telemetry_management_section/public/plugin.tsx @@ -17,17 +17,6 @@ import { TelemetryManagementSectionWrapperProps, } from './components/telemetry_management_section_wrapper'; -export interface TelemetryPluginConfig { - enabled: boolean; - url: string; - banner: boolean; - allowChangingOptInStatus: boolean; - optIn: boolean | null; - optInStatusUrl: string; - sendUsageFrom: 'browser' | 'server'; - telemetryNotifyUserAboutOptInDefault?: boolean; -} - export interface TelemetryManagementSectionPluginDepsSetup { telemetry: TelemetryPluginSetup; advancedSettings: AdvancedSettingsSetup; diff --git a/test/common/config.js b/test/common/config.js index 9b6bb9c61bcbe3..62ad529d5b91f5 100644 --- a/test/common/config.js +++ b/test/common/config.js @@ -48,8 +48,7 @@ export default function () { '--telemetry.banner=false', '--telemetry.optIn=false', // These are *very* important to have them pointing to staging - '--telemetry.url=https://telemetry-staging.elastic.co/xpack/v2/send', - '--telemetry.optInStatusUrl=https://telemetry-staging.elastic.co/opt_in_status/v2/send', + '--telemetry.sendUsageTo=staging', `--server.maxPayload=1679958`, // newsfeed mock service `--plugin-path=${path.join(__dirname, 'fixtures', 'plugins', 'newsfeed')}`,