mirror of
https://salsa.debian.org/freeipa-team/freeipa.git
synced 2025-02-25 18:55:28 -06:00
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:
committed by
Martin Kosek
parent
16ca564b10
commit
68d5fe1ec7
@@ -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')
|
||||
|
@@ -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
|
||||
|
@@ -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)
|
||||
|
@@ -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
|
||||
|
@@ -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
|
||||
|
@@ -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
|
||||
|
@@ -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,
|
||||
|
@@ -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 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 RuntimeError('realm must be set to use ldapi connection')
|
||||
self.admin_conn = self.__get_conn(None, None, ldapi=True, realm=self.realm)
|
||||
raise errors.NotFound(reason="realm is missing for %s" % (self))
|
||||
conn = ipaldap.IPAdmin(ldapi=self.ldapi, realm=self.realm)
|
||||
else:
|
||||
self.admin_conn = self.__get_conn(self.fqdn, self.dm_password)
|
||||
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:
|
||||
# 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,7 +228,18 @@ class Service(object):
|
||||
This server cert should be in DER format.
|
||||
"""
|
||||
|
||||
if not self.admin_conn:
|
||||
# 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)
|
||||
@@ -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)
|
||||
|
Reference in New Issue
Block a user