diff options
-rw-r--r-- | debian/buildsys/pycentral/control.in | 2 | ||||
-rw-r--r-- | src/lib/Proxy.py | 150 |
2 files changed, 133 insertions, 19 deletions
diff --git a/debian/buildsys/pycentral/control.in b/debian/buildsys/pycentral/control.in index 91ef23261..97bf0822f 100644 --- a/debian/buildsys/pycentral/control.in +++ b/debian/buildsys/pycentral/control.in @@ -8,7 +8,7 @@ XS-Python-Version: >= 2.3 Package: bcfg2 Architecture: all -Depends: ${python:Depends}, debsums, python-apt, python-lxml (>= 0.9), ucf, lsb-base (>= 3.1-9) +Depends: ${python:Depends}, debsums, python-apt, python-lxml (>= 0.9), ucf, lsb-base (>= 3.1-9), python (>= 2.6) | python-m2crypto (>= 0.17) | python-ssl XB-Python-Version: ${python:Versions} Description: Configuration management client Bcfg2 is a configuration management system that generates configuration sets diff --git a/src/lib/Proxy.py b/src/lib/Proxy.py index ea07fee47..56390bf40 100644 --- a/src/lib/Proxy.py +++ b/src/lib/Proxy.py @@ -15,7 +15,19 @@ from xmlrpclib import _Method import httplib import logging import socket -import ssl + +# The ssl module is provided by either Python 2.6 or a separate ssl +# package that works on older versions of Python (see +# http://pypi.python.org/pypi/ssl). If neither can be found, look for +# M2Crypto instead. +try: + import ssl + SSL_LIB = 'py26_ssl' +except ImportError, e: + from M2Crypto import SSL + SSL_LIB = 'm2crypto' + + import string import sys import time @@ -23,6 +35,7 @@ import urlparse import xmlrpclib version = string.split(string.split(sys.version)[0], ".") +has_py23 = map(int, version) >= [2, 3] has_py26 = map(int, version) >= [2, 6] __all__ = ["ComponentProxy", "RetryMethod", "SSLHTTPConnection", "XMLRPCTransport"] @@ -63,8 +76,57 @@ class RetryMethod(_Method): xmlrpclib._Method = RetryMethod class SSLHTTPConnection(httplib.HTTPConnection): + + """Extension of HTTPConnection that implements SSL and related behaviors""" + + logger = logging.getLogger('Bcfg2.Proxy.SSLHTTPConnection') + def __init__(self, host, port=None, strict=None, timeout=90, key=None, cert=None, ca=None, scns=None, protocol='xmlrpc/ssl'): + """Initializes the `httplib.HTTPConnection` object and stores security + parameters + + Parameters + ---------- + host : string + Name of host to contact + port : int, optional + Port on which to contact the host. If none is specified, + the default port of 80 will be used unless the `host` + string has a port embedded in the form host:port. + strict : Boolean, optional + Passed to the `httplib.HTTPConnection` constructor and if + True, causes the `BadStatusLine` exception to be raised if + the status line cannot be parsed as a valid HTTP 1.0 or + 1.1 status. + timeout : int, optional + Causes blocking operations to timeout after `timeout` + seconds. + key : string, optional + The file system path to the local endpoint's SSL key. May + specify the same file as `cert` if using a file that + contains both. See + http://docs.python.org/library/ssl.html#ssl-certificates + for details. Required if using xmlrpc/ssl with client + certificate authentication. + cert : string, optional + The file system path to the local endpoint's SSL + certificate. May specify the same file as `cert` if using + a file that contains both. See + http://docs.python.org/library/ssl.html#ssl-certificates + for details. Required if using xmlrpc/ssl with client + certificate authentication. + ca : string, optional + The file system path to a set of concatenated certificate + authority certs, which are used to validate certificates + passed from the other end of the connection. + scns : array-like, optional + List of acceptable server commonNames. The peer cert's + common name must appear in this list, otherwise the + connect() call will throw a `CertificateError`. + protocol : {'xmlrpc/ssl', 'xmlrpc/tlsv1'}, optional + Communication protocol to use. + """ if not has_py26: httplib.HTTPConnection.__init__(self, host, port, strict) else: @@ -73,35 +135,87 @@ class SSLHTTPConnection(httplib.HTTPConnection): self.cert = cert self.ca = ca self.scns = scns - if self.ca: - self.ca_mode = ssl.CERT_REQUIRED - else: - self.ca_mode = ssl.CERT_NONE - if protocol == 'xmlrpc/ssl': - self.ssl_protocol = ssl.PROTOCOL_SSLv23 - elif protocol == 'xmlrpc/tlsv1': - self.ssl_protocol = ssl.PROTOCOL_TLSv1 + self.protocol = protocol + self.timeout = timeout + + def connect(self): + """Initiates a connection using previously set attributes""" + if SSL_LIB == 'py26_ssl': + self._connect_py26ssl() + elif SSL_LIB == 'm2crypto': + self._connect_m2crypto() else: - self.logger.error("Unknown protocol %s" % (protocol)) - raise Exception, "unknown protocol %s" % protocol + raise Exception, "No SSL module support" - def connect(self): + def _connect_py26ssl(self): + """Initiates a connection using the ssl module""" rawsock = socket.socket(socket.AF_INET, socket.SOCK_STREAM) - if has_py26: + if self.protocol == 'xmlrpc/ssl': + ssl_protocol_ver = ssl.PROTOCOL_SSLv23 + elif self.protocol == 'xmlrpc/tlsv1': + ssl_protocol_ver = ssl.PROTOCOL_TLSv1 + else: + self.logger.error("Unknown protocol %s" % (self.protocol)) + raise Exception, "unknown protocol %s" % self.protocol + if self.ca: + other_side_required = ssl.CERT_REQUIRED + else: + other_side_required = ssl.CERT_NONE + self.logger.warning("No ca is specified. Cannot authenticate the server with SSL.") + if self.cert and not self.key: + self.logger.warning("SSL cert specfied, but key. Cannot authenticate this client with SSL.") + self.cert = None + if self.key and not self.cert: + self.logger.warning("SSL key specfied, but no cert. Cannot authenticate this client with SSL.") + self.key = None + + if has_py23: rawsock.settimeout(self.timeout) - self.sock = ssl.SSLSocket(rawsock, cert_reqs=self.ca_mode, + self.sock = ssl.SSLSocket(rawsock, cert_reqs=other_side_required, ca_certs=self.ca, suppress_ragged_eofs=True, keyfile=self.key, certfile=self.cert, - ssl_version=self.ssl_protocol) + ssl_version=ssl_protocol_ver) self.sock.connect((self.host, self.port)) - pc = self.sock.getpeercert() - if pc and self.scns: - scn = [x[0][1] for x in pc['subject'] if x[0][0] == 'commonName'][0] + peer_cert = self.sock.getpeercert() + if peer_cert and self.scns: + scn = [x[0][1] for x in peer_cert['subject'] if x[0][0] == 'commonName'][0] if scn not in self.scns: raise CertificateError, scn self.sock.closeSocket = True + def _connect_m2crypto(self): + """Initiates a connection using the M2Crypto module""" + + if self.protocol == 'xmlrpc/ssl': + ctx = SSL.Context('sslv23') + elif self.protocol == 'xmlrpc/tlsv1': + ctx = SSL.Context('tlsv1') + else: + self.logger.error("Unknown protocol %s" % (self.protocol)) + raise Exception, "unknown protocol %s" % self.protocol + + if self.ca: + # Use the certificate authority to validate the cert + # presented by the server + ctx.set_verify(SSL.verify_peer | SSL.verify_fail_if_no_peer_cert, depth=9) + if ctx.load_verify_locations(self.ca) != 1: + raise Exception('No CA certs') + else: + self.logger.warning("No ca is specified. Cannot authenticate the server with SSL.") + + if self.cert and self.key: + # A cert/key is defined, use them to support client + # authentication to the server + ctx.load_cert(self.cert, self.key) + elif self.cert: + self.logger.warning("SSL cert specfied, but key. Cannot authenticate this client with SSL.") + elif self.key: + self.logger.warning("SSL key specfied, but no cert. Cannot authenticate this client with SSL.") + + self.sock = SSL.Connection(ctx) + self.sock.connect((self.host, self.port)) # automatically checks cert matches host + class XMLRPCTransport(xmlrpclib.Transport): def __init__(self, key=None, cert=None, ca=None, scns=None, use_datetime=0, timeout=90): |