2 # -*- coding: utf-8 -*-
4 # ------------------------------------------------------------
7 # the opensecurityd as RESTful server
9 # Autor: Mihai Bartha, <mihai.bartha@ait.ac.at>
11 # Copyright (C) 2013 AIT Austrian Institute of Technology
12 # AIT Austrian Institute of Technology GmbH
13 # Donau-City-Strasse 1 | 1220 Vienna | Austria
14 # http://www.ait.ac.at
16 # This program is free software; you can redistribute it and/or
17 # modify it under the terms of the GNU General Public License
18 # as published by the Free Software Foundation version 2.
20 # This program is distributed in the hope that it will be useful,
21 # but WITHOUT ANY WARRANTY; without even the implied warranty of
22 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
23 # GNU General Public License for more details.
25 # You should have received a copy of the GNU General Public License
26 # along with this program; if not, write to the Free Software
27 # Foundation, Inc., 51 Franklin Street, Fifth Floor,
28 # Boston, MA 02110-1301, USA.
29 # ------------------------------------------------------------
32 # ------------------------------------------------------------
37 from subprocess import Popen, PIPE, call, STARTUPINFO, _subprocess
41 from cygwin import Cygwin
42 from environment import Environment
50 from opensecurity_util import logger, import_logger, setupLogger, OpenSecurityException, showTrayMessage
63 new_sdvm_lock = threading.Lock()
64 backup_lock = threading.Lock()
66 class VMManagerException(Exception):
67 def __init__(self, value):
70 return repr(self.value)
79 def __init__(self, uuid, vendorid, productid, revision, serial):
81 self.vendorid = vendorid.lower()
82 self.productid = productid.lower()
83 self.revision = revision.lower()
87 def __eq__(self, other):
88 return self.uuid == other.uuid #self.vendorid == other.vendorid and self.productid == other.productid and self.revision == other.revision
91 return hash(self.uuid) ^ hash(self.vendorid) ^ hash(self.productid) ^ hash(self.revision) ^ hash(self.serial)
94 return "UUID:" + str(self.uuid) + " VendorId = \'" + str(self.vendorid) + "\' ProductId = \'" + str(self.productid) + "\'" + "\' Revision = \'" + str(self.revision) + "\' SerialNumber = \'" + str(self.serial)
96 #def __getitem__(self, item):
97 # return self.coords[item]
99 theClass.systemProperties = theClass.getSystemProperties()
100 theClass.machineFolder = theClass.systemProperties["Default machine folder"]
101 #theClass.hostonlyIF = theClass.getHostOnlyIFs()["VirtualBox Host-Only Ethernet Adapter"]
102 theClass.blacklistedRSD = theClass.loadRSDBlacklist()
103 theClass.templateImage = theClass.machineFolder + '\\' + theClass.vmRootName + '\\' + theClass.vmRootName + '.vmdk'
107 class VMManager(object):
108 vmRootName = "SecurityDVM"
109 systemProperties = None
114 blacklistedRSD = None
115 status_message = 'Starting up...'
125 # only proceed if we have a working background environment
126 if self.backend_ok():
127 VMManager.hostonlyIF = self.getHostOnlyIFs()["VirtualBox Host-Only Ethernet Adapter"]
130 logger.critical(self.status_message)
135 if VMManager._instance == None:
136 VMManager._instance = VMManager()
137 return VMManager._instance
139 #list the hostonly IFs exposed by the VBox host
141 def getHostOnlyIFs():
142 results = Cygwin.vboxExecute('list hostonlyifs')[1]
146 items = list( "Name: " + result for result in results.split('Name: ') if result != '')
149 props = dict((k.strip(),v.strip().strip('"')) for k,v in (line.split(':', 1) for line in item.strip().splitlines()))
150 ifs[props["Name"]] = props
153 #list the hostonly IFs exposed by the VBox host
155 def getDHCPServers():
156 results = Cygwin.vboxExecute('list dhcpservers')[1]
159 items = list( "NetworkName: " + result for result in results.split('NetworkName: ') if result != '')
163 props = dict((k.strip(),v.strip().strip('"')) for k,v in (line.split(':', 1) for line in item.strip().splitlines()))
164 dhcps[props["NetworkName"]] = props
167 # return hosty system properties
169 def getSystemProperties():
170 result = Cygwin.vboxExecute('list systemproperties')
173 props = dict((k.strip(),v.strip().strip('"')) for k,v in (line.split(':', 1) for line in result[1].strip().splitlines()))
176 # return the folder containing the guest VMs
177 def getMachineFolder(self):
178 return VMManager.machineFolder
180 # verifies the hostonly interface and DHCP server settings
181 def verifyHostOnlySettings(self):
182 interfaceName = "VirtualBox Host-Only Ethernet Adapter"
183 networkName = "HostInterfaceNetworking-VirtualBox Host-Only Ethernet Adapter"
185 hostonlyifs = self.getHostOnlyIFs()
186 if not interfaceName in hostonlyifs.keys():
187 Cygwin.vboxExecute('hostonlyif create')
188 hostonlyifs = self.getHostOnlyIFs()
189 if not interfaceName in hostonlyifs.keys():
192 interface = hostonlyifs[interfaceName]
193 if interface['VBoxNetworkName'] != networkName or interface['DHCP'] != 'Disabled' or interface['IPAddress'] != '192.168.56.1':
194 Cygwin.vboxExecute('hostonlyif ipconfig "' + interfaceName + '" --ip 192.168.56.1 --netmask 255.255.255.0')
196 dhcpservers = self.getDHCPServers()
197 if not networkName in dhcpservers.keys():
198 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')
199 dhcpservers = self.getDHCPServers()
200 if not networkName in dhcpservers.keys():
203 server = dhcpservers[networkName]
204 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':
205 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')
209 def template_installed(self):
210 """ check if we do have our root VMs installed """
212 if not self.vmRootName in vms:
213 self.status_message = 'Unable to locate root SecurityDVM. Please download and setup the initial image.'
217 def backend_ok(self):
218 """check if the backend (VirtualBox) is sufficient for our task"""
220 # ensure we have our system props
221 if VMManager.systemProperties == None:
222 VMManager.systemProperties = self.getSystemProperties()
223 if VMManager.systemProperties == None:
224 self.status_message = 'Failed to get backend system properties. Is Backend (VirtualBox?) installed?'
227 # check for existing Extension pack
228 if not 'Remote desktop ExtPack' in VMManager.systemProperties:
229 self.status_message = 'No remote desktop extension pack found. Please install the "Oracle VM VirtualBox Extension Pack" from https://www.virtualbox.org/wiki/Downloads.'
231 if VMManager.systemProperties['Remote desktop ExtPack'] == 'Oracle VM VirtualBox Extension Pack ':
232 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.'
235 # check the existing hostOnly network settings and try to reconfigure if faulty
236 if not self.verifyHostOnlySettings():
239 # basically all seems nice and ready to rumble
240 self.status_message = 'All is ok.'
243 def start(self, force = False):
245 if self.importHandler and self.importHandler.isAlive():
246 logger.info("Initial update running canceling start.")
249 if self.updateHandler and self.updateHandler.isAlive():
250 logger.info("Update running canceling start.")
255 if self.backend_ok() and self.template_installed():
256 self.sdvmFactory = SDVMFactory(self)
257 self.sdvmFactory.start()
258 self.deviceHandler = DeviceHandler(self)
259 self.deviceHandler.start()
263 if self.sdvmFactory != None:
264 self.sdvmFactory.stop()
265 self.sdvmFactory.join()
266 self.sdvmFactory = None
267 if self.deviceHandler != None:
268 self.deviceHandler.stop()
269 self.deviceHandler.join()
270 self.deviceHandler = None
277 ip = self.getHostOnlyIP(None)
279 result = urllib2.urlopen('http://127.0.0.1:8090/netcleanup?'+'hostonly_ip='+ip).readline()
280 except urllib2.URLError:
281 logger.info("Network drive cleanup all skipped. OpenSecurity Tray client not started yet.")
283 for vm in self.listSDVM():
288 # list all existing VMs registered with VBox
290 result = Cygwin.vboxExecute('list vms')[1]
291 vms = list(k.strip().strip('"') for k,_ in (line.split(' ') for line in result.splitlines()))
295 def listRunningVMS(self):
296 result = Cygwin.vboxExecute('list runningvms')[1]
297 vms = list(k.strip().strip('"') for k,_ in (line.split(' ') for line in result.splitlines()))
300 # list existing SDVMs
305 if vm.startswith(self.vmRootName) and vm != self.vmRootName:
309 # generate valid (not already existing SDVM name). necessary for creating a new VM
310 def genSDVMName(self):
312 for i in range(0,999):
313 if(not self.vmRootName+str(i) in vms):
314 return self.vmRootName+str(i)
318 def loadRSDBlacklist():
321 fo = open(Environment('OpenSecurity').prefix_path +"\\bin\\blacklist.usb", "r")
323 logger.error("Could not open RSD blacklist file.")
326 lines = fo.readlines()
329 parts = line.strip().split(' ')
330 blacklist[parts[0].lower()] = parts[1].lower()
334 def isBlacklisted(device):
335 if VMManager.blacklistedRSD:
336 blacklisted = device.vendorid.lower() in VMManager.blacklistedRSD.keys() and device.productid.lower() == VMManager.blacklistedRSD[device.vendorid]
340 # check if the device is mass storage type
342 def isMassStorageDevice(device):
347 keyname = 'SYSTEM\CurrentControlSet\Enum\USB' + '\VID_' + device.vendorid+'&'+'PID_'+ device.productid
348 vidkey = win32api.RegOpenKey(win32con.HKEY_LOCAL_MACHINE, keyname)
349 devinfokeyname = win32api.RegEnumKey(vidkey, 0)
350 win32api.RegCloseKey(vidkey)
352 devinfokey = win32api.RegOpenKey(win32con.HKEY_LOCAL_MACHINE, keyname+'\\'+devinfokeyname)
353 value = win32api.RegQueryValueEx(devinfokey, 'SERVICE')[0]
354 win32api.RegCloseKey(devinfokey)
355 except Exception as ex:
356 logger.error('Error reading registry.Exception details: %s' %ex)
358 if vidkey is not None:
359 win32api.RegCloseKey(vidkey)
360 if devinfokey is not None:
361 win32api.RegCloseKey(devinfokey)
363 return 'USBSTOR' in value
365 # return the RSDs connected to the host
367 def getExistingRSDs():
368 results = Cygwin.vboxExecute('list usbhost')[1]
369 results = results.split('Host USB Devices:')[1].strip()
371 items = list( "UUID:"+result for result in results.split('UUID:') if result != '')
375 for line in item.splitlines():
377 k,v = line[:line.index(':')].strip(), line[line.index(':')+1:].strip()
380 uuid = re.search(r"(?P<uuid>[0-9A-Fa-f\-]+)", props['UUID']).groupdict()['uuid']
381 vid = re.search(r"\((?P<vid>[0-9A-Fa-f]+)\)", props['VendorId']).groupdict()['vid']
382 pid = re.search(r"\((?P<pid>[0-9A-Fa-f]+)\)", props['ProductId']).groupdict()['pid']
383 rev = re.search(r"\((?P<rev>[0-9A-Fa-f]+)\)", props['Revision']).groupdict()['rev']
385 if 'SerialNumber' in props.keys():
386 serial = re.search(r"(?P<ser>[0-9A-Fa-f]+)", props['SerialNumber']).groupdict()['ser']
387 usb_filter = USBFilter( uuid, vid, pid, rev, serial)
389 if VMManager.isMassStorageDevice(usb_filter) and not VMManager.isBlacklisted(usb_filter):
390 rsds[uuid] = usb_filter
391 logger.debug(usb_filter)
395 # return the attached USB device as usb descriptor for an existing VM
396 def getAttachedRSD(self, vm_name):
397 props = self.getVMInfo(vm_name)
398 keys = set(['USBAttachedUUID1', 'USBAttachedVendorId1', 'USBAttachedProductId1', 'USBAttachedRevision1', 'USBAttachedSerialNumber1'])
399 keyset = set(props.keys())
401 if keyset.issuperset(keys):
402 usb_filter = USBFilter(props['USBAttachedUUID1'], props['USBAttachedVendorId1'], props['USBAttachedProductId1'], props['USBAttachedRevision1'], props['USBAttachedSerialNumber1'])
405 # return the RSDs attached to all existing SDVMs
406 def getAttachedRSDs(self):
407 vms = self.listSDVM()
408 attached_devices = dict()
410 rsd_filter = self.getAttachedRSD(vm)
411 if rsd_filter != None:
412 attached_devices[vm] = rsd_filter
413 return attached_devices
415 # attach removable storage device to VM by provision of filter
416 def attachRSD(self, vm_name, rsd_filter):
417 #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)
418 return Cygwin.vboxExecute('controlvm ' + vm_name + ' usbattach ' + rsd_filter.uuid )
420 # detach removable storage from VM by
421 def detachRSD(self, vm_name, rsd_filter):
422 #return Cygwin.vboxExecute('usbfilter remove 0 --target ' + vm_name)
423 return Cygwin.vboxExecute('controlvm ' + vm_name + ' usbdetach ' + rsd_filter.uuid )
425 # configures hostonly networking and DHCP server. requires admin rights
426 def configureHostNetworking(self):
427 #cmd = 'vboxmanage list hostonlyifs'
428 #Cygwin.vboxExecute(cmd)
429 #cmd = 'vboxmanage hostonlyif remove \"VirtualBox Host-Only Ethernet Adapter\"'
430 #Cygwin.vboxExecute(cmd)
431 #cmd = 'vboxmanage hostonlyif create'
432 #Cygwin.vboxExecute(cmd)
433 Cygwin.vboxExecute('hostonlyif ipconfig \"VirtualBox Host-Only Ethernet Adapter\" --ip 192.168.56.1 --netmask 255.255.255.0')
434 #cmd = 'vboxmanage dhcpserver add'
435 #Cygwin.vboxExecute(cmd)
436 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')
438 def isSDVMExisting(self, vm_name):
439 sdvms = self.listSDVM()
440 return vm_name in sdvms
442 #create new virtual machine instance based on template vm named SecurityDVM (\SecurityDVM\SecurityDVM.vmdk)
443 def createVM(self, vm_name):
444 if self.isSDVMExisting(vm_name):
446 #remove eventually existing SDVM folder
447 machineFolder = Cygwin.cygPath(VMManager.machineFolder)
448 Cygwin.bashExecute('/usr/bin/rm -rf \\\"' + machineFolder + '/' + vm_name + '\\\"')
449 Cygwin.vboxExecute('createvm --name ' + vm_name + ' --ostype Debian --register')
450 Cygwin.vboxExecute('modifyvm ' + vm_name + ' --memory 768 --vram 10 --cpus 1 --usb on --usbehci on --nic1 hostonly --hostonlyadapter1 \"' + self.hostonlyIF['Name'] + '\" --nic2 nat')
451 Cygwin.vboxExecute('storagectl ' + vm_name + ' --name SATA --add sata --portcount 2')
453 #create new SecurityDVM with automatically generated name from template (thread safe)
456 vm_name = self.genSDVMName()
457 self.createVM(vm_name)
460 #VMManager.machineFolder + '\SecurityDVM\SecurityDVM.vmdk
461 # attach storage image to controller
462 def attachVDisk(self, vm_name, vdisk_controller, vdisk_port, vdisk_device, vdisk_image):
463 if self.isVDiskAttached(vm_name, vdisk_controller, vdisk_port, vdisk_device):
464 self.detachVDisk(vm_name, vdisk_controller, vdisk_port, vdisk_device)
465 Cygwin.vboxExecute('storageattach ' + vm_name + ' --storagectl '+ vdisk_controller + ' --port ' + vdisk_port + ' --device ' + vdisk_device + ' --type hdd --medium "'+ vdisk_image + '"')
467 # return true if storage is attached
468 def isVDiskAttached(self, vm_name, vdisk_controller, vdisk_port, vdisk_device):
469 info = self.getVMInfo(vm_name)
470 return (info[vdisk_controller+'-'+vdisk_port+'-'+vdisk_device] != 'none')
472 # detach storage from controller
473 def detachVDisk(self, vm_name, vdisk_controller, vdisk_port, vdisk_device):
474 if self.isVDiskAttached(vm_name, vdisk_controller, vdisk_port, vdisk_device):
475 Cygwin.vboxExecute('storageattach ' + vm_name + ' --storagectl ' + vdisk_controller + ' --port ' + vdisk_port + ' --device ' + vdisk_device + ' --medium none')
477 # modify type of the vdisk_image
478 def changeVDiskType(self, vdisk_image, storage_type):
479 Cygwin.vboxExecute('modifyhd "' + vdisk_image + '" --type ' + storage_type)
481 # grab VM storage controller, port and device for vdisk image name
482 def getVDiskController(self, vm_name, image_name = '.vmdk'):
483 vm_description = self.getVMInfo(vm_name)
484 vdisk_controller = None
485 for key, value in vm_description.iteritems():
486 if image_name in value:
487 vdisk_controller = key
489 return vdisk_controller
491 # return attached vmdk image name containing image_name
492 def getVDiskImage(self, vm_name, image_name = '.vmdk'):
493 vmInfo = self.getVMInfo(vm_name)
495 for value in vmInfo.values():
496 if image_name in value:
501 def getVDiskImages():
502 results = Cygwin.vboxExecute('list hdds')[1]
503 results = results.replace('Parent UUID', 'Parent')
504 items = list( "UUID:"+result for result in results.split('UUID:') if result != '')
509 for line in item.splitlines():
511 k,v = line[:line.index(':')].strip(), line[line.index(':')+1:].strip()
513 snaps[props['UUID']] = props
517 def getVDiskUUID(vdisk_image):
518 images = VMManager.getVDiskImages()
521 for hdd in images.values():
522 if hdd['Location'] == vdisk_image:
523 template_uuid = hdd['UUID']
527 def removeSnapshots(self, imageUUID):
528 snaps = self.getVDiskImages()
530 for hdd in snaps.values():
531 if hdd['Parent'] == imageUUID:
532 snapshotUUID = hdd['UUID']
533 self.removeImage(snapshotUUID)
535 def removeImage(self, imageUUID):
536 logger.debug('removing snapshot ' + imageUUID)
537 Cygwin.vboxExecute('closemedium disk {' + imageUUID + '} --delete')
538 # parse result 0%...10%...20%...30%...40%...50%...60%...70%...80%...90%...100%
540 #remove VM from the system. should be used on VMs returned by listSDVMs
541 def removeVM(self, vm_name):
542 logger.info('Removing ' + vm_name)
543 Cygwin.vboxExecute('unregistervm ' + vm_name + ' --delete')
544 #try to close medium if still existing
545 #Cygwin.vboxExecute('closemedium disk {' + hdd['UUID'] + '} --delete')
546 self.removeVMFolder(vm_name)
548 def removeVMFolder(self, vm_name):
549 machineFolder = Cygwin.cygPath(VMManager.machineFolder)
550 Cygwin.bashExecute('/usr/bin/rm -rf \\\"' + machineFolder + '\\' + vm_name + '\\\"')
553 def startVM(self, vm_name):
554 logger.info('Starting ' + vm_name)
555 Cygwin.vboxExecute('guestproperty set ' + vm_name + ' SDVMStarted False')
556 result = Cygwin.vboxExecute('startvm ' + vm_name + ' --type headless' )
557 while 'successfully started' not in result[1]:
558 logger.error("Failed to start SDVM: " + vm_name + " retrying")
559 logger.error("Command returned:\n" + result[2])
561 result = Cygwin.vboxExecute('startvm ' + vm_name + ' --type headless')
564 # return wether VM is running or not
565 def isVMRunning(self, vm_name):
566 return vm_name in self.listRunningVMS()
569 def stopVM(self, vm_name):
570 logger.info('Sending shutdown signal to ' + vm_name)
571 Cygwin.sshExecute( '"sudo shutdown -h now"', self.getHostOnlyIP(vm_name), 'osecuser', Cygwin.cygPath(VMManager.machineFolder) + '/' + vm_name + '/dvm_key' )
572 Cygwin.vboxExecute('guestproperty set ' + vm_name + ' SDVMStarted False')
575 def hibernateVM(self, vm_name):
576 logger.info('Sending hibernate-disk signal to ' + vm_name)
577 Cygwin.sshBackgroundExecute( '"sudo hibernate-disk"', self.getHostOnlyIP(vm_name), 'osecuser', Cygwin.cygPath(VMManager.machineFolder) + '/' + vm_name + '/dvm_key', wait_return=False)
578 Cygwin.vboxExecute('guestproperty set ' + vm_name + ' SDVMStarted False')
581 def poweroffVM(self, vm_name):
582 if not self.isVMRunning(vm_name):
584 logger.info('Powering off ' + vm_name)
585 Cygwin.vboxExecute('controlvm ' + vm_name + ' poweroff')
586 Cygwin.vboxExecute('guestproperty set ' + vm_name + ' SDVMStarted False')
589 # return the hostOnly IP for a running guest or the host
590 def getHostOnlyIP(self, vm_name):
592 logger.info('Getting hostOnly IP address for Host')
593 return VMManager.hostonlyIF['IPAddress']
595 logger.info('Getting hostOnly IP address ' + vm_name)
596 result = Cygwin.vboxExecute('guestproperty get ' + vm_name + ' /VirtualBox/GuestInfo/Net/0/V4/IP')
600 if result.startswith('No value set!'):
602 return result[result.index(':')+1:].strip()
604 # return the description set for an existing VM
605 def getVMInfo(self, vm_name):
606 results = Cygwin.vboxExecute('showvminfo ' + vm_name + ' --machinereadable')[1]
607 props = dict((k.strip().strip('"'),v.strip().strip('"')) for k,v in (line.split('=', 1) for line in results.splitlines()))
610 #generates ISO containing authorized_keys for use with guest VM
611 def genCertificate(self, vm_name):
612 machineFolder = Cygwin.cygPath(VMManager.machineFolder)
613 # remove .ssh folder if exists
614 Cygwin.bashExecute('/usr/bin/rm -rf \\\"' + machineFolder + '/' + vm_name + '/.ssh\\\"')
615 # remove .ssh folder if exists
616 Cygwin.bashExecute('/usr/bin/rm -rf \\\"' + machineFolder + '/' + vm_name + '/dvm_key\\\"')
617 # create .ssh folder in vm_name
618 Cygwin.bashExecute('/usr/bin/mkdir -p \\\"' + machineFolder + '/' + vm_name + '/.ssh\\\"')
619 # generate dvm_key pair in vm_name / .ssh
620 Cygwin.bashExecute('/usr/bin/ssh-keygen -q -t rsa -N \\\"\\\" -C \\\"' + vm_name + '\\\" -f \\\"' + machineFolder + '/' + vm_name + '/.ssh/dvm_key\\\"')
621 # move out private key
622 Cygwin.bashExecute('/usr/bin/mv \\\"' + machineFolder + '/' + vm_name + '/.ssh/dvm_key\\\" \\\"' + machineFolder + '/' + vm_name + '\\\"')
623 # set permissions for private key
624 Cygwin.bashExecute('/usr/bin/chmod 500 \\\"' + machineFolder + '/' + vm_name + '/dvm_key\\\"')
625 # rename public key to authorized_keys
626 Cygwin.bashExecute('/usr/bin/mv \\\"' + machineFolder + '/' + vm_name + '/.ssh/dvm_key.pub\\\" \\\"' + machineFolder + '/' + vm_name + '/.ssh/authorized_keys\\\"')
627 # set permissions for authorized_keys
628 Cygwin.bashExecute('/usr/bin/chmod 500 \\\"' + machineFolder + '/' + vm_name + '/.ssh/authorized_keys\\\"')
629 # generate iso image with .ssh/authorized keys
630 Cygwin.bashExecute('/usr/bin/genisoimage -J -R -o \\\"' + machineFolder + '/' + vm_name + '/'+ vm_name + '.iso\\\" \\\"' + machineFolder + '/' + vm_name + '/.ssh\\\"')
632 # attaches generated ssh public cert to guest vm
633 def attachCertificate(self, vm_name):
634 if self.isCertificateAttached(vm_name):
635 self.detachCertificate(vm_name)
636 Cygwin.vboxExecute('storageattach ' + vm_name + ' --storagectl SATA --port 1 --device 0 --type dvddrive --mtype readonly --medium \"' + VMManager.machineFolder + '\\' + vm_name + '\\'+ vm_name + '.iso\"')
638 # return true if storage is attached
639 def isCertificateAttached(self, vm_name):
640 info = self.getVMInfo(vm_name)
641 return (info['SATA-1-0']!='none')
643 # detach storage from controller
644 def detachCertificate(self, vm_name):
645 if self.isCertificateAttached(vm_name):
646 Cygwin.vboxExecute('storageattach ' + vm_name + ' --storagectl SATA --port 1 --device 0 --type hdd --medium none')
648 # wait for machine to come up
649 def waitStartup(self, vm_name):
650 #Cygwin.vboxExecute('guestproperty wait ' + vm_name + ' SDVMStarted --timeout ' + str(timeout_ms) + ' --fail-on-timeout', try_count = 60)
653 result = Cygwin.vboxExecute('guestproperty get ' + vm_name + ' SDVMStarted')[1]
654 if "Value: True" in result:
658 return self.getHostOnlyIP(vm_name)
660 # wait for machine to shutdown
661 def waitShutdown(self, vm_name):
662 while vm_name in self.listRunningVMS():
666 #Small function to check if the mentioned location is a directory
667 def isDirectory(self, path):
668 result = Cygwin.cmdExecute('dir ' + path + ' | FIND ".."')
669 return string.find(result[1], 'DIR',)
671 def genNetworkDrive(self):
672 logical_drives = VMManager.getLogicalDrives()
673 logger.info("Used logical drive letters: "+ str(logical_drives).strip('[]') )
674 drives = list(map(chr, range(68, 91)))
676 if drive not in logical_drives:
680 def getLogicalDrives():
681 drive_bitmask = ctypes.cdll.kernel32.GetLogicalDrives()
682 drives = list(itertools.compress(string.ascii_uppercase, map(lambda x:ord(x) - ord('0'), bin(drive_bitmask)[:1:-1])))
686 def getDriveType(drive):
687 return ctypes.cdll.kernel32.GetDriveTypeW(u"%s:\\"%drive)
690 def getNetworkPath(drive):
691 return win32wnet.WNetGetConnection(drive+':')
694 def getVolumeInfo(drive):
695 volumeNameBuffer = ctypes.create_unicode_buffer(1024)
696 fileSystemNameBuffer = ctypes.create_unicode_buffer(1024)
698 max_component_length = None
699 file_system_flags = None
701 rc = ctypes.cdll.kernel32.GetVolumeInformationW(
704 ctypes.sizeof(volumeNameBuffer),
706 max_component_length,
708 fileSystemNameBuffer,
709 ctypes.sizeof(fileSystemNameBuffer)
711 return volumeNameBuffer.value, fileSystemNameBuffer.value
713 def getNetworkDrive(self, vm_name):
714 ip = self.getHostOnlyIP(vm_name)
716 logger.error("Failed getting hostonly IP for " + vm_name)
718 logger.info("Got IP address for " + vm_name + ': ' + ip)
719 for drive in VMManager.getLogicalDrives():
720 #if is a network drive
721 if VMManager.getDriveType(drive) == 4:
722 network_path = VMManager.getNetworkPath(drive)
723 if ip in network_path:
727 def getNetworkDrives(self):
728 ip = self.getHostOnlyIP(None)
730 logger.error("Failed getting hostonly IP for system")
732 logger.info("Got IP address for system: " + ip)
733 ip = ip[:ip.rindex('.')]
734 network_drives = dict()
735 for drive in VMManager.getLogicalDrives():
736 #if is a network drive
737 if VMManager.getDriveType(drive) == 4:
738 network_path = VMManager.getNetworkPath(drive)
739 if ip in network_path:
740 network_drives[drive] = network_path
741 return network_drives
743 # handles browsing request
744 def handleBrowsingRequest(self, proxy = None, wpad = None):
745 showTrayMessage('Starting Secure Browsing...', 7000)
746 handler = BrowsingHandler(self, proxy, wpad)
750 def getActiveUserName(self):
751 key = win32api.RegOpenKey(win32con.HKEY_LOCAL_MACHINE, 'SOFTWARE\Microsoft\Windows\CurrentVersion\Authentication\LogonUI')
752 v = str(win32api.RegQueryValueEx(key, 'LastLoggedOnUser')[0])
753 win32api.RegCloseKey(key)
754 user_name = win32api.ExpandEnvironmentStrings(v)
757 def getUserSID(self, user_name):
758 domain, user = user_name.split("\\")
759 account_name = win32security.LookupAccountName(domain, user)
760 if account_name == None:
761 logger.error("Failed lookup account name for user " + user_name)
763 sid = win32security.ConvertSidToStringSid(account_name[0])
765 logger.error("Failed converting SID for account " + account_name[0])
769 def getAppDataDirReg(self, sid):
770 key = win32api.RegOpenKey(win32con.HKEY_USERS, sid + '\Software\Microsoft\Windows\CurrentVersion\Explorer\Shell Folders')
771 value, _ = win32api.RegQueryValueEx(key, "AppData")
772 win32api.RegCloseKey(key)
775 def getAppDataDir(self):
776 user = self.getActiveUserName()
778 logger.error("Cannot get active user name")
779 raise OpenSecurityException("Cannot get active user name")
781 logger.info('Got active user name ' + user)
782 sid = self.getUserSID(user)
784 logger.error("Cannot get SID for active user")
785 raise OpenSecurityException("Cannot get SID for active user")
787 logger.info("Got active user SID " + sid + " for user " + user)
789 path = self.getAppDataDirReg(sid)
791 logger.error("Cannot get AppDataDir for active user")
792 raise OpenSecurityException("Cannot get AppDataDir for active user")
794 logger.info("Got AppData dir for user " + user + ': ' + path)
796 return Cygwin.cygPath(path)
798 #import initial template
799 def importTemplate(self, image_path):
800 import_logger.info('Stopping Opensecurity...')
803 import_logger.info('Cleaning up system in preparation for import...')
806 import_logger.info('Removing template SDVM...')
808 if self.vmRootName in self.listVMS():
809 # shutdown template if running
810 self.poweroffVM(self.vmRootName)
811 # detach and remove VDisk
812 tmplateUUID = self.getVDiskUUID(self.templateImage)
813 if tmplateUUID != None:
814 logger.debug('Found template VDisk uuid ' + tmplateUUID)
815 controller = self.getVDiskController(self.vmRootName)
817 controller = controller.split('-')
818 self.detachVDisk(self.vmRootName, controller[0], controller[1], controller[2])
819 self.removeSnapshots(tmplateUUID)
820 self.removeImage(tmplateUUID)
822 logger.info('Template uuid not found')
824 self.removeVM(self.vmRootName)
825 # remove template VM folder
826 self.removeVMFolder(self.vmRootName)
827 import_logger.info('Cleanup finished...')
829 import_logger.info('Checking privileges...')
830 result = Cygwin.bashExecute('id -G')
831 if '544' not in result[1]:
832 import_logger.debug('Insufficient privileges.')
833 import_logger.debug("Trying to continue...")
835 # check OpenSecurity Initial VM Image
836 import_logger.debug('Looking for VM image: ' + image_path)
837 result = os.path.isfile(image_path)
840 import_logger.debug('Warning: no OpenSecurity Initial Image found.')
841 import_logger.debug('Please download using the OpenSecurity download tool.')
842 raise OpenSecurityException('OpenSecurity Initial Image not found.')
843 logger.debug('Initial VM image: ' + image_path + ' found')
845 if not self.template_installed():
846 import_logger.info('Importing SDVm template: ' + image_path)
847 Cygwin.vboxExecute('import "' + image_path + '" --vsys 0 --vmname ' + VMManager.vmRootName + ' --unit 12 --disk "' + self.templateImage + '"')
849 import_logger.info('Found ' + VMManager.vmRootName + ' already present in VBox reusing it.')
850 import_logger.info('if you want a complete new import please remove the VM first.')
851 import_logger.info('starting OpenSecurity service...')
854 # remove unnecessary IDE controller
855 Cygwin.vboxExecute('storagectl ' + VMManager.vmRootName + ' --name IDE --remove')
857 info = self.getVDiskController(VMManager.vmRootName, self.templateImage)
859 info = info.split('-')
860 self.detachVDisk(VMManager.vmRootName, info[0], info[1], info[2])
862 self.changeVDiskType(self.templateImage, 'immutable')
863 self.attachVDisk(VMManager.vmRootName, info[0], info[1], info[2], self.templateImage)
864 import_logger.info('Initial import finished.')
867 def updateTemplate(self):
868 import_logger.debug('Stopping Opensecurity...')
871 import_logger.debug('Cleaning up system in preparation for update...')
874 import_logger.info('Cleanup finished...')
876 # shutdown template if running
877 self.poweroffVM(self.vmRootName)
879 import_logger.info('Starting template VM...')
881 self.genCertificate(self.vmRootName)
882 self.attachCertificate(self.vmRootName)
884 import_logger.info('Removing snapshots...')
886 self.detachVDisk(self.vmRootName, 'SATA', '0', '0')
887 templateUUID = self.getVDiskUUID(self.templateImage)
888 self.removeSnapshots(templateUUID)
890 import_logger.info('Setting VDisk image to normal...')
891 self.changeVDiskType(self.templateImage, 'normal')
892 self.attachVDisk(self.vmRootName, 'SATA', '0', '0', self.templateImage)
894 import_logger.info('Starting VM...')
895 self.startVM(self.vmRootName)
896 self.waitStartup(self.vmRootName)
898 import_logger.info('Updating components...')
899 tmp_ip = self.getHostOnlyIP(self.vmRootName)
900 tmp_machine_folder = Cygwin.cygPath(VMManager.machineFolder)
901 Cygwin.sshExecute('"sudo apt-get -y update"', tmp_ip, 'osecuser', tmp_machine_folder + '/' + self.vmRootName + '/dvm_key')
902 Cygwin.sshExecute('"sudo apt-get -y upgrade"', tmp_ip, 'osecuser', tmp_machine_folder + '/' + self.vmRootName + '/dvm_key')
904 import_logger.info('Restarting template VM...')
905 #check if reboot is required
906 result = Cygwin.sshExecute('"if [ -f /var/run/reboot-required ]; then echo \\\"Yes\\\"; fi"', tmp_ip, 'osecuser', tmp_machine_folder + '/' + self.vmRootName + '/dvm_key')
907 if "Yes" in result[1]:
908 self.stopVM(self.vmRootName)
909 self.waitShutdown(self.vmRootName)
910 self.startVM(self.vmRootName)
911 self.waitStartup(self.vmRootName)
913 import_logger.info('Stopping template VM...')
914 self.stopVM(self.vmRootName)
915 self.waitShutdown(self.vmRootName)
917 import_logger.info('Setting VDisk image to immutable...')
918 self.detachVDisk(self.vmRootName, 'SATA', '0', '0')
919 self.changeVDiskType(self.templateImage, 'immutable')
920 self.attachVDisk(self.vmRootName, 'SATA', '0', '0', self.templateImage)
922 import_logger.info('Update template finished...')
924 def startInitialImport(self):
925 if self.importHandler and self.importHandler.isAlive():
926 import_logger.info("Initial import already running.")
928 self.importHandler = InitialImportHandler(self)
929 self.importHandler.start()
930 import_logger.info("Initial import started.")
932 def startUpdateTemplate(self):
933 if self.updateHandler and self.updateHandler.isAlive():
934 import_logger.info("Template update already running.")
936 self.updateHandler = UpdateHandler(self)
937 self.updateHandler.start()
938 import_logger.info("Template update started.")
940 def createSession(self, browsing=False):
941 new_sdvm = self.newSDVM()
942 self.attachVDisk(new_sdvm, 'SATA', '0', '0', self.templateImage)
943 self.genCertificate(new_sdvm)
944 self.attachCertificate(new_sdvm)
945 self.startVM(new_sdvm)
946 new_ip = self.waitStartup(new_sdvm)
948 logger.error("Error getting IP address of SDVM. Cleaning up.")
949 self.poweroffVM(new_sdvm)
950 self.removeVM(new_sdvm)
953 logger.info("Got IP address for " + new_sdvm + ' ' + new_ip)
954 self.vms[new_sdvm] = {'vm_name' : new_sdvm, 'ip_addr' : new_ip, 'used' : False, 'running' : True, 'browsing' : browsing }
956 # restore browser settings
957 appDataDir = self.getAppDataDir()
958 logger.info("Restoring browser settings in AppData dir " + appDataDir)
959 # create OpenSecurity settings dir on local machine user home /AppData/Roaming
960 Cygwin.bashExecute('/usr/bin/mkdir -p \\\"' + appDataDir + '/OpenSecurity\\\"')
961 # create chromium settings dir on local machine if not existing
962 Cygwin.bashExecute('/usr/bin/mkdir -p \\\"' + appDataDir + '/OpenSecurity/chromium\\\"')
963 # create chromium settings dir on remote machine if not existing
964 Cygwin.sshExecute('"mkdir -p \\\"/home/osecuser/.config\\\""', new_ip, 'osecuser', Cygwin.cygPath(self.getMachineFolder()) + '/' + new_sdvm + '/dvm_key')
965 #restore settings on vm
966 self.restoreFile(new_sdvm, new_ip, appDataDir + '/OpenSecurity/chromium', '/home/osecuser/.config/')
967 return self.vms[new_sdvm]
969 def releaseSession(self, vm_name):
970 del self.vms[vm_name]
971 self.poweroffVM(vm_name)
972 self.removeVM(vm_name)
973 self.sdvmFactory.trigger()
975 def getSession(self, browsing = False):
976 # return first found unused SDVM
977 for vm in self.vms.values():
978 if vm['used'] == False and vm['browsing'] == browsing:
980 self.sdvmFactory.trigger()
982 return self.createSession(browsing)
985 def backupFile(self, vm_name, ip_addr, src, dest):
988 certificate = Cygwin.cygPath(self.getMachineFolder()) + '/' + vm_name + '/dvm_key'
989 command = '-r -o StrictHostKeyChecking=no -i "' + certificate + '" "osecuser@' + ip_addr + ':' + src + '" "' + dest + '"'
990 return Cygwin.execute(Cygwin.cygwin_scp, command, wait_return=True, window=False)
992 def restoreFile(self, vm_name, ip_addr, src, dest):
993 certificate = Cygwin.cygPath(self.getMachineFolder()) + '/' + vm_name + '/dvm_key'
994 command = '-r -o StrictHostKeyChecking=no -i "' + certificate + '" "' + src + '" "osecuser@' + ip_addr + ':' + dest + '"'
995 return Cygwin.execute(Cygwin.cygwin_scp, command, wait_return=True, window=False)
999 class SDVMFactory(threading.Thread):
1004 def __init__(self, vmmanager):
1005 threading.Thread.__init__(self)
1006 self.vmm = vmmanager
1007 self.triggerEv = threading.Event()
1011 self.triggerEv.clear()
1013 # find existance of free device and browsing sessions
1014 freeDeviceSession = False
1015 freeBrowsingSession = False
1016 for vm in self.vmm.vms.values():
1017 if vm['used'] == False and vm['browsing'] == False:
1018 freeDeviceSession = True
1019 if vm['used'] == False and vm['browsing'] == True:
1020 freeBrowsingSession = True
1022 #prepare new sessions if none
1023 if not freeDeviceSession:
1024 self.vmm.createSession(False)
1025 if not freeBrowsingSession:
1026 self.vmm.createSession(True)
1027 self.triggerEv.wait()
1029 def trigger(self, ):
1030 self.triggerEv.set()
1033 self.running = False
1034 self.triggerEv.set()
1036 #handles browsing session creation
1037 class BrowsingHandler(threading.Thread):
1045 def __init__(self, vmmanager, proxy, wpad):
1046 threading.Thread.__init__(self)
1047 self.vmm = vmmanager
1054 session = self.vmm.getSession()
1056 raise OpenSecurityException("Could not get new SDVM session.")
1058 self.ip_addr = session['ip_addr']
1059 self.vm_name = session['vm_name']
1061 self.net_resource = '\\\\' + self.ip_addr + '\\Download'
1062 urllib2.urlopen('http://127.0.0.1:8090/netmount?'+'net_resource='+self.net_resource)#.readline()
1065 browser = '\\\"/usr/bin/chromium --proxy-pac-url=\\\"'+self.wpad+'\\\"\\\"'
1067 browser = '\\\"export http_proxy='+self.proxy+'; /usr/bin/chromium\\\"'
1069 browser = '\\\"/usr/bin/chromium\\\"'
1071 Cygwin.sshExecuteX11(browser, self.ip_addr, 'osecuser', Cygwin.cygPath(self.vmm.getMachineFolder()) + '/' + self.vm_name + '/dvm_key')
1072 appDataDir = self.vmm.getAppDataDir()
1073 self.vmm.backupFile(self.vm_name, self.ip_addr, '/home/osecuser/.config/chromium', appDataDir + '/OpenSecurity/')
1075 except urllib2.URLError:
1076 logger.error("Network drive connect failed. OpenSecurity Tray client not running.")
1077 self.net_resource = None
1080 logger.info("BrowsingHandler failed. See log for details")
1083 if self.net_resource == None:
1084 logger.info("Missing browsing SDVM's network share. Skipping disconnect")
1087 urllib2.urlopen('http://127.0.0.1:8090/netumount?'+'net_resource='+self.net_resource).readline()
1088 self.net_resource = None
1089 except urllib2.URLError:
1090 logger.error("Network share disconnect failed. OpenSecurity Tray client not running.")
1092 self.vmm.releaseSession(self.vm_name)
1094 self.vmm.sdvmFactory.trigger()
1099 class DeviceHandler(threading.Thread):
1104 def __init__(self, vmmanger):
1105 threading.Thread.__init__(self)
1109 self.running = False
1112 self.existingRSDs = dict()
1113 self.attachedRSDs = self.vmm.getAttachedRSDs()
1116 tmp_rsds = self.vmm.getExistingRSDs()
1117 if tmp_rsds.keys() == self.existingRSDs.keys():
1118 logger.debug("Nothing's changed. sleep(3)")
1122 showTrayMessage('System changed.\nEvaluating...', 7000)
1123 logger.info("Something's changed")
1125 tmp_attached = self.attachedRSDs
1126 for vm_name in tmp_attached.keys():
1127 if tmp_attached[vm_name] not in tmp_rsds.values():
1128 ip = self.vmm.getHostOnlyIP(vm_name)
1130 logger.error("Failed getting hostonly IP for " + vm_name)
1134 net_resource = '\\\\' + ip + '\\USB'
1135 result = urllib2.urlopen('http://127.0.0.1:8090/netumount?'+'net_resource='+net_resource).readline()
1136 except urllib2.URLError:
1137 logger.error("Network drive disconnect failed. OpenSecurity Tray client not running.")
1140 # detach not necessary as already removed from vm description upon disconnect
1141 del self.attachedRSDs[vm_name]
1142 self.vmm.releaseSession(vm_name)
1144 #create new vms for new devices if any
1146 for new_device in tmp_rsds.values():
1147 showTrayMessage('Mounting device...', 7000)
1148 if (self.attachedRSDs and False) or (new_device not in self.attachedRSDs.values()):
1150 session = self.vmm.getSession()
1152 logger.info("Could not get new SDVM session.")
1154 #raise OpenSecurityException("Could not get new SDVM session.")
1155 new_sdvm = session['vm_name']
1156 new_ip = session['ip_addr']
1158 self.vmm.attachRSD(new_sdvm, new_device)
1159 self.attachedRSDs[new_sdvm] = new_device
1161 logger.info("RSD prematurely removed. Cleaning up.")
1162 self.vmm.releaseSession(new_sdvm)
1166 net_resource = '\\\\' + new_ip + '\\USB'
1167 result = urllib2.urlopen('http://127.0.0.1:8090/netmount?'+'net_resource='+net_resource).readline()
1168 except urllib2.URLError:
1169 logger.error("Network drive connect failed (tray client not accessible). Cleaning up.")
1170 self.vmm.releaseSession(new_sdvm)
1173 self.existingRSDs = tmp_rsds
1175 class UpdateHandler(threading.Thread):
1177 def __init__(self, vmmanager):
1178 threading.Thread.__init__(self)
1179 self.vmm = vmmanager
1183 self.vmm.updateTemplate()
1185 import_logger.info("Update template failed. Refer to service log for details.")
1186 self.vmm.start(force=True)
1188 class InitialImportHandler(threading.Thread):
1190 def __init__(self, vmmanager):
1191 threading.Thread.__init__(self)
1192 self.vmm = vmmanager
1196 self.vmm.importTemplate(self.vmm.getMachineFolder() + '\\OsecVM.ova')
1197 self.vmm.updateTemplate()
1199 import_logger.info("Initial import failed. Refer to service log for details.")
1200 self.vmm.start(force=True)