summaryrefslogtreecommitdiffstats
path: root/src/jaegertracing/thrift/lib/py/src/transport/TSSLSocket.py
diff options
context:
space:
mode:
Diffstat (limited to '')
-rw-r--r--src/jaegertracing/thrift/lib/py/src/transport/TSSLSocket.py408
1 files changed, 408 insertions, 0 deletions
diff --git a/src/jaegertracing/thrift/lib/py/src/transport/TSSLSocket.py b/src/jaegertracing/thrift/lib/py/src/transport/TSSLSocket.py
new file mode 100644
index 000000000..5b3ae5991
--- /dev/null
+++ b/src/jaegertracing/thrift/lib/py/src/transport/TSSLSocket.py
@@ -0,0 +1,408 @@
+#
+# Licensed to the Apache Software Foundation (ASF) under one
+# or more contributor license agreements. See the NOTICE file
+# distributed with this work for additional information
+# regarding copyright ownership. The ASF licenses this file
+# to you 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.
+#
+
+import logging
+import os
+import socket
+import ssl
+import sys
+import warnings
+
+from .sslcompat import _match_hostname, _match_has_ipaddress
+from thrift.transport import TSocket
+from thrift.transport.TTransport import TTransportException
+
+logger = logging.getLogger(__name__)
+warnings.filterwarnings(
+ 'default', category=DeprecationWarning, module=__name__)
+
+
+class TSSLBase(object):
+ # SSLContext is not available for Python < 2.7.9
+ _has_ssl_context = sys.hexversion >= 0x020709F0
+
+ # ciphers argument is not available for Python < 2.7.0
+ _has_ciphers = sys.hexversion >= 0x020700F0
+
+ # For python >= 2.7.9, use latest TLS that both client and server
+ # supports.
+ # SSL 2.0 and 3.0 are disabled via ssl.OP_NO_SSLv2 and ssl.OP_NO_SSLv3.
+ # For python < 2.7.9, use TLS 1.0 since TLSv1_X nor OP_NO_SSLvX is
+ # unavailable.
+ _default_protocol = ssl.PROTOCOL_SSLv23 if _has_ssl_context else \
+ ssl.PROTOCOL_TLSv1
+
+ def _init_context(self, ssl_version):
+ if self._has_ssl_context:
+ self._context = ssl.SSLContext(ssl_version)
+ if self._context.protocol == ssl.PROTOCOL_SSLv23:
+ self._context.options |= ssl.OP_NO_SSLv2
+ self._context.options |= ssl.OP_NO_SSLv3
+ else:
+ self._context = None
+ self._ssl_version = ssl_version
+
+ @property
+ def _should_verify(self):
+ if self._has_ssl_context:
+ return self._context.verify_mode != ssl.CERT_NONE
+ else:
+ return self.cert_reqs != ssl.CERT_NONE
+
+ @property
+ def ssl_version(self):
+ if self._has_ssl_context:
+ return self.ssl_context.protocol
+ else:
+ return self._ssl_version
+
+ @property
+ def ssl_context(self):
+ return self._context
+
+ SSL_VERSION = _default_protocol
+ """
+ Default SSL version.
+ For backwards compatibility, it can be modified.
+ Use __init__ keyword argument "ssl_version" instead.
+ """
+
+ def _deprecated_arg(self, args, kwargs, pos, key):
+ if len(args) <= pos:
+ return
+ real_pos = pos + 3
+ warnings.warn(
+ '%dth positional argument is deprecated.'
+ 'please use keyword argument instead.'
+ % real_pos, DeprecationWarning, stacklevel=3)
+
+ if key in kwargs:
+ raise TypeError(
+ 'Duplicate argument: %dth argument and %s keyword argument.'
+ % (real_pos, key))
+ kwargs[key] = args[pos]
+
+ def _unix_socket_arg(self, host, port, args, kwargs):
+ key = 'unix_socket'
+ if host is None and port is None and len(args) == 1 and key not in kwargs:
+ kwargs[key] = args[0]
+ return True
+ return False
+
+ def __getattr__(self, key):
+ if key == 'SSL_VERSION':
+ warnings.warn(
+ 'SSL_VERSION is deprecated.'
+ 'please use ssl_version attribute instead.',
+ DeprecationWarning, stacklevel=2)
+ return self.ssl_version
+
+ def __init__(self, server_side, host, ssl_opts):
+ self._server_side = server_side
+ if TSSLBase.SSL_VERSION != self._default_protocol:
+ warnings.warn(
+ 'SSL_VERSION is deprecated.'
+ 'please use ssl_version keyword argument instead.',
+ DeprecationWarning, stacklevel=2)
+ self._context = ssl_opts.pop('ssl_context', None)
+ self._server_hostname = None
+ if not self._server_side:
+ self._server_hostname = ssl_opts.pop('server_hostname', host)
+ if self._context:
+ self._custom_context = True
+ if ssl_opts:
+ raise ValueError(
+ 'Incompatible arguments: ssl_context and %s'
+ % ' '.join(ssl_opts.keys()))
+ if not self._has_ssl_context:
+ raise ValueError(
+ 'ssl_context is not available for this version of Python')
+ else:
+ self._custom_context = False
+ ssl_version = ssl_opts.pop('ssl_version', TSSLBase.SSL_VERSION)
+ self._init_context(ssl_version)
+ self.cert_reqs = ssl_opts.pop('cert_reqs', ssl.CERT_REQUIRED)
+ self.ca_certs = ssl_opts.pop('ca_certs', None)
+ self.keyfile = ssl_opts.pop('keyfile', None)
+ self.certfile = ssl_opts.pop('certfile', None)
+ self.ciphers = ssl_opts.pop('ciphers', None)
+
+ if ssl_opts:
+ raise ValueError(
+ 'Unknown keyword arguments: ', ' '.join(ssl_opts.keys()))
+
+ if self._should_verify:
+ if not self.ca_certs:
+ raise ValueError(
+ 'ca_certs is needed when cert_reqs is not ssl.CERT_NONE')
+ if not os.access(self.ca_certs, os.R_OK):
+ raise IOError('Certificate Authority ca_certs file "%s" '
+ 'is not readable, cannot validate SSL '
+ 'certificates.' % (self.ca_certs))
+
+ @property
+ def certfile(self):
+ return self._certfile
+
+ @certfile.setter
+ def certfile(self, certfile):
+ if self._server_side and not certfile:
+ raise ValueError('certfile is needed for server-side')
+ if certfile and not os.access(certfile, os.R_OK):
+ raise IOError('No such certfile found: %s' % (certfile))
+ self._certfile = certfile
+
+ def _wrap_socket(self, sock):
+ if self._has_ssl_context:
+ if not self._custom_context:
+ self.ssl_context.verify_mode = self.cert_reqs
+ if self.certfile:
+ self.ssl_context.load_cert_chain(self.certfile,
+ self.keyfile)
+ if self.ciphers:
+ self.ssl_context.set_ciphers(self.ciphers)
+ if self.ca_certs:
+ self.ssl_context.load_verify_locations(self.ca_certs)
+ return self.ssl_context.wrap_socket(
+ sock, server_side=self._server_side,
+ server_hostname=self._server_hostname)
+ else:
+ ssl_opts = {
+ 'ssl_version': self._ssl_version,
+ 'server_side': self._server_side,
+ 'ca_certs': self.ca_certs,
+ 'keyfile': self.keyfile,
+ 'certfile': self.certfile,
+ 'cert_reqs': self.cert_reqs,
+ }
+ if self.ciphers:
+ if self._has_ciphers:
+ ssl_opts['ciphers'] = self.ciphers
+ else:
+ logger.warning(
+ 'ciphers is specified but ignored due to old Python version')
+ return ssl.wrap_socket(sock, **ssl_opts)
+
+
+class TSSLSocket(TSocket.TSocket, TSSLBase):
+ """
+ SSL implementation of TSocket
+
+ This class creates outbound sockets wrapped using the
+ python standard ssl module for encrypted connections.
+ """
+
+ # New signature
+ # def __init__(self, host='localhost', port=9090, unix_socket=None,
+ # **ssl_args):
+ # Deprecated signature
+ # def __init__(self, host='localhost', port=9090, validate=True,
+ # ca_certs=None, keyfile=None, certfile=None,
+ # unix_socket=None, ciphers=None):
+ def __init__(self, host='localhost', port=9090, *args, **kwargs):
+ """Positional arguments: ``host``, ``port``, ``unix_socket``
+
+ Keyword arguments: ``keyfile``, ``certfile``, ``cert_reqs``,
+ ``ssl_version``, ``ca_certs``,
+ ``ciphers`` (Python 2.7.0 or later),
+ ``server_hostname`` (Python 2.7.9 or later)
+ Passed to ssl.wrap_socket. See ssl.wrap_socket documentation.
+
+ Alternative keyword arguments: (Python 2.7.9 or later)
+ ``ssl_context``: ssl.SSLContext to be used for SSLContext.wrap_socket
+ ``server_hostname``: Passed to SSLContext.wrap_socket
+
+ Common keyword argument:
+ ``validate_callback`` (cert, hostname) -> None:
+ Called after SSL handshake. Can raise when hostname does not
+ match the cert.
+ ``socket_keepalive`` enable TCP keepalive, default off.
+ """
+ self.is_valid = False
+ self.peercert = None
+
+ if args:
+ if len(args) > 6:
+ raise TypeError('Too many positional argument')
+ if not self._unix_socket_arg(host, port, args, kwargs):
+ self._deprecated_arg(args, kwargs, 0, 'validate')
+ self._deprecated_arg(args, kwargs, 1, 'ca_certs')
+ self._deprecated_arg(args, kwargs, 2, 'keyfile')
+ self._deprecated_arg(args, kwargs, 3, 'certfile')
+ self._deprecated_arg(args, kwargs, 4, 'unix_socket')
+ self._deprecated_arg(args, kwargs, 5, 'ciphers')
+
+ validate = kwargs.pop('validate', None)
+ if validate is not None:
+ cert_reqs_name = 'CERT_REQUIRED' if validate else 'CERT_NONE'
+ warnings.warn(
+ 'validate is deprecated. please use cert_reqs=ssl.%s instead'
+ % cert_reqs_name,
+ DeprecationWarning, stacklevel=2)
+ if 'cert_reqs' in kwargs:
+ raise TypeError('Cannot specify both validate and cert_reqs')
+ kwargs['cert_reqs'] = ssl.CERT_REQUIRED if validate else ssl.CERT_NONE
+
+ unix_socket = kwargs.pop('unix_socket', None)
+ socket_keepalive = kwargs.pop('socket_keepalive', False)
+ self._validate_callback = kwargs.pop('validate_callback', _match_hostname)
+ TSSLBase.__init__(self, False, host, kwargs)
+ TSocket.TSocket.__init__(self, host, port, unix_socket,
+ socket_keepalive=socket_keepalive)
+
+ def close(self):
+ try:
+ self.handle.settimeout(0.001)
+ self.handle = self.handle.unwrap()
+ except (ssl.SSLError, socket.error, OSError):
+ # could not complete shutdown in a reasonable amount of time. bail.
+ pass
+ TSocket.TSocket.close(self)
+
+ @property
+ def validate(self):
+ warnings.warn('validate is deprecated. please use cert_reqs instead',
+ DeprecationWarning, stacklevel=2)
+ return self.cert_reqs != ssl.CERT_NONE
+
+ @validate.setter
+ def validate(self, value):
+ warnings.warn('validate is deprecated. please use cert_reqs instead',
+ DeprecationWarning, stacklevel=2)
+ self.cert_reqs = ssl.CERT_REQUIRED if value else ssl.CERT_NONE
+
+ def _do_open(self, family, socktype):
+ plain_sock = socket.socket(family, socktype)
+ try:
+ return self._wrap_socket(plain_sock)
+ except Exception as ex:
+ plain_sock.close()
+ msg = 'failed to initialize SSL'
+ logger.exception(msg)
+ raise TTransportException(type=TTransportException.NOT_OPEN, message=msg, inner=ex)
+
+ def open(self):
+ super(TSSLSocket, self).open()
+ if self._should_verify:
+ self.peercert = self.handle.getpeercert()
+ try:
+ self._validate_callback(self.peercert, self._server_hostname)
+ self.is_valid = True
+ except TTransportException:
+ raise
+ except Exception as ex:
+ raise TTransportException(message=str(ex), inner=ex)
+
+
+class TSSLServerSocket(TSocket.TServerSocket, TSSLBase):
+ """SSL implementation of TServerSocket
+
+ This uses the ssl module's wrap_socket() method to provide SSL
+ negotiated encryption.
+ """
+
+ # New signature
+ # def __init__(self, host='localhost', port=9090, unix_socket=None, **ssl_args):
+ # Deprecated signature
+ # def __init__(self, host=None, port=9090, certfile='cert.pem', unix_socket=None, ciphers=None):
+ def __init__(self, host=None, port=9090, *args, **kwargs):
+ """Positional arguments: ``host``, ``port``, ``unix_socket``
+
+ Keyword arguments: ``keyfile``, ``certfile``, ``cert_reqs``, ``ssl_version``,
+ ``ca_certs``, ``ciphers`` (Python 2.7.0 or later)
+ See ssl.wrap_socket documentation.
+
+ Alternative keyword arguments: (Python 2.7.9 or later)
+ ``ssl_context``: ssl.SSLContext to be used for SSLContext.wrap_socket
+ ``server_hostname``: Passed to SSLContext.wrap_socket
+
+ Common keyword argument:
+ ``validate_callback`` (cert, hostname) -> None:
+ Called after SSL handshake. Can raise when hostname does not
+ match the cert.
+ """
+ if args:
+ if len(args) > 3:
+ raise TypeError('Too many positional argument')
+ if not self._unix_socket_arg(host, port, args, kwargs):
+ self._deprecated_arg(args, kwargs, 0, 'certfile')
+ self._deprecated_arg(args, kwargs, 1, 'unix_socket')
+ self._deprecated_arg(args, kwargs, 2, 'ciphers')
+
+ if 'ssl_context' not in kwargs:
+ # Preserve existing behaviors for default values
+ if 'cert_reqs' not in kwargs:
+ kwargs['cert_reqs'] = ssl.CERT_NONE
+ if'certfile' not in kwargs:
+ kwargs['certfile'] = 'cert.pem'
+
+ unix_socket = kwargs.pop('unix_socket', None)
+ self._validate_callback = \
+ kwargs.pop('validate_callback', _match_hostname)
+ TSSLBase.__init__(self, True, None, kwargs)
+ TSocket.TServerSocket.__init__(self, host, port, unix_socket)
+ if self._should_verify and not _match_has_ipaddress:
+ raise ValueError('Need ipaddress and backports.ssl_match_hostname '
+ 'module to verify client certificate')
+
+ def setCertfile(self, certfile):
+ """Set or change the server certificate file used to wrap new
+ connections.
+
+ @param certfile: The filename of the server certificate,
+ i.e. '/etc/certs/server.pem'
+ @type certfile: str
+
+ Raises an IOError exception if the certfile is not present or unreadable.
+ """
+ warnings.warn(
+ 'setCertfile is deprecated. please use certfile property instead.',
+ DeprecationWarning, stacklevel=2)
+ self.certfile = certfile
+
+ def accept(self):
+ plain_client, addr = self.handle.accept()
+ try:
+ client = self._wrap_socket(plain_client)
+ except (ssl.SSLError, socket.error, OSError):
+ logger.exception('Error while accepting from %s', addr)
+ # failed handshake/ssl wrap, close socket to client
+ plain_client.close()
+ # raise
+ # We can't raise the exception, because it kills most TServer derived
+ # serve() methods.
+ # Instead, return None, and let the TServer instance deal with it in
+ # other exception handling. (but TSimpleServer dies anyway)
+ return None
+
+ if self._should_verify:
+ client.peercert = client.getpeercert()
+ try:
+ self._validate_callback(client.peercert, addr[0])
+ client.is_valid = True
+ except Exception:
+ logger.warn('Failed to validate client certificate address: %s',
+ addr[0], exc_info=True)
+ client.close()
+ plain_client.close()
+ return None
+
+ result = TSocket.TSocket()
+ result.handle = client
+ return result