Skip to content

Commit

Permalink
Obfuscate License Keys in Logs (#1031)
Browse files Browse the repository at this point in the history
* 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>
  • Loading branch information
TimPansino and mergify[bot] committed Jan 12, 2024
1 parent aebf47f commit 072eba8
Show file tree
Hide file tree
Showing 4 changed files with 50 additions and 14 deletions.
27 changes: 15 additions & 12 deletions newrelic/admin/license_key.py
Expand Up @@ -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 <config_file>.""")
@command(
"license-key",
"config_file [log_file]",
"""Prints out an obfuscated account license key after having loaded the settings
from <config_file>.""",
)
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
Expand All @@ -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

Expand All @@ -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))
3 changes: 2 additions & 1 deletion newrelic/admin/validate_config.py
Expand Up @@ -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

Expand Down Expand Up @@ -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

Expand Down
17 changes: 16 additions & 1 deletion newrelic/common/agent_http.py
Expand Up @@ -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
Expand All @@ -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.
Expand Down Expand Up @@ -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
Expand Down
17 changes: 17 additions & 0 deletions newrelic/common/encoding_utils.py
Expand Up @@ -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)

0 comments on commit 072eba8

Please sign in to comment.