Kaynağa Gözat

Merge pull request #962 from nathanielmanistaatgoogle/interop

Secure python interop (plus Python<->Python interop unit tests)
Masood Malekghassemi 10 yıl önce
ebeveyn
işleme
3aca2a6245

+ 56 - 0
src/python/interop/interop/_insecure_interop_test.py

@@ -0,0 +1,56 @@
+# Copyright 2015, Google Inc.
+# All rights reserved.
+#
+# Redistribution and use in source and binary forms, with or without
+# modification, are permitted provided that the following conditions are
+# met:
+#
+#     * Redistributions of source code must retain the above copyright
+# notice, this list of conditions and the following disclaimer.
+#     * Redistributions in binary form must reproduce the above
+# copyright notice, this list of conditions and the following disclaimer
+# in the documentation and/or other materials provided with the
+# distribution.
+#     * Neither the name of Google Inc. nor the names of its
+# contributors may be used to endorse or promote products derived from
+# this software without specific prior written permission.
+#
+# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+# "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+# LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+# A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+# OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+# SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+# LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+# DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+# THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+# (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+# OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+
+"""Insecure client-server interoperability as a unit test."""
+
+import unittest
+
+from grpc.early_adopter import implementations
+
+from interop import _interop_test_case
+from interop import methods
+
+
+class InsecureInteropTest(
+    _interop_test_case.InteropTestCase,
+    unittest.TestCase):
+
+  def setUp(self):
+    self.server = implementations.insecure_server(methods.SERVER_METHODS, 0)
+    self.server.start()
+    port = self.server.port()
+    self.stub = implementations.insecure_stub(
+        methods.CLIENT_METHODS, 'localhost', port)
+
+  def tearDown(self):
+    self.server.stop()
+
+
+if __name__ == '__main__':
+  unittest.main()

+ 55 - 0
src/python/interop/interop/_interop_test_case.py

@@ -0,0 +1,55 @@
+# Copyright 2015, Google Inc.
+# All rights reserved.
+#
+# Redistribution and use in source and binary forms, with or without
+# modification, are permitted provided that the following conditions are
+# met:
+#
+#     * Redistributions of source code must retain the above copyright
+# notice, this list of conditions and the following disclaimer.
+#     * Redistributions in binary form must reproduce the above
+# copyright notice, this list of conditions and the following disclaimer
+# in the documentation and/or other materials provided with the
+# distribution.
+#     * Neither the name of Google Inc. nor the names of its
+# contributors may be used to endorse or promote products derived from
+# this software without specific prior written permission.
+#
+# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+# "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+# LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+# A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+# OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+# SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+# LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+# DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+# THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+# (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+# OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+
+"""Common code for unit tests of the interoperability test code."""
+
+from interop import methods
+
+
+class InteropTestCase(object):
+  """Unit test methods.
+
+  This class must be mixed in with unittest.TestCase and a class that defines
+  setUp and tearDown methods that manage a stub attribute.
+  """
+
+  def testEmptyUnary(self):
+    methods.TestCase.EMPTY_UNARY.test_interoperability(self.stub)
+
+  def testLargeUnary(self):
+    methods.TestCase.LARGE_UNARY.test_interoperability(self.stub)
+
+  def testServerStreaming(self):
+    methods.TestCase.SERVER_STREAMING.test_interoperability(self.stub)
+
+  def testClientStreaming(self):
+    methods.TestCase.CLIENT_STREAMING.test_interoperability(self.stub)
+
+  def testPingPong(self):
+    methods.TestCase.PING_PONG.test_interoperability(self.stub)

+ 63 - 0
src/python/interop/interop/_secure_interop_test.py

@@ -0,0 +1,63 @@
+# Copyright 2015, Google Inc.
+# All rights reserved.
+#
+# Redistribution and use in source and binary forms, with or without
+# modification, are permitted provided that the following conditions are
+# met:
+#
+#     * Redistributions of source code must retain the above copyright
+# notice, this list of conditions and the following disclaimer.
+#     * Redistributions in binary form must reproduce the above
+# copyright notice, this list of conditions and the following disclaimer
+# in the documentation and/or other materials provided with the
+# distribution.
+#     * Neither the name of Google Inc. nor the names of its
+# contributors may be used to endorse or promote products derived from
+# this software without specific prior written permission.
+#
+# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+# "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+# LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+# A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+# OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+# SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+# LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+# DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+# THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+# (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+# OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+
+"""Secure client-server interoperability as a unit test."""
+
+import unittest
+
+from grpc.early_adopter import implementations
+
+from interop import _interop_test_case
+from interop import methods
+from interop import resources
+
+_SERVER_HOST_OVERRIDE = 'foo.test.google.fr'
+
+
+class SecureInteropTest(
+    _interop_test_case.InteropTestCase,
+    unittest.TestCase):
+
+  def setUp(self):
+    self.server = implementations.secure_server(
+        methods.SERVER_METHODS, 0, resources.private_key(),
+        resources.certificate_chain())
+    self.server.start()
+    port = self.server.port()
+    self.stub = implementations.secure_stub(
+        methods.CLIENT_METHODS, 'localhost', port,
+        resources.test_root_certificates(), None, None,
+        server_host_override=_SERVER_HOST_OVERRIDE)
+
+  def tearDown(self):
+    self.server.stop()
+
+
+if __name__ == '__main__':
+  unittest.main()

+ 12 - 3
src/python/interop/interop/client.py

@@ -65,21 +65,30 @@ def _stub(args):
       root_certificates = resources.test_root_certificates()
       root_certificates = resources.test_root_certificates()
     else:
     else:
       root_certificates = resources.prod_root_certificates()
       root_certificates = resources.prod_root_certificates()
-    # TODO(nathaniel): server host override.
 
 
     stub = implementations.secure_stub(
     stub = implementations.secure_stub(
         methods.CLIENT_METHODS, args.server_host, args.server_port,
         methods.CLIENT_METHODS, args.server_host, args.server_port,
-        root_certificates, None, None)
+        root_certificates, None, None,
+        server_host_override=args.server_host_override)
   else:
   else:
     stub = implementations.insecure_stub(
     stub = implementations.insecure_stub(
         methods.CLIENT_METHODS, args.server_host, args.server_port)
         methods.CLIENT_METHODS, args.server_host, args.server_port)
   return stub
   return stub
 
 
 
 
+def _test_case_from_arg(test_case_arg):
+  for test_case in methods.TestCase:
+    if test_case_arg == test_case.value:
+      return test_case
+  else:
+    raise ValueError('No test case "%s"!' % test_case_arg)
+
+
 def _test_interoperability():
 def _test_interoperability():
   args = _args()
   args = _args()
   stub = _stub(args)
   stub = _stub(args)
-  methods.test_interoperability(args.test_case, stub)
+  test_case = _test_case_from_arg(args.test_case)
+  test_case.test_interoperability(stub)
 
 
 
 
 if __name__ == '__main__':
 if __name__ == '__main__':

+ 22 - 13
src/python/interop/interop/methods.py

@@ -29,6 +29,7 @@
 
 
 """Implementations of interoperability test methods."""
 """Implementations of interoperability test methods."""
 
 
+import enum
 import threading
 import threading
 
 
 from grpc.early_adopter import utilities
 from grpc.early_adopter import utilities
@@ -265,16 +266,24 @@ def _ping_pong(stub):
     pipe.close()
     pipe.close()
 
 
 
 
-def test_interoperability(test_case, stub):
-  if test_case == 'empty_unary':
-    _empty_unary(stub)
-  elif test_case == 'large_unary':
-    _large_unary(stub)
-  elif test_case == 'server_streaming':
-    _server_streaming(stub)
-  elif test_case == 'client_streaming':
-    _client_streaming(stub)
-  elif test_case == 'ping_pong':
-    _ping_pong(stub)
-  else:
-    raise NotImplementedError('Test case "%s" not implemented!')
+@enum.unique
+class TestCase(enum.Enum):
+  EMPTY_UNARY = 'empty_unary'
+  LARGE_UNARY = 'large_unary'
+  SERVER_STREAMING = 'server_streaming'
+  CLIENT_STREAMING = 'client_streaming'
+  PING_PONG = 'ping_pong'
+
+  def test_interoperability(self, stub):
+    if self is TestCase.EMPTY_UNARY:
+      _empty_unary(stub)
+    elif self is TestCase.LARGE_UNARY:
+      _large_unary(stub)
+    elif self is TestCase.SERVER_STREAMING:
+      _server_streaming(stub)
+    elif self is TestCase.CLIENT_STREAMING:
+      _client_streaming(stub)
+    elif self is TestCase.PING_PONG:
+      _ping_pong(stub)
+    else:
+      raise NotImplementedError('Test case "%s" not implemented!' % self.name)

+ 2 - 1
src/python/src/grpc/_adapter/_c_test.py

@@ -70,7 +70,8 @@ class _CTest(unittest.TestCase):
   def testChannel(self):
   def testChannel(self):
     _c.init()
     _c.init()
 
 
-    channel = _c.Channel('test host:12345', None)
+    channel = _c.Channel(
+        'test host:12345', None, server_host_override='ignored')
     del channel
     del channel
 
 
     _c.shut_down()
     _c.shut_down()

+ 22 - 6
src/python/src/grpc/_adapter/_channel.c

@@ -42,19 +42,35 @@
 static int pygrpc_channel_init(Channel *self, PyObject *args, PyObject *kwds) {
 static int pygrpc_channel_init(Channel *self, PyObject *args, PyObject *kwds) {
   const char *hostport;
   const char *hostport;
   PyObject *client_credentials;
   PyObject *client_credentials;
-  static char *kwlist[] = {"hostport", "client_credentials", NULL};
+  char *server_host_override = NULL;
+  static char *kwlist[] = {"hostport", "client_credentials",
+                           "server_host_override", NULL};
+  grpc_arg server_host_override_arg;
+  grpc_channel_args channel_args;
 
 
-  if (!(PyArg_ParseTupleAndKeywords(args, kwds, "sO:Channel", kwlist,
-                                    &hostport, &client_credentials))) {
+  if (!(PyArg_ParseTupleAndKeywords(args, kwds, "sO|z:Channel", kwlist,
+                                    &hostport, &client_credentials,
+                                    &server_host_override))) {
     return -1;
     return -1;
   }
   }
   if (client_credentials == Py_None) {
   if (client_credentials == Py_None) {
     self->c_channel = grpc_channel_create(hostport, NULL);
     self->c_channel = grpc_channel_create(hostport, NULL);
     return 0;
     return 0;
   } else {
   } else {
-    self->c_channel = grpc_secure_channel_create(
-        ((ClientCredentials *)client_credentials)->c_client_credentials,
-        hostport, NULL);
+    if (server_host_override == NULL) {
+      self->c_channel = grpc_secure_channel_create(
+	  ((ClientCredentials *)client_credentials)->c_client_credentials,
+          hostport, NULL);
+    } else {
+      server_host_override_arg.type = GRPC_ARG_STRING;
+      server_host_override_arg.key = GRPC_SSL_TARGET_NAME_OVERRIDE_ARG;
+      server_host_override_arg.value.string = server_host_override;
+      channel_args.num_args = 1;
+      channel_args.args = &server_host_override_arg;
+      self->c_channel = grpc_secure_channel_create(
+          ((ClientCredentials *)client_credentials)->c_client_credentials,
+          hostport, &channel_args);
+    }
     return 0;
     return 0;
   }
   }
 }
 }

+ 17 - 6
src/python/src/grpc/_adapter/rear.py

@@ -93,7 +93,8 @@ class RearLink(ticket_interfaces.RearLink, activated.Activated):
 
 
   def __init__(
   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):
+      secure, root_certificates, private_key, certificate_chain,
+      server_host_override=None):
     """Constructor.
     """Constructor.
 
 
     Args:
     Args:
@@ -111,6 +112,8 @@ class RearLink(ticket_interfaces.RearLink, activated.Activated):
         key should be used.
         key should be used.
       certificate_chain: The PEM-encoded certificate chain to use or None if
       certificate_chain: The PEM-encoded certificate chain to use or None if
         no certificate chain should be used.
         no certificate chain should be used.
+      server_host_override: (For testing only) the target name used for SSL
+        host name checking.
     """
     """
     self._condition = threading.Condition()
     self._condition = threading.Condition()
     self._host = host
     self._host = host
@@ -132,6 +135,7 @@ class RearLink(ticket_interfaces.RearLink, activated.Activated):
     self._root_certificates = root_certificates
     self._root_certificates = root_certificates
     self._private_key = private_key
     self._private_key = private_key
     self._certificate_chain = certificate_chain
     self._certificate_chain = certificate_chain
+    self._server_host_override = server_host_override
 
 
   def _on_write_event(self, operation_id, event, rpc_state):
   def _on_write_event(self, operation_id, event, rpc_state):
     if event.write_accepted:
     if event.write_accepted:
@@ -327,7 +331,8 @@ class RearLink(ticket_interfaces.RearLink, activated.Activated):
     with self._condition:
     with self._condition:
       self._completion_queue = _low.CompletionQueue()
       self._completion_queue = _low.CompletionQueue()
       self._channel = _low.Channel(
       self._channel = _low.Channel(
-          '%s:%d' % (self._host, self._port), self._client_credentials)
+          '%s:%d' % (self._host, self._port), self._client_credentials,
+          server_host_override=self._server_host_override)
     return self
     return self
 
 
   def _stop(self):
   def _stop(self):
@@ -388,7 +393,8 @@ class _ActivatedRearLink(ticket_interfaces.RearLink, activated.Activated):
 
 
   def __init__(
   def __init__(
       self, host, port, request_serializers, response_deserializers, secure,
       self, host, port, request_serializers, response_deserializers, secure,
-      root_certificates, private_key, certificate_chain):
+      root_certificates, private_key, certificate_chain,
+      server_host_override=None):
     self._host = host
     self._host = host
     self._port = port
     self._port = port
     self._request_serializers = request_serializers
     self._request_serializers = request_serializers
@@ -397,6 +403,7 @@ class _ActivatedRearLink(ticket_interfaces.RearLink, activated.Activated):
     self._root_certificates = root_certificates
     self._root_certificates = root_certificates
     self._private_key = private_key
     self._private_key = private_key
     self._certificate_chain = certificate_chain
     self._certificate_chain = certificate_chain
+    self._server_host_override = server_host_override
 
 
     self._lock = threading.Lock()
     self._lock = threading.Lock()
     self._pool = None
     self._pool = None
@@ -415,7 +422,8 @@ class _ActivatedRearLink(ticket_interfaces.RearLink, activated.Activated):
       self._rear_link = RearLink(
       self._rear_link = RearLink(
           self._host, self._port, self._pool, self._request_serializers,
           self._host, self._port, self._pool, self._request_serializers,
           self._response_deserializers, self._secure, self._root_certificates,
           self._response_deserializers, self._secure, self._root_certificates,
-          self._private_key, self._certificate_chain)
+          self._private_key, self._certificate_chain,
+          server_host_override=self._server_host_override)
       self._rear_link.join_fore_link(self._fore_link)
       self._rear_link.join_fore_link(self._fore_link)
       self._rear_link.start()
       self._rear_link.start()
     return self
     return self
@@ -477,7 +485,7 @@ def activated_rear_link(
 
 
 def secure_activated_rear_link(
 def secure_activated_rear_link(
     host, port, request_serializers, response_deserializers, root_certificates,
     host, port, request_serializers, response_deserializers, root_certificates,
-    private_key, certificate_chain):
+    private_key, certificate_chain, server_host_override=None):
   """Creates a RearLink that is also an activated.Activated.
   """Creates a RearLink that is also an activated.Activated.
 
 
   The returned object is only valid for use between calls to its start and stop
   The returned object is only valid for use between calls to its start and stop
@@ -496,7 +504,10 @@ def secure_activated_rear_link(
       should be used.
       should be used.
     certificate_chain: The PEM-encoded certificate chain to use or None if no
     certificate_chain: The PEM-encoded certificate chain to use or None if no
       certificate chain should be used.
       certificate chain should be used.
+    server_host_override: (For testing only) the target name used for SSL
+      host name checking.
   """
   """
   return _ActivatedRearLink(
   return _ActivatedRearLink(
       host, port, request_serializers, response_deserializers, True,
       host, port, request_serializers, response_deserializers, True,
-      root_certificates, private_key, certificate_chain)
+      root_certificates, private_key, certificate_chain,
+      server_host_override=server_host_override)

+ 5 - 2
src/python/src/grpc/early_adopter/implementations.py

@@ -125,7 +125,8 @@ def insecure_stub(methods, host, port):
 
 
 
 
 def secure_stub(
 def secure_stub(
-    methods, host, port, root_certificates, private_key, certificate_chain):
+    methods, host, port, root_certificates, private_key, certificate_chain,
+    server_host_override=None):
   """Constructs an insecure interfaces.Stub.
   """Constructs an insecure interfaces.Stub.
 
 
   Args:
   Args:
@@ -140,6 +141,8 @@ def secure_stub(
       should be used.
       should be used.
     certificate_chain: The PEM-encoded certificate chain to use or None if no
     certificate_chain: The PEM-encoded certificate chain to use or None if no
       certificate chain should be used.
       certificate chain should be used.
+    server_host_override: (For testing only) the target name used for SSL
+      host name checking.
 
 
   Returns:
   Returns:
     An interfaces.Stub affording RPC invocation.
     An interfaces.Stub affording RPC invocation.
@@ -148,7 +151,7 @@ def secure_stub(
   activated_rear_link = _rear.secure_activated_rear_link(
   activated_rear_link = _rear.secure_activated_rear_link(
       host, port, breakdown.request_serializers,
       host, port, breakdown.request_serializers,
       breakdown.response_deserializers, root_certificates, private_key,
       breakdown.response_deserializers, root_certificates, private_key,
-      certificate_chain)
+      certificate_chain, server_host_override=server_host_override)
   return _build_stub(breakdown, activated_rear_link)
   return _build_stub(breakdown, activated_rear_link)
 
 
 
 

+ 1 - 0
tools/run_tests/build_python.sh

@@ -39,3 +39,4 @@ virtualenv -p /usr/bin/python2.7 python2.7_virtual_environment
 source python2.7_virtual_environment/bin/activate
 source python2.7_virtual_environment/bin/activate
 pip install enum34==1.0.4 futures==2.2.0 protobuf==3.0.0-alpha-1
 pip install enum34==1.0.4 futures==2.2.0 protobuf==3.0.0-alpha-1
 CFLAGS=-I$root/include LDFLAGS=-L$root/libs/opt pip install src/python/src
 CFLAGS=-I$root/include LDFLAGS=-L$root/libs/opt pip install src/python/src
+pip install src/python/interop

+ 6 - 0
tools/run_tests/python_tests.json

@@ -46,5 +46,11 @@
   },
   },
   {
   {
     "module": "grpc.framework.foundation._logging_pool_test"
     "module": "grpc.framework.foundation._logging_pool_test"
+  },
+  {
+    "module": "interop._insecure_interop_test"
+  },
+  {
+    "module": "interop._secure_interop_test"
   }
   }
 ]
 ]