1    	#!@PYTHON@ -tt
2    	
3    	import sys
4    	import shutil, tempfile, suds
5    	import logging, requests
6    	import atexit, signal
7    	sys.path.append("@FENCEAGENTSLIBDIR@")
8    	
9    	from suds.client import Client
10   	from suds.sudsobject import Property
11   	from suds.transport.http import HttpAuthenticated
12   	from suds.transport import Reply, TransportError
13   	from fencing import *
14   	from fencing import fail, fail_usage, EC_STATUS, EC_LOGIN_DENIED, EC_INVALID_PRIVILEGES, EC_WAITING_ON, EC_WAITING_OFF
15   	from fencing import run_delay
16   	
17   	options_global = None
18   	conn_global = None
19   	
20   	class RequestsTransport(HttpAuthenticated):
21   		def __init__(self, **kwargs):
22   			self.cert = kwargs.pop('cert', None)
23   			self.verify = kwargs.pop('verify', True)
24   			self.session = requests.Session()
25   			# super won't work because not using new style class
26   			HttpAuthenticated.__init__(self, **kwargs)
27   	
28   		def send(self, request):
29   			self.addcredentials(request)
(1) Event Sigma main event: The `timeout` attribute is undefined or is set to `None`, which disables the timeouts on streaming connections. This makes it easier for an attacker to launch a Denial-of-Service (DoS) attack. Other problems can include large numbers of inactive connections that aren't being closed and running out of ephemeral ports.
(2) Event remediation: Explicitly set the `timeout` attribute to a value greater than 0.
30   			resp = self.session.post(request.url, data=request.message, headers=request.headers, cert=self.cert, verify=self.verify)
31   			result = Reply(resp.status_code, resp.headers, resp.content)
32   			return result
33   	
34   	def soap_login(options):
35   		run_delay(options)
36   	
37   		if "--ssl-secure" in options or "--ssl-insecure" in options:
38   			if "--ssl-insecure" in options:
39   				import ssl
40   				import urllib3
41   				if hasattr(ssl, '_create_unverified_context'):
42   					ssl._create_default_https_context = ssl._create_unverified_context
43   				urllib3.disable_warnings(urllib3.exceptions.InsecureRequestWarning)
44   				verify = False
45   			else:
46   				verify = True
47   			url = "https://"
48   		else:
49   			verify = False
50   			url = "http://"
51   	
52   		url += options["--ip"] + ":" + str(options["--ipport"]) + "/sdk"
53   	
54   		tmp_dir = tempfile.mkdtemp()
55   		tempfile.tempdir = tmp_dir
56   		atexit.register(remove_tmp_dir, tmp_dir)
57   	
58   		try:
59   			headers = {"Content-Type" : "text/xml;charset=UTF-8", "SOAPAction" : "vim25"}
60   			login_timeout = int(options["--login-timeout"]) or 15
61   			conn = Client(url + "/vimService.wsdl", location=url, transport=RequestsTransport(verify=verify), headers=headers, timeout=login_timeout)
62   	
63   			mo_ServiceInstance = Property('ServiceInstance')
64   			mo_ServiceInstance._type = 'ServiceInstance'
65   			ServiceContent = conn.service.RetrieveServiceContent(mo_ServiceInstance)
66   			mo_SessionManager = Property(ServiceContent.sessionManager.value)
67   			mo_SessionManager._type = 'SessionManager'
68   	
69   			conn.service.Login(mo_SessionManager, options["--username"], options["--password"])
70   		except requests.exceptions.SSLError as ex:
71   			fail_usage("Server side certificate verification failed: %s" % ex)
72   		except Exception as e:
73   			logging.error("Server side certificate verification failed: {}".format(str(e)))
74   			fail(EC_LOGIN_DENIED)
75   	
76   		options["ServiceContent"] = ServiceContent
77   		options["mo_SessionManager"] = mo_SessionManager
78   		return conn
79   	
80   	def process_results(results, machines, uuid, mappingToUUID):
81   		for m in results.objects:
82   			info = {}
83   			for i in m.propSet:
84   				info[i.name] = i.val
85   			# Prevent error KeyError: 'config.uuid' when reaching systems which P2V failed,
86   			# since these systems don't have a valid UUID
87   			if "config.uuid" in info:
88   				machines[info["name"]] = (info["config.uuid"], info["summary.runtime.powerState"])
89   				uuid[info["config.uuid"]] = info["summary.runtime.powerState"]
90   				mappingToUUID[m.obj.value] = info["config.uuid"]
91   	
92   		return (machines, uuid, mappingToUUID)
93   	
94   	def get_power_status(conn, options):
95   		mo_ViewManager = Property(options["ServiceContent"].viewManager.value)
96   		mo_ViewManager._type = "ViewManager"
97   	
98   		mo_RootFolder = Property(options["ServiceContent"].rootFolder.value)
99   		mo_RootFolder._type = "Folder"
100  	
101  		mo_PropertyCollector = Property(options["ServiceContent"].propertyCollector.value)
102  		mo_PropertyCollector._type = 'PropertyCollector'
103  	
104  		ContainerView = conn.service.CreateContainerView(mo_ViewManager, recursive=1,
105  				container=mo_RootFolder, type=['VirtualMachine'])
106  		mo_ContainerView = Property(ContainerView.value)
107  		mo_ContainerView._type = "ContainerView"
108  	
109  		FolderTraversalSpec = conn.factory.create('ns0:TraversalSpec')
110  		FolderTraversalSpec.name = "traverseEntities"
111  		FolderTraversalSpec.path = "view"
112  		FolderTraversalSpec.skip = False
113  		FolderTraversalSpec.type = "ContainerView"
114  	
115  		objSpec = conn.factory.create('ns0:ObjectSpec')
116  		objSpec.obj = mo_ContainerView
117  		objSpec.selectSet = [FolderTraversalSpec]
118  		objSpec.skip = True
119  	
120  		propSpec = conn.factory.create('ns0:PropertySpec')
121  		propSpec.all = False
122  		propSpec.pathSet = ["name", "summary.runtime.powerState", "config.uuid"]
123  		propSpec.type = "VirtualMachine"
124  	
125  		propFilterSpec = conn.factory.create('ns0:PropertyFilterSpec')
126  		propFilterSpec.propSet = [propSpec]
127  		propFilterSpec.objectSet = [objSpec]
128  	
129  		try:
130  			raw_machines = conn.service.RetrievePropertiesEx(mo_PropertyCollector, propFilterSpec)
131  		except Exception as e:
132  			logging.error("Failed: {}".format(str(e)))
133  			fail(EC_STATUS)
134  	
135  		(machines, uuid, mappingToUUID) = process_results(raw_machines, {}, {}, {})
136  	
137  	        # Probably need to loop over the ContinueRetreive if there are more results after 1 iteration.
138  		while hasattr(raw_machines, 'token'):
139  			try:
140  				raw_machines = conn.service.ContinueRetrievePropertiesEx(mo_PropertyCollector, raw_machines.token)
141  			except Exception as e:
142  				logging.error("Failed: {}".format(str(e)))
143  				fail(EC_STATUS)
144  			(more_machines, more_uuid, more_mappingToUUID) = process_results(raw_machines, {}, {}, {})
145  			machines.update(more_machines)
146  			uuid.update(more_uuid)
147  			mappingToUUID.update(more_mappingToUUID)
148  			# Do not run unnecessary SOAP requests
149  			if "--uuid" in options and options["--uuid"] in uuid:
150  				break
151  	
152  		if ["list", "monitor"].count(options["--action"]) == 1:
153  			return machines
154  		else:
155  			if "--uuid" not in options:
156  				if options["--plug"].startswith('/'):
157  					## Transform InventoryPath to UUID
158  					mo_SearchIndex = Property(options["ServiceContent"].searchIndex.value)
159  					mo_SearchIndex._type = "SearchIndex"
160  	
161  					vm = conn.service.FindByInventoryPath(mo_SearchIndex, options["--plug"])
162  	
163  					try:
164  						options["--uuid"] = mappingToUUID[vm.value]
165  					except KeyError:
166  						fail(EC_STATUS)
167  					except AttributeError:
168  						fail(EC_STATUS)
169  				else:
170  					## Name of virtual machine instead of path
171  					## warning: if you have same names of machines this won't work correctly
172  					try:
173  						(options["--uuid"], _) = machines[options["--plug"]]
174  					except KeyError:
175  						fail(EC_STATUS)
176  					except AttributeError:
177  						fail(EC_STATUS)
178  	
179  			try:
180  				if uuid[options["--uuid"]] == "poweredOn":
181  					return "on"
182  				else:
183  					return "off"
184  			except KeyError:
185  				fail(EC_STATUS)
186  	
187  	def set_power_status(conn, options):
188  		mo_SearchIndex = Property(options["ServiceContent"].searchIndex.value)
189  		mo_SearchIndex._type = "SearchIndex"
190  		vm = conn.service.FindByUuid(mo_SearchIndex, vmSearch=1, uuid=options["--uuid"])
191  	
192  		mo_machine = Property(vm.value)
193  		mo_machine._type = "VirtualMachine"
194  	
195  		try:
196  			if options["--action"] == "on":
197  				conn.service.PowerOnVM_Task(mo_machine)
198  			else:
199  				conn.service.PowerOffVM_Task(mo_machine)
200  		except suds.WebFault as ex:
201  			if (str(ex).find("Permission to perform this operation was denied")) >= 0:
202  				fail(EC_INVALID_PRIVILEGES)
203  			else:
204  				if options["--action"] == "on":
205  					fail(EC_WAITING_ON)
206  				else:
207  					fail(EC_WAITING_OFF)
208  	
209  	def remove_tmp_dir(tmp_dir):
210  		shutil.rmtree(tmp_dir)
211  	
212  	def logout():
213  		try:
214  			conn_global.service.Logout(options_global["mo_SessionManager"])
215  		except Exception:
216  			pass
217  	
218  	def signal_handler(signum, frame):
219  		raise Exception("Signal \"%d\" received which has triggered an exit of the process." % signum)
220  	
221  	def main():
222  		global options_global
223  		global conn_global
224  		device_opt = ["ipaddr", "login", "passwd", "web", "ssl", "notls", "port"]
225  	
226  		atexit.register(atexit_handler)
227  		atexit.register(logout)
228  	
229  		signal.signal(signal.SIGTERM, signal_handler)
230  	
231  		options_global = check_input(device_opt, process_input(device_opt))
232  	
233  		##
234  		## Fence agent specific defaults
235  		#####
236  		docs = {}
237  		docs["shortdesc"] = "Fence agent for VMWare over SOAP API"
238  		docs["longdesc"] = "fence_vmware_soap is a Power Fencing agent \
239  	which can be used with the virtual machines managed by VMWare products \
240  	that have SOAP API v4.1+. \
241  	\n.P\n\
242  	Name of virtual machine (-n / port) has to be used in inventory path \
243  	format (e.g. /datacenter/vm/Discovered virtual machine/myMachine). \
244  	In the cases when name of yours VM is unique you can use it instead. \
245  	Alternatively you can always use UUID to access virtual machine."
246  		docs["vendorurl"] = "http://www.vmware.com"
247  		show_docs(options_global, docs)
248  	
249  		logging.basicConfig(level=logging.INFO)
250  		logging.getLogger('suds.client').setLevel(logging.CRITICAL)
251  		logging.getLogger("requests").setLevel(logging.CRITICAL)
252  		logging.getLogger("urllib3").setLevel(logging.CRITICAL)
253  	
254  		##
255  		## Operate the fencing device
256  		####
257  		conn_global = soap_login(options_global)
258  	
259  		result = fence_action(conn_global, options_global, set_power_status, get_power_status, get_power_status)
260  	
261  		## Logout from system is done automatically via atexit()
262  		sys.exit(result)
263  	
264  	if __name__ == "__main__":
265  		main()
266