diff options
author | Nathaniel Manista <nathaniel@google.com> | 2015-02-26 16:09:06 +0000 |
---|---|---|
committer | Nathaniel Manista <nathaniel@google.com> | 2015-02-26 17:11:23 +0000 |
commit | 1c37fe62fd99ca9a899002e15342fd3584b83ca0 (patch) | |
tree | 6ceb019958264718e17f894b5edf7ffa7f36e3ba | |
parent | 68acd356764a8fe20a5dbb8fb45d2d9319640715 (diff) |
Work towards invocation-side security.
-rw-r--r-- | src/python/src/grpc/_adapter/_c_test.py | 4 | ||||
-rw-r--r-- | src/python/src/grpc/_adapter/_channel.c | 22 | ||||
-rw-r--r-- | src/python/src/grpc/_adapter/_client_credentials.c | 1 | ||||
-rw-r--r-- | src/python/src/grpc/_adapter/_face_test_case.py | 3 | ||||
-rw-r--r-- | src/python/src/grpc/_adapter/_links_test.py | 6 | ||||
-rw-r--r-- | src/python/src/grpc/_adapter/_lonely_rear_link_test.py | 5 | ||||
-rw-r--r-- | src/python/src/grpc/_adapter/_low_test.py | 6 | ||||
-rw-r--r-- | src/python/src/grpc/_adapter/rear.py | 71 | ||||
-rw-r--r-- | src/python/src/grpc/early_adopter/_reexport.py | 11 | ||||
-rw-r--r-- | src/python/src/grpc/early_adopter/implementations.py | 22 | ||||
-rw-r--r-- | src/python/src/grpc/framework/assembly/implementations.py | 16 |
11 files changed, 128 insertions, 39 deletions
diff --git a/src/python/src/grpc/_adapter/_c_test.py b/src/python/src/grpc/_adapter/_c_test.py index 44aff44ffa..d81c63e346 100644 --- a/src/python/src/grpc/_adapter/_c_test.py +++ b/src/python/src/grpc/_adapter/_c_test.py @@ -70,7 +70,7 @@ class _CTest(unittest.TestCase): def testChannel(self): _c.init() - channel = _c.Channel('test host:12345') + channel = _c.Channel('test host:12345', None) del channel _c.shut_down() @@ -81,7 +81,7 @@ class _CTest(unittest.TestCase): _c.init() - channel = _c.Channel('%s:%d' % (host, 12345)) + channel = _c.Channel('%s:%d' % (host, 12345), None) call = _c.Call(channel, method, host, time.time() + _TIMEOUT) del call del channel diff --git a/src/python/src/grpc/_adapter/_channel.c b/src/python/src/grpc/_adapter/_channel.c index 3ba943e4b2..9cf580bcfb 100644 --- a/src/python/src/grpc/_adapter/_channel.c +++ b/src/python/src/grpc/_adapter/_channel.c @@ -35,18 +35,28 @@ #include <Python.h> #include <grpc/grpc.h> +#include <grpc/grpc_security.h> + +#include "grpc/_adapter/_client_credentials.h" static int pygrpc_channel_init(Channel *self, PyObject *args, PyObject *kwds) { const char *hostport; - static char *kwlist[] = {"hostport", NULL}; + PyObject *client_credentials; + static char *kwlist[] = {"hostport", "client_credentials", NULL}; - if (!(PyArg_ParseTupleAndKeywords(args, kwds, "s:Channel", kwlist, - &hostport))) { + if (!(PyArg_ParseTupleAndKeywords(args, kwds, "sO:Channel", kwlist, + &hostport, &client_credentials))) { return -1; } - - self->c_channel = grpc_channel_create(hostport, NULL); - return 0; + if (client_credentials == Py_None) { + self->c_channel = grpc_channel_create(hostport, NULL); + return 0; + } else { + self->c_channel = grpc_secure_channel_create( + ((ClientCredentials *)client_credentials)->c_client_credentials, + hostport, NULL); + return 0; + } } static void pygrpc_channel_dealloc(Channel *self) { diff --git a/src/python/src/grpc/_adapter/_client_credentials.c b/src/python/src/grpc/_adapter/_client_credentials.c index b970c866ef..e8ccff8d17 100644 --- a/src/python/src/grpc/_adapter/_client_credentials.c +++ b/src/python/src/grpc/_adapter/_client_credentials.c @@ -58,6 +58,7 @@ static int pygrpc_client_credentials_init(ClientCredentials *self, self->c_client_credentials = grpc_ssl_credentials_create(root_certificates, NULL); } + return 0; } static void pygrpc_client_credentials_dealloc(ClientCredentials *self) { diff --git a/src/python/src/grpc/_adapter/_face_test_case.py b/src/python/src/grpc/_adapter/_face_test_case.py index 8cce322d30..475d780c95 100644 --- a/src/python/src/grpc/_adapter/_face_test_case.py +++ b/src/python/src/grpc/_adapter/_face_test_case.py @@ -85,7 +85,8 @@ class FaceTestCase(test_case.FaceTestCase, coverage.BlockingCoverage): port = fore_link.port() rear_link = rear.RearLink( 'localhost', port, pool, - serialization.request_serializers, serialization.response_deserializers) + serialization.request_serializers, + serialization.response_deserializers, False, None, None, None) rear_link.start() front = tickets_implementations.front(pool, pool, pool) back = tickets_implementations.back( diff --git a/src/python/src/grpc/_adapter/_links_test.py b/src/python/src/grpc/_adapter/_links_test.py index 6b3bcee9fa..5d7e677243 100644 --- a/src/python/src/grpc/_adapter/_links_test.py +++ b/src/python/src/grpc/_adapter/_links_test.py @@ -75,7 +75,7 @@ class RoundTripTest(unittest.TestCase): rear_link = rear.RearLink( 'localhost', port, self.rear_link_pool, {test_method: None}, - {test_method: None}) + {test_method: None}, False, None, None, None) rear_link.join_fore_link(test_fore_link) test_fore_link.join_rear_link(rear_link) rear_link.start() @@ -129,7 +129,7 @@ class RoundTripTest(unittest.TestCase): rear_link = rear.RearLink( 'localhost', port, self.rear_link_pool, {test_method: _IDENTITY}, - {test_method: _IDENTITY}) + {test_method: _IDENTITY}, False, None, None, None) rear_link.join_fore_link(test_fore_link) test_fore_link.join_rear_link(rear_link) rear_link.start() @@ -193,7 +193,7 @@ class RoundTripTest(unittest.TestCase): rear_link = rear.RearLink( 'localhost', port, self.rear_link_pool, {test_method: scenario.serialize_request}, - {test_method: scenario.deserialize_response}) + {test_method: scenario.deserialize_response}, False, None, None, None) rear_link.join_fore_link(test_fore_link) test_fore_link.join_rear_link(rear_link) rear_link.start() diff --git a/src/python/src/grpc/_adapter/_lonely_rear_link_test.py b/src/python/src/grpc/_adapter/_lonely_rear_link_test.py index 9a13309a18..77821ba71a 100644 --- a/src/python/src/grpc/_adapter/_lonely_rear_link_test.py +++ b/src/python/src/grpc/_adapter/_lonely_rear_link_test.py @@ -50,7 +50,8 @@ class LonelyRearLinkTest(unittest.TestCase): self.pool.shutdown(wait=True) def testUpAndDown(self): - rear_link = rear.RearLink('nonexistent', 54321, self.pool, {}, {}) + rear_link = rear.RearLink( + 'nonexistent', 54321, self.pool, {}, {}, False, None, None, None) rear_link.start() rear_link.stop() @@ -63,7 +64,7 @@ class LonelyRearLinkTest(unittest.TestCase): rear_link = rear.RearLink( 'nonexistent', 54321, self.pool, {test_method: None}, - {test_method: None}) + {test_method: None}, False, None, None, None) rear_link.join_fore_link(fore_link) rear_link.start() diff --git a/src/python/src/grpc/_adapter/_low_test.py b/src/python/src/grpc/_adapter/_low_test.py index 898c62c002..03e3f473a3 100644 --- a/src/python/src/grpc/_adapter/_low_test.py +++ b/src/python/src/grpc/_adapter/_low_test.py @@ -56,7 +56,7 @@ class LonelyClientTest(unittest.TestCase): finish_tag = object() completion_queue = _low.CompletionQueue() - channel = _low.Channel('%s:%d' % (host, port)) + channel = _low.Channel('%s:%d' % (host, port), None) client_call = _low.Call(channel, method, host, deadline) client_call.invoke(completion_queue, metadata_tag, finish_tag) @@ -87,7 +87,7 @@ class EchoTest(unittest.TestCase): self.server.start() self.client_completion_queue = _low.CompletionQueue() - self.channel = _low.Channel('%s:%d' % (self.host, port)) + self.channel = _low.Channel('%s:%d' % (self.host, port), None) def tearDown(self): self.server.stop() @@ -265,7 +265,7 @@ class CancellationTest(unittest.TestCase): self.server.start() self.client_completion_queue = _low.CompletionQueue() - self.channel = _low.Channel('%s:%d' % (self.host, port)) + self.channel = _low.Channel('%s:%d' % (self.host, port), None) def tearDown(self): self.server.stop() diff --git a/src/python/src/grpc/_adapter/rear.py b/src/python/src/grpc/_adapter/rear.py index 94ff66ffda..bfde5f5c57 100644 --- a/src/python/src/grpc/_adapter/rear.py +++ b/src/python/src/grpc/_adapter/rear.py @@ -92,7 +92,8 @@ class RearLink(ticket_interfaces.RearLink, activated.Activated): """An invocation-side bridge between RPC Framework and the C-ish _low code.""" def __init__( - self, host, port, pool, request_serializers, response_deserializers): + self, host, port, pool, request_serializers, response_deserializers, + secure, root_certificates, private_key, certificate_chain): """Constructor. Args: @@ -103,6 +104,13 @@ class RearLink(ticket_interfaces.RearLink, activated.Activated): serializer behaviors. response_deserializers: A dict from RPC method names to response object deserializer behaviors. + secure: A boolean indicating whether or not to use a secure connection. + root_certificates: The PEM-encoded root certificates or None to ask for + them to be retrieved from a default location. + private_key: The PEM-encoded private key to use or None if no private + key should be used. + certificate_chain: The PEM-encoded certificate chain to use or None if + no certificate chain should be used. """ self._condition = threading.Condition() self._host = host @@ -116,6 +124,14 @@ class RearLink(ticket_interfaces.RearLink, activated.Activated): self._channel = None self._rpc_states = {} self._spinning = False + if secure: + self._client_credentials = _low.ClientCredentials( + root_certificates, private_key, certificate_chain) + else: + self._client_credentials = None + self._root_certificates = root_certificates + self._private_key = private_key + self._certificate_chain = certificate_chain def _on_write_event(self, operation_id, event, rpc_state): if event.write_accepted: @@ -310,7 +326,8 @@ class RearLink(ticket_interfaces.RearLink, activated.Activated): """ with self._condition: self._completion_queue = _low.CompletionQueue() - self._channel = _low.Channel('%s:%d' % (self._host, self._port)) + self._channel = _low.Channel( + '%s:%d' % (self._host, self._port), self._client_credentials) return self def _stop(self): @@ -369,11 +386,17 @@ class RearLink(ticket_interfaces.RearLink, activated.Activated): class _ActivatedRearLink(ticket_interfaces.RearLink, activated.Activated): - def __init__(self, host, port, request_serializers, response_deserializers): + def __init__( + self, host, port, request_serializers, response_deserializers, secure, + root_certificates, private_key, certificate_chain): self._host = host self._port = port self._request_serializers = request_serializers self._response_deserializers = response_deserializers + self._secure = secure + self._root_certificates = root_certificates + self._private_key = private_key + self._certificate_chain = certificate_chain self._lock = threading.Lock() self._pool = None @@ -391,7 +414,8 @@ class _ActivatedRearLink(ticket_interfaces.RearLink, activated.Activated): self._pool = logging_pool.pool(_THREAD_POOL_SIZE) self._rear_link = RearLink( self._host, self._port, self._pool, self._request_serializers, - self._response_deserializers) + self._response_deserializers, self._secure, self._root_certificates, + self._private_key, self._certificate_chain) self._rear_link.join_fore_link(self._fore_link) self._rear_link.start() return self @@ -422,6 +446,7 @@ class _ActivatedRearLink(ticket_interfaces.RearLink, activated.Activated): self._rear_link.accept_front_to_back_ticket(ticket) +# TODO(issue 726): reconcile these two creation functions. def activated_rear_link( host, port, request_serializers, response_deserializers): """Creates a RearLink that is also an activated.Activated. @@ -436,6 +461,42 @@ def activated_rear_link( serializer behavior. response_deserializers: A dictionary from RPC method name to response object deserializer behavior. + secure: A boolean indicating whether or not to use a secure connection. + root_certificates: The PEM-encoded root certificates or None to ask for + them to be retrieved from a default location. + private_key: The PEM-encoded private key to use or None if no private key + should be used. + certificate_chain: The PEM-encoded certificate chain to use or None if no + certificate chain should be used. + """ + return _ActivatedRearLink( + host, port, request_serializers, response_deserializers, False, None, + None, None) + + + +def secure_activated_rear_link( + host, port, request_serializers, response_deserializers, root_certificates, + private_key, certificate_chain): + """Creates a RearLink that is also an activated.Activated. + + The returned object is only valid for use between calls to its start and stop + methods (or in context when used as a context manager). + + Args: + host: The host to which to connect for RPC service. + port: The port to which to connect for RPC service. + request_serializers: A dictionary from RPC method name to request object + serializer behavior. + response_deserializers: A dictionary from RPC method name to response + object deserializer behavior. + root_certificates: The PEM-encoded root certificates or None to ask for + them to be retrieved from a default location. + private_key: The PEM-encoded private key to use or None if no private key + should be used. + certificate_chain: The PEM-encoded certificate chain to use or None if no + certificate chain should be used. """ return _ActivatedRearLink( - host, port, request_serializers, response_deserializers) + host, port, request_serializers, response_deserializers, True, + root_certificates, private_key, certificate_chain) diff --git a/src/python/src/grpc/early_adopter/_reexport.py b/src/python/src/grpc/early_adopter/_reexport.py index 35855bc9c8..35f4e85a72 100644 --- a/src/python/src/grpc/early_adopter/_reexport.py +++ b/src/python/src/grpc/early_adopter/_reexport.py @@ -27,9 +27,6 @@ # (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE # OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. -import abc -import collections - from grpc.framework.face import exceptions as face_exceptions from grpc.framework.face import interfaces as face_interfaces from grpc.framework.foundation import future @@ -186,6 +183,14 @@ class _Stub(interfaces.Stub): def __getattr__(self, attr): underlying_attr = self._assembly_stub.__getattr__(attr) cardinality = self._cardinalities.get(attr) + # TODO(nathaniel): unify this trick with its other occurrence in the code. + if cardinality is None: + for name, cardinality in self._cardinalities.iteritems(): + last_slash_index = name.rfind('/') + if 0 <= last_slash_index and name[last_slash_index + 1:] == attr: + break + else: + raise AttributeError(attr) if cardinality is interfaces.Cardinality.UNARY_UNARY: return _UnaryUnarySyncAsync(underlying_attr) elif cardinality is interfaces.Cardinality.UNARY_STREAM: diff --git a/src/python/src/grpc/early_adopter/implementations.py b/src/python/src/grpc/early_adopter/implementations.py index 241ed7dcdb..6195958624 100644 --- a/src/python/src/grpc/early_adopter/implementations.py +++ b/src/python/src/grpc/early_adopter/implementations.py @@ -93,13 +93,7 @@ class _Server(interfaces.Server): with self._lock: return self._fore_link.port() -def _build_stub( - methods, host, port, root_certificates, private_key, certificate_chain): - breakdown = _assembly_utilities.break_down_invocation(methods) - # TODO(nathaniel): pass security values. - activated_rear_link = _rear.activated_rear_link( - host, port, breakdown.request_serializers, - breakdown.response_deserializers) +def _build_stub(breakdown, activated_rear_link): assembly_stub = _assembly_implementations.assemble_dynamic_inline_stub( breakdown.implementations, activated_rear_link) return _reexport.stub(assembly_stub, breakdown.cardinalities) @@ -123,7 +117,11 @@ def insecure_stub(methods, host, port): Returns: An interfaces.Stub affording RPC invocation. """ - return _build_stub(methods, host, port, None, None, None) + breakdown = _assembly_utilities.break_down_invocation(methods) + activated_rear_link = _rear.activated_rear_link( + host, port, breakdown.request_serializers, + breakdown.response_deserializers) + return _build_stub(breakdown, activated_rear_link) def secure_stub( @@ -146,8 +144,12 @@ def secure_stub( Returns: An interfaces.Stub affording RPC invocation. """ - return _build_stub( - methods, host, port, root_certificates, private_key, certificate_chain) + breakdown = _assembly_utilities.break_down_invocation(methods) + activated_rear_link = _rear.secure_activated_rear_link( + host, port, breakdown.request_serializers, + breakdown.response_deserializers, root_certificates, private_key, + certificate_chain) + return _build_stub(breakdown, activated_rear_link) def insecure_server(methods, port): diff --git a/src/python/src/grpc/framework/assembly/implementations.py b/src/python/src/grpc/framework/assembly/implementations.py index b9d314844c..f7166ed99d 100644 --- a/src/python/src/grpc/framework/assembly/implementations.py +++ b/src/python/src/grpc/framework/assembly/implementations.py @@ -31,16 +31,18 @@ import threading +# tickets_interfaces, face_interfaces, and activated are referenced from +# specification in this module. from grpc.framework.assembly import interfaces from grpc.framework.base import util as base_utilities from grpc.framework.base.packets import implementations as tickets_implementations -from grpc.framework.base.packets import interfaces as tickets_interfaces +from grpc.framework.base.packets import interfaces as tickets_interfaces # pylint: disable=unused-import from grpc.framework.common import cardinality from grpc.framework.common import style from grpc.framework.face import implementations as face_implementations -from grpc.framework.face import interfaces as face_interfaces +from grpc.framework.face import interfaces as face_interfaces # pylint: disable=unused-import from grpc.framework.face import utilities as face_utilities -from grpc.framework.foundation import activated +from grpc.framework.foundation import activated # pylint: disable=unused-import from grpc.framework.foundation import logging_pool _ONE_DAY_IN_SECONDS = 60 * 60 * 24 @@ -138,7 +140,13 @@ class _DynamicInlineStub(object): with self._lock: behavior = self._behaviors.get(attr) if behavior is None: - raise AttributeError(attr) + for name, behavior in self._behaviors.iteritems(): + last_slash_index = name.rfind('/') + if 0 <= last_slash_index and name[last_slash_index + 1:] == attr: + return behavior + else: + raise AttributeError( + '_DynamicInlineStub instance has no attribute "%s"!' % attr) else: return behavior |