From 52e2ec266a293891819682487e37644ffcf11e4a Mon Sep 17 00:00:00 2001 From: Alexander Bokovoy Date: Mon, 6 Jul 2015 14:46:24 +0000 Subject: trust: support retrieving POSIX IDs with one-way trust during trust-add With one-way trust we cannot rely on cross-realm TGT as there will be none. Thus, if we have AD administrator credentials we should reuse them. Additionally, such use should be done over Kerberos. Fixes: https://fedorahosted.org/freeipa/ticket/4960 https://fedorahosted.org/freeipa/ticket/4959 --- install/oddjob/com.redhat.idm.trust-fetch-domains | 4 +- ipalib/plugins/trust.py | 65 ++++++++++++++---- ipaserver/dcerpc.py | 83 ++++++++++++++++++----- 3 files changed, 119 insertions(+), 33 deletions(-) diff --git a/install/oddjob/com.redhat.idm.trust-fetch-domains b/install/oddjob/com.redhat.idm.trust-fetch-domains index 2571dd09a..85e3cc993 100755 --- a/install/oddjob/com.redhat.idm.trust-fetch-domains +++ b/install/oddjob/com.redhat.idm.trust-fetch-domains @@ -186,7 +186,9 @@ if domains: if idrange_type != u'ipa-ad-trust-posix': range_name = name.upper() + '_id_range' dom['range_type'] = u'ipa-ad-trust' - trust.add_range(range_name, dom['ipanttrusteddomainsid'], + # Do not pass ipaserver.dcerpc.TrustInstance to trust.add_range + # to force it using existing credentials cache + trust.add_range(None, range_name, dom['ipanttrusteddomainsid'], trusted_domain, name, **dom) except errors.DuplicateEntry: # Ignore updating duplicate entries diff --git a/ipalib/plugins/trust.py b/ipalib/plugins/trust.py index 9fbaf2507..196df5926 100644 --- a/ipalib/plugins/trust.py +++ b/ipalib/plugins/trust.py @@ -166,6 +166,9 @@ DEFAULT_RANGE_SIZE = 200000 DBUS_IFACE_TRUST = 'com.redhat.idm.trust' +CRED_STYLE_SAMBA = 1 +CRED_STYLE_KERBEROS = 2 + def trust_type_string(level): """ Returns a string representing a type of the trust. The original field is an enum: @@ -196,7 +199,44 @@ def make_trust_dn(env, trust_type, dn): return DN(dn, container_dn) return dn -def add_range(myapi, range_name, dom_sid, *keys, **options): +def generate_creds(trustinstance, style, **options): + """ + Generate string representing credentials using trust instance + Input: + trustinstance -- ipaserver.dcerpc.TrustInstance object + style -- style of credentials + CRED_STYLE_SAMBA -- for using with Samba bindings + CRED_STYLE_KERBEROS -- for obtaining Kerberos ticket + **options -- options with realm_admin and realm_passwd keys + + Result: + a string representing credentials with first % separating username and password + None is returned if realm_passwd key returns nothing from options + """ + creds = None + password = options.get('realm_passwd', None) + if password: + admin_name = options.get('realm_admin') + sp = [] + sep = '@' + if style == CRED_STYLE_SAMBA: + sep = "\\" + sp = admin_name.split(sep) + if len(sp) == 1: + sp.insert(0, trustinstance.remote_domain.info['name']) + elif style == CRED_STYLE_KERBEROS: + sp = admin_name.split('\\') + if len(sp) > 1: + sp = [sp[1]] + else: + sp = admin_name.split(sep) + if len(sp) == 1: + sp.append(trustinstance.remote_domain.info['dns_forest'].upper()) + creds = u"{name}%{password}".format(name=sep.join(sp), + password=password) + return creds + +def add_range(myapi, trustinstance, range_name, dom_sid, *keys, **options): """ First, we try to derive the parameters of the ID range based on the information contained in the Active Directory. @@ -236,6 +276,12 @@ def add_range(myapi, range_name, dom_sid, *keys, **options): 'domain configured. Make sure you have run ' 'ipa-adtrust-install on the IPA server first')) + creds = None + if trustinstance: + # Re-use AD administrator credentials if they were provided + creds = generate_creds(trustinstance, style=CRED_STYLE_KERBEROS, **options) + if creds: + domain_validator._admin_creds = creds # KDC might not get refreshed data at the first time, # retry several times for retry in range(10): @@ -516,7 +562,8 @@ sides. # Store the created range type, since for POSIX trusts no # ranges for the subdomains should be added, POSIX attributes # provide a global mapping across all subdomains - (created_range_type, _, _) = add_range(self.api, range_name, dom_sid, + (created_range_type, _, _) = add_range(self.api, self.trustinstance, + range_name, dom_sid, *keys, **options) else: created_range_type = old_range['result']['iparangetype'][0] @@ -1348,19 +1395,9 @@ class trustdomain_del(LDAPDelete): return result - - def fetch_domains_from_trust(myapi, trustinstance, trust_entry, **options): trust_name = trust_entry['cn'][0] - creds = None - password = options.get('realm_passwd', None) - if password: - admin_name = options.get('realm_admin') - sp = admin_name.split('\\') - if len(sp) == 1: - sp.insert(0, trustinstance.remote_domain.info['name']) - creds = u"{name}%{password}".format(name="\\".join(sp), - password=password) + creds = generate_creds(trustinstance, style=CRED_STYLE_SAMBA, **options) server = options.get('realm_server', None) domains = ipaserver.dcerpc.fetch_domains(myapi, trustinstance.local_flatname, @@ -1394,7 +1431,7 @@ def add_new_domains_from_trust(myapi, trustinstance, trust_entry, domains, **opt if idrange_type != u'ipa-ad-trust-posix': range_name = name.upper() + '_id_range' dom['range_type'] = u'ipa-ad-trust' - add_range(myapi, range_name, dom['ipanttrusteddomainsid'], + add_range(myapi, trustinstance, range_name, dom['ipanttrusteddomainsid'], trust_name, name, **dom) except errors.DuplicateEntry: # Ignore updating duplicate entries diff --git a/ipaserver/dcerpc.py b/ipaserver/dcerpc.py index b11233d8d..bc75a6026 100644 --- a/ipaserver/dcerpc.py +++ b/ipaserver/dcerpc.py @@ -151,6 +151,7 @@ class DomainValidator(object): self._domains = None self._info = dict() self._creds = None + self._admin_creds = None self._parm = None def is_configured(self): @@ -565,6 +566,52 @@ class DomainValidator(object): % (stdout, stderr)) return (None, None) + def kinit_as_administrator(self, domain): + """ + Initializes ccache with http service credentials. + + Applies session code defaults for ccache directory and naming prefix. + Session code uses krbccache_prefix+, we use + krbccache_prefix++ so there is no clash. + + Returns tuple (ccache path, principal) where (None, None) signifes an + error on ccache initialization + """ + + if self._admin_creds == None: + return (None, None) + + domain_suffix = domain.replace('.', '-') + + ccache_name = "%sTDA%s" % (krbccache_prefix, domain_suffix) + ccache_path = os.path.join(krbccache_dir, ccache_name) + + (principal, password) = self._admin_creds.split('%', 1) + + # Destroy the contents of the ccache + root_logger.debug('Destroying the contents of the separate ccache') + + (stdout, stderr, returncode) = ipautil.run( + [paths.KDESTROY, '-A', '-c', ccache_path], + env={'KRB5CCNAME': ccache_path}, + raiseonerr=False) + + # Destroy the contents of the ccache + root_logger.debug('Running kinit with credentials of AD administrator') + + (stdout, stderr, returncode) = ipautil.run( + [paths.KINIT, principal], + env={'KRB5CCNAME': ccache_path}, + stdin=password, + raiseonerr=False) + + if returncode == 0: + return (ccache_path, principal) + else: + root_logger.debug('Kinit failed, stout: %s, stderr: %s' + % (stdout, stderr)) + return (None, None) + def search_in_dc(self, domain, filter, attrs, scope, basedn=None, quiet=False): """ @@ -597,7 +644,8 @@ class DomainValidator(object): Returns LDAP result or None. """ - (ccache_name, principal) = self.kinit_as_http(info['dns_domain']) + if self._admin_creds: + (ccache_name, principal) = self.kinit_as_administrator(info['dns_domain']) if ccache_name: with ipautil.private_ccache(path=ccache_name): @@ -1104,10 +1152,24 @@ def fetch_domains(api, mydomain, trustdomain, creds=None, server=None): raise assess_dcerpc_exception(message=str(e)) td.info['dc'] = unicode(result.pdc_dns_name) - if creds is None: + if type(creds) is bool: + # Rely on existing Kerberos credentials in the environment + td.creds = credentials.Credentials() + td.creds.set_kerberos_state(credentials.MUST_USE_KERBEROS) + td.creds.guess(td.parm) + td.creds.set_workstation(domain_validator.flatname) + domains = communicate(td) + else: # Attempt to authenticate as HTTP/ipa.master and use cross-forest trust + # or as passed-in user in case of a one-way trust domval = DomainValidator(api) - (ccache_name, principal) = domval.kinit_as_http(trustdomain) + ccache_name = None + principal = None + if creds: + domval._admin_creds = creds + (ccache_name, principal) = domval.kinit_as_administrator(trustdomain) + else: + (ccache_name, principal) = domval.kinit_as_http(trustdomain) td.creds = credentials.Credentials() td.creds.set_kerberos_state(credentials.MUST_USE_KERBEROS) if ccache_name: @@ -1115,21 +1177,6 @@ def fetch_domains(api, mydomain, trustdomain, creds=None, server=None): td.creds.guess(td.parm) td.creds.set_workstation(domain_validator.flatname) domains = communicate(td) - elif type(creds) is bool: - # Rely on existing Kerberos credentials in the environment - td.creds = credentials.Credentials() - td.creds.set_kerberos_state(credentials.MUST_USE_KERBEROS) - td.creds.guess(td.parm) - td.creds.set_workstation(domain_validator.flatname) - domains = communicate(td) - else: - # Assume we've got credentials as a string user%password - td.creds = credentials.Credentials() - td.creds.set_kerberos_state(credentials.DONT_USE_KERBEROS) - td.creds.guess(td.parm) - td.creds.parse_string(creds) - td.creds.set_workstation(domain_validator.flatname) - domains = communicate(td) if domains is None: return None -- cgit