2 Created on Nov 19, 2013
8 from subprocess import Popen, PIPE, call
13 from cygwin import Cygwin
14 from environment import Environment
25 class VMManagerException(Exception):
26 def __init__(self, value):
29 return repr(self.value)
36 def __init__(self, vendorid, productid, revision):
37 self.vendorid = vendorid.lower()
38 self.productid = productid.lower()
39 self.revision = revision.lower()
42 def __eq__(self, other):
43 return self.vendorid == other.vendorid and self.productid == other.productid and self.revision == other.revision
46 return hash(self.vendorid) ^ hash(self.productid) ^ hash(self.revision)
49 return "VendorId = \'" + str(self.vendorid) + "\' ProductId = \'" + str(self.productid) + "\' Revision = \'" + str(self.revision) + "\'"
52 class VMManager(object):
53 vmRootName = "SecurityDVM"
54 systemProperties = None
55 cygwin_path = 'c:\\cygwin64\\bin\\'
56 vboxManage = 'VBoxManage'
57 startNotifications = list()
60 #def __new__(cls, *args, **kwargs):
61 # if not cls._instance:
62 # cls._instance = super(VMManager, cls).__new__(cls, *args, **kwargs)
63 # return cls._instance
66 #def __new__(cls, *args, **kwargs):
67 # if not cls._instance:
68 # cls._instance = super(VMManager, cls).__new__(cls, *args, **kwargs)
69 # return cls._instance
72 self.cygwin_path = os.path.join(Cygwin.root(), 'bin') + os.path.sep
73 self.vboxManage = os.path.join(self.getVBoxManagePath(), 'VBoxManage')
74 self.systemProperties = self.getSystemProperties()
79 if VMManager._instance == None:
80 VMManager._instance = VMManager()
81 return VMManager._instance
83 def putStartNotification(self, ip):
84 self.startNotifications.append(ip)
86 def isSDVMStarted(self, ip):
87 return self.startNotifications.contains(ip)
89 def execute(self, cmd, wait_return=True ):
91 print('trying to launch: ' + cmd)
92 process = Popen(cmd, stdout=PIPE, stderr=PIPE) #shell = True
94 print('launched: ' + cmd)
96 return [0, 'working in background', '']
97 result = process.wait()
98 res_stdout = process.stdout.read();
99 res_stderr = process.stderr.read();
106 raise VMManagerException(res_stderr)
107 return result, res_stdout, res_stderr
109 def getVBoxManagePath(self):
110 """get the path to the VirtualBox installation on this system"""
113 k = _winreg.OpenKey(_winreg.HKEY_LOCAL_MACHINE, 'SOFTWARE\Oracle\VirtualBox')
114 p = _winreg.QueryValueEx(k, 'InstallDir')[0]
120 # return hosty system properties
121 def getSystemProperties(self):
122 cmd = self.vboxManage + ' list systemproperties'
123 result = self.execute(cmd)
126 props = dict((k.strip(),v.strip().strip('"')) for k,v in (line.split(':', 1) for line in result[1].strip().splitlines()))
129 # return the folder containing the guest VMs
130 def getDefaultMachineFolder(self):
131 return self.systemProperties["Default machine folder"]
133 #list the hostonly IFs exposed by the VBox host
134 def getHostOnlyIFs(self):
135 cmd = 'VBoxManage list hostonlyifs'
136 result = self.execute(cmd)[1]
139 props = dict((k.strip(),v.strip().strip('"')) for k,v in (line.split(':', 1) for line in result.strip().splitlines()))
143 cmd = 'VBoxManage list usbhost'
144 results = self.execute(cmd)[1]
145 results = results.split('Host USB Devices:')[1].strip()
147 items = list( "UUID:"+result for result in results.split('UUID:') if result != '')
151 for line in item.splitlines():
153 k,v = line[:line.index(':')].strip(), line[line.index(':')+1:].strip()
156 if 'Product' in props.keys() and props['Product'] == 'Mass Storage':
157 usb_filter = USBFilter( re.search(r"\((?P<vid>[0-9A-Fa-f]+)\)", props['VendorId']).groupdict()['vid'],
158 re.search(r"\((?P<pid>[0-9A-Fa-f]+)\)", props['ProductId']).groupdict()['pid'],
159 re.search(r"\((?P<rev>[0-9A-Fa-f]+)\)", props['Revision']).groupdict()['rev'] )
160 rsds[props['UUID']] = usb_filter;
165 # list all existing VMs registered with VBox
167 cmd = 'VBoxManage list vms'
168 result = self.execute(cmd)[1]
169 vms = list(k.strip().strip('"') for k,_ in (line.split(' ') for line in result.splitlines()))
172 # list existing SDVMs
177 if vm.startswith(self.vmRootName) and vm != self.vmRootName:
181 # generate valid (not already existing SDVM name). necessary for creating a new VM
182 def generateSDVMName(self):
184 for i in range(0,999):
185 if(not self.vmRootName+str(i) in vms):
186 return self.vmRootName+str(i)
189 # return the RSDs attached to all existing SDVMs
190 def getAttachedRSDs(self):
191 vms = self.listSDVM()
192 attached_devices = dict()
194 rsd_filter = self.getUSBFilter(vm)
195 if rsd_filter != None:
196 attached_devices[vm] = rsd_filter
197 return attached_devices
199 # configures hostonly networking and DHCP server. requires admin rights
200 def configureHostNetworking(self):
201 #cmd = 'vboxmanage list hostonlyifs'
203 #cmd = 'vboxmanage hostonlyif remove \"VirtualBox Host-Only Ethernet Adapter\"'
205 #cmd = 'vboxmanage hostonlyif create'
207 cmd = 'VBoxManage hostonlyif ipconfig \"VirtualBox Host-Only Ethernet Adapter\" --ip 192.168.56.1 --netmask 255.255.255.0'
209 #cmd = 'vboxmanage dhcpserver add'
211 cmd = 'VBoxManage dhcpserver modify --ifname \"VirtualBox Host-Only Ethernet Adapter\" --ip 192.168.56.100 --netmask 255.255.255.0 --lowerip 192.168.56.101 --upperip 192.168.56.200'
214 #create new virtual machine instance based on template vm named SecurityDVM (\SecurityDVM\SecurityDVM.vmdk)
215 def createVM(self, vm_name):
216 hostonly_if = self.getHostOnlyIFs()
217 machineFolder = self.getDefaultMachineFolder()
218 cmd = 'VBoxManage createvm --name ' + vm_name + ' --ostype Debian --register'
220 cmd = 'VBoxManage modifyvm ' + vm_name + ' --memory 512 --vram 10 --cpus 1 --usb on --usbehci on --nic1 hostonly --hostonlyadapter1 \"' + hostonly_if['Name'] + '\" --nic2 nat'
222 cmd = 'VBoxManage storagectl ' + vm_name + ' --name contr1 --add sata --portcount 2'
224 cmd = 'VBoxManage storageattach ' + vm_name + ' --storagectl contr1 --port 0 --device 0 --type hdd --medium \"'+ machineFolder + '\SecurityDVM\SecurityDVM.vmdk\"' #--mtype immutable
228 #remove VM from the system. should be used on VMs returned by listSDVMs
229 def removeVM(self, vm_name):
230 print('removing ' + vm_name)
231 cmd = 'VBoxManage unregistervm ' + vm_name + ' --delete'
232 print self.execute(cmd)
233 machineFolder = self.getDefaultMachineFolder()
234 cmd = self.cygwin_path + 'bash.exe --login -c \"rm -rf ' + machineFolder + '\\' + vm_name + '*\"'
235 print self.execute(cmd)
238 def startVM(self, vm_name):
239 print('starting ' + vm_name)
240 cmd = 'VBoxManage startvm ' + vm_name + ' --type headless'
241 result = self.execute(cmd)
242 while not string.find(str(result), 'successfully started',):
243 print "Failed to start SDVM: ", vm_name, " retrying"
245 result = self.execute(cmd)
249 def stopVM(self, vm_name):
250 print('stopping ' + vm_name)
251 cmd = 'VBoxManage controlvm ' + vm_name + ' poweroff'
254 # return the hostOnly IP for a running guest
255 def getHostOnlyIP(self, vm_name):
256 print('gettting hostOnly IP address ' + vm_name)
257 cmd = 'VBoxManage guestproperty get ' + vm_name + ' /VirtualBox/GuestInfo/Net/0/V4/IP'
258 result = self.execute(cmd)
262 if result.startswith('No value set!'):
264 return result[result.index(':')+1:].strip()
266 # attach removable storage device to VM by provision of filter
267 def attachRSD(self, vm_name, rsd_filter):
268 cmd = 'VBoxManage usbfilter add 0 --target ' + vm_name + ' --name OpenSecurityRSD --vendorid ' + rsd_filter.vendorid + ' --productid ' + rsd_filter.productid + ' --revision ' + rsd_filter.revision
269 print self.execute(cmd)
272 # return the description set for an existing VM
273 def getVMInfo(self, vm_name):
274 cmd = 'VBoxManage showvminfo ' + vm_name + ' --machinereadable'
275 results = self.execute(cmd)[1]
276 props = dict((k.strip(),v.strip().strip('"')) for k,v in (line.split('=', 1) for line in results.splitlines()))
279 # return the configured USB filter for an existing VM
280 def getUSBFilter(self, vm_name):
281 props = self.getVMInfo(vm_name)
282 keys = set(['USBFilterVendorId1', 'USBFilterProductId1', 'USBFilterRevision1'])
283 keyset = set(props.keys())
285 if keyset.issuperset(keys):
286 usb_filter = USBFilter(props['USBFilterVendorId1'], props['USBFilterProductId1'], props['USBFilterRevision1'])
289 #generates ISO containing authorized_keys for use with guest VM
290 def genCertificateISO(self, vm_name):
292 # create a SSH key pair in a machine subfolder
294 # to avoid any DOS window popping up we use
295 # the cygwin's class which relies on the
298 # shadowrun.exe is derived from a run.exe of
299 # the cygwin utilities but with a fix to
300 # avoid Console Windows to pop up.
302 # However, run.exe suffers from bad
303 # argument handling, when there are spaces
304 # within and so does shadowrun.exe
306 # In order to avoid any complex mechanics
307 # we start a bash script, which creates the
308 # SSH certificate in the local folder.
310 # Even more: to get rid of any potential
311 # space ' ' hazard in path names, we copy
312 # the script to the creation side as well.
314 # ... and yes: shadowrun.exe terminates
315 # with a ACCESS_VIOLATION and creates another
316 # stack-trace-dump file in the folder.
318 # But so does the original cygwin's run.exe
322 # (On the good side: the access violation happens
323 # *after* the wrapped process has been launched)
325 machineFolder = self.getDefaultMachineFolder()
326 vm_folder = os.path.join(machineFolder, vm_name)
327 old_dir = os.getcwd()
329 print(os.path.join(sys.path[0], 'create-cert-and-iso.sh'))
330 shutil.copy(os.path.join(sys.path[0], 'create-cert-and-iso.sh'), vm_folder)
331 p = Cygwin()(['/bin/bash', '-c', './create-cert-and-iso.sh'])
336 # attaches generated ssh public cert to guest vm
337 def attachCertificateISO(self, vm_name):
338 machineFolder = self.getDefaultMachineFolder()
339 cmd = 'vboxmanage storageattach ' + vm_name + ' --storagectl contr1 --port 1 --device 0 --type dvddrive --mtype readonly --medium \"' + machineFolder + '\\' + vm_name + '\\'+ vm_name + '.iso\"'
340 result = self.execute(cmd)
343 handleDeviceChangeLock = threading.Lock()
345 # handles device change events
346 def handleDeviceChange(self):
347 if VMManager.handleDeviceChangeLock.acquire(True):
350 attached_devices = self.getAttachedRSDs()
351 connected_devices = self.listRSDS()
352 for vm_name in attached_devices.keys():
353 if attached_devices[vm_name] not in connected_devices.values():
354 self.unmapNetworkDrive('h:')
356 self.removeVM(vm_name)
357 #create new vm for attached device if any
358 attached_devices = self.getAttachedRSDs()
359 for connected_device in connected_devices.values():
360 if (attached_devices and False) or (connected_device not in attached_devices.values()):
361 new_sdvm = self.generateSDVMName()
362 self.createVM(new_sdvm)
363 self.attachRSD(new_sdvm, connected_device)
366 self.startVM(new_sdvm)
367 # wait for machine to come up
368 while new_ip == None:
370 new_ip = self.getHostOnlyIP(new_sdvm)
371 while new_ip not in self.startNotifications:
374 self.mapNetworkDrive('h:', '\\\\' + new_ip + '\\USB', None, None)
375 #TODO: cleanup notifications somwhere else (eg. machine shutdown)
376 self.startNotifications.remove(new_ip)
377 VMManager.handleDeviceChangeLock.release()
380 def handleBrowsingRequest(self):
381 if VMManager.handleDeviceChangeLock.acquire(True):
383 new_sdvm = self.generateSDVMName()
384 self.createVM(new_sdvm)
385 self.genCertificateISO(new_sdvm)
386 self.attachCertificateISO(new_sdvm)
387 self.startVM(new_sdvm)
388 # wait for machine to come up
389 while new_ip == None:
391 new_ip = self.getHostOnlyIP(new_sdvm)
392 while new_ip not in self.startNotifications:
395 self.mapNetworkDrive('g:', '\\\\' + new_ip + '\\Download', None, None)
396 #TODO: cleanup notifications somwhere else (eg. machine shutdown)
397 self.startNotifications.remove(new_ip)
398 VMManager.handleDeviceChangeLock.release()
401 # executes command over ssh on guest vm
402 def sshGuestExecute(self, vm_name, prog, user_name='osecuser'):
404 address = self.getHostOnlyIP(vm_name)
405 machineFolder = self.getDefaultMachineFolder()
407 cmd = self.cygwin_path+'bash.exe --login -c \"ssh -o StrictHostKeyChecking=no -i \\\"' + machineFolder + '\\' + vm_name + '\\dvm_key\\\" ' + user_name + '@' + address + ' ' + prog + '\"'
408 return self.execute(cmd)
410 # executes command over ssh on guest vm with X forwarding
411 def sshGuestX11Execute(self, vm_name, prog, user_name='osecuser'):
412 #TODO: verify if X server is running on user account
413 #TODO: set DISPLAY accordingly
414 address = self.getHostOnlyIP(vm_name)
415 machineFolder = self.getDefaultMachineFolder()
418 #cmd = self.cygwin_path+'bash.exe --login -c \"DISPLAY=:0 ssh -v -Y -i \\\"' + machineFolder + '\\' + vm_name + '\\dvm_key\\\" ' + user_name + '@' + address + ' ' + prog + '\"'
419 cmd = self.cygwin_path+'mintty.exe -e /bin/env DISPLAY=:0 /usr/bin/ssh -o StrictHostKeyChecking=no -v -Y -i \"' + machineFolder + '\\' + vm_name + '\\dvm_key\" ' + user_name + '@' + address + ' ' + prog + ''
420 #cmd = self.cygwin_path+'mintty.exe -e /bin/bash --login -c \"DISPLAY=:0 /usr/bin/ssh -v -Y -i \\\"' + machineFolder + '\\' + vm_name + '\\dvm_key\\\" ' + user_name + '@' + address + ' ' + prog + '\"'
422 print('trying to launch: ' + cmd)
425 print('launched: ' + cmd)
428 #Small function to check the availability of network resource.
429 def isAvailable(self, path):
430 cmd = 'IF EXIST ' + path + ' echo YES'
431 result = subprocess.Popen(cmd, stdout=subprocess.PIPE, shell=True).communicate()
432 return string.find(str(result), 'YES',)
434 #Small function to check if the mention location is a directory
435 def isDirectory(self, path):
436 cmd = 'dir ' + path + ' | FIND ".."'
437 result = subprocess.Popen(cmd, stdout=subprocess.PIPE, shell=True).communicate()
438 return string.find(str(result), 'DIR',)
440 def mapNetworkDrive(self, drive, networkPath, user, password):
441 self.unmapNetworkDrive('h:')
442 #Check for drive availability
443 if self.isAvailable(drive) > -1:
444 print "Drive letter is already in use: ", drive
446 #Check for network resource availability
447 while self.isAvailable(networkPath) == -1:
449 print "Path not accessible: ", networkPath, " retrying"
452 #Prepare 'NET USE' commands
453 cmd = 'NET USE ' + drive + ' ' + networkPath
455 cmd = cmd + ' ' + password + ' /User' + user
458 #Execute 'NET USE' command with authentication
459 result = subprocess.Popen(cmd, stdout=subprocess.PIPE, shell=True).communicate()
460 print "Executed: ", cmd
461 if string.find(str(result), 'successfully',) == -1:
464 #Mapped with first try
467 def unmapNetworkDrive(self, drive):
468 #Check if the drive is in use
469 if self.isAvailable(drive) == -1:
472 #Prepare 'NET USE' command
473 cmd = 'net use ' + drive + ' /DELETE'
474 result = subprocess.Popen(cmd, stdout=subprocess.PIPE, shell=True).communicate()
475 if string.find(str(result), 'successfully',) == -1:
479 if __name__ == '__main__':
480 man = VMManager.getInstance()
481 #man.removeVM('SecurityDVM0')
482 #man.netUse('192.168.56.134', 'USB\\')
483 #ip = '192.168.56.139'
484 #man.mapNetworkDrive('h:', '\\\\' + ip + '\USB', None, None)
485 #man.cygwin_path = 'c:\\cygwin64\\bin\\'
486 #man.handleDeviceChange()
487 #print man.listSDVM()
488 #man.configureHostNetworking()
489 #new_vm = man.generateSDVMName()
490 #man.createVM(new_vm)
491 #man.genCertificateISO(new_vm)
492 #man.attachCertificateISO(new_vm)
494 #man.attachCertificateISO(vm_name)
495 #man.sshGuestExecute(vm_name, "ls")
496 man.sshGuestX11Execute('SecurityDVM1', '/usr/bin/iceweasel')
498 #cmd = "c:\\cygwin64\\bin\\bash.exe --login -c \"/bin/ls\""