Package modules :: Package machinery :: Module vsphere
[hide private]
[frames] | no frames]

Source Code for Module modules.machinery.vsphere

  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   
28 -class vSphere(Machinery):
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
37 - def __init__(self):
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
46 - def _initialize(self, module_name):
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
55 - def _initialize_check(self):
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
144 - def start(self, label, task):
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
160 - def stop(self, label):
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
174 - def dump_memory(self, label, path):
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
191 - def _list(self):
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
199 - def _status(self, label):
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
215 - def _get_virtual_machines(self, conn):
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
240 - def _get_virtual_machine_by_label(self, conn, label):
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
246 - def _get_snapshot_by_name(self, vm, name):
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
253 - def _get_snapshot_power_state(self, vm, name):
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
259 - def _create_snapshot(self, vm, name):
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
275 - def _delete_snapshot(self, vm, name):
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
295 - def _revert_snapshot(self, vm, name):
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
315 - def _download_snapshot(self, conn, vm, name, path):
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
382 - def _stop_virtual_machine(self, vm):
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
391 - def _wait_task(self, task):
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
410 - def _traverseSnapshots(self, root):
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