Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion docs/topics/development/troubleshooting_and_debugging.md
Original file line number Diff line number Diff line change
Expand Up @@ -91,7 +91,7 @@ To use it, see the official getting started docs: [Django Debug Toolbar Installa

- The Django Debug Toolbar can slow down the website. Mitigate this by deselecting the checkbox next to the `SQL` panel.
- Use the Django Debug Toolbar only when needed, as it affects CSP report only for your local dev environment.
- You might need to disable CSP by setting `CSP_REPORT_ONLY = True` in your local settings because the Django Debug Toolbar uses "data:" for its logo and "unsafe eval" for some panels like templates or SQL.
- You might need to disable CSP by setting `CONTENT_SECURITY_POLICY_REPORT_ONLY = CONTENT_SECURITY_POLICY_REPORT;CONTENT_SECURITY_POLICY_REPORT = {}` in your local settings because the Django Debug Toolbar uses "data:" for its logo and "unsafe eval" for some panels like templates or SQL.

## Additional Debugging Tools

Expand Down
6 changes: 3 additions & 3 deletions requirements/prod.txt
Original file line number Diff line number Diff line change
Expand Up @@ -581,9 +581,9 @@ django-admin-rangefilter==0.13.3 \
django-cors-headers==4.7.0 \
--hash=sha256:6fdf31bf9c6d6448ba09ef57157db2268d515d94fc5c89a0a1028e1fc03ee52b \
--hash=sha256:f1c125dcd58479fe7a67fe2499c16ee38b81b397463cf025f0e2c42937421070
django_csp==3.8 \
--hash=sha256:19b2978b03fcd73517d7d67acbc04fbbcaec0facc3e83baa502965892d1e0719 \
--hash=sha256:ef0f1a9f7d8da68ae6e169c02e9ac661c0ecf04db70e0d1d85640512a68471c0
django_csp==4.0 \
--hash=sha256:b27010bb702eb20a3dad329178df2b61a2b82d338b70fbdc13c3a3bd28712833 \
--hash=sha256:d5a0a05463a6b75a4f1fc1828c58c89af8db9364d09fc6e12f122b4d7f3d00dc
django-environ==0.11.2 \
--hash=sha256:0ff95ab4344bfeff693836aa978e6840abef2e2f1145adff7735892711590c05 \
--hash=sha256:f32a87aa0899894c27d4e1776fa6b477e8164ed7f6b3e410a62a6d72caaf64be
Expand Down
19 changes: 10 additions & 9 deletions settings.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@
"""

import os
from copy import deepcopy
from urllib.parse import urlparse

from olympia.core.utils import get_version_json
Expand Down Expand Up @@ -117,19 +118,19 @@ def insert_debug_toolbar_middleware(middlewares):
FXA_OAUTH_HOST = 'https://oauth.stage.mozaws.net/v1'
FXA_PROFILE_HOST = 'https://profile.stage.mozaws.net/v1'

# CSP report endpoint which returns a 204 from addons-nginx in local dev.
CSP_REPORT_URI = '/csp-report'
RESTRICTED_DOWNLOAD_CSP['REPORT_URI'] = CSP_REPORT_URI

# Set CSP like we do for dev/stage/prod, but also allow GA over http + www subdomain
# for local development.
HTTP_GA_SRC = 'http://www.google-analytics.com'

CSP_CONNECT_SRC += (SITE_URL,)
CSP_FONT_SRC += (STATIC_URL,)
CSP_IMG_SRC += (MEDIA_URL, STATIC_URL, HTTP_GA_SRC)
CSP_SCRIPT_SRC += (STATIC_URL, HTTP_GA_SRC)
CSP_STYLE_SRC += (STATIC_URL,)
# we want to be able to test the settings_base without these overrides interfering
CONTENT_SECURITY_POLICY = deepcopy(CONTENT_SECURITY_POLICY)
CONTENT_SECURITY_POLICY['DIRECTIVES']['connect-src'] += (SITE_URL,)
CONTENT_SECURITY_POLICY['DIRECTIVES']['font-src'] += (STATIC_URL,)
CONTENT_SECURITY_POLICY['DIRECTIVES']['img-src'] += (MEDIA_URL, STATIC_URL, HTTP_GA_SRC)
CONTENT_SECURITY_POLICY['DIRECTIVES']['script-src'] += (STATIC_URL, HTTP_GA_SRC)
CONTENT_SECURITY_POLICY['DIRECTIVES']['style-src'] += (STATIC_URL,)
# CSP report endpoint which returns a 204 from addons-nginx in local dev.
CONTENT_SECURITY_POLICY['DIRECTIVES']['report-uri'] = '/csp-report'

# Auth token required to authorize inbound email.
INBOUND_EMAIL_SECRET_KEY = 'totally-unsecure-secret-string'
Expand Down
174 changes: 131 additions & 43 deletions src/olympia/amo/tests/test_csp_headers.py
Original file line number Diff line number Diff line change
@@ -1,17 +1,15 @@
import os

from django.conf import settings
from django.test.utils import override_settings

from olympia.amo.tests import TestCase
from olympia.lib import settings_base as base_settings


def test_default_settings_no_report_only():
assert settings.CSP_REPORT_ONLY is False
assert getattr(settings, 'CONTENT_SECURITY_POLICY', {}).keys()


@override_settings(CSP_REPORT_ONLY=False)
class TestCSPHeaders(TestCase):
def test_for_specific_csp_settings(self):
"""Test that required settings are provided as headers."""
Expand All @@ -35,101 +33,191 @@ def test_for_specific_csp_settings(self):

def test_unsafe_inline_not_in_script_src(self):
"""Make sure a script-src does not have unsafe-inline."""
assert "'unsafe-inline'" not in base_settings.CSP_SCRIPT_SRC
assert (
"'unsafe-inline'"
not in base_settings.CONTENT_SECURITY_POLICY['DIRECTIVES']['script-src']
)

def test_unsafe_eval_not_in_script_src(self):
"""Make sure a script-src does not have unsafe-eval."""
assert "'unsafe-eval'" not in base_settings.CSP_SCRIPT_SRC
assert (
"'unsafe-eval'"
not in base_settings.CONTENT_SECURITY_POLICY['DIRECTIVES']['script-src']
)

def test_data_and_blob_not_in_script_and_style_src(self):
"""Make sure a script-src/style-src does not have data: or blob:."""
assert 'blob:' not in base_settings.CSP_SCRIPT_SRC
assert 'data:' not in base_settings.CSP_SCRIPT_SRC
assert 'blob:' not in base_settings.CSP_STYLE_SRC
assert 'data:' not in base_settings.CSP_STYLE_SRC
assert (
'blob:'
not in base_settings.CONTENT_SECURITY_POLICY['DIRECTIVES']['script-src']
)
assert (
'data:'
not in base_settings.CONTENT_SECURITY_POLICY['DIRECTIVES']['script-src']
)
assert (
'blob:'
not in base_settings.CONTENT_SECURITY_POLICY['DIRECTIVES']['style-src']
)
assert (
'data:'
not in base_settings.CONTENT_SECURITY_POLICY['DIRECTIVES']['style-src']
)

def test_http_protocol_not_in_script_src(self):
"""Make sure a script-src does not have hosts using http:."""
for val in base_settings.CSP_SCRIPT_SRC:
for val in base_settings.CONTENT_SECURITY_POLICY['DIRECTIVES']['script-src']:
assert not val.startswith('http:')

def test_http_protocol_not_in_frame_src(self):
"""Make sure a frame-src does not have hosts using http:."""
for val in base_settings.CSP_FRAME_SRC:
for val in base_settings.CONTENT_SECURITY_POLICY['DIRECTIVES']['frame-src']:
assert not val.startswith('http:')

def test_http_protocol_not_in_child_src(self):
"""Make sure a child-src does not have hosts using http:."""
for val in base_settings.CSP_CHILD_SRC:
for val in base_settings.CONTENT_SECURITY_POLICY['DIRECTIVES']['child-src']:
assert not val.startswith('http:')

def test_http_protocol_not_in_style_src(self):
"""Make sure a style-src does not have hosts using http:."""
for val in base_settings.CSP_STYLE_SRC:
for val in base_settings.CONTENT_SECURITY_POLICY['DIRECTIVES']['style-src']:
assert not val.startswith('http:')

def test_http_protocol_not_in_img_src(self):
"""Make sure a img-src does not have hosts using http:."""
for val in base_settings.CSP_IMG_SRC:
for val in base_settings.CONTENT_SECURITY_POLICY['DIRECTIVES']['img-src']:
assert not val.startswith('http:')

def test_blob_and_data_in_img_src(self):
"""Test that img-src contains data/blob."""
assert 'blob:' in base_settings.CSP_IMG_SRC
assert 'data:' in base_settings.CSP_IMG_SRC
assert 'blob:' in base_settings.CONTENT_SECURITY_POLICY['DIRECTIVES']['img-src']
assert 'data:' in base_settings.CONTENT_SECURITY_POLICY['DIRECTIVES']['img-src']

def test_child_src_matches_frame_src(self):
"""Check frame-src directive has same settings as child-src"""
assert base_settings.CSP_FRAME_SRC == base_settings.CSP_CHILD_SRC
assert (
base_settings.CONTENT_SECURITY_POLICY['DIRECTIVES']['frame-src']
== base_settings.CONTENT_SECURITY_POLICY['DIRECTIVES']['child-src']
)

def test_prod_static_url_in_common_settings(self):
"""Make sure prod cdn is specified by default for statics."""
prod_static_url = base_settings.PROD_STATIC_URL
assert prod_static_url in base_settings.CSP_FONT_SRC
assert prod_static_url in base_settings.CSP_IMG_SRC
assert prod_static_url in base_settings.CSP_SCRIPT_SRC
assert prod_static_url in base_settings.CSP_STYLE_SRC
assert (
prod_static_url
in base_settings.CONTENT_SECURITY_POLICY['DIRECTIVES']['font-src']
)
assert (
prod_static_url
in base_settings.CONTENT_SECURITY_POLICY['DIRECTIVES']['img-src']
)
assert (
prod_static_url
in base_settings.CONTENT_SECURITY_POLICY['DIRECTIVES']['script-src']
)
assert (
prod_static_url
in base_settings.CONTENT_SECURITY_POLICY['DIRECTIVES']['style-src']
)

prod_media_url = base_settings.PROD_MEDIA_URL
assert prod_media_url not in base_settings.CSP_FONT_SRC
assert prod_media_url in base_settings.CSP_IMG_SRC
assert prod_media_url not in base_settings.CSP_SCRIPT_SRC
assert prod_media_url not in base_settings.CSP_STYLE_SRC
assert (
prod_media_url
not in base_settings.CONTENT_SECURITY_POLICY['DIRECTIVES']['font-src']
)
assert (
prod_media_url
in base_settings.CONTENT_SECURITY_POLICY['DIRECTIVES']['img-src']
)
assert (
prod_media_url
not in base_settings.CONTENT_SECURITY_POLICY['DIRECTIVES']['script-src']
)
assert (
prod_media_url
not in base_settings.CONTENT_SECURITY_POLICY['DIRECTIVES']['style-src']
)

def test_self_in_common_settings(self):
"""Check 'self' is defined for common settings."""
assert "'self'" in base_settings.CSP_CONNECT_SRC
assert "'self'" in base_settings.CSP_IMG_SRC
assert "'self'" in base_settings.CSP_FORM_ACTION
assert (
"'self'"
in base_settings.CONTENT_SECURITY_POLICY['DIRECTIVES']['connect-src']
)
assert (
"'self'" in base_settings.CONTENT_SECURITY_POLICY['DIRECTIVES']['img-src']
)
assert (
"'self'"
in base_settings.CONTENT_SECURITY_POLICY['DIRECTIVES']['form-action']
)

def test_not_self_in_script_child_or_style_src(self):
"""script-src/style-src/child-src should not need 'self' or the entire
a.m.o. domain"""
assert "'self'" not in base_settings.CSP_SCRIPT_SRC
assert 'https://addons.mozilla.org' not in base_settings.CSP_SCRIPT_SRC
assert "'self'" not in base_settings.CSP_STYLE_SRC
assert 'https://addons.mozilla.org' not in base_settings.CSP_STYLE_SRC
assert "'self'" not in base_settings.CSP_CHILD_SRC
assert 'https://addons.mozilla.org' not in base_settings.CSP_CHILD_SRC
assert (
"'self'"
not in base_settings.CONTENT_SECURITY_POLICY['DIRECTIVES']['script-src']
)
assert (
'https://addons.mozilla.org'
not in base_settings.CONTENT_SECURITY_POLICY['DIRECTIVES']['script-src']
)
assert (
"'self'"
not in base_settings.CONTENT_SECURITY_POLICY['DIRECTIVES']['style-src']
)
assert (
'https://addons.mozilla.org'
not in base_settings.CONTENT_SECURITY_POLICY['DIRECTIVES']['style-src']
)
assert (
"'self'"
not in base_settings.CONTENT_SECURITY_POLICY['DIRECTIVES']['child-src']
)
assert (
'https://addons.mozilla.org'
not in base_settings.CONTENT_SECURITY_POLICY['DIRECTIVES']['child-src']
)

def test_analytics_in_common_settings(self):
"""Check for anaytics hosts in connect-src, img-src and script-src"""
# See https://github.com/mozilla/addons/issues/14799#issuecomment-2127359422
assert base_settings.GOOGLE_ANALYTICS_HOST in base_settings.CSP_CONNECT_SRC
assert base_settings.GOOGLE_TAGMANAGER_HOST in base_settings.CSP_CONNECT_SRC
assert (
base_settings.GOOGLE_ANALYTICS_HOST
in base_settings.CONTENT_SECURITY_POLICY['DIRECTIVES']['connect-src']
)
assert (
base_settings.GOOGLE_TAGMANAGER_HOST
in base_settings.CONTENT_SECURITY_POLICY['DIRECTIVES']['connect-src']
)
assert (
base_settings.GOOGLE_ADDITIONAL_ANALYTICS_HOST
in base_settings.CSP_CONNECT_SRC
in base_settings.CONTENT_SECURITY_POLICY['DIRECTIVES']['connect-src']
)

assert base_settings.GOOGLE_ANALYTICS_HOST in base_settings.CSP_IMG_SRC
assert base_settings.GOOGLE_TAGMANAGER_HOST in base_settings.CSP_IMG_SRC
assert (
base_settings.GOOGLE_ANALYTICS_HOST
in base_settings.CONTENT_SECURITY_POLICY['DIRECTIVES']['img-src']
)
assert (
base_settings.GOOGLE_TAGMANAGER_HOST
in base_settings.CONTENT_SECURITY_POLICY['DIRECTIVES']['img-src']
)

assert base_settings.GOOGLE_ANALYTICS_HOST in base_settings.CSP_SCRIPT_SRC
assert base_settings.GOOGLE_TAGMANAGER_HOST in base_settings.CSP_SCRIPT_SRC
assert (
base_settings.GOOGLE_ANALYTICS_HOST
in base_settings.CONTENT_SECURITY_POLICY['DIRECTIVES']['script-src']
)
assert (
base_settings.GOOGLE_TAGMANAGER_HOST
in base_settings.CONTENT_SECURITY_POLICY['DIRECTIVES']['script-src']
)

def test_csp_settings_not_overriden_for_prod(self):
"""Checks sites/prod/settings.py doesn't have CSP_* settings.
"""Checks sites/prod/settings.py doesn't change CONTENT_SECURITY_POLICY
settings.

Because testing the import of site settings is difficult due to
env vars, we specify prod settings in lib/base_settings and then
Expand All @@ -145,4 +233,4 @@ def test_csp_settings_not_overriden_for_prod(self):

with open(path) as f:
data = f.read()
assert 'CSP_' not in data
assert 'CONTENT_SECURITY_POLICY' not in data
10 changes: 5 additions & 5 deletions src/olympia/conf/dev/settings.py
Original file line number Diff line number Diff line change
Expand Up @@ -25,12 +25,12 @@
STATIC_URL = '%s/static-server/' % EXTERNAL_SITE_URL
MEDIA_URL = '%s/user-media/' % EXTERNAL_SITE_URL

CSP_FONT_SRC += (STATIC_URL,)
# CSP_IMG_SRC already contains 'self', but we could be on reviewers or admin
CONTENT_SECURITY_POLICY['DIRECTIVES']['font-src'] += (STATIC_URL,)
# img-src already contains 'self', but we could be on reviewers or admin
# domain and want to load things from the regular domain.
CSP_IMG_SRC += (MEDIA_URL, STATIC_URL)
CSP_SCRIPT_SRC += (STATIC_URL,)
CSP_STYLE_SRC += (STATIC_URL,)
CONTENT_SECURITY_POLICY['DIRECTIVES']['img-src'] += (MEDIA_URL, STATIC_URL)
CONTENT_SECURITY_POLICY['DIRECTIVES']['script-src'] += (STATIC_URL,)
CONTENT_SECURITY_POLICY['DIRECTIVES']['style-src'] += (STATIC_URL,)

SESSION_COOKIE_DOMAIN = '.%s' % DOMAIN

Expand Down
10 changes: 5 additions & 5 deletions src/olympia/conf/stage/settings.py
Original file line number Diff line number Diff line change
Expand Up @@ -24,12 +24,12 @@
STATIC_URL = '%s/static-server/' % EXTERNAL_SITE_URL
MEDIA_URL = '%s/user-media/' % EXTERNAL_SITE_URL

CSP_FONT_SRC += (STATIC_URL,)
# CSP_IMG_SRC already contains 'self', but we could be on reviewers or admin
CONTENT_SECURITY_POLICY['DIRECTIVES']['font-src'] += (STATIC_URL,)
# img-src already contains 'self', but we could be on reviewers or admin
# domain and want to load things from the regular domain.
CSP_IMG_SRC += (MEDIA_URL, STATIC_URL)
CSP_SCRIPT_SRC += (STATIC_URL,)
CSP_STYLE_SRC += (STATIC_URL,)
CONTENT_SECURITY_POLICY['DIRECTIVES']['img-src'] += (MEDIA_URL, STATIC_URL)
CONTENT_SECURITY_POLICY['DIRECTIVES']['script-src'] += (STATIC_URL,)
CONTENT_SECURITY_POLICY['DIRECTIVES']['style-src'] += (STATIC_URL,)

SESSION_COOKIE_DOMAIN = '.%s' % DOMAIN

Expand Down
6 changes: 4 additions & 2 deletions src/olympia/devhub/views.py
Original file line number Diff line number Diff line change
Expand Up @@ -144,8 +144,10 @@ def addon_listing(request, theme=False):


@csp_update(
CONNECT_SRC=settings.MOZILLA_NEWLETTER_URL,
FORM_ACTION=settings.MOZILLA_NEWLETTER_URL,
{
'connect-src': [settings.MOZILLA_NEWLETTER_URL],
'form-action': [settings.MOZILLA_NEWLETTER_URL],
}
)
def index(request):
ctx = {}
Expand Down
Loading
Loading