From 9e6ebc50a00fd7b66d4c11d2e98cd550a543b72d Mon Sep 17 00:00:00 2001 From: Dario Gieselaar Date: Thu, 14 May 2020 16:23:38 +0200 Subject: [PATCH 1/3] [APM] Add license guard for annotations (#65995) --- x-pack/plugins/apm/server/routes/services.ts | 13 ++-- x-pack/plugins/observability/kibana.json | 3 + .../lib/annotations/bootstrap_annotations.ts | 10 ++- .../annotations/create_annotations_client.ts | 74 +++++++++++-------- .../annotations/register_annotation_apis.ts | 26 +++++-- x-pack/scripts/functional_tests.js | 4 + x-pack/test/api_integration/apis/index.js | 2 - .../test/apm_api_integration/basic/config.ts | 14 ++++ .../basic/tests}/agent_configuration.ts | 3 +- .../basic/tests/annotations.ts | 52 +++++++++++++ .../basic/tests}/custom_link.ts | 5 +- .../basic/tests}/feature_controls.ts | 3 +- .../basic/tests}/index.ts | 8 +- .../test/apm_api_integration/common/config.ts | 38 ++++++++++ .../common/ftr_provider_context.ts | 7 ++ .../test/apm_api_integration/trial/config.ts | 14 ++++ .../trial/tests}/annotations.ts | 5 +- .../apm_api_integration/trial/tests/index.ts | 15 ++++ .../basic/config.ts | 14 ++++ .../basic/tests/annotations.ts | 51 +++++++++++++ .../basic/tests/index.ts | 15 ++++ .../common/config.ts | 38 ++++++++++ .../common/ftr_provider_context.ts | 7 ++ .../trial/config.ts | 14 ++++ .../trial/tests}/annotations.ts | 3 +- .../trial/tests/index.ts | 15 ++++ 26 files changed, 392 insertions(+), 61 deletions(-) create mode 100644 x-pack/test/apm_api_integration/basic/config.ts rename x-pack/test/{api_integration/apis/apm => apm_api_integration/basic/tests}/agent_configuration.ts (98%) create mode 100644 x-pack/test/apm_api_integration/basic/tests/annotations.ts rename x-pack/test/{api_integration/apis/apm => apm_api_integration/basic/tests}/custom_link.ts (96%) rename x-pack/test/{api_integration/apis/apm => apm_api_integration/basic/tests}/feature_controls.ts (98%) rename x-pack/test/{api_integration/apis/apm => apm_api_integration/basic/tests}/index.ts (73%) create mode 100644 x-pack/test/apm_api_integration/common/config.ts create mode 100644 x-pack/test/apm_api_integration/common/ftr_provider_context.ts create mode 100644 x-pack/test/apm_api_integration/trial/config.ts rename x-pack/test/{api_integration/apis/apm => apm_api_integration/trial/tests}/annotations.ts (98%) create mode 100644 x-pack/test/apm_api_integration/trial/tests/index.ts create mode 100644 x-pack/test/observability_api_integration/basic/config.ts create mode 100644 x-pack/test/observability_api_integration/basic/tests/annotations.ts create mode 100644 x-pack/test/observability_api_integration/basic/tests/index.ts create mode 100644 x-pack/test/observability_api_integration/common/config.ts create mode 100644 x-pack/test/observability_api_integration/common/ftr_provider_context.ts create mode 100644 x-pack/test/observability_api_integration/trial/config.ts rename x-pack/test/{api_integration/apis/observability => observability_api_integration/trial/tests}/annotations.ts (98%) create mode 100644 x-pack/test/observability_api_integration/trial/tests/index.ts diff --git a/x-pack/plugins/apm/server/routes/services.ts b/x-pack/plugins/apm/server/routes/services.ts index 474ab1c6082a44..782f8957cf188b 100644 --- a/x-pack/plugins/apm/server/routes/services.ts +++ b/x-pack/plugins/apm/server/routes/services.ts @@ -7,7 +7,6 @@ import * as t from 'io-ts'; import Boom from 'boom'; import { unique } from 'lodash'; -import { ScopedAnnotationsClient } from '../../../observability/server'; import { setupRequest } from '../lib/helpers/setup_request'; import { getServiceAgentName } from '../lib/services/get_service_agent_name'; import { getServices } from '../lib/services/get_services'; @@ -95,13 +94,10 @@ export const serviceAnnotationsRoute = createRoute(() => ({ const { serviceName } = context.params.path; const { environment } = context.params.query; - let annotationsClient: ScopedAnnotationsClient | undefined; - - if (context.plugins.observability) { - annotationsClient = await context.plugins.observability.getScopedAnnotationsClient( - request - ); - } + const annotationsClient = await context.plugins.observability?.getScopedAnnotationsClient( + context, + request + ); return getServiceAnnotations({ setup, @@ -143,6 +139,7 @@ export const serviceAnnotationsCreateRoute = createRoute(() => ({ }, handler: async ({ request, context }) => { const annotationsClient = await context.plugins.observability?.getScopedAnnotationsClient( + context, request ); diff --git a/x-pack/plugins/observability/kibana.json b/x-pack/plugins/observability/kibana.json index 8e2cfe980039ce..712a46f76bb747 100644 --- a/x-pack/plugins/observability/kibana.json +++ b/x-pack/plugins/observability/kibana.json @@ -6,6 +6,9 @@ "xpack", "observability" ], + "optionalPlugins": [ + "licensing" + ], "ui": true, "server": true } diff --git a/x-pack/plugins/observability/server/lib/annotations/bootstrap_annotations.ts b/x-pack/plugins/observability/server/lib/annotations/bootstrap_annotations.ts index 58639ef084ce65..6ea9f82d11ab91 100644 --- a/x-pack/plugins/observability/server/lib/annotations/bootstrap_annotations.ts +++ b/x-pack/plugins/observability/server/lib/annotations/bootstrap_annotations.ts @@ -3,7 +3,12 @@ * or more contributor license agreements. Licensed under the Elastic License; * you may not use this file except in compliance with the Elastic License. */ -import { CoreSetup, PluginInitializerContext, KibanaRequest } from 'kibana/server'; +import { + CoreSetup, + PluginInitializerContext, + KibanaRequest, + RequestHandlerContext, +} from 'kibana/server'; import { PromiseReturnType } from '../../../typings/common'; import { createAnnotationsClient } from './create_annotations_client'; import { registerAnnotationAPIs } from './register_annotation_apis'; @@ -31,11 +36,12 @@ export async function bootstrapAnnotations({ index, core, context }: Params) { }); return { - getScopedAnnotationsClient: (request: KibanaRequest) => { + getScopedAnnotationsClient: (requestContext: RequestHandlerContext, request: KibanaRequest) => { return createAnnotationsClient({ index, apiCaller: core.elasticsearch.dataClient.asScoped(request).callAsCurrentUser, logger, + license: requestContext.licensing?.license, }); }, }; diff --git a/x-pack/plugins/observability/server/lib/annotations/create_annotations_client.ts b/x-pack/plugins/observability/server/lib/annotations/create_annotations_client.ts index 3f2604468e17c5..71b1a42b2000d9 100644 --- a/x-pack/plugins/observability/server/lib/annotations/create_annotations_client.ts +++ b/x-pack/plugins/observability/server/lib/annotations/create_annotations_client.ts @@ -7,6 +7,8 @@ import { APICaller, Logger } from 'kibana/server'; import * as t from 'io-ts'; import { Client } from 'elasticsearch'; +import Boom from 'boom'; +import { ILicense } from '../../../../licensing/server'; import { createAnnotationRt, deleteAnnotationRt, @@ -40,8 +42,9 @@ export function createAnnotationsClient(params: { index: string; apiCaller: APICaller; logger: Logger; + license?: ILicense; }) { - const { index, apiCaller, logger } = params; + const { index, apiCaller, logger, license } = params; const initIndex = () => createOrUpdateIndex({ @@ -51,48 +54,59 @@ export function createAnnotationsClient(params: { logger, }); + function ensureGoldLicense any>(fn: T): T { + return ((...args) => { + if (!license?.hasAtLeast('gold')) { + throw Boom.forbidden('Annotations require at least a gold license or a trial license.'); + } + return fn(...args); + }) as T; + } + return { get index() { return index; }, - create: async ( - createParams: CreateParams - ): Promise<{ _id: string; _index: string; _source: Annotation }> => { - const indexExists = await apiCaller('indices.exists', { - index, - }); + create: ensureGoldLicense( + async ( + createParams: CreateParams + ): Promise<{ _id: string; _index: string; _source: Annotation }> => { + const indexExists = await apiCaller('indices.exists', { + index, + }); - if (!indexExists) { - await initIndex(); - } + if (!indexExists) { + await initIndex(); + } - const annotation = { - ...createParams, - event: { - created: new Date().toISOString(), - }, - }; + const annotation = { + ...createParams, + event: { + created: new Date().toISOString(), + }, + }; - const response = (await apiCaller('index', { - index, - body: annotation, - refresh: 'wait_for', - })) as IndexDocumentResponse; + const response = (await apiCaller('index', { + index, + body: annotation, + refresh: 'wait_for', + })) as IndexDocumentResponse; - return apiCaller('get', { - index, - id: response._id, - }); - }, - getById: async (getByIdParams: GetByIdParams) => { + return apiCaller('get', { + index, + id: response._id, + }); + } + ), + getById: ensureGoldLicense(async (getByIdParams: GetByIdParams) => { const { id } = getByIdParams; return apiCaller('get', { id, index, }); - }, - delete: async (deleteParams: DeleteParams) => { + }), + delete: ensureGoldLicense(async (deleteParams: DeleteParams) => { const { id } = deleteParams; const response = (await apiCaller('delete', { @@ -101,6 +115,6 @@ export function createAnnotationsClient(params: { refresh: 'wait_for', })) as PromiseReturnType; return response; - }, + }), }; } diff --git a/x-pack/plugins/observability/server/lib/annotations/register_annotation_apis.ts b/x-pack/plugins/observability/server/lib/annotations/register_annotation_apis.ts index 3c29822acd6dd0..21ebfcd6205e72 100644 --- a/x-pack/plugins/observability/server/lib/annotations/register_annotation_apis.ts +++ b/x-pack/plugins/observability/server/lib/annotations/register_annotation_apis.ts @@ -32,7 +32,7 @@ export function registerAnnotationAPIs({ handler: (params: { data: t.TypeOf; client: ScopedAnnotationsClient }) => Promise ): RequestHandler { return async (...args: Parameters) => { - const [, request, response] = args; + const [context, request, response] = args; const rt = types; @@ -56,16 +56,26 @@ export function registerAnnotationAPIs({ index, apiCaller, logger, + license: context.licensing?.license, }); - const res = await handler({ - data: validation.right, - client, - }); + try { + const res = await handler({ + data: validation.right, + client, + }); - return response.ok({ - body: res, - }); + return response.ok({ + body: res, + }); + } catch (err) { + return response.custom({ + statusCode: err.output?.statusCode ?? 500, + body: { + message: err.output?.payload?.message ?? 'An internal server error occured', + }, + }); + } }; } diff --git a/x-pack/scripts/functional_tests.js b/x-pack/scripts/functional_tests.js index 3ec7776f848afd..8ea554c42d6e0d 100644 --- a/x-pack/scripts/functional_tests.js +++ b/x-pack/scripts/functional_tests.js @@ -20,6 +20,8 @@ const onlyNotInCoverageTests = [ require.resolve('../test/alerting_api_integration/basic/config.ts'), require.resolve('../test/alerting_api_integration/spaces_only/config.ts'), require.resolve('../test/alerting_api_integration/security_and_spaces/config.ts'), + require.resolve('../test/apm_api_integration/basic/config.ts'), + require.resolve('../test/apm_api_integration/trial/config.ts'), require.resolve('../test/detection_engine_api_integration/security_and_spaces/config.ts'), require.resolve('../test/detection_engine_api_integration/basic/config.ts'), require.resolve('../test/plugin_api_integration/config.ts'), @@ -29,6 +31,8 @@ const onlyNotInCoverageTests = [ require.resolve('../test/token_api_integration/config.js'), require.resolve('../test/oidc_api_integration/config.ts'), require.resolve('../test/oidc_api_integration/implicit_flow.config.ts'), + require.resolve('../test/observability_api_integration/basic/config.ts'), + require.resolve('../test/observability_api_integration/trial/config.ts'), require.resolve('../test/pki_api_integration/config.ts'), require.resolve('../test/login_selector_api_integration/config.ts'), require.resolve('../test/encrypted_saved_objects_api_integration/config.ts'), diff --git a/x-pack/test/api_integration/apis/index.js b/x-pack/test/api_integration/apis/index.js index 75fa90bb4c3fe8..321fbce52a75a5 100644 --- a/x-pack/test/api_integration/apis/index.js +++ b/x-pack/test/api_integration/apis/index.js @@ -23,7 +23,6 @@ export default function({ loadTestFile }) { loadTestFile(require.resolve('./management')); loadTestFile(require.resolve('./uptime')); loadTestFile(require.resolve('./maps')); - loadTestFile(require.resolve('./apm')); loadTestFile(require.resolve('./siem')); loadTestFile(require.resolve('./short_urls')); loadTestFile(require.resolve('./lens')); @@ -31,6 +30,5 @@ export default function({ loadTestFile }) { loadTestFile(require.resolve('./ingest')); loadTestFile(require.resolve('./endpoint')); loadTestFile(require.resolve('./ml')); - loadTestFile(require.resolve('./observability')); }); } diff --git a/x-pack/test/apm_api_integration/basic/config.ts b/x-pack/test/apm_api_integration/basic/config.ts new file mode 100644 index 00000000000000..541fe9ec023bcf --- /dev/null +++ b/x-pack/test/apm_api_integration/basic/config.ts @@ -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; + * you may not use this file except in compliance with the Elastic License. + */ + +import { createTestConfig } from '../common/config'; + +// eslint-disable-next-line import/no-default-export +export default createTestConfig({ + license: 'basic', + name: 'X-Pack APM API integration tests (basic)', + testFiles: [require.resolve('./tests')], +}); diff --git a/x-pack/test/api_integration/apis/apm/agent_configuration.ts b/x-pack/test/apm_api_integration/basic/tests/agent_configuration.ts similarity index 98% rename from x-pack/test/api_integration/apis/apm/agent_configuration.ts rename to x-pack/test/apm_api_integration/basic/tests/agent_configuration.ts index 8af648e062cf44..04974d57074a56 100644 --- a/x-pack/test/api_integration/apis/apm/agent_configuration.ts +++ b/x-pack/test/apm_api_integration/basic/tests/agent_configuration.ts @@ -6,8 +6,9 @@ import expect from '@kbn/expect'; import { AgentConfigurationIntake } from '../../../../plugins/apm/common/agent_configuration/configuration_types'; -import { FtrProviderContext } from '../../ftr_provider_context'; +import { FtrProviderContext } from '../../common/ftr_provider_context'; +// eslint-disable-next-line import/no-default-export export default function agentConfigurationTests({ getService }: FtrProviderContext) { const supertest = getService('supertest'); const log = getService('log'); diff --git a/x-pack/test/apm_api_integration/basic/tests/annotations.ts b/x-pack/test/apm_api_integration/basic/tests/annotations.ts new file mode 100644 index 00000000000000..a939ef06273569 --- /dev/null +++ b/x-pack/test/apm_api_integration/basic/tests/annotations.ts @@ -0,0 +1,52 @@ +/* + * 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 expect from '@kbn/expect'; +import { JsonObject } from 'src/plugins/kibana_utils/common'; +import { FtrProviderContext } from '../../common/ftr_provider_context'; + +// eslint-disable-next-line import/no-default-export +export default function annotationApiTests({ getService }: FtrProviderContext) { + const supertest = getService('supertest'); + + function request({ method, url, data }: { method: string; url: string; data?: JsonObject }) { + switch (method.toLowerCase()) { + case 'post': + return supertest + .post(url) + .send(data) + .set('kbn-xsrf', 'foo'); + + default: + throw new Error(`Unsupported methoed ${method}`); + } + } + + describe('APM annotations with a basic license', () => { + describe('when creating an annotation', () => { + it('fails with a 403 forbidden', async () => { + const response = await request({ + url: '/api/apm/services/opbeans-java/annotation', + method: 'POST', + data: { + '@timestamp': new Date().toISOString(), + message: 'New deployment', + tags: ['foo'], + service: { + version: '1.1', + environment: 'production', + }, + }, + }); + + expect(response.status).to.be(403); + expect(response.body.message).to.be( + 'Annotations require at least a gold license or a trial license.' + ); + }); + }); + }); +} diff --git a/x-pack/test/api_integration/apis/apm/custom_link.ts b/x-pack/test/apm_api_integration/basic/tests/custom_link.ts similarity index 96% rename from x-pack/test/api_integration/apis/apm/custom_link.ts rename to x-pack/test/apm_api_integration/basic/tests/custom_link.ts index 8aefadd8117753..910c4797f39b76 100644 --- a/x-pack/test/api_integration/apis/apm/custom_link.ts +++ b/x-pack/test/apm_api_integration/basic/tests/custom_link.ts @@ -3,13 +3,12 @@ * or more contributor license agreements. Licensed under the Elastic License; * you may not use this file except in compliance with the Elastic License. */ -// import querystring from 'querystring'; -// import {isEmpty} from 'lodash' import URL from 'url'; import expect from '@kbn/expect'; import { CustomLink } from '../../../../plugins/apm/common/custom_link/custom_link_types'; -import { FtrProviderContext } from '../../ftr_provider_context'; +import { FtrProviderContext } from '../../common/ftr_provider_context'; +// eslint-disable-next-line import/no-default-export export default function customLinksTests({ getService }: FtrProviderContext) { const supertest = getService('supertest'); const log = getService('log'); diff --git a/x-pack/test/api_integration/apis/apm/feature_controls.ts b/x-pack/test/apm_api_integration/basic/tests/feature_controls.ts similarity index 98% rename from x-pack/test/api_integration/apis/apm/feature_controls.ts rename to x-pack/test/apm_api_integration/basic/tests/feature_controls.ts index 5f61c963a69aa0..f3647c65106c92 100644 --- a/x-pack/test/api_integration/apis/apm/feature_controls.ts +++ b/x-pack/test/apm_api_integration/basic/tests/feature_controls.ts @@ -5,8 +5,9 @@ */ import expect from '@kbn/expect'; -import { FtrProviderContext } from '../../ftr_provider_context'; +import { FtrProviderContext } from '../../common/ftr_provider_context'; +// eslint-disable-next-line import/no-default-export export default function featureControlsTests({ getService }: FtrProviderContext) { const supertest = getService('supertest'); const supertestWithoutAuth = getService('supertestWithoutAuth'); diff --git a/x-pack/test/api_integration/apis/apm/index.ts b/x-pack/test/apm_api_integration/basic/tests/index.ts similarity index 73% rename from x-pack/test/api_integration/apis/apm/index.ts rename to x-pack/test/apm_api_integration/basic/tests/index.ts index de076e8c46729e..f3c1a3c3f63d56 100644 --- a/x-pack/test/api_integration/apis/apm/index.ts +++ b/x-pack/test/apm_api_integration/basic/tests/index.ts @@ -3,11 +3,13 @@ * or more contributor license agreements. Licensed under the Elastic License; * you may not use this file except in compliance with the Elastic License. */ +import { FtrProviderContext } from '../../common/ftr_provider_context'; -import { FtrProviderContext } from '../../ftr_provider_context'; - +// eslint-disable-next-line import/no-default-export export default function apmApiIntegrationTests({ loadTestFile }: FtrProviderContext) { - describe('APM specs', () => { + describe('APM specs (basic)', function() { + this.tags('ciGroup1'); + loadTestFile(require.resolve('./annotations')); loadTestFile(require.resolve('./feature_controls')); loadTestFile(require.resolve('./agent_configuration')); diff --git a/x-pack/test/apm_api_integration/common/config.ts b/x-pack/test/apm_api_integration/common/config.ts new file mode 100644 index 00000000000000..9e011a98bbfcd4 --- /dev/null +++ b/x-pack/test/apm_api_integration/common/config.ts @@ -0,0 +1,38 @@ +/* + * 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 { FtrConfigProviderContext } from '@kbn/test/types/ftr'; + +interface Settings { + license: 'basic' | 'trial'; + testFiles: string[]; + name: string; +} + +export function createTestConfig(settings: Settings) { + const { testFiles, license, name } = settings; + + return async ({ readConfigFile }: FtrConfigProviderContext) => { + const xPackAPITestsConfig = await readConfigFile( + require.resolve('../../api_integration/config.js') + ); + + return { + testFiles, + servers: xPackAPITestsConfig.get('servers'), + services: xPackAPITestsConfig.get('services'), + junit: { + reportName: name, + }, + + esTestCluster: { + ...xPackAPITestsConfig.get('esTestCluster'), + license, + }, + kbnTestServer: xPackAPITestsConfig.get('kbnTestServer'), + }; + }; +} diff --git a/x-pack/test/apm_api_integration/common/ftr_provider_context.ts b/x-pack/test/apm_api_integration/common/ftr_provider_context.ts new file mode 100644 index 00000000000000..90600816d17114 --- /dev/null +++ b/x-pack/test/apm_api_integration/common/ftr_provider_context.ts @@ -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 { FtrProviderContext } from '../../api_integration/ftr_provider_context'; diff --git a/x-pack/test/apm_api_integration/trial/config.ts b/x-pack/test/apm_api_integration/trial/config.ts new file mode 100644 index 00000000000000..ca5b11d469c470 --- /dev/null +++ b/x-pack/test/apm_api_integration/trial/config.ts @@ -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; + * you may not use this file except in compliance with the Elastic License. + */ + +import { createTestConfig } from '../common/config'; + +// eslint-disable-next-line import/no-default-export +export default createTestConfig({ + license: 'trial', + name: 'X-Pack APM API integration tests (trial)', + testFiles: [require.resolve('./tests')], +}); diff --git a/x-pack/test/api_integration/apis/apm/annotations.ts b/x-pack/test/apm_api_integration/trial/tests/annotations.ts similarity index 98% rename from x-pack/test/api_integration/apis/apm/annotations.ts rename to x-pack/test/apm_api_integration/trial/tests/annotations.ts index 4746a7713f34b3..9beea6a53dd4d4 100644 --- a/x-pack/test/api_integration/apis/apm/annotations.ts +++ b/x-pack/test/apm_api_integration/trial/tests/annotations.ts @@ -7,10 +7,11 @@ import expect from '@kbn/expect'; import { merge, cloneDeep, isPlainObject } from 'lodash'; import { JsonObject } from 'src/plugins/kibana_utils/common'; -import { FtrProviderContext } from '../../ftr_provider_context'; +import { FtrProviderContext } from '../../common/ftr_provider_context'; const DEFAULT_INDEX_NAME = 'observability-annotations'; +// eslint-disable-next-line import/no-default-export export default function annotationApiTests({ getService }: FtrProviderContext) { const supertest = getService('supertest'); const es = getService('es'); @@ -42,7 +43,7 @@ export default function annotationApiTests({ getService }: FtrProviderContext) { } } - describe('APM annotations', () => { + describe('APM annotations with a trial license', () => { describe('when creating an annotation', () => { afterEach(async () => { const indexExists = (await es.indices.exists({ index: DEFAULT_INDEX_NAME })).body; diff --git a/x-pack/test/apm_api_integration/trial/tests/index.ts b/x-pack/test/apm_api_integration/trial/tests/index.ts new file mode 100644 index 00000000000000..3a4571afb3d4a7 --- /dev/null +++ b/x-pack/test/apm_api_integration/trial/tests/index.ts @@ -0,0 +1,15 @@ +/* + * 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 { FtrProviderContext } from '../../../api_integration/ftr_provider_context'; + +// eslint-disable-next-line import/no-default-export +export default function observabilityApiIntegrationTests({ loadTestFile }: FtrProviderContext) { + describe('APM specs (trial)', function() { + this.tags('ciGroup1'); + loadTestFile(require.resolve('./annotations')); + }); +} diff --git a/x-pack/test/observability_api_integration/basic/config.ts b/x-pack/test/observability_api_integration/basic/config.ts new file mode 100644 index 00000000000000..0e8bf1daaf9e61 --- /dev/null +++ b/x-pack/test/observability_api_integration/basic/config.ts @@ -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; + * you may not use this file except in compliance with the Elastic License. + */ + +import { createTestConfig } from '../common/config'; + +// eslint-disable-next-line import/no-default-export +export default createTestConfig({ + license: 'basic', + name: 'X-Pack Observability API integration tests (basic)', + testFiles: [require.resolve('./tests')], +}); diff --git a/x-pack/test/observability_api_integration/basic/tests/annotations.ts b/x-pack/test/observability_api_integration/basic/tests/annotations.ts new file mode 100644 index 00000000000000..cd86c8a0f2cda7 --- /dev/null +++ b/x-pack/test/observability_api_integration/basic/tests/annotations.ts @@ -0,0 +1,51 @@ +/* + * 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 expect from '@kbn/expect'; +import { JsonObject } from 'src/plugins/kibana_utils/common'; +import { FtrProviderContext } from '../../common/ftr_provider_context'; + +// eslint-disable-next-line import/no-default-export +export default function annotationApiTests({ getService }: FtrProviderContext) { + const supertest = getService('supertest'); + + function request({ method, url, data }: { method: string; url: string; data?: JsonObject }) { + switch (method.toLowerCase()) { + case 'post': + return supertest + .post(url) + .send(data) + .set('kbn-xsrf', 'foo'); + + default: + throw new Error(`Unsupported methoed ${method}`); + } + } + + describe('Observability annotations with a basic license', () => { + describe('when creating an annotation', () => { + it('fails with a 403 forbidden', async () => { + const response = await request({ + url: '/api/observability/annotation', + method: 'POST', + data: { + annotation: { + type: 'deployment', + }, + '@timestamp': new Date().toISOString(), + message: 'test message', + tags: ['apm'], + }, + }); + + expect(response.status).to.be(403); + expect(response.body.message).to.be( + 'Annotations require at least a gold license or a trial license.' + ); + }); + }); + }); +} diff --git a/x-pack/test/observability_api_integration/basic/tests/index.ts b/x-pack/test/observability_api_integration/basic/tests/index.ts new file mode 100644 index 00000000000000..a4c04a9229fa94 --- /dev/null +++ b/x-pack/test/observability_api_integration/basic/tests/index.ts @@ -0,0 +1,15 @@ +/* + * 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 { FtrProviderContext } from '../../common/ftr_provider_context'; + +// eslint-disable-next-line import/no-default-export +export default function observabilityApiIntegrationTests({ loadTestFile }: FtrProviderContext) { + describe('Observability specs (basic)', function() { + this.tags('ciGroup1'); + loadTestFile(require.resolve('./annotations')); + }); +} diff --git a/x-pack/test/observability_api_integration/common/config.ts b/x-pack/test/observability_api_integration/common/config.ts new file mode 100644 index 00000000000000..9e011a98bbfcd4 --- /dev/null +++ b/x-pack/test/observability_api_integration/common/config.ts @@ -0,0 +1,38 @@ +/* + * 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 { FtrConfigProviderContext } from '@kbn/test/types/ftr'; + +interface Settings { + license: 'basic' | 'trial'; + testFiles: string[]; + name: string; +} + +export function createTestConfig(settings: Settings) { + const { testFiles, license, name } = settings; + + return async ({ readConfigFile }: FtrConfigProviderContext) => { + const xPackAPITestsConfig = await readConfigFile( + require.resolve('../../api_integration/config.js') + ); + + return { + testFiles, + servers: xPackAPITestsConfig.get('servers'), + services: xPackAPITestsConfig.get('services'), + junit: { + reportName: name, + }, + + esTestCluster: { + ...xPackAPITestsConfig.get('esTestCluster'), + license, + }, + kbnTestServer: xPackAPITestsConfig.get('kbnTestServer'), + }; + }; +} diff --git a/x-pack/test/observability_api_integration/common/ftr_provider_context.ts b/x-pack/test/observability_api_integration/common/ftr_provider_context.ts new file mode 100644 index 00000000000000..90600816d17114 --- /dev/null +++ b/x-pack/test/observability_api_integration/common/ftr_provider_context.ts @@ -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 { FtrProviderContext } from '../../api_integration/ftr_provider_context'; diff --git a/x-pack/test/observability_api_integration/trial/config.ts b/x-pack/test/observability_api_integration/trial/config.ts new file mode 100644 index 00000000000000..c073e2e6af7fed --- /dev/null +++ b/x-pack/test/observability_api_integration/trial/config.ts @@ -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; + * you may not use this file except in compliance with the Elastic License. + */ + +import { createTestConfig } from '../common/config'; + +// eslint-disable-next-line import/no-default-export +export default createTestConfig({ + license: 'trial', + name: 'X-Pack Observability API integration tests (trial)', + testFiles: [require.resolve('./tests')], +}); diff --git a/x-pack/test/api_integration/apis/observability/annotations.ts b/x-pack/test/observability_api_integration/trial/tests/annotations.ts similarity index 98% rename from x-pack/test/api_integration/apis/observability/annotations.ts rename to x-pack/test/observability_api_integration/trial/tests/annotations.ts index 6d32162bfcc658..ad3bcdbfabd8b3 100644 --- a/x-pack/test/api_integration/apis/observability/annotations.ts +++ b/x-pack/test/observability_api_integration/trial/tests/annotations.ts @@ -8,10 +8,11 @@ import expect from '@kbn/expect'; import { JsonObject } from 'src/plugins/kibana_utils/common'; import { Annotation } from '../../../../plugins/observability/common/annotations'; import { ESSearchHit } from '../../../../plugins/apm/typings/elasticsearch'; -import { FtrProviderContext } from '../../ftr_provider_context'; +import { FtrProviderContext } from '../../common/ftr_provider_context'; const DEFAULT_INDEX_NAME = 'observability-annotations'; +// eslint-disable-next-line import/no-default-export export default function annotationApiTests({ getService }: FtrProviderContext) { const supertest = getService('supertest'); const es = getService('es'); diff --git a/x-pack/test/observability_api_integration/trial/tests/index.ts b/x-pack/test/observability_api_integration/trial/tests/index.ts new file mode 100644 index 00000000000000..d1acf4d98f7f97 --- /dev/null +++ b/x-pack/test/observability_api_integration/trial/tests/index.ts @@ -0,0 +1,15 @@ +/* + * 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 { FtrProviderContext } from '../../common/ftr_provider_context'; + +// eslint-disable-next-line import/no-default-export +export default function apmApiIntegrationTests({ loadTestFile }: FtrProviderContext) { + describe('Observability specs (trial)', function() { + this.tags('ciGroup1'); + loadTestFile(require.resolve('./annotations')); + }); +} From d2381eb8d29c7ce8a3a8c18271b83f22da58cf6d Mon Sep 17 00:00:00 2001 From: Andrew Cholakian Date: Thu, 14 May 2020 10:17:38 -0500 Subject: [PATCH 2/3] [Uptime] Only show ~latest checks on certs page/alert (#66349) * [Uptime] Only show ~latest checks on certs page/alert Fixes https://github.com/elastic/uptime/issues/199 Also fixes some duplicate constants used in the queries here. For default index we just use a literal `0` now since there's no way that'd ever change. * Change window to 5m * Fix dependency issues in FTR suite for certs page. * Ensures tests can run independently, and always navigate to the certs page * Ensures that the not_after field is consistent * Ensures that timespan field is always present in checked documents --- .../plugins/uptime/server/lib/alerts/tls.ts | 6 +- .../lib/requests/__tests__/get_certs.test.ts | 2 +- .../uptime/server/lib/requests/get_certs.ts | 2 +- .../uptime/server/rest_api/certs/certs.ts | 8 +-- .../apis/uptime/rest/helper/make_ping.ts | 7 ++- .../apis/uptime/rest/helper/make_tls.ts | 2 +- .../functional/apps/uptime/certificates.ts | 58 ++++++++++--------- 7 files changed, 45 insertions(+), 40 deletions(-) diff --git a/x-pack/plugins/uptime/server/lib/alerts/tls.ts b/x-pack/plugins/uptime/server/lib/alerts/tls.ts index c4464ff5752180..6aa9d1aa3c6457 100644 --- a/x-pack/plugins/uptime/server/lib/alerts/tls.ts +++ b/x-pack/plugins/uptime/server/lib/alerts/tls.ts @@ -12,12 +12,10 @@ import { updateState } from './common'; import { ACTION_GROUP_DEFINITIONS, DYNAMIC_SETTINGS_DEFAULTS } from '../../../common/constants'; import { Cert, CertResult } from '../../../common/runtime_types'; import { commonStateTranslations, tlsTranslations } from './translations'; +import { DEFAULT_FROM, DEFAULT_TO } from '../../rest_api/certs/certs'; const { TLS } = ACTION_GROUP_DEFINITIONS; -const DEFAULT_FROM = 'now-1d'; -const DEFAULT_TO = 'now'; -const DEFAULT_INDEX = 0; const DEFAULT_SIZE = 20; interface TlsAlertState { @@ -113,7 +111,7 @@ export const tlsAlertFactory: UptimeAlertTypeFactory = (_server, libs) => ({ dynamicSettings, from: DEFAULT_FROM, to: DEFAULT_TO, - index: DEFAULT_INDEX, + index: 0, size: DEFAULT_SIZE, notValidAfter: `now+${dynamicSettings?.certExpirationThreshold ?? DYNAMIC_SETTINGS_DEFAULTS.certExpirationThreshold}d`, diff --git a/x-pack/plugins/uptime/server/lib/requests/__tests__/get_certs.test.ts b/x-pack/plugins/uptime/server/lib/requests/__tests__/get_certs.test.ts index 689dce98859e15..5fa5c331d398e8 100644 --- a/x-pack/plugins/uptime/server/lib/requests/__tests__/get_certs.test.ts +++ b/x-pack/plugins/uptime/server/lib/requests/__tests__/get_certs.test.ts @@ -180,7 +180,7 @@ describe('getCerts', () => { }, Object { "range": Object { - "@timestamp": Object { + "monitor.timespan": Object { "gte": "now-2d", "lte": "now+1h", }, diff --git a/x-pack/plugins/uptime/server/lib/requests/get_certs.ts b/x-pack/plugins/uptime/server/lib/requests/get_certs.ts index 57a59936ddf7c6..4793d420cbfd83 100644 --- a/x-pack/plugins/uptime/server/lib/requests/get_certs.ts +++ b/x-pack/plugins/uptime/server/lib/requests/get_certs.ts @@ -51,7 +51,7 @@ export const getCerts: UMElasticsearchQueryFn = asyn }, { range: { - '@timestamp': { + 'monitor.timespan': { gte: from, lte: to, }, diff --git a/x-pack/plugins/uptime/server/rest_api/certs/certs.ts b/x-pack/plugins/uptime/server/rest_api/certs/certs.ts index a5ca6e264d299f..fb22d603a2d56e 100644 --- a/x-pack/plugins/uptime/server/rest_api/certs/certs.ts +++ b/x-pack/plugins/uptime/server/rest_api/certs/certs.ts @@ -9,10 +9,10 @@ import { API_URLS } from '../../../common/constants'; import { UMServerLibs } from '../../lib/lib'; import { UMRestApiRouteFactory } from '../types'; -const DEFAULT_INDEX = 0; +export const DEFAULT_FROM = 'now-5m'; +export const DEFAULT_TO = 'now'; + const DEFAULT_SIZE = 25; -const DEFAULT_FROM = 'now-1d'; -const DEFAULT_TO = 'now'; const DEFAULT_SORT = 'not_after'; const DEFAULT_DIRECTION = 'asc'; @@ -31,7 +31,7 @@ export const createGetCertsRoute: UMRestApiRouteFactory = (libs: UMServerLibs) = }), }, handler: async ({ callES, dynamicSettings }, _context, request, response): Promise => { - const index = request.query?.index ?? DEFAULT_INDEX; + const index = request.query?.index ?? 0; const size = request.query?.size ?? DEFAULT_SIZE; const from = request.query?.from ?? DEFAULT_FROM; const to = request.query?.to ?? DEFAULT_TO; diff --git a/x-pack/test/api_integration/apis/uptime/rest/helper/make_ping.ts b/x-pack/test/api_integration/apis/uptime/rest/helper/make_ping.ts index 908c571e07e067..f9bea050293fc3 100644 --- a/x-pack/test/api_integration/apis/uptime/rest/helper/make_ping.ts +++ b/x-pack/test/api_integration/apis/uptime/rest/helper/make_ping.ts @@ -18,6 +18,7 @@ export const makePing = async ( refresh: boolean = true, tls: boolean | TlsProps = false ) => { + const timestamp = new Date(); const baseDoc: any = { tcp: { rtt: { @@ -40,7 +41,7 @@ export const makePing = async ( ephemeral_id: '0d9a8dc6-f604-49e3-86a0-d8f9d6f2cbad', version: '8.0.0', }, - '@timestamp': new Date().toISOString(), + '@timestamp': timestamp.toISOString(), resolve: { rtt: { us: 350, @@ -88,6 +89,10 @@ export const makePing = async ( check_group: uuid.v4(), type: 'http', status: 'up', + timespan: { + gte: timestamp.toISOString(), + lt: new Date(timestamp.getTime() + 5000).toISOString, + }, }, event: { dataset: 'uptime', diff --git a/x-pack/test/api_integration/apis/uptime/rest/helper/make_tls.ts b/x-pack/test/api_integration/apis/uptime/rest/helper/make_tls.ts index 36064625220245..477c9857ca3637 100644 --- a/x-pack/test/api_integration/apis/uptime/rest/helper/make_tls.ts +++ b/x-pack/test/api_integration/apis/uptime/rest/helper/make_tls.ts @@ -39,7 +39,7 @@ export const makeTls = ({ valid = true, commonName = '*.elastic.co', expiry, sha server: { x509: { not_before: '2020-03-01T00:00:00.000Z', - not_after: '2020-05-30T12:00:00.000Z', + not_after: expiryDate, issuer: { distinguished_name: 'CN=DigiCert SHA2 High Assurance Server CA,OU=www.digicert.com,O=DigiCert Inc,C=US', diff --git a/x-pack/test/functional/apps/uptime/certificates.ts b/x-pack/test/functional/apps/uptime/certificates.ts index 93a8a852294b23..4a10955637844a 100644 --- a/x-pack/test/functional/apps/uptime/certificates.ts +++ b/x-pack/test/functional/apps/uptime/certificates.ts @@ -14,13 +14,9 @@ export default ({ getPageObjects, getService }: FtrProviderContext) => { const es = getService('es'); - // FLAKY: https://github.com/elastic/kibana/issues/65010 - describe.skip('certificate page', function() { - before(async () => { - await uptime.goToRoot(true); - }); - + describe('certificates', function() { beforeEach(async () => { + await uptime.goToRoot(true); await makeCheck({ es, tls: true }); await uptimeService.navigation.refreshApp(); }); @@ -30,33 +26,39 @@ export default ({ getPageObjects, getService }: FtrProviderContext) => { await uptimeService.navigation.goToCertificates(); }); - it('displays certificates', async () => { - await uptimeService.cert.hasCertificates(); - }); + describe('page', () => { + beforeEach(async () => { + await uptimeService.navigation.goToCertificates(); + }); - it('displays specific certificates', async () => { - const certId = getSha256(); - const { monitorId } = await makeCheck({ - es, - tls: { - sha256: certId, - }, + it('displays certificates', async () => { + await uptimeService.cert.hasCertificates(); }); - await uptimeService.navigation.refreshApp(); - await uptimeService.cert.certificateExists({ certId, monitorId }); - }); + it('displays specific certificates', async () => { + const certId = getSha256(); + const { monitorId } = await makeCheck({ + es, + tls: { + sha256: certId, + }, + }); - it('performs search against monitor id', async () => { - const certId = getSha256(); - const { monitorId } = await makeCheck({ - es, - tls: { - sha256: certId, - }, + await uptimeService.navigation.refreshApp(); + await uptimeService.cert.certificateExists({ certId, monitorId }); + }); + + it('performs search against monitor id', async () => { + const certId = getSha256(); + const { monitorId } = await makeCheck({ + es, + tls: { + sha256: certId, + }, + }); + await uptimeService.navigation.refreshApp(); + await uptimeService.cert.searchIsWorking(monitorId); }); - await uptimeService.navigation.refreshApp(); - await uptimeService.cert.searchIsWorking(monitorId); }); }); }; From 02a2d075afce96e5ee0b3ddfba3fb0bca3cdff36 Mon Sep 17 00:00:00 2001 From: Matthias Wilhelm Date: Thu, 14 May 2020 17:46:33 +0200 Subject: [PATCH 3/3] [Discover] Catch error when popularizeField fails (#62402) * Refactor index_patterns popularize function, so no message is displayed to the user --- .../index_patterns/index_pattern.ts | 17 ++++++++++++++++- 1 file changed, 16 insertions(+), 1 deletion(-) diff --git a/src/plugins/data/public/index_patterns/index_patterns/index_pattern.ts b/src/plugins/data/public/index_patterns/index_patterns/index_pattern.ts index 98ec4495cef29a..f3297f21c572a5 100644 --- a/src/plugins/data/public/index_patterns/index_patterns/index_pattern.ts +++ b/src/plugins/data/public/index_patterns/index_patterns/index_pattern.ts @@ -299,6 +299,13 @@ export class IndexPattern implements IIndexPattern { } async popularizeField(fieldName: string, unit = 1) { + /** + * This function is just used by Discover and it's high likely to be removed in the near future + * It doesn't use the save function to skip the error message that's displayed when + * a user adds several columns in a higher frequency that the changes can be persisted to ES + * resulting in 409 errors + */ + if (!this.id) return; const field = this.fields.getByName(fieldName); if (!field) { return; @@ -308,7 +315,15 @@ export class IndexPattern implements IIndexPattern { return; } field.count = count; - await this.save(); + + try { + const res = await this.savedObjectsClient.update(type, this.id, this.prepBody(), { + version: this.version, + }); + this.version = res._version; + } catch (e) { + // no need for an error message here + } } getNonScriptedFields() {