Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add an option to disable APM user redaction #176566

Merged
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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);
};