258 lines
		
	
	
	
		
			7.1 KiB
		
	
	
	
		
			Python
		
	
	
	
	
	
			
		
		
	
	
			258 lines
		
	
	
	
		
			7.1 KiB
		
	
	
	
		
			Python
		
	
	
	
	
	
| from ipaddress import ip_interface
 | |
| from json import dumps
 | |
| h = repo.libs.hashable.hashable
 | |
| repo.libs.bind.repo = repo
 | |
| 
 | |
| 
 | |
| defaults = {
 | |
|     'apt': {
 | |
|         'packages': {
 | |
|             'bind9': {},
 | |
|         },
 | |
|     },
 | |
|     'bind': {
 | |
|         'slaves': {},
 | |
|         'acls': {
 | |
|             'our-nets': {
 | |
|                 '127.0.0.1',
 | |
|                 '10.0.0.0/8',
 | |
|                 '169.254.0.0/16',
 | |
|                 '172.16.0.0/12',
 | |
|                 '192.168.0.0/16',
 | |
|             }
 | |
|         },
 | |
|         'views': {
 | |
|             'internal': {
 | |
|                 'is_internal': True,
 | |
|                 'keys': {},
 | |
|                 'match_clients': {
 | |
|                     'our-nets',
 | |
|                 },
 | |
|                 'zones': {},
 | |
|             },
 | |
|             'external': {
 | |
|                 'default': True,
 | |
|                 'is_internal': False,
 | |
|                 'keys': {},
 | |
|                 'match_clients': {
 | |
|                     'any',
 | |
|                 },
 | |
|                 'zones': {},
 | |
|             },
 | |
|         },
 | |
|         'zones': set(),
 | |
|     },
 | |
|     'nftables': {
 | |
|         'input': {
 | |
|             'tcp dport 53 accept',
 | |
|             'udp dport 53 accept',
 | |
|         },
 | |
|     },
 | |
|     'telegraf': {
 | |
|         'config': {
 | |
|             'inputs': {
 | |
|                 'bind': [{
 | |
|                     'urls': ['http://localhost:8053/xml/v3'],
 | |
|                     'gather_memory_contexts': False,
 | |
|                     'gather_views': True,
 | |
|                 }],
 | |
|             },
 | |
|         },
 | |
|     },
 | |
| }
 | |
| 
 | |
| 
 | |
| @metadata_reactor.provides(
 | |
|     'bind/type',
 | |
|     'bind/master_ip',
 | |
|     'bind/slave_ips',
 | |
| )
 | |
| def master_slave(metadata):
 | |
|     if metadata.get('bind/master_node', None):
 | |
|         return {
 | |
|             'bind': {
 | |
|                 'type': 'slave',
 | |
|                 'master_ip': str(ip_interface(repo.get_node(metadata.get('bind/master_node')).metadata.get('network/external/ipv4')).ip),
 | |
|             }
 | |
|         }
 | |
|     else:
 | |
|         return {
 | |
|             'bind': {
 | |
|                 'type': 'master',
 | |
|                 'slave_ips': {
 | |
|                     str(ip_interface(repo.get_node(slave).metadata.get('network/external/ipv4')).ip)
 | |
|                         for slave in metadata.get('bind/slaves')
 | |
|                 }
 | |
|             }
 | |
|         }
 | |
| 
 | |
| 
 | |
| @metadata_reactor.provides(
 | |
|     'dns',
 | |
| )
 | |
| def dns(metadata):
 | |
|     return {
 | |
|         'dns': {
 | |
|             metadata.get('bind/hostname'): repo.libs.ip.get_a_records(metadata),
 | |
|         }
 | |
|     }
 | |
| 
 | |
| 
 | |
| @metadata_reactor.provides(
 | |
|     'bind/views',
 | |
| )
 | |
| def collect_records(metadata):
 | |
|     if metadata.get('bind/type') == 'slave':
 | |
|         return {}
 | |
| 
 | |
|     views = {}
 | |
| 
 | |
|     for view_name, view_conf in metadata.get('bind/views').items():
 | |
|         for other_node in repo.nodes:
 | |
|             for fqdn, records in other_node.metadata.get('dns', {}).items():
 | |
|                 matching_zones = sorted(
 | |
|                     filter(
 | |
|                         lambda potential_zone: fqdn.endswith(potential_zone),
 | |
|                         metadata.get('bind/zones')
 | |
|                     ),
 | |
|                     key=len,
 | |
|                 )
 | |
|                 if matching_zones:
 | |
|                     zone = matching_zones[-1]
 | |
|                 else:
 | |
|                     continue
 | |
| 
 | |
|                 name = fqdn[0:-len(zone) - 1]
 | |
| 
 | |
|                 for type, values in records.items():
 | |
|                     for value in values:
 | |
|                         if repo.libs.bind.record_matches_view(value, type, name, zone, view_name, metadata):
 | |
|                             views\
 | |
|                                 .setdefault(view_name, {})\
 | |
|                                 .setdefault('zones', {})\
 | |
|                                 .setdefault(zone, {})\
 | |
|                                 .setdefault('records', set())\
 | |
|                                 .add(
 | |
|                                     h({'name': name, 'type': type, 'value': value})
 | |
|                                 )
 | |
| 
 | |
|     return {
 | |
|         'bind': {
 | |
|             'views': views,
 | |
|         },
 | |
|     }
 | |
| 
 | |
| 
 | |
| @metadata_reactor.provides(
 | |
|     'bind/views',
 | |
| )
 | |
| def ns_records(metadata):
 | |
|     if metadata.get('bind/type') == 'slave':
 | |
|         return {}
 | |
| 
 | |
|     nameservers = [
 | |
|         node.metadata.get('bind/hostname'),
 | |
|         *[
 | |
|             repo.get_node(slave).metadata.get('bind/hostname')
 | |
|                 for slave in node.metadata.get('bind/slaves')
 | |
|         ]
 | |
|     ]
 | |
|     return {
 | |
|         'bind': {
 | |
|             'views': {
 | |
|                 view_name: {
 | |
|                     'zones': {
 | |
|                         zone_name: {
 | |
|                             'records': {
 | |
|                                 # FIXME: bw currently cant handle lists of dicts :(
 | |
|                                 h({'name': '@', 'type': 'NS', 'value': f"{nameserver}."})
 | |
|                                     for nameserver in nameservers
 | |
|                             }
 | |
|                         }
 | |
|                             for zone_name, zone_conf in view_conf['zones'].items()
 | |
|                     }
 | |
|                 }
 | |
|                     for view_name, view_conf in metadata.get('bind/views').items()
 | |
|             },
 | |
|         },
 | |
|     }
 | |
| 
 | |
| 
 | |
| @metadata_reactor.provides(
 | |
|     'bind/slaves',
 | |
| )
 | |
| def slaves(metadata):
 | |
|     if metadata.get('bind/type') == 'slave':
 | |
|         return {}
 | |
| 
 | |
|     return {
 | |
|         'bind': {
 | |
|             'slaves': [
 | |
|                 other_node.name
 | |
|                     for other_node in repo.nodes
 | |
|                     if other_node.has_bundle('bind') and other_node.metadata.get('bind/master_node', None) == node.name
 | |
|             ],
 | |
|         },
 | |
|     }
 | |
| 
 | |
| 
 | |
| @metadata_reactor.provides(
 | |
|     'bind/views',
 | |
| )
 | |
| def generate_keys(metadata):
 | |
|     if metadata.get('bind/type') == 'slave':
 | |
|         return {}
 | |
| 
 | |
|     return {
 | |
|         'bind': {
 | |
|             'views': {
 | |
|                 view_name: {
 | |
|                     'keys': {
 | |
|                         key: {
 | |
|                             'token':repo.libs.hmac.hmac_sha512(
 | |
|                                 key,
 | |
|                                 str(repo.vault.random_bytes_as_base64_for(
 | |
|                                     f"{metadata.get('id')} bind key {key} 20250713",
 | |
|                                     length=32,
 | |
|                                 )),
 | |
|                             )
 | |
|                         }
 | |
|                             for key in view_conf['keys']
 | |
|                     }
 | |
|                 }
 | |
|                     for view_name, view_conf in metadata.get('bind/views').items()
 | |
|             }
 | |
|         }
 | |
|     }
 | |
| 
 | |
| 
 | |
| @metadata_reactor.provides(
 | |
|     'bind/views',
 | |
| )
 | |
| def generate_acl_entries_for_keys(metadata):
 | |
|     if metadata.get('bind/type') == 'slave':
 | |
|         return {}
 | |
| 
 | |
|     return {
 | |
|         'bind': {
 | |
|             'views': {
 | |
|                 view_name: {
 | |
|                     'match_clients': {
 | |
|                         # allow keys from this view
 | |
|                         *{
 | |
|                             f'key {key}'
 | |
|                                 for key in view_conf['keys']
 | |
|                         },
 | |
|                         # reject keys from other views
 | |
|                         *{
 | |
|                             f'! key {key}'
 | |
|                                 for other_view_name, other_view_conf in metadata.get('bind/views').items()
 | |
|                                 if other_view_name != view_name
 | |
|                                 for key in other_view_conf.get('keys', [])
 | |
|                         }
 | |
|                     }
 | |
|                 }
 | |
|                     for view_name, view_conf in metadata.get('bind/views').items()
 | |
|             },
 | |
|         },
 | |
|     }
 |