mirror of https://github.com/grpc/grpc.git
Merge pull request #14879 from santoshankr/python_ssl_session_cache_lru
TLS session resumption support for Python clients.pull/15689/head
commit
8872a312c3
8 changed files with 270 additions and 0 deletions
@ -0,0 +1,45 @@ |
|||||||
|
# Copyright 2018 gRPC authors. |
||||||
|
# |
||||||
|
# Licensed under the Apache License, Version 2.0 (the "License"); |
||||||
|
# you may not use this file except in compliance with the License. |
||||||
|
# You may obtain a copy of the License at |
||||||
|
# |
||||||
|
# http://www.apache.org/licenses/LICENSE-2.0 |
||||||
|
# |
||||||
|
# Unless required by applicable law or agreed to in writing, software |
||||||
|
# distributed under the License is distributed on an "AS IS" BASIS, |
||||||
|
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
||||||
|
# See the License for the specific language governing permissions and |
||||||
|
# limitations under the License. |
||||||
|
"""gRPC's APIs for TLS Session Resumption support""" |
||||||
|
|
||||||
|
from grpc._cython import cygrpc as _cygrpc |
||||||
|
|
||||||
|
|
||||||
|
def ssl_session_cache_lru(capacity): |
||||||
|
"""Creates an SSLSessionCache with LRU replacement policy |
||||||
|
|
||||||
|
Args: |
||||||
|
capacity: Size of the cache |
||||||
|
|
||||||
|
Returns: |
||||||
|
An SSLSessionCache with LRU replacement policy that can be passed as a value for |
||||||
|
the grpc.ssl_session_cache option to a grpc.Channel. SSL session caches are used |
||||||
|
to store session tickets, which clients can present to resume previous TLS sessions |
||||||
|
with a server. |
||||||
|
""" |
||||||
|
return SSLSessionCache(_cygrpc.SSLSessionCacheLRU(capacity)) |
||||||
|
|
||||||
|
|
||||||
|
class SSLSessionCache(object): |
||||||
|
"""An encapsulation of a session cache used for TLS session resumption. |
||||||
|
|
||||||
|
Instances of this class can be passed to a Channel as values for the |
||||||
|
grpc.ssl_session_cache option |
||||||
|
""" |
||||||
|
|
||||||
|
def __init__(self, cache): |
||||||
|
self._cache = cache |
||||||
|
|
||||||
|
def __int__(self): |
||||||
|
return int(self._cache) |
@ -0,0 +1,145 @@ |
|||||||
|
# Copyright 2018 gRPC authors. |
||||||
|
# |
||||||
|
# Licensed under the Apache License, Version 2.0 (the "License"); |
||||||
|
# you may not use this file except in compliance with the License. |
||||||
|
# You may obtain a copy of the License at |
||||||
|
# |
||||||
|
# http://www.apache.org/licenses/LICENSE-2.0 |
||||||
|
# |
||||||
|
# Unless required by applicable law or agreed to in writing, software |
||||||
|
# distributed under the License is distributed on an "AS IS" BASIS, |
||||||
|
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
||||||
|
# See the License for the specific language governing permissions and |
||||||
|
# limitations under the License. |
||||||
|
"""Tests experimental TLS Session Resumption API""" |
||||||
|
|
||||||
|
import pickle |
||||||
|
import unittest |
||||||
|
|
||||||
|
import grpc |
||||||
|
from grpc import _channel |
||||||
|
from grpc.experimental import session_cache |
||||||
|
|
||||||
|
from tests.unit import test_common |
||||||
|
from tests.unit import resources |
||||||
|
|
||||||
|
_REQUEST = b'\x00\x00\x00' |
||||||
|
_RESPONSE = b'\x00\x00\x00' |
||||||
|
|
||||||
|
_UNARY_UNARY = '/test/UnaryUnary' |
||||||
|
|
||||||
|
_SERVER_HOST_OVERRIDE = 'foo.test.google.fr' |
||||||
|
_ID = 'id' |
||||||
|
_ID_KEY = 'id_key' |
||||||
|
_AUTH_CTX = 'auth_ctx' |
||||||
|
|
||||||
|
_PRIVATE_KEY = resources.private_key() |
||||||
|
_CERTIFICATE_CHAIN = resources.certificate_chain() |
||||||
|
_TEST_ROOT_CERTIFICATES = resources.test_root_certificates() |
||||||
|
_SERVER_CERTS = ((_PRIVATE_KEY, _CERTIFICATE_CHAIN),) |
||||||
|
_PROPERTY_OPTIONS = (( |
||||||
|
'grpc.ssl_target_name_override', |
||||||
|
_SERVER_HOST_OVERRIDE, |
||||||
|
),) |
||||||
|
|
||||||
|
|
||||||
|
def handle_unary_unary(request, servicer_context): |
||||||
|
return pickle.dumps({ |
||||||
|
_ID: servicer_context.peer_identities(), |
||||||
|
_ID_KEY: servicer_context.peer_identity_key(), |
||||||
|
_AUTH_CTX: servicer_context.auth_context() |
||||||
|
}) |
||||||
|
|
||||||
|
|
||||||
|
def start_secure_server(): |
||||||
|
handler = grpc.method_handlers_generic_handler('test', { |
||||||
|
'UnaryUnary': |
||||||
|
grpc.unary_unary_rpc_method_handler(handle_unary_unary) |
||||||
|
}) |
||||||
|
server = test_common.test_server() |
||||||
|
server.add_generic_rpc_handlers((handler,)) |
||||||
|
server_cred = grpc.ssl_server_credentials(_SERVER_CERTS) |
||||||
|
port = server.add_secure_port('[::]:0', server_cred) |
||||||
|
server.start() |
||||||
|
|
||||||
|
return server, port |
||||||
|
|
||||||
|
|
||||||
|
class SSLSessionCacheTest(unittest.TestCase): |
||||||
|
|
||||||
|
def _do_one_shot_client_rpc(self, channel_creds, channel_options, port, |
||||||
|
expect_ssl_session_reused): |
||||||
|
channel = grpc.secure_channel( |
||||||
|
'localhost:{}'.format(port), channel_creds, options=channel_options) |
||||||
|
response = channel.unary_unary(_UNARY_UNARY)(_REQUEST) |
||||||
|
auth_data = pickle.loads(response) |
||||||
|
self.assertEqual(expect_ssl_session_reused, |
||||||
|
auth_data[_AUTH_CTX]['ssl_session_reused']) |
||||||
|
channel.close() |
||||||
|
|
||||||
|
def testSSLSessionCacheLRU(self): |
||||||
|
server_1, port_1 = start_secure_server() |
||||||
|
|
||||||
|
cache = session_cache.ssl_session_cache_lru(1) |
||||||
|
channel_creds = grpc.ssl_channel_credentials( |
||||||
|
root_certificates=_TEST_ROOT_CERTIFICATES) |
||||||
|
channel_options = _PROPERTY_OPTIONS + ( |
||||||
|
('grpc.ssl_session_cache', cache),) |
||||||
|
|
||||||
|
# Initial connection has no session to resume |
||||||
|
self._do_one_shot_client_rpc( |
||||||
|
channel_creds, |
||||||
|
channel_options, |
||||||
|
port_1, |
||||||
|
expect_ssl_session_reused=[b'false']) |
||||||
|
|
||||||
|
# Connection to server_1 resumes from initial session |
||||||
|
self._do_one_shot_client_rpc( |
||||||
|
channel_creds, |
||||||
|
channel_options, |
||||||
|
port_1, |
||||||
|
expect_ssl_session_reused=[b'true']) |
||||||
|
|
||||||
|
# Connection to a different server with the same name overwrites the cache entry |
||||||
|
server_2, port_2 = start_secure_server() |
||||||
|
self._do_one_shot_client_rpc( |
||||||
|
channel_creds, |
||||||
|
channel_options, |
||||||
|
port_2, |
||||||
|
expect_ssl_session_reused=[b'false']) |
||||||
|
self._do_one_shot_client_rpc( |
||||||
|
channel_creds, |
||||||
|
channel_options, |
||||||
|
port_2, |
||||||
|
expect_ssl_session_reused=[b'true']) |
||||||
|
server_2.stop(None) |
||||||
|
|
||||||
|
# Connection to server_1 now falls back to full TLS handshake |
||||||
|
self._do_one_shot_client_rpc( |
||||||
|
channel_creds, |
||||||
|
channel_options, |
||||||
|
port_1, |
||||||
|
expect_ssl_session_reused=[b'false']) |
||||||
|
|
||||||
|
# Re-creating server_1 causes old sessions to become invalid |
||||||
|
server_1.stop(None) |
||||||
|
server_1, port_1 = start_secure_server() |
||||||
|
|
||||||
|
# Old sessions should no longer be valid |
||||||
|
self._do_one_shot_client_rpc( |
||||||
|
channel_creds, |
||||||
|
channel_options, |
||||||
|
port_1, |
||||||
|
expect_ssl_session_reused=[b'false']) |
||||||
|
|
||||||
|
# Resumption should work for subsequent connections |
||||||
|
self._do_one_shot_client_rpc( |
||||||
|
channel_creds, |
||||||
|
channel_options, |
||||||
|
port_1, |
||||||
|
expect_ssl_session_reused=[b'true']) |
||||||
|
server_1.stop(None) |
||||||
|
|
||||||
|
|
||||||
|
if __name__ == '__main__': |
||||||
|
unittest.main(verbosity=2) |
Loading…
Reference in new issue