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. -- / Alexander Bokovoy
>From 93a84dff75560e297e775b838fc12330ebd218e5 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/2] 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..4fd7f841a6cbb04e49eba8a3ae755a688924c010 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_params = [] 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