Skip to content

Commit 04fee88

Browse files
authored
security(remote_method): redact ingest key in trace level logs (#1948)
1 parent 33a59fd commit 04fee88

File tree

3 files changed

+110
-16
lines changed

3 files changed

+110
-16
lines changed

lib/collector/remote-method.js

Lines changed: 16 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -259,15 +259,19 @@ RemoteMethod.prototype._safeRequest = function _safeRequest(options) {
259259
level = 'info'
260260
}
261261

262-
const logBody = Buffer.isBuffer(options.body) ? 'Buffer ' + options.body.length : options.body
263-
logger[level](
264-
{ body: logBody },
265-
'Posting to %s://%s:%s%s',
266-
protocol,
267-
options.host,
268-
options.port,
269-
options.path
270-
)
262+
// check if trace is enabled or audit log(aka info in this case) before attemping
263+
// to get the body length or parse the path with redacted key
264+
if (logger.traceEnabled() || level === 'info') {
265+
const logBody = Buffer.isBuffer(options.body) ? 'Buffer ' + options.body.length : options.body
266+
logger[level](
267+
{ body: logBody },
268+
'Posting to %s://%s:%s%s',
269+
protocol,
270+
options.host,
271+
options.port,
272+
this._path({ redactLicenseKey: true })
273+
)
274+
}
271275

272276
this._request(options)
273277
}
@@ -336,13 +340,14 @@ RemoteMethod.prototype._userAgent = function _userAgent() {
336340
/**
337341
* Generate a URL the collector understands.
338342
*
343+
* @param {boolean} redactLicenseKey flag to redact license key in path
339344
* @returns {string} The URL path to be POSTed to.
340345
*/
341-
RemoteMethod.prototype._path = function _path() {
346+
RemoteMethod.prototype._path = function _path({ redactLicenseKey } = {}) {
342347
const query = {
343348
marshal_format: 'json',
344349
protocol_version: this._protocolVersion,
345-
license_key: this._config.license_key,
350+
license_key: redactLicenseKey ? 'REDACTED' : this._config.license_key,
346351
method: this.name
347352
}
348353

test/unit/collector/remote-method.test.js

Lines changed: 93 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -9,14 +9,14 @@ const tap = require('tap')
99
const dns = require('dns')
1010
const events = require('events')
1111
const https = require('https')
12-
12+
const sinon = require('sinon')
13+
const proxyquire = require('proxyquire')
14+
const RemoteMethod = require('../../../lib/collector/remote-method')
1315
const url = require('url')
1416
const Config = require('../../../lib/config')
15-
const RemoteMethod = require('../../../lib/collector/remote-method')
1617
const helper = require('../../lib/agent_helper')
1718
require('../../lib/metrics_helper')
1819
const NAMES = require('../../../lib/metrics/names')
19-
2020
const BARE_AGENT = { config: {}, metrics: { measureBytes() {} } }
2121

2222
function generate(method, runID, protocolVersion) {
@@ -369,7 +369,7 @@ tap.test('when posting to collector', (t) => {
369369
})
370370
})
371371

372-
t.end('should use the right URL', (t) => {
372+
t.test('should use the right URL', (t) => {
373373
const sendMetrics = nockMetricDataUncompressed()
374374
method._post('[]', {}, (error) => {
375375
t.error(error)
@@ -378,12 +378,13 @@ tap.test('when posting to collector', (t) => {
378378
})
379379
})
380380

381-
t.end('should respect the put_for_data_send config', (t) => {
381+
t.test('should respect the put_for_data_send config', (t) => {
382382
const putMetrics = nock(URL)
383383
.put(generate('metric_data', RUN_ID))
384384
.reply(200, { return_value: [] })
385385

386386
config.put_for_data_send = true
387+
387388
method._post('[]', {}, (error) => {
388389
t.error(error)
389390
t.ok(putMetrics.isDone())
@@ -937,3 +938,90 @@ tap.test('record data usage supportability metrics', (t) => {
937938
)
938939
})
939940
})
941+
942+
tap.test('_safeRequest logging', (t) => {
943+
t.autoend()
944+
t.beforeEach((t) => {
945+
const sandbox = sinon.createSandbox()
946+
const loggerMock = require('../mocks/logger')(sandbox)
947+
const RemoteMethod = proxyquire('../../../lib/collector/remote-method', {
948+
'../logger': {
949+
child: sandbox.stub().callsFake(() => loggerMock)
950+
}
951+
})
952+
sandbox.stub(RemoteMethod.prototype, '_request')
953+
t.context.loggerMock = loggerMock
954+
t.context.RemoteMethod = RemoteMethod
955+
t.context.sandbox = sandbox
956+
t.context.options = {
957+
host: 'collector.newrelic.com',
958+
port: 80,
959+
onError: () => {},
960+
onResponse: () => {},
961+
body: 'test-body',
962+
path: '/nonexistent'
963+
}
964+
t.context.config = { license_key: 'shhh-dont-tell', max_payload_size_in_bytes: 10000 }
965+
})
966+
967+
t.afterEach((t) => {
968+
const { sandbox } = t.context
969+
sandbox.restore()
970+
})
971+
972+
t.test('should redact license key in logs', (t) => {
973+
const { RemoteMethod, loggerMock, options, config } = t.context
974+
loggerMock.traceEnabled.returns(true)
975+
const method = new RemoteMethod('test', { config })
976+
method._safeRequest(options)
977+
t.same(
978+
loggerMock.trace.args,
979+
[
980+
[
981+
{ body: options.body },
982+
'Posting to %s://%s:%s%s',
983+
'https',
984+
options.host,
985+
options.port,
986+
'/agent_listener/invoke_raw_method?marshal_format=json&protocol_version=17&license_key=REDACTED&method=test'
987+
]
988+
],
989+
'should redact key in trace level log'
990+
)
991+
t.end()
992+
})
993+
994+
t.test('should call logger if trace is not enabled but audit logging is enabled', (t) => {
995+
const { RemoteMethod, loggerMock, options, config } = t.context
996+
loggerMock.traceEnabled.returns(false)
997+
config.logging = { level: 'info' }
998+
config.audit_log = { enabled: true, endpoints: ['test'] }
999+
const method = new RemoteMethod('test', { config })
1000+
method._safeRequest(options)
1001+
t.same(
1002+
loggerMock.info.args,
1003+
[
1004+
[
1005+
{ body: options.body },
1006+
'Posting to %s://%s:%s%s',
1007+
'https',
1008+
options.host,
1009+
options.port,
1010+
'/agent_listener/invoke_raw_method?marshal_format=json&protocol_version=17&license_key=REDACTED&method=test'
1011+
]
1012+
],
1013+
'should redact key in trace level log'
1014+
)
1015+
t.end()
1016+
})
1017+
1018+
t.test('should not call logger if trace or audit logging is not enabled', (t) => {
1019+
const { RemoteMethod, loggerMock, options, config } = t.context
1020+
loggerMock.traceEnabled.returns(false)
1021+
const method = new RemoteMethod('test', { config })
1022+
method._safeRequest(options)
1023+
t.ok(loggerMock.trace.callCount === 0, 'should not log outgoing message to collector')
1024+
t.ok(loggerMock.info.callCount === 0, 'should not log outgoing message to collector')
1025+
t.end()
1026+
})
1027+
})

test/unit/mocks/logger.js

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@
77
const sinon = require('sinon')
88

99
module.exports = (sandbox = sinon) => ({
10+
traceEnabled: sandbox.stub().returns(true),
1011
trace: sandbox.stub(),
1112
info: sandbox.stub(),
1213
debug: sandbox.stub(),

0 commit comments

Comments
 (0)