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