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)
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())
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])))
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 ] ) ), )
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)
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()))
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)
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, )
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)
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)
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), )
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))
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))
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 ])))
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")))
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, ])))
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, ), )
def test_ignores_network_larger_than_slash_16(self): network = IPNetwork("%s/15" % factory.make_ipv4_address()) self.assertEqual([], DNSForwardZoneConfig.get_GENERATE_directives(network))
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)
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, )