Skip to content

Commit 072eba8

Browse files
Obfuscate License Keys in Logs (#1031)
* Obfuscate license keys * Run formatter * Fix None errors in obfuscate_license_key * Obfuscate API keys from headers * Add lowercase api-key to denied headers * Change audit log header filters to be case insensitive --------- Co-authored-by: mergify[bot] <37929162+mergify[bot]@users.noreply.github.com>
1 parent aebf47f commit 072eba8

File tree

4 files changed

+50
-14
lines changed

4 files changed

+50
-14
lines changed

newrelic/admin/license_key.py

+15-12
Original file line numberDiff line numberDiff line change
@@ -15,18 +15,22 @@
1515
from __future__ import print_function
1616

1717
from newrelic.admin import command, usage
18+
from newrelic.common.encoding_utils import obfuscate_license_key
1819

1920

20-
@command('license-key', 'config_file [log_file]',
21-
"""Prints out the account license key after having loaded the settings
22-
from <config_file>.""")
21+
@command(
22+
"license-key",
23+
"config_file [log_file]",
24+
"""Prints out an obfuscated account license key after having loaded the settings
25+
from <config_file>.""",
26+
)
2327
def license_key(args):
28+
import logging
2429
import os
2530
import sys
26-
import logging
2731

2832
if len(args) == 0:
29-
usage('license-key')
33+
usage("license-key")
3034
sys.exit(1)
3135

3236
from newrelic.config import initialize
@@ -35,7 +39,7 @@ def license_key(args):
3539
if len(args) >= 2:
3640
log_file = args[1]
3741
else:
38-
log_file = '/tmp/python-agent-test.log'
42+
log_file = "/tmp/python-agent-test.log"
3943

4044
log_level = logging.DEBUG
4145

@@ -45,14 +49,13 @@ def license_key(args):
4549
pass
4650

4751
config_file = args[0]
48-
environment = os.environ.get('NEW_RELIC_ENVIRONMENT')
52+
environment = os.environ.get("NEW_RELIC_ENVIRONMENT")
4953

50-
if config_file == '-':
51-
config_file = os.environ.get('NEW_RELIC_CONFIG_FILE')
54+
if config_file == "-":
55+
config_file = os.environ.get("NEW_RELIC_CONFIG_FILE")
5256

53-
initialize(config_file, environment, ignore_errors=False,
54-
log_file=log_file, log_level=log_level)
57+
initialize(config_file, environment, ignore_errors=False, log_file=log_file, log_level=log_level)
5558

5659
_settings = global_settings()
5760

58-
print('license_key = %r' % _settings.license_key)
61+
print("license_key = %r" % obfuscate_license_key(_settings.license_key))

newrelic/admin/validate_config.py

+2-1
Original file line numberDiff line numberDiff line change
@@ -149,6 +149,7 @@ def validate_config(args):
149149
sys.exit(1)
150150

151151
from newrelic.api.application import register_application
152+
from newrelic.common.encoding_utils import obfuscate_license_key
152153
from newrelic.config import initialize
153154
from newrelic.core.config import global_settings
154155

@@ -200,7 +201,7 @@ def validate_config(args):
200201
_logger.debug("Proxy port is %r.", _settings.proxy_port)
201202
_logger.debug("Proxy user is %r.", _settings.proxy_user)
202203

203-
_logger.debug("License key is %r.", _settings.license_key)
204+
_logger.debug("License key is %r.", obfuscate_license_key(_settings.license_key))
204205

205206
_timeout = 30.0
206207

newrelic/common/agent_http.py

+16-1
Original file line numberDiff line numberDiff line change
@@ -23,7 +23,11 @@
2323
import newrelic.packages.urllib3 as urllib3
2424
from newrelic import version
2525
from newrelic.common import certs
26-
from newrelic.common.encoding_utils import json_decode, json_encode
26+
from newrelic.common.encoding_utils import (
27+
json_decode,
28+
json_encode,
29+
obfuscate_license_key,
30+
)
2731
from newrelic.common.object_names import callable_name
2832
from newrelic.common.object_wrapper import patch_function_wrapper
2933
from newrelic.core.internal_metrics import internal_count_metric, internal_metric
@@ -41,6 +45,9 @@ def get_default_verify_paths():
4145
return _DEFAULT_CERT_PATH
4246

4347

48+
HEADER_AUDIT_LOGGING_DENYLIST = frozenset(("x-api-key", "api-key"))
49+
50+
4451
# User agent string that must be used in all requests. The data collector
4552
# does not rely on this, but is used to target specific agents if there
4653
# is a problem with data collector handling requests.
@@ -119,6 +126,14 @@ def log_request(cls, fp, method, url, params, payload, headers, body=None, compr
119126
if not fp:
120127
return
121128

129+
# Obfuscate license key from headers and URL params
130+
if headers:
131+
headers = {k: obfuscate_license_key(v) if k.lower() in HEADER_AUDIT_LOGGING_DENYLIST else v for k, v in headers.items()}
132+
133+
if params and "license_key" in params:
134+
params = params.copy()
135+
params["license_key"] = obfuscate_license_key(params["license_key"])
136+
122137
# Maintain a global AUDIT_LOG_ID attached to all class instances
123138
# NOTE: this is not thread safe so this class cannot be used
124139
# across threads when audit logging is on

newrelic/common/encoding_utils.py

+17
Original file line numberDiff line numberDiff line change
@@ -613,3 +613,20 @@ def snake_case(string):
613613
return string # Don't touch strings that are already snake cased
614614

615615
return "_".join([s for s in _snake_case_re.split(string) if s]).lower()
616+
617+
618+
_obfuscate_license_key_ending = "*" * 32
619+
620+
621+
def obfuscate_license_key(license_key):
622+
"""Obfuscate license key to allow it to be printed out."""
623+
624+
if not isinstance(license_key, six.string_types):
625+
# For non-string values passed in such as None, return the original.
626+
return license_key
627+
elif len(license_key) == 40:
628+
# For valid license keys of length 40, show the first 8 characters and then replace the remainder with ****
629+
return license_key[:8] + _obfuscate_license_key_ending
630+
else:
631+
# For invalid lengths of license key, it's unclear how much is acceptable to show, so fully redact with ****
632+
return "*" * len(license_key)

0 commit comments

Comments
 (0)