From d5aa1ee04e2e4923f42bccd60d51f063df144a0b Mon Sep 17 00:00:00 2001 From: Alexander Bokovoy Date: Fri, 5 Jun 2015 12:57:02 +0000 Subject: trusts: add support for one-way trust and switch to it by default One-way trust is the default now, use 'trust add --two-way ' to force bidirectional trust https://fedorahosted.org/freeipa/ticket/4959 In case of one-way trust we cannot authenticate using cross-realm TGT against an AD DC. We have to use trusted domain object from within AD domain and access to this object is limited to avoid compromising the whole trust configuration. Instead, IPA framework can call out to oddjob daemon and ask it to run the script which can have access to the TDO object. This script (com.redhat.idm.trust-fetch-domains) is using cifs/ipa.master principal to retrieve TDO object credentials from IPA LDAP if needed and then authenticate against AD DCs using the TDO object credentials. The script pulls the trust topology out of AD DCs and updates IPA LDAP store. Then IPA framework can pick the updated data from the IPA LDAP under normal access conditions. Part of https://fedorahosted.org/freeipa/ticket/4546 Reviewed-By: Tomas Babej --- ipaserver/dcerpc.py | 44 ++++++++++++++++++++++++++++++++------------ 1 file changed, 32 insertions(+), 12 deletions(-) (limited to 'ipaserver/dcerpc.py') diff --git a/ipaserver/dcerpc.py b/ipaserver/dcerpc.py index a54f5f67f..bfdfe2c5d 100644 --- a/ipaserver/dcerpc.py +++ b/ipaserver/dcerpc.py @@ -66,6 +66,10 @@ The code in this module relies heavily on samba4-python package and Samba4 python bindings. """) +# Both constants can be used as masks against trust direction +# because bi-directional has two lower bits set. +TRUST_ONEWAY = 1 +TRUST_BIDIRECTIONAL = 3 def is_sid_valid(sid): try: @@ -949,7 +953,7 @@ class TrustDomainInstance(object): # We can ignore the error here -- setting up name suffix routes may fail pass - def establish_trust(self, another_domain, trustdom_secret): + def establish_trust(self, another_domain, trustdom_secret, trust_type='bidirectional'): """ Establishes trust between our and another domain Input: another_domain -- instance of TrustDomainInstance, initialized with #retrieve call @@ -967,7 +971,9 @@ class TrustDomainInstance(object): info.domain_name.string = another_domain.info['dns_domain'] info.netbios_name.string = another_domain.info['name'] info.sid = security.dom_sid(another_domain.info['sid']) - info.trust_direction = lsa.LSA_TRUST_DIRECTION_INBOUND | lsa.LSA_TRUST_DIRECTION_OUTBOUND + info.trust_direction = lsa.LSA_TRUST_DIRECTION_INBOUND + if trust_type == TRUST_BIDIRECTIONAL: + info.trust_direction |= lsa.LSA_TRUST_DIRECTION_OUTBOUND info.trust_type = lsa.LSA_TRUST_TYPE_UPLEVEL info.trust_attributes = 0 @@ -1005,7 +1011,8 @@ class TrustDomainInstance(object): pass try: - info.trust_attributes = lsa.LSA_TRUST_ATTRIBUTE_FOREST_TRANSITIVE + info = self._pipe.QueryTrustedDomainInfo(trustdom_handle, lsa.LSA_TRUSTED_DOMAIN_INFO_INFO_EX) + info.trust_attributes |= lsa.LSA_TRUST_ATTRIBUTE_FOREST_TRANSITIVE self._pipe.SetInformationTrustedDomain(trustdom_handle, lsa.LSA_TRUSTED_DOMAIN_INFO_INFO_EX, info) except RuntimeError, e: root_logger.error('unable to set trust to transitive: %s' % (str(e))) @@ -1014,10 +1021,10 @@ class TrustDomainInstance(object): self.update_ftinfo(another_domain) def verify_trust(self, another_domain): - def retrieve_netlogon_info_2(domain, function_code, data): + def retrieve_netlogon_info_2(logon_server, domain, function_code, data): try: netr_pipe = netlogon.netlogon(domain.binding, domain.parm, domain.creds) - result = netr_pipe.netr_LogonControl2Ex(logon_server=None, + result = netr_pipe.netr_LogonControl2Ex(logon_server=logon_server, function_code=function_code, level=2, data=data @@ -1026,7 +1033,7 @@ class TrustDomainInstance(object): except RuntimeError, (num, message): raise assess_dcerpc_exception(num=num, message=message) - result = retrieve_netlogon_info_2(self, + result = retrieve_netlogon_info_2(None, self, netlogon.NETLOGON_CONTROL_TC_VERIFY, another_domain.info['dns_domain']) if (result and (result.flags and netlogon.NETLOGON_VERIFY_STATUS_RETURNED)): @@ -1100,6 +1107,7 @@ def fetch_domains(api, mydomain, trustdomain, creds=None, server=None): td.info['dc'] = unicode(result.pdc_dns_name) if creds is None: + # Attempt to authenticate as HTTP/ipa.master and use cross-forest trust domval = DomainValidator(api) (ccache_name, principal) = domval.kinit_as_http(trustdomain) td.creds = credentials.Credentials() @@ -1109,7 +1117,15 @@ 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) @@ -1222,7 +1238,7 @@ class TrustDomainJoins(object): ftinfo['rec_type'] = lsa.LSA_FOREST_TRUST_TOP_LEVEL_NAME self.local_domain.ftinfo_records.append(ftinfo) - def join_ad_full_credentials(self, realm, realm_server, realm_admin, realm_passwd): + def join_ad_full_credentials(self, realm, realm_server, realm_admin, realm_passwd, trust_type): if not self.configured: return None @@ -1240,13 +1256,17 @@ class TrustDomainJoins(object): if not self.remote_domain.read_only: trustdom_pass = samba.generate_random_password(128, 128) self.get_realmdomains() - self.remote_domain.establish_trust(self.local_domain, trustdom_pass) - self.local_domain.establish_trust(self.remote_domain, trustdom_pass) - result = self.remote_domain.verify_trust(self.local_domain) + self.remote_domain.establish_trust(self.local_domain, trustdom_pass, trust_type) + self.local_domain.establish_trust(self.remote_domain, trustdom_pass, trust_type) + # if trust is inbound, we don't need to verify it because AD DC will respond + # with WERR_NO_SUCH_DOMAIN -- in only does verification for outbound trusts. + result = True + if trust_type == TRUST_BIDIRECTIONAL: + result = self.remote_domain.verify_trust(self.local_domain) return dict(local=self.local_domain, remote=self.remote_domain, verified=result) return None - def join_ad_ipa_half(self, realm, realm_server, trustdom_passwd): + def join_ad_ipa_half(self, realm, realm_server, trustdom_passwd, trust_type): if not self.configured: return None @@ -1256,5 +1276,5 @@ class TrustDomainJoins(object): if self.remote_domain.info['dns_domain'] != self.remote_domain.info['dns_forest']: raise errors.NotAForestRootError(forest=self.remote_domain.info['dns_forest'], domain=self.remote_domain.info['dns_domain']) - self.local_domain.establish_trust(self.remote_domain, trustdom_passwd) + self.local_domain.establish_trust(self.remote_domain, trustdom_passwd, trust_type) return dict(local=self.local_domain, remote=self.remote_domain, verified=False) -- cgit