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
This commit is contained in:
Alexander Bokovoy
2012-07-13 18:12:48 +03:00
committed by Martin Kosek
parent 16ca564b10
commit 68d5fe1ec7
8 changed files with 117 additions and 78 deletions

View File

@@ -24,7 +24,7 @@
from ipaserver.plugins.ldap2 import ldap2 from ipaserver.plugins.ldap2 import ldap2
from ipaserver.install import adtrustinstance from ipaserver.install import adtrustinstance
from ipaserver.install.installutils import * from ipaserver.install.installutils import *
from ipaserver.install import installutils from ipaserver.install import service
from ipapython import version from ipapython import version
from ipapython import ipautil, sysrestore from ipapython import ipautil, sysrestore
from ipalib import api, errors, util from ipalib import api, errors, util
@@ -37,8 +37,6 @@ log_file_name = "/var/log/ipaserver-install.log"
def parse_options(): def parse_options():
parser = IPAOptionParser(version=version.VERSION) 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", parser.add_option("-d", "--debug", dest="debug", action="store_true",
default=False, help="print debugging information") default=False, help="print debugging information")
parser.add_option("--ip-address", dest="ip_address", parser.add_option("--ip-address", dest="ip_address",
@@ -98,7 +96,7 @@ def main():
root_logger.debug('%s was invoked with options: %s' % (sys.argv[0], safe_options)) root_logger.debug('%s was invoked with options: %s' % (sys.argv[0], safe_options))
root_logger.debug("missing options might be asked for interactively later\n") root_logger.debug("missing options might be asked for interactively later\n")
installutils.check_server_configuration() check_server_configuration()
global fstore global fstore
fstore = sysrestore.FileStore('/var/lib/ipa/sysrestore') fstore = sysrestore.FileStore('/var/lib/ipa/sysrestore')
@@ -194,24 +192,34 @@ def main():
if not options.unattended and ( not netbios_name or not options.netbios_name): if not options.unattended and ( not netbios_name or not options.netbios_name):
netbios_name = read_netbios_name(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 the connection
try: try:
smb.ldap_connect() ctx = krbV.default_context()
smb.ldap_disconnect() ccache = ctx.default_ccache()
except ldap.INVALID_CREDENTIALS, e: principal = ccache.principal()
sys.exit("Password is not valid!") except krbV.Krb5Error, e:
sys.exit("Must have Kerberos credentials to setup AD trusts on server")
if smb.dm_password: try:
api.Backend.ldap2.connect(bind_dn="cn=Directory Manager", bind_pw=smb.dm_password) api.Backend.ldap2.connect(ccache.name)
else: except errors.ACIError, e:
# See if our LDAP server is up and we can talk to it over GSSAPI sys.exit("Outdated Kerberos credentials. Use kdestroy and kinit to update your ticket")
ccache = krbV.default_context().default_ccache().name except errors.DatabaseError, e:
api.Backend.ldap2.connect(ccache) sys.exit("Cannot connect to the LDAP database. Please check if IPA is running")
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.autobind = service.ENABLED
smb.setup(api.env.host, ip_address, api.env.realm, api.env.domain, smb.setup(api.env.host, ip_address, api.env.realm, api.env.domain,
netbios_name, options.rid_base, options.secondary_rid_base, netbios_name, options.rid_base, options.secondary_rid_base,
options.no_msdcs) options.no_msdcs)
@@ -250,5 +258,5 @@ information"""
return 0 return 0
if __name__ == '__main__': if __name__ == '__main__':
installutils.run_script(main, log_file_name=log_file_name, run_script(main, log_file_name=log_file_name,
operation_name='ipa-adtrust-install') operation_name='ipa-adtrust-install')

View File

@@ -27,9 +27,6 @@ trust to an Active Directory domain. This requires that the IPA server is
already installed and configured. already installed and configured.
.SH "OPTIONS" .SH "OPTIONS"
.TP .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 \fB\-d\fR, \fB\-\-debug\fR
Enable debug logging when more verbose output is needed Enable debug logging when more verbose output is needed
.TP .TP

View File

@@ -96,10 +96,9 @@ class ADTRUSTInstance(service.Service):
OBJC_GROUP = "ipaNTGroupAttrs" OBJC_GROUP = "ipaNTGroupAttrs"
OBJC_DOMAIN = "ipaNTDomainAttrs" OBJC_DOMAIN = "ipaNTDomainAttrs"
def __init__(self, fstore=None, dm_password=None): def __init__(self, fstore=None):
self.fqdn = None self.fqdn = None
self.ip_address = None self.ip_address = None
self.realm_name = None
self.domain_name = None self.domain_name = None
self.netbios_name = None self.netbios_name = None
self.no_msdcs = None self.no_msdcs = None
@@ -118,7 +117,7 @@ class ADTRUSTInstance(service.Service):
self.rid_base = None self.rid_base = None
self.secondary_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: if fstore:
self.fstore = fstore self.fstore = fstore
@@ -436,6 +435,8 @@ class ADTRUSTInstance(service.Service):
# We do not let the system start IPA components on its own, # We do not let the system start IPA components on its own,
# Instead we reply on the IPA init script to start only enabled # Instead we reply on the IPA init script to start only enabled
# components as found in our LDAP configuration tree # 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: try:
self.ldap_enable('ADTRUST', self.fqdn, self.dm_password, \ self.ldap_enable('ADTRUST', self.fqdn, self.dm_password, \
self.suffix) self.suffix)
@@ -449,7 +450,7 @@ class ADTRUSTInstance(service.Service):
root_logger.info("EXTID Service startup entry already exists.") root_logger.info("EXTID Service startup entry already exists.")
def __setup_sub_dict(self): def __setup_sub_dict(self):
self.sub_dict = dict(REALM = self.realm_name, self.sub_dict = dict(REALM = self.realm,
SUFFIX = self.suffix, SUFFIX = self.suffix,
NETBIOS_NAME = self.netbios_name, NETBIOS_NAME = self.netbios_name,
SMB_DN = self.smb_dn, SMB_DN = self.smb_dn,
@@ -460,16 +461,16 @@ class ADTRUSTInstance(service.Service):
rid_base, secondary_rid_base, no_msdcs=False, smbd_user="samba"): rid_base, secondary_rid_base, no_msdcs=False, smbd_user="samba"):
self.fqdn = fqdn self.fqdn = fqdn
self.ip_address = ip_address self.ip_address = ip_address
self.realm_name = realm_name self.realm = realm_name
self.domain_name = domain_name self.domain_name = domain_name
self.netbios_name = netbios_name self.netbios_name = netbios_name
self.rid_base = rid_base self.rid_base = rid_base
self.secondary_rid_base = secondary_rid_base self.secondary_rid_base = secondary_rid_base
self.no_msdcs = no_msdcs self.no_msdcs = no_msdcs
self.smbd_user = smbd_user 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" % \ 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" 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.trust_dn = str(DN(api.env.container_trusts, self.suffix))
self.smb_dom_dn = str(DN(('cn', self.domain_name), self.smb_dom_dn = str(DN(('cn', self.domain_name),
api.env.container_cifsdomains, self.suffix)) 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()), self.cifs_agent = str(DN(('krbprincipalname', self.cifs_principal.lower()),
api.env.container_service, api.env.container_service,
self.suffix)) self.suffix))
@@ -522,11 +523,11 @@ class ADTRUSTInstance(service.Service):
"range.\nAdd local ID range manually and try " \ "range.\nAdd local ID range manually and try " \
"again!") "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, api.env.container_ranges,
self.suffix))) self.suffix)))
entry.setValue('objectclass', 'ipaDomainIDRange') 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('ipaBaseID', str(base_id))
entry.setValue('ipaIDRangeSize', str(id_range_size)) entry.setValue('ipaIDRangeSize', str(id_range_size))
self.admin_conn.addEntry(entry) self.admin_conn.addEntry(entry)

View File

@@ -448,7 +448,7 @@ class DnsBackup(object):
class BindInstance(service.Service): class BindInstance(service.Service):
def __init__(self, fstore=None, dm_password=None): 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.dns_backup = DnsBackup(self)
self.named_user = None self.named_user = None
self.domain = None self.domain = None

View File

@@ -225,10 +225,9 @@ def get_outputList(data):
class CADSInstance(service.Service): class CADSInstance(service.Service):
def __init__(self, host_name=None, realm_name=None, domain_name=None, dm_password=None): 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.serverid = "PKI-IPA"
self.realm_name = realm_name self.realm_name = realm_name
self.dm_password = dm_password
self.sub_dict = None self.sub_dict = None
self.domain = domain_name self.domain = domain_name
self.fqdn = host_name self.fqdn = host_name

View File

@@ -160,7 +160,7 @@ info: IPA V2.0
class DsInstance(service.Service): class DsInstance(service.Service):
def __init__(self, realm_name=None, domain_name=None, dm_password=None, fstore=None): 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.realm_name = realm_name
self.sub_dict = None self.sub_dict = None
self.domain = domain_name self.domain = domain_name

View File

@@ -178,7 +178,7 @@ class KrbInstance(service.Service):
self.start_creation("Configuring Kerberos KDC", 30) self.start_creation("Configuring Kerberos KDC", 30)
self.kpasswd = KpasswdInstance() 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, def create_replica(self, realm_name,
master_fqdn, host_name, master_fqdn, host_name,

View File

@@ -35,6 +35,11 @@ from ipapython.ipa_log_manager import *
CACERT = "/etc/ipa/ca.crt" 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 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. # the first value is the *nix service name, the second the start order.
SERVICE_LIST = { SERVICE_LIST = {
@@ -55,13 +60,14 @@ def print_msg(message, output_fd=sys.stdout):
class Service(object): 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_name = service_name
self.service = ipaservices.service(service_name) self.service = ipaservices.service(service_name)
self.steps = [] self.steps = []
self.output_fd = sys.stdout self.output_fd = sys.stdout
self.dm_password = dm_password self.dm_password = dm_password
self.ldapi = ldapi self.ldapi = ldapi
self.autobind = autobind
self.fqdn = socket.gethostname() self.fqdn = socket.gethostname()
self.admin_conn = None self.admin_conn = None
@@ -77,12 +83,44 @@ class Service(object):
self.dercert = None self.dercert = None
def ldap_connect(self): def ldap_connect(self):
if self.ldapi: # If DM password is provided, we use it
if not self.realm: # If autobind was requested, attempt autobind when root and ldapi
raise RuntimeError('realm must be set to use ldapi connection') # If autobind was disabled or not succeeded, go with GSSAPI
self.admin_conn = self.__get_conn(None, None, ldapi=True, realm=self.realm) # LDAPI can be used with either autobind or GSSAPI
else: # LDAPI requires realm to be set
self.admin_conn = self.__get_conn(self.fqdn, self.dm_password) 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): def ldap_disconnect(self):
self.admin_conn.unbind() self.admin_conn.unbind()
@@ -93,7 +131,6 @@ class Service(object):
pw_name = None pw_name = None
fd = None fd = None
path = ipautil.SHARE_DIR + ldif path = ipautil.SHARE_DIR + ldif
hostname = installutils.get_fqdn()
nologlist=[] nologlist=[]
if sub_dict is not None: if sub_dict is not None:
@@ -107,15 +144,25 @@ class Service(object):
if sub_dict.has_key('RANDOM_PASSWORD'): if sub_dict.has_key('RANDOM_PASSWORD'):
nologlist.append(sub_dict['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: if self.dm_password:
[pw_fd, pw_name] = tempfile.mkstemp() [pw_fd, pw_name] = tempfile.mkstemp()
os.write(pw_fd, self.dm_password) os.write(pw_fd, self.dm_password)
os.close(pw_fd) os.close(pw_fd)
auth_parms = ["-x", "-D", "cn=Directory Manager", "-y", pw_name] auth_parms = ["-x", "-D", "cn=Directory Manager", "-y", pw_name]
else: 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 args += auth_parms
try: try:
@@ -181,8 +228,19 @@ class Service(object):
This server cert should be in DER format. This server cert should be in DER format.
""" """
if not self.admin_conn: # add_cert_to_service() is relatively rare operation
self.ldap_connect() # 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) dn = "krbprincipalname=%s,cn=services,cn=accounts,%s" % (self.principal, self.suffix)
mod = [(ldap.MOD_ADD, 'userCertificate', self.dercert)] mod = [(ldap.MOD_ADD, 'userCertificate', self.dercert)]
@@ -268,33 +326,6 @@ class Service(object):
self.steps = [] 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): def ldap_enable(self, name, fqdn, dm_password, ldap_suffix):
self.disable() self.disable()
if not self.admin_conn: if not self.admin_conn:
@@ -318,11 +349,14 @@ class Service(object):
raise e raise e
class SimpleServiceInstance(Service): 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.gensvc_name = gensvc_name
self.fqdn = fqdn self.fqdn = fqdn
self.dm_password = dm_password self.dm_password = dm_password
self.suffix = ldap_suffix self.suffix = ldap_suffix
self.realm = realm
if not realm:
self.ldapi = False
self.step("starting %s " % self.service_name, self.__start) self.step("starting %s " % self.service_name, self.__start)
self.step("configuring %s to start on boot" % self.service_name, self.__enable) self.step("configuring %s to start on boot" % self.service_name, self.__enable)