On 09/03/2015 04:34 PM, Alexander Bokovoy wrote: > On Thu, 03 Sep 2015, Tomas Babej wrote: >> Hi, >> >> this couple of patches fix https://fedorahosted.org/freeipa/ticket/5278 >> and improve our handling of realmdomains in general. > The code looks good to me. I haven't tested it yet, though. >
Rebased on top of current master.
From 6071024ee951221685c283f23dd9c43667cc4bd9 Mon Sep 17 00:00:00 2001 From: Tomas Babej <tba...@redhat.com> Date: Thu, 3 Sep 2015 12:13:32 +0200 Subject: [PATCH] util: Add detect_dns_zone_realm_type helper https://fedorahosted.org/freeipa/ticket/5278 --- ipalib/util.py | 55 +++++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 55 insertions(+) diff --git a/ipalib/util.py b/ipalib/util.py index 7c7da6af7bbb290dd25832e188d5445a23e5571e..e5e0b5c40d39c4761405c35130e5cdc077d717eb 100644 --- a/ipalib/util.py +++ b/ipalib/util.py @@ -802,3 +802,58 @@ def get_topology_connection_errors(graph): if not_visited: connect_errors.append((m, list(visited), list(not_visited))) return connect_errors + +def detect_dns_zone_realm_type(api, domain): + """ + Detects the type of the realm that the given DNS zone belongs to. + Note: This method is heuristic. Possible values: + - 'current': For IPA domains belonging in the current realm. + - 'foreign': For domains belonging in a foreing kerberos realm. + - 'unknown': For domains whose allegiance could not be detected. + """ + + # First, try to detect _kerberos TXT record in the domain + # This would indicate that the domain belongs to IPA realm + + kerberos_prefix = DNSName('_kerberos') + domain_suffix = DNSName(domain) + kerberos_record_name = kerberos_prefix + domain_suffix + + response = None + + try: + result = resolver.query(kerberos_record_name, rdatatype.TXT) + answer = result.response.answer + + # IPA domain will have only one _kerberos TXT record + if (len(answer) == 1 and + len(answer[0]) == 1 and + answer[0].rdtype == rdatatype.TXT): + + record = answer[0][0] + + # If the record contains our current realm, it is 'ipa-current' + if record.to_text() == '"{0}"'.format(api.env.realm): + return 'current' + else: + return 'foreign' + + except DNSException as e: + pass + + # Try to detect AD specific record in the zone. + # This would indicate that the domain belongs to foreign (AD) realm + + gc_prefix = DNSName('_ldap._tcp.gc._msdcs') + ad_specific_record_name = gc_prefix + domain_suffix + + try: + # The presence of this record is enough, return foreign in such case + result = resolver.query(ad_specific_record_name, rdatatype.SRV) + return 'foreign' + + except DNSException as e: + pass + + # If we could not detect type with certainity, return unknown + return 'unknown' -- 2.1.0
From 2099722b982866537ed11f21cee328d9ff17af4d Mon Sep 17 00:00:00 2001 From: Tomas Babej <tba...@redhat.com> Date: Thu, 3 Sep 2015 12:40:17 +0200 Subject: [PATCH] realmdomains: Minor style and wording improvements https://fedorahosted.org/freeipa/ticket/5278 --- ipalib/plugins/realmdomains.py | 75 +++++++++++++++++++++++++++++++++--------- 1 file changed, 60 insertions(+), 15 deletions(-) diff --git a/ipalib/plugins/realmdomains.py b/ipalib/plugins/realmdomains.py index f8f838d0ede85ee747a4b2f19129dc757fe837eb..4e618f1d1a71ba2133b03384bc056d069c505f43 100644 --- a/ipalib/plugins/realmdomains.py +++ b/ipalib/plugins/realmdomains.py @@ -137,16 +137,46 @@ class realmdomains_mod(LDAPUpdate): del_domain = entry_attrs.get('del_domain') force = options.get('force') + current_domain = get_domain_name() + + missing_soa_ns_record_error = _( + "DNS zone for each realmdomain must contain " + "SOA or NS records. No records found for: %s" + ) + + # User specified the list of domains explicitly if associateddomain: if add_domain or del_domain: - raise errors.MutuallyExclusiveError(reason=_("you cannot specify the --domain option together with --add-domain or --del-domain")) - if get_domain_name() not in associateddomain: - raise errors.ValidationError(name='domain', error=_("cannot delete domain of IPA server")) + raise errors.MutuallyExclusiveError( + reason=_( + "The --domain option cannot be used together " + "with --add-domain or --del-domain. Use --domain " + "to specify the whole realm domain list explicitly, " + "to add/remove individual domains, use " + "--add-domain/del-domain.") + ) + + # Make sure our domain is included in the list + if current_domain not in associateddomain: + raise errors.ValidationError( + name='realmdomain list', + error=_("IPA server domain cannot by omitted") + ) + + # Unless forced, check that each domain has SOA or NS records if not force: - bad_domains = [d for d in associateddomain if not has_soa_or_ns_record(d)] + bad_domains = [ + d for d in associateddomain + if not has_soa_or_ns_record(d) + ] + if bad_domains: bad_domains = ', '.join(bad_domains) - raise errors.ValidationError(name='domain', error=_("no SOA or NS records found for domains: %s" % bad_domains)) + raise errors.ValidationError( + name='domain', + error=missing_soa_ns_record_error % bad_domains + ) + return dn # If --add-domain or --del-domain options were provided, read @@ -155,18 +185,29 @@ class realmdomains_mod(LDAPUpdate): if add_domain: if not force and not has_soa_or_ns_record(add_domain): - raise errors.ValidationError(name='add_domain', error=_("no SOA or NS records found for domain %s" % add_domain)) + raise errors.ValidationError( + name='add_domain', + error=missing_soa_ns_record_error % add_domain + ) + del entry_attrs['add_domain'] domains.append(add_domain) if del_domain: - if del_domain == get_domain_name(): - raise errors.ValidationError(name='del_domain', error=_("cannot delete domain of IPA server")) + if del_domain == current_domain: + raise errors.ValidationError( + name='del_domain', + error=_("IPA server domain cannot be deleted") + ) del entry_attrs['del_domain'] + try: domains.remove(del_domain) except ValueError: - raise errors.AttrValueNotFound(attr='associateddomain', value=del_domain) + raise errors.AttrValueNotFound( + attr='associateddomain', + value=del_domain + ) entry_attrs['associateddomain'] = domains return dn @@ -184,13 +225,15 @@ class realmdomains_mod(LDAPUpdate): # Add a _kerberos TXT record for zones that correspond with # domains which were added - for d in domains_added: + for domain in domains_added: + # Skip our own domain - if d == api.env.domain: + if domain == api.env.domain: continue + try: api.Command['dnsrecord_add']( - unicode(d), + unicode(domain), u'_kerberos', txtrecord=api.env.realm ) @@ -199,13 +242,15 @@ class realmdomains_mod(LDAPUpdate): # Delete _kerberos TXT record from zones that correspond with # domains which were deleted - for d in domains_deleted: + for domain in domains_deleted: + # Skip our own domain - if d == api.env.domain: + if domain == api.env.domain: continue + try: api.Command['dnsrecord_del']( - unicode(d), + unicode(domain), u'_kerberos', txtrecord=api.env.realm ) -- 2.1.0
From a58ec054d08a7b417910b04e36d233bb3ece1fa1 Mon Sep 17 00:00:00 2001 From: Tomas Babej <tba...@redhat.com> Date: Thu, 3 Sep 2015 13:22:41 +0200 Subject: [PATCH] realmdomains: Add validation that realmdomain being added is indeed from our realm https://fedorahosted.org/freeipa/ticket/5278 --- ipalib/plugins/realmdomains.py | 99 ++++++++++++++++++++++++++++++++---------- 1 file changed, 75 insertions(+), 24 deletions(-) diff --git a/ipalib/plugins/realmdomains.py b/ipalib/plugins/realmdomains.py index 4e618f1d1a71ba2133b03384bc056d069c505f43..e725ef1b79647c7e8fec2994fafb1d3082900904 100644 --- a/ipalib/plugins/realmdomains.py +++ b/ipalib/plugins/realmdomains.py @@ -25,6 +25,7 @@ from ipalib import _ from ipalib.plugable import Registry from ipalib.plugins.baseldap import LDAPObject, LDAPUpdate, LDAPRetrieve from ipalib.util import has_soa_or_ns_record, validate_domain_name +from ipalib.util import detect_dns_zone_realm_type from ipapython.dn import DN from ipapython.ipautil import get_domain_name @@ -130,6 +131,76 @@ class realmdomains_mod(LDAPUpdate): ), ) + def validate_domains(self, domains, force): + """ + Validates the list of domains as candidates for additions to the + realmdomains list. + + Requirements: + - Each domain has SOA or NS record + - Each domain belongs to the current realm + """ + + # Unless forced, check that each domain has SOA or NS records + if not force: + invalid_domains = [ + d for d in domains + if not has_soa_or_ns_record(d) + ] + + if invalid_domains: + raise errors.ValidationError( + name='domain', + error= _( + "DNS zone for each realmdomain must contain " + "SOA or NS records. No records found for: %s" + ) % ','.join(invalid_domains) + ) + + # Check realm alliegence for each domain + domains_with_realm = [ + (domain, detect_dns_zone_realm_type(self.api, domain)) + for domain in domains + ] + + foreign_domains = [ + domain for domain, realm in domains_with_realm + if realm == 'foreign' + ] + + unknown_domains = [ + domain for domain, realm in domains_with_realm + if realm == 'unknown' + ] + + # If there are any foreing realm domains, bail out + if foreign_domains: + raise errors.ValidationError( + name='domain', + error=_( + 'The following domains do not belong ' + 'to this realm: %s' + ) % ','.join(foreign_domains) + ) + + # If there are any unknown domains, error out, + # asking for _kerberos TXT records + + # Note: This can be forced, since realmdomains-mod + # is called from dnszone-add where we know that + # the domain being added belongs to our realm + if not force and unknown_domains: + raise errors.ValidationError( + name='domain', + error=_( + 'The realm of the folllowing domains could ' + 'not be detected: %s. If these are domains ' + 'that belong to the this realm, please ' + 'create a _kerberos TXT record containing "%s" ' + 'in each of them.' + ) % (','.join(unknown_domains), self.api.env.realm) + ) + def pre_callback(self, ldap, dn, entry_attrs, attrs_list, *keys, **options): assert isinstance(dn, DN) associateddomain = entry_attrs.get('associateddomain') @@ -139,11 +210,6 @@ class realmdomains_mod(LDAPUpdate): current_domain = get_domain_name() - missing_soa_ns_record_error = _( - "DNS zone for each realmdomain must contain " - "SOA or NS records. No records found for: %s" - ) - # User specified the list of domains explicitly if associateddomain: if add_domain or del_domain: @@ -163,19 +229,9 @@ class realmdomains_mod(LDAPUpdate): error=_("IPA server domain cannot by omitted") ) - # Unless forced, check that each domain has SOA or NS records - if not force: - bad_domains = [ - d for d in associateddomain - if not has_soa_or_ns_record(d) - ] - - if bad_domains: - bad_domains = ', '.join(bad_domains) - raise errors.ValidationError( - name='domain', - error=missing_soa_ns_record_error % bad_domains - ) + # Validate that each domain satisfies the requirements + # for realmdomain + self.validate_domains(domains=associateddomain, force=force) return dn @@ -184,12 +240,7 @@ class realmdomains_mod(LDAPUpdate): domains = ldap.get_entry(dn)['associateddomain'] if add_domain: - if not force and not has_soa_or_ns_record(add_domain): - raise errors.ValidationError( - name='add_domain', - error=missing_soa_ns_record_error % add_domain - ) - + self.validate_domains(domains=[add_domain], force=force) del entry_attrs['add_domain'] domains.append(add_domain) -- 2.1.0
From a63c73ace57173f0120bb4030c80270224f87b4a Mon Sep 17 00:00:00 2001 From: Tomas Babej <tba...@redhat.com> Date: Thu, 3 Sep 2015 14:00:09 +0200 Subject: [PATCH] realmdomains: Issue a warning when automated management of realmdomains failed https://fedorahosted.org/freeipa/ticket/5278 --- ipalib/messages.py | 31 +++++++++++++++++++++++++++++++ ipalib/plugins/realmdomains.py | 28 +++++++++++++++++++++++----- 2 files changed, 54 insertions(+), 5 deletions(-) diff --git a/ipalib/messages.py b/ipalib/messages.py index 375da244328f892eee686c9787524d4a6709c0ce..8140aa2a17fb8deca7b535c9abf5363e971f8f19 100644 --- a/ipalib/messages.py +++ b/ipalib/messages.py @@ -246,6 +246,37 @@ class DNSSECValidationFailingWarning(PublicMessage): u"validation on all IPA servers.") +class KerberosTXTRecordCreationFailure(PublicMessage): + """ + **13011** Used when a _kerberos TXT record could not be added to + a DNS zone. + """ + + errno = 13011 + type = "warning" + format = _( + "The _kerberos TXT record from domain %(domain)s could not be removed " + "(%(error)s).\nThis can happen if the zone is not managed by IPA. " + "Please create the record manually, containing the following " + "value: '%(realm)s'" + ) + + +class KerberosTXTRecordDeletionFailure(PublicMessage): + """ + **13012** Used when a _kerberos TXT record could not be removed from + a DNS zone. + """ + + errno = 13012 + type = "warning" + format = _( + "The _kerberos TXT record from domain %(domain)s could not be removed " + "(%(error)s).\nThis can happen if the zone is not managed by IPA. " + "Please remove the record manually." + ) + + def iter_messages(variables, base): """Return a tuple with all subclasses """ diff --git a/ipalib/plugins/realmdomains.py b/ipalib/plugins/realmdomains.py index e725ef1b79647c7e8fec2994fafb1d3082900904..94379098b30ac324ea20d7ecbac252f84a2d5e49 100644 --- a/ipalib/plugins/realmdomains.py +++ b/ipalib/plugins/realmdomains.py @@ -19,7 +19,7 @@ import six -from ipalib import api, errors +from ipalib import api, errors, messages from ipalib import Str, Flag from ipalib import _ from ipalib.plugable import Registry @@ -288,8 +288,18 @@ class realmdomains_mod(LDAPUpdate): u'_kerberos', txtrecord=api.env.realm ) - except (errors.EmptyModlist, errors.NotFound): - pass + except (errors.EmptyModlist, errors.NotFound) as error: + # If creation of the _kerberos TXT record failed, prompt + # for manual intervention + messages.add_message( + options['version'], + result, + messages.KerberosTXTRecordCreationFailure( + domain=domain, + error=unicode(error), + realm=self.api.env.realm + ) + ) # Delete _kerberos TXT record from zones that correspond with # domains which were deleted @@ -305,8 +315,16 @@ class realmdomains_mod(LDAPUpdate): u'_kerberos', txtrecord=api.env.realm ) - except (errors.AttrValueNotFound, errors.NotFound): - pass + except (errors.AttrValueNotFound, errors.NotFound) as error: + # If deletion of the _kerberos TXT record failed, prompt + # for manual intervention + messages.add_message( + options['version'], + result, + messages.KerberosTXTRecordDeletionFailure( + domain=domain, error=unicode(error) + ) + ) return result -- 2.1.0
From e4998ecda02b4fbd4c81b3b49f24c8469b4786ec Mon Sep 17 00:00:00 2001 From: Tomas Babej <tba...@redhat.com> Date: Thu, 3 Sep 2015 14:03:09 +0200 Subject: [PATCH] realmdomains: Do not fail due the ValidationError when adding _kerberos TXT record https://fedorahosted.org/freeipa/ticket/5278 --- ipalib/plugins/realmdomains.py | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/ipalib/plugins/realmdomains.py b/ipalib/plugins/realmdomains.py index 94379098b30ac324ea20d7ecbac252f84a2d5e49..7ce91bf256fa08ab5c39e8ebb969378e54a44e9e 100644 --- a/ipalib/plugins/realmdomains.py +++ b/ipalib/plugins/realmdomains.py @@ -288,7 +288,9 @@ class realmdomains_mod(LDAPUpdate): u'_kerberos', txtrecord=api.env.realm ) - except (errors.EmptyModlist, errors.NotFound) as error: + except (errors.EmptyModlist, errors.NotFound, + errors.ValidationError) as error: + # If creation of the _kerberos TXT record failed, prompt # for manual intervention messages.add_message( @@ -315,7 +317,8 @@ class realmdomains_mod(LDAPUpdate): u'_kerberos', txtrecord=api.env.realm ) - except (errors.AttrValueNotFound, errors.NotFound) as error: + except (errors.AttrValueNotFound, errors.NotFound, + errors.ValidationError) as error: # If deletion of the _kerberos TXT record failed, prompt # for manual intervention messages.add_message( -- 2.1.0
-- Manage your subscription for the Freeipa-devel mailing list: https://www.redhat.com/mailman/listinfo/freeipa-devel Contribute to FreeIPA: http://www.freeipa.org/page/Contribute/Code