From e3f3f08bac55a84bed774575540ad7bfdc349620 Mon Sep 17 00:00:00 2001 From: restrry Date: Tue, 16 Jun 2020 15:49:32 +0200 Subject: [PATCH 01/32] add generic audit_trail service in core --- .../audit_trail/audit_trail_service.test.ts | 99 +++++++++++++++++++ .../server/audit_trail/audit_trail_service.ts | 65 ++++++++++++ src/core/server/audit_trail/index.ts | 28 ++++++ src/core/server/audit_trail/types.ts | 47 +++++++++ 4 files changed, 239 insertions(+) create mode 100644 src/core/server/audit_trail/audit_trail_service.test.ts create mode 100644 src/core/server/audit_trail/audit_trail_service.ts create mode 100644 src/core/server/audit_trail/index.ts create mode 100644 src/core/server/audit_trail/types.ts diff --git a/src/core/server/audit_trail/audit_trail_service.test.ts b/src/core/server/audit_trail/audit_trail_service.test.ts new file mode 100644 index 0000000000000..e6a95f1d4f23f --- /dev/null +++ b/src/core/server/audit_trail/audit_trail_service.test.ts @@ -0,0 +1,99 @@ +/* + * Licensed to Elasticsearch B.V. under one or more contributor + * license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright + * ownership. Elasticsearch B.V. licenses this file to you under + * the Apache License, Version 2.0 (the "License"); you may + * not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +import { AuditTrailService } from './audit_trail_service'; +import { mockCoreContext } from '../core_context.mock'; +import { httpServerMock } from '../http/http_server.mocks'; + +describe('AuditTrailService', () => { + const coreContext = mockCoreContext.create(); + + describe('#setup', () => { + describe('register', () => { + it('throws if registered the same auditor factory twice', () => { + const auditTrail = new AuditTrailService(coreContext); + const { register } = auditTrail.setup(); + const auditorFactory = { + asScoped() { + return { add: () => undefined }; + }, + }; + register(auditorFactory); + expect(() => register(auditorFactory)).toThrowErrorMatchingInlineSnapshot( + `"An auditor factory has been already registered"` + ); + }); + }); + }); + + describe('#start', () => { + describe('asScoped', () => { + it('initialize every auditor with a request', () => { + const scopedMockOne = jest.fn(() => ({ add: () => undefined })); + const auditorFactoryOne = { asScoped: scopedMockOne }; + const scopedMockTwo = jest.fn(() => ({ add: () => undefined })); + const auditorFactoryTwo = { asScoped: scopedMockTwo }; + + const auditTrail = new AuditTrailService(coreContext); + const { register } = auditTrail.setup(); + register(auditorFactoryOne); + register(auditorFactoryTwo); + + const { asScoped } = auditTrail.start(); + const kibanaRequest = httpServerMock.createKibanaRequest(); + asScoped(kibanaRequest); + + expect(scopedMockOne).toHaveBeenCalledWith(kibanaRequest); + expect(scopedMockTwo).toHaveBeenCalledWith(kibanaRequest); + }); + + it('passes auditable event to every auditor', () => { + const addEventMockOne = jest.fn(); + const auditorFactoryOne = { + asScoped() { + return { add: addEventMockOne }; + }, + }; + const addEventMockTwo = jest.fn(); + const auditorFactoryTwo = { + asScoped() { + return { add: addEventMockTwo }; + }, + }; + + const auditTrail = new AuditTrailService(coreContext); + const { register } = auditTrail.setup(); + register(auditorFactoryOne); + register(auditorFactoryTwo); + + const { asScoped } = auditTrail.start(); + const kibanaRequest = httpServerMock.createKibanaRequest(); + const auditor = asScoped(kibanaRequest); + const message = { + type: 'foo', + message: 'bar', + }; + auditor.add(message); + + expect(addEventMockOne).toHaveBeenLastCalledWith(message); + expect(addEventMockTwo).toHaveBeenLastCalledWith(message); + }); + }); + }); +}); diff --git a/src/core/server/audit_trail/audit_trail_service.ts b/src/core/server/audit_trail/audit_trail_service.ts new file mode 100644 index 0000000000000..7fca0e15f9a0c --- /dev/null +++ b/src/core/server/audit_trail/audit_trail_service.ts @@ -0,0 +1,65 @@ +/* + * Licensed to Elasticsearch B.V. under one or more contributor + * license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright + * ownership. Elasticsearch B.V. licenses this file to you under + * the Apache License, Version 2.0 (the "License"); you may + * not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +import { CoreService } from '../../types'; +import { CoreContext } from '../core_context'; +import { Logger } from '../logging'; +import { KibanaRequest } from '../http'; +import { + AuditorFactory, + AuditableEvent, + InternalAuditTrailServiceSetup, + InternalAuditTrailServiceStart, +} from './types'; + +export class AuditTrailService + implements CoreService { + private readonly log: Logger; + private readonly auditors: Set = new Set(); + + constructor(core: CoreContext) { + this.log = core.logger.get('audit_trail'); + } + + setup() { + return { + register: (auditor: AuditorFactory) => { + if (this.auditors.has(auditor)) { + throw new Error('An auditor factory has been already registered'); + } + this.auditors.add(auditor); + this.log.debug('An auditor factory has been registered'); + }, + }; + } + + start() { + return { + asScoped: (request: KibanaRequest) => { + const scopedAuditors = [...this.auditors].map((a) => a.asScoped(request)); + return { + add: (event: AuditableEvent) => { + scopedAuditors.forEach((a) => a.add(event)); + }, + }; + }, + }; + } + + stop() {} +} diff --git a/src/core/server/audit_trail/index.ts b/src/core/server/audit_trail/index.ts new file mode 100644 index 0000000000000..530da076e39b6 --- /dev/null +++ b/src/core/server/audit_trail/index.ts @@ -0,0 +1,28 @@ +/* + * Licensed to Elasticsearch B.V. under one or more contributor + * license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright + * ownership. Elasticsearch B.V. licenses this file to you under + * the Apache License, Version 2.0 (the "License"); you may + * not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +export { AuditTrailService } from './audit_trail_service'; +export { + AuditableEvent, + Auditor, + AuditorFactory, + AuditTrailSetup, + InternalAuditTrailServiceSetup, + InternalAuditTrailServiceStart, +} from './types'; diff --git a/src/core/server/audit_trail/types.ts b/src/core/server/audit_trail/types.ts new file mode 100644 index 0000000000000..911ab31495ff7 --- /dev/null +++ b/src/core/server/audit_trail/types.ts @@ -0,0 +1,47 @@ +/* + * Licensed to Elasticsearch B.V. under one or more contributor + * license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright + * ownership. Elasticsearch B.V. licenses this file to you under + * the Apache License, Version 2.0 (the "License"); you may + * not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +import { KibanaRequest } from '../http'; + +/** + * Event to audit. + * @public + * + * @remarks + * Not a complete interface. + */ +export interface AuditableEvent { + message: string; + type: string; +} + +export interface Auditor { + add(event: AuditableEvent): void; +} + +export interface AuditorFactory { + asScoped(request: KibanaRequest): Auditor; +} + +export interface InternalAuditTrailServiceSetup { + register(auditor: AuditorFactory): void; +} + +export type AuditTrailSetup = InternalAuditTrailServiceSetup; + +export type InternalAuditTrailServiceStart = AuditorFactory; From 52558cc82720b00b943ffb4c3d745611d0eb2999 Mon Sep 17 00:00:00 2001 From: Mikhail Shustov Date: Tue, 16 Jun 2020 15:57:27 +0200 Subject: [PATCH 02/32] expose auditTraik service to plugins --- src/core/server/index.ts | 5 +++++ src/core/server/internal_types.ts | 3 +++ src/core/server/plugins/plugin_context.ts | 1 + src/core/server/server.ts | 9 +++++++++ 4 files changed, 18 insertions(+) diff --git a/src/core/server/index.ts b/src/core/server/index.ts index 658c24f835020..8f3124314b64a 100644 --- a/src/core/server/index.ts +++ b/src/core/server/index.ts @@ -62,7 +62,9 @@ import { CapabilitiesSetup, CapabilitiesStart } from './capabilities'; import { UuidServiceSetup } from './uuid'; import { MetricsServiceSetup } from './metrics'; import { StatusServiceSetup } from './status'; +import { Auditor, AuditTrailSetup } from './audit_trail'; +export { AuditableEvent, Auditor, AuditorFactory, AuditTrailSetup } from './audit_trail'; export { bootstrap } from './bootstrap'; export { Capabilities, CapabilitiesProvider, CapabilitiesSwitcher } from './capabilities'; export { @@ -359,6 +361,7 @@ export interface RequestHandlerContext { uiSettings: { client: IUiSettingsClient; }; + auditor: Auditor; }; } @@ -395,6 +398,8 @@ export interface CoreSetup; + /** {@link AuditTrailSetup} */ + auditTrail: AuditTrailSetup; } /** diff --git a/src/core/server/internal_types.ts b/src/core/server/internal_types.ts index 09ec772a41756..80b082f4d88d8 100644 --- a/src/core/server/internal_types.ts +++ b/src/core/server/internal_types.ts @@ -34,6 +34,7 @@ import { InternalMetricsServiceSetup } from './metrics'; import { InternalRenderingServiceSetup } from './rendering'; import { InternalHttpResourcesSetup } from './http_resources'; import { InternalStatusServiceSetup } from './status'; +import { InternalAuditTrailServiceSetup, InternalAuditTrailServiceStart } from './audit_trail'; /** @internal */ export interface InternalCoreSetup { @@ -48,6 +49,7 @@ export interface InternalCoreSetup { uuid: UuidServiceSetup; rendering: InternalRenderingServiceSetup; httpResources: InternalHttpResourcesSetup; + auditTrail: InternalAuditTrailServiceSetup; } /** @@ -58,6 +60,7 @@ export interface InternalCoreStart { elasticsearch: ElasticsearchServiceStart; savedObjects: InternalSavedObjectsServiceStart; uiSettings: InternalUiSettingsServiceStart; + auditTrail: InternalAuditTrailServiceStart; } /** diff --git a/src/core/server/plugins/plugin_context.ts b/src/core/server/plugins/plugin_context.ts index f0db3a25e313d..870b9fe5fe188 100644 --- a/src/core/server/plugins/plugin_context.ts +++ b/src/core/server/plugins/plugin_context.ts @@ -186,6 +186,7 @@ export function createPluginSetupContext( getInstanceUuid: deps.uuid.getInstanceUuid, }, getStartServices: () => plugin.startDependencies, + auditTrail: deps.auditTrail, }; } diff --git a/src/core/server/server.ts b/src/core/server/server.ts index 6ca580083648f..553dfc9d69347 100644 --- a/src/core/server/server.ts +++ b/src/core/server/server.ts @@ -27,6 +27,7 @@ import { coreDeprecationProvider, } from './config'; import { CoreApp } from './core_app'; +import { AuditTrailService } from './audit_trail'; import { ElasticsearchService } from './elasticsearch'; import { HttpService } from './http'; import { HttpResourcesService } from './http_resources'; @@ -75,6 +76,7 @@ export class Server { private readonly httpResources: HttpResourcesService; private readonly status: StatusService; private readonly coreApp: CoreApp; + private readonly auditTrail: AuditTrailService; #pluginsInitialized?: boolean; private coreStart?: InternalCoreStart; @@ -102,6 +104,7 @@ export class Server { this.status = new StatusService(core); this.coreApp = new CoreApp(core); this.httpResources = new HttpResourcesService(core); + this.auditTrail = new AuditTrailService(core); } public async setup() { @@ -123,6 +126,7 @@ export class Server { pluginDependencies: new Map([...pluginTree, [this.legacy.legacyId, [...pluginTree.keys()]]]), }); + const auditTrailSetup = this.auditTrail.setup(); const uuidSetup = await this.uuid.setup(); const httpSetup = await this.http.setup({ @@ -176,6 +180,7 @@ export class Server { metrics: metricsSetup, rendering: renderingSetup, httpResources: httpResourcesSetup, + auditTrail: auditTrailSetup, }; const pluginsSetup = await this.plugins.setup(coreSetup); @@ -195,6 +200,8 @@ export class Server { public async start() { this.log.debug('starting server'); + const auditTrailStart = this.auditTrail.start(); + const elasticsearchStart = await this.elasticsearch.start(); const savedObjectsStart = await this.savedObjects.start({ elasticsearch: elasticsearchStart, @@ -208,6 +215,7 @@ export class Server { elasticsearch: elasticsearchStart, savedObjects: savedObjectsStart, uiSettings: uiSettingsStart, + auditTrail: auditTrailStart, }; const pluginsStart = await this.plugins.start(this.coreStart); @@ -264,6 +272,7 @@ export class Server { uiSettings: { client: coreStart.uiSettings.asScopedToClient(savedObjectsClient), }, + auditor: coreStart.auditTrail.asScoped(req), }; } ); From dcc67188571935d7832875c2c9f441c5a0074dc3 Mon Sep 17 00:00:00 2001 From: restrry Date: Tue, 16 Jun 2020 15:58:39 +0200 Subject: [PATCH 03/32] add auditTrail x-pack plugin --- x-pack/plugins/audit_trail/kibana.json | 10 +++ x-pack/plugins/audit_trail/server/config.ts | 18 ++++++ x-pack/plugins/audit_trail/server/index.ts | 13 ++++ x-pack/plugins/audit_trail/server/plugin.ts | 52 +++++++++++++++ .../server/services/audit_trail_client.ts | 63 +++++++++++++++++++ x-pack/plugins/audit_trail/server/types.ts | 25 ++++++++ 6 files changed, 181 insertions(+) create mode 100644 x-pack/plugins/audit_trail/kibana.json create mode 100644 x-pack/plugins/audit_trail/server/config.ts create mode 100644 x-pack/plugins/audit_trail/server/index.ts create mode 100644 x-pack/plugins/audit_trail/server/plugin.ts create mode 100644 x-pack/plugins/audit_trail/server/services/audit_trail_client.ts create mode 100644 x-pack/plugins/audit_trail/server/types.ts diff --git a/x-pack/plugins/audit_trail/kibana.json b/x-pack/plugins/audit_trail/kibana.json new file mode 100644 index 0000000000000..2041dbcd7d614 --- /dev/null +++ b/x-pack/plugins/audit_trail/kibana.json @@ -0,0 +1,10 @@ +{ + "id": "audiTrail", + "version": "8.0.0", + "kibanaVersion": "kibana", + "configPath": ["xpack", "audit_trail"], + "server": true, + "ui": false, + "requiredPlugins": ["licensing", "security", "spaces"], + "optionalPlugins": [] +} diff --git a/x-pack/plugins/audit_trail/server/config.ts b/x-pack/plugins/audit_trail/server/config.ts new file mode 100644 index 0000000000000..3b61dcb8581f5 --- /dev/null +++ b/x-pack/plugins/audit_trail/server/config.ts @@ -0,0 +1,18 @@ +/* + * 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 { schema, TypeOf } from '@kbn/config-schema'; +import { PluginConfigDescriptor } from 'kibana/server'; + +const configSchema = schema.object({ + enabled: schema.boolean({ defaultValue: true }), +}); + +export type ConfigType = TypeOf; + +export const config: PluginConfigDescriptor = { + schema: configSchema, +}; diff --git a/x-pack/plugins/audit_trail/server/index.ts b/x-pack/plugins/audit_trail/server/index.ts new file mode 100644 index 0000000000000..7db48823a0e29 --- /dev/null +++ b/x-pack/plugins/audit_trail/server/index.ts @@ -0,0 +1,13 @@ +/* + * 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 { PluginInitializerContext } from 'src/core/server'; +import { AuditTrailPlugin } from './plugin'; + +export { config } from './config'; +export const plugin = (initializerContext: PluginInitializerContext) => { + return new AuditTrailPlugin(initializerContext); +}; diff --git a/x-pack/plugins/audit_trail/server/plugin.ts b/x-pack/plugins/audit_trail/server/plugin.ts new file mode 100644 index 0000000000000..98858477065f1 --- /dev/null +++ b/x-pack/plugins/audit_trail/server/plugin.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 { Subject } from 'rxjs'; +import { + CoreSetup, + CoreStart, + KibanaRequest, + Logger, + Plugin, + PluginInitializerContext, +} from 'src/core/server'; + +import { AuditEvent } from './types'; +import { AuditTrailClient } from './services/audit_trail_client'; + +import { SecurityPluginSetup } from '../../security/server'; +import { SpacesPluginSetup } from '../../spaces/server'; + +interface DepsSetup { + security: SecurityPluginSetup; + spaces: SpacesPluginSetup; +} + +export class AuditTrailPlugin implements Plugin { + private readonly logger: Logger; + + constructor(private readonly context: PluginInitializerContext) { + this.logger = this.context.logger.get(); + } + + public async setup(core: CoreSetup, deps: DepsSetup) { + const depsApi = { + getCurrentUser: deps.security.authc.getCurrentUser, + getActiveSpace: deps.spaces.spacesService.getActiveSpace, + }; + + const event$ = new Subject(); + event$.subscribe((e) => this.logger.debug('', e)); + + core.auditTrail.register({ + asScoped(request: KibanaRequest) { + return new AuditTrailClient(request, event$, depsApi); + }, + }); + } + + public start(core: CoreStart) {} +} diff --git a/x-pack/plugins/audit_trail/server/services/audit_trail_client.ts b/x-pack/plugins/audit_trail/server/services/audit_trail_client.ts new file mode 100644 index 0000000000000..0ce7ca85bd794 --- /dev/null +++ b/x-pack/plugins/audit_trail/server/services/audit_trail_client.ts @@ -0,0 +1,63 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ +import { Subject } from 'rxjs'; +import { KibanaRequest, Auditor } from 'src/core/server'; +import { AuditableEvent, AuditEvent } from '../types'; + +import { SecurityPluginSetup } from '../../../security/server'; +import { SpacesPluginSetup } from '../../../spaces/server'; + +interface Deps { + getCurrentUser: SecurityPluginSetup['authc']['getCurrentUser']; + getActiveSpace: SpacesPluginSetup['spacesService']['getActiveSpace']; +} + +function tail(array: T[]) { + return array[array.length - 1]; +} + +async function safeCall(fn: () => T): Promise { + try { + return await fn(); + } catch { + return; + } +} + +export class AuditTrailClient implements Auditor { + private readonly scope: string[] = []; + constructor( + private readonly request: KibanaRequest, + private readonly event$: Subject, + private readonly deps: Deps + ) {} + + openScope(name: string) { + if (this.scope.includes(name)) { + throw new Error(`"${name}" scope is already opened`); + } + this.scope.push(name); + } + + closeScope(name: string) { + if (tail(this.scope) !== name) { + throw new Error(`Cannot close ${name} scope`); + } + this.scope.shift(); + } + + async add(event: AuditableEvent) { + const user = await safeCall(() => this.deps.getCurrentUser(this.request)); + const space = await safeCall(() => this.deps.getActiveSpace(this.request)); + + this.event$.next({ + message: event.message, + type: event.type, + user: user?.username, + space: space?.name, + }); + } +} diff --git a/x-pack/plugins/audit_trail/server/types.ts b/x-pack/plugins/audit_trail/server/types.ts new file mode 100644 index 0000000000000..228fd87c4fb0b --- /dev/null +++ b/x-pack/plugins/audit_trail/server/types.ts @@ -0,0 +1,25 @@ +/* + * 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. + */ + +/** + * Event to enhance with request context. Provided by an external plugin. + * @public + */ +export interface AuditableEvent { + message: string; + type: string; +} + +/** + * Event enhanced with request context data. Provided to an external consumer. + * @public + */ +export interface AuditEvent { + message: string; + type: string; + user?: string; + space?: string; +} From 26dc24340139476d66d99cab3b2ca973ebc32bd6 Mon Sep 17 00:00:00 2001 From: restrry Date: Tue, 16 Jun 2020 16:46:47 +0200 Subject: [PATCH 04/32] fix type errors --- .../audit_trail/audit_trail_service.mock.ts | 47 +++++++++++++++++++ src/core/server/index.ts | 2 +- src/core/server/legacy/legacy_service.test.ts | 3 ++ src/core/server/legacy/legacy_service.ts | 1 + src/core/server/mocks.ts | 5 ++ src/core/server/server.ts | 2 +- 6 files changed, 58 insertions(+), 2 deletions(-) create mode 100644 src/core/server/audit_trail/audit_trail_service.mock.ts diff --git a/src/core/server/audit_trail/audit_trail_service.mock.ts b/src/core/server/audit_trail/audit_trail_service.mock.ts new file mode 100644 index 0000000000000..b0e8bd0917f1d --- /dev/null +++ b/src/core/server/audit_trail/audit_trail_service.mock.ts @@ -0,0 +1,47 @@ +/* + * Licensed to Elasticsearch B.V. under one or more contributor + * license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright + * ownership. Elasticsearch B.V. licenses this file to you under + * the Apache License, Version 2.0 (the "License"); you may + * not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +import { AuditTrailSetup, InternalAuditTrailServiceStart, Auditor } from './types'; + +const createSetupContractMock = () => { + const mocked: jest.Mocked = { + register: jest.fn(), + }; + return mocked; +}; + +const createStartContractMock = () => { + const mocked: jest.Mocked = { + asScoped: jest.fn(), + }; + return mocked; +}; + +const createAuditorMock = () => { + const mocked: jest.Mocked = { + add: jest.fn(), + }; + return mocked; +}; + +export const auditTrailServiceMock = { + createSetupContract: createSetupContractMock, + createStartContract: createStartContractMock, + createAuditor: createAuditorMock, +}; diff --git a/src/core/server/index.ts b/src/core/server/index.ts index 8f3124314b64a..ab6e0ee0d61ec 100644 --- a/src/core/server/index.ts +++ b/src/core/server/index.ts @@ -361,7 +361,7 @@ export interface RequestHandlerContext { uiSettings: { client: IUiSettingsClient; }; - auditor: Auditor; + auditTrail: Auditor; }; } diff --git a/src/core/server/legacy/legacy_service.test.ts b/src/core/server/legacy/legacy_service.test.ts index d9a0ac5e4ecff..d997095f8f8d1 100644 --- a/src/core/server/legacy/legacy_service.test.ts +++ b/src/core/server/legacy/legacy_service.test.ts @@ -53,6 +53,7 @@ import { LegacyVars, LegacyServiceSetupDeps, LegacyServiceStartDeps } from './ty import { LegacyService } from './legacy_service'; import { coreMock } from '../mocks'; import { statusServiceMock } from '../status/status_service.mock'; +import { auditTrailServiceMock } from '../audit_trail/audit_trail_service.mock'; const MockKbnServer: jest.Mock = KbnServer as any; @@ -100,6 +101,7 @@ beforeEach(() => { metrics: metricsServiceMock.createInternalSetupContract(), uuid: uuidSetup, status: statusServiceMock.createInternalSetupContract(), + auditTrail: auditTrailServiceMock.createSetupContract(), }, plugins: { 'plugin-id': 'plugin-value' }, uiPlugins: { @@ -120,6 +122,7 @@ beforeEach(() => { startDeps = { core: { ...coreMock.createStart(), + auditTrail: auditTrailServiceMock.createStartContract(), savedObjects: savedObjectsServiceMock.createInternalStartContract(), plugins: { contracts: new Map() }, }, diff --git a/src/core/server/legacy/legacy_service.ts b/src/core/server/legacy/legacy_service.ts index 2ced8b4762406..6ca5d50dca15a 100644 --- a/src/core/server/legacy/legacy_service.ts +++ b/src/core/server/legacy/legacy_service.ts @@ -323,6 +323,7 @@ export class LegacyService implements CoreService { uuid: { getInstanceUuid: setupDeps.core.uuid.getInstanceUuid, }, + auditTrail: setupDeps.core.auditTrail, getStartServices: () => Promise.resolve([coreStart, startDeps.plugins, {}]), }; diff --git a/src/core/server/mocks.ts b/src/core/server/mocks.ts index b6e9ffef6f3f1..3696e0e97680d 100644 --- a/src/core/server/mocks.ts +++ b/src/core/server/mocks.ts @@ -36,6 +36,7 @@ import { capabilitiesServiceMock } from './capabilities/capabilities_service.moc import { metricsServiceMock } from './metrics/metrics_service.mock'; import { uuidServiceMock } from './uuid/uuid_service.mock'; import { statusServiceMock } from './status/status_service.mock'; +import { auditTrailServiceMock } from './audit_trail/audit_trail_service.mock'; export { httpServerMock } from './http/http_server.mocks'; export { httpResourcesMock } from './http_resources/http_resources_service.mock'; @@ -147,6 +148,7 @@ function createCoreSetupMock({ metrics: metricsServiceMock.createSetupContract(), uiSettings: uiSettingsMock, uuid: uuidServiceMock.createSetupContract(), + auditTrail: auditTrailServiceMock.createSetupContract(), getStartServices: jest .fn, object, any]>, []>() .mockResolvedValue([createCoreStartMock(), pluginStartDeps, pluginStartContract]), @@ -179,6 +181,7 @@ function createInternalCoreSetupMock() { httpResources: httpResourcesMock.createSetupContract(), rendering: renderingMock.createSetupContract(), uiSettings: uiSettingsServiceMock.createSetupContract(), + auditTrail: auditTrailServiceMock.createSetupContract(), }; return setupDeps; } @@ -189,6 +192,7 @@ function createInternalCoreStartMock() { elasticsearch: elasticsearchServiceMock.createStart(), savedObjects: savedObjectsServiceMock.createInternalStartContract(), uiSettings: uiSettingsServiceMock.createStartContract(), + auditTrail: auditTrailServiceMock.createStartContract(), }; return startDeps; } @@ -207,6 +211,7 @@ function createCoreRequestHandlerContextMock() { uiSettings: { client: uiSettingsServiceMock.createClient(), }, + auditTrail: auditTrailServiceMock.createAuditor(), }; } diff --git a/src/core/server/server.ts b/src/core/server/server.ts index 553dfc9d69347..0355ab56e1db6 100644 --- a/src/core/server/server.ts +++ b/src/core/server/server.ts @@ -272,7 +272,7 @@ export class Server { uiSettings: { client: coreStart.uiSettings.asScopedToClient(savedObjectsClient), }, - auditor: coreStart.auditTrail.asScoped(req), + auditTrail: coreStart.auditTrail.asScoped(req), }; } ); From 630a12e87bf64ee265479552f7c804a190ad3e64 Mon Sep 17 00:00:00 2001 From: restrry Date: Tue, 23 Jun 2020 10:50:45 +0200 Subject: [PATCH 05/32] update mocks --- src/core/server/legacy/legacy_service.test.ts | 1 - 1 file changed, 1 deletion(-) diff --git a/src/core/server/legacy/legacy_service.test.ts b/src/core/server/legacy/legacy_service.test.ts index 5bd658643682c..ac6491d8e79cd 100644 --- a/src/core/server/legacy/legacy_service.test.ts +++ b/src/core/server/legacy/legacy_service.test.ts @@ -122,7 +122,6 @@ beforeEach(() => { startDeps = { core: { ...coreMock.createInternalStart(), - savedObjects: savedObjectsServiceMock.createInternalStartContract(), plugins: { contracts: new Map() }, }, plugins: {}, From ee53ec3bc281e0650a6a42695d3d5abfda5ad8b1 Mon Sep 17 00:00:00 2001 From: restrry Date: Tue, 23 Jun 2020 13:59:33 +0200 Subject: [PATCH 06/32] expose asScoped interface via start. auditor via request context --- .../server/audit_trail/audit_trail_service.mock.ts | 4 ++-- src/core/server/audit_trail/audit_trail_service.ts | 10 ++-------- src/core/server/audit_trail/index.ts | 9 +-------- src/core/server/audit_trail/types.ts | 6 ++---- src/core/server/index.ts | 7 +++++-- src/core/server/internal_types.ts | 6 +++--- src/core/server/legacy/legacy_service.ts | 1 + src/core/server/mocks.ts | 3 ++- src/core/server/plugins/plugin_context.ts | 1 + src/core/server/server.ts | 2 +- 10 files changed, 20 insertions(+), 29 deletions(-) diff --git a/src/core/server/audit_trail/audit_trail_service.mock.ts b/src/core/server/audit_trail/audit_trail_service.mock.ts index b0e8bd0917f1d..fb3190fab1a1a 100644 --- a/src/core/server/audit_trail/audit_trail_service.mock.ts +++ b/src/core/server/audit_trail/audit_trail_service.mock.ts @@ -17,7 +17,7 @@ * under the License. */ -import { AuditTrailSetup, InternalAuditTrailServiceStart, Auditor } from './types'; +import { AuditTrailSetup, AuditTrailStart, Auditor } from './types'; const createSetupContractMock = () => { const mocked: jest.Mocked = { @@ -27,7 +27,7 @@ const createSetupContractMock = () => { }; const createStartContractMock = () => { - const mocked: jest.Mocked = { + const mocked: jest.Mocked = { asScoped: jest.fn(), }; return mocked; diff --git a/src/core/server/audit_trail/audit_trail_service.ts b/src/core/server/audit_trail/audit_trail_service.ts index 7fca0e15f9a0c..942976d08bc26 100644 --- a/src/core/server/audit_trail/audit_trail_service.ts +++ b/src/core/server/audit_trail/audit_trail_service.ts @@ -20,15 +20,9 @@ import { CoreService } from '../../types'; import { CoreContext } from '../core_context'; import { Logger } from '../logging'; import { KibanaRequest } from '../http'; -import { - AuditorFactory, - AuditableEvent, - InternalAuditTrailServiceSetup, - InternalAuditTrailServiceStart, -} from './types'; +import { AuditorFactory, AuditableEvent, AuditTrailSetup, AuditTrailStart } from './types'; -export class AuditTrailService - implements CoreService { +export class AuditTrailService implements CoreService { private readonly log: Logger; private readonly auditors: Set = new Set(); diff --git a/src/core/server/audit_trail/index.ts b/src/core/server/audit_trail/index.ts index 530da076e39b6..3f01e6fa3582d 100644 --- a/src/core/server/audit_trail/index.ts +++ b/src/core/server/audit_trail/index.ts @@ -18,11 +18,4 @@ */ export { AuditTrailService } from './audit_trail_service'; -export { - AuditableEvent, - Auditor, - AuditorFactory, - AuditTrailSetup, - InternalAuditTrailServiceSetup, - InternalAuditTrailServiceStart, -} from './types'; +export { AuditableEvent, Auditor, AuditorFactory, AuditTrailSetup, AuditTrailStart } from './types'; diff --git a/src/core/server/audit_trail/types.ts b/src/core/server/audit_trail/types.ts index 911ab31495ff7..b24c9a1a014f0 100644 --- a/src/core/server/audit_trail/types.ts +++ b/src/core/server/audit_trail/types.ts @@ -38,10 +38,8 @@ export interface AuditorFactory { asScoped(request: KibanaRequest): Auditor; } -export interface InternalAuditTrailServiceSetup { +export interface AuditTrailSetup { register(auditor: AuditorFactory): void; } -export type AuditTrailSetup = InternalAuditTrailServiceSetup; - -export type InternalAuditTrailServiceStart = AuditorFactory; +export type AuditTrailStart = AuditorFactory; diff --git a/src/core/server/index.ts b/src/core/server/index.ts index 5d0467308d803..2b2a99a332b0f 100644 --- a/src/core/server/index.ts +++ b/src/core/server/index.ts @@ -62,7 +62,7 @@ import { CapabilitiesSetup, CapabilitiesStart } from './capabilities'; import { UuidServiceSetup } from './uuid'; import { MetricsServiceSetup } from './metrics'; import { StatusServiceSetup } from './status'; -import { Auditor, AuditTrailSetup } from './audit_trail'; +import { Auditor, AuditTrailSetup, AuditTrailStart } from './audit_trail'; export { AuditableEvent, Auditor, AuditorFactory, AuditTrailSetup } from './audit_trail'; export { bootstrap } from './bootstrap'; @@ -363,7 +363,7 @@ export interface RequestHandlerContext { uiSettings: { client: IUiSettingsClient; }; - auditTrail: Auditor; + auditor: Auditor; }; } @@ -433,6 +433,8 @@ export interface CoreStart { savedObjects: SavedObjectsServiceStart; /** {@link UiSettingsServiceStart} */ uiSettings: UiSettingsServiceStart; + /** {@link AuditTrailSetup} */ + auditTrail: AuditTrailStart; } export { @@ -444,6 +446,7 @@ export { PluginsServiceStart, PluginOpaqueId, UuidServiceSetup, + AuditTrailStart, }; /** diff --git a/src/core/server/internal_types.ts b/src/core/server/internal_types.ts index 4dd09c23e53e9..a8768a9bff4bf 100644 --- a/src/core/server/internal_types.ts +++ b/src/core/server/internal_types.ts @@ -34,7 +34,7 @@ import { InternalMetricsServiceSetup } from './metrics'; import { InternalRenderingServiceSetup } from './rendering'; import { InternalHttpResourcesSetup } from './http_resources'; import { InternalStatusServiceSetup } from './status'; -import { InternalAuditTrailServiceSetup, InternalAuditTrailServiceStart } from './audit_trail'; +import { AuditTrailSetup, AuditTrailStart } from './audit_trail'; /** @internal */ export interface InternalCoreSetup { @@ -49,7 +49,7 @@ export interface InternalCoreSetup { uuid: UuidServiceSetup; rendering: InternalRenderingServiceSetup; httpResources: InternalHttpResourcesSetup; - auditTrail: InternalAuditTrailServiceSetup; + auditTrail: AuditTrailSetup; } /** @@ -61,7 +61,7 @@ export interface InternalCoreStart { http: InternalHttpServiceStart; savedObjects: InternalSavedObjectsServiceStart; uiSettings: InternalUiSettingsServiceStart; - auditTrail: InternalAuditTrailServiceStart; + auditTrail: AuditTrailStart; } /** diff --git a/src/core/server/legacy/legacy_service.ts b/src/core/server/legacy/legacy_service.ts index 47d152a647907..e3505fd95d038 100644 --- a/src/core/server/legacy/legacy_service.ts +++ b/src/core/server/legacy/legacy_service.ts @@ -277,6 +277,7 @@ export class LegacyService implements CoreService { getTypeRegistry: startDeps.core.savedObjects.getTypeRegistry, }, uiSettings: { asScopedToClient: startDeps.core.uiSettings.asScopedToClient }, + auditTrail: startDeps.core.auditTrail, }; const router = setupDeps.core.http.createRouter('', this.legacyId); diff --git a/src/core/server/mocks.ts b/src/core/server/mocks.ts index 13ec1ceccef91..1492f2041e9a7 100644 --- a/src/core/server/mocks.ts +++ b/src/core/server/mocks.ts @@ -142,6 +142,7 @@ function createCoreSetupMock({ function createCoreStartMock() { const mock: MockedKeys = { + auditTrail: auditTrailServiceMock.createStartContract(), capabilities: capabilitiesServiceMock.createStartContract(), elasticsearch: elasticsearchServiceMock.createStart(), http: httpServiceMock.createStartContract(), @@ -196,7 +197,7 @@ function createCoreRequestHandlerContextMock() { uiSettings: { client: uiSettingsServiceMock.createClient(), }, - auditTrail: auditTrailServiceMock.createAuditor(), + auditor: auditTrailServiceMock.createAuditor(), }; } diff --git a/src/core/server/plugins/plugin_context.ts b/src/core/server/plugins/plugin_context.ts index 24778dd44e5cc..e23737ca8f638 100644 --- a/src/core/server/plugins/plugin_context.ts +++ b/src/core/server/plugins/plugin_context.ts @@ -226,5 +226,6 @@ export function createPluginStartContext( uiSettings: { asScopedToClient: deps.uiSettings.asScopedToClient, }, + auditTrail: deps.auditTrail, }; } diff --git a/src/core/server/server.ts b/src/core/server/server.ts index 987de6dca774d..c0e707c37dc0e 100644 --- a/src/core/server/server.ts +++ b/src/core/server/server.ts @@ -275,7 +275,7 @@ export class Server { uiSettings: { client: coreStart.uiSettings.asScopedToClient(savedObjectsClient), }, - auditTrail: coreStart.auditTrail.asScoped(req), + auditor: coreStart.auditTrail.asScoped(req), }; } ); From 3cb26b690e3ac7fbdb3fc8b42142ce5d1b8f5985 Mon Sep 17 00:00:00 2001 From: Mikhail Shustov Date: Tue, 23 Jun 2020 14:00:48 +0200 Subject: [PATCH 07/32] use type from audit trail service --- .../audit_trail/server/services/audit_trail_client.ts | 4 ++-- x-pack/plugins/audit_trail/server/types.ts | 9 --------- 2 files changed, 2 insertions(+), 11 deletions(-) diff --git a/x-pack/plugins/audit_trail/server/services/audit_trail_client.ts b/x-pack/plugins/audit_trail/server/services/audit_trail_client.ts index 0ce7ca85bd794..41f66a2c7dce5 100644 --- a/x-pack/plugins/audit_trail/server/services/audit_trail_client.ts +++ b/x-pack/plugins/audit_trail/server/services/audit_trail_client.ts @@ -4,8 +4,8 @@ * you may not use this file except in compliance with the Elastic License. */ import { Subject } from 'rxjs'; -import { KibanaRequest, Auditor } from 'src/core/server'; -import { AuditableEvent, AuditEvent } from '../types'; +import { KibanaRequest, Auditor, AuditableEvent } from 'src/core/server'; +import { AuditEvent } from '../types'; import { SecurityPluginSetup } from '../../../security/server'; import { SpacesPluginSetup } from '../../../spaces/server'; diff --git a/x-pack/plugins/audit_trail/server/types.ts b/x-pack/plugins/audit_trail/server/types.ts index 228fd87c4fb0b..1f5c913eb080b 100644 --- a/x-pack/plugins/audit_trail/server/types.ts +++ b/x-pack/plugins/audit_trail/server/types.ts @@ -4,15 +4,6 @@ * you may not use this file except in compliance with the Elastic License. */ -/** - * Event to enhance with request context. Provided by an external plugin. - * @public - */ -export interface AuditableEvent { - message: string; - type: string; -} - /** * Event enhanced with request context data. Provided to an external consumer. * @public From 7212cf7c337dad2cb2a722c9b1348f5f4b4c4013 Mon Sep 17 00:00:00 2001 From: Mikhail Shustov Date: Tue, 23 Jun 2020 14:28:53 +0200 Subject: [PATCH 08/32] wrap getActiveSpace in safeCall only. it throws exception for non-authz --- .../plugins/audit_trail/server/services/audit_trail_client.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/x-pack/plugins/audit_trail/server/services/audit_trail_client.ts b/x-pack/plugins/audit_trail/server/services/audit_trail_client.ts index 41f66a2c7dce5..a2bec21295bf2 100644 --- a/x-pack/plugins/audit_trail/server/services/audit_trail_client.ts +++ b/x-pack/plugins/audit_trail/server/services/audit_trail_client.ts @@ -19,7 +19,7 @@ function tail(array: T[]) { return array[array.length - 1]; } -async function safeCall(fn: () => T): Promise { +async function safeCall(fn: () => T | Promise): Promise { try { return await fn(); } catch { @@ -50,7 +50,7 @@ export class AuditTrailClient implements Auditor { } async add(event: AuditableEvent) { - const user = await safeCall(() => this.deps.getCurrentUser(this.request)); + const user = this.deps.getCurrentUser(this.request); const space = await safeCall(() => this.deps.getActiveSpace(this.request)); this.event$.next({ From 4e5ef72a671d76e7e074428e6adedd594fc8bd37 Mon Sep 17 00:00:00 2001 From: Mikhail Shustov Date: Tue, 23 Jun 2020 15:33:41 +0200 Subject: [PATCH 09/32] pass message to log explicitly --- x-pack/plugins/audit_trail/server/plugin.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/x-pack/plugins/audit_trail/server/plugin.ts b/x-pack/plugins/audit_trail/server/plugin.ts index 98858477065f1..c9d5d9d29add4 100644 --- a/x-pack/plugins/audit_trail/server/plugin.ts +++ b/x-pack/plugins/audit_trail/server/plugin.ts @@ -39,7 +39,7 @@ export class AuditTrailPlugin implements Plugin { }; const event$ = new Subject(); - event$.subscribe((e) => this.logger.debug('', e)); + event$.subscribe(({ message, ...other }) => this.logger.debug(message, other)); core.auditTrail.register({ asScoped(request: KibanaRequest) { From 4c9e781c97d89992c2fb6ce97ed13d95f7696d77 Mon Sep 17 00:00:00 2001 From: restrry Date: Tue, 23 Jun 2020 15:38:25 +0200 Subject: [PATCH 10/32] update docs --- ...ibana-plugin-core-server.auditableevent.md | 25 +++++++++++ ...ugin-core-server.auditableevent.message.md | 11 +++++ ...-plugin-core-server.auditableevent.type.md | 11 +++++ .../kibana-plugin-core-server.auditor.add.md | 22 ++++++++++ .../kibana-plugin-core-server.auditor.md | 18 ++++++++ ...gin-core-server.auditorfactory.asscoped.md | 22 ++++++++++ ...ibana-plugin-core-server.auditorfactory.md | 18 ++++++++ ...bana-plugin-core-server.audittrailsetup.md | 18 ++++++++ ...in-core-server.audittrailsetup.register.md | 22 ++++++++++ ...bana-plugin-core-server.audittrailstart.md | 11 +++++ ...plugin-core-server.coresetup.audittrail.md | 13 ++++++ .../kibana-plugin-core-server.coresetup.md | 1 + ...plugin-core-server.corestart.audittrail.md | 13 ++++++ .../kibana-plugin-core-server.corestart.md | 1 + .../core/server/kibana-plugin-core-server.md | 5 +++ ...-core-server.requesthandlercontext.core.md | 1 + ...lugin-core-server.requesthandlercontext.md | 2 +- src/core/server/server.api.md | 42 +++++++++++++++++++ 18 files changed, 255 insertions(+), 1 deletion(-) create mode 100644 docs/development/core/server/kibana-plugin-core-server.auditableevent.md create mode 100644 docs/development/core/server/kibana-plugin-core-server.auditableevent.message.md create mode 100644 docs/development/core/server/kibana-plugin-core-server.auditableevent.type.md create mode 100644 docs/development/core/server/kibana-plugin-core-server.auditor.add.md create mode 100644 docs/development/core/server/kibana-plugin-core-server.auditor.md create mode 100644 docs/development/core/server/kibana-plugin-core-server.auditorfactory.asscoped.md create mode 100644 docs/development/core/server/kibana-plugin-core-server.auditorfactory.md create mode 100644 docs/development/core/server/kibana-plugin-core-server.audittrailsetup.md create mode 100644 docs/development/core/server/kibana-plugin-core-server.audittrailsetup.register.md create mode 100644 docs/development/core/server/kibana-plugin-core-server.audittrailstart.md create mode 100644 docs/development/core/server/kibana-plugin-core-server.coresetup.audittrail.md create mode 100644 docs/development/core/server/kibana-plugin-core-server.corestart.audittrail.md diff --git a/docs/development/core/server/kibana-plugin-core-server.auditableevent.md b/docs/development/core/server/kibana-plugin-core-server.auditableevent.md new file mode 100644 index 0000000000000..aa109c5064887 --- /dev/null +++ b/docs/development/core/server/kibana-plugin-core-server.auditableevent.md @@ -0,0 +1,25 @@ + + +[Home](./index.md) > [kibana-plugin-core-server](./kibana-plugin-core-server.md) > [AuditableEvent](./kibana-plugin-core-server.auditableevent.md) + +## AuditableEvent interface + +Event to audit. + +Signature: + +```typescript +export interface AuditableEvent +``` + +## Remarks + +Not a complete interface. + +## Properties + +| Property | Type | Description | +| --- | --- | --- | +| [message](./kibana-plugin-core-server.auditableevent.message.md) | string | | +| [type](./kibana-plugin-core-server.auditableevent.type.md) | string | | + diff --git a/docs/development/core/server/kibana-plugin-core-server.auditableevent.message.md b/docs/development/core/server/kibana-plugin-core-server.auditableevent.message.md new file mode 100644 index 0000000000000..3ac4167c6998b --- /dev/null +++ b/docs/development/core/server/kibana-plugin-core-server.auditableevent.message.md @@ -0,0 +1,11 @@ + + +[Home](./index.md) > [kibana-plugin-core-server](./kibana-plugin-core-server.md) > [AuditableEvent](./kibana-plugin-core-server.auditableevent.md) > [message](./kibana-plugin-core-server.auditableevent.message.md) + +## AuditableEvent.message property + +Signature: + +```typescript +message: string; +``` diff --git a/docs/development/core/server/kibana-plugin-core-server.auditableevent.type.md b/docs/development/core/server/kibana-plugin-core-server.auditableevent.type.md new file mode 100644 index 0000000000000..3748748366684 --- /dev/null +++ b/docs/development/core/server/kibana-plugin-core-server.auditableevent.type.md @@ -0,0 +1,11 @@ + + +[Home](./index.md) > [kibana-plugin-core-server](./kibana-plugin-core-server.md) > [AuditableEvent](./kibana-plugin-core-server.auditableevent.md) > [type](./kibana-plugin-core-server.auditableevent.type.md) + +## AuditableEvent.type property + +Signature: + +```typescript +type: string; +``` diff --git a/docs/development/core/server/kibana-plugin-core-server.auditor.add.md b/docs/development/core/server/kibana-plugin-core-server.auditor.add.md new file mode 100644 index 0000000000000..846c3fb8bfc48 --- /dev/null +++ b/docs/development/core/server/kibana-plugin-core-server.auditor.add.md @@ -0,0 +1,22 @@ + + +[Home](./index.md) > [kibana-plugin-core-server](./kibana-plugin-core-server.md) > [Auditor](./kibana-plugin-core-server.auditor.md) > [add](./kibana-plugin-core-server.auditor.add.md) + +## Auditor.add() method + +Signature: + +```typescript +add(event: AuditableEvent): void; +``` + +## Parameters + +| Parameter | Type | Description | +| --- | --- | --- | +| event | AuditableEvent | | + +Returns: + +`void` + diff --git a/docs/development/core/server/kibana-plugin-core-server.auditor.md b/docs/development/core/server/kibana-plugin-core-server.auditor.md new file mode 100644 index 0000000000000..07de631e31e7d --- /dev/null +++ b/docs/development/core/server/kibana-plugin-core-server.auditor.md @@ -0,0 +1,18 @@ + + +[Home](./index.md) > [kibana-plugin-core-server](./kibana-plugin-core-server.md) > [Auditor](./kibana-plugin-core-server.auditor.md) + +## Auditor interface + +Signature: + +```typescript +export interface Auditor +``` + +## Methods + +| Method | Description | +| --- | --- | +| [add(event)](./kibana-plugin-core-server.auditor.add.md) | | + diff --git a/docs/development/core/server/kibana-plugin-core-server.auditorfactory.asscoped.md b/docs/development/core/server/kibana-plugin-core-server.auditorfactory.asscoped.md new file mode 100644 index 0000000000000..4a60931e60940 --- /dev/null +++ b/docs/development/core/server/kibana-plugin-core-server.auditorfactory.asscoped.md @@ -0,0 +1,22 @@ + + +[Home](./index.md) > [kibana-plugin-core-server](./kibana-plugin-core-server.md) > [AuditorFactory](./kibana-plugin-core-server.auditorfactory.md) > [asScoped](./kibana-plugin-core-server.auditorfactory.asscoped.md) + +## AuditorFactory.asScoped() method + +Signature: + +```typescript +asScoped(request: KibanaRequest): Auditor; +``` + +## Parameters + +| Parameter | Type | Description | +| --- | --- | --- | +| request | KibanaRequest | | + +Returns: + +`Auditor` + diff --git a/docs/development/core/server/kibana-plugin-core-server.auditorfactory.md b/docs/development/core/server/kibana-plugin-core-server.auditorfactory.md new file mode 100644 index 0000000000000..32ef3fee5bfab --- /dev/null +++ b/docs/development/core/server/kibana-plugin-core-server.auditorfactory.md @@ -0,0 +1,18 @@ + + +[Home](./index.md) > [kibana-plugin-core-server](./kibana-plugin-core-server.md) > [AuditorFactory](./kibana-plugin-core-server.auditorfactory.md) + +## AuditorFactory interface + +Signature: + +```typescript +export interface AuditorFactory +``` + +## Methods + +| Method | Description | +| --- | --- | +| [asScoped(request)](./kibana-plugin-core-server.auditorfactory.asscoped.md) | | + diff --git a/docs/development/core/server/kibana-plugin-core-server.audittrailsetup.md b/docs/development/core/server/kibana-plugin-core-server.audittrailsetup.md new file mode 100644 index 0000000000000..818743ef7a299 --- /dev/null +++ b/docs/development/core/server/kibana-plugin-core-server.audittrailsetup.md @@ -0,0 +1,18 @@ + + +[Home](./index.md) > [kibana-plugin-core-server](./kibana-plugin-core-server.md) > [AuditTrailSetup](./kibana-plugin-core-server.audittrailsetup.md) + +## AuditTrailSetup interface + +Signature: + +```typescript +export interface AuditTrailSetup +``` + +## Methods + +| Method | Description | +| --- | --- | +| [register(auditor)](./kibana-plugin-core-server.audittrailsetup.register.md) | | + diff --git a/docs/development/core/server/kibana-plugin-core-server.audittrailsetup.register.md b/docs/development/core/server/kibana-plugin-core-server.audittrailsetup.register.md new file mode 100644 index 0000000000000..9e2f53f2d3798 --- /dev/null +++ b/docs/development/core/server/kibana-plugin-core-server.audittrailsetup.register.md @@ -0,0 +1,22 @@ + + +[Home](./index.md) > [kibana-plugin-core-server](./kibana-plugin-core-server.md) > [AuditTrailSetup](./kibana-plugin-core-server.audittrailsetup.md) > [register](./kibana-plugin-core-server.audittrailsetup.register.md) + +## AuditTrailSetup.register() method + +Signature: + +```typescript +register(auditor: AuditorFactory): void; +``` + +## Parameters + +| Parameter | Type | Description | +| --- | --- | --- | +| auditor | AuditorFactory | | + +Returns: + +`void` + diff --git a/docs/development/core/server/kibana-plugin-core-server.audittrailstart.md b/docs/development/core/server/kibana-plugin-core-server.audittrailstart.md new file mode 100644 index 0000000000000..4fb9f5cb93549 --- /dev/null +++ b/docs/development/core/server/kibana-plugin-core-server.audittrailstart.md @@ -0,0 +1,11 @@ + + +[Home](./index.md) > [kibana-plugin-core-server](./kibana-plugin-core-server.md) > [AuditTrailStart](./kibana-plugin-core-server.audittrailstart.md) + +## AuditTrailStart type + +Signature: + +```typescript +export declare type AuditTrailStart = AuditorFactory; +``` diff --git a/docs/development/core/server/kibana-plugin-core-server.coresetup.audittrail.md b/docs/development/core/server/kibana-plugin-core-server.coresetup.audittrail.md new file mode 100644 index 0000000000000..1aa7a75b7a086 --- /dev/null +++ b/docs/development/core/server/kibana-plugin-core-server.coresetup.audittrail.md @@ -0,0 +1,13 @@ + + +[Home](./index.md) > [kibana-plugin-core-server](./kibana-plugin-core-server.md) > [CoreSetup](./kibana-plugin-core-server.coresetup.md) > [auditTrail](./kibana-plugin-core-server.coresetup.audittrail.md) + +## CoreSetup.auditTrail property + +[AuditTrailSetup](./kibana-plugin-core-server.audittrailsetup.md) + +Signature: + +```typescript +auditTrail: AuditTrailSetup; +``` diff --git a/docs/development/core/server/kibana-plugin-core-server.coresetup.md b/docs/development/core/server/kibana-plugin-core-server.coresetup.md index 30c054345928b..43fd90858b6a6 100644 --- a/docs/development/core/server/kibana-plugin-core-server.coresetup.md +++ b/docs/development/core/server/kibana-plugin-core-server.coresetup.md @@ -16,6 +16,7 @@ export interface CoreSetupAuditTrailSetup | [AuditTrailSetup](./kibana-plugin-core-server.audittrailsetup.md) | | [capabilities](./kibana-plugin-core-server.coresetup.capabilities.md) | CapabilitiesSetup | [CapabilitiesSetup](./kibana-plugin-core-server.capabilitiessetup.md) | | [context](./kibana-plugin-core-server.coresetup.context.md) | ContextSetup | [ContextSetup](./kibana-plugin-core-server.contextsetup.md) | | [elasticsearch](./kibana-plugin-core-server.coresetup.elasticsearch.md) | ElasticsearchServiceSetup | [ElasticsearchServiceSetup](./kibana-plugin-core-server.elasticsearchservicesetup.md) | diff --git a/docs/development/core/server/kibana-plugin-core-server.corestart.audittrail.md b/docs/development/core/server/kibana-plugin-core-server.corestart.audittrail.md new file mode 100644 index 0000000000000..879e0df836190 --- /dev/null +++ b/docs/development/core/server/kibana-plugin-core-server.corestart.audittrail.md @@ -0,0 +1,13 @@ + + +[Home](./index.md) > [kibana-plugin-core-server](./kibana-plugin-core-server.md) > [CoreStart](./kibana-plugin-core-server.corestart.md) > [auditTrail](./kibana-plugin-core-server.corestart.audittrail.md) + +## CoreStart.auditTrail property + +[AuditTrailSetup](./kibana-plugin-core-server.audittrailsetup.md) + +Signature: + +```typescript +auditTrail: AuditTrailStart; +``` diff --git a/docs/development/core/server/kibana-plugin-core-server.corestart.md b/docs/development/core/server/kibana-plugin-core-server.corestart.md index 6a6bacf1eef40..2374531aca48a 100644 --- a/docs/development/core/server/kibana-plugin-core-server.corestart.md +++ b/docs/development/core/server/kibana-plugin-core-server.corestart.md @@ -16,6 +16,7 @@ export interface CoreStart | Property | Type | Description | | --- | --- | --- | +| [auditTrail](./kibana-plugin-core-server.corestart.audittrail.md) | AuditTrailStart | [AuditTrailSetup](./kibana-plugin-core-server.audittrailsetup.md) | | [capabilities](./kibana-plugin-core-server.corestart.capabilities.md) | CapabilitiesStart | [CapabilitiesStart](./kibana-plugin-core-server.capabilitiesstart.md) | | [elasticsearch](./kibana-plugin-core-server.corestart.elasticsearch.md) | ElasticsearchServiceStart | [ElasticsearchServiceStart](./kibana-plugin-core-server.elasticsearchservicestart.md) | | [http](./kibana-plugin-core-server.corestart.http.md) | HttpServiceStart | [HttpServiceStart](./kibana-plugin-core-server.httpservicestart.md) | diff --git a/docs/development/core/server/kibana-plugin-core-server.md b/docs/development/core/server/kibana-plugin-core-server.md index 0f1bbbe7176e5..19526520edca5 100644 --- a/docs/development/core/server/kibana-plugin-core-server.md +++ b/docs/development/core/server/kibana-plugin-core-server.md @@ -57,6 +57,10 @@ The plugin integrates with the core system via lifecycle events: `setup` | [APICaller](./kibana-plugin-core-server.apicaller.md) | | | [AssistanceAPIResponse](./kibana-plugin-core-server.assistanceapiresponse.md) | | | [AssistantAPIClientParams](./kibana-plugin-core-server.assistantapiclientparams.md) | | +| [AuditableEvent](./kibana-plugin-core-server.auditableevent.md) | Event to audit. | +| [Auditor](./kibana-plugin-core-server.auditor.md) | | +| [AuditorFactory](./kibana-plugin-core-server.auditorfactory.md) | | +| [AuditTrailSetup](./kibana-plugin-core-server.audittrailsetup.md) | | | [Authenticated](./kibana-plugin-core-server.authenticated.md) | | | [AuthNotHandled](./kibana-plugin-core-server.authnothandled.md) | | | [AuthRedirected](./kibana-plugin-core-server.authredirected.md) | | @@ -209,6 +213,7 @@ The plugin integrates with the core system via lifecycle events: `setup` | Type Alias | Description | | --- | --- | +| [AuditTrailStart](./kibana-plugin-core-server.audittrailstart.md) | | | [AuthenticationHandler](./kibana-plugin-core-server.authenticationhandler.md) | See [AuthToolkit](./kibana-plugin-core-server.authtoolkit.md). | | [AuthHeaders](./kibana-plugin-core-server.authheaders.md) | Auth Headers map | | [AuthResult](./kibana-plugin-core-server.authresult.md) | | diff --git a/docs/development/core/server/kibana-plugin-core-server.requesthandlercontext.core.md b/docs/development/core/server/kibana-plugin-core-server.requesthandlercontext.core.md index 7b887d6d421e4..db153f97f850b 100644 --- a/docs/development/core/server/kibana-plugin-core-server.requesthandlercontext.core.md +++ b/docs/development/core/server/kibana-plugin-core-server.requesthandlercontext.core.md @@ -20,5 +20,6 @@ core: { uiSettings: { client: IUiSettingsClient; }; + auditor: Auditor; }; ``` diff --git a/docs/development/core/server/kibana-plugin-core-server.requesthandlercontext.md b/docs/development/core/server/kibana-plugin-core-server.requesthandlercontext.md index 99be0676bcda3..d08d03f3d3b47 100644 --- a/docs/development/core/server/kibana-plugin-core-server.requesthandlercontext.md +++ b/docs/development/core/server/kibana-plugin-core-server.requesthandlercontext.md @@ -18,5 +18,5 @@ export interface RequestHandlerContext | Property | Type | Description | | --- | --- | --- | -| [core](./kibana-plugin-core-server.requesthandlercontext.core.md) | {
savedObjects: {
client: SavedObjectsClientContract;
typeRegistry: ISavedObjectTypeRegistry;
};
elasticsearch: {
legacy: {
client: IScopedClusterClient;
};
};
uiSettings: {
client: IUiSettingsClient;
};
} | | +| [core](./kibana-plugin-core-server.requesthandlercontext.core.md) | {
savedObjects: {
client: SavedObjectsClientContract;
typeRegistry: ISavedObjectTypeRegistry;
};
elasticsearch: {
legacy: {
client: IScopedClusterClient;
};
};
uiSettings: {
client: IUiSettingsClient;
};
auditor: Auditor;
} | | diff --git a/src/core/server/server.api.md b/src/core/server/server.api.md index 9dc3ac9b94d96..0e9c1a1878e1e 100644 --- a/src/core/server/server.api.md +++ b/src/core/server/server.api.md @@ -409,6 +409,43 @@ export interface AssistantAPIClientParams extends GenericParams { path: '/_migration/assistance'; } +// @public +export interface AuditableEvent { + // (undocumented) + message: string; + // (undocumented) + type: string; +} + +// Warning: (ae-missing-release-tag) "Auditor" is exported by the package, but it is missing a release tag (@alpha, @beta, @public, or @internal) +// +// @public (undocumented) +export interface Auditor { + // (undocumented) + add(event: AuditableEvent): void; +} + +// Warning: (ae-missing-release-tag) "AuditorFactory" is exported by the package, but it is missing a release tag (@alpha, @beta, @public, or @internal) +// +// @public (undocumented) +export interface AuditorFactory { + // (undocumented) + asScoped(request: KibanaRequest): Auditor; +} + +// Warning: (ae-missing-release-tag) "AuditTrailSetup" is exported by the package, but it is missing a release tag (@alpha, @beta, @public, or @internal) +// +// @public (undocumented) +export interface AuditTrailSetup { + // (undocumented) + register(auditor: AuditorFactory): void; +} + +// Warning: (ae-missing-release-tag) "AuditTrailStart" is exported by the package, but it is missing a release tag (@alpha, @beta, @public, or @internal) +// +// @public (undocumented) +export type AuditTrailStart = AuditorFactory; + // @public (undocumented) export interface Authenticated extends AuthResultParams { // (undocumented) @@ -626,6 +663,8 @@ export type CoreId = symbol; // @public export interface CoreSetup { + // (undocumented) + auditTrail: AuditTrailSetup; // (undocumented) capabilities: CapabilitiesSetup; // (undocumented) @@ -652,6 +691,8 @@ export interface CoreSetup Date: Thu, 25 Jun 2020 16:39:51 +0200 Subject: [PATCH 11/32] create one auditor per request --- .../audit_trail/audit_trail_service.mock.ts | 2 + .../audit_trail/audit_trail_service.test.ts | 35 ++++++----------- .../server/audit_trail/audit_trail_service.ts | 32 ++++++++++------ src/core/server/audit_trail/types.ts | 3 +- x-pack/plugins/audit_trail/server/plugin.ts | 2 +- .../server/services/audit_trail_client.ts | 38 ++++++------------- x-pack/plugins/audit_trail/server/types.ts | 1 + 7 files changed, 50 insertions(+), 63 deletions(-) diff --git a/src/core/server/audit_trail/audit_trail_service.mock.ts b/src/core/server/audit_trail/audit_trail_service.mock.ts index fb3190fab1a1a..ca0c329c33ed7 100644 --- a/src/core/server/audit_trail/audit_trail_service.mock.ts +++ b/src/core/server/audit_trail/audit_trail_service.mock.ts @@ -36,6 +36,7 @@ const createStartContractMock = () => { const createAuditorMock = () => { const mocked: jest.Mocked = { add: jest.fn(), + withScope: jest.fn(), }; return mocked; }; @@ -43,5 +44,6 @@ const createAuditorMock = () => { export const auditTrailServiceMock = { createSetupContract: createSetupContractMock, createStartContract: createStartContractMock, + createAuditorFactory: createStartContractMock, createAuditor: createAuditorMock, }; diff --git a/src/core/server/audit_trail/audit_trail_service.test.ts b/src/core/server/audit_trail/audit_trail_service.test.ts index e6a95f1d4f23f..080fa939a4336 100644 --- a/src/core/server/audit_trail/audit_trail_service.test.ts +++ b/src/core/server/audit_trail/audit_trail_service.test.ts @@ -18,6 +18,7 @@ */ import { AuditTrailService } from './audit_trail_service'; +import { AuditorFactory } from './types'; import { mockCoreContext } from '../core_context.mock'; import { httpServerMock } from '../http/http_server.mocks'; @@ -29,9 +30,9 @@ describe('AuditTrailService', () => { it('throws if registered the same auditor factory twice', () => { const auditTrail = new AuditTrailService(coreContext); const { register } = auditTrail.setup(); - const auditorFactory = { + const auditorFactory: AuditorFactory = { asScoped() { - return { add: () => undefined }; + return { add: () => undefined, withScope: (() => {}) as any }; }, }; register(auditorFactory); @@ -45,42 +46,31 @@ describe('AuditTrailService', () => { describe('#start', () => { describe('asScoped', () => { it('initialize every auditor with a request', () => { - const scopedMockOne = jest.fn(() => ({ add: () => undefined })); - const auditorFactoryOne = { asScoped: scopedMockOne }; - const scopedMockTwo = jest.fn(() => ({ add: () => undefined })); - const auditorFactoryTwo = { asScoped: scopedMockTwo }; + const scopedMock = jest.fn(() => ({ add: jest.fn(), withScope: jest.fn() })); + const auditorFactory = { asScoped: scopedMock }; const auditTrail = new AuditTrailService(coreContext); const { register } = auditTrail.setup(); - register(auditorFactoryOne); - register(auditorFactoryTwo); + register(auditorFactory); const { asScoped } = auditTrail.start(); const kibanaRequest = httpServerMock.createKibanaRequest(); asScoped(kibanaRequest); - expect(scopedMockOne).toHaveBeenCalledWith(kibanaRequest); - expect(scopedMockTwo).toHaveBeenCalledWith(kibanaRequest); + expect(scopedMock).toHaveBeenCalledWith(kibanaRequest); }); it('passes auditable event to every auditor', () => { - const addEventMockOne = jest.fn(); - const auditorFactoryOne = { - asScoped() { - return { add: addEventMockOne }; - }, - }; - const addEventMockTwo = jest.fn(); - const auditorFactoryTwo = { + const addEventMock = jest.fn(); + const auditorFactory = { asScoped() { - return { add: addEventMockTwo }; + return { add: addEventMock, withScope: jest.fn() }; }, }; const auditTrail = new AuditTrailService(coreContext); const { register } = auditTrail.setup(); - register(auditorFactoryOne); - register(auditorFactoryTwo); + register(auditorFactory); const { asScoped } = auditTrail.start(); const kibanaRequest = httpServerMock.createKibanaRequest(); @@ -91,8 +81,7 @@ describe('AuditTrailService', () => { }; auditor.add(message); - expect(addEventMockOne).toHaveBeenLastCalledWith(message); - expect(addEventMockTwo).toHaveBeenLastCalledWith(message); + expect(addEventMock).toHaveBeenLastCalledWith(message); }); }); }); diff --git a/src/core/server/audit_trail/audit_trail_service.ts b/src/core/server/audit_trail/audit_trail_service.ts index 942976d08bc26..b6455a19189e4 100644 --- a/src/core/server/audit_trail/audit_trail_service.ts +++ b/src/core/server/audit_trail/audit_trail_service.ts @@ -19,12 +19,24 @@ import { CoreService } from '../../types'; import { CoreContext } from '../core_context'; import { Logger } from '../logging'; -import { KibanaRequest } from '../http'; -import { AuditorFactory, AuditableEvent, AuditTrailSetup, AuditTrailStart } from './types'; +import { KibanaRequest, LegacyRequest } from '../http'; +import { ensureRawRequest } from '../http/router'; +import { Auditor, AuditorFactory, AuditTrailSetup, AuditTrailStart } from './types'; +const defaultAuditorFactory: AuditorFactory = { + asScoped() { + return { + add() {}, + withScope(name, fn) { + return fn(); + }, + }; + }, +}; export class AuditTrailService implements CoreService { private readonly log: Logger; - private readonly auditors: Set = new Set(); + private auditor: AuditorFactory = defaultAuditorFactory; + private readonly auditors = new WeakMap(); constructor(core: CoreContext) { this.log = core.logger.get('audit_trail'); @@ -33,10 +45,10 @@ export class AuditTrailService implements CoreService { - if (this.auditors.has(auditor)) { + if (this.auditor !== defaultAuditorFactory) { throw new Error('An auditor factory has been already registered'); } - this.auditors.add(auditor); + this.auditor = auditor; this.log.debug('An auditor factory has been registered'); }, }; @@ -45,12 +57,10 @@ export class AuditTrailService implements CoreService { - const scopedAuditors = [...this.auditors].map((a) => a.asScoped(request)); - return { - add: (event: AuditableEvent) => { - scopedAuditors.forEach((a) => a.add(event)); - }, - }; + if (!this.auditors.has(ensureRawRequest(request))) { + this.auditors.set(ensureRawRequest(request), this.auditor!.asScoped(request)); + } + return this.auditors.get(ensureRawRequest(request))!; }, }; } diff --git a/src/core/server/audit_trail/types.ts b/src/core/server/audit_trail/types.ts index b24c9a1a014f0..807550e5f4c79 100644 --- a/src/core/server/audit_trail/types.ts +++ b/src/core/server/audit_trail/types.ts @@ -31,7 +31,8 @@ export interface AuditableEvent { } export interface Auditor { - add(event: AuditableEvent): void; + add: (event: AuditableEvent) => void; + withScope: (name: string, fn: (...args: any[]) => Promise) => Promise; } export interface AuditorFactory { diff --git a/x-pack/plugins/audit_trail/server/plugin.ts b/x-pack/plugins/audit_trail/server/plugin.ts index c9d5d9d29add4..e189f80ec7b8d 100644 --- a/x-pack/plugins/audit_trail/server/plugin.ts +++ b/x-pack/plugins/audit_trail/server/plugin.ts @@ -35,7 +35,7 @@ export class AuditTrailPlugin implements Plugin { public async setup(core: CoreSetup, deps: DepsSetup) { const depsApi = { getCurrentUser: deps.security.authc.getCurrentUser, - getActiveSpace: deps.spaces.spacesService.getActiveSpace, + getSpaceId: deps.spaces.spacesService.getSpaceId, }; const event$ = new Subject(); diff --git a/x-pack/plugins/audit_trail/server/services/audit_trail_client.ts b/x-pack/plugins/audit_trail/server/services/audit_trail_client.ts index a2bec21295bf2..dc9b9f0af244e 100644 --- a/x-pack/plugins/audit_trail/server/services/audit_trail_client.ts +++ b/x-pack/plugins/audit_trail/server/services/audit_trail_client.ts @@ -12,19 +12,7 @@ import { SpacesPluginSetup } from '../../../spaces/server'; interface Deps { getCurrentUser: SecurityPluginSetup['authc']['getCurrentUser']; - getActiveSpace: SpacesPluginSetup['spacesService']['getActiveSpace']; -} - -function tail(array: T[]) { - return array[array.length - 1]; -} - -async function safeCall(fn: () => T | Promise): Promise { - try { - return await fn(); - } catch { - return; - } + getSpaceId: SpacesPluginSetup['spacesService']['getSpaceId']; } export class AuditTrailClient implements Auditor { @@ -35,29 +23,25 @@ export class AuditTrailClient implements Auditor { private readonly deps: Deps ) {} - openScope(name: string) { - if (this.scope.includes(name)) { - throw new Error(`"${name}" scope is already opened`); + withScope = async (name: string, fn: (...args: any[]) => Promise): Promise => { + try { + this.scope.push(name); + return await fn(); + } finally { + this.scope.pop(); } - this.scope.push(name); - } - - closeScope(name: string) { - if (tail(this.scope) !== name) { - throw new Error(`Cannot close ${name} scope`); - } - this.scope.shift(); - } + }; async add(event: AuditableEvent) { const user = this.deps.getCurrentUser(this.request); - const space = await safeCall(() => this.deps.getActiveSpace(this.request)); + const spaceId = this.deps.getSpaceId(this.request); this.event$.next({ message: event.message, type: event.type, user: user?.username, - space: space?.name, + space: spaceId, + scope: this.scope.join('>'), }); } } diff --git a/x-pack/plugins/audit_trail/server/types.ts b/x-pack/plugins/audit_trail/server/types.ts index 1f5c913eb080b..3a68747defe51 100644 --- a/x-pack/plugins/audit_trail/server/types.ts +++ b/x-pack/plugins/audit_trail/server/types.ts @@ -11,6 +11,7 @@ export interface AuditEvent { message: string; type: string; + scope: string; user?: string; space?: string; } From b77b407f3b92e5c6b22c1a8e91ef03bbf67b35cd Mon Sep 17 00:00:00 2001 From: Mikhail Shustov Date: Thu, 25 Jun 2020 17:01:29 +0200 Subject: [PATCH 12/32] wire es client up to auditor --- .../elasticsearch/cluster_client.test.ts | 72 +++++++++++++++---- .../server/elasticsearch/cluster_client.ts | 18 ++++- .../elasticsearch_service.test.ts | 1 + .../elasticsearch/elasticsearch_service.ts | 17 ++++- src/core/server/server.ts | 4 +- 5 files changed, 97 insertions(+), 15 deletions(-) diff --git a/src/core/server/elasticsearch/cluster_client.test.ts b/src/core/server/elasticsearch/cluster_client.test.ts index 820272bdf14b8..ca6575a3185b1 100644 --- a/src/core/server/elasticsearch/cluster_client.test.ts +++ b/src/core/server/elasticsearch/cluster_client.test.ts @@ -30,6 +30,7 @@ import { get } from 'lodash'; import { Logger } from '../logging'; import { loggingSystemMock } from '../logging/logging_system.mock'; import { httpServerMock } from '../http/http_server.mocks'; +import { auditTrailServiceMock } from '../audit_trail/audit_trail_service.mock'; import { ClusterClient } from './cluster_client'; const logger = loggingSystemMock.create(); @@ -42,7 +43,11 @@ test('#constructor creates client with parsed config', () => { const mockEsConfig = { apiVersion: 'es-version' } as any; const mockLogger = logger.get(); - const clusterClient = new ClusterClient(mockEsConfig, mockLogger); + const clusterClient = new ClusterClient( + mockEsConfig, + mockLogger, + auditTrailServiceMock.createAuditorFactory + ); expect(clusterClient).toBeDefined(); expect(mockParseElasticsearchClientConfig).toHaveBeenCalledTimes(1); @@ -68,7 +73,11 @@ describe('#callAsInternalUser', () => { }; MockClient.mockImplementation(() => mockEsClientInstance); - clusterClient = new ClusterClient({ apiVersion: 'es-version' } as any, logger.get()); + clusterClient = new ClusterClient( + { apiVersion: 'es-version' } as any, + logger.get(), + auditTrailServiceMock.createAuditorFactory + ); }); test('fails if cluster client is closed', async () => { @@ -237,7 +246,11 @@ describe('#asScoped', () => { requestHeadersWhitelist: ['one', 'two'], } as any; - clusterClient = new ClusterClient(mockEsConfig, mockLogger); + clusterClient = new ClusterClient( + mockEsConfig, + mockLogger, + auditTrailServiceMock.createAuditorFactory + ); jest.clearAllMocks(); }); @@ -272,7 +285,11 @@ describe('#asScoped', () => { test('properly configures `ignoreCertAndKey` for various configurations', () => { // Config without SSL. - clusterClient = new ClusterClient(mockEsConfig, mockLogger); + clusterClient = new ClusterClient( + mockEsConfig, + mockLogger, + auditTrailServiceMock.createAuditorFactory + ); mockParseElasticsearchClientConfig.mockClear(); clusterClient.asScoped(httpServerMock.createRawRequest({ headers: { one: '1' } })); @@ -285,7 +302,11 @@ describe('#asScoped', () => { // Config ssl.alwaysPresentCertificate === false mockEsConfig = { ...mockEsConfig, ssl: { alwaysPresentCertificate: false } } as any; - clusterClient = new ClusterClient(mockEsConfig, mockLogger); + clusterClient = new ClusterClient( + mockEsConfig, + mockLogger, + auditTrailServiceMock.createAuditorFactory + ); mockParseElasticsearchClientConfig.mockClear(); clusterClient.asScoped(httpServerMock.createRawRequest({ headers: { one: '1' } })); @@ -298,7 +319,11 @@ describe('#asScoped', () => { // Config ssl.alwaysPresentCertificate === true mockEsConfig = { ...mockEsConfig, ssl: { alwaysPresentCertificate: true } } as any; - clusterClient = new ClusterClient(mockEsConfig, mockLogger); + clusterClient = new ClusterClient( + mockEsConfig, + mockLogger, + auditTrailServiceMock.createAuditorFactory + ); mockParseElasticsearchClientConfig.mockClear(); clusterClient.asScoped(httpServerMock.createRawRequest({ headers: { one: '1' } })); @@ -341,7 +366,11 @@ describe('#asScoped', () => { }); test('does not fail when scope to not defined request', async () => { - clusterClient = new ClusterClient(mockEsConfig, mockLogger); + clusterClient = new ClusterClient( + mockEsConfig, + mockLogger, + auditTrailServiceMock.createAuditorFactory + ); clusterClient.asScoped(); expect(MockScopedClusterClient).toHaveBeenCalledTimes(1); expect(MockScopedClusterClient).toHaveBeenCalledWith( @@ -352,7 +381,11 @@ describe('#asScoped', () => { }); test('does not fail when scope to a request without headers', async () => { - clusterClient = new ClusterClient(mockEsConfig, mockLogger); + clusterClient = new ClusterClient( + mockEsConfig, + mockLogger, + auditTrailServiceMock.createAuditorFactory + ); clusterClient.asScoped({} as any); expect(MockScopedClusterClient).toHaveBeenCalledTimes(1); expect(MockScopedClusterClient).toHaveBeenCalledWith( @@ -363,7 +396,12 @@ describe('#asScoped', () => { }); test('calls getAuthHeaders and filters results for a real request', async () => { - clusterClient = new ClusterClient(mockEsConfig, mockLogger, () => ({ one: '1', three: '3' })); + clusterClient = new ClusterClient( + mockEsConfig, + mockLogger, + auditTrailServiceMock.createAuditorFactory, + () => ({ one: '1', three: '3' }) + ); clusterClient.asScoped(httpServerMock.createRawRequest({ headers: { two: '2' } })); expect(MockScopedClusterClient).toHaveBeenCalledTimes(1); expect(MockScopedClusterClient).toHaveBeenCalledWith( @@ -374,7 +412,12 @@ describe('#asScoped', () => { }); test('getAuthHeaders results rewrite extends a request headers', async () => { - clusterClient = new ClusterClient(mockEsConfig, mockLogger, () => ({ one: 'foo' })); + clusterClient = new ClusterClient( + mockEsConfig, + mockLogger, + auditTrailServiceMock.createAuditorFactory, + () => ({ one: 'foo' }) + ); clusterClient.asScoped(httpServerMock.createRawRequest({ headers: { one: '1', two: '2' } })); expect(MockScopedClusterClient).toHaveBeenCalledTimes(1); expect(MockScopedClusterClient).toHaveBeenCalledWith( @@ -393,7 +436,11 @@ describe('#asScoped', () => { }); test('filters a fake request headers', async () => { - clusterClient = new ClusterClient(mockEsConfig, mockLogger); + clusterClient = new ClusterClient( + mockEsConfig, + mockLogger, + auditTrailServiceMock.createAuditorFactory + ); clusterClient.asScoped({ headers: { one: '1', two: '2', three: '3' } }); expect(MockScopedClusterClient).toHaveBeenCalledTimes(1); @@ -420,7 +467,8 @@ describe('#close', () => { clusterClient = new ClusterClient( { apiVersion: 'es-version', requestHeadersWhitelist: [] } as any, - logger.get() + logger.get(), + auditTrailServiceMock.createAuditorFactory ); }); diff --git a/src/core/server/elasticsearch/cluster_client.ts b/src/core/server/elasticsearch/cluster_client.ts index 2352677b8d3e0..2ff30289cb490 100644 --- a/src/core/server/elasticsearch/cluster_client.ts +++ b/src/core/server/elasticsearch/cluster_client.ts @@ -20,6 +20,7 @@ import { Client } from 'elasticsearch'; import { get } from 'lodash'; import { ElasticsearchErrorHelpers } from './errors'; +import { Auditor, AuditorFactory } from '../audit_trail'; import { GetAuthHeaders, isRealRequest, LegacyRequest } from '../http'; import { filterHeaders, Headers, KibanaRequest, ensureRawRequest } from '../http/router'; import { Logger } from '../logging'; @@ -135,6 +136,8 @@ export class ClusterClient implements IClusterClient { */ private scopedClient?: Client; + private auditor?: Auditor; + /** * Indicates whether this cluster client (and all internal raw Elasticsearch JS clients) has been closed. */ @@ -143,6 +146,7 @@ export class ClusterClient implements IClusterClient { constructor( private readonly config: ElasticsearchClientConfig, private readonly log: Logger, + private readonly getAuditorFactory: () => AuditorFactory, private readonly getAuthHeaders: GetAuthHeaders = noop ) { this.client = new Client(parseElasticsearchClientConfig(config, log)); @@ -209,7 +213,12 @@ export class ClusterClient implements IClusterClient { }) ); } - + if (request && isRealRequest(request)) { + const kibanaRequest = + request instanceof KibanaRequest ? request : KibanaRequest.from(request); + const auditorFactory = this.getAuditorFactory(); + this.auditor = auditorFactory.asScoped(kibanaRequest); + } return new ScopedClusterClient( this.callAsInternalUser, this.callAsCurrentUser, @@ -233,6 +242,13 @@ export class ClusterClient implements IClusterClient { ) => { this.assertIsNotClosed(); + if (this.auditor) { + this.auditor!.add({ + message: endpoint, + type: 'elasticsearch.call', + }); + } + return await (callAPI.bind(null, this.scopedClient!) as APICaller)( endpoint, clientParams, diff --git a/src/core/server/elasticsearch/elasticsearch_service.test.ts b/src/core/server/elasticsearch/elasticsearch_service.test.ts index 0a7068903e15c..1ce040e3d1e74 100644 --- a/src/core/server/elasticsearch/elasticsearch_service.test.ts +++ b/src/core/server/elasticsearch/elasticsearch_service.test.ts @@ -101,6 +101,7 @@ describe('#setup', () => { expect(MockClusterClient).toHaveBeenCalledWith( expect.objectContaining(customConfig), expect.objectContaining({ context: ['elasticsearch', 'some-custom-type'] }), + expect.any(Function), expect.any(Function) ); }); diff --git a/src/core/server/elasticsearch/elasticsearch_service.ts b/src/core/server/elasticsearch/elasticsearch_service.ts index 26001bf83924f..ffc870cf410bc 100644 --- a/src/core/server/elasticsearch/elasticsearch_service.ts +++ b/src/core/server/elasticsearch/elasticsearch_service.ts @@ -42,6 +42,7 @@ import { import { ElasticsearchClientConfig } from './elasticsearch_client_config'; import { ElasticsearchConfig, ElasticsearchConfigType } from './elasticsearch_config'; import { InternalHttpServiceSetup, GetAuthHeaders } from '../http/'; +import { AuditTrailStart, AuditorFactory } from '../audit_trail'; import { InternalElasticsearchServiceSetup, ElasticsearchServiceStart } from './types'; import { CallAPIOptions } from './api_types'; import { pollEsNodesVersion } from './version_check/ensure_es_version'; @@ -57,12 +58,17 @@ interface SetupDeps { http: InternalHttpServiceSetup; } +interface StartDeps { + auditTrail: AuditTrailStart; +} + /** @internal */ export class ElasticsearchService implements CoreService { private readonly log: Logger; private readonly config$: Observable; private subscription?: Subscription; + private auditorFactory?: AuditorFactory; private stop$ = new Subject(); private kibanaVersion: string; private createClient?: ( @@ -170,7 +176,8 @@ export class ElasticsearchService status$: calculateStatus$(esNodesCompatibility$), }; } - public async start() { + public async start({ auditTrail }: StartDeps) { + this.auditorFactory = auditTrail; if (typeof this.client === 'undefined' || typeof this.createClient === 'undefined') { throw new Error('ElasticsearchService needs to be setup before calling start'); } else { @@ -199,7 +206,15 @@ export class ElasticsearchService return new ClusterClient( config, this.coreContext.logger.get('elasticsearch', type), + this.getAuditorFactory, getAuthHeaders ); } + + private getAuditorFactory = () => { + if (!this.auditorFactory) { + throw new Error('auditTrail has not been initialized'); + } + return this.auditorFactory; + }; } diff --git a/src/core/server/server.ts b/src/core/server/server.ts index fcbea62ab072f..63ec213e31214 100644 --- a/src/core/server/server.ts +++ b/src/core/server/server.ts @@ -211,7 +211,9 @@ export class Server { this.log.debug('starting server'); const auditTrailStart = this.auditTrail.start(); - const elasticsearchStart = await this.elasticsearch.start(); + const elasticsearchStart = await this.elasticsearch.start({ + auditTrail: auditTrailStart, + }); const savedObjectsStart = await this.savedObjects.start({ elasticsearch: elasticsearchStart, pluginsInitialized: this.#pluginsInitialized, From e46a37e15f94ffbbcb35731010040c667277bd1b Mon Sep 17 00:00:00 2001 From: restrry Date: Thu, 25 Jun 2020 17:32:55 +0200 Subject: [PATCH 13/32] update docs --- .../kibana-plugin-core-server.auditor.add.md | 15 ++------------- .../server/kibana-plugin-core-server.auditor.md | 9 +++++---- ...kibana-plugin-core-server.auditor.withscope.md | 11 +++++++++++ ...gin-core-server.clusterclient._constructor_.md | 3 ++- .../kibana-plugin-core-server.clusterclient.md | 2 +- .../core/server/kibana-plugin-core-server.md | 2 +- src/core/server/server.api.md | 6 ++++-- 7 files changed, 26 insertions(+), 22 deletions(-) create mode 100644 docs/development/core/server/kibana-plugin-core-server.auditor.withscope.md diff --git a/docs/development/core/server/kibana-plugin-core-server.auditor.add.md b/docs/development/core/server/kibana-plugin-core-server.auditor.add.md index 846c3fb8bfc48..3b72924bc67f1 100644 --- a/docs/development/core/server/kibana-plugin-core-server.auditor.add.md +++ b/docs/development/core/server/kibana-plugin-core-server.auditor.add.md @@ -2,21 +2,10 @@ [Home](./index.md) > [kibana-plugin-core-server](./kibana-plugin-core-server.md) > [Auditor](./kibana-plugin-core-server.auditor.md) > [add](./kibana-plugin-core-server.auditor.add.md) -## Auditor.add() method +## Auditor.add property Signature: ```typescript -add(event: AuditableEvent): void; +add: (event: AuditableEvent) => void; ``` - -## Parameters - -| Parameter | Type | Description | -| --- | --- | --- | -| event | AuditableEvent | | - -Returns: - -`void` - diff --git a/docs/development/core/server/kibana-plugin-core-server.auditor.md b/docs/development/core/server/kibana-plugin-core-server.auditor.md index 07de631e31e7d..4e4836659a17f 100644 --- a/docs/development/core/server/kibana-plugin-core-server.auditor.md +++ b/docs/development/core/server/kibana-plugin-core-server.auditor.md @@ -10,9 +10,10 @@ export interface Auditor ``` -## Methods +## Properties -| Method | Description | -| --- | --- | -| [add(event)](./kibana-plugin-core-server.auditor.add.md) | | +| Property | Type | Description | +| --- | --- | --- | +| [add](./kibana-plugin-core-server.auditor.add.md) | (event: AuditableEvent) => void | | +| [withScope](./kibana-plugin-core-server.auditor.withscope.md) | <T = unknown>(name: string, fn: (...args: any[]) => Promise<T>) => Promise<T> | | diff --git a/docs/development/core/server/kibana-plugin-core-server.auditor.withscope.md b/docs/development/core/server/kibana-plugin-core-server.auditor.withscope.md new file mode 100644 index 0000000000000..00fb470e01d13 --- /dev/null +++ b/docs/development/core/server/kibana-plugin-core-server.auditor.withscope.md @@ -0,0 +1,11 @@ + + +[Home](./index.md) > [kibana-plugin-core-server](./kibana-plugin-core-server.md) > [Auditor](./kibana-plugin-core-server.auditor.md) > [withScope](./kibana-plugin-core-server.auditor.withscope.md) + +## Auditor.withScope property + +Signature: + +```typescript +withScope: (name: string, fn: (...args: any[]) => Promise) => Promise; +``` diff --git a/docs/development/core/server/kibana-plugin-core-server.clusterclient._constructor_.md b/docs/development/core/server/kibana-plugin-core-server.clusterclient._constructor_.md index d4b2f80443acb..4cddffe3012f9 100644 --- a/docs/development/core/server/kibana-plugin-core-server.clusterclient._constructor_.md +++ b/docs/development/core/server/kibana-plugin-core-server.clusterclient._constructor_.md @@ -9,7 +9,7 @@ Constructs a new instance of the `ClusterClient` class Signature: ```typescript -constructor(config: ElasticsearchClientConfig, log: Logger, getAuthHeaders?: GetAuthHeaders); +constructor(config: ElasticsearchClientConfig, log: Logger, getAuditorFactory: () => AuditorFactory, getAuthHeaders?: GetAuthHeaders); ``` ## Parameters @@ -18,5 +18,6 @@ constructor(config: ElasticsearchClientConfig, log: Logger, getAuthHeaders?: Get | --- | --- | --- | | config | ElasticsearchClientConfig | | | log | Logger | | +| getAuditorFactory | () => AuditorFactory | | | getAuthHeaders | GetAuthHeaders | | diff --git a/docs/development/core/server/kibana-plugin-core-server.clusterclient.md b/docs/development/core/server/kibana-plugin-core-server.clusterclient.md index 0f6b2512a6d94..f309048d09ba2 100644 --- a/docs/development/core/server/kibana-plugin-core-server.clusterclient.md +++ b/docs/development/core/server/kibana-plugin-core-server.clusterclient.md @@ -18,7 +18,7 @@ export declare class ClusterClient implements IClusterClient | Constructor | Modifiers | Description | | --- | --- | --- | -| [(constructor)(config, log, getAuthHeaders)](./kibana-plugin-core-server.clusterclient._constructor_.md) | | Constructs a new instance of the ClusterClient class | +| [(constructor)(config, log, getAuditorFactory, getAuthHeaders)](./kibana-plugin-core-server.clusterclient._constructor_.md) | | Constructs a new instance of the ClusterClient class | ## Properties diff --git a/docs/development/core/server/kibana-plugin-core-server.md b/docs/development/core/server/kibana-plugin-core-server.md index 1977df35c8b1e..2201ef88d7761 100644 --- a/docs/development/core/server/kibana-plugin-core-server.md +++ b/docs/development/core/server/kibana-plugin-core-server.md @@ -215,8 +215,8 @@ The plugin integrates with the core system via lifecycle events: `setup` | Type Alias | Description | | --- | --- | -| [AuditTrailStart](./kibana-plugin-core-server.audittrailstart.md) | | | [AppenderConfigType](./kibana-plugin-core-server.appenderconfigtype.md) | | +| [AuditTrailStart](./kibana-plugin-core-server.audittrailstart.md) | | | [AuthenticationHandler](./kibana-plugin-core-server.authenticationhandler.md) | See [AuthToolkit](./kibana-plugin-core-server.authtoolkit.md). | | [AuthHeaders](./kibana-plugin-core-server.authheaders.md) | Auth Headers map | | [AuthResult](./kibana-plugin-core-server.authresult.md) | | diff --git a/src/core/server/server.api.md b/src/core/server/server.api.md index bf8a77656a32a..bef10e8f5b652 100644 --- a/src/core/server/server.api.md +++ b/src/core/server/server.api.md @@ -427,7 +427,9 @@ export interface AuditableEvent { // @public (undocumented) export interface Auditor { // (undocumented) - add(event: AuditableEvent): void; + add: (event: AuditableEvent) => void; + // (undocumented) + withScope: (name: string, fn: (...args: any[]) => Promise) => Promise; } // Warning: (ae-missing-release-tag) "AuditorFactory" is exported by the package, but it is missing a release tag (@alpha, @beta, @public, or @internal) @@ -569,7 +571,7 @@ export type CapabilitiesSwitcher = (request: KibanaRequest, uiCapabilities: Capa // @public export class ClusterClient implements IClusterClient { - constructor(config: ElasticsearchClientConfig, log: Logger, getAuthHeaders?: GetAuthHeaders); + constructor(config: ElasticsearchClientConfig, log: Logger, getAuditorFactory: () => AuditorFactory, getAuthHeaders?: GetAuthHeaders); asScoped(request?: ScopeableRequest): IScopedClusterClient; callAsInternalUser: APICaller; close(): void; From 9ec5ad6d15236bd4a090e108a2380333a4d5f280 Mon Sep 17 00:00:00 2001 From: Mikhail Shustov Date: Mon, 29 Jun 2020 16:45:49 +0200 Subject: [PATCH 14/32] withScope accepts only one scope --- .../audit_trail/audit_trail_service.mock.ts | 15 +++++++------ .../audit_trail/audit_trail_service.test.ts | 11 ++++++++++ .../server/audit_trail/audit_trail_service.ts | 11 +++++----- src/core/server/audit_trail/types.ts | 22 +++++++++++++++++-- 4 files changed, 44 insertions(+), 15 deletions(-) diff --git a/src/core/server/audit_trail/audit_trail_service.mock.ts b/src/core/server/audit_trail/audit_trail_service.mock.ts index ca0c329c33ed7..de0ea02fbb7ad 100644 --- a/src/core/server/audit_trail/audit_trail_service.mock.ts +++ b/src/core/server/audit_trail/audit_trail_service.mock.ts @@ -26,13 +26,6 @@ const createSetupContractMock = () => { return mocked; }; -const createStartContractMock = () => { - const mocked: jest.Mocked = { - asScoped: jest.fn(), - }; - return mocked; -}; - const createAuditorMock = () => { const mocked: jest.Mocked = { add: jest.fn(), @@ -41,6 +34,14 @@ const createAuditorMock = () => { return mocked; }; +const createStartContractMock = () => { + const mocked: jest.Mocked = { + asScoped: jest.fn(), + }; + mocked.asScoped.mockReturnValue(createAuditorMock()); + return mocked; +}; + export const auditTrailServiceMock = { createSetupContract: createSetupContractMock, createStartContract: createStartContractMock, diff --git a/src/core/server/audit_trail/audit_trail_service.test.ts b/src/core/server/audit_trail/audit_trail_service.test.ts index 080fa939a4336..db71bf545c548 100644 --- a/src/core/server/audit_trail/audit_trail_service.test.ts +++ b/src/core/server/audit_trail/audit_trail_service.test.ts @@ -83,6 +83,17 @@ describe('AuditTrailService', () => { expect(addEventMock).toHaveBeenLastCalledWith(message); }); + + describe('return the same auditor instance for the same KibanaRequest', () => { + const auditTrail = new AuditTrailService(coreContext); + auditTrail.setup(); + const { asScoped } = auditTrail.start(); + + const rawRequest1 = httpServerMock.createKibanaRequest(); + const rawRequest2 = httpServerMock.createKibanaRequest(); + expect(asScoped(rawRequest1)).toBe(asScoped(rawRequest1)); + expect(asScoped(rawRequest1)).not.toBe(asScoped(rawRequest2)); + }); }); }); }); diff --git a/src/core/server/audit_trail/audit_trail_service.ts b/src/core/server/audit_trail/audit_trail_service.ts index b6455a19189e4..cbea3126dc9d4 100644 --- a/src/core/server/audit_trail/audit_trail_service.ts +++ b/src/core/server/audit_trail/audit_trail_service.ts @@ -27,9 +27,7 @@ const defaultAuditorFactory: AuditorFactory = { asScoped() { return { add() {}, - withScope(name, fn) { - return fn(); - }, + withScope() {}, }; }, }; @@ -57,10 +55,11 @@ export class AuditTrailService implements CoreService { - if (!this.auditors.has(ensureRawRequest(request))) { - this.auditors.set(ensureRawRequest(request), this.auditor!.asScoped(request)); + const key = ensureRawRequest(request); + if (!this.auditors.has(key)) { + this.auditors.set(key, this.auditor!.asScoped(request)); } - return this.auditors.get(ensureRawRequest(request))!; + return this.auditors.get(key)!; }, }; } diff --git a/src/core/server/audit_trail/types.ts b/src/core/server/audit_trail/types.ts index 807550e5f4c79..577b669e59e19 100644 --- a/src/core/server/audit_trail/types.ts +++ b/src/core/server/audit_trail/types.ts @@ -30,16 +30,34 @@ export interface AuditableEvent { type: string; } +/** + * Provides methods to log user actions and access events. + * @public + */ export interface Auditor { - add: (event: AuditableEvent) => void; - withScope: (name: string, fn: (...args: any[]) => Promise) => Promise; + /** + * Add a record to audit log. + */ + add(event: AuditableEvent): void; + /** + * Add a high-level scope name for logged events. + * It helps to identify the root cause of low-level events. + */ + withScope(name: string): void; } +/** + * Creates {@link Auditor} instance bound to the current user credentials. + * @public + */ export interface AuditorFactory { asScoped(request: KibanaRequest): Auditor; } export interface AuditTrailSetup { + /** + * Register a custom {@link AuditorFactory} implementation. + */ register(auditor: AuditorFactory): void; } From 3f4e6af86c713f096c6831faa704b3d50d4684f6 Mon Sep 17 00:00:00 2001 From: Mikhail Shustov Date: Mon, 29 Jun 2020 16:47:36 +0200 Subject: [PATCH 15/32] use scoped client in context for callAsInternalUser --- .../server/elasticsearch/elasticsearch_service.ts | 14 ++++++++++++-- 1 file changed, 12 insertions(+), 2 deletions(-) diff --git a/src/core/server/elasticsearch/elasticsearch_service.ts b/src/core/server/elasticsearch/elasticsearch_service.ts index ffc870cf410bc..8c0361890d4fc 100644 --- a/src/core/server/elasticsearch/elasticsearch_service.ts +++ b/src/core/server/elasticsearch/elasticsearch_service.ts @@ -135,14 +135,24 @@ export class ElasticsearchService return await _client.callAsInternalUser(endpoint, clientParams, options); }, asScoped(request: ScopeableRequest) { + const _clientPromise = client$.pipe(take(1)).toPromise(); return { - callAsInternalUser: client.callAsInternalUser, + async callAsInternalUser( + endpoint: string, + clientParams: Record = {}, + options?: CallAPIOptions + ) { + const _client = await _clientPromise; + return await _client + .asScoped(request) + .callAsInternalUser(endpoint, clientParams, options); + }, async callAsCurrentUser( endpoint: string, clientParams: Record = {}, options?: CallAPIOptions ) { - const _client = await client$.pipe(take(1)).toPromise(); + const _client = await _clientPromise; return await _client .asScoped(request) .callAsCurrentUser(endpoint, clientParams, options); From 8d7bac9c394bfd6aaa36c481c218dfab77a02775 Mon Sep 17 00:00:00 2001 From: Mikhail Shustov Date: Mon, 29 Jun 2020 16:50:29 +0200 Subject: [PATCH 16/32] use auditor in scoped cluster client --- .../elasticsearch/cluster_client.test.ts | 38 +++++++++++++++---- .../server/elasticsearch/cluster_client.ts | 28 ++++++-------- .../elasticsearch/scoped_cluster_client.ts | 19 +++++++++- 3 files changed, 61 insertions(+), 24 deletions(-) diff --git a/src/core/server/elasticsearch/cluster_client.test.ts b/src/core/server/elasticsearch/cluster_client.test.ts index ca6575a3185b1..5c83d6c39818e 100644 --- a/src/core/server/elasticsearch/cluster_client.test.ts +++ b/src/core/server/elasticsearch/cluster_client.test.ts @@ -344,7 +344,8 @@ describe('#asScoped', () => { expect(MockScopedClusterClient).toHaveBeenCalledWith( expect.any(Function), expect.any(Function), - { one: '1', two: '2' } + { one: '1', two: '2' }, + expect.any(Object) ); }); @@ -376,7 +377,8 @@ describe('#asScoped', () => { expect(MockScopedClusterClient).toHaveBeenCalledWith( expect.any(Function), expect.any(Function), - {} + {}, + undefined ); }); @@ -391,11 +393,12 @@ describe('#asScoped', () => { expect(MockScopedClusterClient).toHaveBeenCalledWith( expect.any(Function), expect.any(Function), - {} + {}, + undefined ); }); - test('calls getAuthHeaders and filters results for a real request', async () => { + test('calls getAuthHeaders and creates Auditor for a real request', async () => { clusterClient = new ClusterClient( mockEsConfig, mockLogger, @@ -407,7 +410,8 @@ describe('#asScoped', () => { expect(MockScopedClusterClient).toHaveBeenCalledWith( expect.any(Function), expect.any(Function), - { one: '1', two: '2' } + { one: '1', two: '2' }, + expect.any(Object) ); }); @@ -423,11 +427,30 @@ describe('#asScoped', () => { expect(MockScopedClusterClient).toHaveBeenCalledWith( expect.any(Function), expect.any(Function), - { one: 'foo', two: '2' } + { one: 'foo', two: '2' }, + expect.any(Object) ); }); test("doesn't call getAuthHeaders for a fake request", async () => { + clusterClient = new ClusterClient( + mockEsConfig, + mockLogger, + auditTrailServiceMock.createAuditorFactory, + () => ({}) + ); + clusterClient.asScoped({ headers: { one: 'foo' } }); + + expect(MockScopedClusterClient).toHaveBeenCalledTimes(1); + expect(MockScopedClusterClient).toHaveBeenCalledWith( + expect.any(Function), + expect.any(Function), + { one: 'foo' }, + undefined + ); + }); + + test("doesn't create Auditor for a fake request", async () => { const getAuthHeaders = jest.fn(); clusterClient = new ClusterClient(mockEsConfig, mockLogger, getAuthHeaders); clusterClient.asScoped({ headers: { one: '1', two: '2', three: '3' } }); @@ -447,7 +470,8 @@ describe('#asScoped', () => { expect(MockScopedClusterClient).toHaveBeenCalledWith( expect.any(Function), expect.any(Function), - { one: '1', two: '2' } + { one: '1', two: '2' }, + undefined ); }); }); diff --git a/src/core/server/elasticsearch/cluster_client.ts b/src/core/server/elasticsearch/cluster_client.ts index 2ff30289cb490..d34af0667c490 100644 --- a/src/core/server/elasticsearch/cluster_client.ts +++ b/src/core/server/elasticsearch/cluster_client.ts @@ -20,7 +20,7 @@ import { Client } from 'elasticsearch'; import { get } from 'lodash'; import { ElasticsearchErrorHelpers } from './errors'; -import { Auditor, AuditorFactory } from '../audit_trail'; +import { AuditorFactory } from '../audit_trail'; import { GetAuthHeaders, isRealRequest, LegacyRequest } from '../http'; import { filterHeaders, Headers, KibanaRequest, ensureRawRequest } from '../http/router'; import { Logger } from '../logging'; @@ -136,8 +136,6 @@ export class ClusterClient implements IClusterClient { */ private scopedClient?: Client; - private auditor?: Auditor; - /** * Indicates whether this cluster client (and all internal raw Elasticsearch JS clients) has been closed. */ @@ -213,17 +211,22 @@ export class ClusterClient implements IClusterClient { }) ); } + + return new ScopedClusterClient( + this.callAsInternalUser, + this.callAsCurrentUser, + filterHeaders(this.getHeaders(request), this.config.requestHeadersWhitelist), + this.getScopedScopedAuditor(request) + ); + } + + private getScopedScopedAuditor(request?: ScopeableRequest) { if (request && isRealRequest(request)) { const kibanaRequest = request instanceof KibanaRequest ? request : KibanaRequest.from(request); const auditorFactory = this.getAuditorFactory(); - this.auditor = auditorFactory.asScoped(kibanaRequest); + return auditorFactory.asScoped(kibanaRequest); } - return new ScopedClusterClient( - this.callAsInternalUser, - this.callAsCurrentUser, - filterHeaders(this.getHeaders(request), this.config.requestHeadersWhitelist) - ); } /** @@ -242,13 +245,6 @@ export class ClusterClient implements IClusterClient { ) => { this.assertIsNotClosed(); - if (this.auditor) { - this.auditor!.add({ - message: endpoint, - type: 'elasticsearch.call', - }); - } - return await (callAPI.bind(null, this.scopedClient!) as APICaller)( endpoint, clientParams, diff --git a/src/core/server/elasticsearch/scoped_cluster_client.ts b/src/core/server/elasticsearch/scoped_cluster_client.ts index 4b64bfba15190..75cd57866ea09 100644 --- a/src/core/server/elasticsearch/scoped_cluster_client.ts +++ b/src/core/server/elasticsearch/scoped_cluster_client.ts @@ -18,6 +18,7 @@ */ import { intersection, isObject } from 'lodash'; +import { Auditor } from '../audit_trail'; import { Headers } from '../http/router'; import { APICaller, CallAPIOptions } from './api_types'; @@ -47,7 +48,8 @@ export class ScopedClusterClient implements IScopedClusterClient { constructor( private readonly internalAPICaller: APICaller, private readonly scopedAPICaller: APICaller, - private readonly headers?: Headers + private readonly headers?: Headers, + private readonly auditor?: Auditor ) { this.callAsCurrentUser = this.callAsCurrentUser.bind(this); this.callAsInternalUser = this.callAsInternalUser.bind(this); @@ -67,6 +69,13 @@ export class ScopedClusterClient implements IScopedClusterClient { clientParams: Record = {}, options?: CallAPIOptions ) { + if (this.auditor) { + this.auditor.add({ + message: endpoint, + type: 'elasticsearch.call.internalUser', + }); + } + return this.internalAPICaller(endpoint, clientParams, options); } @@ -98,6 +107,14 @@ export class ScopedClusterClient implements IScopedClusterClient { clientParams.headers = Object.assign({}, clientParams.headers, this.headers); } + + if (this.auditor) { + this.auditor.add({ + message: endpoint, + type: 'elasticsearch.call.currentUser', + }); + } + return this.scopedAPICaller(endpoint, clientParams, options); } } From 2936e6649bd189ccae0197d330e0fd9c1029b027 Mon Sep 17 00:00:00 2001 From: Mikhail Shustov Date: Mon, 29 Jun 2020 16:55:29 +0200 Subject: [PATCH 17/32] adopt auditTrail plugin to new interface. configure log from config --- x-pack/plugins/audit_trail/kibana.json | 2 +- x-pack/plugins/audit_trail/server/config.ts | 7 +-- x-pack/plugins/audit_trail/server/plugin.ts | 45 +++++++++++++++++-- .../server/services/audit_trail_client.ts | 18 ++++---- x-pack/plugins/audit_trail/server/types.ts | 8 +++- 5 files changed, 61 insertions(+), 19 deletions(-) diff --git a/x-pack/plugins/audit_trail/kibana.json b/x-pack/plugins/audit_trail/kibana.json index 2041dbcd7d614..eb7255a113a81 100644 --- a/x-pack/plugins/audit_trail/kibana.json +++ b/x-pack/plugins/audit_trail/kibana.json @@ -1,5 +1,5 @@ { - "id": "audiTrail", + "id": "auditTrail", "version": "8.0.0", "kibanaVersion": "kibana", "configPath": ["xpack", "audit_trail"], diff --git a/x-pack/plugins/audit_trail/server/config.ts b/x-pack/plugins/audit_trail/server/config.ts index 3b61dcb8581f5..c1a0c2316f74b 100644 --- a/x-pack/plugins/audit_trail/server/config.ts +++ b/x-pack/plugins/audit_trail/server/config.ts @@ -5,14 +5,15 @@ */ import { schema, TypeOf } from '@kbn/config-schema'; -import { PluginConfigDescriptor } from 'kibana/server'; +import { PluginConfigDescriptor, config as _config } from '../../../../src/core/server'; const configSchema = schema.object({ enabled: schema.boolean({ defaultValue: true }), + appender: schema.maybe(_config.logging.appenders), }); -export type ConfigType = TypeOf; +export type AuditTrailConfigType = TypeOf; -export const config: PluginConfigDescriptor = { +export const config: PluginConfigDescriptor = { schema: configSchema, }; diff --git a/x-pack/plugins/audit_trail/server/plugin.ts b/x-pack/plugins/audit_trail/server/plugin.ts index e189f80ec7b8d..9abc860656058 100644 --- a/x-pack/plugins/audit_trail/server/plugin.ts +++ b/x-pack/plugins/audit_trail/server/plugin.ts @@ -4,18 +4,22 @@ * you may not use this file except in compliance with the Elastic License. */ -import { Subject } from 'rxjs'; +import { Observable, Subject } from 'rxjs'; +import { map } from 'rxjs/operators'; import { + AppenderConfigType, CoreSetup, CoreStart, KibanaRequest, Logger, + LoggerContextConfigInput, Plugin, PluginInitializerContext, } from 'src/core/server'; -import { AuditEvent } from './types'; +import { AuditEvent, AuditTrailPluginSetup } from './types'; import { AuditTrailClient } from './services/audit_trail_client'; +import { AuditTrailConfigType } from './config'; import { SecurityPluginSetup } from '../../security/server'; import { SpacesPluginSetup } from '../../spaces/server'; @@ -25,11 +29,13 @@ interface DepsSetup { spaces: SpacesPluginSetup; } -export class AuditTrailPlugin implements Plugin { +export class AuditTrailPlugin implements Plugin { private readonly logger: Logger; + private readonly config$: Observable; constructor(private readonly context: PluginInitializerContext) { this.logger = this.context.logger.get(); + this.config$ = this.context.config.create(); } public async setup(core: CoreSetup, deps: DepsSetup) { @@ -46,6 +52,39 @@ export class AuditTrailPlugin implements Plugin { return new AuditTrailClient(request, event$, depsApi); }, }); + + core.logging.configure( + this.config$.pipe( + map((config) => ({ + appenders: { + auditTrailAppender: this.getAppender(config), + }, + loggers: [ + { + context: '', // plugins.auditTrail prepended automatically + level: 'off', + appenders: ['auditTrailAppender'], + }, + ], + })) + ) + ); + + return { + event$: event$.asObservable(), + }; + } + + private getAppender(config: AuditTrailConfigType): AppenderConfigType { + return ( + config.appender ?? { + kind: 'console', + layout: { + kind: 'pattern', + pattern: '[%level] %message %meta', + }, + } + ); } public start(core: CoreStart) {} diff --git a/x-pack/plugins/audit_trail/server/services/audit_trail_client.ts b/x-pack/plugins/audit_trail/server/services/audit_trail_client.ts index dc9b9f0af244e..638d4190e0377 100644 --- a/x-pack/plugins/audit_trail/server/services/audit_trail_client.ts +++ b/x-pack/plugins/audit_trail/server/services/audit_trail_client.ts @@ -16,23 +16,21 @@ interface Deps { } export class AuditTrailClient implements Auditor { - private readonly scope: string[] = []; + private scope?: string; constructor( private readonly request: KibanaRequest, private readonly event$: Subject, private readonly deps: Deps ) {} - withScope = async (name: string, fn: (...args: any[]) => Promise): Promise => { - try { - this.scope.push(name); - return await fn(); - } finally { - this.scope.pop(); + public withScope(name: string) { + if (this.scope !== undefined) { + throw new Error(`AuditTrail scope is already set to: ${this.scope}`); } - }; + this.scope = name; + } - async add(event: AuditableEvent) { + public add(event: AuditableEvent) { const user = this.deps.getCurrentUser(this.request); const spaceId = this.deps.getSpaceId(this.request); @@ -41,7 +39,7 @@ export class AuditTrailClient implements Auditor { type: event.type, user: user?.username, space: spaceId, - scope: this.scope.join('>'), + scope: this.scope, }); } } diff --git a/x-pack/plugins/audit_trail/server/types.ts b/x-pack/plugins/audit_trail/server/types.ts index 3a68747defe51..9b3d7d3b4d614 100644 --- a/x-pack/plugins/audit_trail/server/types.ts +++ b/x-pack/plugins/audit_trail/server/types.ts @@ -3,7 +3,7 @@ * or more contributor license agreements. Licensed under the Elastic License; * you may not use this file except in compliance with the Elastic License. */ - +import { Observable } from 'rxjs'; /** * Event enhanced with request context data. Provided to an external consumer. * @public @@ -11,7 +11,11 @@ export interface AuditEvent { message: string; type: string; - scope: string; + scope?: string; user?: string; space?: string; } + +export interface AuditTrailPluginSetup { + event$: Observable; +} From ccdf9bb2e91e7fe310bed6fad0ef479852ca5a94 Mon Sep 17 00:00:00 2001 From: Mikhail Shustov Date: Mon, 29 Jun 2020 17:51:19 +0200 Subject: [PATCH 18/32] do not log audit events in console by default --- x-pack/plugins/audit_trail/server/config.ts | 3 +++ x-pack/plugins/audit_trail/server/plugin.ts | 9 ++++++--- 2 files changed, 9 insertions(+), 3 deletions(-) diff --git a/x-pack/plugins/audit_trail/server/config.ts b/x-pack/plugins/audit_trail/server/config.ts index c1a0c2316f74b..f11636ae0b419 100644 --- a/x-pack/plugins/audit_trail/server/config.ts +++ b/x-pack/plugins/audit_trail/server/config.ts @@ -10,6 +10,9 @@ import { PluginConfigDescriptor, config as _config } from '../../../../src/core/ const configSchema = schema.object({ enabled: schema.boolean({ defaultValue: true }), appender: schema.maybe(_config.logging.appenders), + logger: schema.object({ + enabled: schema.boolean({ defaultValue: false }), + }), }); export type AuditTrailConfigType = TypeOf; diff --git a/x-pack/plugins/audit_trail/server/plugin.ts b/x-pack/plugins/audit_trail/server/plugin.ts index 9abc860656058..f553d31946a19 100644 --- a/x-pack/plugins/audit_trail/server/plugin.ts +++ b/x-pack/plugins/audit_trail/server/plugin.ts @@ -61,8 +61,10 @@ export class AuditTrailPlugin implements Plugin { }, loggers: [ { - context: '', // plugins.auditTrail prepended automatically - level: 'off', + // plugins.auditTrail prepended automatically + context: '', + // do not pipe in root log if disabled + level: config.logger.enabled ? 'debug' : 'off', appenders: ['auditTrailAppender'], }, ], @@ -81,7 +83,8 @@ export class AuditTrailPlugin implements Plugin { kind: 'console', layout: { kind: 'pattern', - pattern: '[%level] %message %meta', + highlight: true, + // pattern: '[%level] %message %meta', }, } ); From 549cfa2d415fa4f9916ecc2827a3597b5772eb2b Mon Sep 17 00:00:00 2001 From: Mikhail Shustov Date: Mon, 29 Jun 2020 17:54:13 +0200 Subject: [PATCH 19/32] add audit trail functional tests --- x-pack/test/plugin_functional/config.ts | 6 + .../plugins/audit_trail_test/kibana.json | 9 ++ .../audit_trail_test/server/.gitignore | 1 + .../plugins/audit_trail_test/server/index.ts | 9 ++ .../plugins/audit_trail_test/server/plugin.ts | 65 +++++++++++ .../test_suites/audit_trail/index.ts | 110 ++++++++++++++++++ 6 files changed, 200 insertions(+) create mode 100644 x-pack/test/plugin_functional/plugins/audit_trail_test/kibana.json create mode 100644 x-pack/test/plugin_functional/plugins/audit_trail_test/server/.gitignore create mode 100644 x-pack/test/plugin_functional/plugins/audit_trail_test/server/index.ts create mode 100644 x-pack/test/plugin_functional/plugins/audit_trail_test/server/plugin.ts create mode 100644 x-pack/test/plugin_functional/test_suites/audit_trail/index.ts diff --git a/x-pack/test/plugin_functional/config.ts b/x-pack/test/plugin_functional/config.ts index d466137c40d48..364579bab3989 100644 --- a/x-pack/test/plugin_functional/config.ts +++ b/x-pack/test/plugin_functional/config.ts @@ -28,6 +28,7 @@ export default async function ({ readConfigFile }: FtrConfigProviderContext) { return { // list paths to the files that contain your plugins tests testFiles: [ + resolve(__dirname, './test_suites/audit_trail'), resolve(__dirname, './test_suites/resolver'), resolve(__dirname, './test_suites/global_search'), ], @@ -50,6 +51,11 @@ export default async function ({ readConfigFile }: FtrConfigProviderContext) { )}`, // Required to load new platform plugins via `--plugin-path` flag. '--env.name=development', + + '--xpack.audit_trail.logger.enabled=true', + '--xpack.audit_trail.appender.kind=file', + '--xpack.audit_trail.appender.path=x-pack/test/plugin_functional/plugins/audit_trail_test/server/pattern_debug.log', + '--xpack.audit_trail.appender.layout.kind=json', ], }, uiSettings: xpackFunctionalConfig.get('uiSettings'), diff --git a/x-pack/test/plugin_functional/plugins/audit_trail_test/kibana.json b/x-pack/test/plugin_functional/plugins/audit_trail_test/kibana.json new file mode 100644 index 0000000000000..f53aa57ad6705 --- /dev/null +++ b/x-pack/test/plugin_functional/plugins/audit_trail_test/kibana.json @@ -0,0 +1,9 @@ +{ + "id": "audit_trail_test", + "version": "1.0.0", + "kibanaVersion": "kibana", + "configPath": [], + "requiredPlugins": ["auditTrail"], + "server": true, + "ui": false +} diff --git a/x-pack/test/plugin_functional/plugins/audit_trail_test/server/.gitignore b/x-pack/test/plugin_functional/plugins/audit_trail_test/server/.gitignore new file mode 100644 index 0000000000000..9a3d281179193 --- /dev/null +++ b/x-pack/test/plugin_functional/plugins/audit_trail_test/server/.gitignore @@ -0,0 +1 @@ +/*debug.log diff --git a/x-pack/test/plugin_functional/plugins/audit_trail_test/server/index.ts b/x-pack/test/plugin_functional/plugins/audit_trail_test/server/index.ts new file mode 100644 index 0000000000000..dd8d975e7dc66 --- /dev/null +++ b/x-pack/test/plugin_functional/plugins/audit_trail_test/server/index.ts @@ -0,0 +1,9 @@ +/* + * 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 { AuditTrailTestPlugin } from './plugin'; + +export const plugin = () => new AuditTrailTestPlugin(); diff --git a/x-pack/test/plugin_functional/plugins/audit_trail_test/server/plugin.ts b/x-pack/test/plugin_functional/plugins/audit_trail_test/server/plugin.ts new file mode 100644 index 0000000000000..bb4fc69d9acc6 --- /dev/null +++ b/x-pack/test/plugin_functional/plugins/audit_trail_test/server/plugin.ts @@ -0,0 +1,65 @@ +/* + * 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 { Plugin, CoreSetup } from 'src/core/server'; + +export class AuditTrailTestPlugin implements Plugin { + public setup(core: CoreSetup) { + core.savedObjects.registerType({ + name: 'audit_trail_test', + hidden: false, + namespaceType: 'agnostic', + mappings: { + properties: {}, + }, + }); + + const router = core.http.createRouter(); + router.get( + { path: '/audit_trail_test/context/as_current_user', validate: false }, + async (context, request, response) => { + context.core.auditor.withScope('audit_trail_test/context/as_current_user'); + await context.core.elasticsearch.legacy.client.callAsCurrentUser('ping'); + return response.noContent(); + } + ); + + router.get( + { path: '/audit_trail_test/context/as_internal_user', validate: false }, + async (context, request, response) => { + context.core.auditor.withScope('audit_trail_test/context/as_internal_user'); + await context.core.elasticsearch.legacy.client.callAsInternalUser('ping'); + return response.noContent(); + } + ); + + router.get( + { path: '/audit_trail_test/contract/as_current_user', validate: false }, + async (context, request, response) => { + const [coreStart] = await core.getStartServices(); + const auditor = coreStart.auditTrail.asScoped(request); + auditor.withScope('audit_trail_test/contract/as_current_user'); + + await context.core.elasticsearch.legacy.client.callAsCurrentUser('ping'); + return response.noContent(); + } + ); + + router.get( + { path: '/audit_trail_test/contract/as_internal_user', validate: false }, + async (context, request, response) => { + const [coreStart] = await core.getStartServices(); + const auditor = coreStart.auditTrail.asScoped(request); + auditor.withScope('audit_trail_test/contract/as_internal_user'); + + await context.core.elasticsearch.legacy.client.callAsInternalUser('ping'); + return response.noContent(); + } + ); + } + + public start() {} +} diff --git a/x-pack/test/plugin_functional/test_suites/audit_trail/index.ts b/x-pack/test/plugin_functional/test_suites/audit_trail/index.ts new file mode 100644 index 0000000000000..af52b6022a9e9 --- /dev/null +++ b/x-pack/test/plugin_functional/test_suites/audit_trail/index.ts @@ -0,0 +1,110 @@ +/* + * 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 Path from 'path'; +import Fs from 'fs'; +import expect from '@kbn/expect'; +import { FtrProviderContext } from '../../ftr_provider_context'; + +class FileWrapper { + constructor(private readonly path: string) {} + async reset() { + // "touch" each file to ensure it exists and is empty before each test + await Fs.promises.writeFile(this.path, ''); + } + async read() { + const content = await Fs.promises.readFile(this.path, { encoding: 'utf8' }); + return content.trim().split('\n'); + } + async readJSON() { + const content = await this.read(); + return content.map((l) => JSON.parse(l)); + } +} + +export default function ({ getPageObjects, getService }: FtrProviderContext) { + const supertest = getService('supertest'); + + describe('Audit trail service', function () { + this.tags('ciGroup7'); + const logFilePath = Path.resolve( + __dirname, + '../../plugins/audit_trail_test/server/pattern_debug.log' + ); + const logFile = new FileWrapper(logFilePath); + + beforeEach(async () => { + await logFile.reset(); + }); + + it('logs current user access to elasticsearch via RequestHandlerContext', async () => { + await supertest + .get('/audit_trail_test/context/as_current_user') + .set('kbn-xsrf', 'foo') + .expect(204); + + const content = await logFile.readJSON(); + + const pingCall = content.find( + (c) => c.meta.scope === 'audit_trail_test/context/as_current_user' + ); + expect(pingCall).to.be.ok(); + expect(pingCall.meta.type).to.be('elasticsearch.call.currentUser'); + expect(pingCall.meta.user).to.be('elastic'); + expect(pingCall.meta.space).to.be('default'); + }); + + it('logs internal user access to elasticsearch via RequestHandlerContext', async () => { + await supertest + .get('/audit_trail_test/context/as_internal_user') + .set('kbn-xsrf', 'foo') + .expect(204); + + const content = await logFile.readJSON(); + + const pingCall = content.find( + (c) => c.meta.scope === 'audit_trail_test/context/as_internal_user' + ); + expect(pingCall).to.be.ok(); + expect(pingCall.meta.type).to.be('elasticsearch.call.internalUser'); + expect(pingCall.meta.user).to.be('elastic'); + expect(pingCall.meta.space).to.be('default'); + }); + + it('logs current user access to elasticsearch via coreStart contract', async () => { + await supertest + .get('/audit_trail_test/contract/as_current_user') + .set('kbn-xsrf', 'foo') + .expect(204); + + const content = await logFile.readJSON(); + + const pingCall = content.find( + (c) => c.meta.scope === 'audit_trail_test/contract/as_current_user' + ); + expect(pingCall).to.be.ok(); + expect(pingCall.meta.type).to.be('elasticsearch.call.currentUser'); + expect(pingCall.meta.user).to.be('elastic'); + expect(pingCall.meta.space).to.be('default'); + }); + + it('logs internal user access to elasticsearch via coreStart contract', async () => { + await supertest + .get('/audit_trail_test/contract/as_internal_user') + .set('kbn-xsrf', 'foo') + .expect(204); + + const content = await logFile.readJSON(); + + const pingCall = content.find( + (c) => c.meta.scope === 'audit_trail_test/contract/as_internal_user' + ); + expect(pingCall).to.be.ok(); + expect(pingCall.meta.type).to.be('elasticsearch.call.internalUser'); + expect(pingCall.meta.user).to.be('elastic'); + expect(pingCall.meta.space).to.be('default'); + }); + }); +} From 34a7fb4ee6b612f4599f59d7f09c7f21547e465b Mon Sep 17 00:00:00 2001 From: restrry Date: Tue, 30 Jun 2020 09:02:57 +0200 Subject: [PATCH 20/32] cleanup --- .../audit_trail_client.ts | 0 x-pack/plugins/audit_trail/server/plugin.ts | 31 ++++++++++--------- x-pack/plugins/audit_trail/server/types.ts | 5 --- 3 files changed, 17 insertions(+), 19 deletions(-) rename x-pack/plugins/audit_trail/server/{services => client}/audit_trail_client.ts (100%) diff --git a/x-pack/plugins/audit_trail/server/services/audit_trail_client.ts b/x-pack/plugins/audit_trail/server/client/audit_trail_client.ts similarity index 100% rename from x-pack/plugins/audit_trail/server/services/audit_trail_client.ts rename to x-pack/plugins/audit_trail/server/client/audit_trail_client.ts diff --git a/x-pack/plugins/audit_trail/server/plugin.ts b/x-pack/plugins/audit_trail/server/plugin.ts index f553d31946a19..48be9f50980b7 100644 --- a/x-pack/plugins/audit_trail/server/plugin.ts +++ b/x-pack/plugins/audit_trail/server/plugin.ts @@ -17,39 +17,44 @@ import { PluginInitializerContext, } from 'src/core/server'; -import { AuditEvent, AuditTrailPluginSetup } from './types'; -import { AuditTrailClient } from './services/audit_trail_client'; +import { AuditEvent } from './types'; +import { AuditTrailClient } from './client/audit_trail_client'; import { AuditTrailConfigType } from './config'; import { SecurityPluginSetup } from '../../security/server'; import { SpacesPluginSetup } from '../../spaces/server'; +import { LicensingPluginStart } from '../../licensing/server'; interface DepsSetup { security: SecurityPluginSetup; spaces: SpacesPluginSetup; } -export class AuditTrailPlugin implements Plugin { +interface DepStart { + licensing: LicensingPluginStart; +} + +export class AuditTrailPlugin implements Plugin { private readonly logger: Logger; private readonly config$: Observable; + private readonly event$ = new Subject(); constructor(private readonly context: PluginInitializerContext) { this.logger = this.context.logger.get(); this.config$ = this.context.config.create(); } - public async setup(core: CoreSetup, deps: DepsSetup) { + public setup(core: CoreSetup, deps: DepsSetup) { const depsApi = { getCurrentUser: deps.security.authc.getCurrentUser, getSpaceId: deps.spaces.spacesService.getSpaceId, }; - const event$ = new Subject(); - event$.subscribe(({ message, ...other }) => this.logger.debug(message, other)); + this.event$.subscribe(({ message, ...other }) => this.logger.debug(message, other)); core.auditTrail.register({ - asScoped(request: KibanaRequest) { - return new AuditTrailClient(request, event$, depsApi); + asScoped: (request: KibanaRequest) => { + return new AuditTrailClient(request, this.event$, depsApi); }, }); @@ -71,10 +76,6 @@ export class AuditTrailPlugin implements Plugin { })) ) ); - - return { - event$: event$.asObservable(), - }; } private getAppender(config: AuditTrailConfigType): AppenderConfigType { @@ -84,11 +85,13 @@ export class AuditTrailPlugin implements Plugin { layout: { kind: 'pattern', highlight: true, - // pattern: '[%level] %message %meta', }, } ); } - public start(core: CoreStart) {} + public start(core: CoreStart, deps: DepStart) {} + public stop() { + this.event$.complete(); + } } diff --git a/x-pack/plugins/audit_trail/server/types.ts b/x-pack/plugins/audit_trail/server/types.ts index 9b3d7d3b4d614..d0eb0e7eaa981 100644 --- a/x-pack/plugins/audit_trail/server/types.ts +++ b/x-pack/plugins/audit_trail/server/types.ts @@ -3,7 +3,6 @@ * or more contributor license agreements. Licensed under the Elastic License; * you may not use this file except in compliance with the Elastic License. */ -import { Observable } from 'rxjs'; /** * Event enhanced with request context data. Provided to an external consumer. * @public @@ -15,7 +14,3 @@ export interface AuditEvent { user?: string; space?: string; } - -export interface AuditTrailPluginSetup { - event$: Observable; -} From cae7ea14f0aca5a43788da19fc519543bc709f87 Mon Sep 17 00:00:00 2001 From: restrry Date: Tue, 30 Jun 2020 12:03:37 +0200 Subject: [PATCH 21/32] add example --- src/core/server/audit_trail/types.ts | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/src/core/server/audit_trail/types.ts b/src/core/server/audit_trail/types.ts index 577b669e59e19..2881ed56346c1 100644 --- a/src/core/server/audit_trail/types.ts +++ b/src/core/server/audit_trail/types.ts @@ -37,6 +37,18 @@ export interface AuditableEvent { export interface Auditor { /** * Add a record to audit log. + * Service attaches to a log record: + * - metadata about an end-user initiating an operation + * - scope name, if presents + * + * @example + * How to add a record in audit log: + * ```typescript + * router.get({ path: '/my_endpoint', validate: false }, async (context, request, response) => { + * context.core.auditor.withScope('my_plugin_operation'); + * const value = await context.core.elasticsearch.legacy.client.callAsCurrentUser('...'); + * context.core.add({ type: 'operation.type', message: 'perform an operation in ... endpoint' }); + * ``` */ add(event: AuditableEvent): void; /** From 94b44db0a9b3d8fe2f3de974685dc5256b082082 Mon Sep 17 00:00:00 2001 From: restrry Date: Tue, 30 Jun 2020 12:03:58 +0200 Subject: [PATCH 22/32] add mocks for spaces plugin --- x-pack/plugins/spaces/server/mocks.ts | 14 ++++++++++++++ 1 file changed, 14 insertions(+) create mode 100644 x-pack/plugins/spaces/server/mocks.ts diff --git a/x-pack/plugins/spaces/server/mocks.ts b/x-pack/plugins/spaces/server/mocks.ts new file mode 100644 index 0000000000000..99d547a92eeb6 --- /dev/null +++ b/x-pack/plugins/spaces/server/mocks.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 { spacesServiceMock } from './spaces_service/spaces_service.mock'; + +function createSetupMock() { + return { spacesService: spacesServiceMock.createSetupContract() }; +} + +export const spacesMock = { + createSetup: createSetupMock, +}; From 2adfb8a701f12bc551d19563dc471a4599887b22 Mon Sep 17 00:00:00 2001 From: restrry Date: Tue, 30 Jun 2020 12:04:23 +0200 Subject: [PATCH 23/32] add unit tests --- .../server/client/audit_trail_client.test.ts | 50 +++++++ .../plugins/audit_trail/server/config.test.ts | 56 ++++++++ .../plugins/audit_trail/server/plugin.test.ts | 125 ++++++++++++++++++ 3 files changed, 231 insertions(+) create mode 100644 x-pack/plugins/audit_trail/server/client/audit_trail_client.test.ts create mode 100644 x-pack/plugins/audit_trail/server/config.test.ts create mode 100644 x-pack/plugins/audit_trail/server/plugin.test.ts diff --git a/x-pack/plugins/audit_trail/server/client/audit_trail_client.test.ts b/x-pack/plugins/audit_trail/server/client/audit_trail_client.test.ts new file mode 100644 index 0000000000000..cd9449271dd7d --- /dev/null +++ b/x-pack/plugins/audit_trail/server/client/audit_trail_client.test.ts @@ -0,0 +1,50 @@ +/* + * 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 { Subject } from 'rxjs'; + +import { AuditTrailClient } from './audit_trail_client'; +import { AuditEvent } from '../types'; + +import { httpServerMock } from '../../../../../src/core/server/mocks'; +import { securityMock } from '../../../security/server/mocks'; +import { spacesMock } from '../../../spaces/server/mocks'; + +describe('AuditTrailClient', () => { + let client: AuditTrailClient; + let event$: Subject; + const deps = { + getCurrentUser: securityMock.createSetup().authc.getCurrentUser, + getSpaceId: spacesMock.createSetup().spacesService.getSpaceId, + }; + + beforeEach(() => { + event$ = new Subject(); + client = new AuditTrailClient(httpServerMock.createKibanaRequest(), event$, deps); + }); + + afterEach(() => { + event$.complete(); + }); + + describe('#withScope', () => { + it('registers upper level scope', (done) => { + client.withScope('scope_name'); + event$.subscribe((event) => { + expect(event.scope).toBe('scope_name'); + done(); + }); + client.add({ message: 'message', type: 'type' }); + }); + + it('throws an exception if tries to re-write a scope', () => { + client.withScope('scope_name'); + expect(() => client.withScope('another_scope_name')).toThrowErrorMatchingInlineSnapshot( + `"AuditTrail scope is already set to: scope_name"` + ); + }); + }); +}); diff --git a/x-pack/plugins/audit_trail/server/config.test.ts b/x-pack/plugins/audit_trail/server/config.test.ts new file mode 100644 index 0000000000000..032e4447c50d2 --- /dev/null +++ b/x-pack/plugins/audit_trail/server/config.test.ts @@ -0,0 +1,56 @@ +/* + * 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 { config } from './config'; + +describe('config schema', () => { + it('generates proper defaults', () => { + expect(config.schema.validate({})).toEqual({ + enabled: true, + logger: { + enabled: false, + }, + }); + }); + + it('accepts an appender', () => { + const appender = config.schema.validate({ + appender: { + kind: 'file', + path: '/path/to/file.txt', + layout: { + kind: 'json', + }, + }, + logger: { + enabled: false, + }, + }).appender; + + expect(appender).toEqual({ + kind: 'file', + path: '/path/to/file.txt', + layout: { + kind: 'json', + }, + }); + }); + + it('rejects an appender if not fully configured', () => { + expect(() => + config.schema.validate({ + // no layout configured + appender: { + kind: 'file', + path: '/path/to/file.txt', + }, + logger: { + enabled: false, + }, + }) + ).toThrow(); + }); +}); diff --git a/x-pack/plugins/audit_trail/server/plugin.test.ts b/x-pack/plugins/audit_trail/server/plugin.test.ts new file mode 100644 index 0000000000000..fa5fd1bcc1e14 --- /dev/null +++ b/x-pack/plugins/audit_trail/server/plugin.test.ts @@ -0,0 +1,125 @@ +/* + * 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 { first } from 'rxjs/operators'; +import { AuditTrailPlugin } from './plugin'; +import { coreMock } from '../../../../src/core/server/mocks'; + +import { securityMock } from '../../security/server/mocks'; +import { spacesMock } from '../../spaces/server/mocks'; + +describe('AuditTrail plugin', () => { + describe('#setup', () => { + let plugin: AuditTrailPlugin; + let pluginInitContextMock: ReturnType; + let coreSetup: ReturnType; + + const deps = { + security: securityMock.createSetup(), + spaces: spacesMock.createSetup(), + }; + + beforeEach(() => { + pluginInitContextMock = coreMock.createPluginInitializerContext(); + plugin = new AuditTrailPlugin(pluginInitContextMock); + coreSetup = coreMock.createSetup(); + }); + + afterEach(async () => { + await plugin.stop(); + }); + + it('registers AuditTrail factory', async () => { + pluginInitContextMock = coreMock.createPluginInitializerContext(); + plugin = new AuditTrailPlugin(pluginInitContextMock); + plugin.setup(coreSetup, deps); + expect(coreSetup.auditTrail.register).toHaveBeenCalledTimes(1); + }); + + describe('logger', () => { + it('registers a custom logger', async () => { + pluginInitContextMock = coreMock.createPluginInitializerContext(); + plugin = new AuditTrailPlugin(pluginInitContextMock); + plugin.setup(coreSetup, deps); + + expect(coreSetup.logging.configure).toHaveBeenCalledTimes(1); + }); + + it('disables logging if config.logger.enabled: false', async () => { + const config = { + logger: { + enabled: false, + }, + }; + pluginInitContextMock = coreMock.createPluginInitializerContext(config); + + plugin = new AuditTrailPlugin(pluginInitContextMock); + plugin.setup(coreSetup, deps); + + const args = coreSetup.logging.configure.mock.calls[0][0]; + const value = await args.pipe(first()).toPromise(); + expect(value.loggers?.every((l) => l.level === 'off')).toBe(true); + }); + it('logs with DEBUG level if config.logger.enabled: true', async () => { + const config = { + logger: { + enabled: true, + }, + }; + pluginInitContextMock = coreMock.createPluginInitializerContext(config); + + plugin = new AuditTrailPlugin(pluginInitContextMock); + plugin.setup(coreSetup, deps); + + const args = coreSetup.logging.configure.mock.calls[0][0]; + const value = await args.pipe(first()).toPromise(); + expect(value.loggers?.every((l) => l.level === 'debug')).toBe(true); + }); + it('uses appender adjusted via config', async () => { + const config = { + appender: { + kind: 'file', + path: '/path/to/file.txt', + }, + logger: { + enabled: true, + }, + }; + pluginInitContextMock = coreMock.createPluginInitializerContext(config); + + plugin = new AuditTrailPlugin(pluginInitContextMock); + plugin.setup(coreSetup, deps); + + const args = coreSetup.logging.configure.mock.calls[0][0]; + const value = await args.pipe(first()).toPromise(); + expect(value.appenders).toEqual({ auditTrailAppender: config.appender }); + }); + it('falls back to the default appender if not configured', async () => { + const config = { + logger: { + enabled: true, + }, + }; + pluginInitContextMock = coreMock.createPluginInitializerContext(config); + + plugin = new AuditTrailPlugin(pluginInitContextMock); + plugin.setup(coreSetup, deps); + + const args = coreSetup.logging.configure.mock.calls[0][0]; + const value = await args.pipe(first()).toPromise(); + expect(value.appenders).toEqual({ + auditTrailAppender: { + kind: 'console', + layout: { + kind: 'pattern', + highlight: true, + }, + }, + }); + }); + }); + }); +}); From 2b0ed2d486f9d3ee3573e36d1678b0208954b71a Mon Sep 17 00:00:00 2001 From: restrry Date: Tue, 30 Jun 2020 12:13:20 +0200 Subject: [PATCH 24/32] update docs --- .../kibana-plugin-core-server.auditor.add.md | 14 +++++++++++++- .../server/kibana-plugin-core-server.auditor.md | 2 +- 2 files changed, 14 insertions(+), 2 deletions(-) diff --git a/docs/development/core/server/kibana-plugin-core-server.auditor.add.md b/docs/development/core/server/kibana-plugin-core-server.auditor.add.md index 1fc7c21449668..2cfdab828452f 100644 --- a/docs/development/core/server/kibana-plugin-core-server.auditor.add.md +++ b/docs/development/core/server/kibana-plugin-core-server.auditor.add.md @@ -4,7 +4,7 @@ ## Auditor.add() method -Add a record to audit log. +Add a record to audit log. Service attaches to a log record: - metadata about an end-user initiating an operation - scope name, if presents Signature: @@ -22,3 +22,15 @@ add(event: AuditableEvent): void; `void` +## Example + +How to add a record in audit log: + +```typescript +router.get({ path: '/my_endpoint', validate: false }, async (context, request, response) => { + context.core.auditor.withScope('my_plugin_operation'); + const value = await context.core.elasticsearch.legacy.client.callAsCurrentUser('...'); + context.core.add({ type: 'operation.type', message: 'perform an operation in ... endpoint' }); + +``` + diff --git a/docs/development/core/server/kibana-plugin-core-server.auditor.md b/docs/development/core/server/kibana-plugin-core-server.auditor.md index 9705f4668bd46..baddd91d7a45c 100644 --- a/docs/development/core/server/kibana-plugin-core-server.auditor.md +++ b/docs/development/core/server/kibana-plugin-core-server.auditor.md @@ -16,6 +16,6 @@ export interface Auditor | Method | Description | | --- | --- | -| [add(event)](./kibana-plugin-core-server.auditor.add.md) | Add a record to audit log. | +| [add(event)](./kibana-plugin-core-server.auditor.add.md) | Add a record to audit log. Service attaches to a log record: - metadata about an end-user initiating an operation - scope name, if presents | | [withScope(name)](./kibana-plugin-core-server.auditor.withscope.md) | Add a high-level scope name for logged events. It helps to identify the root cause of low-level events. | From c6e0e8cadd701d8de77de3c77217d236f46ce61e Mon Sep 17 00:00:00 2001 From: restrry Date: Tue, 30 Jun 2020 12:17:22 +0200 Subject: [PATCH 25/32] test description --- src/core/server/audit_trail/audit_trail_service.test.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/core/server/audit_trail/audit_trail_service.test.ts b/src/core/server/audit_trail/audit_trail_service.test.ts index db71bf545c548..c427ea3111cf3 100644 --- a/src/core/server/audit_trail/audit_trail_service.test.ts +++ b/src/core/server/audit_trail/audit_trail_service.test.ts @@ -60,7 +60,7 @@ describe('AuditTrailService', () => { expect(scopedMock).toHaveBeenCalledWith(kibanaRequest); }); - it('passes auditable event to every auditor', () => { + it('passes auditable event to an auditor', () => { const addEventMock = jest.fn(); const auditorFactory = { asScoped() { From 40de1d01a13a16d31ec7f007fb223ba1bf636659 Mon Sep 17 00:00:00 2001 From: Mikhail Shustov Date: Wed, 1 Jul 2020 09:59:00 +0300 Subject: [PATCH 26/32] Apply suggestions from code review apply @jportner suggestions Co-authored-by: Joe Portner <5295965+jportner@users.noreply.github.com> --- src/core/server/elasticsearch/legacy/cluster_client.ts | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/src/core/server/elasticsearch/legacy/cluster_client.ts b/src/core/server/elasticsearch/legacy/cluster_client.ts index 0c6e2b72fbb3f..7a39113d25a14 100644 --- a/src/core/server/elasticsearch/legacy/cluster_client.ts +++ b/src/core/server/elasticsearch/legacy/cluster_client.ts @@ -206,11 +206,12 @@ export class LegacyClusterClient implements ILegacyClusterClient { this.callAsInternalUser, this.callAsCurrentUser, filterHeaders(this.getHeaders(request), this.config.requestHeadersWhitelist), - this.getScopedScopedAuditor(request) + this.getScopedAuditor(request) ); } - private getScopedScopedAuditor(request?: ScopeableRequest) { + private getScopedAuditor(request?: ScopeableRequest) { + // TODO: support alternative credential owners from outside of Request context in #39430 if (request && isRealRequest(request)) { const kibanaRequest = request instanceof KibanaRequest ? request : KibanaRequest.from(request); From 7cfb50919be2a42a4d111a6f87e845aadce0306a Mon Sep 17 00:00:00 2001 From: restrry Date: Wed, 1 Jul 2020 10:21:33 +0200 Subject: [PATCH 27/32] add unit tests --- .../audit_trail/audit_trail_service.mock.ts | 8 ++++ .../server/audit_trail/audit_trail_service.ts | 1 + .../legacy/cluster_client.test.ts | 42 +++++++++++++---- .../legacy/scoped_cluster_client.test.ts | 45 +++++++++++++++++++ src/core/server/server.test.mocks.ts | 6 +++ src/core/server/server.test.ts | 7 +++ src/core/server/server.ts | 1 + 7 files changed, 102 insertions(+), 8 deletions(-) diff --git a/src/core/server/audit_trail/audit_trail_service.mock.ts b/src/core/server/audit_trail/audit_trail_service.mock.ts index de0ea02fbb7ad..cb978a2902b37 100644 --- a/src/core/server/audit_trail/audit_trail_service.mock.ts +++ b/src/core/server/audit_trail/audit_trail_service.mock.ts @@ -18,6 +18,7 @@ */ import { AuditTrailSetup, AuditTrailStart, Auditor } from './types'; +import { AuditTrailService } from './audit_trail_service'; const createSetupContractMock = () => { const mocked: jest.Mocked = { @@ -42,7 +43,14 @@ const createStartContractMock = () => { return mocked; }; +const createServiceMock = (): jest.Mocked> => ({ + setup: jest.fn().mockResolvedValue(createSetupContractMock()), + start: jest.fn().mockResolvedValue(createStartContractMock()), + stop: jest.fn(), +}); + export const auditTrailServiceMock = { + create: createServiceMock, createSetupContract: createSetupContractMock, createStartContract: createStartContractMock, createAuditorFactory: createStartContractMock, diff --git a/src/core/server/audit_trail/audit_trail_service.ts b/src/core/server/audit_trail/audit_trail_service.ts index cbea3126dc9d4..bdc49423a3b28 100644 --- a/src/core/server/audit_trail/audit_trail_service.ts +++ b/src/core/server/audit_trail/audit_trail_service.ts @@ -31,6 +31,7 @@ const defaultAuditorFactory: AuditorFactory = { }; }, }; + export class AuditTrailService implements CoreService { private readonly log: Logger; private auditor: AuditorFactory = defaultAuditorFactory; diff --git a/src/core/server/elasticsearch/legacy/cluster_client.test.ts b/src/core/server/elasticsearch/legacy/cluster_client.test.ts index dbb4cfdf199f1..2f0f80728c707 100644 --- a/src/core/server/elasticsearch/legacy/cluster_client.test.ts +++ b/src/core/server/elasticsearch/legacy/cluster_client.test.ts @@ -453,14 +453,6 @@ describe('#asScoped', () => { ); }); - test("doesn't create Auditor for a fake request", async () => { - const getAuthHeaders = jest.fn(); - clusterClient = new LegacyClusterClient(mockEsConfig, mockLogger, getAuthHeaders); - clusterClient.asScoped({ headers: { one: '1', two: '2', three: '3' } }); - - expect(getAuthHeaders).not.toHaveBeenCalled(); - }); - test('filters a fake request headers', async () => { clusterClient = new LegacyClusterClient( mockEsConfig, @@ -477,6 +469,40 @@ describe('#asScoped', () => { undefined ); }); + + describe('Auditor', () => { + it('creates Auditor for KibanaRequest', async () => { + const auditor = auditTrailServiceMock.createAuditor(); + const auditorFactory = auditTrailServiceMock.createAuditorFactory(); + auditorFactory.asScoped.mockReturnValue(auditor); + clusterClient = new LegacyClusterClient(mockEsConfig, mockLogger, () => auditorFactory); + clusterClient.asScoped(httpServerMock.createKibanaRequest()); + + expect(MockScopedClusterClient).toHaveBeenCalledTimes(1); + expect(MockScopedClusterClient).toHaveBeenCalledWith( + expect.any(Function), + expect.any(Function), + {}, + auditor + ); + }); + + it("doesn't create Auditor for a fake request", async () => { + const getAuthHeaders = jest.fn(); + clusterClient = new LegacyClusterClient(mockEsConfig, mockLogger, getAuthHeaders); + clusterClient.asScoped({ headers: { one: '1', two: '2', three: '3' } }); + + expect(getAuthHeaders).not.toHaveBeenCalled(); + }); + + it("doesn't create Auditor when no request passed", async () => { + const getAuthHeaders = jest.fn(); + clusterClient = new LegacyClusterClient(mockEsConfig, mockLogger, getAuthHeaders); + clusterClient.asScoped(); + + expect(getAuthHeaders).not.toHaveBeenCalled(); + }); + }); }); describe('#close', () => { diff --git a/src/core/server/elasticsearch/legacy/scoped_cluster_client.test.ts b/src/core/server/elasticsearch/legacy/scoped_cluster_client.test.ts index 2eb8cefb564ae..f1096d5d602f4 100644 --- a/src/core/server/elasticsearch/legacy/scoped_cluster_client.test.ts +++ b/src/core/server/elasticsearch/legacy/scoped_cluster_client.test.ts @@ -18,6 +18,7 @@ */ import { LegacyScopedClusterClient } from './scoped_cluster_client'; +import { auditTrailServiceMock } from '../../audit_trail/audit_trail_service.mock'; let internalAPICaller: jest.Mock; let scopedAPICaller: jest.Mock; @@ -83,6 +84,28 @@ describe('#callAsInternalUser', () => { expect(scopedAPICaller).not.toHaveBeenCalled(); }); + + describe('Auditor', () => { + it('does not fail when no auditor provided', () => { + const clusterClientWithoutAuditor = new LegacyScopedClusterClient(jest.fn(), jest.fn()); + expect(() => clusterClientWithoutAuditor.callAsInternalUser('endpoint')).not.toThrow(); + }); + it('creates an audit record if auditor provided', () => { + const auditor = auditTrailServiceMock.createAuditor(); + const clusterClientWithoutAuditor = new LegacyScopedClusterClient( + jest.fn(), + jest.fn(), + {}, + auditor + ); + clusterClientWithoutAuditor.callAsInternalUser('endpoint'); + expect(auditor.add).toHaveBeenCalledTimes(1); + expect(auditor.add).toHaveBeenLastCalledWith({ + message: 'endpoint', + type: 'elasticsearch.call.internalUser', + }); + }); + }); }); describe('#callAsCurrentUser', () => { @@ -206,4 +229,26 @@ describe('#callAsCurrentUser', () => { expect(internalAPICaller).not.toHaveBeenCalled(); }); + + describe('Auditor', () => { + it('does not fail when no auditor provided', () => { + const clusterClientWithoutAuditor = new LegacyScopedClusterClient(jest.fn(), jest.fn()); + expect(() => clusterClientWithoutAuditor.callAsCurrentUser('endpoint')).not.toThrow(); + }); + it('creates an audit record if auditor provided', () => { + const auditor = auditTrailServiceMock.createAuditor(); + const clusterClientWithoutAuditor = new LegacyScopedClusterClient( + jest.fn(), + jest.fn(), + {}, + auditor + ); + clusterClientWithoutAuditor.callAsCurrentUser('endpoint'); + expect(auditor.add).toHaveBeenCalledTimes(1); + expect(auditor.add).toHaveBeenLastCalledWith({ + message: 'endpoint', + type: 'elasticsearch.call.currentUser', + }); + }); + }); }); diff --git a/src/core/server/server.test.mocks.ts b/src/core/server/server.test.mocks.ts index e5e710d54e04b..82d0c095bfe95 100644 --- a/src/core/server/server.test.mocks.ts +++ b/src/core/server/server.test.mocks.ts @@ -97,3 +97,9 @@ export const mockLoggingService = loggingServiceMock.create(); jest.doMock('./logging/logging_service', () => ({ LoggingService: jest.fn(() => mockLoggingService), })); + +import { auditTrailServiceMock } from './audit_trail/audit_trail_service.mock'; +export const mockAuditTrailService = auditTrailServiceMock.create(); +jest.doMock('./audit_trail/audit_trail_service', () => ({ + AuditTrailService: jest.fn(() => mockAuditTrailService), +})); diff --git a/src/core/server/server.test.ts b/src/core/server/server.test.ts index 1f507a85d3ddf..417f66a2988c2 100644 --- a/src/core/server/server.test.ts +++ b/src/core/server/server.test.ts @@ -31,6 +31,7 @@ import { mockMetricsService, mockStatusService, mockLoggingService, + mockAuditTrailService, } from './server.test.mocks'; import { BehaviorSubject } from 'rxjs'; @@ -70,6 +71,7 @@ test('sets up services on "setup"', async () => { expect(mockMetricsService.setup).not.toHaveBeenCalled(); expect(mockStatusService.setup).not.toHaveBeenCalled(); expect(mockLoggingService.setup).not.toHaveBeenCalled(); + expect(mockAuditTrailService.setup).not.toHaveBeenCalled(); await server.setup(); @@ -83,6 +85,7 @@ test('sets up services on "setup"', async () => { expect(mockMetricsService.setup).toHaveBeenCalledTimes(1); expect(mockStatusService.setup).toHaveBeenCalledTimes(1); expect(mockLoggingService.setup).toHaveBeenCalledTimes(1); + expect(mockAuditTrailService.setup).toHaveBeenCalledTimes(1); }); test('injects legacy dependency to context#setup()', async () => { @@ -123,6 +126,7 @@ test('runs services on "start"', async () => { expect(mockSavedObjectsService.start).not.toHaveBeenCalled(); expect(mockUiSettingsService.start).not.toHaveBeenCalled(); expect(mockMetricsService.start).not.toHaveBeenCalled(); + expect(mockAuditTrailService.start).not.toHaveBeenCalled(); await server.start(); @@ -131,6 +135,7 @@ test('runs services on "start"', async () => { expect(mockSavedObjectsService.start).toHaveBeenCalledTimes(1); expect(mockUiSettingsService.start).toHaveBeenCalledTimes(1); expect(mockMetricsService.start).toHaveBeenCalledTimes(1); + expect(mockAuditTrailService.start).toHaveBeenCalledTimes(1); }); test('does not fail on "setup" if there are unused paths detected', async () => { @@ -155,6 +160,7 @@ test('stops services on "stop"', async () => { expect(mockMetricsService.stop).not.toHaveBeenCalled(); expect(mockStatusService.stop).not.toHaveBeenCalled(); expect(mockLoggingService.stop).not.toHaveBeenCalled(); + expect(mockAuditTrailService.stop).not.toHaveBeenCalled(); await server.stop(); @@ -167,6 +173,7 @@ test('stops services on "stop"', async () => { expect(mockMetricsService.stop).toHaveBeenCalledTimes(1); expect(mockStatusService.stop).toHaveBeenCalledTimes(1); expect(mockLoggingService.stop).toHaveBeenCalledTimes(1); + expect(mockAuditTrailService.stop).toHaveBeenCalledTimes(1); }); test(`doesn't setup core services if config validation fails`, async () => { diff --git a/src/core/server/server.ts b/src/core/server/server.ts index 4bdaa32d3e371..86b8c550b2d9b 100644 --- a/src/core/server/server.ts +++ b/src/core/server/server.ts @@ -264,6 +264,7 @@ export class Server { await this.metrics.stop(); await this.status.stop(); await this.logging.stop(); + await this.auditTrail.stop(); } private registerCoreContext(coreSetup: InternalCoreSetup) { From 40a456d63834fbd1085835203655b777fd8a7056 Mon Sep 17 00:00:00 2001 From: restrry Date: Thu, 2 Jul 2020 10:41:05 +0200 Subject: [PATCH 28/32] more robust tests --- .../server/client/audit_trail_client.ts | 1 + .../test_suites/audit_trail/index.ts | 27 ++++++++++++++++--- 2 files changed, 24 insertions(+), 4 deletions(-) diff --git a/x-pack/plugins/audit_trail/server/client/audit_trail_client.ts b/x-pack/plugins/audit_trail/server/client/audit_trail_client.ts index 638d4190e0377..fe624821f5639 100644 --- a/x-pack/plugins/audit_trail/server/client/audit_trail_client.ts +++ b/x-pack/plugins/audit_trail/server/client/audit_trail_client.ts @@ -32,6 +32,7 @@ export class AuditTrailClient implements Auditor { public add(event: AuditableEvent) { const user = this.deps.getCurrentUser(this.request); + // doesn't use getSpace since it's async operation calling ES const spaceId = this.deps.getSpaceId(this.request); this.event$.next({ diff --git a/x-pack/test/plugin_functional/test_suites/audit_trail/index.ts b/x-pack/test/plugin_functional/test_suites/audit_trail/index.ts index af52b6022a9e9..fb66f0dffc12a 100644 --- a/x-pack/test/plugin_functional/test_suites/audit_trail/index.ts +++ b/x-pack/test/plugin_functional/test_suites/audit_trail/index.ts @@ -22,10 +22,17 @@ class FileWrapper { const content = await this.read(); return content.map((l) => JSON.parse(l)); } + // writing in a file is an async operation. we use this method to make sure logs have been written. + async isNotEmpty() { + const content = await this.read(); + const line = content[0]; + return line.length > 0; + } } export default function ({ getPageObjects, getService }: FtrProviderContext) { const supertest = getService('supertest'); + const retry = getService('retry'); describe('Audit trail service', function () { this.tags('ciGroup7'); @@ -45,8 +52,11 @@ export default function ({ getPageObjects, getService }: FtrProviderContext) { .set('kbn-xsrf', 'foo') .expect(204); - const content = await logFile.readJSON(); + await retry.waitFor('logs event in the dest file', async () => { + return await logFile.isNotEmpty(); + }); + const content = await logFile.readJSON(); const pingCall = content.find( (c) => c.meta.scope === 'audit_trail_test/context/as_current_user' ); @@ -62,8 +72,11 @@ export default function ({ getPageObjects, getService }: FtrProviderContext) { .set('kbn-xsrf', 'foo') .expect(204); - const content = await logFile.readJSON(); + await retry.waitFor('logs event in the dest file', async () => { + return await logFile.isNotEmpty(); + }); + const content = await logFile.readJSON(); const pingCall = content.find( (c) => c.meta.scope === 'audit_trail_test/context/as_internal_user' ); @@ -79,8 +92,11 @@ export default function ({ getPageObjects, getService }: FtrProviderContext) { .set('kbn-xsrf', 'foo') .expect(204); - const content = await logFile.readJSON(); + await retry.waitFor('logs event in the dest file', async () => { + return await logFile.isNotEmpty(); + }); + const content = await logFile.readJSON(); const pingCall = content.find( (c) => c.meta.scope === 'audit_trail_test/contract/as_current_user' ); @@ -96,8 +112,11 @@ export default function ({ getPageObjects, getService }: FtrProviderContext) { .set('kbn-xsrf', 'foo') .expect(204); - const content = await logFile.readJSON(); + await retry.waitFor('logs event in the dest file', async () => { + return await logFile.isNotEmpty(); + }); + const content = await logFile.readJSON(); const pingCall = content.find( (c) => c.meta.scope === 'audit_trail_test/contract/as_internal_user' ); From d2837558b31ff790a0c1db94afc5e30b12f531be Mon Sep 17 00:00:00 2001 From: restrry Date: Thu, 2 Jul 2020 14:21:00 +0200 Subject: [PATCH 29/32] make spaces optional --- x-pack/plugins/audit_trail/kibana.json | 4 ++-- .../plugins/audit_trail/server/client/audit_trail_client.ts | 4 ++-- x-pack/plugins/audit_trail/server/plugin.ts | 4 ++-- 3 files changed, 6 insertions(+), 6 deletions(-) diff --git a/x-pack/plugins/audit_trail/kibana.json b/x-pack/plugins/audit_trail/kibana.json index eb7255a113a81..ce92e232ec13b 100644 --- a/x-pack/plugins/audit_trail/kibana.json +++ b/x-pack/plugins/audit_trail/kibana.json @@ -5,6 +5,6 @@ "configPath": ["xpack", "audit_trail"], "server": true, "ui": false, - "requiredPlugins": ["licensing", "security", "spaces"], - "optionalPlugins": [] + "requiredPlugins": ["licensing", "security"], + "optionalPlugins": ["spaces"] } diff --git a/x-pack/plugins/audit_trail/server/client/audit_trail_client.ts b/x-pack/plugins/audit_trail/server/client/audit_trail_client.ts index fe624821f5639..8703d1e40b9f3 100644 --- a/x-pack/plugins/audit_trail/server/client/audit_trail_client.ts +++ b/x-pack/plugins/audit_trail/server/client/audit_trail_client.ts @@ -12,7 +12,7 @@ import { SpacesPluginSetup } from '../../../spaces/server'; interface Deps { getCurrentUser: SecurityPluginSetup['authc']['getCurrentUser']; - getSpaceId: SpacesPluginSetup['spacesService']['getSpaceId']; + getSpaceId?: SpacesPluginSetup['spacesService']['getSpaceId']; } export class AuditTrailClient implements Auditor { @@ -33,7 +33,7 @@ export class AuditTrailClient implements Auditor { public add(event: AuditableEvent) { const user = this.deps.getCurrentUser(this.request); // doesn't use getSpace since it's async operation calling ES - const spaceId = this.deps.getSpaceId(this.request); + const spaceId = this.deps.getSpaceId ? this.deps.getSpaceId(this.request) : undefined; this.event$.next({ message: event.message, diff --git a/x-pack/plugins/audit_trail/server/plugin.ts b/x-pack/plugins/audit_trail/server/plugin.ts index 48be9f50980b7..cf423f230aef9 100644 --- a/x-pack/plugins/audit_trail/server/plugin.ts +++ b/x-pack/plugins/audit_trail/server/plugin.ts @@ -27,7 +27,7 @@ import { LicensingPluginStart } from '../../licensing/server'; interface DepsSetup { security: SecurityPluginSetup; - spaces: SpacesPluginSetup; + spaces?: SpacesPluginSetup; } interface DepStart { @@ -47,7 +47,7 @@ export class AuditTrailPlugin implements Plugin { public setup(core: CoreSetup, deps: DepsSetup) { const depsApi = { getCurrentUser: deps.security.authc.getCurrentUser, - getSpaceId: deps.spaces.spacesService.getSpaceId, + getSpaceId: deps.spaces?.spacesService.getSpaceId, }; this.event$.subscribe(({ message, ...other }) => this.logger.debug(message, other)); From 05985af2a668b9f225b1cea135b3e899f04345c8 Mon Sep 17 00:00:00 2001 From: restrry Date: Tue, 7 Jul 2020 19:05:47 +0200 Subject: [PATCH 30/32] address comments --- .../server/audit_trail/audit_trail_service.mock.ts | 2 +- .../server/audit_trail/audit_trail_service.test.ts | 6 +++--- src/core/server/audit_trail/audit_trail_service.ts | 2 +- src/core/server/audit_trail/types.ts | 4 ++-- .../server/client/audit_trail_client.test.ts | 10 +++++----- .../audit_trail/server/client/audit_trail_client.ts | 4 ++-- x-pack/plugins/audit_trail/server/config.test.ts | 2 +- x-pack/plugins/audit_trail/server/config.ts | 6 +++--- .../sources/ems_tms_source/ems_tms_source.test.js | 2 +- x-pack/test/plugin_functional/config.ts | 1 + .../plugins/audit_trail_test/server/plugin.ts | 8 ++++---- 11 files changed, 24 insertions(+), 23 deletions(-) diff --git a/src/core/server/audit_trail/audit_trail_service.mock.ts b/src/core/server/audit_trail/audit_trail_service.mock.ts index cb978a2902b37..d63b3539e5cdc 100644 --- a/src/core/server/audit_trail/audit_trail_service.mock.ts +++ b/src/core/server/audit_trail/audit_trail_service.mock.ts @@ -30,7 +30,7 @@ const createSetupContractMock = () => { const createAuditorMock = () => { const mocked: jest.Mocked = { add: jest.fn(), - withScope: jest.fn(), + withAuditScope: jest.fn(), }; return mocked; }; diff --git a/src/core/server/audit_trail/audit_trail_service.test.ts b/src/core/server/audit_trail/audit_trail_service.test.ts index c427ea3111cf3..63b45b62275b6 100644 --- a/src/core/server/audit_trail/audit_trail_service.test.ts +++ b/src/core/server/audit_trail/audit_trail_service.test.ts @@ -32,7 +32,7 @@ describe('AuditTrailService', () => { const { register } = auditTrail.setup(); const auditorFactory: AuditorFactory = { asScoped() { - return { add: () => undefined, withScope: (() => {}) as any }; + return { add: () => undefined, withAuditScope: (() => {}) as any }; }, }; register(auditorFactory); @@ -46,7 +46,7 @@ describe('AuditTrailService', () => { describe('#start', () => { describe('asScoped', () => { it('initialize every auditor with a request', () => { - const scopedMock = jest.fn(() => ({ add: jest.fn(), withScope: jest.fn() })); + const scopedMock = jest.fn(() => ({ add: jest.fn(), withAuditScope: jest.fn() })); const auditorFactory = { asScoped: scopedMock }; const auditTrail = new AuditTrailService(coreContext); @@ -64,7 +64,7 @@ describe('AuditTrailService', () => { const addEventMock = jest.fn(); const auditorFactory = { asScoped() { - return { add: addEventMock, withScope: jest.fn() }; + return { add: addEventMock, withAuditScope: jest.fn() }; }, }; diff --git a/src/core/server/audit_trail/audit_trail_service.ts b/src/core/server/audit_trail/audit_trail_service.ts index bdc49423a3b28..f1841858dbc92 100644 --- a/src/core/server/audit_trail/audit_trail_service.ts +++ b/src/core/server/audit_trail/audit_trail_service.ts @@ -27,7 +27,7 @@ const defaultAuditorFactory: AuditorFactory = { asScoped() { return { add() {}, - withScope() {}, + withAuditScope() {}, }; }, }; diff --git a/src/core/server/audit_trail/types.ts b/src/core/server/audit_trail/types.ts index 2881ed56346c1..b3c1fc3c222fa 100644 --- a/src/core/server/audit_trail/types.ts +++ b/src/core/server/audit_trail/types.ts @@ -45,7 +45,7 @@ export interface Auditor { * How to add a record in audit log: * ```typescript * router.get({ path: '/my_endpoint', validate: false }, async (context, request, response) => { - * context.core.auditor.withScope('my_plugin_operation'); + * context.core.auditor.withAuditScope('my_plugin_operation'); * const value = await context.core.elasticsearch.legacy.client.callAsCurrentUser('...'); * context.core.add({ type: 'operation.type', message: 'perform an operation in ... endpoint' }); * ``` @@ -55,7 +55,7 @@ export interface Auditor { * Add a high-level scope name for logged events. * It helps to identify the root cause of low-level events. */ - withScope(name: string): void; + withAuditScope(name: string): void; } /** diff --git a/x-pack/plugins/audit_trail/server/client/audit_trail_client.test.ts b/x-pack/plugins/audit_trail/server/client/audit_trail_client.test.ts index cd9449271dd7d..cdc0aa4cfd7e7 100644 --- a/x-pack/plugins/audit_trail/server/client/audit_trail_client.test.ts +++ b/x-pack/plugins/audit_trail/server/client/audit_trail_client.test.ts @@ -30,9 +30,9 @@ describe('AuditTrailClient', () => { event$.complete(); }); - describe('#withScope', () => { + describe('#withAuditScope', () => { it('registers upper level scope', (done) => { - client.withScope('scope_name'); + client.withAuditScope('scope_name'); event$.subscribe((event) => { expect(event.scope).toBe('scope_name'); done(); @@ -41,9 +41,9 @@ describe('AuditTrailClient', () => { }); it('throws an exception if tries to re-write a scope', () => { - client.withScope('scope_name'); - expect(() => client.withScope('another_scope_name')).toThrowErrorMatchingInlineSnapshot( - `"AuditTrail scope is already set to: scope_name"` + client.withAuditScope('scope_name'); + expect(() => client.withAuditScope('another_scope_name')).toThrowErrorMatchingInlineSnapshot( + `"Audit scope is already set to: scope_name"` ); }); }); diff --git a/x-pack/plugins/audit_trail/server/client/audit_trail_client.ts b/x-pack/plugins/audit_trail/server/client/audit_trail_client.ts index 8703d1e40b9f3..f12977cddaf0b 100644 --- a/x-pack/plugins/audit_trail/server/client/audit_trail_client.ts +++ b/x-pack/plugins/audit_trail/server/client/audit_trail_client.ts @@ -23,9 +23,9 @@ export class AuditTrailClient implements Auditor { private readonly deps: Deps ) {} - public withScope(name: string) { + public withAuditScope(name: string) { if (this.scope !== undefined) { - throw new Error(`AuditTrail scope is already set to: ${this.scope}`); + throw new Error(`Audit scope is already set to: ${this.scope}`); } this.scope = name; } diff --git a/x-pack/plugins/audit_trail/server/config.test.ts b/x-pack/plugins/audit_trail/server/config.test.ts index 032e4447c50d2..65dfc9f589ec9 100644 --- a/x-pack/plugins/audit_trail/server/config.test.ts +++ b/x-pack/plugins/audit_trail/server/config.test.ts @@ -9,7 +9,7 @@ import { config } from './config'; describe('config schema', () => { it('generates proper defaults', () => { expect(config.schema.validate({})).toEqual({ - enabled: true, + enabled: false, logger: { enabled: false, }, diff --git a/x-pack/plugins/audit_trail/server/config.ts b/x-pack/plugins/audit_trail/server/config.ts index f11636ae0b419..7b05c04c2236f 100644 --- a/x-pack/plugins/audit_trail/server/config.ts +++ b/x-pack/plugins/audit_trail/server/config.ts @@ -5,11 +5,11 @@ */ import { schema, TypeOf } from '@kbn/config-schema'; -import { PluginConfigDescriptor, config as _config } from '../../../../src/core/server'; +import { PluginConfigDescriptor, config as coreConfig } from '../../../../src/core/server'; const configSchema = schema.object({ - enabled: schema.boolean({ defaultValue: true }), - appender: schema.maybe(_config.logging.appenders), + enabled: schema.boolean({ defaultValue: false }), + appender: schema.maybe(coreConfig.logging.appenders), logger: schema.object({ enabled: schema.boolean({ defaultValue: false }), }), diff --git a/x-pack/plugins/maps/public/classes/sources/ems_tms_source/ems_tms_source.test.js b/x-pack/plugins/maps/public/classes/sources/ems_tms_source/ems_tms_source.test.js index 2f466add28262..daac5121938db 100644 --- a/x-pack/plugins/maps/public/classes/sources/ems_tms_source/ems_tms_source.test.js +++ b/x-pack/plugins/maps/public/classes/sources/ems_tms_source/ems_tms_source.test.js @@ -9,7 +9,7 @@ jest.mock('../../../meta', () => { getEmsTmsServices: () => { class MockTMSService { constructor(config) { - this._config = config; + this.coreConfig = config; } getMarkdownAttribution() { return this._config.attributionMarkdown; diff --git a/x-pack/test/plugin_functional/config.ts b/x-pack/test/plugin_functional/config.ts index 364579bab3989..a766e22a34a1d 100644 --- a/x-pack/test/plugin_functional/config.ts +++ b/x-pack/test/plugin_functional/config.ts @@ -52,6 +52,7 @@ export default async function ({ readConfigFile }: FtrConfigProviderContext) { // Required to load new platform plugins via `--plugin-path` flag. '--env.name=development', + '--xpack.audit_trail.enabled=true', '--xpack.audit_trail.logger.enabled=true', '--xpack.audit_trail.appender.kind=file', '--xpack.audit_trail.appender.path=x-pack/test/plugin_functional/plugins/audit_trail_test/server/pattern_debug.log', diff --git a/x-pack/test/plugin_functional/plugins/audit_trail_test/server/plugin.ts b/x-pack/test/plugin_functional/plugins/audit_trail_test/server/plugin.ts index bb4fc69d9acc6..264f436fb1dc0 100644 --- a/x-pack/test/plugin_functional/plugins/audit_trail_test/server/plugin.ts +++ b/x-pack/test/plugin_functional/plugins/audit_trail_test/server/plugin.ts @@ -21,7 +21,7 @@ export class AuditTrailTestPlugin implements Plugin { router.get( { path: '/audit_trail_test/context/as_current_user', validate: false }, async (context, request, response) => { - context.core.auditor.withScope('audit_trail_test/context/as_current_user'); + context.core.auditor.withAuditScope('audit_trail_test/context/as_current_user'); await context.core.elasticsearch.legacy.client.callAsCurrentUser('ping'); return response.noContent(); } @@ -30,7 +30,7 @@ export class AuditTrailTestPlugin implements Plugin { router.get( { path: '/audit_trail_test/context/as_internal_user', validate: false }, async (context, request, response) => { - context.core.auditor.withScope('audit_trail_test/context/as_internal_user'); + context.core.auditor.withAuditScope('audit_trail_test/context/as_internal_user'); await context.core.elasticsearch.legacy.client.callAsInternalUser('ping'); return response.noContent(); } @@ -41,7 +41,7 @@ export class AuditTrailTestPlugin implements Plugin { async (context, request, response) => { const [coreStart] = await core.getStartServices(); const auditor = coreStart.auditTrail.asScoped(request); - auditor.withScope('audit_trail_test/contract/as_current_user'); + auditor.withAuditScope('audit_trail_test/contract/as_current_user'); await context.core.elasticsearch.legacy.client.callAsCurrentUser('ping'); return response.noContent(); @@ -53,7 +53,7 @@ export class AuditTrailTestPlugin implements Plugin { async (context, request, response) => { const [coreStart] = await core.getStartServices(); const auditor = coreStart.auditTrail.asScoped(request); - auditor.withScope('audit_trail_test/contract/as_internal_user'); + auditor.withAuditScope('audit_trail_test/contract/as_internal_user'); await context.core.elasticsearch.legacy.client.callAsInternalUser('ping'); return response.noContent(); From 1133469fc22e15a904abe218877e20524689a047 Mon Sep 17 00:00:00 2001 From: restrry Date: Tue, 7 Jul 2020 19:10:19 +0200 Subject: [PATCH 31/32] update docs --- .../server/kibana-plugin-core-server.auditor.add.md | 2 +- .../server/kibana-plugin-core-server.auditor.md | 2 +- ...na-plugin-core-server.auditor.withauditscope.md} | 6 +++--- ...na-plugin-core-server.httpserverinfo.hostname.md | 13 +++++++++++++ src/core/server/server.api.md | 2 +- 5 files changed, 19 insertions(+), 6 deletions(-) rename docs/development/core/server/{kibana-plugin-core-server.auditor.withscope.md => kibana-plugin-core-server.auditor.withauditscope.md} (75%) create mode 100644 docs/development/core/server/kibana-plugin-core-server.httpserverinfo.hostname.md diff --git a/docs/development/core/server/kibana-plugin-core-server.auditor.add.md b/docs/development/core/server/kibana-plugin-core-server.auditor.add.md index 2cfdab828452f..40245a93753fc 100644 --- a/docs/development/core/server/kibana-plugin-core-server.auditor.add.md +++ b/docs/development/core/server/kibana-plugin-core-server.auditor.add.md @@ -28,7 +28,7 @@ How to add a record in audit log: ```typescript router.get({ path: '/my_endpoint', validate: false }, async (context, request, response) => { - context.core.auditor.withScope('my_plugin_operation'); + context.core.auditor.withAuditScope('my_plugin_operation'); const value = await context.core.elasticsearch.legacy.client.callAsCurrentUser('...'); context.core.add({ type: 'operation.type', message: 'perform an operation in ... endpoint' }); diff --git a/docs/development/core/server/kibana-plugin-core-server.auditor.md b/docs/development/core/server/kibana-plugin-core-server.auditor.md index baddd91d7a45c..191a34df647ab 100644 --- a/docs/development/core/server/kibana-plugin-core-server.auditor.md +++ b/docs/development/core/server/kibana-plugin-core-server.auditor.md @@ -17,5 +17,5 @@ export interface Auditor | Method | Description | | --- | --- | | [add(event)](./kibana-plugin-core-server.auditor.add.md) | Add a record to audit log. Service attaches to a log record: - metadata about an end-user initiating an operation - scope name, if presents | -| [withScope(name)](./kibana-plugin-core-server.auditor.withscope.md) | Add a high-level scope name for logged events. It helps to identify the root cause of low-level events. | +| [withAuditScope(name)](./kibana-plugin-core-server.auditor.withauditscope.md) | Add a high-level scope name for logged events. It helps to identify the root cause of low-level events. | diff --git a/docs/development/core/server/kibana-plugin-core-server.auditor.withscope.md b/docs/development/core/server/kibana-plugin-core-server.auditor.withauditscope.md similarity index 75% rename from docs/development/core/server/kibana-plugin-core-server.auditor.withscope.md rename to docs/development/core/server/kibana-plugin-core-server.auditor.withauditscope.md index 43f22ad01007d..0ae0c48ab92f4 100644 --- a/docs/development/core/server/kibana-plugin-core-server.auditor.withscope.md +++ b/docs/development/core/server/kibana-plugin-core-server.auditor.withauditscope.md @@ -1,15 +1,15 @@ -[Home](./index.md) > [kibana-plugin-core-server](./kibana-plugin-core-server.md) > [Auditor](./kibana-plugin-core-server.auditor.md) > [withScope](./kibana-plugin-core-server.auditor.withscope.md) +[Home](./index.md) > [kibana-plugin-core-server](./kibana-plugin-core-server.md) > [Auditor](./kibana-plugin-core-server.auditor.md) > [withAuditScope](./kibana-plugin-core-server.auditor.withauditscope.md) -## Auditor.withScope() method +## Auditor.withAuditScope() method Add a high-level scope name for logged events. It helps to identify the root cause of low-level events. Signature: ```typescript -withScope(name: string): void; +withAuditScope(name: string): void; ``` ## Parameters diff --git a/docs/development/core/server/kibana-plugin-core-server.httpserverinfo.hostname.md b/docs/development/core/server/kibana-plugin-core-server.httpserverinfo.hostname.md new file mode 100644 index 0000000000000..194a8aea16269 --- /dev/null +++ b/docs/development/core/server/kibana-plugin-core-server.httpserverinfo.hostname.md @@ -0,0 +1,13 @@ + + +[Home](./index.md) > [kibana-plugin-core-server](./kibana-plugin-core-server.md) > [HttpServerInfo](./kibana-plugin-core-server.httpserverinfo.md) > [hostname](./kibana-plugin-core-server.httpserverinfo.hostname.md) + +## HttpServerInfo.hostname property + +The hostname of the server + +Signature: + +```typescript +hostname: string; +``` diff --git a/src/core/server/server.api.md b/src/core/server/server.api.md index 3d514d08de2a8..ea95329bf8fa4 100644 --- a/src/core/server/server.api.md +++ b/src/core/server/server.api.md @@ -181,7 +181,7 @@ export interface AuditableEvent { // @public export interface Auditor { add(event: AuditableEvent): void; - withScope(name: string): void; + withAuditScope(name: string): void; } // @public From 04b7335174e5f96ca4eaadc1a029c1f13e499426 Mon Sep 17 00:00:00 2001 From: restrry Date: Tue, 7 Jul 2020 19:13:27 +0200 Subject: [PATCH 32/32] fix WebStorm refactoring --- .../classes/sources/ems_tms_source/ems_tms_source.test.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/x-pack/plugins/maps/public/classes/sources/ems_tms_source/ems_tms_source.test.js b/x-pack/plugins/maps/public/classes/sources/ems_tms_source/ems_tms_source.test.js index daac5121938db..2f466add28262 100644 --- a/x-pack/plugins/maps/public/classes/sources/ems_tms_source/ems_tms_source.test.js +++ b/x-pack/plugins/maps/public/classes/sources/ems_tms_source/ems_tms_source.test.js @@ -9,7 +9,7 @@ jest.mock('../../../meta', () => { getEmsTmsServices: () => { class MockTMSService { constructor(config) { - this.coreConfig = config; + this._config = config; } getMarkdownAttribution() { return this._config.attributionMarkdown;