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).

--
/ Alexander Bokovoy
>From 46391aa5953426d763c0c1b627c8abbf80d6fec2 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 
ipa-adtrust-install

https://fedorahosted.org/freeipa/ticket/2815
---
 install/tools/ipa-adtrust-install       |   42 +++++++++++++++++++++--------
 install/tools/man/ipa-adtrust-install.1 |    3 ---
 ipaserver/install/adtrustinstance.py    |   21 ++++++++-------
 ipaserver/install/service.py            |   44 ++++++++++++++++++++++---------
 4 files changed, 74 insertions(+), 36 deletions(-)

diff --git a/install/tools/ipa-adtrust-install 
b/install/tools/ipa-adtrust-install
index 
6678018e6346d75d5042894cfb833d38079d3f21..f367d5b2b516bd411bce9275ff299eb3ffdf6bf9
 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",
@@ -86,6 +84,30 @@ def read_netbios_name(netbios_default):
 
     return netbios_name
 
+def ensure_kerberos_admin_rights(api):
+    try:
+        ctx = krbV.default_context()
+        ccache = ctx.default_ccache()
+        principal = ccache.principal()
+        api.Backend.ldap2.connect(ccache.name)
+        user = api.Command.user_show(unicode(principal[0]))['result']
+        group = api.Command.group_show(u'admins')['result']
+        api.Backend.ldap2.disconnect()
+        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 Exception, e:
+        error_messages = dict(
+           ACIError = "Outdated Kerberos credentials. Use kdestroy and kinit 
to update your ticket",
+           Krb5Error = "Must have Kerberos credentials to setup AD trusts on 
server",
+           RequirementError = "Must have administrative privileges to setup AD 
trusts on server"
+        )
+        name = type(e).__name__
+        if name in error_messages:
+            sys.exit(error_messages[name])
+        else:
+            sys.exit("Unrecognized error during check of admin rights: %s\n%s" 
% (name, str(e)))
+
 def main():
     safe_options, options = parse_options()
 
@@ -128,6 +150,8 @@ def main():
     api.bootstrap(**cfg)
     api.finalize()
 
+    ensure_kerberos_admin_rights(api)
+
     if adtrustinstance.ipa_smb_conf_exists():
         if not options.unattended:
             while True:
@@ -194,9 +218,8 @@ 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)
+    smb = adtrustinstance.ADTRUSTInstance(fstore)
+    smb.realm = api.env.realm
 
     # try the connection
     try:
@@ -205,12 +228,9 @@ def main():
     except ldap.INVALID_CREDENTIALS, e:
         sys.exit("Password is not valid!")
 
-    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)
+    # 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)
 
     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 
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..56bd4810d873353378c8aa5ca282df13709d0e07
 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,13 @@ 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')
+        # Always connect with LDAPI when realm is known
+        # the code in Service class always connects to the LDAP on the same
+        # machine so we can use LDAPI safely if realm is configured
+        if self.realm:
             self.admin_conn = self.__get_conn(None, None, ldapi=True, 
realm=self.realm)
         else:
-            self.admin_conn = self.__get_conn(self.fqdn, self.dm_password)
+            self.admin_conn = self.__get_conn(self.fqdn, self.dm_password, 
ldapi=False)
 
     def ldap_disconnect(self):
         self.admin_conn.unbind()
@@ -93,7 +94,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 +107,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 +191,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 largerly
+        # 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)]
@@ -272,14 +293,13 @@ class Service(object):
         # If we are passed a password we'll use it as the DM password
         # otherwise we'll do a GSSAPI bind.
         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.10.4

_______________________________________________
Freeipa-devel mailing list
Freeipa-devel@redhat.com
https://www.redhat.com/mailman/listinfo/freeipa-devel

Reply via email to