Skip to content

Commit 23f969f

Browse files
authored
Nonced CSP Support (#998)
* Add nonce to CSP in browser agent * Adjust nonce position * Add testing for browser timing nonces
1 parent 8bfd2b7 commit 23f969f

File tree

3 files changed

+46
-11
lines changed

3 files changed

+46
-11
lines changed

newrelic/api/transaction.py

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1904,17 +1904,17 @@ def add_framework_info(name, version=None):
19041904
transaction.add_framework_info(name, version)
19051905

19061906

1907-
def get_browser_timing_header():
1907+
def get_browser_timing_header(nonce=None):
19081908
transaction = current_transaction()
19091909
if transaction and hasattr(transaction, "browser_timing_header"):
1910-
return transaction.browser_timing_header()
1910+
return transaction.browser_timing_header(nonce)
19111911
return ""
19121912

19131913

1914-
def get_browser_timing_footer():
1914+
def get_browser_timing_footer(nonce=None):
19151915
transaction = current_transaction()
19161916
if transaction and hasattr(transaction, "browser_timing_footer"):
1917-
return transaction.browser_timing_footer()
1917+
return transaction.browser_timing_footer(nonce)
19181918
return ""
19191919

19201920

newrelic/api/web_transaction.py

Lines changed: 13 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -39,9 +39,8 @@
3939

4040
_logger = logging.getLogger(__name__)
4141

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

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

158157

158+
def _encode_nonce(nonce):
159+
if not nonce:
160+
return ""
161+
else:
162+
return ' nonce="%s"' % ensure_str(nonce) # Extra space intentional
163+
164+
159165
class WebTransaction(Transaction):
160166
unicode_error_reported = False
161167
QUEUE_TIME_HEADERS = ('x-request-start', 'x-queue-start')
@@ -386,7 +392,7 @@ def _update_agent_attributes(self):
386392

387393
return super(WebTransaction, self)._update_agent_attributes()
388394

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

439445
if self._settings.js_agent_loader:
440-
header = _js_agent_header_fragment % self._settings.js_agent_loader
446+
header = _js_agent_header_fragment % (_encode_nonce(nonce), self._settings.js_agent_loader)
441447

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

477483
return header
478484

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

544-
footer = _js_agent_footer_fragment % json_encode(footer_data)
550+
footer = _js_agent_footer_fragment % (_encode_nonce(nonce), json_encode(footer_data))
545551

546552
# To avoid any issues with browser encodings, we will make sure that
547553
# the javascript we inject for the browser agent is ASCII encodable.

tests/agent_features/test_browser.py

Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,7 @@
3232
get_browser_timing_footer,
3333
get_browser_timing_header,
3434
)
35+
from newrelic.api.web_transaction import web_transaction
3536
from newrelic.api.wsgi_application import wsgi_application
3637
from newrelic.common.encoding_utils import deobfuscate
3738

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

10281029
response.mustcontain(no=["NREUM HEADER", "NREUM.info"])
1030+
1031+
1032+
_test_get_browser_timing_nonces_settings = {
1033+
"browser_monitoring.enabled": True,
1034+
"browser_monitoring.auto_instrument": False,
1035+
"js_agent_loader": "<!-- NREUM HEADER -->",
1036+
}
1037+
1038+
@override_application_settings(_test_get_browser_timing_nonces_settings)
1039+
@web_transaction(scheme="http", host="127.0.0.1", port=80, request_method="GET",
1040+
request_path="/", query_string=None, headers={})
1041+
def test_get_browser_timing_nonces():
1042+
header = get_browser_timing_header("NONCE")
1043+
footer = get_browser_timing_footer("NONCE")
1044+
1045+
assert header == '<script type="text/javascript" nonce="NONCE"><!-- NREUM HEADER --></script>'
1046+
assert '<script type="text/javascript" nonce="NONCE">' in footer
1047+
1048+
1049+
@override_application_settings(_test_get_browser_timing_nonces_settings)
1050+
@web_transaction(scheme="http", host="127.0.0.1", port=80, request_method="GET",
1051+
request_path="/", query_string=None, headers={})
1052+
def test_get_browser_timing_no_nonces():
1053+
header = get_browser_timing_header()
1054+
footer = get_browser_timing_footer()
1055+
1056+
assert header == '<script type="text/javascript"><!-- NREUM HEADER --></script>'
1057+
assert '<script type="text/javascript">' in footer

0 commit comments

Comments
 (0)