Encrypt telemetry (#33121)

* draft code

* finalize solution

* encrypted telemetry

* update jwks

* code review follow up

* finalize PR

* @elastic/node-crytpo typings

* node-crypto typings in x-pack

* yarn.lock

* add tests

* useInternalUser

* more tests

* unify variable name

* update typings

* remove root level node-crypto typings

* array of encrypted clusters, update endpoint
This commit is contained in:
Ahmad Bamieh 2019-05-20 22:04:42 -05:00 committed by GitHub
parent 8df19fef0b
commit a27cbc1b13
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
32 changed files with 400 additions and 161 deletions

View file

@ -9,7 +9,7 @@ import moment from 'moment';
export const name = 'telemetry';
export const description = 'Get the clusters stats for the last 1 hour from the Kibana server';
export const method = 'POST';
export const path = '/api/telemetry/v1/clusters/_stats';
export const path = '/api/telemetry/v2/clusters/_stats';
// Get an object with start and end times for the last 1 hour, ISO format, in UTC
function getTimeRange() {
@ -21,4 +21,4 @@ function getTimeRange() {
};
}
export const body = { timeRange: getTimeRange() };
export const body = { timeRange: getTimeRange(), unencrypted: true };

View file

@ -172,6 +172,7 @@
"@elastic/node-crypto": "^1.0.0",
"@elastic/nodegit": "0.25.0-alpha.20",
"@elastic/numeral": "2.3.3",
"@elastic/request-crypto": "^1.0.2",
"@kbn/babel-preset": "1.0.0",
"@kbn/elastic-idx": "1.0.0",
"@kbn/es-query": "1.0.0",

View file

@ -18,10 +18,10 @@ import { createTelemetryUsageCollector } from './server/lib/telemetry';
import { uiCapabilitiesForFeatures } from './server/lib/ui_capabilities_for_features';
import {
xpackInfoRoute,
telemetryRoute,
featuresRoute,
settingsRoute,
} from './server/routes/api/v1';
import { telemetryRoute } from './server/routes/api/v2';
import {
CONFIG_TELEMETRY,
getConfigTelemetryDesc,
@ -57,8 +57,8 @@ export const xpackMain = (kibana) => {
enabled: Joi.boolean().default(true),
url: Joi.when('$dev', {
is: true,
then: Joi.string().default('https://telemetry-staging.elastic.co/xpack/v1/send'),
otherwise: Joi.string().default('https://telemetry.elastic.co/xpack/v1/send')
then: Joi.string().default('https://telemetry-staging.elastic.co/xpack/v2/send'),
otherwise: Joi.string().default('https://telemetry.elastic.co/xpack/v2/send')
}),
}).default(),
xpack_api_polling_frequency_millis: Joi.number().default(XPACK_INFO_API_DEFAULT_POLL_FREQUENCY_IN_MILLIS),

View file

@ -46,16 +46,26 @@ export class OptInExampleFlyout extends Component {
this.state = {
data: null,
isLoading: true,
hasPrivilegeToRead: false,
};
}
componentDidMount() {
this.props.fetchTelemetry()
.then(response => this.setState({ data: Array.isArray(response.data) ? response.data : null, isLoading: false }))
.catch(() => this.setState({ isLoading: false }));
.then(response => this.setState({
data: Array.isArray(response.data) ? response.data : null,
isLoading: false,
hasPrivilegeToRead: true,
}))
.catch(err => {
this.setState({
isLoading: false,
hasPrivilegeToRead: err.status !== 403,
});
});
}
renderBody({ data, isLoading }) {
renderBody({ data, isLoading, hasPrivilegeToRead }) {
if (isLoading) {
return (
<EuiFlexGroup justifyContent="spaceAround">
@ -66,6 +76,24 @@ export class OptInExampleFlyout extends Component {
);
}
if (!hasPrivilegeToRead) {
return (
<EuiCallOut
title={<FormattedMessage
id="xpack.main.telemetry.callout.errorUnprivilegedUserTitle"
defaultMessage="Error displaying cluster statistics"
/>}
color="danger"
iconType="cross"
>
<FormattedMessage
id="xpack.main.telemetry.callout.errorUnprivilegedUserDescription"
defaultMessage="You do not have access to see unencrypted cluster statistics."
/>
</EuiCallOut>
);
}
if (data === null) {
return (
<EuiCallOut

View file

@ -73,7 +73,10 @@ export class TelemetryForm extends Component {
return (
<Fragment>
{showExample &&
<OptInExampleFlyout fetchTelemetry={() => telemetryOptInProvider.fetchExample()} onClose={this.toggleExample} />
<OptInExampleFlyout
fetchTelemetry={() => telemetryOptInProvider.fetchExample()}
onClose={this.toggleExample}
/>
}
<EuiPanel paddingSize="l">
<EuiForm>

View file

@ -26,7 +26,8 @@ describe('fetch_telemetry', () => {
toISOString: () => 'min456'
});
$http.post.withArgs(`fake/api/telemetry/v1/clusters/_stats`, {
$http.post.withArgs(`fake/api/telemetry/v2/clusters/_stats`, {
unencrypted: true,
timeRange: {
min: 'min456',
max: 'max123'
@ -34,7 +35,7 @@ describe('fetch_telemetry', () => {
})
.returns(response);
expect(fetchTelemetry($http, { basePath, _moment: () => moment })).to.be(response);
expect(fetchTelemetry($http, { basePath, _moment: () => moment, unencrypted: true })).to.be(response);
});
});

View file

@ -15,8 +15,9 @@ import moment from 'moment';
* @param {Function} _moment moment.js, but injectable for tests
* @return {Promise} An array of cluster Telemetry objects.
*/
export function fetchTelemetry($http, { basePath = uiChrome.getBasePath(), _moment = moment } = { }) {
return $http.post(`${basePath}/api/telemetry/v1/clusters/_stats`, {
export function fetchTelemetry($http, { basePath = uiChrome.getBasePath(), _moment = moment, unencrypted = false } = { }) {
return $http.post(`${basePath}/api/telemetry/v2/clusters/_stats`, {
unencrypted,
timeRange: {
min: _moment().subtract(20, 'minutes').toISOString(),
max: _moment().toISOString()

View file

@ -65,7 +65,8 @@ export class Telemetry {
return this._fetchTelemetry()
.then(response => {
return Promise.all(response.data.map(cluster => {
const clusters = [].concat(response.data);
return Promise.all(clusters.map(cluster => {
const req = {
method: 'POST',
url: this._telemetryUrl,

View file

@ -44,7 +44,7 @@ async function asyncInjectBanner($injector) {
if (await shouldShowBanner(telemetryOptInProvider, config)) {
const $http = $injector.get('$http');
renderBanner(telemetryOptInProvider, () => fetchTelemetry($http));
renderBanner(telemetryOptInProvider, () => fetchTelemetry($http, { unencrypted: true }));
}
}

View file

@ -13,7 +13,7 @@ export function TelemetryOptInProvider($injector, chrome) {
let currentOptInStatus = $injector.get('telemetryOptedIn');
setCanTrackUiMetrics(currentOptInStatus);
return {
const provider = {
getOptIn: () => currentOptInStatus,
setOptIn: async (enabled) => {
setCanTrackUiMetrics(enabled);
@ -21,7 +21,7 @@ export function TelemetryOptInProvider($injector, chrome) {
const $http = $injector.get('$http');
try {
await $http.post(chrome.addBasePath('/api/telemetry/v1/optIn'), { enabled });
await $http.post(chrome.addBasePath('/api/telemetry/v2/optIn'), { enabled });
currentOptInStatus = enabled;
} catch (error) {
notify.error(error);
@ -32,7 +32,8 @@ export function TelemetryOptInProvider($injector, chrome) {
},
fetchExample: async () => {
const $http = $injector.get('$http');
return $http.post(chrome.addBasePath(`/api/telemetry/v1/clusters/_stats`), {
return $http.post(chrome.addBasePath(`/api/telemetry/v2/clusters/_stats`), {
unencrypted: true,
timeRange: {
min: moment().subtract(20, 'minutes').toISOString(),
max: moment().toISOString()
@ -40,4 +41,6 @@ export function TelemetryOptInProvider($injector, chrome) {
});
}
};
return provider;
}

View file

@ -63,7 +63,7 @@ describe('TelemetryOptInProvider', () => {
const { provider, mockHttp } = setup({ optedIn: true });
await provider.setOptIn(false);
expect(mockHttp.post).toHaveBeenCalledWith(`/api/telemetry/v1/optIn`, { enabled: false });
expect(mockHttp.post).toHaveBeenCalledWith(`/api/telemetry/v2/optIn`, { enabled: false });
expect(provider.getOptIn()).toEqual(false);
});
@ -72,7 +72,7 @@ describe('TelemetryOptInProvider', () => {
const { provider, mockHttp } = setup({ optedIn: false });
await provider.setOptIn(true);
expect(mockHttp.post).toHaveBeenCalledWith(`/api/telemetry/v1/optIn`, { enabled: true });
expect(mockHttp.post).toHaveBeenCalledWith(`/api/telemetry/v2/optIn`, { enabled: true });
expect(provider.getOptIn()).toEqual(true);
});
@ -81,7 +81,7 @@ describe('TelemetryOptInProvider', () => {
const { provider, mockHttp } = setup({ optedIn: false, simulatePostError: true });
await provider.setOptIn(true);
expect(mockHttp.post).toHaveBeenCalledWith(`/api/telemetry/v1/optIn`, { enabled: true });
expect(mockHttp.post).toHaveBeenCalledWith(`/api/telemetry/v2/optIn`, { enabled: true });
// opt-in change should not be reflected
expect(provider.getOptIn()).toEqual(false);

View file

@ -0,0 +1,37 @@
/*
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
* or more contributor license agreements. Licensed under the Elastic License;
* you may not use this file except in compliance with the Elastic License.
*/
import { telemetryJWKS } from './telemetry_jwks';
import { encryptTelemetry, getKID } from './encrypt';
import { createRequestEncryptor } from '@elastic/request-crypto';
jest.mock('@elastic/request-crypto', () => ({
createRequestEncryptor: jest.fn().mockResolvedValue({
encrypt: jest.fn(),
}),
}));
describe('getKID', () => {
it(`returns 'kibana_dev' kid for development`, async () => {
const isProd = false;
const kid = getKID(isProd);
expect(kid).toBe('kibana_dev');
});
it(`returns 'kibana_prod' kid for development`, async () => {
const isProd = true;
const kid = getKID(isProd);
expect(kid).toBe('kibana');
});
});
describe('encryptTelemetry', () => {
it('encrypts payload', async () => {
const payload = { some: 'value' };
await encryptTelemetry(payload, true);
expect(createRequestEncryptor).toBeCalledWith(telemetryJWKS);
});
});

View file

@ -0,0 +1,19 @@
/*
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
* or more contributor license agreements. Licensed under the Elastic License;
* you may not use this file except in compliance with the Elastic License.
*/
import { createRequestEncryptor } from '@elastic/request-crypto';
import { telemetryJWKS } from './telemetry_jwks';
export function getKID(isProd = false): string {
return isProd ? 'kibana' : 'kibana_dev';
}
export async function encryptTelemetry(payload: any, isProd = false): Promise<string[]> {
const kid = getKID(isProd);
const encryptor = await createRequestEncryptor(telemetryJWKS);
const clusters = [].concat(payload);
return Promise.all(clusters.map((cluster: any) => encryptor.encrypt(kid, cluster)));
}

View file

@ -0,0 +1,7 @@
/*
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
* or more contributor license agreements. Licensed under the Elastic License;
* you may not use this file except in compliance with the Elastic License.
*/
export { encryptTelemetry } from './encrypt';

View file

@ -0,0 +1,30 @@
/*
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
* or more contributor license agreements. Licensed under the Elastic License;
* you may not use this file except in compliance with the Elastic License.
*/
import { PublicJWKS } from '@elastic/request-crypto';
export const telemetryJWKS: PublicJWKS = {
keys: [
{
kty: 'RSA',
kid: 'kibana',
use: 'enc',
alg: 'RSA-OAEP',
e: 'AQAB',
n:
'xYYa5XzvENaAzElCxQurloQM2KEQ058YSjZqmOwa-IN-EZMSUaYPY3qfYCG78ioRaKTHq4mgnkyrDKgjY_1pWKytiRD61FG2ZUeOCwzydnqO8Qpz2vFnibEHkZBRsKkLHgm90RgGpcXfz8vwxkz_nu59aWy5Qr7Ct99H0pEV1HoiCvy5Yw3QfWSAeV-3DWmq_0kX49tqk5yZE-vKnUhNMgqM22lMFTE5-vlaeHgv4ZcvCQx_HrOeea8LyZa5YOdqN-9st0g0G-aWp3CNI2-KJlMUTBAfIAtjwmJ-8QlgeIB1aA7OI2Ceh3kd4dNLesGdLvZ0y4f8IMOsO1dsRWSEsQ',
},
{
kty: 'RSA',
kid: 'kibana_dev',
use: 'enc',
alg: 'RSA-OAEP',
e: 'AQAB',
n:
'juVHivsYFznjrDC449oL3xKVTvux_7dEgBGOgJdfzA2R2GspEAOzupT-VkBnqrJnRP_lznM8bQIvvst1f_DNQ1me_Lr9u9cwL5Vq6SWlmw_u9ur_-ewkShU4tBoJDArksOS-ciTaUJoMaxanb7jWexp0pCDlrLrQyAOCnKQL701mD1gdT4rIw7F-jkb5fLUNUVzOGaGyVy6DHAHZx7Tnyw8rswhyRVvuS73imbRp9XcdOFhBDOeSbrSuZGqrVCjoIlWw-UsiW2ueRd8brBoOIHSmTOMIrIMjpPmzMFRKyCvvhnbjrw8j3fQtFII8urhXCVAw8aIHZhiBc5t9ZuwbJw',
},
],
};

View file

@ -6,4 +6,5 @@
export { getAllStats } from './monitoring';
export { getLocalStats } from './local';
export { encryptTelemetry } from './encryption';
export { createTelemetryUsageCollector } from './usage';

View file

@ -9,7 +9,8 @@ import sinon from 'sinon';
import { getClusterInfo } from '../get_cluster_info';
export function mockGetClusterInfo(callCluster, clusterInfo) {
export function mockGetClusterInfo(callCluster, clusterInfo, req) {
callCluster.withArgs(req, 'info').returns(clusterInfo);
callCluster.withArgs('info').returns(clusterInfo);
}

View file

@ -10,7 +10,12 @@ import sinon from 'sinon';
import { TIMEOUT } from '../constants';
import { getClusterStats } from '../get_cluster_stats';
export function mockGetClusterStats(callCluster, clusterStats) {
export function mockGetClusterStats(callCluster, clusterStats, req) {
callCluster.withArgs(req, 'cluster.stats', {
timeout: TIMEOUT
})
.returns(clusterStats);
callCluster.withArgs('cluster.stats', {
timeout: TIMEOUT
})

View file

@ -29,10 +29,10 @@ const getMockServer = (getCluster = sinon.stub(), kibanaUsage = {}) => ({
},
});
function mockGetLocalStats(callCluster, clusterInfo, clusterStats, license, usage) {
mockGetClusterInfo(callCluster, clusterInfo);
mockGetClusterStats(callCluster, clusterStats);
mockGetXPack(callCluster, license, usage);
function mockGetLocalStats(callCluster, clusterInfo, clusterStats, license, usage, req) {
mockGetClusterInfo(callCluster, clusterInfo, req);
mockGetClusterStats(callCluster, clusterStats, req);
mockGetXPack(callCluster, license, usage, req);
}
describe('get_local_stats', () => {
@ -196,7 +196,29 @@ describe('get_local_stats', () => {
Promise.resolve(xpack)
);
const result = await getLocalStats(req);
const result = await getLocalStats(req, { useInternalUser: true });
expect(result.cluster_uuid).to.eql(combinedStatsResult.cluster_uuid);
expect(result.cluster_name).to.eql(combinedStatsResult.cluster_name);
expect(result.version).to.eql(combinedStatsResult.version);
expect(result.cluster_stats).to.eql(combinedStatsResult.cluster_stats);
});
it('uses callWithRequest from data cluster', async () => {
const getCluster = sinon.stub();
const req = { server: getMockServer(getCluster) };
const callWithRequest = sinon.stub();
getCluster.withArgs('data').returns({ callWithRequest });
mockGetLocalStats(
callWithRequest,
Promise.resolve(clusterInfo),
Promise.resolve(clusterStats),
Promise.resolve(license),
Promise.resolve(xpack),
req
);
const result = await getLocalStats(req, { useInternalUser: false });
expect(result.cluster_uuid).to.eql(combinedStatsResult.cluster_uuid);
expect(result.cluster_name).to.eql(combinedStatsResult.cluster_name);
expect(result.version).to.eql(combinedStatsResult.version);

View file

@ -15,7 +15,16 @@ import {
handleXPack,
} from '../get_xpack';
function mockGetXPackLicense(callCluster, license) {
function mockGetXPackLicense(callCluster, license, req) {
callCluster.withArgs(req, 'transport.request', {
method: 'GET',
path: '/_license',
query: {
local: 'true'
}
})
.returns(license.then(response => ({ license: response })));
callCluster.withArgs('transport.request', {
method: 'GET',
path: '/_license',
@ -27,7 +36,16 @@ function mockGetXPackLicense(callCluster, license) {
.returns(license.then(response => ({ license: response })));
}
function mockGetXPackUsage(callCluster, usage) {
function mockGetXPackUsage(callCluster, usage, req) {
callCluster.withArgs(req, 'transport.request', {
method: 'GET',
path: '/_xpack/usage',
query: {
master_timeout: TIMEOUT
}
})
.returns(usage);
callCluster.withArgs('transport.request', {
method: 'GET',
path: '/_xpack/usage',
@ -44,10 +62,11 @@ function mockGetXPackUsage(callCluster, usage) {
* @param {Function} callCluster Sinon function mock.
* @param {Promise} license Promised license response.
* @param {Promise} usage Promised usage response.
* @param {Object} usage reqeust object.
*/
export function mockGetXPack(callCluster, license, usage) {
mockGetXPackLicense(callCluster, license);
mockGetXPackUsage(callCluster, usage);
export function mockGetXPack(callCluster, license, usage, req) {
mockGetXPackLicense(callCluster, license, req);
mockGetXPackUsage(callCluster, usage, req);
}
describe('get_xpack', () => {

View file

@ -58,10 +58,13 @@ export function getLocalStatsWithCaller(server, callCluster) {
* Get statistics for the connected Elasticsearch cluster.
*
* @param {Object} req The incoming request
* @param {Boolean} useRequestUser callWithRequest, otherwise callWithInternalUser
* @return {Promise} The cluster object containing telemetry.
*/
export function getLocalStats(req) {
export function getLocalStats(req, { useInternalUser = false } = {}) {
const { server } = req;
const { callWithInternalUser } = server.plugins.elasticsearch.getCluster('data');
return getLocalStatsWithCaller(server, callWithInternalUser);
const { callWithRequest, callWithInternalUser } = server.plugins.elasticsearch.getCluster('data');
const callCluster = useInternalUser ? callWithInternalUser : (...args) => callWithRequest(req, ...args);
return getLocalStatsWithCaller(server, callCluster);
}

View file

@ -24,10 +24,10 @@ import { getHighLevelStats } from './get_high_level_stats';
* @param {Date} end The ending range to request data
* @return {Promise} The array of clusters joined with the Kibana and Logstash instances.
*/
export function getAllStats(req, start, end) {
export function getAllStats(req, start, end, { useInternalUser = false } = {}) {
const server = req.server;
const { callWithRequest } = server.plugins.elasticsearch.getCluster('monitoring');
const callCluster = (...args) => callWithRequest(req, ...args);
const { callWithRequest, callWithInternalUser } = server.plugins.elasticsearch.getCluster('monitoring');
const callCluster = useInternalUser ? callWithInternalUser : (...args) => callWithRequest(req, ...args);
return getAllStatsWithCaller(server, callCluster, start, end);
}

View file

@ -5,6 +5,5 @@
*/
export { xpackInfoRoute } from './xpack_info';
export { telemetryRoute } from './telemetry';
export { featuresRoute } from './features';
export { settingsRoute } from './settings';

View file

@ -32,7 +32,7 @@ describe('telemetry', () => {
config.get.withArgs('xpack.monitoring.enabled').returns(true);
_getAllStats.withArgs(req, start, end).returns(response);
expect(await getTelemetry(req, config, start, end, { _getAllStats, _getLocalStats })).to.be(response);
expect(await getTelemetry(req, config, start, end, false, { _getAllStats, _getLocalStats })).to.be(response);
expect(config.get.calledOnce).to.be(true);
expect(_getAllStats.calledOnce).to.be(true);
@ -46,7 +46,7 @@ describe('telemetry', () => {
_getAllStats.withArgs(req, start, end).returns([]);
_getLocalStats.withArgs(req).returns(response);
expect(await getTelemetry(req, config, start, end, { _getAllStats, _getLocalStats })).to.eql([ response ]);
expect(await getTelemetry(req, config, start, end, false, { _getAllStats, _getLocalStats })).to.eql([ response ]);
expect(config.get.calledOnce).to.be(true);
expect(_getAllStats.calledOnce).to.be(true);
@ -60,7 +60,7 @@ describe('telemetry', () => {
_getAllStats.withArgs(req, start, end).returns({ not: 'an array' });
_getLocalStats.withArgs(req).returns(response);
expect(await getTelemetry(req, config, start, end, { _getAllStats, _getLocalStats })).to.eql([ response ]);
expect(await getTelemetry(req, config, start, end, false, { _getAllStats, _getLocalStats })).to.eql([ response ]);
expect(config.get.calledOnce).to.be(true);
expect(_getAllStats.calledOnce).to.be(true);
@ -73,7 +73,7 @@ describe('telemetry', () => {
config.get.withArgs('xpack.monitoring.enabled').returns(false);
_getLocalStats.withArgs(req).returns(response);
expect(await getTelemetry(req, config, start, end, { _getAllStats, _getLocalStats })).to.eql([ response ]);
expect(await getTelemetry(req, config, start, end, false, { _getAllStats, _getLocalStats })).to.eql([ response ]);
expect(config.get.calledOnce).to.be(true);
expect(_getAllStats.calledOnce).to.be(false);

View file

@ -0,0 +1,7 @@
/*
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
* or more contributor license agreements. Licensed under the Elastic License;
* you may not use this file except in compliance with the Elastic License.
*/
export { telemetryRoute } from './telemetry';

View file

@ -6,7 +6,7 @@
import Joi from 'joi';
import { boomify } from 'boom';
import { getAllStats, getLocalStats } from '../../../../lib/telemetry';
import { getAllStats, getLocalStats, encryptTelemetry } from '../../../../lib/telemetry';
/**
* Get the telemetry data.
@ -15,15 +15,18 @@ import { getAllStats, getLocalStats } from '../../../../lib/telemetry';
* @param {Object} config Kibana config.
* @param {String} start The start time of the request (likely 20m ago).
* @param {String} end The end time of the request.
* @param {Boolean} unencrypted Is the request payload going to be unencrypted.
* @return {Promise} An array of telemetry objects.
*/
export async function getTelemetry(req, config, start, end, { _getAllStats = getAllStats, _getLocalStats = getLocalStats } = {}) {
export async function getTelemetry(req, config, start, end, unencrypted, statsGetters = {}) {
const { _getAllStats = getAllStats, _getLocalStats = getLocalStats } = statsGetters;
let response = [];
const useInternalUser = !unencrypted;
if (config.get('xpack.monitoring.enabled')) {
try {
// attempt to collect stats from multiple clusters in monitoring data
response = await _getAllStats(req, start, end);
response = await _getAllStats(req, start, end, { useInternalUser });
} catch (err) {
// no-op
}
@ -31,7 +34,7 @@ export async function getTelemetry(req, config, start, end, { _getAllStats = get
if (!Array.isArray(response) || response.length === 0) {
// return it as an array for a consistent API response
response = [await _getLocalStats(req)];
response = [await _getLocalStats(req, { useInternalUser })];
}
return response;
@ -43,7 +46,7 @@ export function telemetryRoute(server) {
*/
server.route({
method: 'POST',
path: '/api/telemetry/v1/optIn',
path: '/api/telemetry/v2/optIn',
config: {
validate: {
payload: Joi.object({
@ -76,10 +79,11 @@ export function telemetryRoute(server) {
*/
server.route({
method: 'POST',
path: '/api/telemetry/v1/clusters/_stats',
path: '/api/telemetry/v2/clusters/_stats',
config: {
validate: {
payload: Joi.object({
unencrypted: Joi.bool(),
timeRange: Joi.object({
min: Joi.date().required(),
max: Joi.date().required()
@ -91,16 +95,21 @@ export function telemetryRoute(server) {
const config = req.server.config();
const start = req.payload.timeRange.min;
const end = req.payload.timeRange.max;
const unencrypted = req.payload.unencrypted;
const isDev = config.get('env.dev');
try {
return await getTelemetry(req, config, start, end);
const usageData = await getTelemetry(req, config, start, end, unencrypted);
if (unencrypted) return usageData;
return encryptTelemetry(usageData, isDev);
} catch (err) {
if (config.get('env.dev')) {
// don't ignore errors when running in dev mode
if (isDev) {
// don't ignore errors when running in dev mode
return boomify(err, { statusCode: err.status });
} else {
// ignore errors, return empty set and a 200
return h.response([]).code(200);
const statusCode = unencrypted && err.status === 403 ? 403 : 200;
// ignore errors and return empty set
return h.response([]).code(statusCode);
}
}
}

View file

@ -43,7 +43,7 @@ export default function featureControlsTests({ getService }: KibanaFunctionalTes
const basePath = spaceId ? `/s/${spaceId}` : '';
return await supertest
.post(`${basePath}/api/telemetry/v1/optIn`)
.post(`${basePath}/api/telemetry/v2/optIn`)
.auth(username, password)
.set('kbn-xsrf', 'foo')
.send({ enabled: true })

View file

@ -12,7 +12,7 @@ export default function ({ getService }) {
const supertest = getService('supertest');
const esArchiver = getService('esArchiver');
describe('/api/telemetry/v1/clusters/_stats', () => {
describe('/api/telemetry/v2/clusters/_stats', () => {
it('should load multiple trial-license clusters', async () => {
const archive = 'monitoring/multicluster';
const timeRange = {
@ -23,9 +23,9 @@ export default function ({ getService }) {
await esArchiver.load(archive);
const { body } = await supertest
.post('/api/telemetry/v1/clusters/_stats')
.post('/api/telemetry/v2/clusters/_stats')
.set('kbn-xsrf', 'xxx')
.send({ timeRange })
.send({ timeRange, unencrypted: true })
.expect(200);
expect(body).to.eql(multiClusterFixture);
@ -43,9 +43,9 @@ export default function ({ getService }) {
await esArchiver.load(archive);
const { body } = await supertest
.post('/api/telemetry/v1/clusters/_stats')
.post('/api/telemetry/v2/clusters/_stats')
.set('kbn-xsrf', 'xxx')
.send({ timeRange })
.send({ timeRange, unencrypted: true })
.expect(200);
expect(body).to.eql(basicClusterFixture);

View file

@ -39,7 +39,7 @@ export default function ({ getService }) {
const supertest = getService('supertest');
const esSupertest = getService('esSupertest');
describe('/api/telemetry/v1/clusters/_stats with monitoring disabled', () => {
describe('/api/telemetry/v2/clusters/_stats with monitoring disabled', () => {
before('', async () => {
await esSupertest.put('/_cluster/settings').send(disableCollection).expect(200);
await new Promise(r => setTimeout(r, 1000));
@ -52,9 +52,9 @@ export default function ({ getService }) {
};
const { body } = await supertest
.post('/api/telemetry/v1/clusters/_stats')
.post('/api/telemetry/v2/clusters/_stats')
.set('kbn-xsrf', 'xxx')
.send({ timeRange })
.send({ timeRange, unencrypted: true })
.expect(200);
expect(body.length).to.be(1);
@ -104,9 +104,9 @@ export default function ({ getService }) {
};
const { body } = await supertest
.post('/api/telemetry/v1/clusters/_stats')
.post('/api/telemetry/v2/clusters/_stats')
.set('kbn-xsrf', 'xxx')
.send({ timeRange })
.send({ timeRange, unencrypted: true })
.expect(200);
const stats = body[0];

View file

@ -0,0 +1,7 @@
/*
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
* or more contributor license agreements. Licensed under the Elastic License;
* you may not use this file except in compliance with the Elastic License.
*/
declare module '@elastic/node-crypto';

225
yarn.lock
View file

@ -1207,14 +1207,6 @@
pirates "^4.0.0"
source-map-support "^0.5.9"
"@babel/runtime-corejs2@^7.3.0":
version "7.3.4"
resolved "https://registry.yarnpkg.com/@babel/runtime-corejs2/-/runtime-corejs2-7.3.4.tgz#63f8bbc77622da202e9ea6f8f6e3bf28991832d9"
integrity sha512-QwPuQE65kNxjsNKk34Rfgen2R5fk0J2So99SD45uXBp34QOfyz11SqVgJ4xvyCpnCIieSQ0X0hSSc9z/ymlJJw==
dependencies:
core-js "^2.5.7"
regenerator-runtime "^0.12.0"
"@babel/runtime-corejs2@^7.4.2":
version "7.4.3"
resolved "https://registry.yarnpkg.com/@babel/runtime-corejs2/-/runtime-corejs2-7.4.3.tgz#40271fc260e570fb356da984e42e5990bd275860"
@ -1326,9 +1318,9 @@
to-fast-properties "^2.0.0"
"@elastic/charts@^3.11.2":
version "3.11.2"
resolved "https://registry.yarnpkg.com/@elastic/charts/-/charts-3.11.2.tgz#3644eeb7c0d17d6c368cfb380c2d2ef268655d72"
integrity sha512-ILU4ijT5GZC4usnbab/IVq1brQ6tFgkfBPwa4ixRtN5TlS+BWjPPVPFfIO3j4K9PijXE6XISfs8L2HkH1IDdtA==
version "3.11.4"
resolved "https://registry.yarnpkg.com/@elastic/charts/-/charts-3.11.4.tgz#174004301cf30cff495f9f10aea5dca9441d677b"
integrity sha512-9wwMO31M/oD50xDs+m7KIW773h0+Ydq2figtnJ9YRQ1V3S/Yr7OVwUWXYIEGKa3ZbaxEMMc8n8uMvPvPwtjOFg==
dependencies:
"@types/d3-shape" "^1.3.1"
"@types/luxon" "^1.11.1"
@ -1473,6 +1465,11 @@
through2 "^2.0.0"
update-notifier "^0.5.0"
"@elastic/node-crypto@^0.1.2":
version "0.1.2"
resolved "https://registry.yarnpkg.com/@elastic/node-crypto/-/node-crypto-0.1.2.tgz#c18ac282f635e88f041cc1555d806e492ca8f3b1"
integrity sha1-wYrCgvY16I8EHMFVXYBuSSyo87E=
"@elastic/node-crypto@^1.0.0":
version "1.0.0"
resolved "https://registry.yarnpkg.com/@elastic/node-crypto/-/node-crypto-1.0.0.tgz#4d325df333fe1319556bb4d54214098ada1171d4"
@ -1499,6 +1496,15 @@
resolved "https://registry.yarnpkg.com/@elastic/numeral/-/numeral-2.3.3.tgz#94d38a35bd315efa7a6918b22695128fc40a885e"
integrity sha512-0OyB9oztlYIq8F1LHjcNf+T089PKfYw78tgUY+q2dtox/jmb4xzFKtI9kv1hwAt5tcgBUTtUMK9kszpSh1UZaQ==
"@elastic/request-crypto@^1.0.2":
version "1.0.2"
resolved "https://registry.yarnpkg.com/@elastic/request-crypto/-/request-crypto-1.0.2.tgz#bf27bf009227166f3eeb2b5193a108752335ebd3"
integrity sha512-8FtGYl7LebhmJmEDWiGn3MorvNiGWSYPqhvgRlKXjNakEuLoPBBe0DHxbwLkj08CMLWczXcO2ixqBPY7fEhJpA==
dependencies:
"@elastic/node-crypto" "^0.1.2"
"@types/node-jose" "1.1.0"
node-jose "1.1.0"
"@elastic/ui-ace@0.2.3":
version "0.2.3"
resolved "https://registry.yarnpkg.com/@elastic/ui-ace/-/ui-ace-0.2.3.tgz#5281aed47a79b7216c55542b0675e435692f20cd"
@ -3407,9 +3413,9 @@
integrity sha512-+UzPmwHSEEyv7aGlNkVpuFxp/BirXgl8NnPGCtmyx2KXIzAapoW3IqSVk87/Z3PUk8vEL8Pe1HXEMJbNBOQgtg==
"@types/memoize-one@^4.1.0":
version "4.1.0"
resolved "https://registry.yarnpkg.com/@types/memoize-one/-/memoize-one-4.1.0.tgz#62119f26055b3193ae43ca1882c5b29b88b71ece"
integrity sha512-cmSgi6JMX/yBwgpVm4GooNWIH+vEeJoa8FAa6ExOhpJbC0Juq32/uYKiKb3VPSqrEA0aOnjvwZanla3O1WZMbw==
version "4.1.1"
resolved "https://registry.yarnpkg.com/@types/memoize-one/-/memoize-one-4.1.1.tgz#41dd138a4335b5041f7d8fc038f9d593d88b3369"
integrity sha512-+9djKUUn8hOyktLCfCy4hLaIPgDNovaU36fsnZe9trFHr6ddlbIn2q0SEsnkCkNR+pBWEU440Molz/+Mpyf+gQ==
"@types/mime-db@*":
version "1.27.0"
@ -3483,6 +3489,13 @@
dependencies:
"@types/node" "*"
"@types/node-jose@1.1.0":
version "1.1.0"
resolved "https://registry.yarnpkg.com/@types/node-jose/-/node-jose-1.1.0.tgz#26e1d234b41a39035482443ef35414bf34ba5d8b"
integrity sha512-ws1RJCQmIDww6/OEW1WcLCqDXZmNMWLoGgW5TEIR7pqJDaGrOkgH8hVw96Wx4rmu65r3/ZiDsb9LjpoizAnlww==
dependencies:
"@types/node" "*"
"@types/node@*", "@types/node@10.12.27", "@types/node@8.5.8", "@types/node@>=6.0.0", "@types/node@^10.12.27":
version "10.12.27"
resolved "https://registry.yarnpkg.com/@types/node/-/node-10.12.27.tgz#eb3843f15d0ba0986cc7e4d734d2ee8b50709ef8"
@ -3595,9 +3608,9 @@
integrity sha512-ce5d3q03Ex0sy4R14722Rmt6MT07Ua+k4FwDfdcToYJcMKNtRVQvJ6JCAPdAmAnbRb6CsX6aYb9m96NGod9uTw==
"@types/react-beautiful-dnd@^10.0.1":
version "10.0.3"
resolved "https://registry.yarnpkg.com/@types/react-beautiful-dnd/-/react-beautiful-dnd-10.0.3.tgz#baa05ebcb6a95cd0d55c9e92263177c0fcaba9f7"
integrity sha512-4h65dOwSC0GrhgmmEGh1p8Fwn6nHtFI3judbDMLDzTkRsPnBx6KwRFveJSMnCULh9gLhPB1ME1bQFEK4j3NgNA==
version "10.1.2"
resolved "https://registry.yarnpkg.com/@types/react-beautiful-dnd/-/react-beautiful-dnd-10.1.2.tgz#74069f7b1d0cb67b7af99a2584b30e496e545d8b"
integrity sha512-76M5VRbhduUarM9wyMWQm3tLKCVMKTlhG0+W67dteg/HBE+kueIwuyLWzE0m5fmuilvrDXoM5NL890KLnHETZw==
dependencies:
"@types/react" "*"
@ -4998,20 +5011,20 @@ apollo-link-dedup@^1.0.0:
apollo-link "^1.2.2"
apollo-link-error@^1.1.7:
version "1.1.8"
resolved "https://registry.yarnpkg.com/apollo-link-error/-/apollo-link-error-1.1.8.tgz#3a957b22b843cf6c307d516709cdc42371c9aafe"
integrity sha512-5hbMIBaINWOsZapWgTF8H2X0q3NjrQD/y4HlqDnUeLmT12OqejLasNh+EFE6q37/l28UHQu1/AuyRn15J7gvCA==
version "1.1.10"
resolved "https://registry.yarnpkg.com/apollo-link-error/-/apollo-link-error-1.1.10.tgz#ce57f0793f0923b598655de5bf5e028d4cf4fba6"
integrity sha512-itG5UV7mQqaalmRkuRsF0cUS4zW2ja8XCbxkMZnIEeN24X3yoJi5hpJeAaEkXf0KgYNsR0+rmtCQNruWyxDnZQ==
dependencies:
apollo-link "^1.2.9"
apollo-link-http-common "^0.2.11"
apollo-link "^1.2.11"
apollo-link-http-common "^0.2.13"
tslib "^1.9.3"
apollo-link-http-common@^0.2.11:
version "0.2.11"
resolved "https://registry.yarnpkg.com/apollo-link-http-common/-/apollo-link-http-common-0.2.11.tgz#d4e494ed1e45ea0e0c0ed60f3df64541d0de682d"
integrity sha512-FjtzEDiG6blH/2MR4fpVNoxdZUFmddP0sez34qnoLaYz6ABFbTDlmRE/dVN79nPExM4Spfs/DtW7KRqyjJ3tOg==
apollo-link-http-common@^0.2.13:
version "0.2.13"
resolved "https://registry.yarnpkg.com/apollo-link-http-common/-/apollo-link-http-common-0.2.13.tgz#c688f6baaffdc7b269b2db7ae89dae7c58b5b350"
integrity sha512-Uyg1ECQpTTA691Fwx5e6Rc/6CPSu4TB4pQRTGIpwZ4l5JDOQ+812Wvi/e3IInmzOZpwx5YrrOfXrtN8BrsDXoA==
dependencies:
apollo-link "^1.2.9"
apollo-link "^1.2.11"
ts-invariant "^0.3.2"
tslib "^1.9.3"
@ -5053,15 +5066,15 @@ apollo-link@^1.0.0, apollo-link@^1.2.2, apollo-link@^1.2.3:
apollo-utilities "^1.0.0"
zen-observable-ts "^0.8.10"
apollo-link@^1.2.9:
version "1.2.9"
resolved "https://registry.yarnpkg.com/apollo-link/-/apollo-link-1.2.9.tgz#40a8f0b90716ce3fd6beb27b7eae1108b92e0054"
integrity sha512-ZLUwthOFZq4lxchQ2jeBfVqS/UDdcVmmh8aUw6Ar9awZH4r+RgkcDeu2ooFLUfodWE3mZr7wIZuYsBas/MaNVA==
apollo-link@^1.2.11:
version "1.2.11"
resolved "https://registry.yarnpkg.com/apollo-link/-/apollo-link-1.2.11.tgz#493293b747ad3237114ccd22e9f559e5e24a194d"
integrity sha512-PQvRCg13VduLy3X/0L79M6uOpTh5iHdxnxYuo8yL7sJlWybKRJwsv4IcRBJpMFbChOOaHY7Og9wgPo6DLKDKDA==
dependencies:
apollo-utilities "^1.2.1"
ts-invariant "^0.3.2"
tslib "^1.9.3"
zen-observable-ts "^0.8.16"
zen-observable-ts "^0.8.18"
apollo-server-core@^1.3.6:
version "1.3.6"
@ -6273,6 +6286,11 @@ base64id@1.0.0:
resolved "https://registry.yarnpkg.com/base64id/-/base64id-1.0.0.tgz#47688cb99bb6804f0e06d3e763b1c32e57d8e6b6"
integrity sha1-R2iMuZu2gE8OBtPnY7HDLlfY5rY=
base64url@^3.0.0:
version "3.0.1"
resolved "https://registry.yarnpkg.com/base64url/-/base64url-3.0.1.tgz#6399d572e2bc3f90a9a8b22d5dbb0a32d33f788d"
integrity sha512-ir1UPr3dkwexU7FdV8qBBbNDRUhMmIekYMFZfi+C/sLNnRESKPl23nB9b2pltqfOQNnGzsDdId90AEtG5tCx4A==
base@^0.11.1:
version "0.11.2"
resolved "https://registry.yarnpkg.com/base/-/base-0.11.2.tgz#7bde5ced145b6d551a90db87f83c558b4eb48a8f"
@ -6733,16 +6751,7 @@ browserslist@4.4.1, browserslist@^4.0.1, browserslist@^4.3.4:
electron-to-chromium "^1.3.103"
node-releases "^1.1.3"
browserslist@^4.4.2:
version "4.4.2"
resolved "https://registry.yarnpkg.com/browserslist/-/browserslist-4.4.2.tgz#6ea8a74d6464bb0bd549105f659b41197d8f0ba2"
integrity sha512-ISS/AIAiHERJ3d45Fz0AVYKkgcy+F/eJHzKEvv1j0wwKGKD9T3BrwKr/5g45L+Y4XIK5PlTqefHciRFcfE1Jxg==
dependencies:
caniuse-lite "^1.0.30000939"
electron-to-chromium "^1.3.113"
node-releases "^1.1.8"
browserslist@^4.5.1:
browserslist@^4.4.2, browserslist@^4.5.1:
version "4.5.2"
resolved "https://registry.yarnpkg.com/browserslist/-/browserslist-4.5.2.tgz#36ad281f040af684555a23c780f5c2081c752df0"
integrity sha512-zmJVLiKLrzko0iszd/V4SsjTaomFeoVzQGYYOYgRgsbh7WNh95RgDB0CmBdFWYs/3MyFSt69NypjL/h3iaddKQ==
@ -7129,11 +7138,6 @@ caniuse-lite@^1.0.30000872, caniuse-lite@^1.0.30000929:
resolved "https://registry.yarnpkg.com/caniuse-lite/-/caniuse-lite-1.0.30000932.tgz#d01763e9ce77810962ca7391ff827b5949ce4272"
integrity sha512-4bghJFItvzz8m0T3lLZbacmEY9X1Z2AtIzTr7s7byqZIOumASfr4ynDx7rtm0J85nDmx8vsgR6vnaSoeU8Oh0A==
caniuse-lite@^1.0.30000939:
version "1.0.30000943"
resolved "https://registry.yarnpkg.com/caniuse-lite/-/caniuse-lite-1.0.30000943.tgz#00b25bd5808edc2ed1cfb53533a6a6ff6ca014ee"
integrity sha512-nJMjU4UaesbOHTcmz6VS+qaog++Fdepg4KAya5DL/AZrL/aaAZDGOOQ0AECtsJa09r4cJBdHZMive5mw8lnQ5A==
caniuse-lite@^1.0.30000947, caniuse-lite@^1.0.30000951:
version "1.0.30000954"
resolved "https://registry.yarnpkg.com/caniuse-lite/-/caniuse-lite-1.0.30000954.tgz#227c2743e40f07c71e6683b6ca9491bfd5755b8e"
@ -10199,11 +10203,6 @@ electron-to-chromium@^1.3.103:
resolved "https://registry.yarnpkg.com/electron-to-chromium/-/electron-to-chromium-1.3.108.tgz#2e79a6fcaa4b3e7c75abf871505bda8e268c910e"
integrity sha512-/QI4hMpAh48a1Sea6PALGv+kuVne9A2EWGd8HrWHMdYhIzGtbhVVHh6heL5fAzGaDnZuPyrlWJRl8WPm4RyiQQ==
electron-to-chromium@^1.3.113:
version "1.3.114"
resolved "https://registry.yarnpkg.com/electron-to-chromium/-/electron-to-chromium-1.3.114.tgz#1862887589db93f832057c81878c56c404960aa6"
integrity sha512-EQEFDVId4dqTrV9wvDmu/Po8Re9nN1sJm9KZECKRf3HC39DUYAEHQ8s7s9HsnhO9iFwl/Gpke9dvm6VwQTss5w==
electron-to-chromium@^1.3.116:
version "1.3.119"
resolved "https://registry.yarnpkg.com/electron-to-chromium/-/electron-to-chromium-1.3.119.tgz#9a7770da667252aeb81f667853f67c2b26e00197"
@ -10551,6 +10550,11 @@ es6-promise@^4.0.3, es6-promise@~4.2.4:
resolved "https://registry.yarnpkg.com/es6-promise/-/es6-promise-4.2.4.tgz#dc4221c2b16518760bd8c39a52d8f356fc00ed29"
integrity sha512-/NdNZVJg+uZgtm9eS3O6lrOLYmQag2DjdEXuPaHlZ6RuVqgqaVZfgYCepEIKsLqwdQArOPtC3XzRLqGGfT8KQQ==
es6-promise@^4.2.5:
version "4.2.6"
resolved "https://registry.yarnpkg.com/es6-promise/-/es6-promise-4.2.6.tgz#b685edd8258886365ea62b57d30de28fadcd974f"
integrity sha512-aRVgGdnmW2OiySVPUC9e6m+plolMAJKjZnQlCwNSuK5yQ0JN61DZSO1X1Ufd1foqWRAlig0rhduTCHe7sVtK5Q==
es6-promisify@^5.0.0:
version "5.0.0"
resolved "https://registry.yarnpkg.com/es6-promisify/-/es6-promisify-5.0.0.tgz#5109d62f3e56ea967c4b63505aef08291c8a5203"
@ -12127,9 +12131,9 @@ fp-ts@^1.0.0:
integrity sha512-fWwnAgVlTsV26Ruo9nx+fxNHIm6l1puE1VJ/C0XJ3nRQJJJIgRHYw6sigB3MuNFZL1o4fpGlhwFhcbxHK0RsOA==
fp-ts@^1.14.2:
version "1.17.0"
resolved "https://registry.yarnpkg.com/fp-ts/-/fp-ts-1.17.0.tgz#289127353ddbb4622ada1920d4ad6643182c1f1f"
integrity sha512-nBq25aCAMbCwVLobUUuM/MZihPKyjn0bCVBf6xMAGriHlf8W8Ze9UhyfLnbmfp0ekFTxMuTfLXrCzpJ34px7PQ==
version "1.17.3"
resolved "https://registry.yarnpkg.com/fp-ts/-/fp-ts-1.17.3.tgz#5064afc4bee8ddcaea567479bfc62d527e015825"
integrity sha512-r4gHfAWaRrYPsmdzRl1U9CkpbdOi8fPg5F5KiazAadENz5DKdWEaCDPl2Tf92fvkZGD/ekZ3EHu3gtXIVcsXtA==
fragment-cache@^0.2.1:
version "0.2.1"
@ -17197,6 +17201,11 @@ lodash.escape@^4.0.1:
resolved "https://registry.yarnpkg.com/lodash.escape/-/lodash.escape-4.0.1.tgz#c9044690c21e04294beaa517712fded1fa88de98"
integrity sha1-yQRGkMIeBClL6qUXcS/e0fqI3pg=
lodash.fill@^3.4.0:
version "3.4.0"
resolved "https://registry.yarnpkg.com/lodash.fill/-/lodash.fill-3.4.0.tgz#a3c74ae640d053adf0dc2079f8720788e8bfef85"
integrity sha1-o8dK5kDQU63w3CB5+HIHiOi/74U=
lodash.filter@^4.4.0:
version "4.6.0"
resolved "https://registry.yarnpkg.com/lodash.filter/-/lodash.filter-4.6.0.tgz#668b1d4981603ae1cc5a6fa760143e480b4c4ace"
@ -17227,6 +17236,11 @@ lodash.includes@^4.3.0:
resolved "https://registry.yarnpkg.com/lodash.includes/-/lodash.includes-4.3.0.tgz#60bb98a87cb923c68ca1e51325483314849f553f"
integrity sha1-YLuYqHy5I8aMoeUTJUgzFISfVT8=
lodash.intersection@^4.4.0:
version "4.4.0"
resolved "https://registry.yarnpkg.com/lodash.intersection/-/lodash.intersection-4.4.0.tgz#0a11ba631d0e95c23c7f2f4cbb9a692ed178e705"
integrity sha1-ChG6Yx0OlcI8fy9Mu5ppLtF45wU=
lodash.isarguments@^3.0.0:
version "3.1.0"
resolved "https://registry.yarnpkg.com/lodash.isarguments/-/lodash.isarguments-3.1.0.tgz#2f573d85c6a24289ff00663b491c1d338ff3458a"
@ -17306,7 +17320,7 @@ lodash.mean@^4.1.0:
resolved "https://registry.yarnpkg.com/lodash.mean/-/lodash.mean-4.1.0.tgz#bb985349628c0b9d7fe0f5fcc0011a2ee2c0dd7a"
integrity sha1-u5hTSWKMC51/4PX8wAEaLuLA3Xo=
lodash.merge@^4.4.0:
lodash.merge@^4.4.0, lodash.merge@^4.6.1:
version "4.6.1"
resolved "https://registry.yarnpkg.com/lodash.merge/-/lodash.merge-4.6.1.tgz#adc25d9cb99b9391c59624f379fbba60d7111d54"
integrity sha512-AOYza4+Hf5z1/0Hztxpm2/xiPZgi/cjMqdnKTUWTBSKchJlxXXuUSxCCl8rJlf4g6yww/j6mA8nC8Hw/EZWxKQ==
@ -17316,6 +17330,11 @@ lodash.mergewith@^4.6.0, lodash.mergewith@^4.6.1:
resolved "https://registry.yarnpkg.com/lodash.mergewith/-/lodash.mergewith-4.6.1.tgz#639057e726c3afbdb3e7d42741caa8d6e4335927"
integrity sha512-eWw5r+PYICtEBgrBE5hhlT6aAa75f411bgDz/ZL2KZqYV03USvucsxcHUIlGTDTECs1eunpI7HOV7U+WLDvNdQ==
lodash.omit@^4.5.0:
version "4.5.0"
resolved "https://registry.yarnpkg.com/lodash.omit/-/lodash.omit-4.5.0.tgz#6eb19ae5a1ee1dd9df0b969e66ce0b7fa30b5e60"
integrity sha1-brGa5aHuHdnfC5aeZs4Lf6MLXmA=
lodash.omitby@^4.6.0:
version "4.6.0"
resolved "https://registry.yarnpkg.com/lodash.omitby/-/lodash.omitby-4.6.0.tgz#5c15ff4754ad555016b53c041311e8f079204791"
@ -17346,6 +17365,11 @@ lodash.padstart@^4.1.0:
resolved "https://registry.yarnpkg.com/lodash.padstart/-/lodash.padstart-4.6.1.tgz#d2e3eebff0d9d39ad50f5cbd1b52a7bce6bb611b"
integrity sha1-0uPuv/DZ05rVD1y9G1KnvOa7YRs=
lodash.partialright@^4.2.1:
version "4.2.1"
resolved "https://registry.yarnpkg.com/lodash.partialright/-/lodash.partialright-4.2.1.tgz#0130d80e83363264d40074f329b8a3e7a8a1cc4b"
integrity sha1-ATDYDoM2MmTUAHTzKbij56ihzEs=
lodash.pick@^4.2.1, lodash.pick@^4.4.0:
version "4.4.0"
resolved "https://registry.yarnpkg.com/lodash.pick/-/lodash.pick-4.4.0.tgz#52f05610fff9ded422611441ed1fc123a03001b3"
@ -17564,6 +17588,11 @@ long@^2.4.0:
resolved "https://registry.yarnpkg.com/long/-/long-2.4.0.tgz#9fa180bb1d9500cdc29c4156766a1995e1f4524f"
integrity sha1-n6GAux2VAM3CnEFWdmoZleH0Uk8=
long@^4.0.0:
version "4.0.0"
resolved "https://registry.yarnpkg.com/long/-/long-4.0.0.tgz#9a7b71cfb7d361a194ea555241c92f7468d5bf28"
integrity sha512-XsP+KhQif4bjX1kbuSiySJFNAehNxgLb6hPRGJ9QsUr8ajHkuXGdrHmFUTUUXhDwVX2R5bY4JNZEwbUiMhV+MA==
longest@^1.0.1:
version "1.0.1"
resolved "https://registry.yarnpkg.com/longest/-/longest-1.0.1.tgz#30a0b2da38f73770e8294a0d22e6625ed77d0097"
@ -17672,9 +17701,9 @@ lru-queue@0.1:
es5-ext "~0.10.2"
luxon@^1.11.3:
version "1.12.1"
resolved "https://registry.yarnpkg.com/luxon/-/luxon-1.12.1.tgz#924bd61404f70b0cc5168918cb0ac108e52aacc4"
integrity sha512-Zv/qJb2X1ESTrlniAViWx2aqGwi2cVpeoZFTbPdPiCu4EsadKsmb/QCH8HQjMUpDZKKJIHKHsJxV5Rwpq47HKQ==
version "1.13.1"
resolved "https://registry.yarnpkg.com/luxon/-/luxon-1.13.1.tgz#b7fb7ba1e5c93ebda098af8d579314797e0a0d69"
integrity sha512-IQKRIiz9ldUrgcozN13SAeNZVYfD3bEI9X6TcrGu+dkgE4GR/Iik03ozbTM5cTr0lz8ucYPL2jtYT7Va2Flbsg==
lz-string@^1.4.4:
version "1.4.4"
@ -18825,6 +18854,11 @@ node-forge@0.7.1:
resolved "https://registry.yarnpkg.com/node-forge/-/node-forge-0.7.1.tgz#9da611ea08982f4b94206b3beb4cc9665f20c300"
integrity sha1-naYR6giYL0uUIGs760zJZl8gwwA=
node-forge@^0.7.6:
version "0.7.6"
resolved "https://registry.yarnpkg.com/node-forge/-/node-forge-0.7.6.tgz#fdf3b418aee1f94f0ef642cd63486c77ca9724ac"
integrity sha512-sol30LUpz1jQFBjOKwbjxijiE3b6pjd74YwfD0fJOKPjF+fONKb2Yg8rYgS6+bK6VDl+/wfr4IYpC7jDzLUIfw==
node-gyp@^3.8.0:
version "3.8.0"
resolved "https://registry.yarnpkg.com/node-gyp/-/node-gyp-3.8.0.tgz#540304261c330e80d0d5edce253a68cb3964218c"
@ -18848,6 +18882,27 @@ node-int64@^0.4.0:
resolved "https://registry.yarnpkg.com/node-int64/-/node-int64-0.4.0.tgz#87a9065cdb355d3182d8f94ce11188b825c68a3b"
integrity sha1-h6kGXNs1XTGC2PlM4RGIuCXGijs=
node-jose@1.1.0:
version "1.1.0"
resolved "https://registry.yarnpkg.com/node-jose/-/node-jose-1.1.0.tgz#15b0808ed7cf40cc6114c95c1a4d7394051c472a"
integrity sha512-Ux5MDElyiAlBQyOdFcwznK2TWMJbG8ZUfIZ28UtBvTB/VUz5HA/1WJV7s+YCah5NilPhkMkNi6xjnFRI+MQAVg==
dependencies:
base64url "^3.0.0"
es6-promise "^4.2.5"
lodash.assign "^4.2.0"
lodash.clone "^4.5.0"
lodash.fill "^3.4.0"
lodash.flatten "^4.4.0"
lodash.intersection "^4.4.0"
lodash.merge "^4.6.1"
lodash.omit "^4.5.0"
lodash.partialright "^4.2.1"
lodash.pick "^4.4.0"
lodash.uniq "^4.5.0"
long "^4.0.0"
node-forge "^0.7.6"
uuid "^3.3.2"
"node-libs-browser@^1.0.0 || ^2.0.0", node-libs-browser@^2.0.0:
version "2.1.0"
resolved "https://registry.yarnpkg.com/node-libs-browser/-/node-libs-browser-2.1.0.tgz#5f94263d404f6e44767d726901fff05478d600df"
@ -18938,13 +18993,6 @@ node-releases@^1.1.3:
dependencies:
semver "^5.3.0"
node-releases@^1.1.8:
version "1.1.10"
resolved "https://registry.yarnpkg.com/node-releases/-/node-releases-1.1.10.tgz#5dbeb6bc7f4e9c85b899e2e7adcc0635c9b2adf7"
integrity sha512-KbUPCpfoBvb3oBkej9+nrU0/7xPlVhmhhUJ1PZqwIP5/1dJkRWKWD3OONjo6M2J7tSCBtDCumLwwqeI+DWWaLQ==
dependencies:
semver "^5.3.0"
node-sass@^4.9.4:
version "4.9.4"
resolved "https://registry.yarnpkg.com/node-sass/-/node-sass-4.9.4.tgz#349bd7f1c89422ffe7e1e4b60f2055a69fbc5512"
@ -21358,21 +21406,7 @@ react-apollo@^2.1.4:
lodash "^4.17.10"
prop-types "^15.6.0"
react-beautiful-dnd@^10.0.1:
version "10.0.4"
resolved "https://registry.yarnpkg.com/react-beautiful-dnd/-/react-beautiful-dnd-10.0.4.tgz#56913a77706ad2d1b002041d9cb1ac3849a4ae89"
integrity sha512-j2Ra/mW48tXz1Mk6bNBuiENpRBt8wQcPbJgHSswmLDonUE8JPwVikaONoMavdowoMKQJoOJ9IwPTo82d/8aKKg==
dependencies:
"@babel/runtime-corejs2" "^7.3.0"
css-box-model "^1.1.1"
memoize-one "^5.0.0"
prop-types "^15.6.1"
raf-schd "^4.0.0"
react-redux "^5.0.7"
redux "^4.0.1"
tiny-invariant "^1.0.3"
react-beautiful-dnd@^10.1.0:
react-beautiful-dnd@^10.0.1, react-beautiful-dnd@^10.1.0:
version "10.1.1"
resolved "https://registry.yarnpkg.com/react-beautiful-dnd/-/react-beautiful-dnd-10.1.1.tgz#d753088d77d7632e77cf8a8935fafcffa38f574b"
integrity sha512-TdE06Shfp56wm28EzjgC56EEMgGI5PDHejJ2bxuAZvZr8CVsbksklsJC06Hxf0MSL7FHbflL/RpkJck9isuxHg==
@ -21732,13 +21766,13 @@ react-markdown@^3.4.1:
xtend "^4.0.1"
react-markdown@^4.0.6:
version "4.0.6"
resolved "https://registry.yarnpkg.com/react-markdown/-/react-markdown-4.0.6.tgz#927d44421735cd90b7634bb221e9d7d8656e01e9"
integrity sha512-E1d/q+OBk5eumId42oYqVrJRB/+whrZdk+YHqUBCCNeWxqeV+Qzt+yLTsft9+4HRDj89Od7eAbUPQBYq8ZwShQ==
version "4.0.8"
resolved "https://registry.yarnpkg.com/react-markdown/-/react-markdown-4.0.8.tgz#e3621b5becaac82a651008d7bc8390d3e4e438c0"
integrity sha512-Z6oa648rufvzyO0KwYJ/9p9AsdYGIluqK6OlpJ35ouJ8HPF0Ko1WDNdyymjDSHxNrkb7HDyEcIDJCQs8NlET5A==
dependencies:
html-to-react "^1.3.4"
mdast-add-list-metadata "1.0.1"
prop-types "^15.6.1"
prop-types "^15.7.2"
remark-parse "^5.0.0"
unified "^6.1.5"
unist-util-visit "^1.3.0"
@ -21966,11 +22000,12 @@ react-sizeme@^2.3.6:
lodash "^4.17.4"
react-spring@^8.0.8:
version "8.0.19"
resolved "https://registry.yarnpkg.com/react-spring/-/react-spring-8.0.19.tgz#62f4f396b4b73fa402838200a1c80374338cb12e"
integrity sha512-DjrwjXqqVEitj6e6GqdW5dUp1BoVyeFQhEcXvPfoQxwyIVSJ9smNt8CNjSvoQqRujVllE7XKaJRWSZO/ewd1/A==
version "8.0.20"
resolved "https://registry.yarnpkg.com/react-spring/-/react-spring-8.0.20.tgz#e25967f6059364b09cf0339168d73014e87c9d17"
integrity sha512-40ZUQ5uI5YHsoQWLPchWNcEUh6zQ6qvcVDeTI2vW10ldoCN3PvDsII9wBH2xEbMl+BQvYmHzGdfLTQxPxJWGnQ==
dependencies:
"@babel/runtime" "^7.3.1"
prop-types "^15.5.8"
react-sticky@^6.0.3:
version "6.0.3"
@ -25819,9 +25854,9 @@ ts-invariant@^0.2.1:
tslib "^1.9.3"
ts-invariant@^0.3.2:
version "0.3.2"
resolved "https://registry.yarnpkg.com/ts-invariant/-/ts-invariant-0.3.2.tgz#89a2ffeb70879b777258df1df1c59383c35209b0"
integrity sha512-QsY8BCaRnHiB5T6iE4DPlJMAKEG3gzMiUco9FEt1jUXQf0XP6zi0idT0i0rMTu8A326JqNSDsmlkA9dRSh1TRg==
version "0.3.3"
resolved "https://registry.yarnpkg.com/ts-invariant/-/ts-invariant-0.3.3.tgz#b5742b1885ecf9e29c31a750307480f045ec0b16"
integrity sha512-UReOKsrJFGC9tUblgSRWo+BsVNbEd77Cl6WiV/XpMlkifXwNIJbknViCucHvVZkXSC/mcWeRnIGdY7uprcwvdQ==
dependencies:
tslib "^1.9.3"
@ -28733,10 +28768,10 @@ zen-observable-ts@^0.8.10:
dependencies:
zen-observable "^0.8.0"
zen-observable-ts@^0.8.16:
version "0.8.16"
resolved "https://registry.yarnpkg.com/zen-observable-ts/-/zen-observable-ts-0.8.16.tgz#969367299074fe17422fe2f46ee417e9a30cf3fa"
integrity sha512-pQl75N7qwgybKVsh6WFO+WwPRijeQ52Gn1vSf4uvPFXald9CbVQXLa5QrOPEJhdZiC+CD4quqOVqSG+Ptz5XLA==
zen-observable-ts@^0.8.18:
version "0.8.18"
resolved "https://registry.yarnpkg.com/zen-observable-ts/-/zen-observable-ts-0.8.18.tgz#ade44b1060cc4a800627856ec10b9c67f5f639c8"
integrity sha512-q7d05s75Rn1j39U5Oapg3HI2wzriVwERVo4N7uFGpIYuHB9ff02P/E92P9B8T7QVC93jCMHpbXH7X0eVR5LA7A==
dependencies:
tslib "^1.9.3"
zen-observable "^0.8.0"