2 Created on Nov 19, 2013
8 from subprocess import Popen, PIPE, call
13 from cygwin import Cygwin
23 class VMManagerException(Exception):
24 def __init__(self, value):
27 return repr(self.value)
34 def __init__(self, vendorid, productid, revision):
35 self.vendorid = vendorid.lower()
36 self.productid = productid.lower()
37 self.revision = revision.lower()
40 def __eq__(self, other):
41 return self.vendorid == other.vendorid and self.productid == other.productid and self.revision == other.revision
44 return hash(self.vendorid) ^ hash(self.productid) ^ hash(self.revision)
47 return "VendorId = \'" + str(self.vendorid) + "\' ProductId = \'" + str(self.productid) + "\' Revision = \'" + str(self.revision) + "\'"
50 class VMManager(object):
51 vmRootName = "SecurityDVM"
52 systemProperties = None
53 cygwin_path = 'c:\\cygwin64\\bin\\'
54 vboxManage = 'VBoxManage'
55 startNotifications = list()
58 #def __new__(cls, *args, **kwargs):
59 # if not cls._instance:
60 # cls._instance = super(VMManager, cls).__new__(cls, *args, **kwargs)
61 # return cls._instance
64 #def __new__(cls, *args, **kwargs):
65 # if not cls._instance:
66 # cls._instance = super(VMManager, cls).__new__(cls, *args, **kwargs)
67 # return cls._instance
70 self.cygwin_path = os.path.join(Cygwin.root(), 'bin') + os.path.sep
71 self.vboxManage = os.path.join(self.getVBoxManagePath(), 'VBoxManage')
72 self.systemProperties = self.getSystemProperties()
77 if VMManager._instance == None:
78 VMManager._instance = VMManager()
79 return VMManager._instance
81 def putStartNotification(self, ip):
82 self.startNotifications.append(ip)
84 def isSDVMStarted(self, ip):
85 return self.startNotifications.contains(ip)
87 def execute(self, cmd, wait_return=True ):
89 print('trying to launch: ' + cmd)
90 process = Popen(cmd, stdout=PIPE, stderr=PIPE) #shell = True
92 print('launched: ' + cmd)
94 return [0, 'working in background', '']
95 result = process.wait()
96 res_stdout = process.stdout.read();
97 res_stderr = process.stderr.read();
104 raise VMManagerException(res_stderr)
105 return result, res_stdout, res_stderr
107 def getVBoxManagePath(self):
108 """get the path to the VirtualBox installation on this system"""
111 k = _winreg.OpenKey(_winreg.HKEY_LOCAL_MACHINE, 'SOFTWARE\Oracle\VirtualBox')
112 p = _winreg.QueryValueEx(k, 'InstallDir')[0]
118 # return hosty system properties
119 def getSystemProperties(self):
120 cmd = self.vboxManage + ' list systemproperties'
121 result = self.execute(cmd)
124 props = dict((k.strip(),v.strip().strip('"')) for k,v in (line.split(':', 1) for line in result[1].strip().splitlines()))
127 # return the folder containing the guest VMs
128 def getDefaultMachineFolder(self):
129 return self.systemProperties["Default machine folder"]
131 #list the hostonly IFs exposed by the VBox host
132 def getHostOnlyIFs(self):
133 cmd = 'VBoxManage list hostonlyifs'
134 result = self.execute(cmd)[1]
137 props = dict((k.strip(),v.strip().strip('"')) for k,v in (line.split(':', 1) for line in result.strip().splitlines()))
141 cmd = 'VBoxManage list usbhost'
142 results = self.execute(cmd)[1]
143 results = results.split('Host USB Devices:')[1].strip()
145 items = list( "UUID:"+result for result in results.split('UUID:') if result != '')
149 for line in item.splitlines():
151 k,v = line[:line.index(':')].strip(), line[line.index(':')+1:].strip()
154 if 'Product' in props.keys() and props['Product'] == 'Mass Storage':
155 usb_filter = USBFilter( re.search(r"\((?P<vid>[0-9A-Fa-f]+)\)", props['VendorId']).groupdict()['vid'],
156 re.search(r"\((?P<pid>[0-9A-Fa-f]+)\)", props['ProductId']).groupdict()['pid'],
157 re.search(r"\((?P<rev>[0-9A-Fa-f]+)\)", props['Revision']).groupdict()['rev'] )
158 rsds[props['UUID']] = usb_filter;
163 # list all existing VMs registered with VBox
165 cmd = 'VBoxManage list vms'
166 result = self.execute(cmd)[1]
167 vms = list(k.strip().strip('"') for k,_ in (line.split(' ') for line in result.splitlines()))
170 # list existing SDVMs
175 if vm.startswith(self.vmRootName) and vm != self.vmRootName:
179 # generate valid (not already existing SDVM name). necessary for creating a new VM
180 def generateSDVMName(self):
182 for i in range(0,999):
183 if(not self.vmRootName+str(i) in vms):
184 return self.vmRootName+str(i)
187 # return the RSDs attached to all existing SDVMs
188 def getAttachedRSDs(self):
189 vms = self.listSDVM()
190 attached_devices = dict()
192 rsd_filter = self.getUSBFilter(vm)
193 if rsd_filter != None:
194 attached_devices[vm] = rsd_filter
195 return attached_devices
197 # configures hostonly networking and DHCP server. requires admin rights
198 def configureHostNetworking(self):
199 #cmd = 'vboxmanage list hostonlyifs'
201 #cmd = 'vboxmanage hostonlyif remove \"VirtualBox Host-Only Ethernet Adapter\"'
203 #cmd = 'vboxmanage hostonlyif create'
205 cmd = 'VBoxManage hostonlyif ipconfig \"VirtualBox Host-Only Ethernet Adapter\" --ip 192.168.56.1 --netmask 255.255.255.0'
207 #cmd = 'vboxmanage dhcpserver add'
209 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'
212 #create new virtual machine instance based on template vm named SecurityDVM (\SecurityDVM\SecurityDVM.vmdk)
213 def createVM(self, vm_name):
214 hostonly_if = self.getHostOnlyIFs()
215 machineFolder = self.getDefaultMachineFolder()
216 cmd = 'VBoxManage createvm --name ' + vm_name + ' --ostype Debian --register'
218 cmd = 'VBoxManage modifyvm ' + vm_name + ' --memory 512 --vram 10 --cpus 1 --usb on --usbehci on --nic1 hostonly --hostonlyadapter1 \"' + hostonly_if['Name'] + '\" --nic2 nat'
220 cmd = 'VBoxManage storagectl ' + vm_name + ' --name contr1 --add sata --portcount 2'
222 cmd = 'VBoxManage storageattach ' + vm_name + ' --storagectl contr1 --port 0 --device 0 --type hdd --medium \"'+ machineFolder + '\SecurityDVM\SecurityDVM.vmdk\"' #--mtype immutable
226 #remove VM from the system. should be used on VMs returned by listSDVMs
227 def removeVM(self, vm_name):
228 print('removing ' + vm_name)
229 cmd = 'VBoxManage unregistervm ' + vm_name + ' --delete'
230 print self.execute(cmd)
231 machineFolder = self.getDefaultMachineFolder()
232 cmd = self.cygwin_path + 'bash.exe --login -c \"rm -rf ' + machineFolder + '\\' + vm_name + '*\"'
233 print self.execute(cmd)
236 def startVM(self, vm_name):
237 print('starting ' + vm_name)
238 cmd = 'VBoxManage startvm ' + vm_name + ' --type headless'
239 result = self.execute(cmd)
240 while not string.find(str(result), 'successfully started',):
241 print "Failed to start SDVM: ", vm_name, " retrying"
243 result = self.execute(cmd)
247 def stopVM(self, vm_name):
248 print('stopping ' + vm_name)
249 cmd = 'VBoxManage controlvm ' + vm_name + ' poweroff'
252 # return the hostOnly IP for a running guest
253 def getHostOnlyIP(self, vm_name):
254 print('gettting hostOnly IP address ' + vm_name)
255 cmd = 'VBoxManage guestproperty get ' + vm_name + ' /VirtualBox/GuestInfo/Net/0/V4/IP'
256 result = self.execute(cmd)
260 if result.startswith('No value set!'):
262 return result[result.index(':')+1:].strip()
264 # attach removable storage device to VM by provision of filter
265 def attachRSD(self, vm_name, rsd_filter):
266 cmd = 'VBoxManage usbfilter add 0 --target ' + vm_name + ' --name OpenSecurityRSD --vendorid ' + rsd_filter.vendorid + ' --productid ' + rsd_filter.productid + ' --revision ' + rsd_filter.revision
267 print self.execute(cmd)
270 # return the description set for an existing VM
271 def getVMInfo(self, vm_name):
272 cmd = 'VBoxManage showvminfo ' + vm_name + ' --machinereadable'
273 results = self.execute(cmd)[1]
274 props = dict((k.strip(),v.strip().strip('"')) for k,v in (line.split('=', 1) for line in results.splitlines()))
277 # return the configured USB filter for an existing VM
278 def getUSBFilter(self, vm_name):
279 props = self.getVMInfo(vm_name)
280 keys = set(['USBFilterVendorId1', 'USBFilterProductId1', 'USBFilterRevision1'])
281 keyset = set(props.keys())
283 if keyset.issuperset(keys):
284 usb_filter = USBFilter(props['USBFilterVendorId1'], props['USBFilterProductId1'], props['USBFilterRevision1'])
287 #generates ISO containing authorized_keys for use with guest VM
288 def genCertificateISO(self, vm_name):
289 machineFolder = self.getDefaultMachineFolder()
291 ## create a SSH key pair in a machine subfolder
292 #vm_folder = os.path.join(machineFolder, vm_name)
293 #ssh_folder = os.path.join(vm_folder, '.ssh')
295 # os.mkdir(ssh_folder)
298 #ssh_keyfile = os.path.join(ssh_folder, 'dvm_key')
300 # delete old key file (if existing)
302 # os.remove(ssh_keyfile)
306 ## create new key file
308 # p = Cygwin()(['/bin/ssh-keygen', '-q', '-t', 'rsa', '-N', '', '-C', vm_name, '-f', ssh_keyfile])
311 # sys.stderr.write('failed to create a new SSH key pair as: ' + ssh_keyfile + '\n')
314 # os.chmod(ssh_keyfile, stat.S_IREAD)
318 ## move out private key
320 # os.rename(ssh_keyfile, os.path.join(vm_folder, 'dvm_key'))
322 # sys.stderr.write('failed to move private SSH key\n')
325 ## rename public key to 'authorized_keys'
327 # os.rename(ssh_keyfile + '.pub', os.path.join(ssh_folder, 'authorized_keys'))
329 # sys.stderr.write('failed to rename public key to "authorized_keys"\n')
332 ## generate ISO image
333 #iso_file = os.path.join(vm_folder, vm_name + '.iso')
335 # p = Cygwin()(['/bin/genisoimage', '-J', '-R', '-o', iso_file, ssh_folder])
338 # sys.stderr.write('failed to create ISO image.\n')
341 # create .ssh folder in vm_name
342 cmd = self.cygwin_path+'bash.exe --login -c \"mkdir -p \\\"' + machineFolder + '\\' + vm_name + '\\.ssh\\\"\"'
344 # generate dvm_key pair in vm_name / .ssh
345 cmd = self.cygwin_path+'bash.exe --login -c \"ssh-keygen -q -t rsa -N \\"\\" -C \\\"' + vm_name + '\\\" -f \\\"' + machineFolder + '\\' + vm_name + '\\.ssh\\dvm_key\\\"\"' #'echo -e "y\\n" |',
347 # set permissions for keys
348 #TODO: test without chmod
349 cmd = self.cygwin_path+'bash.exe --login -c \"chmod 500 \\\"' + machineFolder + '\\' + vm_name + '\\.ssh\\*\\\"\"'
351 # move out private key
352 cmd = self.cygwin_path+'bash.exe --login -c \"mv \\\"' + machineFolder + '\\' + vm_name + '\\.ssh\\dvm_key\\\" \\\"' + machineFolder + '\\' + vm_name + '\\\"'
354 # rename public key to authorized_keys
355 cmd = self.cygwin_path+'bash.exe --login -c \"mv \\\"' + machineFolder + '\\' + vm_name + '\\.ssh\\dvm_key.pub\\\" \\\"' + machineFolder + '\\' + vm_name + '\\.ssh\\authorized_keys\\\"'
357 # generate iso image with .ssh/authorized keys
358 cmd = self.cygwin_path+'bash.exe --login -c \"/usr/bin/genisoimage -J -R -o \\\"' + machineFolder + '\\' + vm_name + '\\'+ vm_name + '.iso\\\" \\\"' + machineFolder + '\\' + vm_name + '\\.ssh\\\"\"'
361 # attaches generated ssh public cert to guest vm
362 def attachCertificateISO(self, vm_name):
363 machineFolder = self.getDefaultMachineFolder()
364 cmd = 'vboxmanage storageattach ' + vm_name + ' --storagectl contr1 --port 1 --device 0 --type dvddrive --mtype readonly --medium \"' + machineFolder + '\\' + vm_name + '\\'+ vm_name + '.iso\"'
365 result = self.execute(cmd)
368 handleDeviceChangeLock = threading.Lock()
370 # handles device change events
371 def handleDeviceChange(self):
372 if VMManager.handleDeviceChangeLock.acquire(True):
375 attached_devices = self.getAttachedRSDs()
376 connected_devices = self.listRSDS()
377 for vm_name in attached_devices.keys():
378 if attached_devices[vm_name] not in connected_devices.values():
379 self.unmapNetworkDrive('h:')
381 self.removeVM(vm_name)
382 #create new vm for attached device if any
383 attached_devices = self.getAttachedRSDs()
384 for connected_device in connected_devices.values():
385 if (attached_devices and False) or (connected_device not in attached_devices.values()):
386 new_sdvm = self.generateSDVMName()
387 self.createVM(new_sdvm)
388 self.attachRSD(new_sdvm, connected_device)
391 self.startVM(new_sdvm)
392 # wait for machine to come up
393 while new_ip == None:
395 new_ip = self.getHostOnlyIP(new_sdvm)
396 while new_ip not in self.startNotifications:
399 self.mapNetworkDrive('h:', '\\\\' + new_ip + '\\USB', None, None)
400 #TODO: cleanup notifications somwhere else (eg. machine shutdown)
401 self.startNotifications.remove(new_ip)
402 VMManager.handleDeviceChangeLock.release()
405 def handleBrowsingRequest(self):
406 if VMManager.handleDeviceChangeLock.acquire(True):
408 new_sdvm = self.generateSDVMName()
409 self.createVM(new_sdvm)
410 self.genCertificateISO(new_sdvm)
411 self.attachCertificateISO(new_sdvm)
412 self.startVM(new_sdvm)
413 # wait for machine to come up
414 while new_ip == None:
416 new_ip = self.getHostOnlyIP(new_sdvm)
417 while new_ip not in self.startNotifications:
420 self.mapNetworkDrive('g:', '\\\\' + new_ip + '\\Download', None, None)
421 #TODO: cleanup notifications somwhere else (eg. machine shutdown)
422 self.startNotifications.remove(new_ip)
423 VMManager.handleDeviceChangeLock.release()
426 # executes command over ssh on guest vm
427 def sshGuestExecute(self, vm_name, prog, user_name='osecuser'):
429 address = self.getHostOnlyIP(vm_name)
430 machineFolder = self.getDefaultMachineFolder()
432 cmd = self.cygwin_path+'bash.exe --login -c \"ssh -i \\\"' + machineFolder + '\\' + vm_name + '\\dvm_key\\\" ' + user_name + '@' + address + ' ' + prog + '\"'
433 return self.execute(cmd)
435 # executes command over ssh on guest vm with X forwarding
436 def sshGuestX11Execute(self, vm_name, prog, user_name='osecuser'):
437 #TODO: verify if X server is running on user account
438 #TODO: set DISPLAY accordingly
439 address = self.getHostOnlyIP(vm_name)
440 machineFolder = self.getDefaultMachineFolder()
443 #cmd = self.cygwin_path+'bash.exe --login -c \"DISPLAY=:0 ssh -v -Y -i \\\"' + machineFolder + '\\' + vm_name + '\\dvm_key\\\" ' + user_name + '@' + address + ' ' + prog + '\"'
444 cmd = self.cygwin_path+'mintty.exe -e /bin/env DISPLAY=:0 /usr/bin/ssh -v -Y -i \"' + machineFolder + '\\' + vm_name + '\\dvm_key\" ' + user_name + '@' + address + ' ' + prog + ''
445 #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 + '\"'
447 print('trying to launch: ' + cmd)
450 print('launched: ' + cmd)
453 #Small function to check the availability of network resource.
454 def isAvailable(self, path):
455 cmd = 'IF EXIST ' + path + ' echo YES'
456 result = subprocess.Popen(cmd, stdout=subprocess.PIPE, shell=True).communicate()
457 return string.find(str(result), 'YES',)
459 #Small function to check if the mention location is a directory
460 def isDirectory(self, path):
461 cmd = 'dir ' + path + ' | FIND ".."'
462 result = subprocess.Popen(cmd, stdout=subprocess.PIPE, shell=True).communicate()
463 return string.find(str(result), 'DIR',)
465 def mapNetworkDrive(self, drive, networkPath, user, password):
466 self.unmapNetworkDrive('h:')
467 #Check for drive availability
468 if self.isAvailable(drive) > -1:
469 print "Drive letter is already in use: ", drive
471 #Check for network resource availability
472 while self.isAvailable(networkPath) == -1:
474 print "Path not accessible: ", networkPath, " retrying"
477 #Prepare 'NET USE' commands
478 cmd = 'NET USE ' + drive + ' ' + networkPath
480 cmd = cmd + ' ' + password + ' /User' + user
483 #Execute 'NET USE' command with authentication
484 result = subprocess.Popen(cmd, stdout=subprocess.PIPE, shell=True).communicate()
485 print "Executed: ", cmd
486 if string.find(str(result), 'successfully',) == -1:
489 #Mapped with first try
492 def unmapNetworkDrive(self, drive):
493 #Check if the drive is in use
494 if self.isAvailable(drive) == -1:
497 #Prepare 'NET USE' command
498 cmd = 'net use ' + drive + ' /DELETE'
499 result = subprocess.Popen(cmd, stdout=subprocess.PIPE, shell=True).communicate()
500 if string.find(str(result), 'successfully',) == -1:
504 if __name__ == '__main__':
505 man = VMManager.getInstance()
506 #man.removeVM('SecurityDVM0')
507 #man.netUse('192.168.56.134', 'USB\\')
508 #ip = '192.168.56.139'
509 #man.mapNetworkDrive('h:', '\\\\' + ip + '\USB', None, None)
510 #man.cygwin_path = 'c:\\cygwin64\\bin\\'
511 #man.handleDeviceChange()
512 #print man.listSDVM()
513 #man.configureHostNetworking()
514 #new_vm = man.generateSDVMName()
515 #man.createVM(new_vm)
516 #man.genCertificateISO(new_vm)
517 #man.attachCertificateISO(new_vm)
519 #man.attachCertificateISO(vm_name)
520 #man.sshGuestExecute(vm_name, "ls")
521 man.sshGuestX11Execute('SecurityDVM1', '/usr/bin/iceweasel')
523 #cmd = "c:\\cygwin64\\bin\\bash.exe --login -c \"/bin/ls\""