2 # -*- coding: utf-8 -*-
4 # ------------------------------------------------------------
7 # management of the Virtual Machines
9 # Autor: Mihai Bartha, <mihai.bartha@ait.ac.at>
11 # Copyright 2013-2014 X-Net and AIT Austrian Institute of Technology
18 # https://www.x-net.at
20 # AIT Austrian Institute of Technology
21 # Donau City Strasse 1
24 # http://www.ait.ac.at
27 # Licensed under the Apache License, Version 2.0 (the "License");
28 # you may not use this file except in compliance with the License.
29 # You may obtain a copy of the License at
31 # http://www.apache.org/licenses/LICENSE-2.0
33 # Unless required by applicable law or agreed to in writing, software
34 # distributed under the License is distributed on an "AS IS" BASIS,
35 # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
36 # See the License for the specific language governing permissions and
37 # limitations under the License.
38 # ------------------------------------------------------------
41 # ------------------------------------------------------------
46 from subprocess import Popen, PIPE, call, STARTUPINFO, _subprocess
50 from cygwin import Cygwin
51 from environment import Environment
59 from opensecurity_util import logger, import_logger, setupLogger, OpenSecurityException, showTrayMessage
72 new_sdvm_lock = threading.Lock()
73 backup_lock = threading.Lock()
75 class VMManagerException(Exception):
76 def __init__(self, value):
79 return repr(self.value)
88 def __init__(self, uuid, vendorid, productid, revision, serial):
90 self.vendorid = vendorid.lower()
91 self.productid = productid.lower()
92 self.revision = revision.lower()
96 def __eq__(self, other):
97 return self.uuid == other.uuid #self.vendorid == other.vendorid and self.productid == other.productid and self.revision == other.revision
100 return hash(self.uuid) ^ hash(self.vendorid) ^ hash(self.productid) ^ hash(self.revision) ^ hash(self.serial)
103 return "UUID:" + str(self.uuid) + " VendorId = \'" + str(self.vendorid) + "\' ProductId = \'" + str(self.productid) + "\'" + "\' Revision = \'" + str(self.revision) + "\' SerialNumber = \'" + str(self.serial)
105 #def __getitem__(self, item):
106 # return self.coords[item]
108 theClass.systemProperties = theClass.getSystemProperties()
109 theClass.machineFolder = theClass.systemProperties["Default machine folder"]
110 #theClass.hostonlyIF = theClass.getHostOnlyIFs()["VirtualBox Host-Only Ethernet Adapter"]
111 theClass.blacklistedRSD = theClass.loadRSDBlacklist()
112 theClass.templateImage = theClass.machineFolder + '\\' + theClass.vmRootName + '\\' + theClass.vmRootName + '.vmdk'
116 class VMManager(object):
117 vmRootName = "SecurityDVM"
118 systemProperties = None
123 blacklistedRSD = None
124 status_message = 'Starting up...'
134 # only proceed if we have a working background environment
135 if self.backend_ok():
136 VMManager.hostonlyIF = self.getHostOnlyIFs()["VirtualBox Host-Only Ethernet Adapter"]
139 logger.critical(self.status_message)
144 if VMManager._instance == None:
145 VMManager._instance = VMManager()
146 return VMManager._instance
148 #list the hostonly IFs exposed by the VBox host
150 def getHostOnlyIFs():
151 results = Cygwin.vboxExecute('list hostonlyifs')[1]
155 items = list( "Name: " + result for result in results.split('Name: ') if result != '')
158 props = dict((k.strip(),v.strip().strip('"')) for k,v in (line.split(':', 1) for line in item.strip().splitlines()))
159 ifs[props["Name"]] = props
162 #list the hostonly IFs exposed by the VBox host
164 def getDHCPServers():
165 results = Cygwin.vboxExecute('list dhcpservers')[1]
168 items = list( "NetworkName: " + result for result in results.split('NetworkName: ') if result != '')
172 props = dict((k.strip(),v.strip().strip('"')) for k,v in (line.split(':', 1) for line in item.strip().splitlines()))
173 dhcps[props["NetworkName"]] = props
176 # return hosty system properties
178 def getSystemProperties():
179 result = Cygwin.vboxExecute('list systemproperties')
182 props = dict((k.strip(),v.strip().strip('"')) for k,v in (line.split(':', 1) for line in result[1].strip().splitlines()))
185 # return the folder containing the guest VMs
186 def getMachineFolder(self):
187 return VMManager.machineFolder
189 # verifies the hostonly interface and DHCP server settings
190 def verifyHostOnlySettings(self):
191 interfaceName = "VirtualBox Host-Only Ethernet Adapter"
192 networkName = "HostInterfaceNetworking-VirtualBox Host-Only Ethernet Adapter"
194 hostonlyifs = self.getHostOnlyIFs()
195 if not interfaceName in hostonlyifs.keys():
196 Cygwin.vboxExecute('hostonlyif create')
197 hostonlyifs = self.getHostOnlyIFs()
198 if not interfaceName in hostonlyifs.keys():
201 interface = hostonlyifs[interfaceName]
202 if interface['VBoxNetworkName'] != networkName or interface['DHCP'] != 'Disabled' or interface['IPAddress'] != '192.168.56.1':
203 Cygwin.vboxExecute('hostonlyif ipconfig "' + interfaceName + '" --ip 192.168.56.1 --netmask 255.255.255.0')
205 dhcpservers = self.getDHCPServers()
206 if not networkName in dhcpservers.keys():
207 Cygwin.vboxExecute('dhcpserver add --ifname "' + interfaceName + '" --ip 192.168.56.100 --netmask 255.255.255.0 --lowerip 192.168.56.101 --upperip 192.168.56.254 --enable')
208 dhcpservers = self.getDHCPServers()
209 if not networkName in dhcpservers.keys():
212 server = dhcpservers[networkName]
213 if server['IP'] != '192.168.56.100' or server['NetworkMask'] != '255.255.255.0' or server['lowerIPAddress'] != '192.168.56.101' or server['upperIPAddress'] != '192.168.56.254' or server['Enabled'] != 'Yes':
214 Cygwin.vboxExecute('VBoxManage dhcpserver modify --netname "' + networkName + '" --ip 192.168.56.100 --netmask 255.255.255.0 --lowerip 192.168.56.101 --upperip 192.168.56.254 --enable')
218 def template_installed(self):
219 """ check if we do have our root VMs installed """
221 if not self.vmRootName in vms:
222 self.status_message = 'Unable to locate root SecurityDVM. Please download and setup the initial image.'
226 def backend_ok(self):
227 """check if the backend (VirtualBox) is sufficient for our task"""
229 # ensure we have our system props
230 if VMManager.systemProperties == None:
231 VMManager.systemProperties = self.getSystemProperties()
232 if VMManager.systemProperties == None:
233 self.status_message = 'Failed to get backend system properties. Is Backend (VirtualBox?) installed?'
236 # check for existing Extension pack
237 if not 'Remote desktop ExtPack' in VMManager.systemProperties:
238 self.status_message = 'No remote desktop extension pack found. Please install the "Oracle VM VirtualBox Extension Pack" from https://www.virtualbox.org/wiki/Downloads.'
240 if VMManager.systemProperties['Remote desktop ExtPack'] == 'Oracle VM VirtualBox Extension Pack ':
241 self.status_message = 'Unsure if suitable extension pack is installed. Please install the "Oracle VM VirtualBox Extension Pack" from https://www.virtualbox.org/wiki/Downloads.'
244 # check the existing hostOnly network settings and try to reconfigure if faulty
245 if not self.verifyHostOnlySettings():
248 # basically all seems nice and ready to rumble
249 self.status_message = 'All is ok.'
252 def start(self, force = False):
254 if self.importHandler and self.importHandler.isAlive():
255 logger.info("Initial update running canceling start.")
258 if self.updateHandler and self.updateHandler.isAlive():
259 logger.info("Update running canceling start.")
264 if self.backend_ok() and self.template_installed():
265 self.sdvmFactory = SDVMFactory(self)
266 self.sdvmFactory.start()
267 self.deviceHandler = DeviceHandler(self)
268 self.deviceHandler.start()
272 if self.sdvmFactory != None:
273 self.sdvmFactory.stop()
274 self.sdvmFactory.join()
275 self.sdvmFactory = None
276 if self.deviceHandler != None:
277 self.deviceHandler.stop()
278 self.deviceHandler.join()
279 self.deviceHandler = None
286 ip = self.getHostOnlyIP(None)
288 result = urllib2.urlopen('http://127.0.0.1:8090/netcleanup?'+'hostonly_ip='+ip).readline()
289 except urllib2.URLError:
290 logger.info("Network drive cleanup all skipped. OpenSecurity Tray client not started yet.")
292 for vm in self.listSDVM():
297 # list all existing VMs registered with VBox
299 result = Cygwin.vboxExecute('list vms')[1]
300 vms = list(k.strip().strip('"') for k,_ in (line.split(' ') for line in result.splitlines()))
304 def listRunningVMS(self):
305 result = Cygwin.vboxExecute('list runningvms')[1]
306 vms = list(k.strip().strip('"') for k,_ in (line.split(' ') for line in result.splitlines()))
309 # list existing SDVMs
314 if vm.startswith(self.vmRootName) and vm != self.vmRootName:
318 # generate valid (not already existing SDVM name). necessary for creating a new VM
319 def genSDVMName(self):
321 for i in range(0,999):
322 if(not self.vmRootName+str(i) in vms):
323 return self.vmRootName+str(i)
327 def loadRSDBlacklist():
330 fo = open(Environment('OpenSecurity').prefix_path +"\\bin\\blacklist.usb", "r")
332 logger.error("Could not open RSD blacklist file.")
335 lines = fo.readlines()
338 parts = line.strip().split(' ')
339 blacklist[parts[0].lower()] = parts[1].lower()
343 def isBlacklisted(device):
344 if VMManager.blacklistedRSD:
345 blacklisted = device.vendorid.lower() in VMManager.blacklistedRSD.keys() and device.productid.lower() == VMManager.blacklistedRSD[device.vendorid]
349 # check if the device is mass storage type
351 def isMassStorageDevice(device):
356 keyname = 'SYSTEM\CurrentControlSet\Enum\USB' + '\VID_' + device.vendorid+'&'+'PID_'+ device.productid
357 vidkey = win32api.RegOpenKey(win32con.HKEY_LOCAL_MACHINE, keyname)
358 devinfokeyname = win32api.RegEnumKey(vidkey, 0)
359 win32api.RegCloseKey(vidkey)
361 devinfokey = win32api.RegOpenKey(win32con.HKEY_LOCAL_MACHINE, keyname+'\\'+devinfokeyname)
362 value = win32api.RegQueryValueEx(devinfokey, 'SERVICE')[0]
363 win32api.RegCloseKey(devinfokey)
364 except Exception as ex:
365 logger.error('Error reading registry.Exception details: %s' %ex)
367 if vidkey is not None:
368 win32api.RegCloseKey(vidkey)
369 if devinfokey is not None:
370 win32api.RegCloseKey(devinfokey)
372 return 'USBSTOR' in value
374 # return the RSDs connected to the host
376 def getExistingRSDs():
377 results = Cygwin.vboxExecute('list usbhost')[1]
378 results = results.split('Host USB Devices:')[1].strip()
380 items = list( "UUID:"+result for result in results.split('UUID:') if result != '')
384 for line in item.splitlines():
386 k,v = line[:line.index(':')].strip(), line[line.index(':')+1:].strip()
389 uuid = re.search(r"(?P<uuid>[0-9A-Fa-f\-]+)", props['UUID']).groupdict()['uuid']
390 vid = re.search(r"\((?P<vid>[0-9A-Fa-f]+)\)", props['VendorId']).groupdict()['vid']
391 pid = re.search(r"\((?P<pid>[0-9A-Fa-f]+)\)", props['ProductId']).groupdict()['pid']
392 rev = re.search(r"\((?P<rev>[0-9A-Fa-f]+)\)", props['Revision']).groupdict()['rev']
394 if 'SerialNumber' in props.keys():
395 serial = re.search(r"(?P<ser>[0-9A-Fa-f]+)", props['SerialNumber']).groupdict()['ser']
396 usb_filter = USBFilter( uuid, vid, pid, rev, serial)
398 if VMManager.isMassStorageDevice(usb_filter) and not VMManager.isBlacklisted(usb_filter):
399 rsds[uuid] = usb_filter
400 logger.debug(usb_filter)
404 # return the attached USB device as usb descriptor for an existing VM
405 def getAttachedRSD(self, vm_name):
406 props = self.getVMInfo(vm_name)
407 keys = set(['USBAttachedUUID1', 'USBAttachedVendorId1', 'USBAttachedProductId1', 'USBAttachedRevision1', 'USBAttachedSerialNumber1'])
408 keyset = set(props.keys())
410 if keyset.issuperset(keys):
411 usb_filter = USBFilter(props['USBAttachedUUID1'], props['USBAttachedVendorId1'], props['USBAttachedProductId1'], props['USBAttachedRevision1'], props['USBAttachedSerialNumber1'])
414 # return the RSDs attached to all existing SDVMs
415 def getAttachedRSDs(self):
416 vms = self.listSDVM()
417 attached_devices = dict()
419 rsd_filter = self.getAttachedRSD(vm)
420 if rsd_filter != None:
421 attached_devices[vm] = rsd_filter
422 return attached_devices
424 # attach removable storage device to VM by provision of filter
425 def attachRSD(self, vm_name, rsd_filter):
426 #return Cygwin.vboxExecute('usbfilter add 0 --target ' + vm_name + ' --name OpenSecurityRSD --vendorid ' + rsd_filter.vendorid + ' --productid ' + rsd_filter.productid + ' --revision ' + rsd_filter.revision + ' --serialnumber ' + rsd_filter.serial)
427 return Cygwin.vboxExecute('controlvm ' + vm_name + ' usbattach ' + rsd_filter.uuid )
429 # detach removable storage from VM by
430 def detachRSD(self, vm_name, rsd_filter):
431 #return Cygwin.vboxExecute('usbfilter remove 0 --target ' + vm_name)
432 return Cygwin.vboxExecute('controlvm ' + vm_name + ' usbdetach ' + rsd_filter.uuid )
434 # configures hostonly networking and DHCP server. requires admin rights
435 def configureHostNetworking(self):
436 #cmd = 'vboxmanage list hostonlyifs'
437 #Cygwin.vboxExecute(cmd)
438 #cmd = 'vboxmanage hostonlyif remove \"VirtualBox Host-Only Ethernet Adapter\"'
439 #Cygwin.vboxExecute(cmd)
440 #cmd = 'vboxmanage hostonlyif create'
441 #Cygwin.vboxExecute(cmd)
442 Cygwin.vboxExecute('hostonlyif ipconfig \"VirtualBox Host-Only Ethernet Adapter\" --ip 192.168.56.1 --netmask 255.255.255.0')
443 #cmd = 'vboxmanage dhcpserver add'
444 #Cygwin.vboxExecute(cmd)
445 Cygwin.vboxExecute('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')
447 def isSDVMExisting(self, vm_name):
448 sdvms = self.listSDVM()
449 return vm_name in sdvms
451 #create new virtual machine instance based on template vm named SecurityDVM (\SecurityDVM\SecurityDVM.vmdk)
452 def createVM(self, vm_name):
453 if self.isSDVMExisting(vm_name):
455 #remove eventually existing SDVM folder
456 machineFolder = Cygwin.cygPath(VMManager.machineFolder)
457 Cygwin.bashExecute('/usr/bin/rm -rf \\\"' + machineFolder + '/' + vm_name + '\\\"')
458 Cygwin.vboxExecute('createvm --name ' + vm_name + ' --ostype Debian --register')
459 Cygwin.vboxExecute('modifyvm ' + vm_name + ' --memory 768 --vram 10 --cpus 1 --usb on --usbehci on --nic1 hostonly --hostonlyadapter1 \"' + self.hostonlyIF['Name'] + '\" --nic2 nat')
460 Cygwin.vboxExecute('storagectl ' + vm_name + ' --name SATA --add sata --portcount 2')
462 #create new SecurityDVM with automatically generated name from template (thread safe)
465 vm_name = self.genSDVMName()
466 self.createVM(vm_name)
469 #VMManager.machineFolder + '\SecurityDVM\SecurityDVM.vmdk
470 # attach storage image to controller
471 def attachVDisk(self, vm_name, vdisk_controller, vdisk_port, vdisk_device, vdisk_image):
472 if self.isVDiskAttached(vm_name, vdisk_controller, vdisk_port, vdisk_device):
473 self.detachVDisk(vm_name, vdisk_controller, vdisk_port, vdisk_device)
474 Cygwin.vboxExecute('storageattach ' + vm_name + ' --storagectl '+ vdisk_controller + ' --port ' + vdisk_port + ' --device ' + vdisk_device + ' --type hdd --medium "'+ vdisk_image + '"')
476 # return true if storage is attached
477 def isVDiskAttached(self, vm_name, vdisk_controller, vdisk_port, vdisk_device):
478 info = self.getVMInfo(vm_name)
479 return (info[vdisk_controller+'-'+vdisk_port+'-'+vdisk_device] != 'none')
481 # detach storage from controller
482 def detachVDisk(self, vm_name, vdisk_controller, vdisk_port, vdisk_device):
483 if self.isVDiskAttached(vm_name, vdisk_controller, vdisk_port, vdisk_device):
484 Cygwin.vboxExecute('storageattach ' + vm_name + ' --storagectl ' + vdisk_controller + ' --port ' + vdisk_port + ' --device ' + vdisk_device + ' --medium none')
486 # modify type of the vdisk_image
487 def changeVDiskType(self, vdisk_image, storage_type):
488 Cygwin.vboxExecute('modifyhd "' + vdisk_image + '" --type ' + storage_type)
490 # grab VM storage controller, port and device for vdisk image name
491 def getVDiskController(self, vm_name, image_name = '.vmdk'):
492 vm_description = self.getVMInfo(vm_name)
493 vdisk_controller = None
494 for key, value in vm_description.iteritems():
495 if image_name in value:
496 vdisk_controller = key
498 return vdisk_controller
500 # return attached vmdk image name containing image_name
501 def getVDiskImage(self, vm_name, image_name = '.vmdk'):
502 vmInfo = self.getVMInfo(vm_name)
504 for value in vmInfo.values():
505 if image_name in value:
510 def getVDiskImages():
511 results = Cygwin.vboxExecute('list hdds')[1]
512 results = results.replace('Parent UUID', 'Parent')
513 items = list( "UUID:"+result for result in results.split('UUID:') if result != '')
518 for line in item.splitlines():
520 k,v = line[:line.index(':')].strip(), line[line.index(':')+1:].strip()
522 snaps[props['UUID']] = props
526 def getVDiskUUID(vdisk_image):
527 images = VMManager.getVDiskImages()
530 for hdd in images.values():
531 if hdd['Location'] == vdisk_image:
532 template_uuid = hdd['UUID']
536 def removeSnapshots(self, imageUUID):
537 snaps = self.getVDiskImages()
539 for hdd in snaps.values():
540 if hdd['Parent'] == imageUUID:
541 snapshotUUID = hdd['UUID']
542 self.removeImage(snapshotUUID)
544 def removeImage(self, imageUUID):
545 logger.debug('removing snapshot ' + imageUUID)
546 Cygwin.vboxExecute('closemedium disk {' + imageUUID + '} --delete')
547 # parse result 0%...10%...20%...30%...40%...50%...60%...70%...80%...90%...100%
549 #remove VM from the system. should be used on VMs returned by listSDVMs
550 def removeVM(self, vm_name):
551 logger.info('Removing ' + vm_name)
552 Cygwin.vboxExecute('unregistervm ' + vm_name + ' --delete')
553 #try to close medium if still existing
554 #Cygwin.vboxExecute('closemedium disk {' + hdd['UUID'] + '} --delete')
555 self.removeVMFolder(vm_name)
557 def removeVMFolder(self, vm_name):
558 machineFolder = Cygwin.cygPath(VMManager.machineFolder)
559 Cygwin.bashExecute('/usr/bin/rm -rf \\\"' + machineFolder + '/' + vm_name + '/\"')
562 def startVM(self, vm_name):
563 logger.info('Starting ' + vm_name)
564 Cygwin.vboxExecute('guestproperty set ' + vm_name + ' SDVMStarted False')
565 result = Cygwin.vboxExecute('startvm ' + vm_name + ' --type headless' )
566 while 'successfully started' not in result[1]:
567 logger.error("Failed to start SDVM: " + vm_name + " retrying")
568 logger.error("Command returned:\n" + result[2])
570 result = Cygwin.vboxExecute('startvm ' + vm_name + ' --type headless')
573 # return wether VM is running or not
574 def isVMRunning(self, vm_name):
575 return vm_name in self.listRunningVMS()
578 def stopVM(self, vm_name):
579 logger.info('Sending shutdown signal to ' + vm_name)
580 Cygwin.sshExecute( '"sudo shutdown -h now"', self.getHostOnlyIP(vm_name), 'osecuser', self.getCertificatePath(vm_name) )
581 Cygwin.vboxExecute('guestproperty set ' + vm_name + ' SDVMStarted False')
584 def hibernateVM(self, vm_name):
585 logger.info('Sending hibernate-disk signal to ' + vm_name)
586 Cygwin.sshBackgroundExecute( '"sudo hibernate-disk"', self.getHostOnlyIP(vm_name), 'osecuser', self.getCertificatePath(vm_name), wait_return=False)
587 Cygwin.vboxExecute('guestproperty set ' + vm_name + ' SDVMStarted False')
590 def poweroffVM(self, vm_name):
591 if not self.isVMRunning(vm_name):
593 logger.info('Powering off ' + vm_name)
594 Cygwin.vboxExecute('controlvm ' + vm_name + ' poweroff')
595 Cygwin.vboxExecute('guestproperty set ' + vm_name + ' SDVMStarted False')
598 # return the hostOnly IP for a running guest or the host
599 def getHostOnlyIP(self, vm_name):
601 logger.info('Getting hostOnly IP address for Host')
602 return VMManager.hostonlyIF['IPAddress']
604 logger.info('Getting hostOnly IP address ' + vm_name)
605 result = Cygwin.vboxExecute('guestproperty get ' + vm_name + ' /VirtualBox/GuestInfo/Net/0/V4/IP')
609 if result.startswith('No value set!'):
611 return result[result.index(':')+1:].strip()
613 # return the description set for an existing VM
614 def getVMInfo(self, vm_name):
615 results = Cygwin.vboxExecute('showvminfo ' + vm_name + ' --machinereadable')[1]
616 props = dict((k.strip().strip('"'),v.strip().strip('"')) for k,v in (line.split('=', 1) for line in results.splitlines()))
619 #generates ISO containing authorized_keys for use with guest VM
620 def genCertificate(self, vm_name):
621 machineFolder = Cygwin.cygPath(VMManager.machineFolder)
622 # remove .ssh folder if exists
623 Cygwin.bashExecute('/usr/bin/rm -rf \\\"' + machineFolder + '/' + vm_name + '/.ssh\\\"')
624 # remove .ssh folder if exists
625 Cygwin.bashExecute('/usr/bin/rm -rf \\\"' + machineFolder + '/' + vm_name + '/dvm_key\\\"')
626 # create .ssh folder in vm_name
627 Cygwin.bashExecute('/usr/bin/mkdir -p \\\"' + machineFolder + '/' + vm_name + '/.ssh\\\"')
628 # generate dvm_key pair in vm_name / .ssh
629 Cygwin.bashExecute('/usr/bin/ssh-keygen -q -t rsa -N \\\"\\\" -C \\\"' + vm_name + '\\\" -f \\\"' + machineFolder + '/' + vm_name + '/.ssh/dvm_key\\\"')
630 # move out private key
631 Cygwin.bashExecute('/usr/bin/mv \\\"' + machineFolder + '/' + vm_name + '/.ssh/dvm_key\\\" \\\"' + machineFolder + '/' + vm_name + '\\\"')
632 # set permissions for private key
633 Cygwin.bashExecute('/usr/bin/chmod 500 \\\"' + machineFolder + '/' + vm_name + '/dvm_key\\\"')
634 # rename public key to authorized_keys
635 Cygwin.bashExecute('/usr/bin/mv \\\"' + machineFolder + '/' + vm_name + '/.ssh/dvm_key.pub\\\" \\\"' + machineFolder + '/' + vm_name + '/.ssh/authorized_keys\\\"')
636 # set permissions for authorized_keys
637 Cygwin.bashExecute('/usr/bin/chmod 500 \\\"' + machineFolder + '/' + vm_name + '/.ssh/authorized_keys\\\"')
638 # generate iso image with .ssh/authorized keys
639 Cygwin.bashExecute('/usr/bin/genisoimage -J -R -o \\\"' + machineFolder + '/' + vm_name + '/'+ vm_name + '.iso\\\" \\\"' + machineFolder + '/' + vm_name + '/.ssh\\\"')
641 # attaches generated ssh public cert to guest vm
642 def attachCertificate(self, vm_name):
643 if self.isCertificateAttached(vm_name):
644 self.detachCertificate(vm_name)
645 Cygwin.vboxExecute('storageattach ' + vm_name + ' --storagectl SATA --port 1 --device 0 --type dvddrive --mtype readonly --medium \"' + VMManager.machineFolder + '\\' + vm_name + '\\'+ vm_name + '.iso\"')
647 # return true if storage is attached
648 def isCertificateAttached(self, vm_name):
649 info = self.getVMInfo(vm_name)
650 return (info['SATA-1-0']!='none')
652 # detach storage from controller
653 def detachCertificate(self, vm_name):
654 if self.isCertificateAttached(vm_name):
655 Cygwin.vboxExecute('storageattach ' + vm_name + ' --storagectl SATA --port 1 --device 0 --type hdd --medium none')
657 # return path for the certificate of a specific vm
658 def getCertificatePath(self, vm_name):
659 return Cygwin.cygPath(VMManager.machineFolder) + '/' + vm_name + '/dvm_key'
661 # wait for machine to come up
662 def waitStartup(self, vm_name):
663 #Cygwin.vboxExecute('guestproperty wait ' + vm_name + ' SDVMStarted --timeout ' + str(timeout_ms) + ' --fail-on-timeout', try_count = 60)
666 result = Cygwin.vboxExecute('guestproperty get ' + vm_name + ' SDVMStarted')[1]
667 if "Value: True" in result:
671 return self.getHostOnlyIP(vm_name)
673 # wait for machine to shutdown
674 def waitShutdown(self, vm_name):
675 while vm_name in self.listRunningVMS():
679 #Small function to check if the mentioned location is a directory
680 def isDirectory(self, path):
681 result = Cygwin.cmdExecute('dir ' + path + ' | FIND ".."')
682 return string.find(result[1], 'DIR',)
684 def genNetworkDrive(self):
685 logical_drives = VMManager.getLogicalDrives()
686 logger.info("Used logical drive letters: "+ str(logical_drives).strip('[]') )
687 drives = list(map(chr, range(68, 91)))
689 if drive not in logical_drives:
693 def getLogicalDrives():
694 drive_bitmask = ctypes.cdll.kernel32.GetLogicalDrives()
695 drives = list(itertools.compress(string.ascii_uppercase, map(lambda x:ord(x) - ord('0'), bin(drive_bitmask)[:1:-1])))
699 def getDriveType(drive):
700 return ctypes.cdll.kernel32.GetDriveTypeW(u"%s:\\"%drive)
703 def getNetworkPath(drive):
704 return win32wnet.WNetGetConnection(drive+':')
707 def getVolumeInfo(drive):
708 volumeNameBuffer = ctypes.create_unicode_buffer(1024)
709 fileSystemNameBuffer = ctypes.create_unicode_buffer(1024)
711 max_component_length = None
712 file_system_flags = None
714 rc = ctypes.cdll.kernel32.GetVolumeInformationW(
717 ctypes.sizeof(volumeNameBuffer),
719 max_component_length,
721 fileSystemNameBuffer,
722 ctypes.sizeof(fileSystemNameBuffer)
724 return volumeNameBuffer.value, fileSystemNameBuffer.value
726 def getNetworkDrive(self, vm_name):
727 ip = self.getHostOnlyIP(vm_name)
729 logger.error("Failed getting hostonly IP for " + vm_name)
731 logger.info("Got IP address for " + vm_name + ': ' + ip)
732 for drive in VMManager.getLogicalDrives():
733 #if is a network drive
734 if VMManager.getDriveType(drive) == 4:
735 network_path = VMManager.getNetworkPath(drive)
736 if ip in network_path:
740 def getNetworkDrives(self):
741 ip = self.getHostOnlyIP(None)
743 logger.error("Failed getting hostonly IP for system")
745 logger.info("Got IP address for system: " + ip)
746 ip = ip[:ip.rindex('.')]
747 network_drives = dict()
748 for drive in VMManager.getLogicalDrives():
749 #if is a network drive
750 if VMManager.getDriveType(drive) == 4:
751 network_path = VMManager.getNetworkPath(drive)
752 if ip in network_path:
753 network_drives[drive] = network_path
754 return network_drives
756 # handles browsing request
757 def handleBrowsingRequest(self, proxy = None, wpad = None):
758 showTrayMessage('Starting Secure Browsing...', 7000)
759 handler = BrowsingHandler(self, proxy, wpad)
763 def getActiveUserName(self):
764 key = win32api.RegOpenKey(win32con.HKEY_LOCAL_MACHINE, 'SOFTWARE\Microsoft\Windows\CurrentVersion\Authentication\LogonUI')
765 v = str(win32api.RegQueryValueEx(key, 'LastLoggedOnUser')[0])
766 win32api.RegCloseKey(key)
767 user_name = win32api.ExpandEnvironmentStrings(v)
770 def getUserSID(self, user_name):
771 domain, user = user_name.split("\\")
772 account_name = win32security.LookupAccountName(domain, user)
773 if account_name == None:
774 logger.error("Failed lookup account name for user " + user_name)
776 sid = win32security.ConvertSidToStringSid(account_name[0])
778 logger.error("Failed converting SID for account " + account_name[0])
782 def getAppDataDirReg(self, sid):
783 key = win32api.RegOpenKey(win32con.HKEY_USERS, sid + '\Software\Microsoft\Windows\CurrentVersion\Explorer\Shell Folders')
784 value, _ = win32api.RegQueryValueEx(key, "AppData")
785 win32api.RegCloseKey(key)
788 def getAppDataDir(self):
789 user = self.getActiveUserName()
791 logger.error("Cannot get active user name")
792 raise OpenSecurityException("Cannot get active user name")
794 logger.info('Got active user name ' + user)
795 sid = self.getUserSID(user)
797 logger.error("Cannot get SID for active user")
798 raise OpenSecurityException("Cannot get SID for active user")
800 logger.info("Got active user SID " + sid + " for user " + user)
802 path = self.getAppDataDirReg(sid)
804 logger.error("Cannot get AppDataDir for active user")
805 raise OpenSecurityException("Cannot get AppDataDir for active user")
807 logger.info("Got AppData dir for user " + user + ': ' + path)
809 return Cygwin.cygPath(path)
811 #import initial template
812 def importTemplate(self, image_path):
813 import_logger.info('Stopping Opensecurity...')
816 import_logger.info('Cleaning up system in preparation for import...')
819 import_logger.info('Removing template SDVM...')
821 if self.vmRootName in self.listVMS():
822 # shutdown template if running
823 self.poweroffVM(self.vmRootName)
824 # detach and remove VDisk
825 tmplateUUID = self.getVDiskUUID(self.templateImage)
826 if tmplateUUID != None:
827 logger.debug('Found template VDisk uuid ' + tmplateUUID)
828 controller = self.getVDiskController(self.vmRootName)
830 controller = controller.split('-')
831 self.detachVDisk(self.vmRootName, controller[0], controller[1], controller[2])
832 self.removeSnapshots(tmplateUUID)
833 self.removeImage(tmplateUUID)
835 logger.info('Template uuid not found')
837 self.removeVM(self.vmRootName)
838 # remove template VM folder
839 self.removeVMFolder(self.vmRootName)
840 import_logger.info('Cleanup finished...')
842 import_logger.info('Checking privileges...')
843 result = Cygwin.bashExecute('id -G')
844 if '544' not in result[1]:
845 import_logger.debug('Insufficient privileges.')
846 import_logger.debug("Trying to continue...")
848 # check OpenSecurity Initial VM Image
849 import_logger.debug('Looking for VM image: ' + image_path)
850 result = os.path.isfile(image_path)
853 import_logger.debug('Warning: no OpenSecurity Initial Image found.')
854 import_logger.debug('Please download using the OpenSecurity download tool.')
855 raise OpenSecurityException('OpenSecurity Initial Image not found.')
856 logger.debug('Initial VM image: ' + image_path + ' found')
858 if not self.template_installed():
859 import_logger.info('Importing SDVm template: ' + image_path)
860 Cygwin.vboxExecute('import "' + image_path + '" --vsys 0 --vmname ' + VMManager.vmRootName + ' --unit 12 --disk "' + self.templateImage + '"')
862 import_logger.info('Found ' + VMManager.vmRootName + ' already present in VBox reusing it.')
863 import_logger.info('if you want a complete new import please remove the VM first.')
864 import_logger.info('starting OpenSecurity service...')
867 # remove unnecessary IDE controller
868 Cygwin.vboxExecute('storagectl ' + VMManager.vmRootName + ' --name IDE --remove')
870 info = self.getVDiskController(VMManager.vmRootName, self.templateImage)
872 info = info.split('-')
873 self.detachVDisk(VMManager.vmRootName, info[0], info[1], info[2])
875 self.changeVDiskType(self.templateImage, 'immutable')
876 self.attachVDisk(VMManager.vmRootName, info[0], info[1], info[2], self.templateImage)
877 import_logger.info('Initial import finished.')
880 def updateTemplate(self):
881 import_logger.debug('Stopping Opensecurity...')
884 import_logger.debug('Cleaning up system in preparation for update...')
887 import_logger.info('Cleanup finished...')
889 # shutdown template if running
890 self.poweroffVM(self.vmRootName)
892 import_logger.info('Starting template VM...')
894 self.genCertificate(self.vmRootName)
895 self.attachCertificate(self.vmRootName)
897 import_logger.info('Removing snapshots...')
899 self.detachVDisk(self.vmRootName, 'SATA', '0', '0')
900 templateUUID = self.getVDiskUUID(self.templateImage)
901 self.removeSnapshots(templateUUID)
903 import_logger.info('Setting VDisk image to normal...')
904 self.changeVDiskType(self.templateImage, 'normal')
905 self.attachVDisk(self.vmRootName, 'SATA', '0', '0', self.templateImage)
907 import_logger.info('Starting VM...')
908 self.startVM(self.vmRootName)
909 self.waitStartup(self.vmRootName)
911 import_logger.info('Updating components...')
912 tmp_ip = self.getHostOnlyIP(self.vmRootName)
914 Cygwin.sshExecute('"sudo apt-get -y update"', tmp_ip, 'osecuser', self.getCertificatePath(self.vmRootName) )
915 Cygwin.sshExecute('"sudo apt-get -y dist-upgrade"', tmp_ip, 'osecuser', self.getCertificatePath(self.vmRootName) )
917 import_logger.info('Restarting template VM...')
918 #check if reboot is required
919 result = Cygwin.sshExecute('"if [ -f /var/run/reboot-required ]; then echo \\\"Yes\\\"; fi"', tmp_ip, 'osecuser', self.getCertificatePath(self.vmRootName) )
920 if "Yes" in result[1]:
921 self.stopVM(self.vmRootName)
922 self.waitShutdown(self.vmRootName)
923 self.startVM(self.vmRootName)
924 self.waitStartup(self.vmRootName)
926 import_logger.info('Stopping template VM...')
927 self.stopVM(self.vmRootName)
928 self.waitShutdown(self.vmRootName)
930 import_logger.info('Setting VDisk image to immutable...')
931 self.detachVDisk(self.vmRootName, 'SATA', '0', '0')
932 self.changeVDiskType(self.templateImage, 'immutable')
933 self.attachVDisk(self.vmRootName, 'SATA', '0', '0', self.templateImage)
935 import_logger.info('Update template finished...')
937 def startInitialImport(self):
938 if self.importHandler and self.importHandler.isAlive():
939 import_logger.info("Initial import already running.")
941 self.importHandler = InitialImportHandler(self)
942 self.importHandler.start()
943 import_logger.info("Initial import started.")
945 def startUpdateTemplate(self):
946 if self.updateHandler and self.updateHandler.isAlive():
947 import_logger.info("Template update already running.")
949 self.updateHandler = UpdateHandler(self)
950 self.updateHandler.start()
951 import_logger.info("Template update started.")
953 def createSession(self, browsing=False):
954 new_sdvm = self.newSDVM()
955 self.attachVDisk(new_sdvm, 'SATA', '0', '0', self.templateImage)
956 self.genCertificate(new_sdvm)
957 self.attachCertificate(new_sdvm)
958 self.startVM(new_sdvm)
959 new_ip = self.waitStartup(new_sdvm)
961 logger.error("Error getting IP address of SDVM. Cleaning up.")
962 self.poweroffVM(new_sdvm)
963 self.removeVM(new_sdvm)
966 logger.info("Got IP address for " + new_sdvm + ' ' + new_ip)
967 self.vms[new_sdvm] = {'vm_name' : new_sdvm, 'ip_addr' : new_ip, 'used' : False, 'running' : True, 'browsing' : browsing }
969 # restore browser settings
970 appDataDir = self.getAppDataDir()
971 logger.info("Restoring browser settings in AppData dir " + appDataDir)
972 # create OpenSecurity settings dir on local machine user home /AppData/Roaming
973 Cygwin.bashExecute('/usr/bin/mkdir -p \\\"' + appDataDir + '/OpenSecurity\\\"')
974 # create chromium settings dir on local machine if not existing
975 Cygwin.bashExecute('/usr/bin/mkdir -p \\\"' + appDataDir + '/OpenSecurity/chromium\\\"')
976 # create chromium settings dir on remote machine if not existing
977 Cygwin.sshExecute('"mkdir -p \\\"/home/osecuser/.config\\\""', new_ip, 'osecuser', self.getCertificatePath(new_sdvm))
978 # restore settings to svm from active user settings dir
979 # self.restoreFile(new_sdvm, new_ip, appDataDir + '/OpenSecurity/chromium', '/home/osecuser/.config/')
980 self.syncRemoteFile(new_sdvm, new_ip, appDataDir + '/OpenSecurity/chromium', '/home/osecuser/.config/')
981 return self.vms[new_sdvm]
983 def releaseSession(self, vm_name):
984 del self.vms[vm_name]
985 self.poweroffVM(vm_name)
986 self.removeVM(vm_name)
987 self.sdvmFactory.trigger()
989 def getSession(self, browsing = False):
990 # return first found unused SDVM
991 for vm in self.vms.values():
992 if vm['used'] == False and vm['browsing'] == browsing:
994 self.sdvmFactory.trigger()
996 return self.createSession(browsing)
998 def backupFile(self, vm_name, ip_addr, src, dest):
1001 certificate = self.getCertificatePath(vm_name)
1002 command = '-r -o StrictHostKeyChecking=no -i "' + certificate + '" "osecuser@' + ip_addr + ':' + src + '" "' + dest + '"'
1003 return Cygwin.execute(Cygwin.cygwin_scp, command, wait_return=True, window=False)
1005 def restoreFile(self, vm_name, ip_addr, src, dest):
1006 certificate = self.getCertificatePath(vm_name)
1007 command = '-r -o StrictHostKeyChecking=no -i "' + certificate + '" "' + src + '" "osecuser@' + ip_addr + ':' + dest + '"'
1008 return Cygwin.execute(Cygwin.cygwin_scp, command, wait_return=True, window=False)
1010 def syncRemoteFile(self, vm_name, ip_addr, src, dest):
1011 certificate = self.getCertificatePath(vm_name)
1012 command = '-av -e "\\"' + Cygwin.cygwin_ssh + '\\" -o StrictHostKeyChecking=no -o UserKnownHostsFile=/dev/null -i \\"' + certificate + '\\"" "' + src + '" "osecuser@' + ip_addr + ':' + dest + '"'
1013 return Cygwin.execute(Cygwin.cygwin_rsync, command, wait_return=True, window=False)
1015 def syncLocalFile(self, vm_name, ip_addr, src, dest):
1018 certificate = self.getCertificatePath(vm_name)
1019 command = '-av -e "\\"' + Cygwin.cygwin_ssh + '\\" -o StrictHostKeyChecking=no -o UserKnownHostsFile=/dev/null -i \\"' + certificate + '\\"" "osecuser@' + ip_addr + ':' + src + '" "' + dest + '"'
1020 return Cygwin.execute(Cygwin.cygwin_rsync, command, wait_return=True, window=False)
1022 class SDVMFactory(threading.Thread):
1027 def __init__(self, vmmanager):
1028 threading.Thread.__init__(self)
1029 self.vmm = vmmanager
1030 self.triggerEv = threading.Event()
1034 self.triggerEv.clear()
1036 # find existance of free device and browsing sessions
1037 freeDeviceSession = False
1038 freeBrowsingSession = False
1039 for vm in self.vmm.vms.values():
1040 if vm['used'] == False and vm['browsing'] == False:
1041 freeDeviceSession = True
1042 if vm['used'] == False and vm['browsing'] == True:
1043 freeBrowsingSession = True
1045 #prepare new sessions if none
1046 if not freeDeviceSession:
1047 self.vmm.createSession(False)
1048 if not freeBrowsingSession:
1049 self.vmm.createSession(True)
1050 self.triggerEv.wait()
1052 def trigger(self, ):
1053 self.triggerEv.set()
1056 self.running = False
1057 self.triggerEv.set()
1059 #handles browsing session creation
1060 class BrowsingHandler(threading.Thread):
1068 def __init__(self, vmmanager, proxy, wpad):
1069 threading.Thread.__init__(self)
1070 self.vmm = vmmanager
1077 session = self.vmm.getSession()
1079 raise OpenSecurityException("Could not get new SDVM session.")
1081 self.ip_addr = session['ip_addr']
1082 self.vm_name = session['vm_name']
1084 self.net_resource = '\\\\' + self.ip_addr + '\\Download'
1085 urllib2.urlopen('http://127.0.0.1:8090/netmount?'+'net_resource='+self.net_resource)#.readline()
1087 # synchronize browser settings
1088 appDataDir = self.vmm.getAppDataDir()
1089 logger.info("Syncing browser settings in AppData dir " + appDataDir)
1090 # sync settings on vm
1091 self.vmm.syncRemoteFile(self.vm_name, self.ip_addr, appDataDir + '/OpenSecurity/chromium', '/home/osecuser/.config/')
1094 browser = '\\\"/usr/bin/chromium --proxy-pac-url=\\\"'+self.wpad+'\\\"\\\"'
1096 browser = '\\\"export http_proxy='+self.proxy+'; /usr/bin/chromium\\\"'
1098 browser = '\\\"/usr/bin/chromium\\\"'
1100 Cygwin.sshExecuteX11(browser, self.ip_addr, 'osecuser', self.vmm.getCertificatePath(self.vm_name))
1101 appDataDir = self.vmm.getAppDataDir()
1102 # self.vmm.backupFile(self.vm_name, self.ip_addr, '/home/osecuser/.config/chromium', appDataDir + '/OpenSecurity/')
1103 self.vmm.syncLocalFile(self.vm_name, self.ip_addr, '/home/osecuser/.config/chromium', appDataDir + '/OpenSecurity/')
1105 except urllib2.URLError:
1106 logger.error("Network drive connect failed. OpenSecurity Tray client not running.")
1107 self.net_resource = None
1110 logger.info("BrowsingHandler failed. See log for details")
1113 if self.net_resource == None:
1114 logger.info("Missing browsing SDVM's network share. Skipping disconnect")
1117 urllib2.urlopen('http://127.0.0.1:8090/netumount?'+'net_resource='+self.net_resource).readline()
1118 self.net_resource = None
1119 except urllib2.URLError:
1120 logger.error("Network share disconnect failed. OpenSecurity Tray client not running.")
1122 self.vmm.releaseSession(self.vm_name)
1124 self.vmm.sdvmFactory.trigger()
1127 class DeviceHandler(threading.Thread):
1132 def __init__(self, vmmanger):
1133 threading.Thread.__init__(self)
1137 self.running = False
1140 self.existingRSDs = dict()
1141 self.attachedRSDs = self.vmm.getAttachedRSDs()
1144 tmp_rsds = self.vmm.getExistingRSDs()
1145 if tmp_rsds.keys() == self.existingRSDs.keys():
1146 logger.debug("Nothing's changed. sleep(3)")
1150 showTrayMessage('System changed.\nEvaluating...', 7000)
1151 logger.info("Something's changed")
1153 tmp_attached = self.attachedRSDs
1154 for vm_name in tmp_attached.keys():
1155 if tmp_attached[vm_name] not in tmp_rsds.values():
1156 ip = self.vmm.getHostOnlyIP(vm_name)
1158 logger.error("Failed getting hostonly IP for " + vm_name)
1162 net_resource = '\\\\' + ip + '\\USB'
1163 result = urllib2.urlopen('http://127.0.0.1:8090/netumount?'+'net_resource='+net_resource).readline()
1164 except urllib2.URLError:
1165 logger.error("Network drive disconnect failed. OpenSecurity Tray client not running.")
1168 # detach not necessary as already removed from vm description upon disconnect
1169 del self.attachedRSDs[vm_name]
1170 self.vmm.releaseSession(vm_name)
1172 #create new vms for new devices if any
1174 for new_device in tmp_rsds.values():
1175 showTrayMessage('Mounting device...', 7000)
1176 if (self.attachedRSDs and False) or (new_device not in self.attachedRSDs.values()):
1178 session = self.vmm.getSession()
1180 logger.info("Could not get new SDVM session.")
1182 #raise OpenSecurityException("Could not get new SDVM session.")
1183 new_sdvm = session['vm_name']
1184 new_ip = session['ip_addr']
1186 self.vmm.attachRSD(new_sdvm, new_device)
1187 self.attachedRSDs[new_sdvm] = new_device
1189 logger.info("RSD prematurely removed. Cleaning up.")
1190 self.vmm.releaseSession(new_sdvm)
1194 net_resource = '\\\\' + new_ip + '\\USB'
1195 result = urllib2.urlopen('http://127.0.0.1:8090/netmount?'+'net_resource='+net_resource).readline()
1196 except urllib2.URLError:
1197 logger.error("Network drive connect failed (tray client not accessible). Cleaning up.")
1198 self.vmm.releaseSession(new_sdvm)
1201 self.existingRSDs = tmp_rsds
1203 class UpdateHandler(threading.Thread):
1205 def __init__(self, vmmanager):
1206 threading.Thread.__init__(self)
1207 self.vmm = vmmanager
1211 self.vmm.updateTemplate()
1213 import_logger.info("Update template failed. Refer to service log for details.")
1214 self.vmm.start(force=True)
1216 class InitialImportHandler(threading.Thread):
1218 def __init__(self, vmmanager):
1219 threading.Thread.__init__(self)
1220 self.vmm = vmmanager
1224 self.vmm.importTemplate(self.vmm.getMachineFolder() + '\\OsecVM.ova')
1225 self.vmm.updateTemplate()
1227 import_logger.info("Initial import failed. Refer to service log for details.")
1228 self.vmm.start(force=True)