Browse Source

Export DNS information obtained during firewall setup to QubesDB

This can e.g. allow DNS applications to pin a FQDN to the IP
used during Qubes OS firewall setup.

Can help with QubesOS/qubes-issues#5225 and other related issues.
3hhh 2 years ago
parent
commit
2a5af195f1
1 changed files with 31 additions and 5 deletions
  1. 31 5
      qubesagent/firewall.py

+ 31 - 5
qubesagent/firewall.py

@@ -127,6 +127,23 @@ class FirewallWorker(object):
         rules.append({'action': policy})
         return rules
 
+    def write_dns_info(self, source, host, hostaddrs):
+        """
+        Write resolved DNS addresses back to QubesDB. This can be useful
+        for the user or DNS applications to pin these DNS addresses to the
+        IPs resolved during firewall setup.
+
+        :param source: VM IP
+        :param host: hostname
+        :param hostaddrs: set of IP addresses :host: was resolved to
+        :return: None
+        """
+        self.qdb.write('/dns/{}/{}'.format(source, host), str(hostaddrs))
+
+    def clear_dns_info(self, source):
+        """ Clear all DNS info for the given VM IP."""
+        self.qdb.rm('/dns/{}/'.format(source))
+
     def list_targets(self):
         return set(t.split('/')[2] for t in self.qdb.list('/qubes-firewall/'))
 
@@ -264,13 +281,14 @@ class IptablesWorker(FirewallWorker):
             ['-I', 'QBS-FORWARD', '-s', addr, '-j', chain])
         self.chains[family].add(chain)
 
-    def prepare_rules(self, chain, rules, family):
+    def prepare_rules(self, chain, rules, family, source):
         """
         Helper function to translate rules list into input for iptables-restore
 
         :param chain: name of the chain to put rules into
         :param rules: list of rules
         :param family: address family (4 or 6)
+        :param source: source for which to apply the chain
         :return: input for iptables-restore
         :rtype: str
         """
@@ -281,6 +299,8 @@ class IptablesWorker(FirewallWorker):
 
         dns = list(addr + fullmask for addr in self.dns_addresses(family))
 
+        self.clear_dns_info(source)
+
         for rule in rules:
             unsupported_opts = set(rule.keys()).difference(
                 set(self.supported_rule_opts))
@@ -312,6 +332,7 @@ class IptablesWorker(FirewallWorker):
                     raise RuleParseError('Failed to resolve {}: {}'.format(
                         rule['dsthost'], str(e)))
                 dsthosts = set(item[4][0] + fullmask for item in addrinfo)
+                self.write_dns_info(source, rule['dsthost'], dsthosts)
             else:
                 dsthosts = None
 
@@ -391,7 +412,7 @@ class IptablesWorker(FirewallWorker):
         if chain not in self.chains[family]:
             self.create_chain(source, chain, family)
 
-        iptables = self.prepare_rules(chain, rules, family)
+        iptables = self.prepare_rules(chain, rules, family, source)
         try:
             self.run_ipt(family, ['-F', chain])
             p = self.run_ipt_restore(family, ['-n'])
@@ -559,13 +580,14 @@ class NftablesWorker(FirewallWorker):
 
         self.run_nft(nft_input)
 
-    def prepare_rules(self, chain, rules, family):
+    def prepare_rules(self, chain, rules, family, source):
         """
         Helper function to translate rules list into input for iptables-restore
 
         :param chain: name of the chain to put rules into
         :param rules: list of rules
         :param family: address family (4 or 6)
+        :param source: source for which to apply the chain
         :return: input for iptables-restore
         :rtype: str
         """
@@ -578,6 +600,8 @@ class NftablesWorker(FirewallWorker):
 
         dns = list(addr + fullmask for addr in self.dns_addresses(family))
 
+        self.clear_dns_info(source)
+
         for rule in rules:
             unsupported_opts = set(rule.keys()).difference(
                 set(self.supported_rule_opts))
@@ -622,8 +646,10 @@ class NftablesWorker(FirewallWorker):
                 except UnicodeError as e:
                     raise RuleParseError('Invalid destination {}: {}'.format(
                         rule['dsthost'], str(e)))
+                dsthosts = set(item[4][0] + fullmask for item in addrinfo)
                 nft_rule += ' {} daddr {{ {} }}'.format(ip_match,
-                    ', '.join(set(item[4][0] + fullmask for item in addrinfo)))
+                    ', '.join(dsthosts))
+                self.write_dns_info(source, rule['dsthost'], dsthosts)
 
             if 'dstports' in rule:
                 dstports = rule['dstports']
@@ -694,7 +720,7 @@ class NftablesWorker(FirewallWorker):
         if chain not in self.chains[family]:
             self.create_chain(source, chain, family)
 
-        self.run_nft(self.prepare_rules(chain, rules, family))
+        self.run_nft(self.prepare_rules(chain, rules, family, source))
 
     def apply_rules(self, source, rules):
         if self.is_ip6(source):