def test_parse_then_make_then_parse_generates_identical_config(self): testdata = dedent("""\ acl canonical-int-ns { 91.189.90.151; 91.189.89.192; }; options { directory "/var/cache/bind"; forwarders { 91.189.94.2; 91.189.94.2; }; dnssec-validation auto; auth-nxdomain no; # conform to RFC1035 listen-on-v6 { any; }; allow-query { any; }; allow-transfer { 10.222.64.1; canonical-int-ns; }; notify explicit; also-notify { 91.189.90.151; 91.189.89.192; }; allow-query-cache { 10.222.64.0/18; }; recursion yes; }; zone "." { type master; file "/etc/bind/db.special"; }; """) config = parse_isc_string(testdata) config_string = make_isc_string(config) config = parse_isc_string(config_string) self.assertEqual( OrderedDict([('acl canonical-int-ns', OrderedDict([('91.189.90.151', True), ('91.189.89.192', True)])), ('options', OrderedDict([ ('directory', '"/var/cache/bind"'), ('forwarders', OrderedDict([('91.189.94.2', True)])), ('dnssec-validation', 'auto'), ('auth-nxdomain', 'no'), ('listen-on-v6', OrderedDict([('any', True)])), ('allow-query', OrderedDict([('any', True)])), ('allow-transfer', OrderedDict([('10.222.64.1', True), ('canonical-int-ns', True)])), ('notify', 'explicit'), ('also-notify', OrderedDict([('91.189.90.151', True), ('91.189.89.192', True)])), ('allow-query-cache', OrderedDict([('10.222.64.0/18', True)])), ('recursion', 'yes') ])), ('zone "."', OrderedDict([('type', 'master'), ('file', '"/etc/bind/db.special"')]))]), config)
def edit_options(config_path, stdout=sys.stdout, dry_run=False, force=False, options_handler=None): """ Edit the named.conf.options file so that it includes the named.conf.options.inside.maas file. """ options_file = read_file(config_path) config_dict = parse_file(config_path, options_file) original_config = deepcopy(config_dict) options_block = config_dict['options'] # Modify the configuration (if necessary). set_up_include_statement(options_block, config_path) # Options handler that can modify the options block more. if options_handler is not None: options_handler(options_block) # Re-parse the new configuration, so we can detect any changes. new_content = make_isc_string(config_dict) new_config = parse_isc_string(new_content) if original_config != new_config or force: # The configuration has changed. Back up and write new file. if dry_run: write_new_named_conf_options(stdout, config_path, new_content) else: backup_filename = back_up_existing_file(config_path) with open(config_path, "w", encoding="ascii") as fd: write_new_named_conf_options(fd, backup_filename, new_content)
def test_parser_preserves_order(self): testdata = dedent("""\ forwarders { 9.9.9.9; 8.8.8.8; 7.7.7.7; 6.6.6.6; 5.5.5.5; 4.4.4.4; 3.3.3.3; 2.2.2.2; 1.1.1.1; }; """) forwarders = parse_isc_string(testdata) self.assertEqual( OrderedDict([( "forwarders", OrderedDict([ ("9.9.9.9", True), ("8.8.8.8", True), ("7.7.7.7", True), ("6.6.6.6", True), ("5.5.5.5", True), ("4.4.4.4", True), ("3.3.3.3", True), ("2.2.2.2", True), ("1.1.1.1", True), ]), )]), forwarders, )
def test_dry_run_migrates_nothing_and_prints_config(self): options_file = self.make_file( contents=OPTIONS_FILE_WITH_FORWARDERS_AND_DNSSEC) call_command( "edit_named_options", config_path=options_file, migrate_conflicting_options=True, dry_run=True, stdout=self.stdout) upstream_dns = get_one(Config.objects.filter(name="upstream_dns")) self.assertIsNone(upstream_dns) dnssec_validation = get_one(Config.objects.filter( name="dnssec_validation")) self.assertIsNone(dnssec_validation) # Check that a proper configuration was written to stdout. config = parse_isc_string(self.stdout.getvalue()) self.assertIsNotNone(config)
def test_parses_bind_acl(self): testdata = dedent("""\ acl goodclients { 192.0.2.0/24; localhost; localnets; }; """) acl = parse_isc_string(testdata) self.assertEqual( { 'acl goodclients': { '192.0.2.0/24': True, 'localhost': True, 'localnets': True, } }, acl)
def parse_file(self, config_path, options_file): """Read the named.conf.options file and parse it. Then insert the include statement that we need. """ try: config_dict = parse_isc_string(options_file) except ISCParseException as e: raise CommandError("Failed to parse %s: %s" % (config_path, str(e))) from e options_block = config_dict.get("options", None) if options_block is None: # Something is horribly wrong with the file; bail out rather # than doing anything drastic. raise CommandError( "Can't find options {} block in %s, bailing out without " "doing anything." % config_path) return config_dict
def test_write_config_with_forwarded_zones(self): name = factory.make_name("domain") ip = factory.make_ip_address() forwarded_zones = [(name, [ip])] target_dir = patch_dns_config_path(self) DNSConfig(forwarded_zones=forwarded_zones).write_config() config_path = os.path.join(target_dir, MAAS_NAMED_CONF_NAME) expected_content = dedent(f""" zone "{name}" {{ type forward; forward only; forwarders {{ {ip}; }}; }}; """) config = read_isc_file(config_path) expected = parse_isc_string(expected_content) self.assertEqual(expected[f'zone "{name}"'], config[f'zone "{name}"'])
def test_parses_bind_acl(self): testdata = dedent("""\ acl goodclients { 192.0.2.0/24; localhost; localnets; }; """) acl = parse_isc_string(testdata) self.assertEqual( { "acl goodclients": { "192.0.2.0/24": True, "localhost": True, "localnets": True, } }, acl, )
def test_parses_multiple_forwarders(self): testdata = dedent("""\ forwarders { 91.189.94.2; 91.189.94.3; 91.189.94.4; 91.189.94.5; 91.189.94.6; }; """) forwarders = parse_isc_string(testdata) self.assertEqual( { 'forwarders': { '91.189.94.2': True, '91.189.94.3': True, '91.189.94.4': True, '91.189.94.5': True, '91.189.94.6': True, } }, forwarders)
def handle(self, *args, **options): """Entry point for BaseCommand.""" # Read stuff in, validate. config_path = options.get('config_path') dry_run = options.get('dry_run') force = options.get('force') stdout = options.get('stdout') if stdout is None: stdout = sys.stdout migrate_conflicting_options = options.get( 'migrate_conflicting_options') options_file = self.read_file(config_path) config_dict = self.parse_file(config_path, options_file) original_config = deepcopy(config_dict) options_block = config_dict['options'] # Modify the configuration (if necessary). self.set_up_include_statement(options_block, config_path) if migrate_conflicting_options: self.migrate_forwarders(options_block, dry_run, stdout) self.migrate_dnssec_validation(options_block, dry_run, stdout) # Re-parse the new configuration, so we can detect any changes. new_content = make_isc_string(config_dict) new_config = parse_isc_string(new_content) if original_config != new_config or force: # The configuration has changed. Back up and write new file. if dry_run: self.write_new_named_conf_options(stdout, config_path, new_content) else: backup_filename = self.back_up_existing_file(config_path) with open(config_path, "w", encoding="ascii") as fd: self.write_new_named_conf_options(fd, backup_filename, new_content)
def test_parses_simple_bind_options(self): testdata = dedent("""\ options { directory "/var/cache/bind"; dnssec-validation auto; auth-nxdomain no; # conform to RFC1035 listen-on-v6 { any; }; }; """) options = parse_isc_string(testdata) self.assertEqual( OrderedDict([ ('options', OrderedDict([ ('directory', '"/var/cache/bind"'), ('dnssec-validation', 'auto'), ('auth-nxdomain', 'no'), ('listen-on-v6', OrderedDict([('any', True)])), ])), ]), options)
def test_parser_preserves_order(self): testdata = dedent("""\ forwarders { 9.9.9.9; 8.8.8.8; 7.7.7.7; 6.6.6.6; 5.5.5.5; 4.4.4.4; 3.3.3.3; 2.2.2.2; 1.1.1.1; }; """) forwarders = parse_isc_string(testdata) self.assertEqual( OrderedDict([('forwarders', OrderedDict([('9.9.9.9', True), ('8.8.8.8', True), ('7.7.7.7', True), ('6.6.6.6', True), ('5.5.5.5', True), ('4.4.4.4', True), ('3.3.3.3', True), ('2.2.2.2', True), ('1.1.1.1', True)]))]), forwarders)
def test_parses_multiple_forwarders(self): testdata = dedent("""\ forwarders { 91.189.94.2; 91.189.94.3; 91.189.94.4; 91.189.94.5; 91.189.94.6; }; """) forwarders = parse_isc_string(testdata) self.assertEqual( { "forwarders": { "91.189.94.2": True, "91.189.94.3": True, "91.189.94.4": True, "91.189.94.5": True, "91.189.94.6": True, } }, forwarders, )
def test_parses_simple_bind_options(self): testdata = dedent("""\ options { directory "/var/cache/bind"; dnssec-validation auto; auth-nxdomain no; # conform to RFC1035 listen-on-v6 { any; }; }; """) options = parse_isc_string(testdata) self.assertEqual( OrderedDict([( "options", OrderedDict([ ("directory", '"/var/cache/bind"'), ("dnssec-validation", "auto"), ("auth-nxdomain", "no"), ("listen-on-v6", OrderedDict([("any", True)])), ]), )]), options, )
def test_parses_bug_1413388_config(self): testdata = dedent("""\ acl canonical-int-ns { 91.189.90.151; 91.189.89.192; }; options { directory "/var/cache/bind"; forwarders { 91.189.94.2; 91.189.94.2; }; dnssec-validation auto; auth-nxdomain no; # conform to RFC1035 listen-on-v6 { any; }; allow-query { any; }; allow-transfer { 10.222.64.1; canonical-int-ns; }; notify explicit; also-notify { 91.189.90.151; 91.189.89.192; }; allow-query-cache { 10.222.64.0/18; }; recursion yes; }; zone "." { type master; file "/etc/bind/db.special"; }; """) config = parse_isc_string(testdata) self.assertEqual( { "acl canonical-int-ns": { "91.189.89.192": True, "91.189.90.151": True, }, "options": { "allow-query": { "any": True }, "allow-query-cache": { "10.222.64.0/18": True }, "allow-transfer": { "10.222.64.1": True, "canonical-int-ns": True, }, "also-notify": { "91.189.89.192": True, "91.189.90.151": True, }, "auth-nxdomain": "no", "directory": '"/var/cache/bind"', "dnssec-validation": "auto", "forwarders": { "91.189.94.2": True }, "listen-on-v6": { "any": True }, "notify": "explicit", "recursion": "yes", }, 'zone "."': { "file": '"/etc/bind/db.special"', "type": "master", }, }, config, )
def test_parse_forgotten_semicolons_throw_iscparseexception(self): with ExpectedException(ISCParseException): parse_isc_string("a { b; } { c; } d e;")
def test_parse_malformed_list_throws_iscparseexception(self): with ExpectedException(ISCParseException): parse_isc_string("forwarders {{}a;;b}")
def test_parse_unmatched_brackets_throws_iscparseexception(self): with ExpectedException(ISCParseException): parse_isc_string("forwarders {")
def test_parse_then_make_then_parse_generates_identical_config(self): testdata = dedent("""\ acl canonical-int-ns { 91.189.90.151; 91.189.89.192; }; options { directory "/var/cache/bind"; forwarders { 91.189.94.2; 91.189.94.2; }; dnssec-validation auto; auth-nxdomain no; # conform to RFC1035 listen-on-v6 { any; }; allow-query { any; }; allow-transfer { 10.222.64.1; canonical-int-ns; }; notify explicit; also-notify { 91.189.90.151; 91.189.89.192; }; allow-query-cache { 10.222.64.0/18; }; recursion yes; }; zone "." { type master; file "/etc/bind/db.special"; }; """) config = parse_isc_string(testdata) config_string = make_isc_string(config) config = parse_isc_string(config_string) self.assertEqual( OrderedDict([ ( "acl canonical-int-ns", OrderedDict([("91.189.90.151", True), ("91.189.89.192", True)]), ), ( "options", OrderedDict([ ("directory", '"/var/cache/bind"'), ( "forwarders", OrderedDict([("91.189.94.2", True)]), ), ("dnssec-validation", "auto"), ("auth-nxdomain", "no"), ("listen-on-v6", OrderedDict([("any", True)])), ("allow-query", OrderedDict([("any", True)])), ( "allow-transfer", OrderedDict([ ("10.222.64.1", True), ("canonical-int-ns", True), ]), ), ("notify", "explicit"), ( "also-notify", OrderedDict([ ("91.189.90.151", True), ("91.189.89.192", True), ]), ), ( "allow-query-cache", OrderedDict([("10.222.64.0/18", True)]), ), ("recursion", "yes"), ]), ), ( 'zone "."', OrderedDict([ ("type", "master"), ("file", '"/etc/bind/db.special"'), ]), ), ]), config, )
def test_parses_bug_1413388_config(self): testdata = dedent("""\ acl canonical-int-ns { 91.189.90.151; 91.189.89.192; }; options { directory "/var/cache/bind"; forwarders { 91.189.94.2; 91.189.94.2; }; dnssec-validation auto; auth-nxdomain no; # conform to RFC1035 listen-on-v6 { any; }; allow-query { any; }; allow-transfer { 10.222.64.1; canonical-int-ns; }; notify explicit; also-notify { 91.189.90.151; 91.189.89.192; }; allow-query-cache { 10.222.64.0/18; }; recursion yes; }; zone "." { type master; file "/etc/bind/db.special"; }; """) config = parse_isc_string(testdata) self.assertEqual( { 'acl canonical-int-ns': { '91.189.89.192': True, '91.189.90.151': True, }, 'options': { 'allow-query': { 'any': True }, 'allow-query-cache': { '10.222.64.0/18': True }, 'allow-transfer': { '10.222.64.1': True, 'canonical-int-ns': True, }, 'also-notify': { '91.189.89.192': True, '91.189.90.151': True, }, 'auth-nxdomain': 'no', 'directory': '"/var/cache/bind"', 'dnssec-validation': 'auto', 'forwarders': { '91.189.94.2': True }, 'listen-on-v6': { 'any': True }, 'notify': 'explicit', 'recursion': 'yes' }, 'zone "."': { 'file': '"/etc/bind/db.special"', 'type': 'master', } }, config)