From 68d5fe1ec7d785f127b3513f84cc632cdb1f9167 Mon Sep 17 00:00:00 2001 From: Alexander Bokovoy Date: Fri, 13 Jul 2012 18:12:48 +0300 Subject: 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 --- ipaserver/install/adtrustinstance.py | 21 ++++--- ipaserver/install/bindinstance.py | 2 +- ipaserver/install/cainstance.py | 3 +- ipaserver/install/dsinstance.py | 2 +- ipaserver/install/krbinstance.py | 2 +- ipaserver/install/service.py | 114 +++++++++++++++++++++++------------ 6 files changed, 89 insertions(+), 55 deletions(-) (limited to 'ipaserver') diff --git a/ipaserver/install/adtrustinstance.py b/ipaserver/install/adtrustinstance.py index 20feec4df..9dcbec2d6 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/bindinstance.py b/ipaserver/install/bindinstance.py index c348cdbb2..f320202ea 100644 --- a/ipaserver/install/bindinstance.py +++ b/ipaserver/install/bindinstance.py @@ -448,7 +448,7 @@ class DnsBackup(object): class BindInstance(service.Service): def __init__(self, fstore=None, dm_password=None): - service.Service.__init__(self, "named", dm_password=dm_password) + service.Service.__init__(self, "named", dm_password=dm_password, ldapi=False, autobind=service.DISABLED) self.dns_backup = DnsBackup(self) self.named_user = None self.domain = None diff --git a/ipaserver/install/cainstance.py b/ipaserver/install/cainstance.py index 2644689a0..dc4374cce 100644 --- a/ipaserver/install/cainstance.py +++ b/ipaserver/install/cainstance.py @@ -225,10 +225,9 @@ def get_outputList(data): class CADSInstance(service.Service): def __init__(self, host_name=None, realm_name=None, domain_name=None, dm_password=None): - service.Service.__init__(self, "pkids") + service.Service.__init__(self, "pkids", dm_password=dm_password, ldapi=False, autobind=service.DISABLED) self.serverid = "PKI-IPA" self.realm_name = realm_name - self.dm_password = dm_password self.sub_dict = None self.domain = domain_name self.fqdn = host_name diff --git a/ipaserver/install/dsinstance.py b/ipaserver/install/dsinstance.py index 25c449a6e..9f3ae7252 100644 --- a/ipaserver/install/dsinstance.py +++ b/ipaserver/install/dsinstance.py @@ -160,7 +160,7 @@ info: IPA V2.0 class DsInstance(service.Service): def __init__(self, realm_name=None, domain_name=None, dm_password=None, fstore=None): - service.Service.__init__(self, "dirsrv", dm_password=dm_password) + service.Service.__init__(self, "dirsrv", dm_password=dm_password, ldapi=False, autobind=service.DISABLED) self.realm_name = realm_name self.sub_dict = None self.domain = domain_name diff --git a/ipaserver/install/krbinstance.py b/ipaserver/install/krbinstance.py index 2faf8e196..8cc50fba4 100644 --- a/ipaserver/install/krbinstance.py +++ b/ipaserver/install/krbinstance.py @@ -178,7 +178,7 @@ class KrbInstance(service.Service): self.start_creation("Configuring Kerberos KDC", 30) self.kpasswd = KpasswdInstance() - self.kpasswd.create_instance('KPASSWD', self.fqdn, self.admin_password, self.suffix) + self.kpasswd.create_instance('KPASSWD', self.fqdn, self.admin_password, self.suffix, realm=self.realm) def create_replica(self, realm_name, master_fqdn, host_name, diff --git a/ipaserver/install/service.py b/ipaserver/install/service.py index 5c2699e3f..198cb3870 100644 --- a/ipaserver/install/service.py +++ b/ipaserver/install/service.py @@ -35,6 +35,11 @@ from ipapython.ipa_log_manager import * CACERT = "/etc/ipa/ca.crt" +# Autobind modes +AUTO = 1 +ENABLED = 2 +DISABLED = 3 + # The service name as stored in cn=masters,cn=ipa,cn=etc. In the tuple # the first value is the *nix service name, the second the start order. SERVICE_LIST = { @@ -55,13 +60,14 @@ 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, autobind=AUTO): self.service_name = service_name self.service = ipaservices.service(service_name) self.steps = [] self.output_fd = sys.stdout self.dm_password = dm_password self.ldapi = ldapi + self.autobind = autobind self.fqdn = socket.gethostname() self.admin_conn = None @@ -77,12 +83,44 @@ 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: - self.admin_conn = self.__get_conn(self.fqdn, self.dm_password) + # If DM password is provided, we use it + # If autobind was requested, attempt autobind when root and ldapi + # If autobind was disabled or not succeeded, go with GSSAPI + # LDAPI can be used with either autobind or GSSAPI + # LDAPI requires realm to be set + try: + if self.ldapi: + if not self.realm: + raise errors.NotFound(reason="realm is missing for %s" % (self)) + conn = ipaldap.IPAdmin(ldapi=self.ldapi, realm=self.realm) + else: + conn = ipaldap.IPAdmin(self.fqdn, port=389) + if self.dm_password: + conn.do_simple_bind(bindpw=self.dm_password) + elif self.autobind in [AUTO, ENABLED]: + if os.getegid() == 0 and self.ldapi: + try: + # autobind + pw_name = pwd.getpwuid(os.geteuid()).pw_name + conn.do_external_bind(pw_name) + except errors.NotFound, e: + if self.autobind == AUTO: + # Fall back + conn.do_sasl_gssapi_bind() + else: + # autobind was required and failed, raise + # exception that it failed + raise e + else: + conn.do_sasl_gssapi_bind() + else: + conn.do_sasl_gssapi_bind() + except Exception, e: + root_logger.debug("Could not connect to the Directory Server on %s: %s" % (self.fqdn, str(e))) + raise e + + self.admin_conn = conn + def ldap_disconnect(self): self.admin_conn.unbind() @@ -93,7 +131,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 +144,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 +228,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)] @@ -268,33 +326,6 @@ class Service(object): self.steps = [] - 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. - 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: - try: - # autobind - pw_name = pwd.getpwuid(os.geteuid()).pw_name - conn.do_external_bind(pw_name) - except errors.NotFound: - # Fall back - conn.do_sasl_gssapi_bind() - else: - conn.do_sasl_gssapi_bind() - except Exception, e: - root_logger.debug("Could not connect to the Directory Server on %s: %s" % (fqdn, str(e))) - raise e - - return conn - def ldap_enable(self, name, fqdn, dm_password, ldap_suffix): self.disable() if not self.admin_conn: @@ -318,11 +349,14 @@ class Service(object): raise e class SimpleServiceInstance(Service): - def create_instance(self, gensvc_name=None, fqdn=None, dm_password=None, ldap_suffix=None): + def create_instance(self, gensvc_name=None, fqdn=None, dm_password=None, ldap_suffix=None, realm=None): self.gensvc_name = gensvc_name self.fqdn = fqdn self.dm_password = dm_password self.suffix = ldap_suffix + self.realm = realm + if not realm: + self.ldapi = False self.step("starting %s " % self.service_name, self.__start) self.step("configuring %s to start on boot" % self.service_name, self.__enable) -- cgit