Skip to content

Commit

Permalink
Add django_csp.
Browse files Browse the repository at this point in the history
Enforce CSPs, make additional hosts configurable.
  • Loading branch information
rtibbles committed Nov 22, 2024
1 parent 0567800 commit bc7b6d4
Show file tree
Hide file tree
Showing 6 changed files with 74 additions and 1 deletion.
29 changes: 29 additions & 0 deletions kolibri/deployment/default/settings/base.py
Original file line number Diff line number Diff line change
Expand Up @@ -99,6 +99,7 @@
"kolibri.core.auth.middleware.KolibriSessionMiddleware",
"kolibri.core.device.middleware.KolibriLocaleMiddleware",
"django.middleware.common.CommonMiddleware",
"csp.middleware.CSPMiddleware",
"django.middleware.csrf.CsrfViewMiddleware",
"kolibri.core.auth.middleware.CustomAuthenticationMiddleware",
"django.contrib.messages.middleware.MessageMiddleware",
Expand Down Expand Up @@ -441,3 +442,31 @@

# whether Kolibri is running within tests
TESTING = False


# Content Security Policy header settings
# https://django-csp.readthedocs.io/en/latest/configuration.html
CSP_DEFAULT_SRC = ("'self'", "data:", "blob:") + tuple(
conf.OPTIONS["Deployment"]["CSP_HOST_SOURCES"]
)

# Use a stricter script source policy to prevent blob: and data: from being used
CSP_SCRIPT_SRC = ("'self'",) + tuple(conf.OPTIONS["Deployment"]["CSP_HOST_SOURCES"])

# Allow inline styles, as we rely on them heavily in our templates
# and the Aphrodite CSS in JS library generates inline styles
CSP_STYLE_SRC = CSP_DEFAULT_SRC + ("'unsafe-inline'",)

# Explicitly allow iframe embedding from the our zipcontent origin
# This is necessary for the zipcontent app to work
if conf.OPTIONS["Deployment"]["ZIP_CONTENT_ORIGIN"]:
# An explicit origin has been specified, just allow that as the iframe source.
frame_src = (conf.OPTIONS["Deployment"]["ZIP_CONTENT_ORIGIN"],)
else:
# Otherwise, we allow any http origin to be the iframe source.
# Because we 'self:<port>' is not a valid CSP source value.
frame_src = ("http:", "https:")

# Always allow 'self' and 'data' sources to allow for the kind of
# iframe manipulation needed for epub.js.
CSP_FRAME_SRC = CSP_DEFAULT_SRC + frame_src
6 changes: 6 additions & 0 deletions kolibri/deployment/default/settings/dev.py
Original file line number Diff line number Diff line change
Expand Up @@ -40,3 +40,9 @@
}

SWAGGER_SETTINGS = {"DEFAULT_INFO": "kolibri.deployment.default.dev_urls.api_info"}

# Ensure that the CSP is set up to allow webpack-dev-server to be accessed during development
# At the moment, this assumes the port will not change from 3000.
CSP_DEFAULT_SRC += ("localhost:3000", "ws:") # noqa F405
CSP_SCRIPT_SRC += ("localhost:3000",) # noqa F405
CSP_STYLE_SRC += ("localhost:3000",) # noqa F405
2 changes: 1 addition & 1 deletion kolibri/plugins/context_translation/kolibri_plugin.py
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,7 @@ def head_html(self):
"\n".join(
[
f"""<script type="text/javascript" src="{js_url}"></script>""",
"""<script type="text/javascript" src="//cdn.crowdin.com/jipt/jipt.js"></script>""",
"""<script type="text/javascript" src="https://cdn.crowdin.com/jipt/jipt.js"></script>""",
]
)
)
1 change: 1 addition & 0 deletions kolibri/plugins/context_translation/option_defaults.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
option_defaults = {
"Deployment": {
"LANGUAGES": "ach-ug",
"CSP_HOST_SOURCES": "https://cdn.crowdin.com,https://fonts.googleapis.com,https://fonts.gstatic.com,https://crowdin-static.downloads.crowdin.com",
}
}
36 changes: 36 additions & 0 deletions kolibri/utils/options.py
Original file line number Diff line number Diff line change
Expand Up @@ -358,6 +358,32 @@ def lazy_import_callback_list(value):
return out


def _process_csp_source(value):
if not isinstance(value, str):
raise VdtValueError(value)
value = value.strip()
url = urlparse(value)
if not url.scheme or not url.netloc:
raise VdtValueError(value)
return value


def csp_source_list(value):
value = _process_list(value)
out = []
errors = []
for entry in value:
try:
entry_list = _process_csp_source(entry)
out.append(entry_list)
except ValueError:
errors.append(entry)
if errors:
raise VdtValueError(errors)

return out


base_option_spec = {
"Cache": {
"CACHE_BACKEND": {
Expand Down Expand Up @@ -688,6 +714,15 @@ def lazy_import_callback_list(value):
Boolean Flag to check Whether to enable Zeroconf discovery.
""",
},
"CSP_HOST_SOURCES": {
"type": "csp_source_list",
"description": """
List of host sources to use in the Content Security Policy header. This should be a list of
host sources as described by in:
https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Content-Security-Policy/Sources#host-source
Allowing deployed Kolibri servers to specify additional hosts from which content can be loaded.
""",
},
},
"Python": {
"PICKLE_PROTOCOL": {
Expand Down Expand Up @@ -746,6 +781,7 @@ def _get_validator():
"multiprocess_bool": multiprocess_bool,
"cache_option": cache_option,
"lazy_import_callback_list": lazy_import_callback_list,
"csp_source_list": csp_source_list,
}
)

Expand Down
1 change: 1 addition & 0 deletions requirements/base.txt
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
diskcache==5.6.3
django_csp==3.8
django-filter==21.1
django-js-reverse==0.10.2
djangorestframework==3.14.0
Expand Down

0 comments on commit bc7b6d4

Please sign in to comment.