diff --git a/dnssec-trigger-0.12-dnssec-conf.patch b/dnssec-trigger-0.12-dnssec-conf.patch new file mode 100644 index 0000000..f803fe0 --- /dev/null +++ b/dnssec-trigger-0.12-dnssec-conf.patch @@ -0,0 +1,167 @@ +diff --git a/dnssec.conf b/dnssec.conf +index 4d9dbfb..bf896d3 100644 +--- a/dnssec.conf ++++ b/dnssec.conf +@@ -1,54 +1,115 @@ ++# The options configured in this file are supported by dnssec-trigger-script ++# which is called due to various events in related services including ++# dnssec-trigger and NetworkManager. As a result, dnssec-trigger-script, ++# together with the dnssec-trigger daemon, reconfigures a running instance ++# of Unbound, your local validating resolver. ++# ++# Changes in this file are typically applied on the next network change. To ++# make them work immediately, restart the dnssec-trigger service. On many ++# systems this is achieved by the following command: ++# ++# systemctl restart dnssec-triggerd ++# ++# To achieve a clean state of Unbound, you can just restart the unbound ++# service and dnssec-trigger gets restarted automatically. Note that some ++# other services like VPN clients may have reconfigured unbound at runtime ++# and thus may need to be restarted as well. ++# ++# systemctl restart unbound ++# ++# In future some of the options may be interpretted by other services as well, ++# so be careful to restart all of them. One such service may be a future ++# version of NetworkManager. ++# ++# systemctl restart NetworkManager ++# ++ + # validate_connection_provided_zones: + # ----------------------------------- +-# Setts if forward zones added into unbound by dnssec-trigger script +-# will be DNSSEC validated or NOT. Note that this setting is global +-# for all added forward zones.. +-# Possible options are: +-# +-# validate_connection_provided_zones=yes - All connection provided zones +-# configured as forward zones into +-# unbound WILL BE DNSSEC validated +-# (NOTE: If connection provided DNS +-# servers are NOT DNSSEC capable, the +-# resolving of provided zones will +-# NOT work!) +-# +-# validate_connection_provided_zones=no - All connection provided zones +-# configured as forward zones into +-# unbound will NOT be DNSSEC validated +-# +-# +-# NOTICE: if you turn the validation OFF then all forward zones added by +-# dnssec-trigger script will NOT be DNSSEC validated. If you turn the +-# validation ON, only newly added forward zones will be DNSSEC validated. +-# Forward zones added before the change will still NOT be DNSSEC validated. +-# To force validation of previously added forward zone you need to restart +-# it. For VPNs this can be done by restart NetworkManager. ++# Ensures that foward zones provided by NetworkManager connections will be ++# validated by Unbound. ++# ++# Security notes: ++# ++# - If this option is turned off, the network you're connecting to ++# can provide you a list of spoofed domains e.g. via DHCP. Those domains ++# are then configured as insecure forward zones in your local validating ++# resolver, constituting a downgrade attack on DNSSEC validation. ++# ++# - See also security notes on the `add_wifi_provided_zones` option. ++# ++# validate_connection_provided_zones=yes ++# ++# - Connection provided zones will be configured in Unbound as secure forward ++# zones, validated using DNSSEC. ++# ++# If the DNS servers for such a connection are not capable of forwarding ++# DNSSEC queries and responses or the local zone is required to be signed ++# according to the global DNSSEC database, local resources will not be ++# resolved correctly and will appear inaccessible. ++# ++# Many networks use fake top level domains which fail DNSSEC validation ++# as there is no way to validate them at all. Do not use this strict ++# option if you want to access resources on such networks. ++# ++# validate_connection_provided_zones=no ++# ++# - Connection provided zones will be configured in Unbound as insecure ++# forward zones, not validated using DNSSEC. This allows you to access ++# local resources on networks with non-compliant DNS servers as well ++# as networks that hijack domains that are either not in the global DNS ++# tree at all or are required to be signed. ++# ++# Turning this option off has security implications, See the security ++# notice above. ++# + validate_connection_provided_zones=yes + + # add_wifi_provided_zones: + # ------------------------ +-# Setts if domains provided by WiFi connection are configured as forward zones +-# into unbound. +-# Possible options are: +-# +-# add_wifi_provided_zones=yes - Domains provided by ANY WiFi connection will +-# be configured as forward zones into unbound. +-# (NOTE: See the possible security implications +-# stated below!) +-# +-# add_wifi_provided_zones=no - Domains provided by ANY WiFi connection will +-# NOT be configured as forward zones into unbound. +-# (NOTE: Forward zones will be still configured +-# for any other type of connection!) +-# +-# NOTICE: Turning ON the addition of WiFi provided domains as forward zones +-# into unbound may have SECURITY implications such as: +-# - A WiFi access point can intentionally provide you a domain via DHCP for +-# which it does not have authority and route all your DNS queries to its +-# DNS servers. +-# - In addition to the previous point, if you have the DNSSEC validation +-# of forward zones turned OFF, the WiFi provided DNS servers can spoof +-# the IP address for domain names from the provided domain WITHOUT YOU +-# KNOWING IT! ++# Ensures that wifi provided zones are accepted by dnssec-trigger-script just ++# as any other connection provided zones. Wireless ethernet is special in ++# that you often connect to network with no authentication or authentication ++# based on a shared secret. ++# ++# Security notes: ++# ++# - Anyone knowing such a shared secret can set up an access point for the ++# network and provide you a spoofed domain list via DHCP. When this option ++# is turned on, the spoofed domains are configured as forward zones in your ++# local validating resolver. ++# ++# - See also security notes on the `validate_connection_provided_zones` option. ++# ++# add_wifi_provided_zones=yes ++# ++# - Domains provided by WiFi connections will be configured as forward zones ++# in your local validating resolver. See the security notice above. ++# ++# add_wifi_provided_zones=no ++# ++# - Domains provided by WiFi connection will be ignored. ++# + add_wifi_provided_zones=no ++ ++# set_search_domains: ++# ------------------- ++# Enable or disable writing of search domains to `/etc/resolv.conf`. ++# ++# set_search_domains=yes - Search domains are written to `/etc/resolv.conf`. ++# ++# set_search_domains=no - Search domains are not written to `/etc/resolv.conf`. ++# ++set_search_domains=no ++ ++# use_private_address_ranges: ++# --------------------------- ++# Enable or disable adding reverse name resolution zones derived from ++# private IP addresses as defined in RFC 1918 and RFC 4193. ++# ++# use_private_address_ranges=yes - Use standard private IP address ranges to build ++# reverse name resolution zones using the global ++# forwarders. ++# ++# use_private_address_ranges=no - Ignore standard IP address ranges. ++use_private_address_ranges=yes diff --git a/dnssec-trigger-0.12-nm-script.patch b/dnssec-trigger-0.12-nm-script.patch index 07fc30e..9d4275e 100644 --- a/dnssec-trigger-0.12-nm-script.patch +++ b/dnssec-trigger-0.12-nm-script.patch @@ -1,5 +1,5 @@ diff --git a/dnssec-trigger-script.in b/dnssec-trigger-script.in -index b572dd1..69f055d 100644 +index b572dd1..830baa9 100644 --- a/dnssec-trigger-script.in +++ b/dnssec-trigger-script.in @@ -6,17 +6,20 @@ @@ -26,7 +26,7 @@ index b572dd1..69f055d 100644 # NetworkManager reportedly doesn't pass the PATH environment variable. os.environ['PATH'] = "/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin" -@@ -24,12 +27,37 @@ os.environ['PATH'] = "/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/b +@@ -24,12 +27,40 @@ os.environ['PATH'] = "/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/b class UserError(Exception): pass @@ -62,11 +62,14 @@ index b572dd1..69f055d 100644 + "use_vpn_global_forwarders": False, + "use_resolv_conf_symlink": False, + "use_resolv_secure_conf_symlink": False, ++ "use_private_address_ranges": TRUE, ++ "set_search_domains": False, ++ "keep_positive_answers": False, + } def __init__(self): try: -@@ -37,35 +65,36 @@ class Config: +@@ -37,35 +68,44 @@ class Config: for line in config_file: if '=' in line: option, value = [part.strip() for part in line.split("=", 1)] @@ -87,6 +90,14 @@ index b572dd1..69f055d 100644 + + def __str__(self): + return "".format(self.bool_options) ++ ++ @property ++ def flush_command(self): ++ return "flush_negative" if self.keep_positive_answers else "flush_zone" ++ ++config = Config() ++if config.debug: ++ log.setLevel(logging.DEBUG); class ConnectionList: """List of NetworkManager active connections""" @@ -115,7 +126,7 @@ index b572dd1..69f055d 100644 def __iter__(self): for item in self.nm_connections: -@@ -82,6 +111,8 @@ class ConnectionList: +@@ -82,6 +122,8 @@ class ConnectionList: # Skip non-default connections if appropriate if self.only_default and not connection.is_default: continue @@ -124,7 +135,7 @@ index b572dd1..69f055d 100644 yield connection def get_zone_connection_mapping(self): -@@ -190,10 +221,10 @@ class UnboundZoneConfig: +@@ -190,10 +232,10 @@ class UnboundZoneConfig: if fields.pop(0) in ('forward', 'forward:'): fields.pop(0) secure = False @@ -137,7 +148,51 @@ index b572dd1..69f055d 100644 log.debug(self) def __repr__(self): -@@ -255,7 +286,7 @@ class Store: +@@ -216,28 +258,33 @@ class UnboundZoneConfig: + self._commit(zone, None, None) + + def _commit(self, name, servers, secure): +- # Check the list of servers. ++ # FIXME: Older versions of unbound don't print +i for insecure zones ++ # and thus we cannot see whether the zone has changed or not. Therefore ++ # we have no other chance than to re-add existing zones as well. + # +- # Older versions of unbound don't print +i and so we can't distinguish +- # secure and insecure zones properly. Therefore we need to ignore the +- # insecure flag which leads to not being able to switch the zone +- # between secure and insecure unless it's removed or its servers change. +- if self.cache.get(name, [None])[0] == servers: +- log.debug("Connection provided zone '{}' already set to {} ({})".format(name, servers, 'secure' if servers else 'insecure')) +- return ++ # old_servers, old_secure = self.cache.get(name, [None, None]) ++ # if servers, secure == old_servers, old_secure: ++ # log.debug("Connection provided zone '{}' already set to {} ({})".format(name, servers, 'secure' if old_secure else 'insecure')) ++ # return + + if servers: + self.cache[name] = servers, secure + self._control(["forward_add"] + ([] if secure else ["+i"]) + [name] + list(servers)) ++ # Unbound doesn't switch an insecure zone to a secure zone when "+i" is ++ # specified and there is no "-i" to add a secure zone explicitly. ++ if secure: ++ self._control(["insecure_remove", name]) + else: + del self.cache[name] + self._control(["forward_remove", name]) +- self._control(["flush_zone", name]) ++ self._control([config.flush_command, name]) + self._control(["flush_requestlist"]) + + log.debug(self) + +- def _control(self, args): ++ @staticmethod ++ def _control(args): ++ log.debug("unbound-control: {}".format(args)) + subprocess.check_call(["unbound-control"] + args, stdout=DEVNULL, stderr=DEVNULL) + + class Store: +@@ -255,7 +302,7 @@ class Store: line = line.strip() if line: self.cache.add(line) @@ -146,7 +201,7 @@ index b572dd1..69f055d 100644 pass log.debug(self) -@@ -277,10 +308,16 @@ class Store: +@@ -277,10 +324,16 @@ class Store: log.debug(self) def update(self, zones): @@ -166,7 +221,7 @@ index b572dd1..69f055d 100644 def remove(self, zone): """Remove zone from the cache.""" -@@ -309,10 +346,21 @@ class GlobalForwarders: +@@ -309,10 +362,29 @@ class GlobalForwarders: line = line.strip() if line: self.cache.add(line) @@ -186,17 +241,22 @@ index b572dd1..69f055d 100644 + + resolvconf_localhost_contents = "# Generated by dnssec-trigger-script\nnameserver 127.0.0.1\n" + ++ rfc1918_reverse_zones = [ ++ "c.f.ip6.arpa", ++ "d.f.ip6.arpa", ++ "168.192.in-addr.arpa", ++ ] + ["{}.172.in-addr.arpa".format(octet) for octet in range(16, 32)] + [ ++ "10.in-addr.arpa", ++ ] if config.use_private_address_ranges else [] ++ def __init__(self, argv): if len(argv) > 1 and argv[1] == '--debug': argv.pop(1) -@@ -327,108 +375,222 @@ class Application: +@@ -327,108 +399,246 @@ class Application: self.method = getattr(self, "run_" + argv[1][2:].replace('-', '_')) except AttributeError: self.usage() -+ - self.config = Config() -+ if self.config.debug: -+ log.setLevel(logging.DEBUG); +- self.config = Config() + + self.client = NMClient.Client() @@ -229,7 +289,7 @@ index b572dd1..69f055d 100644 + try: + with open(path) as source: + if source.read() != self.resolvconf_localhost_contents: -+ log.warning("Detected incorrect contents of {!r}!".format(path)) ++ log.info("Rewriting {!r}!".format(path)) + return False; + return True + except IOError: @@ -269,10 +329,10 @@ index b572dd1..69f055d 100644 def run_prepare(self): - """Prepare for dnssec-trigger.""" + """Prepare for starting dnssec-trigger -+ + + Called by the service manager before starting dnssec-trigger daemon. + """ - ++ + # Backup resolv.conf when appropriate if not self.nm_handles_resolv_conf(): - log.info("Backing up /etc/resolv.conf") @@ -294,9 +354,15 @@ index b572dd1..69f055d 100644 + Called by dnssec-trigger. + """ + ++ if config.add_search_domains: ++ zones = set(sum((connection.zones for connection in ConnectionList(self.client)), [])) ++ log.info("Search domains: " + ' '.join(zones)) ++ self.resolvconf_localhost_contents = self.__class__.resolvconf_localhost_contents ++ self.resolvconf_localhost_contents += "search {}\n".format(' '.join(zones)) ++ + self._install_resolv_conf(self.resolvconf_trigger, self.resolvconf_trigger_tmp, False) -+ self._install_resolv_conf(self.resolvconf, self.resolvconf_tmp, self.config.use_resolv_conf_symlink) -+ self._install_resolv_conf(self.resolvconf_secure, self.resolvconf_secure_tmp, self.config.use_resolv_secure_conf_symlink) ++ self._install_resolv_conf(self.resolvconf, self.resolvconf_tmp, config.use_resolv_conf_symlink) ++ self._install_resolv_conf(self.resolvconf_secure, self.resolvconf_secure_tmp, config.use_resolv_secure_conf_symlink) + + def run_restore(self): + """Restore resolv.conf with original data @@ -354,7 +420,8 @@ index b572dd1..69f055d 100644 + for server in stored_servers: + stored_servers.remove(server) stored_zones.commit() -- ++ stored_servers.commit() + - log.debug("recovering /etc/resolv.conf") - subprocess.check_call(["chattr", "-i", "/etc/resolv.conf"]) - if not self.nm_handles_resolv_conf(): @@ -365,7 +432,15 @@ index b572dd1..69f055d 100644 - subprocess.check_call(["systemctl", "try-restart", "NetworkManager.service"]) - else: - subprocess.check_call(["/etc/init.d/NetworkManager", "restart"]) -+ stored_servers.commit() ++ @property ++ def global_forwarders(self): ++ connections = None ++ if config.use_vpn_global_forwarders: ++ connections = list(ConnectionList(self.client, only_vpn=True)) ++ if not connections: ++ connections = list(ConnectionList(self.client, only_default=True)) ++ ++ return sum((connection.servers for connection in connections), []) def run_update(self): + """Update unbound and dnssec-trigger configuration.""" @@ -373,18 +448,17 @@ index b572dd1..69f055d 100644 self.run_update_global_forwarders() self.run_update_connection_zones() ++ @staticmethod ++ def dnssec_trigger_control(args): ++ log.debug("dnssec-trigger-control: {}".format(args)) ++ subprocess.check_call(["dnssec-trigger-control"] + args, stdout=DEVNULL, stderr=DEVNULL) ++ def run_update_global_forwarders(self): """Configure global forwarders using dnssec-trigger-control.""" - subprocess.check_call(["dnssec-trigger-control", "status"], stdout=DEVNULL, stderr=DEVNULL) + with Lock(): -+ subprocess.check_call(["dnssec-trigger-control", "status"], stdout=DEVNULL, stderr=DEVNULL) -+ -+ connections = None -+ if self.config.use_vpn_global_forwarders: -+ connections = list(ConnectionList(self.client, only_vpn=True)) -+ if not connections: -+ connections = list(ConnectionList(self.client, only_default=True)) ++ self.dnssec_trigger_control(["status"]) - default_connections = ConnectionList(only_default=True) - servers = Store('servers') @@ -395,11 +469,13 @@ index b572dd1..69f055d 100644 - subprocess.check_call(["dnssec-trigger-control", "submit"] + list(servers)) - servers.commit() - log.info("Global forwarders: {}".format(' '.join(servers))) -+ if servers.update(sum((connection.servers for connection in connections), [])): -+ subprocess.check_call(["unbound-control", "flush_zone", "."]) -+ subprocess.check_call(["dnssec-trigger-control", "submit"] + list(servers)) ++ if servers.update(self.global_forwarders): ++ UnboundZoneConfig._control([config.flush_command, "."]) ++ self.dnssec_trigger_control(["submit"] + list(servers)) + servers.commit() -+ log.info("Global forwarders: {}".format(' '.join(servers))) ++ log.info("Global forwarders: {}".format(' '.join(servers))) ++ else: ++ log.info("Global forwarders: {} (unchanged)".format(' '.join(servers))) def run_update_connection_zones(self): """Configures forward zones in the unbound using unbound-control.""" @@ -435,35 +511,46 @@ index b572dd1..69f055d 100644 - - stored_zones.commit() + with Lock(): -+ connections = ConnectionList(self.client, skip_wifi=not self.config.add_wifi_provided_zones).get_zone_connection_mapping() ++ connections = ConnectionList(self.client, skip_wifi=not config.add_wifi_provided_zones).get_zone_connection_mapping() + unbound_zones = UnboundZoneConfig() + stored_zones = Store('zones') + -+ # The purpose of the zone store is to keep the list of Unbound zones -+ # that are managed by dnssec-trigger-script. We don't want to track -+ # zones accoss Unbound restarts. We want to clear any Unbound zones -+ # that are no longer active in NetworkManager. -+ log.debug("removing stored zones not present in both unbound and an active connection") ++ # Remove any zones managed by dnssec-trigger that are no longer ++ # valid. ++ log.debug("removing zones that are no longer valid") + for zone in stored_zones: -+ if zone not in unbound_zones: -+ stored_zones.remove(zone) -+ elif zone not in connections: -+ unbound_zones.remove(zone) ++ # Remove all zones that are not in connections except those for ++ # reverse name resolution of private addresses. ++ if zone not in connections and zone not in self.rfc1918_reverse_zones: ++ if zone in unbound_zones: ++ unbound_zones.remove(zone) + stored_zones.remove(zone) + -+ # We need to install zones that are not yet in Unbound. We also need to -+ # reinstall zones that are already managed by dnssec-trigger in case their -+ # list of nameservers was changed. -+ # -+ # TODO: In some cases, we don't seem to flush Unbound cache properly, -+ # even when Unbound is restarted (and dnssec-trigger as well, because -+ # of dependency). ++ # Install all zones coming from connections except those installed ++ # by other means than dnssec-trigger-script. + log.debug("installing connection provided zones") + for zone in connections: ++ # Reinstall a known zone or install a new zone. + if zone in stored_zones or zone not in unbound_zones: -+ unbound_zones.add(zone, connections[zone].servers, secure=self.config.validate_connection_provided_zones) ++ unbound_zones.add(zone, connections[zone].servers, secure=config.validate_connection_provided_zones) + stored_zones.add(zone) + ++ # Install zones for reverse name resolution of private addresses ++ # except those already provided by connections and those installed ++ # by other means than dnssec-trigger-script. ++ if self.rfc1918_reverse_zones: ++ log.debug("adding RFC 1918 private zones not present in unbound or connections") ++ global_forwarders = self.global_forwarders ++ for zone in self.rfc1918_reverse_zones: ++ # Ignore a connection provided zone as it's been already ++ # processed. ++ if zone in connections: ++ continue ++ # Reinstall a known zone or install a new zone. ++ if zone in stored_zones or zone not in unbound_zones: ++ unbound_zones.add(zone, global_forwarders, secure=False) ++ stored_zones.add(zone) ++ + stored_zones.commit() if __name__ == "__main__": diff --git a/dnssec-trigger.spec b/dnssec-trigger.spec index 617d157..8800034 100644 --- a/dnssec-trigger.spec +++ b/dnssec-trigger.spec @@ -3,7 +3,7 @@ Summary: NetworkManager plugin to update/reconfigure DNSSEC resolving Name: dnssec-trigger Version: 0.12 -Release: 17%{?dist} +Release: 18%{?dist} License: BSD Url: http://www.nlnetlabs.nl/downloads/dnssec-trigger/ Source0: http://www.nlnetlabs.nl/downloads/dnssec-trigger/%{name}-%{version}.tar.gz @@ -27,6 +27,11 @@ Source2: dnssec-trigger.tmpfiles.d # https://bugzilla.redhat.com/show_bug.cgi?id=1165126 # https://bugzilla.redhat.com/show_bug.cgi?id=1125267 # https://bugzilla.redhat.com/show_bug.cgi?id=1089766 +# https://bugzilla.redhat.com/show_bug.cgi?id=1183975 +# https://bugzilla.redhat.com/show_bug.cgi?id=1185796 +# https://bugzilla.redhat.com/show_bug.cgi?id=1130502 +# https://bugzilla.redhat.com/show_bug.cgi?id=1105685 +# https://bugzilla.redhat.com/show_bug.cgi?id=1128310 Patch2: dnssec-trigger-0.12-nm-script.patch # https://bugzilla.redhat.com/show_bug.cgi?id=1112248 Patch3: dnssec-trigger-0.12-service.patch @@ -35,6 +40,9 @@ Patch3: dnssec-trigger-0.12-service.patch Patch4: dnssec-trigger-0.12-reshook.patch # https://bugzilla.redhat.com/show_bug.cgi?id=824219 Patch5: dnssec-trigger-0.12-probe.patch +# https://bugzilla.redhat.com/show_bug.cgi?id=1130502 +# https://bugzilla.redhat.com/show_bug.cgi?id=1128310 +Patch6: dnssec-trigger-0.12-dnssec-conf.patch Requires(postun): initscripts Requires: ldns >= 1.6.10, NetworkManager-glib, unbound, xdg-utils @@ -74,6 +82,9 @@ sed -i "s/-panel//" panel/dnssec-trigger-panel.desktop.in %patch2 -p1 %patch3 -p1 +%patch4 -p1 +%patch5 -p1 +%patch6 -p1 # change default RSA key between deamon/control from 1536 to 3072 sed -i "s/BITS=1536/BITS=3072/" dnssec-trigger-control-setup.sh.in @@ -152,6 +163,9 @@ fi %systemd_postun_with_restart %{name}d.service %changelog +* Mon Jan 26 2015 Pavel Šimerda - 0.12-18 +- Resolves: #1185796, #1130502, #1105685, #1128310 – update + * Tue Jan 20 2015 Pavel Šimerda - 0.12-17 - Resolves: #1183975 - systemd cgroup check fails