isClusterOptedIn should fallback to true when not found (#94980)

Co-authored-by: Kibana Machine <42973632+kibanamachine@users.noreply.github.com>
This commit is contained in:
Alejandro Fernández Haro 2021-03-23 20:23:23 +01:00 committed by GitHub
parent 29ee309dd8
commit 55e513364a
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
8 changed files with 174 additions and 81 deletions

View file

@ -22,6 +22,11 @@ describe('getKID', () => {
const kid = getKID(useProdKey);
expect(kid).toBe('kibana1');
});
it(`should fallback to development`, async () => {
const kid = getKID();
expect(kid).toBe('kibana_dev1');
});
});
describe('encryptTelemetry', () => {
@ -46,4 +51,10 @@ describe('encryptTelemetry', () => {
await encryptTelemetry(payload, { useProdKey: false });
expect(mockEncrypt).toBeCalledWith('kibana_dev1', payload);
});
it('should fallback to { useProdKey: false }', async () => {
const payload = { some: 'value' };
await encryptTelemetry(payload);
expect(mockEncrypt).toBeCalledWith('kibana_dev1', payload);
});
});

View file

@ -9,7 +9,7 @@
import { coreMock, httpServerMock } from '../../../core/server/mocks';
import { usageCollectionPluginMock } from '../../usage_collection/server/mocks';
import { TelemetryCollectionManagerPlugin } from './plugin';
import { CollectionStrategyConfig, StatsGetterConfig } from './types';
import type { BasicStatsPayload, CollectionStrategyConfig, StatsGetterConfig } from './types';
import { TelemetrySavedObjectsClient } from './telemetry_saved_objects_client';
function createCollectionStrategy(priority: number): jest.Mocked<CollectionStrategyConfig> {
@ -87,6 +87,15 @@ describe('Telemetry Collection Manager', () => {
});
describe(`after start`, () => {
const basicStats: BasicStatsPayload = {
cluster_uuid: 'clusterUuid',
cluster_name: 'clusterName',
timestamp: new Date().toISOString(),
cluster_stats: {},
stack_stats: {},
version: 'version',
};
beforeAll(() => {
telemetryCollectionManager.start(coreMock.createStart());
});
@ -97,19 +106,59 @@ describe('Telemetry Collection Manager', () => {
describe('unencrypted: false', () => {
const config: StatsGetterConfig = { unencrypted: false };
test('getStats returns empty because clusterDetails returns empty, and the soClient is an instance of the TelemetrySavedObjectsClient', async () => {
collectionStrategy.clusterDetailsGetter.mockResolvedValue([]);
await expect(setupApi.getStats(config)).resolves.toStrictEqual([]);
expect(collectionStrategy.clusterDetailsGetter.mock.calls[0][0].soClient).toBeInstanceOf(
TelemetrySavedObjectsClient
);
describe('getStats', () => {
test('returns empty because clusterDetails returns empty, and the soClient is an instance of the TelemetrySavedObjectsClient', async () => {
collectionStrategy.clusterDetailsGetter.mockResolvedValue([]);
await expect(setupApi.getStats(config)).resolves.toStrictEqual([]);
expect(
collectionStrategy.clusterDetailsGetter.mock.calls[0][0].soClient
).toBeInstanceOf(TelemetrySavedObjectsClient);
});
test('returns encrypted payload', async () => {
collectionStrategy.clusterDetailsGetter.mockResolvedValue([
{ clusterUuid: 'clusterUuid' },
]);
collectionStrategy.statsGetter.mockResolvedValue([basicStats]);
await expect(setupApi.getStats(config)).resolves.toStrictEqual([expect.any(String)]);
expect(
collectionStrategy.clusterDetailsGetter.mock.calls[0][0].soClient
).toBeInstanceOf(TelemetrySavedObjectsClient);
});
});
test('getOptInStats returns empty', async () => {
collectionStrategy.clusterDetailsGetter.mockResolvedValue([]);
await expect(setupApi.getOptInStats(true, config)).resolves.toStrictEqual([]);
expect(collectionStrategy.clusterDetailsGetter.mock.calls[0][0].soClient).toBeInstanceOf(
TelemetrySavedObjectsClient
);
describe('getOptInStats', () => {
test('returns empty', async () => {
collectionStrategy.clusterDetailsGetter.mockResolvedValue([]);
await expect(setupApi.getOptInStats(true, config)).resolves.toStrictEqual([]);
expect(
collectionStrategy.clusterDetailsGetter.mock.calls[0][0].soClient
).toBeInstanceOf(TelemetrySavedObjectsClient);
});
test('returns encrypted results for opt-in true', async () => {
collectionStrategy.clusterDetailsGetter.mockResolvedValue([
{ clusterUuid: 'clusterUuid' },
]);
await expect(setupApi.getOptInStats(true, config)).resolves.toStrictEqual([
expect.any(String),
]);
expect(
collectionStrategy.clusterDetailsGetter.mock.calls[0][0].soClient
).toBeInstanceOf(TelemetrySavedObjectsClient);
});
test('returns encrypted results for opt-in false', async () => {
collectionStrategy.clusterDetailsGetter.mockResolvedValue([
{ clusterUuid: 'clusterUuid' },
]);
await expect(setupApi.getOptInStats(false, config)).resolves.toStrictEqual([
expect.any(String),
]);
expect(
collectionStrategy.clusterDetailsGetter.mock.calls[0][0].soClient
).toBeInstanceOf(TelemetrySavedObjectsClient);
});
});
});
describe('unencrypted: true', () => {
@ -118,19 +167,60 @@ describe('Telemetry Collection Manager', () => {
request: httpServerMock.createKibanaRequest(),
};
test('getStats returns empty because clusterDetails returns empty, and the soClient is not an instance of the TelemetrySavedObjectsClient', async () => {
collectionStrategy.clusterDetailsGetter.mockResolvedValue([]);
await expect(setupApi.getStats(config)).resolves.toStrictEqual([]);
expect(
collectionStrategy.clusterDetailsGetter.mock.calls[0][0].soClient
).not.toBeInstanceOf(TelemetrySavedObjectsClient);
describe('getStats', () => {
test('getStats returns empty because clusterDetails returns empty, and the soClient is not an instance of the TelemetrySavedObjectsClient', async () => {
collectionStrategy.clusterDetailsGetter.mockResolvedValue([]);
await expect(setupApi.getStats(config)).resolves.toStrictEqual([]);
expect(
collectionStrategy.clusterDetailsGetter.mock.calls[0][0].soClient
).not.toBeInstanceOf(TelemetrySavedObjectsClient);
});
test('returns encrypted payload (assumes opted-in when no explicitly opted-out)', async () => {
collectionStrategy.clusterDetailsGetter.mockResolvedValue([
{ clusterUuid: 'clusterUuid' },
]);
collectionStrategy.statsGetter.mockResolvedValue([basicStats]);
await expect(setupApi.getStats(config)).resolves.toStrictEqual([
{ ...basicStats, collectionSource: 'test_collection' },
]);
expect(
collectionStrategy.clusterDetailsGetter.mock.calls[0][0].soClient
).not.toBeInstanceOf(TelemetrySavedObjectsClient);
});
});
test('getOptInStats returns empty', async () => {
collectionStrategy.clusterDetailsGetter.mockResolvedValue([]);
await expect(setupApi.getOptInStats(true, config)).resolves.toStrictEqual([]);
expect(
collectionStrategy.clusterDetailsGetter.mock.calls[0][0].soClient
).not.toBeInstanceOf(TelemetrySavedObjectsClient);
describe('getOptInStats', () => {
test('returns empty', async () => {
collectionStrategy.clusterDetailsGetter.mockResolvedValue([]);
await expect(setupApi.getOptInStats(true, config)).resolves.toStrictEqual([]);
expect(
collectionStrategy.clusterDetailsGetter.mock.calls[0][0].soClient
).not.toBeInstanceOf(TelemetrySavedObjectsClient);
});
test('returns results for opt-in true', async () => {
collectionStrategy.clusterDetailsGetter.mockResolvedValue([
{ clusterUuid: 'clusterUuid' },
]);
await expect(setupApi.getOptInStats(true, config)).resolves.toStrictEqual([
{ cluster_uuid: 'clusterUuid', opt_in_status: true },
]);
expect(
collectionStrategy.clusterDetailsGetter.mock.calls[0][0].soClient
).not.toBeInstanceOf(TelemetrySavedObjectsClient);
});
test('returns results for opt-in false', async () => {
collectionStrategy.clusterDetailsGetter.mockResolvedValue([
{ clusterUuid: 'clusterUuid' },
]);
await expect(setupApi.getOptInStats(false, config)).resolves.toStrictEqual([
{ cluster_uuid: 'clusterUuid', opt_in_status: false },
]);
expect(
collectionStrategy.clusterDetailsGetter.mock.calls[0][0].soClient
).not.toBeInstanceOf(TelemetrySavedObjectsClient);
});
});
});
});

View file

@ -30,7 +30,6 @@ import {
UsageStatsPayload,
StatsCollectionContext,
} from './types';
import { isClusterOptedIn } from './util';
import { encryptTelemetry } from './encryption';
import { TelemetrySavedObjectsClient } from './telemetry_saved_objects_client';
@ -233,7 +232,7 @@ export class TelemetryCollectionManagerPlugin
return usageData;
}
return encryptTelemetry(usageData.filter(isClusterOptedIn), {
return await encryptTelemetry(usageData, {
useProdKey: this.isDistributable,
});
}

View file

@ -1,11 +0,0 @@
/*
* 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 const isClusterOptedIn = (clusterUsage: any): boolean => {
return clusterUsage?.stack_stats?.kibana?.plugins?.telemetry?.opt_in_status === true;
};

View file

@ -7,13 +7,27 @@
*/
import expect from '@kbn/expect';
import supertestAsPromised from 'supertest-as-promised';
import { basicUiCounters } from './__fixtures__/ui_counters';
import { FtrProviderContext } from '../../ftr_provider_context';
import { SavedObject } from '../../../../src/core/server';
import type { FtrProviderContext } from '../../ftr_provider_context';
import type { SavedObject } from '../../../../src/core/server';
import ossRootTelemetrySchema from '../../../../src/plugins/telemetry/schema/oss_root.json';
import ossPluginsTelemetrySchema from '../../../../src/plugins/telemetry/schema/oss_plugins.json';
import { assertTelemetryPayload, flatKeys } from './utils';
async function retrieveTelemetry(
supertest: supertestAsPromised.SuperTest<supertestAsPromised.Test>
) {
const { body } = await supertest
.post('/api/telemetry/v2/clusters/_stats')
.set('kbn-xsrf', 'xxx')
.send({ unencrypted: true })
.expect(200);
expect(body.length).to.be(1);
return body[0];
}
export default function ({ getService }: FtrProviderContext) {
const supertest = getService('supertest');
const es = getService('es');
@ -35,14 +49,7 @@ export default function ({ getService }: FtrProviderContext) {
let stats: Record<string, any>;
before('pull local stats', async () => {
const { body } = await supertest
.post('/api/telemetry/v2/clusters/_stats')
.set('kbn-xsrf', 'xxx')
.send({ unencrypted: true })
.expect(200);
expect(body.length).to.be(1);
stats = body[0];
stats = await retrieveTelemetry(supertest);
});
it('should pass the schema validation', () => {
@ -141,14 +148,7 @@ export default function ({ getService }: FtrProviderContext) {
before('Add UI Counters saved objects', () => esArchiver.load('saved_objects/ui_counters'));
after('cleanup saved objects changes', () => esArchiver.unload('saved_objects/ui_counters'));
it('returns ui counters aggregated by day', async () => {
const { body } = await supertest
.post('/api/telemetry/v2/clusters/_stats')
.set('kbn-xsrf', 'xxx')
.send({ unencrypted: true })
.expect(200);
expect(body.length).to.be(1);
const stats = body[0];
const stats = await retrieveTelemetry(supertest);
expect(stats.stack_stats.kibana.plugins.ui_counters).to.eql(basicUiCounters);
});
});
@ -191,14 +191,7 @@ export default function ({ getService }: FtrProviderContext) {
});
it('should return application_usage data', async () => {
const { body } = await supertest
.post('/api/telemetry/v2/clusters/_stats')
.set('kbn-xsrf', 'xxx')
.send({ unencrypted: true })
.expect(200);
expect(body.length).to.be(1);
const stats = body[0];
const stats = await retrieveTelemetry(supertest);
expect(stats.stack_stats.kibana.plugins.application_usage).to.eql({
'test-app': {
appId: 'test-app',
@ -262,14 +255,7 @@ export default function ({ getService }: FtrProviderContext) {
// flaky https://github.com/elastic/kibana/issues/94513
it.skip("should only use the first 10k docs for the application_usage data (they'll be rolled up in a later process)", async () => {
const { body } = await supertest
.post('/api/telemetry/v2/clusters/_stats')
.set('kbn-xsrf', 'xxx')
.send({ unencrypted: true })
.expect(200);
expect(body.length).to.be(1);
const stats = body[0];
const stats = await retrieveTelemetry(supertest);
expect(stats.stack_stats.kibana.plugins.application_usage).to.eql({
'test-app': {
appId: 'test-app',

View file

@ -9,6 +9,7 @@ import { StatsGetter } from 'src/plugins/telemetry_collection_manager/server';
import { TelemetryLocalStats, getLocalStats } from '../../../../../src/plugins/telemetry/server';
import { getXPackUsage } from './get_xpack';
import { ESLicense, getLicenseFromLocalOrMaster } from './get_license';
import { isClusterOptedIn } from './is_cluster_opted_in';
export type TelemetryAggregatedStats = TelemetryLocalStats & {
stack_stats: { xpack?: object };
@ -48,6 +49,10 @@ export const getStatsWithXpack: StatsGetter<TelemetryAggregatedStats> = async fu
if (monitoringTelemetry) {
delete stats.stack_stats.kibana!.plugins.monitoringTelemetry;
}
return [...acc, stats, ...(monitoringTelemetry || [])];
// From the monitoring-sourced telemetry, we need to filter out the clusters that are opted-out.
const onlyOptedInMonitoringClusters = (monitoringTelemetry || []).filter(isClusterOptedIn);
return [...acc, stats, ...onlyOptedInMonitoringClusters];
}, [] as TelemetryAggregatedStats[]);
};

View file

@ -1,12 +1,11 @@
/*
* 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.
* 2.0; you may not use this file except in compliance with the Elastic License
* 2.0.
*/
import { isClusterOptedIn } from './util';
import { isClusterOptedIn } from './is_cluster_opted_in';
const createMockClusterUsage = (plugins: any) => {
return {
@ -32,9 +31,9 @@ describe('isClusterOptedIn', () => {
const result = isClusterOptedIn(mockClusterUsage);
expect(result).toBe(false);
});
it('returns false if cluster stats is malformed', () => {
expect(isClusterOptedIn(createMockClusterUsage({}))).toBe(false);
expect(isClusterOptedIn({})).toBe(false);
expect(isClusterOptedIn(undefined)).toBe(false);
it('returns true if kibana.plugins.telemetry does not exist', () => {
expect(isClusterOptedIn(createMockClusterUsage({}))).toBe(true);
expect(isClusterOptedIn({})).toBe(true);
expect(isClusterOptedIn(undefined)).toBe(true);
});
});

View file

@ -0,0 +1,14 @@
/*
* 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; you may not use this file except in compliance with the Elastic License
* 2.0.
*/
export const isClusterOptedIn = (clusterUsage: any): boolean => {
return (
clusterUsage?.stack_stats?.kibana?.plugins?.telemetry?.opt_in_status === true ||
// If stack_stats.kibana.plugins.telemetry does not exist, assume opted-in for BWC
!clusterUsage?.stack_stats?.kibana?.plugins?.telemetry
);
};