Coverage for /home/runner/work/viur-core/viur-core/viur/src/viur/core/securityheaders.py: 10%
102 statements
« prev ^ index » next coverage.py v7.6.3, created at 2024-10-16 22:16 +0000
« prev ^ index » next coverage.py v7.6.3, created at 2024-10-16 22:16 +0000
1"""
2This module provides configuration for most of the http security headers. The features currently supported are:
3 - Content security policy (https://developer.mozilla.org/en-US/docs/Web/HTTP/CSP)
4 - Strict transport security (https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Strict-Transport-Security)
5 - X-Frame-Options (https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/X-Frame-Options)
6 - X-XSS-Protection (https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/X-XSS-Protection)
7 - X-Content-Type-Options (https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/X-Content-Type-Options)
8 - X-Permitted-Cross-Domain-Policies (https://www.adobe.com/devnet-docs/acrobatetk/tools/AppSec/xdomain.html)
9 - Referrer-Policy (https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Referrer-Policy)
10 - Permissions-Policy (https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Feature-Policy)
11 - Cross origin isolation (https://web.dev/coop-coep)
13If a feature is not yet supported, you could always set the header directly (e.g. by attaching a request
14preprocessor). ViUR contains a default configuration for most of these headers where possible, however manual
15review is mandatory for each project.
17The content security policy will prevent inline css and javascript by default, but is configured to allow embedding
18images from cloud-storage and sign-in with google.
20Strict transport security is enabled by default (with a TTL of one year), but without preload or include-subdomains.
22X-Frame-Options is limited to the same origin, preventing urls from this project from being embedded in iframes that
23don't originate from the same origin.
25X-XSS-Protection is enabled.
27X-Content-Type-Options is set to nosniff
29X-Permitted-Cross-Domain-Policies is set to "none", denying embedding resources in pdf files and the like
31Referrer-Policy is set to strict-origin, preventing leakage of URLs to 3rd-partys.
33The Permissions-Policy will only allow auto-play by default (thus access to the camera-api etc. is disabled)
35Cross origin isolation is currently disabled by default (as it's incompatible with many popular services like
36embedding a map or sign-in with google).
38ViUR also protects it's cookies by default (setting httponly, secure and samesite=lax). This can be changed by
39setting the corresponding class-level variables on class:`Session<viur.core.session.Session>`.
40"""
42from viur.core.config import conf
43from viur.core import current
44import logging
45import typing as t
48def addCspRule(objectType: str, srcOrDirective: str, enforceMode: str = "monitor"):
49 """
50 This function helps configuring and reporting of content security policy rules and violations.
51 To enable CSP, call addCspRule() from your projects main file before calling server.setup().
53 .. code-block:: python
55 # Example Usage
57 # Enable CSP for all types and made us the only allowed source
58 security.addCspRule("default-src","self","enforce")
60 # Start a new set of rules for stylesheets whitelist us
61 security.addCspRule("style-src","self","enforce")
63 # This is currently needed for TextBones!
64 security.addCspRule("style-src","unsafe-inline","enforce")
66 If you don't want these rules to be enforced and just getting a report of violations replace "enforce" with
67 "monitor". To add a report-url use something like::
69 security.addCspRule("report-uri","/cspReport","enforce")
71 and register a function at /cspReport to handle the reports.
73 ..note::
75 Our tests showed that enabling a report-url on production systems has limited use. There are literally
76 thousands of browser-extensions out there that inject code into the pages displayed. This causes a whole
77 flood of violations-spam to your report-url.
80 :param objectType: For which type of objects should this directive be enforced? (script-src, img-src, ...)
81 :param srcOrDirective: Either a domain which should be white-listed or a CSP-Keyword like 'self', 'unsafe-inline', etc.
82 :param enforceMode: Should this directive be enforced or just logged?
83 """
84 assert enforceMode in ["monitor", "enforce"], "enforceMode must be 'monitor' or 'enforce'!"
85 assert objectType in {"default-src", "script-src", "object-src", "style-src", "img-src", "media-src",
86 "frame-src", "font-src", "connect-src", "report-uri", "frame-ancestors", "child-src",
87 "form-action", "require-trusted-types-for"}
88 assert conf.main_app is None, "You cannot modify CSP rules after server.buildApp() has been run!"
89 assert not any(
90 [x in srcOrDirective for x in [";", "'", "\"", "\n", ","]]), "Invalid character in srcOrDirective!"
91 if conf.security.content_security_policy is None:
92 conf.security.content_security_policy = {"_headerCache": {}}
93 if enforceMode not in conf.security.content_security_policy:
94 conf.security.content_security_policy[enforceMode] = {}
95 if objectType == "report-uri":
96 conf.security.content_security_policy[enforceMode]["report-uri"] = [srcOrDirective]
97 else:
98 if objectType not in conf.security.content_security_policy[enforceMode]:
99 conf.security.content_security_policy[enforceMode][objectType] = []
100 if srcOrDirective not in conf.security.content_security_policy[enforceMode][objectType]:
101 conf.security.content_security_policy[enforceMode][objectType].append(srcOrDirective)
104def _rebuildCspHeaderCache():
105 """
106 Rebuilds the internal conf.security.content_security_policy["_headerCache"] dictionary, ie. it constructs
107 the Content-Security-Policy-Report-Only and Content-Security-Policy headers based on what has been passed
108 to 'addRule' earlier on. Should not be called directly.
109 """
110 conf.security.content_security_policy["_headerCache"] = {}
111 for enforceMode in ["monitor", "enforce"]:
112 resStr = ""
113 if enforceMode not in conf.security.content_security_policy:
114 continue
115 for key, values in conf.security.content_security_policy[enforceMode].items():
116 resStr += key
117 for value in values:
118 resStr += " "
119 if value in {"self", "unsafe-inline", "unsafe-eval", "script", "none"} or \
120 any([value.startswith(x) for x in ["sha256-", "sha384-", "sha512-"]]):
121 # We don't permit nonce- in project wide config as this will be reused on multiple requests
122 resStr += f"'{value}'"
123 else:
124 resStr += value
125 resStr += "; "
126 if enforceMode == "monitor":
127 conf.security.content_security_policy["_headerCache"][
128 "Content-Security-Policy-Report-Only"] = resStr
129 else:
130 conf.security.content_security_policy["_headerCache"]["Content-Security-Policy"] = resStr
133def extendCsp(additionalRules: dict = None, overrideRules: dict = None) -> None:
134 """
135 Adds additional csp rules to the current request. ViUR will emit a default csp-header based on the
136 project-wide config. For some requests, it's needed to extend or override these rules without having to include
137 them in the project config. Each dictionary must be in the same format as the
138 conf.security.content_security_policy. Values in additionalRules will extend the project-specific
139 configuration, while overrideRules will replace them.
141 ..Note: This function will only work on CSP-Rules in "enforce" mode, "monitor" is not suppored
143 :param additionalRules: Dictionary with additional csp-rules to emit
144 :param overrideRules: Values in this dictionary will override the corresponding default rule
145 """
146 assert additionalRules or overrideRules, "Either additionalRules or overrideRules must be given!"
147 tmpDict = {} # Copy the project-wide config in
148 if conf.security.content_security_policy.get("enforce"):
149 tmpDict.update({k: v[:] for k, v in conf.security.content_security_policy["enforce"].items()})
150 if overrideRules: # Merge overrideRules
151 for k, v in overrideRules.items():
152 if v is None and k in tmpDict:
153 del tmpDict[k]
154 else:
155 tmpDict[k] = v
156 if additionalRules: # Merge the extension dict
157 for k, v in additionalRules.items():
158 if k not in tmpDict:
159 tmpDict[k] = []
160 tmpDict[k].extend(v)
161 resStr = "" # Rebuild the CSP-Header
162 for key, values in tmpDict.items():
163 resStr += key
164 for value in values:
165 resStr += " "
166 if value in {"self", "unsafe-inline", "unsafe-eval", "script", "none"} or \
167 any([value.startswith(x) for x in ["nonce-", "sha256-", "sha384-", "sha512-"]]):
168 resStr += f"'{value}'"
169 else:
170 resStr += value
171 resStr += "; "
172 current.request.get().response.headers["Content-Security-Policy"] = resStr
175def enableStrictTransportSecurity(maxAge: int = 365 * 24 * 60 * 60,
176 includeSubDomains: bool = False,
177 preload: bool = False) -> None:
178 """
179 Enables HTTP strict transport security.
181 :param maxAge: The time, in seconds, that the browser should remember that this site is only to be accessed using HTTPS.
182 :param includeSubDomains: If this parameter is set, this rule applies to all of the site's subdomains as well.
183 :param preload: If set, we'll issue a hint that preloading would be appreciated.
184 """
185 conf.security.strict_transport_security = f"max-age={maxAge}"
186 if includeSubDomains:
187 conf.security.strict_transport_security += "; includeSubDomains"
188 if preload:
189 conf.security.strict_transport_security += "; preload"
192def setXFrameOptions(action: str, uri: t.Optional[str] = None) -> None:
193 """
194 Sets X-Frame-Options to prevent click-jacking attacks.
195 :param action: off | deny | sameorigin | allow-from
196 :param uri: URL to whitelist
197 """
198 if action == "off":
199 conf.security.x_frame_options = None
200 elif action in ["deny", "sameorigin"]:
201 conf.security.x_frame_options = (action, None)
202 elif action == "allow-from":
203 if uri is None or not (uri.lower().startswith("https://") or uri.lower().startswith("http://")):
204 raise ValueError("If action is allow-from, an uri MUST be given and start with http(s)://")
205 conf.security.x_frame_options = (action, uri)
208def setXXssProtection(enable: t.Optional[bool]) -> None:
209 """
210 Sets X-XSS-Protection header. If set, mode will always be block.
211 :param enable: Enable the protection or not. Set to None to drop this header
212 """
213 if enable is True or enable is False or enable is None:
214 conf.security.x_xss_protection = enable
215 else:
216 raise ValueError("enable must be exactly one of None | True | False")
219def setXContentTypeNoSniff(enable: bool) -> None:
220 """
221 Sets X-Content-Type-Options if enable is true, otherwise no header is emited.
222 :param enable: Enable emitting this header or not
223 """
224 if enable is True or enable is False:
225 conf.security.x_content_type_options = enable
226 else:
227 raise ValueError("enable must be one of True | False")
230def setXPermittedCrossDomainPolicies(value: str) -> None:
231 if value not in [None, "none", "master-only", "by-content-type", "all"]:
232 raise ValueError("value [None, \"none\", \"master-only\", \"by-content-type\", \"all\"]")
233 conf.security.x_permitted_cross_domain_policies = value
236# Valid values for the referrer-header as per https://www.w3.org/TR/referrer-policy/#referrer-policies
237validReferrerPolicies = [
238 "no-referrer",
239 "no-referrer-when-downgrade",
240 "origin",
241 "origin-when-cross-origin",
242 "same-origin",
243 "strict-origin",
244 "strict-origin-when-cross-origin",
245 "unsafe-url"
246]
249def setReferrerPolicy(policy: str): # fixme: replace str with literal[validreferrerpolicies] when py3.8 gets supported - This is not how Literal works... We can use a Enum for this.
250 """
251 :param policy: The referrer policy to send
252 """
253 assert policy in validReferrerPolicies, f"Policy must be one of {validReferrerPolicies}"
254 conf.security.referrer_policy = policy
257def _rebuildPermissionHeaderCache() -> None:
258 """
259 Rebuilds the internal conf.security.permissions_policy["_headerCache"] string, ie. it constructs
260 the actual header string that's being emitted to the clients.
261 """
262 conf.security.permissions_policy["_headerCache"] = ", ".join([
263 "%s=(%s)" % (k, " ".join([("\"%s\"" % x if x != "self" else x) for x in v]))
264 for k, v in conf.security.permissions_policy.items() if k != "_headerCache"
265 ])
268def setPermissionPolicyDirective(directive: str, allowList: t.Optional[list[str]]) -> None:
269 """
270 Set the permission policy.
271 :param directive: The directive to set.
272 Must be one of https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Feature-Policy#directives
273 :param allowList:
274 The list of allowed origins. Use "self" to allow the current domain.
275 Empty list means the feature will be disabled by the browser (it's not accessible by javascript)
276 """
277 conf.security.permissions_policy[directive] = allowList
280def setCrossOriginIsolation(coep: bool, coop: str, corp: str) -> None:
281 """
282 Configures the cross origin isolation header that ViUR may emit. This is necessary to enable features like
283 SharedArrayBuffer. See https://web.dev/coop-coep for more information.
285 :param coep: If set True, we'll emit Cross-Origin-Embedder-Policy:
286 - require-corp
287 :param coop: The value for the Cross-Origin-Opener-Policy header. Valid values are
288 - same-origin
289 - same-origin-allow-popups
290 - unsafe-none
291 :param corp: The value for the Cross-Origin-Resource-Policy header. Valid values are
292 - same-site
293 - same-origin
294 - cross-origin
295 """
296 assert coop in ["same-origin", "same-origin-allow-popups", "unsafe-none"], "Invalid value for the COOP Header"
297 assert corp in ["same-site", "same-origin", "cross-origin"], "Invalid value for the CORP Header"
298 conf.security.enable_coep = bool(coep)
299 conf.security.enable_coop = coop
300 conf.security.enable_corp = corp