Commit a39cd44b authored by David Wilson's avatar David Wilson

core: add auth_id field.

parent a54c96fa
......@@ -260,21 +260,55 @@ Stream Protocol
Once connected, a basic framing protocol is used to communicate between
parent and child:
+--------------------+------+------------------------------------------------------+
| Field | Size | Description |
+====================+======+======================================================+
| ``dst_id`` | 2 | Integer target context ID. |
+--------------------+------+------------------------------------------------------+
| ``src_id`` | 2 | Integer source context ID. |
+--------------------+------+------------------------------------------------------+
| ``handle`` | 4 | Integer target handle in recipient. |
+--------------------+------+------------------------------------------------------+
| ``reply_to`` | 4 | Integer response target ID. |
+--------------------+------+------------------------------------------------------+
| ``length`` | 4 | Message length |
+--------------------+------+------------------------------------------------------+
| ``data`` | n/a | Pickled message data. |
+--------------------+------+------------------------------------------------------+
.. list-table::
:header-rows: 1
:widths: auto
* - Field
- Size
- Description
* - `dst_id`
- 2
- Integer target context ID. :py:class:`Router` delivers messages
locally when their `dst_id` matches :py:data:`mitogen.context_id`,
otherwise they are routed up or downstream.
* - `src_id`
- 2
- Integer source context ID. Used as the target of replies if any are
generated.
* - `auth_id`
- 2
- The context ID under whose authority the message is acting. See
:py:ref:`source-verification`.
* - `handle`
- 4
- Integer target handle in the destination context. This is one of the
:py:ref:`standard-handles`, or a dynamically generated handle used to
receive a one-time reply, such as the return value of a function call.
* - `reply_to`
- 4
- Integer target handle to direct any reply to this message. Used to
receive a one-time reply, such as the return value of a function call.
* - `length`
- 4
- Length of the data part of the message.
* - `data`
- n/a
- Message data, which may be raw or pickled.
.. _standard-handles:
Standard Handles
################
Masters listen on the following handles:
......@@ -504,14 +538,21 @@ Source Verification
Before forwarding or dispatching a message it has received,
:py:class:`mitogen.core.Router` first looks up the corresponding
:py:class:`mitogen.core.Stream` it would use to send responses towards the
message source, and if the looked up stream does not match the stream on which
the message was received, the message is discarded and a warning is logged.
context ID listed in the `auth_id` field, and if the looked up stream does not
match the stream on which the message was received, the message is discarded
and a warning is logged.
This creates a trust chain leading up to the root of the tree, preventing
downstream contexts from injecting messages appearing to be from the master or
any more trustworthy parent. In this way, privileged functionality such as
:py:data:`CALL_FUNCTION <mitogen.core.CALL_FUNCTION>` can base trust decisions
on the accuracy of :py:ref:`src_id <stream-protocol>`.
on the accuracy of :py:ref:`auth_id <stream-protocol>`.
The `auth_id` field is separate from `src_id` in order to support granting
privilege to contexts that do not follow the tree's natural trust chain. This
supports cases where siblings are permitted to execute code on one another, or
where isolated processes can connect to a listener and communicate with an
already established established tree.
Future
......
......@@ -217,6 +217,7 @@ def enable_profiling():
class Message(object):
dst_id = None
src_id = None
auth_id = None
handle = None
reply_to = None
data = ''
......@@ -225,6 +226,7 @@ class Message(object):
def __init__(self, **kwargs):
self.src_id = mitogen.context_id
self.auth_id = mitogen.context_id
vars(self).update(kwargs)
def _unpickle_context(self, context_id, name):
......@@ -264,9 +266,9 @@ class Message(object):
raise StreamError('invalid message: %s', ex)
def __repr__(self):
return 'Message(%r, %r, %r, %r, %r..%d)' % (
self.dst_id, self.src_id, self.handle, self.reply_to,
(self.data or '')[:50], len(self.data)
return 'Message(%r, %r, %r, %r, %r, %r..%d)' % (
self.dst_id, self.src_id, self.auth_id, self.handle,
self.reply_to, (self.data or '')[:50], len(self.data)
)
......@@ -307,8 +309,6 @@ def _queue_interruptible_get(queue, timeout=None, block=True):
if timeout is not None:
timeout += time.time()
LOG.info('timeout = %r, block = %r', timeout, block)
msg = None
while msg is None and (timeout is None or timeout > time.time()):
try:
......@@ -407,6 +407,7 @@ class Importer(object):
'mitogen.compat.pkgutil',
'mitogen.fakessh',
'mitogen.master',
'mitogen.parent',
'mitogen.ssh',
'mitogen.sudo',
'mitogen.utils',
......@@ -635,6 +636,7 @@ class Stream(BasicStream):
protocol <stream-protocol>`.
"""
_input_buf = ''
auth_id = None
def __init__(self, router, remote_id, **kwargs):
self._router = router
......@@ -663,7 +665,7 @@ class Stream(BasicStream):
if not buf:
return self.on_disconnect(broker)
HEADER_FMT = '>hhLLL'
HEADER_FMT = '>hhhLLL'
HEADER_LEN = struct.calcsize(HEADER_FMT)
def _receive_one(self, broker):
......@@ -674,7 +676,7 @@ class Stream(BasicStream):
# To support unpickling Contexts.
msg.router = self._router
(msg.dst_id, msg.src_id,
(msg.dst_id, msg.src_id, msg.auth_id,
msg.handle, msg.reply_to, msg_len) = struct.unpack(
self.HEADER_FMT,
self._input_buf[:self.HEADER_LEN]
......@@ -711,7 +713,7 @@ class Stream(BasicStream):
def _send(self, msg):
IOLOG.debug('%r._send(%r)', self, msg)
pkt = struct.pack('>hhLLL', msg.dst_id, msg.src_id,
pkt = struct.pack('>hhhLLL', msg.dst_id, msg.src_id, msg.auth_id,
msg.handle, msg.reply_to or 0, len(msg.data)
) + msg.data
self._output_buf.append(pkt)
......@@ -765,8 +767,6 @@ class Context(object):
"""send `obj` to `handle`, and tell the broker we have output. May
be called from any thread."""
msg.dst_id = self.context_id
if msg.src_id is None:
msg.src_id = mitogen.context_id
self.router.route(msg)
def send_async(self, msg, persist=False):
......@@ -979,10 +979,10 @@ class Router(object):
IOLOG.debug('%r._async_route(%r, %r)', self, msg, stream)
# Perform source verification.
if stream is not None:
expected_stream = self._stream_by_id.get(msg.src_id,
expected_stream = self._stream_by_id.get(msg.auth_id,
self._stream_by_id.get(mitogen.parent_id))
if stream != expected_stream:
LOG.error('%r: bad source: got %r from %r, should be from %r',
LOG.error('%r: bad source: got auth ID %r from %r, should be from %r',
self, msg, stream, expected_stream)
if msg.dst_id == mitogen.context_id:
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment