diff --git a/newrelic/admin/license_key.py b/newrelic/admin/license_key.py index 35aaed1f4..e1eaaa39b 100644 --- a/newrelic/admin/license_key.py +++ b/newrelic/admin/license_key.py @@ -15,18 +15,22 @@ from __future__ import print_function from newrelic.admin import command, usage +from newrelic.common.encoding_utils import obfuscate_license_key -@command('license-key', 'config_file [log_file]', -"""Prints out the account license key after having loaded the settings -from .""") +@command( + "license-key", + "config_file [log_file]", + """Prints out an obfuscated account license key after having loaded the settings +from .""", +) def license_key(args): + import logging import os import sys - import logging if len(args) == 0: - usage('license-key') + usage("license-key") sys.exit(1) from newrelic.config import initialize @@ -35,7 +39,7 @@ def license_key(args): if len(args) >= 2: log_file = args[1] else: - log_file = '/tmp/python-agent-test.log' + log_file = "/tmp/python-agent-test.log" log_level = logging.DEBUG @@ -45,14 +49,13 @@ def license_key(args): pass config_file = args[0] - environment = os.environ.get('NEW_RELIC_ENVIRONMENT') + environment = os.environ.get("NEW_RELIC_ENVIRONMENT") - if config_file == '-': - config_file = os.environ.get('NEW_RELIC_CONFIG_FILE') + if config_file == "-": + config_file = os.environ.get("NEW_RELIC_CONFIG_FILE") - initialize(config_file, environment, ignore_errors=False, - log_file=log_file, log_level=log_level) + initialize(config_file, environment, ignore_errors=False, log_file=log_file, log_level=log_level) _settings = global_settings() - print('license_key = %r' % _settings.license_key) + print("license_key = %r" % obfuscate_license_key(_settings.license_key)) diff --git a/newrelic/admin/validate_config.py b/newrelic/admin/validate_config.py index ac25b715e..64645b0c6 100644 --- a/newrelic/admin/validate_config.py +++ b/newrelic/admin/validate_config.py @@ -149,6 +149,7 @@ def validate_config(args): sys.exit(1) from newrelic.api.application import register_application + from newrelic.common.encoding_utils import obfuscate_license_key from newrelic.config import initialize from newrelic.core.config import global_settings @@ -200,7 +201,7 @@ def validate_config(args): _logger.debug("Proxy port is %r.", _settings.proxy_port) _logger.debug("Proxy user is %r.", _settings.proxy_user) - _logger.debug("License key is %r.", _settings.license_key) + _logger.debug("License key is %r.", obfuscate_license_key(_settings.license_key)) _timeout = 30.0 diff --git a/newrelic/common/agent_http.py b/newrelic/common/agent_http.py index 89876a60c..0e1fa682b 100644 --- a/newrelic/common/agent_http.py +++ b/newrelic/common/agent_http.py @@ -23,7 +23,11 @@ import newrelic.packages.urllib3 as urllib3 from newrelic import version from newrelic.common import certs -from newrelic.common.encoding_utils import json_decode, json_encode +from newrelic.common.encoding_utils import ( + json_decode, + json_encode, + obfuscate_license_key, +) from newrelic.common.object_names import callable_name from newrelic.common.object_wrapper import patch_function_wrapper from newrelic.core.internal_metrics import internal_count_metric, internal_metric @@ -41,6 +45,9 @@ def get_default_verify_paths(): return _DEFAULT_CERT_PATH +HEADER_AUDIT_LOGGING_DENYLIST = frozenset(("x-api-key", "api-key")) + + # User agent string that must be used in all requests. The data collector # does not rely on this, but is used to target specific agents if there # 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 if not fp: return + # Obfuscate license key from headers and URL params + if headers: + headers = {k: obfuscate_license_key(v) if k.lower() in HEADER_AUDIT_LOGGING_DENYLIST else v for k, v in headers.items()} + + if params and "license_key" in params: + params = params.copy() + params["license_key"] = obfuscate_license_key(params["license_key"]) + # Maintain a global AUDIT_LOG_ID attached to all class instances # NOTE: this is not thread safe so this class cannot be used # across threads when audit logging is on diff --git a/newrelic/common/encoding_utils.py b/newrelic/common/encoding_utils.py index 3b7a519a2..41ffb1dfa 100644 --- a/newrelic/common/encoding_utils.py +++ b/newrelic/common/encoding_utils.py @@ -613,3 +613,20 @@ def snake_case(string): return string # Don't touch strings that are already snake cased return "_".join([s for s in _snake_case_re.split(string) if s]).lower() + + +_obfuscate_license_key_ending = "*" * 32 + + +def obfuscate_license_key(license_key): + """Obfuscate license key to allow it to be printed out.""" + + if not isinstance(license_key, six.string_types): + # For non-string values passed in such as None, return the original. + return license_key + elif len(license_key) == 40: + # For valid license keys of length 40, show the first 8 characters and then replace the remainder with **** + return license_key[:8] + _obfuscate_license_key_ending + else: + # For invalid lengths of license key, it's unclear how much is acceptable to show, so fully redact with **** + return "*" * len(license_key)