From 3d148d1f56b17c4a4d964f8ec5a95bcb1a402b75 Mon Sep 17 00:00:00 2001 From: Shawn Hartsock Date: Mon, 13 Apr 2015 11:16:22 -0400 Subject: [PATCH] optionally allow unverified certificates Toggles on and off strict host name verification in the standard python SSL library. A change was introduced to Python's SSL verification behavior with [PEP 0466](https://www.python.org/dev/peps/pep-0466/) which forces this library to consider a set of changes to preserve backward compatability. The intention of PEP 0466 is to improve SSL security for Python programmers but this changes default behaviors. This change proposes keeping the default behaviors undisturbed but forcing users and developers working in insecure environments to become aware of the change and adjust their security measures and code accordingly. fixes: #212, #235, and #179 --- pyVim/connect.py | 20 +- pyVmomi/SoapAdapter.py | 35 ++- tests/fixtures/vm_to_mac.yaml | 410 +++++++++++++++++++++++++++ tests/test_virtual_machine_object.py | 17 +- 4 files changed, 473 insertions(+), 9 deletions(-) create mode 100644 tests/fixtures/vm_to_mac.yaml diff --git a/pyVim/connect.py b/pyVim/connect.py index 21b3b4133..f0736867b 100644 --- a/pyVim/connect.py +++ b/pyVim/connect.py @@ -34,6 +34,7 @@ import requests from requests.auth import HTTPBasicAuth +import ssl from pyVmomi import vim, vmodl, SoapStubAdapter, SessionOrientedStub from pyVmomi.VmomiSupport import nsMap, versionIdMap, versionMap, IsChildVersion @@ -179,7 +180,8 @@ def _doLogin(soapStub): def Connect(host='localhost', port=443, user='root', pwd='', service="hostd", adapter="SOAP", namespace=None, path="/sdk", - version=None, keyFile=None, certFile=None): + version=None, keyFile=None, certFile=None, + unverified=False): """ Connect to the specified server, login and return the service instance object. @@ -213,6 +215,8 @@ def Connect(host='localhost', port=443, user='root', pwd='', @type keyFile: string @param certFile: ssl cert file path @type certFile: string + @param unverified: allow unsigned certificates + @type unverified: bool """ try: info = re.match(_rx, host) @@ -231,7 +235,7 @@ def Connect(host='localhost', port=443, user='root', pwd='', elif not version: version="vim.version.version6" si, stub = __Login(host, port, user, pwd, service, adapter, version, path, - keyFile, certFile) + keyFile, certFile, unverified) SetSi(si) return si @@ -266,7 +270,7 @@ def GetLocalTicket(si, user): ## connected service instance object. def __Login(host, port, user, pwd, service, adapter, version, path, - keyFile, certFile): + keyFile, certFile, unverified=False): """ Private method that performs the actual Connect and returns a connected service instance object. @@ -299,7 +303,8 @@ def __Login(host, port, user, pwd, service, adapter, version, path, # Create the SOAP stub adapter stub = SoapStubAdapter(host, port, version=version, path=path, - certKeyFile=keyFile, certFile=certFile) + certKeyFile=keyFile, certFile=certFile, + unverified=unverified) # Get Service instance si = vim.ServiceInstance("ServiceInstance", stub) @@ -532,7 +537,7 @@ def __FindSupportedVersion(protocol, server, port, path, preferredApiVersions): def SmartConnect(protocol='https', host='localhost', port=443, user='root', pwd='', service="hostd", path="/sdk", - preferredApiVersions=None): + preferredApiVersions=None, unverified=False): """ Determine the most preferred API version supported by the specified server, then connect to the specified server using that API version, login and return @@ -565,6 +570,8 @@ def SmartConnect(protocol='https', host='localhost', port=443, user='root', pwd= specified, the list of versions support by pyVmomi will be used. @type preferredApiVersions: string or string list + @param unverified: set to true to allow for unsigned certs + @type unverified: bool """ if preferredApiVersions is None: @@ -587,7 +594,8 @@ def SmartConnect(protocol='https', host='localhost', port=443, user='root', pwd= service=service, adapter='SOAP', version=supportedVersion, - path=path) + path=path, + unverified=unverified) def OpenUrlWithBasicAuth(url, user='root', pwd=''): """ diff --git a/pyVmomi/SoapAdapter.py b/pyVmomi/SoapAdapter.py index 5434a0cd0..6295c4d89 100644 --- a/pyVmomi/SoapAdapter.py +++ b/pyVmomi/SoapAdapter.py @@ -38,6 +38,9 @@ if PY3: from io import StringIO + +import warnings + from pyVmomi.VmomiSupport import * from pyVmomi.StubAdapterAccessorImpl import StubAdapterAccessorMixin import pyVmomi.Iso8601 @@ -1029,8 +1032,12 @@ def __init__(self, proxyPath): # SSLTunnelConnection def __call__(self, path, key_file=None, cert_file=None, **kwargs): # Don't pass any keyword args that HTTPConnection won't understand. + # see: https://docs.python.org/3/library/http.client.html for arg in kwargs.keys(): - if arg not in ("port", "strict", "timeout", "source_address"): + if arg not in ("port", "strict", "timeout", "source_address", + "context", "check_hostname"): + warnings.warn("http_client.HTTPConnection does not understand" + "the argument {0}".format(arg)) del kwargs[arg] tunnel = http_client.HTTPConnection(path, **kwargs) tunnel.request('CONNECT', self.proxyPath) @@ -1148,7 +1155,7 @@ def __init__(self, host='localhost', port=443, ns=None, path='/sdk', thumbprint=None, cacertsFile=None, version=None, acceptCompressedResponses=True, connectionPoolTimeout=CONNECTION_POOL_IDLE_TIMEOUT_SEC, - samlToken=None): + samlToken=None, unverified=False): if ns: assert(version is None) version = versionMap[ns] @@ -1184,6 +1191,9 @@ def __init__(self, host='localhost', port=443, ns=None, path='/sdk', else: self.thumbprint = None + self.unverified = unverified + + # SSL connection actually occurs if sslProxyPath: self.scheme = SSLTunnelConnection(sslProxyPath) elif httpProxyHost: @@ -1213,6 +1223,21 @@ def __init__(self, host='localhost', port=443, ns=None, path='/sdk', self._acceptCompressedResponses = acceptCompressedResponses + def _set_unverified_https_context(self): + self._create_default_https_context = None + if hasattr(ssl, '_create_unverified_context'): + # hold the normal HTTPS context creation method aside + warnings.warn("unverified=True was set on vSphere connection") + self._create_default_https_context = ssl._create_default_https_context + ssl._create_default_https_context = ssl._create_unverified_context + + + def _restore_default_https_context(self): + if self._create_default_https_context is not None: + ssl._create_default_https_context = _create_default_https_context + self._create_default_https_context = None + + # Context modifier used to modify the SOAP request. # @param func The func that takes in the serialized message and modifies the # the request. The func is appended to the requestModifierList and then @@ -1237,6 +1262,9 @@ def requestModifier(self, func): # deserialized object so that it's easier to distinguish an API error from # a connection error. def InvokeMethod(self, mo, info, args, outerStub=None): + if self.unverified: + self._set_unverified_https_context() + if outerStub is None: outerStub = self @@ -1302,6 +1330,9 @@ def InvokeMethod(self, mo, info, args, outerStub=None): conn.close() raise http_client.HTTPException("{0} {1}".format(resp.status, resp.reason)) + if self.unverified: + self._restore_default_https_context() + ## Clean up connection pool to throw away idle timed-out connections # SoapStubAdapter lock must be acquired before this method is called. def _CloseIdleConnections(self): diff --git a/tests/fixtures/vm_to_mac.yaml b/tests/fixtures/vm_to_mac.yaml new file mode 100644 index 000000000..63905b478 --- /dev/null +++ b/tests/fixtures/vm_to_mac.yaml @@ -0,0 +1,410 @@ +interactions: +- request: + body: null + headers: + Accept: ['*/*'] + Accept-Encoding: ['gzip, deflate'] + Connection: [keep-alive] + User-Agent: [python-requests/2.4.0 CPython/2.6.8 Darwin/13.4.0] + method: GET + uri: https://vcsa:443//sdk/vimServiceVersions.xml + response: + body: {string: !!python/unicode "\n\n\n \n urn:vim25\n 5.5\n + \ \n 5.1\n 5.0\n + \ 4.1\n 4.0\n 2.5u2\n + \ 2.5\n \n \n \n + \ urn:vim2\n 2.0\n \n\n"} + headers: + connection: [Keep-Alive] + content-length: ['530'] + content-type: [text/xml] + date: ['Fri, 10 Apr 2015 23:20:44 GMT'] + status: {code: 200, message: OK} +- request: + body: ' + + + + <_this type="ServiceInstance">ServiceInstance + + ' + headers: + Accept-Encoding: ['gzip, deflate'] + Content-Type: [text/xml; charset=UTF-8] + Cookie: [''] + SOAPAction: ['"urn:vim25/5.5"'] + method: POST + uri: https://vcsa:443/sdk + response: + body: {string: !!python/unicode "\n\n\ngroup-d1propertyCollectorViewManagerVMware vCenter + ServerVMware vCenter Server 5.5.0 build-1623101VMware, + Inc.5.5.01623101INTL000linux-x64vpxVirtualCenter5.57FE5EAEC-D956-4BF1-BC24-28D6242EA017VMware + VirtualCenter Server5.0VpxSettingsUserDirectorySessionManagerAuthorizationManagerServiceMgrPerfMgrScheduledTaskManagerAlarmManagerEventManagerTaskManagerExtensionManagerCustomizationSpecManagerCustomFieldsManagerDiagMgrLicenseManagerSearchIndexFileManagerDatastoreNamespaceManagervirtualDiskManagerSnmpSystemProvCheckerCompatCheckerOvfManagerIpPoolManagerDVSManagerHostProfileManagerClusterProfileManagerMoComplianceManagerLocalizationManagerStorageResourceManagerguestOperationsManager\n\n"} + headers: + cache-control: [no-cache] + connection: [Keep-Alive] + content-length: ['3599'] + content-type: [text/xml; charset=utf-8] + date: ['Fri, 10 Apr 2015 23:20:44 GMT'] + set-cookie: [vmware_soap_session="5271f341-16fd-3d6e-6d12-2bccb326c12d"; Path=/; + HttpOnly; Secure;] + status: {code: 200, message: OK} +- request: + body: ' + + + + <_this type="SessionManager">SessionManagermy_usermy_password + + ' + headers: + Accept-Encoding: ['gzip, deflate'] + Content-Type: [text/xml; charset=UTF-8] + Cookie: [vmware_soap_session="5271f341-16fd-3d6e-6d12-2bccb326c12d"; Path=/; + HttpOnly; Secure;] + SOAPAction: ['"urn:vim25/5.5"'] + method: POST + uri: https://vcsa:443/sdk + response: + body: {string: !!python/unicode "\n\n\n521b0b0d-cff3-0275-7afe-f8555219a9f5my_usermy_user + 2015-04-10T23:20:45.007085Z2015-04-10T23:20:45.007085Zenenfalse192.168.2.1000\n\n"} + headers: + cache-control: [no-cache] + connection: [Keep-Alive] + content-length: ['790'] + content-type: [text/xml; charset=utf-8] + date: ['Fri, 10 Apr 2015 23:20:45 GMT'] + status: {code: 200, message: OK} +- request: + body: ' + + + + <_this type="ServiceInstance">ServiceInstance + + ' + headers: + Accept-Encoding: ['gzip, deflate'] + Content-Type: [text/xml; charset=UTF-8] + Cookie: [vmware_soap_session="5271f341-16fd-3d6e-6d12-2bccb326c12d"; Path=/; + HttpOnly; Secure;] + SOAPAction: ['"urn:vim25/5.5"'] + method: POST + uri: https://vcsa:443/sdk + response: + body: {string: !!python/unicode "\n\n\ngroup-d1propertyCollectorViewManagerVMware vCenter + ServerVMware vCenter Server 5.5.0 build-1623101VMware, + Inc.5.5.01623101INTL000linux-x64vpxVirtualCenter5.57FE5EAEC-D956-4BF1-BC24-28D6242EA017VMware + VirtualCenter Server5.0VpxSettingsUserDirectorySessionManagerAuthorizationManagerServiceMgrPerfMgrScheduledTaskManagerAlarmManagerEventManagerTaskManagerExtensionManagerCustomizationSpecManagerCustomFieldsManagerDiagMgrLicenseManagerSearchIndexFileManagerDatastoreNamespaceManagervirtualDiskManagerSnmpSystemProvCheckerCompatCheckerOvfManagerIpPoolManagerDVSManagerHostProfileManagerClusterProfileManagerMoComplianceManagerLocalizationManagerStorageResourceManagerguestOperationsManager\n\n"} + headers: + cache-control: [no-cache] + connection: [Keep-Alive] + content-length: ['3599'] + content-type: [text/xml; charset=utf-8] + date: ['Fri, 10 Apr 2015 23:20:45 GMT'] + status: {code: 200, message: OK} +- request: + body: ' + + + + <_this type="SearchIndex">SearchIndexDatacenter0/vm/box + + ' + headers: + Accept-Encoding: ['gzip, deflate'] + Content-Type: [text/xml; charset=UTF-8] + Cookie: [vmware_soap_session="5271f341-16fd-3d6e-6d12-2bccb326c12d"; Path=/; + HttpOnly; Secure;] + SOAPAction: ['"urn:vim25/5.5"'] + method: POST + uri: https://vcsa:443/sdk + response: + body: {string: !!python/unicode "\n\n\nvm-722\n\n"} + headers: + cache-control: [no-cache] + connection: [Keep-Alive] + content-length: ['455'] + content-type: [text/xml; charset=utf-8] + date: ['Fri, 10 Apr 2015 23:20:45 GMT'] + status: {code: 200, message: OK} +- request: + body: ' + + + + <_this type="ServiceInstance">ServiceInstance + + ' + headers: + Accept-Encoding: ['gzip, deflate'] + Content-Type: [text/xml; charset=UTF-8] + Cookie: [vmware_soap_session="5271f341-16fd-3d6e-6d12-2bccb326c12d"; Path=/; + HttpOnly; Secure;] + SOAPAction: ['"urn:vim25/5.5"'] + method: POST + uri: https://vcsa:443/sdk + response: + body: {string: !!python/unicode "\n\n\ngroup-d1propertyCollectorViewManagerVMware vCenter + ServerVMware vCenter Server 5.5.0 build-1623101VMware, + Inc.5.5.01623101INTL000linux-x64vpxVirtualCenter5.57FE5EAEC-D956-4BF1-BC24-28D6242EA017VMware + VirtualCenter Server5.0VpxSettingsUserDirectorySessionManagerAuthorizationManagerServiceMgrPerfMgrScheduledTaskManagerAlarmManagerEventManagerTaskManagerExtensionManagerCustomizationSpecManagerCustomFieldsManagerDiagMgrLicenseManagerSearchIndexFileManagerDatastoreNamespaceManagervirtualDiskManagerSnmpSystemProvCheckerCompatCheckerOvfManagerIpPoolManagerDVSManagerHostProfileManagerClusterProfileManagerMoComplianceManagerLocalizationManagerStorageResourceManagerguestOperationsManager\n\n"} + headers: + cache-control: [no-cache] + connection: [Keep-Alive] + content-length: ['3599'] + content-type: [text/xml; charset=utf-8] + date: ['Fri, 10 Apr 2015 23:20:45 GMT'] + status: {code: 200, message: OK} +- request: + body: ' + + + + <_this type="PropertyCollector">propertyCollectorVirtualMachinefalsenamevm-722false1 + + ' + headers: + Accept-Encoding: ['gzip, deflate'] + Content-Type: [text/xml; charset=UTF-8] + Cookie: [vmware_soap_session="5271f341-16fd-3d6e-6d12-2bccb326c12d"; Path=/; + HttpOnly; Secure;] + SOAPAction: ['"urn:vim25/5.5"'] + method: POST + uri: https://vcsa:443/sdk + response: + body: {string: !!python/unicode "\n\n\nvm-722namebox\n\n"} + headers: + cache-control: [no-cache] + connection: [Keep-Alive] + content-length: ['559'] + content-type: [text/xml; charset=utf-8] + date: ['Fri, 10 Apr 2015 23:20:45 GMT'] + status: {code: 200, message: OK} +- request: + body: ' + + + + <_this type="PropertyCollector">propertyCollectorVirtualMachinefalseconfigvm-722false1 + + ' + headers: + Accept-Encoding: ['gzip, deflate'] + Content-Type: [text/xml; charset=UTF-8] + Cookie: [vmware_soap_session="5271f341-16fd-3d6e-6d12-2bccb326c12d"; Path=/; + HttpOnly; Secure;] + SOAPAction: ['"urn:vim25/5.5"'] + method: POST + uri: https://vcsa:443/sdk + response: + body: {string: !!python/unicode "\n\n\nvm-722config2015-04-10T21:56:46.79505Z1970-01-01T00:00:00ZboxUbuntu + Linux (64-bit)vmx-08420264ab-848b-1586-b589-b9bd3a71b3aa500221fe-3473-60ff-fab2-1811600208a0true564d7427-3c77-fede-9448-44dbaa170c80ubuntu64Guest[storage0] + box/box.vmx[storage0] box/[storage0] + box/[storage0] box/9216truetruetruetruemanualfalse0falsetruefalsefalsereleaseanyfalseautomatichvAutopowerOfffalsesofthardsoftsofthardsoftcheckpoint422048falsefalse200IDE + 00201IDE 113002300PS2 controller 00600700100PCI controller 005001200010004000400SIO controller 008000600Keyboard3000700Pointing device; Devicefalseautodetect3001500Video card100040961falsefalseautomatic12000Device on the virtual machine PCI bus that provides + support for the virtual machine communication interface3310017980530090false1000LSI Logic16100302000truenoSharing7200016,777,216 + KB[storage0] + box/box-000001.vmdkdatastore-21persistentfalsefalsetrue6000C295-0720-c428-8fe9-fcb547507d1350c631d86f4d7efcffa707ab954b86d8[storage0] + box/box.vmdkdatastore-21persistenttrue6000C295-0720-c428-8fe9-fcb547507d13977342514f17e373aa3ac64b22a4c16eredoLogFormatfalse10000167772161000normal-11000normal3002ATAPI + /vmfs/devices/cdrom/mpx.vmhba0:C0:T0:L0/vmfs/devices/cdrom/mpx.vmhba0:C0:T0:L0falsefalsetruefalseuntried20104000VM NetworkVM + Networkfalsenetwork-22truetruetrueok321007assigned00:50:56:82:28:7dtrue8000Remotefalsefalsetruefalseuntried40000false-11000normal0false-110240normalnormalfalsefalsefalse20480hpet0.presenttruenvrambox.nvramvirtualHW.productCompatibilityhostedpciBridge0.presenttruepciBridge4.presenttruescsi0.pciSlotNumber16ethernet0.pciSlotNumber32vmci0.pciSlotNumber33replay.supportedfalsesched.swap.derivedName/vmfs/volumes/501fa6d9-8907f56a-fa19-782bcb74158e/box/box-fbe87ab9.vswpreplay.filenamescsi0:0.redopciBridge0.pciSlotNumber17pciBridge4.pciSlotNumber21pciBridge5.pciSlotNumber22pciBridge6.pciSlotNumber23pciBridge7.pciSlotNumber24pciBridge4.virtualDevpcieRootPorttools.remindInstalltruehostCPUID.00000000b756e65476c65746e49656e69hostCPUID.1000206c220200800029ee3ffbfebfbffhostCPUID.800000010000000000000000000000012c100800guestCPUID.00000000b756e65476c65746e49656e69guestCPUID.1000206c200020800829822031fabfbffguestCPUID.8000000100000000000000000000000128100800userCPUID.00000000b756e65476c65746e49656e69userCPUID.1000206c220200800029822031fabfbffuserCPUID.8000000100000000000000000000000128100800evcCompatibilityModefalsevmotion.checkpointFBSize4194304softPowerOffFALSEcpuid.coresPerSocket2pciBridge4.functions8unity.wasCapablefalsepciBridge5.presenttruepciBridge5.virtualDevpcieRootPortpciBridge5.functions8pciBridge6.presenttruepciBridge6.virtualDevpcieRootPortpciBridge6.functions8pciBridge7.presenttruepciBridge7.virtualDevpcieRootPortpciBridge7.functions8vmware.tools.internalversion9216vmware.tools.requiredversion9216vmware.tools.installstatenonevmware.tools.lastInstallStatusunknownmigrate.hostLogStatenonemigrate.migrationId0storage0/vmfs/volumes/501fa6d9-8907f56a-fa19-782bcb74158einherit0falsefalse10000falsefalsebios40falsefalse207392768324444160falsefalsenevernone\n\n"} + headers: + cache-control: [no-cache] + connection: [Keep-Alive] + content-length: ['15380'] + content-type: [text/xml; charset=utf-8] + date: ['Fri, 10 Apr 2015 23:20:45 GMT'] + status: {code: 200, message: OK} +version: 1 diff --git a/tests/test_virtual_machine_object.py b/tests/test_virtual_machine_object.py index d95103596..aab876b50 100644 --- a/tests/test_virtual_machine_object.py +++ b/tests/test_virtual_machine_object.py @@ -14,9 +14,9 @@ # limitations under the License. from __future__ import print_function -import tests import vcr +import tests from pyVim import connect from pyVmomi import vim @@ -70,3 +70,18 @@ def test_vm_nic_data(self): if virtual_machine.guest: for net in virtual_machine.guest.net: self.assertTrue(net.macAddress in macs) + + @vcr.use_cassette('vm_to_mac.yaml', + cassette_library_dir=tests.fixtures_path, + record_mode='once') + def test_vm_to_mac(self): + si = connect.SmartConnect(host='vcsa', + user='my_user', + pwd='my_password') + content = si.RetrieveContent() + # where the name of the VM is 'box' + vm = content.searchIndex.FindByInventoryPath("Datacenter0/vm/box") + self.assertEqual(vm.name, 'box') + nics = [dev for dev in vm.config.hardware.device + if isinstance(dev, vim.vm.device.VirtualEthernetCard)] + self.assertEqual(nics[0].macAddress, '00:50:56:82:28:7d')