diff options
-rw-r--r-- | ipa-python/ipautil.py | 2 | ||||
-rwxr-xr-x | ipa-server/freeipa-server.spec | 2 | ||||
-rw-r--r-- | ipa-server/freeipa-server.spec.in | 2 | ||||
-rw-r--r-- | ipa-server/ipa-install/Makefile.am | 2 | ||||
-rw-r--r-- | ipa-server/ipa-install/ipa-replica-install | 142 | ||||
-rw-r--r-- | ipa-server/ipa-install/ipa-replica-prepare | 114 | ||||
-rw-r--r-- | ipa-server/ipa-install/ipa-server-install | 159 | ||||
-rw-r--r-- | ipa-server/ipa-install/share/kerberos.ldif | 18 | ||||
-rw-r--r-- | ipa-server/ipaserver/Makefile.am | 2 | ||||
-rw-r--r-- | ipa-server/ipaserver/dsinstance.py | 104 | ||||
-rw-r--r-- | ipa-server/ipaserver/installutils.py | 108 | ||||
-rw-r--r-- | ipa-server/ipaserver/ipaldap.py | 101 | ||||
-rw-r--r-- | ipa-server/ipaserver/krbinstance.py | 199 | ||||
-rw-r--r-- | ipa-server/ipaserver/radiusinstance.py | 3 |
14 files changed, 705 insertions, 253 deletions
diff --git a/ipa-python/ipautil.py b/ipa-python/ipautil.py index e7f59419..cd8eac16 100644 --- a/ipa-python/ipautil.py +++ b/ipa-python/ipautil.py @@ -25,6 +25,7 @@ import logging import subprocess import os import stat +import socket from string import lower import re @@ -36,7 +37,6 @@ def realm_to_suffix(realm_name): terms = ["dc=" + x.lower() for x in s] return ",".join(terms) - def template_str(txt, vars): return string.Template(txt).substitute(vars) diff --git a/ipa-server/freeipa-server.spec b/ipa-server/freeipa-server.spec index 80b77bc9..93a3bae1 100755 --- a/ipa-server/freeipa-server.spec +++ b/ipa-server/freeipa-server.spec @@ -71,6 +71,8 @@ rm -rf %{buildroot} %files %defattr(-,root,root,-) %{_sbindir}/ipa-server-install +%{_sbindir}/ipa-replica-install +%{_sbindir}/ipa-replica-prepare %{_sbindir}/ipa_kpasswd %{_sbindir}/ipa-webgui %attr(755,root,root) %{_initrddir}/ipa-kpasswd diff --git a/ipa-server/freeipa-server.spec.in b/ipa-server/freeipa-server.spec.in index f7db08d7..d50cd4e9 100644 --- a/ipa-server/freeipa-server.spec.in +++ b/ipa-server/freeipa-server.spec.in @@ -71,6 +71,8 @@ rm -rf %{buildroot} %files %defattr(-,root,root,-) %{_sbindir}/ipa-server-install +%{_sbindir}/ipa-replica-install +%{_sbindir}/ipa-replica-prepare %{_sbindir}/ipa_kpasswd %{_sbindir}/ipa-webgui %attr(755,root,root) %{_initrddir}/ipa-kpasswd diff --git a/ipa-server/ipa-install/Makefile.am b/ipa-server/ipa-install/Makefile.am index 9ecf7e20..4765cfb5 100644 --- a/ipa-server/ipa-install/Makefile.am +++ b/ipa-server/ipa-install/Makefile.am @@ -6,6 +6,8 @@ SUBDIRS = \ sbin_SCRIPTS = \ ipa-server-install \ + ipa-replica-install \ + ipa-replica-prepare \ $(NULL) appdir = $(IPA_DATA_DIR) diff --git a/ipa-server/ipa-install/ipa-replica-install b/ipa-server/ipa-install/ipa-replica-install new file mode 100644 index 00000000..706dc323 --- /dev/null +++ b/ipa-server/ipa-install/ipa-replica-install @@ -0,0 +1,142 @@ +#! /usr/bin/python -E +# Authors: 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; version 2 or later +# +# 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, write to the Free Software +# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA +# + +import sys +sys.path.append("/usr/share/ipa") + +import tempfile +from ConfigParser import SafeConfigParser + +from ipa import ipautil + +from ipaserver import dsinstance, replication, installutils, krbinstance, service +from ipaserver import httpinstance, webguiinstance, radiusinstance, ntpinstance + +class ReplicaConfig: + def __init__(self): + self.realm_name = "" + self.master_host_name = "" + self.dirman_password = "" + self.ds_user = "" + self.host_name = "" + self.repl_password = "" + self.dir = "" + +def parse_options(): + from optparse import OptionParser + parser = OptionParser() + parser.add_option("-r", "--read-only", dest="master", action="store_false", + default=True, help="create read-only replica - default is master") + + options, args = parser.parse_args() + + if len(args) != 1: + parser.error("you must provide a file generated by ipa-replica-prepare") + + return options, args[0] + +def get_dirman_password(): + return installutils.read_password("Directory Manager (existing master)") + +def expand_info(filename): + top_dir = tempfile.mkdtemp("ipa") + dir = top_dir + "/realm_info" + ipautil.run(["tar", "xfz", filename, "-C", top_dir]) + + return top_dir, dir + +def read_info(dir, rconfig): + filename = dir + "/realm_info" + fd = open(filename) + config = SafeConfigParser() + config.readfp(fd) + + rconfig.realm_name = config.get("realm", "realm_name") + rconfig.master_host_name = config.get("realm", "master_host_name") + rconfig.ds_user = config.get("realm", "ds_user") + +def get_host_name(): + hostname = installutils.get_fqdn() + try: + installutils.verify_fqdn(hostname) + except RuntimeError, e: + logging.error(str(e)) + sys.exit(1) + + return hostname + +def install_ds(config): + dsinstance.check_existing_installation() + dsinstance.check_ports() + + ds = dsinstance.DsInstance() + ds.create_instance(config.ds_user, config.realm_name, config.host_name, config.dirman_password) + +def install_krb(config): + krb = krbinstance.KrbInstance() + ldappwd_filename = config.dir + "/ldappwd" + krb.create_replica(config.ds_user, config.realm_name, config.host_name, + config.dirman_password, ldappwd_filename) + +def install_http(config): + http = httpinstance.HTTPInstance() + http.create_instance(config.realm_name, config.host_name) + +def main(): + options, filename = parse_options() + top_dir, dir = expand_info(filename) + + config = ReplicaConfig() + read_info(dir, config) + config.host_name = get_host_name() + config.repl_password = "box" + config.dir = dir + + # get the directory manager password + config.dirman_password = get_dirman_password() + + install_ds(config) + + repl = replication.ReplicationManager(config.host_name, config.dirman_password) + repl.setup_replication(config.master_host_name, config.realm_name, options.master) + + install_krb(config) + install_http(config) + + # Create a Web Gui instance + webgui = webguiinstance.WebGuiInstance() + webgui.create_instance() + + # Create a radius instance + radius = radiusinstance.RadiusInstance() + # FIXME: ldap_server should be derived, not hardcoded to localhost, also should it be a URL? + radius.create_instance(config.realm_name, config.host_name, 'localhost') + + # Configure ntpd + ntp = ntpinstance.NTPInstance() + ntp.create_instance() + + + service.restart("dirsrv") + service.restart("krb5kdc") + +main() + + diff --git a/ipa-server/ipa-install/ipa-replica-prepare b/ipa-server/ipa-install/ipa-replica-prepare new file mode 100644 index 00000000..705c731d --- /dev/null +++ b/ipa-server/ipa-install/ipa-replica-prepare @@ -0,0 +1,114 @@ +#! /usr/bin/python -E +# Authors: 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; version 2 or later +# +# 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, write to the Free Software +# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA +# + +import sys +sys.path.append("/usr/share/ipa") + +import logging, tempfile, shutil, os, pwd +from ConfigParser import SafeConfigParser +import krbV + +from ipa import ipautil +from ipaserver import dsinstance +from ipaserver import installutils + +certutil = "/usr/bin/certutil" + +def get_host_name(): + hostname = installutils.get_fqdn() + try: + installutils.verify_fqdn(hostname) + except RuntimeError, e: + logging.error(str(e)) + sys.exit(1) + + return hostname + +def get_realm_name(): + c = krbV.default_context() + return c.default_realm + +def check_ipa_configuration(realm_name): + config_dir = dsinstance.config_dirname(realm_name) + if not ipautil.dir_exists(config_dir): + logging.error("could not find directory instance: %s" % config_dir) + sys.exit(1) + +def create_certdb(ds_dir, dir): + # copy the passwd, noise, and pin files + shutil.copyfile(ds_dir + "/pwdfile.txt", dir + "/pwdfile.txt") + shutil.copyfile(ds_dir + "/noise.txt", dir + "/noise.txt") + shutil.copyfile(ds_dir + "/pin.txt", dir + "/pin.txt") + + # create a new cert db + ipautil.run([certutil, "-N", "-d", dir, "-f", dir + "/pwdfile.txt"]) + + # Add the CA cert + ipautil.run([certutil, "-A", "-d", dir, "-n", "CA certificate", "-t", "CT,CT", "-a", "-i", + ds_dir + "/cacert.asc"]) + +def get_ds_user(ds_dir): + uid = os.stat(ds_dir).st_uid + user = pwd.getpwuid(uid)[0] + + return user + +def copy_files(realm_name, dir): + shutil.copy("/var/kerberos/krb5kdc/ldappwd", dir + "/ldappwd") + + +def save_config(dir, realm_name, host_name, ds_user): + config = SafeConfigParser() + config.add_section("realm") + config.set("realm", "realm_name", realm_name) + config.set("realm", "master_host_name", host_name) + config.set("realm", "ds_user", ds_user) + fd = open(dir + "/realm_info", "w") + config.write(fd) + + +def main(): + realm_name = get_realm_name() + host_name = get_host_name() + ds_dir = dsinstance.config_dirname(realm_name) + ds_user = get_ds_user(ds_dir) + + check_ipa_configuration(realm_name) + + top_dir = tempfile.mkdtemp("ipa") + dir = top_dir + "/realm_info" + os.mkdir(dir, 0700) + + create_certdb(ds_dir, dir) + + copy_files(realm_name, dir) + + save_config(dir, realm_name, host_name, ds_user) + + ipautil.run(["/bin/tar", "cfz", "replica-info-" + realm_name, "-C", top_dir, "realm_info"]) + + shutil.rmtree(dir) + +main() + + + + + diff --git a/ipa-server/ipa-install/ipa-server-install b/ipa-server/ipa-install/ipa-server-install index 2de687fd..b957e522 100644 --- a/ipa-server/ipa-install/ipa-server-install +++ b/ipa-server/ipa-install/ipa-server-install @@ -34,7 +34,6 @@ import socket import errno import logging import pwd -import getpass import subprocess import signal import shutil @@ -51,8 +50,9 @@ import ipaserver.radiusinstance import ipaserver.webguiinstance from ipaserver import service +from ipaserver.installutils import * -from ipa.ipautil import run +from ipa.ipautil import * def parse_options(): parser = OptionParser(version=VERSION) @@ -86,39 +86,6 @@ def parse_options(): return options -def logging_setup(options): - # Always log everything (i.e., DEBUG) to the log - # file. - logging.basicConfig(level=logging.DEBUG, - format='%(asctime)s %(levelname)s %(message)s', - filename='ipaserver-install.log', - filemode='w') - - 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 erase_ds_instance_data(serverid): - try: - shutil.rmtree("/etc/dirsrv/slapd-%s" % serverid) - except: - pass - try: - shutil.rmtree("/var/lib/dirsrv/slapd-%s" % serverid) - except: - pass - try: - shutil.rmtree("/var/lock/dirsrv/slapd-%s" % serverid) - except: - pass - def signal_handler(signum, frame): global ds print "\nCleaning up..." @@ -126,59 +93,9 @@ def signal_handler(signum, frame): print "Removing configuration for %s instance" % ds.serverid ds.stop() if ds.serverid: - erase_ds_instance_data (ds.serverid) + ipaserver.dsinstance.erase_ds_instance_data (ds.serverid) sys.exit(1) -def check_existing_installation(): - dirs = glob.glob("/etc/dirsrv/slapd-*") - if not dirs: - return - print "" - print "An existing Directory Server has been detected." - yesno = raw_input("Do you wish to remove it and create a new one? [no]: ") - if not yesno or yesno.lower()[0] != "y": - sys.exit(1) - - try: - run(["/sbin/service", "dirsrv", "stop"]) - except: - pass - for d in dirs: - serverid = os.path.basename(d).split("slapd-", 1)[1] - if serverid: - erase_ds_instance_data(serverid) - -def check_ports(): - ds_unsecure = port_available(389) - ds_secure = port_available(636) - if not ds_unsecure or not ds_secure: - print "IPA requires ports 389 and 636 for the Directory Server." - print "These are currently in use:" - if not ds_unsecure: - print "\t389" - if not ds_secure: - print "\t636" - sys.exit(1) - -def get_fqdn(): - fqdn = "" - try: - fqdn = socket.getfqdn() - except: - try: - fqdn = socket.gethostname() - except: - fqdn = "" - return fqdn - -def verify_fqdn(host_name): - is_ok = True - if len(host_name.split(".")) < 2 or host_name == "localhost.localdomain": - print "Invalid hostname: " + host_name - print "This host name can't be used as a hostname for an IPA Server" - is_ok = False - return is_ok - def read_host_name(host_default): host_ok = False host_name = "" @@ -198,7 +115,9 @@ def read_host_name(host_default): host_name = host_default else: host_name = host_input - if not verify_fqdn(host_name): + try: + verify_fqdn(host_name) + except: host_name = "" continue else: @@ -256,36 +175,6 @@ def read_ip_address(host_name): return ip -def port_available(port): - """Try to bind to a port on the wildcard host - Return 1 if the port is available - Return 0 if the port is in use - """ - rv = 1 - - try: - s = socket.socket(socket.AF_INET, socket.SOCK_STREAM) - s.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1) - s.bind(('', port)) - s.shutdown(0) - s.close() - except socket.error, e: - if e[0] == errno.EADDRINUSE: - rv = 0 - - if rv: - try: - s = socket.socket(socket.AF_INET6, socket.SOCK_STREAM) - s.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1) - s.bind(('', port)) - s.shutdown(0) - s.close() - except socket.error, e: - if e[0] == errno.EADDRINUSE: - rv = 0 - - return rv - def read_ds_user(): print "The server must run as a specific user in a specific group." print "It is strongly recommended that this user should have no privileges" @@ -333,23 +222,6 @@ def read_realm_name(domain_name): realm_name = upper_dom return realm_name -def read_password(user): - correct = False - pwd = "" - while not correct: - pwd = getpass.getpass(user + " password: ") - if not pwd: - continue - pwd_confirm = getpass.getpass("Password (confirm): ") - if pwd != pwd_confirm: - print "Password mismatch!" - print "" - else: - correct = True - #TODO: check validity/length - print "" - return pwd - def read_dm_password(): print "Certain directory server operations require an administrative user." print "This user is referred to as the Directory Manager and has full access" @@ -392,6 +264,8 @@ def main(): global ds ds = None + options = parse_options() + if os.getegid() != 0: print "Must be root to setup server" return @@ -399,17 +273,17 @@ def main(): signal.signal(signal.SIGTERM, signal_handler) signal.signal(signal.SIGINT, signal_handler) + standard_logging_setup("ipaserver-install.log", options.debug) + print "==============================================================================" print "This program will setup the FreeIPA Server." print "" print "To accept the default shown in brackets, press the Enter key." print "" - check_existing_installation() - check_ports() + ipaserver.dsinstance.check_existing_installation() + ipaserver.dsinstance.check_ports() - options = parse_options() - logging_setup(options) ds_user = "" realm_name = "" @@ -439,10 +313,13 @@ def main(): host_default = get_fqdn() if options.unattended: - if not verify_fqdn(host_default): + try: + verify_fqdn(host_default) + except RuntimeError, e: + logging.error(str(e) + "\n") return "-Fatal Error-" - else: - host_name = host_default + + host_name = host_default else: host_name = read_host_name(host_default) diff --git a/ipa-server/ipa-install/share/kerberos.ldif b/ipa-server/ipa-install/share/kerberos.ldif index d55f39ce..75057aa3 100644 --- a/ipa-server/ipa-install/share/kerberos.ldif +++ b/ipa-server/ipa-install/share/kerberos.ldif @@ -14,22 +14,4 @@ objectClass: top cn: kerberos aci: (targetattr="*")(version 3.0; acl "KDC System Account"; allow (all) userdn= "ldap:///uid=kdc,cn=sysaccounts,cn=etc,$SUFFIX";) -#sasl mapping -dn: cn=Full Principal,cn=mapping,cn=sasl,cn=config -changetype: add -objectclass: top -objectclass: nsSaslMapping -cn: Full Principal -nsSaslMapRegexString: \(.*\)@\(.*\) -nsSaslMapBaseDNTemplate: $SUFFIX -nsSaslMapFilterTemplate: (krbPrincipalName=\1@\2) - -dn: cn=Name Only,cn=mapping,cn=sasl,cn=config -changetype: add -objectclass: top -objectclass: nsSaslMapping -cn: Name Only -nsSaslMapRegexString: \(.*\) -nsSaslMapBaseDNTemplate: $SUFFIX -nsSaslMapFilterTemplate: (krbPrincipalName=\1@$REALM) diff --git a/ipa-server/ipaserver/Makefile.am b/ipa-server/ipaserver/Makefile.am index 25b85687..c2d164b9 100644 --- a/ipa-server/ipaserver/Makefile.am +++ b/ipa-server/ipaserver/Makefile.am @@ -12,6 +12,8 @@ app_PYTHON = \ radiusinstance.py \ webguiinstance.py \ service.py \ + installutils.py \ + replication.py \ $(NULL) EXTRA_DIST = \ diff --git a/ipa-server/ipaserver/dsinstance.py b/ipa-server/ipaserver/dsinstance.py index 3cf80f46..79a57182 100644 --- a/ipa-server/ipaserver/dsinstance.py +++ b/ipa-server/ipaserver/dsinstance.py @@ -24,10 +24,14 @@ import tempfile import shutil import logging import pwd +import glob +import sys from ipa.ipautil import * import service +import installutils + SERVER_ROOT_64 = "/usr/lib64/dirsrv" SERVER_ROOT_32 = "/usr/lib/dirsrv" @@ -35,9 +39,6 @@ def ldap_mod(fd, dn, pwd): args = ["/usr/bin/ldapmodify", "-h", "127.0.0.1", "-xv", "-D", dn, "-w", pwd, "-f", fd.name] run(args) - text = fd.read() - print text - def realm_to_suffix(realm_name): s = realm_name.split(".") terms = ["dc=" + x.lower() for x in s] @@ -49,6 +50,61 @@ def find_server_root(): else: return SERVER_ROOT_32 +def realm_to_serverid(realm_name): + return "-".join(realm_name.split(".")) + +def config_dirname(realm_name): + return "/etc/dirsrv/slapd-" + realm_to_serverid(realm_name) + "/" + +def schema_dirname(realm_name): + return config_dirname(realm_name) + "/schema/" + +def erase_ds_instance_data(serverid): + try: + shutil.rmtree("/etc/dirsrv/slapd-%s" % serverid) + except: + pass + try: + shutil.rmtree("/var/lib/dirsrv/slapd-%s" % serverid) + except: + pass + try: + shutil.rmtree("/var/lock/dirsrv/slapd-%s" % serverid) + except: + pass + +def check_existing_installation(): + dirs = glob.glob("/etc/dirsrv/slapd-*") + if not dirs: + return + print "" + print "An existing Directory Server has been detected." + yesno = raw_input("Do you wish to remove it and create a new one? [no]: ") + if not yesno or yesno.lower()[0] != "y": + sys.exit(1) + + try: + run(["/sbin/service", "dirsrv", "stop"]) + except: + pass + for d in dirs: + serverid = os.path.basename(d).split("slapd-", 1)[1] + if serverid: + erase_ds_instance_data(serverid) + +def check_ports(): + ds_unsecure = installutils.port_available(389) + ds_secure = installutils.port_available(636) + if not ds_unsecure or not ds_secure: + print "IPA requires ports 389 and 636 for the Directory Server." + print "These are currently in use:" + if not ds_unsecure: + print "\t389" + if not ds_secure: + print "\t636" + sys.exit(1) + + INF_TEMPLATE = """ [General] FullMachineName= $FQHN @@ -72,20 +128,25 @@ class DsInstance(service.Service): self.dm_password = None self.sub_dict = None - def create_instance(self, ds_user, realm_name, host_name, dm_password): + def create_instance(self, ds_user, realm_name, host_name, dm_password, ro_replica=False): self.ds_user = ds_user self.realm_name = realm_name.upper() - self.serverid = "-".join(self.realm_name.split(".")) + self.serverid = realm_to_serverid(self.realm_name) self.suffix = realm_to_suffix(self.realm_name) self.host_name = host_name self.dm_password = dm_password self.__setup_sub_dict() + + if ro_replica: + self.start_creation(15, "Configuring directory server:") + else: + self.start_creation(15, "Configuring directory server:") - self.start_creation(15, "Configuring directory server:") self.__create_ds_user() self.__create_instance() self.__add_default_schemas() - self.__add_memberof_module() + if not ro_replica: + self.__add_memberof_module() self.__add_referint_module() self.__add_dna_module() self.__create_indeces() @@ -97,10 +158,11 @@ class DsInstance(service.Service): except: # TODO: roll back here? logging.critical("Failed to restart the ds instance") - self.__config_uidgid_gen_first_master() self.__add_default_layout() - self.__add_master_entry_first_master() - self.__init_memberof() + if not ro_replica: + self.__config_uidgid_gen_first_master() + self.__add_master_entry_first_master() + self.__init_memberof() self.step("configuring directoy to start on boot") @@ -108,14 +170,6 @@ class DsInstance(service.Service): self.done_creation() - def config_dirname(self): - if not self.serverid: - raise RuntimeError("serverid not set") - return "/etc/dirsrv/slapd-" + self.serverid + "/" - - def schema_dirname(self): - return self.config_dirname() + "/schema/" - def __setup_sub_dict(self): server_root = find_server_root() self.sub_dict = dict(FQHN=self.host_name, SERVERID=self.serverid, @@ -165,13 +219,13 @@ class DsInstance(service.Service): def __add_default_schemas(self): self.step("adding default schema") shutil.copyfile(SHARE_DIR + "60kerberos.ldif", - self.schema_dirname() + "60kerberos.ldif") + schema_dirname(self.realm_name) + "60kerberos.ldif") shutil.copyfile(SHARE_DIR + "60samba.ldif", - self.schema_dirname() + "60samba.ldif") + schema_dirname(self.realm_name) + "60samba.ldif") shutil.copyfile(SHARE_DIR + "60radius.ldif", - self.schema_dirname() + "60radius.ldif") + schema_dirname(self.realm_name) + "60radius.ldif") shutil.copyfile(SHARE_DIR + "60ipaconfig.ldif", - self.schema_dirname() + "60ipaconfig.ldif") + schema_dirname(self.realm_name) + "60ipaconfig.ldif") def __add_memberof_module(self): self.step("enabling memboerof plugin") @@ -235,7 +289,7 @@ class DsInstance(service.Service): def __enable_ssl(self): self.step("configuring ssl for ds instance") - dirname = self.config_dirname() + dirname = config_dirname(self.realm_name) args = ["/usr/share/ipa/ipa-server-setupssl", self.dm_password, dirname, self.host_name] try: @@ -273,7 +327,7 @@ class DsInstance(service.Service): def __certmap_conf(self): self.step("configuring certmap.conf") - dirname = self.config_dirname() + dirname = config_dirname(self.realm_name) certmap_conf = template_file(SHARE_DIR+"certmap.conf.template", self.sub_dict) certmap_fd = open(dirname+"certmap.conf", "w+") certmap_fd.write(certmap_conf) @@ -281,7 +335,7 @@ class DsInstance(service.Service): def change_admin_password(self, password): logging.debug("Changing admin password") - dirname = self.config_dirname() + dirname = config_dirname(self.realm_name) if dir_exists("/usr/lib64/mozldap"): app = "/usr/lib64/mozldap/ldappasswd" else: diff --git a/ipa-server/ipaserver/installutils.py b/ipa-server/ipaserver/installutils.py new file mode 100644 index 00000000..a403e815 --- /dev/null +++ b/ipa-server/ipaserver/installutils.py @@ -0,0 +1,108 @@ +# Authors: Simo Sorce <ssorce@redhat.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; version 2 or later +# +# 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, write to the Free Software +# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA +# + +import logging +import socket +import errno +import getpass + +def get_fqdn(): + fqdn = "" + try: + fqdn = socket.getfqdn() + except: + try: + fqdn = socket.gethostname() + except: + fqdn = "" + return fqdn + +def verify_fqdn(host_name): + if len(host_name.split(".")) < 2 or host_name == "localhost.localdomain": + raise RuntimeError("Invalid hostname: " + host_name) + +def port_available(port): + """Try to bind to a port on the wildcard host + Return 1 if the port is available + Return 0 if the port is in use + """ + rv = 1 + + try: + s = socket.socket(socket.AF_INET, socket.SOCK_STREAM) + s.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1) + s.bind(('', port)) + s.shutdown(0) + s.close() + except socket.error, e: + if e[0] == errno.EADDRINUSE: + rv = 0 + + if rv: + try: + s = socket.socket(socket.AF_INET6, socket.SOCK_STREAM) + s.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1) + s.bind(('', port)) + s.shutdown(0) + s.close() + except socket.error, e: + if e[0] == errno.EADDRINUSE: + rv = 0 + + return rv + +def standard_logging_setup(log_filename, debug=False): + # Always log everything (i.e., DEBUG) to the log + # file. + logging.basicConfig(level=logging.DEBUG, + format='%(asctime)s %(levelname)s %(message)s', + filename=log_filename, + filemode='w') + + console = logging.StreamHandler() + # If the debug option is set, also log debug messages to the console + if 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 read_password(user): + correct = False + pwd = "" + while not correct: + pwd = getpass.getpass(user + " password: ") + if not pwd: + continue + if len(pwd) < 8: + print "Password must be at least 8 characters long" + continue + pwd_confirm = getpass.getpass("Password (confirm): ") + if pwd != pwd_confirm: + print "Password mismatch!" + print "" + else: + correct = True + print "" + return pwd + + diff --git a/ipa-server/ipaserver/ipaldap.py b/ipa-server/ipaserver/ipaldap.py index d319869d..ef8becec 100644 --- a/ipa-server/ipaserver/ipaldap.py +++ b/ipa-server/ipaserver/ipaldap.py @@ -176,25 +176,90 @@ def wrapper(f,name): return f(*args, **kargs) return inner +class LDIFConn(ldif.LDIFParser): + def __init__( + self, + input_file, + ignored_attr_types=None,max_entries=0,process_url_schemes=None + ): + """ + See LDIFParser.__init__() + + Additional Parameters: + all_records + List instance for storing parsed records + """ + self.dndict = {} # maps dn to Entry + self.dnlist = [] # contains entries in order read + myfile = input_file + if isinstance(input_file,str) or isinstance(input_file,unicode): + myfile = open(input_file, "r") + ldif.LDIFParser.__init__(self,myfile,ignored_attr_types,max_entries,process_url_schemes) + self.parse() + if isinstance(input_file,str) or isinstance(input_file,unicode): + myfile.close() + + def handle(self,dn,entry): + """ + Append single record to dictionary of all records. + """ + if not dn: + dn = '' + newentry = Entry((dn, entry)) + self.dndict[IPAdmin.normalizeDN(dn)] = newentry + self.dnlist.append(newentry) + + def get(self,dn): + ndn = IPAdmin.normalizeDN(dn) + return self.dndict.get(ndn, Entry(None)) + class IPAdmin(SimpleLDAPObject): CFGSUFFIX = "o=NetscapeRoot" DEFAULT_USER_ID = "nobody" + + def getDseAttr(self,attrname): + conffile = self.confdir + '/dse.ldif' + dseldif = LDIFConn(conffile) + cnconfig = dseldif.get("cn=config") + if cnconfig: + return cnconfig.getValue(attrname) + return None def __initPart2(self): if self.binddn and len(self.binddn) and not hasattr(self,'sroot'): try: ent = self.getEntry('cn=config', ldap.SCOPE_BASE, '(objectclass=*)', - [ 'nsslapd-instancedir', 'nsslapd-errorlog' ]) - instdir = ent.getValue('nsslapd-instancedir') - self.sroot, self.inst = re.match(r'(.*)[\/]slapd-(\w+)$', instdir).groups() + [ 'nsslapd-instancedir', 'nsslapd-errorlog', + 'nsslapd-certdir', 'nsslapd-schemadir' ]) self.errlog = ent.getValue('nsslapd-errorlog') - except (ldap.INSUFFICIENT_ACCESS, ldap.CONNECT_ERROR, - ipaerror.exception_for(ipaerror.LDAP_NOT_FOUND)): + self.confdir = None + if self.isLocal: + self.confdir = ent.getValue('nsslapd-certdir') + if not self.confdir or not os.access(self.confdir + '/dse.ldif', os.R_OK): + self.confdir = ent.getValue('nsslapd-schemadir') + if self.confdir: + self.confdir = os.path.dirname(self.confdir) + instdir = ent.getValue('nsslapd-instancedir') + if not instdir: + # get instance name from errorlog + self.inst = re.match(r'(.*)[\/]slapd-([\w-]+)/errors', self.errlog).group(2) + if self.confdir: + instdir = self.getDseAttr('nsslapd-instancedir') + else: + if self.isLocal: + print instdir + self.sroot, self.inst = re.match(r'(.*)[\/]slapd-([\w-]+)$', instdir).groups() + instdir = re.match(r'(.*/slapd-.*)/errors', self.errlog).group(1) + #self.sroot, self.inst = re.match(r'(.*)[\/]slapd-([\w-]+)$', instdir).groups() + ent = self.getEntry('cn=config,cn=ldbm database,cn=plugins,cn=config', + ldap.SCOPE_BASE, '(objectclass=*)', + [ 'nsslapd-directory' ]) + self.dbdir = os.path.dirname(ent.getValue('nsslapd-directory')) + except (ldap.INSUFFICIENT_ACCESS, ldap.CONNECT_ERROR): pass # usually means -# print "ignored exception" except ldap.LDAPError, e: print "caught exception ", e - raise ipaerror.gen_exception(ipaerror.LDAP_DATABASE_ERROR, None, e) + raise def __localinit__(self): """If a CA certificate is provided then it is assumed that we are @@ -209,7 +274,7 @@ class IPAdmin(SimpleLDAPObject): else: SimpleLDAPObject.__init__(self,'ldap://%s:%d' % (self.host,self.port)) - def __init__(self,host,port,cacert,bindcert,bindkey,proxydn=None,debug=None): + def __init__(self,host,port=389,cacert=None,bindcert=None,bindkey=None,proxydn=None,debug=None): """We just set our instance variables and wrap the methods - the real work is done in __localinit__ and __initPart2 - these are separated out this way so that we can call them from places other than @@ -223,7 +288,7 @@ class IPAdmin(SimpleLDAPObject): ldap.set_option(ldap.OPT_X_TLS_KEYFILE,bindkey) self.__wrapmethods() - self.port = port or 389 + self.port = port self.host = host self.cacert = cacert self.bindcert = bindcert @@ -272,6 +337,12 @@ class IPAdmin(SimpleLDAPObject): self.principal = principal self.proxydn = None + def do_simple_bind(self, binddn="cn=directory manager", bindpw=""): + self.binddn = binddn + self.bindpwd = bindpw + self.simple_bind_s(binddn, bindpw) + self.__initPart2() + def getEntry(self,*args): """This wraps the search function. It is common to just get one entry""" @@ -283,8 +354,9 @@ class IPAdmin(SimpleLDAPObject): try: res = self.search(*args) type, obj = self.result(res) - - # res = self.search_ext(args[0], args[1], filterstr=args[2], attrlist=args[3], serverctrls=sctrl) + except ldap.NO_SUCH_OBJECT: + raise ipaerror.gen_exception(ipaerror.LDAP_NOT_FOUND, + "no such entry for " + str(args)) except ldap.LDAPError, e: raise ipaerror.gen_exception(ipaerror.LDAP_DATABASE_ERROR, None, e) @@ -538,7 +610,7 @@ class IPAdmin(SimpleLDAPObject): print "Export task %s for file %s completed successfully" % (cn,file) return rc - def waitForEntry(self, dn, timeout=7200, attr='', quiet=False): + def waitForEntry(self, dn, timeout=7200, attr='', quiet=True): scope = ldap.SCOPE_BASE filter = "(objectclass=*)" attrlist = [] @@ -560,7 +632,8 @@ class IPAdmin(SimpleLDAPObject): entry = self.getEntry(dn, scope, filter, attrlist) except ipaerror.exception_for(ipaerror.LDAP_NOT_FOUND): pass # found entry, but no attr - except ldap.NO_SUCH_OBJECT: pass # no entry yet + except ldap.NO_SUCH_OBJECT: + pass # no entry yet except ldap.LDAPError, e: # badness print "\nError reading entry", dn, e break @@ -574,7 +647,7 @@ class IPAdmin(SimpleLDAPObject): print "\nwaitForEntry timeout for %s for %s" % (self,dn) elif entry and not quiet: print "\nThe waited for entry is:", entry - else: + elif not entry: print "\nError: could not read entry %s from %s" % (dn,self) return entry diff --git a/ipa-server/ipaserver/krbinstance.py b/ipa-server/ipaserver/krbinstance.py index 827c9815..aca9e261 100644 --- a/ipa-server/ipaserver/krbinstance.py +++ b/ipa-server/ipaserver/krbinstance.py @@ -32,16 +32,21 @@ import os import pwd import socket import time +import shutil import service from ipa.ipautil import * +from ipa import ipaerror + +import ipaldap import ldap from ldap import LDAPError from ldap import ldapobject -from pyasn1.type import univ +from pyasn1.type import univ, namedtype import pyasn1.codec.ber.encoder +import pyasn1.codec.ber.decoder import struct import base64 @@ -88,18 +93,26 @@ class KrbInstance(service.Service): self.kdc_password = None self.sub_dict = None - def create_instance(self, ds_user, realm_name, host_name, admin_password, master_password): + def __common_setup(self, ds_user, realm_name, host_name, admin_password): self.ds_user = ds_user - self.fqdn = host_name - self.ip = socket.gethostbyname(host_name) + self.fqdn = host_name self.realm = realm_name.upper() self.host = host_name.split(".")[0] - self.domain = host_to_domain(host_name) - self.admin_password = admin_password - self.master_password = master_password - + self.ip = socket.gethostbyname(host_name) + self.domain = host_to_domain(host_name) self.suffix = realm_to_suffix(self.realm) self.kdc_password = generate_kdc_password() + self.admin_password = admin_password + + self.__setup_sub_dict() + + # get a connection to the DS + try: + self.conn = ipaldap.IPAdmin(self.fqdn) + self.conn.do_simple_bind(bindpw=self.admin_password) + except ipaerror.exception_for(ipaerror.LDAP_DATABASE_ERROR), e: + logging.critical("Could not connect to DS") + raise e try: self.stop() @@ -107,22 +120,7 @@ class KrbInstance(service.Service): # It could have been not running pass - self.start_creation(10, "Configuring Kerberos KDC") - - self.__configure_kdc_account_password() - - self.__setup_sub_dict() - - self.__configure_ldap() - - self.__create_instance() - - self.__create_ds_keytab() - - self.__export_kadmin_changepw_keytab() - - self.__add_pwd_extop_module() - + def __common_post_setup(self): try: self.step("starting the KDC") self.start() @@ -138,8 +136,48 @@ class KrbInstance(service.Service): self.step("starting ipa-kpasswd") service.start("ipa-kpasswd") + + def create_instance(self, ds_user, realm_name, host_name, admin_password, master_password): + self.master_password = master_password + + self.__common_setup(ds_user, realm_name, host_name, admin_password) + + self.start_creation(11, "Configuring Kerberos KDC") + + self.__configure_kdc_account_password() + self.__configure_sasl_mappings() + self.__add_krb_entries() + self.__create_instance() + self.__create_ds_keytab() + self.__export_kadmin_changepw_keytab() + self.__add_pwd_extop_module() + + self.__common_post_setup() + + self.done_creation() + + + def create_replica(self, ds_user, realm_name, host_name, admin_password, ldap_passwd_filename): + + self.__common_setup(ds_user, realm_name, host_name, admin_password) + + self.start_creation(9, "Configuring Kerberos KDC") + self.__copy_ldap_passwd(ldap_passwd_filename) + self.__configure_sasl_mappings() + self.__write_stash_from_ds() + self.__create_instance(replica=True) + self.__create_ds_keytab() + self.__export_kadmin_changepw_keytab() + + self.__common_post_setup() + self.done_creation() + + def __copy_ldap_passwd(self, filename): + shutil.copy(filename, "/var/kerberos/krb5kdc/ldappwd") + + def __configure_kdc_account_password(self): self.step("setting KDC account password") hexpwd = '' @@ -158,23 +196,60 @@ class KrbInstance(service.Service): HOST=self.host, REALM=self.realm) - def __configure_ldap(self): - self.step("adding kerberos configuration to the directory") + def __configure_sasl_mappings(self): + self.step("adding sasl mappings to the directory") # we need to remove any existing SASL mappings in the directory as otherwise they # they may conflict. There is no way to define the order they are used in atm. + + # FIXME: for some reason IPAdmin dies here, so we switch + # it out for a regular ldapobject. + conn = self.conn + self.conn = ldapobject.SimpleLDAPObject("ldap://127.0.0.1/") + self.conn.bind("cn=directory manager", self.admin_password) try: - lo = ldapobject.SimpleLDAPObject("ldap://127.0.0.1/") - lo.bind("cn=Directory Manager", self.admin_password) - msgid = lo.search("cn=mapping,cn=sasl,cn=config", ldap.SCOPE_ONELEVEL, "(objectclass=nsSaslMapping)") - res = lo.result(msgid) + msgid = self.conn.search("cn=mapping,cn=sasl,cn=config", ldap.SCOPE_ONELEVEL, "(objectclass=nsSaslMapping)") + res = self.conn.result(msgid) for r in res[1]: - mid = lo.delete(r[0]) - delres = lo.result(mid) - lo.unbind() - except LDAPError, e: - logging.critical("Error during SASL mapping removal: %s" % str(e)) + mid = self.conn.delete_s(r[0]) + #except LDAPError, e: + # logging.critical("Error during SASL mapping removal: %s" % str(e)) + except Exception, e: + print type(e) + print dir(e) + raise e + + self.conn = conn + + entry = ipaldap.Entry("cn=Full Principal,cn=mapping,cn=sasl,cn=config") + entry.setValues("objectclass", "top", "nsSaslMapping") + entry.setValues("cn", "Full Principal") + entry.setValues("nsSaslMapRegexString", '\(.*\)@\(.*\)') + entry.setValues("nsSaslMapBaseDNTemplate", self.suffix) + entry.setValues("nsSaslMapFilterTemplate", '(krbPrincipalName=\\1@\\2)') + + try: + self.conn.add_s(entry) + except ldap.ALREADY_EXISTS: + logging.critical("failed to add Full Principal Sasl mapping") + raise e + + entry = ipaldap.Entry("cn=Name Only,cn=mapping,cn=sasl,cn=config") + entry.setValues("objectclass", "top", "nsSaslMapping") + entry.setValues("cn", "Name Only") + entry.setValues("nsSaslMapRegexString", '\(.*\)') + entry.setValues("nsSaslMapBaseDNTemplate", self.suffix) + entry.setValues("nsSaslMapFilterTemplate", '(krbPrincipalName=\\1@%s)' % self.realm) + + try: + self.conn.add_s(entry) + except ldap.ALREADY_EXISTS: + logging.critical("failed to add Name Only Sasl mapping") + raise e + + def __add_krb_entries(self): + self.step("adding kerberos entries to the DS") - #TODO: test that the ldif is ok with any random charcter we may use in the password + #TODO: test that the ldif is ok with any random charcter we may use in the password kerberos_txt = template_file(SHARE_DIR + "kerberos.ldif", self.sub_dict) kerberos_fd = write_tmp_file(kerberos_txt) try: @@ -192,7 +267,7 @@ class KrbInstance(service.Service): logging.critical("Failed to load default-aci.ldif: %s" % str(e)) aci_fd.close() - def __create_instance(self): + def __create_instance(self, replica=False): self.step("configuring KDC") kdc_conf = template_file(SHARE_DIR+"kdc.conf.template", self.sub_dict) kdc_fd = open("/var/kerberos/krb5kdc/kdc.conf", "w+") @@ -220,12 +295,34 @@ class KrbInstance(service.Service): krb_fd.write(krb_realm) krb_fd.close() - #populate the directory with the realm structure - args = ["/usr/kerberos/sbin/kdb5_ldap_util", "-D", "uid=kdc,cn=sysaccounts,cn=etc,"+self.suffix, "-w", self.kdc_password, "create", "-s", "-P", self.master_password, "-r", self.realm, "-subtrees", self.suffix, "-sscope", "sub"] + if not replica: + #populate the directory with the realm structure + args = ["/usr/kerberos/sbin/kdb5_ldap_util", "-D", "uid=kdc,cn=sysaccounts,cn=etc,"+self.suffix, "-w", self.kdc_password, "create", "-s", "-P", self.master_password, "-r", self.realm, "-subtrees", self.suffix, "-sscope", "sub"] + try: + run(args) + except subprocess.CalledProcessError, e: + print "Failed to populate the realm structure in kerberos", e + + def __write_stash_from_ds(self): + self.step("writing stash file from DS") try: - run(args) - except subprocess.CalledProcessError, e: - print "Failed to populate the realm structure in kerberos", e + entry = self.conn.getEntry("cn=%s, cn=kerberos, %s" % (self.realm, self.suffix), ldap.SCOPE_SUBTREE) + except ipaerror.exception_for(ipaerror.LDAP_NOT_FOUND), e: + logging.critical("Could not find master key in DS") + raise e + + krbMKey = pyasn1.codec.ber.decoder.decode(entry.krbmkey) + keytype = int(krbMKey[0][1][0]) + keydata = str(krbMKey[0][1][1]) + + format = '=hi%ss' % len(keydata) + s = struct.pack(format, keytype, len(keydata), keydata) + try: + fd = open("/var/kerberos/krb5kdc/.k5."+self.realm, "w") + fd.write(s) + except os.error, e: + logging.critical("failed to write stash file") + raise e #add the password extop module def __add_pwd_extop_module(self): @@ -255,18 +352,14 @@ class KrbInstance(service.Service): krbMKey.setComponentByPosition(1, MasterKey) asn1key = pyasn1.codec.ber.encoder.encode(krbMKey) - #put the attribute in the Directory - mod_txt = "dn: cn="+self.realm+",cn=kerberos,"+self.suffix+"\n" - mod_txt += "changetype: modify\n" - mod_txt += "add: krbMKey\n" - mod_txt += "krbMKey:: "+base64.encodestring(asn1key)+"\n" - mod_txt += "\n" - mod_fd = write_tmp_file(mod_txt) + entry = ipaldap.Entry("cn="+self.realm+",cn=kerberos,"+self.suffix) + dn = "cn="+self.realm+",cn=kerberos,"+self.suffix + mod = [(ldap.MOD_ADD, 'krbMKey', str(asn1key))] try: - ldap_mod(mod_fd, "cn=Directory Manager", self.admin_password) - except subprocess.CalledProcessError, e: - logging.critical("Failed to load Master Key: %s" % str(e)) - mod_fd.close() + self.conn.modify_s(dn, mod) + except ldap.TYPE_OR_VALUE_EXISTS, e: + logging.critical("failed to add master key to kerberos database\n") + raise e def __create_ds_keytab(self): self.step("creating a keytab for the directory") diff --git a/ipa-server/ipaserver/radiusinstance.py b/ipa-server/ipaserver/radiusinstance.py index 2aee09b3..72cfe6cb 100644 --- a/ipa-server/ipaserver/radiusinstance.py +++ b/ipa-server/ipaserver/radiusinstance.py @@ -25,6 +25,7 @@ import shutil import logging import pwd import time +import sys from ipa.ipautil import * import service @@ -149,7 +150,7 @@ class RadiusInstance(service.Service): retry += 1 if retry > 15: print "Error timed out waiting for kadmin to finish operations\n" - os.exit() + sys.exit(1) try: pent = pwd.getpwnam(RADIUS_USER) |