Commit 91cdbde6 authored by Vincent Pelletier's avatar Vincent Pelletier

HTTP: Strip nonce values from Content-Security-Policy headers

Thus stabilising the value of these headers.
parent e335e8fb
...@@ -17,6 +17,7 @@ ...@@ -17,6 +17,7 @@
# See COPYING file for full licensing terms. # See COPYING file for full licensing terms.
# See https://www.nexedi.com/licensing for rationale and options. # See https://www.nexedi.com/licensing for rationale and options.
import re
import requests import requests
import sys import sys
import traceback import traceback
...@@ -29,6 +30,8 @@ PREFERRED_TYPE = "text/html" ...@@ -29,6 +30,8 @@ PREFERRED_TYPE = "text/html"
TIMEOUT = 2 TIMEOUT = 2
ELAPSED_FAST = 0.2 ELAPSED_FAST = 0.2
ELAPSED_MODERATE = 0.5 ELAPSED_MODERATE = 0.5
SUB_CSP_NONCE_REPL = "'nonce-'"
SUB_CSP_NONCE = re.compile(r"'nonce-[^']*'").sub
def getUrlHostname(url): def getUrlHostname(url):
...@@ -244,7 +247,6 @@ def checkHttpStatus( ...@@ -244,7 +247,6 @@ def checkHttpStatus(
"Content-Encoding", "Content-Encoding",
"Content-Disposition", "Content-Disposition",
# Security # Security
"Content-Security-Policy",
"Referrer-Policy", "Referrer-Policy",
"Strict-Transport-Security", "Strict-Transport-Security",
"Feature-Policy", "Feature-Policy",
...@@ -263,6 +265,15 @@ def checkHttpStatus( ...@@ -263,6 +265,15 @@ def checkHttpStatus(
if header_value is not None: if header_value is not None:
header_dict[header_key] = header_value header_dict[header_key] = header_value
# Strip nonces from Content-Security-Policy
header_key = "Content-Security-Policy"
content_security_policy = response.headers.get(header_key, None)
if content_security_policy is not None:
header_dict[header_key] = SUB_CSP_NONCE(
repl=SUB_CSP_NONCE_REPL,
string=content_security_policy,
)
# Store key only, because of non stability # Store key only, because of non stability
# 'Etag', 'Last-Modified', 'Set-Cookie', 'Date', 'Age' # 'Etag', 'Last-Modified', 'Set-Cookie', 'Date', 'Age'
key_only_header_list = [ key_only_header_list = [
......
...@@ -1001,6 +1001,36 @@ class SurykatkaHttpTestCase(unittest.TestCase): ...@@ -1001,6 +1001,36 @@ class SurykatkaHttpTestCase(unittest.TestCase):
result_dict[header] = header + " bar" result_dict[header] = header + " bar"
return result_dict return result_dict
@httpretty.activate
def test_checkContentSecurityPolicyNonceStripper(self):
header_key = "Content-Security-Policy"
httpretty.register_uri(
httpretty.GET,
"http://127.0.0.1/foo?bar=1",
status=418,
adding_headers={
header_key: (
"script-src 'self' 'nonce-dHkctJcn0BOAQxGH1YNXbg==' 'strict-dynamic';"
"style-src 'self' 'nonce-dHkctJcn0BOAQxGH1YNXbg==';"
"object-src 'none';"
),
},
)
checkHttpStatus(
self.db,
logStatus(self.db, "foo"),
"http://example.org/foo?bar=1",
"127.0.0.1",
1,
"http://example.org/contact",
)
assert self.db.HttpCodeChange.select().count() == 1
assert self.db.HttpCodeChange.get().http_header_dict[header_key] == (
"script-src 'self' 'nonce-' 'strict-dynamic';"
"style-src 'self' 'nonce-';"
"object-src 'none';"
)
@httpretty.activate @httpretty.activate
def test_checkHttpStatus_whitelistHttpHeader(self): def test_checkHttpStatus_whitelistHttpHeader(self):
ip = "127.0.0.1" ip = "127.0.0.1"
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment