mirror of
https://salsa.debian.org/freeipa-team/freeipa.git
synced 2024-12-26 00:41:25 -06:00
1e6a204b43
Some API contexts are used to modify global state (e.g. files in /etc and /var). These contexts do not support confdir overrides. Initialize the API with an explicit confdir argument to paths.ETC_IPA. The special contexts are: * backup * cli_installer * installer * ipctl * renew * restore * server * updates The patch also corrects the context of the ipa-httpd-kdcproxy script to 'server'. https://fedorahosted.org/freeipa/ticket/6389 Signed-off-by: Christian Heimes <cheimes@redhat.com> Reviewed-By: Jan Cholasta <jcholast@redhat.com>
521 lines
22 KiB
Python
Executable File
521 lines
22 KiB
Python
Executable File
#! /usr/bin/python2
|
|
#
|
|
# Authors: Sumit Bose <sbose@redhat.com>
|
|
# Based on ipa-server-install by Karl MacMillan <kmacmillan@mentalrootkit.com>
|
|
# and ipa-dns-install by Martin Nagy
|
|
#
|
|
# Copyright (C) 2011 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/>.
|
|
#
|
|
|
|
from __future__ import print_function
|
|
|
|
import os
|
|
import sys
|
|
import ldap
|
|
|
|
import six
|
|
|
|
from optparse import SUPPRESS_HELP # pylint: disable=deprecated-module
|
|
|
|
from ipalib.install import sysrestore
|
|
from ipaserver.install import adtrustinstance
|
|
from ipaserver.install.installutils import (
|
|
read_password,
|
|
check_server_configuration,
|
|
run_script)
|
|
from ipaserver.install import service
|
|
from ipapython import version
|
|
from ipapython import ipautil, ipaldap
|
|
from ipalib import api, errors, krb_utils
|
|
from ipapython.config import IPAOptionParser
|
|
from ipaplatform.paths import paths
|
|
from ipapython.ipa_log_manager import root_logger, standard_logging_setup
|
|
from ipapython.dn import DN
|
|
|
|
if six.PY3:
|
|
unicode = str
|
|
|
|
log_file_name = paths.IPASERVER_INSTALL_LOG
|
|
|
|
def parse_options():
|
|
parser = IPAOptionParser(version=version.VERSION)
|
|
parser.add_option("-d", "--debug", dest="debug", action="store_true",
|
|
default=False, help="print debugging information")
|
|
parser.add_option("--netbios-name", dest="netbios_name",
|
|
help="NetBIOS name of the IPA domain")
|
|
|
|
# no-msdcs has not effect, option is here just for backward compatibility
|
|
parser.add_option("--no-msdcs", dest="no_msdcs", action="store_true",
|
|
default=False, help=SUPPRESS_HELP)
|
|
|
|
parser.add_option("--rid-base", dest="rid_base", type=int, default=1000,
|
|
help="Start value for mapping UIDs and GIDs to RIDs")
|
|
parser.add_option("--secondary-rid-base", dest="secondary_rid_base",
|
|
type=int, default=100000000,
|
|
help="Start value of the secondary range for mapping " \
|
|
"UIDs and GIDs to RIDs")
|
|
parser.add_option("-U", "--unattended", dest="unattended", action="store_true",
|
|
default=False, help="unattended installation never prompts the user")
|
|
parser.add_option("-a", "--admin-password",
|
|
sensitive=True, dest="admin_password",
|
|
help="admin user kerberos password")
|
|
parser.add_option("-A", "--admin-name",
|
|
sensitive=True, dest="admin_name", default='admin',
|
|
help="admin user principal")
|
|
parser.add_option("--add-sids", dest="add_sids", action="store_true",
|
|
default=False, help="Add SIDs for existing users and" \
|
|
" groups as the final step")
|
|
parser.add_option("--add-agents", dest="add_agents", action="store_true",
|
|
default=False, help="Add IPA masters to a list of hosts allowed to serve" \
|
|
"information about users from trusted forests")
|
|
parser.add_option("--enable-compat",
|
|
dest="enable_compat", default=False, action="store_true",
|
|
help="Enable support for trusted domains for old clients")
|
|
|
|
options, _args = parser.parse_args()
|
|
safe_options = parser.get_safe_opts(options)
|
|
|
|
return safe_options, options
|
|
|
|
def netbios_name_error(name):
|
|
print("\nIllegal NetBIOS name [%s].\n" % name)
|
|
print("Up to 15 characters and only uppercase ASCII letters, digits "
|
|
"and dashes are allowed. Empty string is not allowed.")
|
|
|
|
def read_netbios_name(netbios_default):
|
|
netbios_name = ""
|
|
|
|
print("Enter the NetBIOS name for the IPA domain.")
|
|
print("Only up to 15 uppercase ASCII letters, digits "
|
|
"and dashes are allowed.")
|
|
print("Example: EXAMPLE.")
|
|
print("")
|
|
print("")
|
|
if not netbios_default:
|
|
netbios_default = "EXAMPLE"
|
|
while True:
|
|
netbios_name = ipautil.user_input("NetBIOS domain name", netbios_default, allow_empty = False)
|
|
print("")
|
|
if adtrustinstance.check_netbios_name(netbios_name):
|
|
break
|
|
|
|
netbios_name_error(netbios_name)
|
|
|
|
return netbios_name
|
|
|
|
def read_admin_password(admin_name):
|
|
print("Configuring cross-realm trusts for IPA server requires password for user '%s'." % (admin_name))
|
|
print("This user is a regular system account used for IPA server administration.")
|
|
print("")
|
|
admin_password = read_password(admin_name, confirm=False, validate=None)
|
|
return admin_password
|
|
|
|
def set_and_check_netbios_name(netbios_name, unattended):
|
|
"""
|
|
Depending if trust in already configured or not a given NetBIOS domain
|
|
name must be handled differently.
|
|
|
|
If trust is not configured the given NetBIOS is used or the NetBIOS is
|
|
generated if none was given on the command line.
|
|
|
|
If trust is already configured the given NetBIOS name is used to reset
|
|
the stored NetBIOS name it it differs from the current one.
|
|
"""
|
|
|
|
flat_name_attr = 'ipantflatname'
|
|
cur_netbios_name = None
|
|
gen_netbios_name = None
|
|
reset_netbios_name = False
|
|
entry = None
|
|
|
|
try:
|
|
entry = api.Backend.ldap2.get_entry(
|
|
DN(('cn', api.env.domain), api.env.container_cifsdomains,
|
|
ipautil.realm_to_suffix(api.env.realm)),
|
|
[flat_name_attr])
|
|
except errors.NotFound:
|
|
# trust not configured
|
|
pass
|
|
else:
|
|
cur_netbios_name = entry.get(flat_name_attr)[0]
|
|
|
|
if cur_netbios_name and not netbios_name:
|
|
# keep the current NetBIOS name
|
|
netbios_name = cur_netbios_name
|
|
reset_netbios_name = False
|
|
elif cur_netbios_name and cur_netbios_name != netbios_name:
|
|
# change the NetBIOS name
|
|
print("Current NetBIOS domain name is %s, new name is %s.\n" % \
|
|
(cur_netbios_name, netbios_name))
|
|
print("Please note that changing the NetBIOS name might " \
|
|
"break existing trust relationships.")
|
|
if unattended:
|
|
reset_netbios_name = True
|
|
print("NetBIOS domain name will be changed to %s.\n" % \
|
|
netbios_name)
|
|
else:
|
|
print("Say 'yes' if the NetBIOS shall be changed and " \
|
|
"'no' if the old one shall be kept.")
|
|
reset_netbios_name = ipautil.user_input(
|
|
'Do you want to reset the NetBIOS domain name?',
|
|
default = False, allow_empty = False)
|
|
if not reset_netbios_name:
|
|
netbios_name = cur_netbios_name
|
|
elif cur_netbios_name and cur_netbios_name == netbios_name:
|
|
# keep the current NetBIOS name
|
|
reset_netbios_name = False
|
|
elif not cur_netbios_name:
|
|
if not netbios_name:
|
|
gen_netbios_name = adtrustinstance.make_netbios_name(api.env.domain)
|
|
|
|
if entry is not None:
|
|
# Fix existing trust configuration
|
|
print("Trust is configured but no NetBIOS domain name found, " \
|
|
"setting it now.")
|
|
reset_netbios_name = True
|
|
else:
|
|
# initial trust configuration
|
|
reset_netbios_name = False
|
|
else:
|
|
# all possible cases should be covered above
|
|
raise Exception('Unexpected state while checking NetBIOS domain name')
|
|
|
|
if unattended and netbios_name is None and gen_netbios_name:
|
|
netbios_name = gen_netbios_name
|
|
|
|
if not adtrustinstance.check_netbios_name(netbios_name):
|
|
if unattended:
|
|
netbios_name_error(netbios_name)
|
|
sys.exit("Aborting installation.")
|
|
else:
|
|
if netbios_name:
|
|
netbios_name_error(netbios_name)
|
|
netbios_name = None
|
|
|
|
if not unattended and not netbios_name:
|
|
netbios_name = read_netbios_name(gen_netbios_name)
|
|
|
|
return (netbios_name, reset_netbios_name)
|
|
|
|
def ensure_admin_kinit(admin_name, admin_password):
|
|
try:
|
|
ipautil.run(['kinit', admin_name], stdin=admin_password+'\n')
|
|
except ipautil.CalledProcessError:
|
|
print("There was error to automatically re-kinit your admin user ticket.")
|
|
return False
|
|
return True
|
|
|
|
def enable_compat_tree():
|
|
print("Do you want to enable support for trusted domains in Schema Compatibility plugin?")
|
|
print("This will allow clients older than SSSD 1.9 and non-Linux clients to work with trusted users.")
|
|
print("")
|
|
enable_compat = ipautil.user_input("Enable trusted domains support in slapi-nis?", default = False, allow_empty = False)
|
|
print("")
|
|
return enable_compat
|
|
|
|
|
|
def main():
|
|
safe_options, options = parse_options()
|
|
|
|
if os.getegid() != 0:
|
|
sys.exit("Must be root to setup AD trusts on server")
|
|
|
|
standard_logging_setup(log_file_name, debug=options.debug, filemode='a')
|
|
print("\nThe log file for this installation can be found in %s" % log_file_name)
|
|
|
|
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('IPA version %s' % version.VENDOR_VERSION)
|
|
|
|
check_server_configuration()
|
|
|
|
fstore = sysrestore.FileStore(paths.SYSRESTORE)
|
|
|
|
print("==============================================================================")
|
|
print("This program will setup components needed to establish trust to AD domains for")
|
|
print("the FreeIPA Server.")
|
|
print("")
|
|
print("This includes:")
|
|
print(" * Configure Samba")
|
|
print(" * Add trust related objects to FreeIPA LDAP server")
|
|
#TODO:
|
|
#print " * Add a SID to all users and Posix groups"
|
|
print("")
|
|
print("To accept the default shown in brackets, press the Enter key.")
|
|
print("")
|
|
|
|
# Check if samba packages are installed
|
|
if not adtrustinstance.check_inst():
|
|
sys.exit("Aborting installation.")
|
|
|
|
# Initialize the ipalib api
|
|
api.bootstrap(
|
|
in_server=True,
|
|
debug=options.debug,
|
|
context='install',
|
|
confdir=paths.ETC_IPA
|
|
)
|
|
api.finalize()
|
|
|
|
# If domain name and realm does not match, IPA server will not be able
|
|
# to estabilish trust with Active Directory. Print big fat warning.
|
|
|
|
realm_not_matching_domain = (api.env.domain.upper() != api.env.realm)
|
|
|
|
if realm_not_matching_domain:
|
|
print("WARNING: Realm name does not match the domain name.\n"
|
|
"You will not be able to estabilish trusts with Active "
|
|
"Directory unless\nthe realm name of the IPA server matches its "
|
|
"domain name.\n\n")
|
|
if not options.unattended:
|
|
if not ipautil.user_input("Do you wish to continue?",
|
|
default = False,
|
|
allow_empty = False):
|
|
sys.exit("Aborting installation.")
|
|
|
|
# Check if /etc/samba/smb.conf already exists. In case it was not generated
|
|
# by IPA, print a warning that we will break existing configuration.
|
|
|
|
if adtrustinstance.ipa_smb_conf_exists():
|
|
if not options.unattended:
|
|
print("IPA generated smb.conf detected.")
|
|
if not ipautil.user_input("Overwrite smb.conf?",
|
|
default = False,
|
|
allow_empty = False):
|
|
sys.exit("Aborting installation.")
|
|
|
|
elif os.path.exists(paths.SMB_CONF):
|
|
print("WARNING: The smb.conf already exists. Running "
|
|
"ipa-adtrust-install will break your existing samba "
|
|
"configuration.\n\n")
|
|
if not options.unattended:
|
|
if not ipautil.user_input("Do you wish to continue?",
|
|
default = False,
|
|
allow_empty = False):
|
|
sys.exit("Aborting installation.")
|
|
|
|
if not options.unattended and not options.enable_compat:
|
|
options.enable_compat = enable_compat_tree()
|
|
|
|
admin_password = options.admin_password
|
|
if not (options.unattended or admin_password):
|
|
admin_password = read_admin_password(options.admin_name)
|
|
|
|
admin_kinited = None
|
|
if admin_password:
|
|
admin_kinited = ensure_admin_kinit(options.admin_name, admin_password)
|
|
if not admin_kinited:
|
|
print("Proceeding with credentials that existed before")
|
|
|
|
try:
|
|
principal = krb_utils.get_principal()
|
|
except errors.CCacheError as e:
|
|
sys.exit("Must have Kerberos credentials to setup AD trusts on server: %s" % e.message)
|
|
|
|
try:
|
|
api.Backend.ldap2.connect()
|
|
except errors.ACIError as e:
|
|
sys.exit("Outdated Kerberos credentials. Use kdestroy and kinit to update your ticket")
|
|
except errors.DatabaseError as e:
|
|
sys.exit("Cannot connect to the LDAP database. Please check if IPA is running")
|
|
|
|
try:
|
|
user = api.Command.user_show(principal.partition('@')[0].partition('/')[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 as e:
|
|
sys.exit("Must have administrative privileges to setup AD trusts on server")
|
|
except Exception as e:
|
|
sys.exit("Unrecognized error during check of admin rights: %s" % (str(e)))
|
|
|
|
(netbios_name, reset_netbios_name) = \
|
|
set_and_check_netbios_name(options.netbios_name,
|
|
options.unattended)
|
|
|
|
if not options.add_sids:
|
|
# The filter corresponds to ipa_sidgen_task.c LDAP search filter
|
|
filter = '(&(objectclass=ipaobject)(!(objectclass=mepmanagedentry))' \
|
|
'(|(objectclass=posixaccount)(objectclass=posixgroup)' \
|
|
'(objectclass=ipaidobject))(!(ipantsecurityidentifier=*)))'
|
|
base_dn = api.env.basedn
|
|
try:
|
|
root_logger.debug("Searching for objects with missing SID with "
|
|
"filter=%s, base_dn=%s", filter, base_dn)
|
|
entries, _truncated = api.Backend.ldap2.find_entries(
|
|
filter=filter, base_dn=base_dn, attrs_list=[''])
|
|
except errors.NotFound:
|
|
# All objects have SIDs assigned
|
|
pass
|
|
except (errors.DatabaseError, errors.NetworkError) as e:
|
|
print("Could not retrieve a list of objects that need a SID identifier assigned:")
|
|
print(unicode(e))
|
|
else:
|
|
object_count = len(entries)
|
|
if object_count > 0:
|
|
print("")
|
|
print("WARNING: %d existing users or groups do not have a SID identifier assigned." \
|
|
% len(entries))
|
|
print("Installer can run a task to have ipa-sidgen Directory Server plugin generate")
|
|
print("the SID identifier for all these users. Please note, the in case of a high")
|
|
print("number of users and groups, the operation might lead to high replication")
|
|
print("traffic and performance degradation. Refer to ipa-adtrust-install(1) man page")
|
|
print("for details.")
|
|
print("")
|
|
if options.unattended:
|
|
print("Unattended mode was selected, installer will NOT run ipa-sidgen task!")
|
|
else:
|
|
if ipautil.user_input("Do you want to run the ipa-sidgen task?", default=False,
|
|
allow_empty=False):
|
|
options.add_sids = True
|
|
|
|
if not options.unattended:
|
|
print("")
|
|
print("The following operations may take some minutes to complete.")
|
|
print("Please wait until the prompt is returned.")
|
|
print("")
|
|
|
|
smb = adtrustinstance.ADTRUSTInstance(fstore)
|
|
smb.realm = api.env.realm
|
|
smb.autobind = ipaldap.AUTOBIND_ENABLED
|
|
smb.setup(api.env.host, api.env.realm,
|
|
netbios_name, reset_netbios_name,
|
|
options.rid_base, options.secondary_rid_base,
|
|
options.add_sids,
|
|
enable_compat = options.enable_compat)
|
|
smb.find_local_id_range()
|
|
smb.create_instance()
|
|
|
|
if options.add_agents:
|
|
# Find out IPA masters which are not part of the cn=adtrust agents
|
|
# and propose them to be added to the list
|
|
base_dn = api.env.basedn
|
|
masters_dn = DN(('cn', 'masters'), ('cn', 'ipa'), ('cn', 'etc'), base_dn)
|
|
agents_dn = DN(('cn', 'adtrust agents'), ('cn', 'sysaccounts'), ('cn', 'etc'), base_dn)
|
|
new_agents = []
|
|
entries_m = []
|
|
entries_a = []
|
|
try:
|
|
# Search only masters which have support for domain levels
|
|
# because only these masters will have SSSD recent enough to support AD trust agents
|
|
entries_m, _truncated = api.Backend.ldap2.find_entries(
|
|
filter="(&(objectclass=ipaSupportedDomainLevelConfig)(ipaMaxDomainLevel=*)(ipaMinDomainLevel=*))",
|
|
base_dn=masters_dn, attrs_list=['cn'], scope=ldap.SCOPE_ONELEVEL)
|
|
except errors.NotFound:
|
|
pass
|
|
except (errors.DatabaseError, errors.NetworkError) as e:
|
|
print("Could not retrieve a list of existing IPA masters:")
|
|
print(unicode(e))
|
|
|
|
try:
|
|
entries_a, _truncated = api.Backend.ldap2.find_entries(
|
|
filter="", base_dn=agents_dn, attrs_list=['member'],
|
|
scope=ldap.SCOPE_BASE)
|
|
except errors.NotFound:
|
|
pass
|
|
except (errors.DatabaseError, errors.NetworkError) as e:
|
|
print("Could not retrieve a list of adtrust agents:")
|
|
print(unicode(e))
|
|
|
|
if len(entries_m) > 0:
|
|
existing_masters = [x['cn'][0] for x in entries_m]
|
|
adtrust_agents = entries_a[0]['member']
|
|
potential_agents = []
|
|
for m in existing_masters:
|
|
mdn = DN(('fqdn', m), api.env.container_host, api.env.basedn)
|
|
found = False
|
|
for a in adtrust_agents:
|
|
if mdn == a:
|
|
found = True
|
|
break
|
|
if not found:
|
|
potential_agents += [[m, mdn]]
|
|
|
|
object_count = len(potential_agents)
|
|
if object_count > 0:
|
|
print("")
|
|
print("WARNING: %d IPA masters are not yet able to serve information about users from trusted forests." \
|
|
% (object_count))
|
|
print("Installer can add them to the list of IPA masters allowed to access information about trusts.")
|
|
print("If you choose to do so, you also need to restart LDAP service on those masters.")
|
|
print("Refer to ipa-adtrust-install(1) man page for details.")
|
|
print("")
|
|
if options.unattended:
|
|
print("Unattended mode was selected, installer will NOT add other IPA masters to the list of allowed to")
|
|
print("access information about trusted forests!")
|
|
else:
|
|
print("Do you want to allow following IPA masters to serve information about users from trusted forests?")
|
|
for (name, dn) in potential_agents:
|
|
if name == api.env.host:
|
|
# Don't add this host here
|
|
# it shouldn't be here as it was added by the adtrustinstance setup code
|
|
continue
|
|
if ipautil.user_input("IPA master [%s]?" % (name), default=False, allow_empty=False):
|
|
new_agents += [[name, dn]]
|
|
|
|
if len(new_agents) > 0:
|
|
# Add the CIFS and host principals to the 'adtrust agents' group
|
|
# as 389-ds only operates with GroupOfNames, we have to use
|
|
# the principal's proper dn as defined in self.cifs_agent
|
|
service.add_principals_to_group(api.Backend.ldap2, agents_dn, "member",
|
|
[x[1] for x in new_agents])
|
|
print("""
|
|
WARNING: you MUST restart (e.g. ipactl restart) the following IPA masters in order
|
|
to activate them to serve information about users from trusted forests:""")
|
|
for x in new_agents:
|
|
print(x[0])
|
|
|
|
print("""
|
|
=============================================================================
|
|
Setup complete
|
|
|
|
You must make sure these network ports are open:
|
|
\tTCP Ports:
|
|
\t * 135: epmap
|
|
\t * 138: netbios-dgm
|
|
\t * 139: netbios-ssn
|
|
\t * 445: microsoft-ds
|
|
\t * 1024..1300: epmap listener range
|
|
\t * 3268: msft-gc
|
|
\tUDP Ports:
|
|
\t * 138: netbios-dgm
|
|
\t * 139: netbios-ssn
|
|
\t * 389: (C)LDAP
|
|
\t * 445: microsoft-ds
|
|
|
|
See the ipa-adtrust-install(1) man page for more details
|
|
|
|
=============================================================================
|
|
""")
|
|
if admin_password:
|
|
admin_kinited = ensure_admin_kinit(options.admin_name, admin_password)
|
|
|
|
if not admin_kinited:
|
|
print("""
|
|
WARNING: you MUST re-kinit admin user before using 'ipa trust-*' commands
|
|
family in order to re-generate Kerberos tickets to include AD-specific
|
|
information""")
|
|
|
|
api.Backend.ldap2.disconnect()
|
|
|
|
return 0
|
|
|
|
if __name__ == '__main__':
|
|
run_script(main, log_file_name=log_file_name,
|
|
operation_name='ipa-adtrust-install')
|