mirror of
https://salsa.debian.org/freeipa-team/freeipa.git
synced 2025-02-25 18:55:28 -06:00
* Check remote LDAP server to see if it is a V2 server * Replace numeric return values with alphanumeric constants * Display the error message from the ipa-enrollment extended op * Remove generic join failed error message when XML-RPC fails * Don't display Certificate subject base when enrollment fails * Return proper error message when LDAP bind fails https://fedorahosted.org/freeipa/ticket/1417
1024 lines
40 KiB
Python
Executable File
1024 lines
40 KiB
Python
Executable File
#! /usr/bin/python -E
|
|
# Authors: Simo Sorce <ssorce@redhat.com>
|
|
# Karl MacMillan <kmacmillan@mentalrootkit.com>
|
|
#
|
|
# Copyright (C) 2007 Red Hat
|
|
# see file 'COPYING' for use and warranty information
|
|
#
|
|
# This program is free software; you can redistribute it and/or modify
|
|
# it under the terms of the GNU General Public License as published by
|
|
# the Free Software Foundation, either version 3 of the License, or
|
|
# (at your option) any later version.
|
|
#
|
|
# This program is distributed in the hope that it will be useful,
|
|
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
# GNU General Public License for more details.
|
|
#
|
|
# You should have received a copy of the GNU General Public License
|
|
# along with this program. If not, see <http://www.gnu.org/licenses/>.
|
|
#
|
|
|
|
try:
|
|
import sys
|
|
|
|
import os
|
|
import time
|
|
import socket
|
|
import logging
|
|
import tempfile
|
|
import getpass
|
|
from ipaclient import ipadiscovery
|
|
import ipaclient.ipachangeconf
|
|
import ipaclient.ntpconf
|
|
from ipapython.ipautil import run, user_input, CalledProcessError, file_exists
|
|
from ipapython import ipautil
|
|
from ipapython import dnsclient
|
|
from ipapython import sysrestore
|
|
from ipapython import version
|
|
from ipapython import certmonger
|
|
from ipapython.config import IPAOptionParser
|
|
import SSSDConfig
|
|
from ConfigParser import RawConfigParser
|
|
from optparse import SUPPRESS_HELP
|
|
except ImportError:
|
|
print >> sys.stderr, """\
|
|
There was a problem importing one of the required Python modules. The
|
|
error was:
|
|
|
|
%s
|
|
""" % sys.exc_value
|
|
sys.exit(1)
|
|
|
|
client_nss_nickname_format = 'IPA Machine Certificate - %s'
|
|
|
|
def parse_options():
|
|
parser = IPAOptionParser(version=version.VERSION)
|
|
parser.add_option("--domain", dest="domain", help="domain name")
|
|
parser.add_option("--server", dest="server", help="IPA server")
|
|
parser.add_option("--realm", dest="realm_name", help="realm name")
|
|
parser.add_option("-f", "--force", dest="force", action="store_true",
|
|
default=False, help="force setting of LDAP/Kerberos conf")
|
|
parser.add_option("-d", "--debug", dest="debug", action="store_true",
|
|
default=False, help="print debugging information")
|
|
parser.add_option("-U", "--unattended", dest="unattended",
|
|
action="store_true",
|
|
help="unattended installation never prompts the user")
|
|
parser.add_option("--ntp-server", dest="ntp_server", help="ntp server to use")
|
|
parser.add_option("-S", "--no-sssd", action="store_false",
|
|
help="Do not configure the client to use SSSD for authentication", default=True, dest="sssd")
|
|
parser.add_option("-N", "--no-ntp", action="store_false",
|
|
help="do not configure ntp", default=True, dest="conf_ntp")
|
|
parser.add_option("-w", "--password", dest="password", sensitive=True,
|
|
help="password to join the IPA realm (assumes bulk password unless principal is also set)"),
|
|
parser.add_option("-W", dest="prompt_password", action="store_true",
|
|
default=False,
|
|
help="Prompt for a password to join the IPA realm"),
|
|
parser.add_option("-p", "--principal", dest="principal",
|
|
help="principal to use to join the IPA realm"),
|
|
# --on-master is used in ipa-server-install and ipa-replica-install
|
|
# only, it isn't meant to be used on clients.
|
|
parser.add_option("--on-master", dest="on_master", action="store_true",
|
|
help=SUPPRESS_HELP, default=False)
|
|
parser.add_option("--permit", dest="permit", action="store_true",
|
|
help="disable access rules by default, permit all access.", default=False)
|
|
parser.add_option("--mkhomedir", dest="mkhomedir", action="store_true",
|
|
help="create home directories for users on their first login", default=False)
|
|
parser.add_option("", "--uninstall", dest="uninstall", action="store_true",
|
|
default=False, help="uninstall an existing installation")
|
|
parser.add_option("", "--hostname", dest="hostname",
|
|
help="The hostname of this server (FQDN). By default of nodename from uname(2) is used.")
|
|
parser.add_option("", "--enable-dns-updates", dest="dns_updates", action="store_true", default=False,
|
|
help="Configures the machine to attempt dns updates when the ip address changes.")
|
|
parser.add_option("--no-krb5-offline-passwords", dest="krb5_offline_passwords", action="store_false",
|
|
help="Configure SSSD not to store user password when the server is offline", default=True)
|
|
|
|
options, args = parser.parse_args()
|
|
safe_opts = parser.get_safe_opts(options)
|
|
|
|
if (options.server and not options.domain):
|
|
parser.error("--server cannot be used without providing --domain")
|
|
|
|
return safe_opts, options
|
|
|
|
def logging_setup(options):
|
|
# Always log everything (i.e., DEBUG) to the log
|
|
# file.
|
|
|
|
log_file = "/var/log/ipaclient-install.log"
|
|
if options.uninstall:
|
|
log_file = "/var/log/ipaclient-uninstall.log"
|
|
|
|
old_umask = os.umask(077)
|
|
logging.basicConfig(level=logging.DEBUG,
|
|
format='%(asctime)s %(levelname)s %(message)s',
|
|
filename=log_file,
|
|
filemode='w')
|
|
os.umask(old_umask)
|
|
|
|
console = logging.StreamHandler()
|
|
# If the debug option is set, also log debug messages to the console
|
|
if options.debug:
|
|
console.setLevel(logging.DEBUG)
|
|
else:
|
|
# Otherwise, log critical and error messages
|
|
console.setLevel(logging.ERROR)
|
|
formatter = logging.Formatter('%(name)-12s: %(levelname)-8s %(message)s')
|
|
console.setFormatter(formatter)
|
|
logging.getLogger('').addHandler(console)
|
|
|
|
def nickname_exists(nickname):
|
|
(sout, serr, returncode) = run(["/usr/bin/certutil", "-L", "-d", "/etc/pki/nssdb", "-n", nickname], raiseonerr=False)
|
|
|
|
if returncode == 0:
|
|
return True
|
|
else:
|
|
return False
|
|
|
|
def uninstall(options, env):
|
|
|
|
if not fstore.has_files():
|
|
print "IPA client is not configured on this system."
|
|
return 2
|
|
|
|
server_fstore = sysrestore.FileStore('/var/lib/ipa/sysrestore')
|
|
if server_fstore.has_files() and not options.on_master:
|
|
print "IPA client is configured as a part of IPA server on this system."
|
|
print "Please refer to ipa-server-install for uninstallation."
|
|
return 2
|
|
|
|
sssdconfig = SSSDConfig.SSSDConfig()
|
|
sssdconfig.import_config()
|
|
domains = sssdconfig.list_active_domains()
|
|
|
|
hostname = None
|
|
for name in domains:
|
|
domain = sssdconfig.get_domain(name)
|
|
try:
|
|
provider = domain.get_option('id_provider')
|
|
except SSSDConfig.NoOptionError:
|
|
continue
|
|
if provider == "ipa":
|
|
try:
|
|
hostname = domain.get_option('ipa_hostname')
|
|
except SSSDConfig.NoOptionError:
|
|
continue
|
|
|
|
if hostname is None:
|
|
hostname = socket.getfqdn()
|
|
|
|
client_nss_nickname = client_nss_nickname_format % hostname
|
|
|
|
# Remove our host cert and CA cert
|
|
if nickname_exists("IPA CA"):
|
|
try:
|
|
run(["/usr/bin/certutil", "-D", "-d", "/etc/pki/nssdb", "-n", "IPA CA"])
|
|
except Exception, e:
|
|
print "Failed to remove IPA CA from /etc/pki/nssdb: %s" % str(e)
|
|
|
|
# Always start certmonger. We can't untrack something if it isn't
|
|
# running
|
|
try:
|
|
ipautil.service_start('certmonger')
|
|
except Exception, e:
|
|
logging.error("certmonger failed to start: %s" % str(e))
|
|
|
|
try:
|
|
certmonger.stop_tracking('/etc/pki/nssdb', nickname=client_nss_nickname)
|
|
except (CalledProcessError, RuntimeError), e:
|
|
logging.error("certmonger failed to stop tracking certificate: %s" % str(e))
|
|
|
|
if nickname_exists(client_nss_nickname):
|
|
try:
|
|
run(["/usr/bin/certutil", "-D", "-d", "/etc/pki/nssdb", "-n", client_nss_nickname])
|
|
except Exception, e:
|
|
print "Failed to remove %s from /etc/pki/nssdb: %s" % (client_nss_nickname, str(e))
|
|
|
|
try:
|
|
ipautil.service_stop('certmonger')
|
|
except Exception, e:
|
|
logging.error("certmonger failed to stop: %s" % str(e))
|
|
|
|
# Remove any special principal names we added to the IPA CA helper
|
|
certmonger.remove_principal_from_cas()
|
|
|
|
try:
|
|
ipautil.chkconfig_off('certmonger')
|
|
except Exception, e:
|
|
print "Failed to disable automatic startup of the certmonger daemon"
|
|
logging.error("Failed to disable automatic startup of the certmonger daemon: %s" % str(e))
|
|
|
|
if not options.on_master:
|
|
print "Unenrolling client from IPA server"
|
|
join_args = ["/usr/sbin/ipa-join", "--unenroll", "-h", hostname]
|
|
(stdout, stderr, returncode) = run(join_args, raiseonerr=False, env=env)
|
|
if returncode != 0:
|
|
print "Unenrolling host failed: %s" % stderr
|
|
|
|
print "Removing Kerberos service principals from /etc/krb5.keytab"
|
|
try:
|
|
parser = RawConfigParser()
|
|
fp = open('/etc/ipa/default.conf', 'r')
|
|
parser.readfp(fp)
|
|
fp.close()
|
|
realm = parser.get('global', 'realm')
|
|
run(["/usr/sbin/ipa-rmkeytab", "-k", "/etc/krb5.keytab", "-r", realm])
|
|
except Exception, e:
|
|
print "Failed to clean up /etc/krb5.keytab"
|
|
logging.error("Failed to remove Kerberos service principals: %s" % str(e))
|
|
|
|
print "Disabling client Kerberos and LDAP configurations"
|
|
try:
|
|
run(["/usr/sbin/authconfig", "--disableldap", "--disablekrb5", "--disablesssd", "--disablesssdauth", "--disablemkhomedir", "--update"])
|
|
except Exception, e:
|
|
print "Failed to remove krb5/LDAP configuration. " +str(e)
|
|
sys.exit(1)
|
|
|
|
print "Restoring client configuration files"
|
|
fstore.restore_all_files()
|
|
|
|
if ipautil.service_is_installed('nscd'):
|
|
try:
|
|
ipautil.service_restart('nscd')
|
|
except:
|
|
print "Failed to restart start the NSCD daemon"
|
|
|
|
try:
|
|
ipautil.chkconfig_on('nscd')
|
|
except:
|
|
print "Failed to configure automatic startup of the NSCD daemon"
|
|
else:
|
|
# this is optional service, just log
|
|
logging.info("NSCD daemon is not installed, skip configuration")
|
|
|
|
if ipautil.service_is_installed('nslcd'):
|
|
try:
|
|
ipautil.service_stop('nslcd')
|
|
except:
|
|
print "Failed to stop the NSLCD daemon"
|
|
|
|
try:
|
|
ipautil.chkconfig_off('nslcd')
|
|
except:
|
|
print "Failed to disable automatic startup of the NSLCD daemon"
|
|
else:
|
|
# this is optional service, just log
|
|
logging.info("NSLCD daemon is not installed, skip configuration")
|
|
|
|
if not options.unattended:
|
|
print "The original nsswitch.conf configuration has been restored."
|
|
print "You may need to restart services or reboot the machine."
|
|
if not options.on_master:
|
|
if user_input("Do you want to reboot the machine?", False):
|
|
try:
|
|
run(["/usr/bin/reboot"])
|
|
except Exception, e:
|
|
print "Reboot command failed to exceute. " + str(e)
|
|
sys.exit(1)
|
|
|
|
# Remove the IPA configuration file
|
|
try:
|
|
os.remove("/etc/ipa/default.conf")
|
|
except:
|
|
pass
|
|
|
|
def configure_ipa_conf(fstore, cli_basedn, cli_realm, cli_domain, cli_server):
|
|
ipaconf = ipaclient.ipachangeconf.IPAChangeConf("IPA Installer")
|
|
ipaconf.setOptionAssignment(" = ")
|
|
ipaconf.setSectionNameDelimiters(("[","]"))
|
|
|
|
opts = [{'name':'comment', 'type':'comment', 'value':'File modified by ipa-client-install'},
|
|
{'name':'empty', 'type':'empty'}]
|
|
|
|
#[global]
|
|
defopts = [{'name':'basedn', 'type':'option', 'value':cli_basedn},
|
|
{'name':'realm', 'type':'option', 'value':cli_realm},
|
|
{'name':'domain', 'type':'option', 'value':cli_domain},
|
|
{'name':'server', 'type':'option', 'value':cli_server},
|
|
{'name':'xmlrpc_uri', 'type':'option', 'value':'https://%s/ipa/xml' % cli_server},
|
|
{'name':'enable_ra', 'type':'option', 'value':'True'}]
|
|
|
|
opts.append({'name':'global', 'type':'section', 'value':defopts})
|
|
opts.append({'name':'empty', 'type':'empty'})
|
|
|
|
fstore.backup_file("/etc/ipa/default.conf")
|
|
ipaconf.newConf("/etc/ipa/default.conf", opts)
|
|
|
|
return 0
|
|
|
|
def configure_ldap_conf(fstore, cli_basedn, cli_realm, cli_domain, cli_server, dnsok, options):
|
|
ldapconf = ipaclient.ipachangeconf.IPAChangeConf("IPA Installer")
|
|
ldapconf.setOptionAssignment(" ")
|
|
|
|
opts = [{'name':'comment', 'type':'comment', 'value':'File modified by ipa-client-install'},
|
|
{'name':'empty', 'type':'empty'},
|
|
{'name':'ldap_version', 'type':'option', 'value':'3'},
|
|
{'name':'base', 'type':'option', 'value':cli_basedn},
|
|
{'name':'empty', 'type':'empty'},
|
|
{'name':'nss_base_passwd', 'type':'option', 'value':'cn=users,cn=accounts,'+cli_basedn+'?sub'},
|
|
{'name':'nss_base_group', 'type':'option', 'value':'cn=groups,cn=accounts,'+cli_basedn+'?sub'},
|
|
{'name':'nss_schema', 'type':'option', 'value':'rfc2307bis'},
|
|
{'name':'nss_map_attribute', 'type':'option', 'value':'uniqueMember member'},
|
|
{'name':'nss_initgroups_ignoreusers', 'type':'option', 'value':'root,dirsrv'},
|
|
{'name':'empty', 'type':'empty'},
|
|
{'name':'nss_reconnect_maxsleeptime', 'type':'option', 'value':'8'},
|
|
{'name':'nss_reconnect_sleeptime', 'type':'option', 'value':'1'},
|
|
{'name':'bind_timelimit', 'type':'option', 'value':'5'},
|
|
{'name':'timelimit', 'type':'option', 'value':'15'},
|
|
{'name':'empty', 'type':'empty'}]
|
|
if not dnsok or options.force or options.on_master:
|
|
if options.on_master:
|
|
opts.append({'name':'uri', 'type':'option', 'value':'ldap://localhost'})
|
|
else:
|
|
opts.append({'name':'uri', 'type':'option', 'value':'ldap://'+cli_server})
|
|
else:
|
|
opts.append({'name':'nss_srv_domain', 'type':'option', 'value':cli_domain})
|
|
|
|
opts.append({'name':'empty', 'type':'empty'})
|
|
|
|
# Depending on the release and distribution this may exist in any
|
|
# number of different file names, update what we find
|
|
for filename in ['/etc/ldap.conf', '/etc/nss_ldap.conf', '/etc/libnss-ldap.conf', '/etc/pam_ldap.conf']:
|
|
if file_exists(filename):
|
|
try:
|
|
fstore.backup_file(filename)
|
|
ldapconf.newConf(filename, opts)
|
|
except Exception, e:
|
|
print "Creation of %s: %s" % (filename, str(e))
|
|
return 1
|
|
|
|
return 0
|
|
|
|
def configure_nslcd_conf(fstore, cli_basedn, cli_realm, cli_domain, cli_server, dnsok, options):
|
|
nslcdconf = ipaclient.ipachangeconf.IPAChangeConf("IPA Installer")
|
|
nslcdconf.setOptionAssignment(" ")
|
|
|
|
opts = [{'name':'comment', 'type':'comment', 'value':'File modified by ipa-client-install'},
|
|
{'name':'empty', 'type':'empty'},
|
|
{'name':'ldap_version', 'type':'option', 'value':'3'},
|
|
{'name':'base', 'type':'option', 'value':cli_basedn},
|
|
{'name':'empty', 'type':'empty'},
|
|
{'name':'base passwd', 'type':'option', 'value':'cn=users,cn=accounts,'+cli_basedn},
|
|
{'name':'base group', 'type':'option', 'value':'cn=groups,cn=accounts,'+cli_basedn},
|
|
{'name':'map group', 'type':'option', 'value':'uniqueMember member'},
|
|
{'name':'timelimit', 'type':'option', 'value':'15'},
|
|
{'name':'empty', 'type':'empty'}]
|
|
if not dnsok or options.force or options.on_master:
|
|
if options.on_master:
|
|
opts.append({'name':'uri', 'type':'option', 'value':'ldap://localhost'})
|
|
else:
|
|
opts.append({'name':'uri', 'type':'option', 'value':'ldap://'+cli_server})
|
|
else:
|
|
opts.append({'name':'uri', 'type':'option', 'value':'DNS'})
|
|
|
|
opts.append({'name':'empty', 'type':'empty'})
|
|
|
|
if file_exists('/etc/nslcd.conf'):
|
|
try:
|
|
fstore.backup_file('/etc/nslcd.conf')
|
|
nslcdconf.newConf('/etc/nslcd.conf', opts)
|
|
except Exception, e:
|
|
print "Creation of %s: %s" % ('/etc/nslcd.conf', str(e))
|
|
return 1
|
|
|
|
if ipautil.service_is_installed('nslcd'):
|
|
try:
|
|
ipautil.service_restart('nslcd')
|
|
except Exception, e:
|
|
logging.error("nslcd failed to restart: %s" % str(e))
|
|
|
|
try:
|
|
ipautil.chkconfig_on('nslcd')
|
|
except Exception, e:
|
|
print "Failed to configure automatic startup of the NSLCD daemon"
|
|
logging.error("Failed to enable automatic startup of the NSLCD daemon: %s" % str(e))
|
|
else:
|
|
logging.debug("NSLCD daemon is not installed, skip configuration")
|
|
|
|
return 0
|
|
|
|
def hardcode_ldap_server(cli_server):
|
|
"""
|
|
DNS Discovery didn't return a valid IPA server, hardcode a value into
|
|
the file instead.
|
|
"""
|
|
if not file_exists('/etc/ldap.conf'):
|
|
return
|
|
|
|
ldapconf = ipaclient.ipachangeconf.IPAChangeConf("IPA Installer")
|
|
ldapconf.setOptionAssignment(" ")
|
|
|
|
opts = [{'name':'uri', 'type':'option', 'action':'set', 'value':'ldap://'+cli_server},
|
|
{'name':'empty', 'type':'empty'}]
|
|
|
|
# Errors raised by this should be caught by the caller
|
|
ldapconf.changeConf("/etc/ldap.conf", opts)
|
|
|
|
return
|
|
|
|
def configure_krb5_conf(fstore, cli_basedn, cli_realm, cli_domain, cli_server, cli_kdc, dnsok, options, filename):
|
|
|
|
krbconf = ipaclient.ipachangeconf.IPAChangeConf("IPA Installer")
|
|
krbconf.setOptionAssignment(" = ")
|
|
krbconf.setSectionNameDelimiters(("[","]"))
|
|
krbconf.setSubSectionDelimiters(("{","}"))
|
|
krbconf.setIndent((""," "," "))
|
|
|
|
opts = [{'name':'comment', 'type':'comment', 'value':'File modified by ipa-client-install'},
|
|
{'name':'empty', 'type':'empty'}]
|
|
|
|
#[libdefaults]
|
|
libopts = [{'name':'default_realm', 'type':'option', 'value':cli_realm}]
|
|
if not dnsok or not cli_kdc or options.force:
|
|
libopts.append({'name':'dns_lookup_realm', 'type':'option', 'value':'false'})
|
|
libopts.append({'name':'dns_lookup_kdc', 'type':'option', 'value':'false'})
|
|
else:
|
|
libopts.append({'name':'dns_lookup_realm', 'type':'option', 'value':'true'})
|
|
libopts.append({'name':'dns_lookup_kdc', 'type':'option', 'value':'true'})
|
|
libopts.append({'name':'rdns', 'type':'option', 'value':'false'})
|
|
libopts.append({'name':'ticket_lifetime', 'type':'option', 'value':'24h'})
|
|
libopts.append({'name':'forwardable', 'type':'option', 'value':'yes'})
|
|
|
|
opts.append({'name':'libdefaults', 'type':'section', 'value':libopts})
|
|
opts.append({'name':'empty', 'type':'empty'})
|
|
|
|
#the following are necessary only if DNS discovery does not work
|
|
if not dnsok or not cli_kdc or options.force:
|
|
#[realms]
|
|
kropts =[{'name':'kdc', 'type':'option', 'value':cli_server+':88'},
|
|
{'name':'admin_server', 'type':'option', 'value':cli_server+':749'},
|
|
{'name':'default_domain', 'type':'option', 'value':cli_domain}]
|
|
else:
|
|
kropts = []
|
|
kropts.append({'name':'pkinit_anchors', 'type':'option', 'value':'FILE:/etc/ipa/ca.crt'})
|
|
ropts = [{'name':cli_realm, 'type':'subsection', 'value':kropts}]
|
|
|
|
opts.append({'name':'realms', 'type':'section', 'value':ropts})
|
|
opts.append({'name':'empty', 'type':'empty'})
|
|
|
|
#[domain_realm]
|
|
dropts = [{'name':'.'+cli_domain, 'type':'option', 'value':cli_realm},
|
|
{'name':cli_domain, 'type':'option', 'value':cli_realm}]
|
|
opts.append({'name':'domain_realm', 'type':'section', 'value':dropts})
|
|
opts.append({'name':'empty', 'type':'empty'})
|
|
|
|
#[appdefaults]
|
|
pamopts = [{'name':'debug', 'type':'option', 'value':'false'},
|
|
{'name':'krb4_convert', 'type':'option', 'value':'false'}]
|
|
appopts = [{'name':'pam', 'type':'subsection', 'value':pamopts}]
|
|
opts.append({'name':'appdefaults', 'type':'section', 'value':appopts})
|
|
|
|
logging.debug("Writing Kerberos configuration to %s:\n%s"
|
|
% (filename, krbconf.dump(opts)))
|
|
|
|
krbconf.newConf(filename, opts);
|
|
|
|
return 0
|
|
|
|
def configure_certmonger(fstore, subject_base, cli_realm, hostname, options):
|
|
started = True
|
|
principal = 'host/%s@%s' % (hostname, cli_realm)
|
|
|
|
# Ensure that certmonger has been started at least once to generate the
|
|
# cas files in /var/lib/certmonger/cas.
|
|
try:
|
|
ipautil.service_restart('certmonger')
|
|
except Exception, e:
|
|
logging.error("certmonger failed to restart: %s" % str(e))
|
|
|
|
if options.hostname:
|
|
# It needs to be stopped if we touch them
|
|
try:
|
|
ipautil.service_stop('certmonger')
|
|
except Exception, e:
|
|
logging.error("certmonger failed to stop: %s" % str(e))
|
|
# If the hostname is explicitly set then we need to tell certmonger
|
|
# which principal name to use when requesting certs.
|
|
certmonger.add_principal_to_cas(principal)
|
|
|
|
try:
|
|
ipautil.service_restart('certmonger')
|
|
except Exception, e:
|
|
print "Failed to start the certmonger daemon"
|
|
print "Automatic certificate management will not be available"
|
|
logging.error("certmonger failed to restart: %s" % str(e))
|
|
started = False
|
|
|
|
try:
|
|
ipautil.chkconfig_on('certmonger')
|
|
except Exception, e:
|
|
print "Failed to configure automatic startup of the certmonger daemon"
|
|
print "Automatic certificate management will not be available"
|
|
logging.error("Failed to disable automatic startup of the certmonger daemon: %s" % str(e))
|
|
|
|
# Request our host cert
|
|
if started:
|
|
client_nss_nickname = client_nss_nickname_format % hostname
|
|
subject = 'CN=%s,%s' % (hostname, subject_base)
|
|
try:
|
|
run(["ipa-getcert", "request", "-d", "/etc/pki/nssdb", "-n", client_nss_nickname, "-N", subject, "-K", principal])
|
|
except:
|
|
print "certmonger request for host certificate failed"
|
|
|
|
def configure_sssd_conf(fstore, cli_realm, cli_domain, cli_server, options):
|
|
sssdconfig = SSSDConfig.SSSDConfig()
|
|
sssdconfig.new_config()
|
|
|
|
domain = sssdconfig.new_domain(cli_domain)
|
|
domain.add_provider('ipa', 'id')
|
|
|
|
if not options.on_master:
|
|
domain.set_option('ipa_server', '_srv_, %s' % cli_server)
|
|
else:
|
|
# the master should only use itself for Kerberos
|
|
domain.set_option('ipa_server', cli_server)
|
|
domain.set_option('ipa_domain', cli_domain)
|
|
if options.hostname:
|
|
domain.set_option('ipa_hostname', options.hostname)
|
|
if cli_domain.lower() != cli_realm.lower():
|
|
domain.set_option('krb5_realm', cli_realm)
|
|
|
|
# Might need this if /bin/hostname doesn't return a FQDN
|
|
#domain.set_option('ipa_hostname', 'client.example.com')
|
|
|
|
domain.add_provider('ipa', 'auth')
|
|
domain.add_provider('ipa', 'chpass')
|
|
if not options.permit:
|
|
domain.add_provider('ipa', 'access')
|
|
else:
|
|
domain.add_provider('permit', 'access')
|
|
|
|
domain.set_option('cache_credentials', True)
|
|
|
|
# SSSD will need TLS for checking if ipaMigrationEnabled attribute is set
|
|
# Note that SSSD will force StartTLS because the channel is later used for
|
|
# authentication as well if password migration is enabled. Thus set the option
|
|
# unconditionally.
|
|
domain.set_option('ldap_tls_cacert', '/etc/ipa/ca.crt')
|
|
|
|
if options.dns_updates:
|
|
domain.set_option('ipa_dyndns_update', True)
|
|
if options.krb5_offline_passwords:
|
|
domain.set_option('krb5_store_password_if_offline', True)
|
|
|
|
domain.set_active(True)
|
|
|
|
sssdconfig.save_domain(domain)
|
|
sssdconfig.write("/etc/sssd/sssd.conf")
|
|
|
|
return 0
|
|
|
|
def resolve_ipaddress(server):
|
|
""" Connect to the server's LDAP port in order to determine what ip
|
|
address this machine uses as "public" ip (relative to the server).
|
|
"""
|
|
|
|
s = socket.socket(socket.AF_INET, socket.SOCK_STREAM, socket.IPPROTO_TCP)
|
|
s.connect((server, 389))
|
|
addr, port = s.getsockname()
|
|
s.close()
|
|
|
|
return addr
|
|
|
|
UPDATE_TEMPLATE_A = """
|
|
zone $ZONE.
|
|
update delete $HOSTNAME. IN A
|
|
send
|
|
update add $HOSTNAME. $TTL IN A $IPADDRESS
|
|
send
|
|
"""
|
|
|
|
UPDATE_TEMPLATE_AAAA = """
|
|
zone $ZONE.
|
|
update delete $HOSTNAME. IN AAAA
|
|
send
|
|
update add $HOSTNAME. $TTL IN AAAA $IPADDRESS
|
|
send
|
|
"""
|
|
|
|
UPDATE_FILE = "/etc/ipa/.dns_update.txt"
|
|
CCACHE_FILE = "/etc/ipa/.dns_ccache"
|
|
|
|
def update_dns(server, hostname):
|
|
|
|
ip = resolve_ipaddress(server)
|
|
princ = 'host/%s' % hostname
|
|
|
|
sub_dict = dict(HOSTNAME=hostname,
|
|
IPADDRESS=ip,
|
|
TTL=1200,
|
|
ZONE='.'.join(hostname.split('.')[1:])
|
|
)
|
|
|
|
template = None
|
|
if len(ip.split('.')) == 4:
|
|
template = UPDATE_TEMPLATE_A
|
|
elif len(ip.split(':')) > 1:
|
|
template = UPDATE_TEMPLATE_AAAA
|
|
|
|
if template is None:
|
|
print >>sys.stderr, "Failed to determine machine's ip address."
|
|
print >>sys.stderr, "Failed to update DNS A record."
|
|
return
|
|
|
|
update_txt = ipautil.template_str(template, sub_dict)
|
|
|
|
logging.debug("Writing nsupdate commands to %s:\n%s"
|
|
% (UPDATE_FILE, update_txt))
|
|
|
|
update_fd = file(UPDATE_FILE, "w")
|
|
update_fd.write(update_txt)
|
|
update_fd.flush()
|
|
update_fd.close()
|
|
|
|
try:
|
|
ipautil.run(['/usr/bin/kinit', '-k', '-t', '/etc/krb5.keytab', princ],
|
|
env={'KRB5CCNAME':CCACHE_FILE})
|
|
except CalledProcessError, e:
|
|
print >>sys.stderr, "Failed to obtain host TGT."
|
|
|
|
try:
|
|
ipautil.run(['/usr/bin/nsupdate', '-g', UPDATE_FILE],
|
|
env={'KRB5CCNAME':CCACHE_FILE})
|
|
print "DNS server record set to: %s -> %s" % (hostname, ip)
|
|
except CalledProcessError, e:
|
|
print >>sys.stderr, "Failed to update DNS A record. (%s)" % str(e)
|
|
|
|
try:
|
|
os.remove(UPDATE_FILE)
|
|
os.remove(CCACHE_FILE)
|
|
except:
|
|
pass
|
|
|
|
def client_dns(server, hostname, dns_updates=False):
|
|
|
|
dns_ok = False
|
|
|
|
# Check if the client has an A record registered in its name.
|
|
rs = dnsclient.query(hostname+".", dnsclient.DNS_C_IN, dnsclient.DNS_T_A)
|
|
if len([ rec for rec in rs if rec.dns_type is not dnsclient.DNS_T_SOA ]) > 0:
|
|
dns_ok = True
|
|
else:
|
|
rs = dnsclient.query(hostname+".", dnsclient.DNS_C_IN, dnsclient.DNS_T_AAAA)
|
|
if len([ rec for rec in rs if rec.dns_type is not dnsclient.DNS_T_SOA ]) > 0:
|
|
dns_ok = True
|
|
else:
|
|
print "Warning: Hostname (%s) not found in DNS" % hostname
|
|
|
|
if dns_updates or not dns_ok:
|
|
update_dns(server, hostname)
|
|
|
|
def main():
|
|
safe_options, options = parse_options()
|
|
logging_setup(options)
|
|
logging.debug('%s was invoked with options: %s' % (sys.argv[0], safe_options))
|
|
logging.debug("missing options might be asked for interactively later\n")
|
|
dnsok = False
|
|
env={"PATH":"/bin:/sbin:/usr/kerberos/bin:/usr/kerberos/sbin:/usr/bin:/usr/sbin"}
|
|
|
|
global fstore
|
|
fstore = sysrestore.FileStore('/var/lib/ipa-client/sysrestore')
|
|
|
|
if options.uninstall:
|
|
return uninstall(options, env)
|
|
|
|
if fstore.has_files():
|
|
sys.exit("IPA client is already configured on this system.\n"
|
|
+ "If you want to reinstall the IPA client please uninstall it first.")
|
|
|
|
cli_domain = None
|
|
cli_server = None
|
|
cli_realm = None
|
|
cli_basedn = None
|
|
subject_base = None
|
|
|
|
if options.unattended and (options.password is None and options.principal is None and options.prompt_password is False) and not options.on_master:
|
|
sys.exit("One of password and principal are required.")
|
|
|
|
if options.hostname:
|
|
hostname = options.hostname
|
|
else:
|
|
hostname = socket.getfqdn()
|
|
if hostname != hostname.lower():
|
|
sys.exit('Invalid hostname \'%s\', must be lower-case.' % hostname)
|
|
|
|
# Create the discovery instance
|
|
ds = ipadiscovery.IPADiscovery()
|
|
|
|
ret = ds.search(domain=options.domain, server=options.server, hostname=hostname)
|
|
|
|
if ret == ipadiscovery.BAD_HOST_CONFIG:
|
|
print >>sys.stderr, "Can't get the fully qualified name of this host"
|
|
print >>sys.stderr, "Please check that the client is properly configured"
|
|
return ret
|
|
if ret == ipadiscovery.NOT_FQDN:
|
|
print >>sys.stderr, "%s is not a fully-qualified hostname" % hostname
|
|
return ret
|
|
if ret == ipadiscovery.NO_LDAP_SERVER or not ds.getDomainName():
|
|
logging.debug("Domain not found")
|
|
if options.domain:
|
|
cli_domain = options.domain
|
|
elif options.unattended:
|
|
print >>sys.stderr, "Unable to discover domain, not provided on command line"
|
|
return ret
|
|
else:
|
|
print "DNS discovery failed to determine your DNS domain"
|
|
cli_domain = user_input("Please provide the domain name of your IPA server (ex: example.com)", allow_empty = False)
|
|
logging.debug("will use domain: %s\n", cli_domain)
|
|
ret = ds.search(domain=cli_domain, server=options.server, hostname=hostname)
|
|
|
|
if not cli_domain:
|
|
if ds.getDomainName():
|
|
cli_domain = ds.getDomainName()
|
|
logging.debug("will use domain: %s\n", cli_domain)
|
|
|
|
if ret == ipadiscovery.NO_LDAP_SERVER or not ds.getServerName():
|
|
logging.debug("IPA Server not found")
|
|
if options.server:
|
|
cli_server = options.server
|
|
elif options.unattended:
|
|
print >>sys.stderr, "Unable to find IPA Server to join"
|
|
return ret
|
|
else:
|
|
print "DNS discovery failed to find the IPA Server"
|
|
cli_server = user_input("Please provide your IPA server name (ex: ipa.example.com)", allow_empty = False)
|
|
logging.debug("will use server: %s\n", cli_server)
|
|
ret = ds.search(domain=cli_domain, server=cli_server, hostname=hostname)
|
|
else:
|
|
dnsok = True
|
|
if not cli_server:
|
|
if ds.getServerName():
|
|
cli_server = ds.getServerName()
|
|
logging.debug("will use server: %s\n", cli_server)
|
|
|
|
if ret == ipadiscovery.NOT_IPA_SERVER:
|
|
print >>sys.stderr, "%s is not an IPA v2 Server." % cli_server
|
|
return ret
|
|
if ret != 0:
|
|
print >>sys.stderr, "Failed to verify that "+cli_server+" is an IPA Server."
|
|
print >>sys.stderr, "This may mean that the remote server is not up or is not reachable"
|
|
print >>sys.stderr, "due to network or firewall settings."
|
|
return ret
|
|
|
|
cli_kdc = ds.getKDCName()
|
|
if dnsok and not cli_kdc:
|
|
print >>sys.stderr, "DNS domain '%s' is not configured for automatic KDC address lookup." % ds.getRealmName().lower()
|
|
print >>sys.stderr, "KDC address will be set to fixed value.\n"
|
|
|
|
if dnsok:
|
|
print "Discovery was successful!"
|
|
elif not options.unattended:
|
|
print "\nThe failure to use DNS to find your IPA server indicates that your"
|
|
print "resolv.conf file is not properly configured.\n"
|
|
print "Autodiscovery of servers for failover cannot work with this configuration.\n"
|
|
print "If you proceed with the installation, services will be configured to always"
|
|
print "access the discovered server for all operation and will not fail over to"
|
|
print "other servers in case of failure.\n"
|
|
if not user_input("Proceed with fixed values and no DNS discovery?", False):
|
|
return ret
|
|
|
|
if options.realm_name and options.realm_name != ds.getRealmName():
|
|
if not options.unattended:
|
|
print >>sys.stderr, "ERROR: The provided realm name: ["+options.realm_name+"] does not match with the discovered one: ["+ds.getRealmName()+"]\n"
|
|
return -3
|
|
|
|
cli_realm = ds.getRealmName()
|
|
logging.debug("will use cli_realm: %s\n", cli_realm)
|
|
cli_basedn = ds.getBaseDN()
|
|
logging.debug("will use cli_basedn: %s\n", cli_basedn)
|
|
subject_base = "O=%s" % ds.getRealmName()
|
|
|
|
print "Hostname: "+hostname
|
|
print "Realm: "+cli_realm
|
|
print "DNS Domain: "+cli_domain
|
|
print "IPA Server: "+cli_server
|
|
print "BaseDN: "+cli_basedn
|
|
|
|
print "\n"
|
|
if not options.unattended and not user_input("Continue to configure the system with these values?", False):
|
|
return 1
|
|
|
|
if not options.unattended:
|
|
if options.principal is None and options.password is None and options.prompt_password is False:
|
|
options.principal = user_input("User authorized to enroll computers", allow_empty=False)
|
|
logging.debug("will use principal: %s\n", options.principal)
|
|
|
|
# Get the CA certificate
|
|
try:
|
|
# Remove anything already there so that wget doesn't use its
|
|
# too-clever renaming feature
|
|
os.remove("/etc/ipa/ca.crt")
|
|
except:
|
|
pass
|
|
|
|
try:
|
|
run(["/usr/bin/wget", "-O", "/etc/ipa/ca.crt", "http://%s/ipa/config/ca.crt" % cli_server])
|
|
except CalledProcessError, e:
|
|
sys.exit('Retrieving CA from %s failed.\n%s' % (cli_server, str(e)))
|
|
|
|
if not options.on_master:
|
|
# First test out the kerberos configuration
|
|
try:
|
|
(krb_fd, krb_name) = tempfile.mkstemp()
|
|
os.close(krb_fd)
|
|
if configure_krb5_conf(fstore, cli_basedn, cli_realm, cli_domain, cli_server, cli_kdc, dnsok, options, krb_name):
|
|
sys.exit("Test kerberos configuration failed")
|
|
env['KRB5_CONFIG'] = krb_name
|
|
join_args = ["/usr/sbin/ipa-join", "-s", cli_server]
|
|
if options.debug:
|
|
join_args.append("-d")
|
|
if options.hostname:
|
|
join_args.append("-h")
|
|
join_args.append(options.hostname)
|
|
if options.principal is not None:
|
|
stdin = None
|
|
principal = options.principal
|
|
if principal.find('@') == -1:
|
|
principal = '%s@%s' % (principal, cli_realm)
|
|
if options.password is not None:
|
|
stdin = options.password
|
|
else:
|
|
if not options.unattended:
|
|
stdin = getpass.getpass("Password for %s: " % principal)
|
|
if not stdin:
|
|
sys.exit("Password must be provided for %s. " %
|
|
principal)
|
|
else:
|
|
if sys.stdin.isatty():
|
|
sys.exit("Password must be provided in non-interactive mode.\nThis can be done via echo password | ipa-client-install ... or\nwith the -w option.")
|
|
else:
|
|
stdin = sys.stdin.readline()
|
|
|
|
(stderr, stdout, returncode) = run(["kinit", principal], raiseonerr=False, stdin=stdin, env=env)
|
|
print ""
|
|
if returncode != 0:
|
|
sys.exit(stdout)
|
|
elif options.password:
|
|
join_args.append("-w")
|
|
join_args.append(options.password)
|
|
elif options.prompt_password:
|
|
if options.unattended:
|
|
sys.exit("Password must be provided in non-interactive mode")
|
|
password = getpass.getpass("Password: ")
|
|
join_args.append("-w")
|
|
join_args.append(password)
|
|
|
|
# Now join the domain
|
|
(stdout, stderr, returncode) = run(join_args, raiseonerr=False, env=env)
|
|
|
|
if returncode != 0:
|
|
print >>sys.stderr, "Joining realm failed: %s" % stderr,
|
|
if not options.force:
|
|
return 1
|
|
print " Use ipa-getkeytab to obtain a host principal for this server."
|
|
else:
|
|
print "Enrolled in IPA realm %s" % cli_realm
|
|
|
|
start = stderr.find('Certificate subject base is: ')
|
|
if start >= 0:
|
|
start = start + 29
|
|
subject_base = stderr[start:]
|
|
subject_base = subject_base.strip()
|
|
|
|
finally:
|
|
if options.principal is not None:
|
|
(stderr, stdout, returncode) = run(["kdestroy"], raiseonerr=False, env=env)
|
|
os.remove(krb_name)
|
|
os.remove(krb_name + ".ipabkp")
|
|
|
|
# Configure ipa.conf
|
|
if not options.on_master:
|
|
configure_ipa_conf(fstore, cli_basedn, cli_realm, cli_domain, cli_server)
|
|
print "Created /etc/ipa/default.conf"
|
|
|
|
# Always back up sssd.conf. It gets updated by authconfig --enablekrb5.
|
|
fstore.backup_file("/etc/sssd/sssd.conf")
|
|
if options.sssd:
|
|
if configure_sssd_conf(fstore, cli_realm, cli_domain, cli_server, options):
|
|
return 1
|
|
print "Configured /etc/sssd/sssd.conf"
|
|
|
|
# Add the CA to the default NSS database and trust it
|
|
run(["/usr/bin/certutil", "-A", "-d", "/etc/pki/nssdb", "-n", "IPA CA", "-t", "CT,C,C", "-a", "-i", "/etc/ipa/ca.crt"])
|
|
|
|
# If on master assume kerberos is already configured properly.
|
|
if not options.on_master:
|
|
# Configure krb5.conf
|
|
fstore.backup_file("/etc/krb5.conf")
|
|
if configure_krb5_conf(fstore, cli_basedn, cli_realm, cli_domain, cli_server, cli_kdc, dnsok, options, "/etc/krb5.conf"):
|
|
return 1
|
|
|
|
print "Configured /etc/krb5.conf for IPA realm " + cli_realm
|
|
|
|
configure_certmonger(fstore, subject_base, cli_realm, hostname, options)
|
|
|
|
#Try to update the DNS records, failure is not fatal
|
|
if not options.on_master:
|
|
client_dns(cli_server, hostname, options.dns_updates)
|
|
|
|
#Name Server Caching Daemon. Disable for SSSD, use otherwise (if installed)
|
|
if ipautil.service_is_installed("nscd"):
|
|
if options.sssd:
|
|
nscd_service_action = "stop"
|
|
nscd_service_cmd = ipautil.service_stop
|
|
nscd_chkconfig_cmd = ipautil.chkconfig_off
|
|
else:
|
|
nscd_service_action = "restart"
|
|
nscd_service_cmd = ipautil.service_restart
|
|
nscd_chkconfig_cmd = ipautil.chkconfig_on
|
|
|
|
try:
|
|
nscd_service_cmd('nscd')
|
|
except:
|
|
print >>sys.stderr, "Failed to %s the NSCD daemon" % nscd_service_action
|
|
if not options.sssd:
|
|
print >>sys.stderr, "Caching of users/groups will not be available"
|
|
|
|
try:
|
|
nscd_chkconfig_cmd('nscd')
|
|
except:
|
|
if not options.sssd:
|
|
print >>sys.stderr, "Failed to configure automatic startup of the NSCD daemon"
|
|
print >>sys.stderr, "Caching of users/groups will not be available after reboot"
|
|
else:
|
|
print >>sys.stderr, "Failed to disable NSCD daemon. Please disable it manually."
|
|
|
|
else:
|
|
# this is optional service, just log
|
|
logging.info("NSCD daemon is not installed, skip configuration")
|
|
|
|
# Modify nsswitch/pam stack
|
|
if options.sssd:
|
|
cmd = ["/usr/sbin/authconfig", "--enablesssd", "--enablesssdauth", "--update"]
|
|
message = "SSSD enabled"
|
|
else:
|
|
cmd = ["/usr/sbin/authconfig", "--enableldap", "--enableforcelegacy", "--update"]
|
|
message = "LDAP enabled"
|
|
|
|
if options.mkhomedir:
|
|
cmd.append("--enablemkhomedir")
|
|
run(cmd)
|
|
print message
|
|
|
|
#Modify pam to add pam_krb5
|
|
run(["/usr/sbin/authconfig", "--enablekrb5", "--update", "--nostart"])
|
|
print "Kerberos 5 enabled"
|
|
|
|
# Update non-SSSD LDAP configuration after authconfig calls as it would
|
|
# change its configuration otherways
|
|
if not options.sssd:
|
|
if configure_ldap_conf(fstore, cli_basedn, cli_realm, cli_domain, cli_server, dnsok, options):
|
|
return 1
|
|
if configure_nslcd_conf(fstore, cli_basedn, cli_realm, cli_domain, cli_server, dnsok, options):
|
|
return 1
|
|
print "LDAP configured"
|
|
|
|
#Check that nss is working properly
|
|
if not options.on_master:
|
|
n = 0
|
|
found = False
|
|
# Loop for up to 5 seconds to see if nss is working properly.
|
|
# It can sometimes take a few seconds to connect to the remote
|
|
# provider.
|
|
while n < 5 and not found:
|
|
try:
|
|
run(["getent", "passwd", "admin"])
|
|
found = True
|
|
except Exception, e:
|
|
time.sleep(1)
|
|
n = n + 1
|
|
|
|
if not found:
|
|
print "nss_ldap is not able to use DNS discovery!"
|
|
print "Changing configuration to use hardcoded server name: " +cli_server
|
|
|
|
try:
|
|
hardcode_ldap_server(cli_server)
|
|
except Exception, e:
|
|
sys.exit("Adding hardcoded server name to /etc/ldap.conf failed: " + str(e))
|
|
|
|
if options.conf_ntp and not options.on_master:
|
|
if options.ntp_server:
|
|
ntp_server = options.ntp_server
|
|
else:
|
|
ntp_server = cli_server
|
|
ipaclient.ntpconf.config_ntp(ntp_server, fstore)
|
|
print "NTP enabled"
|
|
|
|
print "Client configuration complete."
|
|
|
|
return 0
|
|
|
|
try:
|
|
if __name__ == "__main__":
|
|
if not os.getegid() == 0:
|
|
sys.exit("\nYou must be root to run ipa-client-install.\n")
|
|
|
|
sys.exit(main())
|
|
except SystemExit, e:
|
|
sys.exit(e)
|
|
except KeyboardInterrupt:
|
|
sys.exit(1)
|
|
except RuntimeError, e:
|
|
sys.exit(e)
|