Show More
@@ -0,0 +1,4 b'' | |||||
|
1 | # URI for the CSP Report. Included here to prevent a cyclic dependency. | |||
|
2 | # csp_report_uri is needed both by the BaseHandler (for setting the report-uri) | |||
|
3 | # and by the CSPReportHandler (which depends on the BaseHandler). | |||
|
4 | csp_report_uri = r"/api/security/csp-report" |
@@ -0,0 +1,23 b'' | |||||
|
1 | """Tornado handlers for security logging.""" | |||
|
2 | ||||
|
3 | # Copyright (c) IPython Development Team. | |||
|
4 | # Distributed under the terms of the Modified BSD License. | |||
|
5 | ||||
|
6 | from tornado import gen, web | |||
|
7 | ||||
|
8 | from ...base.handlers import IPythonHandler, json_errors | |||
|
9 | from . import csp_report_uri | |||
|
10 | ||||
|
11 | class CSPReportHandler(IPythonHandler): | |||
|
12 | '''Accepts a content security policy violation report''' | |||
|
13 | @web.authenticated | |||
|
14 | @json_errors | |||
|
15 | def post(self): | |||
|
16 | '''Log a content security policy violation report''' | |||
|
17 | csp_report = self.get_json_body() | |||
|
18 | self.log.warn("Content security violation: %s", | |||
|
19 | self.request.body.decode('utf8', 'replace')) | |||
|
20 | ||||
|
21 | default_handlers = [ | |||
|
22 | (csp_report_uri, CSPReportHandler) | |||
|
23 | ] |
@@ -32,6 +32,8 b' from IPython.utils.path import filefind' | |||||
32 | from IPython.utils.py3compat import string_types |
|
32 | from IPython.utils.py3compat import string_types | |
33 | from IPython.html.utils import is_hidden, url_path_join, url_escape |
|
33 | from IPython.html.utils import is_hidden, url_path_join, url_escape | |
34 |
|
34 | |||
|
35 | from IPython.html.services.security import csp_report_uri | |||
|
36 | ||||
35 | #----------------------------------------------------------------------------- |
|
37 | #----------------------------------------------------------------------------- | |
36 | # Top-level handlers |
|
38 | # Top-level handlers | |
37 | #----------------------------------------------------------------------------- |
|
39 | #----------------------------------------------------------------------------- | |
@@ -45,17 +47,22 b' class AuthenticatedHandler(web.RequestHandler):' | |||||
45 | def set_default_headers(self): |
|
47 | def set_default_headers(self): | |
46 | headers = self.settings.get('headers', {}) |
|
48 | headers = self.settings.get('headers', {}) | |
47 |
|
49 | |||
48 |
if " |
|
50 | if "Content-Security-Policy" not in headers: | |
49 | headers["X-Frame-Options"] = "SAMEORIGIN" |
|
51 | headers["Content-Security-Policy"] = ( | |
|
52 | "frame-ancestors 'self'; " | |||
|
53 | # Make sure the report-uri is relative to the base_url | |||
|
54 | "report-uri " + url_path_join(self.base_url, csp_report_uri) + ";" | |||
|
55 | ) | |||
50 |
|
56 | |||
|
57 | # Allow for overriding headers | |||
51 | for header_name,value in headers.items() : |
|
58 | for header_name,value in headers.items() : | |
52 | try: |
|
59 | try: | |
53 | self.set_header(header_name, value) |
|
60 | self.set_header(header_name, value) | |
54 | except Exception: |
|
61 | except Exception as e: | |
55 | # tornado raise Exception (not a subclass) |
|
62 | # tornado raise Exception (not a subclass) | |
56 | # if method is unsupported (websocket and Access-Control-Allow-Origin |
|
63 | # if method is unsupported (websocket and Access-Control-Allow-Origin | |
57 | # for example, so just ignore) |
|
64 | # for example, so just ignore) | |
58 |
|
|
65 | self.log.debug(e) | |
59 |
|
66 | |||
60 | def clear_login_cookie(self): |
|
67 | def clear_login_cookie(self): | |
61 | self.clear_cookie(self.cookie_name) |
|
68 | self.clear_cookie(self.cookie_name) |
@@ -225,7 +225,7 b' class NotebookWebApplication(web.Application):' | |||||
225 | handlers.extend(load_handlers('services.sessions.handlers')) |
|
225 | handlers.extend(load_handlers('services.sessions.handlers')) | |
226 | handlers.extend(load_handlers('services.nbconvert.handlers')) |
|
226 | handlers.extend(load_handlers('services.nbconvert.handlers')) | |
227 | handlers.extend(load_handlers('services.kernelspecs.handlers')) |
|
227 | handlers.extend(load_handlers('services.kernelspecs.handlers')) | |
228 |
|
228 | handlers.extend(load_handlers('services.security.handlers')) | ||
229 | handlers.append( |
|
229 | handlers.append( | |
230 | (r"/nbextensions/(.*)", FileFindHandler, { |
|
230 | (r"/nbextensions/(.*)", FileFindHandler, { | |
231 | 'path': settings['nbextensions_path'], |
|
231 | 'path': settings['nbextensions_path'], |
@@ -65,7 +65,10 b' class KernelAPITest(NotebookTestBase):' | |||||
65 | self.assertEqual(r.status_code, 201) |
|
65 | self.assertEqual(r.status_code, 201) | |
66 | self.assertIsInstance(kern1, dict) |
|
66 | self.assertIsInstance(kern1, dict) | |
67 |
|
67 | |||
68 |
self.assertEqual(r.headers[' |
|
68 | self.assertEqual(r.headers['Content-Security-Policy'], ( | |
|
69 | "frame-ancestors 'self'; " | |||
|
70 | "report-uri /api/security/csp-report;" | |||
|
71 | )) | |||
69 |
|
72 | |||
70 | def test_main_kernel_handler(self): |
|
73 | def test_main_kernel_handler(self): | |
71 | # POST request |
|
74 | # POST request | |
@@ -75,7 +78,10 b' class KernelAPITest(NotebookTestBase):' | |||||
75 | self.assertEqual(r.status_code, 201) |
|
78 | self.assertEqual(r.status_code, 201) | |
76 | self.assertIsInstance(kern1, dict) |
|
79 | self.assertIsInstance(kern1, dict) | |
77 |
|
80 | |||
78 |
self.assertEqual(r.headers[' |
|
81 | self.assertEqual(r.headers['Content-Security-Policy'], ( | |
|
82 | "frame-ancestors 'self'; " | |||
|
83 | "report-uri /api/security/csp-report;" | |||
|
84 | )) | |||
79 |
|
85 | |||
80 | # GET request |
|
86 | # GET request | |
81 | r = self.kern_api.list() |
|
87 | r = self.kern_api.list() |
@@ -180,16 +180,42 b' Backwards incompatible changes' | |||||
180 |
|
180 | |||
181 | .. DO NOT EDIT THIS LINE BEFORE RELEASE. INCOMPAT INSERTION POINT. |
|
181 | .. DO NOT EDIT THIS LINE BEFORE RELEASE. INCOMPAT INSERTION POINT. | |
182 |
|
182 | |||
183 | IFrame embedding |
|
183 | Content Security Policy | |
184 | ```````````````` |
|
184 | ``````````````````````` | |
185 |
|
185 | |||
186 | The IPython Notebook and its APIs by default will only be allowed to be |
|
186 | The Content Security Policy is a web standard for adding a layer of security to | |
187 | embedded in an iframe on the same origin. |
|
187 | detect and mitigate certain classes of attacks, including Cross Site Scripting | |
|
188 | (XSS) and data injection attacks. This was introduced into the notebook to | |||
|
189 | ensure that the IPython Notebook and its APIs (by default) can only be embedded | |||
|
190 | in an iframe on the same origin. | |||
188 |
|
191 | |||
189 | To override this, set ``headers[X-Frame-Options]`` to one of |
|
192 | Override ``headers['Content-Security-Policy']`` within your notebook | |
|
193 | configuration to extend for alternate domains and security settings.:: | |||
190 |
|
|
194 | ||
191 | * DENY |
|
195 | c.NotebookApp.tornado_settings = { | |
192 | * SAMEORIGIN |
|
196 | 'headers': { | |
193 | * ALLOW-FROM uri |
|
197 | 'Content-Security-Policy': "frame-ancestors 'self'" | |
|
198 | } | |||
|
199 | } | |||
194 |
|
|
200 | ||
195 | See `Mozilla's guide to X-Frame-Options <https://developer.mozilla.org/en-US/docs/Web/HTTP/X-Frame-Options>`_ for more examples. |
|
201 | Example policies:: | |
|
202 | ||||
|
203 | Content-Security-Policy: default-src 'self' https://*.jupyter.org | |||
|
204 | ||||
|
205 | Matches embeddings on any subdomain of jupyter.org, so long as they are served | |||
|
206 | over SSL. | |||
|
207 | ||||
|
208 | There is a `report-uri <https://developer.mozilla.org/en-US/docs/Web/Security/CSP/CSP_policy_directives#report-uri>`_ endpoint available for logging CSP violations, located at | |||
|
209 | ``/api/security/csp-report``. To use it, set ``report-uri`` as part of the CSP:: | |||
|
210 | ||||
|
211 | c.NotebookApp.tornado_settings = { | |||
|
212 | 'headers': { | |||
|
213 | 'Content-Security-Policy': "frame-ancestors 'self'; report-uri /api/security/csp-report" | |||
|
214 | } | |||
|
215 | } | |||
|
216 | ||||
|
217 | It simply provides the CSP report as a warning in IPython's logs. The default | |||
|
218 | CSP sets this report-uri relative to the ``base_url`` (not shown above). | |||
|
219 | ||||
|
220 | For a more thorough and accurate guide on Content Security Policies, check out | |||
|
221 | `MDN's Using Content Security Policy <https://developer.mozilla.org/en-US/docs/Web/Security/CSP/Using_Content_Security_Policy>`_ for more examples. |
General Comments 0
You need to be logged in to leave comments.
Login now