Browse Source

Initial code parts for CSP implementation for sentry and self-hosted (#47980)

This PR adds some preliminary code for adding a
`Content-Security-Policy-Report-Only` header with minimal required
permissions (at least I could not find any violations on `sentry
devserver` and self-hosted).
- The CSP middleware is disabled (commented in the `MIDDLEWARE`)
- There is no report collecting enabled by default (`CSP_REPORT_URI` is
not set), the intent is to customize it depending on the use case. Also
users could customize CSP rules in this way:
```
CSP_CONNECT_SRC += ["custom.resource.example.com",]
```
Alexander Tarasov 1 year ago
parent
commit
2a1449220c

+ 1 - 0
requirements-base.txt

@@ -11,6 +11,7 @@ croniter>=1.3.7
 cssselect>=1.0.3
 datadog>=0.29.3
 django-crispy-forms>=1.14.0
+django-csp>=3.7
 django-pg-zero-downtime-migrations>=0.11
 Django>=2.2.28
 djangorestframework>=3.12.4

+ 1 - 0
requirements-dev-frozen.txt

@@ -40,6 +40,7 @@ dictpath==0.1.3
 distlib==0.3.4
 django==2.2.28
 django-crispy-forms==1.14.0
+django-csp==3.7
 django-pg-zero-downtime-migrations==0.11
 djangorestframework==3.12.4
 docker==3.7.0

+ 1 - 0
requirements-frozen.txt

@@ -34,6 +34,7 @@ datadog==0.29.3
 decorator==5.1.1
 django==2.2.28
 django-crispy-forms==1.14.0
+django-csp==3.7
 django-pg-zero-downtime-migrations==0.11
 djangorestframework==3.12.4
 drf-spectacular==0.22.1

+ 58 - 0
src/sentry/conf/server.py

@@ -301,6 +301,8 @@ USE_TZ = True
 # response modifying middleware reset the Content-Length header.
 # This is because CommonMiddleware Sets the Content-Length header for non-streaming responses.
 MIDDLEWARE = (
+    # Uncomment to enable Content Security Policy on this Sentry installation (experimental)
+    # "csp.middleware.CSPMiddleware",
     "sentry.middleware.health.HealthCheck",
     "sentry.middleware.security.SecurityHeadersMiddleware",
     "sentry.middleware.env.SentryEnvMiddleware",
@@ -407,6 +409,62 @@ SILENCED_SYSTEM_CHECKS = (
     "urls.E007",
 )
 
+CSP_INCLUDE_NONCE_IN = [
+    "script-src",
+]
+
+CSP_DEFAULT_SRC = [
+    "'none'",
+]
+CSP_SCRIPT_SRC = [
+    "'self'",
+    "'unsafe-inline'",
+]
+CSP_FONT_SRC = [
+    "'self'",
+    "data:",
+]
+CSP_CONNECT_SRC = [
+    "'self'",
+]
+CSP_FRAME_ANCESTORS = [
+    "'none'",
+]
+CSP_OBJECT_SRC = [
+    "'none'",
+]
+CSP_BASE_URI = [
+    "'none'",
+]
+CSP_STYLE_SRC = [
+    "'self'",
+    "'unsafe-inline'",
+]
+CSP_IMG_SRC = [
+    "'self'",
+    "blob:",
+    "data:",
+    "https://secure.gravatar.com",
+]
+
+if ENVIRONMENT == "development":
+    CSP_SCRIPT_SRC += [
+        "'unsafe-eval'",
+    ]
+    CSP_CONNECT_SRC += [
+        "ws://127.0.0.1:8000",
+    ]
+
+# Before enforcing Content Security Policy, we recommend creating a separate
+# Sentry project and collecting CSP violations in report only mode:
+# https://docs.sentry.io/product/security-policy-reporting/
+
+# Point this parameter to your Sentry installation:
+# CSP_REPORT_URI = "https://example.com/api/{PROJECT_ID}/security/?sentry_key={SENTRY_KEY}"
+
+# To enforce CSP (block violated resources), update the following parameter to False
+CSP_REPORT_ONLY = True
+
 STATIC_ROOT = os.path.realpath(os.path.join(PROJECT_ROOT, "static"))
 STATIC_URL = "/_static/{version}/"
 # webpack assets live at a different URL that is unversioned

+ 18 - 2
src/sentry/integrations/jira/views/base.py

@@ -1,3 +1,5 @@
+from csp.middleware import CSPMiddleware
+from django.conf import settings
 from django.views.generic import View
 
 from sentry import options
@@ -21,6 +23,20 @@ class JiraSentryUIBaseView(View):
             self.request.GET.get("xdm_e"),
             options.get("system.url-prefix"),
         ]
-        sources_string = " ".join(s for s in sources if s)  # Filter out None
-        response["Content-Security-Policy"] = f"frame-ancestors 'self' {sources_string}"
+
+        settings.CSP_FRAME_ANCESTORS = [
+            "'self'",
+        ] + [s for s in sources if s and ";" not in s]
+        settings.CSP_SCRIPT_SRC = [
+            "'self'",
+            "'unsafe-inline'",
+            "connect-cdn.atl-paas.net",
+        ]
+
+        header = "Content-Security-Policy"
+        if getattr(settings, "CSP_REPORT_ONLY", False):
+            header += "-Report-Only"
+
+        middleware = CSPMiddleware()
+        response[header] = middleware.build_policy(request=self.request, response=response)
         return response