From e23159596e1851f156461d00b9f9f99dc698e12b Mon Sep 17 00:00:00 2001 From: Martin Basti Date: Jun 17 2016 13:22:24 +0000 Subject: DNS Locations: command dns-update-system-records command dns-update-system-records updates/fixes DNS records for IPA services: * updating A, AAAA records for CA * updating SRV records for LDAP, kerberos and AD trust * updating TXT record in _kerberos with proper realm * updating dns locations if used https://fedorahosted.org/freeipa/ticket/2008 Reviewed-By: Petr Spacek Reviewed-By: Jan Cholasta --- diff --git a/API.txt b/API.txt index 93b009b..5d63ba8 100644 --- a/API.txt +++ b/API.txt @@ -1054,6 +1054,12 @@ option: Str('version?') output: Output('result', type=[]) output: Output('summary', type=[, ]) output: Output('value', type=[]) +command: dns_update_system_records +args: 0,2,2 +option: Flag('dry_run', autofill=True, default=False) +option: Str('version?') +output: Output('result', type=[]) +output: Output('value', type=[]) command: dnsconfig_mod args: 0,11,3 option: Str('addattr*', cli_name='addattr') diff --git a/VERSION b/VERSION index 1f8e8ed..3341cf5 100644 --- a/VERSION +++ b/VERSION @@ -90,5 +90,5 @@ IPA_DATA_VERSION=20100614120000 # # ######################################################## IPA_API_VERSION_MAJOR=2 -IPA_API_VERSION_MINOR=184 -# Last change: ftweedal - add issuer options to cert-show and cert-find +IPA_API_VERSION_MINOR=185 +# Last change: mbasti - added command dns-update-system-records diff --git a/ipaclient/plugins/dns.py b/ipaclient/plugins/dns.py index 4defb7c..06cd2c2 100644 --- a/ipaclient/plugins/dns.py +++ b/ipaclient/plugins/dns.py @@ -21,8 +21,9 @@ from __future__ import print_function import six +import copy -from ipaclient.frontend import MethodOverride +from ipaclient.frontend import MethodOverride, CommandOverride from ipalib import errors from ipalib.dns import (get_record_rrtype, has_cli_options, @@ -342,3 +343,29 @@ class dnsforwardzone_mod(MethodOverride): _("Server will check DNS forwarder(s).")) self.Backend.textui.print_plain( _("This may take some time, please wait ...")) + + +@register(override=True) +class dns_update_system_records(CommandOverride): + def output_for_cli(self, textui, output, *args, **options): + output_super = copy.deepcopy(output) + super_res = output_super.get('result', {}) + super_res.pop('ipa_records', None) + super_res.pop('location_records', None) + + super(dns_update_system_records, self).output_for_cli( + textui, output_super, *args, **options) + + labels = { + p.name: unicode(p.label) for p in self.output_params() + } + + result = output.get('result', {}) + for key in ('ipa_records', 'location_records'): + if result.get(key): + textui.print_indented(u'{}:'.format(labels[key]), indent=1) + for val in sorted(result[key]): + textui.print_indented(val, indent=2) + textui.print_line(u'') + + return int(not output['value']) diff --git a/ipalib/messages.py b/ipalib/messages.py index e863bdd..a81cc66 100644 --- a/ipalib/messages.py +++ b/ipalib/messages.py @@ -395,6 +395,29 @@ class DNSForwardPolicyConflictWithEmptyZone(PublicMessage): ) +class DNSUpdateOfSystemRecordFailed(PublicMessage): + """ + ** 13022 ** Update of a DNS system record failed + """ + errno = 13022 + type = "warning" + format = _( + "Update of system record '%(record)s' failed with error: %(error)s" + ) + + +class DNSUpdateNotIPAManagedZone(PublicMessage): + """ + ** 13023 ** Zone for system records is not managed by IPA + """ + errno = 13023 + type = "warning" + format = _( + "IPA does not manage the zone %(zone)s, please add records " + "to your DNS server manually" + ) + + def iter_messages(variables, base): """Return a tuple with all subclasses """ diff --git a/ipaserver/dns_data_management.py b/ipaserver/dns_data_management.py index 7e5ad18..b5b9c1c 100644 --- a/ipaserver/dns_data_management.py +++ b/ipaserver/dns_data_management.py @@ -4,6 +4,8 @@ from __future__ import absolute_import +import six + from collections import defaultdict from dns import ( rdataclass, @@ -17,6 +19,10 @@ from ipalib import errors from ipalib.dns import record_name_format from ipapython.dnsutil import DNSName, resolve_rrsets +if six.PY3: + unicode=str + + IPA_DEFAULT_MASTER_SRV_REC = ( # srv record name, port (DNSName(u'_ldap._tcp'), 389), @@ -214,7 +220,7 @@ class IPASystemRecords(object): for rdata in rdataset: option_name = (record_name_format % rdatatype.to_text( rdata.rdtype).lower()) - update_dict[option_name].append(rdata.to_text()) + update_dict[option_name].append(unicode(rdata.to_text())) return update_dict def __update_dns_records( @@ -378,3 +384,19 @@ class IPASystemRecords(object): self.update_base_records(), self.update_locations_records() ) + + @classmethod + def records_list_from_node(cls, name, node): + records = [] + for rdataset in node: + for rd in rdataset: + records.append( + u'{name} {ttl} {rdclass} {rdtype} {rdata}'.format( + name=name.ToASCII(), + ttl=rdataset.ttl, + rdclass=rdataclass.to_text(rd.rdclass), + rdtype=rdatatype.to_text(rd.rdtype), + rdata=rd.to_text() + ) + ) + return records diff --git a/ipaserver/plugins/dns.py b/ipaserver/plugins/dns.py index 8e2d402..e9eb1f9 100644 --- a/ipaserver/plugins/dns.py +++ b/ipaserver/plugins/dns.py @@ -72,6 +72,10 @@ from ipapython.ipautil import CheckedIPAddress from ipapython.dnsutil import check_zone_overlap from ipapython.dnsutil import DNSName from ipapython.dnsutil import related_to_auto_empty_zone +from ipaserver.dns_data_management import ( + IPASystemRecords, + IPADomainIsNotManagedByIPAError, +) if six.PY3: unicode = str @@ -4430,3 +4434,104 @@ class dnsforwardzone_add_permission(DNSZoneBase_add_permission): @register() class dnsforwardzone_remove_permission(DNSZoneBase_remove_permission): __doc__ = _('Remove a permission for per-forward zone access delegation.') + + +@register() +class dns_update_system_records(Command): + __doc__ = _('Update location and IPA server DNS records') + + + has_output_params = ( + Str( + 'ipa_records*', + label=_('IPA DNS records') + ), + Str( + 'location_records*', + label=_('IPA location records') + ) + ) + + has_output = ( + output.Output( + 'result', + type=dict, + doc=_('Dictionary mapping variable name to value'), + ), + output.Output( + 'value', bool, + _('Result of the command'), ['no_display'] + ) + ) + + takes_options = ( + Flag( + 'dry_run', + label=_('Dry run'), + doc=_('Do not update recors only return expected records') + ) + ) + + def execute(self, *args, **options): + + def output_to_list(iterable): + rec_list = [] + for name, node in iterable: + rec_list.extend(IPASystemRecords.records_list_from_node( + name, node)) + return rec_list + + def output_to_list_with_failed(iterable): + err_rec_list = [] + for name, node, error in iterable: + err_rec_list.extend([ + (v, unicode(error)) for v in + IPASystemRecords.records_list_from_node(name, node) + ]) + return err_rec_list + + result = { + 'result': {}, + 'value': True, + } + + system_records = IPASystemRecords(self.api) + + if options.get('dry_run'): + result['result']['ipa_records'] = output_to_list( + system_records.get_base_records().items()) + result['result']['location_records'] = output_to_list( + system_records.get_locations_records().items()) + else: + try: + ( + (success_base, failed_base), + (success_loc, failed_loc), + ) = system_records.update_dns_records() + except IPADomainIsNotManagedByIPAError: + result['value'] = False + self.add_message( + messages.DNSUpdateNotIPAManagedZone( + zone=self.api.env.domain) + ) + result['result']['ipa_records'] = output_to_list( + system_records.get_base_records().items()) + else: + if success_base: + result['result']['ipa_records'] = output_to_list( + success_base) + if success_loc: + result['result']['location_records'] = output_to_list( + success_loc) + for failed in (failed_base, failed_loc): + for record, error in output_to_list_with_failed(failed): + self.add_message( + messages.DNSUpdateOfSystemRecordFailed( + record=record, + error=error + ) + ) + if failed_base or failed_loc: + result['value'] = False + + return result