Initial replication setup.

This add replication setup through two new commands: ipa-replica-prepare
and ipa-replica-install. The procedure is to run ipa-replica-prepare
on an existing master. This will collect information about the realm
and the current master and create a file storing all of the information.
After copying that file to the new replica, ipa-replica-install is
run (with -r to create a read-only replica).

This version of the patch also includes fixes for the sasl mappings
on the replicas.

Remaining features:
- ssl for replication.
- automatic configuration of mesh topology for
  master (or a simpler way to replicate multiple
  masters.
- tool for view / configuring current replication.
This commit is contained in:
Karl MacMillan
-
parent b456d8424a
commit c373ed5c5c
14 changed files with 707 additions and 255 deletions

View File

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

View File

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

View File

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

View File

@@ -6,6 +6,8 @@ SUBDIRS = \
sbin_SCRIPTS = \
ipa-server-install \
ipa-replica-install \
ipa-replica-prepare \
$(NULL)
appdir = $(IPA_DATA_DIR)

View File

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

View File

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

View File

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

View File

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

View File

@@ -12,6 +12,8 @@ app_PYTHON = \
radiusinstance.py \
webguiinstance.py \
service.py \
installutils.py \
replication.py \
$(NULL)
EXTRA_DIST = \

View File

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

View File

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

View File

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

View File

@@ -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.
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)
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))
#TODO: test that the ldif is ok with any random charcter we may use in the password
# 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:
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 = 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
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")

View File

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