Skip to content
This repository has been archived by the owner on Aug 26, 2022. It is now read-only.

Commit

Permalink
bug 948151: Add CSP config, reporting endpoint
Browse files Browse the repository at this point in the history
Add a CSP configuration, based on testing several pages on development.
Enabling CSP and other options are configured from the environment, and
off by default. The policy is configured in the Django settings, and not
configured by the environment. The policy allows inline CSS and
JavaScript, so that the site works without modification, but doesn't
add as much security as it could.

CKEditor 4.5.10 still requires unsafe-eval, but 4.7 claims to remove
this requirement. A decorator adds unsafe-eval only on the editing
pages that use CKEditor.

The reporting view is copied from mozilla/bedrock, and it used to
forward violation reports to Sentry. There were no tests to copy.
  • Loading branch information
jwhitlock committed Oct 2, 2018
1 parent aaaf5b1 commit abf6061
Show file tree
Hide file tree
Showing 5 changed files with 97 additions and 3 deletions.
3 changes: 3 additions & 0 deletions kuma/health/urls.py
Original file line number Diff line number Diff line change
Expand Up @@ -13,4 +13,7 @@
url(r'^_kuma_status.json$',
views.status,
name='health.status'),
url(r'^csp-violation-capture$',
views.csp_violation_capture,
name='health.csp_violation_capture'),
]
45 changes: 43 additions & 2 deletions kuma/health/views.py
Original file line number Diff line number Diff line change
@@ -1,10 +1,15 @@
import json
import logging

from django.conf import settings
from django.db import DatabaseError
from django.http import HttpResponse, JsonResponse
from django.http import HttpResponse, HttpResponseBadRequest, JsonResponse
from django.views.decorators.cache import never_cache
from django.views.decorators.http import require_safe
from django.views.decorators.csrf import csrf_exempt
from django.views.decorators.http import require_POST, require_safe
from elasticsearch.exceptions import (ConnectionError as ES_ConnectionError,
NotFoundError)
from raven.contrib.django.models import client
from requests.exceptions import ConnectionError as Requests_ConnectionError

from kuma.users.models import User
Expand Down Expand Up @@ -156,3 +161,39 @@ def status(request):
data['services']['test_accounts'] = test_account_data

return JsonResponse(data)


@csrf_exempt
@require_POST
def csp_violation_capture(request):
"""
Capture CSP violation reports, forward to Sentry.
HT @glogiotatidis https://github.com/mozmeao/lumbergh/pull/180
HT @pmac, @jgmize https://github.com/mozilla/bedrock/pull/4335
"""
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.')
48 changes: 47 additions & 1 deletion kuma/settings/common.py
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@
import dj_email_url
import djcelery
from decouple import config, Csv
from six.moves.urllib.parse import urlsplit
from six.moves.urllib.parse import urlsplit, urlunsplit

_Language = namedtuple(u'Language', u'english native')

Expand Down Expand Up @@ -482,6 +482,13 @@ def _get_locales():
'kuma.core.middleware.RestrictedEndpointsMiddleware',
)

CSP_ENABLE_MIDDLEWARE = config('CSP_ENABLE_MIDDLEWARE',
default=False, cast=bool)
if CSP_ENABLE_MIDDLEWARE:
# For more config, see "Content Security Policy (CSP)" below
MIDDLEWARE += ('csp.middleware.CSPMiddleware',)


# Auth
AUTHENTICATION_BACKENDS = (
'kuma.users.auth_backends.KumaAuthBackend', # Handles User Bans
Expand Down Expand Up @@ -1240,6 +1247,40 @@ def parse_iframe_url(url):
)
EMAIL_FILE_PATH = '/app/tmp/emails'

# Content Security Policy (CSP)
CSP_DEFAULT_SRC = ("'none'",)
CSP_CONNECT_SRC = [
"'self'",
]
CSP_FONT_SRC = [
"'self'",
]
CSP_FRAME_SRC = [
urlunsplit((scheme, netloc, '', '', ''))
for scheme, netloc, ignored_path in ALLOWED_IFRAME_PATTERNS]
CSP_IMG_SRC = [
"'self'",
"data:",
"https://secure.gravatar.com",
"https://www.google-analytics.com",
]
CSP_SCRIPT_SRC = [
"'self'",
"www.google-analytics.com",
# TODO fix things so that we don't need this
"'unsafe-inline'",
]
CSP_STYLE_SRC = [
"'self'",
# TODO fix things so that we don't need this
"'unsafe-inline'",
]
CSP_REPORT_ONLY = config('CSP_REPORT_ONLY', default=False, cast=bool)
CSP_REPORT_ENABLE = config('CSP_REPORT_ENABLE', default=False, cast=bool)
if CSP_REPORT_ENABLE:
CSP_REPORT_URI = config('CSP_REPORT_URI', default='/csp-violation-capture')

# Celery (asynchronous tasks)
BROKER_URL = config('BROKER_URL',
default='amqp://kuma:kuma@developer-local:5672/kuma')

Expand Down Expand Up @@ -1713,3 +1754,8 @@ def get_user_url(user):
CONTRIBUTION_FORM_CHOICES = [32, 64, 128]
CONTRIBUTION_SUPPORT_EMAIL = config('CONTRIBUTION_SUPPORT_EMAIL',
default='mdn-support@mozilla.com')
if MDN_CONTRIBUTION:
CSP_CONNECT_SRC.append('https://checkout.stripe.com')
CSP_FRAME_SRC.append('https://checkout.stripe.com')
CSP_IMG_SRC.append('https://*.stripe.com')
CSP_SCRIPT_SRC.append('https://checkout.stripe.com')
2 changes: 2 additions & 0 deletions kuma/wiki/views/edit.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
# -*- coding: utf-8 -*-

import newrelic.agent
from csp.decorators import csp_update
from django.conf import settings
from django.core.exceptions import PermissionDenied
from django.http import HttpResponse, JsonResponse
Expand Down Expand Up @@ -80,6 +81,7 @@ def _edit_document_collision(request, orig_rev, curr_rev, is_async_submit,
@login_required # TODO: Stop repeating this knowledge here and in Document.allows_editing_by.
@ratelimit(key='user', rate='60/m', block=True)
@block_banned_ips
@csp_update(SCRIPT_SRC="'unsafe-eval'") # Required until CKEditor 4.7
@process_document_path
@check_readonly
@prevent_indexing
Expand Down
2 changes: 2 additions & 0 deletions kuma/wiki/views/translate.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
# -*- coding: utf-8 -*-
from csp.decorators import csp_update
from django.conf import settings
from django.core.exceptions import ObjectDoesNotExist
from django.http import JsonResponse
Expand Down Expand Up @@ -39,6 +40,7 @@ def select_locale(request, document_slug, document_locale):
@never_cache
@block_user_agents
@login_required
@csp_update(SCRIPT_SRC="'unsafe-eval'") # Required until CKEditor 4.7
@process_document_path
@check_readonly
@prevent_indexing
Expand Down

0 comments on commit abf6061

Please sign in to comment.