Blob Blame History Raw
From a4e9e8f368c8a61d539e9da26e4a16a6234c138f Mon Sep 17 00:00:00 2001
From: Alexander Bokovoy <abokovoy@redhat.com>
Date: Fri, 17 Nov 2017 17:19:25 +0200
Subject: [PATCH] trust: detect and error out when non-AD trust with IPA domain
 name exists

Quite often users choose wrong type of trust on Active Directory side
when setting up a trust to freeIPA. The trust type supported by freeIPA
is just a normal forest trust to another Active Directory. However,
some people follow old internet recipes that force using a trust to MIT
Kerberos realm.

This is a wrong type of trust. Unfortunately, when someone used MIT
Kerberos realm trust, there is no way to programmatically remote the
trust from freeIPA side. As result, we have to detect such situation and
report an error.

To do proper reporting, we need reuse some constants and trust type
names we use in IPA CLI/Web UI. These common components were moved to
a separate ipaserver/dcerpc_common.py module that is imported by both
ipaserver/plugins/trust.py and ipaserver/dcerpc.py.

Fixes https://pagure.io/freeipa/issue/7264

Reviewed-By: Christian Heimes <cheimes@redhat.com>
Reviewed-By: Thierry Bordaz <tbordaz@redhat.com>
---
 ipaserver/dcerpc.py        | 37 +++++++++++++++--------
 ipaserver/dcerpc_common.py | 73 ++++++++++++++++++++++++++++++++++++++++++++++
 ipaserver/plugins/trust.py | 65 ++++++++++-------------------------------
 3 files changed, 113 insertions(+), 62 deletions(-)
 create mode 100644 ipaserver/dcerpc_common.py

diff --git a/ipaserver/dcerpc.py b/ipaserver/dcerpc.py
index aa63cd9db0a1d47b5309cc6bed2ff7584760a39d..ac1b2a34784df491a3851aa21bbadbec2297241c 100644
--- a/ipaserver/dcerpc.py
+++ b/ipaserver/dcerpc.py
@@ -31,6 +31,10 @@ from ipapython import ipautil
 from ipapython.ipa_log_manager import root_logger
 from ipapython.dn import DN
 from ipaserver.install import installutils
+from ipaserver.dcerpc_common import (TRUST_BIDIRECTIONAL,
+                                     TRUST_JOIN_EXTERNAL,
+                                     trust_type_string)
+
 from ipalib.util import normalize_name
 
 import os
@@ -77,15 +81,6 @@ 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
-
-# Trust join behavior
-# External trust -- allow creating trust to a non-root domain in the forest
-TRUST_JOIN_EXTERNAL = 1
-
 
 def is_sid_valid(sid):
     try:
@@ -151,6 +146,7 @@ pysss_type_key_translation_dict = {
     pysss_nss_idmap.ID_BOTH: 'both',
 }
 
+
 class TrustTopologyConflictSolved(Exception):
     """
     Internal trust error: raised when previously detected
@@ -1254,9 +1250,26 @@ class TrustDomainInstance(object):
             dname = lsa.String()
             dname.string = another_domain.info['dns_domain']
             res = self._pipe.QueryTrustedDomainInfoByName(
-                                self._policy_handle,
-                                dname,
-                                lsa.LSA_TRUSTED_DOMAIN_INFO_FULL_INFO)
+                self._policy_handle,
+                dname,
+                lsa.LSA_TRUSTED_DOMAIN_INFO_FULL_INFO
+            )
+            if res.info_ex.trust_type != lsa.LSA_TRUST_TYPE_UPLEVEL:
+                msg = _('There is already a trust to {ipa_domain} with '
+                        'unsupported type {trust_type}. Please remove '
+                        'it manually on AD DC side.')
+                ttype = trust_type_string(
+                    res.info_ex.trust_type, res.info_ex.trust_attributes
+                )
+                err = unicode(msg).format(
+                    ipa_domain=another_domain.info['dns_domain'],
+                    trust_type=ttype)
+
+                raise errors.ValidationError(
+                    name=_('AD domain controller'),
+                    error=err
+                )
+
             self._pipe.DeleteTrustedDomain(self._policy_handle,
                                            res.info_ex.sid)
         except RuntimeError as e:
diff --git a/ipaserver/dcerpc_common.py b/ipaserver/dcerpc_common.py
new file mode 100644
index 0000000000000000000000000000000000000000..526b025e3282c8a556088eb2ed1ba467b889b86c
--- /dev/null
+++ b/ipaserver/dcerpc_common.py
@@ -0,0 +1,73 @@
+import six
+from ipalib import _
+if six.PY3:
+    unicode = six.text_type
+
+# Both constants can be used as masks against trust direction
+# because bi-directional has two lower bits set.
+TRUST_ONEWAY = 1
+TRUST_BIDIRECTIONAL = 3
+
+# Trust join behavior
+# External trust -- allow creating trust to a non-root domain in the forest
+TRUST_JOIN_EXTERNAL = 1
+
+# We don't want to import any of Samba Python code here just for constants
+# Since these constants set in MS-ADTS, we can rely on their stability
+LSA_TRUST_ATTRIBUTE_NON_TRANSITIVE = 0x00000001
+
+_trust_direction_dict = {
+        1: _('Trusting forest'),
+        2: _('Trusted forest'),
+        3: _('Two-way trust')
+}
+
+_trust_status_dict = {
+        True: _('Established and verified'),
+        False: _('Waiting for confirmation by remote side')
+}
+
+_trust_type_dict_unknown = _('Unknown')
+
+# Trust type is a combination of ipanttrusttype and ipanttrustattributes
+# We shift trust attributes by 3 bits to left so bit 0 becomes bit 3 and
+# 2+(1 << 3) becomes 10.
+_trust_type_dict = {
+        1: _('Non-Active Directory domain'),
+        2: _('Active Directory domain'),
+        3: _('RFC4120-compliant Kerberos realm'),
+        10: _('Non-transitive external trust to a domain in '
+              'another Active Directory forest'),
+        11: _('Non-transitive external trust to an RFC4120-'
+              'compliant Kerberos realm')
+}
+
+
+def trust_type_string(level, attrs):
+    """
+    Returns a string representing a type of the trust.
+    The original field is an enum:
+      LSA_TRUST_TYPE_DOWNLEVEL  = 0x00000001,
+      LSA_TRUST_TYPE_UPLEVEL    = 0x00000002,
+      LSA_TRUST_TYPE_MIT        = 0x00000003
+    """
+    transitive = int(attrs) & LSA_TRUST_ATTRIBUTE_NON_TRANSITIVE
+    string = _trust_type_dict.get(int(level) | (transitive << 3),
+                                  _trust_type_dict_unknown)
+    return unicode(string)
+
+
+def trust_direction_string(level):
+    """
+    Returns a string representing a direction of the trust.
+    The original field is a bitmask taking two bits in use
+      LSA_TRUST_DIRECTION_INBOUND  = 0x00000001,
+      LSA_TRUST_DIRECTION_OUTBOUND = 0x00000002
+    """
+    string = _trust_direction_dict.get(int(level), _trust_type_dict_unknown)
+    return unicode(string)
+
+
+def trust_status_string(level):
+    string = _trust_status_dict.get(level, _trust_type_dict_unknown)
+    return unicode(string)
diff --git a/ipaserver/plugins/trust.py b/ipaserver/plugins/trust.py
index d0bbfbc47ca65c9c5229685fc9d202c293fe41cd..ecc5fa0b22f94de05cc5282758be093f0cfca13f 100644
--- a/ipaserver/plugins/trust.py
+++ b/ipaserver/plugins/trust.py
@@ -44,6 +44,13 @@ from ipalib import errors
 from ipalib import output
 from ldap import SCOPE_SUBTREE
 from time import sleep
+from ipaserver.dcerpc_common import (TRUST_ONEWAY,
+                                     TRUST_BIDIRECTIONAL,
+                                     TRUST_JOIN_EXTERNAL,
+                                     LSA_TRUST_ATTRIBUTE_NON_TRANSITIVE,
+                                     trust_type_string,
+                                     trust_direction_string,
+                                     trust_status_string)
 
 if six.PY3:
     unicode = str
@@ -63,9 +70,6 @@ except Exception as e:
 if api.env.in_server and api.env.context in ['lite', 'server']:
     try:
         import ipaserver.dcerpc
-        from ipaserver.dcerpc import (TRUST_ONEWAY,
-                                      TRUST_BIDIRECTIONAL,
-                                      TRUST_JOIN_EXTERNAL)
         import dbus
         import dbus.mainloop.glib
         _bindings_installed = True
@@ -157,28 +161,14 @@ particular type.
 
 register = Registry()
 
-# Trust type is a combination of ipanttrusttype and ipanttrustattributes
-# We shift trust attributes by 3 bits to left so bit 0 becomes bit 3 and
-# 2+(1 << 3) becomes 10.
-_trust_type_dict = {1 : _('Non-Active Directory domain'),
-                    2 : _('Active Directory domain'),
-                    3 : _('RFC4120-compliant Kerberos realm'),
-                    10: _('Non-transitive external trust to a domain in another Active Directory forest')}
-
-_trust_direction_dict = {1 : _('Trusting forest'),
-                         2 : _('Trusted forest'),
-                         3 : _('Two-way trust')}
-_trust_status_dict = {True : _('Established and verified'),
-                 False : _('Waiting for confirmation by remote side')}
-_trust_type_dict_unknown = _('Unknown')
-
-_trust_type_option = StrEnum('trust_type',
-                        cli_name='type',
-                        label=_('Trust type (ad for Active Directory, default)'),
-                        values=(u'ad',),
-                        default=u'ad',
-                        autofill=True,
-                    )
+_trust_type_option = StrEnum(
+         'trust_type',
+         cli_name='type',
+         label=_('Trust type (ad for Active Directory, default)'),
+         values=(u'ad',),
+         default=u'ad',
+         autofill=True,
+        )
 
 DEFAULT_RANGE_SIZE = 200000
 
@@ -187,31 +177,6 @@ DBUS_IFACE_TRUST = 'com.redhat.idm.trust'
 CRED_STYLE_SAMBA = 1
 CRED_STYLE_KERBEROS = 2
 
-LSA_TRUST_ATTRIBUTE_NON_TRANSITIVE = 0x00000001
-
-def trust_type_string(level, attrs):
-    """
-    Returns a string representing a type of the trust. The original field is an enum:
-      LSA_TRUST_TYPE_DOWNLEVEL  = 0x00000001,
-      LSA_TRUST_TYPE_UPLEVEL    = 0x00000002,
-      LSA_TRUST_TYPE_MIT        = 0x00000003
-    """
-    transitive = int(attrs) & LSA_TRUST_ATTRIBUTE_NON_TRANSITIVE
-    string = _trust_type_dict.get(int(level) | (transitive << 3), _trust_type_dict_unknown)
-    return unicode(string)
-
-def trust_direction_string(level):
-    """
-    Returns a string representing a direction of the trust. The original field is a bitmask taking two bits in use
-      LSA_TRUST_DIRECTION_INBOUND  = 0x00000001,
-      LSA_TRUST_DIRECTION_OUTBOUND = 0x00000002
-    """
-    string = _trust_direction_dict.get(int(level), _trust_type_dict_unknown)
-    return unicode(string)
-
-def trust_status_string(level):
-    string = _trust_status_dict.get(level, _trust_type_dict_unknown)
-    return unicode(string)
 
 def make_trust_dn(env, trust_type, dn):
     assert isinstance(dn, DN)
-- 
2.13.6