MPP-3838: restore safer CSP
Use a new EagerNonceCSPMiddleware to add nonce to the CSP and update the React app to include it in dynamic scripts.
This commit is contained in:
Родитель
687d385493
Коммит
7f0fb8b328
|
@ -34,6 +34,7 @@ import { HolidayPromoBanner } from "./topmessage/HolidayPromoBanner";
|
|||
import { isFlagActive } from "../../functions/waffle";
|
||||
import { useMetrics } from "../../hooks/metrics";
|
||||
import { GoogleAnalyticsWorkaround } from "../GoogleAnalyticsWorkaround";
|
||||
import { getCookie } from "../../functions/cookies";
|
||||
|
||||
export type Props = {
|
||||
children: ReactNode;
|
||||
|
@ -293,6 +294,7 @@ export const Layout = (props: Props) => {
|
|||
metricsEnabled === "enabled" ? (
|
||||
<GoogleAnalyticsWorkaround
|
||||
gaId={props.runtimeData.GA4_MEASUREMENT_ID}
|
||||
nonce={getCookie("csp_nonce")}
|
||||
debugMode={
|
||||
process.env.NEXT_PUBLIC_GA4_DEBUG_MODE === "true" &&
|
||||
process.env.NODE_ENV !== "test"
|
||||
|
|
|
@ -1,3 +1,5 @@
|
|||
import binascii
|
||||
import os
|
||||
import re
|
||||
import time
|
||||
from collections.abc import Callable
|
||||
|
@ -8,11 +10,61 @@ from django.http import HttpRequest, HttpResponse
|
|||
from django.shortcuts import redirect
|
||||
|
||||
import markus
|
||||
from csp.middleware import CSPMiddleware
|
||||
from whitenoise.middleware import WhiteNoiseMiddleware
|
||||
|
||||
metrics = markus.get_metrics("fx-private-relay")
|
||||
|
||||
|
||||
# To find all the URL paths that serve HTML which need the CSP nonce:
|
||||
# python manage.py collectstatic
|
||||
# find staticfiles -type f -name 'index.html'
|
||||
CSP_NONCE_COOKIE_PATHS = [
|
||||
"/",
|
||||
"/contains-tracker-warning",
|
||||
"/flags",
|
||||
"/faq",
|
||||
"/vpn-relay/waitlist",
|
||||
"/accounts/settings",
|
||||
"/accounts/profile",
|
||||
"/accounts/account_inactive",
|
||||
"/vpn-relay-welcome",
|
||||
"/phone/waitlist",
|
||||
"/phone",
|
||||
"/404",
|
||||
"/tracker-report",
|
||||
"/premium/waitlist",
|
||||
"/premium",
|
||||
]
|
||||
|
||||
|
||||
class EagerNonceCSPMiddleware(CSPMiddleware):
|
||||
# We need a nonce to use Google Tag Manager with a safe CSP:
|
||||
# https://developers.google.com/tag-platform/security/guides/csp
|
||||
# django-csp only includes the nonce value in the CSP header if the csp_nonce
|
||||
# attribute is accessed:
|
||||
# https://django-csp.readthedocs.io/en/latest/nonce.html
|
||||
# That works for urls served by Django views that access the attribute but it
|
||||
# doesn't work for urls that are served by views which don't access the attribute.
|
||||
# (e.g., Whitenoise)
|
||||
# So, to ensure django-csp includes the nonce value in the CSP header of every
|
||||
# response, we override the default CSPMiddleware with this middleware. If the
|
||||
# request is for one of the HTML urls, this middleware sets the request.csp_nonce
|
||||
# attribute and adds a cookie for the React app to get the nonce value for scripts.
|
||||
def process_request(self, request):
|
||||
if request.path in CSP_NONCE_COOKIE_PATHS:
|
||||
request_nonce = binascii.hexlify(os.urandom(16)).decode("ascii")
|
||||
request._csp_nonce = request_nonce
|
||||
|
||||
def process_response(self, request, response):
|
||||
response = super().process_response(request, response)
|
||||
if request.path in CSP_NONCE_COOKIE_PATHS:
|
||||
response.set_cookie(
|
||||
"csp_nonce", request._csp_nonce, secure=True, samesite="Strict"
|
||||
)
|
||||
return response
|
||||
|
||||
|
||||
class RedirectRootIfLoggedIn:
|
||||
def __init__(self, get_response):
|
||||
self.get_response = get_response
|
||||
|
|
|
@ -26,7 +26,7 @@ import dj_database_url
|
|||
import django_stubs_ext
|
||||
import markus
|
||||
import sentry_sdk
|
||||
from csp.constants import NONE, SELF, UNSAFE_INLINE
|
||||
from csp.constants import NONCE, NONE, SELF, UNSAFE_INLINE
|
||||
from decouple import Choices, Csv, config
|
||||
from sentry_sdk.integrations.django import DjangoIntegration
|
||||
from sentry_sdk.integrations.logging import ignore_logger
|
||||
|
@ -141,6 +141,7 @@ else:
|
|||
|
||||
API_DOCS_ENABLED = config("API_DOCS_ENABLED", False, cast=bool) or DEBUG
|
||||
_CSP_SCRIPT_INLINE = API_DOCS_ENABLED or USE_SILK
|
||||
_CSP_SCRIPT_INLINE = False
|
||||
|
||||
# When running locally, styles might get refreshed while the server is running, so their
|
||||
# hashes would get oudated. Hence, we just allow all of them.
|
||||
|
@ -189,21 +190,26 @@ CONTENT_SECURITY_POLICY: CONTENT_SECURITY_POLICY_T = {
|
|||
"default-src": [SELF],
|
||||
"connect-src": [
|
||||
SELF,
|
||||
"https://www.google-analytics.com/",
|
||||
"https://www.googletagmanager.com/",
|
||||
"https://*.google-analytics.com",
|
||||
"https://*.analytics.google.com",
|
||||
"https://*.googletagmanager.com",
|
||||
"https://location.services.mozilla.com",
|
||||
"https://api.stripe.com",
|
||||
BASKET_ORIGIN,
|
||||
],
|
||||
"font-src": [SELF, "https://relay.firefox.com/"],
|
||||
"frame-src": ["https://js.stripe.com", "https://hooks.stripe.com"],
|
||||
"img-src": [SELF],
|
||||
"img-src": [
|
||||
SELF,
|
||||
"https://*.google-analytics.com",
|
||||
"https://*.googletagmanager.com",
|
||||
],
|
||||
"object-src": [NONE],
|
||||
"script-src": [
|
||||
SELF,
|
||||
UNSAFE_INLINE, # TODO: remove this temporary fix for GA4
|
||||
NONCE,
|
||||
"https://www.google-analytics.com/",
|
||||
"https://www.googletagmanager.com/",
|
||||
"https://*.googletagmanager.com",
|
||||
"https://js.stripe.com/",
|
||||
],
|
||||
"style-src": [SELF],
|
||||
|
@ -373,7 +379,7 @@ if DEBUG:
|
|||
|
||||
MIDDLEWARE += [
|
||||
"django.middleware.security.SecurityMiddleware",
|
||||
"csp.middleware.CSPMiddleware",
|
||||
"privaterelay.middleware.EagerNonceCSPMiddleware",
|
||||
"privaterelay.middleware.RedirectRootIfLoggedIn",
|
||||
"privaterelay.middleware.RelayStaticFilesMiddleware",
|
||||
"django.contrib.sessions.middleware.SessionMiddleware",
|
||||
|
|
|
@ -51,6 +51,7 @@ module = [
|
|||
"botocore.exceptions",
|
||||
"codetiming",
|
||||
"csp.constants",
|
||||
"csp.middleware",
|
||||
"debug_toolbar",
|
||||
"dj_database_url",
|
||||
"django_filters.*",
|
||||
|
|
Загрузка…
Ссылка в новой задаче