From 71f05bd90e1beb4c6d632b7fddda781b0254fcdd Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Gon=C3=A9ri=20Le=20Bouder?= Date: Mon, 10 Jun 2019 12:14:27 -0400 Subject: [PATCH] vmware: refresh the proxy support (#6) - simplify the way we pass the proxy paramerer with just two new parameters. - simplify a bit the way we format the error message - ensure `vmware_host` can also collaborate with a proxy This PR depends on https://github.com/vmware/pyvmomi/pull/799. --- .../fragments/52936-vmware-proxy-support.yaml | 2 + lib/ansible/module_utils/vmware.py | 56 +++++-------------- .../modules/cloud/vmware/vmware_host.py | 54 +++++++++++------- lib/ansible/plugins/doc_fragments/vmware.py | 19 +++---- test/units/module_utils/test_vmware.py | 21 ++----- 5 files changed, 67 insertions(+), 85 deletions(-) create mode 100644 changelogs/fragments/52936-vmware-proxy-support.yaml diff --git a/changelogs/fragments/52936-vmware-proxy-support.yaml b/changelogs/fragments/52936-vmware-proxy-support.yaml new file mode 100644 index 00000000000000..507469e8525539 --- /dev/null +++ b/changelogs/fragments/52936-vmware-proxy-support.yaml @@ -0,0 +1,2 @@ +minor_changes: +- vmware - The VMware modules can now access a server behind a HTTP proxy (https://github.com/ansible/ansible/pull/52936) diff --git a/lib/ansible/module_utils/vmware.py b/lib/ansible/module_utils/vmware.py index 29fe66c79c21b4..b685e3eb1671e0 100644 --- a/lib/ansible/module_utils/vmware.py +++ b/lib/ansible/module_utils/vmware.py @@ -490,31 +490,17 @@ def vmware_argument_spec(): default=True, fallback=(env_fallback, ['VMWARE_VALIDATE_CERTS']) ), - https_proxy=dict(type='str', - required=False, - default=None, - fallback=(env_fallback, ['https_proxy']) - ), - http_proxy=dict(type='str', + proxy_host=dict(type='str', required=False, default=None, - fallback=(env_fallback, ['http_proxy']) - ), + fallback=(env_fallback, ['VMWARE_PROXY_HOST'])), + proxy_port=dict(type='int', + required=False, + default=8080, + fallback=(env_fallback, ['VMWARE_PROXY_PORT'])), ) -def parse_proxy_url(module, proxy_url): - proxy_parts = generic_urlparse(urlparse(proxy_url)) - proxy_port = proxy_parts.get('port') or 80 - proxy_hostname = proxy_parts.get('hostname', None) - proxy_scheme = proxy_parts.get('scheme', '') - if proxy_hostname is None or proxy_scheme == '' or proxy_scheme not in ('https', 'http'): - module.fail_json(msg="Failed to parse proxy url. Please make sure you" - " provide proxy as '://:'") - - return proxy_hostname, proxy_port - - def connect_to_api(module, disconnect_atexit=True): hostname = module.params['hostname'] username = module.params['username'] @@ -548,8 +534,8 @@ def connect_to_api(module, disconnect_atexit=True): ssl_context.load_default_certs() service_instance = None - https_proxy = module.params.get('https_proxy') or None - http_proxy = module.params.get('http_proxy') or None + proxy_host = module.params.get('proxy_host') + proxy_port = module.params.get('proxy_port') connect_args = dict( host=hostname, @@ -558,26 +544,20 @@ def connect_to_api(module, disconnect_atexit=True): if ssl_context: connect_args.update(sslContext=ssl_context) - proxy_url, proxy_host, proxy_port = (None, None, None) - - if http_proxy or https_proxy: - proxy_url = http_proxy or https_proxy - proxy_host, proxy_port = parse_proxy_url(module, proxy_url=proxy_url) - + msg_suffix = '' try: - if proxy_url and proxy_host and proxy_port: + if proxy_host: connect_args.update(httpProxyHost=proxy_host, httpProxyPort=proxy_port) smart_stub = connect.SmartStubAdapter(**connect_args) session_stub = connect.VimSessionOrientedStub(smart_stub, connect.VimSessionOrientedStub.makeUserLoginMethod(username, password)) service_instance = vim.ServiceInstance('ServiceInstance', session_stub) + msg_suffix = " [proxy: %s:%d]" % (proxy_host, proxy_port) else: connect_args.update(user=username, pwd=password) service_instance = connect.SmartConnect(**connect_args) except vim.fault.InvalidLogin as invalid_login: msg = "Unable to log on to vCenter or ESXi API at %s:%s " % (hostname, port) - if proxy_url: - msg += "using proxy url %s" % proxy_url - module.fail_json(msg="%s as %s: %s" % (msg, username, invalid_login.msg)) + module.fail_json(msg="%s as %s: %s" % (msg, username, invalid_login.msg) + msg_suffix) except vim.fault.NoPermission as no_permission: module.fail_json(msg="User %s does not have required permission" " to log on to vCenter or ESXi API at %s:%s : %s" % (username, hostname, port, no_permission.msg)) @@ -586,20 +566,14 @@ def connect_to_api(module, disconnect_atexit=True): except vmodl.fault.InvalidRequest as invalid_request: # Request is malformed msg = "Failed to get a response from server %s:%s " % (hostname, port) - if proxy_url: - msg += "using proxy url %s" % proxy_url - module.fail_json(msg="%s as request is malformed: %s" % (msg, invalid_request.msg)) + module.fail_json(msg="%s as request is malformed: %s" % (msg, invalid_request.msg) + msg_suffix) except Exception as generic_exc: msg = "Unknown error while connecting to vCenter or ESXi API at %s:%s" % (hostname, port) - if proxy_url: - msg += " using proxy url %s" % proxy_url - module.fail_json(msg="%s : %s" % (msg, generic_exc)) + module.fail_json(msg="%s : %s" % (msg, generic_exc) + msg_suffix) if service_instance is None: msg = "Unknown error while connecting to vCenter or ESXi API at %s:%s" % (hostname, port) - if proxy_url: - msg += " using proxy url %s" % proxy_url - module.fail_json(msg=msg) + module.fail_json(msg=msg + msg_suffix) # Disabling atexit should be used in special cases only. # Such as IP change of the ESXi host which removes the connection anyway. diff --git a/lib/ansible/modules/cloud/vmware/vmware_host.py b/lib/ansible/modules/cloud/vmware/vmware_host.py index c6a5b6d40f54a3..f4a32e38c7d8ef 100644 --- a/lib/ansible/modules/cloud/vmware/vmware_host.py +++ b/lib/ansible/modules/cloud/vmware/vmware_host.py @@ -497,31 +497,47 @@ def get_host_connect_spec(self): Function to return Host connection specification Returns: host connection specification """ - host_connect_spec = vim.host.ConnectSpec() - host_connect_spec.hostName = self.esxi_hostname - host_connect_spec.userName = self.esxi_username - host_connect_spec.password = self.esxi_password - host_connect_spec.force = self.force_connection # Get the thumbprint of the SSL certificate if self.fetch_ssl_thumbprint and self.esxi_ssl_thumbprint == '': - # We need to grab the thumbprint manually because it's not included in - # the task error via an SSLVerifyFault exception anymore sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM) sock.settimeout(1) - wrapped_socket = ssl.wrap_socket(sock) - try: - wrapped_socket.connect((self.esxi_hostname, 443)) - except socket.error as socket_error: - self.module.fail_json(msg="Cannot connect to host : %s" % socket_error) + if self.module.params['proxy_host']: + sock.connect(( + self.module.params['proxy_host'], + self.module.params['proxy_port'])) + sock.send("CONNECT %s:443 HTTP/1.0\r\n\r\n" % (self.esxi_hostname)) + buf = sock.recv(8192) + if buf.split()[1] != '200': + self.module.fail_json(msg="Failed to connect to the proxy") + ctx = ssl.create_default_context() + ctx.check_hostname = False + ctx.verify_mode = ssl.CERT_NONE + der_cert_bin = ctx.wrap_socket(sock, server_hostname = self.esxi_hostname).getpeercert(True) + sock.close() else: - der_cert_bin = wrapped_socket.getpeercert(True) - # thumb_md5 = hashlib.md5(der_cert_bin).hexdigest() - thumb_sha1 = self.format_number(hashlib.sha1(der_cert_bin).hexdigest()) - # thumb_sha256 = hashlib.sha256(der_cert_bin).hexdigest() - wrapped_socket.close() - host_connect_spec.sslThumbprint = thumb_sha1 + wrapped_socket = ssl.wrap_socket(sock) + try: + wrapped_socket.connect((self.esxi_hostname, 443)) + except socket.error as socket_error: + self.module.fail_json(msg="Cannot connect to host : %s" % socket_error) + else: + der_cert_bin = wrapped_socket.getpeercert(True) + wrapped_socket.close() + + thumb_sha1 = self.format_number(hashlib.sha1(der_cert_bin).hexdigest()) + + # thumb_md5 = hashlib.md5(der_cert_bin).hexdigest() + # thumb_sha256 = hashlib.sha256(der_cert_bin).hexdigest() + sslThumbprint = thumb_sha1 else: - host_connect_spec.sslThumbprint = self.esxi_ssl_thumbprint + sslThumbprint = self.esxi_ssl_thumbprint + + host_connect_spec = vim.host.ConnectSpec() + host_connect_spec.sslThumbprint = sslThumbprint + host_connect_spec.hostName = self.esxi_hostname + host_connect_spec.userName = self.esxi_username + host_connect_spec.password = self.esxi_password + host_connect_spec.force = self.force_connection return host_connect_spec @staticmethod diff --git a/lib/ansible/plugins/doc_fragments/vmware.py b/lib/ansible/plugins/doc_fragments/vmware.py index 1003a605deab81..c7693fb1a7c4bd 100644 --- a/lib/ansible/plugins/doc_fragments/vmware.py +++ b/lib/ansible/plugins/doc_fragments/vmware.py @@ -46,21 +46,20 @@ class ModuleDocFragment(object): type: int default: 443 version_added: '2.5' - http_proxy: + proxy_host: description: - - Address of a proxy that will receive all HTTP requests and relay them. - - The format is a URL including a port number. For example, http://10.0.0.1:9090. - - If the value is not specified in the task, the value of environment variable C(https_proxy) will be used instead. + - Address of a proxy that will receive all HTTPS requests and relay them. + - The format is a hostname or a IP. + - If the value is not specified in the task, the value of environment variable C(VMWARE_PROXY_HOST) will be used instead. type: str - version_added: '2.8' + version_added: '2.9' required: False - https_proxy: + proxy_port: description: - - Address of a proxy that will receive all HTTPS requests and relay them. - - The format is a URL including a port number. For example, https://10.0.0.1:8443. - - If the value is not specified in the task, the value of environment variable C(https_proxy) will be used instead. + - Port of the HTTP proxy that will receive all HTTPS requests and relay them. + - If the value is not specified in the task, the value of environment variable C(VMWARE_PROXY_PORT) will be used instead. type: str - version_added: '2.8' + version_added: '2.9' required: False ''' diff --git a/test/units/module_utils/test_vmware.py b/test/units/module_utils/test_vmware.py index 2d729c544baadd..94b7f72d5a0748 100644 --- a/test/units/module_utils/test_vmware.py +++ b/test/units/module_utils/test_vmware.py @@ -59,35 +59,26 @@ username='Administrator@vsphere.local', password='Esxi@123$%', hostname='esxi1', - http_proxy='ftp://myproxyserver.com:80', + proxy_host='myproxyserver.com', validate_certs=False, ), - "Failed to parse proxy url" + "using proxy url myproxyserver.com:8080" ), ( dict( username='Administrator@vsphere.local', password='Esxi@123$%', hostname='esxi1', - http_proxy='http://myproxyserver.com:80', + proxy_host='myproxyserver.com', + proxy_port=80, validate_certs=False, ), - "using proxy url http://myproxyserver.com:80" - ), - ( - dict( - username='Administrator@vsphere.local', - password='Esxi@123$%', - hostname='esxi1', - http_proxy='https://myproxyserver.com:80', - validate_certs=False, - ), - "using proxy url https://myproxyserver.com:80" + "using proxy url myproxyserver.com:80" ), ] test_ids = ['hostname', 'username', 'password', 'validate_certs', - 'invalid_http_proxy', 'valid_http_proxy', 'valid_https_proxy', + 'valid_http_proxy', 'valid_https_proxy', ]