##// END OF EJS Templates
Merge pull request #6094 from minrk/sign-profile...
Thomas Kluyver -
r17169:ca79fd46 merge
parent child Browse files
Show More
@@ -1,312 +1,319 b''
1 """Functions for signing notebooks"""
1 """Functions for signing notebooks"""
2 #-----------------------------------------------------------------------------
2 #-----------------------------------------------------------------------------
3 # Copyright (C) 2014, The IPython Development Team
3 # Copyright (C) 2014, The IPython Development Team
4 #
4 #
5 # Distributed under the terms of the BSD License. The full license is in
5 # Distributed under the terms of the BSD License. The full license is in
6 # the file COPYING, distributed as part of this software.
6 # the file COPYING, distributed as part of this software.
7 #-----------------------------------------------------------------------------
7 #-----------------------------------------------------------------------------
8
8
9 #-----------------------------------------------------------------------------
9 #-----------------------------------------------------------------------------
10 # Imports
10 # Imports
11 #-----------------------------------------------------------------------------
11 #-----------------------------------------------------------------------------
12
12
13 import base64
13 import base64
14 from contextlib import contextmanager
14 from contextlib import contextmanager
15 import hashlib
15 import hashlib
16 from hmac import HMAC
16 from hmac import HMAC
17 import io
17 import io
18 import os
18 import os
19
19
20 from IPython.utils.py3compat import string_types, unicode_type, cast_bytes
20 from IPython.utils.py3compat import string_types, unicode_type, cast_bytes
21 from IPython.utils.traitlets import Instance, Bytes, Enum, Any, Unicode, Bool
21 from IPython.utils.traitlets import Instance, Bytes, Enum, Any, Unicode, Bool
22 from IPython.config import LoggingConfigurable, MultipleInstanceError
22 from IPython.config import LoggingConfigurable, MultipleInstanceError
23 from IPython.core.application import BaseIPythonApplication, base_flags
23 from IPython.core.application import BaseIPythonApplication, base_flags
24
24
25 from .current import read, write
25 from .current import read, write
26
26
27 #-----------------------------------------------------------------------------
27 #-----------------------------------------------------------------------------
28 # Code
28 # Code
29 #-----------------------------------------------------------------------------
29 #-----------------------------------------------------------------------------
30 try:
30 try:
31 # Python 3
31 # Python 3
32 algorithms = hashlib.algorithms_guaranteed
32 algorithms = hashlib.algorithms_guaranteed
33 except AttributeError:
33 except AttributeError:
34 algorithms = hashlib.algorithms
34 algorithms = hashlib.algorithms
35
35
36 def yield_everything(obj):
36 def yield_everything(obj):
37 """Yield every item in a container as bytes
37 """Yield every item in a container as bytes
38
38
39 Allows any JSONable object to be passed to an HMAC digester
39 Allows any JSONable object to be passed to an HMAC digester
40 without having to serialize the whole thing.
40 without having to serialize the whole thing.
41 """
41 """
42 if isinstance(obj, dict):
42 if isinstance(obj, dict):
43 for key in sorted(obj):
43 for key in sorted(obj):
44 value = obj[key]
44 value = obj[key]
45 yield cast_bytes(key)
45 yield cast_bytes(key)
46 for b in yield_everything(value):
46 for b in yield_everything(value):
47 yield b
47 yield b
48 elif isinstance(obj, (list, tuple)):
48 elif isinstance(obj, (list, tuple)):
49 for element in obj:
49 for element in obj:
50 for b in yield_everything(element):
50 for b in yield_everything(element):
51 yield b
51 yield b
52 elif isinstance(obj, unicode_type):
52 elif isinstance(obj, unicode_type):
53 yield obj.encode('utf8')
53 yield obj.encode('utf8')
54 else:
54 else:
55 yield unicode_type(obj).encode('utf8')
55 yield unicode_type(obj).encode('utf8')
56
56
57
57
58 @contextmanager
58 @contextmanager
59 def signature_removed(nb):
59 def signature_removed(nb):
60 """Context manager for operating on a notebook with its signature removed
60 """Context manager for operating on a notebook with its signature removed
61
61
62 Used for excluding the previous signature when computing a notebook's signature.
62 Used for excluding the previous signature when computing a notebook's signature.
63 """
63 """
64 save_signature = nb['metadata'].pop('signature', None)
64 save_signature = nb['metadata'].pop('signature', None)
65 try:
65 try:
66 yield
66 yield
67 finally:
67 finally:
68 if save_signature is not None:
68 if save_signature is not None:
69 nb['metadata']['signature'] = save_signature
69 nb['metadata']['signature'] = save_signature
70
70
71
71
72 class NotebookNotary(LoggingConfigurable):
72 class NotebookNotary(LoggingConfigurable):
73 """A class for computing and verifying notebook signatures."""
73 """A class for computing and verifying notebook signatures."""
74
74
75 profile_dir = Instance("IPython.core.profiledir.ProfileDir")
75 profile_dir = Instance("IPython.core.profiledir.ProfileDir")
76 def _profile_dir_default(self):
76 def _profile_dir_default(self):
77 from IPython.core.application import BaseIPythonApplication
77 from IPython.core.application import BaseIPythonApplication
78 app = None
78 app = None
79 try:
79 try:
80 if BaseIPythonApplication.initialized():
80 if BaseIPythonApplication.initialized():
81 app = BaseIPythonApplication.instance()
81 app = BaseIPythonApplication.instance()
82 except MultipleInstanceError:
82 except MultipleInstanceError:
83 pass
83 pass
84 if app is None:
84 if app is None:
85 # create an app, without the global instance
85 # create an app, without the global instance
86 app = BaseIPythonApplication()
86 app = BaseIPythonApplication()
87 app.initialize(argv=[])
87 app.initialize(argv=[])
88 return app.profile_dir
88 return app.profile_dir
89
89
90 algorithm = Enum(algorithms, default_value='sha256', config=True,
90 algorithm = Enum(algorithms, default_value='sha256', config=True,
91 help="""The hashing algorithm used to sign notebooks."""
91 help="""The hashing algorithm used to sign notebooks."""
92 )
92 )
93 def _algorithm_changed(self, name, old, new):
93 def _algorithm_changed(self, name, old, new):
94 self.digestmod = getattr(hashlib, self.algorithm)
94 self.digestmod = getattr(hashlib, self.algorithm)
95
95
96 digestmod = Any()
96 digestmod = Any()
97 def _digestmod_default(self):
97 def _digestmod_default(self):
98 return getattr(hashlib, self.algorithm)
98 return getattr(hashlib, self.algorithm)
99
99
100 secret_file = Unicode(config=True,
100 secret_file = Unicode(config=True,
101 help="""The file where the secret key is stored."""
101 help="""The file where the secret key is stored."""
102 )
102 )
103 def _secret_file_default(self):
103 def _secret_file_default(self):
104 if self.profile_dir is None:
104 if self.profile_dir is None:
105 return ''
105 return ''
106 return os.path.join(self.profile_dir.security_dir, 'notebook_secret')
106 return os.path.join(self.profile_dir.security_dir, 'notebook_secret')
107
107
108 secret = Bytes(config=True,
108 secret = Bytes(config=True,
109 help="""The secret key with which notebooks are signed."""
109 help="""The secret key with which notebooks are signed."""
110 )
110 )
111 def _secret_default(self):
111 def _secret_default(self):
112 # note : this assumes an Application is running
112 # note : this assumes an Application is running
113 if os.path.exists(self.secret_file):
113 if os.path.exists(self.secret_file):
114 with io.open(self.secret_file, 'rb') as f:
114 with io.open(self.secret_file, 'rb') as f:
115 return f.read()
115 return f.read()
116 else:
116 else:
117 secret = base64.encodestring(os.urandom(1024))
117 secret = base64.encodestring(os.urandom(1024))
118 self._write_secret_file(secret)
118 self._write_secret_file(secret)
119 return secret
119 return secret
120
120
121 def _write_secret_file(self, secret):
121 def _write_secret_file(self, secret):
122 """write my secret to my secret_file"""
122 """write my secret to my secret_file"""
123 self.log.info("Writing notebook-signing key to %s", self.secret_file)
123 self.log.info("Writing notebook-signing key to %s", self.secret_file)
124 with io.open(self.secret_file, 'wb') as f:
124 with io.open(self.secret_file, 'wb') as f:
125 f.write(secret)
125 f.write(secret)
126 try:
126 try:
127 os.chmod(self.secret_file, 0o600)
127 os.chmod(self.secret_file, 0o600)
128 except OSError:
128 except OSError:
129 self.log.warn(
129 self.log.warn(
130 "Could not set permissions on %s",
130 "Could not set permissions on %s",
131 self.secret_file
131 self.secret_file
132 )
132 )
133 return secret
133 return secret
134
134
135 def compute_signature(self, nb):
135 def compute_signature(self, nb):
136 """Compute a notebook's signature
136 """Compute a notebook's signature
137
137
138 by hashing the entire contents of the notebook via HMAC digest.
138 by hashing the entire contents of the notebook via HMAC digest.
139 """
139 """
140 hmac = HMAC(self.secret, digestmod=self.digestmod)
140 hmac = HMAC(self.secret, digestmod=self.digestmod)
141 # don't include the previous hash in the content to hash
141 # don't include the previous hash in the content to hash
142 with signature_removed(nb):
142 with signature_removed(nb):
143 # sign the whole thing
143 # sign the whole thing
144 for b in yield_everything(nb):
144 for b in yield_everything(nb):
145 hmac.update(b)
145 hmac.update(b)
146
146
147 return hmac.hexdigest()
147 return hmac.hexdigest()
148
148
149 def check_signature(self, nb):
149 def check_signature(self, nb):
150 """Check a notebook's stored signature
150 """Check a notebook's stored signature
151
151
152 If a signature is stored in the notebook's metadata,
152 If a signature is stored in the notebook's metadata,
153 a new signature is computed and compared with the stored value.
153 a new signature is computed and compared with the stored value.
154
154
155 Returns True if the signature is found and matches, False otherwise.
155 Returns True if the signature is found and matches, False otherwise.
156
156
157 The following conditions must all be met for a notebook to be trusted:
157 The following conditions must all be met for a notebook to be trusted:
158 - a signature is stored in the form 'scheme:hexdigest'
158 - a signature is stored in the form 'scheme:hexdigest'
159 - the stored scheme matches the requested scheme
159 - the stored scheme matches the requested scheme
160 - the requested scheme is available from hashlib
160 - the requested scheme is available from hashlib
161 - the computed hash from notebook_signature matches the stored hash
161 - the computed hash from notebook_signature matches the stored hash
162 """
162 """
163 stored_signature = nb['metadata'].get('signature', None)
163 stored_signature = nb['metadata'].get('signature', None)
164 if not stored_signature \
164 if not stored_signature \
165 or not isinstance(stored_signature, string_types) \
165 or not isinstance(stored_signature, string_types) \
166 or ':' not in stored_signature:
166 or ':' not in stored_signature:
167 return False
167 return False
168 stored_algo, sig = stored_signature.split(':', 1)
168 stored_algo, sig = stored_signature.split(':', 1)
169 if self.algorithm != stored_algo:
169 if self.algorithm != stored_algo:
170 return False
170 return False
171 my_signature = self.compute_signature(nb)
171 my_signature = self.compute_signature(nb)
172 return my_signature == sig
172 return my_signature == sig
173
173
174 def sign(self, nb):
174 def sign(self, nb):
175 """Sign a notebook, indicating that its output is trusted
175 """Sign a notebook, indicating that its output is trusted
176
176
177 stores 'algo:hmac-hexdigest' in notebook.metadata.signature
177 stores 'algo:hmac-hexdigest' in notebook.metadata.signature
178
178
179 e.g. 'sha256:deadbeef123...'
179 e.g. 'sha256:deadbeef123...'
180 """
180 """
181 signature = self.compute_signature(nb)
181 signature = self.compute_signature(nb)
182 nb['metadata']['signature'] = "%s:%s" % (self.algorithm, signature)
182 nb['metadata']['signature'] = "%s:%s" % (self.algorithm, signature)
183
183
184 def mark_cells(self, nb, trusted):
184 def mark_cells(self, nb, trusted):
185 """Mark cells as trusted if the notebook's signature can be verified
185 """Mark cells as trusted if the notebook's signature can be verified
186
186
187 Sets ``cell.trusted = True | False`` on all code cells,
187 Sets ``cell.trusted = True | False`` on all code cells,
188 depending on whether the stored signature can be verified.
188 depending on whether the stored signature can be verified.
189
189
190 This function is the inverse of check_cells
190 This function is the inverse of check_cells
191 """
191 """
192 if not nb['worksheets']:
192 if not nb['worksheets']:
193 # nothing to mark if there are no cells
193 # nothing to mark if there are no cells
194 return
194 return
195 for cell in nb['worksheets'][0]['cells']:
195 for cell in nb['worksheets'][0]['cells']:
196 if cell['cell_type'] == 'code':
196 if cell['cell_type'] == 'code':
197 cell['trusted'] = trusted
197 cell['trusted'] = trusted
198
198
199 def _check_cell(self, cell):
199 def _check_cell(self, cell):
200 """Do we trust an individual cell?
200 """Do we trust an individual cell?
201
201
202 Return True if:
202 Return True if:
203
203
204 - cell is explicitly trusted
204 - cell is explicitly trusted
205 - cell has no potentially unsafe rich output
205 - cell has no potentially unsafe rich output
206
206
207 If a cell has no output, or only simple print statements,
207 If a cell has no output, or only simple print statements,
208 it will always be trusted.
208 it will always be trusted.
209 """
209 """
210 # explicitly trusted
210 # explicitly trusted
211 if cell.pop("trusted", False):
211 if cell.pop("trusted", False):
212 return True
212 return True
213
213
214 # explicitly safe output
214 # explicitly safe output
215 safe = {
215 safe = {
216 'text/plain', 'image/png', 'image/jpeg',
216 'text/plain', 'image/png', 'image/jpeg',
217 'text', 'png', 'jpg', # v3-style short keys
217 'text', 'png', 'jpg', # v3-style short keys
218 }
218 }
219
219
220 for output in cell['outputs']:
220 for output in cell['outputs']:
221 output_type = output['output_type']
221 output_type = output['output_type']
222 if output_type in ('pyout', 'display_data'):
222 if output_type in ('pyout', 'display_data'):
223 # if there are any data keys not in the safe whitelist
223 # if there are any data keys not in the safe whitelist
224 output_keys = set(output).difference({"output_type", "prompt_number", "metadata"})
224 output_keys = set(output).difference({"output_type", "prompt_number", "metadata"})
225 if output_keys.difference(safe):
225 if output_keys.difference(safe):
226 return False
226 return False
227
227
228 return True
228 return True
229
229
230 def check_cells(self, nb):
230 def check_cells(self, nb):
231 """Return whether all code cells are trusted
231 """Return whether all code cells are trusted
232
232
233 If there are no code cells, return True.
233 If there are no code cells, return True.
234
234
235 This function is the inverse of mark_cells.
235 This function is the inverse of mark_cells.
236 """
236 """
237 if not nb['worksheets']:
237 if not nb['worksheets']:
238 return True
238 return True
239 trusted = True
239 trusted = True
240 for cell in nb['worksheets'][0]['cells']:
240 for cell in nb['worksheets'][0]['cells']:
241 if cell['cell_type'] != 'code':
241 if cell['cell_type'] != 'code':
242 continue
242 continue
243 # only distrust a cell if it actually has some output to distrust
243 # only distrust a cell if it actually has some output to distrust
244 if not self._check_cell(cell):
244 if not self._check_cell(cell):
245 trusted = False
245 trusted = False
246 return trusted
246 return trusted
247
247
248
248
249 trust_flags = {
249 trust_flags = {
250 'reset' : (
250 'reset' : (
251 {'TrustNotebookApp' : { 'reset' : True}},
251 {'TrustNotebookApp' : { 'reset' : True}},
252 """Generate a new key for notebook signature.
252 """Generate a new key for notebook signature.
253 All previously signed notebooks will become untrusted.
253 All previously signed notebooks will become untrusted.
254 """
254 """
255 ),
255 ),
256 }
256 }
257 trust_flags.update(base_flags)
257 trust_flags.update(base_flags)
258 trust_flags.pop('init')
258 trust_flags.pop('init')
259
259
260
260
261 class TrustNotebookApp(BaseIPythonApplication):
261 class TrustNotebookApp(BaseIPythonApplication):
262
262
263 description="""Sign one or more IPython notebooks with your key,
263 description="""Sign one or more IPython notebooks with your key,
264 to trust their dynamic (HTML, Javascript) output.
264 to trust their dynamic (HTML, Javascript) output.
265
265
266 Trusting a notebook only applies to the current IPython profile.
267 To trust a notebook for use with a profile other than default,
268 add `--profile [profile name]`.
269
266 Otherwise, you will have to re-execute the notebook to see output.
270 Otherwise, you will have to re-execute the notebook to see output.
267 """
271 """
268
272
269 examples = """ipython trust mynotebook.ipynb and_this_one.ipynb"""
273 examples = """
274 ipython trust mynotebook.ipynb and_this_one.ipynb
275 ipython trust --profile myprofile mynotebook.ipynb
276 """
270
277
271 flags = trust_flags
278 flags = trust_flags
272
279
273 reset = Bool(False, config=True,
280 reset = Bool(False, config=True,
274 help="""If True, generate a new key for notebook signature.
281 help="""If True, generate a new key for notebook signature.
275 After reset, all previously signed notebooks will become untrusted.
282 After reset, all previously signed notebooks will become untrusted.
276 """
283 """
277 )
284 )
278
285
279 notary = Instance(NotebookNotary)
286 notary = Instance(NotebookNotary)
280 def _notary_default(self):
287 def _notary_default(self):
281 return NotebookNotary(parent=self, profile_dir=self.profile_dir)
288 return NotebookNotary(parent=self, profile_dir=self.profile_dir)
282
289
283 def sign_notebook(self, notebook_path):
290 def sign_notebook(self, notebook_path):
284 if not os.path.exists(notebook_path):
291 if not os.path.exists(notebook_path):
285 self.log.error("Notebook missing: %s" % notebook_path)
292 self.log.error("Notebook missing: %s" % notebook_path)
286 self.exit(1)
293 self.exit(1)
287 with io.open(notebook_path, encoding='utf8') as f:
294 with io.open(notebook_path, encoding='utf8') as f:
288 nb = read(f, 'json')
295 nb = read(f, 'json')
289 if self.notary.check_signature(nb):
296 if self.notary.check_signature(nb):
290 print("Notebook already signed: %s" % notebook_path)
297 print("Notebook already signed: %s" % notebook_path)
291 else:
298 else:
292 print("Signing notebook: %s" % notebook_path)
299 print("Signing notebook: %s" % notebook_path)
293 self.notary.sign(nb)
300 self.notary.sign(nb)
294 with io.open(notebook_path, 'w', encoding='utf8') as f:
301 with io.open(notebook_path, 'w', encoding='utf8') as f:
295 write(nb, f, 'json')
302 write(nb, f, 'json')
296
303
297 def generate_new_key(self):
304 def generate_new_key(self):
298 """Generate a new notebook signature key"""
305 """Generate a new notebook signature key"""
299 print("Generating new notebook key: %s" % self.notary.secret_file)
306 print("Generating new notebook key: %s" % self.notary.secret_file)
300 self.notary._write_secret_file(os.urandom(1024))
307 self.notary._write_secret_file(os.urandom(1024))
301
308
302 def start(self):
309 def start(self):
303 if self.reset:
310 if self.reset:
304 self.generate_new_key()
311 self.generate_new_key()
305 return
312 return
306 if not self.extra_args:
313 if not self.extra_args:
307 self.log.critical("Specify at least one notebook to sign.")
314 self.log.critical("Specify at least one notebook to sign.")
308 self.exit(1)
315 self.exit(1)
309
316
310 for notebook_path in self.extra_args:
317 for notebook_path in self.extra_args:
311 self.sign_notebook(notebook_path)
318 self.sign_notebook(notebook_path)
312
319
@@ -1,148 +1,154 b''
1 .. _notebook_security:
1 .. _notebook_security:
2
2
3 Security in IPython notebooks
3 Security in IPython notebooks
4 =============================
4 =============================
5
5
6 As IPython notebooks become more popular for sharing and collaboration,
6 As IPython notebooks become more popular for sharing and collaboration,
7 the potential for malicious people to attempt to exploit the notebook
7 the potential for malicious people to attempt to exploit the notebook
8 for their nefarious purposes increases. IPython 2.0 introduces a
8 for their nefarious purposes increases. IPython 2.0 introduces a
9 security model to prevent execution of untrusted code without explicit
9 security model to prevent execution of untrusted code without explicit
10 user input.
10 user input.
11
11
12 The problem
12 The problem
13 -----------
13 -----------
14
14
15 The whole point of IPython is arbitrary code execution. We have no
15 The whole point of IPython is arbitrary code execution. We have no
16 desire to limit what can be done with a notebook, which would negatively
16 desire to limit what can be done with a notebook, which would negatively
17 impact its utility.
17 impact its utility.
18
18
19 Unlike other programs, an IPython notebook document includes output.
19 Unlike other programs, an IPython notebook document includes output.
20 Unlike other documents, that output exists in a context that can execute
20 Unlike other documents, that output exists in a context that can execute
21 code (via Javascript).
21 code (via Javascript).
22
22
23 The security problem we need to solve is that no code should execute
23 The security problem we need to solve is that no code should execute
24 just because a user has **opened** a notebook that **they did not
24 just because a user has **opened** a notebook that **they did not
25 write**. Like any other program, once a user decides to execute code in
25 write**. Like any other program, once a user decides to execute code in
26 a notebook, it is considered trusted, and should be allowed to do
26 a notebook, it is considered trusted, and should be allowed to do
27 anything.
27 anything.
28
28
29 Our security model
29 Our security model
30 ------------------
30 ------------------
31
31
32 - Untrusted HTML is always sanitized
32 - Untrusted HTML is always sanitized
33 - Untrusted Javascript is never executed
33 - Untrusted Javascript is never executed
34 - HTML and Javascript in Markdown cells are never trusted
34 - HTML and Javascript in Markdown cells are never trusted
35 - **Outputs** generated by the user are trusted
35 - **Outputs** generated by the user are trusted
36 - Any other HTML or Javascript (in Markdown cells, output generated by
36 - Any other HTML or Javascript (in Markdown cells, output generated by
37 others) is never trusted
37 others) is never trusted
38 - The central question of trust is "Did the current user do this?"
38 - The central question of trust is "Did the current user do this?"
39
39
40 The details of trust
40 The details of trust
41 --------------------
41 --------------------
42
42
43 IPython notebooks store a signature in metadata, which is used to answer
43 IPython notebooks store a signature in metadata, which is used to answer
44 the question "Did the current user do this?"
44 the question "Did the current user do this?"
45
45
46 This signature is a digest of the notebooks contents plus a secret key,
46 This signature is a digest of the notebooks contents plus a secret key,
47 known only to the user. The secret key is a user-only readable file in
47 known only to the user. The secret key is a user-only readable file in
48 the IPython profile's security directory. By default, this is::
48 the IPython profile's security directory. By default, this is::
49
49
50 ~/.ipython/profile_default/security/notebook_secret
50 ~/.ipython/profile_default/security/notebook_secret
51
51
52 .. note::
53
54 The notebook secret being stored in the profile means that
55 loading a notebook in another profile results in it being untrusted,
56 unless you copy or symlink the notebook secret to share it across profiles.
57
52 When a notebook is opened by a user, the server computes a signature
58 When a notebook is opened by a user, the server computes a signature
53 with the user's key, and compares it with the signature stored in the
59 with the user's key, and compares it with the signature stored in the
54 notebook's metadata. If the signature matches, HTML and Javascript
60 notebook's metadata. If the signature matches, HTML and Javascript
55 output in the notebook will be trusted at load, otherwise it will be
61 output in the notebook will be trusted at load, otherwise it will be
56 untrusted.
62 untrusted.
57
63
58 Any output generated during an interactive session is trusted.
64 Any output generated during an interactive session is trusted.
59
65
60 Updating trust
66 Updating trust
61 **************
67 **************
62
68
63 A notebook's trust is updated when the notebook is saved. If there are
69 A notebook's trust is updated when the notebook is saved. If there are
64 any untrusted outputs still in the notebook, the notebook will not be
70 any untrusted outputs still in the notebook, the notebook will not be
65 trusted, and no signature will be stored. If all untrusted outputs have
71 trusted, and no signature will be stored. If all untrusted outputs have
66 been removed (either via ``Clear Output`` or re-execution), then the
72 been removed (either via ``Clear Output`` or re-execution), then the
67 notebook will become trusted.
73 notebook will become trusted.
68
74
69 While trust is updated per output, this is only for the duration of a
75 While trust is updated per output, this is only for the duration of a
70 single session. A notebook file on disk is either trusted or not in its
76 single session. A notebook file on disk is either trusted or not in its
71 entirety.
77 entirety.
72
78
73 Explicit trust
79 Explicit trust
74 **************
80 **************
75
81
76 Sometimes re-executing a notebook to generate trusted output is not an
82 Sometimes re-executing a notebook to generate trusted output is not an
77 option, either because dependencies are unavailable, or it would take a
83 option, either because dependencies are unavailable, or it would take a
78 long time. Users can explicitly trust a notebook in two ways:
84 long time. Users can explicitly trust a notebook in two ways:
79
85
80 - At the command-line, with::
86 - At the command-line, with::
81
87
82 ipython trust /path/to/notebook.ipynb
88 ipython trust /path/to/notebook.ipynb
83
89
84 - After loading the untrusted notebook, with ``File / Trust Notebook``
90 - After loading the untrusted notebook, with ``File / Trust Notebook``
85
91
86 These two methods simply load the notebook, compute a new signature with
92 These two methods simply load the notebook, compute a new signature with
87 the user's key, and then store the newly signed notebook.
93 the user's key, and then store the newly signed notebook.
88
94
89 Reporting security issues
95 Reporting security issues
90 -------------------------
96 -------------------------
91
97
92 If you find a security vulnerability in IPython, either a failure of the
98 If you find a security vulnerability in IPython, either a failure of the
93 code to properly implement the model described here, or a failure of the
99 code to properly implement the model described here, or a failure of the
94 model itself, please report it to security@ipython.org.
100 model itself, please report it to security@ipython.org.
95
101
96 If you prefer to encrypt your security reports,
102 If you prefer to encrypt your security reports,
97 you can use :download:`this PGP public key <ipython_security.asc>`.
103 you can use :download:`this PGP public key <ipython_security.asc>`.
98
104
99 Affected use cases
105 Affected use cases
100 ------------------
106 ------------------
101
107
102 Some use cases that work in IPython 1.0 will become less convenient in
108 Some use cases that work in IPython 1.0 will become less convenient in
103 2.0 as a result of the security changes. We do our best to minimize
109 2.0 as a result of the security changes. We do our best to minimize
104 these annoyance, but security is always at odds with convenience.
110 these annoyance, but security is always at odds with convenience.
105
111
106 Javascript and CSS in Markdown cells
112 Javascript and CSS in Markdown cells
107 ************************************
113 ************************************
108
114
109 While never officially supported, it had become common practice to put
115 While never officially supported, it had become common practice to put
110 hidden Javascript or CSS styling in Markdown cells, so that they would
116 hidden Javascript or CSS styling in Markdown cells, so that they would
111 not be visible on the page. Since Markdown cells are now sanitized (by
117 not be visible on the page. Since Markdown cells are now sanitized (by
112 `Google Caja <https://developers.google.com/caja>`__), all Javascript
118 `Google Caja <https://developers.google.com/caja>`__), all Javascript
113 (including click event handlers, etc.) and CSS will be stripped.
119 (including click event handlers, etc.) and CSS will be stripped.
114
120
115 We plan to provide a mechanism for notebook themes, but in the meantime
121 We plan to provide a mechanism for notebook themes, but in the meantime
116 styling the notebook can only be done via either ``custom.css`` or CSS
122 styling the notebook can only be done via either ``custom.css`` or CSS
117 in HTML output. The latter only have an effect if the notebook is
123 in HTML output. The latter only have an effect if the notebook is
118 trusted, because otherwise the output will be sanitized just like
124 trusted, because otherwise the output will be sanitized just like
119 Markdown.
125 Markdown.
120
126
121 Collaboration
127 Collaboration
122 *************
128 *************
123
129
124 When collaborating on a notebook, people probably want to see the
130 When collaborating on a notebook, people probably want to see the
125 outputs produced by their colleagues' most recent executions. Since each
131 outputs produced by their colleagues' most recent executions. Since each
126 collaborator's key will differ, this will result in each share starting
132 collaborator's key will differ, this will result in each share starting
127 in an untrusted state. There are three basic approaches to this:
133 in an untrusted state. There are three basic approaches to this:
128
134
129 - re-run notebooks when you get them (not always viable)
135 - re-run notebooks when you get them (not always viable)
130 - explicitly trust notebooks via ``ipython trust`` or the notebook menu
136 - explicitly trust notebooks via ``ipython trust`` or the notebook menu
131 (annoying, but easy)
137 (annoying, but easy)
132 - share a notebook secret, and use an IPython profile dedicated to the
138 - share a notebook secret, and use an IPython profile dedicated to the
133 collaboration while working on the project.
139 collaboration while working on the project.
134
140
135 Multiple profiles or machines
141 Multiple profiles or machines
136 *****************************
142 *****************************
137
143
138 Since the notebook secret is stored in a profile directory by default,
144 Since the notebook secret is stored in a profile directory by default,
139 opening a notebook with a different profile or on a different machine
145 opening a notebook with a different profile or on a different machine
140 will result in a different key, and thus be untrusted. The only current
146 will result in a different key, and thus be untrusted. The only current
141 way to address this is by sharing the notebook secret. This can be
147 way to address this is by sharing the notebook secret. This can be
142 facilitated by setting the configurable:
148 facilitated by setting the configurable:
143
149
144 .. sourcecode:: python
150 .. sourcecode:: python
145
151
146 c.NotebookApp.secret_file = "/path/to/notebook_secret"
152 c.NotebookApp.secret_file = "/path/to/notebook_secret"
147
153
148 in each profile, and only sharing the secret once per machine.
154 in each profile, and only sharing the secret once per machine.
General Comments 0
You need to be logged in to leave comments. Login now