| Trees | Indices | Help |
|
|---|
|
|
1 # Copyright (C) 2015 eSentire, Inc (jacob.gajek@esentire.com).
2 # This file is part of Cuckoo Sandbox - http://www.cuckoosandbox.org
3 # See the file 'docs/LICENSE' for copying permission.
4
5 import datetime
6 import logging
7 import random
8 import re
9 import requests
10 import ssl
11 import time
12
13 from lib.cuckoo.common.abstracts import Machinery
14 from lib.cuckoo.common.exceptions import CuckooMachineError
15 from lib.cuckoo.common.exceptions import CuckooDependencyError
16 from lib.cuckoo.common.exceptions import CuckooCriticalError
17
18 try:
19 from pyVim.connect import SmartConnection
20 HAVE_PYVMOMI = True
21 except ImportError:
22 HAVE_PYVMOMI = False
23
24 log = logging.getLogger(__name__)
25 logging.getLogger("requests").setLevel(logging.WARNING)
26
27
29 """vSphere/ESXi machinery class based on pyVmomi Python SDK."""
30
31 # VM states
32 RUNNING = "poweredOn"
33 POWEROFF = "poweredOff"
34 SUSPENDED = "suspended"
35 ABORTED = "aborted"
36
38 if not HAVE_PYVMOMI:
39 raise CuckooDependencyError(
40 "Couldn't import pyVmomi, please install it (using "
41 "`pip install -U pyvmomi`)"
42 )
43
44 super(vSphere, self).__init__()
45
47 """Read configuration.
48 @param module_name: module name.
49 """
50 super(vSphere, self)._initialize(module_name)
51
52 # Initialize random number generator
53 random.seed()
54
56 """Runs checks against virtualization software when a machine manager
57 is initialized.
58 @raise CuckooCriticalError: if a misconfiguration or unsupported state
59 is found.
60 """
61 self.connect_opts = {}
62
63 if self.options.vsphere.host:
64 self.connect_opts["host"] = self.options.vsphere.host
65 else:
66 raise CuckooCriticalError(
67 "vSphere host address setting not found, please add it "
68 "to the config file."
69 )
70
71 if self.options.vsphere.port:
72 self.connect_opts["port"] = self.options.vsphere.port
73 else:
74 raise CuckooCriticalError(
75 "vSphere port setting not found, please add it to the "
76 "config file."
77 )
78
79 if self.options.vsphere.user:
80 self.connect_opts["user"] = self.options.vsphere.user
81 else:
82 raise CuckooCriticalError(
83 "vSphere username setting not found, please add it to "
84 "the config file."
85 )
86
87 if self.options.vsphere.pwd:
88 self.connect_opts["pwd"] = self.options.vsphere.pwd
89 else:
90 raise CuckooCriticalError(
91 "vSphere password setting not found, please add it to "
92 "the config file."
93 )
94
95 # Workaround for PEP-0476 issues in recent Python versions
96 if self.options.vsphere.unverified_ssl:
97 sslContext = ssl.SSLContext(ssl.PROTOCOL_TLSv1)
98 sslContext.verify_mode = ssl.CERT_NONE
99 self.connect_opts["sslContext"] = sslContext
100 log.warn("Turning off SSL certificate verification!")
101
102 # Check that a snapshot is configured for each machine
103 # and that it was taken in a powered-on state
104 try:
105 with SmartConnection(**self.connect_opts) as conn:
106 for machine in self.machines():
107 if not machine.snapshot:
108 raise CuckooCriticalError(
109 "Snapshot name not specified for machine %s, "
110 "please add it to the config file." %
111 machine.label
112 )
113
114 vm = self._get_virtual_machine_by_label(conn, machine.label)
115 if not vm:
116 raise CuckooCriticalError(
117 "Unable to find machine %s on vSphere host, "
118 "please update your configuration." %
119 machine.label
120 )
121
122 state = self._get_snapshot_power_state(vm, machine.snapshot)
123 if not state:
124 raise CuckooCriticalError(
125 "Unable to find snapshot %s for machine %s, "
126 "please update your configuration." %
127 (machine.snapshot, machine.label)
128 )
129
130 if state != self.RUNNING:
131 raise CuckooCriticalError(
132 "Snapshot for machine %s not in powered-on "
133 "state, please create one." % machine.label
134 )
135 except CuckooCriticalError as e:
136 raise e
137 except Exception as e:
138 raise CuckooCriticalError(
139 "Couldn't connect to vSphere host: %s" % e
140 )
141
142 super(vSphere, self)._initialize_check()
143
145 """Start a machine.
146 @param label: machine name.
147 @param task: task object.
148 @raise CuckooMachineError: if unable to start machine.
149 """
150 name = self.db.view_machine_by_label(label).snapshot
151 with SmartConnection(**self.connect_opts) as conn:
152 vm = self._get_virtual_machine_by_label(conn, label)
153 if vm:
154 self._revert_snapshot(vm, name)
155 else:
156 raise CuckooMachineError(
157 "Machine %s not found on host" % label
158 )
159
161 """Stop a machine.
162 @param label: machine name.
163 @raise CuckooMachineError: if unable to stop machine
164 """
165 with SmartConnection(**self.connect_opts) as conn:
166 vm = self._get_virtual_machine_by_label(conn, label)
167 if vm:
168 self._stop_virtual_machine(vm)
169 else:
170 raise CuckooMachineError(
171 "Machine %s not found on host" % label
172 )
173
175 """Take a memory dump of a machine.
176 @param path: path to where to store the memory dump
177 @raise CuckooMachineError: if error taking the memory dump
178 """
179 name = "cuckoo_memdump_{0}".format(random.randint(100000, 999999))
180 with SmartConnection(**self.connect_opts) as conn:
181 vm = self._get_virtual_machine_by_label(conn, label)
182 if vm:
183 self._create_snapshot(vm, name)
184 self._download_snapshot(conn, vm, name, path)
185 self._delete_snapshot(vm, name)
186 else:
187 raise CuckooMachineError(
188 "Machine %s not found on host" % label
189 )
190
192 """List virtual machines on vSphere host"""
193 ret = []
194 with SmartConnection(**self.connect_opts) as conn:
195 for vm in self._get_virtual_machines(conn):
196 ret.append(vm.summary.config.name)
197 return ret
198
200 """Get power state of vm from vSphere host.
201 @param label: virtual machine name
202 @raise CuckooMachineError: if error getting status or machine not found
203 """
204 with SmartConnection(**self.connect_opts) as conn:
205 vm = self._get_virtual_machine_by_label(conn, label)
206 if not vm:
207 raise CuckooMachineError(
208 "Machine %s not found on server" % label
209 )
210
211 status = vm.runtime.powerState
212 self.set_status(label, status)
213 return status
214
216 """Iterate over all VirtualMachine managed objects on vSphere host"""
217 def traverseDCFolders(conn, nodes, path=""):
218 for node in nodes:
219 if hasattr(node, "childEntity"):
220 for child, childpath in traverseDCFolders(conn, node.childEntity, path + node.name + "/"):
221 yield child, childpath
222 else:
223 yield node, path + node.name
224
225 def traverseVMFolders(conn, nodes):
226 for node in nodes:
227 if hasattr(node, "childEntity"):
228 for child in traverseVMFolders(conn, node.childEntity):
229 yield child
230 else:
231 yield node
232
233 self.VMtoDC = {}
234
235 for dc, dcpath in traverseDCFolders(conn, conn.content.rootFolder.childEntity):
236 for vm in traverseVMFolders(conn, dc.vmFolder.childEntity):
237 self.VMtoDC[vm.summary.config.name] = dcpath
238 yield vm
239
241 """Return the named VirtualMachine managed object"""
242 for vm in self._get_virtual_machines(conn):
243 if vm.summary.config.name == label:
244 return vm
245
247 """Return the named VirtualMachineSnapshot managed object for
248 a virtual machine"""
249 for ss in self._traverseSnapshots(vm.snapshot.rootSnapshotList):
250 if ss.name == name:
251 return ss.snapshot
252
254 """Return the power state for a named VirtualMachineSnapshot object"""
255 for ss in self._traverseSnapshots(vm.snapshot.rootSnapshotList):
256 if ss.name == name:
257 return ss.state
258
260 """Create named snapshot of virtual machine"""
261 log.info(
262 "Creating snapshot %s for machine %s",
263 name, vm.summary.config.name
264 )
265
266 task = vm.CreateSnapshot_Task(name=name,
267 description="Created by Cuckoo sandbox",
268 memory=True,
269 quiesce=False)
270 try:
271 self._wait_task(task)
272 except CuckooMachineError as e:
273 raise CuckooMachineError("CreateSnapshot: %s" % e)
274
276 """Remove named snapshot of virtual machine"""
277 snapshot = self._get_snapshot_by_name(vm, name)
278 if snapshot:
279 log.info(
280 "Removing snapshot %s for machine %s",
281 name, vm.summary.config.name
282 )
283
284 task = snapshot.RemoveSnapshot_Task(removeChildren=True)
285 try:
286 self._wait_task(task)
287 except CuckooMachineError as e:
288 log.error("RemoveSnapshot: {0}".format(e))
289 else:
290 raise CuckooMachineError(
291 "Snapshot %s for machine %s not found" %
292 (name, vm.summary.config.name)
293 )
294
296 """Revert virtual machine to named snapshot"""
297 snapshot = self._get_snapshot_by_name(vm, name)
298 if snapshot:
299 log.info(
300 "Reverting machine %s to snapshot %s",
301 vm.summary.config.name, name
302 )
303
304 task = snapshot.RevertToSnapshot_Task()
305 try:
306 self._wait_task(task)
307 except CuckooMachineError as e:
308 raise CuckooMachineError("RevertToSnapshot: %s" % e)
309 else:
310 raise CuckooMachineError(
311 "Snapshot %s for machine %s not found" %
312 (name, vm.summary.config.name)
313 )
314
316 """Download snapshot file from host to local path"""
317
318 # Get filespec to .vmsn or .vmem file of named snapshot
319 snapshot = self._get_snapshot_by_name(vm, name)
320 if not snapshot:
321 raise CuckooMachineError(
322 "Snapshot %s for machine %s not found" %
323 (name, vm.summary.config.name)
324 )
325
326 memorykey = datakey = filespec = None
327 for s in vm.layoutEx.snapshot:
328 if s.key == snapshot:
329 memorykey = s.memoryKey
330 datakey = s.dataKey
331 break
332
333 for f in vm.layoutEx.file:
334 if f.key == memorykey and (f.type == "snapshotMemory" or
335 f.type == "suspendMemory"):
336 filespec = f.name
337 break
338
339 if not filespec:
340 for f in vm.layoutEx.file:
341 if f.key == datakey and f.type == "snapshotData":
342 filespec = f.name
343 break
344
345 if not filespec:
346 raise CuckooMachineError("Could not find snapshot memory file")
347
348 log.info("Downloading memory dump %s to %s", filespec, path)
349
350 # Parse filespec to get datastore and file path.
351 datastore, filepath = re.match(r"\[([^\]]*)\] (.*)", filespec).groups()
352
353 # Construct URL request
354 params = {
355 "dsName": datastore,
356 "dcPath": self.VMtoDC.get(vm.summary.config.name, "ha-datacenter")
357 }
358 headers = {
359 "Cookie": conn._stub.cookie,
360 }
361 url = "https://%s:%s/folder/%s" % (
362 self.connect_opts["host"], self.connect_opts["port"], filepath
363 )
364
365 # Stream download to specified local path
366 try:
367 response = requests.get(url, params=params, headers=headers,
368 verify=False, stream=True)
369
370 response.raise_for_status()
371
372 with open(path, "wb") as localfile:
373 for chunk in response.iter_content(16*1024):
374 localfile.write(chunk)
375
376 except Exception as e:
377 raise CuckooMachineError(
378 "Error downloading memory dump %s: %s" %
379 (filespec, e)
380 )
381
383 """Power off a virtual machine"""
384 log.info("Powering off virtual machine %s", vm.summary.config.name)
385 task = vm.PowerOffVM_Task()
386 try:
387 self._wait_task(task)
388 except CuckooMachineError as e:
389 log.error("PowerOffVM: %s", e)
390
392 """Wait for a task to complete with timeout"""
393 limit = datetime.timedelta(
394 seconds=int(self.options_globals.timeouts.vm_state)
395 )
396 start = datetime.datetime.utcnow()
397
398 while True:
399 if task.info.state == "error":
400 raise CuckooMachineError("Task error")
401
402 if task.info.state == "success":
403 break
404
405 if datetime.datetime.utcnow() - start > limit:
406 raise CuckooMachineError("Task timed out")
407
408 time.sleep(1)
409
411 """Recursive depth-first traversal of snapshot tree"""
412 for node in root:
413 if len(node.childSnapshotList) > 0:
414 for child in self._traverseSnapshots(node.childSnapshotList):
415 yield child
416 yield node
417
| Trees | Indices | Help |
|
|---|
| Generated by Epydoc 3.0.1 on Fri Nov 4 23:21:59 2016 | http://epydoc.sourceforge.net |