Skip to content

Commit

Permalink
Fix bug 1260746: Enable CSP
Browse files Browse the repository at this point in the history
Tested and working with Optimizely and Google Analytics/Tag Manager.

Also works with:

* Mapbox
* Youtube
* trackertest.org (moz service for tracking detection)
* surveygizmo.com
* firefox accounts
  • Loading branch information
pmac committed Sep 19, 2016
1 parent 08dafbe commit 3bedbf1
Show file tree
Hide file tree
Showing 6 changed files with 117 additions and 1 deletion.
1 change: 1 addition & 0 deletions .bedrock_demo_env
@@ -1,5 +1,6 @@
LOGLEVEL=info
ENABLE_CSP_MIDDLEWARE=True
CSP_EXTRA_FRAME_SRC=*.mozaws.net
DEBUG=False
DATABASE_URL=sqlite:///bedrock.db
DEV=True
Expand Down
41 changes: 40 additions & 1 deletion bedrock/base/views.py
@@ -1,4 +1,12 @@
from django.http import HttpResponse
import json
import logging

from django.conf import settings
from django.http import HttpResponse, HttpResponseBadRequest
from django.views.decorators.csrf import csrf_exempt
from django.views.decorators.http import require_POST

from raven.contrib.django.models import client

from lib import l10n_utils

Expand All @@ -10,3 +18,34 @@ def health_check(request):
def server_error_view(request, template_name='500.html'):
"""500 error handler that runs context processors."""
return l10n_utils.render(request, template_name, status=500)


@csrf_exempt
@require_POST
def csp_violation_capture(request):
# HT @glogiotatidis https://github.com/mozmar/lumbergh/pull/180/
if not settings.CSP_REPORT_ENABLE:
# mitigation option for a flood of violation reports
return HttpResponse()

data = client.get_data_from_request(request)
data.update({
'level': logging.INFO,
'logger': 'CSP',
})
try:
csp_data = json.loads(request.body)
except ValueError:
# Cannot decode CSP violation data, ignore
return HttpResponseBadRequest('Invalid CSP Report')

try:
blocked_uri = csp_data['csp-report']['blocked-uri']
except KeyError:
# Incomplete CSP report
return HttpResponseBadRequest('Incomplete CSP Report')

client.captureMessage(message='CSP Violation: {}'.format(blocked_uri),
data=data)

return HttpResponse('Captured CSP violation, thanks for reporting.')
68 changes: 68 additions & 0 deletions bedrock/settings/base.py
Expand Up @@ -221,6 +221,7 @@ def lazy_langs():
'keymaster',
'microsummaries',
'xbl',
'csp-violation-capture',
]

ALLOWED_HOSTS = config(
Expand Down Expand Up @@ -326,6 +327,10 @@ def lazy_langs():
'dnt.middleware.DoNotTrackMiddleware',
]

ENABLE_CSP_MIDDLEWARE = config('ENABLE_CSP_MIDDLEWARE', default=False, cast=bool)
if ENABLE_CSP_MIDDLEWARE:
MIDDLEWARE_CLASSES.append('csp.middleware.CSPMiddleware')

INSTALLED_APPS = (
'cronjobs', # for ./manage.py cron * cmd line tasks

Expand Down Expand Up @@ -1069,3 +1074,66 @@ def lazy_email_backend():
'dsn': config('SENTRY_DSN', None),
'release': config('GIT_SHA', None),
}

# Django-CSP
CSP_DEFAULT_SRC = (
"'self'",
'*.mozilla.net',
'*.mozilla.org',
'*.mozilla.com',
)
CSP_IMG_SRC = CSP_DEFAULT_SRC + (
'data:',
'*.optimizely.com',
'www.googletagmanager.com',
'www.google-analytics.com',
'*.tiles.mapbox.com',
'api.mapbox.com',
'creativecommons.org',
)
CSP_SCRIPT_SRC = CSP_DEFAULT_SRC + (
# TODO fix things so that we don't need this
"'unsafe-inline'",
# TODO snap.svg.js passes a string to Function() which is
# blocked without unsafe-eval. Find a way to remove that.
"'unsafe-eval'",
'*.optimizely.com',
'optimizely.s3.amazonaws.com',
'www.googletagmanager.com',
'www.google-analytics.com',
'www.youtube.com',
's.ytimg.com',
)
CSP_STYLE_SRC = CSP_DEFAULT_SRC + (
# TODO fix things so that we don't need this
"'unsafe-inline'",
)
CSP_CHILD_SRC = (
'*.optimizely.com',
'www.googletagmanager.com',
'www.google-analytics.com',
'www.youtube-nocookie.com',
'trackertest.org', # mozilla service for tracker detection
'www.surveygizmo.com',
'accounts.firefox.com',
'accounts.firefox.com.cn',
'www.youtube.com',
)
CSP_CONNECT_SRC = CSP_DEFAULT_SRC + (
'*.optimizely.com',
'www.googletagmanager.com',
'www.google-analytics.com',
'*.tiles.mapbox.com',
'api.mapbox.com',
)
CSP_REPORT_ONLY = config('CSP_REPORT_ONLY', default=False, cast=bool)
CSP_REPORT_ENABLE = config('CSP_REPORT_ENABLE', default=True, cast=bool)
if CSP_REPORT_ENABLE:
CSP_REPORT_URI = config('CSP_REPORT_URI', default='/csp-violation-capture')

CSP_EXTRA_FRAME_SRC = config('CSP_EXTRA_FRAME_SRC', default='', cast=Csv())
if CSP_EXTRA_FRAME_SRC:
CSP_CHILD_SRC += tuple(CSP_EXTRA_FRAME_SRC)

# support older browsers (mainly Safari)
CSP_FRAME_SRC = CSP_CHILD_SRC
2 changes: 2 additions & 0 deletions bedrock/urls.py
Expand Up @@ -39,6 +39,8 @@
include('bedrock.l10n_example.urls')),

url(r'^healthz/$', 'bedrock.base.views.health_check'),
url(r'^csp-violation-capture$', 'bedrock.base.views.csp_violation_capture',
name='csp-violation-capture'),

# Uncomment the admin/doc line below to enable admin documentation:
# url(r'^admin/doc/', include('django.contrib.admindocs.urls')),
Expand Down
3 changes: 3 additions & 0 deletions bin/circleci-demo-deploy.sh
Expand Up @@ -21,6 +21,9 @@ if ./deis apps:create "$DEIS_APP_NAME" --no-remote; then
./deis perms:create "$CIRCLE_USERNAME" -a "$DEIS_APP_NAME" || true
echo "Configuring the new demo app"
./deis config:push -a "$DEIS_APP_NAME" -p .bedrock_demo_env
if [[ -n "$SENTRY_DEMO_DSN" ]]; then
./deis config:set -a "$DEIS_APP_NAME" "SENTRY_DSN=$SENTRY_DEMO_DSN"
fi
fi
echo "Pulling $DOCKER_IMAGE_TAG into Deis app $DEIS_APP_NAME"
./deis pull "$DOCKER_IMAGE_TAG" -a "$DEIS_APP_NAME"
3 changes: 3 additions & 0 deletions requirements/base.txt
Expand Up @@ -127,3 +127,6 @@ raven==5.25.0 \
contextlib2==0.5.4 \
--hash=sha256:399f659f2a8b5d5d529f132e1136fc404fbbc28e34e4618c5c92bd595be9b162 \
--hash=sha256:710626cde569f51a87f216ff757fe60f5cd13ae8f8114706590510cd5649ce88
django_csp==3.0 \
--hash=sha256:e6e627955651235852508f98238f3f04c7ab9a77b625a6bd101cd0cf1a1e3419 \
--hash=sha256:e44479be426ffd5371b7522f43f8c32f8597a5003c236f31bb98ff4497a20a21

0 comments on commit 3bedbf1

Please sign in to comment.