Example #1
0
 def test_config_file_is_world_readable(self):
     patch_dns_config_path(self)
     dns_zone_config = DNSForwardZoneConfig(factory.make_string(),
                                            serial=random.randint(1, 100))
     dns_zone_config.write_config()
     filepath = FilePath(dns_zone_config.zone_info[0].target_path)
     self.assertTrue(filepath.getPermissions().other.read)
Example #2
0
 def test_ignores_generate_directives_for_v6_dynamic_ranges(self):
     patch_dns_config_path(self)
     domain = factory.make_string()
     network = factory.make_ipv4_network()
     ipv4_hostname = factory.make_name("host")
     ipv4_ip = factory.pick_ip_in_network(network)
     ipv6_hostname = factory.make_name("host")
     ipv6_ip = factory.make_ipv6_address()
     ipv6_network = factory.make_ipv6_network()
     dynamic_range = IPRange(ipv6_network.first, ipv6_network.last)
     ttl = random.randint(10, 300)
     mapping = {
         ipv4_hostname: HostnameIPMapping(None, ttl, {ipv4_ip}),
         ipv6_hostname: HostnameIPMapping(None, ttl, {ipv6_ip}),
     }
     dns_zone_config = DNSForwardZoneConfig(
         domain,
         serial=random.randint(1, 100),
         mapping=mapping,
         default_ttl=ttl,
         dynamic_ranges=[dynamic_range],
     )
     get_generate_directives = self.patch(dns_zone_config,
                                          "get_GENERATE_directives")
     dns_zone_config.write_config()
     self.assertThat(get_generate_directives, MockNotCalled())
Example #3
0
 def test_writes_dns_zone_config_with_NS_record(self):
     target_dir = patch_dns_config_path(self)
     addr_ttl = random.randint(10, 100)
     ns_host_name = factory.make_name("ns")
     dns_zone_config = DNSForwardZoneConfig(factory.make_string(),
                                            serial=random.randint(1, 100),
                                            ns_host_name=ns_host_name,
                                            ipv4_ttl=addr_ttl,
                                            ipv6_ttl=addr_ttl)
     dns_zone_config.write_config()
     self.assertThat(
         os.path.join(target_dir, 'zone.%s' % dns_zone_config.domain),
         FileContains(matcher=ContainsAll(['30 IN NS %s.' % ns_host_name])))
Example #4
0
 def test_handles_slash_32_dynamic_range(self):
     target_dir = patch_dns_config_path(self)
     domain = factory.make_string()
     network = factory.make_ipv4_network()
     ipv4_hostname = factory.make_name("host")
     ipv4_ip = factory.pick_ip_in_network(network)
     range_ip = factory.pick_ip_in_network(network, but_not={ipv4_ip})
     ipv6_hostname = factory.make_name("host")
     ipv6_ip = factory.make_ipv6_address()
     ttl = random.randint(10, 300)
     mapping = {
         ipv4_hostname: HostnameIPMapping(None, ttl, {ipv4_ip}),
         ipv6_hostname: HostnameIPMapping(None, ttl, {ipv6_ip}),
     }
     dynamic_range = IPRange(IPAddress(range_ip), IPAddress(range_ip))
     expected_generate_directives = (
         DNSForwardZoneConfig.get_GENERATE_directives(dynamic_range)
     )
     other_mapping = {
         ipv4_hostname: HostnameRRsetMapping(None, {(ttl, "MX", "10 bar")})
     }
     dns_zone_config = DNSForwardZoneConfig(
         domain,
         serial=random.randint(1, 100),
         other_mapping=other_mapping,
         default_ttl=ttl,
         mapping=mapping,
         dynamic_ranges=[dynamic_range],
     )
     dns_zone_config.write_config()
     self.assertThat(
         os.path.join(target_dir, "zone.%s" % domain),
         FileContains(
             matcher=ContainsAll(
                 [
                     "$TTL %d" % ttl,
                     "%s %d IN A %s" % (ipv4_hostname, ttl, ipv4_ip),
                     "%s %d IN AAAA %s" % (ipv6_hostname, ttl, ipv6_ip),
                     "%s %d IN MX 10 bar" % (ipv4_hostname, ttl),
                 ]
                 + [
                     "$GENERATE %s %s IN A %s"
                     % (iterator_values, reverse_dns, hostname)
                     for iterator_values, reverse_dns, hostname in expected_generate_directives
                 ]
             )
         ),
     )
Example #5
0
 def test_get_a_mapping_returns_ipv4_mapping(self):
     ttl = random.randint(10, 300)
     ns_ttl = random.randint(10, 300)
     ipv4_mapping = {
         factory.make_name('host'):
         HostnameIPMapping(None, ttl, [factory.make_ipv4_address()]),
         factory.make_name('host'):
         HostnameIPMapping(None, ttl, [factory.make_ipv4_address()]),
     }
     ipv6_mapping = {
         factory.make_name('host'):
         HostnameIPMapping(None, ttl, [factory.make_ipv6_address()]),
         factory.make_name('host'):
         HostnameIPMapping(None, ttl, [factory.make_ipv6_address()]),
     }
     combined_mapping = {
         hostname: value
         for hostname, value in chain(ipv4_mapping.items(),
                                      ipv6_mapping.items())
     }
     expected = [(n, info.ttl, ip) for n, info in ipv4_mapping.items()
                 for ip in info.ips]
     expect = [(n, t, ip) for n, t, ip in expected]
     actual = DNSForwardZoneConfig.get_A_mapping(combined_mapping, ns_ttl)
     self.assertItemsEqual(expect, actual)
Example #6
0
    def test_bind_write_zones_writes_file(self):
        domain = factory.make_string()
        network = IPNetwork("192.168.0.3/24")
        dns_ip_list = [factory.pick_ip_in_network(network)]
        ip = factory.pick_ip_in_network(network)
        ttl = random.randint(10, 1000)
        forward_zone = DNSForwardZoneConfig(
            domain,
            serial=random.randint(1, 100),
            mapping={
                factory.make_string(): HostnameIPMapping(None, ttl, {ip})
            },
            dns_ip_list=dns_ip_list,
        )
        reverse_zone = DNSReverseZoneConfig(
            domain, serial=random.randint(1, 100), network=network
        )
        actions.bind_write_zones(zones=[forward_zone, reverse_zone])

        forward_file_name = "zone.%s" % domain
        reverse_file_name = "zone.0.168.192.in-addr.arpa"
        expected_files = [
            join(self.dns_conf_dir, forward_file_name),
            join(self.dns_conf_dir, reverse_file_name),
        ]
        self.assertThat(expected_files, AllMatch(FileExists()))
Example #7
0
 def test_ignores_networks_that_span_slash_16s(self):
     # If the upper and lower bounds of a range span two /16 networks
     # (but contain between them no more than 65536 addresses),
     # get_GENERATE_directives() will return early
     ip_range = IPRange("10.0.0.55", "10.1.0.54")
     directives = DNSForwardZoneConfig.get_GENERATE_directives(ip_range)
     self.assertEqual([], directives)
Example #8
0
 def test_computes_dns_config_file_paths(self):
     domain = factory.make_name("zone")
     dns_zone_config = DNSForwardZoneConfig(domain)
     self.assertEqual(
         os.path.join(get_dns_config_dir(), "zone.%s" % domain),
         dns_zone_config.zone_info[0].target_path,
     )
Example #9
0
    def test_returns_single_entry_for_tiny_network(self):
        network = IPNetwork("%s/31" % factory.make_ipv4_address())

        expected_directives = self.get_expected_generate_directives(network)
        directives = DNSForwardZoneConfig.get_GENERATE_directives(network)
        self.assertEqual(1, len(expected_directives))
        self.assertItemsEqual(expected_directives, directives)
Example #10
0
    def test_returns_two_entries_for_slash_23_network(self):
        network = IPNetwork("%s/23" % factory.make_ipv4_address())

        expected_directives = self.get_expected_generate_directives(network)
        directives = DNSForwardZoneConfig.get_GENERATE_directives(network)
        self.assertEqual(2, len(expected_directives))
        self.assertItemsEqual(expected_directives, directives)
Example #11
0
 def test_get_aaaa_mapping_returns_ipv6_mapping(self):
     ttl = random.randint(10, 300)
     ns_ttl = random.randint(10, 300)
     ipv4_mapping = {
         factory.make_name("host"):
         HostnameIPMapping(None, ttl, {factory.make_ipv4_address()}),
         factory.make_name("host"):
         HostnameIPMapping(None, ttl, {factory.make_ipv4_address()}),
     }
     ipv6_mapping = {
         factory.make_name("host"):
         HostnameIPMapping(None, ttl, {factory.make_ipv6_address()}),
         factory.make_name("host"):
         HostnameIPMapping(None, ttl, {factory.make_ipv6_address()}),
     }
     combined_mapping = {
         hostname: value
         for hostname, value in chain(ipv4_mapping.items(),
                                      ipv6_mapping.items())
     }
     self.assertItemsEqual(
         [(n, info.ttl, ip) for n, info in ipv6_mapping.items()
          for ip in info.ips],
         DNSForwardZoneConfig.get_AAAA_mapping(combined_mapping, ns_ttl),
     )
Example #12
0
 def test_dtrt_for_larger_networks(self):
     # For every other network size that we're not explicitly
     # testing here,
     # DNSForwardZoneConfig.get_GENERATE_directives() will return
     # one GENERATE directive for every 255 addresses in the network.
     for prefixlen in range(23, 16):
         network = IPNetwork("%s/%s" %
                             (factory.make_ipv4_address(), prefixlen))
         directives = DNSForwardZoneConfig.get_GENERATE_directives(network)
         self.assertIsEqual(network.size / 256, len(directives))
Example #13
0
 def test_excplicitly(self):
     # The other tests in this TestCase rely on
     # get_expected_generate_directives(), which is quite dense. Here
     # we test get_GENERATE_directives() explicitly.
     ip_range = IPRange('192.168.0.55', '192.168.2.128')
     expected_directives = [
         ("55-255", "192-168-0-$", "192.168.0.$"),
         ("0-255", "192-168-1-$", "192.168.1.$"),
         ("0-128", "192-168-2-$", "192.168.2.$"),
     ]
     self.assertItemsEqual(
         expected_directives,
         DNSForwardZoneConfig.get_GENERATE_directives(ip_range))
Example #14
0
 def test_writes_dns_zone_config(self):
     target_dir = patch_dns_config_path(self)
     domain = factory.make_string()
     network = factory.make_ipv4_network()
     ipv4_hostname = factory.make_name('host')
     ipv4_ip = factory.pick_ip_in_network(network)
     ipv6_hostname = factory.make_name('host')
     ipv6_ip = factory.make_ipv6_address()
     ttl = random.randint(10, 300)
     mapping = {
         ipv4_hostname: HostnameIPMapping(None, ttl, {ipv4_ip}),
         ipv6_hostname: HostnameIPMapping(None, ttl, {ipv6_ip}),
     }
     expected_generate_directives = (
         DNSForwardZoneConfig.get_GENERATE_directives(network))
     other_mapping = {
         ipv4_hostname: HostnameRRsetMapping(None, {(ttl, 'MX', '10 bar')})
     }
     dns_zone_config = DNSForwardZoneConfig(
         domain,
         serial=random.randint(1, 100),
         other_mapping=other_mapping,
         default_ttl=ttl,
         mapping=mapping,
         dynamic_ranges=[IPRange(network.first, network.last)])
     dns_zone_config.write_config()
     self.assertThat(
         os.path.join(target_dir, 'zone.%s' % domain),
         FileContains(matcher=ContainsAll([
             '$TTL %d' % ttl,
             '%s %d IN A %s' % (ipv4_hostname, ttl, ipv4_ip),
             '%s %d IN AAAA %s' % (ipv6_hostname, ttl, ipv6_ip),
             '%s %d IN MX 10 bar' % (ipv4_hostname, ttl),
         ] + [
             '$GENERATE %s %s IN A %s' %
             (iterator_values, reverse_dns, hostname) for iterator_values,
             reverse_dns, hostname in expected_generate_directives
         ])))
Example #15
0
    def test_sorts_output(self):
        network = IPNetwork("10.0.0.0/23")

        expected_hostname = "10-0-%s-$"
        expected_address = "10.0.%s.$"

        directives = list(
            DNSForwardZoneConfig.get_GENERATE_directives(network))
        self.expectThat(len(directives), Equals(2))
        self.expectThat(
            directives[0],
            Equals(("0-255", expected_hostname % "0", expected_address % "0")))
        self.expectThat(
            directives[1],
            Equals(("0-255", expected_hostname % "1", expected_address % "1")))
Example #16
0
 def test_write_config_writes_config(self):
     target_dir = patch_dns_config_path(self)
     domain = factory.make_string()
     network = IPNetwork('192.168.0.3/24')
     ip = factory.pick_ip_in_network(network)
     forward_zone = DNSForwardZoneConfig(
         domain, mapping={factory.make_string(): ip})
     reverse_zone = DNSReverseZoneConfig(domain, network=network)
     dnsconfig = DNSConfig((forward_zone, reverse_zone))
     dnsconfig.write_config()
     self.assertThat(
         os.path.join(target_dir, MAAS_NAMED_CONF_NAME),
         FileContains(matcher=ContainsAll([
             'zone.%s' % domain,
             'zone.0.168.192.in-addr.arpa',
             MAAS_NAMED_RNDC_CONF_NAME,
         ])))
Example #17
0
 def test_fields(self):
     domain = factory.make_string()
     serial = random.randint(1, 200)
     hostname = factory.make_string()
     network = factory.make_ipv4_network()
     ip = factory.pick_ip_in_network(network)
     default_ttl = random.randint(10, 300)
     mapping = {hostname: [ip]}
     dns_zone_config = DNSForwardZoneConfig(
         domain, serial=serial, default_ttl=default_ttl, mapping=mapping
     )
     self.assertThat(
         dns_zone_config,
         MatchesStructure.byEquality(
             domain=domain,
             serial=serial,
             _mapping=mapping,
             default_ttl=default_ttl,
         ),
     )
Example #18
0
 def test_ignores_network_larger_than_slash_16(self):
     network = IPNetwork("%s/15" % factory.make_ipv4_address())
     self.assertEqual([],
                      DNSForwardZoneConfig.get_GENERATE_directives(network))
Example #19
0
 def test_returns_single_entry_for_slash_24_network(self):
     network = IPNetwork("%s/24" % factory.make_ipv4_address())
     expected_directives = self.get_expected_generate_directives(network)
     directives = DNSForwardZoneConfig.get_GENERATE_directives(network)
     self.expectThat(directives, HasLength(1))
     self.assertItemsEqual(expected_directives, directives)
Example #20
0
    def _gen_forward_zones(domains, serial, ns_host_name, mappings,
                           rrset_mappings, default_ttl):
        """Generator of forward zones, collated by domain name."""
        dns_ip_list = get_dns_server_addresses()
        domains = set(domains)

        # For each of the domains that we are generating, create the zone from:
        # 1. Node: ip mapping(domain) (which includes dnsresource addresses).
        # 2. Dnsresource non-address records in this domain.
        # 3. For the default domain all forward look ups for the managed and
        #    unmanaged dynamic ranges.
        for domain in domains:
            zone_ttl = default_ttl if domain.ttl is None else domain.ttl
            # 1. node: ip mapping(domain)
            # Map all of the nodes in this domain, including the user-reserved
            # ip addresses.  Separate_fqdn handles top-of-domain names needing
            # to have the name '@', and we already know the domain name, so we
            # discard that part of the return.
            mapping = {
                separate_fqdn(hostname, domainname=domain.name)[0]: info
                for hostname, info in mappings[domain].items()
            }
            # 2a. Create non-address records.  Specifically ignore any CNAME
            # records that collide with addresses in mapping.
            other_mapping = rrset_mappings[domain]

            # 2b. Capture NS RRsets for anything that is a child of this domain
            domain.add_delegations(other_mapping, ns_host_name, dns_ip_list,
                                   default_ttl)

            # 3. All of the special handling for the default domain.
            dynamic_ranges = []
            if domain.is_default():
                # 3a. All forward entries for the managed and unmanaged dynamic
                # ranges go into the default domain.
                subnets = Subnet.objects.all().prefetch_related("iprange_set")
                for subnet in subnets:
                    # We loop through the whole set so the prefetch above works
                    # in one query.
                    for ip_range in subnet.iprange_set.all():
                        if ip_range.type == IPRANGE_TYPE.DYNAMIC:
                            dynamic_ranges.append(ip_range.get_MAASIPRange())
                # 3b. Add A/AAAA RRset for @.  If glue is needed for any other
                # domain, adding the glue is the responsibility of the admin.
                ttl = domain.get_base_ttl('A', default_ttl)
                for dns_ip in dns_ip_list:
                    if dns_ip.version == 4:
                        other_mapping['@'].rrset.add(
                            (ttl, 'A', dns_ip.format()))
                    else:
                        other_mapping['@'].rrset.add(
                            (ttl, 'AAAA', dns_ip.format()))

            yield DNSForwardZoneConfig(
                domain.name,
                serial=serial,
                default_ttl=zone_ttl,
                ns_ttl=domain.get_base_ttl('NS', default_ttl),
                ipv4_ttl=domain.get_base_ttl('A', default_ttl),
                ipv6_ttl=domain.get_base_ttl('AAAA', default_ttl),
                mapping=mapping,
                ns_host_name=ns_host_name,
                other_mapping=other_mapping,
                dynamic_ranges=dynamic_ranges,
            )