Skip to content

Commit

Permalink
Add an option to disable APM user redaction (elastic#176566)
Browse files Browse the repository at this point in the history
## Summary

Fix elastic#174743

Add an `elastic.apm.redactUsers` configuration option that defaults to
`true` (preserving current behavior)

---------

Co-authored-by: Kibana Machine <[email protected]>
  • Loading branch information
2 people authored and fkanout committed Mar 4, 2024
1 parent 700ea7b commit 4fe6924
Show file tree
Hide file tree
Showing 5 changed files with 68 additions and 13 deletions.
1 change: 1 addition & 0 deletions packages/kbn-apm-config-loader/src/apm_config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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 })),
});
26 changes: 26 additions & 0 deletions packages/kbn-apm-config-loader/src/config.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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);
});
});
});
8 changes: 7 additions & 1 deletion packages/kbn-apm-config-loader/src/config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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();
Expand Down Expand Up @@ -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);

Expand Down
21 changes: 20 additions & 1 deletion packages/kbn-apm-config-loader/src/init_apm.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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,
}));
});

Expand All @@ -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',
Expand Down
25 changes: 14 additions & 11 deletions packages/kbn-apm-config-loader/src/init_apm.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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<string, any>) => {
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<string, any>) => {
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);
};

0 comments on commit 4fe6924

Please sign in to comment.