Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

document the wire protocol #3756

Merged
merged 8 commits into from
Jul 26, 2013
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
2 changes: 2 additions & 0 deletions IPython/consoleapp.py
Expand Up @@ -265,6 +265,8 @@ def load_connection_file(self):
setattr(self, name, cfg[name])
if 'key' in cfg:
self.config.Session.key = str_to_bytes(cfg['key'])
if 'signature_scheme' in cfg:
self.config.Session.signature_scheme = cfg['signature_scheme']

def init_ssh(self):
"""set up ssh tunnels, if needed."""
Expand Down
23 changes: 20 additions & 3 deletions IPython/kernel/connect.py
Expand Up @@ -50,7 +50,9 @@
#-----------------------------------------------------------------------------

def write_connection_file(fname=None, shell_port=0, iopub_port=0, stdin_port=0, hb_port=0,
control_port=0, ip=LOCALHOST, key=b'', transport='tcp'):
control_port=0, ip=LOCALHOST, key=b'', transport='tcp',
signature_scheme='hmac-sha256',
):
"""Generates a JSON config file, including the selection of random ports.

Parameters
Expand Down Expand Up @@ -78,7 +80,15 @@ def write_connection_file(fname=None, shell_port=0, iopub_port=0, stdin_port=0,
The ip address the kernel will bind to.

key : str, optional
The Session key used for HMAC authentication.
The Session key used for message authentication.

signature_scheme : str, optional
The scheme used for message authentication.
This has the form 'digest-hash', where 'digest'
is the scheme used for digests, and 'hash' is the name of the hash function
used by the digest scheme.
Currently, 'hmac' is the only supported digest scheme,
and 'sha256' is the default hash function.

"""
# default to temporary connector file
Expand Down Expand Up @@ -129,6 +139,7 @@ def write_connection_file(fname=None, shell_port=0, iopub_port=0, stdin_port=0,
cfg['ip'] = ip
cfg['key'] = bytes_to_str(key)
cfg['transport'] = transport
cfg['signature_scheme'] = signature_scheme

with open(fname, 'w') as f:
f.write(json.dumps(cfg, indent=2))
Expand Down Expand Up @@ -380,6 +391,7 @@ class ConnectionFileMixin(HasTraits):
_connection_file_written = Bool(False)

transport = CaselessStrEnum(['tcp', 'ipc'], default_value='tcp', config=True)
signature_scheme = Unicode('')

ip = Unicode(LOCALHOST, config=True,
help="""Set the kernel\'s IP address [default localhost].
Expand Down Expand Up @@ -427,6 +439,7 @@ def get_connection_info(self):
stdin_port=self.stdin_port,
hb_port=self.hb_port,
control_port=self.control_port,
signature_scheme=self.signature_scheme,
)

def cleanup_connection_file(self):
Expand Down Expand Up @@ -463,6 +476,7 @@ def write_connection_file(self):
stdin_port=self.stdin_port, iopub_port=self.iopub_port,
shell_port=self.shell_port, hb_port=self.hb_port,
control_port=self.control_port,
signature_scheme=self.signature_scheme,
)
# write_connection_file also sets default ports:
for name in port_names:
Expand All @@ -479,7 +493,10 @@ def load_connection_file(self):
self.ip = cfg['ip']
for name in port_names:
setattr(self, name, cfg[name])
self.session.key = str_to_bytes(cfg['key'])
if 'key' in cfg:
self.session.key = str_to_bytes(cfg['key'])
if cfg.get('signature_scheme'):
self.session.signature_scheme = cfg['signature_scheme']

#--------------------------------------------------------------------------
# Creating connected sockets
Expand Down
2 changes: 1 addition & 1 deletion IPython/kernel/zmq/displayhook.py
Expand Up @@ -10,7 +10,7 @@
class ZMQDisplayHook(object):
"""A simple displayhook that publishes the object's repr over a ZeroMQ
socket."""
topic=None
topic=b'pyout'

def __init__(self, session, pub_socket):
self.session = session
Expand Down
1 change: 1 addition & 0 deletions IPython/kernel/zmq/iostream.py
Expand Up @@ -44,6 +44,7 @@ def __init__(self, session, pub_socket, name, pipe=True):
self.session = session
self.pub_socket = pub_socket
self.name = name
self.topic = b'stream.' + py3compat.cast_bytes(name)
self.parent_header = {}
self._new_buffer()
self._buffer_lock = threading.Lock()
Expand Down
28 changes: 26 additions & 2 deletions IPython/kernel/zmq/session.py
Expand Up @@ -24,6 +24,7 @@
# Imports
#-----------------------------------------------------------------------------

import hashlib
import hmac
import logging
import os
Expand All @@ -50,7 +51,9 @@
from IPython.utils.jsonutil import extract_dates, squash_dates, date_default
from IPython.utils.py3compat import str_to_bytes, str_to_unicode
from IPython.utils.traitlets import (CBytes, Unicode, Bool, Any, Instance, Set,
DottedObjectName, CUnicode, Dict, Integer)
DottedObjectName, CUnicode, Dict, Integer,
TraitError,
)
from IPython.kernel.zmq.serialize import MAX_ITEMS, MAX_BYTES

#-----------------------------------------------------------------------------
Expand Down Expand Up @@ -308,10 +311,26 @@ def _session_changed(self, name, old, new):
help="""execution key, for extra authentication.""")
def _key_changed(self, name, old, new):
if new:
self.auth = hmac.HMAC(new)
self.auth = hmac.HMAC(new, digestmod=self.digest_mod)
else:
self.auth = None

signature_scheme = Unicode('hmac-sha256', config=True,
help="""The digest scheme used to construct the message signatures.
Must have the form 'hmac-HASH'.""")
def _signature_scheme_changed(self, name, old, new):
if not new.startswith('hmac-'):
raise TraitError("signature_scheme must start with 'hmac-', got %r" % new)
hash_name = new.split('-', 1)[1]
try:
self.digest_mod = getattr(hashlib, hash_name)
except AttributeError:
raise TraitError("hashlib has no such attribute: %s" % hash_name)

digest_mod = Any()
def _digest_mod_default(self):
return hashlib.sha256

auth = Instance(hmac.HMAC)

digest_history = Set()
Expand Down Expand Up @@ -387,6 +406,11 @@ def __init__(self, **kwargs):
key : bytes
The key used to initialize an HMAC signature. If unset, messages
will not be signed or checked.
signature_scheme : str
The message digest scheme. Currently must be of the form 'hmac-HASH',
where 'HASH' is a hashing function available in Python's hashlib.
The default is 'hmac-sha256'.
This is ignored if 'key' is empty.
keyfile : filepath
The file containing a key. If this is set, `key` will be
initialized to the contents of the file.
Expand Down
2 changes: 1 addition & 1 deletion IPython/kernel/zmq/zmqshell.py
Expand Up @@ -61,7 +61,7 @@ class ZMQDisplayPublisher(DisplayPublisher):
session = Instance(Session)
pub_socket = Instance(SocketABC)
parent_header = Dict({})
topic = CBytes(b'displaypub')
topic = CBytes(b'display_data')

def set_parent(self, parent):
"""Set the parent for outbound messages."""
Expand Down
7 changes: 4 additions & 3 deletions IPython/parallel/apps/ipcontrollerapp.py
Expand Up @@ -251,7 +251,7 @@ def load_config_from_json(self):
ecfg = json.loads(f.read())

# json gives unicode, Session.key wants bytes
c.Session.key = ecfg['exec_key'].encode('ascii')
c.Session.key = ecfg['key'].encode('ascii')

xport,ip = ecfg['interface'].split('://')

Expand All @@ -269,7 +269,7 @@ def load_config_from_json(self):
with open(fname) as f:
ccfg = json.loads(f.read())

for key in ('exec_key', 'registration', 'pack', 'unpack'):
for key in ('key', 'registration', 'pack', 'unpack', 'signature_scheme'):
assert ccfg[key] == ecfg[key], "mismatch between engine and client info: %r" % key

xport,addr = ccfg['interface'].split('://')
Expand Down Expand Up @@ -338,10 +338,11 @@ def init_hub(self):
# save to new json config files
f = self.factory
base = {
'exec_key' : f.session.key.decode('ascii'),
'key' : f.session.key.decode('ascii'),
'location' : self.location,
'pack' : f.session.packer,
'unpack' : f.session.unpacker,
'signature_scheme' : f.session.signature_scheme,
}

cdict = {'ssh' : self.ssh_server}
Expand Down
5 changes: 3 additions & 2 deletions IPython/parallel/apps/ipengineapp.py
Expand Up @@ -227,9 +227,10 @@ def load_connector_file(self):
ip = disambiguate_ip_address(ip, location)
d['interface'] = '%s://%s' % (proto, ip)

# DO NOT allow override of basic URLs, serialization, or exec_key
# DO NOT allow override of basic URLs, serialization, or key
# JSON file takes top priority there
config.Session.key = cast_bytes(d['exec_key'])
config.Session.key = cast_bytes(d['key'])
config.Session.signature_scheme = d['signature_scheme']

config.EngineFactory.url = d['interface'] + ':%i' % d['registration']

Expand Down
3 changes: 2 additions & 1 deletion IPython/parallel/client/client.py
Expand Up @@ -462,7 +462,8 @@ def __init__(self, url_file=None, profile=None, profile_dir=None, ipython_dir=No
# configure and construct the session
extra_args['packer'] = cfg['pack']
extra_args['unpacker'] = cfg['unpack']
extra_args['key'] = cast_bytes(cfg['exec_key'])
extra_args['key'] = cast_bytes(cfg['key'])
extra_args['signature_scheme'] = cfg['signature_scheme']

self.session = Session(**extra_args)

Expand Down