On Fri, 27 Jul 2012, Rob Crittenden wrote:
Alexander Bokovoy wrote:
On Thu, 26 Jul 2012, Alexander Bokovoy wrote:

When setting up AD trusts support, ipa-adtrust-install utility
needs to be run as:
 - root, for performing Samba configuration and using LDAPI/autobind
 - kinit-ed IPA admin user, to ensure proper ACIs are granted to
   fetch keytab

As result, we can get rid of Directory Manager credentials in


This ticket also simplifies a bit the way we handle admin connection in
Service class and particulary in Service._ldap_mod() by defaulting to
LDAPI/autobind in case of running as root and to GSSAPI otherwise.
Except few cases in remote replica management (not applicable in
_ldap_mod() case) we always run installation tools as root and can
benefit from using autobind feature. Unfortunately, it is not yet
possible to get away from using DM credentials for all cases as the same
class is used to perform initial directory server instance

One side effect is explicit disconnect and reconnect in
Service.add_cert_to_service() due to way how SimpleLDAPObject class
handles stale connections (no handling at all). I've put some comments
in place so that others would not try to err out optimizing it in

Finally, with next patch series which will introduce syncing ipaNTHash
attribute with RC4 key in existing kerberos credentials, we can remove
requirements to change passwords or re-kinit for majority of trust
cases. This should then conclude our trusts content for beta2 release.

Patch updated, fixed small typo (auth_parms was initialized as
auth_params which led to non-existing auth_parms in ipa-adtrust-install

Nack, a couple of minor issues:

The exception handling is rather unusual in ensure_kerberos_admin_rights(api). I'm not sure if this is any more efficient than a series of excepts...
I've rewrote this code and put it directly in the main.

You don't need to pass in api, it's a global.

It may be safe to see if the user is in the group the way you are doing it, I wonder if it would be clearer to cast those into DN objects.
Not sure if checking DNs would be sustaining in long run. Ideally we
should check ACI here, not just hardcoded group name. I'd like to keep
it explicit with memberof for now because it shows what exactly we want
to check.

In the Service class what is the point of ldapi if it is going to be ignored in the case we know the realm? What if I really, really just want to use a password?
LDAPI bind in IPAAdmin.__local_init() requires that there is realm known.
No realm -- no LDAPI use because we otherwise cannot construct the
socket name. For 'just want to use a password' case you can simply set

However, I've changed the code in Service.ldap_connect() to do

1. if DM password is provided, we'll try to use it
2. Otherwise, if LDAPI is asked for and realm is set, we'll use LDAPI and realm
3. Otherwise (ldapi was False or realm not provided), we'll try to
   connect to fqdn:389 with GSSAPI

I think this covers all cases.

And later where it forces ldapi, it seems better to either commit all the way and drop the ldapi argument or convert it to a better name (like autobind).
ldapi requires realm but can be used with either GSSAPI or autobind.
Calling it autobind isn't really correct as autobind only available on
ldapi under root.

/ Alexander Bokovoy
>From d7fea3f6f50218821560ea739a26030f3a76640f Mon Sep 17 00:00:00 2001
From: Alexander Bokovoy <aboko...@redhat.com>
Date: Fri, 13 Jul 2012 18:12:48 +0300
Subject: [PATCH 2/6] Ensure ipa-adtrust-install is run with Kerberos ticket
 for admin user

When setting up AD trusts support, ipa-adtrust-install utility
needs to be run as:
   - root, for performing Samba configuration and using LDAPI/autobind
   - kinit-ed IPA admin user, to ensure proper ACIs are granted to
     fetch keytab

As result, we can get rid of Directory Manager credentials in 

 install/tools/ipa-adtrust-install       | 37 +++++++++++++----------
 install/tools/man/ipa-adtrust-install.1 |  3 --
 ipaserver/install/adtrustinstance.py    | 21 ++++++-------
 ipaserver/install/service.py            | 53 ++++++++++++++++++++++++---------
 4 files changed, 71 insertions(+), 43 deletions(-)

diff --git a/install/tools/ipa-adtrust-install 
--- a/install/tools/ipa-adtrust-install
+++ b/install/tools/ipa-adtrust-install
@@ -37,8 +37,6 @@ log_file_name = "/var/log/ipaserver-install.log"
 def parse_options():
     parser = IPAOptionParser(version=version.VERSION)
-    parser.add_option("-p", "--ds-password", dest="dm_password",
-                      sensitive=True, help="directory manager password")
     parser.add_option("-d", "--debug", dest="debug", action="store_true",
                       default=False, help="print debugging information")
     parser.add_option("--ip-address", dest="ip_address",
@@ -194,24 +192,31 @@ def main():
     if not options.unattended and ( not netbios_name or not 
         netbios_name = read_netbios_name(netbios_name)
-    dm_password = options.dm_password or read_password("Directory Manager",
-                                             confirm=False, validate=False)
-    smb = adtrustinstance.ADTRUSTInstance(fstore, dm_password)
+    try:
+        ctx = krbV.default_context()
+        ccache = ctx.default_ccache()
+        principal = ccache.principal()
+    except krbV.Krb5Error, e:
+        sys.exit("Must have Kerberos credentials to setup AD trusts on server")
-    # try the connection
-        smb.ldap_connect()
-        smb.ldap_disconnect()
-    except ldap.INVALID_CREDENTIALS, e:
-        sys.exit("Password is not valid!")
+        api.Backend.ldap2.connect(ccache.name)
+    except errors.ACIError, e:
+        sys.exit("Outdated Kerberos credentials. Use kdestroy and kinit to 
update your ticket")
-    if smb.dm_password:
-        api.Backend.ldap2.connect(bind_dn="cn=Directory Manager", 
-    else:
-        # See if our LDAP server is up and we can talk to it over GSSAPI
-        ccache = krbV.default_context().default_ccache().name
-        api.Backend.ldap2.connect(ccache)
+    try:
+        user = api.Command.user_show(unicode(principal[0]))['result']
+        group = api.Command.group_show(u'admins')['result']
+        if not (user['uid'][0] in group['member_user'] and
+                group['cn'][0] in user['memberof_group']):
+            raise errors.RequirementError(name='admins group membership')
+    except errors.RequirementError, e:
+           sys.exit("Must have administrative privileges to setup AD trusts on 
+    except Exception, e:
+           sys.exit("Unrecognized error during check of admin rights: %s" % 
+    smb = adtrustinstance.ADTRUSTInstance(fstore)
+    smb.realm = api.env.realm
     smb.setup(api.env.host, ip_address, api.env.realm, api.env.domain,
               netbios_name, options.rid_base, options.secondary_rid_base,
diff --git a/install/tools/man/ipa-adtrust-install.1 
--- a/install/tools/man/ipa-adtrust-install.1
+++ b/install/tools/man/ipa-adtrust-install.1
@@ -27,9 +27,6 @@ trust to an Active Directory domain. This requires that the 
IPA server is
 already installed and configured.
-\fB\-p\fR \fIDM_PASSWORD\fR, \fB\-\-ds\-password\fR=\fIDM_PASSWORD\fR
-The password to be used by the Directory Server for the Directory Manager user
 \fB\-d\fR, \fB\-\-debug\fR
 Enable debug logging when more verbose output is needed
diff --git a/ipaserver/install/adtrustinstance.py 
--- a/ipaserver/install/adtrustinstance.py
+++ b/ipaserver/install/adtrustinstance.py
@@ -96,10 +96,9 @@ class ADTRUSTInstance(service.Service):
     OBJC_GROUP = "ipaNTGroupAttrs"
     OBJC_DOMAIN = "ipaNTDomainAttrs"
-    def __init__(self, fstore=None, dm_password=None):
+    def __init__(self, fstore=None):
         self.fqdn = None
         self.ip_address = None
-        self.realm_name = None
         self.domain_name = None
         self.netbios_name = None
         self.no_msdcs = None
@@ -118,7 +117,7 @@ class ADTRUSTInstance(service.Service):
         self.rid_base = None
         self.secondary_rid_base = None
-        service.Service.__init__(self, "smb", dm_password=dm_password)
+        service.Service.__init__(self, "smb", dm_password=None, ldapi=True)
         if fstore:
             self.fstore = fstore
@@ -436,6 +435,8 @@ class ADTRUSTInstance(service.Service):
         # We do not let the system start IPA components on its own,
         # Instead we reply on the IPA init script to start only enabled
         # components as found in our LDAP configuration tree
+        # Note that self.dm_password is None for ADTrustInstance because
+        # we ensure to be called as root and using ldapi to use autobind
             self.ldap_enable('ADTRUST', self.fqdn, self.dm_password, \
@@ -449,7 +450,7 @@ class ADTRUSTInstance(service.Service):
             root_logger.info("EXTID Service startup entry already exists.")
     def __setup_sub_dict(self):
-        self.sub_dict = dict(REALM = self.realm_name,
+        self.sub_dict = dict(REALM = self.realm,
                              SUFFIX = self.suffix,
                              NETBIOS_NAME = self.netbios_name,
                              SMB_DN = self.smb_dn,
@@ -460,16 +461,16 @@ class ADTRUSTInstance(service.Service):
               rid_base, secondary_rid_base, no_msdcs=False, smbd_user="samba"):
         self.fqdn = fqdn
         self.ip_address = ip_address
-        self.realm_name = realm_name
+        self.realm = realm_name
         self.domain_name = domain_name
         self.netbios_name = netbios_name
         self.rid_base = rid_base
         self.secondary_rid_base = secondary_rid_base
         self.no_msdcs = no_msdcs
         self.smbd_user = smbd_user
-        self.suffix = ipautil.realm_to_suffix(self.realm_name)
+        self.suffix = ipautil.realm_to_suffix(self.realm)
         self.ldapi_socket = "%%2fvar%%2frun%%2fslapd-%s.socket" % \
-                            realm_to_serverid(self.realm_name)
+                            realm_to_serverid(self.realm)
         self.smb_conf = "/etc/samba/smb.conf"
@@ -479,7 +480,7 @@ class ADTRUSTInstance(service.Service):
         self.trust_dn = str(DN(api.env.container_trusts, self.suffix))
         self.smb_dom_dn = str(DN(('cn', self.domain_name),
                                  api.env.container_cifsdomains, self.suffix))
-        self.cifs_principal = "cifs/" + self.fqdn + "@" + self.realm_name
+        self.cifs_principal = "cifs/" + self.fqdn + "@" + self.realm
         self.cifs_agent = str(DN(('krbprincipalname', 
@@ -522,11 +523,11 @@ class ADTRUSTInstance(service.Service):
                              "range.\nAdd local ID range manually and try " \
-        entry = ipaldap.Entry(str(DN(('cn', ('%s_id_range' % self.realm_name)),
+        entry = ipaldap.Entry(str(DN(('cn', ('%s_id_range' % self.realm)),
         entry.setValue('objectclass', 'ipaDomainIDRange')
-        entry.setValue('cn', ('%s_id_range' % self.realm_name))
+        entry.setValue('cn', ('%s_id_range' % self.realm))
         entry.setValue('ipaBaseID', str(base_id))
         entry.setValue('ipaIDRangeSize', str(id_range_size))
diff --git a/ipaserver/install/service.py b/ipaserver/install/service.py
--- a/ipaserver/install/service.py
+++ b/ipaserver/install/service.py
@@ -55,7 +55,7 @@ def print_msg(message, output_fd=sys.stdout):
 class Service(object):
-    def __init__(self, service_name, sstore=None, dm_password=None, 
+    def __init__(self, service_name, sstore=None, dm_password=None, 
         self.service_name = service_name
         self.service = ipaservices.service(service_name)
         self.steps = []
@@ -77,12 +77,16 @@ class Service(object):
         self.dercert = None
     def ldap_connect(self):
-        if self.ldapi:
-            if not self.realm:
-                raise RuntimeError('realm must be set to use ldapi connection')
-            self.admin_conn = self.__get_conn(None, None, ldapi=True, 
-        else:
+        # If DM password is provided, we use it
+        # Otherwise, we attempt LDAPI connection if realm is available and
+        # ldapi was asked for
+        # Finally, we attempt to connect to self.fqdn with GSSAPI
+        if self.dm_password:
             self.admin_conn = self.__get_conn(self.fqdn, self.dm_password)
+        elif self.ldapi and self.realm:
+            self.admin_conn = self.__get_conn(None, None, ldapi=True, 
+        else:
+            self.admin_conn = self.__get_conn(self.fqdn, None, 
ldapi=self.ldapi, realm=self.realm)
     def ldap_disconnect(self):
@@ -93,7 +97,6 @@ class Service(object):
         pw_name = None
         fd = None
         path = ipautil.SHARE_DIR + ldif
-        hostname = installutils.get_fqdn()
         if sub_dict is not None:
@@ -107,15 +110,25 @@ class Service(object):
             if sub_dict.has_key('RANDOM_PASSWORD'):
+        args = ["/usr/bin/ldapmodify", "-v", "-f", path]
+        # As we always connect to the local host,
+        # use URI of admin connection
+        if not self.admin_conn:
+            self.ldap_connect()
+        args += ["-H", self.admin_conn._uri]
+        auth_parms = []
         if self.dm_password:
             [pw_fd, pw_name] = tempfile.mkstemp()
             os.write(pw_fd, self.dm_password)
             auth_parms = ["-x", "-D", "cn=Directory Manager", "-y", pw_name]
-            auth_parms = ["-Y", "GSSAPI"]
+            # always try GSSAPI auth when not using DM password or not being 
+            if os.getegid() != 0:
+                auth_parms = ["-Y", "GSSAPI"]
-        args = ["/usr/bin/ldapmodify", "-h", hostname, "-v", "-f", path]
         args += auth_parms
@@ -181,8 +194,19 @@ class Service(object):
         This server cert should be in DER format.
-        if not self.admin_conn:
-            self.ldap_connect()
+        # add_cert_to_service() is relatively rare operation
+        # we actually call it twice during ipa-server-install, for different
+        # instances: ds and cs. Unfortunately, it may happen that admin
+        # connection was created well before add_cert_to_service() is called
+        # If there are other operations in between, it will become stale and
+        # since we are using SimpleLDAPObject, not ReconnectLDAPObject, the
+        # action will fail. Thus, explicitly disconnect and connect again.
+        # Using ReconnectLDAPObject instead of SimpleLDAPObject was considered
+        # but consequences for other parts of the framework are largely
+        # unknown.
+        if self.admin_conn:
+            self.ldap_disconnect()
+        self.ldap_connect()
         dn = "krbprincipalname=%s,cn=services,cn=accounts,%s" % 
(self.principal, self.suffix)
         mod = [(ldap.MOD_ADD, 'userCertificate', self.dercert)]
@@ -270,16 +294,17 @@ class Service(object):
     def __get_conn(self, fqdn, dm_password, ldapi=False, realm=None):
         # If we are passed a password we'll use it as the DM password
-        # otherwise we'll do a GSSAPI bind.
+        # otherwise we'll do a GSSAPI bind unless we are using LDAPI under
+        # root, in which case we'll try autobind and revert to GSSAPI if
+        # that attempt has failed
-#            conn = ipaldap.IPAdmin(fqdn, port=636, cacert=CACERT)
             if ldapi:
                 conn = ipaldap.IPAdmin(ldapi=ldapi, realm=realm)
                 conn = ipaldap.IPAdmin(fqdn, port=389)
             if dm_password:
-            elif os.getegid() == 0 and self.ldapi:
+            elif os.getegid() == 0 and ldapi:
                     # autobind
                     pw_name = pwd.getpwuid(os.geteuid()).pw_name

Freeipa-devel mailing list

Reply via email to