1from django.conf import settings
2from django.core.exceptions import ImproperlyConfigured
3
4from .. import Error, Tags, Warning, register
5
6CROSS_ORIGIN_OPENER_POLICY_VALUES = {
7 "same-origin",
8 "same-origin-allow-popups",
9 "unsafe-none",
10}
11REFERRER_POLICY_VALUES = {
12 "no-referrer",
13 "no-referrer-when-downgrade",
14 "origin",
15 "origin-when-cross-origin",
16 "same-origin",
17 "strict-origin",
18 "strict-origin-when-cross-origin",
19 "unsafe-url",
20}
21
22SECRET_KEY_INSECURE_PREFIX = "django-insecure-"
23SECRET_KEY_MIN_LENGTH = 50
24SECRET_KEY_MIN_UNIQUE_CHARACTERS = 5
25
26SECRET_KEY_WARNING_MSG = (
27 f"Your %s has less than {SECRET_KEY_MIN_LENGTH} characters, less than "
28 f"{SECRET_KEY_MIN_UNIQUE_CHARACTERS} unique characters, or it's prefixed "
29 f"with '{SECRET_KEY_INSECURE_PREFIX}' indicating that it was generated "
30 f"automatically by Django. Please generate a long and random value, "
31 f"otherwise many of Django's security-critical features will be "
32 f"vulnerable to attack."
33)
34
35W001 = Warning(
36 "You do not have 'django.middleware.security.SecurityMiddleware' "
37 "in your MIDDLEWARE so the SECURE_HSTS_SECONDS, "
38 "SECURE_CONTENT_TYPE_NOSNIFF, SECURE_REFERRER_POLICY, "
39 "SECURE_CROSS_ORIGIN_OPENER_POLICY, and SECURE_SSL_REDIRECT settings will "
40 "have no effect.",
41 id="security.W001",
42)
43
44W002 = Warning(
45 "You do not have "
46 "'django.middleware.clickjacking.XFrameOptionsMiddleware' in your "
47 "MIDDLEWARE, so your pages will not be served with an "
48 "'x-frame-options' header. Unless there is a good reason for your "
49 "site to be served in a frame, you should consider enabling this "
50 "header to help prevent clickjacking attacks.",
51 id="security.W002",
52)
53
54W004 = Warning(
55 "You have not set a value for the SECURE_HSTS_SECONDS setting. "
56 "If your entire site is served only over SSL, you may want to consider "
57 "setting a value and enabling HTTP Strict Transport Security. "
58 "Be sure to read the documentation first; enabling HSTS carelessly "
59 "can cause serious, irreversible problems.",
60 id="security.W004",
61)
62
63W005 = Warning(
64 "You have not set the SECURE_HSTS_INCLUDE_SUBDOMAINS setting to True. "
65 "Without this, your site is potentially vulnerable to attack "
66 "via an insecure connection to a subdomain. Only set this to True if "
67 "you are certain that all subdomains of your domain should be served "
68 "exclusively via SSL.",
69 id="security.W005",
70)
71
72W006 = Warning(
73 "Your SECURE_CONTENT_TYPE_NOSNIFF setting is not set to True, "
74 "so your pages will not be served with an "
75 "'X-Content-Type-Options: nosniff' header. "
76 "You should consider enabling this header to prevent the "
77 "browser from identifying content types incorrectly.",
78 id="security.W006",
79)
80
81W008 = Warning(
82 "Your SECURE_SSL_REDIRECT setting is not set to True. "
83 "Unless your site should be available over both SSL and non-SSL "
84 "connections, you may want to either set this setting True "
85 "or configure a load balancer or reverse-proxy server "
86 "to redirect all connections to HTTPS.",
87 id="security.W008",
88)
89
90W009 = Warning(
91 SECRET_KEY_WARNING_MSG % "SECRET_KEY",
92 id="security.W009",
93)
94
95W018 = Warning(
96 "You should not have DEBUG set to True in deployment.",
97 id="security.W018",
98)
99
100W019 = Warning(
101 "You have "
102 "'django.middleware.clickjacking.XFrameOptionsMiddleware' in your "
103 "MIDDLEWARE, but X_FRAME_OPTIONS is not set to 'DENY'. "
104 "Unless there is a good reason for your site to serve other parts of "
105 "itself in a frame, you should change it to 'DENY'.",
106 id="security.W019",
107)
108
109W020 = Warning(
110 "ALLOWED_HOSTS must not be empty in deployment.",
111 id="security.W020",
112)
113
114W021 = Warning(
115 "You have not set the SECURE_HSTS_PRELOAD setting to True. Without this, "
116 "your site cannot be submitted to the browser preload list.",
117 id="security.W021",
118)
119
120W022 = Warning(
121 "You have not set the SECURE_REFERRER_POLICY setting. Without this, your "
122 "site will not send a Referrer-Policy header. You should consider "
123 "enabling this header to protect user privacy.",
124 id="security.W022",
125)
126
127E023 = Error(
128 "You have set the SECURE_REFERRER_POLICY setting to an invalid value.",
129 hint="Valid values are: {}.".format(", ".join(sorted(REFERRER_POLICY_VALUES))),
130 id="security.E023",
131)
132
133E024 = Error(
134 "You have set the SECURE_CROSS_ORIGIN_OPENER_POLICY setting to an invalid "
135 "value.",
136 hint="Valid values are: {}.".format(
137 ", ".join(sorted(CROSS_ORIGIN_OPENER_POLICY_VALUES)),
138 ),
139 id="security.E024",
140)
141
142W025 = Warning(SECRET_KEY_WARNING_MSG, id="security.W025")
143
144
145def _security_middleware():
146 return "django.middleware.security.SecurityMiddleware" in settings.MIDDLEWARE
147
148
149def _xframe_middleware():
150 return (
151 "django.middleware.clickjacking.XFrameOptionsMiddleware" in settings.MIDDLEWARE
152 )
153
154
155@register(Tags.security, deploy=True)
156def check_security_middleware(app_configs, **kwargs):
157 passed_check = _security_middleware()
158 return [] if passed_check else [W001]
159
160
161@register(Tags.security, deploy=True)
162def check_xframe_options_middleware(app_configs, **kwargs):
163 passed_check = _xframe_middleware()
164 return [] if passed_check else [W002]
165
166
167@register(Tags.security, deploy=True)
168def check_sts(app_configs, **kwargs):
169 passed_check = not _security_middleware() or settings.SECURE_HSTS_SECONDS
170 return [] if passed_check else [W004]
171
172
173@register(Tags.security, deploy=True)
174def check_sts_include_subdomains(app_configs, **kwargs):
175 passed_check = (
176 not _security_middleware()
177 or not settings.SECURE_HSTS_SECONDS
178 or settings.SECURE_HSTS_INCLUDE_SUBDOMAINS is True
179 )
180 return [] if passed_check else [W005]
181
182
183@register(Tags.security, deploy=True)
184def check_sts_preload(app_configs, **kwargs):
185 passed_check = (
186 not _security_middleware()
187 or not settings.SECURE_HSTS_SECONDS
188 or settings.SECURE_HSTS_PRELOAD is True
189 )
190 return [] if passed_check else [W021]
191
192
193@register(Tags.security, deploy=True)
194def check_content_type_nosniff(app_configs, **kwargs):
195 passed_check = (
196 not _security_middleware() or settings.SECURE_CONTENT_TYPE_NOSNIFF is True
197 )
198 return [] if passed_check else [W006]
199
200
201@register(Tags.security, deploy=True)
202def check_ssl_redirect(app_configs, **kwargs):
203 passed_check = not _security_middleware() or settings.SECURE_SSL_REDIRECT is True
204 return [] if passed_check else [W008]
205
206
207def _check_secret_key(secret_key):
208 return (
209 len(set(secret_key)) >= SECRET_KEY_MIN_UNIQUE_CHARACTERS
210 and len(secret_key) >= SECRET_KEY_MIN_LENGTH
211 and not secret_key.startswith(SECRET_KEY_INSECURE_PREFIX)
212 )
213
214
215@register(Tags.security, deploy=True)
216def check_secret_key(app_configs, **kwargs):
217 try:
218 secret_key = settings.SECRET_KEY
219 except (ImproperlyConfigured, AttributeError):
220 passed_check = False
221 else:
222 passed_check = _check_secret_key(secret_key)
223 return [] if passed_check else [W009]
224
225
226@register(Tags.security, deploy=True)
227def check_secret_key_fallbacks(app_configs, **kwargs):
228 warnings = []
229 try:
230 fallbacks = settings.SECRET_KEY_FALLBACKS
231 except (ImproperlyConfigured, AttributeError):
232 warnings.append(Warning(W025.msg % "SECRET_KEY_FALLBACKS", id=W025.id))
233 else:
234 for index, key in enumerate(fallbacks):
235 if not _check_secret_key(key):
236 warnings.append(
237 Warning(W025.msg % f"SECRET_KEY_FALLBACKS[{index}]", id=W025.id)
238 )
239 return warnings
240
241
242@register(Tags.security, deploy=True)
243def check_debug(app_configs, **kwargs):
244 passed_check = not settings.DEBUG
245 return [] if passed_check else [W018]
246
247
248@register(Tags.security, deploy=True)
249def check_xframe_deny(app_configs, **kwargs):
250 passed_check = not _xframe_middleware() or settings.X_FRAME_OPTIONS == "DENY"
251 return [] if passed_check else [W019]
252
253
254@register(Tags.security, deploy=True)
255def check_allowed_hosts(app_configs, **kwargs):
256 return [] if settings.ALLOWED_HOSTS else [W020]
257
258
259@register(Tags.security, deploy=True)
260def check_referrer_policy(app_configs, **kwargs):
261 if _security_middleware():
262 if settings.SECURE_REFERRER_POLICY is None:
263 return [W022]
264 # Support a comma-separated string or iterable of values to allow fallback.
265 if isinstance(settings.SECURE_REFERRER_POLICY, str):
266 values = {v.strip() for v in settings.SECURE_REFERRER_POLICY.split(",")}
267 else:
268 values = set(settings.SECURE_REFERRER_POLICY)
269 if not values <= REFERRER_POLICY_VALUES:
270 return [E023]
271 return []
272
273
274@register(Tags.security, deploy=True)
275def check_cross_origin_opener_policy(app_configs, **kwargs):
276 if (
277 _security_middleware()
278 and settings.SECURE_CROSS_ORIGIN_OPENER_POLICY is not None
279 and settings.SECURE_CROSS_ORIGIN_OPENER_POLICY
280 not in CROSS_ORIGIN_OPENER_POLICY_VALUES
281 ):
282 return [E024]
283 return []