Skip to content

Commit

Permalink
vmware: refresh the proxy support (#6)
Browse files Browse the repository at this point in the history
- 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 vmware/pyvmomi#799.
  • Loading branch information
goneri authored and Akasurde committed Jun 10, 2019
1 parent cefff7b commit 165b334
Show file tree
Hide file tree
Showing 5 changed files with 67 additions and 85 deletions.
2 changes: 2 additions & 0 deletions changelogs/fragments/52936-vmware-proxy-support.yaml
Original file line number Diff line number Diff line change
@@ -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)
56 changes: 15 additions & 41 deletions lib/ansible/module_utils/vmware.py
Original file line number Diff line number Diff line change
Expand Up @@ -486,31 +486,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 '<SCHEME>://<IP_ADDRESS>:<PORT>'")

return proxy_hostname, proxy_port


def connect_to_api(module, disconnect_atexit=True):
hostname = module.params['hostname']
username = module.params['username']
Expand Down Expand Up @@ -543,8 +529,8 @@ def connect_to_api(module, disconnect_atexit=True):
ssl_context.verify_mode = ssl.CERT_NONE

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,
Expand All @@ -553,26 +539,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))
Expand All @@ -581,20 +561,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.
Expand Down
54 changes: 35 additions & 19 deletions lib/ansible/modules/cloud/vmware/vmware_host.py
Original file line number Diff line number Diff line change
Expand Up @@ -495,31 +495,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
Expand Down
19 changes: 9 additions & 10 deletions lib/ansible/plugins/doc_fragments/vmware.py
Original file line number Diff line number Diff line change
Expand Up @@ -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
'''

Expand Down
21 changes: 6 additions & 15 deletions test/units/module_utils/test_vmware.py
Original file line number Diff line number Diff line change
Expand Up @@ -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',
]


Expand Down

0 comments on commit 165b334

Please sign in to comment.