Skip to content

Commit

Permalink
Nonced CSP Support (#998)
Browse files Browse the repository at this point in the history
* Add nonce to CSP in browser agent

* Adjust nonce position

* Add testing for browser timing nonces
  • Loading branch information
TimPansino committed Dec 21, 2023
1 parent 8bfd2b7 commit 23f969f
Show file tree
Hide file tree
Showing 3 changed files with 46 additions and 11 deletions.
8 changes: 4 additions & 4 deletions newrelic/api/transaction.py
Original file line number Diff line number Diff line change
Expand Up @@ -1904,17 +1904,17 @@ def add_framework_info(name, version=None):
transaction.add_framework_info(name, version)


def get_browser_timing_header():
def get_browser_timing_header(nonce=None):
transaction = current_transaction()
if transaction and hasattr(transaction, "browser_timing_header"):
return transaction.browser_timing_header()
return transaction.browser_timing_header(nonce)
return ""


def get_browser_timing_footer():
def get_browser_timing_footer(nonce=None):
transaction = current_transaction()
if transaction and hasattr(transaction, "browser_timing_footer"):
return transaction.browser_timing_footer()
return transaction.browser_timing_footer(nonce)
return ""


Expand Down
20 changes: 13 additions & 7 deletions newrelic/api/web_transaction.py
Original file line number Diff line number Diff line change
Expand Up @@ -39,9 +39,8 @@

_logger = logging.getLogger(__name__)

_js_agent_header_fragment = '<script type="text/javascript">%s</script>'
_js_agent_footer_fragment = '<script type="text/javascript">'\
'window.NREUM||(NREUM={});NREUM.info=%s</script>'
_js_agent_header_fragment = '<script type="text/javascript"%s>%s</script>'
_js_agent_footer_fragment = '<script type="text/javascript"%s>window.NREUM||(NREUM={});NREUM.info=%s</script>'

# Seconds since epoch for Jan 1 2000
JAN_1_2000 = time.mktime((2000, 1, 1, 0, 0, 0, 0, 0, 0))
Expand Down Expand Up @@ -156,6 +155,13 @@ def _is_websocket(environ):
return environ.get('HTTP_UPGRADE', '').lower() == 'websocket'


def _encode_nonce(nonce):
if not nonce:
return ""
else:
return ' nonce="%s"' % ensure_str(nonce) # Extra space intentional


class WebTransaction(Transaction):
unicode_error_reported = False
QUEUE_TIME_HEADERS = ('x-request-start', 'x-queue-start')
Expand Down Expand Up @@ -386,7 +392,7 @@ def _update_agent_attributes(self):

return super(WebTransaction, self)._update_agent_attributes()

def browser_timing_header(self):
def browser_timing_header(self, nonce=None):
"""Returns the JavaScript header to be included in any HTML
response to perform real user monitoring. This function returns
the header as a native Python string. In Python 2 native strings
Expand Down Expand Up @@ -437,7 +443,7 @@ def browser_timing_header(self):
# 'none'.

if self._settings.js_agent_loader:
header = _js_agent_header_fragment % self._settings.js_agent_loader
header = _js_agent_header_fragment % (_encode_nonce(nonce), self._settings.js_agent_loader)

# To avoid any issues with browser encodings, we will make sure
# that the javascript we inject for the browser agent is ASCII
Expand Down Expand Up @@ -476,7 +482,7 @@ def browser_timing_header(self):

return header

def browser_timing_footer(self):
def browser_timing_footer(self, nonce=None):
"""Returns the JavaScript footer to be included in any HTML
response to perform real user monitoring. This function returns
the footer as a native Python string. In Python 2 native strings
Expand Down Expand Up @@ -541,7 +547,7 @@ def browser_timing_footer(self):
attributes = obfuscate(json_encode(attributes), obfuscation_key)
footer_data['atts'] = attributes

footer = _js_agent_footer_fragment % json_encode(footer_data)
footer = _js_agent_footer_fragment % (_encode_nonce(nonce), json_encode(footer_data))

# To avoid any issues with browser encodings, we will make sure that
# the javascript we inject for the browser agent is ASCII encodable.
Expand Down
29 changes: 29 additions & 0 deletions tests/agent_features/test_browser.py
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,7 @@
get_browser_timing_footer,
get_browser_timing_header,
)
from newrelic.api.web_transaction import web_transaction
from newrelic.api.wsgi_application import wsgi_application
from newrelic.common.encoding_utils import deobfuscate

Expand Down Expand Up @@ -1026,3 +1027,31 @@ def test_html_insertion_manual_rum_insertion():
# footer added by the agent.

response.mustcontain(no=["NREUM HEADER", "NREUM.info"])


_test_get_browser_timing_nonces_settings = {
"browser_monitoring.enabled": True,
"browser_monitoring.auto_instrument": False,
"js_agent_loader": "<!-- NREUM HEADER -->",
}

@override_application_settings(_test_get_browser_timing_nonces_settings)
@web_transaction(scheme="http", host="127.0.0.1", port=80, request_method="GET",
request_path="/", query_string=None, headers={})
def test_get_browser_timing_nonces():
header = get_browser_timing_header("NONCE")
footer = get_browser_timing_footer("NONCE")

assert header == '<script type="text/javascript" nonce="NONCE"><!-- NREUM HEADER --></script>'
assert '<script type="text/javascript" nonce="NONCE">' in footer


@override_application_settings(_test_get_browser_timing_nonces_settings)
@web_transaction(scheme="http", host="127.0.0.1", port=80, request_method="GET",
request_path="/", query_string=None, headers={})
def test_get_browser_timing_no_nonces():
header = get_browser_timing_header()
footer = get_browser_timing_footer()

assert header == '<script type="text/javascript"><!-- NREUM HEADER --></script>'
assert '<script type="text/javascript">' in footer

0 comments on commit 23f969f

Please sign in to comment.