@ -99,6 +99,13 @@ try:
except :
except :
HAS_SSL = False
HAS_SSL = False
try :
# SNI Handling needs python2.7.9's SSLContext
from ssl import create_default_context , SSLContext
HAS_SSLCONTEXT = True
except ImportError :
HAS_SSLCONTEXT = False
HAS_MATCH_HOSTNAME = True
HAS_MATCH_HOSTNAME = True
try :
try :
from ssl import match_hostname , CertificateError
from ssl import match_hostname , CertificateError
@ -277,6 +284,13 @@ class NoSSLError(SSLValidationError):
class CustomHTTPSConnection ( httplib . HTTPSConnection ) :
class CustomHTTPSConnection ( httplib . HTTPSConnection ) :
def __init__ ( self , * args , * * kwargs ) :
httplib . HTTPSConnection . __init__ ( self , * args , * * kwargs )
if HAS_SSLCONTEXT :
self . context = create_default_context ( )
if self . cert_file :
self . context . load_cert_chain ( self . cert_file , self . key_file )
def connect ( self ) :
def connect ( self ) :
" Connect to a host on a given (SSL) port. "
" Connect to a host on a given (SSL) port. "
@ -287,6 +301,9 @@ class CustomHTTPSConnection(httplib.HTTPSConnection):
if self . _tunnel_host :
if self . _tunnel_host :
self . sock = sock
self . sock = sock
self . _tunnel ( )
self . _tunnel ( )
if HAS_SSLCONTEXT :
self . sock = self . context . wrap_socket ( sock , server_hostname = self . host )
else :
self . sock = ssl . wrap_socket ( sock , keyfile = self . key_file , certfile = self . cert_file , ssl_version = ssl . PROTOCOL_TLSv1 )
self . sock = ssl . wrap_socket ( sock , keyfile = self . key_file , certfile = self . cert_file , ssl_version = ssl . PROTOCOL_TLSv1 )
class CustomHTTPSHandler ( urllib2 . HTTPSHandler ) :
class CustomHTTPSHandler ( urllib2 . HTTPSHandler ) :
@ -462,9 +479,17 @@ class SSLValidationHandler(urllib2.BaseHandler):
return False
return False
return True
return True
def _make_context ( self , tmp_ca_cert_path ) :
context = create_default_context ( )
context . load_verify_locations ( tmp_ca_cert_path )
return context
def http_request ( self , req ) :
def http_request ( self , req ) :
tmp_ca_cert_path , paths_checked = self . get_ca_certs ( )
tmp_ca_cert_path , paths_checked = self . get_ca_certs ( )
https_proxy = os . environ . get ( ' https_proxy ' )
https_proxy = os . environ . get ( ' https_proxy ' )
context = None
if HAS_SSLCONTEXT :
context = self . _make_context ( tmp_ca_cert_path )
# Detect if 'no_proxy' environment variable is set and if our URL is included
# Detect if 'no_proxy' environment variable is set and if our URL is included
use_proxy = self . detect_no_proxy ( req . get_full_url ( ) )
use_proxy = self . detect_no_proxy ( req . get_full_url ( ) )
@ -486,13 +511,19 @@ class SSLValidationHandler(urllib2.BaseHandler):
s . sendall ( ' \r \n ' )
s . sendall ( ' \r \n ' )
connect_result = s . recv ( 4096 )
connect_result = s . recv ( 4096 )
self . validate_proxy_response ( connect_result )
self . validate_proxy_response ( connect_result )
ssl_s = ssl . wrap_socket ( s , ca_certs = tmp_ca_cert_path , cert_reqs = ssl . CERT_REQUIRED )
if context :
ssl_s = context . wrap_socket ( s , server_hostname = proxy_parts . get ( ' hostname ' ) )
else :
ssl_s = ssl . wrap_socket ( s , ca_certs = tmp_ca_cert_path , cert_reqs = ssl . CERT_REQUIRED , ssl_version = ssl . PROTOCOL_TLSv1 )
match_hostname ( ssl_s . getpeercert ( ) , self . hostname )
match_hostname ( ssl_s . getpeercert ( ) , self . hostname )
else :
else :
raise ProxyError ( ' Unsupported proxy scheme: %s . Currently ansible only supports HTTP proxies. ' % proxy_parts . get ( ' scheme ' ) )
raise ProxyError ( ' Unsupported proxy scheme: %s . Currently ansible only supports HTTP proxies. ' % proxy_parts . get ( ' scheme ' ) )
else :
else :
s . connect ( ( self . hostname , self . port ) )
s . connect ( ( self . hostname , self . port ) )
ssl_s = ssl . wrap_socket ( s , ca_certs = tmp_ca_cert_path , cert_reqs = ssl . CERT_REQUIRED )
if context :
ssl_s = context . wrap_socket ( s , server_hostname = self . hostname )
else :
ssl_s = ssl . wrap_socket ( s , ca_certs = tmp_ca_cert_path , cert_reqs = ssl . CERT_REQUIRED , ssl_version = ssl . PROTOCOL_TLSv1 )
match_hostname ( ssl_s . getpeercert ( ) , self . hostname )
match_hostname ( ssl_s . getpeercert ( ) , self . hostname )
# close the ssl connection
# close the ssl connection
#ssl_s.unwrap()
#ssl_s.unwrap()
@ -503,7 +534,12 @@ class SSLValidationHandler(urllib2.BaseHandler):
raise ConnectionError ( ' Failed to connect to %s : %s . ' % ( self . hostname , self . port ) )
raise ConnectionError ( ' Failed to connect to %s : %s . ' % ( self . hostname , self . port ) )
else :
else :
raise SSLValidationError ( ' Failed to validate the SSL certificate for %s : %s . '
raise SSLValidationError ( ' Failed to validate the SSL certificate for %s : %s . '
' Use validate_certs=False (insecure) or make sure your managed systems have a valid CA certificate installed. '
' Make sure your managed systems have a valid CA '
' certificate installed. If the website serving the url '
' uses SNI you need python >= 2.7.9 on your managed '
' machine. You can use validate_certs=False if you do '
' not need to confirm the server \ s identity but this is '
' unsafe and not recommended '
' Paths checked for this platform: %s ' % ( self . hostname , self . port , " , " . join ( paths_checked ) )
' Paths checked for this platform: %s ' % ( self . hostname , self . port , " , " . join ( paths_checked ) )
)
)
except CertificateError :
except CertificateError :
@ -534,8 +570,6 @@ def open_url(url, data=None, headers=None, method=None, use_proxy=True,
if parsed [ 0 ] == ' https ' and validate_certs :
if parsed [ 0 ] == ' https ' and validate_certs :
if not HAS_SSL :
if not HAS_SSL :
raise NoSSLError ( ' SSL validation is not available in your version of python. You can use validate_certs=False, however this is unsafe and not recommended ' )
raise NoSSLError ( ' SSL validation is not available in your version of python. You can use validate_certs=False, however this is unsafe and not recommended ' )
if not HAS_MATCH_HOSTNAME :
raise SSLValidationError ( ' Available SSL validation does not check that the certificate matches the hostname. You can install backports.ssl_match_hostname or update your managed machine to python-2.7.9 or newer. You could also use validate_certs=False, however this is unsafe and not recommended ' )
# do the cert validation
# do the cert validation
netloc = parsed [ 1 ]
netloc = parsed [ 1 ]
@ -630,13 +664,22 @@ def open_url(url, data=None, headers=None, method=None, use_proxy=True,
for header in headers :
for header in headers :
request . add_header ( header , headers [ header ] )
request . add_header ( header , headers [ header ] )
if sys . version_info < ( 2 , 6 , 0 ) :
urlopen_args = [ request , None ]
if sys . version_info > = ( 2 , 6 , 0 ) :
# urlopen in python prior to 2.6.0 did not
# urlopen in python prior to 2.6.0 did not
# have a timeout parameter
# have a timeout parameter
r = urllib2 . urlopen ( request , None )
urlopen_args . append ( timeout )
else :
r = urllib2 . urlopen ( request , None , timeout )
if HAS_SSLCONTEXT and not validate_certs :
# In 2.7.9, the default context validates certificates
context = SSLContext ( ssl . PROTOCOL_SSLv23 )
context . options | = ssl . OP_NO_SSLv2
context . options | = ssl . OP_NO_SSLv3
context . verify_mode = ssl . CERT_NONE
context . check_hostname = False
urlopen_args + = ( None , None , None , context )
r = urllib2 . urlopen ( * urlopen_args )
return r
return r
#
#