From 655011eea53cc50fd2f2475675d7cff1c7d4261d Mon Sep 17 00:00:00 2001 From: Pierre Gayvallet Date: Sun, 18 Feb 2024 16:30:01 +0100 Subject: [PATCH] Add an option to disable APM user redaction (#176566) ## Summary Fix https://github.com/elastic/kibana/issues/174743 Add an `elastic.apm.redactUsers` configuration option that defaults to `true` (preserving current behavior) --------- Co-authored-by: Kibana Machine <42973632+kibanamachine@users.noreply.github.com> (cherry picked from commit d65460cb3973826f680b0934815b9de7a0682905) --- .../kbn-apm-config-loader/src/apm_config.ts | 1 + .../kbn-apm-config-loader/src/config.test.ts | 26 +++++++++++++++++++ packages/kbn-apm-config-loader/src/config.ts | 8 +++++- .../src/init_apm.test.ts | 21 ++++++++++++++- .../kbn-apm-config-loader/src/init_apm.ts | 25 ++++++++++-------- 5 files changed, 68 insertions(+), 13 deletions(-) diff --git a/packages/kbn-apm-config-loader/src/apm_config.ts b/packages/kbn-apm-config-loader/src/apm_config.ts index c4f2bfdf60e82..434147c209e1f 100644 --- a/packages/kbn-apm-config-loader/src/apm_config.ts +++ b/packages/kbn-apm-config-loader/src/apm_config.ts @@ -24,4 +24,5 @@ const apmReusableConfigSchema = schema.object( export const apmConfigSchema = apmReusableConfigSchema.extends({ servicesOverrides: schema.maybe(schema.recordOf(schema.string(), apmReusableConfigSchema)), + redactUsers: schema.maybe(schema.boolean({ defaultValue: true })), }); diff --git a/packages/kbn-apm-config-loader/src/config.test.ts b/packages/kbn-apm-config-loader/src/config.test.ts index ec4355922f90e..ac139b220768c 100644 --- a/packages/kbn-apm-config-loader/src/config.test.ts +++ b/packages/kbn-apm-config-loader/src/config.test.ts @@ -405,4 +405,30 @@ describe('ApmConfiguration', () => { ); }); }); + + describe('isUsersRedactionEnabled', () => { + it('defaults to true', () => { + const kibanaConfig = { + elastic: { + apm: {}, + }, + }; + + const config = new ApmConfiguration(mockedRootDir, kibanaConfig, false); + expect(config.isUsersRedactionEnabled()).toEqual(true); + }); + + it('uses the value defined in the config if specified', () => { + const kibanaConfig = { + elastic: { + apm: { + redactUsers: false, + }, + }, + }; + + const config = new ApmConfiguration(mockedRootDir, kibanaConfig, false); + expect(config.isUsersRedactionEnabled()).toEqual(false); + }); + }); }); diff --git a/packages/kbn-apm-config-loader/src/config.ts b/packages/kbn-apm-config-loader/src/config.ts index 7430454cc4c2a..f25feafe90eb7 100644 --- a/packages/kbn-apm-config-loader/src/config.ts +++ b/packages/kbn-apm-config-loader/src/config.ts @@ -93,6 +93,11 @@ export class ApmConfiguration { return baseConfig; } + public isUsersRedactionEnabled(): boolean { + const { redactUsers = true } = this.getConfigFromKibanaConfig(); + return redactUsers; + } + private getBaseConfig() { if (!this.baseConfig) { const configFromSources = this.getConfigFromAllSources(); @@ -287,7 +292,8 @@ export class ApmConfiguration { * Reads APM configuration from different sources and merges them together. */ private getConfigFromAllSources(): AgentConfigOptions { - const { servicesOverrides, ...configFromKibanaConfig } = this.getConfigFromKibanaConfig(); + const { servicesOverrides, redactUsers, ...configFromKibanaConfig } = + this.getConfigFromKibanaConfig(); const configFromEnv = this.getConfigFromEnv(configFromKibanaConfig); const config = merge({}, configFromKibanaConfig, configFromEnv); diff --git a/packages/kbn-apm-config-loader/src/init_apm.test.ts b/packages/kbn-apm-config-loader/src/init_apm.test.ts index cabab421519bd..6d3e04177bb9f 100644 --- a/packages/kbn-apm-config-loader/src/init_apm.test.ts +++ b/packages/kbn-apm-config-loader/src/init_apm.test.ts @@ -15,14 +15,17 @@ describe('initApm', () => { let apmAddFilterMock: jest.Mock; let apmStartMock: jest.Mock; let getConfig: jest.Mock; + let isUsersRedactionEnabled: jest.Mock; beforeEach(() => { apmAddFilterMock = apm.addFilter as jest.Mock; apmStartMock = apm.start as jest.Mock; getConfig = jest.fn(); + isUsersRedactionEnabled = jest.fn(); mockLoadConfiguration.mockImplementation(() => ({ getConfig, + isUsersRedactionEnabled, })); }); @@ -46,13 +49,29 @@ describe('initApm', () => { expect(getConfig).toHaveBeenCalledWith('service-name'); }); - it('registers a filter using `addFilter`', () => { + it('calls `apmConfigLoader.isUsersRedactionEnabled`', () => { + initApm(['foo', 'bar'], 'rootDir', true, 'service-name'); + + expect(isUsersRedactionEnabled).toHaveBeenCalledTimes(1); + }); + + it('registers a filter using `addFilter` when user redaction is enabled', () => { + isUsersRedactionEnabled.mockReturnValue(true); + initApm(['foo', 'bar'], 'rootDir', true, 'service-name'); expect(apmAddFilterMock).toHaveBeenCalledTimes(1); expect(apmAddFilterMock).toHaveBeenCalledWith(expect.any(Function)); }); + it('does not register a filter using `addFilter` when user redaction is disabled', () => { + isUsersRedactionEnabled.mockReturnValue(false); + + initApm(['foo', 'bar'], 'rootDir', true, 'service-name'); + + expect(apmAddFilterMock).not.toHaveBeenCalled(); + }); + it('starts apm with the config returned from `getConfig`', () => { const config = { foo: 'bar', diff --git a/packages/kbn-apm-config-loader/src/init_apm.ts b/packages/kbn-apm-config-loader/src/init_apm.ts index 21c40c8b39419..e4c05f9772200 100644 --- a/packages/kbn-apm-config-loader/src/init_apm.ts +++ b/packages/kbn-apm-config-loader/src/init_apm.ts @@ -16,24 +16,27 @@ export const initApm = ( ) => { const apmConfigLoader = loadConfiguration(argv, rootDir, isDistributable); const apmConfig = apmConfigLoader.getConfig(serviceName); + const shouldRedactUsers = apmConfigLoader.isUsersRedactionEnabled(); // we want to only load the module when effectively used // eslint-disable-next-line @typescript-eslint/no-var-requires const apm = require('elastic-apm-node'); // Filter out all user PII - apm.addFilter((payload: Record) => { - try { - if (payload.context?.user && typeof payload.context.user === 'object') { - Object.keys(payload.context.user).forEach((key) => { - payload.context.user[key] = '[REDACTED]'; - }); + if (shouldRedactUsers) { + apm.addFilter((payload: Record) => { + try { + if (payload.context?.user && typeof payload.context.user === 'object') { + Object.keys(payload.context.user).forEach((key) => { + payload.context.user[key] = '[REDACTED]'; + }); + } + } catch (e) { + // just silently ignore the error } - } catch (e) { - // just silently ignore the error - } - return payload; - }); + return payload; + }); + } apm.start(apmConfig); };