[Freeipa-devel] [PATCH] 0065 Ensure ipa-adtrust-install is run with administrator privileges and Kerberos ticket

Alexander Bokovoy abokovoy at redhat.com
Mon Jul 30 11:34:51 UTC 2012


On Fri, 27 Jul 2012, Rob Crittenden wrote:
>Alexander Bokovoy wrote:
>>On Thu, 26 Jul 2012, Alexander Bokovoy wrote:
>>>Hi,
>>>
>>>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
>>>ipa-adtrust-install
>>>
>>>https://fedorahosted.org/freeipa/ticket/2815
>>>
>>>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
>>>configuration.
>>>
>>>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
>>>future.
>>>
>>>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
>>case).
>
>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.
Fixed.


>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
self.dm_password.

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

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
-------------- next part --------------
>From d7fea3f6f50218821560ea739a26030f3a76640f Mon Sep 17 00:00:00 2001
From: Alexander Bokovoy <abokovoy at 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 ipa-adtrust-install

https://fedorahosted.org/freeipa/ticket/2815
---
 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 b/install/tools/ipa-adtrust-install
index 6678018e6346d75d5042894cfb833d38079d3f21..f1cff30bd439ee07226034b3e49ae17cc59ce6b0 100755
--- 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 options.netbios_name):
         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
     try:
-        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", bind_pw=smb.dm_password)
-    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 server")
+    except Exception, e:
+           sys.exit("Unrecognized error during check of admin rights: %s" % (str(e)))
 
+    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,
               options.no_msdcs)
diff --git a/install/tools/man/ipa-adtrust-install.1 b/install/tools/man/ipa-adtrust-install.1
index b61da19088b40d6a9e53784f9a061913ecda4321..22337c3df8827670657bf405b6c49ba2f8624d6d 100644
--- 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.
 .SH "OPTIONS"
 .TP
-\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
-.TP
 \fB\-d\fR, \fB\-\-debug\fR
 Enable debug logging when more verbose output is needed
 .TP
diff --git a/ipaserver/install/adtrustinstance.py b/ipaserver/install/adtrustinstance.py
index 20feec4df309b5793aa1c29fdf18bc5bfe180943..9dcbec2d61d935f90e74cc65b30a0f1d0c0f9d2a 100644
--- 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
         try:
             self.ldap_enable('ADTRUST', self.fqdn, self.dm_password, \
                              self.suffix)
@@ -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', self.cifs_principal.lower()),
                                  api.env.container_service,
                                  self.suffix))
@@ -522,11 +523,11 @@ class ADTRUSTInstance(service.Service):
                              "range.\nAdd local ID range manually and try " \
                              "again!")
 
-        entry = ipaldap.Entry(str(DN(('cn', ('%s_id_range' % self.realm_name)),
+        entry = ipaldap.Entry(str(DN(('cn', ('%s_id_range' % self.realm)),
                                      api.env.container_ranges,
                                      self.suffix)))
         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))
         self.admin_conn.addEntry(entry)
diff --git a/ipaserver/install/service.py b/ipaserver/install/service.py
index 5c2699e3fa4c115c972528d4c2cc6aa170711837..f08fd47306c301d67483959034c163accd6f3b6e 100644
--- 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, ldapi=False):
+    def __init__(self, service_name, sstore=None, dm_password=None, ldapi=True):
         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, realm=self.realm)
-        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, realm=self.realm)
+        else:
+            self.admin_conn = self.__get_conn(self.fqdn, None, ldapi=self.ldapi, realm=self.realm)
 
     def ldap_disconnect(self):
         self.admin_conn.unbind()
@@ -93,7 +97,6 @@ class Service(object):
         pw_name = None
         fd = None
         path = ipautil.SHARE_DIR + ldif
-        hostname = installutils.get_fqdn()
         nologlist=[]
 
         if sub_dict is not None:
@@ -107,15 +110,25 @@ class Service(object):
             if sub_dict.has_key('RANDOM_PASSWORD'):
                 nologlist.append(sub_dict['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)
             os.close(pw_fd)
             auth_parms = ["-x", "-D", "cn=Directory Manager", "-y", pw_name]
         else:
-            auth_parms = ["-Y", "GSSAPI"]
+            # always try GSSAPI auth when not using DM password or not being root
+            if os.getegid() != 0:
+                auth_parms = ["-Y", "GSSAPI"]
 
-        args = ["/usr/bin/ldapmodify", "-h", hostname, "-v", "-f", path]
         args += auth_parms
 
         try:
@@ -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
         try:
-#            conn = ipaldap.IPAdmin(fqdn, port=636, cacert=CACERT)
             if ldapi:
                 conn = ipaldap.IPAdmin(ldapi=ldapi, realm=realm)
             else:
                 conn = ipaldap.IPAdmin(fqdn, port=389)
             if dm_password:
                 conn.do_simple_bind(bindpw=dm_password)
-            elif os.getegid() == 0 and self.ldapi:
+            elif os.getegid() == 0 and ldapi:
                 try:
                     # autobind
                     pw_name = pwd.getpwuid(os.geteuid()).pw_name
-- 
1.7.11.2



More information about the Freeipa-devel mailing list