|
124 | 124 | # External applications.
|
125 | 125 | "axes",
|
126 | 126 | "django_filters",
|
| 127 | + "csp", |
127 | 128 | "corsheaders",
|
128 | 129 | "vng_api_common",
|
129 | 130 | "notifications_api_common",
|
|
162 | 163 | "django.contrib.messages.middleware.MessageMiddleware",
|
163 | 164 | "django.middleware.clickjacking.XFrameOptionsMiddleware",
|
164 | 165 | "axes.middleware.AxesMiddleware",
|
| 166 | + "csp.contrib.rate_limiting.RateLimitedCSPMiddleware", |
165 | 167 | ]
|
166 | 168 |
|
167 | 169 | ROOT_URLCONF = f"{PROJECT_DIRNAME}.urls"
|
|
421 | 423 | CSRF_COOKIE_SECURE = IS_HTTPS
|
422 | 424 | CSRF_COOKIE_SAMESITE = config("CSRF_COOKIE_SAMESITE", "Strict")
|
423 | 425 |
|
| 426 | +if IS_HTTPS: |
| 427 | + SECURE_HSTS_SECONDS = 31536000 |
| 428 | + |
424 | 429 | X_FRAME_OPTIONS = "DENY"
|
425 | 430 |
|
426 | 431 | #
|
@@ -672,3 +677,64 @@ def init_sentry(before_send: Callable | None = None):
|
672 | 677 | LOG_OUTGOING_REQUESTS_MAX_AGE = config(
|
673 | 678 | "LOG_OUTGOING_REQUESTS_MAX_AGE", default=7
|
674 | 679 | ) # number of days
|
| 680 | + |
| 681 | + |
| 682 | +# |
| 683 | +# Django CSP settings |
| 684 | +# |
| 685 | +# explanation of directives: https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Content-Security-Policy |
| 686 | +# and how to specify them: https://django-csp.readthedocs.io/en/latest/configuration.html |
| 687 | +# |
| 688 | +# NOTE: make sure values are a tuple or list, and to quote special values like 'self' |
| 689 | + |
| 690 | +# ideally we'd use BASE_URI but it'd have to be lazy or cause issues |
| 691 | +CSP_DEFAULT_SRC = [ |
| 692 | + "'self'", |
| 693 | +] + config("CSP_EXTRA_DEFAULT_SRC", default=[], split=True) |
| 694 | + |
| 695 | +CSP_REPORT_PERCENTAGE = config("CSP_REPORT_PERCENTAGE", 1.0) # float between 0 and 1 |
| 696 | + |
| 697 | +CSP_FORM_ACTION = ( |
| 698 | + config( |
| 699 | + "CSP_FORM_ACTION", |
| 700 | + default=["\"'self'\""] |
| 701 | + + config("CSP_EXTRA_FORM_ACTION", default=[], split=True), |
| 702 | + split=True, |
| 703 | + ) |
| 704 | + + CORS_ALLOWED_ORIGINS |
| 705 | +) |
| 706 | + |
| 707 | +CSP_IMG_SRC = ( |
| 708 | + CSP_DEFAULT_SRC |
| 709 | + + config("CSP_EXTRA_IMG_SRC", default=[], split=True) |
| 710 | +) |
| 711 | + |
| 712 | +# affects <object> and <embed> tags, block everything by default but allow deploy-time |
| 713 | +# overrides. |
| 714 | +CSP_OBJECT_SRC = config("CSP_OBJECT_SRC", default=["\"'none'\""], split=True) |
| 715 | + |
| 716 | +# we must include this explicitly, otherwise the style-src only includes the nonce because |
| 717 | +# of CSP_INCLUDE_NONCE_IN |
| 718 | +CSP_STYLE_SRC = CSP_DEFAULT_SRC |
| 719 | +CSP_SCRIPT_SRC = CSP_DEFAULT_SRC |
| 720 | + |
| 721 | +# firefox does not get the nonce from default-src, see |
| 722 | +# https://stackoverflow.com/a/63376012 |
| 723 | +CSP_INCLUDE_NONCE_IN = ["style-src", "script-src"] |
| 724 | + |
| 725 | +# directives that don't fallback to default-src |
| 726 | +CSP_BASE_URI = ["'self'"] |
| 727 | + |
| 728 | +# Frame directives do not fall back to default-src |
| 729 | +CSP_FRAME_ANCESTORS = ["'none'"] # equivalent to X-Frame-Options: deny |
| 730 | +CSP_FRAME_SRC = ["'self'"] |
| 731 | +# CSP_NAVIGATE_TO = ["'self'"] # this will break all outgoing links etc # too much & tricky, see note on MDN |
| 732 | +# CSP_SANDBOX # too much |
| 733 | + |
| 734 | +CSP_UPGRADE_INSECURE_REQUESTS = False # TODO enable on production? |
| 735 | + |
| 736 | +CSP_EXCLUDE_URL_PREFIXES = ( |
| 737 | + # ReDoc/Swagger pull in external sources, so don't enforce CSP on API endpoints/documentation. |
| 738 | + "/api/", |
| 739 | + "/admin/", |
| 740 | +) |
0 commit comments