You are viewing a plain text version of this content. The canonical link for it is here.
Posted to notifications@libcloud.apache.org by je...@apache.org on 2011/01/03 03:38:24 UTC
svn commit: r1054518 - in /incubator/libcloud/trunk/libcloud: base.py
httplib_ssl.py security.py
Author: jerry
Date: Mon Jan 3 02:38:23 2011
New Revision: 1054518
URL: http://svn.apache.org/viewvc?rev=1054518&view=rev
Log:
SSL Certificate Name Verification
- libcloud.security
- libcloud.security.VERIFY_SSL_CERT = False
- libcloud.security.CA_CERTS_PATH
- LibcloudHTTPSConnection, LibcloudHTTPConnection
Added:
incubator/libcloud/trunk/libcloud/httplib_ssl.py
incubator/libcloud/trunk/libcloud/security.py
Modified:
incubator/libcloud/trunk/libcloud/base.py
Modified: incubator/libcloud/trunk/libcloud/base.py
URL: http://svn.apache.org/viewvc/incubator/libcloud/trunk/libcloud/base.py?rev=1054518&r1=1054517&r2=1054518&view=diff
==============================================================================
--- incubator/libcloud/trunk/libcloud/base.py (original)
+++ incubator/libcloud/trunk/libcloud/base.py Mon Jan 3 02:38:23 2011
@@ -20,15 +20,17 @@ import httplib, urllib
import libcloud
from libcloud.types import NodeState, DeploymentError
from libcloud.ssh import SSHClient
+from libcloud.httplib_ssl import LibcloudHTTPSConnection
+from httplib import HTTPConnection as LibcloudHTTPConnection
import time
import hashlib
import StringIO
+import ssl
import os
import socket
import struct
from pipes import quote as pquote
-
class Node(object):
"""
A Base Node class to derive from.
@@ -257,13 +259,13 @@ class LoggingConnection():
cmd.extend([pquote("https://%s:%d%s" % (self.host, self.port, url))])
return " ".join(cmd)
-class LoggingHTTPSConnection(LoggingConnection, httplib.HTTPSConnection):
+class LoggingHTTPSConnection(LoggingConnection, LibcloudHTTPSConnection):
"""
Utility Class for logging HTTPS connections
"""
def getresponse(self):
- r = httplib.HTTPSConnection.getresponse(self)
+ r = LibcloudHTTPSConnection.getresponse(self)
if self.log is not None:
r, rv = self._log_response(r)
self.log.write(rv + "\n")
@@ -277,16 +279,15 @@ class LoggingHTTPSConnection(LoggingConn
self.log.write(pre +
self._log_curl(method, url, body, headers) + "\n")
self.log.flush()
- return httplib.HTTPSConnection.request(self, method, url,
- body, headers)
+ return LibcloudHTTPSConnection.request(self, method, url, body, headers)
-class LoggingHTTPConnection(LoggingConnection, httplib.HTTPConnection):
+class LoggingHTTPConnection(LoggingConnection, LibcloudHTTPConnection):
"""
Utility Class for logging HTTP connections
"""
def getresponse(self):
- r = httplib.HTTPConnection.getresponse(self)
+ r = LibcloudHTTPConnection.getresponse(self)
if self.log is not None:
r, rv = self._log_response(r)
self.log.write(rv + "\n")
@@ -300,7 +301,7 @@ class LoggingHTTPConnection(LoggingConne
self.log.write(pre +
self._log_curl(method, url, body, headers) + "\n")
self.log.flush()
- return httplib.HTTPConnection.request(self, method, url,
+ return LibcloudHTTPConnection.request(self, method, url,
body, headers)
class ConnectionKey(object):
@@ -315,8 +316,8 @@ class ConnectionKey(object):
# with upstream Python (see http://bugs.python.org/issue1589 for details)
# and not with libcloud.
- #conn_classes = (httplib.LoggingHTTPConnection, LoggingHTTPSConnection)
- conn_classes = (httplib.HTTPConnection, httplib.HTTPSConnection)
+ #conn_classes = (LoggingHTTPSConnection)
+ conn_classes = (LibcloudHTTPConnection, LibcloudHTTPSConnection)
responseCls = Response
connection = None
@@ -355,7 +356,9 @@ class ConnectionKey(object):
host = host or self.host
port = port or self.port[self.secure]
- connection = self.conn_classes[self.secure](host, port)
+ kwargs = {'host': host, 'port': port}
+
+ connection = self.conn_classes[self.secure](**kwargs)
# You can uncoment this line, if you setup a reverse proxy server
# which proxies to your endpoint, and lets you easily capture
# connections in cleartext when you setup the proxy to do SSL
@@ -439,8 +442,12 @@ class ConnectionKey(object):
# Removed terrible hack...this a less-bad hack that doesn't execute a
# request twice, but it's still a hack.
self.connect()
- self.connection.request(method=method, url=url, body=data,
- headers=headers)
+ try:
+ self.connection.request(method=method, url=url, body=data,
+ headers=headers)
+ except ssl.SSLError, e:
+ raise ssl.SSLError(str(e))
+
response = self.responseCls(self.connection.getresponse())
response.connection = self
return response
Added: incubator/libcloud/trunk/libcloud/httplib_ssl.py
URL: http://svn.apache.org/viewvc/incubator/libcloud/trunk/libcloud/httplib_ssl.py?rev=1054518&view=auto
==============================================================================
--- incubator/libcloud/trunk/libcloud/httplib_ssl.py (added)
+++ incubator/libcloud/trunk/libcloud/httplib_ssl.py Mon Jan 3 02:38:23 2011
@@ -0,0 +1,152 @@
+# 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.
+"""
+Subclass for httplib.HTTPSConnection with optional certificate name
+verification, depending on libcloud.security settings.
+"""
+import httplib
+import os
+import re
+import socket
+import ssl
+import warnings
+
+import libcloud.security
+
+class LibcloudHTTPSConnection(httplib.HTTPSConnection):
+ """LibcloudHTTPSConnection
+
+ Subclass of HTTPSConnection which verifies certificate names
+ if and only if CA certificates are available.
+ """
+ verify = False # does not verify
+ ca_cert = None # no default CA Certificate
+
+ def __init__(self, *args, **kwargs):
+ """Constructor
+ """
+ self._setup_verify()
+ httplib.HTTPSConnection.__init__(self, *args, **kwargs)
+
+ def _setup_verify(self):
+ """Setup Verify SSL or not
+
+ Reads security module's VERIFY_SSL_CERT and toggles whether
+ the class overrides the connect() class method or runs the
+ inherited httplib.HTTPSConnection connect()
+ """
+ self.verify = libcloud.security.VERIFY_SSL_CERT
+
+ if self.verify:
+ self._setup_ca_cert()
+ else:
+ warnings.warn(libcloud.security.VERIFY_SSL_DISABLED_MSG)
+
+ def _setup_ca_cert(self):
+ """Setup CA Certs
+
+ Search in CA_CERTS_PATH for valid candidates and
+ return first match. Otherwise, complain about certs
+ not being available.
+ """
+ if not self.verify:
+ return
+
+ ca_certs_available = [cert
+ for cert in libcloud.security.CA_CERTS_PATH
+ if os.path.exists(cert)]
+ if ca_certs_available:
+ # use first available certificate
+ self.ca_cert = ca_certs_available[0]
+ else:
+ # no certificates found; toggle verify to False
+ warnings.warn(libcloud.security.CA_CERTS_UNAVAILABLE_MSG)
+ self.ca_cert = None
+ self.verify = False
+
+ def connect(self):
+ """Connect
+
+ Checks if verification is toggled; if not, just call
+ httplib.HTTPSConnection's connect
+ """
+ if not self.verify:
+ return httplib.HTTPSConnection.connect(self)
+
+ # otherwise, create a connection and verify the hostname
+ sock = socket.create_connection((self.host, self.port),
+ self.timeout)
+ self.sock = ssl.wrap_socket(sock,
+ self.key_file,
+ self.cert_file,
+ cert_reqs=ssl.CERT_REQUIRED,
+ ca_certs=self.ca_cert,
+ ssl_version=ssl.PROTOCOL_TLSv1)
+ cert = self.sock.getpeercert()
+ if not self._verify_hostname(self.host, cert):
+ raise ssl.SSLError('Failed to verify hostname')
+
+ def _verify_hostname(self, hostname, cert):
+ """Verify hostname against peer cert
+
+ Check both commonName and entries in subjectAltName, using a
+ rudimentary glob to dns regex check to find matches
+ """
+ common_name = self._get_common_name(cert)
+ alt_names = self._get_subject_alt_names(cert)
+
+ # replace * with alphanumeric and dash
+ # replace . with literal .
+ valid_patterns = [
+ re.compile(
+ pattern.replace(
+ r".", r"\."
+ ).replace(
+ r"*", r"[0-9A-Za-z]+"
+ )
+ )
+ for pattern
+ in (set(common_name) | set(alt_names))
+ ]
+
+ return any(
+ pattern.search(hostname)
+ for pattern in valid_patterns
+ )
+
+ def _get_subject_alt_names(self, cert):
+ """Get SubjectAltNames
+
+ Retrieve 'subjectAltName' attributes from cert data structure
+ """
+ if 'subjectAltName' not in cert:
+ values = []
+ else:
+ values = [value
+ for field, value in cert['subjectAltName']
+ if field == 'DNS']
+ return values
+
+ def _get_common_name(self, cert):
+ """Get Common Name
+
+ Retrieve 'commonName' attribute from cert data structure
+ """
+ if 'subject' not in cert:
+ return None
+ values = [value[0][1]
+ for value in cert['subject']
+ if value[0][0] == 'commonName']
+ return values
Added: incubator/libcloud/trunk/libcloud/security.py
URL: http://svn.apache.org/viewvc/incubator/libcloud/trunk/libcloud/security.py?rev=1054518&view=auto
==============================================================================
--- incubator/libcloud/trunk/libcloud/security.py (added)
+++ incubator/libcloud/trunk/libcloud/security.py Mon Jan 3 02:38:23 2011
@@ -0,0 +1,51 @@
+# 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.
+"""
+Security (SSL) Settings
+
+Usage:
+ import libcloud.security
+ libcloud.security.VERIFY_SSL_CERT = True
+
+ # optional
+ libcloud.security.CA_CERTS_PATH.append("/path/to/cacert.txt")
+"""
+# For backward compatibility this option is disabled by default
+VERIFY_SSL_CERT = False
+
+# File containing one or more PEM-encoded CA certificates
+# concatenated together
+CA_CERTS_PATH = [
+ # centos/fedora: openssl
+ '/etc/pki/tls/certs/ca-bundle.crt',
+
+ # debian/ubuntu/arch/gentoo: ca-certificates
+ '/etc/ssl/certs/ca-certificates.crt',
+
+ # macports: curl-ca-bundle
+ '/opt/local/share/curl/curl-ca-bundle.crt',
+]
+
+CA_CERTS_UNAVAILABLE_MSG = (
+ 'Warning: No CA Certificates were found in CA_CERTS_PATH. '
+ 'Toggling VERIFY_SSL_CERT to False.'
+)
+
+VERIFY_SSL_DISABLED_MSG = (
+ 'SSL certificate verification is disabled, this can pose a '
+ 'security risk. For more information how to enable the SSL '
+ 'certificate verification, please visit the libcloud '
+ 'documentation.'
+)