core: add auth_id field.

wip-fakessh-exit-status
David Wilson 7 years ago
parent a54c96faae
commit a39cd44bf2

@ -260,21 +260,55 @@ Stream Protocol
Once connected, a basic framing protocol is used to communicate between Once connected, a basic framing protocol is used to communicate between
parent and child: parent and child:
+--------------------+------+------------------------------------------------------+ .. list-table::
| Field | Size | Description | :header-rows: 1
+====================+======+======================================================+ :widths: auto
| ``dst_id`` | 2 | Integer target context ID. |
+--------------------+------+------------------------------------------------------+ * - Field
| ``src_id`` | 2 | Integer source context ID. | - Size
+--------------------+------+------------------------------------------------------+ - Description
| ``handle`` | 4 | Integer target handle in recipient. |
+--------------------+------+------------------------------------------------------+ * - `dst_id`
| ``reply_to`` | 4 | Integer response target ID. | - 2
+--------------------+------+------------------------------------------------------+ - Integer target context ID. :py:class:`Router` delivers messages
| ``length`` | 4 | Message length | locally when their `dst_id` matches :py:data:`mitogen.context_id`,
+--------------------+------+------------------------------------------------------+ otherwise they are routed up or downstream.
| ``data`` | n/a | Pickled message data. |
+--------------------+------+------------------------------------------------------+ * - `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: Masters listen on the following handles:
@ -504,14 +538,21 @@ Source Verification
Before forwarding or dispatching a message it has received, Before forwarding or dispatching a message it has received,
:py:class:`mitogen.core.Router` first looks up the corresponding :py:class:`mitogen.core.Router` first looks up the corresponding
:py:class:`mitogen.core.Stream` it would use to send responses towards the :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 context ID listed in the `auth_id` field, and if the looked up stream does not
the message was received, the message is discarded and a warning is logged. 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 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 downstream contexts from injecting messages appearing to be from the master or
any more trustworthy parent. In this way, privileged functionality such as any more trustworthy parent. In this way, privileged functionality such as
:py:data:`CALL_FUNCTION <mitogen.core.CALL_FUNCTION>` can base trust decisions :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 Future

@ -217,6 +217,7 @@ def enable_profiling():
class Message(object): class Message(object):
dst_id = None dst_id = None
src_id = None src_id = None
auth_id = None
handle = None handle = None
reply_to = None reply_to = None
data = '' data = ''
@ -225,6 +226,7 @@ class Message(object):
def __init__(self, **kwargs): def __init__(self, **kwargs):
self.src_id = mitogen.context_id self.src_id = mitogen.context_id
self.auth_id = mitogen.context_id
vars(self).update(kwargs) vars(self).update(kwargs)
def _unpickle_context(self, context_id, name): def _unpickle_context(self, context_id, name):
@ -264,9 +266,9 @@ class Message(object):
raise StreamError('invalid message: %s', ex) raise StreamError('invalid message: %s', ex)
def __repr__(self): def __repr__(self):
return 'Message(%r, %r, %r, %r, %r..%d)' % ( return 'Message(%r, %r, %r, %r, %r, %r..%d)' % (
self.dst_id, self.src_id, self.handle, self.reply_to, self.dst_id, self.src_id, self.auth_id, self.handle,
(self.data or '')[:50], len(self.data) 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: if timeout is not None:
timeout += time.time() timeout += time.time()
LOG.info('timeout = %r, block = %r', timeout, block)
msg = None msg = None
while msg is None and (timeout is None or timeout > time.time()): while msg is None and (timeout is None or timeout > time.time()):
try: try:
@ -407,6 +407,7 @@ class Importer(object):
'mitogen.compat.pkgutil', 'mitogen.compat.pkgutil',
'mitogen.fakessh', 'mitogen.fakessh',
'mitogen.master', 'mitogen.master',
'mitogen.parent',
'mitogen.ssh', 'mitogen.ssh',
'mitogen.sudo', 'mitogen.sudo',
'mitogen.utils', 'mitogen.utils',
@ -635,6 +636,7 @@ class Stream(BasicStream):
protocol <stream-protocol>`. protocol <stream-protocol>`.
""" """
_input_buf = '' _input_buf = ''
auth_id = None
def __init__(self, router, remote_id, **kwargs): def __init__(self, router, remote_id, **kwargs):
self._router = router self._router = router
@ -663,7 +665,7 @@ class Stream(BasicStream):
if not buf: if not buf:
return self.on_disconnect(broker) return self.on_disconnect(broker)
HEADER_FMT = '>hhLLL' HEADER_FMT = '>hhhLLL'
HEADER_LEN = struct.calcsize(HEADER_FMT) HEADER_LEN = struct.calcsize(HEADER_FMT)
def _receive_one(self, broker): def _receive_one(self, broker):
@ -674,7 +676,7 @@ class Stream(BasicStream):
# To support unpickling Contexts. # To support unpickling Contexts.
msg.router = self._router 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( msg.handle, msg.reply_to, msg_len) = struct.unpack(
self.HEADER_FMT, self.HEADER_FMT,
self._input_buf[:self.HEADER_LEN] self._input_buf[:self.HEADER_LEN]
@ -711,7 +713,7 @@ class Stream(BasicStream):
def _send(self, msg): def _send(self, msg):
IOLOG.debug('%r._send(%r)', 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.handle, msg.reply_to or 0, len(msg.data)
) + msg.data ) + msg.data
self._output_buf.append(pkt) self._output_buf.append(pkt)
@ -765,8 +767,6 @@ class Context(object):
"""send `obj` to `handle`, and tell the broker we have output. May """send `obj` to `handle`, and tell the broker we have output. May
be called from any thread.""" be called from any thread."""
msg.dst_id = self.context_id msg.dst_id = self.context_id
if msg.src_id is None:
msg.src_id = mitogen.context_id
self.router.route(msg) self.router.route(msg)
def send_async(self, msg, persist=False): def send_async(self, msg, persist=False):
@ -979,10 +979,10 @@ class Router(object):
IOLOG.debug('%r._async_route(%r, %r)', self, msg, stream) IOLOG.debug('%r._async_route(%r, %r)', self, msg, stream)
# Perform source verification. # Perform source verification.
if stream is not None: 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)) self._stream_by_id.get(mitogen.parent_id))
if stream != expected_stream: 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) self, msg, stream, expected_stream)
if msg.dst_id == mitogen.context_id: if msg.dst_id == mitogen.context_id:

Loading…
Cancel
Save