Browse Source

backup: add firewall and appmenus list handling

Marek Marczykowski-Górecki 6 years ago
parent
commit
f1036c27a7
3 changed files with 135 additions and 2 deletions
  1. 21 2
      qubesadmin/backup/__init__.py
  2. 97 0
      qubesadmin/backup/core2.py
  3. 17 0
      qubesadmin/backup/core3.py

+ 21 - 2
qubesadmin/backup/__init__.py

@@ -442,6 +442,9 @@ class ExtractWorker3(Process):
         for fname, (data_func, size_func) in self.handlers.items():
             if not fname.startswith(dirname + '/'):
                 continue
+            if not os.path.exists(fname):
+                # for example firewall.xml
+                continue
             if size_func is not None:
                 size_func(os.path.getsize(fname))
             with open(fname, 'rb') as input_file:
@@ -741,6 +744,10 @@ class BackupVM(object):
         '''Report whether a VM is included in the backup'''
         return False
 
+    def handle_firewall_xml(self, vm, stream):
+        '''Import appropriate format of firewall.xml'''
+        raise NotImplementedError
+
 class BackupRestoreOptions(object):
     '''Options for restore operation'''
     # pylint: disable=too-few-public-methods
@@ -1717,6 +1724,15 @@ class BackupRestore(object):
         if retcode != 0:
             self.log.error("*** Error while setting home directory owner")
 
+    def _handle_appmenus_list(self, vm, stream):
+        '''Handle whitelisted-appmenus.list file'''
+        try:
+            subprocess.check_call(
+                ['qvm-appmenus', '--set-whitelist=-', vm.name],
+                stdin=stream)
+        except subprocess.CalledProcessError:
+            self.log.exception('Failed to set application list for %s', vm.name)
+
     def restore_do(self, restore_info):
         '''
 
@@ -1753,8 +1769,11 @@ class BackupRestore(object):
                     size_func = volume.resize
                     handlers[os.path.join(vm_info.subdir, name + '.img')] = \
                         (data_func, size_func)
-                # TODO applications whitelist
-                # TODO firewall
+                handlers[os.path.join(vm_info.subdir, 'firewall.xml')] = (
+                    functools.partial(vm_info.vm.handle_firewall_xml, vm), None)
+                handlers[os.path.join(vm_info.subdir,
+                    'whitelisted-appmenus.list')] = (
+                    functools.partial(self._handle_appmenus_list, vm), None)
 
         if 'dom0' in restore_info.keys() and \
                 restore_info['dom0'].good_to_go:

+ 97 - 0
qubesadmin/backup/core2.py

@@ -25,6 +25,7 @@ import xml.parsers
 import logging
 import lxml.etree
 
+from qubesadmin.firewall import Rule, Action, Proto, DstHost, SpecialTarget
 import qubesadmin.backup
 
 service_to_feature = {
@@ -44,6 +45,102 @@ class Core2VM(qubesadmin.backup.BackupVM):
     def included_in_backup(self):
         return self.backup_content
 
+    @staticmethod
+    def rule_from_xml_v1(node, action):
+        '''Parse single rule in old XML format (pre Qubes 4.0)
+
+        :param node: XML node for the rule
+        :param action: action to apply (in old format it wasn't part of the
+        rule itself)
+        '''
+        netmask = node.get('netmask')
+        if netmask is None:
+            netmask = 32
+        else:
+            netmask = int(netmask)
+        address = node.get('address')
+        if address:
+            dsthost = DstHost(address, netmask)
+        else:
+            dsthost = None
+
+        proto = node.get('proto')
+
+        port = node.get('port')
+        toport = node.get('toport')
+        if port and toport:
+            dstports = port + '-' + toport
+        elif port:
+            dstports = port
+        else:
+            dstports = None
+
+        # backward compatibility: protocol defaults to TCP if port is specified
+        if dstports and not proto:
+            proto = 'tcp'
+
+        if proto == 'any':
+            proto = None
+
+        expire = node.get('expire')
+
+        kwargs = {
+            'action': action,
+        }
+        if dsthost:
+            kwargs['dsthost'] = dsthost
+        if dstports:
+            kwargs['dstports'] = dstports
+        if proto:
+            kwargs['proto'] = proto
+        if expire:
+            kwargs['expire'] = expire
+
+        return Rule(None, **kwargs)
+
+
+    def handle_firewall_xml(self, vm, stream):
+        '''Load old (Qubes < 4.0) firewall XML format'''
+        try:
+            tree = lxml.etree.parse(stream)  # pylint: disable=no-member
+            xml_root = tree.getroot()
+            policy_v1 = xml_root.get('policy')
+            assert policy_v1 in ('allow', 'deny')
+            default_policy_is_accept = (policy_v1 == 'allow')
+            rules = []
+
+            def _translate_action(key):
+                '''Translate action name'''
+                if xml_root.get(key, policy_v1) == 'allow':
+                    return Action.accept
+                return Action.drop
+
+            rules.append(Rule(None,
+                action=_translate_action('dns'),
+                specialtarget=SpecialTarget('dns')))
+
+            rules.append(Rule(None,
+                action=_translate_action('icmp'),
+                proto=Proto.icmp))
+
+            if default_policy_is_accept:
+                rule_action = Action.drop
+            else:
+                rule_action = Action.accept
+
+            for element in xml_root:
+                rule = self.rule_from_xml_v1(element, rule_action)
+                rules.append(rule)
+            if default_policy_is_accept:
+                rules.append(Rule(None, action='accept'))
+            else:
+                rules.append(Rule(None, action='drop'))
+
+            vm.firewall.rules = rules
+        except:  # pylint: disable=bare-except
+            vm.log.exception('Failed to set firewall')
+
+
 class Core2Qubes(qubesadmin.backup.BackupApp):
     '''Parsed qubes.xml'''
     def __init__(self, store=None):

+ 17 - 0
qubesadmin/backup/core3.py

@@ -25,6 +25,7 @@ import logging
 import lxml.etree
 
 import qubesadmin.backup
+import qubesadmin.firewall
 
 class Core3VM(qubesadmin.backup.BackupVM):
     '''VM object'''
@@ -33,6 +34,22 @@ class Core3VM(qubesadmin.backup.BackupVM):
     def included_in_backup(self):
         return self.backup_path is not None
 
+    def handle_firewall_xml(self, vm, stream):
+        '''Load new (Qubes >= 4.0) firewall XML format'''
+        try:
+            tree = lxml.etree.parse(stream)  # pylint: disable=no-member
+            xml_root = tree.getroot()
+            rules = []
+            for rule_node in xml_root.findall('./rules/rule'):
+                rule_opts = {}
+                for rule_opt in rule_node.findall('./properties/property'):
+                    rule_opts[rule_opt.get('name')] = rule_opt.text
+                rules.append(qubesadmin.firewall.Rule(None, **rule_opts))
+
+            vm.firewall.rules = rules
+        except:  # pylint: disable=bare-except
+            vm.log.exception('Failed to set firewall')
+
 class Core3Qubes(qubesadmin.backup.BackupApp):
     '''Parsed qubes.xml'''
     def __init__(self, store=None):