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.install import adtrustinstance
from ipaserver.install.installutils import *
from ipaserver.install import installutils
from ipaserver.install import service
from ipapython import version
from ipapython import ipautil, sysrestore
from ipalib import api, errors, util
@@ -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",
@@ -98,7 +96,7 @@ def main():
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")
installutils.check_server_configuration()
check_server_configuration()
global fstore
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):
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:
smb.ldap_connect()
smb.ldap_disconnect()
except ldap.INVALID_CREDENTIALS, e:
sys.exit("Password is not valid!")
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")
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:
api.Backend.ldap2.connect(ccache.name)
except errors.ACIError, e:
sys.exit("Outdated Kerberos credentials. Use kdestroy and kinit to update your ticket")
except errors.DatabaseError, e:
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,
netbios_name, options.rid_base, options.secondary_rid_base,
options.no_msdcs)
@@ -250,5 +258,5 @@ information"""
return 0
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')

View File

@@ -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

View File

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

View File

@@ -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

View File

@@ -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

View File

@@ -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

View File

@@ -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,

View File

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